@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.
Files changed (76) hide show
  1. package/README.md +123 -53
  2. package/dist/Collection.d.ts +21 -9
  3. package/dist/Collection.d.ts.map +1 -1
  4. package/dist/Collection.js +39 -37
  5. package/dist/Collection.js.map +1 -1
  6. package/dist/Resource.d.ts +0 -4
  7. package/dist/Resource.d.ts.map +1 -1
  8. package/dist/Resource.js +1 -6
  9. package/dist/Resource.js.map +1 -1
  10. package/dist/Space.d.ts +97 -24
  11. package/dist/Space.d.ts.map +1 -1
  12. package/dist/Space.js +168 -84
  13. package/dist/Space.js.map +1 -1
  14. package/dist/WasClient.d.ts +135 -55
  15. package/dist/WasClient.d.ts.map +1 -1
  16. package/dist/WasClient.js +220 -100
  17. package/dist/WasClient.js.map +1 -1
  18. package/dist/edv/EdvCodec.d.ts.map +1 -1
  19. package/dist/edv/EdvCodec.js +7 -4
  20. package/dist/edv/EdvCodec.js.map +1 -1
  21. package/dist/edv/docCipher.js +2 -2
  22. package/dist/edv/docCipher.js.map +1 -1
  23. package/dist/index.d.ts +0 -1
  24. package/dist/index.d.ts.map +1 -1
  25. package/dist/index.js.map +1 -1
  26. package/dist/internal/codec.d.ts +11 -1
  27. package/dist/internal/codec.d.ts.map +1 -1
  28. package/dist/internal/codec.js +11 -1
  29. package/dist/internal/codec.js.map +1 -1
  30. package/dist/internal/describe.d.ts +6 -0
  31. package/dist/internal/describe.d.ts.map +1 -1
  32. package/dist/internal/describe.js +18 -1
  33. package/dist/internal/describe.js.map +1 -1
  34. package/dist/internal/grant.js +1 -1
  35. package/dist/internal/grant.js.map +1 -1
  36. package/dist/internal/indexSchema.d.ts +19 -4
  37. package/dist/internal/indexSchema.d.ts.map +1 -1
  38. package/dist/internal/indexSchema.js +33 -6
  39. package/dist/internal/indexSchema.js.map +1 -1
  40. package/dist/internal/paths.d.ts +113 -18
  41. package/dist/internal/paths.d.ts.map +1 -1
  42. package/dist/internal/paths.js +224 -40
  43. package/dist/internal/paths.js.map +1 -1
  44. package/dist/internal/request.d.ts +47 -14
  45. package/dist/internal/request.d.ts.map +1 -1
  46. package/dist/internal/request.js +64 -44
  47. package/dist/internal/request.js.map +1 -1
  48. package/dist/internal/reserved.d.ts +15 -3
  49. package/dist/internal/reserved.d.ts.map +1 -1
  50. package/dist/internal/reserved.js +37 -8
  51. package/dist/internal/reserved.js.map +1 -1
  52. package/dist/internal/revoke.d.ts +1 -65
  53. package/dist/internal/revoke.d.ts.map +1 -1
  54. package/dist/internal/revoke.js +5 -136
  55. package/dist/internal/revoke.js.map +1 -1
  56. package/dist/internal/rootCapability.d.ts +66 -0
  57. package/dist/internal/rootCapability.d.ts.map +1 -0
  58. package/dist/internal/rootCapability.js +144 -0
  59. package/dist/internal/rootCapability.js.map +1 -0
  60. package/dist/internal/service.d.ts +5 -3
  61. package/dist/internal/service.d.ts.map +1 -1
  62. package/dist/internal/service.js +5 -3
  63. package/dist/internal/service.js.map +1 -1
  64. package/dist/internal/withCodec.d.ts +19 -4
  65. package/dist/internal/withCodec.d.ts.map +1 -1
  66. package/dist/internal/withCodec.js +18 -2
  67. package/dist/internal/withCodec.js.map +1 -1
  68. package/dist/internal/write.js +13 -9
  69. package/dist/internal/write.js.map +1 -1
  70. package/dist/paths.d.ts +5 -3
  71. package/dist/paths.d.ts.map +1 -1
  72. package/dist/paths.js +5 -3
  73. package/dist/paths.js.map +1 -1
  74. package/dist/sync/provisioning.js +1 -1
  75. package/dist/sync/provisioning.js.map +1 -1
  76. 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({ serviceUrl, 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()` |
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
- serviceUrl: 'https://was.example/service',
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
- serviceUrl: 'https://was.example/service',
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 exactly one of two URLs. `serviceUrl` is the URL of the
143
- server's service description, read with an unsigned `GET`. `spacesUrl` is the
144
- Spaces Repository URL (`https://host/spaces/`), which the client probes with an
145
- unsigned `HEAD` for the `Link: <...>; rel="service"` header. Giving both or
146
- neither, or a URL that is not absolute `http(s)`, is refused with
147
- `ValidationError`. The one given is exposed as `was.serviceUrl` or
148
- `was.spacesUrl` (the latter with its trailing slash supplied), and the other is
149
- `undefined`.
150
-
151
- A Space URL is received. It is not assembled from a server root. It arrives as
152
- the `Location` of a create response, a listing item's `url`, a linkset entry, a
153
- stored pointer, or a self-locating DID. The client holds no base URL, and no
154
- function in the `./paths` entry takes one. `was.spaceAt(spaceUrl)` opens a
155
- handle on a Space URL you hold.
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
- serviceUrl: 'https://was.example/service',
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 `serviceUrl` reads that document with an
212
- unsigned `GET`. A client built from `spacesUrl` first sends an unsigned `HEAD`
213
- to it and follows the `Link: <...>; rel="service"` header on the response,
214
- whatever its status, then reads the linked document with an unsigned `GET`. The
215
- PWS server itself answers the Spaces Repository URL. The server root is often a
216
- landing page served by something else, with no `service` link. The spec fixes no
217
- path for the document, so the client assumes none.
218
-
219
- The client does not compare the document's `spaces` member against any URL it
220
- was given. `createSpace()` and `listSpaces()` address the `spaces` URL the
221
- document names.
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 URL the service description names
298
- (see [Service discovery](#service-discovery)). Against a server whose
299
- description has no `spaces` URL, they throw `NotSupportedError` without sending
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 {spacesUrl}
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 is not the controller.
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 is controller-only on the server, the reads included, since
984
- a registration holds the pull capability. A delegated capability gets the
985
- masked 404. `replicas()`, `replica()`, and `replicaStatus()` then return `null`,
986
- and `registerReplica()` throws `NotFoundError`. `deregisterReplica()` reports it
987
- as `{ outcome: 'not-found' }` rather than swallowing it, so a delegated caller
988
- can tell that nothing was removed. The three reads reserve `null` for that 404.
989
- A 2xx with no JSON body is a server fault and throws `WasServerError`.
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({ serviceUrl, signer, encryption })
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.
@@ -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. Every fault
680
- * `changes()` rejects on fails the walk the same way.
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
@@ -1 +1 @@
1
- {"version":3,"file":"Collection.d.ts","sourceRoot":"","sources":["../src/Collection.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"Collection.d.ts","sourceRoot":"","sources":["../src/Collection.ts"],"names":[],"mappings":"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"}
@@ -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, spaceIdOfCanonicalUrl, collectionUrl, collectionPolicyUrl, collectionLinksetUrl, collectionBackendUrl, collectionQuotaUrl, collectionQueryUrl, collectionMetaUrl, collectionLogUrl, resourceUrl } from './internal/paths.js';
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
- this.#resetCodecIfDeclared(desc.encryption);
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.#resetCodecIfDeclared(description.encryption);
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. Every fault
1637
- * `changes()` rejects on fails the walk the same way.
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