Skip to content

Commit

Permalink
feat: add CAR upload method (#72)
Browse files Browse the repository at this point in the history
  • Loading branch information
alanshaw committed Jan 11, 2023
1 parent f3b3ec4 commit 8b31255
Show file tree
Hide file tree
Showing 6 changed files with 153 additions and 48 deletions.
23 changes: 23 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -155,6 +155,7 @@ In the example above, `directoryCid` resolves to an IPFS directory with the foll
- `Client`
- [`uploadDirectory`](#uploaddirectory)
- [`uploadFile`](#uploadfile)
- [`uploadCAR`](#uploadcar)
- [`agent`](#agent)
- [`currentSpace`](#currentspace)
- [`setCurrentSpace`](#setcurrentspace)
Expand Down Expand Up @@ -215,6 +216,7 @@ function uploadDirectory (
signal?: AbortSignal
onShardStored?: ShardStoredCallback
shardSize?: number
concurrentRequests?: number
} = {}
): Promise<CID>
```
Expand All @@ -233,6 +235,7 @@ function uploadFile (
signal?: AbortSignal
onShardStored?: ShardStoredCallback
shardSize?: number
concurrentRequests?: number
} = {}
): Promise<CID>
```
Expand All @@ -241,6 +244,26 @@ Uploads a file to the service and returns the root data CID for the generated DA

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

### `uploadCAR`

```ts
function uploadCAR (
car: Blob,
options: {
retries?: number
signal?: AbortSignal
onShardStored?: ShardStoredCallback
shardSize?: number
concurrentRequests?: number
rootCID?: CID
} = {}
): Promise<void>
```

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.

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

### `agent`

```ts
Expand Down
90 changes: 45 additions & 45 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@
"@ucanto/transport": "^4.0.2",
"@web3-storage/access": "^9.1.1",
"@web3-storage/capabilities": "^2.0.0",
"@web3-storage/upload-client": "^5.2.0"
"@web3-storage/upload-client": "^5.3.0"
},
"devDependencies": {
"@ucanto/server": "^4.0.2",
Expand Down
20 changes: 19 additions & 1 deletion src/client.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { uploadFile, uploadDirectory } from '@web3-storage/upload-client'
import { uploadFile, uploadDirectory, uploadCAR } from '@web3-storage/upload-client'
import { Store as StoreCapabilities, Upload as UploadCapabilities } from '@web3-storage/capabilities'
import { Base } from './base.js'
import { Space } from './space.js'
Expand Down Expand Up @@ -49,6 +49,24 @@ export class Client extends Base {
return uploadDirectory(conf, files, options)
}

/**
* Uploads a CAR file to the service.
*
* The difference between this function and `capability.store.add` is that the
* CAR file is automatically sharded and an "upload" is registered, linking
* the individual shards (see `capability.upload.add`).
*
* Use the `onShardStored` callback to obtain the CIDs of the CAR file shards.
*
* @param {import('./types').BlobLike} car CAR file.
* @param {import('./types').UploadOptions} [options]
*/
async uploadCAR (car, options = {}) {
const conf = await this._invocationConfig([StoreCapabilities.add.can, UploadCapabilities.add.can])
options.connection = this._serviceConf.upload
return uploadCAR(conf, car, options)
}

/**
* The current user agent (this device).
*/
Expand Down
1 change: 1 addition & 0 deletions src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,7 @@ export type {
RequestOptions,
ListRequestOptions,
ShardingOptions,
ShardStoringOptions,
UploadOptions,
FileLike,
BlobLike
Expand Down
65 changes: 64 additions & 1 deletion test/client.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import * as Signer from '@ucanto/principal/ed25519'
import * as StoreCapabilities from '@web3-storage/capabilities/store'
import * as UploadCapabilities from '@web3-storage/capabilities/upload'
import { AgentData } from '@web3-storage/access/agent'
import { randomBytes } from './helpers/random.js'
import { randomBytes, randomCAR } from './helpers/random.js'
import { toCAR } from './helpers/car.js'
import { mockService, mockServiceConf } from './helpers/mocks.js'
import { File } from './helpers/shims.js'
Expand Down Expand Up @@ -160,6 +160,69 @@ describe('Client', () => {
})
})

describe('uploadCAR', () => {
it('uploads a CAR file to the service', async () => {
const car = await randomCAR(32)

/** @type {import('../src/types').CARLink?} */
let carCID

const service = mockService({
store: {
add: provide(StoreCapabilities.add, ({ invocation }) => {
assert.equal(invocation.issuer.did(), alice.agent().did())
assert.equal(invocation.capabilities.length, 1)
const invCap = invocation.capabilities[0]
assert.equal(invCap.can, StoreCapabilities.add.can)
assert.equal(invCap.with, space.did())
return {
status: 'upload',
headers: { 'x-test': 'true' },
url: 'http://localhost:9200'
}
})
},
upload: {
add: provide(UploadCapabilities.add, ({ invocation }) => {
assert.equal(invocation.issuer.did(), alice.agent().did())
assert.equal(invocation.capabilities.length, 1)
const invCap = invocation.capabilities[0]
assert.equal(invCap.can, UploadCapabilities.add.can)
assert.equal(invCap.with, space.did())
if (!invCap.nb) throw new Error('nb must be present')
assert.equal(invCap.nb.shards?.length, 1)
assert.equal(invCap.nb.shards[0].toString(), carCID.toString())
return invCap.nb
})
}
})

const server = createServer({
id: await Signer.generate(),
service,
decoder: CAR,
encoder: CBOR
})

const alice = new Client(
await AgentData.create(),
{ serviceConf: await mockServiceConf(server) }
)

const space = await alice.createSpace()
await alice.setCurrentSpace(space.did())
await alice.uploadCAR(car, { onShardStored: meta => { carCID = meta.cid } })

assert(service.store.add.called)
assert.equal(service.store.add.callCount, 1)
assert(service.upload.add.called)
assert.equal(service.upload.add.callCount, 1)

assert(carCID)
assert.equal(carCID.toString(), car.cid.toString())
})
})

describe('currentSpace', () => {
it('should return undefined or space', async () => {
const alice = new Client(await AgentData.create())
Expand Down

0 comments on commit 8b31255

Please sign in to comment.