@optimystic/db-core 0.13.5 → 0.14.1
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 +13 -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 +9 -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 +34 -2
- package/dist/src/transactor/network-transactor.d.ts.map +1 -1
- package/dist/src/transactor/network-transactor.js +113 -13
- package/dist/src/transactor/network-transactor.js.map +1 -1
- package/package.json +2 -2
- package/src/cluster/structs.ts +13 -0
- 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 +9 -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 +133 -15
|
@@ -0,0 +1,415 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Reactivity — tail rotation lifecycle (`docs/reactivity.md` §Rotating tail anchor, §Tail rotation).
|
|
3
|
+
*
|
|
4
|
+
* `topicId = H(tailId ‖ "reactivity")` is derived per emission ({@link reactivityTopicId}). When the tail
|
|
5
|
+
* block fills (`block_fill_size` transactions, default 64) a new tail block is born and `tailId` — hence
|
|
6
|
+
* `topicId` — changes; the cohort-topic layer treats the new `topicId` as an **entirely new topic** (fresh
|
|
7
|
+
* tree, new ring coord). Reactivity manages the subscriber/replay-state migration explicitly. This module
|
|
8
|
+
* owns that lifecycle:
|
|
9
|
+
*
|
|
10
|
+
* - **Pre-announce** — the block-filling commit's notification carries {@link RotationHintV1}; built here
|
|
11
|
+
* by {@link buildRotationHint} and detected subscriber-side by {@link detectRotation}.
|
|
12
|
+
* - **Block-fill tracking** — {@link BlockFillTracker} counts transactions in the current tail block and
|
|
13
|
+
* fires anticipatory **warm-up** at `block_fill_size − warm_threshold` and the **filling** signal at
|
|
14
|
+
* `block_fill_size` (the commit that carries the hint).
|
|
15
|
+
* - **Drain** — {@link TailDrainGate} keeps the outgoing tail serving renewals/replays for `T_drain` while
|
|
16
|
+
* bouncing *new* subscriptions with a `Promoted`-shaped {@link RotationRedirectV1} to the new tree.
|
|
17
|
+
* - **Jittered re-registration** — {@link planReRegistration} / {@link planReRegistrationWave} schedule a
|
|
18
|
+
* subscriber's move to the new `topicId` over `T_rejoin_jitter` (the cohort-topic {@link RejoinJitter}),
|
|
19
|
+
* carrying its existing `lastRevision` (revisions are continuous across rotations).
|
|
20
|
+
* - **Buffer-to-checkpoint handoff** — {@link buildRotationHandoffCheckpoint} folds the outgoing tail's
|
|
21
|
+
* replay buffer into a final {@link CheckpointSummary} covering `[lastCheckpoint.toRevision + 1,
|
|
22
|
+
* rotationRevision]`, the **only** state migrated across a rotation; {@link applyRotationHandoff} lands it
|
|
23
|
+
* at the new tail so a `ResumeV1` spanning the rotation is recoverable.
|
|
24
|
+
*
|
|
25
|
+
* Forwarder draining is *emergent*: forwarders under the old tail watch their direct-subscriber count drop
|
|
26
|
+
* as subscribers re-register elsewhere and demote naturally per the cohort-topic demotion protocol (no
|
|
27
|
+
* state migrates; the new tree rebuilds via re-registration). It needs no code here.
|
|
28
|
+
*
|
|
29
|
+
* Coordination with [reactivity-backfill-resume-checkpoints]: that ticket owns the `ResumeReplyV1.TailRotated`
|
|
30
|
+
* variant and the `latestKnownTailId`-staleness classification; this ticket produces the handoff checkpoint
|
|
31
|
+
* and the rotation *condition*. The new tail's {@link PushState.inheritedCheckpoint} (set by
|
|
32
|
+
* {@link applyRotationHandoff}) is the seam the resume classifier consults to answer a checkpoint-window
|
|
33
|
+
* resume whose span crosses the rotation: `classifyResume`/`serveResume` now read it (after the rolling
|
|
34
|
+
* `checkpoint` misses) and serve the inherited summary, so a cross-rotation resume no longer falls to
|
|
35
|
+
* `out_of_window` (`docs/reactivity.md` §Resume, §Tail rotation step 5). The drain-window redirect a new
|
|
36
|
+
* subscription receives, {@link RotationRedirectV1}, is serialized by {@link validateRotationRedirectV1} and
|
|
37
|
+
* rides the recover reply envelope as `kind: "rotated"` ({@link import("./recover.js").RecoverReplyV1}).
|
|
38
|
+
*/
|
|
39
|
+
|
|
40
|
+
import { bytesToB64url, b64urlToBytes } from "../cohort-topic/wire/codec.js";
|
|
41
|
+
import { createRingHash } from "../cohort-topic/ring-hash.js";
|
|
42
|
+
import type { IRingHash } from "../cohort-topic/ports.js";
|
|
43
|
+
import type { RejoinJitter } from "../cohort-topic/antiflood/jitter.js";
|
|
44
|
+
import { BLOCK_FILL_SIZE_DEFAULT, T_DRAIN_MS, WARM_THRESHOLD_DEFAULT } from "./config.js";
|
|
45
|
+
import { buildCheckpointSummary, type CheckpointSummary } from "./checkpoint.js";
|
|
46
|
+
import { reactivityTopicId } from "./topic-anchor.js";
|
|
47
|
+
import type { PushState } from "./push-state.js";
|
|
48
|
+
import type { NotificationV1, RotationHintV1 } from "./wire.js";
|
|
49
|
+
import { asObject, b64urlField, failWire, reqIntInRange, reqString, requireV1 } from "./wire-validate.js";
|
|
50
|
+
|
|
51
|
+
// --- pre-announce + subscriber-side detection --------------------------------
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Build the rotation pre-announce embedded in the block-filling commit's notification. The new tail block
|
|
55
|
+
* id is known at the filling commit; the rotation becomes effective at the next revision, so
|
|
56
|
+
* `effectiveAtRevision = fillingRevision + 1` (`docs/reactivity.md` §Tail rotation, worked scenario:
|
|
57
|
+
* revision 5400 carries `{ newTailId: T_6, effectiveAtRevision: 5401 }`).
|
|
58
|
+
*/
|
|
59
|
+
export function buildRotationHint(newTailId: string, fillingRevision: number): RotationHintV1 {
|
|
60
|
+
return { newTailId, effectiveAtRevision: fillingRevision + 1 };
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/** The subscriber-side rotation verdict for one delivered notification. */
|
|
64
|
+
export interface RotationDetection {
|
|
65
|
+
/** True iff the subscriber's tree has rotated (delivered tail differs) or a rotation is pre-announced. */
|
|
66
|
+
readonly rotated: boolean;
|
|
67
|
+
/** The tail id to re-register under, base64url — present iff `rotated`. */
|
|
68
|
+
readonly newTailId?: string;
|
|
69
|
+
/**
|
|
70
|
+
* True iff this is a **pre-announce**: the delivered `tailId` still matches `tailIdAtAttach`, but the
|
|
71
|
+
* notification's `rotationHint.newTailId` names a different successor. (False when the delivered tail
|
|
72
|
+
* already differs — the tree has *already* migrated.)
|
|
73
|
+
*/
|
|
74
|
+
readonly preAnnounced: boolean;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Detect tail rotation for a subscriber attached at `tailIdAtAttach` (base64url) from a delivered
|
|
79
|
+
* notification. Rotation is signaled when the delivered `tailId` **or** the `rotationHint.newTailId`
|
|
80
|
+
* differs from `tailIdAtAttach` (`docs/reactivity.md` §Rotating tail anchor). An already-rotated delivery
|
|
81
|
+
* (different `tailId`) takes precedence over a pre-announce.
|
|
82
|
+
*/
|
|
83
|
+
export function detectRotation(tailIdAtAttach: string, n: Pick<NotificationV1, "tailId" | "rotationHint">): RotationDetection {
|
|
84
|
+
if (n.tailId !== tailIdAtAttach) {
|
|
85
|
+
// The delivered notification rides the *new* tree already — a hard rotation, not a pre-announce.
|
|
86
|
+
return { rotated: true, newTailId: n.tailId, preAnnounced: false };
|
|
87
|
+
}
|
|
88
|
+
if (n.rotationHint !== undefined && n.rotationHint.newTailId !== tailIdAtAttach) {
|
|
89
|
+
return { rotated: true, newTailId: n.rotationHint.newTailId, preAnnounced: true };
|
|
90
|
+
}
|
|
91
|
+
return { rotated: false, preAnnounced: false };
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// --- block-fill tracking (warm-up + filling signal) --------------------------
|
|
95
|
+
|
|
96
|
+
/** The fill signal for one committed transaction in the current tail block. */
|
|
97
|
+
export type BlockFillSignal =
|
|
98
|
+
/** Below the warm-up threshold — nothing to do. */
|
|
99
|
+
| { readonly kind: "none"; readonly count: number }
|
|
100
|
+
/**
|
|
101
|
+
* Anticipatory warm-up: the block has reached `block_fill_size − warm_threshold` transactions with
|
|
102
|
+
* `remaining` to go. The outgoing tail opportunistically biases FRET pre-dialing toward likely-successor
|
|
103
|
+
* coords (best-effort; the next `tailId` is not yet knowable). No state migrates.
|
|
104
|
+
*/
|
|
105
|
+
| { readonly kind: "warmup"; readonly count: number; readonly remaining: number }
|
|
106
|
+
/**
|
|
107
|
+
* The **block-filling** transaction: this commit's notification carries the {@link RotationHintV1} and
|
|
108
|
+
* triggers the rotation. The tracker resets for the next block after emitting this.
|
|
109
|
+
*/
|
|
110
|
+
| { readonly kind: "filling"; readonly count: number };
|
|
111
|
+
|
|
112
|
+
/** Construction inputs for a {@link BlockFillTracker}. */
|
|
113
|
+
export interface BlockFillTrackerInit {
|
|
114
|
+
/** Transactions per block before the tail rotates (default {@link BLOCK_FILL_SIZE_DEFAULT}). */
|
|
115
|
+
readonly blockFillSize?: number;
|
|
116
|
+
/** Transactions remaining in the tail when anticipatory warm-up fires (default {@link WARM_THRESHOLD_DEFAULT}). */
|
|
117
|
+
readonly warmThreshold?: number;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* Counts transactions committed into the current tail block and emits the {@link BlockFillSignal} that
|
|
122
|
+
* drives anticipatory warm-up and the block-filling rotation trigger (`docs/reactivity.md` §Tail rotation,
|
|
123
|
+
* §Anticipatory warm-up). One tracker per collection on the tail-cohort primary.
|
|
124
|
+
*/
|
|
125
|
+
export class BlockFillTracker {
|
|
126
|
+
readonly blockFillSize: number;
|
|
127
|
+
readonly warmThreshold: number;
|
|
128
|
+
private count = 0;
|
|
129
|
+
|
|
130
|
+
constructor(init: BlockFillTrackerInit = {}) {
|
|
131
|
+
const blockFillSize = init.blockFillSize ?? BLOCK_FILL_SIZE_DEFAULT;
|
|
132
|
+
const warmThreshold = init.warmThreshold ?? WARM_THRESHOLD_DEFAULT;
|
|
133
|
+
if (!Number.isInteger(blockFillSize) || blockFillSize < 1) {
|
|
134
|
+
throw new RangeError(`reactivity rotation: blockFillSize must be an integer >= 1, got ${blockFillSize}`);
|
|
135
|
+
}
|
|
136
|
+
if (!Number.isInteger(warmThreshold) || warmThreshold < 0 || warmThreshold >= blockFillSize) {
|
|
137
|
+
throw new RangeError(`reactivity rotation: warmThreshold must be an integer in 0..${blockFillSize - 1}, got ${warmThreshold}`);
|
|
138
|
+
}
|
|
139
|
+
this.blockFillSize = blockFillSize;
|
|
140
|
+
this.warmThreshold = warmThreshold;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/** Transactions committed into the current (not-yet-rotated) block. */
|
|
144
|
+
get transactionsInBlock(): number {
|
|
145
|
+
return this.count;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
/** The fill count at which anticipatory warm-up fires (`block_fill_size − warm_threshold`). */
|
|
149
|
+
get warmAt(): number {
|
|
150
|
+
return this.blockFillSize - this.warmThreshold;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
/**
|
|
154
|
+
* Record one committed transaction and return its fill signal. Emits `warmup` exactly when the block
|
|
155
|
+
* reaches `block_fill_size − warm_threshold`, and `filling` when it reaches `block_fill_size` — after
|
|
156
|
+
* which the counter resets for the next block.
|
|
157
|
+
*/
|
|
158
|
+
onCommit(): BlockFillSignal {
|
|
159
|
+
this.count += 1;
|
|
160
|
+
if (this.count >= this.blockFillSize) {
|
|
161
|
+
const count = this.count;
|
|
162
|
+
this.count = 0; // a new tail block is born; start counting it
|
|
163
|
+
return { kind: "filling", count };
|
|
164
|
+
}
|
|
165
|
+
if (this.count === this.warmAt && this.warmThreshold > 0) {
|
|
166
|
+
return { kind: "warmup", count: this.count, remaining: this.blockFillSize - this.count };
|
|
167
|
+
}
|
|
168
|
+
return { kind: "none", count: this.count };
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
/** Reset the block counter (e.g. an externally-observed rotation the tracker did not drive). */
|
|
172
|
+
reset(): void {
|
|
173
|
+
this.count = 0;
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
// --- drain gate (old tail serves renewals/replays, bounces new subscriptions) -
|
|
178
|
+
|
|
179
|
+
/** The kind of request arriving at the outgoing tail during its drain window. */
|
|
180
|
+
export type DrainOp =
|
|
181
|
+
/** A *fresh* subscription (`lastKnownRev == 0` or a never-seen subscriber) — bounced to the new tree. */
|
|
182
|
+
| "new_subscribe"
|
|
183
|
+
/** A renewal of an existing registration — served through the drain. */
|
|
184
|
+
| "renew"
|
|
185
|
+
/** A replay/backfill/resume request — served through the drain. */
|
|
186
|
+
| "replay";
|
|
187
|
+
|
|
188
|
+
/**
|
|
189
|
+
* A `Promoted`-shaped redirect to the rotated tree (`docs/reactivity.md` §Tail rotation step 2). Unlike the
|
|
190
|
+
* cohort-topic tier-based `Promoted`, this redirects to an entirely **new topic** — the new tail's tree at
|
|
191
|
+
* `coord_0(_, newTopicId)` — so it carries the successor's `newTailId` + derived `newTopicId`.
|
|
192
|
+
*/
|
|
193
|
+
export interface RotationRedirectV1 {
|
|
194
|
+
readonly v: 1;
|
|
195
|
+
readonly result: "rotated";
|
|
196
|
+
/** New tail block id the topic anchor rotated to, base64url. */
|
|
197
|
+
readonly newTailId: string;
|
|
198
|
+
/** `H(newTailId ‖ "reactivity")` — the new tree's topic id, base64url (the redirect target). */
|
|
199
|
+
readonly newTopicId: string;
|
|
200
|
+
/** Revision at which the rotation took effect. */
|
|
201
|
+
readonly effectiveAtRevision: number;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
/**
|
|
205
|
+
* Narrow an already-parsed value to a {@link RotationRedirectV1}, throwing {@link CohortWireError} on any
|
|
206
|
+
* defect. The redirect rides the recover reply envelope as `kind: "rotated"`
|
|
207
|
+
* ({@link import("./recover.js").RecoverReplyV1}); it carries no signature — like the other recover reply
|
|
208
|
+
* variants it is an unauthenticated routing hint (the subscriber re-walks and verifies the new tree on
|
|
209
|
+
* arrival), so its trust comes from the new tree, not the redirect.
|
|
210
|
+
*/
|
|
211
|
+
export function validateRotationRedirectV1(value: unknown): RotationRedirectV1 {
|
|
212
|
+
const what = "RotationRedirectV1";
|
|
213
|
+
const obj = asObject(value, what);
|
|
214
|
+
requireV1(obj, what);
|
|
215
|
+
if (obj["result"] !== "rotated") {
|
|
216
|
+
failWire(`${what}: field "result" must be "rotated"`);
|
|
217
|
+
}
|
|
218
|
+
return {
|
|
219
|
+
v: 1,
|
|
220
|
+
result: "rotated",
|
|
221
|
+
newTailId: b64urlField(reqString(obj, "newTailId", what), "newTailId", what),
|
|
222
|
+
newTopicId: b64urlField(reqString(obj, "newTopicId", what), "newTopicId", what),
|
|
223
|
+
effectiveAtRevision: reqIntInRange(obj, "effectiveAtRevision", what, 0),
|
|
224
|
+
};
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
/** The drain gate's decision for one inbound request. */
|
|
228
|
+
export type DrainDecision =
|
|
229
|
+
/** Serve normally (renewals / replays during the drain window). */
|
|
230
|
+
| { readonly kind: "serve" }
|
|
231
|
+
/** Bounce to the new tree (a new subscription during the drain window). */
|
|
232
|
+
| { readonly kind: "redirect"; readonly redirect: RotationRedirectV1 }
|
|
233
|
+
/** The drain window has elapsed — the outgoing tail holds nothing; the caller re-registers from `d_max`. */
|
|
234
|
+
| { readonly kind: "drained" };
|
|
235
|
+
|
|
236
|
+
/** Construction inputs for a {@link TailDrainGate}. */
|
|
237
|
+
export interface TailDrainGateInit {
|
|
238
|
+
/** Unix ms the rotation took effect (drain starts here). */
|
|
239
|
+
readonly rotatedAt: number;
|
|
240
|
+
/** New tail block id the topic rotated to, base64url. */
|
|
241
|
+
readonly newTailId: string;
|
|
242
|
+
/** Revision at which the rotation took effect. */
|
|
243
|
+
readonly effectiveAtRevision: number;
|
|
244
|
+
/** Drain duration `T_drain` (ms, default {@link T_DRAIN_MS}). */
|
|
245
|
+
readonly tDrainMs?: number;
|
|
246
|
+
/** Ring hash for the `newTopicId` derivation (must match cohort-topic routing). Default db-core SHA-256. */
|
|
247
|
+
readonly hash?: IRingHash;
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
/**
|
|
251
|
+
* The outgoing tail's drain state machine. For `T_drain` after a rotation it accepts **renewals** and
|
|
252
|
+
* serves **replays**, but bounces **new subscriptions** with a {@link RotationRedirectV1} to the new tree;
|
|
253
|
+
* after `T_drain` it reports `drained` for everything (the subscriber re-registers from `d_max`).
|
|
254
|
+
*/
|
|
255
|
+
export class TailDrainGate {
|
|
256
|
+
readonly rotatedAt: number;
|
|
257
|
+
readonly tDrainMs: number;
|
|
258
|
+
private readonly redirect: RotationRedirectV1;
|
|
259
|
+
|
|
260
|
+
constructor(init: TailDrainGateInit) {
|
|
261
|
+
const tDrainMs = init.tDrainMs ?? T_DRAIN_MS;
|
|
262
|
+
if (!(tDrainMs > 0)) {
|
|
263
|
+
throw new RangeError(`reactivity rotation: tDrainMs must be > 0, got ${tDrainMs}`);
|
|
264
|
+
}
|
|
265
|
+
this.rotatedAt = init.rotatedAt;
|
|
266
|
+
this.tDrainMs = tDrainMs;
|
|
267
|
+
const hash = init.hash ?? createRingHash();
|
|
268
|
+
const newTopicId = bytesToB64url(reactivityTopicId(b64urlToBytes(init.newTailId), hash));
|
|
269
|
+
this.redirect = {
|
|
270
|
+
v: 1,
|
|
271
|
+
result: "rotated",
|
|
272
|
+
newTailId: init.newTailId,
|
|
273
|
+
newTopicId,
|
|
274
|
+
effectiveAtRevision: init.effectiveAtRevision,
|
|
275
|
+
};
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
/** Unix ms the drain window closes. */
|
|
279
|
+
get drainEndsAt(): number {
|
|
280
|
+
return this.rotatedAt + this.tDrainMs;
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
/** True while the outgoing tail is still draining (within `T_drain` of the rotation). */
|
|
284
|
+
isDraining(now: number): boolean {
|
|
285
|
+
return now < this.drainEndsAt;
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
/** The redirect this gate hands a new subscription (exposed for the redirect transport / tests). */
|
|
289
|
+
get rotationRedirect(): RotationRedirectV1 {
|
|
290
|
+
return this.redirect;
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
/**
|
|
294
|
+
* Classify an inbound request. Within the drain window: `new_subscribe` → redirect, `renew`/`replay` →
|
|
295
|
+
* serve. After the window: `drained` (the old tail has released its forwarder state).
|
|
296
|
+
*/
|
|
297
|
+
classify(op: DrainOp, now: number): DrainDecision {
|
|
298
|
+
if (!this.isDraining(now)) {
|
|
299
|
+
return { kind: "drained" };
|
|
300
|
+
}
|
|
301
|
+
return op === "new_subscribe" ? { kind: "redirect", redirect: this.redirect } : { kind: "serve" };
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
// --- jittered re-registration (subscriber moves to the new tree) -------------
|
|
306
|
+
|
|
307
|
+
/** A subscriber's planned re-registration at the rotated tree. */
|
|
308
|
+
export interface ReRegistrationPlan {
|
|
309
|
+
/** New tail block id (raw bytes) to attach under. */
|
|
310
|
+
readonly newTailId: Uint8Array;
|
|
311
|
+
/** `H(newTailId ‖ "reactivity")` (raw bytes) — the new tree's topic id to register at. */
|
|
312
|
+
readonly newTopicId: Uint8Array;
|
|
313
|
+
/** The subscriber's existing `lastRevision`, carried so revisions stay continuous across the rotation. */
|
|
314
|
+
readonly lastRevision: number;
|
|
315
|
+
/** Unix ms at which to fire the re-registration (jittered over `T_rejoin_jitter`). */
|
|
316
|
+
readonly fireAt: number;
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
/** Inputs shared by the single and wave re-registration planners. */
|
|
320
|
+
interface ReRegistrationTarget {
|
|
321
|
+
/** The rotation hint (or any object carrying the successor `newTailId`), base64url. */
|
|
322
|
+
readonly hint: Pick<RotationHintV1, "newTailId">;
|
|
323
|
+
/** Ring hash for the `newTopicId` derivation. Default db-core SHA-256. */
|
|
324
|
+
readonly hash?: IRingHash;
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
/** Resolve `(newTailId, newTopicId)` raw bytes for a rotation target. */
|
|
328
|
+
function resolveRotationTarget(target: ReRegistrationTarget): { newTailId: Uint8Array; newTopicId: Uint8Array } {
|
|
329
|
+
const newTailId = b64urlToBytes(target.hint.newTailId);
|
|
330
|
+
const newTopicId = reactivityTopicId(newTailId, target.hash ?? createRingHash());
|
|
331
|
+
return { newTailId, newTopicId };
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
/**
|
|
335
|
+
* Plan **one** subscriber's re-registration at the rotated tree: derive the new `topicId` from the hint's
|
|
336
|
+
* `newTailId`, carry the subscriber's `lastRevision`, and draw a jittered `fireAt` over `T_rejoin_jitter`
|
|
337
|
+
* via the cohort-topic {@link RejoinJitter} (`scheduleRejoin` — a uniform offset, decorrelating this
|
|
338
|
+
* subscriber's re-join from its peers).
|
|
339
|
+
*/
|
|
340
|
+
export function planReRegistration(opts: ReRegistrationTarget & { lastRevision: number; now: number; jitter: RejoinJitter }): ReRegistrationPlan {
|
|
341
|
+
const { newTailId, newTopicId } = resolveRotationTarget(opts);
|
|
342
|
+
return { newTailId, newTopicId, lastRevision: opts.lastRevision, fireAt: opts.jitter.scheduleRejoin(opts.now) };
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
/**
|
|
346
|
+
* Plan a **whole wave** of subscribers' re-registrations at the rotated tree. Uses the
|
|
347
|
+
* {@link RejoinJitter.scheduleWave} hard-bound staggering so any `T_rejoin_jitter`-long window holds at
|
|
348
|
+
* most the injected jitter's `capPromote` arrivals — the new tail never sees more than
|
|
349
|
+
* `capPromote / T_rejoin_jitter` re-registrations per second.
|
|
350
|
+
*
|
|
351
|
+
* **Caller contract:** rotation is governed by the *fast-promote* bound, so the host MUST build this `jitter`
|
|
352
|
+
* with `capPromote = cap_promote_fast` ({@link import("../cohort-topic/promotion.js").DEFAULT_CAP_PROMOTE_FAST}
|
|
353
|
+
* = 32), **not** the default `createRejoinJitter()` cap (the cohort-failure `cap_promote = 64`). With the
|
|
354
|
+
* default cap the wave bounds to 64/window and silently overruns the documented rotation burst ceiling
|
|
355
|
+
* (`docs/reactivity.md` §Tail rotation rotation-cost). Returns one plan per subscriber, ascending by `fireAt`.
|
|
356
|
+
*/
|
|
357
|
+
export function planReRegistrationWave(
|
|
358
|
+
opts: ReRegistrationTarget & { subscribers: readonly { readonly lastRevision: number }[]; now: number; jitter: RejoinJitter },
|
|
359
|
+
): ReRegistrationPlan[] {
|
|
360
|
+
const { newTailId, newTopicId } = resolveRotationTarget(opts);
|
|
361
|
+
const fireAts = opts.jitter.scheduleWave(opts.subscribers.length, opts.now);
|
|
362
|
+
return opts.subscribers.map((s, i) => ({ newTailId, newTopicId, lastRevision: s.lastRevision, fireAt: fireAts[i]! }));
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
// --- buffer-to-checkpoint handoff (the only state migrated across a rotation) -
|
|
366
|
+
|
|
367
|
+
/** The state migrated from the outgoing tail to the new tail on rotation. */
|
|
368
|
+
export interface RotationHandoff {
|
|
369
|
+
/** The final checkpoint covering `[lastCheckpoint.toRevision + 1, rotationRevision]`. */
|
|
370
|
+
readonly checkpoint: CheckpointSummary;
|
|
371
|
+
/** The revision the rotation took effect at (the high edge the handoff checkpoint covers). */
|
|
372
|
+
readonly rotationRevision: number;
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
/**
|
|
376
|
+
* Fold the outgoing tail's replay buffer into the final {@link CheckpointSummary} handed to the new tail
|
|
377
|
+
* (`docs/reactivity.md` §Tail rotation step 5 — the **only** state migration across a rotation). The
|
|
378
|
+
* handoff covers `[lastCheckpoint.toRevision + 1, rotationRevision]`: the revisions still live in the
|
|
379
|
+
* replay ring (above the rolling checkpoint's high edge) up to the rotation revision. Returns `undefined`
|
|
380
|
+
* when the outgoing tail's ring is empty (nothing to migrate). Reuses the rolling checkpoint's own fold
|
|
381
|
+
* options so the merged digest is byte-identical to a steady-state checkpoint.
|
|
382
|
+
*/
|
|
383
|
+
export function buildRotationHandoffCheckpoint(state: PushState, opts: { rotationRevision?: number } = {}): RotationHandoff | undefined {
|
|
384
|
+
const ringLow = state.replayBuffer.lowRevision;
|
|
385
|
+
const ringHigh = state.replayBuffer.highRevision;
|
|
386
|
+
if (ringLow === undefined || ringHigh === undefined) {
|
|
387
|
+
return undefined; // nothing live in the ring to migrate
|
|
388
|
+
}
|
|
389
|
+
const rotationRevision = opts.rotationRevision ?? ringHigh;
|
|
390
|
+
// The handoff continues immediately above the rolling checkpoint's high edge; when no checkpoint has
|
|
391
|
+
// formed yet, that is the ring's low edge. The two windows stack, so `from` abuts `lastCheckpoint`.
|
|
392
|
+
const priorCheckpointTo = state.checkpoint.toRevision;
|
|
393
|
+
const from = priorCheckpointTo !== undefined ? priorCheckpointTo + 1 : ringLow;
|
|
394
|
+
const entries = state.replayBuffer.range(from, rotationRevision);
|
|
395
|
+
const checkpoint = buildCheckpointSummary(entries, state.checkpoint.foldOptions);
|
|
396
|
+
if (checkpoint === undefined) {
|
|
397
|
+
return undefined;
|
|
398
|
+
}
|
|
399
|
+
return { checkpoint, rotationRevision };
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
/**
|
|
403
|
+
* Land a {@link RotationHandoff} at the new tail's {@link PushState}: record the inherited checkpoint so a
|
|
404
|
+
* `ResumeV1` whose span crosses the rotation is recoverable from the new tail (the new tail "holds the old
|
|
405
|
+
* checkpoint", `docs/reactivity.md` §Tail rotation step 5). The handoff is a one-time migration — it does
|
|
406
|
+
* not feed the new tail's rolling checkpoint (that rolls from the new tree's own replay-ring eviction).
|
|
407
|
+
*/
|
|
408
|
+
export function applyRotationHandoff(newTailState: PushState, handoff: RotationHandoff): void {
|
|
409
|
+
newTailState.adoptRotationCheckpoint(handoff.checkpoint);
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
/** True iff `summary` covers `revision` (its inclusive `[fromRevision, toRevision]` span). */
|
|
413
|
+
export function checkpointCovers(summary: CheckpointSummary, revision: number): boolean {
|
|
414
|
+
return revision >= summary.fromRevision && revision <= summary.toRevision;
|
|
415
|
+
}
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Reactivity — subscriber delivery path (`docs/reactivity.md` §Delivery).
|
|
3
|
+
*
|
|
4
|
+
* A subscriber receiving a notification:
|
|
5
|
+
* 1. **verifies** `sig` against the cached tail-cohort `MembershipCertV1` (the {@link NotificationVerifier}
|
|
6
|
+
* owns the **one fetch-and-retry** on a stale cache);
|
|
7
|
+
* 2. checks `revision == lastRevision + 1`. If not, requests a backfill for the gap
|
|
8
|
+
* `[lastRevision + 1, revision]` via the {@link requestBackfill} seam (the `BackfillV1` request shape
|
|
9
|
+
* lands in [reactivity-backfill-resume-checkpoints]; this ticket only *detects* the gap and calls the
|
|
10
|
+
* hook);
|
|
11
|
+
* 3. updates `lastRevision` once revisions are contiguous;
|
|
12
|
+
* 4. surfaces the notification to the application layer.
|
|
13
|
+
*
|
|
14
|
+
* Subscribers dedupe by `(collectionId, revision)`; duplicates from forwarder retries are discarded.
|
|
15
|
+
* A fresh subscription (`lastKnownRev == 0`, uninitialized) adopts the first verified notification as its
|
|
16
|
+
* baseline rather than demanding a backfill from revision 1.
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
import type { NotificationV1 } from "./wire.js";
|
|
20
|
+
import type { NotificationVerifier } from "./verify.js";
|
|
21
|
+
|
|
22
|
+
/** The delivery outcome for one inbound notification. */
|
|
23
|
+
export type DeliveryOutcome =
|
|
24
|
+
/** Verified and contiguous: surfaced to the application, `lastRevision` advanced. */
|
|
25
|
+
| "delivered"
|
|
26
|
+
/** `(collectionId, revision)` already delivered: discarded. */
|
|
27
|
+
| "duplicate"
|
|
28
|
+
/** Verified but ahead of `lastRevision + 1`: a backfill was requested; not yet surfaced. */
|
|
29
|
+
| "gap"
|
|
30
|
+
/** Signature did not verify (even after the one refetch): dropped. */
|
|
31
|
+
| "untrusted"
|
|
32
|
+
/** For a different collection than this subscription: ignored. */
|
|
33
|
+
| "foreign";
|
|
34
|
+
|
|
35
|
+
/** Application sink + backfill seam for a {@link ReactivitySubscriber}. */
|
|
36
|
+
export interface ReactivitySubscriberDeps {
|
|
37
|
+
/** Collection this subscription tracks, base64url (matches {@link NotificationV1.collectionId}). */
|
|
38
|
+
readonly collectionId: string;
|
|
39
|
+
/** Verifies inbound notifications (owns the single stale-cache refetch). */
|
|
40
|
+
readonly verifier: NotificationVerifier;
|
|
41
|
+
/** Surface a verified, contiguous notification to the application. */
|
|
42
|
+
readonly deliver: (n: NotificationV1) => void;
|
|
43
|
+
/**
|
|
44
|
+
* Request a backfill for the inclusive revision gap `[from, to]`. The `BackfillV1` request/response
|
|
45
|
+
* lands in [reactivity-backfill-resume-checkpoints]; the returned entries are fed back through
|
|
46
|
+
* {@link ReactivitySubscriber.onNotification} to close the gap. Absent ⇒ the gap is recorded only.
|
|
47
|
+
*/
|
|
48
|
+
readonly requestBackfill?: (from: number, to: number) => void;
|
|
49
|
+
/** Last revision already held; `0` (default) ⇒ fresh subscribe, adopt the first notification as baseline. */
|
|
50
|
+
readonly lastKnownRev?: number;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/** Drives the subscriber-side verify → contiguity → deliver path for one collection. */
|
|
54
|
+
export interface ReactivitySubscriber {
|
|
55
|
+
/** Last contiguously-delivered revision (`lastKnownRev` until the first delivery). */
|
|
56
|
+
readonly lastRevision: number;
|
|
57
|
+
/** Process one inbound notification; returns the {@link DeliveryOutcome}. */
|
|
58
|
+
onNotification(n: NotificationV1): Promise<DeliveryOutcome>;
|
|
59
|
+
/**
|
|
60
|
+
* Advance the contiguity head to `revision` because a **verified parent checkpoint** summarized every
|
|
61
|
+
* revision up to it (`docs/reactivity.md` §Parent checkpoint summaries). The subscriber then treats
|
|
62
|
+
* `revision + 1` as the next contiguous revision, so the checkpoint's `recentEntries` replay without a
|
|
63
|
+
* spurious gap/backfill for the digest-covered range. Only advances — never rewinds past delivered
|
|
64
|
+
* state. The merged digest itself is applied by the application; this just moves the contiguity head.
|
|
65
|
+
*/
|
|
66
|
+
rebaseline(revision: number): void;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
class CollectionSubscriber implements ReactivitySubscriber {
|
|
70
|
+
private last: number;
|
|
71
|
+
private initialized: boolean;
|
|
72
|
+
|
|
73
|
+
constructor(private readonly deps: ReactivitySubscriberDeps) {
|
|
74
|
+
const lastKnownRev = deps.lastKnownRev ?? 0;
|
|
75
|
+
this.last = lastKnownRev;
|
|
76
|
+
// A fresh subscribe (lastKnownRev == 0) has no baseline yet; the first verified notification sets it.
|
|
77
|
+
this.initialized = lastKnownRev > 0;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
get lastRevision(): number {
|
|
81
|
+
return this.last;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
rebaseline(revision: number): void {
|
|
85
|
+
// A verified checkpoint summarized everything up to `revision`; jump the head forward (never back).
|
|
86
|
+
if (!this.initialized || revision > this.last) {
|
|
87
|
+
this.last = revision;
|
|
88
|
+
this.initialized = true;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
async onNotification(n: NotificationV1): Promise<DeliveryOutcome> {
|
|
93
|
+
if (n.collectionId !== this.deps.collectionId) {
|
|
94
|
+
return "foreign";
|
|
95
|
+
}
|
|
96
|
+
// Verify first (one fetch-and-retry is internal to the verifier). An untrusted notification is
|
|
97
|
+
// dropped before any (collectionId, revision) dedupe or lastRevision update.
|
|
98
|
+
const verdict = await this.deps.verifier.verify(n);
|
|
99
|
+
if (verdict !== "verified") {
|
|
100
|
+
return "untrusted";
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// Fresh subscription: adopt the first verified notification as the contiguity baseline.
|
|
104
|
+
if (!this.initialized) {
|
|
105
|
+
this.initialized = true;
|
|
106
|
+
this.last = n.revision;
|
|
107
|
+
this.deps.deliver(n);
|
|
108
|
+
return "delivered";
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// Dedupe by (collectionId, revision): anything at or below the contiguous head is already delivered.
|
|
112
|
+
if (n.revision <= this.last) {
|
|
113
|
+
return "duplicate";
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// Contiguity check.
|
|
117
|
+
if (n.revision === this.last + 1) {
|
|
118
|
+
this.last = n.revision;
|
|
119
|
+
this.deps.deliver(n);
|
|
120
|
+
return "delivered";
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
// Gap: request the missing range (inclusive of `revision`); the backfilled entries re-enter here.
|
|
124
|
+
this.deps.requestBackfill?.(this.last + 1, n.revision);
|
|
125
|
+
return "gap";
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/** Build a {@link ReactivitySubscriber} for one collection. */
|
|
130
|
+
export function createReactivitySubscriber(deps: ReactivitySubscriberDeps): ReactivitySubscriber {
|
|
131
|
+
return new CollectionSubscriber(deps);
|
|
132
|
+
}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Reactivity — subscriber-side subscription state (`docs/reactivity.md` §Subscription).
|
|
3
|
+
*
|
|
4
|
+
* Subscribing to a collection is an **ordinary cohort-topic registration** at tier T3 with a reactivity
|
|
5
|
+
* `appPayload`: `topicId = H(currentTailId(C) ‖ "reactivity")`, the configured TTL (Edge 60 s / Core 90 s),
|
|
6
|
+
* and the cohort-topic walk-toward-root / willingness / promotion / TTL-renewal all reused unchanged.
|
|
7
|
+
* This module owns only the subscriber-side bookkeeping struct and the `appPayload` builder; the db-p2p
|
|
8
|
+
* subscription manager drives the cohort-topic `RegisterV1`.
|
|
9
|
+
*
|
|
10
|
+
* `tailIdAtAttach` is the subscriber-side detector for tail rotation (the whole-tree migration the
|
|
11
|
+
* rotation ticket handles); `cohortEpoch` detects membership drift within the topic.
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
import { encodeSubscribeAppPayload, type SubscribeAppPayloadV1 } from "./wire.js";
|
|
15
|
+
|
|
16
|
+
/** Subscriber-side live state for one active subscription (`docs/reactivity.md` §Subscriber-side state). */
|
|
17
|
+
export interface ActiveSubscription {
|
|
18
|
+
/** Stable collection identity. */
|
|
19
|
+
readonly collectionId: Uint8Array;
|
|
20
|
+
/** Current tail-anchored topic id. */
|
|
21
|
+
readonly topicId: Uint8Array;
|
|
22
|
+
/** Tail block id at registration time — detects tail rotation. */
|
|
23
|
+
readonly tailIdAtAttach: Uint8Array;
|
|
24
|
+
/** Serving cohort member. */
|
|
25
|
+
primary: Uint8Array;
|
|
26
|
+
/** Warm-failover cohort members. */
|
|
27
|
+
backups: Uint8Array[];
|
|
28
|
+
/** Cohort member hint set for fast re-attach. */
|
|
29
|
+
cohortHint: Uint8Array[];
|
|
30
|
+
/** Cohort epoch for membership-drift detection. */
|
|
31
|
+
cohortEpoch: Uint8Array;
|
|
32
|
+
/** Last contiguously-delivered revision. */
|
|
33
|
+
lastRevision: number;
|
|
34
|
+
/** Unix ms of the last delivery. */
|
|
35
|
+
lastDeliveredAt: number;
|
|
36
|
+
/** Unix ms the subscription attached. */
|
|
37
|
+
attachedAt: number;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/** Parameters for building a subscribe `appPayload`. */
|
|
41
|
+
export interface SubscribeParams {
|
|
42
|
+
/** Collection id, base64url. */
|
|
43
|
+
readonly collectionId: string;
|
|
44
|
+
/** Tail block id at attach time, base64url. */
|
|
45
|
+
readonly tailIdAtAttach: string;
|
|
46
|
+
/** Last revision already held; `0` for a fresh subscribe. */
|
|
47
|
+
readonly lastKnownRev?: number;
|
|
48
|
+
/** Max delta bytes accepted; `0` declines deltas (Edge). */
|
|
49
|
+
readonly deltaMaxBytes: number;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/** Build the validated {@link SubscribeAppPayloadV1} for a subscription. */
|
|
53
|
+
export function buildSubscribeAppPayload(params: SubscribeParams): SubscribeAppPayloadV1 {
|
|
54
|
+
return {
|
|
55
|
+
kind: "reactivity",
|
|
56
|
+
collectionId: params.collectionId,
|
|
57
|
+
tailIdAtAttach: params.tailIdAtAttach,
|
|
58
|
+
lastKnownRev: params.lastKnownRev ?? 0,
|
|
59
|
+
deltaMaxBytes: params.deltaMaxBytes,
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/** Build the opaque `RegisterV1.appPayload` bytes for a subscription. */
|
|
64
|
+
export function subscribeAppPayloadBytes(params: SubscribeParams): Uint8Array {
|
|
65
|
+
return encodeSubscribeAppPayload(buildSubscribeAppPayload(params));
|
|
66
|
+
}
|