@interop/was-react 0.1.8 → 0.1.10

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 (57) hide show
  1. package/README.md +103 -3
  2. package/dist/auth/loginFlow.d.ts +4 -2
  3. package/dist/auth/loginFlow.d.ts.map +1 -1
  4. package/dist/auth/loginFlow.js +1 -1
  5. package/dist/auth/loginFlow.js.map +1 -1
  6. package/dist/auth/loginRequest.d.ts +29 -3
  7. package/dist/auth/loginRequest.d.ts.map +1 -1
  8. package/dist/auth/loginRequest.js +12 -4
  9. package/dist/auth/loginRequest.js.map +1 -1
  10. package/dist/config.d.ts +38 -0
  11. package/dist/config.d.ts.map +1 -1
  12. package/dist/config.js +0 -0
  13. package/dist/config.js.map +1 -1
  14. package/dist/identity/documentLoader.d.ts +2 -8
  15. package/dist/identity/documentLoader.d.ts.map +1 -1
  16. package/dist/identity/documentLoader.js +7 -88
  17. package/dist/identity/documentLoader.js.map +1 -1
  18. package/dist/index.d.ts +5 -4
  19. package/dist/index.d.ts.map +1 -1
  20. package/dist/index.js +3 -2
  21. package/dist/index.js.map +1 -1
  22. package/dist/session/authStore.d.ts.map +1 -1
  23. package/dist/session/authStore.js +21 -9
  24. package/dist/session/authStore.js.map +1 -1
  25. package/dist/storage/entityStore.d.ts +20 -0
  26. package/dist/storage/entityStore.d.ts.map +1 -1
  27. package/dist/storage/entityStore.js +24 -1
  28. package/dist/storage/entityStore.js.map +1 -1
  29. package/dist/storage/localStore.d.ts +30 -13
  30. package/dist/storage/localStore.d.ts.map +1 -1
  31. package/dist/storage/localStore.js +56 -18
  32. package/dist/storage/localStore.js.map +1 -1
  33. package/dist/storage/publicUrl.d.ts +20 -0
  34. package/dist/storage/publicUrl.d.ts.map +1 -0
  35. package/dist/storage/publicUrl.js +33 -0
  36. package/dist/storage/publicUrl.js.map +1 -0
  37. package/dist/storage/storageManager.d.ts +28 -0
  38. package/dist/storage/storageManager.d.ts.map +1 -1
  39. package/dist/storage/storageManager.js +42 -2
  40. package/dist/storage/storageManager.js.map +1 -1
  41. package/dist/storage/wasRemoteStore.d.ts +105 -3
  42. package/dist/storage/wasRemoteStore.d.ts.map +1 -1
  43. package/dist/storage/wasRemoteStore.js +167 -5
  44. package/dist/storage/wasRemoteStore.js.map +1 -1
  45. package/dist/storage/wasSync.d.ts +7 -2
  46. package/dist/storage/wasSync.d.ts.map +1 -1
  47. package/dist/storage/wasSync.js +21 -7
  48. package/dist/storage/wasSync.js.map +1 -1
  49. package/dist/sync/docCipher.d.ts +22 -0
  50. package/dist/sync/docCipher.d.ts.map +1 -1
  51. package/dist/sync/docCipher.js +42 -0
  52. package/dist/sync/docCipher.js.map +1 -1
  53. package/dist/sync/index.d.ts +1 -1
  54. package/dist/sync/index.d.ts.map +1 -1
  55. package/dist/sync/index.js +1 -1
  56. package/dist/sync/index.js.map +1 -1
  57. package/package.json +8 -7
@@ -1,4 +1,5 @@
1
1
  import type { LocalStore } from './localStore.js';
2
+ import type { WasRemoteStore } from './wasRemoteStore.js';
2
3
  /**
3
4
  * Installs the opened store (called once by the app bootstrap).
4
5
  *
@@ -24,6 +25,33 @@ export declare function hasStore(): boolean;
24
25
  * @returns {void}
25
26
  */
26
27
  export declare function clearLocalStore(): void;
28
+ /**
29
+ * Installs the per-session delegated remote store (set once background sync
30
+ * has bootstrapped it from the granted zcaps).
31
+ *
32
+ * @param store {WasRemoteStore}
33
+ * @returns {void}
34
+ */
35
+ export declare function setRemoteStore(store: WasRemoteStore): void;
36
+ /**
37
+ * The connected session's remote store, or throws while no wallet-connected
38
+ * session is active (local-only mode, or sync has not bootstrapped yet).
39
+ *
40
+ * @returns {WasRemoteStore}
41
+ */
42
+ export declare function requireRemoteStore(): WasRemoteStore;
43
+ /**
44
+ * Whether a connected session's remote store is available.
45
+ *
46
+ * @returns {boolean}
47
+ */
48
+ export declare function hasRemoteStore(): boolean;
49
+ /**
50
+ * Releases the held remote store reference (logout / sync teardown).
51
+ *
52
+ * @returns {void}
53
+ */
54
+ export declare function clearRemoteStore(): void;
27
55
  /**
28
56
  * A stable per-install device id (the last-write-wins tiebreak stamped into
29
57
  * every payload), persisted in localStorage under `<prefix>deviceId`.
@@ -1 +1 @@
1
- {"version":3,"file":"storageManager.d.ts","sourceRoot":"","sources":["../../src/storage/storageManager.ts"],"names":[],"mappings":"AAYA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAA;AAIjD;;;;;GAKG;AACH,wBAAgB,aAAa,CAAC,KAAK,EAAE,UAAU,GAAG,IAAI,CAErD;AAED;;;;GAIG;AACH,wBAAgB,YAAY,IAAI,UAAU,CAKzC;AAED;;;;GAIG;AACH,wBAAgB,QAAQ,IAAI,OAAO,CAElC;AAED;;;;GAIG;AACH,wBAAgB,eAAe,IAAI,IAAI,CAEtC;AAED;;;;;;;;GAQG;AACH,wBAAgB,WAAW,CAAC,EAC1B,gBAA6C,EAC9C,GAAE;IAAE,gBAAgB,CAAC,EAAE,MAAM,CAAA;CAAO,GAAG,MAAM,CAQ7C"}
1
+ {"version":3,"file":"storageManager.d.ts","sourceRoot":"","sources":["../../src/storage/storageManager.ts"],"names":[],"mappings":"AAaA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAA;AACjD,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAA;AAKzD;;;;;GAKG;AACH,wBAAgB,aAAa,CAAC,KAAK,EAAE,UAAU,GAAG,IAAI,CAErD;AAED;;;;GAIG;AACH,wBAAgB,YAAY,IAAI,UAAU,CAKzC;AAED;;;;GAIG;AACH,wBAAgB,QAAQ,IAAI,OAAO,CAElC;AAED;;;;GAIG;AACH,wBAAgB,eAAe,IAAI,IAAI,CAEtC;AAED;;;;;;GAMG;AACH,wBAAgB,cAAc,CAAC,KAAK,EAAE,cAAc,GAAG,IAAI,CAE1D;AAED;;;;;GAKG;AACH,wBAAgB,kBAAkB,IAAI,cAAc,CAOnD;AAED;;;;GAIG;AACH,wBAAgB,cAAc,IAAI,OAAO,CAExC;AAED;;;;GAIG;AACH,wBAAgB,gBAAgB,IAAI,IAAI,CAEvC;AAED;;;;;;;;GAQG;AACH,wBAAgB,WAAW,CAAC,EAC1B,gBAA6C,EAC9C,GAAE;IAAE,gBAAgB,CAAC,EAAE,MAAM,CAAA;CAAO,GAAG,MAAM,CAQ7C"}
@@ -3,14 +3,16 @@
3
3
  */
4
4
  /**
5
5
  * The storage manager: a thin process-wide holder for the one {@link LocalStore}
6
- * instance plus the per-install device id. Entity stores reach for the store
7
- * through {@link requireStore} inside their CRUD actions rather than importing it
6
+ * instance, the per-session {@link WasRemoteStore}, plus the per-install device
7
+ * id. Entity stores reach for the stores through {@link requireStore} /
8
+ * {@link requireRemoteStore} inside their verbs rather than importing them
8
9
  * directly, which keeps this module free of store imports (no cycle) and lets
9
10
  * the app own the init/hydrate ordering.
10
11
  */
11
12
  import { uuidv7 } from 'uuidv7';
12
13
  import { DEFAULT_STORAGE_KEY_PREFIX } from '../config.js';
13
14
  let localStore = null;
15
+ let remoteStore = null;
14
16
  /**
15
17
  * Installs the opened store (called once by the app bootstrap).
16
18
  *
@@ -47,6 +49,44 @@ export function hasStore() {
47
49
  export function clearLocalStore() {
48
50
  localStore = null;
49
51
  }
52
+ /**
53
+ * Installs the per-session delegated remote store (set once background sync
54
+ * has bootstrapped it from the granted zcaps).
55
+ *
56
+ * @param store {WasRemoteStore}
57
+ * @returns {void}
58
+ */
59
+ export function setRemoteStore(store) {
60
+ remoteStore = store;
61
+ }
62
+ /**
63
+ * The connected session's remote store, or throws while no wallet-connected
64
+ * session is active (local-only mode, or sync has not bootstrapped yet).
65
+ *
66
+ * @returns {WasRemoteStore}
67
+ */
68
+ export function requireRemoteStore() {
69
+ if (!remoteStore) {
70
+ throw new Error('No WAS remote store is available; connect a wallet session first.');
71
+ }
72
+ return remoteStore;
73
+ }
74
+ /**
75
+ * Whether a connected session's remote store is available.
76
+ *
77
+ * @returns {boolean}
78
+ */
79
+ export function hasRemoteStore() {
80
+ return remoteStore !== null;
81
+ }
82
+ /**
83
+ * Releases the held remote store reference (logout / sync teardown).
84
+ *
85
+ * @returns {void}
86
+ */
87
+ export function clearRemoteStore() {
88
+ remoteStore = null;
89
+ }
50
90
  /**
51
91
  * A stable per-install device id (the last-write-wins tiebreak stamped into
52
92
  * every payload), persisted in localStorage under `<prefix>deviceId`.
@@ -1 +1 @@
1
- {"version":3,"file":"storageManager.js","sourceRoot":"","sources":["../../src/storage/storageManager.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH;;;;;;GAMG;AACH,OAAO,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAA;AAC/B,OAAO,EAAE,0BAA0B,EAAE,MAAM,cAAc,CAAA;AAGzD,IAAI,UAAU,GAAsB,IAAI,CAAA;AAExC;;;;;GAKG;AACH,MAAM,UAAU,aAAa,CAAC,KAAiB;IAC7C,UAAU,GAAG,KAAK,CAAA;AACpB,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,YAAY;IAC1B,IAAI,CAAC,UAAU,EAAE,CAAC;QAChB,MAAM,IAAI,KAAK,CAAC,+CAA+C,CAAC,CAAA;IAClE,CAAC;IACD,OAAO,UAAU,CAAA;AACnB,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,QAAQ;IACtB,OAAO,UAAU,KAAK,IAAI,CAAA;AAC5B,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,eAAe;IAC7B,UAAU,GAAG,IAAI,CAAA;AACnB,CAAC;AAED;;;;;;;;GAQG;AACH,MAAM,UAAU,WAAW,CAAC,EAC1B,gBAAgB,GAAG,0BAA0B,KACZ,EAAE;IACnC,MAAM,WAAW,GAAG,GAAG,gBAAgB,UAAU,CAAA;IACjD,IAAI,EAAE,GAAG,YAAY,CAAC,OAAO,CAAC,WAAW,CAAC,CAAA;IAC1C,IAAI,CAAC,EAAE,EAAE,CAAC;QACR,EAAE,GAAG,MAAM,EAAE,CAAA;QACb,YAAY,CAAC,OAAO,CAAC,WAAW,EAAE,EAAE,CAAC,CAAA;IACvC,CAAC;IACD,OAAO,EAAE,CAAA;AACX,CAAC"}
1
+ {"version":3,"file":"storageManager.js","sourceRoot":"","sources":["../../src/storage/storageManager.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH;;;;;;;GAOG;AACH,OAAO,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAA;AAC/B,OAAO,EAAE,0BAA0B,EAAE,MAAM,cAAc,CAAA;AAIzD,IAAI,UAAU,GAAsB,IAAI,CAAA;AACxC,IAAI,WAAW,GAA0B,IAAI,CAAA;AAE7C;;;;;GAKG;AACH,MAAM,UAAU,aAAa,CAAC,KAAiB;IAC7C,UAAU,GAAG,KAAK,CAAA;AACpB,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,YAAY;IAC1B,IAAI,CAAC,UAAU,EAAE,CAAC;QAChB,MAAM,IAAI,KAAK,CAAC,+CAA+C,CAAC,CAAA;IAClE,CAAC;IACD,OAAO,UAAU,CAAA;AACnB,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,QAAQ;IACtB,OAAO,UAAU,KAAK,IAAI,CAAA;AAC5B,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,eAAe;IAC7B,UAAU,GAAG,IAAI,CAAA;AACnB,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,cAAc,CAAC,KAAqB;IAClD,WAAW,GAAG,KAAK,CAAA;AACrB,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,kBAAkB;IAChC,IAAI,CAAC,WAAW,EAAE,CAAC;QACjB,MAAM,IAAI,KAAK,CACb,mEAAmE,CACpE,CAAA;IACH,CAAC;IACD,OAAO,WAAW,CAAA;AACpB,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,cAAc;IAC5B,OAAO,WAAW,KAAK,IAAI,CAAA;AAC7B,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,gBAAgB;IAC9B,WAAW,GAAG,IAAI,CAAA;AACpB,CAAC;AAED;;;;;;;;GAQG;AACH,MAAM,UAAU,WAAW,CAAC,EAC1B,gBAAgB,GAAG,0BAA0B,KACZ,EAAE;IACnC,MAAM,WAAW,GAAG,GAAG,gBAAgB,UAAU,CAAA;IACjD,IAAI,EAAE,GAAG,YAAY,CAAC,OAAO,CAAC,WAAW,CAAC,CAAA;IAC1C,IAAI,CAAC,EAAE,EAAE,CAAC;QACR,EAAE,GAAG,MAAM,EAAE,CAAA;QACb,YAAY,CAAC,OAAO,CAAC,WAAW,EAAE,EAAE,CAAC,CAAA;IACvC,CAAC;IACD,OAAO,EAAE,CAAA;AACX,CAAC"}
@@ -15,11 +15,18 @@
15
15
  * - a best-effort encryption-marker PUT (whether a delegated collection-scoped
16
16
  * RW zcap authorizes writing the collection description). It is non-fatal
17
17
  * either way -- envelopes replicate into an unmarked (plaintext) collection
18
- * just the same.
18
+ * just the same. A PUBLIC collection is never marked: public implies
19
+ * plaintext, so the marker PUT is skipped outright;
20
+ * - the sibling best-effort `indexes` declaration PUT for public collections
21
+ * that declare equality-indexed attributes, plus the equality query verb
22
+ * itself ({@link WasRemoteStore.queryCollectionByEquality}): the canonical
23
+ * sorted `filter[attr]=value` GET on the collection list endpoint, parsed
24
+ * into the `{ documents, hasMore, cursor? }` page shape.
19
25
  */
20
26
  import type { ZcapClient } from '@interop/ezcap';
21
27
  import type { IZcap } from '@interop/data-integrity-core';
22
28
  import { WasClient } from '@interop/was-client';
29
+ import type { WasCollectionConfig } from '../config.js';
23
30
  import type { ParsedGrants } from '../grants.js';
24
31
  /**
25
32
  * The outcome of a best-effort encryption-marker PUT, for diagnostics.
@@ -29,12 +36,34 @@ export interface MarkerResult {
29
36
  ok: boolean;
30
37
  status?: number;
31
38
  error?: string;
39
+ /**
40
+ * True when no PUT was attempted because the collection is public
41
+ * (plaintext); reported as `ok` since the goal state -- no marker -- holds.
42
+ */
43
+ skipped?: boolean;
44
+ }
45
+ /**
46
+ * One page of equality-query results: the shared shape of the GET
47
+ * `filter[attr]=value` filter and the POST `equality` query profile. `data` is
48
+ * the stored JSON content (absent for a blob resource); `custom` is the
49
+ * resource's custom metadata object, present when it has one. The opaque
50
+ * `cursor` continues the page walk when `hasMore` is true.
51
+ */
52
+ export interface EqualityQueryPage {
53
+ documents: Array<{
54
+ id: string;
55
+ data?: unknown;
56
+ custom?: unknown;
57
+ }>;
58
+ hasMore: boolean;
59
+ cursor?: string;
32
60
  }
33
61
  export declare class WasRemoteStore {
34
62
  readonly was: WasClient;
35
63
  readonly serverUrl: string;
36
64
  readonly spaceId: string;
37
65
  private readonly _byCollectionId;
66
+ private readonly _configById;
38
67
  private constructor();
39
68
  /**
40
69
  * Builds a delegated remote store from a parsed grant set and the app's
@@ -45,11 +74,14 @@ export declare class WasRemoteStore {
45
74
  * @param options {object}
46
75
  * @param options.parsed {ParsedGrants}
47
76
  * @param options.zcapClient {ZcapClient}
77
+ * @param [options.collections] {WasCollectionConfig[]} the collection
78
+ * registry; entries with `visibility: 'public'` are never marked encrypted
48
79
  * @returns {WasRemoteStore}
49
80
  */
50
- static fromGrants({ parsed, zcapClient }: {
81
+ static fromGrants({ parsed, zcapClient, collections }: {
51
82
  parsed: ParsedGrants;
52
83
  zcapClient: ZcapClient;
84
+ collections?: WasCollectionConfig[];
53
85
  }): WasRemoteStore;
54
86
  /**
55
87
  * The delegated capability for one WAS collection, or `undefined` when no
@@ -65,11 +97,81 @@ export declare class WasRemoteStore {
65
97
  * one collection, invoked with that collection's delegated RW zcap. Non-fatal:
66
98
  * returns the outcome rather than throwing, so a server that does not authorize
67
99
  * a delegated description write leaves replication untouched (the collection
68
- * simply stays unmarked / plaintext, which still stores envelopes).
100
+ * simply stays unmarked / plaintext, which still stores envelopes). A PUBLIC
101
+ * collection is never marked (public implies plaintext): the PUT is skipped
102
+ * and reported as `ok` + `skipped`.
69
103
  *
70
104
  * @param collectionId {string} the WAS collection id
71
105
  * @returns {Promise<MarkerResult>}
72
106
  */
73
107
  markCollectionEncrypted(collectionId: string): Promise<MarkerResult>;
108
+ /**
109
+ * Best-effort declaration of a public collection's equality-indexed
110
+ * attributes (`{ indexes: [...] }`) on its collection description, invoked
111
+ * with that collection's delegated RW zcap. The server rejects
112
+ * `filter[attr]=value` queries on undeclared attributes fail-closed, so a
113
+ * public collection that wants `store.query()` must announce its `indexes`
114
+ * here. Non-fatal like the encryption marker: returns the outcome rather
115
+ * than throwing. Skipped (reported `ok` + `skipped`) for a private
116
+ * collection or one that declares no indexes.
117
+ *
118
+ * @param collectionId {string} the WAS collection id
119
+ * @returns {Promise<MarkerResult>}
120
+ */
121
+ declareCollectionIndexes(collectionId: string): Promise<MarkerResult>;
122
+ /**
123
+ * Runs one equality query against a public collection: the canonical GET
124
+ * `filter[attr]=value` form of the server's `equality` profile, invoked with
125
+ * the collection's delegated zcap (an anonymous reader would issue the same
126
+ * URL unsigned against a `PublicCanRead` collection). Filter attributes are
127
+ * emitted in sorted order so identical queries produce identical URLs
128
+ * (cache-friendly); values are string equality only. Fails closed before any
129
+ * network round trip on a non-public collection (the encrypted
130
+ * `blinded-index` path is not yet supported), an empty term set, or an
131
+ * attribute missing from the collection's declared `indexes`.
132
+ *
133
+ * @param options {object}
134
+ * @param options.collectionId {string} the WAS collection id
135
+ * @param options.equals {Record<string, string>} equality terms; multiple
136
+ * attributes AND together
137
+ * @param [options.limit] {number} page size (server default when omitted)
138
+ * @param [options.cursor] {string} opaque continuation cursor from the
139
+ * prior page
140
+ * @returns {Promise<EqualityQueryPage>}
141
+ */
142
+ queryCollectionByEquality({ collectionId, equals, limit, cursor }: {
143
+ collectionId: string;
144
+ equals: Record<string, string>;
145
+ limit?: number;
146
+ cursor?: string;
147
+ }): Promise<EqualityQueryPage>;
148
+ /**
149
+ * The world-readable share URL for one document in a public (plaintext)
150
+ * collection: the exact URL an unauthenticated reader fetches (e.g. via
151
+ * `WasClient.publicRead`) to consume a share link. Because a public
152
+ * collection stores the payload under its own logical `id`, this URL is
153
+ * stable across edits of the document. Fails closed before composing
154
+ * anything on a non-public collection (the encrypted path stores under a
155
+ * random envelope id, so no stable public URL exists), an empty id, or a
156
+ * collection no delegated capability covers (catching typo'd or
157
+ * unprovisioned collection ids). The URL resolves publicly only once the
158
+ * document has replicated to the server -- a locally-inserted doc shares
159
+ * after the next sync push.
160
+ *
161
+ * @param options {object}
162
+ * @param options.collectionId {string} the WAS collection id
163
+ * @param options.id {string} the document's logical uuid
164
+ * @returns {string}
165
+ */
166
+ publicUrlFor({ collectionId, id }: {
167
+ collectionId: string;
168
+ id: string;
169
+ }): string;
170
+ /**
171
+ * The shared best-effort collection-description PUT behind the encryption
172
+ * marker and the indexes declaration: invokes the collection's delegated RW
173
+ * zcap and reports the outcome rather than throwing.
174
+ */
175
+ private _putDescription;
74
176
  }
75
177
  //# sourceMappingURL=wasRemoteStore.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"wasRemoteStore.d.ts","sourceRoot":"","sources":["../../src/storage/wasRemoteStore.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH;;;;;;;;;;;;;;;GAeG;AACH,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,gBAAgB,CAAA;AAChD,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,8BAA8B,CAAA;AACzD,OAAO,EAAE,SAAS,EAAE,MAAM,qBAAqB,CAAA;AAG/C,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,cAAc,CAAA;AAEhD;;GAEG;AACH,MAAM,WAAW,YAAY;IAC3B,YAAY,EAAE,MAAM,CAAA;IACpB,EAAE,EAAE,OAAO,CAAA;IACX,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,KAAK,CAAC,EAAE,MAAM,CAAA;CACf;AAED,qBAAa,cAAc;IACzB,SAAgB,GAAG,EAAE,SAAS,CAAA;IAC9B,SAAgB,SAAS,EAAE,MAAM,CAAA;IACjC,SAAgB,OAAO,EAAE,MAAM,CAAA;IAC/B,OAAO,CAAC,QAAQ,CAAC,eAAe,CAAuB;IAEvD,OAAO;IAaP;;;;;;;;;;OAUG;IACH,MAAM,CAAC,UAAU,CAAC,EAChB,MAAM,EACN,UAAU,EACX,EAAE;QACD,MAAM,EAAE,YAAY,CAAA;QACpB,UAAU,EAAE,UAAU,CAAA;KACvB,GAAG,cAAc;IASlB;;;;;;;OAOG;IACH,oBAAoB,CAAC,YAAY,EAAE,MAAM,GAAG,KAAK,GAAG,SAAS;IAI7D;;;;;;;;;OASG;IACG,uBAAuB,CAAC,YAAY,EAAE,MAAM,GAAG,OAAO,CAAC,YAAY,CAAC;CAmB3E"}
1
+ {"version":3,"file":"wasRemoteStore.d.ts","sourceRoot":"","sources":["../../src/storage/wasRemoteStore.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,gBAAgB,CAAA;AAChD,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,8BAA8B,CAAA;AACzD,OAAO,EAAE,SAAS,EAAE,MAAM,qBAAqB,CAAA;AAG/C,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,cAAc,CAAA;AACvD,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,cAAc,CAAA;AAEhD;;GAEG;AACH,MAAM,WAAW,YAAY;IAC3B,YAAY,EAAE,MAAM,CAAA;IACpB,EAAE,EAAE,OAAO,CAAA;IACX,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,KAAK,CAAC,EAAE,MAAM,CAAA;IACd;;;OAGG;IACH,OAAO,CAAC,EAAE,OAAO,CAAA;CAClB;AAED;;;;;;GAMG;AACH,MAAM,WAAW,iBAAiB;IAChC,SAAS,EAAE,KAAK,CAAC;QAAE,EAAE,EAAE,MAAM,CAAC;QAAC,IAAI,CAAC,EAAE,OAAO,CAAC;QAAC,MAAM,CAAC,EAAE,OAAO,CAAA;KAAE,CAAC,CAAA;IAClE,OAAO,EAAE,OAAO,CAAA;IAChB,MAAM,CAAC,EAAE,MAAM,CAAA;CAChB;AAED,qBAAa,cAAc;IACzB,SAAgB,GAAG,EAAE,SAAS,CAAA;IAC9B,SAAgB,SAAS,EAAE,MAAM,CAAA;IACjC,SAAgB,OAAO,EAAE,MAAM,CAAA;IAC/B,OAAO,CAAC,QAAQ,CAAC,eAAe,CAAuB;IAGvD,OAAO,CAAC,QAAQ,CAAC,WAAW,CAG3B;IAED,OAAO;IAmBP;;;;;;;;;;;;OAYG;IACH,MAAM,CAAC,UAAU,CAAC,EAChB,MAAM,EACN,UAAU,EACV,WAAgB,EACjB,EAAE;QACD,MAAM,EAAE,YAAY,CAAA;QACpB,UAAU,EAAE,UAAU,CAAA;QACtB,WAAW,CAAC,EAAE,mBAAmB,EAAE,CAAA;KACpC,GAAG,cAAc;IAqBlB;;;;;;;OAOG;IACH,oBAAoB,CAAC,YAAY,EAAE,MAAM,GAAG,KAAK,GAAG,SAAS;IAI7D;;;;;;;;;;;OAWG;IACG,uBAAuB,CAAC,YAAY,EAAE,MAAM,GAAG,OAAO,CAAC,YAAY,CAAC;IAU1E;;;;;;;;;;;;OAYG;IACG,wBAAwB,CAAC,YAAY,EAAE,MAAM,GAAG,OAAO,CAAC,YAAY,CAAC;IAe3E;;;;;;;;;;;;;;;;;;;OAmBG;IACG,yBAAyB,CAAC,EAC9B,YAAY,EACZ,MAAM,EACN,KAAK,EACL,MAAM,EACP,EAAE;QACD,YAAY,EAAE,MAAM,CAAA;QACpB,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;QAC9B,KAAK,CAAC,EAAE,MAAM,CAAA;QACd,MAAM,CAAC,EAAE,MAAM,CAAA;KAChB,GAAG,OAAO,CAAC,iBAAiB,CAAC;IAgE9B;;;;;;;;;;;;;;;;;OAiBG;IACH,YAAY,CAAC,EACX,YAAY,EACZ,EAAE,EACH,EAAE;QACD,YAAY,EAAE,MAAM,CAAA;QACpB,EAAE,EAAE,MAAM,CAAA;KACX,GAAG,MAAM;IAyBV;;;;OAIG;YACW,eAAe;CAyB9B"}
@@ -6,11 +6,15 @@ export class WasRemoteStore {
6
6
  serverUrl;
7
7
  spaceId;
8
8
  _byCollectionId;
9
- constructor({ was, parsed }) {
9
+ // Per WAS collection id: the effective visibility + declared equality
10
+ // indexes (the registry guarantees one declaration per id).
11
+ _configById;
12
+ constructor({ was, parsed, configById }) {
10
13
  this.was = was;
11
14
  this.serverUrl = parsed.serverUrl;
12
15
  this.spaceId = parsed.spaceId;
13
16
  this._byCollectionId = parsed.byCollectionId;
17
+ this._configById = configById;
14
18
  }
15
19
  /**
16
20
  * Builds a delegated remote store from a parsed grant set and the app's
@@ -21,15 +25,24 @@ export class WasRemoteStore {
21
25
  * @param options {object}
22
26
  * @param options.parsed {ParsedGrants}
23
27
  * @param options.zcapClient {ZcapClient}
28
+ * @param [options.collections] {WasCollectionConfig[]} the collection
29
+ * registry; entries with `visibility: 'public'` are never marked encrypted
24
30
  * @returns {WasRemoteStore}
25
31
  */
26
- static fromGrants({ parsed, zcapClient }) {
32
+ static fromGrants({ parsed, zcapClient, collections = [] }) {
27
33
  const was = new WasClient({
28
34
  serverUrl: parsed.serverUrl,
29
35
  zcapClient,
30
36
  encryption: createEdvEncryption({ resolveKeys: async () => null })
31
37
  });
32
- return new WasRemoteStore({ was, parsed });
38
+ const configById = new Map(collections.map(entry => [
39
+ entry.id,
40
+ {
41
+ visibility: entry.visibility ?? 'private',
42
+ ...(entry.indexes && { indexes: entry.indexes })
43
+ }
44
+ ]));
45
+ return new WasRemoteStore({ was, parsed, configById });
33
46
  }
34
47
  /**
35
48
  * The delegated capability for one WAS collection, or `undefined` when no
@@ -47,12 +60,161 @@ export class WasRemoteStore {
47
60
  * one collection, invoked with that collection's delegated RW zcap. Non-fatal:
48
61
  * returns the outcome rather than throwing, so a server that does not authorize
49
62
  * a delegated description write leaves replication untouched (the collection
50
- * simply stays unmarked / plaintext, which still stores envelopes).
63
+ * simply stays unmarked / plaintext, which still stores envelopes). A PUBLIC
64
+ * collection is never marked (public implies plaintext): the PUT is skipped
65
+ * and reported as `ok` + `skipped`.
51
66
  *
52
67
  * @param collectionId {string} the WAS collection id
53
68
  * @returns {Promise<MarkerResult>}
54
69
  */
55
70
  async markCollectionEncrypted(collectionId) {
71
+ if (this._configById.get(collectionId)?.visibility === 'public') {
72
+ return { collectionId, ok: true, skipped: true };
73
+ }
74
+ return this._putDescription({
75
+ collectionId,
76
+ description: { id: collectionId, encryption: { scheme: 'edv' } }
77
+ });
78
+ }
79
+ /**
80
+ * Best-effort declaration of a public collection's equality-indexed
81
+ * attributes (`{ indexes: [...] }`) on its collection description, invoked
82
+ * with that collection's delegated RW zcap. The server rejects
83
+ * `filter[attr]=value` queries on undeclared attributes fail-closed, so a
84
+ * public collection that wants `store.query()` must announce its `indexes`
85
+ * here. Non-fatal like the encryption marker: returns the outcome rather
86
+ * than throwing. Skipped (reported `ok` + `skipped`) for a private
87
+ * collection or one that declares no indexes.
88
+ *
89
+ * @param collectionId {string} the WAS collection id
90
+ * @returns {Promise<MarkerResult>}
91
+ */
92
+ async declareCollectionIndexes(collectionId) {
93
+ const config = this._configById.get(collectionId);
94
+ if (config?.visibility !== 'public' ||
95
+ !config.indexes ||
96
+ config.indexes.length === 0) {
97
+ return { collectionId, ok: true, skipped: true };
98
+ }
99
+ return this._putDescription({
100
+ collectionId,
101
+ description: { id: collectionId, indexes: config.indexes }
102
+ });
103
+ }
104
+ /**
105
+ * Runs one equality query against a public collection: the canonical GET
106
+ * `filter[attr]=value` form of the server's `equality` profile, invoked with
107
+ * the collection's delegated zcap (an anonymous reader would issue the same
108
+ * URL unsigned against a `PublicCanRead` collection). Filter attributes are
109
+ * emitted in sorted order so identical queries produce identical URLs
110
+ * (cache-friendly); values are string equality only. Fails closed before any
111
+ * network round trip on a non-public collection (the encrypted
112
+ * `blinded-index` path is not yet supported), an empty term set, or an
113
+ * attribute missing from the collection's declared `indexes`.
114
+ *
115
+ * @param options {object}
116
+ * @param options.collectionId {string} the WAS collection id
117
+ * @param options.equals {Record<string, string>} equality terms; multiple
118
+ * attributes AND together
119
+ * @param [options.limit] {number} page size (server default when omitted)
120
+ * @param [options.cursor] {string} opaque continuation cursor from the
121
+ * prior page
122
+ * @returns {Promise<EqualityQueryPage>}
123
+ */
124
+ async queryCollectionByEquality({ collectionId, equals, limit, cursor }) {
125
+ const config = this._configById.get(collectionId);
126
+ if (config?.visibility !== 'public') {
127
+ throw new Error(`Equality queries require a public (plaintext) collection; ` +
128
+ `"${collectionId}" is not registered as public (the encrypted ` +
129
+ `blinded-index query path is not yet supported).`);
130
+ }
131
+ const attributes = Object.keys(equals);
132
+ if (attributes.length === 0) {
133
+ throw new Error('An equality query needs at least one term.');
134
+ }
135
+ const declared = new Set(config.indexes ?? []);
136
+ for (const name of attributes) {
137
+ if (!declared.has(name)) {
138
+ throw new Error(`Attribute "${name}" is not declared in collection ` +
139
+ `"${collectionId}" indexes; declare it in the collection config.`);
140
+ }
141
+ }
142
+ const capability = this.collectionCapability(collectionId);
143
+ if (!capability) {
144
+ throw new Error(`No delegated capability covers collection "${collectionId}".`);
145
+ }
146
+ // Canonical query string: sorted filter attributes first, then the
147
+ // reserved pagination params. Literal brackets around a percent-encoded
148
+ // attribute name; the server decodes either spelling identically.
149
+ const params = attributes
150
+ .sort()
151
+ .map(name => `filter[${encodeURIComponent(name)}]=` +
152
+ encodeURIComponent(equals[name]));
153
+ if (limit !== undefined) {
154
+ params.push(`limit=${encodeURIComponent(String(limit))}`);
155
+ }
156
+ if (cursor !== undefined) {
157
+ params.push(`cursor=${encodeURIComponent(cursor)}`);
158
+ }
159
+ // The list endpoint is the trailing-slash collection items URL.
160
+ const response = await this.was.request({
161
+ capability,
162
+ path: `/space/${this.spaceId}/${collectionId}/?${params.join('&')}`,
163
+ method: 'GET'
164
+ });
165
+ const page = response.data;
166
+ if (!page || !Array.isArray(page.documents)) {
167
+ throw new Error(`Malformed equality query response for "${collectionId}": expected a ` +
168
+ `{ documents, hasMore } page.`);
169
+ }
170
+ return {
171
+ documents: page.documents,
172
+ hasMore: page.hasMore === true,
173
+ ...(typeof page.cursor === 'string' && { cursor: page.cursor })
174
+ };
175
+ }
176
+ /**
177
+ * The world-readable share URL for one document in a public (plaintext)
178
+ * collection: the exact URL an unauthenticated reader fetches (e.g. via
179
+ * `WasClient.publicRead`) to consume a share link. Because a public
180
+ * collection stores the payload under its own logical `id`, this URL is
181
+ * stable across edits of the document. Fails closed before composing
182
+ * anything on a non-public collection (the encrypted path stores under a
183
+ * random envelope id, so no stable public URL exists), an empty id, or a
184
+ * collection no delegated capability covers (catching typo'd or
185
+ * unprovisioned collection ids). The URL resolves publicly only once the
186
+ * document has replicated to the server -- a locally-inserted doc shares
187
+ * after the next sync push.
188
+ *
189
+ * @param options {object}
190
+ * @param options.collectionId {string} the WAS collection id
191
+ * @param options.id {string} the document's logical uuid
192
+ * @returns {string}
193
+ */
194
+ publicUrlFor({ collectionId, id }) {
195
+ const config = this._configById.get(collectionId);
196
+ if (config?.visibility !== 'public') {
197
+ throw new Error(`Public share URLs require a public (plaintext) collection; ` +
198
+ `"${collectionId}" is not registered as public (a private ` +
199
+ `collection stores documents under a random envelope id, so no ` +
200
+ `stable public URL exists).`);
201
+ }
202
+ if (typeof id !== 'string' || id.length === 0) {
203
+ throw new Error('A public share URL needs a non-empty document id.');
204
+ }
205
+ const capability = this.collectionCapability(collectionId);
206
+ if (!capability) {
207
+ throw new Error(`No delegated capability covers collection "${collectionId}".`);
208
+ }
209
+ return (`${this.serverUrl}/space/${this.spaceId}/${collectionId}/` +
210
+ `${encodeURIComponent(id)}`);
211
+ }
212
+ /**
213
+ * The shared best-effort collection-description PUT behind the encryption
214
+ * marker and the indexes declaration: invokes the collection's delegated RW
215
+ * zcap and reports the outcome rather than throwing.
216
+ */
217
+ async _putDescription({ collectionId, description }) {
56
218
  const capability = this.collectionCapability(collectionId);
57
219
  if (!capability) {
58
220
  return { collectionId, ok: false, error: 'no capability' };
@@ -62,7 +224,7 @@ export class WasRemoteStore {
62
224
  capability,
63
225
  path: `/space/${this.spaceId}/${collectionId}`,
64
226
  method: 'PUT',
65
- json: { id: collectionId, encryption: { scheme: 'edv' } }
227
+ json: description
66
228
  });
67
229
  return { collectionId, ok: true, status: response.status };
68
230
  }
@@ -1 +1 @@
1
- {"version":3,"file":"wasRemoteStore.js","sourceRoot":"","sources":["../../src/storage/wasRemoteStore.ts"],"names":[],"mappings":"AAqBA,OAAO,EAAE,SAAS,EAAE,MAAM,qBAAqB,CAAA;AAC/C,OAAO,EAAE,mBAAmB,EAAE,MAAM,yBAAyB,CAAA;AAC7D,OAAO,EAAE,WAAW,EAAE,YAAY,EAAE,MAAM,kBAAkB,CAAA;AAa5D,MAAM,OAAO,cAAc;IACT,GAAG,CAAW;IACd,SAAS,CAAQ;IACjB,OAAO,CAAQ;IACd,eAAe,CAAuB;IAEvD,YAAoB,EAClB,GAAG,EACH,MAAM,EAIP;QACC,IAAI,CAAC,GAAG,GAAG,GAAG,CAAA;QACd,IAAI,CAAC,SAAS,GAAG,MAAM,CAAC,SAAS,CAAA;QACjC,IAAI,CAAC,OAAO,GAAG,MAAM,CAAC,OAAO,CAAA;QAC7B,IAAI,CAAC,eAAe,GAAG,MAAM,CAAC,cAAc,CAAA;IAC9C,CAAC;IAED;;;;;;;;;;OAUG;IACH,MAAM,CAAC,UAAU,CAAC,EAChB,MAAM,EACN,UAAU,EAIX;QACC,MAAM,GAAG,GAAG,IAAI,SAAS,CAAC;YACxB,SAAS,EAAE,MAAM,CAAC,SAAS;YAC3B,UAAU;YACV,UAAU,EAAE,mBAAmB,CAAC,EAAE,WAAW,EAAE,KAAK,IAAI,EAAE,CAAC,IAAI,EAAE,CAAC;SACnE,CAAC,CAAA;QACF,OAAO,IAAI,cAAc,CAAC,EAAE,GAAG,EAAE,MAAM,EAAE,CAAC,CAAA;IAC5C,CAAC;IAED;;;;;;;OAOG;IACH,oBAAoB,CAAC,YAAoB;QACvC,OAAO,IAAI,CAAC,eAAe,CAAC,YAAY,CAAC,CAAA;IAC3C,CAAC;IAED;;;;;;;;;OASG;IACH,KAAK,CAAC,uBAAuB,CAAC,YAAoB;QAChD,MAAM,UAAU,GAAG,IAAI,CAAC,oBAAoB,CAAC,YAAY,CAAC,CAAA;QAC1D,IAAI,CAAC,UAAU,EAAE,CAAC;YAChB,OAAO,EAAE,YAAY,EAAE,EAAE,EAAE,KAAK,EAAE,KAAK,EAAE,eAAe,EAAE,CAAA;QAC5D,CAAC;QACD,IAAI,CAAC;YACH,MAAM,QAAQ,GAAG,MAAM,IAAI,CAAC,GAAG,CAAC,OAAO,CAAC;gBACtC,UAAU;gBACV,IAAI,EAAE,UAAU,IAAI,CAAC,OAAO,IAAI,YAAY,EAAE;gBAC9C,MAAM,EAAE,KAAK;gBACb,IAAI,EAAE,EAAE,EAAE,EAAE,YAAY,EAAE,UAAU,EAAE,EAAE,MAAM,EAAE,KAAK,EAAE,EAAE;aAC1D,CAAC,CAAA;YACF,OAAO,EAAE,YAAY,EAAE,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE,QAAQ,CAAC,MAAM,EAAE,CAAA;QAC5D,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,MAAM,MAAM,GAAG,WAAW,CAAC,GAAG,CAAC,CAAA;YAC/B,MAAM,OAAO,GAAG,YAAY,CAAC,GAAG,CAAC,CAAA;YACjC,OAAO,EAAE,YAAY,EAAE,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,EAAE,CAAA;QAC5D,CAAC;IACH,CAAC;CACF"}
1
+ {"version":3,"file":"wasRemoteStore.js","sourceRoot":"","sources":["../../src/storage/wasRemoteStore.ts"],"names":[],"mappings":"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"}
@@ -4,7 +4,8 @@
4
4
  /**
5
5
  * Shared WAS replication bootstrap: given a parsed grant set and the invoking
6
6
  * ZcapClient, builds the delegated {@link WasRemoteStore}, best-effort marks
7
- * each collection encrypted, and starts the supplied {@link SyncController} with
7
+ * each private collection encrypted and declares each public collection's
8
+ * equality `indexes`, and starts the supplied {@link SyncController} with
8
9
  * reactive store patching. The caller injects the opened localStore, the
9
10
  * controller, and the per-doc `onRemoteChange` patcher (typically wired to the
10
11
  * rehydrate mechanism over the app's store registry) rather than this module
@@ -13,6 +14,7 @@
13
14
  import type { ZcapClient } from '@interop/ezcap';
14
15
  import type { RxChangeEvent } from 'rxdb/plugins/core';
15
16
  import type { SyncedDoc } from '../sync/index.js';
17
+ import type { WasCollectionConfig } from '../config.js';
16
18
  import type { ParsedGrants } from '../grants.js';
17
19
  import { WasRemoteStore } from './wasRemoteStore.js';
18
20
  import type { LocalStore } from './localStore.js';
@@ -23,6 +25,8 @@ import type { SyncController } from './syncController.js';
23
25
  * @param options {object}
24
26
  * @param options.parsed {ParsedGrants}
25
27
  * @param options.zcapClient {ZcapClient} invocation signer = grants' controller
28
+ * @param options.collections {WasCollectionConfig[]} the collection registry;
29
+ * public (plaintext) collections are never marked encrypted
26
30
  * @param options.localStore {LocalStore} the opened local encrypted replica
27
31
  * @param options.syncController {SyncController} a fresh per-session controller
28
32
  * @param options.onRemoteChange {(collectionKey, event) => void} per-doc
@@ -31,9 +35,10 @@ import type { SyncController } from './syncController.js';
31
35
  * 401/403 (expired/revoked access) -- wired to the reconnect banner
32
36
  * @returns {Promise<WasRemoteStore>}
33
37
  */
34
- export declare function startWasSync({ parsed, zcapClient, localStore, syncController, onRemoteChange, onAuthError }: {
38
+ export declare function startWasSync({ parsed, zcapClient, collections, localStore, syncController, onRemoteChange, onAuthError }: {
35
39
  parsed: ParsedGrants;
36
40
  zcapClient: ZcapClient;
41
+ collections: WasCollectionConfig[];
37
42
  localStore: LocalStore;
38
43
  syncController: SyncController;
39
44
  onRemoteChange: (collectionKey: string, event: RxChangeEvent<SyncedDoc>) => void;
@@ -1 +1 @@
1
- {"version":3,"file":"wasSync.d.ts","sourceRoot":"","sources":["../../src/storage/wasSync.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH;;;;;;;;GAQG;AACH,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,gBAAgB,CAAA;AAChD,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,mBAAmB,CAAA;AACtD,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAA;AACjD,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,cAAc,CAAA;AAChD,OAAO,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAA;AACpD,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAA;AACjD,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAA;AAEzD;;;;;;;;;;;;;GAaG;AACH,wBAAsB,YAAY,CAAC,EACjC,MAAM,EACN,UAAU,EACV,UAAU,EACV,cAAc,EACd,cAAc,EACd,WAAW,EACZ,EAAE;IACD,MAAM,EAAE,YAAY,CAAA;IACpB,UAAU,EAAE,UAAU,CAAA;IACtB,UAAU,EAAE,UAAU,CAAA;IACtB,cAAc,EAAE,cAAc,CAAA;IAC9B,cAAc,EAAE,CACd,aAAa,EAAE,MAAM,EACrB,KAAK,EAAE,aAAa,CAAC,SAAS,CAAC,KAC5B,IAAI,CAAA;IACT,WAAW,CAAC,EAAE,MAAM,IAAI,CAAA;CACzB,GAAG,OAAO,CAAC,cAAc,CAAC,CAuB1B"}
1
+ {"version":3,"file":"wasSync.d.ts","sourceRoot":"","sources":["../../src/storage/wasSync.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH;;;;;;;;;GASG;AACH,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,gBAAgB,CAAA;AAChD,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,mBAAmB,CAAA;AACtD,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAA;AACjD,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,cAAc,CAAA;AACvD,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,cAAc,CAAA;AAChD,OAAO,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAA;AACpD,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAA;AACjD,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAA;AAEzD;;;;;;;;;;;;;;;GAeG;AACH,wBAAsB,YAAY,CAAC,EACjC,MAAM,EACN,UAAU,EACV,WAAW,EACX,UAAU,EACV,cAAc,EACd,cAAc,EACd,WAAW,EACZ,EAAE;IACD,MAAM,EAAE,YAAY,CAAA;IACpB,UAAU,EAAE,UAAU,CAAA;IACtB,WAAW,EAAE,mBAAmB,EAAE,CAAA;IAClC,UAAU,EAAE,UAAU,CAAA;IACtB,cAAc,EAAE,cAAc,CAAA;IAC9B,cAAc,EAAE,CACd,aAAa,EAAE,MAAM,EACrB,KAAK,EAAE,aAAa,CAAC,SAAS,CAAC,KAC5B,IAAI,CAAA;IACT,WAAW,CAAC,EAAE,MAAM,IAAI,CAAA;CACzB,GAAG,OAAO,CAAC,cAAc,CAAC,CAqC1B"}
@@ -5,6 +5,8 @@ import { WasRemoteStore } from './wasRemoteStore.js';
5
5
  * @param options {object}
6
6
  * @param options.parsed {ParsedGrants}
7
7
  * @param options.zcapClient {ZcapClient} invocation signer = grants' controller
8
+ * @param options.collections {WasCollectionConfig[]} the collection registry;
9
+ * public (plaintext) collections are never marked encrypted
8
10
  * @param options.localStore {LocalStore} the opened local encrypted replica
9
11
  * @param options.syncController {SyncController} a fresh per-session controller
10
12
  * @param options.onRemoteChange {(collectionKey, event) => void} per-doc
@@ -13,14 +15,26 @@ import { WasRemoteStore } from './wasRemoteStore.js';
13
15
  * 401/403 (expired/revoked access) -- wired to the reconnect banner
14
16
  * @returns {Promise<WasRemoteStore>}
15
17
  */
16
- export async function startWasSync({ parsed, zcapClient, localStore, syncController, onRemoteChange, onAuthError }) {
17
- const remoteStore = WasRemoteStore.fromGrants({ parsed, zcapClient });
18
- // Best-effort encryption marker; non-fatal either way (envelopes replicate
19
- // into an unmarked collection just the same).
18
+ export async function startWasSync({ parsed, zcapClient, collections, localStore, syncController, onRemoteChange, onAuthError }) {
19
+ const remoteStore = WasRemoteStore.fromGrants({
20
+ parsed,
21
+ zcapClient,
22
+ collections
23
+ });
24
+ // Best-effort collection-description PUTs; non-fatal either way (envelopes
25
+ // replicate into an unmarked collection just the same, and a query against
26
+ // undeclared indexes fails with a descriptive 400). Each helper skips the
27
+ // collections it does not apply to (reported ok + skipped): the encryption
28
+ // marker skips public collections, the indexes declaration skips private
29
+ // ones and public ones with no declared indexes.
20
30
  await Promise.all(Object.keys(parsed.byCollectionId).map(async (collectionId) => {
21
- const result = await remoteStore.markCollectionEncrypted(collectionId);
22
- if (!result.ok) {
23
- console.warn(`Encryption marker PUT not authorized for "${collectionId}" (status ${result.status ?? 'n/a'}).`);
31
+ const marker = await remoteStore.markCollectionEncrypted(collectionId);
32
+ if (!marker.ok) {
33
+ console.warn(`Encryption marker PUT not authorized for "${collectionId}" (status ${marker.status ?? 'n/a'}).`);
34
+ }
35
+ const indexes = await remoteStore.declareCollectionIndexes(collectionId);
36
+ if (!indexes.ok) {
37
+ console.warn(`Indexes declaration PUT not authorized for "${collectionId}" (status ${indexes.status ?? 'n/a'}).`);
24
38
  }
25
39
  }));
26
40
  await syncController.start({
@@ -1 +1 @@
1
- {"version":3,"file":"wasSync.js","sourceRoot":"","sources":["../../src/storage/wasSync.ts"],"names":[],"mappings":"AAgBA,OAAO,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAA;AAIpD;;;;;;;;;;;;;GAaG;AACH,MAAM,CAAC,KAAK,UAAU,YAAY,CAAC,EACjC,MAAM,EACN,UAAU,EACV,UAAU,EACV,cAAc,EACd,cAAc,EACd,WAAW,EAWZ;IACC,MAAM,WAAW,GAAG,cAAc,CAAC,UAAU,CAAC,EAAE,MAAM,EAAE,UAAU,EAAE,CAAC,CAAA;IAErE,2EAA2E;IAC3E,8CAA8C;IAC9C,MAAM,OAAO,CAAC,GAAG,CACf,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,cAAc,CAAC,CAAC,GAAG,CAAC,KAAK,EAAC,YAAY,EAAC,EAAE;QAC1D,MAAM,MAAM,GAAG,MAAM,WAAW,CAAC,uBAAuB,CAAC,YAAY,CAAC,CAAA;QACtE,IAAI,CAAC,MAAM,CAAC,EAAE,EAAE,CAAC;YACf,OAAO,CAAC,IAAI,CACV,6CAA6C,YAAY,aAAa,MAAM,CAAC,MAAM,IAAI,KAAK,IAAI,CACjG,CAAA;QACH,CAAC;IACH,CAAC,CAAC,CACH,CAAA;IAED,MAAM,cAAc,CAAC,KAAK,CAAC;QACzB,WAAW;QACX,UAAU;QACV,cAAc;QACd,GAAG,CAAC,WAAW,IAAI,EAAE,WAAW,EAAE,CAAC;KACpC,CAAC,CAAA;IACF,OAAO,WAAW,CAAA;AACpB,CAAC"}
1
+ {"version":3,"file":"wasSync.js","sourceRoot":"","sources":["../../src/storage/wasSync.ts"],"names":[],"mappings":"AAkBA,OAAO,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAA;AAIpD;;;;;;;;;;;;;;;GAeG;AACH,MAAM,CAAC,KAAK,UAAU,YAAY,CAAC,EACjC,MAAM,EACN,UAAU,EACV,WAAW,EACX,UAAU,EACV,cAAc,EACd,cAAc,EACd,WAAW,EAYZ;IACC,MAAM,WAAW,GAAG,cAAc,CAAC,UAAU,CAAC;QAC5C,MAAM;QACN,UAAU;QACV,WAAW;KACZ,CAAC,CAAA;IAEF,2EAA2E;IAC3E,2EAA2E;IAC3E,0EAA0E;IAC1E,2EAA2E;IAC3E,yEAAyE;IACzE,iDAAiD;IACjD,MAAM,OAAO,CAAC,GAAG,CACf,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,cAAc,CAAC,CAAC,GAAG,CAAC,KAAK,EAAC,YAAY,EAAC,EAAE;QAC1D,MAAM,MAAM,GAAG,MAAM,WAAW,CAAC,uBAAuB,CAAC,YAAY,CAAC,CAAA;QACtE,IAAI,CAAC,MAAM,CAAC,EAAE,EAAE,CAAC;YACf,OAAO,CAAC,IAAI,CACV,6CAA6C,YAAY,aAAa,MAAM,CAAC,MAAM,IAAI,KAAK,IAAI,CACjG,CAAA;QACH,CAAC;QACD,MAAM,OAAO,GAAG,MAAM,WAAW,CAAC,wBAAwB,CAAC,YAAY,CAAC,CAAA;QACxE,IAAI,CAAC,OAAO,CAAC,EAAE,EAAE,CAAC;YAChB,OAAO,CAAC,IAAI,CACV,+CAA+C,YAAY,aAAa,OAAO,CAAC,MAAM,IAAI,KAAK,IAAI,CACpG,CAAA;QACH,CAAC;IACH,CAAC,CAAC,CACH,CAAA;IAED,MAAM,cAAc,CAAC,KAAK,CAAC;QACzB,WAAW;QACX,UAAU;QACV,cAAc;QACd,GAAG,CAAC,WAAW,IAAI,EAAE,WAAW,EAAE,CAAC;KACpC,CAAC,CAAA;IACF,OAAO,WAAW,CAAA;AACpB,CAAC"}
@@ -10,6 +10,10 @@
10
10
  * document under its EXISTING id, advancing the envelope `sequence` from the
11
11
  * prior envelope. `decrypt` reverses it. The sync layer moves the envelope
12
12
  * verbatim; it never touches these keys.
13
+ *
14
+ * A PUBLIC (plaintext) collection uses {@link createPlaintextDocCodec} instead:
15
+ * the same {@link DocCipher} seam with pass-through implementations, so the
16
+ * storage layer above needs no encrypted-vs-plaintext fork.
13
17
  */
14
18
  import type { IKeyAgreementKey, IKeyResolver } from '@interop/data-integrity-core';
15
19
  import type { Json } from './types.js';
@@ -47,6 +51,24 @@ export interface DocCipher {
47
51
  * @returns {boolean}
48
52
  */
49
53
  export declare function isEncryptedEnvelope(data: Json | undefined): boolean;
54
+ /**
55
+ * Builds the pass-through codec for a PUBLIC (plaintext) collection: payloads
56
+ * are stored as-is, and the stored resource id IS the payload's logical `id`
57
+ * (uuid). With nothing to hide there is no reason for a second, opaque id
58
+ * plane, and a public document then keeps a stable, shareable resource URL
59
+ * across edits. `decrypt` refuses an EDV envelope (via
60
+ * {@link isEncryptedEnvelope}) rather than mis-reading its random envelope id
61
+ * as a logical uuid -- a public collection holding ciphertext rows is a
62
+ * visibility misconfiguration, surfaced as a read error instead of silent
63
+ * garbage.
64
+ *
65
+ * @param options {object}
66
+ * @param options.collectionId {string} labels errors only
67
+ * @returns {DocCipher}
68
+ */
69
+ export declare function createPlaintextDocCodec({ collectionId }: {
70
+ collectionId: string;
71
+ }): DocCipher;
50
72
  /**
51
73
  * Builds a {@link DocCipher} for one collection from its derived key material
52
74
  * (the per-collection X25519 key agreement key). Keys are supplied directly (no