@interop/was-client 0.60.0 → 0.62.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +124 -48
- package/dist/Collection.d.ts +135 -94
- package/dist/Collection.d.ts.map +1 -1
- package/dist/Collection.js +612 -194
- package/dist/Collection.js.map +1 -1
- package/dist/Resource.d.ts +1 -1
- package/dist/Resource.d.ts.map +1 -1
- package/dist/Resource.js +0 -3
- package/dist/Resource.js.map +1 -1
- package/dist/Space.d.ts +17 -16
- package/dist/Space.d.ts.map +1 -1
- package/dist/Space.js +41 -24
- package/dist/Space.js.map +1 -1
- package/dist/WasClient.d.ts +51 -6
- package/dist/WasClient.d.ts.map +1 -1
- package/dist/WasClient.js +84 -14
- package/dist/WasClient.js.map +1 -1
- package/dist/codec.d.ts +3 -3
- package/dist/edv/EdvCodec.js +1 -1
- package/dist/edv/descriptorStore.d.ts +16 -14
- package/dist/edv/descriptorStore.d.ts.map +1 -1
- package/dist/edv/descriptorStore.js +17 -14
- package/dist/edv/descriptorStore.js.map +1 -1
- package/dist/edv/index.d.ts +1 -1
- package/dist/edv/index.js +1 -1
- package/dist/edv/logGovernedDescriptorStore.d.ts +2 -2
- package/dist/edv/logGovernedDescriptorStore.d.ts.map +1 -1
- package/dist/edv/logGovernedDescriptorStore.js +10 -9
- package/dist/edv/logGovernedDescriptorStore.js.map +1 -1
- package/dist/edv/recipients.d.ts +1 -1
- package/dist/edv/recipients.js +2 -2
- package/dist/edv/recipients.js.map +1 -1
- package/dist/edv/refresh.d.ts +1 -1
- package/dist/edv/refresh.js +1 -1
- package/dist/errors.d.ts +14 -1
- package/dist/errors.d.ts.map +1 -1
- package/dist/errors.js +16 -3
- package/dist/errors.js.map +1 -1
- package/dist/index.d.ts +2 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/internal/codec.d.ts +22 -1
- package/dist/internal/codec.d.ts.map +1 -1
- package/dist/internal/codec.js +117 -46
- package/dist/internal/codec.js.map +1 -1
- package/dist/internal/describe.d.ts +98 -24
- package/dist/internal/describe.d.ts.map +1 -1
- package/dist/internal/describe.js +125 -18
- package/dist/internal/describe.js.map +1 -1
- package/dist/internal/meta.d.ts +32 -17
- package/dist/internal/meta.d.ts.map +1 -1
- package/dist/internal/meta.js +64 -35
- package/dist/internal/meta.js.map +1 -1
- package/dist/internal/paths.d.ts +33 -21
- package/dist/internal/paths.d.ts.map +1 -1
- package/dist/internal/paths.js +93 -46
- package/dist/internal/paths.js.map +1 -1
- package/dist/internal/request.d.ts +22 -6
- package/dist/internal/request.d.ts.map +1 -1
- package/dist/internal/request.js +31 -13
- package/dist/internal/request.js.map +1 -1
- package/dist/internal/reserved.d.ts +14 -0
- package/dist/internal/reserved.d.ts.map +1 -1
- package/dist/internal/reserved.js +23 -4
- package/dist/internal/reserved.js.map +1 -1
- package/dist/internal/service.d.ts +61 -0
- package/dist/internal/service.d.ts.map +1 -0
- package/dist/internal/service.js +259 -0
- package/dist/internal/service.js.map +1 -0
- package/dist/paths.d.ts +8 -5
- package/dist/paths.d.ts.map +1 -1
- package/dist/paths.js +8 -5
- package/dist/paths.js.map +1 -1
- package/dist/sync/provisioning.d.ts +5 -5
- package/dist/sync/provisioning.d.ts.map +1 -1
- package/dist/sync/provisioning.js +2 -2
- package/dist/sync/provisioning.js.map +1 -1
- package/dist/types.d.ts +43 -10
- package/dist/types.d.ts.map +1 -1
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -13,6 +13,7 @@
|
|
|
13
13
|
- [Usage](#usage)
|
|
14
14
|
- [Creating a client (signer + zcapClient)](#creating-a-client-signer--zcapclient)
|
|
15
15
|
- [Deriving a client from a secret or seed (`./identity`)](#deriving-a-client-from-a-secret-or-seed-identity)
|
|
16
|
+
- [Service discovery](#service-discovery)
|
|
16
17
|
- [The handle model](#the-handle-model)
|
|
17
18
|
- [Spaces](#spaces)
|
|
18
19
|
- [Collections](#collections)
|
|
@@ -187,6 +188,50 @@ accepted. A server that verifies both it and the older `Ed25519Signature2020`
|
|
|
187
188
|
accepts chains whose links mix the two, which is what a fleet upgrading at its
|
|
188
189
|
own pace produces.
|
|
189
190
|
|
|
191
|
+
### Service discovery
|
|
192
|
+
|
|
193
|
+
Before its first signed request, the client reads the server's service
|
|
194
|
+
description. It sends an unsigned `HEAD` to `serverUrl` and follows the
|
|
195
|
+
`Link: <...>; rel="service"` header on the response, whatever its status. Then
|
|
196
|
+
it reads the linked document with an unsigned `GET`. The spec fixes no path for
|
|
197
|
+
the document, so the client assumes none.
|
|
198
|
+
|
|
199
|
+
From the document the client selects the WAS specification version it speaks.
|
|
200
|
+
This client speaks only v0.5. It ignores `specs` keys other than
|
|
201
|
+
`https://w3id.org/pws` and entries without a `version`. If no entry names v0.5,
|
|
202
|
+
or the document is malformed, every signed request rejects with
|
|
203
|
+
`IncompatibleServerError` before anything is signed. A server whose responses
|
|
204
|
+
carry no `service` link predates v0.5 and is refused the same way. The client
|
|
205
|
+
does not fall back to the v0.4 layout.
|
|
206
|
+
|
|
207
|
+
Discovery runs once per client and is shared by every handle. A failure is not
|
|
208
|
+
memoized, so the next request retries. Unsigned public reads (`publicRead`,
|
|
209
|
+
`publicListCollection`, ...) take absolute URLs that may point at another
|
|
210
|
+
server, and do not wait on discovery.
|
|
211
|
+
|
|
212
|
+
`was.service()` returns what discovery found:
|
|
213
|
+
|
|
214
|
+
```ts
|
|
215
|
+
const info = await was.service()
|
|
216
|
+
info.version // '0.5'
|
|
217
|
+
info.entry // the chosen `PwsVersionEntry`
|
|
218
|
+
info.description // the whole `ServiceDescription`
|
|
219
|
+
info.spacesUrl // the Spaces Repository URL, or undefined
|
|
220
|
+
info.features // e.g. ['listing', 'collection-management', ...]
|
|
221
|
+
info.hasFeature('quotas') // false when the token is absent
|
|
222
|
+
|
|
223
|
+
// Discover again, e.g. before relying on an affordance a host may have dropped.
|
|
224
|
+
await was.service({ refresh: true })
|
|
225
|
+
```
|
|
226
|
+
|
|
227
|
+
`features` is an open token list. A token this client does not know is carried
|
|
228
|
+
along and otherwise ignored, and an absent token means unsupported. The client
|
|
229
|
+
gates no behavior on the document's `instance` member.
|
|
230
|
+
|
|
231
|
+
A caller that already holds the document (from a cache, say) can pass it as the
|
|
232
|
+
`serviceDescription` constructor option. The client then selects a version from
|
|
233
|
+
that copy without fetching, and `service({ refresh: true })` discovers afresh.
|
|
234
|
+
|
|
190
235
|
### The handle model
|
|
191
236
|
|
|
192
237
|
The client exposes the WAS containment model
|
|
@@ -220,22 +265,25 @@ one item" footgun. The next sections cover each level in turn.
|
|
|
220
265
|
|
|
221
266
|
A Space is the top-level container, created from the spaces repository. The
|
|
222
267
|
server requires a `name`; `controller` defaults to the client's own DID, and the
|
|
223
|
-
server generates the id unless you pass one.
|
|
268
|
+
server generates the id unless you pass one. `createSpace()` and `listSpaces()`
|
|
269
|
+
address the Spaces Repository URL the service description names (see
|
|
270
|
+
[Service discovery](#service-discovery)). Against a server whose description has
|
|
271
|
+
no `spaces` URL, they throw `NotSupportedError` without sending anything.
|
|
224
272
|
|
|
225
273
|
```ts
|
|
226
|
-
const space = await was.createSpace({ name: 'Home' }) // POST
|
|
274
|
+
const space = await was.createSpace({ name: 'Home' }) // POST {spacesUrl}
|
|
227
275
|
|
|
228
276
|
// Lazy handle to an existing space by id -- no I/O until a verb runs.
|
|
229
277
|
const same = was.space(space.id)
|
|
230
278
|
|
|
231
|
-
// Read the Space
|
|
279
|
+
// Read the Space Metadata object (null if missing or not visible to you).
|
|
232
280
|
const desc = await space.describe() // { id, type: ['Space'], name, controller } | null
|
|
233
281
|
|
|
234
|
-
// Upsert: merges the given fields over the current
|
|
282
|
+
// Upsert: merges the given fields over the current Metadata object.
|
|
235
283
|
await space.configure({ name: 'Home (renamed)' })
|
|
236
284
|
|
|
237
|
-
// Lost-update-safe writes: read the
|
|
238
|
-
// it under `ifMatch` (412 `PreconditionFailedError` if it changed), or
|
|
285
|
+
// Lost-update-safe writes: read the Metadata object with its ETag, then
|
|
286
|
+
// write it under `ifMatch` (412 `PreconditionFailedError` if it changed), or
|
|
239
287
|
// create-if-absent under `ifNoneMatch` (412 if the Space already exists).
|
|
240
288
|
// Nothing is read or merged on the client, so `controller` is required.
|
|
241
289
|
const read = await space.describeWithEtag() // { description, etag? } | null
|
|
@@ -248,7 +296,7 @@ await was
|
|
|
248
296
|
.replaceDescription(
|
|
249
297
|
{ name: 'Fresh', controller: 'did:key:z6Mk...' },
|
|
250
298
|
{ ifNoneMatch: true }
|
|
251
|
-
) // { description, etag? }: a create echoes the
|
|
299
|
+
) // { description, etag? }: a create echoes the Metadata object
|
|
252
300
|
|
|
253
301
|
await space.delete() // idempotent
|
|
254
302
|
|
|
@@ -286,10 +334,10 @@ const collection = await space.createCollection({
|
|
|
286
334
|
// Lazy handle to an existing collection by id.
|
|
287
335
|
const same = space.collection(collection.id)
|
|
288
336
|
|
|
289
|
-
// Read the Collection
|
|
337
|
+
// Read the Collection Metadata object (null if missing or not visible).
|
|
290
338
|
const desc = await collection.describe() // { id, type: ['Collection'], name } | null
|
|
291
339
|
|
|
292
|
-
// Update (upsert; merges over the current
|
|
340
|
+
// Update (upsert; merges over the current Metadata object).
|
|
293
341
|
await collection.configure({ name: 'Credentials' })
|
|
294
342
|
|
|
295
343
|
// List the collections in a space.
|
|
@@ -314,17 +362,16 @@ await collection.delete() // deletes the whole collection; idempotent
|
|
|
314
362
|
To delete a single resource instead of the whole collection, use
|
|
315
363
|
`collection.resource(id).delete()`.
|
|
316
364
|
|
|
317
|
-
The
|
|
318
|
-
leaves it unpopulated by convention and carries its name
|
|
319
|
-
[
|
|
320
|
-
encrypted.
|
|
365
|
+
The Collection Metadata object's top-level `name` is server-visible plaintext.
|
|
366
|
+
An encrypted collection leaves it unpopulated by convention and carries its name
|
|
367
|
+
and tags on the same object's [`custom`](#collection-metadata) member instead,
|
|
368
|
+
where they are encrypted.
|
|
321
369
|
|
|
322
370
|
An application that provisions a collection can record who it was provisioned
|
|
323
371
|
for, with the optional `generator` (the application's DID) and `generatorOrigin`
|
|
324
|
-
(the Web origin that DID was bound to)
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
verify.
|
|
372
|
+
(the Web origin that DID was bound to) fields. Both are accepted at create time
|
|
373
|
+
and stay writable afterwards, so an existing collection can be backfilled; they
|
|
374
|
+
are controller assertions the server persists but does not verify.
|
|
328
375
|
|
|
329
376
|
```ts
|
|
330
377
|
await space.createCollection({
|
|
@@ -544,10 +591,14 @@ collection listings; updating one updates the other.
|
|
|
544
591
|
|
|
545
592
|
### Collection metadata
|
|
546
593
|
|
|
547
|
-
A Collection
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
594
|
+
A Collection's configuration (`name`, `backend`, `encryption`, `generator`, read
|
|
595
|
+
by [`describe()`](#collections)) and its annotations live in one object at the
|
|
596
|
+
Collection's reserved `/meta` path: server-managed properties (`createdAt` /
|
|
597
|
+
`updatedAt` / `createdBy`) plus a user-writable `custom` object (`name` and
|
|
598
|
+
`tags`), under one `metaVersion` validator. `meta()` is that same object with
|
|
599
|
+
`custom` decoded through the codec; `describe()` does not resolve the codec at
|
|
600
|
+
all, so on an encrypted collection it reports `custom` as the opaque envelope. A
|
|
601
|
+
server without the endpoint surfaces its 501 as `NotImplementedError`.
|
|
551
602
|
|
|
552
603
|
```ts
|
|
553
604
|
const meta = await collection.meta() // CollectionMetadata | null (null on a miss)
|
|
@@ -561,19 +612,31 @@ await collection.setTags({ app: 'wallet' }) // keeps existing name
|
|
|
561
612
|
|
|
562
613
|
// Conditional metadata write, against a `conditional-writes` backend.
|
|
563
614
|
await collection.setMeta({ custom: { name: 'Vault' } }, { ifMatch: meta?.etag })
|
|
564
|
-
// ...or
|
|
615
|
+
// ...or create the Collection only if it does not exist yet:
|
|
565
616
|
await collection.setMeta({ custom: { name: 'Vault' } }, { ifNoneMatch: true })
|
|
566
617
|
```
|
|
567
618
|
|
|
568
619
|
A failed precondition throws `PreconditionFailedError` (412). This `/meta` ETag
|
|
569
|
-
(`metaVersion`) is
|
|
570
|
-
|
|
620
|
+
(`metaVersion`) is independent of every Resource's versions, but not of the
|
|
621
|
+
configuration members: one validator covers the whole Collection Metadata
|
|
622
|
+
object, so `configure()` and `setMeta()` advance the same counter.
|
|
623
|
+
|
|
624
|
+
The write is a full replacement of that one object, so an annotation write
|
|
625
|
+
re-sends the configuration members (and any member this client does not model)
|
|
626
|
+
as read, and `configure()` merges over the version it pins to. Two consequences
|
|
627
|
+
worth knowing. `ifNoneMatch: true` is the only annotation write that may create:
|
|
628
|
+
it means "create the Collection only if it does not exist", creates it with no
|
|
629
|
+
configuration, and encodes `custom` with the plaintext codec, since the body it
|
|
630
|
+
sends declares no encryption descriptor (pass a per-handle `encryption` override
|
|
631
|
+
to state one). Every other annotation write requires a readable current object
|
|
632
|
+
and throws `NotFoundError` when there is none -- a masked 404 cannot turn a
|
|
633
|
+
rename into a create.
|
|
571
634
|
|
|
572
635
|
On an encrypted collection `custom` is encrypted into an envelope before it is
|
|
573
636
|
sent, so `name` / `tags` are never stored as server-visible plaintext, and
|
|
574
637
|
`meta()` decrypts them back for a keyed reader. This is the encrypted
|
|
575
|
-
collection's name/tags surface: by convention the plaintext
|
|
576
|
-
|
|
638
|
+
collection's name/tags surface: by convention the plaintext top-level `name` is
|
|
639
|
+
left unpopulated there.
|
|
577
640
|
|
|
578
641
|
#### The governing history log
|
|
579
642
|
|
|
@@ -594,11 +657,12 @@ await collection.putHistoryLog(log.body + nextLine, { ifMatch: log.etag })
|
|
|
594
657
|
|
|
595
658
|
From the create on, `describe()` serves `encryption` as the log head's `state`
|
|
596
659
|
with `history: { method, resource }` stamped on, and a direct `encryption` write
|
|
597
|
-
on the
|
|
598
|
-
a Collection whose
|
|
599
|
-
lost race throws `PreconditionFailedError` (412).
|
|
600
|
-
methods; the `/log` subpath's
|
|
601
|
-
them as the resource-log store
|
|
660
|
+
on the Collection Metadata object throws `ConflictError`. The create is refused
|
|
661
|
+
the same way on a Collection whose Metadata object already carries a
|
|
662
|
+
client-written descriptor. A lost race throws `PreconditionFailedError` (412).
|
|
663
|
+
These are the raw transport methods; the `/log` subpath's
|
|
664
|
+
`resourceLogStore({ collection })` (below) drives them as the resource-log store
|
|
665
|
+
port.
|
|
602
666
|
|
|
603
667
|
A reader that holds the descriptor but not the convention follows its `history`
|
|
604
668
|
pointer through the `/edv` subpath's `logGovernedCollectionDescriptorStore`, the
|
|
@@ -759,10 +823,9 @@ stores faithfully). Two things drive it, kept separate:
|
|
|
759
823
|
|
|
760
824
|
- **Policy** (is this collection encrypted?) is declared on the collection
|
|
761
825
|
itself: `createCollection({ encryption: { scheme: 'edv' } })` writes a
|
|
762
|
-
non-secret `encryption` descriptor to the Collection
|
|
826
|
+
non-secret `encryption` descriptor to the Collection Metadata object. Any
|
|
763
827
|
authorized reader -- including a delegated consumer that did **not** create
|
|
764
|
-
the collection -- discovers it by reading
|
|
765
|
-
decrypt.
|
|
828
|
+
the collection -- discovers it by reading that object, so it knows to decrypt.
|
|
766
829
|
- **Keys** come from an `encryption` provider you pass to `WasClient` (built
|
|
767
830
|
from the opt-in `@interop/was-client/edv` subpath, so plaintext consumers
|
|
768
831
|
never pull the crypto graph). It is a pure **keystore**: `resolveKeys` returns
|
|
@@ -794,21 +857,23 @@ const { id } = await vault.add({ secret: 'hello' }) // encrypted; id is an EDV i
|
|
|
794
857
|
const back = await vault.get(id) // { secret: 'hello' } -- decrypted
|
|
795
858
|
|
|
796
859
|
// A consumer that did not create it discovers the descriptor and decrypts with its
|
|
797
|
-
// own keys -- no override needed; one cached read of the
|
|
860
|
+
// own keys -- no override needed; one cached read of the Collection Metadata
|
|
861
|
+
// object:
|
|
798
862
|
const same = was.space(spaceId).collection('vault')
|
|
799
863
|
await same.get(id) // reads the descriptor, then decrypts
|
|
800
864
|
```
|
|
801
865
|
|
|
802
866
|
The switch is the **descriptor**: a handle encrypts a collection when its
|
|
803
|
-
|
|
804
|
-
caches -- no round-trip for plaintext-only clients or when an
|
|
805
|
-
Keys are then **required**: if the collection is declared
|
|
806
|
-
keystore returns no keys, reads/writes throw
|
|
807
|
-
|
|
867
|
+
Collection Metadata object declares `encryption` (resolution reads that object
|
|
868
|
+
once, then caches -- no round-trip for plaintext-only clients or when an
|
|
869
|
+
override is set). Keys are then **required**: if the collection is declared
|
|
870
|
+
encrypted but your keystore returns no keys, reads/writes throw
|
|
871
|
+
`EncryptionError` (fail-closed) instead of silently falling back to plaintext.
|
|
808
872
|
|
|
809
873
|
**Per-handle override (escape hatch).** Pass `encryption` in the handle options
|
|
810
|
-
to force the decision and skip the
|
|
811
|
-
from the keystore), `{ scheme: 'edv', keys }` (keys
|
|
874
|
+
to force the decision and skip the Collection Metadata read --
|
|
875
|
+
`{ scheme: 'edv' }` (keys from the keystore), `{ scheme: 'edv', keys }` (keys
|
|
876
|
+
inline), or `'plaintext'`:
|
|
812
877
|
|
|
813
878
|
```ts
|
|
814
879
|
const vault = was.space(spaceId).collection('vault', {
|
|
@@ -820,8 +885,12 @@ const vault = was.space(spaceId).collection('vault', {
|
|
|
820
885
|
keys-only): re-declare it once with
|
|
821
886
|
`collection.configure({ encryption: { scheme: 'edv' } })` (the descriptor is
|
|
822
887
|
set-once: declaring it on a collection that lacks one is allowed, changing or
|
|
823
|
-
clearing an existing one is rejected).
|
|
824
|
-
|
|
888
|
+
clearing an existing one is rejected). The same write re-seals the stored
|
|
889
|
+
`custom` under the newly declared descriptor, since the server validates the
|
|
890
|
+
envelope against the incoming one; a client that cannot build that codec (no
|
|
891
|
+
keystore, or no keys for the descriptor) sends no `custom` at all rather than
|
|
892
|
+
plaintext beside the descriptor, which loses the stored name and tags. Until the
|
|
893
|
+
collection is re-declared, a per-handle override reads it correctly.
|
|
825
894
|
|
|
826
895
|
Encrypted collections are a **stricter contract**, not a drop-in (documents-only
|
|
827
896
|
scope for now):
|
|
@@ -842,9 +911,9 @@ scope for now):
|
|
|
842
911
|
endpoint has its own ETag (`metaVersion`), independent of the content ETag.
|
|
843
912
|
The same pair at Collection level (`collection.setName()` / `setTags()` /
|
|
844
913
|
`setMeta()` / `meta()`) is where an encrypted collection carries its own name
|
|
845
|
-
and tags, since the
|
|
846
|
-
collection-level envelope binds no resource id, and a
|
|
847
|
-
served into that slot is refused.
|
|
914
|
+
and tags, since the Collection Metadata object's plaintext `name` is left
|
|
915
|
+
unpopulated; the collection-level envelope binds no resource id, and a
|
|
916
|
+
resource-bound envelope served into that slot is refused.
|
|
848
917
|
- **Binary.** A `Blob`/`Uint8Array` up to `maxBlobBytes` (512 KiB by default) is
|
|
849
918
|
encrypted as a single document. A larger one is routed automatically by
|
|
850
919
|
`add()` to the chunked-stream path: one document plus its chunk resources,
|
|
@@ -1113,7 +1182,8 @@ large Blobs to disk.)
|
|
|
1113
1182
|
`was.request(...)` mirrors ezcap's generic `request()` for hand-built calls. As
|
|
1114
1183
|
a deliberate escape hatch it returns the raw `HttpResponse` and throws raw
|
|
1115
1184
|
ezcap/ky errors -- it does not apply the null-on-404 or typed-error
|
|
1116
|
-
conveniences.
|
|
1185
|
+
conveniences. It still waits on service discovery, so against an incompatible
|
|
1186
|
+
server it rejects with `IncompatibleServerError`.
|
|
1117
1187
|
|
|
1118
1188
|
```ts
|
|
1119
1189
|
const response = await was.request({ path: `/space/${spaceId}`, method: 'GET' })
|
|
@@ -1150,6 +1220,12 @@ instead, as `{ outcome: 'not-found' }`, rather than swallowing it.
|
|
|
1150
1220
|
Spec endpoints a given server has not yet implemented surface as
|
|
1151
1221
|
`NotImplementedError` (the server's 501).
|
|
1152
1222
|
|
|
1223
|
+
`IncompatibleServerError` comes from service discovery, not from a response
|
|
1224
|
+
status. Any signed method, read methods included, throws it when the server
|
|
1225
|
+
speaks no WAS version this client understands (see
|
|
1226
|
+
[Service discovery](#service-discovery)). It carries no `status`, so a read
|
|
1227
|
+
method does not mistake it for a 404.
|
|
1228
|
+
|
|
1153
1229
|
## Contribute
|
|
1154
1230
|
|
|
1155
1231
|
PRs accepted. See [CONTRIBUTING.md](CONTRIBUTING.md) for editor setup (Prettier,
|
package/dist/Collection.d.ts
CHANGED
|
@@ -2,7 +2,7 @@ import type { ClientContext } from './internal/request.js';
|
|
|
2
2
|
import type { IndexDeclaration, IndexSchema } from './codec.js';
|
|
3
3
|
import { Resource } from './Resource.js';
|
|
4
4
|
import type { ChangeDocument, ChangesCheckpoint, ChangesPage } from '@interop/storage-core';
|
|
5
|
-
import type { AddResult, BackendDescriptor, BackendUsage,
|
|
5
|
+
import type { AddResult, BackendDescriptor, BackendUsage, CollectionMetadata, CollectionWritableFields, EncryptionOverride, FindPage, GrantOptions, HandleOptions, IDelegatedZcap, IZcap, Json, ResourceData, LinkSet, PolicyDocument, CollectionResourcesList, ResourceMetadataCustomInput, ResourceSummary } from './types.js';
|
|
6
6
|
export declare class Collection {
|
|
7
7
|
#private;
|
|
8
8
|
readonly spaceId: string;
|
|
@@ -26,93 +26,121 @@ export declare class Collection {
|
|
|
26
26
|
encryption?: EncryptionOverride;
|
|
27
27
|
});
|
|
28
28
|
/**
|
|
29
|
-
* Reads the Collection
|
|
30
|
-
*
|
|
31
|
-
*
|
|
29
|
+
* Reads the Collection Metadata object -- the Collection's configuration
|
|
30
|
+
* (`name`, `backend`, `encryption`, `generator`) beside the server-managed
|
|
31
|
+
* timestamps and the user-writable `custom`, as one object under one
|
|
32
|
+
* validator. Returns `null` if the collection is missing or not visible to
|
|
33
|
+
* you (WAS returns 404 for both not-found and unauthorized).
|
|
32
34
|
*
|
|
33
|
-
*
|
|
35
|
+
* This read never resolves the codec, so it is the configuration read: on an
|
|
36
|
+
* encrypted Collection the served `custom` is the opaque envelope, exactly as
|
|
37
|
+
* stored. {@link meta} is the same object with `custom` decoded.
|
|
38
|
+
*
|
|
39
|
+
* @returns {Promise<(CollectionMetadata & { etag?: string }) | null>}
|
|
34
40
|
*/
|
|
35
|
-
describe(): Promise<
|
|
41
|
+
describe(): Promise<(CollectionMetadata & {
|
|
42
|
+
etag?: string;
|
|
43
|
+
}) | null>;
|
|
36
44
|
/**
|
|
37
|
-
*
|
|
38
|
-
*
|
|
45
|
+
* {@link describe}, with the object and its `ETag` in separate members. The
|
|
46
|
+
* `ETag` is the `metaVersion` validator to pass to
|
|
47
|
+
* {@link replaceDescription}'s `ifMatch` for a lost-update-safe
|
|
48
|
+
* (compare-and-swap) write. One validator covers the whole object, so it is
|
|
49
|
+
* also what {@link setMeta} pins against. Returns `null` if the collection is
|
|
50
|
+
* missing or not visible to you (404 conflation caveat); `etag` is absent
|
|
51
|
+
* against a backend that does not version the object.
|
|
39
52
|
*
|
|
40
|
-
*
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
*
|
|
48
|
-
*
|
|
49
|
-
*
|
|
50
|
-
*
|
|
51
|
-
*
|
|
53
|
+
* @returns {Promise<{ description: CollectionMetadata; etag?: string } | null>}
|
|
54
|
+
*/
|
|
55
|
+
describeWithEtag(): Promise<{
|
|
56
|
+
description: CollectionMetadata;
|
|
57
|
+
etag?: string;
|
|
58
|
+
} | null>;
|
|
59
|
+
/**
|
|
60
|
+
* Creates or updates the collection by id (upsert). Merges the given
|
|
61
|
+
* configuration members over the current ones; the annotations (`custom` and
|
|
62
|
+
* its `epoch` stamp) are carried forward untouched, unless this write
|
|
63
|
+
* changes the encryption scheme, in which case `custom` is re-sealed under
|
|
64
|
+
* the incoming descriptor (see {@link replaceDescription}).
|
|
65
|
+
*
|
|
66
|
+
* The merge runs against the same read the write is pinned to, and rebases
|
|
67
|
+
* with it: a rival configuration change landing in between is re-read and
|
|
68
|
+
* merged over rather than re-applied stale.
|
|
69
|
+
*
|
|
70
|
+
* The merge needs a readable current object to be lost-update-safe, and the
|
|
71
|
+
* read cannot distinguish "absent" from "unreadable" (WAS masks unauthorized
|
|
72
|
+
* reads as 404). When it finds nothing and either `backend` or `encryption`
|
|
73
|
+
* is left out, this fails closed rather than sending a PUT body that would
|
|
74
|
+
* silently drop an existing collection's `backend` (a data-placement change)
|
|
75
|
+
* or trip `encryption-immutable` by clearing its descriptor. Supplying only
|
|
76
|
+
* one of the two does not cover the other: with nothing readable to merge
|
|
77
|
+
* from, the omitted field is dropped either way. Pass `force: true` to
|
|
78
|
+
* proceed anyway -- e.g. when creating a new collection through a handle (or
|
|
79
|
+
* use `space.createCollection()`, which does not merge).
|
|
52
80
|
*
|
|
53
81
|
* @param desc {CollectionWritableFields} the fields to merge; `encryption`
|
|
54
82
|
* declares the client-side encryption descriptor, which is set-once on the
|
|
55
83
|
* server (it may be added to a Collection that lacks one, but
|
|
56
84
|
* changing/clearing an existing descriptor is rejected -- `ConflictError`,
|
|
57
85
|
* `encryption-immutable`)
|
|
58
|
-
* @param [desc.force] {boolean} proceed even when the current
|
|
86
|
+
* @param [desc.force] {boolean} proceed even when the current object
|
|
59
87
|
* is unreadable and `backend`/`encryption` are omitted (see above)
|
|
60
|
-
* @param [desc.current] {
|
|
61
|
-
*
|
|
62
|
-
*
|
|
63
|
-
*
|
|
64
|
-
*
|
|
65
|
-
*
|
|
66
|
-
*
|
|
67
|
-
*
|
|
68
|
-
*
|
|
69
|
-
* @returns {Promise<CollectionDescription>}
|
|
88
|
+
* @param [desc.current] {CollectionMetadata | null} the current Collection
|
|
89
|
+
* Metadata object, when the caller has already read it -- the merge's first
|
|
90
|
+
* attempt then runs against this instead of a second read. It is used only
|
|
91
|
+
* while it carries the `etag` the write pins against, so a copy another
|
|
92
|
+
* client has since overwritten loses the compare-and-swap and is re-read
|
|
93
|
+
* rather than merged forward. `null` means the caller read it and found
|
|
94
|
+
* the collection absent or unreadable; omitting the member entirely is
|
|
95
|
+
* what asks for the read
|
|
96
|
+
* @returns {Promise<CollectionMetadata>}
|
|
70
97
|
*/
|
|
71
98
|
configure(desc: CollectionWritableFields & {
|
|
72
99
|
force?: boolean;
|
|
73
|
-
current?:
|
|
74
|
-
|
|
100
|
+
current?: (CollectionMetadata & {
|
|
101
|
+
etag?: string;
|
|
102
|
+
}) | null;
|
|
103
|
+
}): Promise<CollectionMetadata>;
|
|
75
104
|
/**
|
|
76
|
-
*
|
|
77
|
-
*
|
|
78
|
-
* the opaque validator to pass to {@link replaceDescription}'s `ifMatch` for a
|
|
79
|
-
* lost-update-safe (compare-and-swap) description write. Returns `null` if the
|
|
80
|
-
* collection is missing or not visible to you (404 conflation caveat); `etag`
|
|
81
|
-
* is absent against a server that does not version the description.
|
|
82
|
-
*
|
|
83
|
-
* @returns {Promise<{ description: CollectionDescription; etag?: string } | null>}
|
|
84
|
-
*/
|
|
85
|
-
describeWithEtag(): Promise<{
|
|
86
|
-
description: CollectionDescription;
|
|
87
|
-
etag?: string;
|
|
88
|
-
} | null>;
|
|
89
|
-
/**
|
|
90
|
-
* Writes (replaces) the Collection Description, optionally under a
|
|
91
|
-
* precondition: `ifMatch` (the `ETag` from {@link describeWithEtag}) makes
|
|
105
|
+
* Writes (replaces) the Collection's configuration members, optionally under
|
|
106
|
+
* a precondition: `ifMatch` (the `ETag` from {@link describeWithEtag}) makes
|
|
92
107
|
* it a compare-and-swap so a concurrent writer cannot be silently clobbered,
|
|
93
108
|
* and `ifNoneMatch: true` makes it a guarded create that proceeds only while
|
|
94
109
|
* no Collection exists under this id. A failed precondition surfaces as
|
|
95
|
-
* `PreconditionFailedError` (412). Sends the writable
|
|
96
|
-
* body; omit a
|
|
97
|
-
* pass every
|
|
98
|
-
*
|
|
99
|
-
*
|
|
110
|
+
* `PreconditionFailedError` (412). Sends the writable configuration as the
|
|
111
|
+
* full body; omit a member to drop it (replace semantics), so callers doing
|
|
112
|
+
* CAS pass every member forward. Members this client does not model are
|
|
113
|
+
* carried forward from the stored object rather than cleared.
|
|
114
|
+
*
|
|
115
|
+
* The annotations (`custom` and its `epoch` stamp) are carried forward
|
|
116
|
+
* verbatim -- `custom` exactly as served, so an encrypted Collection's
|
|
117
|
+
* envelope is never re-sealed by an ordinary configuration write. The one
|
|
118
|
+
* exception is a write that changes the encryption scheme: the server
|
|
119
|
+
* validates `custom` against the INCOMING descriptor, so a stored plaintext
|
|
120
|
+
* `custom` cannot travel beside a newly declared `encryption` descriptor.
|
|
121
|
+
* Such a write re-seals `custom` under the incoming descriptor's codec, and
|
|
122
|
+
* drops `custom` (and its stamp) when this client cannot build that codec.
|
|
123
|
+
* A Collection with no stored `custom` sends none, which clears an
|
|
124
|
+
* already-empty value on an encrypted Collection just as on a plaintext one.
|
|
125
|
+
* Returns the new `ETag`, and the object the server answers a create with.
|
|
126
|
+
*
|
|
127
|
+
* This is the generic compare-and-swap primitive the key-epoch recipient
|
|
100
128
|
* operations build on (add/remove a reader is a CAS of the `encryption`
|
|
101
129
|
* descriptor); it is not epoch-specific.
|
|
102
130
|
*
|
|
103
131
|
* @param description {CollectionWritableFields}
|
|
104
132
|
* @param options {object}
|
|
105
133
|
* @param [options.ifMatch] {string} the prior `ETag`; the write applies only
|
|
106
|
-
* if the
|
|
134
|
+
* if the object is unchanged
|
|
107
135
|
* @param [options.ifNoneMatch] {boolean} write only if the Collection does
|
|
108
136
|
* not exist yet
|
|
109
|
-
* @returns {Promise<{ description:
|
|
137
|
+
* @returns {Promise<{ description: CollectionMetadata; etag?: string }>}
|
|
110
138
|
*/
|
|
111
139
|
replaceDescription(description: CollectionWritableFields, options?: {
|
|
112
140
|
ifMatch?: string;
|
|
113
141
|
ifNoneMatch?: boolean;
|
|
114
142
|
}): Promise<{
|
|
115
|
-
description:
|
|
143
|
+
description: CollectionMetadata;
|
|
116
144
|
etag?: string;
|
|
117
145
|
}>;
|
|
118
146
|
/**
|
|
@@ -123,23 +151,25 @@ export declare class Collection {
|
|
|
123
151
|
*/
|
|
124
152
|
delete(): Promise<void>;
|
|
125
153
|
/**
|
|
126
|
-
* Reads the Collection
|
|
127
|
-
*
|
|
128
|
-
*
|
|
129
|
-
*
|
|
130
|
-
*
|
|
154
|
+
* Reads the Collection Metadata object with its user-writable `custom`
|
|
155
|
+
* decoded -- {@link describe} plus the codec. Returns `null` if the
|
|
156
|
+
* collection is missing or not visible to you (404 conflation caveat). A
|
|
157
|
+
* server without Collection metadata support surfaces its 501 as
|
|
158
|
+
* `NotImplementedError`.
|
|
131
159
|
*
|
|
132
160
|
* On an encrypted collection the stored `custom` is an opaque envelope; this
|
|
133
161
|
* decodes it (decrypts, via the codec) so a caller always sees plaintext
|
|
134
162
|
* `{ name, tags }`. A collection with no user metadata reports `custom` as
|
|
135
|
-
* `{}`.
|
|
163
|
+
* `{}`. Resolving the codec can fail closed on a collection this client holds
|
|
164
|
+
* no keys for, which is why {@link describe} -- the configuration read -- goes
|
|
165
|
+
* without it.
|
|
136
166
|
*
|
|
137
167
|
* Against a backend with the `conditional-writes` feature the result also
|
|
138
|
-
* carries the
|
|
139
|
-
*
|
|
140
|
-
*
|
|
141
|
-
*
|
|
142
|
-
*
|
|
168
|
+
* carries the object's current `etag` (its `metaVersion` validator) -- pass it
|
|
169
|
+
* as `setMeta(meta, { ifMatch })` for a lost-update-safe update. One
|
|
170
|
+
* validator covers the whole object: a configuration write and an annotation
|
|
171
|
+
* write advance the same counter, so a client holds one ETag for the
|
|
172
|
+
* Collection rather than one per surface.
|
|
143
173
|
*
|
|
144
174
|
* @returns {Promise<(CollectionMetadata & { etag?: string }) | null>}
|
|
145
175
|
*/
|
|
@@ -147,24 +177,33 @@ export declare class Collection {
|
|
|
147
177
|
etag?: string;
|
|
148
178
|
}) | null>;
|
|
149
179
|
/**
|
|
150
|
-
* Replaces the Collection's user-writable
|
|
151
|
-
* replacement
|
|
152
|
-
* `custom` clears them all.
|
|
153
|
-
*
|
|
154
|
-
*
|
|
155
|
-
*
|
|
180
|
+
* Replaces the Collection's user-writable annotations (`custom`). This is a
|
|
181
|
+
* full replacement of `custom`: any property omitted from it is cleared, and
|
|
182
|
+
* an omitted `custom` clears them all. The Collection's configuration
|
|
183
|
+
* members are carried forward -- the same `PUT` replaces the whole
|
|
184
|
+
* Collection Metadata object, so they are read and re-sent rather than
|
|
185
|
+
* dropped.
|
|
156
186
|
*
|
|
157
187
|
* On an encrypted collection `custom` is encrypted into an opaque envelope by
|
|
158
188
|
* the codec before it is sent, so `name` / `tags` are never stored as
|
|
159
189
|
* server-visible plaintext -- transparently, the same call works on plaintext
|
|
160
|
-
* and encrypted collections alike.
|
|
161
|
-
*
|
|
162
|
-
*
|
|
163
|
-
*
|
|
164
|
-
*
|
|
165
|
-
*
|
|
166
|
-
* `PreconditionFailedError` (412).
|
|
167
|
-
*
|
|
190
|
+
* and encrypted collections alike. The key epoch the codec stamps the
|
|
191
|
+
* envelope with travels as a top-level `epoch` member of the same body.
|
|
192
|
+
*
|
|
193
|
+
* Conditional writes (the backend's `conditional-writes` feature): pass
|
|
194
|
+
* `ifMatch` (the `etag` from a prior {@link meta} or {@link describe}) for an
|
|
195
|
+
* update-if-unchanged, and a failed precondition throws
|
|
196
|
+
* `PreconditionFailedError` (412). Without one the write is pinned to the
|
|
197
|
+
* version it composed the configuration from and rebases on a lost race.
|
|
198
|
+
* `ifNoneMatch: true` is the guarded create: one validator covers the whole
|
|
199
|
+
* object, which exists exactly as long as its Collection does, so it means
|
|
200
|
+
* "create the Collection only if it does not exist" -- and creates it with no
|
|
201
|
+
* configuration, the plaintext codec encoding its `custom` (nothing is
|
|
202
|
+
* stored yet to declare encryption, and a per-handle override is the only
|
|
203
|
+
* way to state it). Every other annotation write requires the Collection to
|
|
204
|
+
* be there: one that cannot read the current object throws `NotFoundError`
|
|
205
|
+
* rather than upserting a configuration-less Collection over one this
|
|
206
|
+
* capability cannot see. Returns the new `etag`.
|
|
168
207
|
*
|
|
169
208
|
* @param meta {object}
|
|
170
209
|
* @param [meta.custom] {ResourceMetadataCustomInput} the user-writable
|
|
@@ -172,9 +211,10 @@ export declare class Collection {
|
|
|
172
211
|
* Collection-level `custom` also carries the persisted `indexSchema`)
|
|
173
212
|
* while `name` / `tags` themselves stay checked at their stored types
|
|
174
213
|
* @param options {object}
|
|
175
|
-
* @param [options.ifMatch] {string} update only if the
|
|
176
|
-
* @param [options.ifNoneMatch] {boolean}
|
|
177
|
-
*
|
|
214
|
+
* @param [options.ifMatch] {string} update only if the `ETag` matches
|
|
215
|
+
* @param [options.ifNoneMatch] {boolean} create only if the Collection does
|
|
216
|
+
* not exist
|
|
217
|
+
* @returns {Promise<{ etag?: string }>} the object's new ETag
|
|
178
218
|
*/
|
|
179
219
|
setMeta(meta?: {
|
|
180
220
|
custom?: ResourceMetadataCustomInput;
|
|
@@ -185,19 +225,20 @@ export declare class Collection {
|
|
|
185
225
|
etag?: string;
|
|
186
226
|
}>;
|
|
187
227
|
/**
|
|
188
|
-
* Sets the Collection's
|
|
189
|
-
* existing `tags`. Convenience over `setMeta()`. The write is pinned to
|
|
190
|
-
* `etag` the `meta()` read returned (when the backend supports
|
|
191
|
-
* `conditional-writes`), so a concurrent
|
|
192
|
-
*
|
|
228
|
+
* Sets the Collection's annotation-level human-readable `name`, preserving
|
|
229
|
+
* any existing `tags`. Convenience over `setMeta()`. The write is pinned to
|
|
230
|
+
* the `etag` the `meta()` read returned (when the backend supports
|
|
231
|
+
* `conditional-writes`) and rebases on a lost race, so a concurrent write --
|
|
232
|
+
* an annotation write or a configuration change, which share the one
|
|
233
|
+
* validator -- is re-read and re-applied rather than silently erased by this
|
|
193
234
|
* full-replacement write.
|
|
194
235
|
*
|
|
195
236
|
* On an encrypted collection this is the collection's client-encrypted name
|
|
196
237
|
* surface: the codec seals it into the `custom` envelope, and by convention
|
|
197
|
-
* the plaintext
|
|
238
|
+
* the plaintext top-level `name` is left unpopulated. On a plaintext
|
|
198
239
|
* collection the two are separate labels -- space-level listings surface the
|
|
199
|
-
*
|
|
200
|
-
*
|
|
240
|
+
* top-level `name` (set via `configure({ name })`), while this one is inside
|
|
241
|
+
* `custom`.
|
|
201
242
|
*
|
|
202
243
|
* @param name {string}
|
|
203
244
|
* @returns {Promise<void>}
|
|
@@ -213,7 +254,7 @@ export declare class Collection {
|
|
|
213
254
|
*/
|
|
214
255
|
setTags(tags: Record<string, string>): Promise<void>;
|
|
215
256
|
/**
|
|
216
|
-
* The absolute URL of the Collection's governing history log, the
|
|
257
|
+
* The absolute URL of the Collection's governing history log, the `meta/log`
|
|
217
258
|
* sub-resource {@link getHistoryLog} reads. A log-governed descriptor's
|
|
218
259
|
* `history.resource` names this URL; a verifying reader compares the two
|
|
219
260
|
* before opening the log.
|