@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.
- package/README.md +103 -3
- package/dist/auth/loginFlow.d.ts +4 -2
- package/dist/auth/loginFlow.d.ts.map +1 -1
- package/dist/auth/loginFlow.js +1 -1
- package/dist/auth/loginFlow.js.map +1 -1
- package/dist/auth/loginRequest.d.ts +29 -3
- package/dist/auth/loginRequest.d.ts.map +1 -1
- package/dist/auth/loginRequest.js +12 -4
- package/dist/auth/loginRequest.js.map +1 -1
- package/dist/config.d.ts +38 -0
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +0 -0
- package/dist/config.js.map +1 -1
- package/dist/identity/documentLoader.d.ts +2 -8
- package/dist/identity/documentLoader.d.ts.map +1 -1
- package/dist/identity/documentLoader.js +7 -88
- package/dist/identity/documentLoader.js.map +1 -1
- package/dist/index.d.ts +5 -4
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -2
- package/dist/index.js.map +1 -1
- package/dist/session/authStore.d.ts.map +1 -1
- package/dist/session/authStore.js +21 -9
- package/dist/session/authStore.js.map +1 -1
- package/dist/storage/entityStore.d.ts +20 -0
- package/dist/storage/entityStore.d.ts.map +1 -1
- package/dist/storage/entityStore.js +24 -1
- package/dist/storage/entityStore.js.map +1 -1
- package/dist/storage/localStore.d.ts +30 -13
- package/dist/storage/localStore.d.ts.map +1 -1
- package/dist/storage/localStore.js +56 -18
- package/dist/storage/localStore.js.map +1 -1
- package/dist/storage/publicUrl.d.ts +20 -0
- package/dist/storage/publicUrl.d.ts.map +1 -0
- package/dist/storage/publicUrl.js +33 -0
- package/dist/storage/publicUrl.js.map +1 -0
- package/dist/storage/storageManager.d.ts +28 -0
- package/dist/storage/storageManager.d.ts.map +1 -1
- package/dist/storage/storageManager.js +42 -2
- package/dist/storage/storageManager.js.map +1 -1
- package/dist/storage/wasRemoteStore.d.ts +105 -3
- package/dist/storage/wasRemoteStore.d.ts.map +1 -1
- package/dist/storage/wasRemoteStore.js +167 -5
- package/dist/storage/wasRemoteStore.js.map +1 -1
- package/dist/storage/wasSync.d.ts +7 -2
- package/dist/storage/wasSync.d.ts.map +1 -1
- package/dist/storage/wasSync.js +21 -7
- package/dist/storage/wasSync.js.map +1 -1
- package/dist/sync/docCipher.d.ts +22 -0
- package/dist/sync/docCipher.d.ts.map +1 -1
- package/dist/sync/docCipher.js +42 -0
- package/dist/sync/docCipher.js.map +1 -1
- package/dist/sync/index.d.ts +1 -1
- package/dist/sync/index.d.ts.map +1 -1
- package/dist/sync/index.js +1 -1
- package/dist/sync/index.js.map +1 -1
- 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":"
|
|
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
|
|
7
|
-
*
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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:
|
|
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":"
|
|
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
|
|
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
|
|
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"}
|
package/dist/storage/wasSync.js
CHANGED
|
@@ -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({
|
|
18
|
-
|
|
19
|
-
|
|
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
|
|
22
|
-
if (!
|
|
23
|
-
console.warn(`Encryption marker PUT not authorized for "${collectionId}" (status ${
|
|
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":"
|
|
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"}
|
package/dist/sync/docCipher.d.ts
CHANGED
|
@@ -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
|