@interop/was-client 0.27.2 → 0.28.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.
@@ -2,36 +2,49 @@
2
2
  * Copyright (c) 2026 Interop Alliance. All rights reserved.
3
3
  */
4
4
  /**
5
- * Idempotent Space + Collection provisioning for a synced collection. Re-running
6
- * is safe: the server upserts the Space and allows a late encryption-descriptor
7
- * declaration, so reconnecting an existing account is a no-op upgrade.
5
+ * Idempotent, non-clobbering Space + Collection provisioning for a synced
6
+ * collection. Everything is create-if-absent: an existing Space description,
7
+ * an existing encryption descriptor (which may carry a key-epoch roster other
8
+ * clients encrypt under), and an existing access policy are never overwritten,
9
+ * so ANY controller-tier client -- including one that joined a Space some
10
+ * other wallet provisioned -- can re-run it to heal a torn provisioning run.
11
+ * On a fully settled Space it issues only reads.
8
12
  */
9
13
  import type { WasClient } from '../WasClient.js';
10
14
  /**
11
15
  * Ensures the controller's Space exists and one synced collection is
12
- * configured. An `'edv'` collection declares the encryption descriptor `{
13
- * scheme: 'edv', version: EDV_SCHEME_VERSION }`, so the server stores only
14
- * ciphertext it can never decrypt and validates every write against the
15
- * declared envelope wire format -- the same version the cipher binds into each
16
- * envelope's AEAD-protected header, so descriptor and envelopes cannot drift;
17
- * a `'plaintext'` collection is configured without one, with `force` so the
18
- * descriptor-less upsert can create a fresh collection (running with the root
19
- * capability, a 404 from the pre-merge describe really means absent). A public
20
- * collection additionally gets a collection-level world-read grant
21
- * (`setPublic`), which is what makes a resource URL in it resolve for anyone.
22
- * Runs full-tier -- the client invokes its own root capability.
16
+ * configured, without overwriting anything already there. The Space is
17
+ * described first and configured only when absent -- an existing Space keeps
18
+ * its name AND its controller, so `controllerDid` is used only at creation
19
+ * (a joined client re-running this never rewrites the description). The
20
+ * collection likewise: when absent, an `'edv'` collection is created with the
21
+ * encryption descriptor `{ scheme: 'edv', version: EDV_SCHEME_VERSION }`, so
22
+ * the server stores only ciphertext it can never decrypt and validates every
23
+ * write against the declared envelope wire format -- the same version the
24
+ * cipher binds into each envelope's AEAD-protected header, so descriptor and
25
+ * envelopes cannot drift; a `'plaintext'` collection is created without one,
26
+ * with `force` so the descriptor-less upsert can create a fresh collection
27
+ * (running full-tier, a 404 from the pre-merge describe really means absent).
28
+ * An existing collection that lacks a descriptor an `'edv'` spec calls for
29
+ * gets the late in-place declaration (set-once on the server); one that
30
+ * already carries a descriptor -- possibly with appended key epochs -- is
31
+ * left untouched, epochs and display name included. A public collection gets
32
+ * the collection-level world-read grant (`setPublic`, what makes a resource
33
+ * URL in it resolve for anyone) only when its policy does not already say so.
34
+ * Runs full-tier -- the client invokes its own root authority.
23
35
  *
24
36
  * @param options {object}
25
37
  * @param options.was {WasClient}
26
38
  * @param options.spaceId {string}
27
- * @param options.controllerDid {string} the Space controller (e.g. `did:key`)
39
+ * @param options.controllerDid {string} the Space controller (e.g. `did:key`);
40
+ * used only when the Space does not exist yet
28
41
  * @param options.collectionId {string} the WAS collection id
29
42
  * @param [options.encryption] {'edv' | 'plaintext'} defaults to `'edv'`
30
43
  * @param [options.isPublic] {boolean} grant collection-level world read
31
- * @param [options.spaceName] {string} the Space display name; defaults to
32
- * `'WAS Space'`
33
- * @param [options.collectionName] {string} the collection display name;
34
- * defaults to the collection id
44
+ * @param [options.spaceName] {string} the Space display name, applied only at
45
+ * Space creation; defaults to `'WAS Space'`
46
+ * @param [options.collectionName] {string} the collection display name,
47
+ * applied only at collection creation; defaults to the collection id
35
48
  * @returns {Promise<void>}
36
49
  */
37
50
  export declare function ensureSpaceAndCollection({ was, spaceId, controllerDid, collectionId, encryption, isPublic, spaceName, collectionName }: {
@@ -1 +1 @@
1
- {"version":3,"file":"provisioning.d.ts","sourceRoot":"","sources":["../../src/sync/provisioning.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH;;;;GAIG;AACH,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAA;AAKhD;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AACH,wBAAsB,wBAAwB,CAAC,EAC7C,GAAG,EACH,OAAO,EACP,aAAa,EACb,YAAY,EACZ,UAAkB,EAClB,QAAgB,EAChB,SAAuB,EACvB,cAA6B,EAC9B,EAAE;IACD,GAAG,EAAE,SAAS,CAAA;IACd,OAAO,EAAE,MAAM,CAAA;IACf,aAAa,EAAE,MAAM,CAAA;IACrB,YAAY,EAAE,MAAM,CAAA;IACpB,UAAU,CAAC,EAAE,KAAK,GAAG,WAAW,CAAA;IAChC,QAAQ,CAAC,EAAE,OAAO,CAAA;IAClB,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,cAAc,CAAC,EAAE,MAAM,CAAA;CACxB,GAAG,OAAO,CAAC,IAAI,CAAC,CA+BhB"}
1
+ {"version":3,"file":"provisioning.d.ts","sourceRoot":"","sources":["../../src/sync/provisioning.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH;;;;;;;;GAQG;AACH,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAA;AAKhD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAmCG;AACH,wBAAsB,wBAAwB,CAAC,EAC7C,GAAG,EACH,OAAO,EACP,aAAa,EACb,YAAY,EACZ,UAAkB,EAClB,QAAgB,EAChB,SAAuB,EACvB,cAA6B,EAC9B,EAAE;IACD,GAAG,EAAE,SAAS,CAAA;IACd,OAAO,EAAE,MAAM,CAAA;IACf,aAAa,EAAE,MAAM,CAAA;IACrB,YAAY,EAAE,MAAM,CAAA;IACpB,UAAU,CAAC,EAAE,KAAK,GAAG,WAAW,CAAA;IAChC,QAAQ,CAAC,EAAE,OAAO,CAAA;IAClB,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,cAAc,CAAC,EAAE,MAAM,CAAA;CACxB,GAAG,OAAO,CAAC,IAAI,CAAC,CA6ChB"}
@@ -3,48 +3,72 @@
3
3
  import { EDV_SCHEME_VERSION } from '../edv/constants.js';
4
4
  /**
5
5
  * Ensures the controller's Space exists and one synced collection is
6
- * configured. An `'edv'` collection declares the encryption descriptor `{
7
- * scheme: 'edv', version: EDV_SCHEME_VERSION }`, so the server stores only
8
- * ciphertext it can never decrypt and validates every write against the
9
- * declared envelope wire format -- the same version the cipher binds into each
10
- * envelope's AEAD-protected header, so descriptor and envelopes cannot drift;
11
- * a `'plaintext'` collection is configured without one, with `force` so the
12
- * descriptor-less upsert can create a fresh collection (running with the root
13
- * capability, a 404 from the pre-merge describe really means absent). A public
14
- * collection additionally gets a collection-level world-read grant
15
- * (`setPublic`), which is what makes a resource URL in it resolve for anyone.
16
- * Runs full-tier -- the client invokes its own root capability.
6
+ * configured, without overwriting anything already there. The Space is
7
+ * described first and configured only when absent -- an existing Space keeps
8
+ * its name AND its controller, so `controllerDid` is used only at creation
9
+ * (a joined client re-running this never rewrites the description). The
10
+ * collection likewise: when absent, an `'edv'` collection is created with the
11
+ * encryption descriptor `{ scheme: 'edv', version: EDV_SCHEME_VERSION }`, so
12
+ * the server stores only ciphertext it can never decrypt and validates every
13
+ * write against the declared envelope wire format -- the same version the
14
+ * cipher binds into each envelope's AEAD-protected header, so descriptor and
15
+ * envelopes cannot drift; a `'plaintext'` collection is created without one,
16
+ * with `force` so the descriptor-less upsert can create a fresh collection
17
+ * (running full-tier, a 404 from the pre-merge describe really means absent).
18
+ * An existing collection that lacks a descriptor an `'edv'` spec calls for
19
+ * gets the late in-place declaration (set-once on the server); one that
20
+ * already carries a descriptor -- possibly with appended key epochs -- is
21
+ * left untouched, epochs and display name included. A public collection gets
22
+ * the collection-level world-read grant (`setPublic`, what makes a resource
23
+ * URL in it resolve for anyone) only when its policy does not already say so.
24
+ * Runs full-tier -- the client invokes its own root authority.
17
25
  *
18
26
  * @param options {object}
19
27
  * @param options.was {WasClient}
20
28
  * @param options.spaceId {string}
21
- * @param options.controllerDid {string} the Space controller (e.g. `did:key`)
29
+ * @param options.controllerDid {string} the Space controller (e.g. `did:key`);
30
+ * used only when the Space does not exist yet
22
31
  * @param options.collectionId {string} the WAS collection id
23
32
  * @param [options.encryption] {'edv' | 'plaintext'} defaults to `'edv'`
24
33
  * @param [options.isPublic] {boolean} grant collection-level world read
25
- * @param [options.spaceName] {string} the Space display name; defaults to
26
- * `'WAS Space'`
27
- * @param [options.collectionName] {string} the collection display name;
28
- * defaults to the collection id
34
+ * @param [options.spaceName] {string} the Space display name, applied only at
35
+ * Space creation; defaults to `'WAS Space'`
36
+ * @param [options.collectionName] {string} the collection display name,
37
+ * applied only at collection creation; defaults to the collection id
29
38
  * @returns {Promise<void>}
30
39
  */
31
40
  export async function ensureSpaceAndCollection({ was, spaceId, controllerDid, collectionId, encryption = 'edv', isPublic = false, spaceName = 'WAS Space', collectionName = collectionId }) {
32
41
  const space = was.space(spaceId);
33
42
  try {
34
- await space.configure({ name: spaceName, controller: controllerDid });
43
+ if ((await space.describe()) === null) {
44
+ await space.configure({ name: spaceName, controller: controllerDid });
45
+ }
35
46
  }
36
47
  catch (err) {
37
48
  throw new Error(`Failed to configure WAS space "${spaceId}" for "${controllerDid}".`, { cause: err });
38
49
  }
39
50
  try {
40
51
  const collection = space.collection(collectionId);
41
- await collection.configure(encryption === 'edv'
42
- ? {
43
- name: collectionName,
52
+ const current = await collection.describe();
53
+ if (current === null) {
54
+ await collection.configure(encryption === 'edv'
55
+ ? {
56
+ name: collectionName,
57
+ encryption: { scheme: 'edv', version: EDV_SCHEME_VERSION }
58
+ }
59
+ : { name: collectionName, force: true });
60
+ }
61
+ else if (encryption === 'edv' && current.encryption === undefined) {
62
+ // The late in-place declaration: adding a descriptor to a collection
63
+ // that lacks one is allowed (set-once), while re-sending one over an
64
+ // existing descriptor would drop its appended key epochs -- which is
65
+ // exactly why an existing descriptor is never touched.
66
+ await collection.configure({
67
+ name: current.name ?? collectionName,
44
68
  encryption: { scheme: 'edv', version: EDV_SCHEME_VERSION }
45
- }
46
- : { name: collectionName, force: true });
47
- if (isPublic) {
69
+ });
70
+ }
71
+ if (isPublic && !(await collection.isPublic())) {
48
72
  await collection.setPublic();
49
73
  }
50
74
  }
@@ -1 +1 @@
1
- {"version":3,"file":"provisioning.js","sourceRoot":"","sources":["../../src/sync/provisioning.ts"],"names":[],"mappings":"AASA,6EAA6E;AAC7E,iEAAiE;AACjE,OAAO,EAAE,kBAAkB,EAAE,MAAM,qBAAqB,CAAA;AAExD;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AACH,MAAM,CAAC,KAAK,UAAU,wBAAwB,CAAC,EAC7C,GAAG,EACH,OAAO,EACP,aAAa,EACb,YAAY,EACZ,UAAU,GAAG,KAAK,EAClB,QAAQ,GAAG,KAAK,EAChB,SAAS,GAAG,WAAW,EACvB,cAAc,GAAG,YAAY,EAU9B;IACC,MAAM,KAAK,GAAG,GAAG,CAAC,KAAK,CAAC,OAAO,CAAC,CAAA;IAEhC,IAAI,CAAC;QACH,MAAM,KAAK,CAAC,SAAS,CAAC,EAAE,IAAI,EAAE,SAAS,EAAE,UAAU,EAAE,aAAa,EAAE,CAAC,CAAA;IACvE,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,MAAM,IAAI,KAAK,CACb,kCAAkC,OAAO,UAAU,aAAa,IAAI,EACpE,EAAE,KAAK,EAAE,GAAG,EAAE,CACf,CAAA;IACH,CAAC;IAED,IAAI,CAAC;QACH,MAAM,UAAU,GAAG,KAAK,CAAC,UAAU,CAAC,YAAY,CAAC,CAAA;QACjD,MAAM,UAAU,CAAC,SAAS,CACxB,UAAU,KAAK,KAAK;YAClB,CAAC,CAAC;gBACE,IAAI,EAAE,cAAc;gBACpB,UAAU,EAAE,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,EAAE,kBAAkB,EAAE;aAC3D;YACH,CAAC,CAAC,EAAE,IAAI,EAAE,cAAc,EAAE,KAAK,EAAE,IAAI,EAAE,CAC1C,CAAA;QACD,IAAI,QAAQ,EAAE,CAAC;YACb,MAAM,UAAU,CAAC,SAAS,EAAE,CAAA;QAC9B,CAAC;IACH,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,MAAM,IAAI,KAAK,CACb,mCAAmC,YAAY,eAAe,OAAO,IAAI,EACzE,EAAE,KAAK,EAAE,GAAG,EAAE,CACf,CAAA;IACH,CAAC;AACH,CAAC"}
1
+ {"version":3,"file":"provisioning.js","sourceRoot":"","sources":["../../src/sync/provisioning.ts"],"names":[],"mappings":"AAaA,6EAA6E;AAC7E,iEAAiE;AACjE,OAAO,EAAE,kBAAkB,EAAE,MAAM,qBAAqB,CAAA;AAExD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAmCG;AACH,MAAM,CAAC,KAAK,UAAU,wBAAwB,CAAC,EAC7C,GAAG,EACH,OAAO,EACP,aAAa,EACb,YAAY,EACZ,UAAU,GAAG,KAAK,EAClB,QAAQ,GAAG,KAAK,EAChB,SAAS,GAAG,WAAW,EACvB,cAAc,GAAG,YAAY,EAU9B;IACC,MAAM,KAAK,GAAG,GAAG,CAAC,KAAK,CAAC,OAAO,CAAC,CAAA;IAEhC,IAAI,CAAC;QACH,IAAI,CAAC,MAAM,KAAK,CAAC,QAAQ,EAAE,CAAC,KAAK,IAAI,EAAE,CAAC;YACtC,MAAM,KAAK,CAAC,SAAS,CAAC,EAAE,IAAI,EAAE,SAAS,EAAE,UAAU,EAAE,aAAa,EAAE,CAAC,CAAA;QACvE,CAAC;IACH,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,MAAM,IAAI,KAAK,CACb,kCAAkC,OAAO,UAAU,aAAa,IAAI,EACpE,EAAE,KAAK,EAAE,GAAG,EAAE,CACf,CAAA;IACH,CAAC;IAED,IAAI,CAAC;QACH,MAAM,UAAU,GAAG,KAAK,CAAC,UAAU,CAAC,YAAY,CAAC,CAAA;QACjD,MAAM,OAAO,GAAG,MAAM,UAAU,CAAC,QAAQ,EAAE,CAAA;QAC3C,IAAI,OAAO,KAAK,IAAI,EAAE,CAAC;YACrB,MAAM,UAAU,CAAC,SAAS,CACxB,UAAU,KAAK,KAAK;gBAClB,CAAC,CAAC;oBACE,IAAI,EAAE,cAAc;oBACpB,UAAU,EAAE,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,EAAE,kBAAkB,EAAE;iBAC3D;gBACH,CAAC,CAAC,EAAE,IAAI,EAAE,cAAc,EAAE,KAAK,EAAE,IAAI,EAAE,CAC1C,CAAA;QACH,CAAC;aAAM,IAAI,UAAU,KAAK,KAAK,IAAI,OAAO,CAAC,UAAU,KAAK,SAAS,EAAE,CAAC;YACpE,qEAAqE;YACrE,qEAAqE;YACrE,qEAAqE;YACrE,uDAAuD;YACvD,MAAM,UAAU,CAAC,SAAS,CAAC;gBACzB,IAAI,EAAE,OAAO,CAAC,IAAI,IAAI,cAAc;gBACpC,UAAU,EAAE,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,EAAE,kBAAkB,EAAE;aAC3D,CAAC,CAAA;QACJ,CAAC;QACD,IAAI,QAAQ,IAAI,CAAC,CAAC,MAAM,UAAU,CAAC,QAAQ,EAAE,CAAC,EAAE,CAAC;YAC/C,MAAM,UAAU,CAAC,SAAS,EAAE,CAAA;QAC9B,CAAC;IACH,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,MAAM,IAAI,KAAK,CACb,mCAAmC,YAAY,eAAe,OAAO,IAAI,EACzE,EAAE,KAAK,EAAE,GAAG,EAAE,CACf,CAAA;IACH,CAAC;AACH,CAAC"}
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@interop/was-client",
3
3
  "description": "A developer-friendly client for Wallet Attached Storage (WAS) servers.",
4
- "version": "0.27.2",
4
+ "version": "0.28.0",
5
5
  "type": "module",
6
6
  "scripts": {
7
7
  "build": "pnpm run clear && tsc",