@interop/was-client 0.100.0 → 0.101.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 +123 -53
- package/dist/Collection.d.ts +21 -9
- package/dist/Collection.d.ts.map +1 -1
- package/dist/Collection.js +39 -37
- package/dist/Collection.js.map +1 -1
- package/dist/Resource.d.ts +0 -4
- package/dist/Resource.d.ts.map +1 -1
- package/dist/Resource.js +1 -6
- package/dist/Resource.js.map +1 -1
- package/dist/Space.d.ts +97 -24
- package/dist/Space.d.ts.map +1 -1
- package/dist/Space.js +168 -84
- package/dist/Space.js.map +1 -1
- package/dist/WasClient.d.ts +135 -55
- package/dist/WasClient.d.ts.map +1 -1
- package/dist/WasClient.js +220 -100
- package/dist/WasClient.js.map +1 -1
- package/dist/edv/EdvCodec.d.ts.map +1 -1
- package/dist/edv/EdvCodec.js +7 -4
- package/dist/edv/EdvCodec.js.map +1 -1
- package/dist/edv/docCipher.js +2 -2
- package/dist/edv/docCipher.js.map +1 -1
- package/dist/index.d.ts +0 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js.map +1 -1
- package/dist/internal/codec.d.ts +11 -1
- package/dist/internal/codec.d.ts.map +1 -1
- package/dist/internal/codec.js +11 -1
- package/dist/internal/codec.js.map +1 -1
- package/dist/internal/describe.d.ts +6 -0
- package/dist/internal/describe.d.ts.map +1 -1
- package/dist/internal/describe.js +18 -1
- package/dist/internal/describe.js.map +1 -1
- package/dist/internal/grant.js +1 -1
- package/dist/internal/grant.js.map +1 -1
- package/dist/internal/indexSchema.d.ts +19 -4
- package/dist/internal/indexSchema.d.ts.map +1 -1
- package/dist/internal/indexSchema.js +33 -6
- package/dist/internal/indexSchema.js.map +1 -1
- package/dist/internal/paths.d.ts +113 -18
- package/dist/internal/paths.d.ts.map +1 -1
- package/dist/internal/paths.js +224 -40
- package/dist/internal/paths.js.map +1 -1
- package/dist/internal/request.d.ts +47 -14
- package/dist/internal/request.d.ts.map +1 -1
- package/dist/internal/request.js +64 -44
- package/dist/internal/request.js.map +1 -1
- package/dist/internal/reserved.d.ts +15 -3
- package/dist/internal/reserved.d.ts.map +1 -1
- package/dist/internal/reserved.js +37 -8
- package/dist/internal/reserved.js.map +1 -1
- package/dist/internal/revoke.d.ts +1 -65
- package/dist/internal/revoke.d.ts.map +1 -1
- package/dist/internal/revoke.js +5 -136
- package/dist/internal/revoke.js.map +1 -1
- package/dist/internal/rootCapability.d.ts +66 -0
- package/dist/internal/rootCapability.d.ts.map +1 -0
- package/dist/internal/rootCapability.js +144 -0
- package/dist/internal/rootCapability.js.map +1 -0
- package/dist/internal/service.d.ts +5 -3
- package/dist/internal/service.d.ts.map +1 -1
- package/dist/internal/service.js +5 -3
- package/dist/internal/service.js.map +1 -1
- package/dist/internal/withCodec.d.ts +19 -4
- package/dist/internal/withCodec.d.ts.map +1 -1
- package/dist/internal/withCodec.js +18 -2
- package/dist/internal/withCodec.js.map +1 -1
- package/dist/internal/write.js +13 -9
- package/dist/internal/write.js.map +1 -1
- package/dist/paths.d.ts +5 -3
- package/dist/paths.d.ts.map +1 -1
- package/dist/paths.js +5 -3
- package/dist/paths.js.map +1 -1
- package/dist/sync/provisioning.js +1 -1
- package/dist/sync/provisioning.js.map +1 -1
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -48,16 +48,16 @@ through cheap, lazy navigational handles modeled on a document store's DX
|
|
|
48
48
|
(`add`/`get`/`put`/`list`/`delete`) rather than `insertOne`/`findOne` (PWS has
|
|
49
49
|
no query-by-filter yet).
|
|
50
50
|
|
|
51
|
-
| Document db driver | PWS client
|
|
52
|
-
| ----------------------------------- |
|
|
53
|
-
| `new Client(url)` | `new WasClient({
|
|
54
|
-
| `client.db('app')` | `was.spaceAt(spaceUrl)`
|
|
55
|
-
| `db.collection('users')` | `space.collection(collectionId)`
|
|
56
|
-
| `collection.insertOne(doc)` | `collection.add(doc)`
|
|
57
|
-
| `collection.findOne({ _id })` | `collection.get(resourceId)`
|
|
58
|
-
| `collection.replaceOne({ _id }, d)` | `collection.put(resourceId, data)`
|
|
59
|
-
| `collection.find().toArray()` | `collection.list()`
|
|
60
|
-
| `collection.deleteOne({ _id })` | `collection.resource(resourceId).delete()`
|
|
51
|
+
| Document db driver | PWS client |
|
|
52
|
+
| ----------------------------------- | ---------------------------------------------- |
|
|
53
|
+
| `new Client(url)` | `new WasClient({ baseServerUrl, zcapClient })` |
|
|
54
|
+
| `client.db('app')` | `was.spaceAt(spaceUrl)` |
|
|
55
|
+
| `db.collection('users')` | `space.collection(collectionId)` |
|
|
56
|
+
| `collection.insertOne(doc)` | `collection.add(doc)` |
|
|
57
|
+
| `collection.findOne({ _id })` | `collection.get(resourceId)` |
|
|
58
|
+
| `collection.replaceOne({ _id }, d)` | `collection.put(resourceId, data)` |
|
|
59
|
+
| `collection.find().toArray()` | `collection.list()` |
|
|
60
|
+
| `collection.deleteOne({ _id })` | `collection.resource(resourceId).delete()` |
|
|
61
61
|
|
|
62
62
|
See also:
|
|
63
63
|
|
|
@@ -110,7 +110,7 @@ const zcapClient = new ZcapClient({
|
|
|
110
110
|
|
|
111
111
|
// 3. Wrap it.
|
|
112
112
|
const was = new WasClient({
|
|
113
|
-
|
|
113
|
+
baseServerUrl: 'https://was.example/',
|
|
114
114
|
zcapClient
|
|
115
115
|
})
|
|
116
116
|
```
|
|
@@ -130,7 +130,7 @@ keyPair.controller = `did:key:${keyPair.fingerprint()}`
|
|
|
130
130
|
keyPair.id = `${keyPair.controller}#${keyPair.fingerprint()}`
|
|
131
131
|
|
|
132
132
|
const was = WasClient.fromSigner({
|
|
133
|
-
|
|
133
|
+
baseServerUrl: 'https://was.example/',
|
|
134
134
|
signer: keyPair.signer()
|
|
135
135
|
})
|
|
136
136
|
```
|
|
@@ -139,20 +139,56 @@ The `seed` is where a passphrase-, stored-secret-, or KMS-derived key plugs in:
|
|
|
139
139
|
deriving the same 32-byte seed yields the same DID, and therefore access to the
|
|
140
140
|
same spaces.
|
|
141
141
|
|
|
142
|
-
A client is built from
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
`ValidationError`. The
|
|
148
|
-
|
|
149
|
-
`
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
142
|
+
A client is built from the server's base server URL. The spec's Server Layout
|
|
143
|
+
places the server-level endpoints under it: `{base}service` is the service
|
|
144
|
+
description, `{base}spaces/` is the Spaces Repository, and
|
|
145
|
+
`{base}space/{space_id}/` is a Space. The base must be an absolute `http(s)` URL
|
|
146
|
+
with no query or fragment. A missing trailing slash is supplied. Anything else
|
|
147
|
+
is refused with `ValidationError`. The result is exposed as `was.baseServerUrl`.
|
|
148
|
+
|
|
149
|
+
`was.space(spaceId)` opens a handle on `{base}space/{spaceId}/`, with no I/O.
|
|
150
|
+
`was.spaceAt(spaceUrl)` opens a handle on a Space URL you hold, such as the
|
|
151
|
+
`Location` of a create response, a listing item's `url`, a linkset entry, a
|
|
152
|
+
stored pointer, a capability's target, or a self-locating DID. A received Space
|
|
153
|
+
URL is used as given. It is never checked against the base, because a grant or a
|
|
154
|
+
DID may name a Space on another server.
|
|
155
|
+
|
|
156
|
+
The `./paths` entry exports the matching builders: `canonicalBaseServerUrl`,
|
|
157
|
+
`serviceDescriptionUrl({ baseServerUrl })`,
|
|
158
|
+
`spacesRepositoryUrl({ baseServerUrl })` and
|
|
159
|
+
`spaceUrlById({ baseServerUrl, spaceId })`. `baseServerUrlOf(url)` is their
|
|
160
|
+
inverse. It accepts the service, Spaces Repository or Space URL and rejects
|
|
161
|
+
anything else. `splitCollectionUrl(url)` splits a Collection URL into its Space
|
|
162
|
+
URL and id.
|
|
163
|
+
|
|
164
|
+
#### Single fixed Space
|
|
165
|
+
|
|
166
|
+
Some servers are one Space with no id and no base server URL, such as a personal
|
|
167
|
+
site whose Collections are `https://alice.example/posts/` and
|
|
168
|
+
`https://alice.example/images/`. Open one with `WasClient.forSpace()` and reach
|
|
169
|
+
a Collection directly with `collectionAt()`:
|
|
170
|
+
|
|
171
|
+
```ts
|
|
172
|
+
const was = WasClient.forSpace({
|
|
173
|
+
spaceUrl: 'https://alice.example/',
|
|
174
|
+
signer: keyPair.didKeySigner()
|
|
175
|
+
})
|
|
176
|
+
|
|
177
|
+
const posts = was.collectionAt('https://alice.example/posts/')
|
|
178
|
+
const post = await posts.get('hello')
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
`forSpace` takes
|
|
182
|
+
`{ spaceUrl, zcapClient | signer, encryption?, serviceDescription? }`. The
|
|
183
|
+
client exposes `was.spaceUrl`, and `was.baseServerUrl` is `undefined`. It finds
|
|
184
|
+
the service description with an unsigned `HEAD` of the Space URL and the
|
|
185
|
+
`Link: <...>; rel="service"` header. `space(id)`, `createSpace()` and
|
|
186
|
+
`listSpaces()` throw `NotSupportedError`.
|
|
187
|
+
|
|
188
|
+
`collectionAt(collectionUrl)` works on any client. The parent of the URL is the
|
|
189
|
+
Space URL and the last segment is the Collection id. A Space URL may be an
|
|
190
|
+
origin root, in which case `Space.id` is `undefined`. At an origin-root Space
|
|
191
|
+
the Collection ids `service`, `spaces` and `space` are reserved.
|
|
156
192
|
|
|
157
193
|
#### Deriving a client from a secret or seed (`./identity`)
|
|
158
194
|
|
|
@@ -167,7 +203,7 @@ import { WasClient } from '@interop/was-client'
|
|
|
167
203
|
const agents = await agentsFromSecret({ secret: 'the account secret' })
|
|
168
204
|
|
|
169
205
|
const was = WasClient.fromSigner({
|
|
170
|
-
|
|
206
|
+
baseServerUrl: 'https://was.example/',
|
|
171
207
|
signer: agents.keyAgent.getSigner()
|
|
172
208
|
})
|
|
173
209
|
```
|
|
@@ -208,17 +244,19 @@ own pace produces.
|
|
|
208
244
|
### Service discovery
|
|
209
245
|
|
|
210
246
|
Before its first signed request, the client reads the server's service
|
|
211
|
-
description. A client built from `
|
|
212
|
-
unsigned `GET`. A client built
|
|
213
|
-
|
|
214
|
-
whatever its status, then reads the linked document with an unsigned
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
247
|
+
description. A client built from `baseServerUrl` reads `{base}service` with an
|
|
248
|
+
unsigned `GET`. A client built by `forSpace` first sends an unsigned `HEAD` to
|
|
249
|
+
the Space URL and follows the `Link: <...>; rel="service"` header on the
|
|
250
|
+
response, whatever its status, then reads the linked document with an unsigned
|
|
251
|
+
`GET`. The spec fixes no path for the document on a fixed-Space server, so that
|
|
252
|
+
client assumes none.
|
|
253
|
+
|
|
254
|
+
A client with a base checks that the document describes that base. Its `url`
|
|
255
|
+
must equal `{base}service`, and its `spaces` member, when present, must equal
|
|
256
|
+
`{base}spaces/`. Otherwise every signed request rejects with
|
|
257
|
+
`IncompatibleServerError` naming both values. The check also applies to a
|
|
258
|
+
caller-supplied `serviceDescription`. A client built by `forSpace` has no base
|
|
259
|
+
and is not checked. `createSpace()` and `listSpaces()` address `{base}spaces/`.
|
|
222
260
|
|
|
223
261
|
From the document the client selects the PWS specification version it speaks.
|
|
224
262
|
This client speaks only v0.5. It ignores `specs` keys other than
|
|
@@ -294,13 +332,12 @@ server requires a `name`; `controller` defaults to the client's own DID, and the
|
|
|
294
332
|
server generates the id unless you pass one. `createSpace()` reads the
|
|
295
333
|
`Location` header of the response and returns a handle on that Space URL. A
|
|
296
334
|
response without `Location` throws `WasServerError`. `createSpace()` and
|
|
297
|
-
`listSpaces()` address the Spaces Repository
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
anything.
|
|
335
|
+
`listSpaces()` address the Spaces Repository, `{base}spaces/` (see
|
|
336
|
+
[Service discovery](#service-discovery)). Against a server whose description has
|
|
337
|
+
no `spaces` URL, they throw `NotSupportedError` without sending anything.
|
|
301
338
|
|
|
302
339
|
```ts
|
|
303
|
-
const space = await was.createSpace({ name: 'Home' }) // POST {
|
|
340
|
+
const space = await was.createSpace({ name: 'Home' }) // POST {base}spaces/
|
|
304
341
|
// A typed Space, e.g. an auxiliary one; `type` defaults to ['Space'].
|
|
305
342
|
const aux = await was.createSpace({
|
|
306
343
|
name: 'Clients',
|
|
@@ -309,6 +346,7 @@ const aux = await was.createSpace({
|
|
|
309
346
|
|
|
310
347
|
// Lazy handle to an existing space by its URL -- no I/O until a verb runs.
|
|
311
348
|
const same = was.spaceAt(space.url)
|
|
349
|
+
const byId = was.space('abc') // {base}space/abc/, no I/O
|
|
312
350
|
|
|
313
351
|
// Read the Space Metadata object (null if missing or not visible to you).
|
|
314
352
|
const desc = await space.describe() // { id, type: ['Space'], name, controller } | null
|
|
@@ -953,18 +991,40 @@ const listing = await space.replicas() // { url, totalItems, items } | null
|
|
|
953
991
|
const one = await space.replica('peer-1') // ReplicaRegistration | null
|
|
954
992
|
|
|
955
993
|
const status = await space.replicaStatus('peer-1') // ReplicaStatus | null
|
|
956
|
-
// status.state: 'idle' | 'pulling' | 'backing-off' | 'stalled'
|
|
994
|
+
// status.state: 'idle' | 'pulling' | 'paused' | 'backing-off' | 'stalled'
|
|
957
995
|
for (const entry of status?.collections ?? []) {
|
|
958
996
|
if (entry.state === 'stalled') {
|
|
959
997
|
// entry.stall: { reason, since, detail? }
|
|
960
998
|
}
|
|
961
999
|
}
|
|
962
1000
|
|
|
1001
|
+
// Stop and restart the pull loop. The registration keeps its id and its
|
|
1002
|
+
// checkpoint, and the data already pulled stays.
|
|
1003
|
+
await space.pauseReplica('peer-1') // status.state reads 'paused'
|
|
1004
|
+
await space.resumeReplica('peer-1')
|
|
1005
|
+
|
|
1006
|
+
// Swap the pull capability in place, before the signer that delegated the
|
|
1007
|
+
// old one is withdrawn. Same id, same checkpoint.
|
|
1008
|
+
await space.replaceReplicaCapability('peer-1', renewedCapability)
|
|
1009
|
+
|
|
963
1010
|
// The data already pulled under the registration stays. 'not-found' is the
|
|
964
|
-
// server's masked 404: absent, or the caller
|
|
1011
|
+
// server's masked 404: absent, or the caller holds no authority on the route.
|
|
965
1012
|
const { outcome } = await space.deregisterReplica('peer-1') // 'deleted' | 'not-found'
|
|
966
1013
|
```
|
|
967
1014
|
|
|
1015
|
+
`pauseReplica()`, `resumeReplica()` and `replaceReplicaCapability()` are each
|
|
1016
|
+
one `PUT` of the registration at its URL, run as a compare-and-swap: the client
|
|
1017
|
+
reads the stored record with its `ETag`, writes it back under `If-Match`, and
|
|
1018
|
+
re-reads and re-applies the change on a 412, so a write that lands in between is
|
|
1019
|
+
rebased on rather than clobbered. `PreconditionFailedError` (412) surfaces only
|
|
1020
|
+
after the attempt limit. A pause sets `pausedAt`, which the server fills with
|
|
1021
|
+
its own time and keeps while the registration stays paused, and a resume omits
|
|
1022
|
+
it. Only `capability` and `pausedAt` may change on a stored registration. All
|
|
1023
|
+
three throw `NotFoundError` on the masked 404, since a `PUT` at an absent
|
|
1024
|
+
registration would register a new replica. A replacement capability is checked
|
|
1025
|
+
against the peer the way `registerReplica()` checks one, so it can throw the
|
|
1026
|
+
same `ConflictError` (`replica-refused`) and `ValidationError`.
|
|
1027
|
+
|
|
968
1028
|
`replicaStatus()` reports the pull loop's runtime state. The response is
|
|
969
1029
|
uncacheable and is not part of the registration record. Each Collection entry
|
|
970
1030
|
has its own `state` (`synced`, `syncing`, `stalled`, or `skipped`). A stalled
|
|
@@ -980,13 +1040,23 @@ Collections continue. Its `stall.reason` is one of:
|
|
|
980
1040
|
registration for.
|
|
981
1041
|
- `container-refused` -- the local Space or Collection refused the write.
|
|
982
1042
|
|
|
983
|
-
Every replica method
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
1043
|
+
Every replica method takes the controller's capability or a delegated one whose
|
|
1044
|
+
chain roots in the Space's root capability, for `GET`, `POST`, `PUT` and
|
|
1045
|
+
`DELETE` alike. A delegated `POST` may only name a `fromSpace` the Space already
|
|
1046
|
+
has a registration for. Any other caller gets the masked 404, as does every
|
|
1047
|
+
delegated caller on a server that still treats the route as controller-only.
|
|
1048
|
+
`replicas()`, `replica()`, and `replicaStatus()` then return `null`, and
|
|
1049
|
+
`registerReplica()` throws `NotFoundError`. `deregisterReplica()` reports it as
|
|
1050
|
+
`{ outcome: 'not-found' }` rather than swallowing it, so such a caller can tell
|
|
1051
|
+
that nothing was removed. The three reads reserve `null` for that 404. A 2xx
|
|
1052
|
+
with no JSON body is a server fault and throws `WasServerError`.
|
|
1053
|
+
|
|
1054
|
+
A grant on the route targets `{spaceUrl}replicas`, the bare URL with no trailing
|
|
1055
|
+
slash. `spaceReplicasUrl({ spaceUrl })` on the `./paths` entry forms it, and
|
|
1056
|
+
`replicaRegistrationUrl({ spaceUrl, replicaId })` forms one registration beneath
|
|
1057
|
+
it. `fromCapability` refuses that target as a reserved sub-resource, so the
|
|
1058
|
+
holder opens the Space handle with `was.space(id, { capability })` (or
|
|
1059
|
+
`was.spaceAt(url, { capability })`) and calls the replica methods on it.
|
|
990
1060
|
|
|
991
1061
|
`registerReplica()` throws `ValidationError` (400 `invalid-request-body`) for a
|
|
992
1062
|
malformed body. Each entry of the error's `problems` carries a `pointer` naming
|
|
@@ -1063,7 +1133,7 @@ const encryption = createEdvEncryption({
|
|
|
1063
1133
|
return { keyAgreementKey, keyResolver }
|
|
1064
1134
|
}
|
|
1065
1135
|
})
|
|
1066
|
-
const was = WasClient.fromSigner({
|
|
1136
|
+
const was = WasClient.fromSigner({ baseServerUrl, signer, encryption })
|
|
1067
1137
|
|
|
1068
1138
|
// Declare the collection encrypted (writes the descriptor). The returned handle is
|
|
1069
1139
|
// pre-seeded, so the first write encrypts with no extra round-trip.
|
package/dist/Collection.d.ts
CHANGED
|
@@ -10,10 +10,6 @@ export declare class Collection {
|
|
|
10
10
|
* The URL of the Space this Collection lives in, in canonical form.
|
|
11
11
|
*/
|
|
12
12
|
readonly spaceUrl: string;
|
|
13
|
-
/**
|
|
14
|
-
* The id of that Space: the last path segment of {@link spaceUrl}.
|
|
15
|
-
*/
|
|
16
|
-
readonly spaceId: string;
|
|
17
13
|
readonly id: string;
|
|
18
14
|
/**
|
|
19
15
|
* The Collection container in canonical (trailing-slash) form: the URL whose
|
|
@@ -194,6 +190,10 @@ export declare class Collection {
|
|
|
194
190
|
* Deletes the whole collection. Idempotent. To delete a single resource, use
|
|
195
191
|
* `collection.resource(id).delete()`.
|
|
196
192
|
*
|
|
193
|
+
* Drops the memoized codec as well: a collection re-created at this URL
|
|
194
|
+
* under another encryption scheme must not be written through the codec
|
|
195
|
+
* the deleted one resolved (see `CodecHolder.reset`).
|
|
196
|
+
*
|
|
197
197
|
* @returns {Promise<void>}
|
|
198
198
|
*/
|
|
199
199
|
delete(): Promise<void>;
|
|
@@ -385,6 +385,10 @@ export declare class Collection {
|
|
|
385
385
|
* attribute was declared carry no token for it, so they do not match a search
|
|
386
386
|
* on it until they are rewritten.
|
|
387
387
|
*
|
|
388
|
+
* The result is a copy: mutating it (a compound entry's `attribute` array
|
|
389
|
+
* included) does not touch the schema installed on this handle's codec.
|
|
390
|
+
* Declare through {@link declareIndex}.
|
|
391
|
+
*
|
|
388
392
|
* @returns {Promise<IndexDeclaration[]>}
|
|
389
393
|
*/
|
|
390
394
|
indexes(): Promise<IndexDeclaration[]>;
|
|
@@ -414,7 +418,8 @@ export declare class Collection {
|
|
|
414
418
|
* for a compound index
|
|
415
419
|
* @param [options.unique] {boolean} reject a second document that carries
|
|
416
420
|
* the same value (the server answers a colliding write with `409`)
|
|
417
|
-
* @returns {Promise<IndexSchema>} the schema now in force
|
|
421
|
+
* @returns {Promise<IndexSchema>} the schema now in force, as a copy (see
|
|
422
|
+
* {@link indexes})
|
|
418
423
|
*/
|
|
419
424
|
declareIndex({ attribute, unique }: {
|
|
420
425
|
attribute: string | string[];
|
|
@@ -437,7 +442,8 @@ export declare class Collection {
|
|
|
437
442
|
* @param options {object}
|
|
438
443
|
* @param options.indexes {Array<{ attribute: string | string[]; unique?: boolean }>}
|
|
439
444
|
* the attributes to declare, in `declareIndex`'s own terms
|
|
440
|
-
* @returns {Promise<IndexSchema>} the schema now in force
|
|
445
|
+
* @returns {Promise<IndexSchema>} the schema now in force, as a copy (see
|
|
446
|
+
* {@link indexes})
|
|
441
447
|
*/
|
|
442
448
|
declareIndexes({ indexes }: {
|
|
443
449
|
indexes: Array<{
|
|
@@ -676,20 +682,25 @@ export declare class Collection {
|
|
|
676
682
|
* checkpoint); a short page is not the end (see `changes()`). Checkpoints
|
|
677
683
|
* are opaque strings compared by equality only. A server that hands back a
|
|
678
684
|
* checkpoint this walk already resumed from (the starting one included)
|
|
679
|
-
* fails the walk with a `WasServerError` rather than looping
|
|
680
|
-
*
|
|
685
|
+
* fails the walk with a `WasServerError` rather than looping, as does one
|
|
686
|
+
* that keeps advancing the checkpoint without ending the feed within
|
|
687
|
+
* `maxPages` pages. Every fault `changes()` rejects on fails the walk the
|
|
688
|
+
* same way.
|
|
681
689
|
*
|
|
682
690
|
* @param [options] {object}
|
|
683
691
|
* @param [options.checkpoint] {ChangesCheckpoint} resume strictly after it
|
|
684
692
|
* @param [options.limit] {number} max entries per request, every kind counted
|
|
685
693
|
* @param [options.method] {'POST' | 'GET'} the request form of each page,
|
|
686
694
|
* as for {@link changes}
|
|
695
|
+
* @param [options.maxPages] {number} the most pages one walk reads before
|
|
696
|
+
* it fails (default 10,000, the same bound as a listing walk)
|
|
687
697
|
* @returns {AsyncGenerator<{ documents: ResourceChangeDocument[], checkpoint: ChangesCheckpoint | null }>}
|
|
688
698
|
*/
|
|
689
699
|
resourceChanges(options?: {
|
|
690
700
|
checkpoint?: ChangesCheckpoint;
|
|
691
701
|
limit?: number;
|
|
692
702
|
method?: 'POST' | 'GET';
|
|
703
|
+
maxPages?: number;
|
|
693
704
|
}): AsyncGenerator<{
|
|
694
705
|
documents: ResourceChangeDocument[];
|
|
695
706
|
checkpoint: ChangesCheckpoint | null;
|
|
@@ -721,7 +732,8 @@ export declare class Collection {
|
|
|
721
732
|
* `changes()` rejects on (a bodiless 2xx, a live JSON entry with no `data`,
|
|
722
733
|
* or a `501` from a server that does not advertise the `changes-query`
|
|
723
734
|
* feature) fail the walk with the same `WasServerError`, as does a server
|
|
724
|
-
* that repeats a checkpoint instead of advancing
|
|
735
|
+
* that repeats a checkpoint instead of advancing, or one that keeps advancing
|
|
736
|
+
* it without ever ending the feed (the page bound of `resourceChanges()`).
|
|
725
737
|
*
|
|
726
738
|
* @param [options] {object}
|
|
727
739
|
* @param [options.limit] {number} max documents per request (default 1000, the teaching server's maximum); the server reduces it to its own maximum
|
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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"}
|
package/dist/Collection.js
CHANGED
|
@@ -15,13 +15,13 @@
|
|
|
15
15
|
* Collection URL is the container: it lists and adds Resources, and `delete()`
|
|
16
16
|
* removes the Collection.
|
|
17
17
|
*/
|
|
18
|
-
import { canonicalSpaceUrl,
|
|
18
|
+
import { canonicalSpaceUrl, collectionUrl, collectionPolicyUrl, collectionLinksetUrl, collectionBackendUrl, collectionQuotaUrl, collectionQueryUrl, collectionMetaUrl, collectionLogUrl, resourceUrl } from './internal/paths.js';
|
|
19
19
|
import { assertNotReserved } from './internal/reserved.js';
|
|
20
20
|
import { NotFoundError, ValidationError, WasServerError, httpStatus } from './errors.js';
|
|
21
21
|
import { allowsAction, delegateGrantAt } from './internal/grant.js';
|
|
22
22
|
import { send, readData } from './internal/request.js';
|
|
23
23
|
import { codecForDescriptor, collectionCodecHolder, identityCodec, openStoredCustom } from './internal/codec.js';
|
|
24
|
-
import { collectWalk, signedPageWalk, walkItems, walkPagesOrEmpty } from './internal/pagination.js';
|
|
24
|
+
import { DEFAULT_MAX_PAGES, collectWalk, signedPageWalk, walkItems, walkPagesOrEmpty } from './internal/pagination.js';
|
|
25
25
|
import { ANNOTATION_MEMBERS, CONFIGURATION_MEMBERS, asCollectionMetadata, carriedForward, collectionWritableFields, isGovernedDescriptor, storedEncryption, unreadableDescriptionError } from './internal/describe.js';
|
|
26
26
|
import { assertSinglePrecondition, readEtag, writeHeaders } from './internal/conditional.js';
|
|
27
27
|
import { compareAndSwap, composeAndSwap } from './internal/cas.js';
|
|
@@ -29,7 +29,7 @@ import { readCollectionMetadata, readMeta, patchCustom } from './internal/meta.j
|
|
|
29
29
|
import { codecRequestContext, insertResource } from './internal/write.js';
|
|
30
30
|
import { readPolicy, readPolicyWithEtag, writePolicy, deletePolicy, isPublicPolicy, setPublicPolicy } from './internal/policy.js';
|
|
31
31
|
import { ENCODER, LOG_CONTENT_TYPE, createdResource, dataOrNull, storedResponse } from './internal/content.js';
|
|
32
|
-
import { INDEX_SCHEMA_PROPERTY, mergeIndexDeclarations, normalizeIndexRequests, readIndexSchema, withStoredIndexSchema } from './internal/indexSchema.js';
|
|
32
|
+
import { INDEX_SCHEMA_PROPERTY, cloneIndexSchema, mergeIndexDeclarations, normalizeIndexRequests, readIndexSchema, withStoredIndexSchema } from './internal/indexSchema.js';
|
|
33
33
|
import { Resource } from './Resource.js';
|
|
34
34
|
import { isJsonResourceChange, isResourceChange } from '@interop/storage-core';
|
|
35
35
|
/**
|
|
@@ -113,10 +113,6 @@ export class Collection {
|
|
|
113
113
|
* The URL of the Space this Collection lives in, in canonical form.
|
|
114
114
|
*/
|
|
115
115
|
spaceUrl;
|
|
116
|
-
/**
|
|
117
|
-
* The id of that Space: the last path segment of {@link spaceUrl}.
|
|
118
|
-
*/
|
|
119
|
-
spaceId;
|
|
120
116
|
id;
|
|
121
117
|
/**
|
|
122
118
|
* The Collection container in canonical (trailing-slash) form: the URL whose
|
|
@@ -158,7 +154,6 @@ export class Collection {
|
|
|
158
154
|
assertNotReserved({ id: collectionId, kind: 'collection' });
|
|
159
155
|
this.#context = context;
|
|
160
156
|
this.spaceUrl = canonicalSpaceUrl(spaceUrl);
|
|
161
|
-
this.spaceId = spaceIdOfCanonicalUrl(this.spaceUrl);
|
|
162
157
|
this.url = collectionUrl({ spaceUrl: this.spaceUrl, collectionId });
|
|
163
158
|
this.id = collectionId;
|
|
164
159
|
// Every URL this handle addresses is derived once: `spaceUrl` is
|
|
@@ -563,7 +558,9 @@ export class Collection {
|
|
|
563
558
|
},
|
|
564
559
|
operation: 'Collection configuration'
|
|
565
560
|
});
|
|
566
|
-
|
|
561
|
+
// Any successful configuration write drops the memoized codec (see
|
|
562
|
+
// `CodecHolder.reset`).
|
|
563
|
+
this.#codecHolder.reset();
|
|
567
564
|
return {
|
|
568
565
|
description: metadata ?? {
|
|
569
566
|
id: this.id,
|
|
@@ -649,7 +646,7 @@ export class Collection {
|
|
|
649
646
|
ifMatch: options.ifMatch,
|
|
650
647
|
ifNoneMatch: options.ifNoneMatch
|
|
651
648
|
});
|
|
652
|
-
this.#
|
|
649
|
+
this.#codecHolder.reset();
|
|
653
650
|
return {
|
|
654
651
|
description: metadata ?? {
|
|
655
652
|
id: this.id,
|
|
@@ -741,29 +738,14 @@ export class Collection {
|
|
|
741
738
|
return {};
|
|
742
739
|
}
|
|
743
740
|
}
|
|
744
|
-
/**
|
|
745
|
-
* Drops any memoized codec when a write declared an `encryption` descriptor.
|
|
746
|
-
* Writing it can rotate the key epoch (the recipient operations CAS this
|
|
747
|
-
* member) or flip the collection from plaintext to encrypted, and a codec
|
|
748
|
-
* memoized from the prior descriptor would keep encrypting under the stale
|
|
749
|
-
* epoch -- whose key a just-removed reader still holds -- or write
|
|
750
|
-
* server-visible plaintext into a now-encrypted collection. Child resource
|
|
751
|
-
* handles share this codec via their thunk, so resetting here propagates to
|
|
752
|
-
* them too.
|
|
753
|
-
*
|
|
754
|
-
* @param [encryption] {CollectionEncryption} the descriptor the write
|
|
755
|
-
* declared, if any
|
|
756
|
-
* @returns {void}
|
|
757
|
-
*/
|
|
758
|
-
#resetCodecIfDeclared(encryption) {
|
|
759
|
-
if (encryption !== undefined) {
|
|
760
|
-
this.#codecHolder.reset();
|
|
761
|
-
}
|
|
762
|
-
}
|
|
763
741
|
/**
|
|
764
742
|
* Deletes the whole collection. Idempotent. To delete a single resource, use
|
|
765
743
|
* `collection.resource(id).delete()`.
|
|
766
744
|
*
|
|
745
|
+
* Drops the memoized codec as well: a collection re-created at this URL
|
|
746
|
+
* under another encryption scheme must not be written through the codec
|
|
747
|
+
* the deleted one resolved (see `CodecHolder.reset`).
|
|
748
|
+
*
|
|
767
749
|
* @returns {Promise<void>}
|
|
768
750
|
*/
|
|
769
751
|
async delete() {
|
|
@@ -773,6 +755,7 @@ export class Collection {
|
|
|
773
755
|
capability: this.#capability,
|
|
774
756
|
idempotent: true
|
|
775
757
|
});
|
|
758
|
+
this.#codecHolder.reset();
|
|
776
759
|
}
|
|
777
760
|
/**
|
|
778
761
|
* Reads the Collection Metadata object with its user-writable `custom`
|
|
@@ -1122,11 +1105,15 @@ export class Collection {
|
|
|
1122
1105
|
* attribute was declared carry no token for it, so they do not match a search
|
|
1123
1106
|
* on it until they are rewritten.
|
|
1124
1107
|
*
|
|
1108
|
+
* The result is a copy: mutating it (a compound entry's `attribute` array
|
|
1109
|
+
* included) does not touch the schema installed on this handle's codec.
|
|
1110
|
+
* Declare through {@link declareIndex}.
|
|
1111
|
+
*
|
|
1125
1112
|
* @returns {Promise<IndexDeclaration[]>}
|
|
1126
1113
|
*/
|
|
1127
1114
|
async indexes() {
|
|
1128
1115
|
const indexing = await this.#indexing('read the index schema');
|
|
1129
|
-
return indexing.schema().indexes;
|
|
1116
|
+
return cloneIndexSchema(indexing.schema()).indexes;
|
|
1130
1117
|
}
|
|
1131
1118
|
/**
|
|
1132
1119
|
* Declares an attribute searchable, persisting it in the Collection's
|
|
@@ -1154,7 +1141,8 @@ export class Collection {
|
|
|
1154
1141
|
* for a compound index
|
|
1155
1142
|
* @param [options.unique] {boolean} reject a second document that carries
|
|
1156
1143
|
* the same value (the server answers a colliding write with `409`)
|
|
1157
|
-
* @returns {Promise<IndexSchema>} the schema now in force
|
|
1144
|
+
* @returns {Promise<IndexSchema>} the schema now in force, as a copy (see
|
|
1145
|
+
* {@link indexes})
|
|
1158
1146
|
*/
|
|
1159
1147
|
async declareIndex({ attribute, unique }) {
|
|
1160
1148
|
return this.declareIndexes({ indexes: [{ attribute, unique }] });
|
|
@@ -1176,7 +1164,8 @@ export class Collection {
|
|
|
1176
1164
|
* @param options {object}
|
|
1177
1165
|
* @param options.indexes {Array<{ attribute: string | string[]; unique?: boolean }>}
|
|
1178
1166
|
* the attributes to declare, in `declareIndex`'s own terms
|
|
1179
|
-
* @returns {Promise<IndexSchema>} the schema now in force
|
|
1167
|
+
* @returns {Promise<IndexSchema>} the schema now in force, as a copy (see
|
|
1168
|
+
* {@link indexes})
|
|
1180
1169
|
*/
|
|
1181
1170
|
async declareIndexes({ indexes }) {
|
|
1182
1171
|
const { indexing, meta: snapshot } = await this.#resolveIndexing('declare indexes');
|
|
@@ -1220,7 +1209,7 @@ export class Collection {
|
|
|
1220
1209
|
});
|
|
1221
1210
|
const schema = readIndexSchema(custom);
|
|
1222
1211
|
indexing.applySchema(schema);
|
|
1223
|
-
return schema;
|
|
1212
|
+
return cloneIndexSchema(schema);
|
|
1224
1213
|
}
|
|
1225
1214
|
/**
|
|
1226
1215
|
* Searches the collection's encrypted documents by indexed attribute. The
|
|
@@ -1633,22 +1622,28 @@ export class Collection {
|
|
|
1633
1622
|
* checkpoint); a short page is not the end (see `changes()`). Checkpoints
|
|
1634
1623
|
* are opaque strings compared by equality only. A server that hands back a
|
|
1635
1624
|
* checkpoint this walk already resumed from (the starting one included)
|
|
1636
|
-
* fails the walk with a `WasServerError` rather than looping
|
|
1637
|
-
*
|
|
1625
|
+
* fails the walk with a `WasServerError` rather than looping, as does one
|
|
1626
|
+
* that keeps advancing the checkpoint without ending the feed within
|
|
1627
|
+
* `maxPages` pages. Every fault `changes()` rejects on fails the walk the
|
|
1628
|
+
* same way.
|
|
1638
1629
|
*
|
|
1639
1630
|
* @param [options] {object}
|
|
1640
1631
|
* @param [options.checkpoint] {ChangesCheckpoint} resume strictly after it
|
|
1641
1632
|
* @param [options.limit] {number} max entries per request, every kind counted
|
|
1642
1633
|
* @param [options.method] {'POST' | 'GET'} the request form of each page,
|
|
1643
1634
|
* as for {@link changes}
|
|
1635
|
+
* @param [options.maxPages] {number} the most pages one walk reads before
|
|
1636
|
+
* it fails (default 10,000, the same bound as a listing walk)
|
|
1644
1637
|
* @returns {AsyncGenerator<{ documents: ResourceChangeDocument[], checkpoint: ChangesCheckpoint | null }>}
|
|
1645
1638
|
*/
|
|
1646
1639
|
async *resourceChanges(options = {}) {
|
|
1647
|
-
const { limit, method } = options;
|
|
1640
|
+
const { limit, method, maxPages = DEFAULT_MAX_PAGES } = options;
|
|
1648
1641
|
let checkpoint = options.checkpoint;
|
|
1649
1642
|
const seen = new Set(checkpoint === undefined ? [] : [checkpoint]);
|
|
1643
|
+
let pagesRead = 0;
|
|
1650
1644
|
for (;;) {
|
|
1651
1645
|
const page = await this.changes({ checkpoint, limit, method });
|
|
1646
|
+
pagesRead += 1;
|
|
1652
1647
|
yield {
|
|
1653
1648
|
documents: page.documents.filter(isResourceChange),
|
|
1654
1649
|
checkpoint: page.checkpoint ?? null
|
|
@@ -1660,6 +1655,12 @@ export class Collection {
|
|
|
1660
1655
|
throw new WasServerError(`The changes feed of collection "${this.id}" repeated checkpoint ` +
|
|
1661
1656
|
`${JSON.stringify(page.checkpoint)} instead of advancing.`);
|
|
1662
1657
|
}
|
|
1658
|
+
// The page already says the feed continues, so the refusal costs no
|
|
1659
|
+
// further request.
|
|
1660
|
+
if (pagesRead >= maxPages) {
|
|
1661
|
+
throw new WasServerError(`The changes feed of collection "${this.id}" did not end within ` +
|
|
1662
|
+
`${maxPages} pages.`);
|
|
1663
|
+
}
|
|
1663
1664
|
seen.add(page.checkpoint);
|
|
1664
1665
|
checkpoint = page.checkpoint;
|
|
1665
1666
|
}
|
|
@@ -1691,7 +1692,8 @@ export class Collection {
|
|
|
1691
1692
|
* `changes()` rejects on (a bodiless 2xx, a live JSON entry with no `data`,
|
|
1692
1693
|
* or a `501` from a server that does not advertise the `changes-query`
|
|
1693
1694
|
* feature) fail the walk with the same `WasServerError`, as does a server
|
|
1694
|
-
* that repeats a checkpoint instead of advancing
|
|
1695
|
+
* that repeats a checkpoint instead of advancing, or one that keeps advancing
|
|
1696
|
+
* it without ever ending the feed (the page bound of `resourceChanges()`).
|
|
1695
1697
|
*
|
|
1696
1698
|
* @param [options] {object}
|
|
1697
1699
|
* @param [options.limit] {number} max documents per request (default 1000, the teaching server's maximum); the server reduces it to its own maximum
|