|
1 | 1 | /**
|
2 | 2 | * Blob Capabilities.
|
3 | 3 | *
|
4 |
| - * Blob is a fixed size byte array addressed by the multihash. |
5 |
| - * Usually blobs are used to represent set of IPLD blocks at different byte ranges. |
| 4 | + * The blob protocol allows authorized agents allocate memory space on a storage |
| 5 | + * node and subsequently verify the content has been accepted by / delivered to |
| 6 | + * said node. |
6 | 7 | *
|
7 | 8 | * These can be imported directly with:
|
8 | 9 | * ```js
|
9 |
| - * import * as Blob from '@storacha/capabilities/blob' |
| 10 | + * import * as Index from '@storacha/capabilities/blob' |
10 | 11 | * ```
|
11 | 12 | *
|
12 | 13 | * @module
|
| 14 | + * @see https://github.com/storacha/specs/blob/main/w3-blob.md |
13 | 15 | */
|
14 |
| -import { equals } from 'uint8arrays/equals' |
15 |
| -import { capability, Schema, fail, ok } from '@ucanto/validator' |
16 |
| -import { equalBlob, equalWith, SpaceDID } from './utils.js' |
17 |
| - |
18 |
| -/** |
19 |
| - * Agent capabilities for Blob protocol |
20 |
| - */ |
| 16 | +import { capability, Schema, Link, ok } from '@ucanto/validator' |
| 17 | +import { content } from './space/blob.js' |
| 18 | +import { |
| 19 | + equalBlob, |
| 20 | + equalWith, |
| 21 | + SpaceDID, |
| 22 | + and, |
| 23 | + equal, |
| 24 | + checkLink, |
| 25 | + Await, |
| 26 | +} from './utils.js' |
21 | 27 |
|
22 | 28 | /**
|
23 | 29 | * Capability can only be delegated (but not invoked) allowing audience to
|
24 |
| - * derived any `space/blob/` prefixed capability for the (memory) space identified |
25 |
| - * by DID in the `with` field. |
| 30 | + * derive any `blob/` prefixed capability. |
26 | 31 | */
|
27 | 32 | export const blob = capability({
|
28 |
| - can: 'space/blob/*', |
29 |
| - /** |
30 |
| - * DID of the (memory) space where Blob is intended to |
31 |
| - * be stored. |
32 |
| - */ |
33 |
| - with: SpaceDID, |
| 33 | + can: 'blob/*', |
| 34 | + /** Storage provider DID. */ |
| 35 | + with: Schema.did(), |
34 | 36 | derives: equalWith,
|
35 | 37 | })
|
36 | 38 |
|
37 | 39 | /**
|
38 |
| - * Blob description for being ingested by the service. |
39 |
| - */ |
40 |
| -export const content = Schema.struct({ |
41 |
| - /** |
42 |
| - * A multihash digest of the blob payload bytes, uniquely identifying blob. |
43 |
| - */ |
44 |
| - digest: Schema.bytes(), |
45 |
| - /** |
46 |
| - * Number of bytes contained by this blob. Service will provision write target |
47 |
| - * for this exact size. Attempt to write a larger Blob file will fail. |
48 |
| - */ |
49 |
| - size: Schema.integer(), |
50 |
| -}) |
51 |
| - |
52 |
| -/** |
53 |
| - * `space/blob/add` capability allows agent to store a Blob into a (memory) space |
54 |
| - * identified by did:key in the `with` field. Agent should compute blob multihash |
55 |
| - * and size and provide it under `nb.blob` field, allowing a service to provision |
56 |
| - * a write location for the agent to PUT desired Blob into. |
| 40 | + * The `blob/allocate` capability can be invoked to create a memory address on a |
| 41 | + * storage node where blob content can be written via a HTTP PUT request. |
57 | 42 | */
|
58 |
| -export const add = capability({ |
59 |
| - can: 'space/blob/add', |
60 |
| - /** |
61 |
| - * DID of the (memory) space where Blob is intended to |
62 |
| - * be stored. |
63 |
| - */ |
64 |
| - with: SpaceDID, |
| 43 | +export const allocate = capability({ |
| 44 | + can: 'blob/allocate', |
| 45 | + /** Storage provider DID. */ |
| 46 | + with: Schema.did(), |
65 | 47 | nb: Schema.struct({
|
66 |
| - /** |
67 |
| - * Blob to be added on the space. |
68 |
| - */ |
| 48 | + /** Blob to allocate. */ |
69 | 49 | blob: content,
|
| 50 | + /** Link to the add blob task that initiated the allocation. */ |
| 51 | + cause: Schema.link({ version: 1 }), |
| 52 | + /** DID of the user space where the allocation takes place. */ |
| 53 | + space: SpaceDID, |
70 | 54 | }),
|
71 |
| - derives: equalBlob, |
72 |
| -}) |
73 |
| - |
74 |
| -/** |
75 |
| - * Capability can be used to remove the stored Blob from the (memory) |
76 |
| - * space identified by `with` field. |
77 |
| - */ |
78 |
| -export const remove = capability({ |
79 |
| - can: 'space/blob/remove', |
80 |
| - /** |
81 |
| - * DID of the (memory) space where Blob is stored. |
82 |
| - */ |
83 |
| - with: SpaceDID, |
84 |
| - nb: Schema.struct({ |
85 |
| - /** |
86 |
| - * A multihash digest of the blob payload bytes, uniquely identifying blob. |
87 |
| - */ |
88 |
| - digest: Schema.bytes(), |
89 |
| - }), |
90 |
| - derives: (claimed, delegated) => { |
91 |
| - if (claimed.with !== delegated.with) { |
92 |
| - return fail( |
93 |
| - `Expected 'with: "${delegated.with}"' instead got '${claimed.with}'` |
94 |
| - ) |
95 |
| - } else if ( |
96 |
| - delegated.nb.digest && |
97 |
| - !equals(delegated.nb.digest, claimed.nb.digest) |
98 |
| - ) { |
99 |
| - return fail( |
100 |
| - `Link ${ |
101 |
| - claimed.nb.digest ? `${claimed.nb.digest}` : '' |
102 |
| - } violates imposed ${delegated.nb.digest} constraint.` |
103 |
| - ) |
104 |
| - } |
105 |
| - return ok({}) |
106 |
| - }, |
107 |
| -}) |
108 |
| - |
109 |
| -/** |
110 |
| - * Capability can be invoked to request a list of stored Blobs in the |
111 |
| - * (memory) space identified by `with` field. |
112 |
| - */ |
113 |
| -export const list = capability({ |
114 |
| - can: 'space/blob/list', |
115 |
| - /** |
116 |
| - * DID of the (memory) space where Blobs to be listed are stored. |
117 |
| - */ |
118 |
| - with: SpaceDID, |
119 |
| - nb: Schema.struct({ |
120 |
| - /** |
121 |
| - * A pointer that can be moved back and forth on the list. |
122 |
| - * It can be used to paginate a list for instance. |
123 |
| - */ |
124 |
| - cursor: Schema.string().optional(), |
125 |
| - /** |
126 |
| - * Maximum number of items per page. |
127 |
| - */ |
128 |
| - size: Schema.integer().optional(), |
129 |
| - }), |
130 |
| - derives: (claimed, delegated) => { |
131 |
| - if (claimed.with !== delegated.with) { |
132 |
| - return fail( |
133 |
| - `Expected 'with: "${delegated.with}"' instead got '${claimed.with}'` |
134 |
| - ) |
135 |
| - } |
136 |
| - return ok({}) |
137 |
| - }, |
| 55 | + derives: (claimed, delegated) => |
| 56 | + and(equalWith(claimed, delegated)) || |
| 57 | + and(equalBlob(claimed, delegated)) || |
| 58 | + and(checkLink(claimed.nb.cause, delegated.nb.cause, 'cause')) || |
| 59 | + and(equal(claimed.nb.space, delegated.nb.space, 'space')) || |
| 60 | + ok({}), |
138 | 61 | })
|
139 | 62 |
|
140 | 63 | /**
|
141 |
| - * Capability can be used to get the stored Blob from the (memory) |
142 |
| - * space identified by `with` field. |
| 64 | + * The `blob/accept` capability invocation should either succeed when content is |
| 65 | + * delivered on allocated address or fail if no content is allocation expires |
| 66 | + * without content being delivered. |
143 | 67 | */
|
144 |
| -export const get = capability({ |
145 |
| - can: 'space/blob/get/0/1', |
146 |
| - /** |
147 |
| - * DID of the (memory) space where Blob is stored. |
148 |
| - */ |
149 |
| - with: SpaceDID, |
| 68 | +export const accept = capability({ |
| 69 | + can: 'blob/accept', |
| 70 | + /** Storage provider DID. */ |
| 71 | + with: Schema.did(), |
150 | 72 | nb: Schema.struct({
|
151 |
| - /** |
152 |
| - * A multihash digest of the blob payload bytes, uniquely identifying blob. |
153 |
| - */ |
154 |
| - digest: Schema.bytes(), |
| 73 | + /** Blob to accept. */ |
| 74 | + blob: content, |
| 75 | + /** DID of the user space where allocation took place. */ |
| 76 | + space: SpaceDID, |
| 77 | + /** This task is blocked on `http/put` receipt available */ |
| 78 | + _put: Await, |
155 | 79 | }),
|
156 |
| - derives: (claimed, delegated) => { |
157 |
| - if (claimed.with !== delegated.with) { |
158 |
| - return fail( |
159 |
| - `Expected 'with: "${delegated.with}"' instead got '${claimed.with}'` |
160 |
| - ) |
161 |
| - } else if ( |
162 |
| - delegated.nb.digest && |
163 |
| - !equals(delegated.nb.digest, claimed.nb.digest) |
164 |
| - ) { |
165 |
| - return fail( |
166 |
| - `Link ${ |
167 |
| - claimed.nb.digest ? `${claimed.nb.digest}` : '' |
168 |
| - } violates imposed ${delegated.nb.digest} constraint.` |
169 |
| - ) |
170 |
| - } |
171 |
| - return ok({}) |
172 |
| - }, |
| 80 | + derives: (claimed, delegated) => |
| 81 | + and(equalWith(claimed, delegated)) || |
| 82 | + and(equalBlob(claimed, delegated)) || |
| 83 | + and(equal(claimed.nb.space, delegated.nb.space, 'space')) || |
| 84 | + ok({}), |
173 | 85 | })
|
174 | 86 |
|
175 | 87 | // ⚠️ We export imports here so they are not omitted in generated typedefs
|
176 | 88 | // @see https://github.com/microsoft/TypeScript/issues/51548
|
177 |
| -export { Schema } |
| 89 | +export { Schema, Link } |
0 commit comments