@optimystic/db-core 0.14.1 → 0.16.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/src/blocks/block-store.d.ts +18 -1
- package/dist/src/blocks/block-store.d.ts.map +1 -1
- package/dist/src/blocks/helpers.d.ts +2 -2
- package/dist/src/blocks/helpers.d.ts.map +1 -1
- package/dist/src/blocks/helpers.js +2 -2
- package/dist/src/blocks/helpers.js.map +1 -1
- package/dist/src/btree/btree.d.ts +27 -6
- package/dist/src/btree/btree.d.ts.map +1 -1
- package/dist/src/btree/btree.js +104 -61
- package/dist/src/btree/btree.js.map +1 -1
- package/dist/src/chain/chain.d.ts.map +1 -1
- package/dist/src/chain/chain.js +11 -1
- package/dist/src/chain/chain.js.map +1 -1
- package/dist/src/cluster/index.d.ts +1 -0
- package/dist/src/cluster/index.d.ts.map +1 -1
- package/dist/src/cluster/index.js +1 -0
- package/dist/src/cluster/index.js.map +1 -1
- package/dist/src/cluster/membership.d.ts +49 -0
- package/dist/src/cluster/membership.d.ts.map +1 -0
- package/dist/src/cluster/membership.js +75 -0
- package/dist/src/cluster/membership.js.map +1 -0
- package/dist/src/cluster/structs.d.ts +50 -1
- package/dist/src/cluster/structs.d.ts.map +1 -1
- package/dist/src/cluster/structs.js +9 -1
- package/dist/src/cluster/structs.js.map +1 -1
- package/dist/src/cohort-topic/addressing.d.ts +8 -2
- package/dist/src/cohort-topic/addressing.d.ts.map +1 -1
- package/dist/src/cohort-topic/addressing.js +11 -3
- package/dist/src/cohort-topic/addressing.js.map +1 -1
- package/dist/src/cohort-topic/antidos/bootstrap-evidence.d.ts +13 -7
- package/dist/src/cohort-topic/antidos/bootstrap-evidence.d.ts.map +1 -1
- package/dist/src/cohort-topic/antidos/bootstrap-evidence.js +17 -6
- package/dist/src/cohort-topic/antidos/bootstrap-evidence.js.map +1 -1
- package/dist/src/cohort-topic/antidos/replay-guard.d.ts +17 -0
- package/dist/src/cohort-topic/antidos/replay-guard.d.ts.map +1 -1
- package/dist/src/cohort-topic/antidos/replay-guard.js +33 -0
- package/dist/src/cohort-topic/antidos/replay-guard.js.map +1 -1
- package/dist/src/cohort-topic/antidos/topic-budget.d.ts +7 -0
- package/dist/src/cohort-topic/antidos/topic-budget.d.ts.map +1 -1
- package/dist/src/cohort-topic/antidos/topic-budget.js +8 -2
- package/dist/src/cohort-topic/antidos/topic-budget.js.map +1 -1
- package/dist/src/cohort-topic/coldstart.d.ts +34 -14
- package/dist/src/cohort-topic/coldstart.d.ts.map +1 -1
- package/dist/src/cohort-topic/coldstart.js +25 -11
- package/dist/src/cohort-topic/coldstart.js.map +1 -1
- package/dist/src/cohort-topic/gossip/bus.d.ts +12 -1
- package/dist/src/cohort-topic/gossip/bus.d.ts.map +1 -1
- package/dist/src/cohort-topic/gossip/bus.js +27 -1
- package/dist/src/cohort-topic/gossip/bus.js.map +1 -1
- package/dist/src/cohort-topic/gossip/view.d.ts +8 -0
- package/dist/src/cohort-topic/gossip/view.d.ts.map +1 -1
- package/dist/src/cohort-topic/gossip/view.js +17 -1
- package/dist/src/cohort-topic/gossip/view.js.map +1 -1
- package/dist/src/cohort-topic/member-engine.d.ts +10 -3
- package/dist/src/cohort-topic/member-engine.d.ts.map +1 -1
- package/dist/src/cohort-topic/member-engine.js +29 -14
- package/dist/src/cohort-topic/member-engine.js.map +1 -1
- package/dist/src/cohort-topic/membership/publisher.d.ts +14 -7
- package/dist/src/cohort-topic/membership/publisher.d.ts.map +1 -1
- package/dist/src/cohort-topic/membership/publisher.js +15 -27
- package/dist/src/cohort-topic/membership/publisher.js.map +1 -1
- package/dist/src/cohort-topic/membership/verifier.d.ts +35 -0
- package/dist/src/cohort-topic/membership/verifier.d.ts.map +1 -1
- package/dist/src/cohort-topic/membership/verifier.js +88 -3
- package/dist/src/cohort-topic/membership/verifier.js.map +1 -1
- package/dist/src/cohort-topic/promotion.d.ts +7 -0
- package/dist/src/cohort-topic/promotion.d.ts.map +1 -1
- package/dist/src/cohort-topic/promotion.js +16 -2
- package/dist/src/cohort-topic/promotion.js.map +1 -1
- package/dist/src/cohort-topic/registration/renewal.d.ts +18 -0
- package/dist/src/cohort-topic/registration/renewal.d.ts.map +1 -1
- package/dist/src/cohort-topic/registration/renewal.js +54 -0
- package/dist/src/cohort-topic/registration/renewal.js.map +1 -1
- package/dist/src/cohort-topic/registration/types.d.ts +12 -0
- package/dist/src/cohort-topic/registration/types.d.ts.map +1 -1
- package/dist/src/cohort-topic/registration/types.js +14 -0
- package/dist/src/cohort-topic/registration/types.js.map +1 -1
- package/dist/src/cohort-topic/service.d.ts +4 -3
- package/dist/src/cohort-topic/service.d.ts.map +1 -1
- package/dist/src/cohort-topic/service.js +38 -12
- package/dist/src/cohort-topic/service.js.map +1 -1
- package/dist/src/cohort-topic/sig/payloads.d.ts +23 -7
- package/dist/src/cohort-topic/sig/payloads.d.ts.map +1 -1
- package/dist/src/cohort-topic/sig/payloads.js +22 -6
- package/dist/src/cohort-topic/sig/payloads.js.map +1 -1
- package/dist/src/cohort-topic/traffic.d.ts +6 -0
- package/dist/src/cohort-topic/traffic.d.ts.map +1 -1
- package/dist/src/cohort-topic/traffic.js +16 -1
- package/dist/src/cohort-topic/traffic.js.map +1 -1
- package/dist/src/cohort-topic/walk.d.ts +22 -7
- package/dist/src/cohort-topic/walk.d.ts.map +1 -1
- package/dist/src/cohort-topic/walk.js +78 -18
- package/dist/src/cohort-topic/walk.js.map +1 -1
- package/dist/src/cohort-topic/wire/codec.d.ts +3 -1
- package/dist/src/cohort-topic/wire/codec.d.ts.map +1 -1
- package/dist/src/cohort-topic/wire/codec.js +7 -1
- package/dist/src/cohort-topic/wire/codec.js.map +1 -1
- package/dist/src/cohort-topic/wire/index.d.ts +1 -1
- package/dist/src/cohort-topic/wire/index.d.ts.map +1 -1
- package/dist/src/cohort-topic/wire/index.js +1 -1
- package/dist/src/cohort-topic/wire/index.js.map +1 -1
- package/dist/src/cohort-topic/wire/payloads.d.ts +5 -4
- package/dist/src/cohort-topic/wire/payloads.d.ts.map +1 -1
- package/dist/src/cohort-topic/wire/payloads.js +14 -5
- package/dist/src/cohort-topic/wire/payloads.js.map +1 -1
- package/dist/src/cohort-topic/wire/primitives.d.ts +65 -0
- package/dist/src/cohort-topic/wire/primitives.d.ts.map +1 -0
- package/dist/src/cohort-topic/wire/primitives.js +173 -0
- package/dist/src/cohort-topic/wire/primitives.js.map +1 -0
- package/dist/src/cohort-topic/wire/types.d.ts +132 -5
- package/dist/src/cohort-topic/wire/types.d.ts.map +1 -1
- package/dist/src/cohort-topic/wire/validate.d.ts +15 -5
- package/dist/src/cohort-topic/wire/validate.d.ts.map +1 -1
- package/dist/src/cohort-topic/wire/validate.js +146 -123
- package/dist/src/cohort-topic/wire/validate.js.map +1 -1
- package/dist/src/collection/collection.d.ts +23 -12
- package/dist/src/collection/collection.d.ts.map +1 -1
- package/dist/src/collection/collection.js +111 -35
- package/dist/src/collection/collection.js.map +1 -1
- package/dist/src/collection/struct.d.ts +37 -2
- package/dist/src/collection/struct.d.ts.map +1 -1
- package/dist/src/collection/struct.js +16 -1
- package/dist/src/collection/struct.js.map +1 -1
- package/dist/src/collections/tree/tree.d.ts +18 -1
- package/dist/src/collections/tree/tree.d.ts.map +1 -1
- package/dist/src/collections/tree/tree.js +36 -6
- package/dist/src/collections/tree/tree.js.map +1 -1
- package/dist/src/index.d.ts +1 -0
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js +1 -0
- package/dist/src/index.js.map +1 -1
- package/dist/src/log/log.d.ts +26 -3
- package/dist/src/log/log.d.ts.map +1 -1
- package/dist/src/log/log.js +34 -7
- package/dist/src/log/log.js.map +1 -1
- package/dist/src/log/struct.d.ts +5 -4
- package/dist/src/log/struct.d.ts.map +1 -1
- package/dist/src/log/struct.js.map +1 -1
- package/dist/src/matchmaking/wire.d.ts +2 -1
- package/dist/src/matchmaking/wire.d.ts.map +1 -1
- package/dist/src/matchmaking/wire.js +13 -92
- package/dist/src/matchmaking/wire.js.map +1 -1
- package/dist/src/network/i-key-network.d.ts +9 -0
- package/dist/src/network/i-key-network.d.ts.map +1 -1
- package/dist/src/network/i-repo.d.ts +8 -0
- package/dist/src/network/i-repo.d.ts.map +1 -1
- package/dist/src/network/struct.d.ts +10 -0
- package/dist/src/network/struct.d.ts.map +1 -1
- package/dist/src/reactivity/push-state.d.ts.map +1 -1
- package/dist/src/reactivity/push-state.js +4 -33
- package/dist/src/reactivity/push-state.js.map +1 -1
- package/dist/src/reactivity/wire-validate.d.ts +7 -22
- package/dist/src/reactivity/wire-validate.d.ts.map +1 -1
- package/dist/src/reactivity/wire-validate.js +7 -73
- package/dist/src/reactivity/wire-validate.js.map +1 -1
- package/dist/src/reactivity/wire.js +3 -3
- package/dist/src/reactivity/wire.js.map +1 -1
- package/dist/src/testing/async-wait.d.ts +23 -0
- package/dist/src/testing/async-wait.d.ts.map +1 -0
- package/dist/src/testing/async-wait.js +42 -0
- package/dist/src/testing/async-wait.js.map +1 -0
- package/dist/src/testing/index.d.ts +3 -0
- package/dist/src/testing/index.d.ts.map +1 -0
- package/dist/src/testing/index.js +3 -0
- package/dist/src/testing/index.js.map +1 -0
- package/dist/src/testing/test-transactor.d.ts +44 -0
- package/dist/src/testing/test-transactor.d.ts.map +1 -0
- package/dist/src/testing/test-transactor.js +423 -0
- package/dist/src/testing/test-transactor.js.map +1 -0
- package/dist/src/transaction/actions-engine.d.ts +6 -3
- package/dist/src/transaction/actions-engine.d.ts.map +1 -1
- package/dist/src/transaction/actions-engine.js +8 -7
- package/dist/src/transaction/actions-engine.js.map +1 -1
- package/dist/src/transaction/coordinator.d.ts +43 -22
- package/dist/src/transaction/coordinator.d.ts.map +1 -1
- package/dist/src/transaction/coordinator.js +443 -178
- package/dist/src/transaction/coordinator.js.map +1 -1
- package/dist/src/transaction/errors.d.ts +82 -0
- package/dist/src/transaction/errors.d.ts.map +1 -0
- package/dist/src/transaction/errors.js +93 -0
- package/dist/src/transaction/errors.js.map +1 -0
- package/dist/src/transaction/index.d.ts +6 -3
- package/dist/src/transaction/index.d.ts.map +1 -1
- package/dist/src/transaction/index.js +4 -2
- package/dist/src/transaction/index.js.map +1 -1
- package/dist/src/transaction/operations-hash.d.ts +119 -0
- package/dist/src/transaction/operations-hash.d.ts.map +1 -0
- package/dist/src/transaction/operations-hash.js +180 -0
- package/dist/src/transaction/operations-hash.js.map +1 -0
- package/dist/src/transaction/read-dependency-collector.d.ts +43 -0
- package/dist/src/transaction/read-dependency-collector.d.ts.map +1 -0
- package/dist/src/transaction/read-dependency-collector.js +69 -0
- package/dist/src/transaction/read-dependency-collector.js.map +1 -0
- package/dist/src/transaction/session.d.ts +12 -3
- package/dist/src/transaction/session.d.ts.map +1 -1
- package/dist/src/transaction/session.js +35 -9
- package/dist/src/transaction/session.js.map +1 -1
- package/dist/src/transaction/transaction.d.ts +162 -1
- package/dist/src/transaction/transaction.d.ts.map +1 -1
- package/dist/src/transaction/transaction.js +85 -3
- package/dist/src/transaction/transaction.js.map +1 -1
- package/dist/src/transaction/validator.d.ts +3 -11
- package/dist/src/transaction/validator.d.ts.map +1 -1
- package/dist/src/transaction/validator.js +69 -28
- package/dist/src/transaction/validator.js.map +1 -1
- package/dist/src/transactor/network-transactor.d.ts +35 -0
- package/dist/src/transactor/network-transactor.d.ts.map +1 -1
- package/dist/src/transactor/network-transactor.js +146 -40
- package/dist/src/transactor/network-transactor.js.map +1 -1
- package/dist/src/transactor/transactor-source.d.ts +19 -5
- package/dist/src/transactor/transactor-source.d.ts.map +1 -1
- package/dist/src/transactor/transactor-source.js +43 -11
- package/dist/src/transactor/transactor-source.js.map +1 -1
- package/dist/src/transform/atomic-proxy.d.ts +35 -5
- package/dist/src/transform/atomic-proxy.d.ts.map +1 -1
- package/dist/src/transform/atomic-proxy.js +39 -7
- package/dist/src/transform/atomic-proxy.js.map +1 -1
- package/dist/src/transform/cache-source.d.ts +36 -5
- package/dist/src/transform/cache-source.d.ts.map +1 -1
- package/dist/src/transform/cache-source.js +84 -8
- package/dist/src/transform/cache-source.js.map +1 -1
- package/dist/src/transform/helpers.d.ts.map +1 -1
- package/dist/src/transform/helpers.js +8 -2
- package/dist/src/transform/helpers.js.map +1 -1
- package/dist/src/transform/tracker.d.ts +17 -2
- package/dist/src/transform/tracker.d.ts.map +1 -1
- package/dist/src/transform/tracker.js +66 -11
- package/dist/src/transform/tracker.js.map +1 -1
- package/dist/src/utility/backoff.d.ts +48 -0
- package/dist/src/utility/backoff.d.ts.map +1 -0
- package/dist/src/utility/backoff.js +60 -0
- package/dist/src/utility/backoff.js.map +1 -0
- package/dist/src/utility/pending.d.ts +1 -0
- package/dist/src/utility/pending.d.ts.map +1 -1
- package/dist/src/utility/pending.js +3 -1
- package/dist/src/utility/pending.js.map +1 -1
- package/package.json +4 -4
- package/src/blocks/block-store.ts +19 -1
- package/src/blocks/helpers.ts +3 -3
- package/src/btree/btree.ts +108 -63
- package/src/chain/chain.ts +11 -1
- package/src/cluster/index.ts +1 -0
- package/src/cluster/membership.ts +85 -0
- package/src/cluster/structs.ts +51 -1
- package/src/cohort-topic/addressing.ts +12 -4
- package/src/cohort-topic/antidos/bootstrap-evidence.ts +21 -9
- package/src/cohort-topic/antidos/replay-guard.ts +37 -0
- package/src/cohort-topic/antidos/topic-budget.ts +17 -2
- package/src/cohort-topic/coldstart.ts +48 -15
- package/src/cohort-topic/gossip/bus.ts +39 -2
- package/src/cohort-topic/gossip/view.ts +26 -1
- package/src/cohort-topic/member-engine.ts +36 -15
- package/src/cohort-topic/membership/publisher.ts +25 -33
- package/src/cohort-topic/membership/verifier.ts +119 -3
- package/src/cohort-topic/promotion.ts +23 -2
- package/src/cohort-topic/registration/renewal.ts +73 -1
- package/src/cohort-topic/registration/types.ts +15 -0
- package/src/cohort-topic/service.ts +41 -15
- package/src/cohort-topic/sig/payloads.ts +29 -9
- package/src/cohort-topic/traffic.ts +23 -1
- package/src/cohort-topic/walk.ts +95 -24
- package/src/cohort-topic/wire/codec.ts +12 -0
- package/src/cohort-topic/wire/index.ts +2 -0
- package/src/cohort-topic/wire/payloads.ts +14 -5
- package/src/cohort-topic/wire/primitives.ts +188 -0
- package/src/cohort-topic/wire/types.ts +136 -4
- package/src/cohort-topic/wire/validate.ts +168 -134
- package/src/collection/collection.ts +123 -40
- package/src/collection/struct.ts +44 -2
- package/src/collections/tree/tree.ts +42 -5
- package/src/index.ts +1 -0
- package/src/log/log.ts +37 -9
- package/src/log/struct.ts +5 -4
- package/src/matchmaking/wire.ts +25 -102
- package/src/network/i-key-network.ts +10 -0
- package/src/network/i-repo.ts +8 -0
- package/src/network/struct.ts +10 -0
- package/src/reactivity/push-state.ts +4 -37
- package/src/reactivity/wire-validate.ts +7 -81
- package/src/reactivity/wire.ts +3 -3
- package/src/testing/async-wait.ts +65 -0
- package/src/testing/index.ts +2 -0
- package/src/testing/test-transactor.ts +468 -0
- package/src/transaction/actions-engine.ts +8 -6
- package/src/transaction/coordinator.ts +498 -229
- package/src/transaction/errors.ts +91 -0
- package/src/transaction/index.ts +23 -3
- package/src/transaction/operations-hash.ts +196 -0
- package/src/transaction/read-dependency-collector.ts +78 -0
- package/src/transaction/session.ts +37 -9
- package/src/transaction/transaction.ts +211 -4
- package/src/transaction/validator.ts +73 -48
- package/src/transactor/network-transactor.ts +164 -49
- package/src/transactor/transactor-source.ts +47 -12
- package/src/transform/atomic-proxy.ts +92 -49
- package/src/transform/cache-source.ts +87 -9
- package/src/transform/helpers.ts +8 -2
- package/src/transform/tracker.ts +69 -11
- package/src/utility/backoff.ts +95 -0
- package/src/utility/pending.ts +3 -1
- package/src/transaction/context.ts +0 -103
package/src/matchmaking/wire.ts
CHANGED
|
@@ -29,7 +29,19 @@ import {
|
|
|
29
29
|
encodeCohortMessage,
|
|
30
30
|
DEFAULT_MAX_MESSAGE_BYTES,
|
|
31
31
|
} from "../cohort-topic/wire/codec.js";
|
|
32
|
-
import {
|
|
32
|
+
import {
|
|
33
|
+
asObject,
|
|
34
|
+
b64urlField,
|
|
35
|
+
failWire as fail,
|
|
36
|
+
optBool,
|
|
37
|
+
optFiniteNumber,
|
|
38
|
+
reqBool,
|
|
39
|
+
reqFiniteNumber,
|
|
40
|
+
reqIntInRange,
|
|
41
|
+
reqString,
|
|
42
|
+
reqStringArray,
|
|
43
|
+
requireV1,
|
|
44
|
+
} from "../cohort-topic/wire/primitives.js";
|
|
33
45
|
import type { TopicTrafficV1 } from "../cohort-topic/wire/types.js";
|
|
34
46
|
import { QUERY_LIMIT_MAX } from "./config.js";
|
|
35
47
|
|
|
@@ -167,7 +179,7 @@ export interface AggregateBucketV1 {
|
|
|
167
179
|
count: number;
|
|
168
180
|
}
|
|
169
181
|
|
|
170
|
-
// ---
|
|
182
|
+
// --- matchmaking-local wire state (generic validation primitives live in cohort-topic/wire/primitives.js) ---
|
|
171
183
|
|
|
172
184
|
const utf8Encoder = new TextEncoder();
|
|
173
185
|
const utf8Decoder = new TextDecoder("utf-8", { fatal: true });
|
|
@@ -175,96 +187,6 @@ const utf8Decoder = new TextDecoder("utf-8", { fatal: true });
|
|
|
175
187
|
/** Ceiling for an opaque app payload (provider/seeker), guarding decode allocation. */
|
|
176
188
|
export const DEFAULT_MAX_APP_PAYLOAD_BYTES = 64 * 1024;
|
|
177
189
|
|
|
178
|
-
function fail(message: string): never {
|
|
179
|
-
throw new CohortWireError(message);
|
|
180
|
-
}
|
|
181
|
-
|
|
182
|
-
function asObject(value: unknown, what: string): Record<string, unknown> {
|
|
183
|
-
if (typeof value !== "object" || value === null || Array.isArray(value)) {
|
|
184
|
-
fail(`${what}: expected an object`);
|
|
185
|
-
}
|
|
186
|
-
return value as Record<string, unknown>;
|
|
187
|
-
}
|
|
188
|
-
|
|
189
|
-
function requireV1(obj: Record<string, unknown>, what: string): void {
|
|
190
|
-
if (obj["v"] !== 1) {
|
|
191
|
-
fail(`${what}: expected v === 1, got ${JSON.stringify(obj["v"])}`);
|
|
192
|
-
}
|
|
193
|
-
}
|
|
194
|
-
|
|
195
|
-
function reqString(obj: Record<string, unknown>, key: string, what: string): string {
|
|
196
|
-
const value = obj[key];
|
|
197
|
-
if (typeof value !== "string") {
|
|
198
|
-
fail(`${what}: field "${key}" must be a string`);
|
|
199
|
-
}
|
|
200
|
-
return value;
|
|
201
|
-
}
|
|
202
|
-
|
|
203
|
-
function reqBool(obj: Record<string, unknown>, key: string, what: string): boolean {
|
|
204
|
-
const value = obj[key];
|
|
205
|
-
if (typeof value !== "boolean") {
|
|
206
|
-
fail(`${what}: field "${key}" must be a boolean`);
|
|
207
|
-
}
|
|
208
|
-
return value;
|
|
209
|
-
}
|
|
210
|
-
|
|
211
|
-
function optBool(obj: Record<string, unknown>, key: string, what: string): boolean | undefined {
|
|
212
|
-
const value = obj[key];
|
|
213
|
-
if (value === undefined) {
|
|
214
|
-
return undefined;
|
|
215
|
-
}
|
|
216
|
-
if (typeof value !== "boolean") {
|
|
217
|
-
fail(`${what}: field "${key}" must be a boolean when present`);
|
|
218
|
-
}
|
|
219
|
-
return value;
|
|
220
|
-
}
|
|
221
|
-
|
|
222
|
-
function reqFiniteNumber(obj: Record<string, unknown>, key: string, what: string): number {
|
|
223
|
-
const value = obj[key];
|
|
224
|
-
if (typeof value !== "number" || !Number.isFinite(value)) {
|
|
225
|
-
fail(`${what}: field "${key}" must be a finite number`);
|
|
226
|
-
}
|
|
227
|
-
return value;
|
|
228
|
-
}
|
|
229
|
-
|
|
230
|
-
function optFiniteNumber(obj: Record<string, unknown>, key: string, what: string): number | undefined {
|
|
231
|
-
const value = obj[key];
|
|
232
|
-
if (value === undefined) {
|
|
233
|
-
return undefined;
|
|
234
|
-
}
|
|
235
|
-
if (typeof value !== "number" || !Number.isFinite(value)) {
|
|
236
|
-
fail(`${what}: field "${key}" must be a finite number when present`);
|
|
237
|
-
}
|
|
238
|
-
return value;
|
|
239
|
-
}
|
|
240
|
-
|
|
241
|
-
function reqStringArray(obj: Record<string, unknown>, key: string, what: string): string[] {
|
|
242
|
-
const value = obj[key];
|
|
243
|
-
if (!Array.isArray(value) || value.some((entry) => typeof entry !== "string")) {
|
|
244
|
-
fail(`${what}: field "${key}" must be an array of strings`);
|
|
245
|
-
}
|
|
246
|
-
return value as string[];
|
|
247
|
-
}
|
|
248
|
-
|
|
249
|
-
/** Assert a base64url string decodes cleanly; returns it unchanged. */
|
|
250
|
-
function b64urlField(value: string, key: string, what: string): string {
|
|
251
|
-
try {
|
|
252
|
-
b64urlToBytes(value);
|
|
253
|
-
} catch {
|
|
254
|
-
fail(`${what}: field "${key}" is not valid base64url`);
|
|
255
|
-
}
|
|
256
|
-
return value;
|
|
257
|
-
}
|
|
258
|
-
|
|
259
|
-
/** Require an integer `>= min` (and `<= max` when supplied). */
|
|
260
|
-
function reqIntInRange(value: number, key: string, what: string, min: number, max?: number): number {
|
|
261
|
-
if (!Number.isInteger(value) || value < min || (max !== undefined && value > max)) {
|
|
262
|
-
const bound = max === undefined ? `>= ${min}` : `in ${min}..${max}`;
|
|
263
|
-
fail(`${what}: field "${key}" must be an integer ${bound}, got ${value}`);
|
|
264
|
-
}
|
|
265
|
-
return value;
|
|
266
|
-
}
|
|
267
|
-
|
|
268
190
|
function validateCapabilityFilter(value: unknown, what: string): CapabilityFilter {
|
|
269
191
|
const obj = asObject(value, what);
|
|
270
192
|
const out: CapabilityFilter = {
|
|
@@ -273,7 +195,7 @@ function validateCapabilityFilter(value: unknown, what: string): CapabilityFilte
|
|
|
273
195
|
};
|
|
274
196
|
const minBudget = optFiniteNumber(obj, "minBudget", what);
|
|
275
197
|
if (minBudget !== undefined) {
|
|
276
|
-
out.minBudget = reqIntInRange(
|
|
198
|
+
out.minBudget = reqIntInRange(obj, "minBudget", what, 0);
|
|
277
199
|
}
|
|
278
200
|
return out;
|
|
279
201
|
}
|
|
@@ -290,7 +212,7 @@ export function validateProviderAppPayloadV1(value: unknown): ProviderAppPayload
|
|
|
290
212
|
const out: ProviderAppPayloadV1 = {
|
|
291
213
|
kind: "match-provider",
|
|
292
214
|
capabilities: reqStringArray(obj, "capabilities", what),
|
|
293
|
-
capacityBudget: reqIntInRange(
|
|
215
|
+
capacityBudget: reqIntInRange(obj, "capacityBudget", what, 0),
|
|
294
216
|
contactHint: reqString(obj, "contactHint", what),
|
|
295
217
|
signature: b64urlField(reqString(obj, "signature", what), "signature", what),
|
|
296
218
|
};
|
|
@@ -310,7 +232,7 @@ export function validateSeekerAppPayloadV1(value: unknown): SeekerAppPayloadV1 {
|
|
|
310
232
|
}
|
|
311
233
|
const out: SeekerAppPayloadV1 = {
|
|
312
234
|
kind: "match-seeker",
|
|
313
|
-
wantCount: reqIntInRange(
|
|
235
|
+
wantCount: reqIntInRange(obj, "wantCount", what, 1),
|
|
314
236
|
contactHint: reqString(obj, "contactHint", what),
|
|
315
237
|
signature: b64urlField(reqString(obj, "signature", what), "signature", what),
|
|
316
238
|
};
|
|
@@ -350,7 +272,8 @@ export type MatchAppPayloadV1 = ProviderAppPayloadV1 | SeekerAppPayloadV1;
|
|
|
350
272
|
/**
|
|
351
273
|
* Decode an opaque `RegisterV1.appPayload` to whichever matchmaking payload it carries, dispatched on
|
|
352
274
|
* `kind` (parsed once — no exception-as-control-flow). The cohort query handler uses this to classify a
|
|
353
|
-
* registration record into a provider vs. seeker entry. Throws
|
|
275
|
+
* registration record into a provider vs. seeker entry. Throws
|
|
276
|
+
* {@link import("../cohort-topic/wire/primitives.js").CohortWireError} on an unknown
|
|
354
277
|
* `kind` or a malformed payload.
|
|
355
278
|
*/
|
|
356
279
|
export function decodeMatchAppPayload(bytes: Uint8Array, maxBytes: number = DEFAULT_MAX_APP_PAYLOAD_BYTES): MatchAppPayloadV1 {
|
|
@@ -395,7 +318,7 @@ export function validateQueryV1(value: unknown): QueryV1 {
|
|
|
395
318
|
topicId: b64urlField(reqString(obj, "topicId", what), "topicId", what),
|
|
396
319
|
includeProviders: reqBool(obj, "includeProviders", what),
|
|
397
320
|
includeSeekers: reqBool(obj, "includeSeekers", what),
|
|
398
|
-
limit: reqIntInRange(
|
|
321
|
+
limit: reqIntInRange(obj, "limit", what, 1, QUERY_LIMIT_MAX),
|
|
399
322
|
requesterId: reqString(obj, "requesterId", what),
|
|
400
323
|
timestamp: reqFiniteNumber(obj, "timestamp", what),
|
|
401
324
|
signature: b64urlField(reqString(obj, "signature", what), "signature", what),
|
|
@@ -423,7 +346,7 @@ function validateProviderEntryV1(value: unknown): ProviderEntryV1 {
|
|
|
423
346
|
return {
|
|
424
347
|
participantId: reqString(obj, "participantId", what),
|
|
425
348
|
capabilities: reqStringArray(obj, "capabilities", what),
|
|
426
|
-
capacityBudget: reqIntInRange(
|
|
349
|
+
capacityBudget: reqIntInRange(obj, "capacityBudget", what, 0),
|
|
427
350
|
contactHint: reqString(obj, "contactHint", what),
|
|
428
351
|
attachedAt: reqFiniteNumber(obj, "attachedAt", what),
|
|
429
352
|
registrationSig: b64urlField(reqString(obj, "registrationSig", what), "registrationSig", what),
|
|
@@ -435,7 +358,7 @@ function validateSeekerEntryV1(value: unknown): SeekerEntryV1 {
|
|
|
435
358
|
const obj = asObject(value, what);
|
|
436
359
|
return {
|
|
437
360
|
participantId: reqString(obj, "participantId", what),
|
|
438
|
-
wantCount: reqIntInRange(
|
|
361
|
+
wantCount: reqIntInRange(obj, "wantCount", what, 1),
|
|
439
362
|
contactHint: reqString(obj, "contactHint", what),
|
|
440
363
|
attachedAt: reqFiniteNumber(obj, "attachedAt", what),
|
|
441
364
|
registrationSig: b64urlField(reqString(obj, "registrationSig", what), "registrationSig", what),
|
|
@@ -473,9 +396,9 @@ function validateAggregateBucketV1(value: unknown): AggregateBucketV1 {
|
|
|
473
396
|
const what = "AggregateBucketV1";
|
|
474
397
|
const obj = asObject(value, what);
|
|
475
398
|
return {
|
|
476
|
-
targetTier: reqIntInRange(
|
|
477
|
-
prefixSlot: reqIntInRange(
|
|
478
|
-
count: reqIntInRange(
|
|
399
|
+
targetTier: reqIntInRange(obj, "targetTier", what, 0),
|
|
400
|
+
prefixSlot: reqIntInRange(obj, "prefixSlot", what, 0),
|
|
401
|
+
count: reqIntInRange(obj, "count", what, 0),
|
|
479
402
|
};
|
|
480
403
|
}
|
|
481
404
|
|
|
@@ -21,4 +21,14 @@ export type IKeyNetwork = {
|
|
|
21
21
|
* @returns Promise resolving to the peers in the cluster
|
|
22
22
|
*/
|
|
23
23
|
findCluster(key: Uint8Array): Promise<ClusterPeers>;
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Optionally cache a resolved coordinator for a key, so a follow-up operation
|
|
27
|
+
* (e.g. commit after pend) reuses the same peer. Implementations that don't
|
|
28
|
+
* cache coordinators simply omit this. `ttlMs` bounds how long the hint lives.
|
|
29
|
+
* @param key The key the coordinator was resolved for
|
|
30
|
+
* @param peerId The coordinating peer
|
|
31
|
+
* @param ttlMs Optional time-to-live for the cached hint, in ms
|
|
32
|
+
*/
|
|
33
|
+
recordCoordinator?(key: Uint8Array, peerId: PeerId, ttlMs?: number): void;
|
|
24
34
|
}
|
package/src/network/i-repo.ts
CHANGED
|
@@ -13,6 +13,14 @@ export type MessageOptions = {
|
|
|
13
13
|
* impose a separate dial cap; the overall budget is the cap".
|
|
14
14
|
*/
|
|
15
15
|
dialTimeoutMs?: number;
|
|
16
|
+
/**
|
|
17
|
+
* Blocks this coordinator is responsible for driving through consensus. Threaded
|
|
18
|
+
* from {@link NetworkTransactor.pend}'s batch (which consolidates several blocks
|
|
19
|
+
* onto one coordinating peer) into {@link CoordinatorRepo.pend}, which uses it to
|
|
20
|
+
* pick the cluster anchor. Absent on bare per-block paths, which fall back to the
|
|
21
|
+
* transforms' own block ids.
|
|
22
|
+
*/
|
|
23
|
+
coordinatingBlockIds?: BlockId[];
|
|
16
24
|
}
|
|
17
25
|
|
|
18
26
|
export type RepoCommitRequest = {
|
package/src/network/struct.ts
CHANGED
|
@@ -39,6 +39,16 @@ export type PendRequest = ActionTransforms & {
|
|
|
39
39
|
operationsHash?: string;
|
|
40
40
|
/** For multi-collection transactions: supercluster nominees for consensus */
|
|
41
41
|
superclusterNominees?: PeerId[];
|
|
42
|
+
/**
|
|
43
|
+
* Aged, advisory retry priority for the *single-collection* pend path (default 0 when absent).
|
|
44
|
+
* The multi-collection path instead carries priority on {@link PendRequest.transaction}
|
|
45
|
+
* ({@link Transaction.priority}); this top-level field is the carrier for a `Collection.sync`
|
|
46
|
+
* pend, which has no `transaction`. A cluster member reads whichever is present as the first
|
|
47
|
+
* `resolveRace` tiebreak. FAIRNESS-ONLY: it rides inside the signed cluster `message` (so it is
|
|
48
|
+
* integrity-protected in transit) but MUST NOT affect the operations hash, stale-read checks, or
|
|
49
|
+
* validity — a stale pend is still rejected regardless of priority.
|
|
50
|
+
*/
|
|
51
|
+
priority?: number;
|
|
42
52
|
};
|
|
43
53
|
|
|
44
54
|
export type BlockActionStatus = ActionBlocks & {
|
|
@@ -18,7 +18,7 @@
|
|
|
18
18
|
*/
|
|
19
19
|
|
|
20
20
|
import { bytesToB64url, b64urlToBytes, decodeCohortMessage, encodeCohortMessage, DEFAULT_MAX_MESSAGE_BYTES } from "../cohort-topic/wire/codec.js";
|
|
21
|
-
import {
|
|
21
|
+
import { asObject, b64urlField, failWire as fail, reqFiniteNumber, reqString } from "../cohort-topic/wire/primitives.js";
|
|
22
22
|
import { DEDUPE_WINDOW_DEFAULT, QUEUE_MAX_DEFAULT, W_DEFAULT } from "./config.js";
|
|
23
23
|
import { createSubscriberBackpressure, type SubscriberBackpressure, type SubscriberEnqueueResult } from "./backpressure.js";
|
|
24
24
|
import { RollingCheckpoint, type CheckpointSummary, type DeltaCoalesce, type DigestFold } from "./checkpoint.js";
|
|
@@ -204,33 +204,9 @@ export class PushState {
|
|
|
204
204
|
}
|
|
205
205
|
|
|
206
206
|
// --- gossip codec (length-framed JSON, mirroring cohort-topic conventions) ---
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
}
|
|
211
|
-
|
|
212
|
-
function asObject(value: unknown, what: string): Record<string, unknown> {
|
|
213
|
-
if (typeof value !== "object" || value === null || Array.isArray(value)) {
|
|
214
|
-
fail(`${what}: expected an object`);
|
|
215
|
-
}
|
|
216
|
-
return value as Record<string, unknown>;
|
|
217
|
-
}
|
|
218
|
-
|
|
219
|
-
function reqString(obj: Record<string, unknown>, key: string, what: string): string {
|
|
220
|
-
const value = obj[key];
|
|
221
|
-
if (typeof value !== "string") {
|
|
222
|
-
fail(`${what}: field "${key}" must be a string`);
|
|
223
|
-
}
|
|
224
|
-
return value;
|
|
225
|
-
}
|
|
226
|
-
|
|
227
|
-
function reqFiniteNumber(obj: Record<string, unknown>, key: string, what: string): number {
|
|
228
|
-
const value = obj[key];
|
|
229
|
-
if (typeof value !== "number" || !Number.isFinite(value)) {
|
|
230
|
-
fail(`${what}: field "${key}" must be a finite number`);
|
|
231
|
-
}
|
|
232
|
-
return value;
|
|
233
|
-
}
|
|
207
|
+
// Generic structural primitives (fail/asObject/reqString/reqFiniteNumber/b64urlField) are imported from
|
|
208
|
+
// cohort-topic/wire/primitives.js; only `reqInt` stays local — it is a range-free integer check (no
|
|
209
|
+
// lower bound), unlike the shared `reqIntInRange`, so folding it in would change behavior.
|
|
234
210
|
|
|
235
211
|
function reqInt(value: number, key: string, what: string): number {
|
|
236
212
|
if (!Number.isInteger(value)) {
|
|
@@ -239,15 +215,6 @@ function reqInt(value: number, key: string, what: string): number {
|
|
|
239
215
|
return value;
|
|
240
216
|
}
|
|
241
217
|
|
|
242
|
-
function b64urlField(value: string, key: string, what: string): string {
|
|
243
|
-
try {
|
|
244
|
-
b64urlToBytes(value);
|
|
245
|
-
} catch {
|
|
246
|
-
fail(`${what}: field "${key}" is not valid base64url`);
|
|
247
|
-
}
|
|
248
|
-
return value;
|
|
249
|
-
}
|
|
250
|
-
|
|
251
218
|
function validateCohortRef(value: unknown, what: string): CohortRef {
|
|
252
219
|
const obj = asObject(value, what);
|
|
253
220
|
const coord = b64urlField(reqString(obj, "coord", what), "coord", what);
|
|
@@ -2,86 +2,12 @@
|
|
|
2
2
|
* Reactivity — shared wire-validation primitives.
|
|
3
3
|
*
|
|
4
4
|
* The reactivity message codecs ({@link import("./wire.js")}, {@link import("./push-state.js")},
|
|
5
|
-
* {@link import("./backfill.js")}, {@link import("./resume.js")}
|
|
6
|
-
* validated V1 shapes with the same structural checks
|
|
7
|
-
*
|
|
8
|
-
*
|
|
5
|
+
* {@link import("./backfill.js")}, {@link import("./resume.js")}, plus checkpoint / rotation / recover)
|
|
6
|
+
* decode untrusted JSON into validated V1 shapes with the same structural checks the rest of the
|
|
7
|
+
* substrate uses. Those generic primitives now live in one place —
|
|
8
|
+
* {@link import("../cohort-topic/wire/primitives.js")} — so a hardening tweak lands once; this module is
|
|
9
|
+
* a thin re-export kept so the reactivity codecs import from a sibling path. Every helper throws
|
|
10
|
+
* {@link CohortWireError} on a defect (base64url byte fields, finite/integer numbers, `v: 1`).
|
|
9
11
|
*/
|
|
10
12
|
|
|
11
|
-
|
|
12
|
-
import { CohortWireError } from "../cohort-topic/wire/validate.js";
|
|
13
|
-
|
|
14
|
-
/** Throw a {@link CohortWireError} with `message`. */
|
|
15
|
-
export function failWire(message: string): never {
|
|
16
|
-
throw new CohortWireError(message);
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
/** Narrow `value` to a plain object (not null, not an array), or throw. */
|
|
20
|
-
export function asObject(value: unknown, what: string): Record<string, unknown> {
|
|
21
|
-
if (typeof value !== "object" || value === null || Array.isArray(value)) {
|
|
22
|
-
failWire(`${what}: expected an object`);
|
|
23
|
-
}
|
|
24
|
-
return value as Record<string, unknown>;
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
/** Require `obj.v === 1`. */
|
|
28
|
-
export function requireV1(obj: Record<string, unknown>, what: string): void {
|
|
29
|
-
if (obj["v"] !== 1) {
|
|
30
|
-
failWire(`${what}: expected v === 1, got ${JSON.stringify(obj["v"])}`);
|
|
31
|
-
}
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
/** Require `obj[key]` to be a string; returns it. */
|
|
35
|
-
export function reqString(obj: Record<string, unknown>, key: string, what: string): string {
|
|
36
|
-
const value = obj[key];
|
|
37
|
-
if (typeof value !== "string") {
|
|
38
|
-
failWire(`${what}: field "${key}" must be a string`);
|
|
39
|
-
}
|
|
40
|
-
return value;
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
/** Require `obj[key]` to be a boolean; returns it. */
|
|
44
|
-
export function reqBoolean(obj: Record<string, unknown>, key: string, what: string): boolean {
|
|
45
|
-
const value = obj[key];
|
|
46
|
-
if (typeof value !== "boolean") {
|
|
47
|
-
failWire(`${what}: field "${key}" must be a boolean`);
|
|
48
|
-
}
|
|
49
|
-
return value;
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
/** Require `obj[key]` to be a finite number; returns it. */
|
|
53
|
-
export function reqFiniteNumber(obj: Record<string, unknown>, key: string, what: string): number {
|
|
54
|
-
const value = obj[key];
|
|
55
|
-
if (typeof value !== "number" || !Number.isFinite(value)) {
|
|
56
|
-
failWire(`${what}: field "${key}" must be a finite number`);
|
|
57
|
-
}
|
|
58
|
-
return value;
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
/** Require `obj[key]` to be an integer `>= min` (and `<= max` when supplied); returns it. */
|
|
62
|
-
export function reqIntInRange(obj: Record<string, unknown>, key: string, what: string, min: number, max?: number): number {
|
|
63
|
-
const value = reqFiniteNumber(obj, key, what);
|
|
64
|
-
if (!Number.isInteger(value) || value < min || (max !== undefined && value > max)) {
|
|
65
|
-
const bound = max === undefined ? `>= ${min}` : `in ${min}..${max}`;
|
|
66
|
-
failWire(`${what}: field "${key}" must be an integer ${bound}, got ${value}`);
|
|
67
|
-
}
|
|
68
|
-
return value;
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
/** Assert a base64url string decodes cleanly; returns it unchanged. */
|
|
72
|
-
export function b64urlField(value: string, key: string, what: string): string {
|
|
73
|
-
try {
|
|
74
|
-
b64urlToBytes(value);
|
|
75
|
-
} catch {
|
|
76
|
-
failWire(`${what}: field "${key}" is not valid base64url`);
|
|
77
|
-
}
|
|
78
|
-
return value;
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
/** Require `value` to be an array of strings; returns it (callers map each element through {@link b64urlField}). */
|
|
82
|
-
export function reqStringArray(value: unknown, key: string, what: string): string[] {
|
|
83
|
-
if (!Array.isArray(value) || value.some((entry) => typeof entry !== "string")) {
|
|
84
|
-
failWire(`${what}: field "${key}" must be an array of strings`);
|
|
85
|
-
}
|
|
86
|
-
return value as string[];
|
|
87
|
-
}
|
|
13
|
+
export * from "../cohort-topic/wire/primitives.js";
|
package/src/reactivity/wire.ts
CHANGED
|
@@ -31,7 +31,7 @@ import {
|
|
|
31
31
|
asObject,
|
|
32
32
|
b64urlField,
|
|
33
33
|
failWire as fail,
|
|
34
|
-
|
|
34
|
+
reqBool,
|
|
35
35
|
reqFiniteNumber,
|
|
36
36
|
reqIntInRange,
|
|
37
37
|
reqString,
|
|
@@ -179,7 +179,7 @@ export function validateNotificationV1(value: unknown): NotificationV1 {
|
|
|
179
179
|
const what = "NotificationV1";
|
|
180
180
|
const obj = asObject(value, what);
|
|
181
181
|
requireV1(obj, what);
|
|
182
|
-
const signers = reqStringArray(obj
|
|
182
|
+
const signers = reqStringArray(obj, "signers", what);
|
|
183
183
|
const out: NotificationV1 = {
|
|
184
184
|
v: 1,
|
|
185
185
|
collectionId: b64urlField(reqString(obj, "collectionId", what), "collectionId", what),
|
|
@@ -197,7 +197,7 @@ export function validateNotificationV1(value: unknown): NotificationV1 {
|
|
|
197
197
|
out.rotationHint = validateRotationHint(obj["rotationHint"], `${what}.rotationHint`);
|
|
198
198
|
}
|
|
199
199
|
if (obj["invalidation"] !== undefined) {
|
|
200
|
-
out.invalidation =
|
|
200
|
+
out.invalidation = reqBool(obj, "invalidation", what);
|
|
201
201
|
}
|
|
202
202
|
if (obj["invalidatedActionId"] !== undefined) {
|
|
203
203
|
out.invalidatedActionId = reqString(obj, "invalidatedActionId", what);
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
export interface WaitForOptions {
|
|
2
|
+
// NOTE: default is tuned for in-process settles; integration callers over real libp2p
|
|
3
|
+
// pass explicit 10k–90k bounds. A new caller that omits timeoutMs gets 2s — fine for
|
|
4
|
+
// fast local conditions, but if a slow CI machine flakes on a legitimately-slow wait,
|
|
5
|
+
// give that call site an explicit timeoutMs rather than raising this default.
|
|
6
|
+
/** Upper bound before the poll gives up. Default 2_000. */
|
|
7
|
+
timeoutMs?: number;
|
|
8
|
+
/** Poll cadence. Default 10. */
|
|
9
|
+
intervalMs?: number;
|
|
10
|
+
/** Included in the thrown message so a failure says WHAT never became true. */
|
|
11
|
+
description?: string;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/** Real-time sleep. Retain ONLY for residual cases a condition poll cannot cover. */
|
|
15
|
+
export const delay = (ms: number): Promise<void> =>
|
|
16
|
+
new Promise((resolve) => setTimeout(resolve, ms));
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Poll `predicate` until it returns true or `timeoutMs` elapses; throws on timeout.
|
|
20
|
+
* `predicate` may be sync or async; a thrown/rejected predicate propagates immediately.
|
|
21
|
+
* `undefined` is treated as `false` (not ready yet).
|
|
22
|
+
*/
|
|
23
|
+
export async function waitFor(
|
|
24
|
+
predicate: () => boolean | Promise<boolean>,
|
|
25
|
+
opts?: WaitForOptions,
|
|
26
|
+
): Promise<void> {
|
|
27
|
+
const { timeoutMs = 2_000, intervalMs = 10, description } = opts ?? {};
|
|
28
|
+
const deadline = Date.now() + timeoutMs;
|
|
29
|
+
for (;;) {
|
|
30
|
+
if (await predicate()) return;
|
|
31
|
+
if (Date.now() >= deadline) {
|
|
32
|
+
throw new Error(
|
|
33
|
+
description
|
|
34
|
+
? `waitFor timed out after ${timeoutMs}ms: ${description}`
|
|
35
|
+
: `waitFor timed out after ${timeoutMs}ms`,
|
|
36
|
+
);
|
|
37
|
+
}
|
|
38
|
+
await delay(intervalMs);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Poll `fn` until it returns a non-undefined value or `timeoutMs` elapses; throws on timeout.
|
|
44
|
+
* `undefined` means "not ready yet" — callers needing to wait for an actual `undefined`
|
|
45
|
+
* should use `waitFor` on a separate flag.
|
|
46
|
+
*/
|
|
47
|
+
export async function waitForValue<T>(
|
|
48
|
+
fn: () => T | undefined | Promise<T | undefined>,
|
|
49
|
+
opts?: WaitForOptions,
|
|
50
|
+
): Promise<T> {
|
|
51
|
+
const { timeoutMs = 2_000, intervalMs = 10, description } = opts ?? {};
|
|
52
|
+
const deadline = Date.now() + timeoutMs;
|
|
53
|
+
for (;;) {
|
|
54
|
+
const value = await fn();
|
|
55
|
+
if (value !== undefined) return value;
|
|
56
|
+
if (Date.now() >= deadline) {
|
|
57
|
+
throw new Error(
|
|
58
|
+
description
|
|
59
|
+
? `waitForValue timed out after ${timeoutMs}ms: ${description}`
|
|
60
|
+
: `waitForValue timed out after ${timeoutMs}ms`,
|
|
61
|
+
);
|
|
62
|
+
}
|
|
63
|
+
await delay(intervalMs);
|
|
64
|
+
}
|
|
65
|
+
}
|