@bandeira-tech/b3nd-save 0.8.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.
Files changed (177) hide show
  1. package/LICENSE +21 -0
  2. package/esm/_dnt.shims.d.ts +2 -0
  3. package/esm/_dnt.shims.d.ts.map +1 -0
  4. package/esm/_dnt.shims.js +57 -0
  5. package/esm/byte-entity-shim.d.ts +47 -0
  6. package/esm/byte-entity-shim.d.ts.map +1 -0
  7. package/esm/byte-entity-shim.js +138 -0
  8. package/esm/clients/mod.d.ts +15 -0
  9. package/esm/clients/mod.d.ts.map +1 -0
  10. package/esm/clients/mod.js +14 -0
  11. package/esm/clients/save-client.d.ts +87 -0
  12. package/esm/clients/save-client.d.ts.map +1 -0
  13. package/esm/clients/save-client.js +174 -0
  14. package/esm/deps/jsr.io/@bandeira-tech/b3nd-core/0.22.0/mod.d.ts +40 -0
  15. package/esm/deps/jsr.io/@bandeira-tech/b3nd-core/0.22.0/mod.d.ts.map +1 -0
  16. package/esm/deps/jsr.io/@bandeira-tech/b3nd-core/0.22.0/mod.js +31 -0
  17. package/esm/deps/jsr.io/@bandeira-tech/b3nd-core/0.22.0/src/client-console/client.d.ts +30 -0
  18. package/esm/deps/jsr.io/@bandeira-tech/b3nd-core/0.22.0/src/client-console/client.d.ts.map +1 -0
  19. package/esm/deps/jsr.io/@bandeira-tech/b3nd-core/0.22.0/src/client-console/client.js +70 -0
  20. package/esm/deps/jsr.io/@bandeira-tech/b3nd-core/0.22.0/src/encoding/encoding.d.ts +16 -0
  21. package/esm/deps/jsr.io/@bandeira-tech/b3nd-core/0.22.0/src/encoding/encoding.d.ts.map +1 -0
  22. package/esm/deps/jsr.io/@bandeira-tech/b3nd-core/0.22.0/src/encoding/encoding.js +50 -0
  23. package/esm/deps/jsr.io/@bandeira-tech/b3nd-core/0.22.0/src/encrypt/mod.d.ts +235 -0
  24. package/esm/deps/jsr.io/@bandeira-tech/b3nd-core/0.22.0/src/encrypt/mod.d.ts.map +1 -0
  25. package/esm/deps/jsr.io/@bandeira-tech/b3nd-core/0.22.0/src/encrypt/mod.js +619 -0
  26. package/esm/deps/jsr.io/@bandeira-tech/b3nd-core/0.22.0/src/functional-client/functional-client.d.ts +43 -0
  27. package/esm/deps/jsr.io/@bandeira-tech/b3nd-core/0.22.0/src/functional-client/functional-client.d.ts.map +1 -0
  28. package/esm/deps/jsr.io/@bandeira-tech/b3nd-core/0.22.0/src/functional-client/functional-client.js +54 -0
  29. package/esm/deps/jsr.io/@bandeira-tech/b3nd-core/0.22.0/src/match-pattern/match-pattern.d.ts +47 -0
  30. package/esm/deps/jsr.io/@bandeira-tech/b3nd-core/0.22.0/src/match-pattern/match-pattern.d.ts.map +1 -0
  31. package/esm/deps/jsr.io/@bandeira-tech/b3nd-core/0.22.0/src/match-pattern/match-pattern.js +134 -0
  32. package/esm/deps/jsr.io/@bandeira-tech/b3nd-core/0.22.0/src/network/decorators.d.ts +20 -0
  33. package/esm/deps/jsr.io/@bandeira-tech/b3nd-core/0.22.0/src/network/decorators.d.ts.map +1 -0
  34. package/esm/deps/jsr.io/@bandeira-tech/b3nd-core/0.22.0/src/network/decorators.js +34 -0
  35. package/esm/deps/jsr.io/@bandeira-tech/b3nd-core/0.22.0/src/network/network.d.ts +46 -0
  36. package/esm/deps/jsr.io/@bandeira-tech/b3nd-core/0.22.0/src/network/network.d.ts.map +1 -0
  37. package/esm/deps/jsr.io/@bandeira-tech/b3nd-core/0.22.0/src/network/network.js +131 -0
  38. package/esm/deps/jsr.io/@bandeira-tech/b3nd-core/0.22.0/src/network/peer.d.ts +27 -0
  39. package/esm/deps/jsr.io/@bandeira-tech/b3nd-core/0.22.0/src/network/peer.d.ts.map +1 -0
  40. package/esm/deps/jsr.io/@bandeira-tech/b3nd-core/0.22.0/src/network/peer.js +25 -0
  41. package/esm/deps/jsr.io/@bandeira-tech/b3nd-core/0.22.0/src/network/policies/flood.d.ts +59 -0
  42. package/esm/deps/jsr.io/@bandeira-tech/b3nd-core/0.22.0/src/network/policies/flood.d.ts.map +1 -0
  43. package/esm/deps/jsr.io/@bandeira-tech/b3nd-core/0.22.0/src/network/policies/flood.js +188 -0
  44. package/esm/deps/jsr.io/@bandeira-tech/b3nd-core/0.22.0/src/network/policies/path-vector.d.ts +35 -0
  45. package/esm/deps/jsr.io/@bandeira-tech/b3nd-core/0.22.0/src/network/policies/path-vector.d.ts.map +1 -0
  46. package/esm/deps/jsr.io/@bandeira-tech/b3nd-core/0.22.0/src/network/policies/path-vector.js +57 -0
  47. package/esm/deps/jsr.io/@bandeira-tech/b3nd-core/0.22.0/src/network/policies/tell-and-read.d.ts +117 -0
  48. package/esm/deps/jsr.io/@bandeira-tech/b3nd-core/0.22.0/src/network/policies/tell-and-read.d.ts.map +1 -0
  49. package/esm/deps/jsr.io/@bandeira-tech/b3nd-core/0.22.0/src/network/policies/tell-and-read.js +125 -0
  50. package/esm/deps/jsr.io/@bandeira-tech/b3nd-core/0.22.0/src/network/types.d.ts +112 -0
  51. package/esm/deps/jsr.io/@bandeira-tech/b3nd-core/0.22.0/src/network/types.d.ts.map +1 -0
  52. package/esm/deps/jsr.io/@bandeira-tech/b3nd-core/0.22.0/src/network/types.js +21 -0
  53. package/esm/deps/jsr.io/@bandeira-tech/b3nd-core/0.22.0/src/observe-emitter/observe-emitter.d.ts +35 -0
  54. package/esm/deps/jsr.io/@bandeira-tech/b3nd-core/0.22.0/src/observe-emitter/observe-emitter.d.ts.map +1 -0
  55. package/esm/deps/jsr.io/@bandeira-tech/b3nd-core/0.22.0/src/observe-emitter/observe-emitter.js +106 -0
  56. package/esm/deps/jsr.io/@bandeira-tech/b3nd-core/0.22.0/src/rig/connection.d.ts +88 -0
  57. package/esm/deps/jsr.io/@bandeira-tech/b3nd-core/0.22.0/src/rig/connection.d.ts.map +1 -0
  58. package/esm/deps/jsr.io/@bandeira-tech/b3nd-core/0.22.0/src/rig/connection.js +86 -0
  59. package/esm/deps/jsr.io/@bandeira-tech/b3nd-core/0.22.0/src/rig/events.d.ts +83 -0
  60. package/esm/deps/jsr.io/@bandeira-tech/b3nd-core/0.22.0/src/rig/events.d.ts.map +1 -0
  61. package/esm/deps/jsr.io/@bandeira-tech/b3nd-core/0.22.0/src/rig/events.js +115 -0
  62. package/esm/deps/jsr.io/@bandeira-tech/b3nd-core/0.22.0/src/rig/hooks.d.ts +164 -0
  63. package/esm/deps/jsr.io/@bandeira-tech/b3nd-core/0.22.0/src/rig/hooks.d.ts.map +1 -0
  64. package/esm/deps/jsr.io/@bandeira-tech/b3nd-core/0.22.0/src/rig/hooks.js +67 -0
  65. package/esm/deps/jsr.io/@bandeira-tech/b3nd-core/0.22.0/src/rig/identity.d.ts +99 -0
  66. package/esm/deps/jsr.io/@bandeira-tech/b3nd-core/0.22.0/src/rig/identity.d.ts.map +1 -0
  67. package/esm/deps/jsr.io/@bandeira-tech/b3nd-core/0.22.0/src/rig/identity.js +190 -0
  68. package/esm/deps/jsr.io/@bandeira-tech/b3nd-core/0.22.0/src/rig/operation-handle.d.ts +185 -0
  69. package/esm/deps/jsr.io/@bandeira-tech/b3nd-core/0.22.0/src/rig/operation-handle.d.ts.map +1 -0
  70. package/esm/deps/jsr.io/@bandeira-tech/b3nd-core/0.22.0/src/rig/operation-handle.js +103 -0
  71. package/esm/deps/jsr.io/@bandeira-tech/b3nd-core/0.22.0/src/rig/reactions.d.ts +58 -0
  72. package/esm/deps/jsr.io/@bandeira-tech/b3nd-core/0.22.0/src/rig/reactions.d.ts.map +1 -0
  73. package/esm/deps/jsr.io/@bandeira-tech/b3nd-core/0.22.0/src/rig/reactions.js +64 -0
  74. package/esm/deps/jsr.io/@bandeira-tech/b3nd-core/0.22.0/src/rig/rig.d.ts +295 -0
  75. package/esm/deps/jsr.io/@bandeira-tech/b3nd-core/0.22.0/src/rig/rig.d.ts.map +1 -0
  76. package/esm/deps/jsr.io/@bandeira-tech/b3nd-core/0.22.0/src/rig/rig.js +898 -0
  77. package/esm/deps/jsr.io/@bandeira-tech/b3nd-core/0.22.0/src/rig/types.d.ts +167 -0
  78. package/esm/deps/jsr.io/@bandeira-tech/b3nd-core/0.22.0/src/rig/types.d.ts.map +1 -0
  79. package/esm/deps/jsr.io/@bandeira-tech/b3nd-core/0.22.0/src/rig/types.js +5 -0
  80. package/esm/deps/jsr.io/@bandeira-tech/b3nd-core/0.22.0/src/types/types.d.ts +278 -0
  81. package/esm/deps/jsr.io/@bandeira-tech/b3nd-core/0.22.0/src/types/types.d.ts.map +1 -0
  82. package/esm/deps/jsr.io/@bandeira-tech/b3nd-core/0.22.0/src/types/types.js +89 -0
  83. package/esm/dispatch.d.ts +35 -0
  84. package/esm/dispatch.d.ts.map +1 -0
  85. package/esm/dispatch.js +40 -0
  86. package/esm/elasticsearch/mod.d.ts +31 -0
  87. package/esm/elasticsearch/mod.d.ts.map +1 -0
  88. package/esm/elasticsearch/mod.js +8 -0
  89. package/esm/elasticsearch/store.d.ts +44 -0
  90. package/esm/elasticsearch/store.d.ts.map +1 -0
  91. package/esm/elasticsearch/store.js +213 -0
  92. package/esm/entity-store.d.ts +106 -0
  93. package/esm/entity-store.d.ts.map +1 -0
  94. package/esm/entity-store.js +46 -0
  95. package/esm/entity.d.ts +125 -0
  96. package/esm/entity.d.ts.map +1 -0
  97. package/esm/entity.js +66 -0
  98. package/esm/errors.d.ts +23 -0
  99. package/esm/errors.d.ts.map +1 -0
  100. package/esm/errors.js +25 -0
  101. package/esm/fs/mod.d.ts +28 -0
  102. package/esm/fs/mod.d.ts.map +1 -0
  103. package/esm/fs/mod.js +13 -0
  104. package/esm/fs/store.d.ts +45 -0
  105. package/esm/fs/store.d.ts.map +1 -0
  106. package/esm/fs/store.js +194 -0
  107. package/esm/indexeddb/mod.d.ts +9 -0
  108. package/esm/indexeddb/mod.d.ts.map +1 -0
  109. package/esm/indexeddb/mod.js +8 -0
  110. package/esm/indexeddb/store.d.ts +47 -0
  111. package/esm/indexeddb/store.d.ts.map +1 -0
  112. package/esm/indexeddb/store.js +299 -0
  113. package/esm/ipfs/mod.d.ts +24 -0
  114. package/esm/ipfs/mod.d.ts.map +1 -0
  115. package/esm/ipfs/mod.js +13 -0
  116. package/esm/ipfs/store.d.ts +42 -0
  117. package/esm/ipfs/store.d.ts.map +1 -0
  118. package/esm/ipfs/store.js +197 -0
  119. package/esm/localstorage/mod.d.ts +9 -0
  120. package/esm/localstorage/mod.d.ts.map +1 -0
  121. package/esm/localstorage/mod.js +8 -0
  122. package/esm/localstorage/store.d.ts +45 -0
  123. package/esm/localstorage/store.d.ts.map +1 -0
  124. package/esm/localstorage/store.js +147 -0
  125. package/esm/memory/mod.d.ts +12 -0
  126. package/esm/memory/mod.d.ts.map +1 -0
  127. package/esm/memory/mod.js +11 -0
  128. package/esm/memory/store.d.ts +73 -0
  129. package/esm/memory/store.d.ts.map +1 -0
  130. package/esm/memory/store.js +300 -0
  131. package/esm/mod.d.ts +36 -0
  132. package/esm/mod.d.ts.map +1 -0
  133. package/esm/mod.js +31 -0
  134. package/esm/mongo/mod.d.ts +35 -0
  135. package/esm/mongo/mod.d.ts.map +1 -0
  136. package/esm/mongo/mod.js +8 -0
  137. package/esm/mongo/store.d.ts +42 -0
  138. package/esm/mongo/store.d.ts.map +1 -0
  139. package/esm/mongo/store.js +185 -0
  140. package/esm/package.json +3 -0
  141. package/esm/payload.d.ts +24 -0
  142. package/esm/payload.d.ts.map +1 -0
  143. package/esm/payload.js +30 -0
  144. package/esm/postgres/columns.d.ts +33 -0
  145. package/esm/postgres/columns.d.ts.map +1 -0
  146. package/esm/postgres/columns.js +51 -0
  147. package/esm/postgres/mod.d.ts +19 -0
  148. package/esm/postgres/mod.d.ts.map +1 -0
  149. package/esm/postgres/mod.js +9 -0
  150. package/esm/postgres/store.d.ts +75 -0
  151. package/esm/postgres/store.d.ts.map +1 -0
  152. package/esm/postgres/store.js +364 -0
  153. package/esm/read.d.ts +40 -0
  154. package/esm/read.d.ts.map +1 -0
  155. package/esm/read.js +67 -0
  156. package/esm/s3/mod.d.ts +23 -0
  157. package/esm/s3/mod.d.ts.map +1 -0
  158. package/esm/s3/mod.js +13 -0
  159. package/esm/s3/store.d.ts +46 -0
  160. package/esm/s3/store.d.ts.map +1 -0
  161. package/esm/s3/store.js +183 -0
  162. package/esm/sqlite/mod.d.ts +18 -0
  163. package/esm/sqlite/mod.d.ts.map +1 -0
  164. package/esm/sqlite/mod.js +8 -0
  165. package/esm/sqlite/schema.d.ts +10 -0
  166. package/esm/sqlite/schema.d.ts.map +1 -0
  167. package/esm/sqlite/schema.js +26 -0
  168. package/esm/sqlite/store.d.ts +42 -0
  169. package/esm/sqlite/store.d.ts.map +1 -0
  170. package/esm/sqlite/store.js +184 -0
  171. package/esm/types.d.ts +101 -0
  172. package/esm/types.d.ts.map +1 -0
  173. package/esm/types.js +21 -0
  174. package/esm/url.d.ts +108 -0
  175. package/esm/url.d.ts.map +1 -0
  176. package/esm/url.js +151 -0
  177. package/package.json +93 -0
@@ -0,0 +1,188 @@
1
+ /**
2
+ * @module
3
+ * `flood(peers)` — the baseline remote-client strategy.
4
+ *
5
+ * Returns a `ProtocolInterfaceNode` that:
6
+ * - **receive**: fans every write out to every peer in parallel
7
+ * - **read**: tries peers in order and returns the first successful hit
8
+ * - **observe**: merges every peer's observe stream into one iterator
9
+ * - **status**: aggregates peer health (healthy / degraded / unhealthy)
10
+ *
11
+ * Use as a rig connection:
12
+ *
13
+ * ```ts
14
+ * import { connection, Rig } from "@bandeira-tech/b3nd-core";
15
+ * import { flood, peer } from "@bandeira-tech/b3nd-core/network";
16
+ *
17
+ * const peers = connection(flood([peer(remoteClient)]), ["**"]);
18
+ * const rig = new Rig({
19
+ * routes: { receive: [peers], read: [peers], observe: [peers] },
20
+ * });
21
+ * ```
22
+ *
23
+ * For bidirectional signed meshes that need loop avoidance, use
24
+ * `pathVector(peers)` instead — same shape, plus a signer-chain filter
25
+ * on outbound.
26
+ *
27
+ * ## Error handling
28
+ *
29
+ * - `receive` propagates transport-level failures — one peer rejecting
30
+ * aborts the fan-out. Wrap individual peers with the `bestEffort`
31
+ * decorator if you want per-peer failures to be non-fatal.
32
+ * - `read` falls through: a failing peer is skipped, and if no peer
33
+ * returns a hit the result is a `not-found` per input URI.
34
+ * - `observe` silently drops a peer whose stream throws (the merged
35
+ * stream keeps flowing from the remaining peers). This is
36
+ * intentional — `flood` is a one-shot PIN with no `onError` hook.
37
+ * For observability over the inbound side, use `network()`, which
38
+ * accepts an `onError` callback and reports per-peer failures.
39
+ * - `status` aggregates; individual unhealthy peers degrade the
40
+ * overall status rather than throwing.
41
+ */
42
+ import { validatePeers } from "../network.js";
43
+ /**
44
+ * Build a flood PIN from peers. Peers must have unique ids and the list
45
+ * must be non-empty.
46
+ */
47
+ export function flood(peers) {
48
+ const { originId, peers: frozenPeers } = validatePeers(peers);
49
+ return floodImpl(originId, frozenPeers, identityTransform);
50
+ }
51
+ const identityTransform = (xs) => xs;
52
+ /**
53
+ * Treat both `undefined` and `null` (common miss representations
54
+ * across protocols), and `[]` (empty ls), as "empty — try next peer."
55
+ * Everything else — including `0` from `fn=count` — is a real answer.
56
+ *
57
+ * Protocols that intentionally store `null` will see those treated as
58
+ * misses by flood; pair a different aggregation policy with such
59
+ * protocols, or wrap the null in a sentinel.
60
+ */
61
+ function isEmptyPayload(payload) {
62
+ if (payload === undefined || payload === null)
63
+ return true;
64
+ if (Array.isArray(payload) && payload.length === 0)
65
+ return true;
66
+ return false;
67
+ }
68
+ /**
69
+ * Internal shared implementation used by `flood`, `pathVector`, and
70
+ * `tellAndRead.outbound`. The `transform` rewrites the per-peer
71
+ * outbound batch: return `msgs` unchanged to flood as-is, return a
72
+ * subset to filter, return rewritten outputs to change what the peer
73
+ * receives, or return `[]` to skip the peer entirely.
74
+ *
75
+ * @internal
76
+ */
77
+ export function floodImpl(originId, peers, transform) {
78
+ return {
79
+ // ── receive ──────────────────────────────────────────────────────
80
+ async receive(msgs) {
81
+ if (msgs.length === 0)
82
+ return [];
83
+ await Promise.all(peers.map(async (p) => {
84
+ const outbound = transform(msgs, p);
85
+ if (outbound.length === 0)
86
+ return;
87
+ await p.client.receive(outbound);
88
+ }));
89
+ // Success per *input* message — we fanned out to the peers the
90
+ // transform selected. Transport-level failures throw and
91
+ // propagate; wrap individual peers with a best-effort decorator
92
+ // if you want rejection tolerance.
93
+ return msgs.map(() => ({ accepted: true }));
94
+ },
95
+ // ── read ─────────────────────────────────────────────────────────
96
+ async read(urls) {
97
+ if (urls.length === 0)
98
+ return [];
99
+ // 1:1 with input. Per url, try peers in order; first peer that
100
+ // returns a non-empty payload wins. "Empty" = `undefined` for
101
+ // point reads, `[]` for `fn=ls`, `0` for `fn=count`. If every
102
+ // peer is empty (or throws), the slot stays empty (`undefined`).
103
+ // Transport errors are swallowed per-peer.
104
+ const out = await Promise.all(urls.map(async (url) => {
105
+ let fallback = [url, undefined];
106
+ for (const p of peers) {
107
+ try {
108
+ const [r] = await p.client.read([url]);
109
+ if (!r)
110
+ continue;
111
+ fallback = r;
112
+ if (!isEmptyPayload(r[1]))
113
+ return r;
114
+ }
115
+ catch {
116
+ // try next peer
117
+ }
118
+ }
119
+ return fallback;
120
+ }));
121
+ return out;
122
+ },
123
+ // ── observe ──────────────────────────────────────────────────────
124
+ async *observe(urls, signal) {
125
+ const queue = [];
126
+ let wake = null;
127
+ const forwarders = peers.map(async (p) => {
128
+ try {
129
+ for await (const r of p.client.observe(urls, signal)) {
130
+ queue.push(r);
131
+ const w = wake;
132
+ if (w) {
133
+ wake = null;
134
+ w();
135
+ }
136
+ }
137
+ }
138
+ catch {
139
+ // Per-peer observe errors are swallowed — one broken peer
140
+ // should not tear down the merged stream. The signal is still
141
+ // the caller's mechanism to stop everything.
142
+ }
143
+ });
144
+ const onAbort = () => {
145
+ const w = wake;
146
+ if (w) {
147
+ wake = null;
148
+ w();
149
+ }
150
+ };
151
+ signal.addEventListener("abort", onAbort, { once: true });
152
+ try {
153
+ while (true) {
154
+ while (queue.length > 0)
155
+ yield queue.shift();
156
+ if (signal.aborted)
157
+ return;
158
+ await new Promise((resolve) => {
159
+ wake = resolve;
160
+ });
161
+ }
162
+ }
163
+ finally {
164
+ signal.removeEventListener("abort", onAbort);
165
+ await Promise.allSettled(forwarders);
166
+ }
167
+ },
168
+ // ── status ───────────────────────────────────────────────────────
169
+ async status() {
170
+ const settled = await Promise.allSettled(peers.map((p) => p.client.status()));
171
+ let healthy = 0;
172
+ for (const s of settled) {
173
+ if (s.status === "fulfilled" && s.value.status === "healthy")
174
+ healthy++;
175
+ }
176
+ const total = peers.length;
177
+ return {
178
+ status: healthy === total
179
+ ? "healthy"
180
+ : healthy > 0
181
+ ? "degraded"
182
+ : "unhealthy",
183
+ message: `${healthy}/${total} peers healthy`,
184
+ details: { originId, peerCount: total, healthyPeers: healthy },
185
+ };
186
+ },
187
+ };
188
+ }
@@ -0,0 +1,35 @@
1
+ /**
2
+ * @module
3
+ * `pathVector(peers)` — flood with signer-chain loop avoidance.
4
+ *
5
+ * Returns the same shape as `flood(peers)` but filters the outbound fan
6
+ * per-peer: before sending a message to peer P, inspect
7
+ * `data.auth[*].pubkey` on the message (the signer chain of an
8
+ * `AuthenticatedMessage`-shaped payload); if P's id appears in that
9
+ * chain, skip P — they have already seen (or signed) the message.
10
+ *
11
+ * Handles arbitrary-length cycles (A → B → C → A) without any state,
12
+ * because the chain grows with every relay that re-signs. Works "for
13
+ * free" when messages are signed with `Identity.sign()` + `message()`.
14
+ *
15
+ * ## Peer id convention
16
+ *
17
+ * For this to work, each `peer.id` must equal the peer's signing pubkey
18
+ * (typically the hex-encoded Ed25519 key):
19
+ *
20
+ * ```ts
21
+ * peer(client, { id: peerPubkeyHex })
22
+ * ```
23
+ *
24
+ * Auto-assigned UUID ids never match any signer and the filter becomes
25
+ * a no-op — pathVector then degenerates to plain `flood`.
26
+ *
27
+ * ## Scope
28
+ *
29
+ * pathVector only *reads* the chain. It does not add the local
30
+ * identity's signature on relay — that's the application's responsibility.
31
+ */
32
+ import type { ProtocolInterfaceNode } from "../../types/types.js";
33
+ import type { Peer } from "../types.js";
34
+ export declare function pathVector(peers: Peer[]): ProtocolInterfaceNode;
35
+ //# sourceMappingURL=path-vector.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"path-vector.d.ts","sourceRoot":"","sources":["../../../../../../../../../src/deps/jsr.io/@bandeira-tech/b3nd-core/0.22.0/src/network/policies/path-vector.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8BG;AAEH,OAAO,KAAK,EAAU,qBAAqB,EAAE,MAAM,sBAAsB,CAAC;AAC1E,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,aAAa,CAAC;AAIxC,wBAAgB,UAAU,CAAC,KAAK,EAAE,IAAI,EAAE,GAAG,qBAAqB,CAO/D"}
@@ -0,0 +1,57 @@
1
+ /**
2
+ * @module
3
+ * `pathVector(peers)` — flood with signer-chain loop avoidance.
4
+ *
5
+ * Returns the same shape as `flood(peers)` but filters the outbound fan
6
+ * per-peer: before sending a message to peer P, inspect
7
+ * `data.auth[*].pubkey` on the message (the signer chain of an
8
+ * `AuthenticatedMessage`-shaped payload); if P's id appears in that
9
+ * chain, skip P — they have already seen (or signed) the message.
10
+ *
11
+ * Handles arbitrary-length cycles (A → B → C → A) without any state,
12
+ * because the chain grows with every relay that re-signs. Works "for
13
+ * free" when messages are signed with `Identity.sign()` + `message()`.
14
+ *
15
+ * ## Peer id convention
16
+ *
17
+ * For this to work, each `peer.id` must equal the peer's signing pubkey
18
+ * (typically the hex-encoded Ed25519 key):
19
+ *
20
+ * ```ts
21
+ * peer(client, { id: peerPubkeyHex })
22
+ * ```
23
+ *
24
+ * Auto-assigned UUID ids never match any signer and the filter becomes
25
+ * a no-op — pathVector then degenerates to plain `flood`.
26
+ *
27
+ * ## Scope
28
+ *
29
+ * pathVector only *reads* the chain. It does not add the local
30
+ * identity's signature on relay — that's the application's responsibility.
31
+ */
32
+ import { validatePeers } from "../network.js";
33
+ import { floodImpl } from "./flood.js";
34
+ export function pathVector(peers) {
35
+ const { originId, peers: frozenPeers } = validatePeers(peers);
36
+ return floodImpl(originId, frozenPeers, (msgs, peer) => msgs.filter((m) => !signerChain(m).includes(peer.id)));
37
+ }
38
+ /**
39
+ * Extract the list of signer pubkeys from an `AuthenticatedMessage`-
40
+ * shaped payload. Returns `[]` for any payload that doesn't carry an
41
+ * `auth` array — non-authenticated outputs flow freely.
42
+ */
43
+ function signerChain(msg) {
44
+ const [, payload] = msg;
45
+ if (!payload || typeof payload !== "object")
46
+ return [];
47
+ const auth = payload.auth;
48
+ if (!Array.isArray(auth))
49
+ return [];
50
+ const keys = [];
51
+ for (const entry of auth) {
52
+ const pubkey = entry?.pubkey;
53
+ if (typeof pubkey === "string")
54
+ keys.push(pubkey);
55
+ }
56
+ return keys;
57
+ }
@@ -0,0 +1,117 @@
1
+ /**
2
+ * @module
3
+ * `tellAndRead(opts)` — INV/READ-style content synchronization.
4
+ *
5
+ * The pattern: instead of pushing full payloads to every peer, announce
6
+ * what you have with a small message; peers that want the content pull
7
+ * it via the existing `read()` primitive (no separate GETDATA message).
8
+ *
9
+ * ```
10
+ * A writes M B observes A's announcement
11
+ * │ │
12
+ * ▼ ▼
13
+ * outbound announce inbound onAnnounce
14
+ * ───────────────── ──────────────────
15
+ * transform full msg parse announcement;
16
+ * into inv/ announcement pull full content
17
+ * sent to peers via source.client.read
18
+ * │ │
19
+ * ▼ ▼
20
+ * peers see inv/ only content flows into
21
+ * (cheap over the wire) rig.receive pipeline
22
+ * ```
23
+ *
24
+ * This is URI-agnostic. The protocol decides:
25
+ * - What an "announcement" looks like (`announce`).
26
+ * - How to recognize one and which URI(s) to pull (`onAnnounce`).
27
+ *
28
+ * The "pull" leg is the existing peer `client.read()` — the remote
29
+ * side's read handler serves the payload with whatever auth/access it
30
+ * already enforces. No GETDATA message, no new transport, no new
31
+ * surface area to reason about.
32
+ *
33
+ * ## Shape
34
+ *
35
+ * `tellAndRead()` returns a pair: an outbound strategy factory
36
+ * (`outbound(peers)` → `ProtocolInterfaceNode`) and an inbound Policy.
37
+ * Use them on their respective sides:
38
+ *
39
+ * ```ts
40
+ * const sync = tellAndRead({
41
+ * announce: (msgs) => msgs.map(([uri, vals, data]) =>
42
+ * uri.startsWith("hash://")
43
+ * ? [`net://inv/${uri}`, { have: uri }]
44
+ * : [uri, vals, data]
45
+ * ),
46
+ * onAnnounce: (ev) => {
47
+ * if (ev.uri?.startsWith("net://inv/")) {
48
+ * return [(ev.record?.data as { have: string }).have];
49
+ * }
50
+ * return null;
51
+ * },
52
+ * });
53
+ *
54
+ * const local = connection(localStore, ["**"]);
55
+ * const outbound = connection(sync.outbound(peers), ["hash://**"]);
56
+ * const rig = new Rig({
57
+ * routes: {
58
+ * receive: [local, outbound],
59
+ * read: [local],
60
+ * observe: [local],
61
+ * },
62
+ * });
63
+ * const unbind = network(rig, peers, [sync.inbound]);
64
+ * ```
65
+ *
66
+ * ## Batching
67
+ *
68
+ * `announce` runs on the full per-peer batch, so the protocol can
69
+ * choose per-item announcements, a single compound announcement
70
+ * carrying many URIs, or per-peer asymmetry (full to trusted, INV to
71
+ * untrusted). `onAnnounce` returns a list of URIs so multi-item
72
+ * announcements fan the pulls out naturally.
73
+ *
74
+ * ## What stays the protocol's responsibility
75
+ *
76
+ * - URI layout for the control plane (`net://inv/...`, `inv://...`,
77
+ * `...?inv=true`, however you want).
78
+ * - Detection (which URIs are announcements vs. data).
79
+ * - "I already have this" short-circuit: `onAnnounce` closes over the
80
+ * protocol's local store to decide whether to return the URI to
81
+ * pull. The network layer doesn't plumb a local accessor.
82
+ */
83
+ import type { Output, ProtocolInterfaceNode } from "../../types/types.js";
84
+ import type { Peer, Policy } from "../types.js";
85
+ export interface TellAndReadOptions {
86
+ /**
87
+ * Transform outbound outputs per peer. Return the outputs that
88
+ * should actually be sent to this peer: pass-through (`msgs`), a
89
+ * filtered subset, or rewritten announcements. Return `[]` to skip
90
+ * the peer entirely.
91
+ *
92
+ * Defaults to identity — `tellAndRead` without an `announce` is
93
+ * effectively `flood` on the outbound side.
94
+ */
95
+ announce?: (msgs: Output[], peer: Peer) => Output[];
96
+ /**
97
+ * Examine an inbound event. Return:
98
+ * - `null` / `undefined` — not an announcement. The event passes
99
+ * through to the target unchanged.
100
+ * - `string[]` — URIs to pull from the source peer via
101
+ * `source.client.read`. The fetched content replaces the
102
+ * announcement in the inbound stream. Return `[]` to consume the
103
+ * announcement without pulling anything (e.g., "I already have it").
104
+ */
105
+ onAnnounce?: (ev: Output<unknown>, source: Peer) => Promise<string[] | null> | string[] | null;
106
+ }
107
+ export interface TellAndReadBundle {
108
+ /** Strategy factory — build the outbound PIN for `connection()`. */
109
+ outbound: (peers: Peer[]) => ProtocolInterfaceNode;
110
+ /** Participant-side Policy — pass to `network(rig, peers, [bundle.inbound])`. */
111
+ inbound: Policy;
112
+ }
113
+ /**
114
+ * Construct a matched outbound/inbound pair for INV/READ-style sync.
115
+ */
116
+ export declare function tellAndRead(opts: TellAndReadOptions): TellAndReadBundle;
117
+ //# sourceMappingURL=tell-and-read.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tell-and-read.d.ts","sourceRoot":"","sources":["../../../../../../../../../src/deps/jsr.io/@bandeira-tech/b3nd-core/0.22.0/src/network/policies/tell-and-read.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAiFG;AAEH,OAAO,KAAK,EAAE,MAAM,EAAE,qBAAqB,EAAE,MAAM,sBAAsB,CAAC;AAC1E,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AAIhD,MAAM,WAAW,kBAAkB;IACjC;;;;;;;;OAQG;IACH,QAAQ,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE,IAAI,EAAE,IAAI,KAAK,MAAM,EAAE,CAAC;IAEpD;;;;;;;;OAQG;IACH,UAAU,CAAC,EAAE,CACX,EAAE,EAAE,MAAM,CAAC,OAAO,CAAC,EACnB,MAAM,EAAE,IAAI,KACT,OAAO,CAAC,MAAM,EAAE,GAAG,IAAI,CAAC,GAAG,MAAM,EAAE,GAAG,IAAI,CAAC;CACjD;AAED,MAAM,WAAW,iBAAiB;IAChC,oEAAoE;IACpE,QAAQ,EAAE,CAAC,KAAK,EAAE,IAAI,EAAE,KAAK,qBAAqB,CAAC;IACnD,iFAAiF;IACjF,OAAO,EAAE,MAAM,CAAC;CACjB;AAED;;GAEG;AACH,wBAAgB,WAAW,CAAC,IAAI,EAAE,kBAAkB,GAAG,iBAAiB,CAwCvE"}
@@ -0,0 +1,125 @@
1
+ /**
2
+ * @module
3
+ * `tellAndRead(opts)` — INV/READ-style content synchronization.
4
+ *
5
+ * The pattern: instead of pushing full payloads to every peer, announce
6
+ * what you have with a small message; peers that want the content pull
7
+ * it via the existing `read()` primitive (no separate GETDATA message).
8
+ *
9
+ * ```
10
+ * A writes M B observes A's announcement
11
+ * │ │
12
+ * ▼ ▼
13
+ * outbound announce inbound onAnnounce
14
+ * ───────────────── ──────────────────
15
+ * transform full msg parse announcement;
16
+ * into inv/ announcement pull full content
17
+ * sent to peers via source.client.read
18
+ * │ │
19
+ * ▼ ▼
20
+ * peers see inv/ only content flows into
21
+ * (cheap over the wire) rig.receive pipeline
22
+ * ```
23
+ *
24
+ * This is URI-agnostic. The protocol decides:
25
+ * - What an "announcement" looks like (`announce`).
26
+ * - How to recognize one and which URI(s) to pull (`onAnnounce`).
27
+ *
28
+ * The "pull" leg is the existing peer `client.read()` — the remote
29
+ * side's read handler serves the payload with whatever auth/access it
30
+ * already enforces. No GETDATA message, no new transport, no new
31
+ * surface area to reason about.
32
+ *
33
+ * ## Shape
34
+ *
35
+ * `tellAndRead()` returns a pair: an outbound strategy factory
36
+ * (`outbound(peers)` → `ProtocolInterfaceNode`) and an inbound Policy.
37
+ * Use them on their respective sides:
38
+ *
39
+ * ```ts
40
+ * const sync = tellAndRead({
41
+ * announce: (msgs) => msgs.map(([uri, vals, data]) =>
42
+ * uri.startsWith("hash://")
43
+ * ? [`net://inv/${uri}`, { have: uri }]
44
+ * : [uri, vals, data]
45
+ * ),
46
+ * onAnnounce: (ev) => {
47
+ * if (ev.uri?.startsWith("net://inv/")) {
48
+ * return [(ev.record?.data as { have: string }).have];
49
+ * }
50
+ * return null;
51
+ * },
52
+ * });
53
+ *
54
+ * const local = connection(localStore, ["**"]);
55
+ * const outbound = connection(sync.outbound(peers), ["hash://**"]);
56
+ * const rig = new Rig({
57
+ * routes: {
58
+ * receive: [local, outbound],
59
+ * read: [local],
60
+ * observe: [local],
61
+ * },
62
+ * });
63
+ * const unbind = network(rig, peers, [sync.inbound]);
64
+ * ```
65
+ *
66
+ * ## Batching
67
+ *
68
+ * `announce` runs on the full per-peer batch, so the protocol can
69
+ * choose per-item announcements, a single compound announcement
70
+ * carrying many URIs, or per-peer asymmetry (full to trusted, INV to
71
+ * untrusted). `onAnnounce` returns a list of URIs so multi-item
72
+ * announcements fan the pulls out naturally.
73
+ *
74
+ * ## What stays the protocol's responsibility
75
+ *
76
+ * - URI layout for the control plane (`net://inv/...`, `inv://...`,
77
+ * `...?inv=true`, however you want).
78
+ * - Detection (which URIs are announcements vs. data).
79
+ * - "I already have this" short-circuit: `onAnnounce` closes over the
80
+ * protocol's local store to decide whether to return the URI to
81
+ * pull. The network layer doesn't plumb a local accessor.
82
+ */
83
+ import { validatePeers } from "../network.js";
84
+ import { floodImpl } from "./flood.js";
85
+ /**
86
+ * Construct a matched outbound/inbound pair for INV/READ-style sync.
87
+ */
88
+ export function tellAndRead(opts) {
89
+ const { announce, onAnnounce } = opts;
90
+ return {
91
+ outbound(peers) {
92
+ const { originId, peers: frozen } = validatePeers(peers);
93
+ const transform = announce
94
+ ? (msgs, peer) => announce(msgs, peer)
95
+ : (msgs) => msgs;
96
+ return floodImpl(originId, frozen, transform);
97
+ },
98
+ inbound: {
99
+ async *receive(ev, source) {
100
+ if (!onAnnounce) {
101
+ yield ev;
102
+ return;
103
+ }
104
+ const result = await Promise.resolve(onAnnounce(ev, source));
105
+ if (result == null) {
106
+ // Not an announcement — pass through.
107
+ yield ev;
108
+ return;
109
+ }
110
+ // It's an announcement (possibly empty). Pull each listed URI
111
+ // in parallel — announcements carrying many URIs (compact
112
+ // block-style) shouldn't serialize into a chain of RTTs.
113
+ const pulls = await Promise.all(result.map((uri) => source.client.read([uri])));
114
+ for (const outputs of pulls) {
115
+ // `read` is 1:1 with input; misses surface as `payload === undefined`.
116
+ // Skip miss slots so consumers only see real content.
117
+ for (const out of outputs) {
118
+ if (out?.[1] !== undefined)
119
+ yield out;
120
+ }
121
+ }
122
+ },
123
+ },
124
+ };
125
+ }
@@ -0,0 +1,112 @@
1
+ /**
2
+ * @module
3
+ * Types for `@bandeira-tech/b3nd-core/network`.
4
+ *
5
+ * Two mental primitives, both just functions over a peer list:
6
+ *
7
+ * - **`network(target, peers, policies?, opts?)`** — participant verb.
8
+ * Subscribes each peer's observe stream, passes events through the
9
+ * (optional) chain of `Policy.receive` hooks, and forwards yielded
10
+ * events into `target.receive`. Returns an async unbind.
11
+ *
12
+ * - **Strategy factories** (`flood(peers)`, `pathVector(peers)`, …) —
13
+ * remote-client shape. Each factory returns a
14
+ * `ProtocolInterfaceNode` consumed as a rig connection:
15
+ * `connection(flood(peers), patterns)`.
16
+ *
17
+ * `network()` and the strategy factories are entirely different shapes
18
+ * (a verb vs. a ProtocolInterfaceNode), so they cannot be accidentally
19
+ * swapped.
20
+ */
21
+ import type { Output, ProtocolInterfaceNode } from "../types/types.js";
22
+ /**
23
+ * A peer is a client plus a stable id used for local routing control.
24
+ *
25
+ * The id is always present — auto-generated at runtime via `peer()` when
26
+ * not supplied. Protocols that key on cryptographic identity (e.g.,
27
+ * path-vector loop avoidance) pass their pubkey hex as the id explicitly.
28
+ *
29
+ * The id is not advertised on the wire by the network itself; it is
30
+ * purely local bookkeeping used by strategy factories and Policies.
31
+ */
32
+ export interface Peer {
33
+ readonly id: string;
34
+ readonly client: ProtocolInterfaceNode;
35
+ }
36
+ /**
37
+ * A PeerDecorator wraps a client with middleware (best-effort, retry,
38
+ * rate-limit, logging, etc.) while preserving the `ProtocolInterfaceNode`
39
+ * shape. Applied via `peer(client, { via: [decoratorA, decoratorB] })`.
40
+ */
41
+ export type PeerDecorator = (client: ProtocolInterfaceNode) => ProtocolInterfaceNode;
42
+ /**
43
+ * Context passed to `Policy.receive` — inbound path, peer → rig.
44
+ *
45
+ * Carries only information the bridge uniquely knows at call time. Any
46
+ * data sources a policy needs (local store, cache, index, etc.) are
47
+ * the policy's own concern and should be injected at its construction.
48
+ */
49
+ export interface InboundCtx {
50
+ /** Local identity for this network invocation. */
51
+ readonly originId: string;
52
+ /** The peer this event came from. `source.client` exposes `read`/`receive`
53
+ * for side-requests to the sender (e.g., pulling a full payload after
54
+ * an announcement). */
55
+ readonly source: Peer;
56
+ }
57
+ /**
58
+ * A Policy shapes how the `network()` verb translates inbound peer
59
+ * events before feeding them into the target rig. It is scoped to the
60
+ * participant side — strategy factories (flood, pathVector, etc.)
61
+ * encapsulate their own outbound behavior and do not consult a Policy.
62
+ *
63
+ * Multiple policies can be passed as an array to `network()`; their
64
+ * `receive` hooks are chained left-to-right (each yielded event flows
65
+ * through the next policy).
66
+ */
67
+ export interface Policy {
68
+ /**
69
+ * Per-event transform on the inbound path. Yield zero or more
70
+ * `Output`s that should be delivered into the consuming target's
71
+ * pipeline.
72
+ *
73
+ * The async-generator shape lets the policy:
74
+ * - consume a control-plane event silently (yield nothing)
75
+ * - trigger a side-read from the source peer (via `source.client.read`)
76
+ * and yield the fetched content to the target
77
+ * - pass the event through unchanged
78
+ */
79
+ receive?(ev: Output<unknown>, source: Peer, ctx: InboundCtx): AsyncIterable<Output<unknown>>;
80
+ }
81
+ /**
82
+ * Options passed to the `network()` verb — strictly bridge-level concerns.
83
+ */
84
+ export interface NetworkOptions {
85
+ /**
86
+ * Observe pattern subscribed to on each peer. Defaults to `"**"` —
87
+ * every event is bridged. Narrow to reduce noise in busy networks.
88
+ */
89
+ pattern?: string;
90
+ /**
91
+ * Called when a peer's observe stream or `target.receive` throws.
92
+ * Defaults to a silent catch so one bad peer/message does not tear
93
+ * down the whole bridge.
94
+ */
95
+ onError?: (err: Error, ctx: {
96
+ peerId?: string;
97
+ }) => void;
98
+ }
99
+ /**
100
+ * A StrategyFactory builds a plain `ProtocolInterfaceNode` from a peer
101
+ * list. Examples: `flood(peers)` (fan-out to all, first-match reads),
102
+ * `pathVector(peers)` (flood with signer-chain loop avoidance).
103
+ *
104
+ * The returned value is an ordinary client and goes into a rig
105
+ * connection list unchanged:
106
+ *
107
+ * ```ts
108
+ * connection(flood(peers), ["**"])
109
+ * ```
110
+ */
111
+ export type StrategyFactory = (peers: Peer[]) => ProtocolInterfaceNode;
112
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../../../../../../src/deps/jsr.io/@bandeira-tech/b3nd-core/0.22.0/src/network/types.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AAEH,OAAO,KAAK,EAAE,MAAM,EAAE,qBAAqB,EAAE,MAAM,mBAAmB,CAAC;AAEvE;;;;;;;;;GASG;AACH,MAAM,WAAW,IAAI;IACnB,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,MAAM,EAAE,qBAAqB,CAAC;CACxC;AAED;;;;GAIG;AACH,MAAM,MAAM,aAAa,GAAG,CAC1B,MAAM,EAAE,qBAAqB,KAC1B,qBAAqB,CAAC;AAE3B;;;;;;GAMG;AACH,MAAM,WAAW,UAAU;IACzB,kDAAkD;IAClD,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B;;4BAEwB;IACxB,QAAQ,CAAC,MAAM,EAAE,IAAI,CAAC;CACvB;AAED;;;;;;;;;GASG;AACH,MAAM,WAAW,MAAM;IACrB;;;;;;;;;;OAUG;IACH,OAAO,CAAC,CACN,EAAE,EAAE,MAAM,CAAC,OAAO,CAAC,EACnB,MAAM,EAAE,IAAI,EACZ,GAAG,EAAE,UAAU,GACd,aAAa,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,CAAC;CACnC;AAED;;GAEG;AACH,MAAM,WAAW,cAAc;IAC7B;;;OAGG;IACH,OAAO,CAAC,EAAE,MAAM,CAAC;IAEjB;;;;OAIG;IACH,OAAO,CAAC,EAAE,CAAC,GAAG,EAAE,KAAK,EAAE,GAAG,EAAE;QAAE,MAAM,CAAC,EAAE,MAAM,CAAA;KAAE,KAAK,IAAI,CAAC;CAC1D;AAED;;;;;;;;;;;GAWG;AACH,MAAM,MAAM,eAAe,GAAG,CAAC,KAAK,EAAE,IAAI,EAAE,KAAK,qBAAqB,CAAC"}
@@ -0,0 +1,21 @@
1
+ /**
2
+ * @module
3
+ * Types for `@bandeira-tech/b3nd-core/network`.
4
+ *
5
+ * Two mental primitives, both just functions over a peer list:
6
+ *
7
+ * - **`network(target, peers, policies?, opts?)`** — participant verb.
8
+ * Subscribes each peer's observe stream, passes events through the
9
+ * (optional) chain of `Policy.receive` hooks, and forwards yielded
10
+ * events into `target.receive`. Returns an async unbind.
11
+ *
12
+ * - **Strategy factories** (`flood(peers)`, `pathVector(peers)`, …) —
13
+ * remote-client shape. Each factory returns a
14
+ * `ProtocolInterfaceNode` consumed as a rig connection:
15
+ * `connection(flood(peers), patterns)`.
16
+ *
17
+ * `network()` and the strategy factories are entirely different shapes
18
+ * (a verb vs. a ProtocolInterfaceNode), so they cannot be accidentally
19
+ * swapped.
20
+ */
21
+ export {};