Skip to content

Commit

Permalink
chore: address review
Browse files Browse the repository at this point in the history
  • Loading branch information
vasco-santos committed Apr 5, 2024
1 parent 7f0c84e commit 6f1b921
Show file tree
Hide file tree
Showing 18 changed files with 464 additions and 480 deletions.
4 changes: 4 additions & 0 deletions packages/capabilities/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,10 @@
"types": "./dist/src/filecoin/dealer.d.ts",
"import": "./src/filecoin/dealer.js"
},
"./web3.storage/blob": {
"types": "./dist/src/web3.storage/blob.d.ts",
"import": "./src/web3.storage/blob.js"
},
"./types": {
"types": "./dist/src/types.d.ts",
"import": "./src/types.js"
Expand Down
200 changes: 3 additions & 197 deletions packages/capabilities/src/blob.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,16 +11,8 @@
*
* @module
*/
import { capability, Link, Schema, ok, fail } from '@ucanto/validator'
import {
equal,
equalBlob,
equalContent,
equalWith,
checkLink,
SpaceDID,
and,
} from './utils.js'
import { capability, Schema } from '@ucanto/validator'
import { equalBlob, equalWith, SpaceDID } from './utils.js'

/**
* Agent capabilities for Blob protocol
Expand Down Expand Up @@ -79,192 +71,6 @@ export const add = capability({
derives: equalBlob,
})

/**
* `blob/remove` capability can be used to remove the stored Blob from the (memory)
* space identified by `with` field.
*/
export const remove = capability({
can: 'blob/remove',
/**
* DID of the (memory) space where Blob is intended to
* be stored.
*/
with: SpaceDID,
nb: Schema.struct({
/**
* A multihash digest of the blob payload bytes, uniquely identifying blob.
*/
content: Schema.bytes(),
}),
derives: equalContent,
})

/**
* `blob/list` capability can be invoked to request a list of stored Blobs in the
* (memory) space identified by `with` field.
*/
export const list = capability({
can: 'blob/list',
/**
* DID of the (memory) space where Blob is intended to
* be stored.
*/
with: SpaceDID,
nb: Schema.struct({
/**
* A pointer that can be moved back and forth on the list.
* It can be used to paginate a list for instance.
*/
cursor: Schema.string().optional(),
/**
* Maximum number of items per page.
*/
size: Schema.integer().optional(),
/**
* If true, return page of results preceding cursor. Defaults to false.
*/
pre: Schema.boolean().optional(),
}),
derives: (claimed, delegated) => {
if (claimed.with !== delegated.with) {
return fail(
`Expected 'with: "${delegated.with}"' instead got '${claimed.with}'`
)
}
return ok({})
},
})

/**
* Service capabilities for Blob protocol
*/
/**
* Capability can only be delegated (but not invoked) allowing audience to
* derived any `web3.storage/blob/` prefixed capability for the (memory) space identified
* by DID in the `with` field.
*/
export const serviceBlob = capability({
can: 'web3.storage/blob/*',
/**
* DID of the (memory) space where Blob is intended to
* be stored.
*/
with: SpaceDID,
derives: equalWith,
})

/**
* `web3.storage/blob//allocate` capability can be invoked to create a memory
* address where blob content can be written via HTTP PUT request.
*/
export const allocate = capability({
can: 'web3.storage/blob/allocate',
/**
* Provider DID.
*/
with: Schema.did(),
nb: Schema.struct({
/**
* Blob to allocate on the space.
*/
blob: blobStruct,
/**
* The Link for an Add Blob task, that caused an allocation
*/
cause: Link,
/**
* DID of the user space where allocation takes place
*/
space: SpaceDID,
}),
derives: (claim, from) => {
return (
and(equalWith(claim, from)) ||
and(equalBlob(claim, from)) ||
and(checkLink(claim.nb.cause, from.nb.cause, 'cause')) ||
and(equal(claim.nb.space, from.nb.space, 'space')) ||
ok({})
)
},
})

/**
* `http/put` capability invocation MAY be performed by any agent on behalf of the subject.
* The `blob/add` provider MUST add `/http/put` effect and capture private key of the
* `subject` in the `meta` field so that any agent could perform it.
*/
export const put = capability({
can: 'http/put',
/**
* DID of the (memory) space where Blob is intended to
* be stored.
*/
with: SpaceDID,
nb: Schema.struct({
/**
* Blob to allocate on the space.
*/
blob: blobStruct,
/**
* Blob to accept.
*/
address: Schema.struct({
/**
* HTTP(S) location that can receive blob content via HTTP PUT request.
*/
url: Schema.string(),
/**
* HTTP headers.
*/
headers: Schema.unknown(),
}).optional(),
}),
derives: (claim, from) => {
return (
and(equalWith(claim, from)) ||
and(equalBlob(claim, from)) ||
and(equal(claim.nb.address?.url, from.nb.address, 'url')) ||
and(equal(claim.nb.address?.headers, from.nb.address, 'headers')) ||
ok({})
)
},
})

/**
* `blob/accept` capability invocation should either succeed when content is
* delivered on allocated address or fail if no content is allocation expires
* without content being delivered.
*/
export const accept = capability({
can: 'web3.storage/blob/accept',
/**
* Provider DID.
*/
with: Schema.did(),
nb: Schema.struct({
/**
* Blob to accept.
*/
blob: blobStruct,
/**
* Expiration..
*/
exp: Schema.integer(),
}),
derives: (claim, from) => {
const result = equalBlob(claim, from)
if (result.error) {
return result
} else if (claim.nb.exp !== undefined && from.nb.exp !== undefined) {
return claim.nb.exp > from.nb.exp
? fail(`exp constraint violation: ${claim.nb.exp} > ${from.nb.exp}`)
: ok({})
} else {
return ok({})
}
},
})

// ⚠️ We export imports here so they are not omitted in generated typedes
// @see https://github.com/microsoft/TypeScript/issues/51548
export { Schema, Link }
export { Schema }
55 changes: 55 additions & 0 deletions packages/capabilities/src/http.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
/**
* HTTP Capabilities.
*
* These can be imported directly with:
* ```js
* import * as HTTP from '@web3-storage/capabilities/http'
* ```
*
* @module
*/
import { capability, Schema, ok } from '@ucanto/validator'
import { blobStruct } from './blob.js'
import { equal, equalBlob, equalWith, SpaceDID, and } from './utils.js'

/**
* `http/put` capability invocation MAY be performed by any agent on behalf of the subject.
* The `blob/add` provider MUST add `/http/put` effect and capture private key of the
* `subject` in the `meta` field so that any agent could perform it.
*/
export const put = capability({
can: 'http/put',
/**
* DID of the (memory) space where Blob is intended to
* be stored.
*/
with: SpaceDID,
nb: Schema.struct({
/**
* Blob to allocate on the space.
*/
blob: blobStruct,
/**
* Blob to accept.
*/
address: Schema.struct({
/**
* HTTP(S) location that can receive blob content via HTTP PUT request.
*/
url: Schema.string(),
/**
* HTTP headers.
*/
headers: Schema.unknown(),
}).optional(),
}),
derives: (claim, from) => {
return (
and(equalWith(claim, from)) ||
and(equalBlob(claim, from)) ||
and(equal(claim.nb.address?.url, from.nb.address, 'url')) ||
and(equal(claim.nb.address?.headers, from.nb.address, 'headers')) ||
ok({})
)
},
})
12 changes: 6 additions & 6 deletions packages/capabilities/src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ import * as UCAN from './ucan.js'
import * as Plan from './plan.js'
import * as Usage from './usage.js'
import * as Blob from './blob.js'
import * as W3sBlob from './web3.storage/blob.js'
import * as HTTP from './http.js'

export {
Access,
Expand Down Expand Up @@ -90,10 +92,8 @@ export const abilitiesAsStrings = [
Usage.report.can,
Blob.blob.can,
Blob.add.can,
Blob.remove.can,
Blob.list.can,
Blob.serviceBlob.can,
Blob.put.can,
Blob.allocate.can,
Blob.accept.can,
W3sBlob.blob.can,
W3sBlob.allocate.can,
W3sBlob.accept.can,
HTTP.put.can,
]
40 changes: 24 additions & 16 deletions packages/capabilities/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ import { space, info } from './space.js'
import * as provider from './provider.js'
import { top } from './top.js'
import * as BlobCaps from './blob.js'
import * as W3sBlobCaps from './web3.storage/blob.js'
import * as HTTPCaps from './http.js'
import * as StoreCaps from './store.js'
import * as UploadCaps from './upload.js'
import * as AccessCaps from './access.js'
Expand Down Expand Up @@ -440,22 +442,26 @@ export interface UploadNotFound extends Ucanto.Failure {

export type UploadGetFailure = UploadNotFound | Ucanto.Failure

// HTTP
export type HTTPPut = InferInvokedCapability<typeof HTTPCaps.put>

// Blob
export type Blob = InferInvokedCapability<typeof BlobCaps.blob>
export type BlobAdd = InferInvokedCapability<typeof BlobCaps.add>
export type BlobRemove = InferInvokedCapability<typeof BlobCaps.remove>
export type BlobList = InferInvokedCapability<typeof BlobCaps.list>
export type ServiceBlob = InferInvokedCapability<typeof BlobCaps.serviceBlob>
export type BlobPut = InferInvokedCapability<typeof BlobCaps.put>
export type BlobAllocate = InferInvokedCapability<typeof BlobCaps.allocate>
export type BlobAccept = InferInvokedCapability<typeof BlobCaps.accept>
export type ServiceBlob = InferInvokedCapability<typeof W3sBlobCaps.blob>
export type BlobAllocate = InferInvokedCapability<typeof W3sBlobCaps.allocate>
export type BlobAccept = InferInvokedCapability<typeof W3sBlobCaps.accept>

export type BlobMultihash = Uint8Array
export interface BlobModel {
content: BlobMultihash
size: number
}

// Blob add
export interface BlobAddSuccess {
claim: {
'await/ok': Link
location: {
'ucan/await': ['.out.ok.claim', Link]
}
}

Expand All @@ -473,12 +479,13 @@ export interface BlobItemNotFound extends Ucanto.Failure {
name: 'BlobItemNotFound'
}

// TODO: Add more errors from stores
export type BlobRemoveFailure = BlobItemNotFound | Ucanto.Failure

// Blob list
export interface BlobListSuccess extends ListResponse<BlobListItem> {}
export interface BlobListItem {
blob: { content: Uint8Array; size: number }
blob: BlobModel
insertedAt: ISO8601Date
}

Expand Down Expand Up @@ -657,11 +664,14 @@ export type UCANRevokeFailure =
| UnauthorizedRevocation
| RevocationsStoreFailure

export interface InvocationNotFound extends Ucanto.Failure {
name: 'InvocationNotFound'
/**
* Error is raised when receipt is received for unknown invocation
*/
export interface InvocationNotFoundForReceipt extends Ucanto.Failure {
name: 'InvocationNotFoundForReceipt'
}

export type UCANConcludeFailure = InvocationNotFound | Ucanto.Failure
export type UCANConcludeFailure = InvocationNotFoundForReceipt | Ucanto.Failure

// Admin
export type Admin = InferInvokedCapability<typeof AdminCaps.admin>
Expand Down Expand Up @@ -797,12 +807,10 @@ export type ServiceAbilityArray = [
UsageReport['can'],
Blob['can'],
BlobAdd['can'],
BlobRemove['can'],
BlobList['can'],
ServiceBlob['can'],
BlobPut['can'],
BlobAllocate['can'],
BlobAccept['can']
BlobAccept['can'],
HTTPPut['can']
]

/**
Expand Down
Loading

0 comments on commit 6f1b921

Please sign in to comment.