@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.
- package/LICENSE +21 -0
- package/esm/_dnt.shims.d.ts +2 -0
- package/esm/_dnt.shims.d.ts.map +1 -0
- package/esm/_dnt.shims.js +57 -0
- package/esm/byte-entity-shim.d.ts +47 -0
- package/esm/byte-entity-shim.d.ts.map +1 -0
- package/esm/byte-entity-shim.js +138 -0
- package/esm/clients/mod.d.ts +15 -0
- package/esm/clients/mod.d.ts.map +1 -0
- package/esm/clients/mod.js +14 -0
- package/esm/clients/save-client.d.ts +87 -0
- package/esm/clients/save-client.d.ts.map +1 -0
- package/esm/clients/save-client.js +174 -0
- package/esm/deps/jsr.io/@bandeira-tech/b3nd-core/0.22.0/mod.d.ts +40 -0
- package/esm/deps/jsr.io/@bandeira-tech/b3nd-core/0.22.0/mod.d.ts.map +1 -0
- package/esm/deps/jsr.io/@bandeira-tech/b3nd-core/0.22.0/mod.js +31 -0
- package/esm/deps/jsr.io/@bandeira-tech/b3nd-core/0.22.0/src/client-console/client.d.ts +30 -0
- package/esm/deps/jsr.io/@bandeira-tech/b3nd-core/0.22.0/src/client-console/client.d.ts.map +1 -0
- package/esm/deps/jsr.io/@bandeira-tech/b3nd-core/0.22.0/src/client-console/client.js +70 -0
- package/esm/deps/jsr.io/@bandeira-tech/b3nd-core/0.22.0/src/encoding/encoding.d.ts +16 -0
- package/esm/deps/jsr.io/@bandeira-tech/b3nd-core/0.22.0/src/encoding/encoding.d.ts.map +1 -0
- package/esm/deps/jsr.io/@bandeira-tech/b3nd-core/0.22.0/src/encoding/encoding.js +50 -0
- package/esm/deps/jsr.io/@bandeira-tech/b3nd-core/0.22.0/src/encrypt/mod.d.ts +235 -0
- package/esm/deps/jsr.io/@bandeira-tech/b3nd-core/0.22.0/src/encrypt/mod.d.ts.map +1 -0
- package/esm/deps/jsr.io/@bandeira-tech/b3nd-core/0.22.0/src/encrypt/mod.js +619 -0
- package/esm/deps/jsr.io/@bandeira-tech/b3nd-core/0.22.0/src/functional-client/functional-client.d.ts +43 -0
- package/esm/deps/jsr.io/@bandeira-tech/b3nd-core/0.22.0/src/functional-client/functional-client.d.ts.map +1 -0
- package/esm/deps/jsr.io/@bandeira-tech/b3nd-core/0.22.0/src/functional-client/functional-client.js +54 -0
- package/esm/deps/jsr.io/@bandeira-tech/b3nd-core/0.22.0/src/match-pattern/match-pattern.d.ts +47 -0
- package/esm/deps/jsr.io/@bandeira-tech/b3nd-core/0.22.0/src/match-pattern/match-pattern.d.ts.map +1 -0
- package/esm/deps/jsr.io/@bandeira-tech/b3nd-core/0.22.0/src/match-pattern/match-pattern.js +134 -0
- package/esm/deps/jsr.io/@bandeira-tech/b3nd-core/0.22.0/src/network/decorators.d.ts +20 -0
- package/esm/deps/jsr.io/@bandeira-tech/b3nd-core/0.22.0/src/network/decorators.d.ts.map +1 -0
- package/esm/deps/jsr.io/@bandeira-tech/b3nd-core/0.22.0/src/network/decorators.js +34 -0
- package/esm/deps/jsr.io/@bandeira-tech/b3nd-core/0.22.0/src/network/network.d.ts +46 -0
- package/esm/deps/jsr.io/@bandeira-tech/b3nd-core/0.22.0/src/network/network.d.ts.map +1 -0
- package/esm/deps/jsr.io/@bandeira-tech/b3nd-core/0.22.0/src/network/network.js +131 -0
- package/esm/deps/jsr.io/@bandeira-tech/b3nd-core/0.22.0/src/network/peer.d.ts +27 -0
- package/esm/deps/jsr.io/@bandeira-tech/b3nd-core/0.22.0/src/network/peer.d.ts.map +1 -0
- package/esm/deps/jsr.io/@bandeira-tech/b3nd-core/0.22.0/src/network/peer.js +25 -0
- package/esm/deps/jsr.io/@bandeira-tech/b3nd-core/0.22.0/src/network/policies/flood.d.ts +59 -0
- package/esm/deps/jsr.io/@bandeira-tech/b3nd-core/0.22.0/src/network/policies/flood.d.ts.map +1 -0
- package/esm/deps/jsr.io/@bandeira-tech/b3nd-core/0.22.0/src/network/policies/flood.js +188 -0
- package/esm/deps/jsr.io/@bandeira-tech/b3nd-core/0.22.0/src/network/policies/path-vector.d.ts +35 -0
- package/esm/deps/jsr.io/@bandeira-tech/b3nd-core/0.22.0/src/network/policies/path-vector.d.ts.map +1 -0
- package/esm/deps/jsr.io/@bandeira-tech/b3nd-core/0.22.0/src/network/policies/path-vector.js +57 -0
- package/esm/deps/jsr.io/@bandeira-tech/b3nd-core/0.22.0/src/network/policies/tell-and-read.d.ts +117 -0
- package/esm/deps/jsr.io/@bandeira-tech/b3nd-core/0.22.0/src/network/policies/tell-and-read.d.ts.map +1 -0
- package/esm/deps/jsr.io/@bandeira-tech/b3nd-core/0.22.0/src/network/policies/tell-and-read.js +125 -0
- package/esm/deps/jsr.io/@bandeira-tech/b3nd-core/0.22.0/src/network/types.d.ts +112 -0
- package/esm/deps/jsr.io/@bandeira-tech/b3nd-core/0.22.0/src/network/types.d.ts.map +1 -0
- package/esm/deps/jsr.io/@bandeira-tech/b3nd-core/0.22.0/src/network/types.js +21 -0
- package/esm/deps/jsr.io/@bandeira-tech/b3nd-core/0.22.0/src/observe-emitter/observe-emitter.d.ts +35 -0
- package/esm/deps/jsr.io/@bandeira-tech/b3nd-core/0.22.0/src/observe-emitter/observe-emitter.d.ts.map +1 -0
- package/esm/deps/jsr.io/@bandeira-tech/b3nd-core/0.22.0/src/observe-emitter/observe-emitter.js +106 -0
- package/esm/deps/jsr.io/@bandeira-tech/b3nd-core/0.22.0/src/rig/connection.d.ts +88 -0
- package/esm/deps/jsr.io/@bandeira-tech/b3nd-core/0.22.0/src/rig/connection.d.ts.map +1 -0
- package/esm/deps/jsr.io/@bandeira-tech/b3nd-core/0.22.0/src/rig/connection.js +86 -0
- package/esm/deps/jsr.io/@bandeira-tech/b3nd-core/0.22.0/src/rig/events.d.ts +83 -0
- package/esm/deps/jsr.io/@bandeira-tech/b3nd-core/0.22.0/src/rig/events.d.ts.map +1 -0
- package/esm/deps/jsr.io/@bandeira-tech/b3nd-core/0.22.0/src/rig/events.js +115 -0
- package/esm/deps/jsr.io/@bandeira-tech/b3nd-core/0.22.0/src/rig/hooks.d.ts +164 -0
- package/esm/deps/jsr.io/@bandeira-tech/b3nd-core/0.22.0/src/rig/hooks.d.ts.map +1 -0
- package/esm/deps/jsr.io/@bandeira-tech/b3nd-core/0.22.0/src/rig/hooks.js +67 -0
- package/esm/deps/jsr.io/@bandeira-tech/b3nd-core/0.22.0/src/rig/identity.d.ts +99 -0
- package/esm/deps/jsr.io/@bandeira-tech/b3nd-core/0.22.0/src/rig/identity.d.ts.map +1 -0
- package/esm/deps/jsr.io/@bandeira-tech/b3nd-core/0.22.0/src/rig/identity.js +190 -0
- package/esm/deps/jsr.io/@bandeira-tech/b3nd-core/0.22.0/src/rig/operation-handle.d.ts +185 -0
- package/esm/deps/jsr.io/@bandeira-tech/b3nd-core/0.22.0/src/rig/operation-handle.d.ts.map +1 -0
- package/esm/deps/jsr.io/@bandeira-tech/b3nd-core/0.22.0/src/rig/operation-handle.js +103 -0
- package/esm/deps/jsr.io/@bandeira-tech/b3nd-core/0.22.0/src/rig/reactions.d.ts +58 -0
- package/esm/deps/jsr.io/@bandeira-tech/b3nd-core/0.22.0/src/rig/reactions.d.ts.map +1 -0
- package/esm/deps/jsr.io/@bandeira-tech/b3nd-core/0.22.0/src/rig/reactions.js +64 -0
- package/esm/deps/jsr.io/@bandeira-tech/b3nd-core/0.22.0/src/rig/rig.d.ts +295 -0
- package/esm/deps/jsr.io/@bandeira-tech/b3nd-core/0.22.0/src/rig/rig.d.ts.map +1 -0
- package/esm/deps/jsr.io/@bandeira-tech/b3nd-core/0.22.0/src/rig/rig.js +898 -0
- package/esm/deps/jsr.io/@bandeira-tech/b3nd-core/0.22.0/src/rig/types.d.ts +167 -0
- package/esm/deps/jsr.io/@bandeira-tech/b3nd-core/0.22.0/src/rig/types.d.ts.map +1 -0
- package/esm/deps/jsr.io/@bandeira-tech/b3nd-core/0.22.0/src/rig/types.js +5 -0
- package/esm/deps/jsr.io/@bandeira-tech/b3nd-core/0.22.0/src/types/types.d.ts +278 -0
- package/esm/deps/jsr.io/@bandeira-tech/b3nd-core/0.22.0/src/types/types.d.ts.map +1 -0
- package/esm/deps/jsr.io/@bandeira-tech/b3nd-core/0.22.0/src/types/types.js +89 -0
- package/esm/dispatch.d.ts +35 -0
- package/esm/dispatch.d.ts.map +1 -0
- package/esm/dispatch.js +40 -0
- package/esm/elasticsearch/mod.d.ts +31 -0
- package/esm/elasticsearch/mod.d.ts.map +1 -0
- package/esm/elasticsearch/mod.js +8 -0
- package/esm/elasticsearch/store.d.ts +44 -0
- package/esm/elasticsearch/store.d.ts.map +1 -0
- package/esm/elasticsearch/store.js +213 -0
- package/esm/entity-store.d.ts +106 -0
- package/esm/entity-store.d.ts.map +1 -0
- package/esm/entity-store.js +46 -0
- package/esm/entity.d.ts +125 -0
- package/esm/entity.d.ts.map +1 -0
- package/esm/entity.js +66 -0
- package/esm/errors.d.ts +23 -0
- package/esm/errors.d.ts.map +1 -0
- package/esm/errors.js +25 -0
- package/esm/fs/mod.d.ts +28 -0
- package/esm/fs/mod.d.ts.map +1 -0
- package/esm/fs/mod.js +13 -0
- package/esm/fs/store.d.ts +45 -0
- package/esm/fs/store.d.ts.map +1 -0
- package/esm/fs/store.js +194 -0
- package/esm/indexeddb/mod.d.ts +9 -0
- package/esm/indexeddb/mod.d.ts.map +1 -0
- package/esm/indexeddb/mod.js +8 -0
- package/esm/indexeddb/store.d.ts +47 -0
- package/esm/indexeddb/store.d.ts.map +1 -0
- package/esm/indexeddb/store.js +299 -0
- package/esm/ipfs/mod.d.ts +24 -0
- package/esm/ipfs/mod.d.ts.map +1 -0
- package/esm/ipfs/mod.js +13 -0
- package/esm/ipfs/store.d.ts +42 -0
- package/esm/ipfs/store.d.ts.map +1 -0
- package/esm/ipfs/store.js +197 -0
- package/esm/localstorage/mod.d.ts +9 -0
- package/esm/localstorage/mod.d.ts.map +1 -0
- package/esm/localstorage/mod.js +8 -0
- package/esm/localstorage/store.d.ts +45 -0
- package/esm/localstorage/store.d.ts.map +1 -0
- package/esm/localstorage/store.js +147 -0
- package/esm/memory/mod.d.ts +12 -0
- package/esm/memory/mod.d.ts.map +1 -0
- package/esm/memory/mod.js +11 -0
- package/esm/memory/store.d.ts +73 -0
- package/esm/memory/store.d.ts.map +1 -0
- package/esm/memory/store.js +300 -0
- package/esm/mod.d.ts +36 -0
- package/esm/mod.d.ts.map +1 -0
- package/esm/mod.js +31 -0
- package/esm/mongo/mod.d.ts +35 -0
- package/esm/mongo/mod.d.ts.map +1 -0
- package/esm/mongo/mod.js +8 -0
- package/esm/mongo/store.d.ts +42 -0
- package/esm/mongo/store.d.ts.map +1 -0
- package/esm/mongo/store.js +185 -0
- package/esm/package.json +3 -0
- package/esm/payload.d.ts +24 -0
- package/esm/payload.d.ts.map +1 -0
- package/esm/payload.js +30 -0
- package/esm/postgres/columns.d.ts +33 -0
- package/esm/postgres/columns.d.ts.map +1 -0
- package/esm/postgres/columns.js +51 -0
- package/esm/postgres/mod.d.ts +19 -0
- package/esm/postgres/mod.d.ts.map +1 -0
- package/esm/postgres/mod.js +9 -0
- package/esm/postgres/store.d.ts +75 -0
- package/esm/postgres/store.d.ts.map +1 -0
- package/esm/postgres/store.js +364 -0
- package/esm/read.d.ts +40 -0
- package/esm/read.d.ts.map +1 -0
- package/esm/read.js +67 -0
- package/esm/s3/mod.d.ts +23 -0
- package/esm/s3/mod.d.ts.map +1 -0
- package/esm/s3/mod.js +13 -0
- package/esm/s3/store.d.ts +46 -0
- package/esm/s3/store.d.ts.map +1 -0
- package/esm/s3/store.js +183 -0
- package/esm/sqlite/mod.d.ts +18 -0
- package/esm/sqlite/mod.d.ts.map +1 -0
- package/esm/sqlite/mod.js +8 -0
- package/esm/sqlite/schema.d.ts +10 -0
- package/esm/sqlite/schema.d.ts.map +1 -0
- package/esm/sqlite/schema.js +26 -0
- package/esm/sqlite/store.d.ts +42 -0
- package/esm/sqlite/store.d.ts.map +1 -0
- package/esm/sqlite/store.js +184 -0
- package/esm/types.d.ts +101 -0
- package/esm/types.d.ts.map +1 -0
- package/esm/types.js +21 -0
- package/esm/url.d.ts +108 -0
- package/esm/url.d.ts.map +1 -0
- package/esm/url.js +151 -0
- 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
|
package/esm/deps/jsr.io/@bandeira-tech/b3nd-core/0.22.0/src/network/policies/path-vector.d.ts.map
ADDED
|
@@ -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
|
+
}
|
package/esm/deps/jsr.io/@bandeira-tech/b3nd-core/0.22.0/src/network/policies/tell-and-read.d.ts
ADDED
|
@@ -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
|
package/esm/deps/jsr.io/@bandeira-tech/b3nd-core/0.22.0/src/network/policies/tell-and-read.d.ts.map
ADDED
|
@@ -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 {};
|