@optimystic/db-p2p 0.24.0 → 0.24.2

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.
Files changed (60) hide show
  1. package/dist/src/cluster/service.d.ts +8 -0
  2. package/dist/src/cluster/service.d.ts.map +1 -1
  3. package/dist/src/cluster/service.js +16 -4
  4. package/dist/src/cluster/service.js.map +1 -1
  5. package/dist/src/cohort-topic/host.js +34 -11
  6. package/dist/src/cohort-topic/host.js.map +1 -1
  7. package/dist/src/cohort-topic/stream-util.d.ts +25 -11
  8. package/dist/src/cohort-topic/stream-util.d.ts.map +1 -1
  9. package/dist/src/cohort-topic/stream-util.js +31 -19
  10. package/dist/src/cohort-topic/stream-util.js.map +1 -1
  11. package/dist/src/libp2p-key-network.d.ts +68 -0
  12. package/dist/src/libp2p-key-network.d.ts.map +1 -1
  13. package/dist/src/libp2p-key-network.js +123 -14
  14. package/dist/src/libp2p-key-network.js.map +1 -1
  15. package/dist/src/libp2p-node-base.d.ts.map +1 -1
  16. package/dist/src/libp2p-node-base.js +8 -5
  17. package/dist/src/libp2p-node-base.js.map +1 -1
  18. package/dist/src/logger.d.ts +2 -2
  19. package/dist/src/logger.js +2 -2
  20. package/dist/src/matchmaking/query-transport.js +3 -3
  21. package/dist/src/matchmaking/query-transport.js.map +1 -1
  22. package/dist/src/peer-address-book.d.ts +69 -0
  23. package/dist/src/peer-address-book.d.ts.map +1 -1
  24. package/dist/src/peer-address-book.js +110 -15
  25. package/dist/src/peer-address-book.js.map +1 -1
  26. package/dist/src/reactivity/notify-transport.d.ts +4 -4
  27. package/dist/src/reactivity/notify-transport.js +6 -6
  28. package/dist/src/reactivity/notify-transport.js.map +1 -1
  29. package/dist/src/reactivity/push-state-gossip.js +2 -2
  30. package/dist/src/reactivity/push-state-gossip.js.map +1 -1
  31. package/dist/src/reactivity/recover-transport.d.ts +6 -2
  32. package/dist/src/reactivity/recover-transport.d.ts.map +1 -1
  33. package/dist/src/reactivity/recover-transport.js +7 -3
  34. package/dist/src/reactivity/recover-transport.js.map +1 -1
  35. package/dist/src/repo/service.d.ts +6 -0
  36. package/dist/src/repo/service.d.ts.map +1 -1
  37. package/dist/src/repo/service.js +12 -2
  38. package/dist/src/repo/service.js.map +1 -1
  39. package/dist/src/routing/libp2p-known-peers.d.ts.map +1 -1
  40. package/dist/src/routing/libp2p-known-peers.js +5 -0
  41. package/dist/src/routing/libp2p-known-peers.js.map +1 -1
  42. package/dist/src/testing/cohort-topic-mesh-harness.d.ts +13 -6
  43. package/dist/src/testing/cohort-topic-mesh-harness.d.ts.map +1 -1
  44. package/dist/src/testing/cohort-topic-mesh-harness.js +15 -6
  45. package/dist/src/testing/cohort-topic-mesh-harness.js.map +1 -1
  46. package/package.json +3 -3
  47. package/src/cluster/service.ts +305 -293
  48. package/src/cohort-topic/host.ts +2932 -2901
  49. package/src/cohort-topic/stream-util.ts +147 -135
  50. package/src/libp2p-key-network.ts +1235 -1120
  51. package/src/libp2p-node-base.ts +1678 -1675
  52. package/src/logger.ts +27 -27
  53. package/src/matchmaking/query-transport.ts +492 -492
  54. package/src/peer-address-book.ts +266 -149
  55. package/src/reactivity/notify-transport.ts +144 -144
  56. package/src/reactivity/push-state-gossip.ts +291 -291
  57. package/src/reactivity/recover-transport.ts +412 -408
  58. package/src/repo/service.ts +323 -313
  59. package/src/routing/libp2p-known-peers.ts +31 -26
  60. package/src/testing/cohort-topic-mesh-harness.ts +673 -663
@@ -1,144 +1,144 @@
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
-
21
- import type { NotificationV1, PeerRef } from "@optimystic/db-core";
22
- import { encodeNotificationV1, decodeNotificationV1 } from "@optimystic/db-core";
23
- import type { Libp2p } from "libp2p";
24
- import type { Connection, Stream } from "@libp2p/interface";
25
- import { peerIdFromString } from "@libp2p/peer-id";
26
- import { readAllBounded } from "p2p-fret";
27
- import { peerIdToBytes } from "../cohort-topic/peer-codec.js";
28
- import { sendOneWay, DEFAULT_STREAM_MAX_BYTES } from "../cohort-topic/stream-util.js";
29
- import { PROTOCOL_REACTIVITY_NOTIFY } from "./protocols.js";
30
- import { createLogger } from "../logger.js";
31
-
32
- const log = createLogger("reactivity-notify");
33
-
34
- /** One-way `NotificationV1` transport: unicast send, inbound subscribe, and the host's deliver seam. */
35
- export interface ReactivityNotifyTransport {
36
- /**
37
- * Frame `n` ({@link encodeNotificationV1}) and dial `target` (peer-id string) over the notify protocol.
38
- * Fire-and-forget, failure-isolated: a dead/unreachable target's rejection is swallowed (logged), never
39
- * propagated to the caller's fan-out loop.
40
- */
41
- send(target: string, n: NotificationV1): Promise<void>;
42
- /** Subscribe to inbound notifications (after decode); returns an unsubscribe handle. */
43
- onNotification(handler: (from: PeerRef, n: NotificationV1) => void): () => void;
44
- /** Feed an inbound notify frame (called by the host's notify protocol handler). */
45
- deliver(fromPeerId: string, frame: Uint8Array): void;
46
- }
47
-
48
- /** Construction options for {@link Libp2pReactivityNotifyTransport}. */
49
- export interface ReactivityNotifyTransportOptions {
50
- /** Notify protocol ID; default {@link PROTOCOL_REACTIVITY_NOTIFY}. */
51
- readonly notifyProtocol?: string;
52
- /** Per-frame ceiling for the inbound decode bound. Default {@link DEFAULT_STREAM_MAX_BYTES}. */
53
- readonly maxBytes?: number;
54
- /** This node's peer-id string; when set, {@link Libp2pReactivityNotifyTransport.send} never dials self. */
55
- readonly selfPeerId?: string;
56
- }
57
-
58
- /**
59
- * libp2p-backed {@link ReactivityNotifyTransport}: {@link send} frames + dials a single target over
60
- * `/optimystic/reactivity/1.0.0/notify` (fire-and-forget, failure-isolated); inbound frames arrive through
61
- * the host's notify protocol handler ({@link registerNotifyHandler}), which calls {@link deliver}.
62
- * Subscribers registered via {@link onNotification} see every decoded notification.
63
- */
64
- export class Libp2pReactivityNotifyTransport implements ReactivityNotifyTransport {
65
- private readonly handlers = new Set<(from: PeerRef, n: NotificationV1) => void>();
66
- private readonly notifyProtocol: string;
67
- private readonly maxBytes: number;
68
- private readonly selfPeerId?: string;
69
-
70
- constructor(private readonly node: Libp2p, options: ReactivityNotifyTransportOptions = {}) {
71
- this.notifyProtocol = options.notifyProtocol ?? PROTOCOL_REACTIVITY_NOTIFY;
72
- this.maxBytes = options.maxBytes ?? DEFAULT_STREAM_MAX_BYTES;
73
- this.selfPeerId = options.selfPeerId;
74
- }
75
-
76
- send(target: string, n: NotificationV1): Promise<void> {
77
- if (this.selfPeerId !== undefined && target === this.selfPeerId) {
78
- // Never dial self; a co-located subscriber is delivered in-process by the forwarder host.
79
- return Promise.resolve();
80
- }
81
- try {
82
- const frame = encodeNotificationV1(n);
83
- return sendOneWay(this.node, peerIdFromString(target), this.notifyProtocol, frame).catch((err: unknown) => {
84
- // Best-effort, failure-isolated: a dead/unreachable target must not break the fan-out or a commit.
85
- log("send to %s failed (swallowed): %o", target, err);
86
- });
87
- } catch (err) {
88
- // Malformed notification or peer-id string: log + drop, never reject (reactivity is hint-only).
89
- log("dropped a send to %s: %o", target, err);
90
- return Promise.resolve();
91
- }
92
- }
93
-
94
- onNotification(handler: (from: PeerRef, n: NotificationV1) => void): () => void {
95
- this.handlers.add(handler);
96
- return () => this.handlers.delete(handler);
97
- }
98
-
99
- /** Feed an inbound notify frame (called by the host's notify protocol handler). */
100
- deliver(fromPeerId: string, frame: Uint8Array): void {
101
- let n: NotificationV1;
102
- try {
103
- n = decodeNotificationV1(frame, this.maxBytes);
104
- } catch (err) {
105
- // A malformed frame must never throw out of a stream handler: log + drop.
106
- log("dropped an undecodable inbound frame from %s: %o", fromPeerId, err);
107
- return;
108
- }
109
- const from: PeerRef = { id: peerIdToBytes(fromPeerId) };
110
- for (const handler of this.handlers) {
111
- handler(from, n);
112
- }
113
- }
114
- }
115
-
116
- /**
117
- * Register the inbound notify protocol handler: read one bounded frame and hand it to
118
- * {@link ReactivityNotifyTransport.deliver}, then close. One-way — no reply frame (notify is strictly
119
- * fire-and-forget; a reply would desync the dialer's {@link sendOneWay}). Mirrors the cohort-topic
120
- * one-way handlers: a read error aborts the stream, and {@link ReactivityNotifyTransport.deliver}
121
- * swallows a decode failure, so the handler never throws on the stream.
122
- */
123
- export function registerNotifyHandler(
124
- node: Libp2p,
125
- protocol: string,
126
- transport: ReactivityNotifyTransport,
127
- maxBytes = DEFAULT_STREAM_MAX_BYTES,
128
- ): void {
129
- void node.handle(protocol, (stream: Stream, connection: Connection) => {
130
- void (async (): Promise<void> => {
131
- try {
132
- const frame = await readAllBounded(stream, maxBytes);
133
- transport.deliver(connection.remotePeer.toString(), frame);
134
- await stream.close();
135
- } catch {
136
- try {
137
- stream.abort(new Error("reactivity notify stream handler error"));
138
- } catch {
139
- /* already aborted */
140
- }
141
- }
142
- })();
143
- });
144
- }
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}) encodes
11
+ * the body; this layer rides one varint-length-prefixed frame each way over the notify protocol, reusing
12
+ * the cohort-topic {@link sendOneWay} / FRET `readFramed` stream lifecycle so the two protocol families
13
+ * 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
+
21
+ import type { NotificationV1, PeerRef } from "@optimystic/db-core";
22
+ import { encodeNotificationV1, decodeNotificationV1 } from "@optimystic/db-core";
23
+ import type { Libp2p } from "libp2p";
24
+ import type { Connection, Stream } from "@libp2p/interface";
25
+ import { peerIdFromString } from "@libp2p/peer-id";
26
+ import { readFramed } from "p2p-fret";
27
+ import { peerIdToBytes } from "../cohort-topic/peer-codec.js";
28
+ import { sendOneWay, DEFAULT_STREAM_MAX_BYTES } from "../cohort-topic/stream-util.js";
29
+ import { PROTOCOL_REACTIVITY_NOTIFY } from "./protocols.js";
30
+ import { createLogger } from "../logger.js";
31
+
32
+ const log = createLogger("reactivity-notify");
33
+
34
+ /** One-way `NotificationV1` transport: unicast send, inbound subscribe, and the host's deliver seam. */
35
+ export interface ReactivityNotifyTransport {
36
+ /**
37
+ * Frame `n` ({@link encodeNotificationV1}) and dial `target` (peer-id string) over the notify protocol.
38
+ * Fire-and-forget, failure-isolated: a dead/unreachable target's rejection is swallowed (logged), never
39
+ * propagated to the caller's fan-out loop.
40
+ */
41
+ send(target: string, n: NotificationV1): Promise<void>;
42
+ /** Subscribe to inbound notifications (after decode); returns an unsubscribe handle. */
43
+ onNotification(handler: (from: PeerRef, n: NotificationV1) => void): () => void;
44
+ /** Feed an inbound notify frame (called by the host's notify protocol handler). */
45
+ deliver(fromPeerId: string, frame: Uint8Array): void;
46
+ }
47
+
48
+ /** Construction options for {@link Libp2pReactivityNotifyTransport}. */
49
+ export interface ReactivityNotifyTransportOptions {
50
+ /** Notify protocol ID; default {@link PROTOCOL_REACTIVITY_NOTIFY}. */
51
+ readonly notifyProtocol?: string;
52
+ /** Per-frame ceiling for the inbound decode bound. Default {@link DEFAULT_STREAM_MAX_BYTES}. */
53
+ readonly maxBytes?: number;
54
+ /** This node's peer-id string; when set, {@link Libp2pReactivityNotifyTransport.send} never dials self. */
55
+ readonly selfPeerId?: string;
56
+ }
57
+
58
+ /**
59
+ * libp2p-backed {@link ReactivityNotifyTransport}: {@link send} frames + dials a single target over
60
+ * `/optimystic/reactivity/1.0.0/notify` (fire-and-forget, failure-isolated); inbound frames arrive through
61
+ * the host's notify protocol handler ({@link registerNotifyHandler}), which calls {@link deliver}.
62
+ * Subscribers registered via {@link onNotification} see every decoded notification.
63
+ */
64
+ export class Libp2pReactivityNotifyTransport implements ReactivityNotifyTransport {
65
+ private readonly handlers = new Set<(from: PeerRef, n: NotificationV1) => void>();
66
+ private readonly notifyProtocol: string;
67
+ private readonly maxBytes: number;
68
+ private readonly selfPeerId?: string;
69
+
70
+ constructor(private readonly node: Libp2p, options: ReactivityNotifyTransportOptions = {}) {
71
+ this.notifyProtocol = options.notifyProtocol ?? PROTOCOL_REACTIVITY_NOTIFY;
72
+ this.maxBytes = options.maxBytes ?? DEFAULT_STREAM_MAX_BYTES;
73
+ this.selfPeerId = options.selfPeerId;
74
+ }
75
+
76
+ send(target: string, n: NotificationV1): Promise<void> {
77
+ if (this.selfPeerId !== undefined && target === this.selfPeerId) {
78
+ // Never dial self; a co-located subscriber is delivered in-process by the forwarder host.
79
+ return Promise.resolve();
80
+ }
81
+ try {
82
+ const frame = encodeNotificationV1(n);
83
+ return sendOneWay(this.node, peerIdFromString(target), this.notifyProtocol, frame).catch((err: unknown) => {
84
+ // Best-effort, failure-isolated: a dead/unreachable target must not break the fan-out or a commit.
85
+ log("send to %s failed (swallowed): %o", target, err);
86
+ });
87
+ } catch (err) {
88
+ // Malformed notification or peer-id string: log + drop, never reject (reactivity is hint-only).
89
+ log("dropped a send to %s: %o", target, err);
90
+ return Promise.resolve();
91
+ }
92
+ }
93
+
94
+ onNotification(handler: (from: PeerRef, n: NotificationV1) => void): () => void {
95
+ this.handlers.add(handler);
96
+ return () => this.handlers.delete(handler);
97
+ }
98
+
99
+ /** Feed an inbound notify frame (called by the host's notify protocol handler). */
100
+ deliver(fromPeerId: string, frame: Uint8Array): void {
101
+ let n: NotificationV1;
102
+ try {
103
+ n = decodeNotificationV1(frame, this.maxBytes);
104
+ } catch (err) {
105
+ // A malformed frame must never throw out of a stream handler: log + drop.
106
+ log("dropped an undecodable inbound frame from %s: %o", fromPeerId, err);
107
+ return;
108
+ }
109
+ const from: PeerRef = { id: peerIdToBytes(fromPeerId) };
110
+ for (const handler of this.handlers) {
111
+ handler(from, n);
112
+ }
113
+ }
114
+ }
115
+
116
+ /**
117
+ * Register the inbound notify protocol handler: read one bounded frame and hand it to
118
+ * {@link ReactivityNotifyTransport.deliver}, then close. One-way — no reply frame (notify is strictly
119
+ * fire-and-forget; a reply would desync the dialer's {@link sendOneWay}). Mirrors the cohort-topic
120
+ * one-way handlers: a read error aborts the stream, and {@link ReactivityNotifyTransport.deliver}
121
+ * swallows a decode failure, so the handler never throws on the stream.
122
+ */
123
+ export function registerNotifyHandler(
124
+ node: Libp2p,
125
+ protocol: string,
126
+ transport: ReactivityNotifyTransport,
127
+ maxBytes = DEFAULT_STREAM_MAX_BYTES,
128
+ ): void {
129
+ void node.handle(protocol, (stream: Stream, connection: Connection) => {
130
+ void (async (): Promise<void> => {
131
+ try {
132
+ const frame = await readFramed(stream, maxBytes);
133
+ transport.deliver(connection.remotePeer.toString(), frame);
134
+ await stream.close();
135
+ } catch {
136
+ try {
137
+ stream.abort(new Error("reactivity notify stream handler error"));
138
+ } catch {
139
+ /* already aborted */
140
+ }
141
+ }
142
+ })();
143
+ });
144
+ }