@interop/was-react 0.1.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/LICENSE.md +20 -0
- package/README.md +410 -0
- package/dist/auth/chapi.d.ts +43 -0
- package/dist/auth/chapi.d.ts.map +1 -0
- package/dist/auth/chapi.js +107 -0
- package/dist/auth/chapi.js.map +1 -0
- package/dist/auth/loginFlow.d.ts +93 -0
- package/dist/auth/loginFlow.d.ts.map +1 -0
- package/dist/auth/loginFlow.js +149 -0
- package/dist/auth/loginFlow.js.map +1 -0
- package/dist/auth/loginRequest.d.ts +66 -0
- package/dist/auth/loginRequest.d.ts.map +1 -0
- package/dist/auth/loginRequest.js +83 -0
- package/dist/auth/loginRequest.js.map +1 -0
- package/dist/auth/verifyResponse.d.ts +52 -0
- package/dist/auth/verifyResponse.d.ts.map +1 -0
- package/dist/auth/verifyResponse.js +138 -0
- package/dist/auth/verifyResponse.js.map +1 -0
- package/dist/auth/verifyResponse.test.d.ts +2 -0
- package/dist/auth/verifyResponse.test.d.ts.map +1 -0
- package/dist/auth/verifyResponse.test.js +282 -0
- package/dist/auth/verifyResponse.test.js.map +1 -0
- package/dist/auth/walletRequestTypes.d.ts +143 -0
- package/dist/auth/walletRequestTypes.d.ts.map +1 -0
- package/dist/auth/walletRequestTypes.js +2 -0
- package/dist/auth/walletRequestTypes.js.map +1 -0
- package/dist/config.d.ts +150 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +27 -0
- package/dist/config.js.map +1 -0
- package/dist/dev/cli.d.ts +16 -0
- package/dist/dev/cli.d.ts.map +1 -0
- package/dist/dev/cli.js +122 -0
- package/dist/dev/cli.js.map +1 -0
- package/dist/dev/cli.test.d.ts +2 -0
- package/dist/dev/cli.test.d.ts.map +1 -0
- package/dist/dev/cli.test.js +39 -0
- package/dist/dev/cli.test.js.map +1 -0
- package/dist/dev/index.d.ts +10 -0
- package/dist/dev/index.d.ts.map +1 -0
- package/dist/dev/index.js +10 -0
- package/dist/dev/index.js.map +1 -0
- package/dist/dev/provisionDevGrants.d.ts +73 -0
- package/dist/dev/provisionDevGrants.d.ts.map +1 -0
- package/dist/dev/provisionDevGrants.js +176 -0
- package/dist/dev/provisionDevGrants.js.map +1 -0
- package/dist/grants.d.ts +59 -0
- package/dist/grants.d.ts.map +1 -0
- package/dist/grants.js +82 -0
- package/dist/grants.js.map +1 -0
- package/dist/grants.test.d.ts +2 -0
- package/dist/grants.test.d.ts.map +1 -0
- package/dist/grants.test.js +79 -0
- package/dist/grants.test.js.map +1 -0
- package/dist/identity/agents.d.ts +99 -0
- package/dist/identity/agents.d.ts.map +1 -0
- package/dist/identity/agents.js +132 -0
- package/dist/identity/agents.js.map +1 -0
- package/dist/identity/appSession.d.ts +78 -0
- package/dist/identity/appSession.d.ts.map +1 -0
- package/dist/identity/appSession.js +93 -0
- package/dist/identity/appSession.js.map +1 -0
- package/dist/identity/documentLoader.d.ts +20 -0
- package/dist/identity/documentLoader.d.ts.map +1 -0
- package/dist/identity/documentLoader.js +99 -0
- package/dist/identity/documentLoader.js.map +1 -0
- package/dist/identity/initAppSession.d.ts +27 -0
- package/dist/identity/initAppSession.d.ts.map +1 -0
- package/dist/identity/initAppSession.js +30 -0
- package/dist/identity/initAppSession.js.map +1 -0
- package/dist/identity/seedCredential.d.ts +74 -0
- package/dist/identity/seedCredential.d.ts.map +1 -0
- package/dist/identity/seedCredential.js +165 -0
- package/dist/identity/seedCredential.js.map +1 -0
- package/dist/identity/seedCredential.test.d.ts +2 -0
- package/dist/identity/seedCredential.test.d.ts.map +1 -0
- package/dist/identity/seedCredential.test.js +205 -0
- package/dist/identity/seedCredential.test.js.map +1 -0
- package/dist/identity/seedStore.d.ts +27 -0
- package/dist/identity/seedStore.d.ts.map +1 -0
- package/dist/identity/seedStore.js +74 -0
- package/dist/identity/seedStore.js.map +1 -0
- package/dist/index.d.ts +37 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +46 -0
- package/dist/index.js.map +1 -0
- package/dist/mui/ProtectedRoute.d.ts +10 -0
- package/dist/mui/ProtectedRoute.d.ts.map +1 -0
- package/dist/mui/ProtectedRoute.js +54 -0
- package/dist/mui/ProtectedRoute.js.map +1 -0
- package/dist/mui/ReconnectBanner.d.ts +2 -0
- package/dist/mui/ReconnectBanner.d.ts.map +1 -0
- package/dist/mui/ReconnectBanner.js +20 -0
- package/dist/mui/ReconnectBanner.js.map +1 -0
- package/dist/mui/SyncStatusChip.d.ts +2 -0
- package/dist/mui/SyncStatusChip.d.ts.map +1 -0
- package/dist/mui/SyncStatusChip.js +27 -0
- package/dist/mui/SyncStatusChip.js.map +1 -0
- package/dist/mui/index.d.ts +12 -0
- package/dist/mui/index.d.ts.map +1 -0
- package/dist/mui/index.js +12 -0
- package/dist/mui/index.js.map +1 -0
- package/dist/react/WasSessionProvider.d.ts +36 -0
- package/dist/react/WasSessionProvider.d.ts.map +1 -0
- package/dist/react/WasSessionProvider.js +35 -0
- package/dist/react/WasSessionProvider.js.map +1 -0
- package/dist/react/hooks.d.ts +66 -0
- package/dist/react/hooks.d.ts.map +1 -0
- package/dist/react/hooks.js +121 -0
- package/dist/react/hooks.js.map +1 -0
- package/dist/react/index.d.ts +11 -0
- package/dist/react/index.d.ts.map +1 -0
- package/dist/react/index.js +11 -0
- package/dist/react/index.js.map +1 -0
- package/dist/session/appReadyStore.d.ts +10 -0
- package/dist/session/appReadyStore.d.ts.map +1 -0
- package/dist/session/appReadyStore.js +22 -0
- package/dist/session/appReadyStore.js.map +1 -0
- package/dist/session/authStore.d.ts +71 -0
- package/dist/session/authStore.d.ts.map +1 -0
- package/dist/session/authStore.js +347 -0
- package/dist/session/authStore.js.map +1 -0
- package/dist/session/index.d.ts +10 -0
- package/dist/session/index.d.ts.map +1 -0
- package/dist/session/index.js +10 -0
- package/dist/session/index.js.map +1 -0
- package/dist/storage/entityStore.d.ts +51 -0
- package/dist/storage/entityStore.d.ts.map +1 -0
- package/dist/storage/entityStore.js +78 -0
- package/dist/storage/entityStore.js.map +1 -0
- package/dist/storage/localStore.d.ts +144 -0
- package/dist/storage/localStore.d.ts.map +1 -0
- package/dist/storage/localStore.js +289 -0
- package/dist/storage/localStore.js.map +1 -0
- package/dist/storage/rehydrate.d.ts +56 -0
- package/dist/storage/rehydrate.d.ts.map +1 -0
- package/dist/storage/rehydrate.js +87 -0
- package/dist/storage/rehydrate.js.map +1 -0
- package/dist/storage/rehydrate.test.d.ts +2 -0
- package/dist/storage/rehydrate.test.d.ts.map +1 -0
- package/dist/storage/rehydrate.test.js +172 -0
- package/dist/storage/rehydrate.test.js.map +1 -0
- package/dist/storage/storageManager.d.ts +39 -0
- package/dist/storage/storageManager.d.ts.map +1 -0
- package/dist/storage/storageManager.js +68 -0
- package/dist/storage/storageManager.js.map +1 -0
- package/dist/storage/syncController.d.ts +79 -0
- package/dist/storage/syncController.d.ts.map +1 -0
- package/dist/storage/syncController.js +189 -0
- package/dist/storage/syncController.js.map +1 -0
- package/dist/storage/syncStatusStore.d.ts +17 -0
- package/dist/storage/syncStatusStore.d.ts.map +1 -0
- package/dist/storage/syncStatusStore.js +19 -0
- package/dist/storage/syncStatusStore.js.map +1 -0
- package/dist/storage/wasRemoteStore.d.ts +73 -0
- package/dist/storage/wasRemoteStore.d.ts.map +1 -0
- package/dist/storage/wasRemoteStore.js +76 -0
- package/dist/storage/wasRemoteStore.js.map +1 -0
- package/dist/storage/wasSync.d.ts +42 -0
- package/dist/storage/wasSync.d.ts.map +1 -0
- package/dist/storage/wasSync.js +34 -0
- package/dist/storage/wasSync.js.map +1 -0
- package/dist/sync/changesQuery.d.ts +44 -0
- package/dist/sync/changesQuery.d.ts.map +1 -0
- package/dist/sync/changesQuery.js +61 -0
- package/dist/sync/changesQuery.js.map +1 -0
- package/dist/sync/changesQuery.test.d.ts +2 -0
- package/dist/sync/changesQuery.test.d.ts.map +1 -0
- package/dist/sync/changesQuery.test.js +145 -0
- package/dist/sync/changesQuery.test.js.map +1 -0
- package/dist/sync/docCipher.d.ts +68 -0
- package/dist/sync/docCipher.d.ts.map +1 -0
- package/dist/sync/docCipher.js +89 -0
- package/dist/sync/docCipher.js.map +1 -0
- package/dist/sync/feedMasterPort.d.ts +30 -0
- package/dist/sync/feedMasterPort.d.ts.map +1 -0
- package/dist/sync/feedMasterPort.js +58 -0
- package/dist/sync/feedMasterPort.js.map +1 -0
- package/dist/sync/index.d.ts +21 -0
- package/dist/sync/index.d.ts.map +1 -0
- package/dist/sync/index.js +21 -0
- package/dist/sync/index.js.map +1 -0
- package/dist/sync/lww.d.ts +29 -0
- package/dist/sync/lww.d.ts.map +1 -0
- package/dist/sync/lww.js +28 -0
- package/dist/sync/lww.js.map +1 -0
- package/dist/sync/lww.test.d.ts +2 -0
- package/dist/sync/lww.test.d.ts.map +1 -0
- package/dist/sync/lww.test.js +28 -0
- package/dist/sync/lww.test.js.map +1 -0
- package/dist/sync/lwwConflictHandler.d.ts +49 -0
- package/dist/sync/lwwConflictHandler.d.ts.map +1 -0
- package/dist/sync/lwwConflictHandler.js +65 -0
- package/dist/sync/lwwConflictHandler.js.map +1 -0
- package/dist/sync/lwwConflictHandler.test.d.ts +2 -0
- package/dist/sync/lwwConflictHandler.test.d.ts.map +1 -0
- package/dist/sync/lwwConflictHandler.test.js +78 -0
- package/dist/sync/lwwConflictHandler.test.js.map +1 -0
- package/dist/sync/pushWrites.d.ts +57 -0
- package/dist/sync/pushWrites.d.ts.map +1 -0
- package/dist/sync/pushWrites.js +159 -0
- package/dist/sync/pushWrites.js.map +1 -0
- package/dist/sync/pushWrites.test.d.ts +2 -0
- package/dist/sync/pushWrites.test.d.ts.map +1 -0
- package/dist/sync/pushWrites.test.js +287 -0
- package/dist/sync/pushWrites.test.js.map +1 -0
- package/dist/sync/syncedDocSchema.d.ts +22 -0
- package/dist/sync/syncedDocSchema.d.ts.map +1 -0
- package/dist/sync/syncedDocSchema.js +26 -0
- package/dist/sync/syncedDocSchema.js.map +1 -0
- package/dist/sync/types.d.ts +194 -0
- package/dist/sync/types.d.ts.map +1 -0
- package/dist/sync/types.js +36 -0
- package/dist/sync/types.js.map +1 -0
- package/dist/sync/wasReplication.d.ts +47 -0
- package/dist/sync/wasReplication.d.ts.map +1 -0
- package/dist/sync/wasReplication.js +55 -0
- package/dist/sync/wasReplication.js.map +1 -0
- package/dist/sync/wasSyncPort.d.ts +45 -0
- package/dist/sync/wasSyncPort.d.ts.map +1 -0
- package/dist/sync/wasSyncPort.js +170 -0
- package/dist/sync/wasSyncPort.js.map +1 -0
- package/package.json +142 -0
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
/*!
|
|
2
|
+
* Copyright (c) 2026 Interop Alliance. All rights reserved.
|
|
3
|
+
*/
|
|
4
|
+
/**
|
|
5
|
+
* Shared types for the collection-agnostic WAS replication adapter.
|
|
6
|
+
*
|
|
7
|
+
* The adapter drives an RxDB `replicateRxCollection` state machine against a
|
|
8
|
+
* remote WAS Collection's replication endpoints. It is deliberately free of any
|
|
9
|
+
* React or app imports so it can stand alone; all WAS access is injected through
|
|
10
|
+
* the {@link WasSyncPort} seam rather than importing `@interop/was-client`
|
|
11
|
+
* directly.
|
|
12
|
+
*
|
|
13
|
+
* The wire contract follows the WAS `changes` feed and its V2 encrypted-metadata
|
|
14
|
+
* profile: a synced document carries both a content revision (`version` /
|
|
15
|
+
* `data`) and an independently-versioned metadata sub-resource (`metaVersion` /
|
|
16
|
+
* `custom`). A metadata-only edit re-surfaces the resource with a bumped
|
|
17
|
+
* `updatedAt` / `metaVersion` but unchanged `version` / `data`. The sync layer
|
|
18
|
+
* moves both bodies opaquely: `data` is the stored content body (plaintext JSON,
|
|
19
|
+
* or the EDV envelope on an encrypted collection) and `custom` is the stored
|
|
20
|
+
* metadata body (an opaque envelope on an encrypted collection); encrypt/decrypt
|
|
21
|
+
* stays a read/write-time concern above this layer.
|
|
22
|
+
*/
|
|
23
|
+
/**
|
|
24
|
+
* A JSON value -- the opaque stored resource body the sync layer moves verbatim.
|
|
25
|
+
* For a plaintext collection this is the user document; for an encrypted one it
|
|
26
|
+
* is the EDV envelope. The adapter never inspects or transforms it.
|
|
27
|
+
*/
|
|
28
|
+
export type Json = null | boolean | number | string | Json[] | {
|
|
29
|
+
[key: string]: Json;
|
|
30
|
+
};
|
|
31
|
+
/**
|
|
32
|
+
* The keyset position in the change feed: the `{ id, updatedAt }` of the last
|
|
33
|
+
* document a pull returned. Passed back verbatim to resume, and used as the
|
|
34
|
+
* RxDB replication checkpoint. `id` is the total-order tiebreaker within a
|
|
35
|
+
* single `updatedAt`.
|
|
36
|
+
*/
|
|
37
|
+
export interface SyncCheckpoint {
|
|
38
|
+
id: string;
|
|
39
|
+
updatedAt: string;
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* One document as it travels on the `changes` feed wire
|
|
43
|
+
* (`POST /space/:s/:c/query`, profile `changes`). `id` is the WAS resourceId,
|
|
44
|
+
* `version` is the content master revision (feeds the content push `If-Match`
|
|
45
|
+
* ETag) and the user content body is nested under `data`; `metaVersion` is the
|
|
46
|
+
* independent metadata revision (feeds the `/meta` push `If-Match` ETag) and the
|
|
47
|
+
* user-writable metadata body is under `custom`. A tombstone carries
|
|
48
|
+
* `_deleted: true` with no `data`. `metaVersion` / `custom` are present only
|
|
49
|
+
* once metadata has been written for the resource.
|
|
50
|
+
*/
|
|
51
|
+
export interface WireDoc {
|
|
52
|
+
id: string;
|
|
53
|
+
_deleted: boolean;
|
|
54
|
+
updatedAt: string;
|
|
55
|
+
version: number;
|
|
56
|
+
metaVersion?: number;
|
|
57
|
+
data?: Json;
|
|
58
|
+
custom?: Json;
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* The local RxDB document shape, shared across every synced collection. The
|
|
62
|
+
* envelope fields are top-level (`id` primary key, `updatedAt` the checkpoint
|
|
63
|
+
* sort field, `version` / `metaVersion` the server master revisions); the user
|
|
64
|
+
* bodies stay nested (`data` for content, `custom` for metadata) to avoid field
|
|
65
|
+
* collisions. `_deleted` is managed by RxDB via `deletedField` and so is not
|
|
66
|
+
* part of this "clean" shape (handlers work with RxDB's `WithDeleted<SyncedDoc>`).
|
|
67
|
+
*/
|
|
68
|
+
export interface SyncedDoc {
|
|
69
|
+
id: string;
|
|
70
|
+
updatedAt: string;
|
|
71
|
+
version: number;
|
|
72
|
+
metaVersion?: number;
|
|
73
|
+
data?: Json;
|
|
74
|
+
custom?: Json;
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* The current master state of a single resource, as read back for the 412
|
|
78
|
+
* conflict path. `deleted` distinguishes a tombstone from a live resource;
|
|
79
|
+
* `metaVersion` / `custom` are present only once metadata has been written.
|
|
80
|
+
*/
|
|
81
|
+
export interface MasterState {
|
|
82
|
+
version: number;
|
|
83
|
+
updatedAt: string;
|
|
84
|
+
deleted: boolean;
|
|
85
|
+
metaVersion?: number;
|
|
86
|
+
data?: Json;
|
|
87
|
+
custom?: Json;
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* The injected WAS-access seam. An adapter (the app-side `wasSyncPort.ts`)
|
|
91
|
+
* implements this over `@interop/was-client`; the core module depends only on
|
|
92
|
+
* this interface, never on `was-client` itself. Every method moves the stored
|
|
93
|
+
* body verbatim -- no codec, no key handling -- so the same port works for
|
|
94
|
+
* plaintext and encrypted collections alike.
|
|
95
|
+
*
|
|
96
|
+
* `putContent` / `deleteContent` / `putMeta` MUST throw
|
|
97
|
+
* {@link WasSyncConflictError} when the server rejects a conditional write with
|
|
98
|
+
* `412 precondition-failed`, and let every other error propagate so RxDB's
|
|
99
|
+
* retry/backoff handles it.
|
|
100
|
+
*/
|
|
101
|
+
export interface WasSyncPort {
|
|
102
|
+
/**
|
|
103
|
+
* Pulls one page of the `changes` feed. Omit `checkpoint` for the first page.
|
|
104
|
+
* Returns the page's `documents` and its resume `checkpoint`, or
|
|
105
|
+
* `checkpoint: null` for an empty (no-change) page.
|
|
106
|
+
*
|
|
107
|
+
* @param options {object}
|
|
108
|
+
* @param [options.checkpoint] {SyncCheckpoint} resume position
|
|
109
|
+
* @param options.limit {number} requested batch size
|
|
110
|
+
* @returns {Promise<{ documents: WireDoc[], checkpoint: SyncCheckpoint | null }>}
|
|
111
|
+
*/
|
|
112
|
+
query(options: {
|
|
113
|
+
checkpoint?: SyncCheckpoint;
|
|
114
|
+
limit: number;
|
|
115
|
+
}): Promise<{
|
|
116
|
+
documents: WireDoc[];
|
|
117
|
+
checkpoint: SyncCheckpoint | null;
|
|
118
|
+
}>;
|
|
119
|
+
/**
|
|
120
|
+
* Conditionally writes the content body verbatim (`PUT /:id`). Pass
|
|
121
|
+
* `ifNoneMatch: true` for a create-if-absent, or `ifMatch` (a quoted ETag over
|
|
122
|
+
* the content `version`) for an update-if-unchanged.
|
|
123
|
+
*
|
|
124
|
+
* @param options {object}
|
|
125
|
+
* @param options.id {string}
|
|
126
|
+
* @param options.data {Json}
|
|
127
|
+
* @param [options.ifMatch] {string}
|
|
128
|
+
* @param [options.ifNoneMatch] {boolean}
|
|
129
|
+
* @returns {Promise<void>}
|
|
130
|
+
*/
|
|
131
|
+
putContent(options: {
|
|
132
|
+
id: string;
|
|
133
|
+
data: Json;
|
|
134
|
+
ifMatch?: string;
|
|
135
|
+
ifNoneMatch?: boolean;
|
|
136
|
+
}): Promise<void>;
|
|
137
|
+
/**
|
|
138
|
+
* Conditionally deletes a resource (writes a tombstone; `DELETE /:id`). Pass
|
|
139
|
+
* `ifMatch` (a quoted ETag over the content `version`) to delete only if
|
|
140
|
+
* unchanged.
|
|
141
|
+
*
|
|
142
|
+
* @param options {object}
|
|
143
|
+
* @param options.id {string}
|
|
144
|
+
* @param [options.ifMatch] {string}
|
|
145
|
+
* @returns {Promise<void>}
|
|
146
|
+
*/
|
|
147
|
+
deleteContent(options: {
|
|
148
|
+
id: string;
|
|
149
|
+
ifMatch?: string;
|
|
150
|
+
}): Promise<void>;
|
|
151
|
+
/**
|
|
152
|
+
* Conditionally writes the metadata body verbatim (`PUT /:id/meta`, body
|
|
153
|
+
* `{ custom }`). Pass `ifNoneMatch: true` when the resource has no metadata
|
|
154
|
+
* yet, or `ifMatch` (a quoted ETag over `metaVersion`) for an
|
|
155
|
+
* update-if-unchanged. The resource must already exist (the server does not
|
|
156
|
+
* create a resource from a `/meta` write).
|
|
157
|
+
*
|
|
158
|
+
* @param options {object}
|
|
159
|
+
* @param options.id {string}
|
|
160
|
+
* @param options.custom {Json}
|
|
161
|
+
* @param [options.ifMatch] {string}
|
|
162
|
+
* @param [options.ifNoneMatch] {boolean}
|
|
163
|
+
* @returns {Promise<void>}
|
|
164
|
+
*/
|
|
165
|
+
putMeta(options: {
|
|
166
|
+
id: string;
|
|
167
|
+
custom: Json;
|
|
168
|
+
ifMatch?: string;
|
|
169
|
+
ifNoneMatch?: boolean;
|
|
170
|
+
}): Promise<void>;
|
|
171
|
+
/**
|
|
172
|
+
* Re-reads a single resource's current master state (content + metadata) for
|
|
173
|
+
* the 412 conflict assembler. Returns `null` when the resource is genuinely
|
|
174
|
+
* absent (a delete/delete race).
|
|
175
|
+
*
|
|
176
|
+
* @param options {object}
|
|
177
|
+
* @param options.id {string}
|
|
178
|
+
* @returns {Promise<MasterState | null>}
|
|
179
|
+
*/
|
|
180
|
+
get(options: {
|
|
181
|
+
id: string;
|
|
182
|
+
}): Promise<MasterState | null>;
|
|
183
|
+
}
|
|
184
|
+
/**
|
|
185
|
+
* Thrown by a {@link WasSyncPort} implementation when a conditional write is
|
|
186
|
+
* rejected with `412 precondition-failed` (a lost-update conflict, or a
|
|
187
|
+
* create-if-absent whose target already exists). The core push handler catches
|
|
188
|
+
* exactly this type to trigger the re-read-and-report-conflict path; any other
|
|
189
|
+
* error propagates to RxDB for retry.
|
|
190
|
+
*/
|
|
191
|
+
export declare class WasSyncConflictError extends Error {
|
|
192
|
+
constructor(message?: string);
|
|
193
|
+
}
|
|
194
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/sync/types.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH;;;;;;;;;;;;;;;;;;GAkBG;AAEH;;;;GAIG;AACH,MAAM,MAAM,IAAI,GACd,IAAI,GAAG,OAAO,GAAG,MAAM,GAAG,MAAM,GAAG,IAAI,EAAE,GAAG;IAAE,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,CAAA;CAAE,CAAA;AAErE;;;;;GAKG;AACH,MAAM,WAAW,cAAc;IAC7B,EAAE,EAAE,MAAM,CAAA;IACV,SAAS,EAAE,MAAM,CAAA;CAClB;AAED;;;;;;;;;GASG;AACH,MAAM,WAAW,OAAO;IACtB,EAAE,EAAE,MAAM,CAAA;IACV,QAAQ,EAAE,OAAO,CAAA;IACjB,SAAS,EAAE,MAAM,CAAA;IACjB,OAAO,EAAE,MAAM,CAAA;IACf,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,IAAI,CAAC,EAAE,IAAI,CAAA;IACX,MAAM,CAAC,EAAE,IAAI,CAAA;CACd;AAED;;;;;;;GAOG;AACH,MAAM,WAAW,SAAS;IACxB,EAAE,EAAE,MAAM,CAAA;IACV,SAAS,EAAE,MAAM,CAAA;IACjB,OAAO,EAAE,MAAM,CAAA;IACf,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,IAAI,CAAC,EAAE,IAAI,CAAA;IACX,MAAM,CAAC,EAAE,IAAI,CAAA;CACd;AAED;;;;GAIG;AACH,MAAM,WAAW,WAAW;IAC1B,OAAO,EAAE,MAAM,CAAA;IACf,SAAS,EAAE,MAAM,CAAA;IACjB,OAAO,EAAE,OAAO,CAAA;IAChB,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,IAAI,CAAC,EAAE,IAAI,CAAA;IACX,MAAM,CAAC,EAAE,IAAI,CAAA;CACd;AAED;;;;;;;;;;;GAWG;AACH,MAAM,WAAW,WAAW;IAC1B;;;;;;;;;OASG;IACH,KAAK,CAAC,OAAO,EAAE;QAAE,UAAU,CAAC,EAAE,cAAc,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,GAAG,OAAO,CAAC;QACtE,SAAS,EAAE,OAAO,EAAE,CAAA;QACpB,UAAU,EAAE,cAAc,GAAG,IAAI,CAAA;KAClC,CAAC,CAAA;IAEF;;;;;;;;;;;OAWG;IACH,UAAU,CAAC,OAAO,EAAE;QAClB,EAAE,EAAE,MAAM,CAAA;QACV,IAAI,EAAE,IAAI,CAAA;QACV,OAAO,CAAC,EAAE,MAAM,CAAA;QAChB,WAAW,CAAC,EAAE,OAAO,CAAA;KACtB,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;IAEjB;;;;;;;;;OASG;IACH,aAAa,CAAC,OAAO,EAAE;QAAE,EAAE,EAAE,MAAM,CAAC;QAAC,OAAO,CAAC,EAAE,MAAM,CAAA;KAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;IAEvE;;;;;;;;;;;;;OAaG;IACH,OAAO,CAAC,OAAO,EAAE;QACf,EAAE,EAAE,MAAM,CAAA;QACV,MAAM,EAAE,IAAI,CAAA;QACZ,OAAO,CAAC,EAAE,MAAM,CAAA;QAChB,WAAW,CAAC,EAAE,OAAO,CAAA;KACtB,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;IAEjB;;;;;;;;OAQG;IACH,GAAG,CAAC,OAAO,EAAE;QAAE,EAAE,EAAE,MAAM,CAAA;KAAE,GAAG,OAAO,CAAC,WAAW,GAAG,IAAI,CAAC,CAAA;CAC1D;AAED;;;;;;GAMG;AACH,qBAAa,oBAAqB,SAAQ,KAAK;gBACjC,OAAO,SAA+C;CAInE"}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/*!
|
|
2
|
+
* Copyright (c) 2026 Interop Alliance. All rights reserved.
|
|
3
|
+
*/
|
|
4
|
+
/**
|
|
5
|
+
* Shared types for the collection-agnostic WAS replication adapter.
|
|
6
|
+
*
|
|
7
|
+
* The adapter drives an RxDB `replicateRxCollection` state machine against a
|
|
8
|
+
* remote WAS Collection's replication endpoints. It is deliberately free of any
|
|
9
|
+
* React or app imports so it can stand alone; all WAS access is injected through
|
|
10
|
+
* the {@link WasSyncPort} seam rather than importing `@interop/was-client`
|
|
11
|
+
* directly.
|
|
12
|
+
*
|
|
13
|
+
* The wire contract follows the WAS `changes` feed and its V2 encrypted-metadata
|
|
14
|
+
* profile: a synced document carries both a content revision (`version` /
|
|
15
|
+
* `data`) and an independently-versioned metadata sub-resource (`metaVersion` /
|
|
16
|
+
* `custom`). A metadata-only edit re-surfaces the resource with a bumped
|
|
17
|
+
* `updatedAt` / `metaVersion` but unchanged `version` / `data`. The sync layer
|
|
18
|
+
* moves both bodies opaquely: `data` is the stored content body (plaintext JSON,
|
|
19
|
+
* or the EDV envelope on an encrypted collection) and `custom` is the stored
|
|
20
|
+
* metadata body (an opaque envelope on an encrypted collection); encrypt/decrypt
|
|
21
|
+
* stays a read/write-time concern above this layer.
|
|
22
|
+
*/
|
|
23
|
+
/**
|
|
24
|
+
* Thrown by a {@link WasSyncPort} implementation when a conditional write is
|
|
25
|
+
* rejected with `412 precondition-failed` (a lost-update conflict, or a
|
|
26
|
+
* create-if-absent whose target already exists). The core push handler catches
|
|
27
|
+
* exactly this type to trigger the re-read-and-report-conflict path; any other
|
|
28
|
+
* error propagates to RxDB for retry.
|
|
29
|
+
*/
|
|
30
|
+
export class WasSyncConflictError extends Error {
|
|
31
|
+
constructor(message = 'WAS conditional write precondition failed.') {
|
|
32
|
+
super(message);
|
|
33
|
+
this.name = 'WasSyncConflictError';
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
//# sourceMappingURL=types.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/sync/types.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH;;;;;;;;;;;;;;;;;;GAkBG;AAoKH;;;;;;GAMG;AACH,MAAM,OAAO,oBAAqB,SAAQ,KAAK;IAC7C,YAAY,OAAO,GAAG,4CAA4C;QAChE,KAAK,CAAC,OAAO,CAAC,CAAA;QACd,IAAI,CAAC,IAAI,GAAG,sBAAsB,CAAA;IACpC,CAAC;CACF"}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
/*!
|
|
2
|
+
* Copyright (c) 2026 Interop Alliance. All rights reserved.
|
|
3
|
+
*/
|
|
4
|
+
/**
|
|
5
|
+
* `createWasReplication` -- wires an RxDB collection to a remote WAS Collection
|
|
6
|
+
* through the collection-agnostic pull/push handlers. This is the single public
|
|
7
|
+
* entry point of the sync module; the caller supplies an already-open RxDB
|
|
8
|
+
* collection and a {@link WasSyncPort} (its only WAS dependency), and gets back
|
|
9
|
+
* the live `RxReplicationState` to observe (`error$` / `active$`) and control
|
|
10
|
+
* (`reSync()` / `cancel()`).
|
|
11
|
+
*
|
|
12
|
+
* No React and no `@interop/was-client` imports -- only RxDB (the replication
|
|
13
|
+
* engine being wrapped) and the injected port. This is the seam that keeps the
|
|
14
|
+
* WAS access injectable.
|
|
15
|
+
*/
|
|
16
|
+
import { type RxReplicationState } from 'rxdb/plugins/replication';
|
|
17
|
+
import type { RxCollection } from 'rxdb/plugins/core';
|
|
18
|
+
import type { SyncCheckpoint, SyncedDoc, WasSyncPort } from './types.js';
|
|
19
|
+
/**
|
|
20
|
+
* Starts (or configures) replication of one RxDB collection against a remote WAS
|
|
21
|
+
* Collection. Poll-based only -- no `pull.stream$` (live streaming is deferred
|
|
22
|
+
* server-side); RxDB's own `retryTime` backoff and `error$` are the reachability
|
|
23
|
+
* signal (the replication attempt is the probe).
|
|
24
|
+
*
|
|
25
|
+
* @param options {object}
|
|
26
|
+
* @param options.rxCollection {RxCollection<SyncedDoc>} the local replica
|
|
27
|
+
* @param options.wasPort {WasSyncPort} injected WAS access
|
|
28
|
+
* @param options.replicationIdentifier {string} stable id (include the server
|
|
29
|
+
* URL + collection) so RxDB can resume across reloads
|
|
30
|
+
* @param [options.batchSize] {number} pull `limit` / push batch (default 100)
|
|
31
|
+
* @param [options.retryTime] {number} ms backoff between failed cycles
|
|
32
|
+
* @param [options.live] {boolean} ongoing (default true) vs one-shot
|
|
33
|
+
* @param [options.autoStart] {boolean} start immediately (default true)
|
|
34
|
+
* @param [options.deletedField] {string} RxDB deleted flag (default `_deleted`)
|
|
35
|
+
* @returns {RxReplicationState<SyncedDoc, SyncCheckpoint>}
|
|
36
|
+
*/
|
|
37
|
+
export declare function createWasReplication({ rxCollection, wasPort, replicationIdentifier, batchSize, retryTime, live, autoStart, deletedField }: {
|
|
38
|
+
rxCollection: RxCollection<SyncedDoc>;
|
|
39
|
+
wasPort: WasSyncPort;
|
|
40
|
+
replicationIdentifier: string;
|
|
41
|
+
batchSize?: number;
|
|
42
|
+
retryTime?: number;
|
|
43
|
+
live?: boolean;
|
|
44
|
+
autoStart?: boolean;
|
|
45
|
+
deletedField?: string;
|
|
46
|
+
}): RxReplicationState<SyncedDoc, SyncCheckpoint>;
|
|
47
|
+
//# sourceMappingURL=wasReplication.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"wasReplication.d.ts","sourceRoot":"","sources":["../../src/sync/wasReplication.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH;;;;;;;;;;;GAWG;AACH,OAAO,EAEL,KAAK,kBAAkB,EACxB,MAAM,0BAA0B,CAAA;AACjC,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAA;AACrD,OAAO,KAAK,EAAE,cAAc,EAAE,SAAS,EAAE,WAAW,EAAE,MAAM,YAAY,CAAA;AAIxE;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,oBAAoB,CAAC,EACnC,YAAY,EACZ,OAAO,EACP,qBAAqB,EACrB,SAAe,EACf,SAAS,EACT,IAAW,EACX,SAAgB,EAChB,YAAyB,EAC1B,EAAE;IACD,YAAY,EAAE,YAAY,CAAC,SAAS,CAAC,CAAA;IACrC,OAAO,EAAE,WAAW,CAAA;IACpB,qBAAqB,EAAE,MAAM,CAAA;IAC7B,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,IAAI,CAAC,EAAE,OAAO,CAAA;IACd,SAAS,CAAC,EAAE,OAAO,CAAA;IACnB,YAAY,CAAC,EAAE,MAAM,CAAA;CACtB,GAAG,kBAAkB,CAAC,SAAS,EAAE,cAAc,CAAC,CAiBhD"}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
/*!
|
|
2
|
+
* Copyright (c) 2026 Interop Alliance. All rights reserved.
|
|
3
|
+
*/
|
|
4
|
+
/**
|
|
5
|
+
* `createWasReplication` -- wires an RxDB collection to a remote WAS Collection
|
|
6
|
+
* through the collection-agnostic pull/push handlers. This is the single public
|
|
7
|
+
* entry point of the sync module; the caller supplies an already-open RxDB
|
|
8
|
+
* collection and a {@link WasSyncPort} (its only WAS dependency), and gets back
|
|
9
|
+
* the live `RxReplicationState` to observe (`error$` / `active$`) and control
|
|
10
|
+
* (`reSync()` / `cancel()`).
|
|
11
|
+
*
|
|
12
|
+
* No React and no `@interop/was-client` imports -- only RxDB (the replication
|
|
13
|
+
* engine being wrapped) and the injected port. This is the seam that keeps the
|
|
14
|
+
* WAS access injectable.
|
|
15
|
+
*/
|
|
16
|
+
import { replicateRxCollection } from 'rxdb/plugins/replication';
|
|
17
|
+
import { createPullHandler } from './changesQuery.js';
|
|
18
|
+
import { createPushHandler } from './pushWrites.js';
|
|
19
|
+
/**
|
|
20
|
+
* Starts (or configures) replication of one RxDB collection against a remote WAS
|
|
21
|
+
* Collection. Poll-based only -- no `pull.stream$` (live streaming is deferred
|
|
22
|
+
* server-side); RxDB's own `retryTime` backoff and `error$` are the reachability
|
|
23
|
+
* signal (the replication attempt is the probe).
|
|
24
|
+
*
|
|
25
|
+
* @param options {object}
|
|
26
|
+
* @param options.rxCollection {RxCollection<SyncedDoc>} the local replica
|
|
27
|
+
* @param options.wasPort {WasSyncPort} injected WAS access
|
|
28
|
+
* @param options.replicationIdentifier {string} stable id (include the server
|
|
29
|
+
* URL + collection) so RxDB can resume across reloads
|
|
30
|
+
* @param [options.batchSize] {number} pull `limit` / push batch (default 100)
|
|
31
|
+
* @param [options.retryTime] {number} ms backoff between failed cycles
|
|
32
|
+
* @param [options.live] {boolean} ongoing (default true) vs one-shot
|
|
33
|
+
* @param [options.autoStart] {boolean} start immediately (default true)
|
|
34
|
+
* @param [options.deletedField] {string} RxDB deleted flag (default `_deleted`)
|
|
35
|
+
* @returns {RxReplicationState<SyncedDoc, SyncCheckpoint>}
|
|
36
|
+
*/
|
|
37
|
+
export function createWasReplication({ rxCollection, wasPort, replicationIdentifier, batchSize = 100, retryTime, live = true, autoStart = true, deletedField = '_deleted' }) {
|
|
38
|
+
return replicateRxCollection({
|
|
39
|
+
replicationIdentifier,
|
|
40
|
+
collection: rxCollection,
|
|
41
|
+
deletedField,
|
|
42
|
+
live,
|
|
43
|
+
autoStart,
|
|
44
|
+
...(retryTime !== undefined && { retryTime }),
|
|
45
|
+
pull: {
|
|
46
|
+
handler: createPullHandler(wasPort),
|
|
47
|
+
batchSize
|
|
48
|
+
},
|
|
49
|
+
push: {
|
|
50
|
+
handler: createPushHandler(wasPort),
|
|
51
|
+
batchSize
|
|
52
|
+
}
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
//# sourceMappingURL=wasReplication.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"wasReplication.js","sourceRoot":"","sources":["../../src/sync/wasReplication.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH;;;;;;;;;;;GAWG;AACH,OAAO,EACL,qBAAqB,EAEtB,MAAM,0BAA0B,CAAA;AAGjC,OAAO,EAAE,iBAAiB,EAAE,MAAM,mBAAmB,CAAA;AACrD,OAAO,EAAE,iBAAiB,EAAE,MAAM,iBAAiB,CAAA;AAEnD;;;;;;;;;;;;;;;;;GAiBG;AACH,MAAM,UAAU,oBAAoB,CAAC,EACnC,YAAY,EACZ,OAAO,EACP,qBAAqB,EACrB,SAAS,GAAG,GAAG,EACf,SAAS,EACT,IAAI,GAAG,IAAI,EACX,SAAS,GAAG,IAAI,EAChB,YAAY,GAAG,UAAU,EAU1B;IACC,OAAO,qBAAqB,CAA4B;QACtD,qBAAqB;QACrB,UAAU,EAAE,YAAY;QACxB,YAAY;QACZ,IAAI;QACJ,SAAS;QACT,GAAG,CAAC,SAAS,KAAK,SAAS,IAAI,EAAE,SAAS,EAAE,CAAC;QAC7C,IAAI,EAAE;YACJ,OAAO,EAAE,iBAAiB,CAAC,OAAO,CAAC;YACnC,SAAS;SACV;QACD,IAAI,EAAE;YACJ,OAAO,EAAE,iBAAiB,CAAC,OAAO,CAAC;YACnC,SAAS;SACV;KACF,CAAC,CAAA;AACJ,CAAC"}
|
|
@@ -0,0 +1,45 @@
|
|
|
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 `WasSyncPort` interface for one remote WAS Collection using the
|
|
7
|
+
* raw, signed `was.request()` escape hatch.
|
|
8
|
+
*
|
|
9
|
+
* Using `request()` (rather than the `Resource` / `Collection` handles) is
|
|
10
|
+
* deliberate: it moves the stored body VERBATIM, bypassing the encryption codec.
|
|
11
|
+
* The `changes` feed already ships opaque stored bodies (plaintext for a
|
|
12
|
+
* plaintext collection, the EDV envelope for an encrypted one), and the push
|
|
13
|
+
* side must write those same bytes back unchanged -- running them through
|
|
14
|
+
* `resource.put()` would re-encrypt an already-encrypted envelope. Encrypt /
|
|
15
|
+
* decrypt stays a read/write-time concern above this layer; this port is
|
|
16
|
+
* collection-agnostic and never touches keys.
|
|
17
|
+
*
|
|
18
|
+
* Conditional writes ride the server's monotonic `version` (content) and
|
|
19
|
+
* `metaVersion` (metadata) ETags, which the reference server enforces uniformly
|
|
20
|
+
* for plaintext and encrypted resources alike -- so there is no plaintext-vs-
|
|
21
|
+
* encrypted fork here.
|
|
22
|
+
*/
|
|
23
|
+
import type { WasClient } from '@interop/was-client';
|
|
24
|
+
import type { IZcap } from '@interop/data-integrity-core';
|
|
25
|
+
import { type WasSyncPort } from './types.js';
|
|
26
|
+
/**
|
|
27
|
+
* Builds a `WasSyncPort` bound to a single Space + Collection on the remote WAS
|
|
28
|
+
* server, backed by the session's signed `WasClient`.
|
|
29
|
+
*
|
|
30
|
+
* @param options {object}
|
|
31
|
+
* @param options.was {WasClient} the session client (holds the signer)
|
|
32
|
+
* @param options.spaceId {string}
|
|
33
|
+
* @param options.collectionId {string} the WAS collection id (e.g. `public-credentials`)
|
|
34
|
+
* @param [options.capability] {IZcap} the delegated session capability for
|
|
35
|
+
* this collection (a restored `delegated` tier session); absent in the full
|
|
36
|
+
* tier, where requests invoke root capabilities
|
|
37
|
+
* @returns {WasSyncPort}
|
|
38
|
+
*/
|
|
39
|
+
export declare function createWasSyncPort({ was, spaceId, collectionId, capability }: {
|
|
40
|
+
was: WasClient;
|
|
41
|
+
spaceId: string;
|
|
42
|
+
collectionId: string;
|
|
43
|
+
capability?: IZcap;
|
|
44
|
+
}): WasSyncPort;
|
|
45
|
+
//# sourceMappingURL=wasSyncPort.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"wasSyncPort.d.ts","sourceRoot":"","sources":["../../src/sync/wasSyncPort.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH;;;;;;;;;;;;;;;;;;GAkBG;AACH,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,qBAAqB,CAAA;AACpD,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,8BAA8B,CAAA;AACzD,OAAO,EAKL,KAAK,WAAW,EAEjB,MAAM,YAAY,CAAA;AAgCnB;;;;;;;;;;;;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,WAAW,CA0Jd"}
|
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
import { WasSyncConflictError } from './types.js';
|
|
2
|
+
/**
|
|
3
|
+
* Extracts an HTTP status from a raw ky/ezcap error. `was.request()` rejects on
|
|
4
|
+
* any non-2xx with `err.status` set (see `@interop/http-client`'s error
|
|
5
|
+
* normaliser); this reads it defensively from either location.
|
|
6
|
+
*
|
|
7
|
+
* @param err {unknown}
|
|
8
|
+
* @returns {number | undefined}
|
|
9
|
+
*/
|
|
10
|
+
function errorStatus(err) {
|
|
11
|
+
return (err.status ??
|
|
12
|
+
err.response?.status);
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Parses a quoted strong ETag (`"3"`) into its numeric revision, or `undefined`
|
|
16
|
+
* when the header is absent (the resource has no such revision yet).
|
|
17
|
+
*
|
|
18
|
+
* @param etag {string | null}
|
|
19
|
+
* @returns {number | undefined}
|
|
20
|
+
*/
|
|
21
|
+
function parseEtag(etag) {
|
|
22
|
+
if (!etag) {
|
|
23
|
+
return undefined;
|
|
24
|
+
}
|
|
25
|
+
const revision = Number(etag.replace(/"/g, ''));
|
|
26
|
+
return Number.isFinite(revision) ? revision : undefined;
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Builds a `WasSyncPort` bound to a single Space + Collection on the remote WAS
|
|
30
|
+
* server, backed by the session's signed `WasClient`.
|
|
31
|
+
*
|
|
32
|
+
* @param options {object}
|
|
33
|
+
* @param options.was {WasClient} the session client (holds the signer)
|
|
34
|
+
* @param options.spaceId {string}
|
|
35
|
+
* @param options.collectionId {string} the WAS collection id (e.g. `public-credentials`)
|
|
36
|
+
* @param [options.capability] {IZcap} the delegated session capability for
|
|
37
|
+
* this collection (a restored `delegated` tier session); absent in the full
|
|
38
|
+
* tier, where requests invoke root capabilities
|
|
39
|
+
* @returns {WasSyncPort}
|
|
40
|
+
*/
|
|
41
|
+
export function createWasSyncPort({ was, spaceId, collectionId, capability }) {
|
|
42
|
+
const collectionPath = `/space/${spaceId}/${collectionId}`;
|
|
43
|
+
const resourcePath = (id) => `${collectionPath}/${encodeURIComponent(id)}`;
|
|
44
|
+
/**
|
|
45
|
+
* Builds the conditional-write headers from the port's precondition options.
|
|
46
|
+
*/
|
|
47
|
+
const writeHeaders = ({ ifMatch, ifNoneMatch }) => {
|
|
48
|
+
const headers = {};
|
|
49
|
+
if (ifMatch !== undefined) {
|
|
50
|
+
headers['if-match'] = ifMatch;
|
|
51
|
+
}
|
|
52
|
+
if (ifNoneMatch) {
|
|
53
|
+
headers['if-none-match'] = '*';
|
|
54
|
+
}
|
|
55
|
+
return Object.keys(headers).length > 0 ? headers : undefined;
|
|
56
|
+
};
|
|
57
|
+
/**
|
|
58
|
+
* Runs a conditional write, mapping the server's `412 precondition-failed`
|
|
59
|
+
* into the core's `WasSyncConflictError` and letting all else propagate.
|
|
60
|
+
*/
|
|
61
|
+
const conditionalWrite = async (run) => {
|
|
62
|
+
try {
|
|
63
|
+
await run();
|
|
64
|
+
}
|
|
65
|
+
catch (err) {
|
|
66
|
+
if (errorStatus(err) === 412) {
|
|
67
|
+
throw new WasSyncConflictError();
|
|
68
|
+
}
|
|
69
|
+
throw err;
|
|
70
|
+
}
|
|
71
|
+
};
|
|
72
|
+
return {
|
|
73
|
+
async query({ checkpoint, limit }) {
|
|
74
|
+
const response = await was.request({
|
|
75
|
+
capability,
|
|
76
|
+
path: `${collectionPath}/query`,
|
|
77
|
+
method: 'POST',
|
|
78
|
+
json: {
|
|
79
|
+
profile: 'changes',
|
|
80
|
+
...(checkpoint !== undefined && { checkpoint }),
|
|
81
|
+
limit
|
|
82
|
+
}
|
|
83
|
+
});
|
|
84
|
+
return response.data;
|
|
85
|
+
},
|
|
86
|
+
async putContent({ id, data, ifMatch, ifNoneMatch }) {
|
|
87
|
+
await conditionalWrite(() => was.request({
|
|
88
|
+
capability,
|
|
89
|
+
path: resourcePath(id),
|
|
90
|
+
method: 'PUT',
|
|
91
|
+
json: data,
|
|
92
|
+
headers: writeHeaders({ ifMatch, ifNoneMatch })
|
|
93
|
+
}));
|
|
94
|
+
},
|
|
95
|
+
async deleteContent({ id, ifMatch }) {
|
|
96
|
+
await conditionalWrite(() => was.request({
|
|
97
|
+
capability,
|
|
98
|
+
path: resourcePath(id),
|
|
99
|
+
method: 'DELETE',
|
|
100
|
+
headers: writeHeaders({ ifMatch })
|
|
101
|
+
}));
|
|
102
|
+
},
|
|
103
|
+
async putMeta({ id, custom, ifMatch, ifNoneMatch }) {
|
|
104
|
+
await conditionalWrite(() => was.request({
|
|
105
|
+
capability,
|
|
106
|
+
path: `${resourcePath(id)}/meta`,
|
|
107
|
+
method: 'PUT',
|
|
108
|
+
json: { custom },
|
|
109
|
+
headers: writeHeaders({ ifMatch, ifNoneMatch })
|
|
110
|
+
}));
|
|
111
|
+
},
|
|
112
|
+
async get({ id }) {
|
|
113
|
+
// Content re-read: raw GET returns the stored body verbatim (no decrypt)
|
|
114
|
+
// and the content `version` in the ETag. A 404 means the resource is gone
|
|
115
|
+
// (or tombstoned) -- report absent so the core synthesizes a tombstone.
|
|
116
|
+
let contentResponse;
|
|
117
|
+
try {
|
|
118
|
+
contentResponse = await was.request({
|
|
119
|
+
capability,
|
|
120
|
+
path: resourcePath(id),
|
|
121
|
+
method: 'GET'
|
|
122
|
+
});
|
|
123
|
+
}
|
|
124
|
+
catch (err) {
|
|
125
|
+
if (errorStatus(err) === 404) {
|
|
126
|
+
return null;
|
|
127
|
+
}
|
|
128
|
+
throw err;
|
|
129
|
+
}
|
|
130
|
+
const master = {
|
|
131
|
+
version: parseEtag(contentResponse.headers.get('etag')) ?? 0,
|
|
132
|
+
// Filled from the `/meta` body's server-managed `updatedAt` below; the
|
|
133
|
+
// change feed remains the authority on ordering, so this only feeds the
|
|
134
|
+
// one-off conflict entry and is corrected on the next pull.
|
|
135
|
+
updatedAt: '',
|
|
136
|
+
deleted: false,
|
|
137
|
+
data: contentResponse.data
|
|
138
|
+
};
|
|
139
|
+
// Metadata re-read: the `/meta` body carries the server-managed
|
|
140
|
+
// `updatedAt` plus the user-writable `custom` (opaque), and its own
|
|
141
|
+
// `metaVersion` ETag (absent until metadata has been written).
|
|
142
|
+
try {
|
|
143
|
+
const metaResponse = await was.request({
|
|
144
|
+
capability,
|
|
145
|
+
path: `${resourcePath(id)}/meta`,
|
|
146
|
+
method: 'GET'
|
|
147
|
+
});
|
|
148
|
+
const metaBody = metaResponse.data;
|
|
149
|
+
if (metaBody?.updatedAt) {
|
|
150
|
+
master.updatedAt = metaBody.updatedAt;
|
|
151
|
+
}
|
|
152
|
+
const metaVersion = parseEtag(metaResponse.headers.get('etag'));
|
|
153
|
+
if (metaVersion !== undefined) {
|
|
154
|
+
master.metaVersion = metaVersion;
|
|
155
|
+
}
|
|
156
|
+
if (metaBody?.custom !== undefined) {
|
|
157
|
+
master.custom = metaBody.custom;
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
catch (err) {
|
|
161
|
+
// Metadata is optional; only a hard error (not 404) should propagate.
|
|
162
|
+
if (errorStatus(err) !== 404) {
|
|
163
|
+
throw err;
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
return master;
|
|
167
|
+
}
|
|
168
|
+
};
|
|
169
|
+
}
|
|
170
|
+
//# sourceMappingURL=wasSyncPort.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"wasSyncPort.js","sourceRoot":"","sources":["../../src/sync/wasSyncPort.ts"],"names":[],"mappings":"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"}
|