@interop/was-react 0.11.1 → 0.13.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +23 -25
- package/dist/auth/loginFlow.d.ts +8 -5
- package/dist/auth/loginFlow.d.ts.map +1 -1
- package/dist/auth/loginFlow.js +46 -16
- package/dist/auth/loginFlow.js.map +1 -1
- package/dist/auth/loginRequest.d.ts +27 -81
- package/dist/auth/loginRequest.d.ts.map +1 -1
- package/dist/auth/loginRequest.js +94 -41
- package/dist/auth/loginRequest.js.map +1 -1
- package/dist/auth/verifyResponse.d.ts +3 -3
- package/dist/auth/verifyResponse.d.ts.map +1 -1
- package/dist/auth/verifyResponse.js +16 -13
- package/dist/auth/verifyResponse.js.map +1 -1
- package/dist/auth/walletRequestTypes.d.ts +8 -6
- package/dist/auth/walletRequestTypes.d.ts.map +1 -1
- package/dist/config.d.ts +19 -13
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +0 -0
- package/dist/config.js.map +1 -1
- package/dist/dev/provisionDevGrants.d.ts.map +1 -1
- package/dist/dev/provisionDevGrants.js +7 -12
- package/dist/dev/provisionDevGrants.js.map +1 -1
- package/dist/grants.d.ts +0 -14
- package/dist/grants.d.ts.map +1 -1
- package/dist/grants.js +0 -13
- package/dist/grants.js.map +1 -1
- package/dist/identity/seedCredential.d.ts +39 -38
- package/dist/identity/seedCredential.d.ts.map +1 -1
- package/dist/identity/seedCredential.js +79 -77
- package/dist/identity/seedCredential.js.map +1 -1
- package/dist/index.d.ts +3 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -3
- package/dist/index.js.map +1 -1
- package/dist/react/documentApp.d.ts +6 -6
- package/dist/react/documentApp.d.ts.map +1 -1
- package/dist/react/documentApp.js +7 -6
- package/dist/react/documentApp.js.map +1 -1
- package/dist/react/hooks.d.ts +1 -1
- package/dist/react/hooks.js +1 -1
- package/dist/session/authStore.d.ts +1 -1
- package/dist/session/authStore.js +6 -6
- package/dist/session/authStore.js.map +1 -1
- package/dist/storage/adopt.d.ts +3 -3
- package/dist/storage/adopt.js +5 -5
- package/dist/storage/entityStore.d.ts +1 -1
- package/dist/storage/localStore.d.ts +2 -2
- package/dist/storage/localStore.js +2 -2
- package/dist/storage/storageManager.d.ts +8 -3
- package/dist/storage/storageManager.d.ts.map +1 -1
- package/dist/storage/storageManager.js +19 -12
- package/dist/storage/storageManager.js.map +1 -1
- package/dist/storage/wasRemoteStore.d.ts +1 -1
- package/dist/storage/wasRemoteStore.d.ts.map +1 -1
- package/dist/storage/wasRemoteStore.js +8 -8
- package/dist/storage/wasRemoteStore.js.map +1 -1
- package/dist/sync/index.d.ts +2 -2
- package/dist/sync/index.d.ts.map +1 -1
- package/dist/sync/index.js +2 -2
- package/dist/sync/index.js.map +1 -1
- package/dist/sync/lww.d.ts +4 -4
- package/dist/sync/lww.js +7 -7
- package/dist/sync/lwwConflictHandler.d.ts +4 -4
- package/dist/sync/lwwConflictHandler.js +1 -1
- package/dist/sync/types.d.ts +28 -45
- package/dist/sync/types.d.ts.map +1 -1
- package/dist/sync/types.js +17 -35
- package/dist/sync/types.js.map +1 -1
- package/dist/sync/wasSyncPort.d.ts +14 -31
- package/dist/sync/wasSyncPort.d.ts.map +1 -1
- package/dist/sync/wasSyncPort.js +27 -169
- package/dist/sync/wasSyncPort.js.map +1 -1
- package/package.json +3 -3
package/dist/sync/index.js
CHANGED
|
@@ -13,12 +13,12 @@ export { syncedDocSchema } from './syncedDocSchema.js';
|
|
|
13
13
|
export { createPullHandler, wireDocToRxDoc } from './changesQuery.js';
|
|
14
14
|
export { createPushHandler } from './pushWrites.js';
|
|
15
15
|
export { WasSyncConflictError, WasSyncAuthError } from './types.js';
|
|
16
|
-
export { createWasSyncPort
|
|
16
|
+
export { createWasSyncPort } from './wasSyncPort.js';
|
|
17
17
|
export { withFeedMasterRead } from './feedMasterPort.js';
|
|
18
18
|
export { createDocCipher, createPlaintextDocCodec, createUnprovisionedDocCipher, hasKeyEpochs, isUnknownEpochError } from './docCipher.js';
|
|
19
19
|
export { makeLwwConflictHandler } from './lwwConflictHandler.js';
|
|
20
20
|
export { lwwFields, remotePayloadWins } from './lww.js';
|
|
21
21
|
// Owned by the client's sync subpath and re-exported verbatim, so the names this
|
|
22
22
|
// package's consumers already import stay put.
|
|
23
|
-
export { errorStatus, formatEtag, isEncryptedEnvelope } from '@interop/was-client/sync';
|
|
23
|
+
export { errorStatus, errorMessage, formatEtag, isEncryptedEnvelope } from '@interop/was-client/sync';
|
|
24
24
|
//# sourceMappingURL=index.js.map
|
package/dist/sync/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/sync/index.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH;;;;;;GAMG;AACH,OAAO,EAAE,oBAAoB,EAAE,MAAM,qBAAqB,CAAA;AAC1D,OAAO,EAAE,eAAe,EAAE,MAAM,sBAAsB,CAAA;AACtD,OAAO,EAAE,iBAAiB,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAA;AACrE,OAAO,EAAE,iBAAiB,EAAqB,MAAM,iBAAiB,CAAA;AACtE,OAAO,EACL,oBAAoB,EACpB,gBAAgB,EASjB,MAAM,YAAY,CAAA;AACnB,OAAO,EAAE,iBAAiB,EAAE,
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/sync/index.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH;;;;;;GAMG;AACH,OAAO,EAAE,oBAAoB,EAAE,MAAM,qBAAqB,CAAA;AAC1D,OAAO,EAAE,eAAe,EAAE,MAAM,sBAAsB,CAAA;AACtD,OAAO,EAAE,iBAAiB,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAA;AACrE,OAAO,EAAE,iBAAiB,EAAqB,MAAM,iBAAiB,CAAA;AACtE,OAAO,EACL,oBAAoB,EACpB,gBAAgB,EASjB,MAAM,YAAY,CAAA;AACnB,OAAO,EAAE,iBAAiB,EAAE,MAAM,kBAAkB,CAAA;AACpD,OAAO,EAAE,kBAAkB,EAAE,MAAM,qBAAqB,CAAA;AACxD,OAAO,EACL,eAAe,EACf,uBAAuB,EACvB,4BAA4B,EAC5B,YAAY,EACZ,mBAAmB,EAEpB,MAAM,gBAAgB,CAAA;AACvB,OAAO,EAAE,sBAAsB,EAAE,MAAM,yBAAyB,CAAA;AAChE,OAAO,EAAE,SAAS,EAAE,iBAAiB,EAAkB,MAAM,UAAU,CAAA;AACvE,iFAAiF;AACjF,+CAA+C;AAC/C,OAAO,EACL,WAAW,EACX,YAAY,EACZ,UAAU,EACV,mBAAmB,EACpB,MAAM,0BAA0B,CAAA"}
|
package/dist/sync/lww.d.ts
CHANGED
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
* `updatedAt` (an ISO-8601 string) decides, compared CHRONOLOGICALLY (parsed to
|
|
10
10
|
* epoch ms, never lexically -- `updatedAt` is app-owned, so mixed precision like
|
|
11
11
|
* `...05Z` vs `...05.400Z`, or a `+00:00` offset, must still order by instant);
|
|
12
|
-
* `
|
|
12
|
+
* `writerId` breaks an exact-instant tie deterministically. Both fields live in
|
|
13
13
|
* the payload (never the envelope-level checkpoint `updatedAt`).
|
|
14
14
|
*/
|
|
15
15
|
/**
|
|
@@ -20,12 +20,12 @@
|
|
|
20
20
|
*/
|
|
21
21
|
export interface LwwFields {
|
|
22
22
|
updatedAt: string;
|
|
23
|
-
|
|
23
|
+
writerId: string;
|
|
24
24
|
}
|
|
25
25
|
/**
|
|
26
26
|
* Whether the remote payload wins over the local one under last-write-wins.
|
|
27
27
|
* The chronologically later `updatedAt` wins; on an exact-instant tie the
|
|
28
|
-
* lexically greater `
|
|
28
|
+
* lexically greater `writerId` wins (an arbitrary but deterministic,
|
|
29
29
|
* replica-independent choice).
|
|
30
30
|
*
|
|
31
31
|
* Unparseable `updatedAt` values are rejected as losers: a side whose stamp
|
|
@@ -40,7 +40,7 @@ export interface LwwFields {
|
|
|
40
40
|
export declare function remotePayloadWins(remote: LwwFields, local: LwwFields): boolean;
|
|
41
41
|
/**
|
|
42
42
|
* Reads the LWW fields off a doc when it carries them. Storage payloads are
|
|
43
|
-
* generic over `{ id: string }`, so docs without `updatedAt`/`
|
|
43
|
+
* generic over `{ id: string }`, so docs without `updatedAt`/`writerId` are
|
|
44
44
|
* legal; callers fall back to their own rule for those.
|
|
45
45
|
*
|
|
46
46
|
* @param doc {unknown}
|
package/dist/sync/lww.js
CHANGED
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
* `updatedAt` (an ISO-8601 string) decides, compared CHRONOLOGICALLY (parsed to
|
|
10
10
|
* epoch ms, never lexically -- `updatedAt` is app-owned, so mixed precision like
|
|
11
11
|
* `...05Z` vs `...05.400Z`, or a `+00:00` offset, must still order by instant);
|
|
12
|
-
* `
|
|
12
|
+
* `writerId` breaks an exact-instant tie deterministically. Both fields live in
|
|
13
13
|
* the payload (never the envelope-level checkpoint `updatedAt`).
|
|
14
14
|
*/
|
|
15
15
|
/**
|
|
@@ -22,7 +22,7 @@ function parseInstant(updatedAt) {
|
|
|
22
22
|
/**
|
|
23
23
|
* Whether the remote payload wins over the local one under last-write-wins.
|
|
24
24
|
* The chronologically later `updatedAt` wins; on an exact-instant tie the
|
|
25
|
-
* lexically greater `
|
|
25
|
+
* lexically greater `writerId` wins (an arbitrary but deterministic,
|
|
26
26
|
* replica-independent choice).
|
|
27
27
|
*
|
|
28
28
|
* Unparseable `updatedAt` values are rejected as losers: a side whose stamp
|
|
@@ -50,20 +50,20 @@ export function remotePayloadWins(remote, local) {
|
|
|
50
50
|
// Neither parses: fall back to the deterministic lexical compare.
|
|
51
51
|
return remote.updatedAt > local.updatedAt;
|
|
52
52
|
}
|
|
53
|
-
return remote.
|
|
53
|
+
return remote.writerId > local.writerId;
|
|
54
54
|
}
|
|
55
55
|
/**
|
|
56
56
|
* Reads the LWW fields off a doc when it carries them. Storage payloads are
|
|
57
|
-
* generic over `{ id: string }`, so docs without `updatedAt`/`
|
|
57
|
+
* generic over `{ id: string }`, so docs without `updatedAt`/`writerId` are
|
|
58
58
|
* legal; callers fall back to their own rule for those.
|
|
59
59
|
*
|
|
60
60
|
* @param doc {unknown}
|
|
61
61
|
* @returns {LwwFields | null}
|
|
62
62
|
*/
|
|
63
63
|
export function lwwFields(doc) {
|
|
64
|
-
const { updatedAt,
|
|
65
|
-
return typeof updatedAt === 'string' && typeof
|
|
66
|
-
? { updatedAt,
|
|
64
|
+
const { updatedAt, writerId } = doc;
|
|
65
|
+
return typeof updatedAt === 'string' && typeof writerId === 'string'
|
|
66
|
+
? { updatedAt, writerId }
|
|
67
67
|
: null;
|
|
68
68
|
}
|
|
69
69
|
//# sourceMappingURL=lww.js.map
|
|
@@ -7,17 +7,17 @@
|
|
|
7
7
|
* correct for content-addressed (immutable-per-id) collections but wrong here:
|
|
8
8
|
* every entity is a mutable head that two devices can edit concurrently, so a
|
|
9
9
|
* genuine content conflict must be settled by last-write-wins on the payload's
|
|
10
|
-
* own `updatedAt` (
|
|
10
|
+
* own `updatedAt` (writerId tiebreak) -- exactly the rule two offline replicas
|
|
11
11
|
* apply independently to converge.
|
|
12
12
|
*
|
|
13
13
|
* The wrinkle is that the conflicting bodies are EDV envelopes (ciphertext), so
|
|
14
14
|
* `resolve` must decrypt both sides through this collection's cipher before it
|
|
15
|
-
* can compare the plaintext `updatedAt` / `
|
|
15
|
+
* can compare the plaintext `updatedAt` / `writerId`. `isEqual` stays cheap and
|
|
16
16
|
* synchronous (a structural compare of the opaque bodies), as RxDB requires.
|
|
17
17
|
*
|
|
18
18
|
* Convergence: the server holds ONE winner of the push race as `realMasterState`;
|
|
19
19
|
* every replica compares that same master against its own local edit, and the
|
|
20
|
-
* `payloadWins` comparator is a total order over `(updatedAt,
|
|
20
|
+
* `payloadWins` comparator is a total order over `(updatedAt, writerId)`, so the
|
|
21
21
|
* globally-latest payload wins on every replica with no coordination.
|
|
22
22
|
*
|
|
23
23
|
* The resolution rules, in order:
|
|
@@ -69,7 +69,7 @@ import { type LwwFields } from './lww.js';
|
|
|
69
69
|
* @param [payloadWins] {(remote: LwwFields, local: LwwFields) => boolean}
|
|
70
70
|
* the total-order comparator deciding whether the remote payload replaces the
|
|
71
71
|
* local one; defaults to {@link remotePayloadWins} (later `updatedAt` wins,
|
|
72
|
-
* `
|
|
72
|
+
* `writerId` breaks a tie)
|
|
73
73
|
* @returns {import('rxdb/plugins/core').RxConflictHandler<SyncedDoc>}
|
|
74
74
|
*/
|
|
75
75
|
export declare function makeLwwConflictHandler(decrypt: (envelope: Json) => Promise<Json>, payloadWins?: (remote: LwwFields, local: LwwFields) => boolean): {
|
|
@@ -25,7 +25,7 @@ async function lwwFieldsOf(doc, decrypt) {
|
|
|
25
25
|
* @param [payloadWins] {(remote: LwwFields, local: LwwFields) => boolean}
|
|
26
26
|
* the total-order comparator deciding whether the remote payload replaces the
|
|
27
27
|
* local one; defaults to {@link remotePayloadWins} (later `updatedAt` wins,
|
|
28
|
-
* `
|
|
28
|
+
* `writerId` breaks a tie)
|
|
29
29
|
* @returns {import('rxdb/plugins/core').RxConflictHandler<SyncedDoc>}
|
|
30
30
|
*/
|
|
31
31
|
export function makeLwwConflictHandler(decrypt, payloadWins = remotePayloadWins) {
|
package/dist/sync/types.d.ts
CHANGED
|
@@ -25,6 +25,24 @@
|
|
|
25
25
|
* copy every wire-to-local mapping runs.
|
|
26
26
|
*/
|
|
27
27
|
import type { Json } from '@interop/was-client';
|
|
28
|
+
import type { MasterState as ClientMasterState, SyncCheckpoint as ClientSyncCheckpoint } from '@interop/was-client/sync';
|
|
29
|
+
/**
|
|
30
|
+
* The typed port signals, owned by `@interop/was-client` (its sync port raises
|
|
31
|
+
* them) and re-exported here so the names this package's consumers already
|
|
32
|
+
* import stay put -- and so an `instanceof` check anywhere in this package
|
|
33
|
+
* matches the very class the port throws.
|
|
34
|
+
*
|
|
35
|
+
* `WasSyncConflictError` marks a conditional write rejected with `412
|
|
36
|
+
* precondition-failed` (a lost-update conflict, or a create-if-absent whose
|
|
37
|
+
* target already exists); the push handler catches exactly it to trigger the
|
|
38
|
+
* re-read-and-report-conflict path. `WasSyncAuthError` marks a request refused
|
|
39
|
+
* on authorization grounds -- `401`, `403`, or the `404` a WAS server masks a
|
|
40
|
+
* failed capability invocation as, which on the sync paths (where the invoked
|
|
41
|
+
* collection is known to exist) means the invocation was rejected. It keeps the
|
|
42
|
+
* offending HTTP status on `status`, which the `/meta` push path reads to tell
|
|
43
|
+
* a masked denial from an ordinary delete race.
|
|
44
|
+
*/
|
|
45
|
+
export { WasSyncAuthError, WasSyncConflictError } from '@interop/was-client/sync';
|
|
28
46
|
/**
|
|
29
47
|
* A JSON value -- the opaque stored resource body the sync layer moves verbatim.
|
|
30
48
|
* For a plaintext collection this is the user document; for an encrypted one it
|
|
@@ -80,12 +98,10 @@ export declare function copyOptionalBodyFields({ source, target }: {
|
|
|
80
98
|
* The keyset position in the change feed: the `{ id, updatedAt }` of the last
|
|
81
99
|
* document a pull returned. Passed back verbatim to resume, and used as the
|
|
82
100
|
* RxDB replication checkpoint. `id` is the total-order tiebreaker within a
|
|
83
|
-
* single `updatedAt`.
|
|
101
|
+
* single `updatedAt`. The client's own checkpoint type, aliased here so the
|
|
102
|
+
* replication layer and the port agree by construction.
|
|
84
103
|
*/
|
|
85
|
-
export
|
|
86
|
-
id: string;
|
|
87
|
-
updatedAt: string;
|
|
88
|
-
}
|
|
104
|
+
export type SyncCheckpoint = ClientSyncCheckpoint;
|
|
89
105
|
/**
|
|
90
106
|
* One document as it travels on the `changes` feed wire
|
|
91
107
|
* (`POST /space/:s/:c/query`, profile `changes`). `id` is the WAS resourceId,
|
|
@@ -136,20 +152,15 @@ export interface SyncedDoc {
|
|
|
136
152
|
}
|
|
137
153
|
/**
|
|
138
154
|
* The current master state of a single resource, as read back for the 412
|
|
139
|
-
* conflict path
|
|
140
|
-
* `metaVersion` / `
|
|
155
|
+
* conflict path: the client's own `MasterState` (content `version` /
|
|
156
|
+
* `updatedAt`, plus the optional `metaVersion` / `data` / `custom` /
|
|
157
|
+
* `createdBy` / `epoch`) plus the `deleted` flag that distinguishes a tombstone
|
|
158
|
+
* from a live resource. The flag is required here because this package resolves
|
|
159
|
+
* the master from the changes feed, where a tombstone travels as a document
|
|
160
|
+
* rather than as an absent read.
|
|
141
161
|
*/
|
|
142
|
-
export interface MasterState {
|
|
143
|
-
version: number;
|
|
144
|
-
updatedAt: string;
|
|
162
|
+
export interface MasterState extends ClientMasterState {
|
|
145
163
|
deleted: boolean;
|
|
146
|
-
metaVersion?: number;
|
|
147
|
-
data?: Json;
|
|
148
|
-
custom?: Json;
|
|
149
|
-
/**
|
|
150
|
-
* The key-epoch stamp of the master's content body, when the feed carries one.
|
|
151
|
-
*/
|
|
152
|
-
epoch?: string;
|
|
153
164
|
}
|
|
154
165
|
/**
|
|
155
166
|
* The short-lived master-read memo the rows of ONE push batch share. A
|
|
@@ -305,32 +316,4 @@ export interface WasSyncPort extends WasSyncBasePort {
|
|
|
305
316
|
cache?: MasterReadCache;
|
|
306
317
|
}): Promise<MasterState | null>;
|
|
307
318
|
}
|
|
308
|
-
/**
|
|
309
|
-
* Thrown by a {@link WasSyncPort} implementation when a conditional write is
|
|
310
|
-
* rejected with `412 precondition-failed` (a lost-update conflict, or a
|
|
311
|
-
* create-if-absent whose target already exists). The core push handler catches
|
|
312
|
-
* exactly this type to trigger the re-read-and-report-conflict path; any other
|
|
313
|
-
* error propagates to RxDB for retry.
|
|
314
|
-
*/
|
|
315
|
-
export declare class WasSyncConflictError extends Error {
|
|
316
|
-
constructor(message?: string);
|
|
317
|
-
}
|
|
318
|
-
/**
|
|
319
|
-
* Thrown by a {@link WasSyncPort} implementation when a request is rejected with
|
|
320
|
-
* `401 unauthorized`, `403 forbidden`, or `404 not found` -- the
|
|
321
|
-
* storage-access-expired/revoked signal. `404` qualifies because a WAS server
|
|
322
|
-
* masks a failed capability invocation as `404` (never confirming to an
|
|
323
|
-
* unauthorized caller whether the resource exists); on the sync paths the
|
|
324
|
-
* invoked collection is known to exist, so a `404` means the invocation was
|
|
325
|
-
* rejected. Mapping the raw HTTP status to a typed error at the port boundary
|
|
326
|
-
* (the same way `412` becomes {@link WasSyncConflictError}) lets the sync
|
|
327
|
-
* controller recognise expired access with an `instanceof` check rather than
|
|
328
|
-
* re-extracting status codes from a wrapped RxDB error graph. The offending
|
|
329
|
-
* HTTP status is kept on `status` -- diagnostics, plus the delete path's
|
|
330
|
-
* already-absent-404 check.
|
|
331
|
-
*/
|
|
332
|
-
export declare class WasSyncAuthError extends Error {
|
|
333
|
-
readonly status: number;
|
|
334
|
-
constructor(status: number);
|
|
335
|
-
}
|
|
336
319
|
//# sourceMappingURL=types.d.ts.map
|
package/dist/sync/types.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/sync/types.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,qBAAqB,CAAA;
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/sync/types.ts"],"names":[],"mappings":"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"}
|
package/dist/sync/types.js
CHANGED
|
@@ -1,3 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The typed port signals, owned by `@interop/was-client` (its sync port raises
|
|
3
|
+
* them) and re-exported here so the names this package's consumers already
|
|
4
|
+
* import stay put -- and so an `instanceof` check anywhere in this package
|
|
5
|
+
* matches the very class the port throws.
|
|
6
|
+
*
|
|
7
|
+
* `WasSyncConflictError` marks a conditional write rejected with `412
|
|
8
|
+
* precondition-failed` (a lost-update conflict, or a create-if-absent whose
|
|
9
|
+
* target already exists); the push handler catches exactly it to trigger the
|
|
10
|
+
* re-read-and-report-conflict path. `WasSyncAuthError` marks a request refused
|
|
11
|
+
* on authorization grounds -- `401`, `403`, or the `404` a WAS server masks a
|
|
12
|
+
* failed capability invocation as, which on the sync paths (where the invoked
|
|
13
|
+
* collection is known to exist) means the invocation was rejected. It keeps the
|
|
14
|
+
* offending HTTP status on `status`, which the `/meta` push path reads to tell
|
|
15
|
+
* a masked denial from an ordinary delete race.
|
|
16
|
+
*/
|
|
17
|
+
export { WasSyncAuthError, WasSyncConflictError } from '@interop/was-client/sync';
|
|
1
18
|
/**
|
|
2
19
|
* Structural equality over two opaque bodies, by canonical-free JSON string.
|
|
3
20
|
* Decides whether the content or the metadata half changed -- which endpoint(s)
|
|
@@ -38,39 +55,4 @@ export function copyOptionalBodyFields({ source, target }) {
|
|
|
38
55
|
target.epoch = source.epoch;
|
|
39
56
|
}
|
|
40
57
|
}
|
|
41
|
-
/**
|
|
42
|
-
* Thrown by a {@link WasSyncPort} implementation when a conditional write is
|
|
43
|
-
* rejected with `412 precondition-failed` (a lost-update conflict, or a
|
|
44
|
-
* create-if-absent whose target already exists). The core push handler catches
|
|
45
|
-
* exactly this type to trigger the re-read-and-report-conflict path; any other
|
|
46
|
-
* error propagates to RxDB for retry.
|
|
47
|
-
*/
|
|
48
|
-
export class WasSyncConflictError extends Error {
|
|
49
|
-
constructor(message = 'WAS conditional write precondition failed.') {
|
|
50
|
-
super(message);
|
|
51
|
-
this.name = 'WasSyncConflictError';
|
|
52
|
-
}
|
|
53
|
-
}
|
|
54
|
-
/**
|
|
55
|
-
* Thrown by a {@link WasSyncPort} implementation when a request is rejected with
|
|
56
|
-
* `401 unauthorized`, `403 forbidden`, or `404 not found` -- the
|
|
57
|
-
* storage-access-expired/revoked signal. `404` qualifies because a WAS server
|
|
58
|
-
* masks a failed capability invocation as `404` (never confirming to an
|
|
59
|
-
* unauthorized caller whether the resource exists); on the sync paths the
|
|
60
|
-
* invoked collection is known to exist, so a `404` means the invocation was
|
|
61
|
-
* rejected. Mapping the raw HTTP status to a typed error at the port boundary
|
|
62
|
-
* (the same way `412` becomes {@link WasSyncConflictError}) lets the sync
|
|
63
|
-
* controller recognise expired access with an `instanceof` check rather than
|
|
64
|
-
* re-extracting status codes from a wrapped RxDB error graph. The offending
|
|
65
|
-
* HTTP status is kept on `status` -- diagnostics, plus the delete path's
|
|
66
|
-
* already-absent-404 check.
|
|
67
|
-
*/
|
|
68
|
-
export class WasSyncAuthError extends Error {
|
|
69
|
-
status;
|
|
70
|
-
constructor(status) {
|
|
71
|
-
super(`WAS storage access denied (HTTP ${status}).`);
|
|
72
|
-
this.name = 'WasSyncAuthError';
|
|
73
|
-
this.status = status;
|
|
74
|
-
}
|
|
75
|
-
}
|
|
76
58
|
//# sourceMappingURL=types.js.map
|
package/dist/sync/types.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/sync/types.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/sync/types.ts"],"names":[],"mappings":"AAgCA;;;;;;;;;;;;;;;GAeG;AACH,OAAO,EACL,gBAAgB,EAChB,oBAAoB,EACrB,MAAM,0BAA0B,CAAA;AA2BjC;;;;;;;;;;GAUG;AACH,MAAM,UAAU,WAAW,CACzB,IAAsB,EACtB,KAAuB;IAEvB,OAAO,IAAI,CAAC,SAAS,CAAC,IAAI,IAAI,IAAI,CAAC,KAAK,IAAI,CAAC,SAAS,CAAC,KAAK,IAAI,IAAI,CAAC,CAAA;AACvE,CAAC;AAED;;;;;;;;;;;GAWG;AACH,MAAM,UAAU,sBAAsB,CAAC,EACrC,MAAM,EACN,MAAM,EAIP;IACC,IAAI,MAAM,CAAC,IAAI,KAAK,SAAS,EAAE,CAAC;QAC9B,MAAM,CAAC,IAAI,GAAG,MAAM,CAAC,IAAI,CAAA;IAC3B,CAAC;IACD,IAAI,MAAM,CAAC,WAAW,KAAK,SAAS,EAAE,CAAC;QACrC,MAAM,CAAC,WAAW,GAAG,MAAM,CAAC,WAAW,CAAA;IACzC,CAAC;IACD,IAAI,MAAM,CAAC,MAAM,KAAK,SAAS,EAAE,CAAC;QAChC,MAAM,CAAC,MAAM,GAAG,MAAM,CAAC,MAAM,CAAA;IAC/B,CAAC;IACD,IAAI,MAAM,CAAC,KAAK,KAAK,SAAS,EAAE,CAAC;QAC/B,MAAM,CAAC,KAAK,GAAG,MAAM,CAAC,KAAK,CAAA;IAC7B,CAAC;AACH,CAAC"}
|
|
@@ -3,41 +3,24 @@
|
|
|
3
3
|
*/
|
|
4
4
|
/**
|
|
5
5
|
* The app-side seam between the generic sync adapter and `@interop/was-client`.
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
6
|
+
* Delegates to the client's own `createWasSyncPort` -- which owns the WAS URL
|
|
7
|
+
* grammar, the verbatim (codec-bypassing) `was.request()` writes, the `changes`
|
|
8
|
+
* feed pull, the key-epoch header, and the conditional-write ETag handling --
|
|
9
|
+
* and pins the two options this package needs: the collection's delegated
|
|
10
|
+
* capability, and `mapAuthErrors` so a `401` / `403` / the server's masked `404`
|
|
11
|
+
* arrive as a `WasSyncAuthError` the sync controller can `instanceof`-match.
|
|
11
12
|
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
* side must write those same bytes back unchanged -- running them through
|
|
17
|
-
* `resource.put()` would re-encrypt an already-encrypted envelope. Encrypt /
|
|
18
|
-
* decrypt stays a read/write-time concern above this layer; this port is
|
|
19
|
-
* collection-agnostic and never touches keys.
|
|
20
|
-
*
|
|
21
|
-
* Conditional writes ride the server's monotonic `version` (content) and
|
|
22
|
-
* `metaVersion` (metadata) ETags, which the reference server enforces uniformly
|
|
23
|
-
* for plaintext and encrypted resources alike -- so there is no plaintext-vs-
|
|
24
|
-
* encrypted fork here.
|
|
13
|
+
* What stays here is the narrowing to a {@link WasSyncBasePort}: the 412
|
|
14
|
+
* conflict re-read (`get`) is supplied separately by `withFeedMasterRead`, which
|
|
15
|
+
* resolves the master state from the changes-feed BODY -- origin-independent,
|
|
16
|
+
* unlike a cross-origin `GET` whose ETag header CORS hides.
|
|
25
17
|
*/
|
|
26
|
-
import {
|
|
18
|
+
import type { WasClient } from '@interop/was-client';
|
|
27
19
|
import type { IZcap } from '@interop/data-integrity-core';
|
|
28
|
-
import {
|
|
29
|
-
/**
|
|
30
|
-
* Normalizes an unknown caught error into a display string: the `Error`'s
|
|
31
|
-
* `message` when it is one, else its `String(...)` coercion. Shared by the
|
|
32
|
-
* remote store's descriptor PUT and the session activation error path.
|
|
33
|
-
*
|
|
34
|
-
* @param err {unknown}
|
|
35
|
-
* @returns {string}
|
|
36
|
-
*/
|
|
37
|
-
export declare function errorMessage(err: unknown): string;
|
|
20
|
+
import type { WasSyncBasePort } from './types.js';
|
|
38
21
|
/**
|
|
39
|
-
* Builds a `
|
|
40
|
-
* server, backed by the session's signed `WasClient`.
|
|
22
|
+
* Builds a `WasSyncBasePort` bound to a single Space + Collection on the remote
|
|
23
|
+
* WAS server, backed by the session's signed `WasClient`.
|
|
41
24
|
*
|
|
42
25
|
* @param options {object}
|
|
43
26
|
* @param options.was {WasClient} the session client (holds the signer)
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"wasSyncPort.d.ts","sourceRoot":"","sources":["../../src/sync/wasSyncPort.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH
|
|
1
|
+
{"version":3,"file":"wasSyncPort.d.ts","sourceRoot":"","sources":["../../src/sync/wasSyncPort.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH;;;;;;;;;;;;;GAaG;AACH,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,qBAAqB,CAAA;AAEpD,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,8BAA8B,CAAA;AACzD,OAAO,KAAK,EAAkB,eAAe,EAAW,MAAM,YAAY,CAAA;AAE1E;;;;;;;;;;;;GAYG;AACH,wBAAgB,iBAAiB,CAAC,EAChC,GAAG,EACH,OAAO,EACP,YAAY,EACZ,UAAU,EACX,EAAE;IACD,GAAG,EAAE,SAAS,CAAA;IACd,OAAO,EAAE,MAAM,CAAA;IACf,YAAY,EAAE,MAAM,CAAA;IACpB,UAAU,CAAC,EAAE,KAAK,CAAA;CACnB,GAAG,eAAe,CAqClB"}
|
package/dist/sync/wasSyncPort.js
CHANGED
|
@@ -1,75 +1,7 @@
|
|
|
1
|
-
|
|
2
|
-
* Copyright (c) 2026 Interop Alliance. All rights reserved.
|
|
3
|
-
*/
|
|
4
|
-
/**
|
|
5
|
-
* The app-side seam between the generic sync adapter and `@interop/was-client`.
|
|
6
|
-
* Implements the `WasSyncBasePort` interface (query + conditional writes) for one
|
|
7
|
-
* remote WAS Collection using the raw, signed `was.request()` escape hatch. The
|
|
8
|
-
* 412 conflict re-read (`get`) is supplied separately by `withFeedMasterRead`,
|
|
9
|
-
* which resolves the master state from the changes-feed body -- origin-
|
|
10
|
-
* independent, unlike a cross-origin `GET` ETag header.
|
|
11
|
-
*
|
|
12
|
-
* Using `request()` (rather than the `Resource` / `Collection` handles) is
|
|
13
|
-
* deliberate: it moves the stored body VERBATIM, bypassing the encryption codec.
|
|
14
|
-
* The `changes` feed already ships opaque stored bodies (plaintext for a
|
|
15
|
-
* plaintext collection, the EDV envelope for an encrypted one), and the push
|
|
16
|
-
* side must write those same bytes back unchanged -- running them through
|
|
17
|
-
* `resource.put()` would re-encrypt an already-encrypted envelope. Encrypt /
|
|
18
|
-
* decrypt stays a read/write-time concern above this layer; this port is
|
|
19
|
-
* collection-agnostic and never touches keys.
|
|
20
|
-
*
|
|
21
|
-
* Conditional writes ride the server's monotonic `version` (content) and
|
|
22
|
-
* `metaVersion` (metadata) ETags, which the reference server enforces uniformly
|
|
23
|
-
* for plaintext and encrypted resources alike -- so there is no plaintext-vs-
|
|
24
|
-
* encrypted fork here.
|
|
25
|
-
*/
|
|
26
|
-
import { readEtag, writeHeaders } from '@interop/was-client';
|
|
27
|
-
import { errorStatus, parseEtag } from '@interop/was-client/sync';
|
|
28
|
-
import { collectionPath } from '../grants.js';
|
|
29
|
-
import { WasSyncAuthError, WasSyncConflictError } from './types.js';
|
|
30
|
-
/**
|
|
31
|
-
* Normalizes an unknown caught error into a display string: the `Error`'s
|
|
32
|
-
* `message` when it is one, else its `String(...)` coercion. Shared by the
|
|
33
|
-
* remote store's descriptor PUT and the session activation error path.
|
|
34
|
-
*
|
|
35
|
-
* @param err {unknown}
|
|
36
|
-
* @returns {string}
|
|
37
|
-
*/
|
|
38
|
-
export function errorMessage(err) {
|
|
39
|
-
return err instanceof Error ? err.message : String(err);
|
|
40
|
-
}
|
|
41
|
-
/**
|
|
42
|
-
* Maps a raw `was.request()` rejection to the sync layer's typed errors: `412`
|
|
43
|
-
* to {@link WasSyncConflictError} (a lost-update conflict) and `401` / `403` /
|
|
44
|
-
* `404` to {@link WasSyncAuthError} (expired/revoked storage access). Every
|
|
45
|
-
* other error is returned unchanged so RxDB's retry/backoff handles it. Returns
|
|
46
|
-
* the error to throw (the caller re-throws) rather than throwing itself.
|
|
47
|
-
*
|
|
48
|
-
* `404` counts as access denied because a WAS server masks a failed capability
|
|
49
|
-
* invocation as `404` ("URL not found or invalid authorization") rather than
|
|
50
|
-
* `403`, so an unauthorized caller cannot probe which resources exist. On the
|
|
51
|
-
* sync paths the invoked space and collection are known to exist (the session
|
|
52
|
-
* synced them), so a `404` means the invocation itself was rejected -- the
|
|
53
|
-
* revoked/expired-grant signal. The one route where `404` is routine --
|
|
54
|
-
* deleting an already-absent resource -- inspects the preserved `status` on the
|
|
55
|
-
* mapped error and stays a success (see `deleteContent`).
|
|
56
|
-
*
|
|
57
|
-
* @param err {unknown}
|
|
58
|
-
* @returns {unknown}
|
|
59
|
-
*/
|
|
60
|
-
function toPortError(err) {
|
|
61
|
-
const status = errorStatus(err);
|
|
62
|
-
if (status === 412) {
|
|
63
|
-
return new WasSyncConflictError();
|
|
64
|
-
}
|
|
65
|
-
if (status === 401 || status === 403 || status === 404) {
|
|
66
|
-
return new WasSyncAuthError(status);
|
|
67
|
-
}
|
|
68
|
-
return err;
|
|
69
|
-
}
|
|
1
|
+
import { createWasSyncPort as createClientSyncPort } from '@interop/was-client/sync';
|
|
70
2
|
/**
|
|
71
|
-
* Builds a `
|
|
72
|
-
* server, backed by the session's signed `WasClient`.
|
|
3
|
+
* Builds a `WasSyncBasePort` bound to a single Space + Collection on the remote
|
|
4
|
+
* WAS server, backed by the session's signed `WasClient`.
|
|
73
5
|
*
|
|
74
6
|
* @param options {object}
|
|
75
7
|
* @param options.was {WasClient} the session client (holds the signer)
|
|
@@ -81,106 +13,32 @@ function toPortError(err) {
|
|
|
81
13
|
* @returns {WasSyncBasePort}
|
|
82
14
|
*/
|
|
83
15
|
export function createWasSyncPort({ was, spaceId, collectionId, capability }) {
|
|
84
|
-
const
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
const response = await run();
|
|
101
|
-
// `readEtag` reports an absent header as `undefined`; `parseEtag` reads
|
|
102
|
-
// the `null` spelling of the same thing.
|
|
103
|
-
return parseEtag(readEtag(response) ?? null);
|
|
104
|
-
}
|
|
105
|
-
catch (err) {
|
|
106
|
-
throw toPortError(err);
|
|
107
|
-
}
|
|
108
|
-
};
|
|
16
|
+
const port = createClientSyncPort({
|
|
17
|
+
was,
|
|
18
|
+
spaceId,
|
|
19
|
+
collectionId,
|
|
20
|
+
...(capability !== undefined && { capability }),
|
|
21
|
+
mapAuthErrors: true
|
|
22
|
+
});
|
|
23
|
+
// The client types `putMeta` as optional (a port may replicate content only);
|
|
24
|
+
// its own implementation always supplies it, and this package's replication
|
|
25
|
+
// always writes the `/meta` half, so require it at the boundary rather than
|
|
26
|
+
// guarding at every call site.
|
|
27
|
+
const { putMeta } = port;
|
|
28
|
+
if (putMeta === undefined) {
|
|
29
|
+
throw new Error('The WAS sync port does not implement `putMeta`, which this ' +
|
|
30
|
+
"package's replication requires.");
|
|
31
|
+
}
|
|
109
32
|
return {
|
|
110
|
-
async query(
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
method: 'POST',
|
|
116
|
-
json: {
|
|
117
|
-
profile: 'changes',
|
|
118
|
-
...(checkpoint !== undefined && { checkpoint }),
|
|
119
|
-
limit
|
|
120
|
-
}
|
|
121
|
-
});
|
|
122
|
-
return response.data;
|
|
123
|
-
}
|
|
124
|
-
catch (err) {
|
|
125
|
-
// Map a pull-path auth rejection (401/403, or the masked 404) to
|
|
126
|
-
// WasSyncAuthError too, so expired/revoked access is recognised
|
|
127
|
-
// whether it surfaces on the pull or the push side.
|
|
128
|
-
throw toPortError(err);
|
|
129
|
-
}
|
|
130
|
-
},
|
|
131
|
-
async putContent({ id, data, ifMatch, ifNoneMatch, epoch }) {
|
|
132
|
-
return conditionalWrite(() => was.request({
|
|
133
|
-
capability,
|
|
134
|
-
path: resourcePath(id),
|
|
135
|
-
method: 'PUT',
|
|
136
|
-
json: data,
|
|
137
|
-
// `epoch` rides as the `Key-Epoch` header so the server's stamp
|
|
138
|
-
// stays in step with the envelope (an absent epoch clears any prior
|
|
139
|
-
// stamp, per the `key-epochs` feature).
|
|
140
|
-
headers: writeHeaders({
|
|
141
|
-
precondition: { ifMatch, ifNoneMatch },
|
|
142
|
-
epoch
|
|
143
|
-
})
|
|
144
|
-
}));
|
|
145
|
-
},
|
|
146
|
-
async deleteContent({ id, ifMatch }) {
|
|
147
|
-
try {
|
|
148
|
-
return await conditionalWrite(() => was.request({
|
|
149
|
-
capability,
|
|
150
|
-
path: resourcePath(id),
|
|
151
|
-
method: 'DELETE',
|
|
152
|
-
headers: writeHeaders({ precondition: { ifMatch } })
|
|
153
|
-
}));
|
|
154
|
-
}
|
|
155
|
-
catch (err) {
|
|
156
|
-
// A 404 means the resource is already absent -- the row was created
|
|
157
|
-
// and deleted locally before ever being pushed, or another device
|
|
158
|
-
// deleted it first. Either way the tombstone's goal state holds, so
|
|
159
|
-
// report success (no acked revision) instead of rejecting the batch,
|
|
160
|
-
// which RxDB would otherwise retry forever. (A masked auth-failure 404
|
|
161
|
-
// is swallowed here too -- indistinguishable by design -- but revoked
|
|
162
|
-
// access still surfaces within one pull-poll interval, where 404 maps
|
|
163
|
-
// to WasSyncAuthError.) The mapped error keeps the raw `status`, so
|
|
164
|
-
// this check works on the WasSyncAuthError conditionalWrite now throws
|
|
165
|
-
// for a 404.
|
|
166
|
-
if (errorStatus(err) === 404) {
|
|
167
|
-
return undefined;
|
|
168
|
-
}
|
|
169
|
-
throw err;
|
|
170
|
-
}
|
|
33
|
+
async query(options) {
|
|
34
|
+
// The feed page's bodies are `unknown` upstream (a stored body is any
|
|
35
|
+
// JSON, and a future encryption scheme's envelope need not be an EDV
|
|
36
|
+
// document); this layer moves them verbatim as `Json`.
|
|
37
|
+
return (await port.query(options));
|
|
171
38
|
},
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
path: `${resourcePath(id)}/meta`,
|
|
176
|
-
method: 'PUT',
|
|
177
|
-
// The `/meta` PUT is a full replacement of `custom`: a body with no
|
|
178
|
-
// `custom` member writes the CLEARED state (the server clears every
|
|
179
|
-
// property the body omits), which is how a metadata clear replicates.
|
|
180
|
-
json: custom === undefined ? {} : { custom },
|
|
181
|
-
headers: writeHeaders({ precondition: { ifMatch, ifNoneMatch } })
|
|
182
|
-
}));
|
|
183
|
-
}
|
|
39
|
+
putContent: options => port.putContent(options),
|
|
40
|
+
deleteContent: options => port.deleteContent(options),
|
|
41
|
+
putMeta: options => putMeta(options)
|
|
184
42
|
};
|
|
185
43
|
}
|
|
186
44
|
//# sourceMappingURL=wasSyncPort.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"wasSyncPort.js","sourceRoot":"","sources":["../../src/sync/wasSyncPort.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"wasSyncPort.js","sourceRoot":"","sources":["../../src/sync/wasSyncPort.ts"],"names":[],"mappings":"AAkBA,OAAO,EAAE,iBAAiB,IAAI,oBAAoB,EAAE,MAAM,0BAA0B,CAAA;AAIpF;;;;;;;;;;;;GAYG;AACH,MAAM,UAAU,iBAAiB,CAAC,EAChC,GAAG,EACH,OAAO,EACP,YAAY,EACZ,UAAU,EAMX;IACC,MAAM,IAAI,GAAG,oBAAoB,CAAC;QAChC,GAAG;QACH,OAAO;QACP,YAAY;QACZ,GAAG,CAAC,UAAU,KAAK,SAAS,IAAI,EAAE,UAAU,EAAE,CAAC;QAC/C,aAAa,EAAE,IAAI;KACpB,CAAC,CAAA;IACF,8EAA8E;IAC9E,4EAA4E;IAC5E,4EAA4E;IAC5E,+BAA+B;IAC/B,MAAM,EAAE,OAAO,EAAE,GAAG,IAAI,CAAA;IACxB,IAAI,OAAO,KAAK,SAAS,EAAE,CAAC;QAC1B,MAAM,IAAI,KAAK,CACb,6DAA6D;YAC3D,iCAAiC,CACpC,CAAA;IACH,CAAC;IAED,OAAO;QACL,KAAK,CAAC,KAAK,CAAC,OAAO;YACjB,sEAAsE;YACtE,qEAAqE;YACrE,uDAAuD;YACvD,OAAO,CAAC,MAAM,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,CAGhC,CAAA;QACH,CAAC;QAED,UAAU,EAAE,OAAO,CAAC,EAAE,CAAC,IAAI,CAAC,UAAU,CAAC,OAAO,CAAC;QAE/C,aAAa,EAAE,OAAO,CAAC,EAAE,CAAC,IAAI,CAAC,aAAa,CAAC,OAAO,CAAC;QAErD,OAAO,EAAE,OAAO,CAAC,EAAE,CAAC,OAAO,CAAC,OAAO,CAAC;KACrC,CAAA;AACH,CAAC"}
|