@interop/wallet-core 0.90.0 → 0.91.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/dist/clientAnnex/grantRevocation.d.ts +9 -4
- package/dist/clientAnnex/grantRevocation.d.ts.map +1 -1
- package/dist/clientAnnex/grantRevocation.js +8 -4
- package/dist/clientAnnex/grantRevocation.js.map +1 -1
- package/dist/clientAnnex/index.d.ts +1 -1
- package/dist/clientAnnex/index.d.ts.map +1 -1
- package/dist/clientAnnex/index.js +1 -1
- package/dist/clientAnnex/index.js.map +1 -1
- package/dist/clientAnnex/ladder.d.ts +11 -13
- package/dist/clientAnnex/ladder.d.ts.map +1 -1
- package/dist/clientAnnex/ladder.js +71 -75
- package/dist/clientAnnex/ladder.js.map +1 -1
- package/dist/clientAnnex/ladderAnchored.d.ts +7 -58
- package/dist/clientAnnex/ladderAnchored.d.ts.map +1 -1
- package/dist/clientAnnex/ladderAnchored.js +9 -72
- package/dist/clientAnnex/ladderAnchored.js.map +1 -1
- package/dist/clientAnnex/log.d.ts +4 -2
- package/dist/clientAnnex/log.d.ts.map +1 -1
- package/dist/clientAnnex/log.js +12 -2
- package/dist/clientAnnex/log.js.map +1 -1
- package/dist/clientAnnex/recoveryLadderAnchored.d.ts +53 -17
- package/dist/clientAnnex/recoveryLadderAnchored.d.ts.map +1 -1
- package/dist/clientAnnex/recoveryLadderAnchored.js +52 -24
- package/dist/clientAnnex/recoveryLadderAnchored.js.map +1 -1
- package/dist/clientAnnex/selfEnroll.d.ts +3 -8
- package/dist/clientAnnex/selfEnroll.d.ts.map +1 -1
- package/dist/clientAnnex/selfEnroll.js +2 -1
- package/dist/clientAnnex/selfEnroll.js.map +1 -1
- package/dist/recovery/continuation.d.ts +13 -6
- package/dist/recovery/continuation.d.ts.map +1 -1
- package/dist/recovery/continuation.js +14 -2
- package/dist/recovery/continuation.js.map +1 -1
- package/dist/recovery/recoveryWebvh.d.ts +17 -4
- package/dist/recovery/recoveryWebvh.d.ts.map +1 -1
- package/dist/recovery/recoveryWebvh.js +14 -1
- package/dist/recovery/recoveryWebvh.js.map +1 -1
- package/dist/sync/contactsConflict.d.ts +9 -9
- package/dist/sync/contactsConflict.js +9 -9
- package/dist/sync/engine.d.ts +3 -3
- package/dist/sync/engine.js +3 -3
- package/dist/sync/engine.js.map +1 -1
- package/dist/sync/index.d.ts +3 -3
- package/dist/sync/index.d.ts.map +1 -1
- package/dist/sync/index.js +2 -2
- package/dist/sync/pull.d.ts +5 -5
- package/dist/sync/pull.js +5 -5
- package/dist/sync/push.d.ts +7 -6
- package/dist/sync/push.d.ts.map +1 -1
- package/dist/sync/push.js +65 -59
- package/dist/sync/push.js.map +1 -1
- package/dist/sync/remint.d.ts +37 -33
- package/dist/sync/remint.d.ts.map +1 -1
- package/dist/sync/remint.js +70 -64
- package/dist/sync/remint.js.map +1 -1
- package/dist/sync/types.d.ts +88 -83
- package/dist/sync/types.d.ts.map +1 -1
- package/dist/sync/types.js +12 -12
- package/dist/unlock/standingWebvh.d.ts.map +1 -1
- package/dist/unlock/standingWebvh.js +3 -1
- package/dist/unlock/standingWebvh.js.map +1 -1
- package/dist/webvh/accountEntry.d.ts +10 -1
- package/dist/webvh/accountEntry.d.ts.map +1 -1
- package/dist/webvh/accountEntry.js +23 -1
- package/dist/webvh/accountEntry.js.map +1 -1
- package/dist/webvh/builtOnHead.d.ts +79 -0
- package/dist/webvh/builtOnHead.d.ts.map +1 -0
- package/dist/webvh/builtOnHead.js +77 -0
- package/dist/webvh/builtOnHead.js.map +1 -0
- package/dist/webvh/didWebvh.d.ts +14 -6
- package/dist/webvh/didWebvh.d.ts.map +1 -1
- package/dist/webvh/didWebvh.js +5 -2
- package/dist/webvh/didWebvh.js.map +1 -1
- package/dist/webvh/enrollClient.d.ts +4 -1
- package/dist/webvh/enrollClient.d.ts.map +1 -1
- package/dist/webvh/enrollClient.js +38 -8
- package/dist/webvh/enrollClient.js.map +1 -1
- package/dist/webvh/index.d.ts +2 -0
- package/dist/webvh/index.d.ts.map +1 -1
- package/dist/webvh/index.js +1 -0
- package/dist/webvh/index.js.map +1 -1
- package/dist/webvh/revokeClient.d.ts.map +1 -1
- package/dist/webvh/revokeClient.js +4 -6
- package/dist/webvh/revokeClient.js.map +1 -1
- package/dist/webvh/verifyLog.d.ts +5 -1
- package/dist/webvh/verifyLog.d.ts.map +1 -1
- package/dist/webvh/verifyLog.js +4 -2
- package/dist/webvh/verifyLog.js.map +1 -1
- package/package.json +1 -1
package/dist/sync/push.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"push.js","sourceRoot":"","sources":["../../src/sync/push.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8BG;AACH,OAAO,EACL,mBAAmB,EACnB,mBAAmB,EACpB,MAAM,0BAA0B,CAAA;AAGjC
|
|
1
|
+
{"version":3,"file":"push.js","sourceRoot":"","sources":["../../src/sync/push.ts"],"names":[],"mappings":"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"}
|
package/dist/sync/remint.d.ts
CHANGED
|
@@ -1,44 +1,48 @@
|
|
|
1
1
|
import type { DocCipher, Json, SyncStore } from './types.js';
|
|
2
2
|
/**
|
|
3
|
-
* Re-mints every pending (dirty, never-acked, live)
|
|
4
|
-
* given cipher cannot route to an epoch it knows -- the
|
|
5
|
-
* an eager minter, run after adopting a published
|
|
6
|
-
* not install itself (`ensureWalletSpaceEpochs`
|
|
7
|
-
* descriptor for a collection it had already minted
|
|
8
|
-
* the `installed` flag, decides per
|
|
3
|
+
* Re-mints every pending (dirty, never-acked, live) resource replica whose
|
|
4
|
+
* envelope the given cipher cannot route to an epoch it knows -- the
|
|
5
|
+
* create-loss path for an eager minter, run after adopting a published
|
|
6
|
+
* descriptor this client did not install itself (`ensureWalletSpaceEpochs`
|
|
7
|
+
* returning an adopted descriptor for a collection it had already minted
|
|
8
|
+
* against; the envelope, not the `installed` flag, decides per resource
|
|
9
|
+
* replica) and before the next push.
|
|
9
10
|
*
|
|
10
|
-
* Each such
|
|
11
|
-
* or a plaintext-projection read keyed by the
|
|
12
|
-
* `cipher` (built from the adopted descriptor,
|
|
13
|
-
* and handed to {@link SyncStore.replacePending}
|
|
14
|
-
* since the re-mint is a fresh
|
|
15
|
-
*
|
|
16
|
-
*
|
|
11
|
+
* Each such resource replica is decrypted through `decryptStale` (the
|
|
12
|
+
* pre-adoption cipher, or a plaintext-projection read keyed by the resource
|
|
13
|
+
* replica id), re-encrypted with `cipher` (built from the adopted descriptor,
|
|
14
|
+
* so under its current epoch), and handed to {@link SyncStore.replacePending}
|
|
15
|
+
* -- which may re-key the resource replica, since the re-mint is a fresh
|
|
16
|
+
* encryption. Resource replicas already readable under the adopted descriptor,
|
|
17
|
+
* acked resource replicas (`version > 0` -- they HAVE feed existence and are
|
|
18
|
+
* never re-minted), and tombstones are left untouched.
|
|
17
19
|
*
|
|
18
20
|
* Two decrypt failures are told apart. An unknown epoch is the create-loss
|
|
19
|
-
* shape this helper exists for, and the
|
|
20
|
-
* refusal (was-client's `IntegrityError`: the envelope is sealed for
|
|
21
|
-
* other resource id, the shape a
|
|
22
|
-
* left where it is, logged once, and the pass
|
|
23
|
-
*
|
|
24
|
-
*
|
|
25
|
-
*
|
|
21
|
+
* shape this helper exists for, and the resource replica is re-minted. An
|
|
22
|
+
* addressing refusal (was-client's `IntegrityError`: the envelope is sealed for
|
|
23
|
+
* some other resource id, the shape a resource replica minted by a
|
|
24
|
+
* pre-addressed writer has) is left where it is, logged once, and the pass
|
|
25
|
+
* moves to the next resource replica -- the resource replica is not this
|
|
26
|
+
* helper's to settle, and aborting the pass over it would strand every other
|
|
27
|
+
* pending resource replica under the losing epoch and block the adoption from
|
|
28
|
+
* ever completing. Any other decrypt failure still propagates.
|
|
26
29
|
*
|
|
27
30
|
* A replace the store SKIPS (`{ applied: false }` -- a local write bumped the
|
|
28
|
-
*
|
|
29
|
-
* body this pass never saw) is not counted as
|
|
30
|
-
* re-snapshots and re-probes, settling the
|
|
31
|
-
*
|
|
32
|
-
* {@link MAX_REMINT_ATTEMPTS}, and a pass
|
|
33
|
-
* rather than returning -- a
|
|
34
|
-
* be pushed, since it would land on
|
|
35
|
-
* entry.
|
|
31
|
+
* resource replica's revision between the snapshot and the replace, so the
|
|
32
|
+
* resource replica now holds a body this pass never saw) is not counted as
|
|
33
|
+
* re-minted: the whole pass re-snapshots and re-probes, settling the resource
|
|
34
|
+
* replica under its fresh revision. Only resource replicas still unroutable are
|
|
35
|
+
* re-processed; the retry is bounded by {@link MAX_REMINT_ATTEMPTS}, and a pass
|
|
36
|
+
* that exhausts the bound throws rather than returning -- a resource replica
|
|
37
|
+
* left sealed under the losing epoch MUST NOT be pushed, since it would land on
|
|
38
|
+
* the feed as a permanently unroutable entry.
|
|
36
39
|
*
|
|
37
40
|
* The store parameter is narrowed to one that implements `replacePending`
|
|
38
41
|
* (optional on {@link SyncStore} itself, since a lazy-minting consumer never
|
|
39
42
|
* re-mints): the requirement is a compile-time one, not a runtime throw.
|
|
40
43
|
*
|
|
41
|
-
* Idempotent: a re-run finds the re-minted
|
|
44
|
+
* Idempotent: a re-run finds the re-minted resource replicas readable and does
|
|
45
|
+
* nothing.
|
|
42
46
|
*
|
|
43
47
|
* @param options {object}
|
|
44
48
|
* @param options.store {SyncStore} must implement `replacePending`
|
|
@@ -46,11 +50,11 @@ import type { DocCipher, Json, SyncStore } from './types.js';
|
|
|
46
50
|
* descriptor
|
|
47
51
|
* @param options.decryptStale {function} opens envelopes minted before the
|
|
48
52
|
* adoption; receives `{ id, envelope }` so a projection-backed caller can
|
|
49
|
-
* look the plaintext up by
|
|
50
|
-
* @param [options.signal] {AbortSignal} checked between
|
|
53
|
+
* look the plaintext up by resource replica id instead of decrypting
|
|
54
|
+
* @param [options.signal] {AbortSignal} checked between resource replicas
|
|
51
55
|
* @returns {Promise<{ pending: number; reminted: number }>} pending live
|
|
52
|
-
*
|
|
53
|
-
* replaces only, across all passes)
|
|
56
|
+
* resource replicas the first pass scanned, and how many were re-minted
|
|
57
|
+
* (applied replaces only, across all passes)
|
|
54
58
|
*/
|
|
55
59
|
export declare function remintPendingEnvelopes({ store, cipher, decryptStale, signal }: {
|
|
56
60
|
store: SyncStore & {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"remint.d.ts","sourceRoot":"","sources":["../../src/sync/remint.ts"],"names":[],"mappings":"AAkCA,OAAO,KAAK,EAAE,SAAS,EAAE,IAAI,EAAE,SAAS,EAAE,MAAM,YAAY,CAAA;
|
|
1
|
+
{"version":3,"file":"remint.d.ts","sourceRoot":"","sources":["../../src/sync/remint.ts"],"names":[],"mappings":"AAkCA,OAAO,KAAK,EAAE,SAAS,EAAE,IAAI,EAAE,SAAS,EAAE,MAAM,YAAY,CAAA;AAW5D;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAwDG;AACH,wBAAsB,sBAAsB,CAAC,EAC3C,KAAK,EACL,MAAM,EACN,YAAY,EACZ,MAAM,EACP,EAAE;IACD,KAAK,EAAE,SAAS,GAAG;QACjB,cAAc,EAAE,WAAW,CAAC,SAAS,CAAC,gBAAgB,CAAC,CAAC,CAAA;KACzD,CAAA;IACD,MAAM,EAAE,SAAS,CAAA;IACjB,YAAY,EAAE,CAAC,OAAO,EAAE;QAAE,EAAE,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,IAAI,CAAA;KAAE,KAAK,OAAO,CAAC,IAAI,CAAC,CAAA;IACxE,MAAM,CAAC,EAAE,WAAW,CAAA;CACrB,GAAG,OAAO,CAAC;IAAE,OAAO,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAA;CAAE,CAAC,CAgFjD"}
|
package/dist/sync/remint.js
CHANGED
|
@@ -19,65 +19,70 @@
|
|
|
19
19
|
* create won. At the next sync, provisioning adopts the winner's descriptor
|
|
20
20
|
* (the create is compare-and-swap, never clobbering), leaving the pending
|
|
21
21
|
* envelopes sealed under an epoch the published descriptor does not carry;
|
|
22
|
-
* pushing them would land feed entries no other replica can route. The
|
|
23
|
-
*
|
|
24
|
-
*
|
|
25
|
-
* decrypted through the caller's stale-capable seam and re-encrypted under
|
|
26
|
-
*
|
|
27
|
-
*
|
|
28
|
-
* immutability bites only once an envelope is ON the feed -- and the
|
|
29
|
-
* re-encryption may therefore re-key the
|
|
30
|
-
* the ciphertext, so a re-mint mints a new id).
|
|
22
|
+
* pushing them would land feed entries no other replica can route. The remedy
|
|
23
|
+
* is {@link remintPendingEnvelopes}, run after adoption and before the next
|
|
24
|
+
* push: each pending resource replica the adopted cipher cannot route is
|
|
25
|
+
* decrypted through the caller's stale-capable seam and re-encrypted under the
|
|
26
|
+
* adopted descriptor's current epoch. This is legal exactly because a pending
|
|
27
|
+
* resource replica (dirty, never server-acked) has no feed existence --
|
|
28
|
+
* envelope immutability bites only once an envelope is ON the feed -- and the
|
|
29
|
+
* re-encryption may therefore re-key the resource replica (a content-derived id
|
|
30
|
+
* hashes the ciphertext, so a re-mint mints a new id).
|
|
31
31
|
*/
|
|
32
32
|
import { isIntegrityError, isUnknownEpochError } from '@interop/was-client/sync';
|
|
33
33
|
import { log } from '../log.js';
|
|
34
34
|
/**
|
|
35
|
-
* How many full re-mint passes are attempted before giving up. A pass that
|
|
36
|
-
*
|
|
37
|
-
* snapshot and the replace) re-snapshots and retries only
|
|
38
|
-
* unroutable; the bound keeps a hot-writing
|
|
35
|
+
* How many full re-mint passes are attempted before giving up. A pass that ends
|
|
36
|
+
* with skipped resource replicas (a local write bumped a resource replica's
|
|
37
|
+
* revision between the snapshot and the replace) re-snapshots and retries only
|
|
38
|
+
* what is still unroutable; the bound keeps a hot-writing resource replica from
|
|
39
|
+
* livelocking the pass.
|
|
39
40
|
*/
|
|
40
41
|
const MAX_REMINT_ATTEMPTS = 5;
|
|
41
42
|
/**
|
|
42
|
-
* Re-mints every pending (dirty, never-acked, live)
|
|
43
|
-
* given cipher cannot route to an epoch it knows -- the
|
|
44
|
-
* an eager minter, run after adopting a published
|
|
45
|
-
* not install itself (`ensureWalletSpaceEpochs`
|
|
46
|
-
* descriptor for a collection it had already minted
|
|
47
|
-
* the `installed` flag, decides per
|
|
43
|
+
* Re-mints every pending (dirty, never-acked, live) resource replica whose
|
|
44
|
+
* envelope the given cipher cannot route to an epoch it knows -- the
|
|
45
|
+
* create-loss path for an eager minter, run after adopting a published
|
|
46
|
+
* descriptor this client did not install itself (`ensureWalletSpaceEpochs`
|
|
47
|
+
* returning an adopted descriptor for a collection it had already minted
|
|
48
|
+
* against; the envelope, not the `installed` flag, decides per resource
|
|
49
|
+
* replica) and before the next push.
|
|
48
50
|
*
|
|
49
|
-
* Each such
|
|
50
|
-
* or a plaintext-projection read keyed by the
|
|
51
|
-
* `cipher` (built from the adopted descriptor,
|
|
52
|
-
* and handed to {@link SyncStore.replacePending}
|
|
53
|
-
* since the re-mint is a fresh
|
|
54
|
-
*
|
|
55
|
-
*
|
|
51
|
+
* Each such resource replica is decrypted through `decryptStale` (the
|
|
52
|
+
* pre-adoption cipher, or a plaintext-projection read keyed by the resource
|
|
53
|
+
* replica id), re-encrypted with `cipher` (built from the adopted descriptor,
|
|
54
|
+
* so under its current epoch), and handed to {@link SyncStore.replacePending}
|
|
55
|
+
* -- which may re-key the resource replica, since the re-mint is a fresh
|
|
56
|
+
* encryption. Resource replicas already readable under the adopted descriptor,
|
|
57
|
+
* acked resource replicas (`version > 0` -- they HAVE feed existence and are
|
|
58
|
+
* never re-minted), and tombstones are left untouched.
|
|
56
59
|
*
|
|
57
60
|
* Two decrypt failures are told apart. An unknown epoch is the create-loss
|
|
58
|
-
* shape this helper exists for, and the
|
|
59
|
-
* refusal (was-client's `IntegrityError`: the envelope is sealed for
|
|
60
|
-
* other resource id, the shape a
|
|
61
|
-
* left where it is, logged once, and the pass
|
|
62
|
-
*
|
|
63
|
-
*
|
|
64
|
-
*
|
|
61
|
+
* shape this helper exists for, and the resource replica is re-minted. An
|
|
62
|
+
* addressing refusal (was-client's `IntegrityError`: the envelope is sealed for
|
|
63
|
+
* some other resource id, the shape a resource replica minted by a
|
|
64
|
+
* pre-addressed writer has) is left where it is, logged once, and the pass
|
|
65
|
+
* moves to the next resource replica -- the resource replica is not this
|
|
66
|
+
* helper's to settle, and aborting the pass over it would strand every other
|
|
67
|
+
* pending resource replica under the losing epoch and block the adoption from
|
|
68
|
+
* ever completing. Any other decrypt failure still propagates.
|
|
65
69
|
*
|
|
66
70
|
* A replace the store SKIPS (`{ applied: false }` -- a local write bumped the
|
|
67
|
-
*
|
|
68
|
-
* body this pass never saw) is not counted as
|
|
69
|
-
* re-snapshots and re-probes, settling the
|
|
70
|
-
*
|
|
71
|
-
* {@link MAX_REMINT_ATTEMPTS}, and a pass
|
|
72
|
-
* rather than returning -- a
|
|
73
|
-
* be pushed, since it would land on
|
|
74
|
-
* entry.
|
|
71
|
+
* resource replica's revision between the snapshot and the replace, so the
|
|
72
|
+
* resource replica now holds a body this pass never saw) is not counted as
|
|
73
|
+
* re-minted: the whole pass re-snapshots and re-probes, settling the resource
|
|
74
|
+
* replica under its fresh revision. Only resource replicas still unroutable are
|
|
75
|
+
* re-processed; the retry is bounded by {@link MAX_REMINT_ATTEMPTS}, and a pass
|
|
76
|
+
* that exhausts the bound throws rather than returning -- a resource replica
|
|
77
|
+
* left sealed under the losing epoch MUST NOT be pushed, since it would land on
|
|
78
|
+
* the feed as a permanently unroutable entry.
|
|
75
79
|
*
|
|
76
80
|
* The store parameter is narrowed to one that implements `replacePending`
|
|
77
81
|
* (optional on {@link SyncStore} itself, since a lazy-minting consumer never
|
|
78
82
|
* re-mints): the requirement is a compile-time one, not a runtime throw.
|
|
79
83
|
*
|
|
80
|
-
* Idempotent: a re-run finds the re-minted
|
|
84
|
+
* Idempotent: a re-run finds the re-minted resource replicas readable and does
|
|
85
|
+
* nothing.
|
|
81
86
|
*
|
|
82
87
|
* @param options {object}
|
|
83
88
|
* @param options.store {SyncStore} must implement `replacePending`
|
|
@@ -85,45 +90,46 @@ const MAX_REMINT_ATTEMPTS = 5;
|
|
|
85
90
|
* descriptor
|
|
86
91
|
* @param options.decryptStale {function} opens envelopes minted before the
|
|
87
92
|
* adoption; receives `{ id, envelope }` so a projection-backed caller can
|
|
88
|
-
* look the plaintext up by
|
|
89
|
-
* @param [options.signal] {AbortSignal} checked between
|
|
93
|
+
* look the plaintext up by resource replica id instead of decrypting
|
|
94
|
+
* @param [options.signal] {AbortSignal} checked between resource replicas
|
|
90
95
|
* @returns {Promise<{ pending: number; reminted: number }>} pending live
|
|
91
|
-
*
|
|
92
|
-
* replaces only, across all passes)
|
|
96
|
+
* resource replicas the first pass scanned, and how many were re-minted
|
|
97
|
+
* (applied replaces only, across all passes)
|
|
93
98
|
*/
|
|
94
99
|
export async function remintPendingEnvelopes({ store, cipher, decryptStale, signal }) {
|
|
95
100
|
let pending;
|
|
96
101
|
let reminted = 0;
|
|
97
102
|
let skipped = [];
|
|
98
|
-
//
|
|
99
|
-
// deterministic over the same envelope, so a later attempt's pass
|
|
100
|
-
// a
|
|
103
|
+
// Resource replicas the cipher refused as sealed for another resource id. The
|
|
104
|
+
// refusal is deterministic over the same envelope, so a later attempt's pass
|
|
105
|
+
// skips such a resource replica instead of paying for the decrypt (and
|
|
106
|
+
// warning) again.
|
|
101
107
|
const misaddressed = new Set();
|
|
102
108
|
for (let attempt = 0; attempt < MAX_REMINT_ATTEMPTS; attempt += 1) {
|
|
103
109
|
if (signal?.aborted) {
|
|
104
110
|
break;
|
|
105
111
|
}
|
|
106
|
-
const
|
|
107
|
-
const
|
|
108
|
-
pending ??=
|
|
112
|
+
const replicas = await store.getDirtyResourceReplicas();
|
|
113
|
+
const pendingReplicas = replicas.filter(replica => !replica.deleted && replica.version === 0 && replica.data !== null);
|
|
114
|
+
pending ??= pendingReplicas.length;
|
|
109
115
|
skipped = [];
|
|
110
|
-
for (const
|
|
116
|
+
for (const replica of pendingReplicas) {
|
|
111
117
|
if (signal?.aborted) {
|
|
112
118
|
break;
|
|
113
119
|
}
|
|
114
|
-
if (misaddressed.has(
|
|
120
|
+
if (misaddressed.has(replica.id)) {
|
|
115
121
|
continue;
|
|
116
122
|
}
|
|
117
|
-
const envelope =
|
|
123
|
+
const envelope = replica.data;
|
|
118
124
|
try {
|
|
119
|
-
await cipher.decrypt({ id:
|
|
125
|
+
await cipher.decrypt({ id: replica.id, envelope });
|
|
120
126
|
continue;
|
|
121
127
|
}
|
|
122
128
|
catch (err) {
|
|
123
129
|
if (isIntegrityError(err)) {
|
|
124
|
-
misaddressed.add(
|
|
125
|
-
log.warn('Leaving a pending
|
|
126
|
-
id:
|
|
130
|
+
misaddressed.add(replica.id);
|
|
131
|
+
log.warn('Leaving a pending resource replica sealed for another resource id', {
|
|
132
|
+
id: replica.id,
|
|
127
133
|
err
|
|
128
134
|
});
|
|
129
135
|
continue;
|
|
@@ -132,21 +138,21 @@ export async function remintPendingEnvelopes({ store, cipher, decryptStale, sign
|
|
|
132
138
|
throw err;
|
|
133
139
|
}
|
|
134
140
|
}
|
|
135
|
-
const payload = await decryptStale({ id:
|
|
141
|
+
const payload = await decryptStale({ id: replica.id, envelope });
|
|
136
142
|
const { id: newId, envelope: remintedEnvelope } = await cipher.encrypt({
|
|
137
143
|
data: payload
|
|
138
144
|
});
|
|
139
145
|
const { applied } = await store.replacePending({
|
|
140
|
-
id:
|
|
146
|
+
id: replica.id,
|
|
141
147
|
newId,
|
|
142
148
|
envelope: remintedEnvelope,
|
|
143
|
-
...(
|
|
149
|
+
...(replica.revision !== undefined && { revision: replica.revision })
|
|
144
150
|
});
|
|
145
151
|
if (applied) {
|
|
146
152
|
reminted += 1;
|
|
147
153
|
}
|
|
148
154
|
else {
|
|
149
|
-
skipped.push(
|
|
155
|
+
skipped.push(replica.id);
|
|
150
156
|
}
|
|
151
157
|
}
|
|
152
158
|
if (skipped.length === 0 || signal?.aborted) {
|
|
@@ -155,7 +161,7 @@ export async function remintPendingEnvelopes({ store, cipher, decryptStale, sign
|
|
|
155
161
|
}
|
|
156
162
|
if (skipped.length > 0 && !signal?.aborted) {
|
|
157
163
|
throw new Error(`Re-mint gave up after ${MAX_REMINT_ATTEMPTS} attempts: pending ` +
|
|
158
|
-
`
|
|
164
|
+
`resource replica(s) ${skipped.join(', ')} kept being rewritten locally and are ` +
|
|
159
165
|
'still sealed under an epoch the adopted descriptor does not carry. ' +
|
|
160
166
|
'They must not be pushed; re-run the re-mint once local writes settle.');
|
|
161
167
|
}
|
package/dist/sync/remint.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"remint.js","sourceRoot":"","sources":["../../src/sync/remint.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AACH,OAAO,EAAE,gBAAgB,EAAE,mBAAmB,EAAE,MAAM,0BAA0B,CAAA;AAEhF,OAAO,EAAE,GAAG,EAAE,MAAM,WAAW,CAAA;AAG/B
|
|
1
|
+
{"version":3,"file":"remint.js","sourceRoot":"","sources":["../../src/sync/remint.ts"],"names":[],"mappings":"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"}
|
package/dist/sync/types.d.ts
CHANGED
|
@@ -10,21 +10,21 @@
|
|
|
10
10
|
* signals) come from `@interop/was-client/sync` and are re-exported here so a
|
|
11
11
|
* single import gives a consumer both the wire types and the replica-side
|
|
12
12
|
* seams. The predicates that CLASSIFY those signals ship from
|
|
13
|
-
* `@interop/was-client/sync` too, beside the classes that assign the names
|
|
14
|
-
*
|
|
13
|
+
* `@interop/was-client/sync` too, beside the classes that assign the names they
|
|
14
|
+
* match; `push.ts` and `remint.ts` import them from there.
|
|
15
15
|
*
|
|
16
|
-
* The server's `ETag` is opaque (it embeds a per-record generation marker
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
16
|
+
* The server's `ETag` is opaque (it embeds a per-record generation marker ahead
|
|
17
|
+
* of the content `version`, so it can no longer be rebuilt from a bare revision
|
|
18
|
+
* number). `MasterState.etag` / `WireDoc.etag` / `WriteAck.etag` carry it
|
|
19
|
+
* verbatim; a store that persists resource replicas MUST keep the string
|
|
20
20
|
* alongside `version` and echo it back as a later write's `ifMatch`.
|
|
21
21
|
*
|
|
22
|
-
* The local-persistence seam (`SyncStore`, `
|
|
23
|
-
* `ResolveConflict`) is the replica's side of the contract:
|
|
24
|
-
* concrete store (an RxDB collection, a SQLite `synced_docs`
|
|
25
|
-
* table pair, or an in-memory test double). The engine
|
|
26
|
-
* decrypts OUTSIDE the store transaction, so these
|
|
27
|
-
* material.
|
|
22
|
+
* The local-persistence seam (`SyncStore`, `SyncedResourceReplica`,
|
|
23
|
+
* `ProjectionAction`, `ResolveConflict`) is the replica's side of the contract:
|
|
24
|
+
* it stands in for a concrete store (an RxDB collection, a SQLite `synced_docs`
|
|
25
|
+
* / `sync_checkpoints` table pair, or an in-memory test double). The engine
|
|
26
|
+
* owns the `DocCipher` and decrypts OUTSIDE the store transaction, so these
|
|
27
|
+
* methods never see key material.
|
|
28
28
|
*
|
|
29
29
|
* This module (and `pull.ts` / `push.ts` / `engine.ts`) has no runtime imports
|
|
30
30
|
* beyond the wire contract, so the engine runs anywhere: browser, Node, or React
|
|
@@ -34,23 +34,25 @@ export { UnknownEpochError, WasSyncConflictError, WasSyncNotFoundError } from '@
|
|
|
34
34
|
export type { Json, SyncCheckpoint, WireDoc, MasterState, WriteAck, WasSyncPort, DocCipher } from '@interop/was-client/sync';
|
|
35
35
|
import type { Json, MasterState, SyncCheckpoint, WireDoc } from '@interop/was-client/sync';
|
|
36
36
|
/**
|
|
37
|
-
* A dirty
|
|
38
|
-
*
|
|
39
|
-
* one), `null` for a
|
|
40
|
-
*
|
|
41
|
-
*
|
|
42
|
-
* a later write's `ifMatch` --
|
|
43
|
-
*
|
|
37
|
+
* A dirty resource replica awaiting push: the replica's local copy of one WAS
|
|
38
|
+
* Resource. `data` is the stored body (the EDV envelope on an encrypted
|
|
39
|
+
* collection, or the plaintext JSON on a plaintext one), `null` for a
|
|
40
|
+
* tombstone. `version` is the last server-acked content revision (`0` = never
|
|
41
|
+
* acked, so a create); `etag` is the opaque `ETag` validator that revision was
|
|
42
|
+
* acked (or pulled) under, echoed back verbatim as a later write's `ifMatch` --
|
|
43
|
+
* absent when the resource replica has never been acked, or against a backend
|
|
44
|
+
* that does not version resources.
|
|
44
45
|
*
|
|
45
|
-
* `revision` is the store's own local revision token for the
|
|
46
|
-
* value the store bumps on EVERY local write (a counter, a hash of
|
|
47
|
-
* body, an RxDB `_rev`). The push loop carries it back through
|
|
46
|
+
* `revision` is the store's own local revision token for the resource replica:
|
|
47
|
+
* any opaque value the store bumps on EVERY local write (a counter, a hash of
|
|
48
|
+
* the stored body, an RxDB `_rev`). The push loop carries it back through
|
|
48
49
|
* {@link SyncStore.markPushed} / {@link SyncStore.markDeletedPushed} so the ack
|
|
49
50
|
* can be made conditional -- a local write that lands while the HTTP write is
|
|
50
|
-
* in flight leaves a different token behind and keeps the
|
|
51
|
-
* that omits it opts out of that protection and is acked
|
|
51
|
+
* in flight leaves a different token behind and keeps the resource replica
|
|
52
|
+
* dirty. A store that omits it opts out of that protection and is acked
|
|
53
|
+
* unconditionally.
|
|
52
54
|
*/
|
|
53
|
-
export interface
|
|
55
|
+
export interface SyncedResourceReplica {
|
|
54
56
|
id: string;
|
|
55
57
|
version: number;
|
|
56
58
|
etag?: string;
|
|
@@ -78,15 +80,17 @@ export type ProjectionAction = {
|
|
|
78
80
|
kind: 'none';
|
|
79
81
|
};
|
|
80
82
|
/**
|
|
81
|
-
* The per-
|
|
82
|
-
* injected into the push loop. Insert-only content-addressed collections leave
|
|
83
|
+
* The per-resource 412-conflict policy for a mutable (last-write-wins)
|
|
84
|
+
* collection, injected into the push loop. Insert-only content-addressed collections leave
|
|
83
85
|
* it undefined: their settlement rules -- identical-envelope adoption and
|
|
84
86
|
* tombstone-wins -- already cover every 412. For a mutable head document the
|
|
85
87
|
* resolver re-reads the master, decides the winner deterministically, and either
|
|
86
88
|
* applies the remote payload or re-encrypts the local one for the next push.
|
|
87
|
-
*
|
|
89
|
+
* `local` is the conflicting local resource replica; the resolver reads the
|
|
90
|
+
* remote side itself. Bound to a profile's cipher + store by the caller;
|
|
91
|
+
* opaque to the push loop.
|
|
88
92
|
*/
|
|
89
|
-
export type ResolveConflict = (
|
|
93
|
+
export type ResolveConflict = (local: {
|
|
90
94
|
id: string;
|
|
91
95
|
version: number;
|
|
92
96
|
data: Json | null;
|
|
@@ -104,17 +108,17 @@ export interface SyncStore {
|
|
|
104
108
|
*/
|
|
105
109
|
getCheckpoint(): Promise<SyncCheckpoint | undefined>;
|
|
106
110
|
/**
|
|
107
|
-
* All
|
|
111
|
+
* All resource replicas awaiting push (dirty).
|
|
108
112
|
*/
|
|
109
|
-
|
|
113
|
+
getDirtyResourceReplicas(): Promise<SyncedResourceReplica[]>;
|
|
110
114
|
/**
|
|
111
115
|
* Applies one pulled page in a single exclusive transaction: reconcile each
|
|
112
|
-
* document against
|
|
113
|
-
* the matching projection action, and advance the checkpoint.
|
|
114
|
-
* is keyed by document id. Each document's `etag` (and
|
|
115
|
-
* collection that syncs metadata) MUST be recorded onto its
|
|
116
|
-
* `version` -- it is the validator a later local
|
|
117
|
-
* `ifMatch`.
|
|
116
|
+
* document against its local resource replica (per the pull-apply conflict
|
|
117
|
+
* table), write the matching projection action, and advance the checkpoint.
|
|
118
|
+
* `projections` is keyed by document id. Each document's `etag` (and
|
|
119
|
+
* `metaEtag`, on a collection that syncs metadata) MUST be recorded onto its
|
|
120
|
+
* resource replica alongside `version` -- it is the validator a later local
|
|
121
|
+
* write echoes back as `ifMatch`.
|
|
118
122
|
*/
|
|
119
123
|
applyPulledPage(options: {
|
|
120
124
|
documents: WireDoc[];
|
|
@@ -124,13 +128,13 @@ export interface SyncStore {
|
|
|
124
128
|
/**
|
|
125
129
|
* Marks a pushed create/update as acked: record the server `version` and the
|
|
126
130
|
* opaque `etag` validator it lives behind (the write's {@link WriteAck}) when
|
|
127
|
-
* provided, and clear dirty -- but ONLY if the
|
|
128
|
-
* {@link
|
|
129
|
-
* local write that landed while the write was in flight leaves a
|
|
130
|
-
* and that
|
|
131
|
-
* recorded, so the re-push's `ifMatch` is current)
|
|
132
|
-
* it. When `revision` is `undefined` -- a store
|
|
133
|
-
* revision token -- dirty is cleared unconditionally.
|
|
131
|
+
* provided, and clear dirty -- but ONLY if the resource replica's current
|
|
132
|
+
* {@link SyncedResourceReplica.revision} still equals the `revision` that was
|
|
133
|
+
* pushed. A local write that landed while the write was in flight leaves a
|
|
134
|
+
* newer token, and that resource replica MUST stay dirty (with the acked
|
|
135
|
+
* `version` / `etag` still recorded, so the re-push's `ifMatch` is current)
|
|
136
|
+
* so the rerun cycle pushes it. When `revision` is `undefined` -- a store
|
|
137
|
+
* that does not track a revision token -- dirty is cleared unconditionally.
|
|
134
138
|
*/
|
|
135
139
|
markPushed(options: {
|
|
136
140
|
id: string;
|
|
@@ -141,9 +145,9 @@ export interface SyncStore {
|
|
|
141
145
|
/**
|
|
142
146
|
* Marks a pushed delete as settled: keep the tombstone, record the server
|
|
143
147
|
* `version` / `etag` when provided, and clear dirty under the same revision
|
|
144
|
-
* condition as {@link SyncStore.markPushed} -- a
|
|
145
|
-
* mid-flight stays dirty and keeps its local state rather than being
|
|
146
|
-
* to a clean tombstone.
|
|
148
|
+
* condition as {@link SyncStore.markPushed} -- a resource replica rewritten
|
|
149
|
+
* locally mid-flight stays dirty and keeps its local state rather than being
|
|
150
|
+
* forced to a clean tombstone.
|
|
147
151
|
*/
|
|
148
152
|
markDeletedPushed(options: {
|
|
149
153
|
id: string;
|
|
@@ -153,12 +157,13 @@ export interface SyncStore {
|
|
|
153
157
|
}): Promise<void>;
|
|
154
158
|
/**
|
|
155
159
|
* Adopts the server's latest state (its {@link MasterState}, in the wire
|
|
156
|
-
* contract's RxDB-derived naming) for a
|
|
157
|
-
* `projection` in the same transaction. `latest === null`
|
|
158
|
-
* has a tombstone (or the resource is absent): record the
|
|
159
|
-
* delete the projection. A non-null `latest` carries its own
|
|
160
|
-
* the store MUST record onto the
|
|
161
|
-
* validator the
|
|
160
|
+
* contract's RxDB-derived naming) for a resource replica whose push hit a
|
|
161
|
+
* `412`, applying `projection` in the same transaction. `latest === null`
|
|
162
|
+
* means the server has a tombstone (or the resource is absent): record the
|
|
163
|
+
* tombstone and delete the projection. A non-null `latest` carries its own
|
|
164
|
+
* `etag`, which the store MUST record onto the resource replica alongside
|
|
165
|
+
* `version` -- it is the validator the resource replica's next push echoes
|
|
166
|
+
* back as `ifMatch`.
|
|
162
167
|
*/
|
|
163
168
|
adoptLatest(options: {
|
|
164
169
|
id: string;
|
|
@@ -167,16 +172,16 @@ export interface SyncStore {
|
|
|
167
172
|
}): Promise<void>;
|
|
168
173
|
/**
|
|
169
174
|
* Which of these feed revisions does the replica already hold? Answers with
|
|
170
|
-
* the ids whose local
|
|
171
|
-
* one a push ack or an earlier pull recorded). The pull loop
|
|
172
|
-
* about live feed documents carrying the engine's own
|
|
173
|
-
* the decrypt of each confirmed one, applying it with a
|
|
174
|
-
* (see `pull.ts`). The label is host-visible plaintext, so
|
|
175
|
-
* requires the `etag` match. That match limits the skip to a
|
|
176
|
-
* replica already records. It does not authenticate the label or
|
|
177
|
-
* envelope, since the `etag` is host-supplied too. A host that replays an
|
|
178
|
-
* older envelope under the current `etag` gets the local projection left
|
|
179
|
-
*
|
|
175
|
+
* the ids whose local resource replica records exactly that `etag` as its
|
|
176
|
+
* validator (the one a push ack or an earlier pull recorded). The pull loop
|
|
177
|
+
* asks it only about live feed documents carrying the engine's own
|
|
178
|
+
* `writerId`, and skips the decrypt of each confirmed one, applying it with a
|
|
179
|
+
* `none` projection (see `pull.ts`). The label is host-visible plaintext, so
|
|
180
|
+
* the skip also requires the `etag` match. That match limits the skip to a
|
|
181
|
+
* revision the replica already records. It does not authenticate the label or
|
|
182
|
+
* the envelope, since the `etag` is host-supplied too. A host that replays an
|
|
183
|
+
* older envelope under the current `etag` gets the local projection left as
|
|
184
|
+
* it was, where a decrypt would have rolled it back.
|
|
180
185
|
*
|
|
181
186
|
* Optional: a store that omits it turns echo suppression off, and every
|
|
182
187
|
* echo is decrypted as before.
|
|
@@ -188,30 +193,30 @@ export interface SyncStore {
|
|
|
188
193
|
}[];
|
|
189
194
|
}): Promise<Set<string>>;
|
|
190
195
|
/**
|
|
191
|
-
* Replaces the body of a pending
|
|
192
|
-
* existence) with a re-minted envelope, re-keying
|
|
196
|
+
* Replaces the body of a pending resource replica (dirty, never-acked, live
|
|
197
|
+
* -- no feed existence) with a re-minted envelope, re-keying it from `id` to
|
|
193
198
|
* `newId` when the fresh encryption minted a different resource id, all in
|
|
194
|
-
* ONE transaction: the
|
|
195
|
-
* links the plaintext projection / app row to the
|
|
196
|
-
* `newId` with it. Under the same revision condition as
|
|
197
|
-
* {@link SyncStore.markPushed}: when `revision` is provided and the
|
|
198
|
-
* current token differs (a local write landed mid-re-mint), the
|
|
199
|
-
* skipped -- the newer state stays as-is for the next pass.
|
|
199
|
+
* ONE transaction: the resource replica keeps `version 0` and stays dirty,
|
|
200
|
+
* and whatever links the plaintext projection / app row to the resource
|
|
201
|
+
* replica moves to `newId` with it. Under the same revision condition as
|
|
202
|
+
* {@link SyncStore.markPushed}: when `revision` is provided and the resource
|
|
203
|
+
* replica's current token differs (a local write landed mid-re-mint), the
|
|
204
|
+
* replace is skipped -- the newer state stays as-is for the next pass.
|
|
200
205
|
*
|
|
201
206
|
* The outcome MUST be reported: resolve with `{ applied: true }` when the
|
|
202
|
-
*
|
|
203
|
-
* condition skipped it. A skip is not a failure, but it leaves a
|
|
204
|
-
* sealed under an epoch the published descriptor does
|
|
205
|
-
* caller (`remintPendingEnvelopes`) re-probes and retries
|
|
206
|
-
* counting it as re-minted -- a silently skipped
|
|
207
|
-
* pushed to the feed as a permanently unroutable
|
|
208
|
-
* not track a revision token replaces
|
|
209
|
-
* `{ applied: true }`.
|
|
207
|
+
* resource replica's body was replaced, and `{ applied: false }` when the
|
|
208
|
+
* revision condition skipped it. A skip is not a failure, but it leaves a
|
|
209
|
+
* resource replica still sealed under an epoch the published descriptor does
|
|
210
|
+
* not carry, and the caller (`remintPendingEnvelopes`) re-probes and retries
|
|
211
|
+
* it rather than counting it as re-minted -- a silently skipped resource
|
|
212
|
+
* replica would otherwise be pushed to the feed as a permanently unroutable
|
|
213
|
+
* entry. A store that does not track a revision token replaces
|
|
214
|
+
* unconditionally and always reports `{ applied: true }`.
|
|
210
215
|
*
|
|
211
|
-
* Optional: only an eager-minting replica (one that mints envelopes at
|
|
212
|
-
*
|
|
213
|
-
*
|
|
214
|
-
*
|
|
216
|
+
* Optional: only an eager-minting replica (one that mints envelopes at local
|
|
217
|
+
* write time against a cached descriptor) needs it, for the create-loss
|
|
218
|
+
* re-mint (`remintPendingEnvelopes`). A replica that mints lazily in the
|
|
219
|
+
* engine's migration sweep never re-mints and may omit it.
|
|
215
220
|
*/
|
|
216
221
|
replacePending?(options: {
|
|
217
222
|
id: string;
|