@optimystic/db-p2p 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/README.md +6 -6
- package/dist/src/cluster/block-transfer-service.d.ts +67 -8
- package/dist/src/cluster/block-transfer-service.d.ts.map +1 -1
- package/dist/src/cluster/block-transfer-service.js +101 -62
- package/dist/src/cluster/block-transfer-service.js.map +1 -1
- package/dist/src/cluster/client.d.ts +18 -1
- package/dist/src/cluster/client.d.ts.map +1 -1
- package/dist/src/cluster/client.js +55 -39
- package/dist/src/cluster/client.js.map +1 -1
- package/dist/src/cluster/cluster-error.d.ts +38 -0
- package/dist/src/cluster/cluster-error.d.ts.map +1 -0
- package/dist/src/cluster/cluster-error.js +51 -0
- package/dist/src/cluster/cluster-error.js.map +1 -0
- package/dist/src/cluster/cluster-repo.d.ts +135 -3
- package/dist/src/cluster/cluster-repo.d.ts.map +1 -1
- package/dist/src/cluster/cluster-repo.js +333 -58
- package/dist/src/cluster/cluster-repo.js.map +1 -1
- package/dist/src/cluster/commit-cert.d.ts +63 -0
- package/dist/src/cluster/commit-cert.d.ts.map +1 -0
- package/dist/src/cluster/commit-cert.js +108 -0
- package/dist/src/cluster/commit-cert.js.map +1 -0
- package/dist/src/cluster/service.d.ts +46 -11
- package/dist/src/cluster/service.d.ts.map +1 -1
- package/dist/src/cluster/service.js +97 -33
- package/dist/src/cluster/service.js.map +1 -1
- package/dist/src/cluster/spread-on-churn.d.ts +13 -0
- package/dist/src/cluster/spread-on-churn.d.ts.map +1 -1
- package/dist/src/cluster/spread-on-churn.js +34 -4
- package/dist/src/cluster/spread-on-churn.js.map +1 -1
- package/dist/src/cohort-topic/bootstrap-evidence-builder.d.ts +59 -0
- package/dist/src/cohort-topic/bootstrap-evidence-builder.d.ts.map +1 -0
- package/dist/src/cohort-topic/bootstrap-evidence-builder.js +79 -0
- package/dist/src/cohort-topic/bootstrap-evidence-builder.js.map +1 -0
- package/dist/src/cohort-topic/bootstrap-evidence-verifiers.d.ts +81 -0
- package/dist/src/cohort-topic/bootstrap-evidence-verifiers.d.ts.map +1 -0
- package/dist/src/cohort-topic/bootstrap-evidence-verifiers.js +93 -0
- package/dist/src/cohort-topic/bootstrap-evidence-verifiers.js.map +1 -0
- package/dist/src/cohort-topic/bootstrap-parent-reference.d.ts +108 -0
- package/dist/src/cohort-topic/bootstrap-parent-reference.d.ts.map +1 -0
- package/dist/src/cohort-topic/bootstrap-parent-reference.js +108 -0
- package/dist/src/cohort-topic/bootstrap-parent-reference.js.map +1 -0
- package/dist/src/cohort-topic/change-bridge.d.ts +65 -0
- package/dist/src/cohort-topic/change-bridge.d.ts.map +1 -0
- package/dist/src/cohort-topic/change-bridge.js +76 -0
- package/dist/src/cohort-topic/change-bridge.js.map +1 -0
- package/dist/src/cohort-topic/cohort-gossip-driver.d.ts +74 -0
- package/dist/src/cohort-topic/cohort-gossip-driver.d.ts.map +1 -0
- package/dist/src/cohort-topic/cohort-gossip-driver.js +87 -0
- package/dist/src/cohort-topic/cohort-gossip-driver.js.map +1 -0
- package/dist/src/cohort-topic/cohort-gossip-transport.d.ts +42 -0
- package/dist/src/cohort-topic/cohort-gossip-transport.d.ts.map +1 -0
- package/dist/src/cohort-topic/cohort-gossip-transport.js +57 -0
- package/dist/src/cohort-topic/cohort-gossip-transport.js.map +1 -0
- package/dist/src/cohort-topic/fret-trust-anchor.d.ts +100 -0
- package/dist/src/cohort-topic/fret-trust-anchor.d.ts.map +1 -0
- package/dist/src/cohort-topic/fret-trust-anchor.js +115 -0
- package/dist/src/cohort-topic/fret-trust-anchor.js.map +1 -0
- package/dist/src/cohort-topic/host.d.ts +573 -0
- package/dist/src/cohort-topic/host.d.ts.map +1 -0
- package/dist/src/cohort-topic/host.js +1460 -0
- package/dist/src/cohort-topic/host.js.map +1 -0
- package/dist/src/cohort-topic/index.d.ts +14 -0
- package/dist/src/cohort-topic/index.d.ts.map +1 -0
- package/dist/src/cohort-topic/index.js +14 -0
- package/dist/src/cohort-topic/index.js.map +1 -0
- package/dist/src/cohort-topic/membership-publish-sink.d.ts +14 -0
- package/dist/src/cohort-topic/membership-publish-sink.d.ts.map +1 -0
- package/dist/src/cohort-topic/membership-publish-sink.js +17 -0
- package/dist/src/cohort-topic/membership-publish-sink.js.map +1 -0
- package/dist/src/cohort-topic/membership-source.d.ts +36 -0
- package/dist/src/cohort-topic/membership-source.d.ts.map +1 -0
- package/dist/src/cohort-topic/membership-source.js +58 -0
- package/dist/src/cohort-topic/membership-source.js.map +1 -0
- package/dist/src/cohort-topic/peer-codec.d.ts +18 -0
- package/dist/src/cohort-topic/peer-codec.d.ts.map +1 -0
- package/dist/src/cohort-topic/peer-codec.js +26 -0
- package/dist/src/cohort-topic/peer-codec.js.map +1 -0
- package/dist/src/cohort-topic/peer-sig.d.ts +52 -0
- package/dist/src/cohort-topic/peer-sig.d.ts.map +1 -0
- package/dist/src/cohort-topic/peer-sig.js +83 -0
- package/dist/src/cohort-topic/peer-sig.js.map +1 -0
- package/dist/src/cohort-topic/protocols.d.ts +49 -0
- package/dist/src/cohort-topic/protocols.d.ts.map +1 -0
- package/dist/src/cohort-topic/protocols.js +58 -0
- package/dist/src/cohort-topic/protocols.js.map +1 -0
- package/dist/src/cohort-topic/reactivity-membership-gate.d.ts +49 -0
- package/dist/src/cohort-topic/reactivity-membership-gate.d.ts.map +1 -0
- package/dist/src/cohort-topic/reactivity-membership-gate.js +42 -0
- package/dist/src/cohort-topic/reactivity-membership-gate.js.map +1 -0
- package/dist/src/cohort-topic/size-estimator.d.ts +16 -0
- package/dist/src/cohort-topic/size-estimator.d.ts.map +1 -0
- package/dist/src/cohort-topic/size-estimator.js +16 -0
- package/dist/src/cohort-topic/size-estimator.js.map +1 -0
- package/dist/src/cohort-topic/stream-util.d.ts +19 -0
- package/dist/src/cohort-topic/stream-util.d.ts.map +1 -0
- package/dist/src/cohort-topic/stream-util.js +76 -0
- package/dist/src/cohort-topic/stream-util.js.map +1 -0
- package/dist/src/cohort-topic/threshold-crypto.d.ts +94 -0
- package/dist/src/cohort-topic/threshold-crypto.d.ts.map +1 -0
- package/dist/src/cohort-topic/threshold-crypto.js +178 -0
- package/dist/src/cohort-topic/threshold-crypto.js.map +1 -0
- package/dist/src/cohort-topic/topic-router.d.ts +41 -0
- package/dist/src/cohort-topic/topic-router.d.ts.map +1 -0
- package/dist/src/cohort-topic/topic-router.js +63 -0
- package/dist/src/cohort-topic/topic-router.js.map +1 -0
- package/dist/src/dispute/cascade.d.ts +179 -0
- package/dist/src/dispute/cascade.d.ts.map +1 -0
- package/dist/src/dispute/cascade.js +315 -0
- package/dist/src/dispute/cascade.js.map +1 -0
- package/dist/src/dispute/client.d.ts +2 -1
- package/dist/src/dispute/client.d.ts.map +1 -1
- package/dist/src/dispute/client.js +12 -3
- package/dist/src/dispute/client.js.map +1 -1
- package/dist/src/dispute/dispute-service.d.ts +41 -1
- package/dist/src/dispute/dispute-service.d.ts.map +1 -1
- package/dist/src/dispute/dispute-service.js +147 -21
- package/dist/src/dispute/dispute-service.js.map +1 -1
- package/dist/src/dispute/index.d.ts +2 -0
- package/dist/src/dispute/index.d.ts.map +1 -1
- package/dist/src/dispute/index.js +2 -0
- package/dist/src/dispute/index.js.map +1 -1
- package/dist/src/dispute/invalidation.d.ts +266 -0
- package/dist/src/dispute/invalidation.d.ts.map +1 -0
- package/dist/src/dispute/invalidation.js +405 -0
- package/dist/src/dispute/invalidation.js.map +1 -0
- package/dist/src/dispute/types.d.ts +14 -0
- package/dist/src/dispute/types.d.ts.map +1 -1
- package/dist/src/dispute/types.js.map +1 -1
- package/dist/src/index.d.ts +4 -0
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js +4 -0
- package/dist/src/index.js.map +1 -1
- package/dist/src/libp2p-key-network.d.ts +7 -0
- package/dist/src/libp2p-key-network.d.ts.map +1 -1
- package/dist/src/libp2p-key-network.js +49 -5
- package/dist/src/libp2p-key-network.js.map +1 -1
- package/dist/src/libp2p-node-base.d.ts +45 -3
- package/dist/src/libp2p-node-base.d.ts.map +1 -1
- package/dist/src/libp2p-node-base.js +522 -5
- package/dist/src/libp2p-node-base.js.map +1 -1
- package/dist/src/matchmaking/aggregate-counts.d.ts +67 -0
- package/dist/src/matchmaking/aggregate-counts.d.ts.map +1 -0
- package/dist/src/matchmaking/aggregate-counts.js +60 -0
- package/dist/src/matchmaking/aggregate-counts.js.map +1 -0
- package/dist/src/matchmaking/index.d.ts +20 -0
- package/dist/src/matchmaking/index.d.ts.map +1 -0
- package/dist/src/matchmaking/index.js +20 -0
- package/dist/src/matchmaking/index.js.map +1 -0
- package/dist/src/matchmaking/module.d.ts +181 -0
- package/dist/src/matchmaking/module.d.ts.map +1 -0
- package/dist/src/matchmaking/module.js +221 -0
- package/dist/src/matchmaking/module.js.map +1 -0
- package/dist/src/matchmaking/protocols.d.ts +37 -0
- package/dist/src/matchmaking/protocols.d.ts.map +1 -0
- package/dist/src/matchmaking/protocols.js +42 -0
- package/dist/src/matchmaking/protocols.js.map +1 -0
- package/dist/src/matchmaking/provider-manager.d.ts +52 -0
- package/dist/src/matchmaking/provider-manager.d.ts.map +1 -0
- package/dist/src/matchmaking/provider-manager.js +74 -0
- package/dist/src/matchmaking/provider-manager.js.map +1 -0
- package/dist/src/matchmaking/query-handler.d.ts +34 -0
- package/dist/src/matchmaking/query-handler.d.ts.map +1 -0
- package/dist/src/matchmaking/query-handler.js +62 -0
- package/dist/src/matchmaking/query-handler.js.map +1 -0
- package/dist/src/matchmaking/query-transport.d.ts +180 -0
- package/dist/src/matchmaking/query-transport.d.ts.map +1 -0
- package/dist/src/matchmaking/query-transport.js +315 -0
- package/dist/src/matchmaking/query-transport.js.map +1 -0
- package/dist/src/matchmaking/seeker-manager.d.ts +39 -0
- package/dist/src/matchmaking/seeker-manager.d.ts.map +1 -0
- package/dist/src/matchmaking/seeker-manager.js +49 -0
- package/dist/src/matchmaking/seeker-manager.js.map +1 -0
- package/dist/src/matchmaking/seeker-walk-client.d.ts +127 -0
- package/dist/src/matchmaking/seeker-walk-client.d.ts.map +1 -0
- package/dist/src/matchmaking/seeker-walk-client.js +189 -0
- package/dist/src/matchmaking/seeker-walk-client.js.map +1 -0
- package/dist/src/matchmaking/traffic-validation.d.ts +128 -0
- package/dist/src/matchmaking/traffic-validation.d.ts.map +1 -0
- package/dist/src/matchmaking/traffic-validation.js +105 -0
- package/dist/src/matchmaking/traffic-validation.js.map +1 -0
- package/dist/src/network/network-manager-service.d.ts +4 -2
- package/dist/src/network/network-manager-service.d.ts.map +1 -1
- package/dist/src/network/network-manager-service.js +9 -3
- package/dist/src/network/network-manager-service.js.map +1 -1
- package/dist/src/protocol-client.d.ts +13 -0
- package/dist/src/protocol-client.d.ts.map +1 -1
- package/dist/src/protocol-client.js +74 -2
- package/dist/src/protocol-client.js.map +1 -1
- package/dist/src/reactivity/forwarder-host.d.ts +198 -0
- package/dist/src/reactivity/forwarder-host.d.ts.map +1 -0
- package/dist/src/reactivity/forwarder-host.js +355 -0
- package/dist/src/reactivity/forwarder-host.js.map +1 -0
- package/dist/src/reactivity/index.d.ts +19 -0
- package/dist/src/reactivity/index.d.ts.map +1 -0
- package/dist/src/reactivity/index.js +19 -0
- package/dist/src/reactivity/index.js.map +1 -0
- package/dist/src/reactivity/notify-transport.d.ts +70 -0
- package/dist/src/reactivity/notify-transport.d.ts.map +1 -0
- package/dist/src/reactivity/notify-transport.js +111 -0
- package/dist/src/reactivity/notify-transport.js.map +1 -0
- package/dist/src/reactivity/origination-manager.d.ts +110 -0
- package/dist/src/reactivity/origination-manager.d.ts.map +1 -0
- package/dist/src/reactivity/origination-manager.js +129 -0
- package/dist/src/reactivity/origination-manager.js.map +1 -0
- package/dist/src/reactivity/protocols.d.ts +43 -0
- package/dist/src/reactivity/protocols.d.ts.map +1 -0
- package/dist/src/reactivity/protocols.js +50 -0
- package/dist/src/reactivity/protocols.js.map +1 -0
- package/dist/src/reactivity/push-state-gossip.d.ts +132 -0
- package/dist/src/reactivity/push-state-gossip.d.ts.map +1 -0
- package/dist/src/reactivity/push-state-gossip.js +231 -0
- package/dist/src/reactivity/push-state-gossip.js.map +1 -0
- package/dist/src/reactivity/recover-transport.d.ts +167 -0
- package/dist/src/reactivity/recover-transport.d.ts.map +1 -0
- package/dist/src/reactivity/recover-transport.js +308 -0
- package/dist/src/reactivity/recover-transport.js.map +1 -0
- package/dist/src/reactivity/rotation-rereg-scheduler.d.ts +142 -0
- package/dist/src/reactivity/rotation-rereg-scheduler.d.ts.map +1 -0
- package/dist/src/reactivity/rotation-rereg-scheduler.js +223 -0
- package/dist/src/reactivity/rotation-rereg-scheduler.js.map +1 -0
- package/dist/src/reactivity/subscriber-registry.d.ts +47 -0
- package/dist/src/reactivity/subscriber-registry.d.ts.map +1 -0
- package/dist/src/reactivity/subscriber-registry.js +87 -0
- package/dist/src/reactivity/subscriber-registry.js.map +1 -0
- package/dist/src/reactivity/subscription-manager.d.ts +221 -0
- package/dist/src/reactivity/subscription-manager.d.ts.map +1 -0
- package/dist/src/reactivity/subscription-manager.js +302 -0
- package/dist/src/reactivity/subscription-manager.js.map +1 -0
- package/dist/src/reactivity/topic-bytes.d.ts +32 -0
- package/dist/src/reactivity/topic-bytes.d.ts.map +1 -0
- package/dist/src/reactivity/topic-bytes.js +34 -0
- package/dist/src/reactivity/topic-bytes.js.map +1 -0
- package/dist/src/repo/client.d.ts.map +1 -1
- package/dist/src/repo/client.js +51 -23
- package/dist/src/repo/client.js.map +1 -1
- package/dist/src/repo/service.d.ts +46 -1
- package/dist/src/repo/service.d.ts.map +1 -1
- package/dist/src/repo/service.js +79 -45
- package/dist/src/repo/service.js.map +1 -1
- package/dist/src/rpc-deadline.d.ts +37 -0
- package/dist/src/rpc-deadline.d.ts.map +1 -0
- package/dist/src/rpc-deadline.js +31 -0
- package/dist/src/rpc-deadline.js.map +1 -0
- package/dist/src/storage/block-storage.d.ts +2 -0
- package/dist/src/storage/block-storage.d.ts.map +1 -1
- package/dist/src/storage/block-storage.js +95 -2
- package/dist/src/storage/block-storage.js.map +1 -1
- package/dist/src/storage/i-block-storage.d.ts +25 -0
- package/dist/src/storage/i-block-storage.d.ts.map +1 -1
- package/dist/src/storage/storage-repo.d.ts +53 -2
- package/dist/src/storage/storage-repo.d.ts.map +1 -1
- package/dist/src/storage/storage-repo.js +193 -9
- package/dist/src/storage/storage-repo.js.map +1 -1
- package/dist/src/sync/client.d.ts +4 -1
- package/dist/src/sync/client.d.ts.map +1 -1
- package/dist/src/sync/client.js +5 -2
- package/dist/src/sync/client.js.map +1 -1
- package/dist/src/testing/cohort-topic-mesh-harness.d.ts +219 -0
- package/dist/src/testing/cohort-topic-mesh-harness.d.ts.map +1 -0
- package/dist/src/testing/cohort-topic-mesh-harness.js +467 -0
- package/dist/src/testing/cohort-topic-mesh-harness.js.map +1 -0
- package/dist/src/testing/matchmaking-mesh-harness.d.ts +187 -0
- package/dist/src/testing/matchmaking-mesh-harness.d.ts.map +1 -0
- package/dist/src/testing/matchmaking-mesh-harness.js +358 -0
- package/dist/src/testing/matchmaking-mesh-harness.js.map +1 -0
- package/dist/src/testing/mesh-harness.d.ts.map +1 -1
- package/dist/src/testing/mesh-harness.js +23 -1
- package/dist/src/testing/mesh-harness.js.map +1 -1
- package/dist/src/testing/reactivity-mesh-harness.d.ts +298 -0
- package/dist/src/testing/reactivity-mesh-harness.d.ts.map +1 -0
- package/dist/src/testing/reactivity-mesh-harness.js +663 -0
- package/dist/src/testing/reactivity-mesh-harness.js.map +1 -0
- package/package.json +7 -4
- package/src/cluster/block-transfer-service.ts +133 -68
- package/src/cluster/client.ts +64 -45
- package/src/cluster/cluster-error.ts +64 -0
- package/src/cluster/cluster-repo.ts +405 -55
- package/src/cluster/commit-cert.ts +139 -0
- package/src/cluster/service.ts +116 -44
- package/src/cluster/spread-on-churn.ts +328 -285
- package/src/cohort-topic/bootstrap-evidence-builder.ts +122 -0
- package/src/cohort-topic/bootstrap-evidence-verifiers.ts +132 -0
- package/src/cohort-topic/bootstrap-parent-reference.ts +159 -0
- package/src/cohort-topic/change-bridge.ts +109 -0
- package/src/cohort-topic/cohort-gossip-driver.ts +144 -0
- package/src/cohort-topic/cohort-gossip-transport.ts +74 -0
- package/src/cohort-topic/fret-trust-anchor.ts +153 -0
- package/src/cohort-topic/host.ts +2175 -0
- package/src/cohort-topic/index.ts +13 -0
- package/src/cohort-topic/membership-publish-sink.ts +20 -0
- package/src/cohort-topic/membership-source.ts +68 -0
- package/src/cohort-topic/peer-codec.ts +31 -0
- package/src/cohort-topic/peer-sig.ts +86 -0
- package/src/cohort-topic/protocols.ts +71 -0
- package/src/cohort-topic/reactivity-membership-gate.ts +77 -0
- package/src/cohort-topic/size-estimator.ts +16 -0
- package/src/cohort-topic/stream-util.ts +87 -0
- package/src/cohort-topic/threshold-crypto.ts +239 -0
- package/src/cohort-topic/topic-router.ts +77 -0
- package/src/dispute/cascade.ts +517 -0
- package/src/dispute/client.ts +12 -2
- package/src/dispute/dispute-service.ts +171 -24
- package/src/dispute/index.ts +42 -0
- package/src/dispute/invalidation.ts +587 -0
- package/src/dispute/types.ts +14 -0
- package/src/index.ts +4 -0
- package/src/libp2p-key-network.ts +49 -6
- package/src/libp2p-node-base.ts +620 -12
- package/src/matchmaking/aggregate-counts.ts +104 -0
- package/src/matchmaking/index.ts +20 -0
- package/src/matchmaking/module.ts +363 -0
- package/src/matchmaking/protocols.ts +51 -0
- package/src/matchmaking/provider-manager.ts +95 -0
- package/src/matchmaking/query-handler.ts +88 -0
- package/src/matchmaking/query-transport.ts +483 -0
- package/src/matchmaking/seeker-manager.ts +64 -0
- package/src/matchmaking/seeker-walk-client.ts +293 -0
- package/src/matchmaking/traffic-validation.ts +195 -0
- package/src/network/network-manager-service.ts +9 -2
- package/src/protocol-client.ts +64 -3
- package/src/reactivity/forwarder-host.ts +438 -0
- package/src/reactivity/index.ts +19 -0
- package/src/reactivity/notify-transport.ts +144 -0
- package/src/reactivity/origination-manager.ts +192 -0
- package/src/reactivity/protocols.ts +61 -0
- package/src/reactivity/push-state-gossip.ts +291 -0
- package/src/reactivity/recover-transport.ts +408 -0
- package/src/reactivity/rotation-rereg-scheduler.ts +256 -0
- package/src/reactivity/subscriber-registry.ts +96 -0
- package/src/reactivity/subscription-manager.ts +450 -0
- package/src/reactivity/topic-bytes.ts +37 -0
- package/src/repo/client.ts +54 -23
- package/src/repo/service.ts +90 -42
- package/src/rpc-deadline.ts +45 -0
- package/src/storage/block-storage.ts +106 -3
- package/src/storage/i-block-storage.ts +27 -0
- package/src/storage/storage-repo.ts +209 -13
- package/src/sync/client.ts +5 -2
- package/src/testing/cohort-topic-mesh-harness.ts +613 -0
- package/src/testing/matchmaking-mesh-harness.ts +475 -0
- package/src/testing/mesh-harness.ts +22 -2
- package/src/testing/reactivity-mesh-harness.ts +922 -0
|
@@ -0,0 +1,355 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Reactivity — forwarder **host** orchestrator (`docs/reactivity.md` §Propagation, §Slow-subscriber
|
|
3
|
+
* backpressure, §Per-cohort policy).
|
|
4
|
+
*
|
|
5
|
+
* This is the piece that turns the in-process forward *decision* (db-core's {@link ReactivityForwarder})
|
|
6
|
+
* into live fan-out over the {@link ReactivityNotifyTransport}. For one {@link NotificationV1} — whether
|
|
7
|
+
* locally originated (the change-bridge's `emit` seam binds to {@link ReactivityForwarderHost.ingest}) or
|
|
8
|
+
* inbound off a dial ({@link ReactivityForwarderHost.onInbound}) — it:
|
|
9
|
+
*
|
|
10
|
+
* 1. lazily instantiates the per-collection {@link PushState} + forwarder behind the Edge policy gate
|
|
11
|
+
* ({@link instantiateForwarderPushState}: `undefined` on an Edge node ⇒ this node never forwards);
|
|
12
|
+
* 2. runs the db-core receive path (verify → dedupe → buffer) and stops on `"duplicate"` / `"untrusted"`;
|
|
13
|
+
* 3. on `"forward"`, fans the **unmodified** frame out to every direct subscriber (through the
|
|
14
|
+
* per-subscriber bounded queue, so a slow/dead subscriber's drops never stall the rest) and every
|
|
15
|
+
* child cohort.
|
|
16
|
+
*
|
|
17
|
+
* It owns the per-subscriber dequeue→dial delivery loop, the bounded-queue backpressure interaction,
|
|
18
|
+
* subscriber-queue eviction (the memory bound — a departed subscriber's queue is reclaimed lazily each
|
|
19
|
+
* fan-out round), and per-topic serialization so the replay ring + dedupe set never interleave across two
|
|
20
|
+
* concurrent notifications for the same collection.
|
|
21
|
+
*
|
|
22
|
+
* **Subscriber-id space.** `selfPeerId`, the {@link ReactivityForwarderHostDeps.directSubscribers} output,
|
|
23
|
+
* the {@link CohortRef.primary} child targets, and every {@link ReactivityNotifyTransport.send} target are
|
|
24
|
+
* the **same** string space (it is also the `perSubscriberQueue` key). This host is agnostic to the concrete
|
|
25
|
+
* encoding so long as it is consistent across all four seams; the **transport** pins which space —
|
|
26
|
+
* {@link import("./notify-transport.js").Libp2pReactivityNotifyTransport.send} calls `peerIdFromString`, so
|
|
27
|
+
* the space is the **canonical peer-id string** (NOT base64url-of-member-bytes — that would silently fail
|
|
28
|
+
* `peerIdFromString` and never dial). The {@link reactivityDirectSubscribers} adapter and the node-wiring
|
|
29
|
+
* layer ([reactivity-notification-transport]) therefore both emit peer-id strings.
|
|
30
|
+
*
|
|
31
|
+
* This module deliberately touches neither the libp2p node assembly ([reactivity-notification-transport])
|
|
32
|
+
* nor gossip ([reactivity-pushstate-gossip]); it depends only on the transport interface + db-core logic,
|
|
33
|
+
* so it is unit-testable with a fake transport.
|
|
34
|
+
*/
|
|
35
|
+
import { reactivityTopicId, b64urlToBytes, bytesToB64url, createReactivityForwarder, instantiateForwarderPushState, decodeSubscribeAppPayload, TailDrainGate, } from "@optimystic/db-core";
|
|
36
|
+
import { bytesToPeerIdString } from "../cohort-topic/peer-codec.js";
|
|
37
|
+
import { createLogger } from "../logger.js";
|
|
38
|
+
const log = createLogger("reactivity-forwarder-host");
|
|
39
|
+
/**
|
|
40
|
+
* The reactivity topic id a notification belongs to: `H(tailId ‖ "reactivity")` over the notification's
|
|
41
|
+
* tail anchor. Kept in one place so it matches origination's `reactivityTailBytes` encoding and the
|
|
42
|
+
* subscriber/forwarder verifier's coord derivation byte-for-byte (both decode `tailId` as base64url first).
|
|
43
|
+
*/
|
|
44
|
+
export function reactivityNotificationTopicId(n) {
|
|
45
|
+
return reactivityTopicId(b64urlToBytes(n.tailId));
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* The direct-subscriber **peer-id strings** for a reactivity topic, read from a cohort's
|
|
49
|
+
* {@link RegistrationRecord} set: a record carries a subscriber iff its opaque `appState` decodes to a
|
|
50
|
+
* reactivity {@link import("@optimystic/db-core").SubscribeAppPayloadV1}. `record.participantId` is the
|
|
51
|
+
* subscriber's dialable member id (db-core's `service` pins `participantId = self`), carried as
|
|
52
|
+
* `peerIdToBytes(peerId) = utf8(peerIdString)`; this decodes it back to the canonical peer-id string via
|
|
53
|
+
* {@link bytesToPeerIdString} so the value is both the `transport.send` dial target (which calls
|
|
54
|
+
* `peerIdFromString`) and the `perSubscriberQueue` key. **The whole host runs in this peer-id-string space**
|
|
55
|
+
* — `selfPeerId`, `resolveChildPrimary`, and every `send` target must agree, or a base64url-vs-peer-id
|
|
56
|
+
* mismatch is a silent no-dial. A record whose `appState` is absent or decodes to a non-reactivity payload
|
|
57
|
+
* (e.g. a matchmaking registration sharing the cohort store) is skipped.
|
|
58
|
+
*/
|
|
59
|
+
export function reactivityDirectSubscribers(source, topicId) {
|
|
60
|
+
const out = [];
|
|
61
|
+
for (const record of source.records(topicId)) {
|
|
62
|
+
if (record.appState === undefined) {
|
|
63
|
+
continue;
|
|
64
|
+
}
|
|
65
|
+
try {
|
|
66
|
+
decodeSubscribeAppPayload(record.appState); // throws on a non-reactivity / malformed payload
|
|
67
|
+
}
|
|
68
|
+
catch {
|
|
69
|
+
continue; // not a reactivity subscriber — leave the record to its own application
|
|
70
|
+
}
|
|
71
|
+
out.push(bytesToPeerIdString(record.participantId));
|
|
72
|
+
}
|
|
73
|
+
return out;
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* The reactivity fan-out orchestrator. Build one per node; it serves every reactivity topic the node is a
|
|
77
|
+
* cohort member for, lazily instantiating per-collection state on first ingest. Bind
|
|
78
|
+
* {@link ReactivityForwarderHost.ingest} to the origination `emit` seam and
|
|
79
|
+
* {@link ReactivityForwarderHost.onInbound} to the transport's inbound subscribe.
|
|
80
|
+
*/
|
|
81
|
+
export class ReactivityForwarderHost {
|
|
82
|
+
transport;
|
|
83
|
+
selfPeerId;
|
|
84
|
+
profile;
|
|
85
|
+
pushStateInit;
|
|
86
|
+
verifierFor;
|
|
87
|
+
directSubscribers;
|
|
88
|
+
resolveChildPrimary;
|
|
89
|
+
deliverLocal;
|
|
90
|
+
clock;
|
|
91
|
+
/** Per-topic served state. `ServedTopic` ⇒ forwards; `null` ⇒ resolved subscriber-only (Edge); absent ⇒ unresolved. */
|
|
92
|
+
served = new Map();
|
|
93
|
+
/** Per-topic serialization tail: an ingest chains onto its topic's prior ingest so the ring/dedupe never interleave. */
|
|
94
|
+
ingestTails = new Map();
|
|
95
|
+
/**
|
|
96
|
+
* Per **old-topic** drain gate: set by {@link markRotated} when this node observes the collection's tail
|
|
97
|
+
* rotate, keyed by the outgoing tail's topicId (base64url). For `T_drain` after the rotation
|
|
98
|
+
* {@link rotationRedirectFor} answers a recover request reaching the outgoing tail with the gate's
|
|
99
|
+
* {@link RotationRedirectV1} ("this moved — go to the new tree"); once the window closes the entry is
|
|
100
|
+
* evicted along with the topic's served `PushState`.
|
|
101
|
+
*/
|
|
102
|
+
rotationGates = new Map();
|
|
103
|
+
constructor(deps) {
|
|
104
|
+
this.transport = deps.transport;
|
|
105
|
+
this.selfPeerId = deps.selfPeerId;
|
|
106
|
+
this.profile = deps.profile;
|
|
107
|
+
this.pushStateInit = deps.pushStateInit;
|
|
108
|
+
this.verifierFor = deps.verifierFor;
|
|
109
|
+
this.directSubscribers = deps.directSubscribers;
|
|
110
|
+
this.resolveChildPrimary = deps.resolveChildPrimary;
|
|
111
|
+
this.deliverLocal = deps.deliverLocal;
|
|
112
|
+
this.clock = deps.clock ?? (() => Date.now());
|
|
113
|
+
}
|
|
114
|
+
/**
|
|
115
|
+
* Local origination emit **and** the inbound forwarder path: receive → forward → fan-out for one
|
|
116
|
+
* notification on `topicId`. Ingests for one topic run strictly in sequence (verify is async and the
|
|
117
|
+
* replay ring + dedupe set must not interleave); ingests for different topics proceed concurrently.
|
|
118
|
+
* Never rejects — a fan-out fault can never surface as a commit failure on the origination seam.
|
|
119
|
+
*/
|
|
120
|
+
ingest(topicId, n) {
|
|
121
|
+
const key = this.topicKey(topicId);
|
|
122
|
+
const prev = this.ingestTails.get(key) ?? Promise.resolve();
|
|
123
|
+
// Run regardless of the prior ingest's outcome; `ingestSerialized` isolates its own throws, so the
|
|
124
|
+
// chain never accumulates a rejection that would leak out of a later `ingest`.
|
|
125
|
+
const next = prev.then(() => this.ingestSerialized(topicId, n, key), () => this.ingestSerialized(topicId, n, key));
|
|
126
|
+
this.ingestTails.set(key, next);
|
|
127
|
+
return next;
|
|
128
|
+
}
|
|
129
|
+
/** One serialized ingest: resolve served state, run receive, fan out on `"forward"`. Isolates all throws. */
|
|
130
|
+
async ingestSerialized(topicId, n, key) {
|
|
131
|
+
try {
|
|
132
|
+
const served = this.resolveServed(topicId, n, key);
|
|
133
|
+
if (served === null) {
|
|
134
|
+
return; // Edge / subscriber-only: this node never forwards (delivery rides onInbound → deliverLocal).
|
|
135
|
+
}
|
|
136
|
+
const decision = await served.forwarder.receive(n, this.clock());
|
|
137
|
+
if (decision !== "forward") {
|
|
138
|
+
return; // "duplicate" (already buffered) or "untrusted" (dropped before any state mutation).
|
|
139
|
+
}
|
|
140
|
+
this.fanOut(topicId, served.pushState, n);
|
|
141
|
+
}
|
|
142
|
+
catch (err) {
|
|
143
|
+
log("ingest failed for topic=%s rev=%d (isolated): %o", key, n.revision, err);
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
/**
|
|
147
|
+
* Inbound notify dial: run the **subscriber** role (in-process delivery to a co-located manager) and the
|
|
148
|
+
* **forwarder** role (receive + fan-out) as applicable. A node that is both a cohort member and a
|
|
149
|
+
* subscriber does both — the forwarder dedupe + the subscriber's `(collectionId, revision)` dedupe keep
|
|
150
|
+
* it idempotent. Never rejects.
|
|
151
|
+
*/
|
|
152
|
+
async onInbound(_from, n) {
|
|
153
|
+
let topicId;
|
|
154
|
+
try {
|
|
155
|
+
topicId = reactivityNotificationTopicId(n);
|
|
156
|
+
}
|
|
157
|
+
catch (err) {
|
|
158
|
+
log("onInbound: undecodable tailId on rev=%d (dropped): %o", n.revision, err);
|
|
159
|
+
return;
|
|
160
|
+
}
|
|
161
|
+
// Subscriber role first: deliver in-process to a co-located subscription manager, if any.
|
|
162
|
+
this.deliverInProcess(topicId, n);
|
|
163
|
+
// Forwarder role: ingest self-gates (Edge ⇒ no PushState; a non-member ⇒ empty fan-out).
|
|
164
|
+
await this.ingest(topicId, n);
|
|
165
|
+
}
|
|
166
|
+
/** The live {@link PushState} for a served topic, or `undefined` (Edge, or not yet ingested). Test/diagnostic. */
|
|
167
|
+
pushStateFor(topicId) {
|
|
168
|
+
const served = this.served.get(this.topicKey(topicId));
|
|
169
|
+
return served === undefined || served === null ? undefined : served.pushState;
|
|
170
|
+
}
|
|
171
|
+
/**
|
|
172
|
+
* Every live forwarder {@link PushState} this node currently serves: the topics it has ingested at least
|
|
173
|
+
* one notification for and instantiated state behind the Edge gate (subscriber-only `null`s and
|
|
174
|
+
* not-yet-ingested topics are skipped). The push-state-gossip driver iterates these each round to
|
|
175
|
+
* broadcast convergence state to each collection's cohort, so a member that missed an origin dial still
|
|
176
|
+
* ends up holding the replay entry.
|
|
177
|
+
*/
|
|
178
|
+
livePushStates() {
|
|
179
|
+
const out = [];
|
|
180
|
+
for (const served of this.served.values()) {
|
|
181
|
+
if (served !== null) {
|
|
182
|
+
out.push(served.pushState);
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
return out;
|
|
186
|
+
}
|
|
187
|
+
/**
|
|
188
|
+
* The live {@link PushState} this node serves for `collectionId` (base64url), or `undefined` if none.
|
|
189
|
+
* The served map is keyed by **topicId**, but a backfill request carries only a collectionId and a
|
|
190
|
+
* resume that lost its tail must still resolve the collection's current tail — so this scans for the
|
|
191
|
+
* served topic whose `PushState.collectionId` matches. Across a tail-rotation **drain window** a
|
|
192
|
+
* collection can briefly have two served topics (the outgoing and incoming tail); the one with the
|
|
193
|
+
* highest `lastRevision` is the current tail, so that is the one returned. (The precise drain-window
|
|
194
|
+
* redirect choreography is `reactivity-tail-rotation-transport`'s; this picks the current tail.)
|
|
195
|
+
*/
|
|
196
|
+
pushStateForCollection(collectionId) {
|
|
197
|
+
let best;
|
|
198
|
+
for (const served of this.served.values()) {
|
|
199
|
+
if (served === null || served.pushState.collectionId !== collectionId) {
|
|
200
|
+
continue;
|
|
201
|
+
}
|
|
202
|
+
if (best === undefined || served.pushState.lastRevision > best.lastRevision) {
|
|
203
|
+
best = served.pushState;
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
return best;
|
|
207
|
+
}
|
|
208
|
+
/**
|
|
209
|
+
* Record that the topic anchored on `oldTopicId` has **rotated** to a successor tail, starting a
|
|
210
|
+
* {@link TailDrainGate} so the outgoing tail bounces recover requests to the new tree for `T_drain`
|
|
211
|
+
* (`docs/reactivity.md` §Tail rotation step 2). db-core derives the redirect's `newTopicId` internally via
|
|
212
|
+
* `reactivityTopicId(newTailId)`. The trigger is origination observing the collection's `event.tailId`
|
|
213
|
+
* change ({@link reactivity-rotation-host-wiring-e2e}); this seam is called directly in unit tests.
|
|
214
|
+
*
|
|
215
|
+
* **Idempotent / chained.** A second `markRotated` for the **same** successor is a no-op; a `markRotated`
|
|
216
|
+
* to a **later** successor (higher `effectiveAtRevision`) replaces the gate — so a chained OLD→A→B rotation
|
|
217
|
+
* advances the redirect to the most recent successor and restarts its drain window from `now`. An earlier
|
|
218
|
+
* (or equal) successor leaves the existing gate untouched.
|
|
219
|
+
*/
|
|
220
|
+
markRotated(oldTopicId, redirect, now) {
|
|
221
|
+
const key = this.topicKey(oldTopicId);
|
|
222
|
+
const existing = this.rotationGates.get(key);
|
|
223
|
+
if (existing !== undefined && redirect.effectiveAtRevision <= existing.rotationRedirect.effectiveAtRevision) {
|
|
224
|
+
return; // same or earlier successor — the gate already points at this-or-a-later tail.
|
|
225
|
+
}
|
|
226
|
+
this.rotationGates.set(key, new TailDrainGate({
|
|
227
|
+
rotatedAt: now,
|
|
228
|
+
newTailId: redirect.newTailId,
|
|
229
|
+
effectiveAtRevision: redirect.effectiveAtRevision,
|
|
230
|
+
}));
|
|
231
|
+
}
|
|
232
|
+
/**
|
|
233
|
+
* The drain-window redirect for a recover request that reached the outgoing tail anchored on `oldTopicId`,
|
|
234
|
+
* or `undefined` if this topic never rotated / its drain window has elapsed. While the gate is draining
|
|
235
|
+
* (`isDraining(now)`) returns its {@link RotationRedirectV1}; once `T_drain` closes, evicts the gate **and**
|
|
236
|
+
* the topic's served `PushState` (the old tail originates nothing further — this also reclaims the
|
|
237
|
+
* `served` / `ingestTails` maps the 12.31 review flagged as un-evicted on rotation) and returns `undefined`,
|
|
238
|
+
* so the next request gets no reply and the subscriber re-walks / chain-reads.
|
|
239
|
+
*/
|
|
240
|
+
rotationRedirectFor(oldTopicId, now) {
|
|
241
|
+
const key = this.topicKey(oldTopicId);
|
|
242
|
+
const gate = this.rotationGates.get(key);
|
|
243
|
+
if (gate === undefined) {
|
|
244
|
+
return undefined;
|
|
245
|
+
}
|
|
246
|
+
if (gate.isDraining(now)) {
|
|
247
|
+
return gate.rotationRedirect;
|
|
248
|
+
}
|
|
249
|
+
// Drain window closed: the outgoing tail is done. Drop the gate and reclaim its served state so the
|
|
250
|
+
// per-topic maps don't leak across rotations.
|
|
251
|
+
this.rotationGates.delete(key);
|
|
252
|
+
this.served.delete(key);
|
|
253
|
+
this.ingestTails.delete(key);
|
|
254
|
+
return undefined;
|
|
255
|
+
}
|
|
256
|
+
/**
|
|
257
|
+
* Fan the unmodified frame out: reconcile (GC) departed-subscriber queues, enqueue per-subscriber
|
|
258
|
+
* (drop-oldest under pressure), drain each queue and deliver (self in-process, others dialed, per-target
|
|
259
|
+
* isolated), then dial each resolved child cohort. One slow/dead target never blocks the loop for the rest.
|
|
260
|
+
*/
|
|
261
|
+
fanOut(topicId, pushState, n) {
|
|
262
|
+
const subscriberIds = this.directSubscribers(topicId);
|
|
263
|
+
// Memory bound: drop any per-subscriber queue whose id left the live set (departed / TTL-expired /
|
|
264
|
+
// withdrawn). The map can never grow past the live subscriber set + the current round (lazy GC, so it
|
|
265
|
+
// shrinks at most one fan-out round late) — this satisfies the `subscribers × queue_max × size` bound.
|
|
266
|
+
this.reconcileQueues(pushState, subscriberIds);
|
|
267
|
+
// Enqueue onto each subscriber's bounded queue (a slow subscriber's drop-oldest isolates to its queue).
|
|
268
|
+
pushState.enqueueForSubscribers(subscriberIds, n);
|
|
269
|
+
// Drain + deliver per subscriber, isolated: one dead subscriber's dial never stalls the others.
|
|
270
|
+
for (const subId of subscriberIds) {
|
|
271
|
+
const queue = pushState.perSubscriberQueue.peekQueue(subId);
|
|
272
|
+
if (queue === undefined) {
|
|
273
|
+
continue;
|
|
274
|
+
}
|
|
275
|
+
for (const m of queue.drain()) {
|
|
276
|
+
this.deliverTo(topicId, subId, m);
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
// Child cohorts get the unmodified frame (forwarders never re-sign). No-op until the parent/child link
|
|
280
|
+
// lands ([cohort-topic-parent-child-link]) populates `childCohorts`.
|
|
281
|
+
for (const ref of pushState.childCohorts) {
|
|
282
|
+
const target = ref.primary ?? this.resolveChildPrimary?.(ref);
|
|
283
|
+
if (target !== undefined && target !== this.selfPeerId) {
|
|
284
|
+
this.dispatch(target, n);
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
/** Reclaim the queues of subscribers no longer in the live set (lazy, one round late). */
|
|
289
|
+
reconcileQueues(pushState, subscriberIds) {
|
|
290
|
+
const live = new Set(subscriberIds);
|
|
291
|
+
// Snapshot the keys: `remove` mutates the backing map, which would invalidate a live iterator.
|
|
292
|
+
for (const id of [...pushState.perSubscriberQueue.subscribers()]) {
|
|
293
|
+
if (!live.has(id)) {
|
|
294
|
+
pushState.perSubscriberQueue.remove(id);
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
/** Deliver one notification to one subscriber: self in-process, everyone else over the transport. */
|
|
299
|
+
deliverTo(topicId, subId, n) {
|
|
300
|
+
if (subId === this.selfPeerId) {
|
|
301
|
+
this.deliverInProcess(topicId, n);
|
|
302
|
+
}
|
|
303
|
+
else {
|
|
304
|
+
this.dispatch(subId, n);
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
/** Route to a co-located subscription manager, isolating a throwing subscriber from the rest of the fan-out. */
|
|
308
|
+
deliverInProcess(topicId, n) {
|
|
309
|
+
if (this.deliverLocal === undefined) {
|
|
310
|
+
return;
|
|
311
|
+
}
|
|
312
|
+
try {
|
|
313
|
+
this.deliverLocal(topicId, n);
|
|
314
|
+
}
|
|
315
|
+
catch (err) {
|
|
316
|
+
log("local delivery threw (isolated) for rev=%d: %o", n.revision, err);
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
/** Fire one unicast send, isolated: a synchronous throw or a rejection is swallowed, never the loop's concern. */
|
|
320
|
+
dispatch(target, n) {
|
|
321
|
+
let pending;
|
|
322
|
+
try {
|
|
323
|
+
pending = this.transport.send(target, n);
|
|
324
|
+
}
|
|
325
|
+
catch (err) {
|
|
326
|
+
log("notify send to %s threw synchronously (isolated): %o", target, err);
|
|
327
|
+
return;
|
|
328
|
+
}
|
|
329
|
+
void pending.catch((err) => {
|
|
330
|
+
log("notify send to %s rejected (isolated): %o", target, err);
|
|
331
|
+
});
|
|
332
|
+
}
|
|
333
|
+
/** Resolve (instantiating once, behind the Edge gate) the served state for a topic. */
|
|
334
|
+
resolveServed(topicId, n, key) {
|
|
335
|
+
const existing = this.served.get(key);
|
|
336
|
+
if (existing !== undefined) {
|
|
337
|
+
return existing; // a `ServedTopic` or a remembered `null` (Edge / subscriber-only)
|
|
338
|
+
}
|
|
339
|
+
const pushState = instantiateForwarderPushState(this.profile, this.pushStateInit(topicId, n));
|
|
340
|
+
if (pushState === undefined) {
|
|
341
|
+
this.served.set(key, null); // Edge node: remember it never forwards this topic.
|
|
342
|
+
return null;
|
|
343
|
+
}
|
|
344
|
+
const served = {
|
|
345
|
+
pushState,
|
|
346
|
+
forwarder: createReactivityForwarder({ state: pushState, verifier: this.verifierFor(topicId) }),
|
|
347
|
+
};
|
|
348
|
+
this.served.set(key, served);
|
|
349
|
+
return served;
|
|
350
|
+
}
|
|
351
|
+
topicKey(topicId) {
|
|
352
|
+
return bytesToB64url(topicId);
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
//# sourceMappingURL=forwarder-host.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"forwarder-host.js","sourceRoot":"","sources":["../../../src/reactivity/forwarder-host.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Reactivity — db-p2p wiring to the cohort-topic substrate.
|
|
3
|
+
*
|
|
4
|
+
* The db-core `reactivity` module owns the wire codecs, topic anchor, config, origination assembler,
|
|
5
|
+
* forwarder/dedupe/replay logic, and subscriber-side verify/deliver; these managers bind that to the
|
|
6
|
+
* participant-facing `CohortTopicService` (subscribe/renew/withdraw at tier T3) and install the
|
|
7
|
+
* `onLocalCommit` origination hook. See `docs/reactivity.md` §Subscription / §Notification origination.
|
|
8
|
+
*/
|
|
9
|
+
export * from "./topic-bytes.js";
|
|
10
|
+
export * from "./protocols.js";
|
|
11
|
+
export * from "./notify-transport.js";
|
|
12
|
+
export * from "./recover-transport.js";
|
|
13
|
+
export * from "./subscription-manager.js";
|
|
14
|
+
export * from "./rotation-rereg-scheduler.js";
|
|
15
|
+
export * from "./origination-manager.js";
|
|
16
|
+
export * from "./forwarder-host.js";
|
|
17
|
+
export * from "./push-state-gossip.js";
|
|
18
|
+
export * from "./subscriber-registry.js";
|
|
19
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/reactivity/index.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,cAAc,kBAAkB,CAAC;AACjC,cAAc,gBAAgB,CAAC;AAC/B,cAAc,uBAAuB,CAAC;AACtC,cAAc,wBAAwB,CAAC;AACvC,cAAc,2BAA2B,CAAC;AAC1C,cAAc,+BAA+B,CAAC;AAC9C,cAAc,0BAA0B,CAAC;AACzC,cAAc,qBAAqB,CAAC;AACpC,cAAc,wBAAwB,CAAC;AACvC,cAAc,0BAA0B,CAAC"}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Reactivity — db-p2p wiring to the cohort-topic substrate.
|
|
3
|
+
*
|
|
4
|
+
* The db-core `reactivity` module owns the wire codecs, topic anchor, config, origination assembler,
|
|
5
|
+
* forwarder/dedupe/replay logic, and subscriber-side verify/deliver; these managers bind that to the
|
|
6
|
+
* participant-facing `CohortTopicService` (subscribe/renew/withdraw at tier T3) and install the
|
|
7
|
+
* `onLocalCommit` origination hook. See `docs/reactivity.md` §Subscription / §Notification origination.
|
|
8
|
+
*/
|
|
9
|
+
export * from "./topic-bytes.js";
|
|
10
|
+
export * from "./protocols.js";
|
|
11
|
+
export * from "./notify-transport.js";
|
|
12
|
+
export * from "./recover-transport.js";
|
|
13
|
+
export * from "./subscription-manager.js";
|
|
14
|
+
export * from "./rotation-rereg-scheduler.js";
|
|
15
|
+
export * from "./origination-manager.js";
|
|
16
|
+
export * from "./forwarder-host.js";
|
|
17
|
+
export * from "./push-state-gossip.js";
|
|
18
|
+
export * from "./subscriber-registry.js";
|
|
19
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/reactivity/index.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,cAAc,kBAAkB,CAAC;AACjC,cAAc,gBAAgB,CAAC;AAC/B,cAAc,uBAAuB,CAAC;AACtC,cAAc,wBAAwB,CAAC;AACvC,cAAc,2BAA2B,CAAC;AAC1C,cAAc,+BAA+B,CAAC;AAC9C,cAAc,0BAA0B,CAAC;AACzC,cAAc,qBAAqB,CAAC;AACpC,cAAc,wBAAwB,CAAC;AACvC,cAAc,0BAA0B,CAAC"}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Reactivity notify transport — the one-way `NotificationV1` delivery primitive (`docs/reactivity.md`
|
|
3
|
+
* §Propagation).
|
|
4
|
+
*
|
|
5
|
+
* Unlike the cohort-gossip transport (which *broadcasts* a frame to a FRET-assembled cohort), notify is
|
|
6
|
+
* **unicast**: the fan-out orchestration above this layer (the `reactivity-forwarder-host` ticket) decides
|
|
7
|
+
* who to dial and calls {@link ReactivityNotifyTransport.send} once per named target. This module owns only
|
|
8
|
+
* the framing + dial + inbound-decode plumbing — no fan-out, no role decision, no gossip.
|
|
9
|
+
*
|
|
10
|
+
* The db-core `NotificationV1` codec ({@link encodeNotificationV1} / {@link decodeNotificationV1}) does the
|
|
11
|
+
* length-prefixed JSON framing; this layer rides one self-delimiting frame each way over the notify
|
|
12
|
+
* protocol, reusing the cohort-topic {@link sendOneWay} / {@link readAllBounded} stream lifecycle so the
|
|
13
|
+
* two protocol families behave identically on the wire.
|
|
14
|
+
*
|
|
15
|
+
* Failure isolation is the load-bearing property: notify is fire-and-forget and hint-only, so a dead /
|
|
16
|
+
* unreachable target's rejection is swallowed (logged), never propagated to the caller's fan-out loop or a
|
|
17
|
+
* commit. There is no reply frame — a handler that tried to send one back would desync the dialer's
|
|
18
|
+
* {@link sendOneWay} (which closes after send).
|
|
19
|
+
*/
|
|
20
|
+
import type { NotificationV1, PeerRef } from "@optimystic/db-core";
|
|
21
|
+
import type { Libp2p } from "libp2p";
|
|
22
|
+
/** One-way `NotificationV1` transport: unicast send, inbound subscribe, and the host's deliver seam. */
|
|
23
|
+
export interface ReactivityNotifyTransport {
|
|
24
|
+
/**
|
|
25
|
+
* Frame `n` ({@link encodeNotificationV1}) and dial `target` (peer-id string) over the notify protocol.
|
|
26
|
+
* Fire-and-forget, failure-isolated: a dead/unreachable target's rejection is swallowed (logged), never
|
|
27
|
+
* propagated to the caller's fan-out loop.
|
|
28
|
+
*/
|
|
29
|
+
send(target: string, n: NotificationV1): Promise<void>;
|
|
30
|
+
/** Subscribe to inbound notifications (after decode); returns an unsubscribe handle. */
|
|
31
|
+
onNotification(handler: (from: PeerRef, n: NotificationV1) => void): () => void;
|
|
32
|
+
/** Feed an inbound notify frame (called by the host's notify protocol handler). */
|
|
33
|
+
deliver(fromPeerId: string, frame: Uint8Array): void;
|
|
34
|
+
}
|
|
35
|
+
/** Construction options for {@link Libp2pReactivityNotifyTransport}. */
|
|
36
|
+
export interface ReactivityNotifyTransportOptions {
|
|
37
|
+
/** Notify protocol ID; default {@link PROTOCOL_REACTIVITY_NOTIFY}. */
|
|
38
|
+
readonly notifyProtocol?: string;
|
|
39
|
+
/** Per-frame ceiling for the inbound decode bound. Default {@link DEFAULT_STREAM_MAX_BYTES}. */
|
|
40
|
+
readonly maxBytes?: number;
|
|
41
|
+
/** This node's peer-id string; when set, {@link Libp2pReactivityNotifyTransport.send} never dials self. */
|
|
42
|
+
readonly selfPeerId?: string;
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* libp2p-backed {@link ReactivityNotifyTransport}: {@link send} frames + dials a single target over
|
|
46
|
+
* `/optimystic/reactivity/1.0.0/notify` (fire-and-forget, failure-isolated); inbound frames arrive through
|
|
47
|
+
* the host's notify protocol handler ({@link registerNotifyHandler}), which calls {@link deliver}.
|
|
48
|
+
* Subscribers registered via {@link onNotification} see every decoded notification.
|
|
49
|
+
*/
|
|
50
|
+
export declare class Libp2pReactivityNotifyTransport implements ReactivityNotifyTransport {
|
|
51
|
+
private readonly node;
|
|
52
|
+
private readonly handlers;
|
|
53
|
+
private readonly notifyProtocol;
|
|
54
|
+
private readonly maxBytes;
|
|
55
|
+
private readonly selfPeerId?;
|
|
56
|
+
constructor(node: Libp2p, options?: ReactivityNotifyTransportOptions);
|
|
57
|
+
send(target: string, n: NotificationV1): Promise<void>;
|
|
58
|
+
onNotification(handler: (from: PeerRef, n: NotificationV1) => void): () => void;
|
|
59
|
+
/** Feed an inbound notify frame (called by the host's notify protocol handler). */
|
|
60
|
+
deliver(fromPeerId: string, frame: Uint8Array): void;
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Register the inbound notify protocol handler: read one bounded frame and hand it to
|
|
64
|
+
* {@link ReactivityNotifyTransport.deliver}, then close. One-way — no reply frame (notify is strictly
|
|
65
|
+
* fire-and-forget; a reply would desync the dialer's {@link sendOneWay}). Mirrors the cohort-topic
|
|
66
|
+
* one-way handlers: a read error aborts the stream, and {@link ReactivityNotifyTransport.deliver}
|
|
67
|
+
* swallows a decode failure, so the handler never throws on the stream.
|
|
68
|
+
*/
|
|
69
|
+
export declare function registerNotifyHandler(node: Libp2p, protocol: string, transport: ReactivityNotifyTransport, maxBytes?: number): void;
|
|
70
|
+
//# sourceMappingURL=notify-transport.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"notify-transport.d.ts","sourceRoot":"","sources":["../../../src/reactivity/notify-transport.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAEH,OAAO,KAAK,EAAE,cAAc,EAAE,OAAO,EAAE,MAAM,qBAAqB,CAAC;AAEnE,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AAWrC,wGAAwG;AACxG,MAAM,WAAW,yBAAyB;IACzC;;;;OAIG;IACH,IAAI,CAAC,MAAM,EAAE,MAAM,EAAE,CAAC,EAAE,cAAc,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACvD,wFAAwF;IACxF,cAAc,CAAC,OAAO,EAAE,CAAC,IAAI,EAAE,OAAO,EAAE,CAAC,EAAE,cAAc,KAAK,IAAI,GAAG,MAAM,IAAI,CAAC;IAChF,mFAAmF;IACnF,OAAO,CAAC,UAAU,EAAE,MAAM,EAAE,KAAK,EAAE,UAAU,GAAG,IAAI,CAAC;CACrD;AAED,wEAAwE;AACxE,MAAM,WAAW,gCAAgC;IAChD,sEAAsE;IACtE,QAAQ,CAAC,cAAc,CAAC,EAAE,MAAM,CAAC;IACjC,gGAAgG;IAChG,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAC3B,2GAA2G;IAC3G,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,CAAC;CAC7B;AAED;;;;;GAKG;AACH,qBAAa,+BAAgC,YAAW,yBAAyB;IAMpE,OAAO,CAAC,QAAQ,CAAC,IAAI;IALjC,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAyD;IAClF,OAAO,CAAC,QAAQ,CAAC,cAAc,CAAS;IACxC,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAS;IAClC,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAC,CAAS;gBAER,IAAI,EAAE,MAAM,EAAE,OAAO,GAAE,gCAAqC;IAMzF,IAAI,CAAC,MAAM,EAAE,MAAM,EAAE,CAAC,EAAE,cAAc,GAAG,OAAO,CAAC,IAAI,CAAC;IAkBtD,cAAc,CAAC,OAAO,EAAE,CAAC,IAAI,EAAE,OAAO,EAAE,CAAC,EAAE,cAAc,KAAK,IAAI,GAAG,MAAM,IAAI;IAK/E,mFAAmF;IACnF,OAAO,CAAC,UAAU,EAAE,MAAM,EAAE,KAAK,EAAE,UAAU,GAAG,IAAI;CAcpD;AAED;;;;;;GAMG;AACH,wBAAgB,qBAAqB,CACpC,IAAI,EAAE,MAAM,EACZ,QAAQ,EAAE,MAAM,EAChB,SAAS,EAAE,yBAAyB,EACpC,QAAQ,SAA2B,GACjC,IAAI,CAgBN"}
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Reactivity notify transport — the one-way `NotificationV1` delivery primitive (`docs/reactivity.md`
|
|
3
|
+
* §Propagation).
|
|
4
|
+
*
|
|
5
|
+
* Unlike the cohort-gossip transport (which *broadcasts* a frame to a FRET-assembled cohort), notify is
|
|
6
|
+
* **unicast**: the fan-out orchestration above this layer (the `reactivity-forwarder-host` ticket) decides
|
|
7
|
+
* who to dial and calls {@link ReactivityNotifyTransport.send} once per named target. This module owns only
|
|
8
|
+
* the framing + dial + inbound-decode plumbing — no fan-out, no role decision, no gossip.
|
|
9
|
+
*
|
|
10
|
+
* The db-core `NotificationV1` codec ({@link encodeNotificationV1} / {@link decodeNotificationV1}) does the
|
|
11
|
+
* length-prefixed JSON framing; this layer rides one self-delimiting frame each way over the notify
|
|
12
|
+
* protocol, reusing the cohort-topic {@link sendOneWay} / {@link readAllBounded} stream lifecycle so the
|
|
13
|
+
* two protocol families behave identically on the wire.
|
|
14
|
+
*
|
|
15
|
+
* Failure isolation is the load-bearing property: notify is fire-and-forget and hint-only, so a dead /
|
|
16
|
+
* unreachable target's rejection is swallowed (logged), never propagated to the caller's fan-out loop or a
|
|
17
|
+
* commit. There is no reply frame — a handler that tried to send one back would desync the dialer's
|
|
18
|
+
* {@link sendOneWay} (which closes after send).
|
|
19
|
+
*/
|
|
20
|
+
import { encodeNotificationV1, decodeNotificationV1 } from "@optimystic/db-core";
|
|
21
|
+
import { peerIdFromString } from "@libp2p/peer-id";
|
|
22
|
+
import { readAllBounded } from "p2p-fret";
|
|
23
|
+
import { peerIdToBytes } from "../cohort-topic/peer-codec.js";
|
|
24
|
+
import { sendOneWay, DEFAULT_STREAM_MAX_BYTES } from "../cohort-topic/stream-util.js";
|
|
25
|
+
import { PROTOCOL_REACTIVITY_NOTIFY } from "./protocols.js";
|
|
26
|
+
import { createLogger } from "../logger.js";
|
|
27
|
+
const log = createLogger("reactivity-notify");
|
|
28
|
+
/**
|
|
29
|
+
* libp2p-backed {@link ReactivityNotifyTransport}: {@link send} frames + dials a single target over
|
|
30
|
+
* `/optimystic/reactivity/1.0.0/notify` (fire-and-forget, failure-isolated); inbound frames arrive through
|
|
31
|
+
* the host's notify protocol handler ({@link registerNotifyHandler}), which calls {@link deliver}.
|
|
32
|
+
* Subscribers registered via {@link onNotification} see every decoded notification.
|
|
33
|
+
*/
|
|
34
|
+
export class Libp2pReactivityNotifyTransport {
|
|
35
|
+
node;
|
|
36
|
+
handlers = new Set();
|
|
37
|
+
notifyProtocol;
|
|
38
|
+
maxBytes;
|
|
39
|
+
selfPeerId;
|
|
40
|
+
constructor(node, options = {}) {
|
|
41
|
+
this.node = node;
|
|
42
|
+
this.notifyProtocol = options.notifyProtocol ?? PROTOCOL_REACTIVITY_NOTIFY;
|
|
43
|
+
this.maxBytes = options.maxBytes ?? DEFAULT_STREAM_MAX_BYTES;
|
|
44
|
+
this.selfPeerId = options.selfPeerId;
|
|
45
|
+
}
|
|
46
|
+
send(target, n) {
|
|
47
|
+
if (this.selfPeerId !== undefined && target === this.selfPeerId) {
|
|
48
|
+
// Never dial self; a co-located subscriber is delivered in-process by the forwarder host.
|
|
49
|
+
return Promise.resolve();
|
|
50
|
+
}
|
|
51
|
+
try {
|
|
52
|
+
const frame = encodeNotificationV1(n);
|
|
53
|
+
return sendOneWay(this.node, peerIdFromString(target), this.notifyProtocol, frame).catch((err) => {
|
|
54
|
+
// Best-effort, failure-isolated: a dead/unreachable target must not break the fan-out or a commit.
|
|
55
|
+
log("send to %s failed (swallowed): %o", target, err);
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
catch (err) {
|
|
59
|
+
// Malformed notification or peer-id string: log + drop, never reject (reactivity is hint-only).
|
|
60
|
+
log("dropped a send to %s: %o", target, err);
|
|
61
|
+
return Promise.resolve();
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
onNotification(handler) {
|
|
65
|
+
this.handlers.add(handler);
|
|
66
|
+
return () => this.handlers.delete(handler);
|
|
67
|
+
}
|
|
68
|
+
/** Feed an inbound notify frame (called by the host's notify protocol handler). */
|
|
69
|
+
deliver(fromPeerId, frame) {
|
|
70
|
+
let n;
|
|
71
|
+
try {
|
|
72
|
+
n = decodeNotificationV1(frame, this.maxBytes);
|
|
73
|
+
}
|
|
74
|
+
catch (err) {
|
|
75
|
+
// A malformed frame must never throw out of a stream handler: log + drop.
|
|
76
|
+
log("dropped an undecodable inbound frame from %s: %o", fromPeerId, err);
|
|
77
|
+
return;
|
|
78
|
+
}
|
|
79
|
+
const from = { id: peerIdToBytes(fromPeerId) };
|
|
80
|
+
for (const handler of this.handlers) {
|
|
81
|
+
handler(from, n);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* Register the inbound notify protocol handler: read one bounded frame and hand it to
|
|
87
|
+
* {@link ReactivityNotifyTransport.deliver}, then close. One-way — no reply frame (notify is strictly
|
|
88
|
+
* fire-and-forget; a reply would desync the dialer's {@link sendOneWay}). Mirrors the cohort-topic
|
|
89
|
+
* one-way handlers: a read error aborts the stream, and {@link ReactivityNotifyTransport.deliver}
|
|
90
|
+
* swallows a decode failure, so the handler never throws on the stream.
|
|
91
|
+
*/
|
|
92
|
+
export function registerNotifyHandler(node, protocol, transport, maxBytes = DEFAULT_STREAM_MAX_BYTES) {
|
|
93
|
+
void node.handle(protocol, (stream, connection) => {
|
|
94
|
+
void (async () => {
|
|
95
|
+
try {
|
|
96
|
+
const frame = await readAllBounded(stream, maxBytes);
|
|
97
|
+
transport.deliver(connection.remotePeer.toString(), frame);
|
|
98
|
+
await stream.close();
|
|
99
|
+
}
|
|
100
|
+
catch {
|
|
101
|
+
try {
|
|
102
|
+
stream.abort(new Error("reactivity notify stream handler error"));
|
|
103
|
+
}
|
|
104
|
+
catch {
|
|
105
|
+
/* already aborted */
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
})();
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
//# sourceMappingURL=notify-transport.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"notify-transport.js","sourceRoot":"","sources":["../../../src/reactivity/notify-transport.ts"],"names":[],"mappings":"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"}
|