@optimystic/db-core 0.13.4 → 0.14.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 +29 -0
- package/dist/src/cluster/structs.d.ts.map +1 -1
- package/dist/src/cohort-topic/addressing.d.ts +53 -0
- package/dist/src/cohort-topic/addressing.d.ts.map +1 -0
- package/dist/src/cohort-topic/addressing.js +95 -0
- package/dist/src/cohort-topic/addressing.js.map +1 -0
- package/dist/src/cohort-topic/antidos/bootstrap-evidence-envelope.d.ts +105 -0
- package/dist/src/cohort-topic/antidos/bootstrap-evidence-envelope.d.ts.map +1 -0
- package/dist/src/cohort-topic/antidos/bootstrap-evidence-envelope.js +197 -0
- package/dist/src/cohort-topic/antidos/bootstrap-evidence-envelope.js.map +1 -0
- package/dist/src/cohort-topic/antidos/bootstrap-evidence.d.ts +50 -0
- package/dist/src/cohort-topic/antidos/bootstrap-evidence.d.ts.map +1 -0
- package/dist/src/cohort-topic/antidos/bootstrap-evidence.js +62 -0
- package/dist/src/cohort-topic/antidos/bootstrap-evidence.js.map +1 -0
- package/dist/src/cohort-topic/antidos/index.d.ts +6 -0
- package/dist/src/cohort-topic/antidos/index.d.ts.map +1 -0
- package/dist/src/cohort-topic/antidos/index.js +6 -0
- package/dist/src/cohort-topic/antidos/index.js.map +1 -0
- package/dist/src/cohort-topic/antidos/rate-limiter.d.ts +95 -0
- package/dist/src/cohort-topic/antidos/rate-limiter.d.ts.map +1 -0
- package/dist/src/cohort-topic/antidos/rate-limiter.js +146 -0
- package/dist/src/cohort-topic/antidos/rate-limiter.js.map +1 -0
- package/dist/src/cohort-topic/antidos/replay-guard.d.ts +37 -0
- package/dist/src/cohort-topic/antidos/replay-guard.d.ts.map +1 -0
- package/dist/src/cohort-topic/antidos/replay-guard.js +78 -0
- package/dist/src/cohort-topic/antidos/replay-guard.js.map +1 -0
- package/dist/src/cohort-topic/antidos/topic-budget.d.ts +50 -0
- package/dist/src/cohort-topic/antidos/topic-budget.d.ts.map +1 -0
- package/dist/src/cohort-topic/antidos/topic-budget.js +95 -0
- package/dist/src/cohort-topic/antidos/topic-budget.js.map +1 -0
- package/dist/src/cohort-topic/antiflood/index.d.ts +3 -0
- package/dist/src/cohort-topic/antiflood/index.d.ts.map +1 -0
- package/dist/src/cohort-topic/antiflood/index.js +3 -0
- package/dist/src/cohort-topic/antiflood/index.js.map +1 -0
- package/dist/src/cohort-topic/antiflood/invariants.d.ts +70 -0
- package/dist/src/cohort-topic/antiflood/invariants.d.ts.map +1 -0
- package/dist/src/cohort-topic/antiflood/invariants.js +86 -0
- package/dist/src/cohort-topic/antiflood/invariants.js.map +1 -0
- package/dist/src/cohort-topic/antiflood/jitter.d.ts +65 -0
- package/dist/src/cohort-topic/antiflood/jitter.d.ts.map +1 -0
- package/dist/src/cohort-topic/antiflood/jitter.js +75 -0
- package/dist/src/cohort-topic/antiflood/jitter.js.map +1 -0
- package/dist/src/cohort-topic/coldstart.d.ts +117 -0
- package/dist/src/cohort-topic/coldstart.d.ts.map +1 -0
- package/dist/src/cohort-topic/coldstart.js +127 -0
- package/dist/src/cohort-topic/coldstart.js.map +1 -0
- package/dist/src/cohort-topic/dmax.d.ts +44 -0
- package/dist/src/cohort-topic/dmax.d.ts.map +1 -0
- package/dist/src/cohort-topic/dmax.js +70 -0
- package/dist/src/cohort-topic/dmax.js.map +1 -0
- package/dist/src/cohort-topic/gossip/bus.d.ts +85 -0
- package/dist/src/cohort-topic/gossip/bus.d.ts.map +1 -0
- package/dist/src/cohort-topic/gossip/bus.js +146 -0
- package/dist/src/cohort-topic/gossip/bus.js.map +1 -0
- package/dist/src/cohort-topic/gossip/index.d.ts +4 -0
- package/dist/src/cohort-topic/gossip/index.d.ts.map +1 -0
- package/dist/src/cohort-topic/gossip/index.js +4 -0
- package/dist/src/cohort-topic/gossip/index.js.map +1 -0
- package/dist/src/cohort-topic/gossip/records.d.ts +12 -0
- package/dist/src/cohort-topic/gossip/records.d.ts.map +1 -0
- package/dist/src/cohort-topic/gossip/records.js +41 -0
- package/dist/src/cohort-topic/gossip/records.js.map +1 -0
- package/dist/src/cohort-topic/gossip/view.d.ts +39 -0
- package/dist/src/cohort-topic/gossip/view.d.ts.map +1 -0
- package/dist/src/cohort-topic/gossip/view.js +30 -0
- package/dist/src/cohort-topic/gossip/view.js.map +1 -0
- package/dist/src/cohort-topic/index.d.ts +21 -0
- package/dist/src/cohort-topic/index.d.ts.map +1 -0
- package/dist/src/cohort-topic/index.js +21 -0
- package/dist/src/cohort-topic/index.js.map +1 -0
- package/dist/src/cohort-topic/load/barometer.d.ts +78 -0
- package/dist/src/cohort-topic/load/barometer.d.ts.map +1 -0
- package/dist/src/cohort-topic/load/barometer.js +93 -0
- package/dist/src/cohort-topic/load/barometer.js.map +1 -0
- package/dist/src/cohort-topic/load/index.d.ts +2 -0
- package/dist/src/cohort-topic/load/index.d.ts.map +1 -0
- package/dist/src/cohort-topic/load/index.js +2 -0
- package/dist/src/cohort-topic/load/index.js.map +1 -0
- package/dist/src/cohort-topic/member-engine.d.ts +134 -0
- package/dist/src/cohort-topic/member-engine.d.ts.map +1 -0
- package/dist/src/cohort-topic/member-engine.js +264 -0
- package/dist/src/cohort-topic/member-engine.js.map +1 -0
- package/dist/src/cohort-topic/membership/index.d.ts +4 -0
- package/dist/src/cohort-topic/membership/index.d.ts.map +1 -0
- package/dist/src/cohort-topic/membership/index.js +4 -0
- package/dist/src/cohort-topic/membership/index.js.map +1 -0
- package/dist/src/cohort-topic/membership/publisher.d.ts +82 -0
- package/dist/src/cohort-topic/membership/publisher.d.ts.map +1 -0
- package/dist/src/cohort-topic/membership/publisher.js +107 -0
- package/dist/src/cohort-topic/membership/publisher.js.map +1 -0
- package/dist/src/cohort-topic/membership/source.d.ts +31 -0
- package/dist/src/cohort-topic/membership/source.d.ts.map +1 -0
- package/dist/src/cohort-topic/membership/source.js +32 -0
- package/dist/src/cohort-topic/membership/source.js.map +1 -0
- package/dist/src/cohort-topic/membership/verifier.d.ts +96 -0
- package/dist/src/cohort-topic/membership/verifier.d.ts.map +1 -0
- package/dist/src/cohort-topic/membership/verifier.js +250 -0
- package/dist/src/cohort-topic/membership/verifier.js.map +1 -0
- package/dist/src/cohort-topic/ports.d.ts +149 -0
- package/dist/src/cohort-topic/ports.d.ts.map +1 -0
- package/dist/src/cohort-topic/ports.js +25 -0
- package/dist/src/cohort-topic/ports.js.map +1 -0
- package/dist/src/cohort-topic/promotion.d.ts +135 -0
- package/dist/src/cohort-topic/promotion.d.ts.map +1 -0
- package/dist/src/cohort-topic/promotion.js +269 -0
- package/dist/src/cohort-topic/promotion.js.map +1 -0
- package/dist/src/cohort-topic/registration/bytes.d.ts +16 -0
- package/dist/src/cohort-topic/registration/bytes.d.ts.map +1 -0
- package/dist/src/cohort-topic/registration/bytes.js +32 -0
- package/dist/src/cohort-topic/registration/bytes.js.map +1 -0
- package/dist/src/cohort-topic/registration/handoff.d.ts +69 -0
- package/dist/src/cohort-topic/registration/handoff.d.ts.map +1 -0
- package/dist/src/cohort-topic/registration/handoff.js +98 -0
- package/dist/src/cohort-topic/registration/handoff.js.map +1 -0
- package/dist/src/cohort-topic/registration/index.d.ts +7 -0
- package/dist/src/cohort-topic/registration/index.d.ts.map +1 -0
- package/dist/src/cohort-topic/registration/index.js +7 -0
- package/dist/src/cohort-topic/registration/index.js.map +1 -0
- package/dist/src/cohort-topic/registration/renewal.d.ts +115 -0
- package/dist/src/cohort-topic/registration/renewal.d.ts.map +1 -0
- package/dist/src/cohort-topic/registration/renewal.js +306 -0
- package/dist/src/cohort-topic/registration/renewal.js.map +1 -0
- package/dist/src/cohort-topic/registration/sharding.d.ts +31 -0
- package/dist/src/cohort-topic/registration/sharding.d.ts.map +1 -0
- package/dist/src/cohort-topic/registration/sharding.js +48 -0
- package/dist/src/cohort-topic/registration/sharding.js.map +1 -0
- package/dist/src/cohort-topic/registration/store.d.ts +13 -0
- package/dist/src/cohort-topic/registration/store.d.ts.map +1 -0
- package/dist/src/cohort-topic/registration/store.js +72 -0
- package/dist/src/cohort-topic/registration/store.js.map +1 -0
- package/dist/src/cohort-topic/registration/types.d.ts +71 -0
- package/dist/src/cohort-topic/registration/types.d.ts.map +1 -0
- package/dist/src/cohort-topic/registration/types.js +27 -0
- package/dist/src/cohort-topic/registration/types.js.map +1 -0
- package/dist/src/cohort-topic/ring-hash.d.ts +27 -0
- package/dist/src/cohort-topic/ring-hash.d.ts.map +1 -0
- package/dist/src/cohort-topic/ring-hash.js +45 -0
- package/dist/src/cohort-topic/ring-hash.js.map +1 -0
- package/dist/src/cohort-topic/service.d.ts +157 -0
- package/dist/src/cohort-topic/service.d.ts.map +1 -0
- package/dist/src/cohort-topic/service.js +237 -0
- package/dist/src/cohort-topic/service.js.map +1 -0
- package/dist/src/cohort-topic/sig/index.d.ts +3 -0
- package/dist/src/cohort-topic/sig/index.d.ts.map +1 -0
- package/dist/src/cohort-topic/sig/index.js +3 -0
- package/dist/src/cohort-topic/sig/index.js.map +1 -0
- package/dist/src/cohort-topic/sig/payloads.d.ts +25 -0
- package/dist/src/cohort-topic/sig/payloads.d.ts.map +1 -0
- package/dist/src/cohort-topic/sig/payloads.js +25 -0
- package/dist/src/cohort-topic/sig/payloads.js.map +1 -0
- package/dist/src/cohort-topic/sig/threshold.d.ts +33 -0
- package/dist/src/cohort-topic/sig/threshold.d.ts.map +1 -0
- package/dist/src/cohort-topic/sig/threshold.js +51 -0
- package/dist/src/cohort-topic/sig/threshold.js.map +1 -0
- package/dist/src/cohort-topic/tiers.d.ts +58 -0
- package/dist/src/cohort-topic/tiers.d.ts.map +1 -0
- package/dist/src/cohort-topic/tiers.js +49 -0
- package/dist/src/cohort-topic/tiers.js.map +1 -0
- package/dist/src/cohort-topic/traffic.d.ts +90 -0
- package/dist/src/cohort-topic/traffic.d.ts.map +1 -0
- package/dist/src/cohort-topic/traffic.js +146 -0
- package/dist/src/cohort-topic/traffic.js.map +1 -0
- package/dist/src/cohort-topic/walk.d.ts +123 -0
- package/dist/src/cohort-topic/walk.d.ts.map +1 -0
- package/dist/src/cohort-topic/walk.js +150 -0
- package/dist/src/cohort-topic/walk.js.map +1 -0
- package/dist/src/cohort-topic/willingness.d.ts +123 -0
- package/dist/src/cohort-topic/willingness.d.ts.map +1 -0
- package/dist/src/cohort-topic/willingness.js +143 -0
- package/dist/src/cohort-topic/willingness.js.map +1 -0
- package/dist/src/cohort-topic/wire/codec.d.ts +42 -0
- package/dist/src/cohort-topic/wire/codec.d.ts.map +1 -0
- package/dist/src/cohort-topic/wire/codec.js +163 -0
- package/dist/src/cohort-topic/wire/codec.js.map +1 -0
- package/dist/src/cohort-topic/wire/index.d.ts +6 -0
- package/dist/src/cohort-topic/wire/index.d.ts.map +1 -0
- package/dist/src/cohort-topic/wire/index.js +6 -0
- package/dist/src/cohort-topic/wire/index.js.map +1 -0
- package/dist/src/cohort-topic/wire/payloads.d.ts +38 -0
- package/dist/src/cohort-topic/wire/payloads.d.ts.map +1 -0
- package/dist/src/cohort-topic/wire/payloads.js +102 -0
- package/dist/src/cohort-topic/wire/payloads.js.map +1 -0
- package/dist/src/cohort-topic/wire/types.d.ts +304 -0
- package/dist/src/cohort-topic/wire/types.d.ts.map +1 -0
- package/dist/src/cohort-topic/wire/types.js +16 -0
- package/dist/src/cohort-topic/wire/types.js.map +1 -0
- package/dist/src/cohort-topic/wire/validate.d.ts +25 -0
- package/dist/src/cohort-topic/wire/validate.d.ts.map +1 -0
- package/dist/src/cohort-topic/wire/validate.js +427 -0
- package/dist/src/cohort-topic/wire/validate.js.map +1 -0
- package/dist/src/collection/collection.d.ts +58 -1
- package/dist/src/collection/collection.d.ts.map +1 -1
- package/dist/src/collection/collection.js +76 -0
- package/dist/src/collection/collection.js.map +1 -1
- package/dist/src/collections/tree/tree.d.ts +58 -2
- package/dist/src/collections/tree/tree.d.ts.map +1 -1
- package/dist/src/collections/tree/tree.js +59 -2
- package/dist/src/collections/tree/tree.js.map +1 -1
- package/dist/src/index.d.ts +3 -0
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js +3 -0
- package/dist/src/index.js.map +1 -1
- package/dist/src/log/log.d.ts +42 -4
- package/dist/src/log/log.d.ts.map +1 -1
- package/dist/src/log/log.js +71 -3
- package/dist/src/log/log.js.map +1 -1
- package/dist/src/log/struct.d.ts +122 -2
- package/dist/src/log/struct.d.ts.map +1 -1
- package/dist/src/log/struct.js.map +1 -1
- package/dist/src/matchmaking/capability-filter.d.ts +24 -0
- package/dist/src/matchmaking/capability-filter.d.ts.map +1 -0
- package/dist/src/matchmaking/capability-filter.js +37 -0
- package/dist/src/matchmaking/capability-filter.js.map +1 -0
- package/dist/src/matchmaking/config.d.ts +77 -0
- package/dist/src/matchmaking/config.d.ts.map +1 -0
- package/dist/src/matchmaking/config.js +69 -0
- package/dist/src/matchmaking/config.js.map +1 -0
- package/dist/src/matchmaking/index.d.ts +21 -0
- package/dist/src/matchmaking/index.d.ts.map +1 -0
- package/dist/src/matchmaking/index.js +21 -0
- package/dist/src/matchmaking/index.js.map +1 -0
- package/dist/src/matchmaking/multi-cohort-seeker.d.ts +133 -0
- package/dist/src/matchmaking/multi-cohort-seeker.d.ts.map +1 -0
- package/dist/src/matchmaking/multi-cohort-seeker.js +142 -0
- package/dist/src/matchmaking/multi-cohort-seeker.js.map +1 -0
- package/dist/src/matchmaking/provider.d.ts +70 -0
- package/dist/src/matchmaking/provider.d.ts.map +1 -0
- package/dist/src/matchmaking/provider.js +93 -0
- package/dist/src/matchmaking/provider.js.map +1 -0
- package/dist/src/matchmaking/query-eval.d.ts +53 -0
- package/dist/src/matchmaking/query-eval.d.ts.map +1 -0
- package/dist/src/matchmaking/query-eval.js +69 -0
- package/dist/src/matchmaking/query-eval.js.map +1 -0
- package/dist/src/matchmaking/seeker-walk.d.ts +95 -0
- package/dist/src/matchmaking/seeker-walk.d.ts.map +1 -0
- package/dist/src/matchmaking/seeker-walk.js +75 -0
- package/dist/src/matchmaking/seeker-walk.js.map +1 -0
- package/dist/src/matchmaking/seeker.d.ts +51 -0
- package/dist/src/matchmaking/seeker.d.ts.map +1 -0
- package/dist/src/matchmaking/seeker.js +62 -0
- package/dist/src/matchmaking/seeker.js.map +1 -0
- package/dist/src/matchmaking/topic-anchor.d.ts +54 -0
- package/dist/src/matchmaking/topic-anchor.d.ts.map +1 -0
- package/dist/src/matchmaking/topic-anchor.js +67 -0
- package/dist/src/matchmaking/topic-anchor.js.map +1 -0
- package/dist/src/matchmaking/voting-quorum.d.ts +233 -0
- package/dist/src/matchmaking/voting-quorum.d.ts.map +1 -0
- package/dist/src/matchmaking/voting-quorum.js +238 -0
- package/dist/src/matchmaking/voting-quorum.js.map +1 -0
- package/dist/src/matchmaking/wire.d.ts +259 -0
- package/dist/src/matchmaking/wire.d.ts.map +1 -0
- package/dist/src/matchmaking/wire.js +484 -0
- package/dist/src/matchmaking/wire.js.map +1 -0
- package/dist/src/network/i-repo.d.ts +19 -0
- package/dist/src/network/i-repo.d.ts.map +1 -1
- package/dist/src/network/repo-protocol.d.ts +3 -1
- package/dist/src/network/repo-protocol.d.ts.map +1 -1
- package/dist/src/network/struct.d.ts +23 -1
- package/dist/src/network/struct.d.ts.map +1 -1
- package/dist/src/reactivity/backfill.d.ts +118 -0
- package/dist/src/reactivity/backfill.d.ts.map +1 -0
- package/dist/src/reactivity/backfill.js +137 -0
- package/dist/src/reactivity/backfill.js.map +1 -0
- package/dist/src/reactivity/backpressure.d.ts +99 -0
- package/dist/src/reactivity/backpressure.d.ts.map +1 -0
- package/dist/src/reactivity/backpressure.js +153 -0
- package/dist/src/reactivity/backpressure.js.map +1 -0
- package/dist/src/reactivity/checkpoint.d.ts +165 -0
- package/dist/src/reactivity/checkpoint.d.ts.map +1 -0
- package/dist/src/reactivity/checkpoint.js +232 -0
- package/dist/src/reactivity/checkpoint.js.map +1 -0
- package/dist/src/reactivity/config.d.ts +131 -0
- package/dist/src/reactivity/config.d.ts.map +1 -0
- package/dist/src/reactivity/config.js +135 -0
- package/dist/src/reactivity/config.js.map +1 -0
- package/dist/src/reactivity/dedupe.d.ts +51 -0
- package/dist/src/reactivity/dedupe.d.ts.map +1 -0
- package/dist/src/reactivity/dedupe.js +90 -0
- package/dist/src/reactivity/dedupe.js.map +1 -0
- package/dist/src/reactivity/forwarder.d.ts +50 -0
- package/dist/src/reactivity/forwarder.d.ts.map +1 -0
- package/dist/src/reactivity/forwarder.js +52 -0
- package/dist/src/reactivity/forwarder.js.map +1 -0
- package/dist/src/reactivity/index.d.ts +34 -0
- package/dist/src/reactivity/index.d.ts.map +1 -0
- package/dist/src/reactivity/index.js +34 -0
- package/dist/src/reactivity/index.js.map +1 -0
- package/dist/src/reactivity/notification.d.ts +72 -0
- package/dist/src/reactivity/notification.d.ts.map +1 -0
- package/dist/src/reactivity/notification.js +92 -0
- package/dist/src/reactivity/notification.js.map +1 -0
- package/dist/src/reactivity/policy.d.ts +59 -0
- package/dist/src/reactivity/policy.d.ts.map +1 -0
- package/dist/src/reactivity/policy.js +64 -0
- package/dist/src/reactivity/policy.js.map +1 -0
- package/dist/src/reactivity/push-state.d.ts +140 -0
- package/dist/src/reactivity/push-state.d.ts.map +1 -0
- package/dist/src/reactivity/push-state.js +270 -0
- package/dist/src/reactivity/push-state.js.map +1 -0
- package/dist/src/reactivity/recover.d.ts +75 -0
- package/dist/src/reactivity/recover.d.ts.map +1 -0
- package/dist/src/reactivity/recover.js +105 -0
- package/dist/src/reactivity/recover.js.map +1 -0
- package/dist/src/reactivity/replay-buffer.d.ts +57 -0
- package/dist/src/reactivity/replay-buffer.d.ts.map +1 -0
- package/dist/src/reactivity/replay-buffer.js +88 -0
- package/dist/src/reactivity/replay-buffer.js.map +1 -0
- package/dist/src/reactivity/resume.d.ts +214 -0
- package/dist/src/reactivity/resume.d.ts.map +1 -0
- package/dist/src/reactivity/resume.js +358 -0
- package/dist/src/reactivity/resume.js.map +1 -0
- package/dist/src/reactivity/rotation.d.ts +283 -0
- package/dist/src/reactivity/rotation.d.ts.map +1 -0
- package/dist/src/reactivity/rotation.js +265 -0
- package/dist/src/reactivity/rotation.js.map +1 -0
- package/dist/src/reactivity/subscriber.d.ts +66 -0
- package/dist/src/reactivity/subscriber.d.ts.map +1 -0
- package/dist/src/reactivity/subscriber.js +75 -0
- package/dist/src/reactivity/subscriber.js.map +1 -0
- package/dist/src/reactivity/subscription.d.ts +52 -0
- package/dist/src/reactivity/subscription.d.ts.map +1 -0
- package/dist/src/reactivity/subscription.js +28 -0
- package/dist/src/reactivity/subscription.js.map +1 -0
- package/dist/src/reactivity/topic-anchor.d.ts +46 -0
- package/dist/src/reactivity/topic-anchor.d.ts.map +1 -0
- package/dist/src/reactivity/topic-anchor.js +61 -0
- package/dist/src/reactivity/topic-anchor.js.map +1 -0
- package/dist/src/reactivity/verify.d.ts +41 -0
- package/dist/src/reactivity/verify.d.ts.map +1 -0
- package/dist/src/reactivity/verify.js +48 -0
- package/dist/src/reactivity/verify.js.map +1 -0
- package/dist/src/reactivity/wire-validate.d.ts +28 -0
- package/dist/src/reactivity/wire-validate.d.ts.map +1 -0
- package/dist/src/reactivity/wire-validate.js +79 -0
- package/dist/src/reactivity/wire-validate.js.map +1 -0
- package/dist/src/reactivity/wire.d.ts +102 -0
- package/dist/src/reactivity/wire.d.ts.map +1 -0
- package/dist/src/reactivity/wire.js +129 -0
- package/dist/src/reactivity/wire.js.map +1 -0
- package/dist/src/transaction/coordinator.d.ts +9 -5
- package/dist/src/transaction/coordinator.d.ts.map +1 -1
- package/dist/src/transaction/coordinator.js +48 -25
- package/dist/src/transaction/coordinator.js.map +1 -1
- package/dist/src/transactor/change-notifier.d.ts +74 -0
- package/dist/src/transactor/change-notifier.d.ts.map +1 -0
- package/dist/src/transactor/change-notifier.js +4 -0
- package/dist/src/transactor/change-notifier.js.map +1 -0
- 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 +44 -2
- package/dist/src/transactor/network-transactor.d.ts.map +1 -1
- package/dist/src/transactor/network-transactor.js +132 -17
- package/dist/src/transactor/network-transactor.js.map +1 -1
- package/package.json +3 -2
- package/src/cluster/structs.ts +86 -57
- package/src/cohort-topic/addressing.ts +112 -0
- package/src/cohort-topic/antidos/bootstrap-evidence-envelope.ts +253 -0
- package/src/cohort-topic/antidos/bootstrap-evidence.ts +94 -0
- package/src/cohort-topic/antidos/index.ts +5 -0
- package/src/cohort-topic/antidos/rate-limiter.ts +210 -0
- package/src/cohort-topic/antidos/replay-guard.ts +109 -0
- package/src/cohort-topic/antidos/topic-budget.ts +145 -0
- package/src/cohort-topic/antiflood/index.ts +2 -0
- package/src/cohort-topic/antiflood/invariants.ts +108 -0
- package/src/cohort-topic/antiflood/jitter.ts +117 -0
- package/src/cohort-topic/coldstart.ts +204 -0
- package/src/cohort-topic/dmax.ts +88 -0
- package/src/cohort-topic/gossip/bus.ts +217 -0
- package/src/cohort-topic/gossip/index.ts +3 -0
- package/src/cohort-topic/gossip/records.ts +45 -0
- package/src/cohort-topic/gossip/view.ts +66 -0
- package/src/cohort-topic/index.ts +20 -0
- package/src/cohort-topic/load/barometer.ts +134 -0
- package/src/cohort-topic/load/index.ts +1 -0
- package/src/cohort-topic/member-engine.ts +409 -0
- package/src/cohort-topic/membership/index.ts +3 -0
- package/src/cohort-topic/membership/publisher.ts +171 -0
- package/src/cohort-topic/membership/source.ts +41 -0
- package/src/cohort-topic/membership/verifier.ts +345 -0
- package/src/cohort-topic/ports.ts +157 -0
- package/src/cohort-topic/promotion.ts +384 -0
- package/src/cohort-topic/registration/bytes.ts +37 -0
- package/src/cohort-topic/registration/handoff.ts +154 -0
- package/src/cohort-topic/registration/index.ts +6 -0
- package/src/cohort-topic/registration/renewal.ts +423 -0
- package/src/cohort-topic/registration/sharding.ts +61 -0
- package/src/cohort-topic/registration/store.ts +81 -0
- package/src/cohort-topic/registration/types.ts +76 -0
- package/src/cohort-topic/ring-hash.ts +50 -0
- package/src/cohort-topic/service.ts +390 -0
- package/src/cohort-topic/sig/index.ts +2 -0
- package/src/cohort-topic/sig/payloads.ts +39 -0
- package/src/cohort-topic/sig/threshold.ts +64 -0
- package/src/cohort-topic/tiers.ts +74 -0
- package/src/cohort-topic/traffic.ts +211 -0
- package/src/cohort-topic/walk.ts +255 -0
- package/src/cohort-topic/willingness.ts +237 -0
- package/src/cohort-topic/wire/codec.ts +204 -0
- package/src/cohort-topic/wire/index.ts +16 -0
- package/src/cohort-topic/wire/payloads.ts +117 -0
- package/src/cohort-topic/wire/types.ts +343 -0
- package/src/cohort-topic/wire/validate.ts +478 -0
- package/src/collection/collection.ts +93 -1
- package/src/collections/tree/tree.ts +84 -3
- package/src/index.ts +3 -0
- package/src/log/log.ts +86 -7
- package/src/log/struct.ts +126 -2
- package/src/matchmaking/capability-filter.ts +45 -0
- package/src/matchmaking/config.ts +98 -0
- package/src/matchmaking/index.ts +21 -0
- package/src/matchmaking/multi-cohort-seeker.ts +234 -0
- package/src/matchmaking/provider.ts +123 -0
- package/src/matchmaking/query-eval.ts +105 -0
- package/src/matchmaking/seeker-walk.ts +127 -0
- package/src/matchmaking/seeker.ts +86 -0
- package/src/matchmaking/topic-anchor.ts +90 -0
- package/src/matchmaking/voting-quorum.ts +394 -0
- package/src/matchmaking/wire.ts +680 -0
- package/src/network/i-repo.ts +19 -0
- package/src/network/repo-protocol.ts +3 -2
- package/src/network/struct.ts +24 -1
- package/src/reactivity/backfill.ts +220 -0
- package/src/reactivity/backpressure.ts +191 -0
- package/src/reactivity/checkpoint.ts +308 -0
- package/src/reactivity/config.ts +172 -0
- package/src/reactivity/dedupe.ts +132 -0
- package/src/reactivity/forwarder.ts +87 -0
- package/src/reactivity/index.ts +34 -0
- package/src/reactivity/notification.ts +123 -0
- package/src/reactivity/policy.ts +79 -0
- package/src/reactivity/push-state.ts +343 -0
- package/src/reactivity/recover.ts +153 -0
- package/src/reactivity/replay-buffer.ts +141 -0
- package/src/reactivity/resume.ts +549 -0
- package/src/reactivity/rotation.ts +415 -0
- package/src/reactivity/subscriber.ts +132 -0
- package/src/reactivity/subscription.ts +66 -0
- package/src/reactivity/topic-anchor.ts +71 -0
- package/src/reactivity/verify.ts +73 -0
- package/src/reactivity/wire-validate.ts +87 -0
- package/src/reactivity/wire.ts +224 -0
- package/src/transaction/coordinator.ts +53 -27
- package/src/transactor/change-notifier.ts +80 -0
- package/src/transactor/index.ts +1 -0
- package/src/transactor/network-transactor.ts +162 -19
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Reactivity — forwarder receive path (`docs/reactivity.md` §Propagation).
|
|
3
|
+
*
|
|
4
|
+
* A receiving forwarder primary, for each inbound notification:
|
|
5
|
+
* 1. **verifies** the threshold signature against the tail cohort's `MembershipCertV1`;
|
|
6
|
+
* 2. runs the **dedupe** check (sliding `(revision, sigDigest)` window);
|
|
7
|
+
* 3. **appends** to the replay buffer;
|
|
8
|
+
* 4. **forwards the unmodified notification** to its own direct subscribers and child cohorts.
|
|
9
|
+
*
|
|
10
|
+
* This module owns steps 1–3 and the forward *decision* (step 4's transport — dialing each subscriber's
|
|
11
|
+
* primary and each child cohort — is the db-p2p binding). Forwarders never re-sign: a compromised
|
|
12
|
+
* forwarder can drop or delay, but cannot forge. An unverifiable notification is dropped **before**
|
|
13
|
+
* touching the dedupe set or buffer, so a forged payload can neither poison dedupe nor occupy a ring
|
|
14
|
+
* slot. The buffer + dedupe set are gossiped across the cohort by the host's gossip cadence (see
|
|
15
|
+
* {@link PushState}), so any member can serve a replay.
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
import { dedupeKey, sigDigest } from "./notification.js";
|
|
19
|
+
import type { PushState } from "./push-state.js";
|
|
20
|
+
import type { NotificationV1 } from "./wire.js";
|
|
21
|
+
import type { NotificationVerifier } from "./verify.js";
|
|
22
|
+
import type { IRingHash } from "../cohort-topic/ports.js";
|
|
23
|
+
|
|
24
|
+
/** The forwarder's decision for one inbound notification. */
|
|
25
|
+
export type ForwardDecision =
|
|
26
|
+
/** Verified, fresh: append to buffer and fan out unmodified. */
|
|
27
|
+
| "forward"
|
|
28
|
+
/** Already in the dedupe set: drop silently (an honest retransmit or merge duplicate). */
|
|
29
|
+
| "duplicate"
|
|
30
|
+
/** Signature did not verify against the tail cohort: drop without buffering. */
|
|
31
|
+
| "untrusted";
|
|
32
|
+
|
|
33
|
+
/** Drives the forwarder receive path over one collection's {@link PushState}. */
|
|
34
|
+
export interface ReactivityForwarder {
|
|
35
|
+
/** The per-collection push state this forwarder serves. */
|
|
36
|
+
readonly state: PushState;
|
|
37
|
+
/**
|
|
38
|
+
* Run verify → dedupe → append for one inbound notification, returning the forward decision. The
|
|
39
|
+
* caller fans the *unmodified* notification out only on `"forward"`.
|
|
40
|
+
*/
|
|
41
|
+
receive(n: NotificationV1, now: number): Promise<ForwardDecision>;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/** Construction inputs for a {@link ReactivityForwarder}. */
|
|
45
|
+
export interface ReactivityForwarderDeps {
|
|
46
|
+
readonly state: PushState;
|
|
47
|
+
readonly verifier: NotificationVerifier;
|
|
48
|
+
/** Ring hash for the `sigDigest` dedupe key (must match the verifier's). Default db-core SHA-256. */
|
|
49
|
+
readonly hash?: IRingHash;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
class PushStateForwarder implements ReactivityForwarder {
|
|
53
|
+
readonly state: PushState;
|
|
54
|
+
|
|
55
|
+
constructor(private readonly deps: ReactivityForwarderDeps) {
|
|
56
|
+
this.state = deps.state;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
async receive(n: NotificationV1, now: number): Promise<ForwardDecision> {
|
|
60
|
+
// 1. Verify end-to-end before any state mutation — a forged notification touches nothing.
|
|
61
|
+
const verdict = await this.deps.verifier.verify(n);
|
|
62
|
+
if (verdict !== "verified") {
|
|
63
|
+
return "untrusted";
|
|
64
|
+
}
|
|
65
|
+
// 2. Dedupe on `(revision, sigDigest)`. Already-seen → drop silently.
|
|
66
|
+
const digest = sigDigest(n.sig, this.deps.hash);
|
|
67
|
+
if (this.state.dedupe.observe(n.revision, digest) === "duplicate") {
|
|
68
|
+
return "duplicate";
|
|
69
|
+
}
|
|
70
|
+
// 3. Append the full signed notification to the replay ring (gossiped across the cohort).
|
|
71
|
+
this.state.replayBuffer.append({ revision: n.revision, payload: n, receivedAt: now });
|
|
72
|
+
if (n.revision > this.state.lastRevision) {
|
|
73
|
+
this.state.lastRevision = n.revision;
|
|
74
|
+
}
|
|
75
|
+
return "forward";
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/** Build a {@link ReactivityForwarder} over a collection's {@link PushState} and a verifier. */
|
|
80
|
+
export function createReactivityForwarder(deps: ReactivityForwarderDeps): ReactivityForwarder {
|
|
81
|
+
return new PushStateForwarder(deps);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/** The dedupe key a forwarder would compute for a notification (exposed for tests/diagnostics). */
|
|
85
|
+
export function notificationDedupeKey(n: NotificationV1, hash?: IRingHash): string {
|
|
86
|
+
return dedupeKey(n.revision, n.sig, hash);
|
|
87
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Reactivity — push-based change notifications on the cohort-topic substrate.
|
|
3
|
+
*
|
|
4
|
+
* See `docs/reactivity.md`. This module lands the reactivity hot path — the rotating tail-anchored topic,
|
|
5
|
+
* the subscribe `appPayload`, notification origination (reusing the commit cert unchanged), the forwarder
|
|
6
|
+
* receive path (verify → dedupe → buffer → forward), the `W`-entry replay ring with cohort gossip, the
|
|
7
|
+
* sliding `(revision, sigDigest)` dedupe window, and subscriber-side verify/deliver with gap detection
|
|
8
|
+
* ([reactivity-origination-replay-delivery]) — plus **recovery beyond the live stream**
|
|
9
|
+
* ([reactivity-backfill-resume-checkpoints]): the {@link ./backfill.js} RPC served from the replay ring,
|
|
10
|
+
* the rolling parent {@link ./checkpoint.js} stacked below it, and the four-variant {@link ./resume.js}
|
|
11
|
+
* protocol for mobile wake — plus **tail rotation, slow-subscriber backpressure, and Edge/Core policy**
|
|
12
|
+
* ([reactivity-rotation-backpressure-policy]): the {@link ./rotation.js} lifecycle (pre-announce, drain,
|
|
13
|
+
* jittered re-registration, buffer-to-checkpoint handoff, warm-up), the per-subscriber drop-oldest
|
|
14
|
+
* {@link ./backpressure.js}, and the {@link ./policy.js} Edge-subscriber-only / `delta_max` gate.
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
export * from "./config.js";
|
|
18
|
+
export * from "./topic-anchor.js";
|
|
19
|
+
export * from "./wire.js";
|
|
20
|
+
export * from "./notification.js";
|
|
21
|
+
export * from "./dedupe.js";
|
|
22
|
+
export * from "./replay-buffer.js";
|
|
23
|
+
export * from "./checkpoint.js";
|
|
24
|
+
export * from "./backpressure.js";
|
|
25
|
+
export * from "./push-state.js";
|
|
26
|
+
export * from "./verify.js";
|
|
27
|
+
export * from "./forwarder.js";
|
|
28
|
+
export * from "./subscriber.js";
|
|
29
|
+
export * from "./subscription.js";
|
|
30
|
+
export * from "./backfill.js";
|
|
31
|
+
export * from "./resume.js";
|
|
32
|
+
export * from "./recover.js";
|
|
33
|
+
export * from "./rotation.js";
|
|
34
|
+
export * from "./policy.js";
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Reactivity — notification origination (`docs/reactivity.md` §Notification origination).
|
|
3
|
+
*
|
|
4
|
+
* The tail cohort's primary for a collection is, by construction, the transaction-layer tail-cluster:
|
|
5
|
+
* the cohort-topic primary at `coord_0(_, topicId)` where `topicId = H(tailId ‖ "reactivity")`. As soon
|
|
6
|
+
* as the commit's threshold signature is assembled, it emits a {@link NotificationV1} whose `sig` is
|
|
7
|
+
* **bit-for-bit** the commit certificate's threshold signature — reactivity never re-signs.
|
|
8
|
+
*
|
|
9
|
+
* This module is the pure assembler. The local-change-notifier bridge ([local-change-notifier-bridge])
|
|
10
|
+
* supplies the {@link CollectionChangeEvent} and the pass-through {@link CommitCert}; db-p2p's
|
|
11
|
+
* origination manager calls {@link buildNotificationV1} with those plus the per-emission context
|
|
12
|
+
* (`tailId`, `timestamp`, the per-collection `deltaMaxBytes`) and fans the result out. Crypto-free: it
|
|
13
|
+
* copies `commitCert.thresholdSig` and `commitCert.signers` through unchanged.
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
import { createRingHash } from "../cohort-topic/ring-hash.js";
|
|
17
|
+
import { bytesToB64url } from "../cohort-topic/wire/codec.js";
|
|
18
|
+
import type { IRingHash } from "../cohort-topic/ports.js";
|
|
19
|
+
import type { CollectionChangeEvent, CommitCert } from "../transactor/change-notifier.js";
|
|
20
|
+
import type { NotificationV1, RotationHintV1 } from "./wire.js";
|
|
21
|
+
|
|
22
|
+
const utf8 = new TextEncoder();
|
|
23
|
+
|
|
24
|
+
/** Per-emission context the origination point supplies alongside the bridge's event + cert. */
|
|
25
|
+
export interface OriginationContext {
|
|
26
|
+
/** Current tail block id (base64url) the reactivity topic is anchored on. */
|
|
27
|
+
readonly tailId: string;
|
|
28
|
+
/** Emission timestamp (unix ms). */
|
|
29
|
+
readonly timestamp: number;
|
|
30
|
+
/**
|
|
31
|
+
* Per-collection delta budget (bytes). `0` ⇒ omit `delta` entirely (Edge profile, or a collection
|
|
32
|
+
* that declines deltas). Origination MUST respect a `deltaMaxBytes` of `0` by omitting the field.
|
|
33
|
+
*/
|
|
34
|
+
readonly deltaMaxBytes: number;
|
|
35
|
+
/** Optional bounded delta (raw bytes); included only when within `deltaMaxBytes` and `> 0`. */
|
|
36
|
+
readonly delta?: Uint8Array;
|
|
37
|
+
/** Tail-rotation pre-announce to embed (rotation ticket supplies it). */
|
|
38
|
+
readonly rotationHint?: RotationHintV1;
|
|
39
|
+
/**
|
|
40
|
+
* Map a {@link CommitCert} signer (the cluster's peer-id string keying its commit vote) to the
|
|
41
|
+
* base64url cohort member-id bytes the subscriber's membership verifier compares against. Default:
|
|
42
|
+
* identity (the signer is already base64url) — db-p2p supplies `s ⇒ bytesToB64url(peerIdToBytes(s))`.
|
|
43
|
+
*/
|
|
44
|
+
readonly encodeSigner?: (signer: string) => string;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Assemble the {@link NotificationV1} for one committed change, reusing the commit cert's threshold
|
|
49
|
+
* signature unchanged.
|
|
50
|
+
*
|
|
51
|
+
* `digest` carries the commit-vote **signed payload** the threshold signature was computed over —
|
|
52
|
+
* `commitCert.signedPayload`, the cluster's per-member `utf8(commitHash + ":approve")` byte image,
|
|
53
|
+
* identical across all approving signers. Because the verifier recomputes the cohort threshold check over
|
|
54
|
+
* exactly these bytes (`payload = b64urlToBytes(n.digest)`), a subscriber's **real** Ed25519
|
|
55
|
+
* threshold-verify over `digest` reproduces the signed image and succeeds — the cluster↔reactivity
|
|
56
|
+
* integration seam is **closed** (no pass-crypto stub required). The hint-only contract still holds for
|
|
57
|
+
* the optional `delta`: deltas are advisory; the digest + threshold signature are the authoritative proof.
|
|
58
|
+
*/
|
|
59
|
+
export function buildNotificationV1(event: CollectionChangeEvent, commitCert: CommitCert, ctx: OriginationContext): NotificationV1 {
|
|
60
|
+
const encodeSigner = ctx.encodeSigner ?? ((s: string): string => s);
|
|
61
|
+
const notification: NotificationV1 = {
|
|
62
|
+
v: 1,
|
|
63
|
+
collectionId: event.collectionId,
|
|
64
|
+
tailId: ctx.tailId,
|
|
65
|
+
revision: event.rev,
|
|
66
|
+
digest: bytesToB64url(commitCert.signedPayload),
|
|
67
|
+
timestamp: ctx.timestamp,
|
|
68
|
+
sig: bytesToB64url(commitCert.thresholdSig),
|
|
69
|
+
signers: commitCert.signers.map(encodeSigner),
|
|
70
|
+
};
|
|
71
|
+
if (ctx.deltaMaxBytes > 0 && ctx.delta !== undefined && ctx.delta.length <= ctx.deltaMaxBytes) {
|
|
72
|
+
notification.delta = bytesToB64url(ctx.delta);
|
|
73
|
+
}
|
|
74
|
+
if (ctx.rotationHint !== undefined) {
|
|
75
|
+
notification.rotationHint = ctx.rotationHint;
|
|
76
|
+
}
|
|
77
|
+
// An invalidation change event carries a typed marker the subscriber uses to distinguish a reversal
|
|
78
|
+
// from an ordinary commit (drop derived results + resubmit, vs. refresh). It rides the same path and
|
|
79
|
+
// reuses the invalidation's commit cert as `sig` — see {@link CollectionChangeEvent.invalidation}.
|
|
80
|
+
if (event.invalidation) {
|
|
81
|
+
notification.invalidation = true;
|
|
82
|
+
if (event.invalidatedActionId !== undefined) {
|
|
83
|
+
notification.invalidatedActionId = event.invalidatedActionId;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
return notification;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Compact, stable digest of a notification's signature for the dedupe key `(revision, sigDigest)`
|
|
91
|
+
* (`docs/reactivity.md` §Per-revision dedupe). Hashing the signature bytes keeps the key bounded and
|
|
92
|
+
* distinguishes two distinct threshold sigs that legitimately share a revision during partition merge.
|
|
93
|
+
*/
|
|
94
|
+
export function sigDigest(sig: string, hash: IRingHash = createRingHash()): string {
|
|
95
|
+
return bytesToB64url(hash.H(utf8.encode(sig)));
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/** The dedupe-set key for a notification: `${revision}:${sigDigest(sig)}`. */
|
|
99
|
+
export function dedupeKey(revision: number, sig: string, hash?: IRingHash): string {
|
|
100
|
+
return `${revision}:${sigDigest(sig, hash)}`;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* Collapse a batch of inbound notifications to the distinct **invalidated action ids** a client must act
|
|
105
|
+
* on, in first-seen order. A single dispute can fan out several invalidation notifications — one per
|
|
106
|
+
* cascade child (`docs/right-is-right.md` §Read-Dependency Cascade) — and a client holding dependents of
|
|
107
|
+
* several may receive several; coalescing by `invalidatedActionId` lets it drop + resubmit each affected
|
|
108
|
+
* unit exactly once instead of once per notification. Notifications that are not invalidations (ordinary
|
|
109
|
+
* commits) and invalidations missing an `invalidatedActionId` are ignored here — the former drive a plain
|
|
110
|
+
* refresh, the latter still surface the reverted state on the next authoritative read. A hint-layer helper:
|
|
111
|
+
* the client still verifies against committed state.
|
|
112
|
+
*/
|
|
113
|
+
export function coalesceInvalidatedActionIds(notifications: Iterable<NotificationV1>): string[] {
|
|
114
|
+
const seen = new Set<string>();
|
|
115
|
+
const ordered: string[] = [];
|
|
116
|
+
for (const n of notifications) {
|
|
117
|
+
if (n.invalidation && n.invalidatedActionId !== undefined && !seen.has(n.invalidatedActionId)) {
|
|
118
|
+
seen.add(n.invalidatedActionId);
|
|
119
|
+
ordered.push(n.invalidatedActionId);
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
return ordered;
|
|
123
|
+
}
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Reactivity — per-cohort Edge/Core policy (`docs/reactivity.md` §Per-cohort policy).
|
|
3
|
+
*
|
|
4
|
+
* Reactivity is **T3 (luxury)** at the cohort-topic layer. Two consequences are enforced here, the
|
|
5
|
+
* authoritative home for the reactivity producer-side policy:
|
|
6
|
+
*
|
|
7
|
+
* 1. **Edge nodes never serve as reactivity forwarders** — only as subscribers. A T3 *consumer* is fine
|
|
8
|
+
* on Edge (a phone wants notifications); a T3 *producer* is off in the Edge profile. The willingness
|
|
9
|
+
* check ({@link import("../cohort-topic/willingness.js")}) already declines T3 admission on an Edge
|
|
10
|
+
* node because `edgeProfile().willingTiers` excludes {@link Tier.T3}; this module adds the *reactivity*
|
|
11
|
+
* gate at the point a node decides whether to instantiate a {@link PushState} / become a forwarder, so
|
|
12
|
+
* the decision is explicit and testable rather than implicit in cohort admission.
|
|
13
|
+
* 2. **`delta_max` is Edge 0 / Core 4096** — the authoritative plumbing the origination ticket only
|
|
14
|
+
* *consumed*. Re-exported here from {@link import("./config.js")} so the policy surface is singular.
|
|
15
|
+
*
|
|
16
|
+
* The per-cohort topic budget (`topics_max`) bounds the collections a cohort serves; reactivity needs no
|
|
17
|
+
* admission policy beyond the cohort-topic default, so it is not re-implemented here.
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
import { Tier, type NodeProfile } from "../cohort-topic/tiers.js";
|
|
21
|
+
import { deltaMaxForProfile } from "./config.js";
|
|
22
|
+
import { PushState, type PushStateInit } from "./push-state.js";
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Whether a node with `profile` may serve as a reactivity **forwarder** (T3 producer). `true` only when
|
|
26
|
+
* the profile is willing to forward T3 — Core by default, an operator-narrowable Core, never Edge.
|
|
27
|
+
* Equivalent to `profile.willingTiers.has(Tier.T3)`: Edge's willing set is `{T0, T1}`, so this is `false`
|
|
28
|
+
* on every Edge node and on a Core node an operator has restricted away from T3.
|
|
29
|
+
*/
|
|
30
|
+
export function mayServeAsReactivityForwarder(profile: NodeProfile): boolean {
|
|
31
|
+
return profile.willingTiers.has(Tier.T3);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/** A subscriber-only node (Edge, or a Core narrowed off T3) attempted to instantiate a reactivity forwarder. */
|
|
35
|
+
export class ReactivityForwarderForbiddenError extends Error {
|
|
36
|
+
constructor(readonly profileKind: NodeProfile["kind"]) {
|
|
37
|
+
super(`reactivity: a ${profileKind} node that does not forward T3 cannot instantiate a forwarder (subscriber-only)`);
|
|
38
|
+
this.name = "ReactivityForwarderForbiddenError";
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Instantiate a forwarder {@link PushState} **only if** `profile` may forward T3. Returns `undefined` for a
|
|
44
|
+
* subscriber-only node (Edge, or a Core narrowed off T3) — the caller does not become a forwarder, leaving
|
|
45
|
+
* it a pure subscriber. This is the explicit gate the §Per-cohort policy "Edge nodes never serve as
|
|
46
|
+
* reactivity forwarders" rule names; a node consults it before allocating any per-collection push state.
|
|
47
|
+
*/
|
|
48
|
+
export function instantiateForwarderPushState(profile: NodeProfile, init: PushStateInit): PushState | undefined {
|
|
49
|
+
return mayServeAsReactivityForwarder(profile) ? new PushState(init) : undefined;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Like {@link instantiateForwarderPushState} but **throws** {@link ReactivityForwarderForbiddenError} on a
|
|
54
|
+
* subscriber-only node, for call sites that treat an Edge forwarder attempt as a programming error rather
|
|
55
|
+
* than a silent no-op (e.g. a host wiring that should never route forwarder duty to an Edge node).
|
|
56
|
+
*/
|
|
57
|
+
export function requireForwarderPushState(profile: NodeProfile, init: PushStateInit): PushState {
|
|
58
|
+
if (!mayServeAsReactivityForwarder(profile)) {
|
|
59
|
+
throw new ReactivityForwarderForbiddenError(profile.kind);
|
|
60
|
+
}
|
|
61
|
+
return new PushState(init);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* The effective reactivity producer policy for a node profile: whether it may forward, and its `delta_max`
|
|
66
|
+
* budget (Edge 0 — reject inbound `delta`, re-read the chain; Core 4096). The single struct a host reads to
|
|
67
|
+
* configure reactivity on a node (`docs/reactivity.md` §Per-cohort policy, §Delta payloads).
|
|
68
|
+
*/
|
|
69
|
+
export interface ReactivityNodePolicy {
|
|
70
|
+
/** Whether this node may instantiate a forwarder / become a T3 producer. */
|
|
71
|
+
readonly mayForward: boolean;
|
|
72
|
+
/** `delta_max` (bytes): Core 4096, Edge 0 (declines deltas). */
|
|
73
|
+
readonly deltaMaxBytes: number;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/** Resolve the {@link ReactivityNodePolicy} for a profile (forwarder eligibility + `delta_max`). */
|
|
77
|
+
export function reactivityNodePolicy(profile: NodeProfile): ReactivityNodePolicy {
|
|
78
|
+
return { mayForward: mayServeAsReactivityForwarder(profile), deltaMaxBytes: deltaMaxForProfile(profile) };
|
|
79
|
+
}
|
|
@@ -0,0 +1,343 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Reactivity — forwarder-cohort `PushState` and its intra-cohort gossip codec
|
|
3
|
+
* (`docs/reactivity.md` §Forwarder-cohort state, §Replay window).
|
|
4
|
+
*
|
|
5
|
+
* A reactivity forwarder cohort holds the cohort-topic registration records (the direct-subscriber
|
|
6
|
+
* list **is** the `RegistrationRecord` set with `appPayload.kind == "reactivity"` — reactivity reads it,
|
|
7
|
+
* it does not duplicate it) plus this per-collection {@link PushState}: topic identity, parent/child
|
|
8
|
+
* cohort refs, the {@link ReplayBuffer} ring, the {@link DedupeWindow}, and `lastRevision`.
|
|
9
|
+
*
|
|
10
|
+
* `PushState` is gossiped within the cohort (over the cohort-topic gossip channel) so any member can
|
|
11
|
+
* serve a backfill/replay if the primary is unavailable. This module defines the **full** struct and its
|
|
12
|
+
* gossip codec so all three reactivity tickets share one definition. The rolling parent {@link checkpoint}
|
|
13
|
+
* (and its derived {@link parentCheckpoint} summary) is fed by replay-ring eviction
|
|
14
|
+
* ([reactivity-backfill-resume-checkpoints]); the `perSubscriberQueue` field carries the primary-local
|
|
15
|
+
* per-subscriber {@link SubscriberBackpressure} ([reactivity-rotation-backpressure-policy]). The
|
|
16
|
+
* backpressure map is **primary-local fan-out state, never gossiped** — it is absent from
|
|
17
|
+
* {@link PushStateGossipV1}; a handoff rebuilds it empty and the replay buffer covers the gap.
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
import { bytesToB64url, b64urlToBytes, decodeCohortMessage, encodeCohortMessage, DEFAULT_MAX_MESSAGE_BYTES } from "../cohort-topic/wire/codec.js";
|
|
21
|
+
import { CohortWireError } from "../cohort-topic/wire/validate.js";
|
|
22
|
+
import { DEDUPE_WINDOW_DEFAULT, QUEUE_MAX_DEFAULT, W_DEFAULT } from "./config.js";
|
|
23
|
+
import { createSubscriberBackpressure, type SubscriberBackpressure, type SubscriberEnqueueResult } from "./backpressure.js";
|
|
24
|
+
import { RollingCheckpoint, type CheckpointSummary, type DeltaCoalesce, type DigestFold } from "./checkpoint.js";
|
|
25
|
+
import { createDedupeWindow, type DedupeStateV1, type DedupeWindow } from "./dedupe.js";
|
|
26
|
+
import { createReplayBuffer, type ReplayBuffer, type ReplayBufferStateV1, type RevisionEntry } from "./replay-buffer.js";
|
|
27
|
+
import { validateNotificationV1, type NotificationV1 } from "./wire.js";
|
|
28
|
+
|
|
29
|
+
/** A reference to a parent/child cohort in the reactivity tree (`tier-(d∓1)`). */
|
|
30
|
+
export interface CohortRef {
|
|
31
|
+
/** The cohort's served ring coordinate, base64url. */
|
|
32
|
+
readonly coord: string;
|
|
33
|
+
/** The cohort's primary member id, base64url (when known). */
|
|
34
|
+
readonly primary?: string;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/** Construction inputs for a {@link PushState}. */
|
|
38
|
+
export interface PushStateInit {
|
|
39
|
+
/** Collection id, base64url. */
|
|
40
|
+
readonly collectionId: string;
|
|
41
|
+
/** Reactivity topic id, base64url. */
|
|
42
|
+
readonly topicId: string;
|
|
43
|
+
/** Tail block id when this cohort joined the topic, base64url. */
|
|
44
|
+
readonly tailIdAtJoin: string;
|
|
45
|
+
/** Tier-`(d−1)` parent cohort (absent at the tail/root). */
|
|
46
|
+
readonly parentCohort?: CohortRef;
|
|
47
|
+
/** Tier-`(d+1)` child cohorts. */
|
|
48
|
+
readonly childCohorts?: CohortRef[];
|
|
49
|
+
/** Replay buffer depth `W` (default {@link W_DEFAULT}). */
|
|
50
|
+
readonly w?: number;
|
|
51
|
+
/** Dedupe-window span (default {@link DEDUPE_WINDOW_DEFAULT}). */
|
|
52
|
+
readonly dedupeWindow?: number;
|
|
53
|
+
/** Parent-checkpoint span `W_checkpoint` (default {@link import("./config.js").W_CHECKPOINT_DEFAULT}). */
|
|
54
|
+
readonly wCheckpoint?: number;
|
|
55
|
+
/** Per-subscriber bounded-queue depth `queue_max` (default {@link QUEUE_MAX_DEFAULT}). */
|
|
56
|
+
readonly queueMax?: number;
|
|
57
|
+
/** `delta_max` for the checkpoint's coalesced `mergedDelta`; `0` (default) ⇒ never emit a delta. */
|
|
58
|
+
readonly deltaMaxBytes?: number;
|
|
59
|
+
/** `mergedDigest` fold override (default: deterministic running hash over per-revision commit digests). */
|
|
60
|
+
readonly checkpointFold?: DigestFold;
|
|
61
|
+
/** `mergedDelta` coalescer override (default: ordered concatenation). */
|
|
62
|
+
readonly coalesceDeltas?: DeltaCoalesce;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/** The cohort-gossiped wire image of a {@link PushState}. */
|
|
66
|
+
export interface PushStateGossipV1 {
|
|
67
|
+
v: 1;
|
|
68
|
+
collectionId: string;
|
|
69
|
+
topicId: string;
|
|
70
|
+
tailIdAtJoin: string;
|
|
71
|
+
parentCohort?: CohortRef;
|
|
72
|
+
childCohorts: CohortRef[];
|
|
73
|
+
lastRevision: number;
|
|
74
|
+
replayBuffer: ReplayBufferStateV1;
|
|
75
|
+
dedupe: DedupeStateV1;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Per-collection forwarder-cohort state. Owns the live {@link ReplayBuffer} and {@link DedupeWindow}
|
|
80
|
+
* components and the scalar identity/revision state; the reserved sibling-ticket fields hang off it.
|
|
81
|
+
*/
|
|
82
|
+
export class PushState {
|
|
83
|
+
readonly collectionId: string;
|
|
84
|
+
readonly topicId: string;
|
|
85
|
+
tailIdAtJoin: string;
|
|
86
|
+
parentCohort?: CohortRef;
|
|
87
|
+
childCohorts: CohortRef[];
|
|
88
|
+
readonly replayBuffer: ReplayBuffer;
|
|
89
|
+
readonly dedupe: DedupeWindow;
|
|
90
|
+
lastRevision: number;
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* The rolling parent checkpoint ([reactivity-backfill-resume-checkpoints], `docs/reactivity.md`
|
|
94
|
+
* §Parent checkpoint summaries). Fed by replay-ring eviction — as revisions retire from the `W`-deep
|
|
95
|
+
* ring they roll into this `W_checkpoint`-span summary sitting immediately below the ring, so a resume
|
|
96
|
+
* recovers `W + W_checkpoint` revisions in one round trip (the stacked-window semantics).
|
|
97
|
+
*/
|
|
98
|
+
readonly checkpoint: RollingCheckpoint;
|
|
99
|
+
/**
|
|
100
|
+
* The forwarder primary's per-subscriber drop-oldest backpressure map
|
|
101
|
+
* ([reactivity-rotation-backpressure-policy], `docs/reactivity.md` §Slow-subscriber backpressure).
|
|
102
|
+
* Primary-local fan-out state — **not** gossiped, so it is absent from {@link PushStateGossipV1}.
|
|
103
|
+
*/
|
|
104
|
+
readonly perSubscriberQueue: SubscriberBackpressure;
|
|
105
|
+
/**
|
|
106
|
+
* The checkpoint migrated from the **outgoing** tail when this cohort became the new tail across a
|
|
107
|
+
* rotation ([reactivity-rotation-backpressure-policy], `docs/reactivity.md` §Tail rotation step 5). The
|
|
108
|
+
* new tail "holds the old checkpoint" so a `ResumeV1` whose span crosses the rotation is recoverable;
|
|
109
|
+
* `undefined` until a handoff lands via {@link adoptRotationCheckpoint}. Distinct from the live rolling
|
|
110
|
+
* {@link checkpoint}, which rolls from this cohort's *own* replay-ring eviction.
|
|
111
|
+
*/
|
|
112
|
+
private inherited?: CheckpointSummary;
|
|
113
|
+
|
|
114
|
+
constructor(init: PushStateInit) {
|
|
115
|
+
this.collectionId = init.collectionId;
|
|
116
|
+
this.topicId = init.topicId;
|
|
117
|
+
this.tailIdAtJoin = init.tailIdAtJoin;
|
|
118
|
+
this.parentCohort = init.parentCohort;
|
|
119
|
+
this.childCohorts = init.childCohorts ?? [];
|
|
120
|
+
this.checkpoint = new RollingCheckpoint({
|
|
121
|
+
collectionId: init.collectionId,
|
|
122
|
+
span: init.wCheckpoint,
|
|
123
|
+
deltaMaxBytes: init.deltaMaxBytes,
|
|
124
|
+
fold: init.checkpointFold,
|
|
125
|
+
coalesceDeltas: init.coalesceDeltas,
|
|
126
|
+
});
|
|
127
|
+
// Wire the ring's low-edge eviction into the rolling checkpoint: a revision leaving the replay
|
|
128
|
+
// window is exactly a revision that should roll into the parent-checkpoint summary.
|
|
129
|
+
this.replayBuffer = createReplayBuffer(init.w ?? W_DEFAULT, (evicted) => this.checkpoint.retire(evicted));
|
|
130
|
+
this.dedupe = createDedupeWindow(init.dedupeWindow ?? DEDUPE_WINDOW_DEFAULT);
|
|
131
|
+
this.perSubscriberQueue = createSubscriberBackpressure(init.queueMax ?? QUEUE_MAX_DEFAULT);
|
|
132
|
+
this.lastRevision = -1;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* Fan one notification onto each subscriber's bounded queue (the primary's per-subscriber backpressure,
|
|
137
|
+
* `docs/reactivity.md` §Slow-subscriber backpressure). Returns only the subscribers whose queue dropped
|
|
138
|
+
* its oldest under pressure — a slow subscriber's drop never stalls fan-out to the rest of the cohort.
|
|
139
|
+
*/
|
|
140
|
+
enqueueForSubscribers(subscriberIds: Iterable<string>, n: NotificationV1): SubscriberEnqueueResult[] {
|
|
141
|
+
return this.perSubscriberQueue.fanOut(subscriberIds, n);
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
/** The checkpoint inherited from the outgoing tail on rotation, or `undefined` until a handoff lands. */
|
|
145
|
+
get inheritedCheckpoint(): CheckpointSummary | undefined {
|
|
146
|
+
return this.inherited;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/**
|
|
150
|
+
* Land an outgoing tail's rotation handoff checkpoint on this (new tail) cohort
|
|
151
|
+
* (`docs/reactivity.md` §Tail rotation step 5). Idempotent: a later handoff replaces only if it covers a
|
|
152
|
+
* higher revision, so a re-delivered or older handoff never rewinds the inherited span.
|
|
153
|
+
*/
|
|
154
|
+
adoptRotationCheckpoint(summary: CheckpointSummary): void {
|
|
155
|
+
if (this.inherited === undefined || summary.toRevision > this.inherited.toRevision) {
|
|
156
|
+
this.inherited = summary;
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
/**
|
|
161
|
+
* The current parent-checkpoint summary, or `undefined` until revisions have retired from the replay
|
|
162
|
+
* ring (`docs/reactivity.md` §Parent checkpoint summaries). Re-derived from the live rolling checkpoint
|
|
163
|
+
* on each read so it always reflects the latest retired range.
|
|
164
|
+
*/
|
|
165
|
+
get parentCheckpoint(): CheckpointSummary | undefined {
|
|
166
|
+
return this.checkpoint.summary();
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
/** Snapshot the gossipable slice for cohort replication. */
|
|
170
|
+
serializeGossip(): PushStateGossipV1 {
|
|
171
|
+
const out: PushStateGossipV1 = {
|
|
172
|
+
v: 1,
|
|
173
|
+
collectionId: this.collectionId,
|
|
174
|
+
topicId: this.topicId,
|
|
175
|
+
tailIdAtJoin: this.tailIdAtJoin,
|
|
176
|
+
childCohorts: this.childCohorts,
|
|
177
|
+
lastRevision: this.lastRevision,
|
|
178
|
+
replayBuffer: this.replayBuffer.serialize(),
|
|
179
|
+
dedupe: this.dedupe.serialize(),
|
|
180
|
+
};
|
|
181
|
+
if (this.parentCohort !== undefined) {
|
|
182
|
+
out.parentCohort = this.parentCohort;
|
|
183
|
+
}
|
|
184
|
+
return out;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
/** Merge a peer member's gossiped state so the whole cohort converges (any member can then serve). */
|
|
188
|
+
mergeGossip(g: PushStateGossipV1): void {
|
|
189
|
+
if (g.collectionId !== this.collectionId || g.topicId !== this.topicId) {
|
|
190
|
+
return; // gossip for a different collection/topic is not ours to merge
|
|
191
|
+
}
|
|
192
|
+
this.replayBuffer.merge(g.replayBuffer);
|
|
193
|
+
this.dedupe.merge(g.dedupe);
|
|
194
|
+
if (g.lastRevision > this.lastRevision) {
|
|
195
|
+
this.lastRevision = g.lastRevision;
|
|
196
|
+
}
|
|
197
|
+
if (g.childCohorts.length > this.childCohorts.length) {
|
|
198
|
+
this.childCohorts = g.childCohorts;
|
|
199
|
+
}
|
|
200
|
+
if (this.parentCohort === undefined && g.parentCohort !== undefined) {
|
|
201
|
+
this.parentCohort = g.parentCohort;
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
// --- gossip codec (length-framed JSON, mirroring cohort-topic conventions) ---
|
|
207
|
+
|
|
208
|
+
function fail(message: string): never {
|
|
209
|
+
throw new CohortWireError(message);
|
|
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
|
+
}
|
|
234
|
+
|
|
235
|
+
function reqInt(value: number, key: string, what: string): number {
|
|
236
|
+
if (!Number.isInteger(value)) {
|
|
237
|
+
fail(`${what}: field "${key}" must be an integer, got ${value}`);
|
|
238
|
+
}
|
|
239
|
+
return value;
|
|
240
|
+
}
|
|
241
|
+
|
|
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
|
+
function validateCohortRef(value: unknown, what: string): CohortRef {
|
|
252
|
+
const obj = asObject(value, what);
|
|
253
|
+
const coord = b64urlField(reqString(obj, "coord", what), "coord", what);
|
|
254
|
+
if (obj["primary"] !== undefined) {
|
|
255
|
+
return { coord, primary: b64urlField(reqString(obj, "primary", what), "primary", what) };
|
|
256
|
+
}
|
|
257
|
+
return { coord };
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
function validateRevisionEntry(value: unknown, what: string): RevisionEntry {
|
|
261
|
+
const obj = asObject(value, what);
|
|
262
|
+
return {
|
|
263
|
+
revision: reqInt(reqFiniteNumber(obj, "revision", what), "revision", what),
|
|
264
|
+
payload: validateNotificationV1(obj["payload"]),
|
|
265
|
+
receivedAt: reqFiniteNumber(obj, "receivedAt", what),
|
|
266
|
+
};
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
function validateReplayBufferState(value: unknown, what: string): ReplayBufferStateV1 {
|
|
270
|
+
const obj = asObject(value, what);
|
|
271
|
+
const entries = obj["entries"];
|
|
272
|
+
if (!Array.isArray(entries)) {
|
|
273
|
+
fail(`${what}: field "entries" must be an array`);
|
|
274
|
+
}
|
|
275
|
+
return {
|
|
276
|
+
capacity: reqInt(reqFiniteNumber(obj, "capacity", what), "capacity", what),
|
|
277
|
+
entries: entries.map((e) => validateRevisionEntry(e, `${what}.entry`)),
|
|
278
|
+
};
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
function validateDedupeState(value: unknown, what: string): DedupeStateV1 {
|
|
282
|
+
const obj = asObject(value, what);
|
|
283
|
+
const entries = obj["entries"];
|
|
284
|
+
if (!Array.isArray(entries)) {
|
|
285
|
+
fail(`${what}: field "entries" must be an array`);
|
|
286
|
+
}
|
|
287
|
+
return {
|
|
288
|
+
highestRevision: reqInt(reqFiniteNumber(obj, "highestRevision", what), "highestRevision", what),
|
|
289
|
+
entries: entries.map((e) => {
|
|
290
|
+
const eo = asObject(e, `${what}.entry`);
|
|
291
|
+
return { key: reqString(eo, "key", `${what}.entry`), revision: reqInt(reqFiniteNumber(eo, "revision", `${what}.entry`), "revision", `${what}.entry`) };
|
|
292
|
+
}),
|
|
293
|
+
};
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
/** Narrow an already-parsed value to {@link PushStateGossipV1}, throwing on any defect. */
|
|
297
|
+
export function validatePushStateGossipV1(value: unknown): PushStateGossipV1 {
|
|
298
|
+
const what = "PushStateGossipV1";
|
|
299
|
+
const obj = asObject(value, what);
|
|
300
|
+
if (obj["v"] !== 1) {
|
|
301
|
+
fail(`${what}: expected v === 1, got ${JSON.stringify(obj["v"])}`);
|
|
302
|
+
}
|
|
303
|
+
const childCohorts = obj["childCohorts"];
|
|
304
|
+
if (!Array.isArray(childCohorts)) {
|
|
305
|
+
fail(`${what}: field "childCohorts" must be an array`);
|
|
306
|
+
}
|
|
307
|
+
const out: PushStateGossipV1 = {
|
|
308
|
+
v: 1,
|
|
309
|
+
collectionId: b64urlField(reqString(obj, "collectionId", what), "collectionId", what),
|
|
310
|
+
topicId: b64urlField(reqString(obj, "topicId", what), "topicId", what),
|
|
311
|
+
tailIdAtJoin: b64urlField(reqString(obj, "tailIdAtJoin", what), "tailIdAtJoin", what),
|
|
312
|
+
childCohorts: childCohorts.map((c) => validateCohortRef(c, `${what}.childCohort`)),
|
|
313
|
+
lastRevision: reqInt(reqFiniteNumber(obj, "lastRevision", what), "lastRevision", what),
|
|
314
|
+
replayBuffer: validateReplayBufferState(obj["replayBuffer"], `${what}.replayBuffer`),
|
|
315
|
+
dedupe: validateDedupeState(obj["dedupe"], `${what}.dedupe`),
|
|
316
|
+
};
|
|
317
|
+
if (obj["parentCohort"] !== undefined) {
|
|
318
|
+
out.parentCohort = validateCohortRef(obj["parentCohort"], `${what}.parentCohort`);
|
|
319
|
+
}
|
|
320
|
+
return out;
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
/** Encode a {@link PushStateGossipV1} as a length-prefixed UTF-8 JSON frame. */
|
|
324
|
+
export function encodePushStateGossipV1(msg: PushStateGossipV1, maxMessageBytes: number = DEFAULT_MAX_MESSAGE_BYTES): Uint8Array {
|
|
325
|
+
return encodeCohortMessage(validatePushStateGossipV1(msg), maxMessageBytes);
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
/** Decode a length-prefixed {@link PushStateGossipV1} frame. */
|
|
329
|
+
export function decodePushStateGossipV1(bytes: Uint8Array, maxMessageBytes?: number): PushStateGossipV1 {
|
|
330
|
+
return validatePushStateGossipV1(decodeCohortMessage(bytes, maxMessageBytes));
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
/** Round-trip helper for `bytesToB64url` consumers wanting the raw coord bytes of a {@link CohortRef}. */
|
|
334
|
+
export function cohortRefCoordBytes(ref: CohortRef): Uint8Array {
|
|
335
|
+
return b64urlToBytes(ref.coord);
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
/** Build a {@link CohortRef} from raw coord (and optional primary) bytes. */
|
|
339
|
+
export function makeCohortRef(coord: Uint8Array, primary?: Uint8Array): CohortRef {
|
|
340
|
+
return primary !== undefined
|
|
341
|
+
? { coord: bytesToB64url(coord), primary: bytesToB64url(primary) }
|
|
342
|
+
: { coord: bytesToB64url(coord) };
|
|
343
|
+
}
|