@interop/wallet-core 0.90.0 → 0.92.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/space/activity.d.ts +37 -12
- package/dist/space/activity.d.ts.map +1 -1
- package/dist/space/activity.js +52 -16
- package/dist/space/activity.js.map +1 -1
- package/dist/space/index.d.ts +1 -1
- package/dist/space/index.d.ts.map +1 -1
- package/dist/space/index.js +1 -1
- package/dist/space/index.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/pull.d.ts
CHANGED
|
@@ -7,7 +7,7 @@ import type { Json, ProjectionAction, SyncStore, WasSyncPort, WireDoc } from './
|
|
|
7
7
|
*
|
|
8
8
|
* Decryption runs here, outside the store transaction, so a slow/failing decrypt
|
|
9
9
|
* never holds the store's write lock. A document whose body cannot be decrypted
|
|
10
|
-
* (legacy plaintext
|
|
10
|
+
* (legacy plaintext body, corrupt/foreign envelope, key mismatch) is skipped with
|
|
11
11
|
* a `none` projection rather than throwing: the body is still stored and the
|
|
12
12
|
* checkpoint advances past it, so one poison document can never permanently
|
|
13
13
|
* wedge the feed for the whole replica. A document that decrypts but fails the
|
|
@@ -15,17 +15,17 @@ import type { Json, ProjectionAction, SyncStore, WasSyncPort, WireDoc } from './
|
|
|
15
15
|
* possibly a buggy or schema-incompatible writer) is skipped the same way:
|
|
16
16
|
* stored, checkpoint advanced, never projected.
|
|
17
17
|
*
|
|
18
|
-
* The decrypt is addressed: the feed
|
|
18
|
+
* The decrypt is addressed: the feed document's own `id` goes to `decryptDoc`, and
|
|
19
19
|
* the cipher refuses an envelope sealed for some other resource with was-
|
|
20
20
|
* client's `IntegrityError`. That refusal has two causes and the reader
|
|
21
21
|
* cannot tell them apart: a host that moved one resource's stored envelope
|
|
22
|
-
* under another resource's id, and a
|
|
23
|
-
* addressed sealing (the legacy contacts
|
|
22
|
+
* under another resource's id, and a document minted by a writer that
|
|
23
|
+
* predates addressed sealing (the legacy contacts documents
|
|
24
24
|
* `docs/cross-replica-sync-compatibility.md` records as a stated loss, which
|
|
25
25
|
* every fresh replica bootstrap re-reads). It is logged apart from the
|
|
26
26
|
* ordinary undecryptable skip so a caller can count it, but the benign cause
|
|
27
27
|
* is the common one and an alert on it needs a rate rather than a single
|
|
28
|
-
* event. The projection outcome is the same `none`: the
|
|
28
|
+
* event. The projection outcome is the same `none`: the document is not applied,
|
|
29
29
|
* the body is still stored, and the checkpoint advances past it.
|
|
30
30
|
*
|
|
31
31
|
* @param doc {WireDoc}
|
package/dist/sync/pull.js
CHANGED
|
@@ -41,7 +41,7 @@ import { log } from '../log.js';
|
|
|
41
41
|
*
|
|
42
42
|
* Decryption runs here, outside the store transaction, so a slow/failing decrypt
|
|
43
43
|
* never holds the store's write lock. A document whose body cannot be decrypted
|
|
44
|
-
* (legacy plaintext
|
|
44
|
+
* (legacy plaintext body, corrupt/foreign envelope, key mismatch) is skipped with
|
|
45
45
|
* a `none` projection rather than throwing: the body is still stored and the
|
|
46
46
|
* checkpoint advances past it, so one poison document can never permanently
|
|
47
47
|
* wedge the feed for the whole replica. A document that decrypts but fails the
|
|
@@ -49,17 +49,17 @@ import { log } from '../log.js';
|
|
|
49
49
|
* possibly a buggy or schema-incompatible writer) is skipped the same way:
|
|
50
50
|
* stored, checkpoint advanced, never projected.
|
|
51
51
|
*
|
|
52
|
-
* The decrypt is addressed: the feed
|
|
52
|
+
* The decrypt is addressed: the feed document's own `id` goes to `decryptDoc`, and
|
|
53
53
|
* the cipher refuses an envelope sealed for some other resource with was-
|
|
54
54
|
* client's `IntegrityError`. That refusal has two causes and the reader
|
|
55
55
|
* cannot tell them apart: a host that moved one resource's stored envelope
|
|
56
|
-
* under another resource's id, and a
|
|
57
|
-
* addressed sealing (the legacy contacts
|
|
56
|
+
* under another resource's id, and a document minted by a writer that
|
|
57
|
+
* predates addressed sealing (the legacy contacts documents
|
|
58
58
|
* `docs/cross-replica-sync-compatibility.md` records as a stated loss, which
|
|
59
59
|
* every fresh replica bootstrap re-reads). It is logged apart from the
|
|
60
60
|
* ordinary undecryptable skip so a caller can count it, but the benign cause
|
|
61
61
|
* is the common one and an alert on it needs a rate rather than a single
|
|
62
|
-
* event. The projection outcome is the same `none`: the
|
|
62
|
+
* event. The projection outcome is the same `none`: the document is not applied,
|
|
63
63
|
* the body is still stored, and the checkpoint advances past it.
|
|
64
64
|
*
|
|
65
65
|
* @param doc {WireDoc}
|
package/dist/sync/push.d.ts
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import type { ResolveConflict, SyncStore, WasSyncPort } from './types.js';
|
|
2
2
|
/**
|
|
3
|
-
* Pushes every dirty
|
|
4
|
-
* keeps conflict reconciliation deterministic). Honors
|
|
5
|
-
* A non-conflict error from any
|
|
6
|
-
*
|
|
3
|
+
* Pushes every dirty resource replica for one feed, sequentially (bounds
|
|
4
|
+
* sockets/CPU, and keeps conflict reconciliation deterministic). Honors
|
|
5
|
+
* `signal` between resource replicas. A non-conflict error from any resource
|
|
6
|
+
* replica propagates so the engine aborts the cycle and backs off;
|
|
7
|
+
* already-pushed resource replicas in the batch stay settled.
|
|
7
8
|
*
|
|
8
9
|
* @param options {object}
|
|
9
10
|
* @param options.port {WasSyncPort}
|
|
@@ -12,8 +13,8 @@ import type { ResolveConflict, SyncStore, WasSyncPort } from './types.js';
|
|
|
12
13
|
* @param [options.writerId] {string} this writer's attribution label, sent
|
|
13
14
|
* as `Writer-Id` on every write and delete; absent sends none
|
|
14
15
|
* @param [options.signal] {AbortSignal}
|
|
15
|
-
* @returns {Promise<{ pushed: number; conflictsResolved: number }>} dirty
|
|
16
|
-
* processed this cycle, and how many invoked the LWW resolver (a positive
|
|
16
|
+
* @returns {Promise<{ pushed: number; conflictsResolved: number }>} dirty
|
|
17
|
+
* resource replicas processed this cycle, and how many invoked the LWW resolver (a positive
|
|
17
18
|
* count means the caller should rerun so a local-wins re-push settles)
|
|
18
19
|
*/
|
|
19
20
|
export declare function runPush({ port, store, resolveConflict, writerId, signal }: {
|
package/dist/sync/push.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"push.d.ts","sourceRoot":"","sources":["../../src/sync/push.ts"],"names":[],"mappings":"AAsCA,OAAO,KAAK,EAAQ,eAAe,EAAE,SAAS,EAAE,WAAW,EAAE,MAAM,YAAY,CAAA;
|
|
1
|
+
{"version":3,"file":"push.d.ts","sourceRoot":"","sources":["../../src/sync/push.ts"],"names":[],"mappings":"AAsCA,OAAO,KAAK,EAAQ,eAAe,EAAE,SAAS,EAAE,WAAW,EAAE,MAAM,YAAY,CAAA;AA0M/E;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAsB,OAAO,CAAC,EAC5B,IAAI,EACJ,KAAK,EACL,eAAe,EACf,QAAQ,EACR,MAAM,EACP,EAAE;IACD,IAAI,EAAE,WAAW,CAAA;IACjB,KAAK,EAAE,SAAS,CAAA;IAChB,eAAe,CAAC,EAAE,eAAe,CAAA;IACjC,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,MAAM,CAAC,EAAE,WAAW,CAAA;CACrB,GAAG,OAAO,CAAC;IAAE,MAAM,EAAE,MAAM,CAAC;IAAC,iBAAiB,EAAE,MAAM,CAAA;CAAE,CAAC,CAyBzD"}
|
package/dist/sync/push.js
CHANGED
|
@@ -2,23 +2,23 @@
|
|
|
2
2
|
* Copyright (c) 2026 Interop Alliance. All rights reserved.
|
|
3
3
|
*/
|
|
4
4
|
/**
|
|
5
|
-
* The push side of the WAS replication engine core: fan each dirty local
|
|
6
|
-
* to a conditional WAS write, then reconcile per the
|
|
7
|
-
* table.
|
|
5
|
+
* The push side of the WAS replication engine core: fan each dirty local
|
|
6
|
+
* resource replica out to a conditional WAS write, then reconcile per the
|
|
7
|
+
* content-addressed conflict table.
|
|
8
8
|
*
|
|
9
|
-
* On a content-addressed collection an id's `data` never mutates, so a live
|
|
10
|
-
* only ever pushes as a create (`If-None-Match: *`) and a
|
|
11
|
-
* there is no update path. A mutable (last-write-wins)
|
|
12
|
-
*
|
|
13
|
-
* (`If-Match`) once acked, and settles
|
|
14
|
-
* {@link ResolveConflict} policy.
|
|
9
|
+
* On a content-addressed collection an id's `data` never mutates, so a live
|
|
10
|
+
* resource replica only ever pushes as a create (`If-None-Match: *`) and a
|
|
11
|
+
* tombstone as a delete; there is no update path. A mutable (last-write-wins)
|
|
12
|
+
* collection pushes a live resource replica as a create while never-acked
|
|
13
|
+
* (`version 0`) and as an in-place update (`If-Match`) once acked, and settles
|
|
14
|
+
* a `412` through its injected {@link ResolveConflict} policy.
|
|
15
15
|
*
|
|
16
16
|
* This loop covers the CONTENT sub-resource only (`data` / `version`, at
|
|
17
17
|
* `PUT/DELETE /:id`). It does not drive the independently-versioned METADATA
|
|
18
18
|
* sub-resource (`custom` / `metaVersion`, at `PUT /:id/meta`): a replica that
|
|
19
19
|
* syncs user-writable metadata (the web wallet's RxDB driver, via
|
|
20
|
-
* `WasSyncPort.putMeta`) keeps that half in its own push handler. It is left
|
|
21
|
-
* of this core deliberately -- none of the wallet Space collections
|
|
20
|
+
* `WasSyncPort.putMeta`) keeps that half in its own push handler. It is left
|
|
21
|
+
* out of this core deliberately -- none of the wallet Space collections
|
|
22
22
|
* (`private-credentials`, `public-credentials`, `wallet-activity`, `contacts`,
|
|
23
23
|
* `contacts-history`) versions its metadata independently of its content, so
|
|
24
24
|
* folding a `putMeta` diff into this loop would add an untested code path with
|
|
@@ -34,14 +34,15 @@
|
|
|
34
34
|
*/
|
|
35
35
|
import { isSyncConflictError, isSyncNotFoundError } from '@interop/was-client/sync';
|
|
36
36
|
/**
|
|
37
|
-
* Pushes a dirty live
|
|
38
|
-
* (`PUT /:id` with `If-None-Match: *`); an acked
|
|
39
|
-
* in-place update (`If-Match` over its
|
|
40
|
-
* absent only against a backend that
|
|
41
|
-
* a mutable collection, since a
|
|
42
|
-
*
|
|
43
|
-
*
|
|
44
|
-
*
|
|
37
|
+
* Pushes a dirty live resource replica. A never-acked resource replica
|
|
38
|
+
* (`version 0`) is a create (`PUT /:id` with `If-None-Match: *`); an acked
|
|
39
|
+
* resource replica (`version > 0`) is an in-place update (`If-Match` over its
|
|
40
|
+
* stored `etag`, echoed back verbatim -- absent only against a backend that
|
|
41
|
+
* never returned one) -- reachable only on a mutable collection, since a
|
|
42
|
+
* content-addressed resource replica never mutates in place. On success the
|
|
43
|
+
* acked `version` / `etag` are recorded and the resource replica goes clean --
|
|
44
|
+
* unless a local write landed while the write was in flight, which the resource
|
|
45
|
+
* replica's `revision` token detects (see {@link SyncStore.markPushed}) so the
|
|
45
46
|
* newer write stays dirty for the rerun cycle.
|
|
46
47
|
*
|
|
47
48
|
* A `412` is settled by the collection's policy:
|
|
@@ -53,19 +54,21 @@ import { isSyncConflictError, isSyncNotFoundError } from '@interop/was-client/sy
|
|
|
53
54
|
* absent/tombstone -> deletion wins, adopt the tombstone and delete the
|
|
54
55
|
* projection (a later re-add re-encrypts to a fresh id, so nothing is blocked).
|
|
55
56
|
*/
|
|
56
|
-
async function pushUpsert({ port, store,
|
|
57
|
+
async function pushUpsert({ port, store, replica, resolveConflict, writerId }) {
|
|
57
58
|
try {
|
|
58
59
|
const ack = await port.putContent({
|
|
59
|
-
id:
|
|
60
|
-
data:
|
|
61
|
-
...(
|
|
60
|
+
id: replica.id,
|
|
61
|
+
data: replica.data ?? null,
|
|
62
|
+
...(replica.version > 0
|
|
63
|
+
? { ifMatch: replica.etag }
|
|
64
|
+
: { ifNoneMatch: true }),
|
|
62
65
|
...(writerId !== undefined && { writerId })
|
|
63
66
|
});
|
|
64
67
|
await store.markPushed({
|
|
65
|
-
id:
|
|
68
|
+
id: replica.id,
|
|
66
69
|
version: ack.version,
|
|
67
70
|
etag: ack.etag,
|
|
68
|
-
...(
|
|
71
|
+
...(replica.revision !== undefined && { revision: replica.revision })
|
|
69
72
|
});
|
|
70
73
|
return { conflictResolved: false };
|
|
71
74
|
}
|
|
@@ -75,26 +78,27 @@ async function pushUpsert({ port, store, row, resolveConflict, writerId }) {
|
|
|
75
78
|
}
|
|
76
79
|
if (resolveConflict) {
|
|
77
80
|
await resolveConflict({
|
|
78
|
-
id:
|
|
79
|
-
version:
|
|
80
|
-
data:
|
|
81
|
+
id: replica.id,
|
|
82
|
+
version: replica.version,
|
|
83
|
+
data: replica.data
|
|
81
84
|
});
|
|
82
|
-
// The resolver may have left the
|
|
83
|
-
// caller reruns so the re-push settles within the same
|
|
85
|
+
// The resolver may have left the resource replica dirty (local-wins
|
|
86
|
+
// re-encrypt); the caller reruns so the re-push settles within the same
|
|
87
|
+
// sync run.
|
|
84
88
|
return { conflictResolved: true };
|
|
85
89
|
}
|
|
86
|
-
const master = await port.get({ id:
|
|
90
|
+
const master = await port.get({ id: replica.id });
|
|
87
91
|
// An absent or tombstoned resource surfaces as `get` resolving null.
|
|
88
92
|
if (master === null) {
|
|
89
93
|
await store.adoptLatest({
|
|
90
|
-
id:
|
|
94
|
+
id: replica.id,
|
|
91
95
|
latest: null,
|
|
92
96
|
projection: { kind: 'delete' }
|
|
93
97
|
});
|
|
94
98
|
}
|
|
95
99
|
else {
|
|
96
100
|
await store.adoptLatest({
|
|
97
|
-
id:
|
|
101
|
+
id: replica.id,
|
|
98
102
|
latest: master,
|
|
99
103
|
projection: { kind: 'none' }
|
|
100
104
|
});
|
|
@@ -136,51 +140,53 @@ async function tryDelete({ port, store, id, ifMatch, revision, writerId }) {
|
|
|
136
140
|
}
|
|
137
141
|
}
|
|
138
142
|
/**
|
|
139
|
-
* Pushes a dirty tombstone. `DELETE /:id` with `If-Match` over the
|
|
140
|
-
* `etag` when the
|
|
143
|
+
* Pushes a dirty tombstone. `DELETE /:id` with `If-Match` over the resource
|
|
144
|
+
* replica's stored `etag` when the resource replica was ever acked (`version >
|
|
145
|
+
* 0`), unconditional otherwise:
|
|
141
146
|
* - `204` / `404` -> settled (clean).
|
|
142
147
|
* - `412` then master absent/tombstone -> delete/delete race, settled.
|
|
143
148
|
* - `412` then master live -> retry once with the master's fresh `etag`; a
|
|
144
|
-
* second `412` leaves the
|
|
145
|
-
* refreshes its `version` / `etag` via the dirty-deleted-vs-live rule,
|
|
146
|
-
* retry's `If-Match` becomes current).
|
|
149
|
+
* second `412` leaves the resource replica dirty for the next cycle (the next
|
|
150
|
+
* pull refreshes its `version` / `etag` via the dirty-deleted-vs-live rule,
|
|
151
|
+
* so the retry's `If-Match` becomes current).
|
|
147
152
|
*/
|
|
148
|
-
async function pushDelete({ port, store,
|
|
149
|
-
const revisionAck =
|
|
150
|
-
const firstIfMatch =
|
|
153
|
+
async function pushDelete({ port, store, replica, writerId }) {
|
|
154
|
+
const revisionAck = replica.revision !== undefined ? { revision: replica.revision } : {};
|
|
155
|
+
const firstIfMatch = replica.version > 0 ? replica.etag : undefined;
|
|
151
156
|
if (await tryDelete({
|
|
152
157
|
port,
|
|
153
158
|
store,
|
|
154
|
-
id:
|
|
159
|
+
id: replica.id,
|
|
155
160
|
ifMatch: firstIfMatch,
|
|
156
161
|
writerId,
|
|
157
162
|
...revisionAck
|
|
158
163
|
})) {
|
|
159
164
|
return;
|
|
160
165
|
}
|
|
161
|
-
const master = await port.get({ id:
|
|
166
|
+
const master = await port.get({ id: replica.id });
|
|
162
167
|
if (master === null) {
|
|
163
168
|
// delete/delete race -- the resource is already a tombstone / absent.
|
|
164
|
-
await store.markDeletedPushed({ id:
|
|
169
|
+
await store.markDeletedPushed({ id: replica.id, ...revisionAck });
|
|
165
170
|
return;
|
|
166
171
|
}
|
|
167
|
-
// Second attempt with the master's current etag. If it too hits 412 we
|
|
168
|
-
//
|
|
169
|
-
// write).
|
|
172
|
+
// Second attempt with the master's current etag. If it too hits 412 we simply
|
|
173
|
+
// leave the resource replica dirty (tryDelete returned false and made no
|
|
174
|
+
// store write).
|
|
170
175
|
await tryDelete({
|
|
171
176
|
port,
|
|
172
177
|
store,
|
|
173
|
-
id:
|
|
178
|
+
id: replica.id,
|
|
174
179
|
ifMatch: master.etag,
|
|
175
180
|
writerId,
|
|
176
181
|
...revisionAck
|
|
177
182
|
});
|
|
178
183
|
}
|
|
179
184
|
/**
|
|
180
|
-
* Pushes every dirty
|
|
181
|
-
* keeps conflict reconciliation deterministic). Honors
|
|
182
|
-
* A non-conflict error from any
|
|
183
|
-
*
|
|
185
|
+
* Pushes every dirty resource replica for one feed, sequentially (bounds
|
|
186
|
+
* sockets/CPU, and keeps conflict reconciliation deterministic). Honors
|
|
187
|
+
* `signal` between resource replicas. A non-conflict error from any resource
|
|
188
|
+
* replica propagates so the engine aborts the cycle and backs off;
|
|
189
|
+
* already-pushed resource replicas in the batch stay settled.
|
|
184
190
|
*
|
|
185
191
|
* @param options {object}
|
|
186
192
|
* @param options.port {WasSyncPort}
|
|
@@ -189,26 +195,26 @@ async function pushDelete({ port, store, row, writerId }) {
|
|
|
189
195
|
* @param [options.writerId] {string} this writer's attribution label, sent
|
|
190
196
|
* as `Writer-Id` on every write and delete; absent sends none
|
|
191
197
|
* @param [options.signal] {AbortSignal}
|
|
192
|
-
* @returns {Promise<{ pushed: number; conflictsResolved: number }>} dirty
|
|
193
|
-
* processed this cycle, and how many invoked the LWW resolver (a positive
|
|
198
|
+
* @returns {Promise<{ pushed: number; conflictsResolved: number }>} dirty
|
|
199
|
+
* resource replicas processed this cycle, and how many invoked the LWW resolver (a positive
|
|
194
200
|
* count means the caller should rerun so a local-wins re-push settles)
|
|
195
201
|
*/
|
|
196
202
|
export async function runPush({ port, store, resolveConflict, writerId, signal }) {
|
|
197
|
-
const
|
|
203
|
+
const replicas = await store.getDirtyResourceReplicas();
|
|
198
204
|
let pushed = 0;
|
|
199
205
|
let conflictsResolved = 0;
|
|
200
|
-
for (const
|
|
206
|
+
for (const replica of replicas) {
|
|
201
207
|
if (signal?.aborted) {
|
|
202
208
|
break;
|
|
203
209
|
}
|
|
204
|
-
if (
|
|
205
|
-
await pushDelete({ port, store,
|
|
210
|
+
if (replica.deleted) {
|
|
211
|
+
await pushDelete({ port, store, replica, writerId });
|
|
206
212
|
}
|
|
207
213
|
else {
|
|
208
214
|
const { conflictResolved } = await pushUpsert({
|
|
209
215
|
port,
|
|
210
216
|
store,
|
|
211
|
-
|
|
217
|
+
replica,
|
|
212
218
|
resolveConflict,
|
|
213
219
|
writerId
|
|
214
220
|
});
|
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"}
|