@interop/was-react 0.8.2 → 0.9.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 +55 -44
- package/dist/auth/chapi.d.ts.map +1 -1
- package/dist/auth/chapi.js +16 -5
- package/dist/auth/chapi.js.map +1 -1
- package/dist/auth/loginFlow.d.ts.map +1 -1
- package/dist/auth/loginFlow.js +55 -49
- package/dist/auth/loginFlow.js.map +1 -1
- package/dist/auth/verifyResponse.d.ts.map +1 -1
- package/dist/auth/verifyResponse.js +27 -10
- package/dist/auth/verifyResponse.js.map +1 -1
- package/dist/auth/walletRequestTypes.d.ts +9 -90
- package/dist/auth/walletRequestTypes.d.ts.map +1 -1
- package/dist/config.d.ts +9 -9
- package/dist/config.js +0 -0
- package/dist/dev/provisionDevGrants.d.ts.map +1 -1
- package/dist/dev/provisionDevGrants.js +18 -19
- package/dist/dev/provisionDevGrants.js.map +1 -1
- package/dist/grants.d.ts +19 -9
- package/dist/grants.d.ts.map +1 -1
- package/dist/grants.js +17 -2
- package/dist/grants.js.map +1 -1
- package/dist/identity/agents.d.ts +1 -1
- package/dist/identity/agents.d.ts.map +1 -1
- package/dist/identity/agents.js +10 -25
- package/dist/identity/agents.js.map +1 -1
- package/dist/identity/appSession.d.ts +27 -0
- package/dist/identity/appSession.d.ts.map +1 -1
- package/dist/identity/appSession.js +51 -13
- package/dist/identity/appSession.js.map +1 -1
- package/dist/identity/seedCredential.d.ts +7 -0
- package/dist/identity/seedCredential.d.ts.map +1 -1
- package/dist/identity/seedCredential.js +11 -14
- package/dist/identity/seedCredential.js.map +1 -1
- package/dist/identity/seedStore.d.ts +19 -0
- package/dist/identity/seedStore.d.ts.map +1 -1
- package/dist/identity/seedStore.js +40 -13
- package/dist/identity/seedStore.js.map +1 -1
- package/dist/index.d.ts +4 -5
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -4
- package/dist/index.js.map +1 -1
- package/dist/jsonLd.d.ts +19 -0
- package/dist/jsonLd.d.ts.map +1 -0
- package/dist/jsonLd.js +24 -0
- package/dist/jsonLd.js.map +1 -0
- package/dist/mui/AdoptDialog.d.ts.map +1 -1
- package/dist/mui/AdoptDialog.js +7 -3
- package/dist/mui/AdoptDialog.js.map +1 -1
- package/dist/mui/ClearDataDialog.d.ts.map +1 -1
- package/dist/mui/ClearDataDialog.js +12 -11
- package/dist/mui/ClearDataDialog.js.map +1 -1
- package/dist/mui/ConfirmDialog.d.ts +35 -0
- package/dist/mui/ConfirmDialog.d.ts.map +1 -0
- package/dist/mui/ConfirmDialog.js +18 -0
- package/dist/mui/ConfirmDialog.js.map +1 -0
- package/dist/mui/LogoutDialog.d.ts.map +1 -1
- package/dist/mui/LogoutDialog.js +7 -3
- package/dist/mui/LogoutDialog.js.map +1 -1
- package/dist/react/documentApp.d.ts +29 -0
- package/dist/react/documentApp.d.ts.map +1 -1
- package/dist/react/documentApp.js +9 -38
- package/dist/react/documentApp.js.map +1 -1
- package/dist/react/hooks.d.ts +13 -7
- package/dist/react/hooks.d.ts.map +1 -1
- package/dist/react/hooks.js +17 -11
- package/dist/react/hooks.js.map +1 -1
- package/dist/session/authStore.d.ts +9 -9
- package/dist/session/authStore.d.ts.map +1 -1
- package/dist/session/authStore.js +203 -112
- package/dist/session/authStore.js.map +1 -1
- package/dist/storage/adopt.d.ts +5 -1
- package/dist/storage/adopt.d.ts.map +1 -1
- package/dist/storage/adopt.js +8 -24
- package/dist/storage/adopt.js.map +1 -1
- package/dist/storage/entityStore.d.ts.map +1 -1
- package/dist/storage/entityStore.js +27 -20
- package/dist/storage/entityStore.js.map +1 -1
- package/dist/storage/localStore.d.ts +12 -6
- package/dist/storage/localStore.d.ts.map +1 -1
- package/dist/storage/localStore.js +146 -77
- package/dist/storage/localStore.js.map +1 -1
- package/dist/storage/rehydrate.d.ts.map +1 -1
- package/dist/storage/rehydrate.js +20 -4
- package/dist/storage/rehydrate.js.map +1 -1
- package/dist/storage/sharedCollectionReader.d.ts +1 -1
- package/dist/storage/sharedCollectionReader.d.ts.map +1 -1
- package/dist/storage/sharedCollectionReader.js +79 -64
- package/dist/storage/sharedCollectionReader.js.map +1 -1
- package/dist/storage/storageManager.d.ts +3 -1
- package/dist/storage/storageManager.d.ts.map +1 -1
- package/dist/storage/storageManager.js +20 -6
- package/dist/storage/storageManager.js.map +1 -1
- package/dist/storage/syncController.d.ts +1 -16
- package/dist/storage/syncController.d.ts.map +1 -1
- package/dist/storage/syncController.js +48 -37
- package/dist/storage/syncController.js.map +1 -1
- package/dist/storage/syncStatusStore.d.ts +8 -6
- package/dist/storage/syncStatusStore.d.ts.map +1 -1
- package/dist/storage/syncStatusStore.js +6 -6
- package/dist/storage/syncStatusStore.js.map +1 -1
- package/dist/storage/wasRemoteStore.d.ts +34 -2
- package/dist/storage/wasRemoteStore.d.ts.map +1 -1
- package/dist/storage/wasRemoteStore.js +45 -7
- package/dist/storage/wasRemoteStore.js.map +1 -1
- package/dist/storage/wasSync.d.ts.map +1 -1
- package/dist/storage/wasSync.js +47 -38
- package/dist/storage/wasSync.js.map +1 -1
- package/dist/sync/changesQuery.d.ts.map +1 -1
- package/dist/sync/changesQuery.js +2 -9
- package/dist/sync/changesQuery.js.map +1 -1
- package/dist/sync/docCipher.d.ts +23 -10
- package/dist/sync/docCipher.d.ts.map +1 -1
- package/dist/sync/docCipher.js +17 -11
- package/dist/sync/docCipher.js.map +1 -1
- package/dist/sync/feedMasterPort.d.ts +6 -0
- package/dist/sync/feedMasterPort.d.ts.map +1 -1
- package/dist/sync/feedMasterPort.js +82 -37
- package/dist/sync/feedMasterPort.js.map +1 -1
- package/dist/sync/index.d.ts +6 -5
- package/dist/sync/index.d.ts.map +1 -1
- package/dist/sync/index.js +6 -3
- package/dist/sync/index.js.map +1 -1
- package/dist/sync/lww.d.ts +27 -18
- package/dist/sync/lww.d.ts.map +1 -1
- package/dist/sync/lww.js +35 -8
- package/dist/sync/lww.js.map +1 -1
- package/dist/sync/lwwConflictHandler.d.ts +15 -14
- package/dist/sync/lwwConflictHandler.d.ts.map +1 -1
- package/dist/sync/lwwConflictHandler.js +37 -26
- package/dist/sync/lwwConflictHandler.js.map +1 -1
- package/dist/sync/pushWrites.d.ts +9 -9
- package/dist/sync/pushWrites.d.ts.map +1 -1
- package/dist/sync/pushWrites.js +102 -68
- package/dist/sync/pushWrites.js.map +1 -1
- package/dist/sync/syncedDocSchema.d.ts.map +1 -1
- package/dist/sync/syncedDocSchema.js +3 -0
- package/dist/sync/syncedDocSchema.js.map +1 -1
- package/dist/sync/types.d.ts +115 -15
- package/dist/sync/types.d.ts.map +1 -1
- package/dist/sync/types.js +36 -18
- package/dist/sync/types.js.map +1 -1
- package/dist/sync/wasSyncPort.d.ts +0 -11
- package/dist/sync/wasSyncPort.d.ts.map +1 -1
- package/dist/sync/wasSyncPort.js +20 -36
- package/dist/sync/wasSyncPort.js.map +1 -1
- package/package.json +6 -6
- package/dist/identity/initAppSession.d.ts +0 -27
- package/dist/identity/initAppSession.d.ts.map +0 -1
- package/dist/identity/initAppSession.js +0 -30
- package/dist/identity/initAppSession.js.map +0 -1
package/dist/sync/pushWrites.js
CHANGED
|
@@ -1,44 +1,20 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { formatEtag } from '@interop/was-client/sync';
|
|
2
|
+
import { bodiesEqual, copyOptionalBodyFields, WasSyncAuthError, WasSyncConflictError } from './types.js';
|
|
2
3
|
/**
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*/
|
|
9
|
-
export function formatEtag(revision) {
|
|
10
|
-
return `"${revision}"`;
|
|
11
|
-
}
|
|
12
|
-
/**
|
|
13
|
-
* Structural equality over two opaque bodies, by canonical-free JSON string.
|
|
14
|
-
* Used only to decide whether the content or the metadata half changed and thus
|
|
15
|
-
* which endpoint(s) to write. Content-addressed collections never mutate `data`
|
|
16
|
-
* for a given id (so the content half only fires on create/delete), and a real
|
|
17
|
-
* metadata edit re-encrypts to fresh bytes, so this coarse comparison is
|
|
18
|
-
* sufficient for routing.
|
|
19
|
-
*
|
|
20
|
-
* @param left {Json | undefined}
|
|
21
|
-
* @param right {Json | undefined}
|
|
22
|
-
* @returns {boolean}
|
|
23
|
-
*/
|
|
24
|
-
function bodiesEqual(left, right) {
|
|
25
|
-
return JSON.stringify(left ?? null) === JSON.stringify(right ?? null);
|
|
26
|
-
}
|
|
27
|
-
/**
|
|
28
|
-
* Builds the RxDB conflict entry (the real current master state) for a row whose
|
|
29
|
-
* conditional write was rejected with `412`. Re-reads the resource; when it is
|
|
30
|
-
* genuinely absent (a delete/delete race) the master is a tombstone synthesized
|
|
31
|
-
* from what we know locally.
|
|
4
|
+
* Maps a re-read master state into the RxDB conflict entry for one row, or --
|
|
5
|
+
* when the re-read found the resource genuinely absent (`master === null`, a
|
|
6
|
+
* delete/delete race) -- synthesizes the tombstone conflict entry from what we
|
|
7
|
+
* know locally. Shared by the `412` assembler and the `/meta` 404 recovery, so
|
|
8
|
+
* both report an absent master identically.
|
|
32
9
|
*
|
|
33
10
|
* @param options {object}
|
|
34
|
-
* @param options.port {WasSyncPort}
|
|
35
11
|
* @param options.id {string}
|
|
12
|
+
* @param options.master {MasterState | null}
|
|
36
13
|
* @param options.fallbackUpdatedAt {string} used if the resource is now absent
|
|
37
14
|
* @param options.fallbackVersion {number} used if the resource is now absent
|
|
38
|
-
* @returns {
|
|
15
|
+
* @returns {WithDeleted<SyncedDoc>}
|
|
39
16
|
*/
|
|
40
|
-
|
|
41
|
-
const master = await port.get({ id });
|
|
17
|
+
function masterOrTombstone({ id, master, fallbackUpdatedAt, fallbackVersion }) {
|
|
42
18
|
if (master === null) {
|
|
43
19
|
return {
|
|
44
20
|
id,
|
|
@@ -53,36 +29,59 @@ async function assembleConflict({ port, id, fallbackUpdatedAt, fallbackVersion }
|
|
|
53
29
|
version: master.version,
|
|
54
30
|
_deleted: master.deleted
|
|
55
31
|
};
|
|
56
|
-
|
|
57
|
-
conflict.data = master.data;
|
|
58
|
-
}
|
|
59
|
-
if (master.metaVersion !== undefined) {
|
|
60
|
-
conflict.metaVersion = master.metaVersion;
|
|
61
|
-
}
|
|
62
|
-
if (master.custom !== undefined) {
|
|
63
|
-
conflict.custom = master.custom;
|
|
64
|
-
}
|
|
32
|
+
copyOptionalBodyFields({ source: master, target: conflict });
|
|
65
33
|
return conflict;
|
|
66
34
|
}
|
|
67
35
|
/**
|
|
68
36
|
* Sends one local change to the remote Collection as up to two conditional
|
|
69
37
|
* writes (content, then metadata). Returns the master-state conflict entry on a
|
|
70
|
-
* `412` at either step
|
|
71
|
-
*
|
|
38
|
+
* `412` at either step, or the accepted writes' acked revisions on success
|
|
39
|
+
* (`ack: null` when no response carried a revision). A conflict on the
|
|
40
|
+
* metadata half still returns the content half's earned ack alongside the
|
|
41
|
+
* conflict entry, so an accepted content version is never discarded.
|
|
72
42
|
*
|
|
73
43
|
* @param options {object}
|
|
74
44
|
* @param options.port {WasSyncPort}
|
|
75
45
|
* @param options.newDocumentState {WithDeleted<SyncedDoc>}
|
|
76
46
|
* @param [options.assumedMasterState] {WithDeleted<SyncedDoc>}
|
|
47
|
+
* @param [options.cache] {MasterReadCache} the push batch's shared
|
|
48
|
+
* master-read memo
|
|
77
49
|
* @returns {Promise<{ conflict: WithDeleted<SyncedDoc> | null,
|
|
78
50
|
* ack: PushWriteAck | null }>}
|
|
79
51
|
*/
|
|
80
|
-
async function pushRow({ port, newDocumentState, assumedMasterState }) {
|
|
52
|
+
async function pushRow({ port, newDocumentState, assumedMasterState, cache }) {
|
|
81
53
|
const { id } = newDocumentState;
|
|
82
54
|
const assumedVersion = assumedMasterState?.version;
|
|
83
55
|
const isCreate = assumedMasterState === undefined;
|
|
56
|
+
const ack = { id };
|
|
57
|
+
const hasAck = () => ack.version !== undefined || ack.metaVersion !== undefined;
|
|
58
|
+
const fallbackVersion = () => ack.version ?? assumedVersion ?? newDocumentState.version;
|
|
59
|
+
// Re-reads this row's master. A row that has ALREADY written this batch
|
|
60
|
+
// (a content write accepted before a `/meta` rejection) bypasses the batch
|
|
61
|
+
// memo: a sibling's feed read may have paged past this resource before our
|
|
62
|
+
// own write landed, and the conflict entry must carry the revision that write
|
|
63
|
+
// produced. Rows that wrote nothing -- the ordinary content 412, the common
|
|
64
|
+
// case -- are exactly what the memo is for.
|
|
65
|
+
const readMaster = async () => hasAck()
|
|
66
|
+
? port.get({ id })
|
|
67
|
+
: port.get({ id, ...(cache !== undefined && { cache }) });
|
|
68
|
+
// Builds the conflict outcome from a re-read master (or from its absence, a
|
|
69
|
+
// delete/delete race), PRESERVING any ack already earned: a content write
|
|
70
|
+
// accepted before a `/meta` 412 must keep its acked `version` (and feed it to
|
|
71
|
+
// the absent-master fallback), or the local row keeps the pre-write version
|
|
72
|
+
// and every later conditional write sends a stale `If-Match`.
|
|
73
|
+
const conflictOutcome = (master) => ({
|
|
74
|
+
conflict: masterOrTombstone({
|
|
75
|
+
id,
|
|
76
|
+
master,
|
|
77
|
+
fallbackUpdatedAt: newDocumentState.updatedAt,
|
|
78
|
+
fallbackVersion: fallbackVersion()
|
|
79
|
+
}),
|
|
80
|
+
ack: hasAck() ? ack : null
|
|
81
|
+
});
|
|
82
|
+
// The 412 path: re-read the resource, then report its real master state.
|
|
83
|
+
const conflictResult = async () => conflictOutcome(await readMaster());
|
|
84
84
|
try {
|
|
85
|
-
const ack = { id };
|
|
86
85
|
if (newDocumentState._deleted) {
|
|
87
86
|
// Delete supersedes any metadata write: drop the content, tombstone wins.
|
|
88
87
|
const ackedVersion = await port.deleteContent({
|
|
@@ -104,6 +103,9 @@ async function pushRow({ port, newDocumentState, assumedMasterState }) {
|
|
|
104
103
|
const ackedVersion = await port.putContent({
|
|
105
104
|
id,
|
|
106
105
|
data: newDocumentState.data ?? null,
|
|
106
|
+
...(newDocumentState.epoch !== undefined && {
|
|
107
|
+
epoch: newDocumentState.epoch
|
|
108
|
+
}),
|
|
107
109
|
...(isCreate
|
|
108
110
|
? { ifNoneMatch: true }
|
|
109
111
|
: assumedVersion !== undefined && {
|
|
@@ -114,14 +116,29 @@ async function pushRow({ port, newDocumentState, assumedMasterState }) {
|
|
|
114
116
|
ack.version = ackedVersion;
|
|
115
117
|
}
|
|
116
118
|
}
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
119
|
+
}
|
|
120
|
+
catch (err) {
|
|
121
|
+
if (err instanceof WasSyncConflictError) {
|
|
122
|
+
return await conflictResult();
|
|
123
|
+
}
|
|
124
|
+
// Any non-conflict error (network, 5xx, auth) propagates so RxDB retries
|
|
125
|
+
// the whole batch with backoff.
|
|
126
|
+
throw err;
|
|
127
|
+
}
|
|
128
|
+
// Metadata half: write when the metadata changed -- including a CLEAR (the
|
|
129
|
+
// new state carries no `custom` while the assumed master does; `putMeta`
|
|
130
|
+
// with no `custom` writes the cleared state). On a create this runs after
|
|
131
|
+
// the content write (the resource must exist first). Its own try/catch so a
|
|
132
|
+
// rejection here can never discard an ack the content half already earned.
|
|
133
|
+
const metadataChanged = !bodiesEqual(newDocumentState.custom, assumedMasterState?.custom);
|
|
134
|
+
if (metadataChanged) {
|
|
135
|
+
try {
|
|
121
136
|
const assumedMetaVersion = assumedMasterState?.metaVersion;
|
|
122
137
|
const ackedMetaVersion = await port.putMeta({
|
|
123
138
|
id,
|
|
124
|
-
|
|
139
|
+
...(newDocumentState.custom !== undefined && {
|
|
140
|
+
custom: newDocumentState.custom
|
|
141
|
+
}),
|
|
125
142
|
...(assumedMetaVersion !== undefined
|
|
126
143
|
? { ifMatch: formatEtag(assumedMetaVersion) }
|
|
127
144
|
: { ifNoneMatch: true })
|
|
@@ -130,23 +147,32 @@ async function pushRow({ port, newDocumentState, assumedMasterState }) {
|
|
|
130
147
|
ack.metaVersion = ackedMetaVersion;
|
|
131
148
|
}
|
|
132
149
|
}
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
150
|
+
catch (err) {
|
|
151
|
+
if (err instanceof WasSyncConflictError) {
|
|
152
|
+
return await conflictResult();
|
|
153
|
+
}
|
|
154
|
+
// Corroborate before condemnation: under WAS 404-masking a `/meta` 404
|
|
155
|
+
// is ambiguous -- expired access, or an ordinary race with a remote
|
|
156
|
+
// delete (a PUT to the `/meta` of a nonexistent resource legitimately
|
|
157
|
+
// 404s). An independent request decides: re-read the master off the
|
|
158
|
+
// changes feed. A feed read that is itself denied rethrows its own auth
|
|
159
|
+
// error (access genuinely expired, so the controller escalates); a feed
|
|
160
|
+
// that answers with an absent/deleted master confirms the delete race,
|
|
161
|
+
// and the row is resolved with that tombstone as the conflict entry
|
|
162
|
+
// (the conflict handler reconciles it) instead of flipping the whole
|
|
163
|
+
// session to "access expired" and wedging the batch in RxDB's retries.
|
|
164
|
+
if (err instanceof WasSyncAuthError && err.status === 404) {
|
|
165
|
+
const master = await readMaster();
|
|
166
|
+
if (master === null || master.deleted) {
|
|
167
|
+
return conflictOutcome(master);
|
|
168
|
+
}
|
|
169
|
+
// The resource is alive and readable while its `/meta` write 404s:
|
|
170
|
+
// the write itself was rejected, so the auth signal stands.
|
|
171
|
+
}
|
|
172
|
+
throw err;
|
|
145
173
|
}
|
|
146
|
-
// Any non-conflict error (network, 5xx, auth) propagates so RxDB retries
|
|
147
|
-
// the whole batch with backoff.
|
|
148
|
-
throw err;
|
|
149
174
|
}
|
|
175
|
+
return { conflict: null, ack: hasAck() ? ack : null };
|
|
150
176
|
}
|
|
151
177
|
/**
|
|
152
178
|
* Builds the RxDB push handler that fans a batch of local changes out to
|
|
@@ -160,6 +186,12 @@ async function pushRow({ port, newDocumentState, assumedMasterState }) {
|
|
|
160
186
|
* caller can write the new `version` / `metaVersion` back into the local row
|
|
161
187
|
* and keep subsequent conditional writes' `If-Match` in step with the server.
|
|
162
188
|
*
|
|
189
|
+
* Each batch gets one short-lived master-read memo, shared by its rows and
|
|
190
|
+
* discarded with the batch (never held across batches, where it would go
|
|
191
|
+
* stale). A conflict re-read walks the changes feed from its origin and so pages
|
|
192
|
+
* past every other conflicting row's master on the way; without the memo a batch
|
|
193
|
+
* with k conflicts would run k concurrent full-feed walks.
|
|
194
|
+
*
|
|
163
195
|
* @param port {WasSyncPort}
|
|
164
196
|
* @param [onWriteAccepted] {(ack: PushWriteAck) => Promise<void>}
|
|
165
197
|
* @returns {(rows: Array<{ newDocumentState: WithDeleted<SyncedDoc>,
|
|
@@ -168,11 +200,13 @@ async function pushRow({ port, newDocumentState, assumedMasterState }) {
|
|
|
168
200
|
*/
|
|
169
201
|
export function createPushHandler(port, onWriteAccepted) {
|
|
170
202
|
return async function push(rows) {
|
|
203
|
+
const cache = { byId: new Map(), inFlight: null };
|
|
171
204
|
const results = await Promise.all(rows.map(async (row) => {
|
|
172
205
|
const result = await pushRow({
|
|
173
206
|
port,
|
|
174
207
|
newDocumentState: row.newDocumentState,
|
|
175
|
-
assumedMasterState: row.assumedMasterState
|
|
208
|
+
assumedMasterState: row.assumedMasterState,
|
|
209
|
+
cache
|
|
176
210
|
});
|
|
177
211
|
if (result.ack !== null && onWriteAccepted !== undefined) {
|
|
178
212
|
await onWriteAccepted(result.ack);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"pushWrites.js","sourceRoot":"","sources":["../../src/sync/pushWrites.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"pushWrites.js","sourceRoot":"","sources":["../../src/sync/pushWrites.ts"],"names":[],"mappings":"AAmCA,OAAO,EAAE,UAAU,EAAE,MAAM,0BAA0B,CAAA;AAOrD,OAAO,EACL,WAAW,EACX,sBAAsB,EACtB,gBAAgB,EAChB,oBAAoB,EACrB,MAAM,YAAY,CAAA;AAcnB;;;;;;;;;;;;;GAaG;AACH,SAAS,iBAAiB,CAAC,EACzB,EAAE,EACF,MAAM,EACN,iBAAiB,EACjB,eAAe,EAMhB;IACC,IAAI,MAAM,KAAK,IAAI,EAAE,CAAC;QACpB,OAAO;YACL,EAAE;YACF,SAAS,EAAE,iBAAiB;YAC5B,OAAO,EAAE,eAAe;YACxB,QAAQ,EAAE,IAAI;SACf,CAAA;IACH,CAAC;IACD,MAAM,QAAQ,GAA2B;QACvC,EAAE;QACF,SAAS,EAAE,MAAM,CAAC,SAAS;QAC3B,OAAO,EAAE,MAAM,CAAC,OAAO;QACvB,QAAQ,EAAE,MAAM,CAAC,OAAO;KACzB,CAAA;IACD,sBAAsB,CAAC,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,CAAC,CAAA;IAC5D,OAAO,QAAQ,CAAA;AACjB,CAAC;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,KAAK,UAAU,OAAO,CAAC,EACrB,IAAI,EACJ,gBAAgB,EAChB,kBAAkB,EAClB,KAAK,EAMN;IAIC,MAAM,EAAE,EAAE,EAAE,GAAG,gBAAgB,CAAA;IAC/B,MAAM,cAAc,GAAG,kBAAkB,EAAE,OAAO,CAAA;IAClD,MAAM,QAAQ,GAAG,kBAAkB,KAAK,SAAS,CAAA;IACjD,MAAM,GAAG,GAAiB,EAAE,EAAE,EAAE,CAAA;IAChC,MAAM,MAAM,GAAG,GAAG,EAAE,CAClB,GAAG,CAAC,OAAO,KAAK,SAAS,IAAI,GAAG,CAAC,WAAW,KAAK,SAAS,CAAA;IAC5D,MAAM,eAAe,GAAG,GAAG,EAAE,CAC3B,GAAG,CAAC,OAAO,IAAI,cAAc,IAAI,gBAAgB,CAAC,OAAO,CAAA;IAE3D,wEAAwE;IACxE,2EAA2E;IAC3E,2EAA2E;IAC3E,8EAA8E;IAC9E,4EAA4E;IAC5E,4CAA4C;IAC5C,MAAM,UAAU,GAAG,KAAK,IAAiC,EAAE,CACzD,MAAM,EAAE;QACN,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,EAAE,EAAE,CAAC;QAClB,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,EAAE,EAAE,GAAG,CAAC,KAAK,KAAK,SAAS,IAAI,EAAE,KAAK,EAAE,CAAC,EAAE,CAAC,CAAA;IAE7D,4EAA4E;IAC5E,0EAA0E;IAC1E,8EAA8E;IAC9E,4EAA4E;IAC5E,8DAA8D;IAC9D,MAAM,eAAe,GAAG,CAAC,MAA0B,EAAE,EAAE,CAAC,CAAC;QACvD,QAAQ,EAAE,iBAAiB,CAAC;YAC1B,EAAE;YACF,MAAM;YACN,iBAAiB,EAAE,gBAAgB,CAAC,SAAS;YAC7C,eAAe,EAAE,eAAe,EAAE;SACnC,CAAC;QACF,GAAG,EAAE,MAAM,EAAE,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI;KAC3B,CAAC,CAAA;IAEF,yEAAyE;IACzE,MAAM,cAAc,GAAG,KAAK,IAAI,EAAE,CAAC,eAAe,CAAC,MAAM,UAAU,EAAE,CAAC,CAAA;IAEtE,IAAI,CAAC;QACH,IAAI,gBAAgB,CAAC,QAAQ,EAAE,CAAC;YAC9B,0EAA0E;YAC1E,MAAM,YAAY,GAAG,MAAM,IAAI,CAAC,aAAa,CAAC;gBAC5C,EAAE;gBACF,GAAG,CAAC,cAAc,KAAK,SAAS,IAAI;oBAClC,OAAO,EAAE,UAAU,CAAC,cAAc,CAAC;iBACpC,CAAC;aACH,CAAC,CAAA;YACF,IAAI,YAAY,KAAK,SAAS,EAAE,CAAC;gBAC/B,GAAG,CAAC,OAAO,GAAG,YAAY,CAAA;YAC5B,CAAC;YACD,OAAO,EAAE,QAAQ,EAAE,IAAI,EAAE,GAAG,EAAE,GAAG,CAAC,OAAO,KAAK,SAAS,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,EAAE,CAAA;QACxE,CAAC;QAED,yEAAyE;QACzE,yEAAyE;QACzE,2DAA2D;QAC3D,MAAM,cAAc,GAClB,QAAQ,IAAI,CAAC,WAAW,CAAC,gBAAgB,CAAC,IAAI,EAAE,kBAAkB,EAAE,IAAI,CAAC,CAAA;QAC3E,IAAI,cAAc,EAAE,CAAC;YACnB,MAAM,YAAY,GAAG,MAAM,IAAI,CAAC,UAAU,CAAC;gBACzC,EAAE;gBACF,IAAI,EAAE,gBAAgB,CAAC,IAAI,IAAI,IAAI;gBACnC,GAAG,CAAC,gBAAgB,CAAC,KAAK,KAAK,SAAS,IAAI;oBAC1C,KAAK,EAAE,gBAAgB,CAAC,KAAK;iBAC9B,CAAC;gBACF,GAAG,CAAC,QAAQ;oBACV,CAAC,CAAC,EAAE,WAAW,EAAE,IAAI,EAAE;oBACvB,CAAC,CAAC,cAAc,KAAK,SAAS,IAAI;wBAC9B,OAAO,EAAE,UAAU,CAAC,cAAc,CAAC;qBACpC,CAAC;aACP,CAAC,CAAA;YACF,IAAI,YAAY,KAAK,SAAS,EAAE,CAAC;gBAC/B,GAAG,CAAC,OAAO,GAAG,YAAY,CAAA;YAC5B,CAAC;QACH,CAAC;IACH,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,IAAI,GAAG,YAAY,oBAAoB,EAAE,CAAC;YACxC,OAAO,MAAM,cAAc,EAAE,CAAA;QAC/B,CAAC;QACD,yEAAyE;QACzE,gCAAgC;QAChC,MAAM,GAAG,CAAA;IACX,CAAC;IAED,2EAA2E;IAC3E,yEAAyE;IACzE,0EAA0E;IAC1E,4EAA4E;IAC5E,2EAA2E;IAC3E,MAAM,eAAe,GAAG,CAAC,WAAW,CAClC,gBAAgB,CAAC,MAAM,EACvB,kBAAkB,EAAE,MAAM,CAC3B,CAAA;IACD,IAAI,eAAe,EAAE,CAAC;QACpB,IAAI,CAAC;YACH,MAAM,kBAAkB,GAAG,kBAAkB,EAAE,WAAW,CAAA;YAC1D,MAAM,gBAAgB,GAAG,MAAM,IAAI,CAAC,OAAO,CAAC;gBAC1C,EAAE;gBACF,GAAG,CAAC,gBAAgB,CAAC,MAAM,KAAK,SAAS,IAAI;oBAC3C,MAAM,EAAE,gBAAgB,CAAC,MAAM;iBAChC,CAAC;gBACF,GAAG,CAAC,kBAAkB,KAAK,SAAS;oBAClC,CAAC,CAAC,EAAE,OAAO,EAAE,UAAU,CAAC,kBAAkB,CAAC,EAAE;oBAC7C,CAAC,CAAC,EAAE,WAAW,EAAE,IAAI,EAAE,CAAC;aAC3B,CAAC,CAAA;YACF,IAAI,gBAAgB,KAAK,SAAS,EAAE,CAAC;gBACnC,GAAG,CAAC,WAAW,GAAG,gBAAgB,CAAA;YACpC,CAAC;QACH,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,IAAI,GAAG,YAAY,oBAAoB,EAAE,CAAC;gBACxC,OAAO,MAAM,cAAc,EAAE,CAAA;YAC/B,CAAC;YACD,uEAAuE;YACvE,oEAAoE;YACpE,sEAAsE;YACtE,oEAAoE;YACpE,wEAAwE;YACxE,wEAAwE;YACxE,uEAAuE;YACvE,oEAAoE;YACpE,qEAAqE;YACrE,uEAAuE;YACvE,IAAI,GAAG,YAAY,gBAAgB,IAAI,GAAG,CAAC,MAAM,KAAK,GAAG,EAAE,CAAC;gBAC1D,MAAM,MAAM,GAAG,MAAM,UAAU,EAAE,CAAA;gBACjC,IAAI,MAAM,KAAK,IAAI,IAAI,MAAM,CAAC,OAAO,EAAE,CAAC;oBACtC,OAAO,eAAe,CAAC,MAAM,CAAC,CAAA;gBAChC,CAAC;gBACD,mEAAmE;gBACnE,4DAA4D;YAC9D,CAAC;YACD,MAAM,GAAG,CAAA;QACX,CAAC;IACH,CAAC;IAED,OAAO,EAAE,QAAQ,EAAE,IAAI,EAAE,GAAG,EAAE,MAAM,EAAE,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,EAAE,CAAA;AACvD,CAAC;AAED;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,MAAM,UAAU,iBAAiB,CAC/B,IAAiB,EACjB,eAAsD;IAEtD,OAAO,KAAK,UAAU,IAAI,CACxB,IAGE;QAEF,MAAM,KAAK,GAAoB,EAAE,IAAI,EAAE,IAAI,GAAG,EAAE,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAA;QAClE,MAAM,OAAO,GAAG,MAAM,OAAO,CAAC,GAAG,CAC/B,IAAI,CAAC,GAAG,CAAC,KAAK,EAAC,GAAG,EAAC,EAAE;YACnB,MAAM,MAAM,GAAG,MAAM,OAAO,CAAC;gBAC3B,IAAI;gBACJ,gBAAgB,EAAE,GAAG,CAAC,gBAAgB;gBACtC,kBAAkB,EAAE,GAAG,CAAC,kBAAkB;gBAC1C,KAAK;aACN,CAAC,CAAA;YACF,IAAI,MAAM,CAAC,GAAG,KAAK,IAAI,IAAI,eAAe,KAAK,SAAS,EAAE,CAAC;gBACzD,MAAM,eAAe,CAAC,MAAM,CAAC,GAAG,CAAC,CAAA;YACnC,CAAC;YACD,OAAO,MAAM,CAAA;QACf,CAAC,CAAC,CACH,CAAA;QACD,OAAO,OAAO;aACX,GAAG,CAAC,MAAM,CAAC,EAAE,CAAC,MAAM,CAAC,QAAQ,CAAC;aAC9B,MAAM,CACL,CAAC,QAAQ,EAAsC,EAAE,CAAC,QAAQ,KAAK,IAAI,CACpE,CAAA;IACL,CAAC,CAAA;AACH,CAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"syncedDocSchema.d.ts","sourceRoot":"","sources":["../../src/sync/syncedDocSchema.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH;;;;;;;GAOG;AACH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAA;AACrD,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,YAAY,CAAA;AAE3C;;;;;;GAMG;AACH,wBAAgB,eAAe,IAAI,YAAY,CAAC,SAAS,CAAC,
|
|
1
|
+
{"version":3,"file":"syncedDocSchema.d.ts","sourceRoot":"","sources":["../../src/sync/syncedDocSchema.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH;;;;;;;GAOG;AACH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAA;AACrD,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,YAAY,CAAA;AAE3C;;;;;;GAMG;AACH,wBAAgB,eAAe,IAAI,YAAY,CAAC,SAAS,CAAC,CAoBzD"}
|
|
@@ -15,6 +15,9 @@ export function syncedDocSchema() {
|
|
|
15
15
|
updatedAt: { type: 'string', maxLength: 64 },
|
|
16
16
|
version: { type: 'number' },
|
|
17
17
|
metaVersion: { type: 'number' },
|
|
18
|
+
// The opaque key-epoch id `data` was encrypted under (the `WAS-Key-Epoch`
|
|
19
|
+
// content-push header), when known.
|
|
20
|
+
epoch: { type: 'string', maxLength: 256 },
|
|
18
21
|
// Opaque stored bodies -- content and metadata envelopes -- moved verbatim.
|
|
19
22
|
data: { type: 'object', additionalProperties: true },
|
|
20
23
|
custom: { type: 'object', additionalProperties: true }
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"syncedDocSchema.js","sourceRoot":"","sources":["../../src/sync/syncedDocSchema.ts"],"names":[],"mappings":"AAcA;;;;;;GAMG;AACH,MAAM,UAAU,eAAe;IAC7B,OAAO;QACL,OAAO,EAAE,CAAC;QACV,UAAU,EAAE,IAAI;QAChB,IAAI,EAAE,QAAQ;QACd,UAAU,EAAE;YACV,EAAE,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE,SAAS,EAAE,GAAG,EAAE;YACtC,SAAS,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE,SAAS,EAAE,EAAE,EAAE;YAC5C,OAAO,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE;YAC3B,WAAW,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE;YAC/B,4EAA4E;YAC5E,IAAI,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE,oBAAoB,EAAE,IAAI,EAAE;YACpD,MAAM,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE,oBAAoB,EAAE,IAAI,EAAE;SACvD;QACD,QAAQ,EAAE,CAAC,IAAI,EAAE,WAAW,EAAE,SAAS,CAAC;QACxC,OAAO,EAAE,CAAC,WAAW,CAAC;KACI,CAAA;AAC9B,CAAC"}
|
|
1
|
+
{"version":3,"file":"syncedDocSchema.js","sourceRoot":"","sources":["../../src/sync/syncedDocSchema.ts"],"names":[],"mappings":"AAcA;;;;;;GAMG;AACH,MAAM,UAAU,eAAe;IAC7B,OAAO;QACL,OAAO,EAAE,CAAC;QACV,UAAU,EAAE,IAAI;QAChB,IAAI,EAAE,QAAQ;QACd,UAAU,EAAE;YACV,EAAE,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE,SAAS,EAAE,GAAG,EAAE;YACtC,SAAS,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE,SAAS,EAAE,EAAE,EAAE;YAC5C,OAAO,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE;YAC3B,WAAW,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE;YAC/B,0EAA0E;YAC1E,oCAAoC;YACpC,KAAK,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE,SAAS,EAAE,GAAG,EAAE;YACzC,4EAA4E;YAC5E,IAAI,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE,oBAAoB,EAAE,IAAI,EAAE;YACpD,MAAM,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE,oBAAoB,EAAE,IAAI,EAAE;SACvD;QACD,QAAQ,EAAE,CAAC,IAAI,EAAE,WAAW,EAAE,SAAS,CAAC;QACxC,OAAO,EAAE,CAAC,WAAW,CAAC;KACI,CAAA;AAC9B,CAAC"}
|
package/dist/sync/types.d.ts
CHANGED
|
@@ -19,15 +19,63 @@
|
|
|
19
19
|
* or the EDV envelope on an encrypted collection) and `custom` is the stored
|
|
20
20
|
* metadata body (an opaque envelope on an encrypted collection); encrypt/decrypt
|
|
21
21
|
* stays a read/write-time concern above this layer.
|
|
22
|
+
*
|
|
23
|
+
* The two small helpers that travel with these shapes live here as well: the
|
|
24
|
+
* opaque-body equality every routing decision is made on, and the optional-field
|
|
25
|
+
* copy every wire-to-local mapping runs.
|
|
22
26
|
*/
|
|
27
|
+
import type { Json } from '@interop/was-client';
|
|
23
28
|
/**
|
|
24
29
|
* A JSON value -- the opaque stored resource body the sync layer moves verbatim.
|
|
25
30
|
* 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.
|
|
31
|
+
* is the EDV envelope. The adapter never inspects or transforms it. Re-exported
|
|
32
|
+
* from `@interop/was-client`, which owns the wire model, so a body crosses the
|
|
33
|
+
* port boundary without a cast.
|
|
34
|
+
*/
|
|
35
|
+
export type { Json };
|
|
36
|
+
/**
|
|
37
|
+
* The optional half of a synced document, shared verbatim by every shape it
|
|
38
|
+
* travels in ({@link WireDoc} on the feed, {@link SyncedDoc} locally,
|
|
39
|
+
* {@link MasterState} on the conflict re-read): the independently-versioned
|
|
40
|
+
* metadata revision and body, the content body, and the content body's
|
|
41
|
+
* key-epoch stamp. Each is genuinely absent rather than `undefined` when the
|
|
42
|
+
* server has nothing for it, so every mapping between the shapes copies them
|
|
43
|
+
* conditionally -- see {@link copyOptionalBodyFields}.
|
|
44
|
+
*/
|
|
45
|
+
export interface OptionalBodyFields {
|
|
46
|
+
metaVersion?: number;
|
|
47
|
+
data?: Json;
|
|
48
|
+
custom?: Json;
|
|
49
|
+
epoch?: string;
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Structural equality over two opaque bodies, by canonical-free JSON string.
|
|
53
|
+
* Decides whether the content or the metadata half changed -- which endpoint(s)
|
|
54
|
+
* a push writes, and whether two states compare equal for conflict resolution.
|
|
55
|
+
* Content-addressed collections never mutate `data` for a given id, and a real
|
|
56
|
+
* metadata edit re-encrypts to fresh bytes, so this coarse comparison suffices.
|
|
57
|
+
*
|
|
58
|
+
* @param left {Json | undefined}
|
|
59
|
+
* @param right {Json | undefined}
|
|
60
|
+
* @returns {boolean}
|
|
61
|
+
*/
|
|
62
|
+
export declare function bodiesEqual(left: Json | undefined, right: Json | undefined): boolean;
|
|
63
|
+
/**
|
|
64
|
+
* Copies the {@link OptionalBodyFields} that are present on `source` onto
|
|
65
|
+
* `target`, leaving an absent field absent (never writing an explicit
|
|
66
|
+
* `undefined`, which would surface the key in a serialized body). The single
|
|
67
|
+
* place every wire-to-local, feed-to-master, and master-to-conflict mapping
|
|
68
|
+
* shares, so a new optional wire field is added once.
|
|
69
|
+
*
|
|
70
|
+
* @param options {object}
|
|
71
|
+
* @param options.source {OptionalBodyFields}
|
|
72
|
+
* @param options.target {OptionalBodyFields} mutated in place
|
|
73
|
+
* @returns {void}
|
|
27
74
|
*/
|
|
28
|
-
export
|
|
29
|
-
|
|
30
|
-
|
|
75
|
+
export declare function copyOptionalBodyFields({ source, target }: {
|
|
76
|
+
source: OptionalBodyFields;
|
|
77
|
+
target: OptionalBodyFields;
|
|
78
|
+
}): void;
|
|
31
79
|
/**
|
|
32
80
|
* The keyset position in the change feed: the `{ id, updatedAt }` of the last
|
|
33
81
|
* document a pull returned. Passed back verbatim to resume, and used as the
|
|
@@ -56,6 +104,12 @@ export interface WireDoc {
|
|
|
56
104
|
metaVersion?: number;
|
|
57
105
|
data?: Json;
|
|
58
106
|
custom?: Json;
|
|
107
|
+
/**
|
|
108
|
+
* The opaque key-epoch id the content body was encrypted under (the
|
|
109
|
+
* `key-epochs` feature), present when the server holds a stamp for the
|
|
110
|
+
* resource. Moved verbatim; the sync layer never interprets it.
|
|
111
|
+
*/
|
|
112
|
+
epoch?: string;
|
|
59
113
|
}
|
|
60
114
|
/**
|
|
61
115
|
* The local RxDB document shape, shared across every synced collection. The
|
|
@@ -72,6 +126,13 @@ export interface SyncedDoc {
|
|
|
72
126
|
metaVersion?: number;
|
|
73
127
|
data?: Json;
|
|
74
128
|
custom?: Json;
|
|
129
|
+
/**
|
|
130
|
+
* The opaque key-epoch id `data` was encrypted under, when known (stamped by
|
|
131
|
+
* the encrypting cipher on a local write, or pulled off the feed). Sent as
|
|
132
|
+
* the `WAS-Key-Epoch` header on the content push so the server's stamp stays
|
|
133
|
+
* in step with the envelope.
|
|
134
|
+
*/
|
|
135
|
+
epoch?: string;
|
|
75
136
|
}
|
|
76
137
|
/**
|
|
77
138
|
* The current master state of a single resource, as read back for the 412
|
|
@@ -85,6 +146,30 @@ export interface MasterState {
|
|
|
85
146
|
metaVersion?: number;
|
|
86
147
|
data?: Json;
|
|
87
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
|
+
}
|
|
154
|
+
/**
|
|
155
|
+
* The short-lived master-read memo the rows of ONE push batch share. A
|
|
156
|
+
* resolving `get` implementation may answer from `byId` and MUST record every
|
|
157
|
+
* master it resolved on the way there; a feed-walking implementation pages past
|
|
158
|
+
* all of them anyway, so a batch of k conflicts costs one walk instead of k.
|
|
159
|
+
*
|
|
160
|
+
* `inFlight` is what makes that hold under concurrency: the batch's rows push in
|
|
161
|
+
* parallel, so without it every row would start its own walk before the first
|
|
162
|
+
* one finished and the memo would never be read. A `get` that must walk
|
|
163
|
+
* publishes its walk here; a `get` that finds a walk already running awaits it
|
|
164
|
+
* and re-checks `byId` first. It settles (never rejects) so one row's failed
|
|
165
|
+
* read is not another row's, and is `null` whenever no walk is running.
|
|
166
|
+
*
|
|
167
|
+
* The whole object is created per batch invocation and dropped with it: a memo
|
|
168
|
+
* held across batches would go stale.
|
|
169
|
+
*/
|
|
170
|
+
export interface MasterReadCache {
|
|
171
|
+
byId: Map<string, MasterState | null>;
|
|
172
|
+
inFlight: Promise<void> | null;
|
|
88
173
|
}
|
|
89
174
|
/**
|
|
90
175
|
* The write/query half of the injected WAS-access seam. An adapter (the app-side
|
|
@@ -124,15 +209,19 @@ export interface WasSyncBasePort {
|
|
|
124
209
|
/**
|
|
125
210
|
* Conditionally writes the content body verbatim (`PUT /:id`). Pass
|
|
126
211
|
* `ifNoneMatch: true` for a create-if-absent, or `ifMatch` (a quoted ETag over
|
|
127
|
-
* the content `version`) for an update-if-unchanged.
|
|
128
|
-
*
|
|
129
|
-
*
|
|
212
|
+
* the content `version`) for an update-if-unchanged. `epoch` is the opaque
|
|
213
|
+
* key-epoch id the body was encrypted under, sent as the `WAS-Key-Epoch`
|
|
214
|
+
* header (an absent epoch clears any prior stamp on the server, per the
|
|
215
|
+
* `key-epochs` feature). Returns the new content `version` parsed from the
|
|
216
|
+
* response ETag (the acked master revision), or `undefined` when the server
|
|
217
|
+
* does not supply one.
|
|
130
218
|
*
|
|
131
219
|
* @param options {object}
|
|
132
220
|
* @param options.id {string}
|
|
133
221
|
* @param options.data {Json}
|
|
134
222
|
* @param [options.ifMatch] {string}
|
|
135
223
|
* @param [options.ifNoneMatch] {boolean}
|
|
224
|
+
* @param [options.epoch] {string}
|
|
136
225
|
* @returns {Promise<number | undefined>}
|
|
137
226
|
*/
|
|
138
227
|
putContent(options: {
|
|
@@ -140,6 +229,7 @@ export interface WasSyncBasePort {
|
|
|
140
229
|
data: Json;
|
|
141
230
|
ifMatch?: string;
|
|
142
231
|
ifNoneMatch?: boolean;
|
|
232
|
+
epoch?: string;
|
|
143
233
|
}): Promise<number | undefined>;
|
|
144
234
|
/**
|
|
145
235
|
* Conditionally deletes a resource (writes a tombstone; `DELETE /:id`). Pass
|
|
@@ -162,23 +252,25 @@ export interface WasSyncBasePort {
|
|
|
162
252
|
}): Promise<number | undefined>;
|
|
163
253
|
/**
|
|
164
254
|
* Conditionally writes the metadata body verbatim (`PUT /:id/meta`, body
|
|
165
|
-
* `{ custom }`).
|
|
166
|
-
*
|
|
167
|
-
*
|
|
168
|
-
*
|
|
169
|
-
*
|
|
170
|
-
*
|
|
255
|
+
* `{ custom }`). An ABSENT `custom` writes the CLEARED state (a body with no
|
|
256
|
+
* `custom` member -- the server's metadata replace clears every property the
|
|
257
|
+
* body omits), so removing a resource's metadata replicates rather than being
|
|
258
|
+
* skipped. Pass `ifNoneMatch: true` when the resource has no metadata yet, or
|
|
259
|
+
* `ifMatch` (a quoted ETag over `metaVersion`) for an update-if-unchanged.
|
|
260
|
+
* The resource must already exist (the server does not create a resource from
|
|
261
|
+
* a `/meta` write). Returns the new `metaVersion` parsed from the response
|
|
262
|
+
* ETag, or `undefined` when the server does not supply one.
|
|
171
263
|
*
|
|
172
264
|
* @param options {object}
|
|
265
|
+
* @param [options.custom] {Json} absent = write the cleared state
|
|
173
266
|
* @param options.id {string}
|
|
174
|
-
* @param options.custom {Json}
|
|
175
267
|
* @param [options.ifMatch] {string}
|
|
176
268
|
* @param [options.ifNoneMatch] {boolean}
|
|
177
269
|
* @returns {Promise<number | undefined>}
|
|
178
270
|
*/
|
|
179
271
|
putMeta(options: {
|
|
180
272
|
id: string;
|
|
181
|
-
custom
|
|
273
|
+
custom?: Json;
|
|
182
274
|
ifMatch?: string;
|
|
183
275
|
ifNoneMatch?: boolean;
|
|
184
276
|
}): Promise<number | undefined>;
|
|
@@ -197,12 +289,20 @@ export interface WasSyncPort extends WasSyncBasePort {
|
|
|
197
289
|
* cannot be resolved (e.g. a feed re-read that exhausts its scan budget), so
|
|
198
290
|
* the replication cycle retries rather than fabricating a false tombstone.
|
|
199
291
|
*
|
|
292
|
+
* `cache` is an OPTIONAL {@link MasterReadCache}, shared by the rows of one
|
|
293
|
+
* push batch (see its own docs for the contract). A hit is no staler than a
|
|
294
|
+
* read issued at the same moment, since the memo lives only for that batch. An
|
|
295
|
+
* implementation that ignores it is fully conformant, and a caller that omits
|
|
296
|
+
* it gets an uncached read.
|
|
297
|
+
*
|
|
200
298
|
* @param options {object}
|
|
201
299
|
* @param options.id {string}
|
|
300
|
+
* @param [options.cache] {MasterReadCache}
|
|
202
301
|
* @returns {Promise<MasterState | null>}
|
|
203
302
|
*/
|
|
204
303
|
get(options: {
|
|
205
304
|
id: string;
|
|
305
|
+
cache?: MasterReadCache;
|
|
206
306
|
}): Promise<MasterState | null>;
|
|
207
307
|
}
|
|
208
308
|
/**
|
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
|
|
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,25 +1,43 @@
|
|
|
1
|
-
/*!
|
|
2
|
-
* Copyright (c) 2026 Interop Alliance. All rights reserved.
|
|
3
|
-
*/
|
|
4
1
|
/**
|
|
5
|
-
*
|
|
2
|
+
* Structural equality over two opaque bodies, by canonical-free JSON string.
|
|
3
|
+
* Decides whether the content or the metadata half changed -- which endpoint(s)
|
|
4
|
+
* a push writes, and whether two states compare equal for conflict resolution.
|
|
5
|
+
* Content-addressed collections never mutate `data` for a given id, and a real
|
|
6
|
+
* metadata edit re-encrypts to fresh bytes, so this coarse comparison suffices.
|
|
6
7
|
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
|
|
11
|
-
|
|
8
|
+
* @param left {Json | undefined}
|
|
9
|
+
* @param right {Json | undefined}
|
|
10
|
+
* @returns {boolean}
|
|
11
|
+
*/
|
|
12
|
+
export function bodiesEqual(left, right) {
|
|
13
|
+
return JSON.stringify(left ?? null) === JSON.stringify(right ?? null);
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Copies the {@link OptionalBodyFields} that are present on `source` onto
|
|
17
|
+
* `target`, leaving an absent field absent (never writing an explicit
|
|
18
|
+
* `undefined`, which would surface the key in a serialized body). The single
|
|
19
|
+
* place every wire-to-local, feed-to-master, and master-to-conflict mapping
|
|
20
|
+
* shares, so a new optional wire field is added once.
|
|
12
21
|
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
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
|
+
* @param options {object}
|
|
23
|
+
* @param options.source {OptionalBodyFields}
|
|
24
|
+
* @param options.target {OptionalBodyFields} mutated in place
|
|
25
|
+
* @returns {void}
|
|
22
26
|
*/
|
|
27
|
+
export function copyOptionalBodyFields({ source, target }) {
|
|
28
|
+
if (source.data !== undefined) {
|
|
29
|
+
target.data = source.data;
|
|
30
|
+
}
|
|
31
|
+
if (source.metaVersion !== undefined) {
|
|
32
|
+
target.metaVersion = source.metaVersion;
|
|
33
|
+
}
|
|
34
|
+
if (source.custom !== undefined) {
|
|
35
|
+
target.custom = source.custom;
|
|
36
|
+
}
|
|
37
|
+
if (source.epoch !== undefined) {
|
|
38
|
+
target.epoch = source.epoch;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
23
41
|
/**
|
|
24
42
|
* Thrown by a {@link WasSyncPort} implementation when a conditional write is
|
|
25
43
|
* rejected with `412 precondition-failed` (a lost-update conflict, or a
|
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":"AAqDA;;;;;;;;;;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;AA8OD;;;;;;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;AAED;;;;;;;;;;;;;GAaG;AACH,MAAM,OAAO,gBAAiB,SAAQ,KAAK;IAChC,MAAM,CAAQ;IACvB,YAAY,MAAc;QACxB,KAAK,CAAC,mCAAmC,MAAM,IAAI,CAAC,CAAA;QACpD,IAAI,CAAC,IAAI,GAAG,kBAAkB,CAAA;QAC9B,IAAI,CAAC,MAAM,GAAG,MAAM,CAAA;IACtB,CAAC;CACF"}
|
|
@@ -26,17 +26,6 @@
|
|
|
26
26
|
import { type WasClient } from '@interop/was-client';
|
|
27
27
|
import type { IZcap } from '@interop/data-integrity-core';
|
|
28
28
|
import { type WasSyncBasePort } from './types.js';
|
|
29
|
-
/**
|
|
30
|
-
* Extracts an HTTP status from a raw ky/ezcap error. `was.request()` rejects on
|
|
31
|
-
* any non-2xx with `err.status` set (see `@interop/http-client`'s error
|
|
32
|
-
* normaliser); this reads it defensively from either location. Shared with the
|
|
33
|
-
* remote store's descriptor PUT and the dev-grant provisioner, which need the raw
|
|
34
|
-
* status for diagnostics rather than the mapped sync error.
|
|
35
|
-
*
|
|
36
|
-
* @param err {unknown}
|
|
37
|
-
* @returns {number | undefined}
|
|
38
|
-
*/
|
|
39
|
-
export declare function errorStatus(err: unknown): number | undefined;
|
|
40
29
|
/**
|
|
41
30
|
* Normalizes an unknown caught error into a display string: the `Error`'s
|
|
42
31
|
* `message` when it is one, else its `String(...)` coercion. Shared by the
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"wasSyncPort.d.ts","sourceRoot":"","sources":["../../src/sync/wasSyncPort.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,OAAO,EAA0B,KAAK,SAAS,EAAE,MAAM,qBAAqB,CAAA;
|
|
1
|
+
{"version":3,"file":"wasSyncPort.d.ts","sourceRoot":"","sources":["../../src/sync/wasSyncPort.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,OAAO,EAA0B,KAAK,SAAS,EAAE,MAAM,qBAAqB,CAAA;AAE5E,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,8BAA8B,CAAA;AAEzD,OAAO,EAIL,KAAK,eAAe,EAErB,MAAM,YAAY,CAAA;AAEnB;;;;;;;GAOG;AACH,wBAAgB,YAAY,CAAC,GAAG,EAAE,OAAO,GAAG,MAAM,CAEjD;AAgCD;;;;;;;;;;;;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,CAkHlB"}
|