@interop/was-client 0.60.0 → 0.61.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 +66 -45
- 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/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 +1 -1
- package/dist/errors.js +3 -3
- package/dist/errors.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.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 -17
- package/dist/internal/paths.d.ts.map +1 -1
- package/dist/internal/paths.js +92 -40
- package/dist/internal/paths.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/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 +16 -9
- package/dist/types.d.ts.map +1 -1
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -228,14 +228,14 @@ const space = await was.createSpace({ name: 'Home' }) // POST /spaces/
|
|
|
228
228
|
// Lazy handle to an existing space by id -- no I/O until a verb runs.
|
|
229
229
|
const same = was.space(space.id)
|
|
230
230
|
|
|
231
|
-
// Read the Space
|
|
231
|
+
// Read the Space Metadata object (null if missing or not visible to you).
|
|
232
232
|
const desc = await space.describe() // { id, type: ['Space'], name, controller } | null
|
|
233
233
|
|
|
234
|
-
// Upsert: merges the given fields over the current
|
|
234
|
+
// Upsert: merges the given fields over the current Metadata object.
|
|
235
235
|
await space.configure({ name: 'Home (renamed)' })
|
|
236
236
|
|
|
237
|
-
// Lost-update-safe writes: read the
|
|
238
|
-
// it under `ifMatch` (412 `PreconditionFailedError` if it changed), or
|
|
237
|
+
// Lost-update-safe writes: read the Metadata object with its ETag, then
|
|
238
|
+
// write it under `ifMatch` (412 `PreconditionFailedError` if it changed), or
|
|
239
239
|
// create-if-absent under `ifNoneMatch` (412 if the Space already exists).
|
|
240
240
|
// Nothing is read or merged on the client, so `controller` is required.
|
|
241
241
|
const read = await space.describeWithEtag() // { description, etag? } | null
|
|
@@ -248,7 +248,7 @@ await was
|
|
|
248
248
|
.replaceDescription(
|
|
249
249
|
{ name: 'Fresh', controller: 'did:key:z6Mk...' },
|
|
250
250
|
{ ifNoneMatch: true }
|
|
251
|
-
) // { description, etag? }: a create echoes the
|
|
251
|
+
) // { description, etag? }: a create echoes the Metadata object
|
|
252
252
|
|
|
253
253
|
await space.delete() // idempotent
|
|
254
254
|
|
|
@@ -286,10 +286,10 @@ const collection = await space.createCollection({
|
|
|
286
286
|
// Lazy handle to an existing collection by id.
|
|
287
287
|
const same = space.collection(collection.id)
|
|
288
288
|
|
|
289
|
-
// Read the Collection
|
|
289
|
+
// Read the Collection Metadata object (null if missing or not visible).
|
|
290
290
|
const desc = await collection.describe() // { id, type: ['Collection'], name } | null
|
|
291
291
|
|
|
292
|
-
// Update (upsert; merges over the current
|
|
292
|
+
// Update (upsert; merges over the current Metadata object).
|
|
293
293
|
await collection.configure({ name: 'Credentials' })
|
|
294
294
|
|
|
295
295
|
// List the collections in a space.
|
|
@@ -314,17 +314,16 @@ await collection.delete() // deletes the whole collection; idempotent
|
|
|
314
314
|
To delete a single resource instead of the whole collection, use
|
|
315
315
|
`collection.resource(id).delete()`.
|
|
316
316
|
|
|
317
|
-
The
|
|
318
|
-
leaves it unpopulated by convention and carries its name
|
|
319
|
-
[
|
|
320
|
-
encrypted.
|
|
317
|
+
The Collection Metadata object's top-level `name` is server-visible plaintext.
|
|
318
|
+
An encrypted collection leaves it unpopulated by convention and carries its name
|
|
319
|
+
and tags on the same object's [`custom`](#collection-metadata) member instead,
|
|
320
|
+
where they are encrypted.
|
|
321
321
|
|
|
322
322
|
An application that provisions a collection can record who it was provisioned
|
|
323
323
|
for, with the optional `generator` (the application's DID) and `generatorOrigin`
|
|
324
|
-
(the Web origin that DID was bound to)
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
verify.
|
|
324
|
+
(the Web origin that DID was bound to) fields. Both are accepted at create time
|
|
325
|
+
and stay writable afterwards, so an existing collection can be backfilled; they
|
|
326
|
+
are controller assertions the server persists but does not verify.
|
|
328
327
|
|
|
329
328
|
```ts
|
|
330
329
|
await space.createCollection({
|
|
@@ -544,10 +543,14 @@ collection listings; updating one updates the other.
|
|
|
544
543
|
|
|
545
544
|
### Collection metadata
|
|
546
545
|
|
|
547
|
-
A Collection
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
546
|
+
A Collection's configuration (`name`, `backend`, `encryption`, `generator`, read
|
|
547
|
+
by [`describe()`](#collections)) and its annotations live in one object at the
|
|
548
|
+
Collection's reserved `/meta` path: server-managed properties (`createdAt` /
|
|
549
|
+
`updatedAt` / `createdBy`) plus a user-writable `custom` object (`name` and
|
|
550
|
+
`tags`), under one `metaVersion` validator. `meta()` is that same object with
|
|
551
|
+
`custom` decoded through the codec; `describe()` does not resolve the codec at
|
|
552
|
+
all, so on an encrypted collection it reports `custom` as the opaque envelope. A
|
|
553
|
+
server without the endpoint surfaces its 501 as `NotImplementedError`.
|
|
551
554
|
|
|
552
555
|
```ts
|
|
553
556
|
const meta = await collection.meta() // CollectionMetadata | null (null on a miss)
|
|
@@ -561,19 +564,31 @@ await collection.setTags({ app: 'wallet' }) // keeps existing name
|
|
|
561
564
|
|
|
562
565
|
// Conditional metadata write, against a `conditional-writes` backend.
|
|
563
566
|
await collection.setMeta({ custom: { name: 'Vault' } }, { ifMatch: meta?.etag })
|
|
564
|
-
// ...or
|
|
567
|
+
// ...or create the Collection only if it does not exist yet:
|
|
565
568
|
await collection.setMeta({ custom: { name: 'Vault' } }, { ifNoneMatch: true })
|
|
566
569
|
```
|
|
567
570
|
|
|
568
571
|
A failed precondition throws `PreconditionFailedError` (412). This `/meta` ETag
|
|
569
|
-
(`metaVersion`) is
|
|
570
|
-
|
|
572
|
+
(`metaVersion`) is independent of every Resource's versions, but not of the
|
|
573
|
+
configuration members: one validator covers the whole Collection Metadata
|
|
574
|
+
object, so `configure()` and `setMeta()` advance the same counter.
|
|
575
|
+
|
|
576
|
+
The write is a full replacement of that one object, so an annotation write
|
|
577
|
+
re-sends the configuration members (and any member this client does not model)
|
|
578
|
+
as read, and `configure()` merges over the version it pins to. Two consequences
|
|
579
|
+
worth knowing. `ifNoneMatch: true` is the only annotation write that may create:
|
|
580
|
+
it means "create the Collection only if it does not exist", creates it with no
|
|
581
|
+
configuration, and encodes `custom` with the plaintext codec, since the body it
|
|
582
|
+
sends declares no encryption descriptor (pass a per-handle `encryption` override
|
|
583
|
+
to state one). Every other annotation write requires a readable current object
|
|
584
|
+
and throws `NotFoundError` when there is none -- a masked 404 cannot turn a
|
|
585
|
+
rename into a create.
|
|
571
586
|
|
|
572
587
|
On an encrypted collection `custom` is encrypted into an envelope before it is
|
|
573
588
|
sent, so `name` / `tags` are never stored as server-visible plaintext, and
|
|
574
589
|
`meta()` decrypts them back for a keyed reader. This is the encrypted
|
|
575
|
-
collection's name/tags surface: by convention the plaintext
|
|
576
|
-
|
|
590
|
+
collection's name/tags surface: by convention the plaintext top-level `name` is
|
|
591
|
+
left unpopulated there.
|
|
577
592
|
|
|
578
593
|
#### The governing history log
|
|
579
594
|
|
|
@@ -594,11 +609,12 @@ await collection.putHistoryLog(log.body + nextLine, { ifMatch: log.etag })
|
|
|
594
609
|
|
|
595
610
|
From the create on, `describe()` serves `encryption` as the log head's `state`
|
|
596
611
|
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
|
|
612
|
+
on the Collection Metadata object throws `ConflictError`. The create is refused
|
|
613
|
+
the same way on a Collection whose Metadata object already carries a
|
|
614
|
+
client-written descriptor. A lost race throws `PreconditionFailedError` (412).
|
|
615
|
+
These are the raw transport methods; the `/log` subpath's
|
|
616
|
+
`resourceLogStore({ collection })` (below) drives them as the resource-log store
|
|
617
|
+
port.
|
|
602
618
|
|
|
603
619
|
A reader that holds the descriptor but not the convention follows its `history`
|
|
604
620
|
pointer through the `/edv` subpath's `logGovernedCollectionDescriptorStore`, the
|
|
@@ -759,10 +775,9 @@ stores faithfully). Two things drive it, kept separate:
|
|
|
759
775
|
|
|
760
776
|
- **Policy** (is this collection encrypted?) is declared on the collection
|
|
761
777
|
itself: `createCollection({ encryption: { scheme: 'edv' } })` writes a
|
|
762
|
-
non-secret `encryption` descriptor to the Collection
|
|
778
|
+
non-secret `encryption` descriptor to the Collection Metadata object. Any
|
|
763
779
|
authorized reader -- including a delegated consumer that did **not** create
|
|
764
|
-
the collection -- discovers it by reading
|
|
765
|
-
decrypt.
|
|
780
|
+
the collection -- discovers it by reading that object, so it knows to decrypt.
|
|
766
781
|
- **Keys** come from an `encryption` provider you pass to `WasClient` (built
|
|
767
782
|
from the opt-in `@interop/was-client/edv` subpath, so plaintext consumers
|
|
768
783
|
never pull the crypto graph). It is a pure **keystore**: `resolveKeys` returns
|
|
@@ -794,21 +809,23 @@ const { id } = await vault.add({ secret: 'hello' }) // encrypted; id is an EDV i
|
|
|
794
809
|
const back = await vault.get(id) // { secret: 'hello' } -- decrypted
|
|
795
810
|
|
|
796
811
|
// 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
|
|
812
|
+
// own keys -- no override needed; one cached read of the Collection Metadata
|
|
813
|
+
// object:
|
|
798
814
|
const same = was.space(spaceId).collection('vault')
|
|
799
815
|
await same.get(id) // reads the descriptor, then decrypts
|
|
800
816
|
```
|
|
801
817
|
|
|
802
818
|
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
|
-
|
|
819
|
+
Collection Metadata object declares `encryption` (resolution reads that object
|
|
820
|
+
once, then caches -- no round-trip for plaintext-only clients or when an
|
|
821
|
+
override is set). Keys are then **required**: if the collection is declared
|
|
822
|
+
encrypted but your keystore returns no keys, reads/writes throw
|
|
823
|
+
`EncryptionError` (fail-closed) instead of silently falling back to plaintext.
|
|
808
824
|
|
|
809
825
|
**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
|
|
826
|
+
to force the decision and skip the Collection Metadata read --
|
|
827
|
+
`{ scheme: 'edv' }` (keys from the keystore), `{ scheme: 'edv', keys }` (keys
|
|
828
|
+
inline), or `'plaintext'`:
|
|
812
829
|
|
|
813
830
|
```ts
|
|
814
831
|
const vault = was.space(spaceId).collection('vault', {
|
|
@@ -820,8 +837,12 @@ const vault = was.space(spaceId).collection('vault', {
|
|
|
820
837
|
keys-only): re-declare it once with
|
|
821
838
|
`collection.configure({ encryption: { scheme: 'edv' } })` (the descriptor is
|
|
822
839
|
set-once: declaring it on a collection that lacks one is allowed, changing or
|
|
823
|
-
clearing an existing one is rejected).
|
|
824
|
-
|
|
840
|
+
clearing an existing one is rejected). The same write re-seals the stored
|
|
841
|
+
`custom` under the newly declared descriptor, since the server validates the
|
|
842
|
+
envelope against the incoming one; a client that cannot build that codec (no
|
|
843
|
+
keystore, or no keys for the descriptor) sends no `custom` at all rather than
|
|
844
|
+
plaintext beside the descriptor, which loses the stored name and tags. Until the
|
|
845
|
+
collection is re-declared, a per-handle override reads it correctly.
|
|
825
846
|
|
|
826
847
|
Encrypted collections are a **stricter contract**, not a drop-in (documents-only
|
|
827
848
|
scope for now):
|
|
@@ -842,9 +863,9 @@ scope for now):
|
|
|
842
863
|
endpoint has its own ETag (`metaVersion`), independent of the content ETag.
|
|
843
864
|
The same pair at Collection level (`collection.setName()` / `setTags()` /
|
|
844
865
|
`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.
|
|
866
|
+
and tags, since the Collection Metadata object's plaintext `name` is left
|
|
867
|
+
unpopulated; the collection-level envelope binds no resource id, and a
|
|
868
|
+
resource-bound envelope served into that slot is refused.
|
|
848
869
|
- **Binary.** A `Blob`/`Uint8Array` up to `maxBlobBytes` (512 KiB by default) is
|
|
849
870
|
encrypted as a single document. A larger one is routed automatically by
|
|
850
871
|
`add()` to the chunked-stream path: one document plus its chunk resources,
|
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.
|
package/dist/Collection.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Collection.d.ts","sourceRoot":"","sources":["../src/Collection.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"Collection.d.ts","sourceRoot":"","sources":["../src/Collection.ts"],"names":[],"mappings":"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"}
|