@optimystic/db-core 1.0.0-beta.3 → 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/src/cluster/structs.d.ts +30 -6
- package/dist/src/cluster/structs.d.ts.map +1 -1
- package/dist/src/cluster/structs.js +5 -3
- package/dist/src/cluster/structs.js.map +1 -1
- package/dist/src/cohort-topic/antidos/bootstrap-evidence-envelope.d.ts +2 -1
- package/dist/src/cohort-topic/antidos/bootstrap-evidence-envelope.d.ts.map +1 -1
- package/dist/src/cohort-topic/antidos/bootstrap-evidence-envelope.js +10 -3
- package/dist/src/cohort-topic/antidos/bootstrap-evidence-envelope.js.map +1 -1
- package/dist/src/cohort-topic/wire/codec.d.ts.map +1 -1
- package/dist/src/cohort-topic/wire/codec.js +8 -2
- package/dist/src/cohort-topic/wire/codec.js.map +1 -1
- package/dist/src/collection/collection.d.ts +439 -34
- package/dist/src/collection/collection.d.ts.map +1 -1
- package/dist/src/collection/collection.js +843 -148
- package/dist/src/collection/collection.js.map +1 -1
- package/dist/src/collection/struct.d.ts +109 -4
- package/dist/src/collection/struct.d.ts.map +1 -1
- package/dist/src/collection/struct.js +88 -1
- package/dist/src/collection/struct.js.map +1 -1
- package/dist/src/collections/diary/diary.d.ts +6 -2
- package/dist/src/collections/diary/diary.d.ts.map +1 -1
- package/dist/src/collections/diary/diary.js +5 -2
- package/dist/src/collections/diary/diary.js.map +1 -1
- package/dist/src/collections/tree/struct.d.ts +124 -16
- package/dist/src/collections/tree/struct.d.ts.map +1 -1
- package/dist/src/collections/tree/struct.js +86 -11
- package/dist/src/collections/tree/struct.js.map +1 -1
- package/dist/src/collections/tree/tree.d.ts +13 -3
- package/dist/src/collections/tree/tree.d.ts.map +1 -1
- package/dist/src/collections/tree/tree.js +106 -40
- package/dist/src/collections/tree/tree.js.map +1 -1
- package/dist/src/index.d.ts +1 -1
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js +1 -1
- package/dist/src/index.js.map +1 -1
- package/dist/src/matchmaking/wire.d.ts.map +1 -1
- package/dist/src/matchmaking/wire.js +8 -2
- package/dist/src/matchmaking/wire.js.map +1 -1
- package/dist/src/network/durability.d.ts +45 -0
- package/dist/src/network/durability.d.ts.map +1 -0
- package/dist/src/network/durability.js +114 -0
- package/dist/src/network/durability.js.map +1 -0
- package/dist/src/network/i-key-network.d.ts +14 -6
- package/dist/src/network/i-key-network.d.ts.map +1 -1
- package/dist/src/network/index.d.ts +3 -0
- package/dist/src/network/index.d.ts.map +1 -1
- package/dist/src/network/index.js +3 -0
- package/dist/src/network/index.js.map +1 -1
- package/dist/src/network/lineage.d.ts +39 -0
- package/dist/src/network/lineage.d.ts.map +1 -0
- package/dist/src/network/lineage.js +61 -0
- package/dist/src/network/lineage.js.map +1 -0
- package/dist/src/network/routing-key.d.ts +24 -0
- package/dist/src/network/routing-key.d.ts.map +1 -0
- package/dist/src/network/routing-key.js +9 -0
- package/dist/src/network/routing-key.js.map +1 -0
- package/dist/src/network/stale-failure.d.ts +5 -1
- package/dist/src/network/stale-failure.d.ts.map +1 -1
- package/dist/src/network/stale-failure.js +5 -1
- package/dist/src/network/stale-failure.js.map +1 -1
- package/dist/src/network/struct.d.ts +151 -8
- package/dist/src/network/struct.d.ts.map +1 -1
- package/dist/src/network/struct.js.map +1 -1
- package/dist/src/reactivity/wire.d.ts.map +1 -1
- package/dist/src/reactivity/wire.js +8 -2
- package/dist/src/reactivity/wire.js.map +1 -1
- package/dist/src/testing/test-transactor.d.ts +64 -7
- package/dist/src/testing/test-transactor.d.ts.map +1 -1
- package/dist/src/testing/test-transactor.js +150 -23
- package/dist/src/testing/test-transactor.js.map +1 -1
- package/dist/src/transaction/coordinator.d.ts +141 -21
- package/dist/src/transaction/coordinator.d.ts.map +1 -1
- package/dist/src/transaction/coordinator.js +408 -122
- package/dist/src/transaction/coordinator.js.map +1 -1
- package/dist/src/transaction/errors.d.ts +39 -10
- package/dist/src/transaction/errors.d.ts.map +1 -1
- package/dist/src/transaction/errors.js +34 -9
- package/dist/src/transaction/errors.js.map +1 -1
- package/dist/src/transaction/index.d.ts +1 -0
- package/dist/src/transaction/index.d.ts.map +1 -1
- package/dist/src/transaction/index.js.map +1 -1
- package/dist/src/transactor/block-floors.d.ts +78 -0
- package/dist/src/transactor/block-floors.d.ts.map +1 -0
- package/dist/src/transactor/block-floors.js +62 -0
- package/dist/src/transactor/block-floors.js.map +1 -0
- package/dist/src/transactor/change-notifier.d.ts +43 -0
- package/dist/src/transactor/change-notifier.d.ts.map +1 -1
- package/dist/src/transactor/change-notifier.js +3 -0
- package/dist/src/transactor/change-notifier.js.map +1 -1
- package/dist/src/transactor/index.d.ts +1 -0
- package/dist/src/transactor/index.d.ts.map +1 -1
- package/dist/src/transactor/index.js +1 -0
- package/dist/src/transactor/index.js.map +1 -1
- package/dist/src/transactor/network-transactor.d.ts +43 -6
- package/dist/src/transactor/network-transactor.d.ts.map +1 -1
- package/dist/src/transactor/network-transactor.js +279 -86
- package/dist/src/transactor/network-transactor.js.map +1 -1
- package/dist/src/transactor/transactor-source.d.ts +94 -4
- package/dist/src/transactor/transactor-source.d.ts.map +1 -1
- package/dist/src/transactor/transactor-source.js +120 -42
- package/dist/src/transactor/transactor-source.js.map +1 -1
- package/dist/src/transactor/transactor.d.ts +14 -1
- package/dist/src/transactor/transactor.d.ts.map +1 -1
- package/dist/src/transform/atomic.d.ts.map +1 -1
- package/dist/src/transform/atomic.js +12 -9
- package/dist/src/transform/atomic.js.map +1 -1
- package/dist/src/transform/base-pins.d.ts +50 -17
- package/dist/src/transform/base-pins.d.ts.map +1 -1
- package/dist/src/transform/base-pins.js +58 -11
- package/dist/src/transform/base-pins.js.map +1 -1
- package/dist/src/transform/cache-source.d.ts +75 -11
- package/dist/src/transform/cache-source.d.ts.map +1 -1
- package/dist/src/transform/cache-source.js +162 -36
- package/dist/src/transform/cache-source.js.map +1 -1
- package/dist/src/transform/digest.d.ts +8 -1
- package/dist/src/transform/digest.d.ts.map +1 -1
- package/dist/src/transform/digest.js +19 -9
- package/dist/src/transform/digest.js.map +1 -1
- package/dist/src/transform/helpers.d.ts +6 -2
- package/dist/src/transform/helpers.d.ts.map +1 -1
- package/dist/src/transform/helpers.js +8 -3
- package/dist/src/transform/helpers.js.map +1 -1
- package/dist/src/transform/tracker.d.ts +94 -14
- package/dist/src/transform/tracker.d.ts.map +1 -1
- package/dist/src/transform/tracker.js +222 -68
- package/dist/src/transform/tracker.js.map +1 -1
- package/dist/src/utility/structural-equals.d.ts +38 -0
- package/dist/src/utility/structural-equals.d.ts.map +1 -0
- package/dist/src/utility/structural-equals.js +77 -0
- package/dist/src/utility/structural-equals.js.map +1 -0
- package/package.json +2 -2
- package/src/cluster/structs.ts +28 -7
- package/src/cohort-topic/antidos/bootstrap-evidence-envelope.ts +260 -253
- package/src/cohort-topic/wire/codec.ts +222 -216
- package/src/collection/collection.ts +960 -157
- package/src/collection/struct.ts +99 -4
- package/src/collections/diary/diary.ts +71 -68
- package/src/collections/tree/struct.ts +155 -23
- package/src/collections/tree/tree.ts +127 -40
- package/src/index.ts +1 -1
- package/src/matchmaking/wire.ts +8 -2
- package/src/network/durability.ts +123 -0
- package/src/network/i-key-network.ts +60 -52
- package/src/network/index.ts +10 -7
- package/src/network/lineage.ts +80 -0
- package/src/network/routing-key.ts +27 -0
- package/src/network/stale-failure.ts +5 -1
- package/src/network/struct.ts +156 -8
- package/src/reactivity/wire.ts +230 -224
- package/src/testing/test-transactor.ts +152 -24
- package/src/transaction/coordinator.ts +470 -131
- package/src/transaction/errors.ts +34 -9
- package/src/transaction/index.ts +1 -0
- package/src/transactor/block-floors.ts +105 -0
- package/src/transactor/change-notifier.ts +129 -80
- package/src/transactor/index.ts +1 -0
- package/src/transactor/network-transactor.ts +297 -89
- package/src/transactor/transactor-source.ts +131 -42
- package/src/transactor/transactor.ts +13 -1
- package/src/transform/atomic.ts +25 -22
- package/src/transform/base-pins.ts +76 -18
- package/src/transform/cache-source.ts +327 -194
- package/src/transform/digest.ts +21 -10
- package/src/transform/helpers.ts +8 -3
- package/src/transform/tracker.ts +423 -270
- package/src/utility/structural-equals.ts +72 -0
- package/dist/src/utility/block-id-to-bytes.d.ts +0 -3
- package/dist/src/utility/block-id-to-bytes.d.ts.map +0 -1
- package/dist/src/utility/block-id-to-bytes.js +0 -7
- package/dist/src/utility/block-id-to-bytes.js.map +0 -1
- package/src/utility/block-id-to-bytes.ts +0 -8
package/src/reactivity/wire.ts
CHANGED
|
@@ -1,224 +1,230 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Reactivity — V1 wire types, codecs, and per-message validation.
|
|
3
|
-
*
|
|
4
|
-
* Transcribed from `docs/reactivity.md` §Wire formats. Reactivity reuses the cohort-topic `RegisterV1`
|
|
5
|
-
* / `RenewV1` envelopes **unchanged**; the reactivity-specific shapes live in two places:
|
|
6
|
-
*
|
|
7
|
-
* 1. **Subscribe app payload** ({@link SubscribeAppPayloadV1}) carried opaquely inside
|
|
8
|
-
* `RegisterV1.appPayload`. Serialized to UTF-8 JSON bytes and handed to the cohort-topic
|
|
9
|
-
* {@link import("../cohort-topic/service.js").RegisterRequest}`.appPayload` slot, which base64url-encodes
|
|
10
|
-
* it on the wire. It is therefore **not** length-framed here — the cohort-topic `RegisterV1` frame
|
|
11
|
-
* wraps it. The subscribe RPC's authentication (peer-key signature over `correlationId` + `timestamp`)
|
|
12
|
-
* is the cohort-topic `RegisterV1` envelope's job, so the payload itself carries no signature.
|
|
13
|
-
*
|
|
14
|
-
* 2. **Notification** ({@link NotificationV1}) sent as a standalone RPC over the reactivity application
|
|
15
|
-
* protocol. It rides the same length-prefixed UTF-8 JSON framing as cohort-topic messages
|
|
16
|
-
* ({@link encodeCohortMessage}). Its `sig` is **bit-for-bit** the commit certificate's threshold
|
|
17
|
-
* signature — reactivity never re-signs (`docs/reactivity.md` §Notification origination).
|
|
18
|
-
*
|
|
19
|
-
* Conventions (matching the cohort-topic wire conventions): all JSON, byte fields base64url (no
|
|
20
|
-
* padding), unix-millisecond timestamps, per-message structural validation on decode, byte-fidelity
|
|
21
|
-
* round-trips (encode→decode→encode is stable). `collectionId` / `tailId` travel as the collection's
|
|
22
|
-
* base64url block ids verbatim. The `BackfillV1` / `ResumeV1` codecs belong to the sibling tickets.
|
|
23
|
-
*/
|
|
24
|
-
|
|
25
|
-
import {
|
|
26
|
-
decodeCohortMessage,
|
|
27
|
-
encodeCohortMessage,
|
|
28
|
-
DEFAULT_MAX_MESSAGE_BYTES,
|
|
29
|
-
} from "../cohort-topic/wire/codec.js";
|
|
30
|
-
import {
|
|
31
|
-
asObject,
|
|
32
|
-
b64urlField,
|
|
33
|
-
failWire as fail,
|
|
34
|
-
reqBool,
|
|
35
|
-
reqFiniteNumber,
|
|
36
|
-
reqIntInRange,
|
|
37
|
-
reqString,
|
|
38
|
-
reqStringArray,
|
|
39
|
-
requireV1,
|
|
40
|
-
} from "./wire-validate.js";
|
|
41
|
-
|
|
42
|
-
const utf8Encoder = new TextEncoder();
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
*
|
|
103
|
-
*
|
|
104
|
-
*
|
|
105
|
-
*
|
|
106
|
-
*
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
}
|
|
199
|
-
if (obj["
|
|
200
|
-
out.
|
|
201
|
-
}
|
|
202
|
-
if (obj["
|
|
203
|
-
out.
|
|
204
|
-
}
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
}
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
1
|
+
/**
|
|
2
|
+
* Reactivity — V1 wire types, codecs, and per-message validation.
|
|
3
|
+
*
|
|
4
|
+
* Transcribed from `docs/reactivity.md` §Wire formats. Reactivity reuses the cohort-topic `RegisterV1`
|
|
5
|
+
* / `RenewV1` envelopes **unchanged**; the reactivity-specific shapes live in two places:
|
|
6
|
+
*
|
|
7
|
+
* 1. **Subscribe app payload** ({@link SubscribeAppPayloadV1}) carried opaquely inside
|
|
8
|
+
* `RegisterV1.appPayload`. Serialized to UTF-8 JSON bytes and handed to the cohort-topic
|
|
9
|
+
* {@link import("../cohort-topic/service.js").RegisterRequest}`.appPayload` slot, which base64url-encodes
|
|
10
|
+
* it on the wire. It is therefore **not** length-framed here — the cohort-topic `RegisterV1` frame
|
|
11
|
+
* wraps it. The subscribe RPC's authentication (peer-key signature over `correlationId` + `timestamp`)
|
|
12
|
+
* is the cohort-topic `RegisterV1` envelope's job, so the payload itself carries no signature.
|
|
13
|
+
*
|
|
14
|
+
* 2. **Notification** ({@link NotificationV1}) sent as a standalone RPC over the reactivity application
|
|
15
|
+
* protocol. It rides the same length-prefixed UTF-8 JSON framing as cohort-topic messages
|
|
16
|
+
* ({@link encodeCohortMessage}). Its `sig` is **bit-for-bit** the commit certificate's threshold
|
|
17
|
+
* signature — reactivity never re-signs (`docs/reactivity.md` §Notification origination).
|
|
18
|
+
*
|
|
19
|
+
* Conventions (matching the cohort-topic wire conventions): all JSON, byte fields base64url (no
|
|
20
|
+
* padding), unix-millisecond timestamps, per-message structural validation on decode, byte-fidelity
|
|
21
|
+
* round-trips (encode→decode→encode is stable). `collectionId` / `tailId` travel as the collection's
|
|
22
|
+
* base64url block ids verbatim. The `BackfillV1` / `ResumeV1` codecs belong to the sibling tickets.
|
|
23
|
+
*/
|
|
24
|
+
|
|
25
|
+
import {
|
|
26
|
+
decodeCohortMessage,
|
|
27
|
+
encodeCohortMessage,
|
|
28
|
+
DEFAULT_MAX_MESSAGE_BYTES,
|
|
29
|
+
} from "../cohort-topic/wire/codec.js";
|
|
30
|
+
import {
|
|
31
|
+
asObject,
|
|
32
|
+
b64urlField,
|
|
33
|
+
failWire as fail,
|
|
34
|
+
reqBool,
|
|
35
|
+
reqFiniteNumber,
|
|
36
|
+
reqIntInRange,
|
|
37
|
+
reqString,
|
|
38
|
+
reqStringArray,
|
|
39
|
+
requireV1,
|
|
40
|
+
} from "./wire-validate.js";
|
|
41
|
+
|
|
42
|
+
const utf8Encoder = new TextEncoder();
|
|
43
|
+
// Built on first use, not at module load: Hermes (React Native) has no native `TextDecoder`, so a
|
|
44
|
+
// module-scope construction fails the import itself whenever it runs ahead of the host's polyfill.
|
|
45
|
+
let utf8DecoderInstance: TextDecoder | undefined;
|
|
46
|
+
const utf8Decoder = (): TextDecoder => utf8DecoderInstance ??= new TextDecoder("utf-8", { fatal: true });
|
|
47
|
+
|
|
48
|
+
/** Ceiling for an opaque subscribe app payload, guarding decode allocation. */
|
|
49
|
+
export const DEFAULT_MAX_SUBSCRIBE_PAYLOAD_BYTES = 64 * 1024;
|
|
50
|
+
|
|
51
|
+
// --- Subscribe app payload (carried inside cohort-topic RegisterV1.appPayload) ---
|
|
52
|
+
|
|
53
|
+
/** A subscriber's registration payload (`RegisterV1.appPayload`, `kind == "reactivity"`). */
|
|
54
|
+
export interface SubscribeAppPayloadV1 {
|
|
55
|
+
kind: "reactivity";
|
|
56
|
+
/** Stable collection identity, base64url (the collection's id block id). */
|
|
57
|
+
collectionId: string;
|
|
58
|
+
/** Tail block id at registration time, base64url (the subscriber-side rotation detector). */
|
|
59
|
+
tailIdAtAttach: string;
|
|
60
|
+
/** Last revision the subscriber already holds; `0` for a fresh subscribe. */
|
|
61
|
+
lastKnownRev: number;
|
|
62
|
+
/** Max delta bytes the subscriber accepts; `0` declines delta payloads (Edge). */
|
|
63
|
+
deltaMaxBytes: number;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// --- Notification (length-framed RPC) ---
|
|
67
|
+
|
|
68
|
+
/** The tail cohort's rotation pre-announce, carried inside (and signed by) a {@link NotificationV1}. */
|
|
69
|
+
export interface RotationHintV1 {
|
|
70
|
+
/** New tail block id the topic anchor is rotating to, base64url. */
|
|
71
|
+
newTailId: string;
|
|
72
|
+
/** Revision at which the new tail becomes effective. */
|
|
73
|
+
effectiveAtRevision: number;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/** A signed change notification fanned out through the reactivity tree (`docs/reactivity.md` §Notification origination). */
|
|
77
|
+
export interface NotificationV1 {
|
|
78
|
+
v: 1;
|
|
79
|
+
/** Collection id, base64url. */
|
|
80
|
+
collectionId: string;
|
|
81
|
+
/** Tail block id the reactivity topic is anchored on, base64url. */
|
|
82
|
+
tailId: string;
|
|
83
|
+
/** Per-collection monotonic revision. */
|
|
84
|
+
revision: number;
|
|
85
|
+
/**
|
|
86
|
+
* The commit-vote **signed payload** `utf8(commitHash + ":approve")`, base64url — the exact bytes the
|
|
87
|
+
* threshold signature {@link sig} was computed over. A subscriber threshold-verifies {@link sig} over
|
|
88
|
+
* `b64urlToBytes(digest)`, so this reproduces the signed image against real Ed25519.
|
|
89
|
+
*/
|
|
90
|
+
digest: string;
|
|
91
|
+
/** Optional bounded delta, base64url; omitted when `delta_max == 0` or the collection declines it. */
|
|
92
|
+
delta?: string;
|
|
93
|
+
/** Unix ms. */
|
|
94
|
+
timestamp: number;
|
|
95
|
+
/** Threshold signature, base64url — **= the commit cert**, never re-signed. */
|
|
96
|
+
sig: string;
|
|
97
|
+
/** PeerIds contributing to {@link sig}, base64url of the cohort member-id bytes. */
|
|
98
|
+
signers: string[];
|
|
99
|
+
/** Tail-rotation pre-announce, when this notification carries one (rotation ticket fills it). */
|
|
100
|
+
rotationHint?: RotationHintV1;
|
|
101
|
+
/**
|
|
102
|
+
* `true` iff this notification announces a durable **invalidation** (a reversal of a previously
|
|
103
|
+
* committed action proven invalid by dispute) rather than an ordinary commit
|
|
104
|
+
* (`docs/reactivity.md` §Notification origination, `docs/right-is-right.md` §Durable Invalidation).
|
|
105
|
+
* An invalidation is a committed collection change like any other and rides the same notification
|
|
106
|
+
* path, reusing the **invalidation's** commit cert as {@link sig} (never re-signed) — so it verifies
|
|
107
|
+
* against the tail cohort exactly like a commit notification. The flag is a hint that lets a
|
|
108
|
+
* subscriber react (drop derived results + resubmit) rather than merely refresh; a subscriber that
|
|
109
|
+
* ignores it still re-reads the authoritative reverted state. Omitted (falsy) on commit notifications.
|
|
110
|
+
*/
|
|
111
|
+
invalidation?: boolean;
|
|
112
|
+
/**
|
|
113
|
+
* When {@link invalidation} is set, the original action id that was reversed
|
|
114
|
+
* (`InvalidationEntry.invalidatedActionId`). Lets a client dedup/coalesce the several cascade
|
|
115
|
+
* notifications a single dispute can produce by the action they reverse. Omitted on commits.
|
|
116
|
+
*/
|
|
117
|
+
invalidatedActionId?: string;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// --- validation helpers ---
|
|
121
|
+
// Generic primitives live in ./wire-validate.js (shared across the reactivity codecs); the
|
|
122
|
+
// notification-specific narrowing stays here next to the type it produces.
|
|
123
|
+
|
|
124
|
+
function validateRotationHint(value: unknown, what: string): RotationHintV1 {
|
|
125
|
+
const obj = asObject(value, what);
|
|
126
|
+
return {
|
|
127
|
+
newTailId: b64urlField(reqString(obj, "newTailId", what), "newTailId", what),
|
|
128
|
+
effectiveAtRevision: reqIntInRange(obj, "effectiveAtRevision", what, 0),
|
|
129
|
+
};
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
// --- Subscribe app payload (opaque RegisterV1.appPayload bytes) ---
|
|
133
|
+
|
|
134
|
+
/** Narrow an already-parsed value to {@link SubscribeAppPayloadV1}, throwing on any defect. */
|
|
135
|
+
export function validateSubscribeAppPayloadV1(value: unknown): SubscribeAppPayloadV1 {
|
|
136
|
+
const what = "SubscribeAppPayloadV1";
|
|
137
|
+
const obj = asObject(value, what);
|
|
138
|
+
if (obj["kind"] !== "reactivity") {
|
|
139
|
+
fail(`${what}: field "kind" must be exactly "reactivity"`);
|
|
140
|
+
}
|
|
141
|
+
return {
|
|
142
|
+
kind: "reactivity",
|
|
143
|
+
collectionId: b64urlField(reqString(obj, "collectionId", what), "collectionId", what),
|
|
144
|
+
tailIdAtAttach: b64urlField(reqString(obj, "tailIdAtAttach", what), "tailIdAtAttach", what),
|
|
145
|
+
lastKnownRev: reqIntInRange(obj, "lastKnownRev", what, 0),
|
|
146
|
+
deltaMaxBytes: reqIntInRange(obj, "deltaMaxBytes", what, 0),
|
|
147
|
+
};
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
/** Serialize a {@link SubscribeAppPayloadV1} to the opaque UTF-8 JSON bytes for `RegisterV1.appPayload`. */
|
|
151
|
+
export function encodeSubscribeAppPayload(payload: SubscribeAppPayloadV1): Uint8Array {
|
|
152
|
+
return utf8Encoder.encode(JSON.stringify(validateSubscribeAppPayloadV1(payload)));
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
/** Decode opaque `RegisterV1.appPayload` bytes back to a validated {@link SubscribeAppPayloadV1}. */
|
|
156
|
+
export function decodeSubscribeAppPayload(bytes: Uint8Array, maxBytes: number = DEFAULT_MAX_SUBSCRIBE_PAYLOAD_BYTES): SubscribeAppPayloadV1 {
|
|
157
|
+
return validateSubscribeAppPayloadV1(parseJsonBytes(bytes, maxBytes, "SubscribeAppPayloadV1"));
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
/** Parse opaque (non-framed) UTF-8 JSON payload bytes, rejecting oversized/invalid input. */
|
|
161
|
+
function parseJsonBytes(bytes: Uint8Array, maxBytes: number, what: string): unknown {
|
|
162
|
+
if (bytes.length > maxBytes) {
|
|
163
|
+
fail(`${what}: payload ${bytes.length} exceeds max ${maxBytes} bytes`);
|
|
164
|
+
}
|
|
165
|
+
// Fetched outside the try, so a missing `TextDecoder` polyfill surfaces as itself rather than as
|
|
166
|
+
// "not valid UTF-8".
|
|
167
|
+
const decoder = utf8Decoder();
|
|
168
|
+
let text: string;
|
|
169
|
+
try {
|
|
170
|
+
text = decoder.decode(bytes);
|
|
171
|
+
} catch {
|
|
172
|
+
fail(`${what}: payload is not valid UTF-8`);
|
|
173
|
+
}
|
|
174
|
+
try {
|
|
175
|
+
return JSON.parse(text) as unknown;
|
|
176
|
+
} catch {
|
|
177
|
+
fail(`${what}: payload is not valid JSON`);
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
// --- Notification (length-framed) ---
|
|
182
|
+
|
|
183
|
+
/** Narrow an already-parsed value to {@link NotificationV1}, throwing on any defect. */
|
|
184
|
+
export function validateNotificationV1(value: unknown): NotificationV1 {
|
|
185
|
+
const what = "NotificationV1";
|
|
186
|
+
const obj = asObject(value, what);
|
|
187
|
+
requireV1(obj, what);
|
|
188
|
+
const signers = reqStringArray(obj, "signers", what);
|
|
189
|
+
const out: NotificationV1 = {
|
|
190
|
+
v: 1,
|
|
191
|
+
collectionId: b64urlField(reqString(obj, "collectionId", what), "collectionId", what),
|
|
192
|
+
tailId: b64urlField(reqString(obj, "tailId", what), "tailId", what),
|
|
193
|
+
revision: reqIntInRange(obj, "revision", what, 0),
|
|
194
|
+
digest: b64urlField(reqString(obj, "digest", what), "digest", what),
|
|
195
|
+
timestamp: reqFiniteNumber(obj, "timestamp", what),
|
|
196
|
+
sig: b64urlField(reqString(obj, "sig", what), "sig", what),
|
|
197
|
+
signers: signers.map((s, i) => b64urlField(s, `signers[${i}]`, what)),
|
|
198
|
+
};
|
|
199
|
+
if (obj["delta"] !== undefined) {
|
|
200
|
+
out.delta = b64urlField(reqString(obj, "delta", what), "delta", what);
|
|
201
|
+
}
|
|
202
|
+
if (obj["rotationHint"] !== undefined) {
|
|
203
|
+
out.rotationHint = validateRotationHint(obj["rotationHint"], `${what}.rotationHint`);
|
|
204
|
+
}
|
|
205
|
+
if (obj["invalidation"] !== undefined) {
|
|
206
|
+
out.invalidation = reqBool(obj, "invalidation", what);
|
|
207
|
+
}
|
|
208
|
+
if (obj["invalidatedActionId"] !== undefined) {
|
|
209
|
+
out.invalidatedActionId = reqString(obj, "invalidatedActionId", what);
|
|
210
|
+
}
|
|
211
|
+
return out;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
/** Validate an array of {@link NotificationV1} (the `entries`/`recentEntries` carried in backfill/resume replies). */
|
|
215
|
+
export function validateNotificationArray(value: unknown, what: string): NotificationV1[] {
|
|
216
|
+
if (!Array.isArray(value)) {
|
|
217
|
+
fail(`${what}: field "entries" must be an array`);
|
|
218
|
+
}
|
|
219
|
+
return value.map((entry) => validateNotificationV1(entry));
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
/** Encode a {@link NotificationV1} as a length-prefixed UTF-8 JSON frame. */
|
|
223
|
+
export function encodeNotificationV1(msg: NotificationV1, maxMessageBytes: number = DEFAULT_MAX_MESSAGE_BYTES): Uint8Array {
|
|
224
|
+
return encodeCohortMessage(validateNotificationV1(msg), maxMessageBytes);
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
/** Decode a length-prefixed {@link NotificationV1} frame. */
|
|
228
|
+
export function decodeNotificationV1(bytes: Uint8Array, maxMessageBytes?: number): NotificationV1 {
|
|
229
|
+
return validateNotificationV1(decodeCohortMessage(bytes, maxMessageBytes));
|
|
230
|
+
}
|