@interop/was-react 0.1.2 → 0.1.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (149) hide show
  1. package/README.md +9 -1
  2. package/dist/auth/chapi.d.ts +3 -1
  3. package/dist/auth/chapi.d.ts.map +1 -1
  4. package/dist/auth/chapi.js +6 -2
  5. package/dist/auth/chapi.js.map +1 -1
  6. package/dist/auth/loginFlow.d.ts +30 -10
  7. package/dist/auth/loginFlow.d.ts.map +1 -1
  8. package/dist/auth/loginFlow.js +3 -1
  9. package/dist/auth/loginFlow.js.map +1 -1
  10. package/dist/auth/loginRequest.d.ts +9 -3
  11. package/dist/auth/loginRequest.d.ts.map +1 -1
  12. package/dist/auth/loginRequest.js +9 -3
  13. package/dist/auth/loginRequest.js.map +1 -1
  14. package/dist/auth/verifyResponse.d.ts +9 -3
  15. package/dist/auth/verifyResponse.d.ts.map +1 -1
  16. package/dist/auth/verifyResponse.js +6 -2
  17. package/dist/auth/verifyResponse.js.map +1 -1
  18. package/dist/config.d.ts +60 -20
  19. package/dist/config.d.ts.map +1 -1
  20. package/dist/config.js +12 -4
  21. package/dist/config.js.map +1 -1
  22. package/dist/dev/cli.d.ts.map +1 -1
  23. package/dist/dev/cli.js +4 -7
  24. package/dist/dev/cli.js.map +1 -1
  25. package/dist/dev/provisionDevGrants.d.ts +15 -5
  26. package/dist/dev/provisionDevGrants.d.ts.map +1 -1
  27. package/dist/dev/provisionDevGrants.js +2 -2
  28. package/dist/dev/provisionDevGrants.js.map +1 -1
  29. package/dist/grants.d.ts +7 -19
  30. package/dist/grants.d.ts.map +1 -1
  31. package/dist/grants.js +28 -7
  32. package/dist/grants.js.map +1 -1
  33. package/dist/identity/appSession.d.ts +9 -3
  34. package/dist/identity/appSession.d.ts.map +1 -1
  35. package/dist/identity/appSession.js +3 -1
  36. package/dist/identity/appSession.js.map +1 -1
  37. package/dist/identity/documentLoader.d.ts +3 -1
  38. package/dist/identity/documentLoader.d.ts.map +1 -1
  39. package/dist/identity/documentLoader.js +6 -2
  40. package/dist/identity/documentLoader.js.map +1 -1
  41. package/dist/identity/seedCredential.d.ts +9 -3
  42. package/dist/identity/seedCredential.d.ts.map +1 -1
  43. package/dist/identity/seedCredential.js +9 -15
  44. package/dist/identity/seedCredential.js.map +1 -1
  45. package/dist/identity/seedStore.d.ts +18 -6
  46. package/dist/identity/seedStore.d.ts.map +1 -1
  47. package/dist/identity/seedStore.js.map +1 -1
  48. package/dist/index.d.ts +1 -1
  49. package/dist/index.d.ts.map +1 -1
  50. package/dist/index.js +1 -1
  51. package/dist/index.js.map +1 -1
  52. package/dist/react/WasSessionProvider.d.ts.map +1 -1
  53. package/dist/react/WasSessionProvider.js +10 -1
  54. package/dist/react/WasSessionProvider.js.map +1 -1
  55. package/dist/react/hooks.d.ts +10 -5
  56. package/dist/react/hooks.d.ts.map +1 -1
  57. package/dist/react/hooks.js +6 -30
  58. package/dist/react/hooks.js.map +1 -1
  59. package/dist/session/authStore.d.ts +29 -7
  60. package/dist/session/authStore.d.ts.map +1 -1
  61. package/dist/session/authStore.js +123 -63
  62. package/dist/session/authStore.js.map +1 -1
  63. package/dist/storage/entityStore.d.ts +32 -9
  64. package/dist/storage/entityStore.d.ts.map +1 -1
  65. package/dist/storage/entityStore.js +88 -48
  66. package/dist/storage/entityStore.js.map +1 -1
  67. package/dist/storage/localStore.d.ts +13 -0
  68. package/dist/storage/localStore.d.ts.map +1 -1
  69. package/dist/storage/localStore.js +17 -0
  70. package/dist/storage/localStore.js.map +1 -1
  71. package/dist/storage/rehydrate.d.ts.map +1 -1
  72. package/dist/storage/rehydrate.js +3 -1
  73. package/dist/storage/rehydrate.js.map +1 -1
  74. package/dist/storage/syncController.d.ts +7 -3
  75. package/dist/storage/syncController.d.ts.map +1 -1
  76. package/dist/storage/syncController.js +28 -15
  77. package/dist/storage/syncController.js.map +1 -1
  78. package/dist/storage/syncStatusStore.d.ts +26 -1
  79. package/dist/storage/syncStatusStore.d.ts.map +1 -1
  80. package/dist/storage/syncStatusStore.js +40 -0
  81. package/dist/storage/syncStatusStore.js.map +1 -1
  82. package/dist/storage/wasRemoteStore.d.ts +3 -1
  83. package/dist/storage/wasRemoteStore.d.ts.map +1 -1
  84. package/dist/storage/wasRemoteStore.js +3 -3
  85. package/dist/storage/wasRemoteStore.js.map +1 -1
  86. package/dist/sync/feedMasterPort.d.ts +21 -19
  87. package/dist/sync/feedMasterPort.d.ts.map +1 -1
  88. package/dist/sync/feedMasterPort.js +18 -5
  89. package/dist/sync/feedMasterPort.js.map +1 -1
  90. package/dist/sync/index.d.ts +3 -3
  91. package/dist/sync/index.d.ts.map +1 -1
  92. package/dist/sync/index.js +2 -2
  93. package/dist/sync/index.js.map +1 -1
  94. package/dist/sync/lwwConflictHandler.d.ts +35 -2
  95. package/dist/sync/lwwConflictHandler.d.ts.map +1 -1
  96. package/dist/sync/lwwConflictHandler.js +47 -9
  97. package/dist/sync/lwwConflictHandler.js.map +1 -1
  98. package/dist/sync/pushWrites.d.ts +27 -8
  99. package/dist/sync/pushWrites.d.ts.map +1 -1
  100. package/dist/sync/pushWrites.js +43 -15
  101. package/dist/sync/pushWrites.js.map +1 -1
  102. package/dist/sync/types.d.ts +49 -16
  103. package/dist/sync/types.d.ts.map +1 -1
  104. package/dist/sync/types.js +17 -0
  105. package/dist/sync/types.js.map +1 -1
  106. package/dist/sync/wasReplication.d.ts.map +1 -1
  107. package/dist/sync/wasReplication.js +38 -1
  108. package/dist/sync/wasReplication.js.map +1 -1
  109. package/dist/sync/wasSyncPort.d.ts +29 -6
  110. package/dist/sync/wasSyncPort.d.ts.map +1 -1
  111. package/dist/sync/wasSyncPort.js +105 -101
  112. package/dist/sync/wasSyncPort.js.map +1 -1
  113. package/package.json +4 -3
  114. package/dist/auth/verifyResponse.test.d.ts +0 -2
  115. package/dist/auth/verifyResponse.test.d.ts.map +0 -1
  116. package/dist/auth/verifyResponse.test.js +0 -282
  117. package/dist/auth/verifyResponse.test.js.map +0 -1
  118. package/dist/dev/cli.test.d.ts +0 -2
  119. package/dist/dev/cli.test.d.ts.map +0 -1
  120. package/dist/dev/cli.test.js +0 -39
  121. package/dist/dev/cli.test.js.map +0 -1
  122. package/dist/grants.test.d.ts +0 -2
  123. package/dist/grants.test.d.ts.map +0 -1
  124. package/dist/grants.test.js +0 -79
  125. package/dist/grants.test.js.map +0 -1
  126. package/dist/identity/seedCredential.test.d.ts +0 -2
  127. package/dist/identity/seedCredential.test.d.ts.map +0 -1
  128. package/dist/identity/seedCredential.test.js +0 -205
  129. package/dist/identity/seedCredential.test.js.map +0 -1
  130. package/dist/storage/rehydrate.test.d.ts +0 -2
  131. package/dist/storage/rehydrate.test.d.ts.map +0 -1
  132. package/dist/storage/rehydrate.test.js +0 -172
  133. package/dist/storage/rehydrate.test.js.map +0 -1
  134. package/dist/sync/changesQuery.test.d.ts +0 -2
  135. package/dist/sync/changesQuery.test.d.ts.map +0 -1
  136. package/dist/sync/changesQuery.test.js +0 -145
  137. package/dist/sync/changesQuery.test.js.map +0 -1
  138. package/dist/sync/lww.test.d.ts +0 -2
  139. package/dist/sync/lww.test.d.ts.map +0 -1
  140. package/dist/sync/lww.test.js +0 -28
  141. package/dist/sync/lww.test.js.map +0 -1
  142. package/dist/sync/lwwConflictHandler.test.d.ts +0 -2
  143. package/dist/sync/lwwConflictHandler.test.d.ts.map +0 -1
  144. package/dist/sync/lwwConflictHandler.test.js +0 -78
  145. package/dist/sync/lwwConflictHandler.test.js.map +0 -1
  146. package/dist/sync/pushWrites.test.d.ts +0 -2
  147. package/dist/sync/pushWrites.test.d.ts.map +0 -1
  148. package/dist/sync/pushWrites.test.js +0 -287
  149. package/dist/sync/pushWrites.test.js.map +0 -1
@@ -19,10 +19,42 @@
19
19
  * every replica compares that same master against its own local edit, and the
20
20
  * `payloadWins` comparator is a total order over `(updatedAt, deviceId)`, so the
21
21
  * globally-latest payload wins on every replica with no coordination.
22
+ *
23
+ * The resolution rules, in order:
24
+ *
25
+ * 1. Version-only conflict: when the real master's whole content (`data` +
26
+ * `custom` + `_deleted`) still equals the assumed master's, the server holds
27
+ * nothing newer than what this replica last synced -- the 412 came from a
28
+ * stale `If-Match` (typically our own earlier write racing its feed echo).
29
+ * The local state (edit or tombstone) is re-asserted and re-pushed against
30
+ * the corrected version. Without this rule a local delete would be dropped by
31
+ * rule 5 and the entity would silently resurrect. `custom` MUST be part of
32
+ * this comparison, or a concurrent metadata-only edit committed on the server
33
+ * would be misclassified here and clobbered (rule 2 is what settles it).
34
+ * 2. Metadata conflict: `data` and `_deleted` are unchanged from the assumed
35
+ * master but `custom` differs -- a metadata-only edit committed on the server
36
+ * since this replica last synced (another device won the `/meta` race).
37
+ * Metadata carries no LWW timestamp of its own (the payload `updatedAt` lives
38
+ * in the encrypted `data`, which is equal on both sides here), so there is no
39
+ * payload to compare; the sound, replica-independent default is that the
40
+ * server-committed state wins -- the real master is adopted for `custom`.
41
+ * Without this rule the equal-`data` case would fall through to rule 3, where
42
+ * the two payloads compare equal and the tie keeps the local (stale) `custom`,
43
+ * silently clobbering the committed metadata.
44
+ * 3. Both sides carry an LWW payload: pure payload LWW via `payloadWins`.
45
+ * 4. A live local edit vs an incomparable remote (e.g. a remote tombstone):
46
+ * the edit wins and is re-pushed (resurrection).
47
+ * 5. Everything else -- a local tombstone vs a REAL remote content change, or
48
+ * both sides incomparable: the master wins. Together with rule 4 this makes
49
+ * the delete-vs-concurrent-edit rule "the edit wins" on every replica: a
50
+ * tombstone carries no LWW payload of its own, so a genuine racing edit
51
+ * deterministically survives, whichever write reached the server first.
22
52
  */
23
53
  import type { WithDeleted } from 'rxdb/plugins/core';
24
54
  import type { Json, SyncedDoc } from './types.js';
25
- /** The LWW fields read out of a decrypted entity payload. */
55
+ /**
56
+ * The LWW fields read out of a decrypted entity payload.
57
+ */
26
58
  interface LwwPayload {
27
59
  updatedAt: string;
28
60
  deviceId: string;
@@ -40,9 +72,10 @@ interface LwwPayload {
40
72
  */
41
73
  export declare function makeLwwConflictHandler(decrypt: (envelope: Json) => Promise<Json>, payloadWins?: (remote: LwwPayload, local: LwwPayload) => boolean): {
42
74
  isEqual(a: WithDeleted<SyncedDoc>, b: WithDeleted<SyncedDoc>): boolean;
43
- resolve({ realMasterState, newDocumentState }: {
75
+ resolve({ realMasterState, newDocumentState, assumedMasterState }: {
44
76
  realMasterState: WithDeleted<SyncedDoc>;
45
77
  newDocumentState: WithDeleted<SyncedDoc>;
78
+ assumedMasterState?: WithDeleted<SyncedDoc>;
46
79
  }): Promise<WithDeleted<SyncedDoc>>;
47
80
  };
48
81
  export {};
@@ -1 +1 @@
1
- {"version":3,"file":"lwwConflictHandler.d.ts","sourceRoot":"","sources":["../../src/sync/lwwConflictHandler.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH;;;;;;;;;;;;;;;;;;GAkBG;AACH,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAA;AACpD,OAAO,KAAK,EAAE,IAAI,EAAE,SAAS,EAAE,MAAM,YAAY,CAAA;AAGjD,6DAA6D;AAC7D,UAAU,UAAU;IAClB,SAAS,EAAE,MAAM,CAAA;IACjB,QAAQ,EAAE,MAAM,CAAA;CACjB;AAiCD;;;;;;;;;;GAUG;AACH,wBAAgB,sBAAsB,CACpC,OAAO,EAAE,CAAC,QAAQ,EAAE,IAAI,KAAK,OAAO,CAAC,IAAI,CAAC,EAC1C,WAAW,GAAE,CACX,MAAM,EAAE,UAAU,EAClB,KAAK,EAAE,UAAU,KACd,OAA2B;eAKnB,WAAW,CAAC,SAAS,CAAC,KAAK,WAAW,CAAC,SAAS,CAAC,GAAG,OAAO;mDAOnE;QACD,eAAe,EAAE,WAAW,CAAC,SAAS,CAAC,CAAA;QACvC,gBAAgB,EAAE,WAAW,CAAC,SAAS,CAAC,CAAA;KACzC,GAAG,OAAO,CAAC,WAAW,CAAC,SAAS,CAAC,CAAC;EAmBtC"}
1
+ {"version":3,"file":"lwwConflictHandler.d.ts","sourceRoot":"","sources":["../../src/sync/lwwConflictHandler.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgDG;AACH,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAA;AACpD,OAAO,KAAK,EAAE,IAAI,EAAE,SAAS,EAAE,MAAM,YAAY,CAAA;AAGjD;;GAEG;AACH,UAAU,UAAU;IAClB,SAAS,EAAE,MAAM,CAAA;IACjB,QAAQ,EAAE,MAAM,CAAA;CACjB;AAmCD;;;;;;;;;;GAUG;AACH,wBAAgB,sBAAsB,CACpC,OAAO,EAAE,CAAC,QAAQ,EAAE,IAAI,KAAK,OAAO,CAAC,IAAI,CAAC,EAC1C,WAAW,GAAE,CACX,MAAM,EAAE,UAAU,EAClB,KAAK,EAAE,UAAU,KACd,OAA2B;eAQnB,WAAW,CAAC,SAAS,CAAC,KAAK,WAAW,CAAC,SAAS,CAAC,GAAG,OAAO;uEAcnE;QACD,eAAe,EAAE,WAAW,CAAC,SAAS,CAAC,CAAA;QACvC,gBAAgB,EAAE,WAAW,CAAC,SAAS,CAAC,CAAA;QACxC,kBAAkB,CAAC,EAAE,WAAW,CAAC,SAAS,CAAC,CAAA;KAC5C,GAAG,OAAO,CAAC,WAAW,CAAC,SAAS,CAAC,CAAC;EAoDtC"}
@@ -1,5 +1,7 @@
1
1
  import { remotePayloadWins } from './lww.js';
2
- /** Structural equality of two opaque bodies (used for the fast `isEqual`). */
2
+ /**
3
+ * Structural equality of two opaque bodies (used for the fast `isEqual`).
4
+ */
3
5
  function bodiesEqual(a, b) {
4
6
  return JSON.stringify(a ?? null) === JSON.stringify(b ?? null);
5
7
  }
@@ -37,27 +39,63 @@ async function lwwFieldsOf(doc, decrypt) {
37
39
  */
38
40
  export function makeLwwConflictHandler(decrypt, payloadWins = remotePayloadWins) {
39
41
  return {
40
- // Non-async and fast, as RxDB requires: no real conflict when the opaque
41
- // content + deletion flag already agree (e.g. our own write echoing back).
42
+ // Non-async and fast, as RxDB requires. The server revisions (`version` /
43
+ // `metaVersion`) participate deliberately: our own write's feed echo comes
44
+ // back byte-identical but one revision ahead, and it must NOT compare
45
+ // equal, or the higher version is never adopted and every later
46
+ // conditional write sends a stale `If-Match` (a guaranteed 412).
42
47
  isEqual(a, b) {
43
- return a._deleted === b._deleted && bodiesEqual(a.data, b.data);
48
+ return (a._deleted === b._deleted &&
49
+ a.version === b.version &&
50
+ a.metaVersion === b.metaVersion &&
51
+ bodiesEqual(a.data, b.data) &&
52
+ bodiesEqual(a.custom, b.custom));
44
53
  },
45
- async resolve({ realMasterState, newDocumentState }) {
54
+ async resolve({ realMasterState, newDocumentState, assumedMasterState }) {
55
+ // Rule 1 -- version-only conflict: the master's whole content (`data` +
56
+ // `custom`) is exactly what this replica last synced (only the revision
57
+ // moved, e.g. our own write racing its feed echo), so nothing remote is
58
+ // actually newer. Re-assert the local state -- crucially including a local
59
+ // TOMBSTONE, which rule 5 would otherwise drop (the silent-resurrection
60
+ // bug). `custom` is part of this equality so a concurrent metadata-only
61
+ // edit is NOT misclassified here (rule 2 handles it).
62
+ if (assumedMasterState !== undefined &&
63
+ realMasterState._deleted === assumedMasterState._deleted &&
64
+ bodiesEqual(realMasterState.data, assumedMasterState.data) &&
65
+ bodiesEqual(realMasterState.custom, assumedMasterState.custom)) {
66
+ return newDocumentState;
67
+ }
68
+ // Rule 2 -- metadata conflict: `data` and `_deleted` are unchanged from
69
+ // the assumed master, but `custom` moved on the server (a metadata-only
70
+ // edit that won the `/meta` race on another device). Metadata has no LWW
71
+ // timestamp of its own -- the payload `updatedAt` lives in `data`, equal
72
+ // on both sides here -- so the sound, replica-independent default is that
73
+ // the server-committed state wins: adopt the real master for `custom`.
74
+ // Without this rule the equal-`data` case would reach rule 3 with two
75
+ // equal payloads and the tie would keep the local (stale) metadata.
76
+ if (assumedMasterState !== undefined &&
77
+ realMasterState._deleted === assumedMasterState._deleted &&
78
+ bodiesEqual(realMasterState.data, assumedMasterState.data) &&
79
+ !bodiesEqual(realMasterState.custom, assumedMasterState.custom)) {
80
+ return realMasterState;
81
+ }
46
82
  const [remote, local] = await Promise.all([
47
83
  lwwFieldsOf(realMasterState, decrypt),
48
84
  lwwFieldsOf(newDocumentState, decrypt)
49
85
  ]);
50
- // Both sides comparable: pure payload LWW.
86
+ // Rule 3 -- both sides comparable: pure payload LWW.
51
87
  if (remote && local) {
52
88
  return payloadWins(remote, local) ? realMasterState : newDocumentState;
53
89
  }
54
- // A live local edit against a remote tombstone (or otherwise
90
+ // Rule 4 -- a live local edit against a remote tombstone (or otherwise
55
91
  // incomparable remote): keep the edit (resurrect / re-assert the write).
56
92
  if (local && !remote) {
57
93
  return newDocumentState;
58
94
  }
59
- // Everything else (local tombstone, both incomparable): the master wins,
60
- // which is deterministic and convergent across replicas.
95
+ // Rule 5 -- everything else (a local tombstone racing a REAL remote
96
+ // content change, or both incomparable): the master wins, which is
97
+ // deterministic and convergent across replicas (the edit survives the
98
+ // delete on every device).
61
99
  return realMasterState;
62
100
  }
63
101
  };
@@ -1 +1 @@
1
- {"version":3,"file":"lwwConflictHandler.js","sourceRoot":"","sources":["../../src/sync/lwwConflictHandler.ts"],"names":[],"mappings":"AAwBA,OAAO,EAAE,iBAAiB,EAAE,MAAM,UAAU,CAAA;AAQ5C,8EAA8E;AAC9E,SAAS,WAAW,CAAC,CAAmB,EAAE,CAAmB;IAC3D,OAAO,IAAI,CAAC,SAAS,CAAC,CAAC,IAAI,IAAI,CAAC,KAAK,IAAI,CAAC,SAAS,CAAC,CAAC,IAAI,IAAI,CAAC,CAAA;AAChE,CAAC;AAED;;;;GAIG;AACH,KAAK,UAAU,WAAW,CACxB,GAA2B,EAC3B,OAA0C;IAE1C,IAAI,GAAG,CAAC,QAAQ,IAAI,GAAG,CAAC,IAAI,KAAK,SAAS,EAAE,CAAC;QAC3C,OAAO,IAAI,CAAA;IACb,CAAC;IACD,IAAI,CAAC;QACH,MAAM,OAAO,GAAG,CAAC,MAAM,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,CAAwB,CAAA;QAChE,IACE,OAAO,OAAO,CAAC,SAAS,KAAK,QAAQ;YACrC,OAAO,OAAO,CAAC,QAAQ,KAAK,QAAQ,EACpC,CAAC;YACD,OAAO,EAAE,SAAS,EAAE,OAAO,CAAC,SAAS,EAAE,QAAQ,EAAE,OAAO,CAAC,QAAQ,EAAE,CAAA;QACrE,CAAC;QACD,OAAO,IAAI,CAAA;IACb,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,IAAI,CAAA;IACb,CAAC;AACH,CAAC;AAED;;;;;;;;;;GAUG;AACH,MAAM,UAAU,sBAAsB,CACpC,OAA0C,EAC1C,cAGe,iBAAiB;IAEhC,OAAO;QACL,yEAAyE;QACzE,2EAA2E;QAC3E,OAAO,CAAC,CAAyB,EAAE,CAAyB;YAC1D,OAAO,CAAC,CAAC,QAAQ,KAAK,CAAC,CAAC,QAAQ,IAAI,WAAW,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC,IAAI,CAAC,CAAA;QACjE,CAAC;QAED,KAAK,CAAC,OAAO,CAAC,EACZ,eAAe,EACf,gBAAgB,EAIjB;YACC,MAAM,CAAC,MAAM,EAAE,KAAK,CAAC,GAAG,MAAM,OAAO,CAAC,GAAG,CAAC;gBACxC,WAAW,CAAC,eAAe,EAAE,OAAO,CAAC;gBACrC,WAAW,CAAC,gBAAgB,EAAE,OAAO,CAAC;aACvC,CAAC,CAAA;YACF,2CAA2C;YAC3C,IAAI,MAAM,IAAI,KAAK,EAAE,CAAC;gBACpB,OAAO,WAAW,CAAC,MAAM,EAAE,KAAK,CAAC,CAAC,CAAC,CAAC,eAAe,CAAC,CAAC,CAAC,gBAAgB,CAAA;YACxE,CAAC;YACD,6DAA6D;YAC7D,yEAAyE;YACzE,IAAI,KAAK,IAAI,CAAC,MAAM,EAAE,CAAC;gBACrB,OAAO,gBAAgB,CAAA;YACzB,CAAC;YACD,yEAAyE;YACzE,yDAAyD;YACzD,OAAO,eAAe,CAAA;QACxB,CAAC;KACF,CAAA;AACH,CAAC"}
1
+ {"version":3,"file":"lwwConflictHandler.js","sourceRoot":"","sources":["../../src/sync/lwwConflictHandler.ts"],"names":[],"mappings":"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"}
@@ -20,16 +20,29 @@
20
20
  * `/meta` write to a resource that does not yet exist.
21
21
  *
22
22
  * RxDB's push contract asks only for *conflicts* back (the current master state
23
- * of each rejected row); a successful write's new `version` / `metaVersion` is
24
- * not returned here -- it is picked up on the next pull, where our own write
25
- * echoes back as a remote change (idempotent, since the bodies are
26
- * byte-identical). We deliberately do not capture the `204` ETag to short-circuit
27
- * that echo: doing so would require a side write to the local revision fields
28
- * from inside the push handler, which risks a re-push loop, for no correctness
29
- * gain on immutable content-addressed collections.
23
+ * of each rejected row), so a successful write's new `version` / `metaVersion`
24
+ * is reported out-of-band: the response ETag of each accepted write is captured
25
+ * and handed to the optional `onWriteAccepted` callback, which writes the acked
26
+ * revision back into the local row (see `createWasReplication`). Without that
27
+ * write-back the local `version` would stay one revision behind the server and
28
+ * every subsequent conditional write would send a stale `If-Match` and 412. The
29
+ * write-back only touches the revision fields (never `data` / `updatedAt`), so
30
+ * the follow-up push cycle it triggers finds nothing changed to write and
31
+ * settles -- no re-push loop.
30
32
  */
31
33
  import type { WithDeleted } from 'rxdb/plugins/core';
32
34
  import type { SyncedDoc, WasSyncPort } from './types.js';
35
+ /**
36
+ * The acked server revisions of one row's accepted writes: the new content
37
+ * `version` (from a `PUT /:id` or `DELETE /:id` response ETag) and/or the new
38
+ * `metaVersion` (from a `PUT /:id/meta` response ETag). Absent fields mean the
39
+ * corresponding write did not run or its response carried no ETag.
40
+ */
41
+ export interface PushWriteAck {
42
+ id: string;
43
+ version?: number;
44
+ metaVersion?: number;
45
+ }
33
46
  /**
34
47
  * Formats a master revision (`version` or `metaVersion`) as the quoted strong
35
48
  * ETag the server compares `If-Match` against (revision `3` becomes `"3"`).
@@ -45,12 +58,18 @@ export declare function formatEtag(revision: number): string;
45
58
  * Rows are pushed concurrently; if any non-conflict error is thrown the whole
46
59
  * batch rejects (RxDB re-sends it later), matching RxDB's all-or-nothing retry.
47
60
  *
61
+ * Each accepted write's acked server revision(s) are handed to
62
+ * `onWriteAccepted` (when supplied) as soon as that row's writes settle, so the
63
+ * caller can write the new `version` / `metaVersion` back into the local row
64
+ * and keep subsequent conditional writes' `If-Match` in step with the server.
65
+ *
48
66
  * @param port {WasSyncPort}
67
+ * @param [onWriteAccepted] {(ack: PushWriteAck) => Promise<void>}
49
68
  * @returns {(rows: Array<{ newDocumentState: WithDeleted<SyncedDoc>,
50
69
  * assumedMasterState?: WithDeleted<SyncedDoc> }>) =>
51
70
  * Promise<WithDeleted<SyncedDoc>[]>}
52
71
  */
53
- export declare function createPushHandler(port: WasSyncPort): (rows: Array<{
72
+ export declare function createPushHandler(port: WasSyncPort, onWriteAccepted?: (ack: PushWriteAck) => Promise<void>): (rows: Array<{
54
73
  newDocumentState: WithDeleted<SyncedDoc>;
55
74
  assumedMasterState?: WithDeleted<SyncedDoc>;
56
75
  }>) => Promise<WithDeleted<SyncedDoc>[]>;
@@ -1 +1 @@
1
- {"version":3,"file":"pushWrites.d.ts","sourceRoot":"","sources":["../../src/sync/pushWrites.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AACH,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAA;AACpD,OAAO,KAAK,EAAqB,SAAS,EAAE,WAAW,EAAE,MAAM,YAAY,CAAA;AAG3E;;;;;;GAMG;AACH,wBAAgB,UAAU,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,CAEnD;AA0JD;;;;;;;;;;;GAWG;AACH,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,WAAW,IAE/C,MAAM,KAAK,CAAC;IACV,gBAAgB,EAAE,WAAW,CAAC,SAAS,CAAC,CAAA;IACxC,kBAAkB,CAAC,EAAE,WAAW,CAAC,SAAS,CAAC,CAAA;CAC5C,CAAC,KACD,OAAO,CAAC,WAAW,CAAC,SAAS,CAAC,EAAE,CAAC,CAcrC"}
1
+ {"version":3,"file":"pushWrites.d.ts","sourceRoot":"","sources":["../../src/sync/pushWrites.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AACH,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAA;AACpD,OAAO,KAAK,EAAqB,SAAS,EAAE,WAAW,EAAE,MAAM,YAAY,CAAA;AAG3E;;;;;GAKG;AACH,MAAM,WAAW,YAAY;IAC3B,EAAE,EAAE,MAAM,CAAA;IACV,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,WAAW,CAAC,EAAE,MAAM,CAAA;CACrB;AAED;;;;;;GAMG;AACH,wBAAgB,UAAU,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,CAEnD;AA4KD;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,iBAAiB,CAC/B,IAAI,EAAE,WAAW,EACjB,eAAe,CAAC,EAAE,CAAC,GAAG,EAAE,YAAY,KAAK,OAAO,CAAC,IAAI,CAAC,IAGpD,MAAM,KAAK,CAAC;IACV,gBAAgB,EAAE,WAAW,CAAC,SAAS,CAAC,CAAA;IACxC,kBAAkB,CAAC,EAAE,WAAW,CAAC,SAAS,CAAC,CAAA;CAC5C,CAAC,KACD,OAAO,CAAC,WAAW,CAAC,SAAS,CAAC,EAAE,CAAC,CAoBrC"}
@@ -67,35 +67,41 @@ async function assembleConflict({ port, id, fallbackUpdatedAt, fallbackVersion }
67
67
  /**
68
68
  * Sends one local change to the remote Collection as up to two conditional
69
69
  * writes (content, then metadata). Returns the master-state conflict entry on a
70
- * `412` at either step, or `null` on success.
70
+ * `412` at either step (with `ack: null`), or the accepted writes' acked
71
+ * revisions on success (`ack: null` when no response carried a revision).
71
72
  *
72
73
  * @param options {object}
73
74
  * @param options.port {WasSyncPort}
74
75
  * @param options.newDocumentState {WithDeleted<SyncedDoc>}
75
76
  * @param [options.assumedMasterState] {WithDeleted<SyncedDoc>}
76
- * @returns {Promise<WithDeleted<SyncedDoc> | null>}
77
+ * @returns {Promise<{ conflict: WithDeleted<SyncedDoc> | null,
78
+ * ack: PushWriteAck | null }>}
77
79
  */
78
80
  async function pushRow({ port, newDocumentState, assumedMasterState }) {
79
81
  const { id } = newDocumentState;
80
82
  const assumedVersion = assumedMasterState?.version;
81
83
  const isCreate = assumedMasterState === undefined;
82
84
  try {
85
+ const ack = { id };
83
86
  if (newDocumentState._deleted) {
84
87
  // Delete supersedes any metadata write: drop the content, tombstone wins.
85
- await port.deleteContent({
88
+ const ackedVersion = await port.deleteContent({
86
89
  id,
87
90
  ...(assumedVersion !== undefined && {
88
91
  ifMatch: formatEtag(assumedVersion)
89
92
  })
90
93
  });
91
- return null;
94
+ if (ackedVersion !== undefined) {
95
+ ack.version = ackedVersion;
96
+ }
97
+ return { conflict: null, ack: ack.version !== undefined ? ack : null };
92
98
  }
93
99
  // Content half: write on create, or when the content body changed. For a
94
100
  // content-addressed collection the update case never fires (an immutable
95
101
  // body for a stable id), but it is handled for generality.
96
102
  const contentChanged = isCreate || !bodiesEqual(newDocumentState.data, assumedMasterState?.data);
97
103
  if (contentChanged) {
98
- await port.putContent({
104
+ const ackedVersion = await port.putContent({
99
105
  id,
100
106
  data: newDocumentState.data ?? null,
101
107
  ...(isCreate
@@ -104,30 +110,38 @@ async function pushRow({ port, newDocumentState, assumedMasterState }) {
104
110
  ifMatch: formatEtag(assumedVersion)
105
111
  })
106
112
  });
113
+ if (ackedVersion !== undefined) {
114
+ ack.version = ackedVersion;
115
+ }
107
116
  }
108
117
  // Metadata half: write when the resource has metadata and it changed. On a
109
118
  // create this runs after the content write (the resource must exist first).
110
119
  const metadataChanged = !bodiesEqual(newDocumentState.custom, assumedMasterState?.custom);
111
120
  if (newDocumentState.custom !== undefined && metadataChanged) {
112
121
  const assumedMetaVersion = assumedMasterState?.metaVersion;
113
- await port.putMeta({
122
+ const ackedMetaVersion = await port.putMeta({
114
123
  id,
115
124
  custom: newDocumentState.custom,
116
125
  ...(assumedMetaVersion !== undefined
117
126
  ? { ifMatch: formatEtag(assumedMetaVersion) }
118
127
  : { ifNoneMatch: true })
119
128
  });
129
+ if (ackedMetaVersion !== undefined) {
130
+ ack.metaVersion = ackedMetaVersion;
131
+ }
120
132
  }
121
- return null;
133
+ const hasAck = ack.version !== undefined || ack.metaVersion !== undefined;
134
+ return { conflict: null, ack: hasAck ? ack : null };
122
135
  }
123
136
  catch (err) {
124
137
  if (err instanceof WasSyncConflictError) {
125
- return assembleConflict({
138
+ const conflict = await assembleConflict({
126
139
  port,
127
140
  id,
128
141
  fallbackUpdatedAt: newDocumentState.updatedAt,
129
142
  fallbackVersion: assumedVersion ?? newDocumentState.version
130
143
  });
144
+ return { conflict, ack: null };
131
145
  }
132
146
  // Any non-conflict error (network, 5xx, auth) propagates so RxDB retries
133
147
  // the whole batch with backoff.
@@ -141,19 +155,33 @@ async function pushRow({ port, newDocumentState, assumedMasterState }) {
141
155
  * Rows are pushed concurrently; if any non-conflict error is thrown the whole
142
156
  * batch rejects (RxDB re-sends it later), matching RxDB's all-or-nothing retry.
143
157
  *
158
+ * Each accepted write's acked server revision(s) are handed to
159
+ * `onWriteAccepted` (when supplied) as soon as that row's writes settle, so the
160
+ * caller can write the new `version` / `metaVersion` back into the local row
161
+ * and keep subsequent conditional writes' `If-Match` in step with the server.
162
+ *
144
163
  * @param port {WasSyncPort}
164
+ * @param [onWriteAccepted] {(ack: PushWriteAck) => Promise<void>}
145
165
  * @returns {(rows: Array<{ newDocumentState: WithDeleted<SyncedDoc>,
146
166
  * assumedMasterState?: WithDeleted<SyncedDoc> }>) =>
147
167
  * Promise<WithDeleted<SyncedDoc>[]>}
148
168
  */
149
- export function createPushHandler(port) {
169
+ export function createPushHandler(port, onWriteAccepted) {
150
170
  return async function push(rows) {
151
- const results = await Promise.all(rows.map(row => pushRow({
152
- port,
153
- newDocumentState: row.newDocumentState,
154
- assumedMasterState: row.assumedMasterState
155
- })));
156
- return results.filter((conflict) => conflict !== null);
171
+ const results = await Promise.all(rows.map(async (row) => {
172
+ const result = await pushRow({
173
+ port,
174
+ newDocumentState: row.newDocumentState,
175
+ assumedMasterState: row.assumedMasterState
176
+ });
177
+ if (result.ack !== null && onWriteAccepted !== undefined) {
178
+ await onWriteAccepted(result.ack);
179
+ }
180
+ return result;
181
+ }));
182
+ return results
183
+ .map(result => result.conflict)
184
+ .filter((conflict) => conflict !== null);
157
185
  };
158
186
  }
159
187
  //# sourceMappingURL=pushWrites.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"pushWrites.js","sourceRoot":"","sources":["../../src/sync/pushWrites.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"pushWrites.js","sourceRoot":"","sources":["../../src/sync/pushWrites.ts"],"names":[],"mappings":"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"}
@@ -87,18 +87,23 @@ export interface MasterState {
87
87
  custom?: Json;
88
88
  }
89
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.
90
+ * The write/query half of the injected WAS-access seam. An adapter (the app-side
91
+ * `createWasSyncPort`) implements this over `@interop/was-client`; the core
92
+ * module depends only on this interface, never on `was-client` itself. Every
93
+ * method moves the stored body verbatim -- no codec, no key handling -- so the
94
+ * same port works for plaintext and encrypted collections alike.
95
95
  *
96
96
  * `putContent` / `deleteContent` / `putMeta` MUST throw
97
97
  * {@link WasSyncConflictError} when the server rejects a conditional write with
98
98
  * `412 precondition-failed`, and let every other error propagate so RxDB's
99
99
  * retry/backoff handles it.
100
+ *
101
+ * The 412 conflict re-read (`get`) is deliberately NOT part of this base:
102
+ * `createWasSyncPort` returns a `WasSyncBasePort`, and `withFeedMasterRead`
103
+ * supplies a `get` that resolves the master state from the changes-feed body
104
+ * (origin-independent) to produce a full {@link WasSyncPort}.
100
105
  */
101
- export interface WasSyncPort {
106
+ export interface WasSyncBasePort {
102
107
  /**
103
108
  * Pulls one page of the `changes` feed. Omit `checkpoint` for the first page.
104
109
  * Returns the page's `documents` and its resume `checkpoint`, or
@@ -119,59 +124,74 @@ export interface WasSyncPort {
119
124
  /**
120
125
  * Conditionally writes the content body verbatim (`PUT /:id`). Pass
121
126
  * `ifNoneMatch: true` for a create-if-absent, or `ifMatch` (a quoted ETag over
122
- * the content `version`) for an update-if-unchanged.
127
+ * the content `version`) for an update-if-unchanged. Returns the new content
128
+ * `version` parsed from the response ETag (the acked master revision), or
129
+ * `undefined` when the server does not supply one.
123
130
  *
124
131
  * @param options {object}
125
132
  * @param options.id {string}
126
133
  * @param options.data {Json}
127
134
  * @param [options.ifMatch] {string}
128
135
  * @param [options.ifNoneMatch] {boolean}
129
- * @returns {Promise<void>}
136
+ * @returns {Promise<number | undefined>}
130
137
  */
131
138
  putContent(options: {
132
139
  id: string;
133
140
  data: Json;
134
141
  ifMatch?: string;
135
142
  ifNoneMatch?: boolean;
136
- }): Promise<void>;
143
+ }): Promise<number | undefined>;
137
144
  /**
138
145
  * Conditionally deletes a resource (writes a tombstone; `DELETE /:id`). Pass
139
146
  * `ifMatch` (a quoted ETag over the content `version`) to delete only if
140
- * unchanged.
147
+ * unchanged. Returns the tombstone `version` parsed from the response ETag
148
+ * when the server supplies one (the reference server does not).
141
149
  *
142
150
  * @param options {object}
143
151
  * @param options.id {string}
144
152
  * @param [options.ifMatch] {string}
145
- * @returns {Promise<void>}
153
+ * @returns {Promise<number | undefined>}
146
154
  */
147
155
  deleteContent(options: {
148
156
  id: string;
149
157
  ifMatch?: string;
150
- }): Promise<void>;
158
+ }): Promise<number | undefined>;
151
159
  /**
152
160
  * Conditionally writes the metadata body verbatim (`PUT /:id/meta`, body
153
161
  * `{ custom }`). Pass `ifNoneMatch: true` when the resource has no metadata
154
162
  * yet, or `ifMatch` (a quoted ETag over `metaVersion`) for an
155
163
  * update-if-unchanged. The resource must already exist (the server does not
156
- * create a resource from a `/meta` write).
164
+ * create a resource from a `/meta` write). Returns the new `metaVersion`
165
+ * parsed from the response ETag, or `undefined` when the server does not
166
+ * supply one.
157
167
  *
158
168
  * @param options {object}
159
169
  * @param options.id {string}
160
170
  * @param options.custom {Json}
161
171
  * @param [options.ifMatch] {string}
162
172
  * @param [options.ifNoneMatch] {boolean}
163
- * @returns {Promise<void>}
173
+ * @returns {Promise<number | undefined>}
164
174
  */
165
175
  putMeta(options: {
166
176
  id: string;
167
177
  custom: Json;
168
178
  ifMatch?: string;
169
179
  ifNoneMatch?: boolean;
170
- }): Promise<void>;
180
+ }): Promise<number | undefined>;
181
+ }
182
+ /**
183
+ * The full sync port the core consumes: a {@link WasSyncBasePort}'s write/query
184
+ * methods plus the `get` used by the 412 conflict assembler. The push handler
185
+ * and `createWasReplication` require this full port; build one by wrapping a
186
+ * base port with `withFeedMasterRead`, which supplies `get`.
187
+ */
188
+ export interface WasSyncPort extends WasSyncBasePort {
171
189
  /**
172
190
  * Re-reads a single resource's current master state (content + metadata) for
173
191
  * the 412 conflict assembler. Returns `null` when the resource is genuinely
174
- * absent (a delete/delete race).
192
+ * absent (a delete/delete race); throws a retryable error when the master
193
+ * cannot be resolved (e.g. a feed re-read that exhausts its scan budget), so
194
+ * the replication cycle retries rather than fabricating a false tombstone.
175
195
  *
176
196
  * @param options {object}
177
197
  * @param options.id {string}
@@ -191,4 +211,17 @@ export interface WasSyncPort {
191
211
  export declare class WasSyncConflictError extends Error {
192
212
  constructor(message?: string);
193
213
  }
214
+ /**
215
+ * Thrown by a {@link WasSyncPort} implementation when a request is rejected with
216
+ * `401 unauthorized` or `403 forbidden` -- the storage-access-expired/revoked
217
+ * signal. Mapping the raw HTTP status to a typed error at the port boundary (the
218
+ * same way `412` becomes {@link WasSyncConflictError}) lets the sync controller
219
+ * recognise expired access with an `instanceof` check rather than re-extracting
220
+ * status codes from a wrapped RxDB error graph. The offending HTTP status is
221
+ * kept on `status` for diagnostics.
222
+ */
223
+ export declare class WasSyncAuthError extends Error {
224
+ readonly status: number;
225
+ constructor(status: number);
226
+ }
194
227
  //# sourceMappingURL=types.d.ts.map
@@ -1 +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"}
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/sync/types.ts"],"names":[],"mappings":"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"}
@@ -33,4 +33,21 @@ export class WasSyncConflictError extends Error {
33
33
  this.name = 'WasSyncConflictError';
34
34
  }
35
35
  }
36
+ /**
37
+ * Thrown by a {@link WasSyncPort} implementation when a request is rejected with
38
+ * `401 unauthorized` or `403 forbidden` -- the storage-access-expired/revoked
39
+ * signal. Mapping the raw HTTP status to a typed error at the port boundary (the
40
+ * same way `412` becomes {@link WasSyncConflictError}) lets the sync controller
41
+ * recognise expired access with an `instanceof` check rather than re-extracting
42
+ * status codes from a wrapped RxDB error graph. The offending HTTP status is
43
+ * kept on `status` for diagnostics.
44
+ */
45
+ export class WasSyncAuthError extends Error {
46
+ status;
47
+ constructor(status) {
48
+ super(`WAS storage access denied (HTTP ${status}).`);
49
+ this.name = 'WasSyncAuthError';
50
+ this.status = status;
51
+ }
52
+ }
36
53
  //# sourceMappingURL=types.js.map
@@ -1 +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"}
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/sync/types.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH;;;;;;;;;;;;;;;;;;GAkBG;AA2LH;;;;;;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;;;;;;;;GAQG;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"}
@@ -1 +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"}
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;AA2CxE;;;;;;;;;;;;;;;;;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"}
@@ -16,6 +16,43 @@
16
16
  import { replicateRxCollection } from 'rxdb/plugins/replication';
17
17
  import { createPullHandler } from './changesQuery.js';
18
18
  import { createPushHandler } from './pushWrites.js';
19
+ /**
20
+ * Builds the push write-back: patches an accepted write's acked server
21
+ * revision(s) (`version` / `metaVersion`) into the local row so the next
22
+ * conditional write's `If-Match` matches the server. Skips rows that are gone
23
+ * or already current (a tombstoned row is invisible to `findOne` and needs no
24
+ * write-back -- nothing further is pushed for a deleted id). Failures are
25
+ * swallowed: the write itself succeeded, and a missed write-back only means
26
+ * the acked revision is adopted from the change feed's echo on a later pull.
27
+ *
28
+ * @param rxCollection {RxCollection<SyncedDoc>}
29
+ * @returns {(ack: PushWriteAck) => Promise<void>}
30
+ */
31
+ function createAckWriteBack(rxCollection) {
32
+ return async function writeBack(ack) {
33
+ try {
34
+ const doc = await rxCollection.findOne(ack.id).exec();
35
+ if (doc === null) {
36
+ return;
37
+ }
38
+ const patch = {};
39
+ if (ack.version !== undefined && doc.get('version') !== ack.version) {
40
+ patch.version = ack.version;
41
+ }
42
+ if (ack.metaVersion !== undefined &&
43
+ doc.get('metaVersion') !== ack.metaVersion) {
44
+ patch.metaVersion = ack.metaVersion;
45
+ }
46
+ if (Object.keys(patch).length > 0) {
47
+ await doc.incrementalPatch(patch);
48
+ }
49
+ }
50
+ catch {
51
+ // Best-effort: the server write was accepted; the revision echo on the
52
+ // next pull corrects the row if this local patch could not be applied.
53
+ }
54
+ };
55
+ }
19
56
  /**
20
57
  * Starts (or configures) replication of one RxDB collection against a remote WAS
21
58
  * Collection. Poll-based only -- no `pull.stream$` (live streaming is deferred
@@ -47,7 +84,7 @@ export function createWasReplication({ rxCollection, wasPort, replicationIdentif
47
84
  batchSize
48
85
  },
49
86
  push: {
50
- handler: createPushHandler(wasPort),
87
+ handler: createPushHandler(wasPort, createAckWriteBack(rxCollection)),
51
88
  batchSize
52
89
  }
53
90
  });
@@ -1 +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"}
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,EAAqB,MAAM,iBAAiB,CAAA;AAEtE;;;;;;;;;;;GAWG;AACH,SAAS,kBAAkB,CAAC,YAAqC;IAC/D,OAAO,KAAK,UAAU,SAAS,CAAC,GAAiB;QAC/C,IAAI,CAAC;YACH,MAAM,GAAG,GAAG,MAAM,YAAY,CAAC,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC,IAAI,EAAE,CAAA;YACrD,IAAI,GAAG,KAAK,IAAI,EAAE,CAAC;gBACjB,OAAM;YACR,CAAC;YACD,MAAM,KAAK,GAAuB,EAAE,CAAA;YACpC,IAAI,GAAG,CAAC,OAAO,KAAK,SAAS,IAAI,GAAG,CAAC,GAAG,CAAC,SAAS,CAAC,KAAK,GAAG,CAAC,OAAO,EAAE,CAAC;gBACpE,KAAK,CAAC,OAAO,GAAG,GAAG,CAAC,OAAO,CAAA;YAC7B,CAAC;YACD,IACE,GAAG,CAAC,WAAW,KAAK,SAAS;gBAC7B,GAAG,CAAC,GAAG,CAAC,aAAa,CAAC,KAAK,GAAG,CAAC,WAAW,EAC1C,CAAC;gBACD,KAAK,CAAC,WAAW,GAAG,GAAG,CAAC,WAAW,CAAA;YACrC,CAAC;YACD,IAAI,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;gBAClC,MAAM,GAAG,CAAC,gBAAgB,CAAC,KAAK,CAAC,CAAA;YACnC,CAAC;QACH,CAAC;QAAC,MAAM,CAAC;YACP,uEAAuE;YACvE,uEAAuE;QACzE,CAAC;IACH,CAAC,CAAA;AACH,CAAC;AAED;;;;;;;;;;;;;;;;;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,EAAE,kBAAkB,CAAC,YAAY,CAAC,CAAC;YACrE,SAAS;SACV;KACF,CAAC,CAAA;AACJ,CAAC"}