Skip to content
This repository was archived by the owner on Mar 29, 2023. It is now read-only.

Commit 8b31255

Browse files
author
Alan Shaw
authored
feat: add CAR upload method (#72)
1 parent f3b3ec4 commit 8b31255

File tree

6 files changed

+153
-48
lines changed

6 files changed

+153
-48
lines changed

README.md

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -155,6 +155,7 @@ In the example above, `directoryCid` resolves to an IPFS directory with the foll
155155
- `Client`
156156
- [`uploadDirectory`](#uploaddirectory)
157157
- [`uploadFile`](#uploadfile)
158+
- [`uploadCAR`](#uploadcar)
158159
- [`agent`](#agent)
159160
- [`currentSpace`](#currentspace)
160161
- [`setCurrentSpace`](#setcurrentspace)
@@ -215,6 +216,7 @@ function uploadDirectory (
215216
signal?: AbortSignal
216217
onShardStored?: ShardStoredCallback
217218
shardSize?: number
219+
concurrentRequests?: number
218220
} = {}
219221
): Promise<CID>
220222
```
@@ -233,6 +235,7 @@ function uploadFile (
233235
signal?: AbortSignal
234236
onShardStored?: ShardStoredCallback
235237
shardSize?: number
238+
concurrentRequests?: number
236239
} = {}
237240
): Promise<CID>
238241
```
@@ -241,6 +244,26 @@ Uploads a file to the service and returns the root data CID for the generated DA
241244

242245
More information: [`ShardStoredCallback`](#shardstoredcallback)
243246

247+
### `uploadCAR`
248+
249+
```ts
250+
function uploadCAR (
251+
car: Blob,
252+
options: {
253+
retries?: number
254+
signal?: AbortSignal
255+
onShardStored?: ShardStoredCallback
256+
shardSize?: number
257+
concurrentRequests?: number
258+
rootCID?: CID
259+
} = {}
260+
): Promise<void>
261+
```
262+
263+
Uploads a CAR file to the service. The difference between this function and [capability.store.add](#capabilitystoreadd) is that the CAR file is automatically sharded and an "upload" is registered (see [`capability.upload.add`](#capabilityuploadadd)), linking the individual shards. Use the `onShardStored` callback to obtain the CIDs of the CAR file shards.
264+
265+
More information: [`ShardStoredCallback`](#shardstoredcallback)
266+
244267
### `agent`
245268

246269
```ts

package-lock.json

Lines changed: 45 additions & 45 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,7 @@
7575
"@ucanto/transport": "^4.0.2",
7676
"@web3-storage/access": "^9.1.1",
7777
"@web3-storage/capabilities": "^2.0.0",
78-
"@web3-storage/upload-client": "^5.2.0"
78+
"@web3-storage/upload-client": "^5.3.0"
7979
},
8080
"devDependencies": {
8181
"@ucanto/server": "^4.0.2",

src/client.js

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { uploadFile, uploadDirectory } from '@web3-storage/upload-client'
1+
import { uploadFile, uploadDirectory, uploadCAR } from '@web3-storage/upload-client'
22
import { Store as StoreCapabilities, Upload as UploadCapabilities } from '@web3-storage/capabilities'
33
import { Base } from './base.js'
44
import { Space } from './space.js'
@@ -49,6 +49,24 @@ export class Client extends Base {
4949
return uploadDirectory(conf, files, options)
5050
}
5151

52+
/**
53+
* Uploads a CAR file to the service.
54+
*
55+
* The difference between this function and `capability.store.add` is that the
56+
* CAR file is automatically sharded and an "upload" is registered, linking
57+
* the individual shards (see `capability.upload.add`).
58+
*
59+
* Use the `onShardStored` callback to obtain the CIDs of the CAR file shards.
60+
*
61+
* @param {import('./types').BlobLike} car CAR file.
62+
* @param {import('./types').UploadOptions} [options]
63+
*/
64+
async uploadCAR (car, options = {}) {
65+
const conf = await this._invocationConfig([StoreCapabilities.add.can, UploadCapabilities.add.can])
66+
options.connection = this._serviceConf.upload
67+
return uploadCAR(conf, car, options)
68+
}
69+
5270
/**
5371
* The current user agent (this device).
5472
*/

src/types.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,7 @@ export type {
7575
RequestOptions,
7676
ListRequestOptions,
7777
ShardingOptions,
78+
ShardStoringOptions,
7879
UploadOptions,
7980
FileLike,
8081
BlobLike

test/client.test.js

Lines changed: 64 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import * as Signer from '@ucanto/principal/ed25519'
66
import * as StoreCapabilities from '@web3-storage/capabilities/store'
77
import * as UploadCapabilities from '@web3-storage/capabilities/upload'
88
import { AgentData } from '@web3-storage/access/agent'
9-
import { randomBytes } from './helpers/random.js'
9+
import { randomBytes, randomCAR } from './helpers/random.js'
1010
import { toCAR } from './helpers/car.js'
1111
import { mockService, mockServiceConf } from './helpers/mocks.js'
1212
import { File } from './helpers/shims.js'
@@ -160,6 +160,69 @@ describe('Client', () => {
160160
})
161161
})
162162

163+
describe('uploadCAR', () => {
164+
it('uploads a CAR file to the service', async () => {
165+
const car = await randomCAR(32)
166+
167+
/** @type {import('../src/types').CARLink?} */
168+
let carCID
169+
170+
const service = mockService({
171+
store: {
172+
add: provide(StoreCapabilities.add, ({ invocation }) => {
173+
assert.equal(invocation.issuer.did(), alice.agent().did())
174+
assert.equal(invocation.capabilities.length, 1)
175+
const invCap = invocation.capabilities[0]
176+
assert.equal(invCap.can, StoreCapabilities.add.can)
177+
assert.equal(invCap.with, space.did())
178+
return {
179+
status: 'upload',
180+
headers: { 'x-test': 'true' },
181+
url: 'http://localhost:9200'
182+
}
183+
})
184+
},
185+
upload: {
186+
add: provide(UploadCapabilities.add, ({ invocation }) => {
187+
assert.equal(invocation.issuer.did(), alice.agent().did())
188+
assert.equal(invocation.capabilities.length, 1)
189+
const invCap = invocation.capabilities[0]
190+
assert.equal(invCap.can, UploadCapabilities.add.can)
191+
assert.equal(invCap.with, space.did())
192+
if (!invCap.nb) throw new Error('nb must be present')
193+
assert.equal(invCap.nb.shards?.length, 1)
194+
assert.equal(invCap.nb.shards[0].toString(), carCID.toString())
195+
return invCap.nb
196+
})
197+
}
198+
})
199+
200+
const server = createServer({
201+
id: await Signer.generate(),
202+
service,
203+
decoder: CAR,
204+
encoder: CBOR
205+
})
206+
207+
const alice = new Client(
208+
await AgentData.create(),
209+
{ serviceConf: await mockServiceConf(server) }
210+
)
211+
212+
const space = await alice.createSpace()
213+
await alice.setCurrentSpace(space.did())
214+
await alice.uploadCAR(car, { onShardStored: meta => { carCID = meta.cid } })
215+
216+
assert(service.store.add.called)
217+
assert.equal(service.store.add.callCount, 1)
218+
assert(service.upload.add.called)
219+
assert.equal(service.upload.add.callCount, 1)
220+
221+
assert(carCID)
222+
assert.equal(carCID.toString(), car.cid.toString())
223+
})
224+
})
225+
163226
describe('currentSpace', () => {
164227
it('should return undefined or space', async () => {
165228
const alice = new Client(await AgentData.create())

0 commit comments

Comments
 (0)