@muhkoo/connect 0.14.0-alpha.1 → 0.14.0-alpha.3

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@muhkoo/connect",
3
- "version": "0.14.0-alpha.1",
3
+ "version": "0.14.0-alpha.3",
4
4
  "description": "Simple client SDK for building distributed edge based applications",
5
5
  "main": "dist/server/index.js",
6
6
  "module": "dist/server/index.js",
@@ -1,349 +0,0 @@
1
- /**
2
- * Bitswap-lite wire protocol for peer block exchange. Four frame types over a
3
- * {@link ../transport/PeerTransport} data channel:
4
- *
5
- * WANT(hash) — "does anyone have this block?" / "send it to me"
6
- * HAVE(hash) — "I hold this block" (announce / reply to a WANT)
7
- * BLOCK(hash, bytes) — the block itself (verified by hash on receipt)
8
- * CANCEL(hash) — "stop, I got it elsewhere"
9
- *
10
- * Frames are compact binary so a 4 MiB shard isn't base64-bloated through JSON:
11
- *
12
- * byte 0 : type (0 WANT | 1 HAVE | 2 BLOCK | 3 CANCEL)
13
- * bytes 1..33 : 32-byte raw SHA-256 of the block (our shard hash, hex-decoded)
14
- * bytes 33.. : payload (BLOCK only)
15
- *
16
- * Blocks are AES-GCM ciphertext, so a frame carries nothing readable; the hash
17
- * is the capability check (the receiver re-hashes the payload and drops a
18
- * mismatch). See {@link ./blockEngine}.
19
- */
20
- var FrameType;
21
- (function (FrameType) {
22
- FrameType[FrameType["WANT"] = 0] = "WANT";
23
- FrameType[FrameType["HAVE"] = 1] = "HAVE";
24
- FrameType[FrameType["BLOCK"] = 2] = "BLOCK";
25
- FrameType[FrameType["CANCEL"] = 3] = "CANCEL";
26
- })(FrameType || (FrameType = {}));
27
- const HASH_BYTES = 32;
28
- const HEADER = 1 + HASH_BYTES; // type + hash
29
- function hexToBytes(hex) {
30
- if (hex.length !== HASH_BYTES * 2) {
31
- throw new Error(`protocol: expected a ${HASH_BYTES}-byte hex hash, got ${hex.length / 2} bytes`);
32
- }
33
- const out = new Uint8Array(HASH_BYTES);
34
- for (let i = 0; i < HASH_BYTES; i++)
35
- out[i] = parseInt(hex.substr(i * 2, 2), 16);
36
- return out;
37
- }
38
- const HEX = Array.from({ length: 256 }, (_, i) => i.toString(16).padStart(2, "0"));
39
- function bytesToHex(bytes) {
40
- let s = "";
41
- for (let i = 0; i < bytes.length; i++)
42
- s += HEX[bytes[i]];
43
- return s;
44
- }
45
- function header(type, hash, extra = 0) {
46
- const buf = new Uint8Array(HEADER + extra);
47
- buf[0] = type;
48
- buf.set(hexToBytes(hash), 1);
49
- return { buf, offset: HEADER };
50
- }
51
- function encodeWant(hash) {
52
- return header(FrameType.WANT, hash).buf;
53
- }
54
- function encodeHave(hash) {
55
- return header(FrameType.HAVE, hash).buf;
56
- }
57
- function encodeCancel(hash) {
58
- return header(FrameType.CANCEL, hash).buf;
59
- }
60
- function encodeBlock(hash, payload) {
61
- const { buf, offset } = header(FrameType.BLOCK, hash, payload.length);
62
- buf.set(payload, offset);
63
- return buf;
64
- }
65
- function decode(frame) {
66
- if (frame.length < HEADER)
67
- throw new Error("protocol: frame too short");
68
- const type = frame[0];
69
- const hash = bytesToHex(frame.subarray(1, HEADER));
70
- if (type === FrameType.BLOCK) {
71
- // Copy the payload out so it doesn't alias a transferred/pooled buffer.
72
- return { type, hash, payload: frame.slice(HEADER) };
73
- }
74
- return { type, hash };
75
- }
76
-
77
- /**
78
- * `BlockEngine` — the bitswap-lite want/have ledger. Host-agnostic: it speaks
79
- * only to a {@link BlockStore} (get/put/has) and an `out` sink that ships
80
- * encoded frames to a peer (or `"*"` to broadcast). The host wires `out` and
81
- * `handleFrame` to a real transport — on the main thread or inside the Web
82
- * Worker (see {@link ./engineClient}) — so this class is pure and unit-testable.
83
- *
84
- * It never sees keys: blocks are AES-GCM ciphertext, addressed by SHA-256. The
85
- * one trust check is integrity — an inbound BLOCK is re-hashed and dropped on
86
- * mismatch, so a malicious peer can't poison the store (the requester just
87
- * falls back to origin).
88
- */
89
- class BlockEngine {
90
- store;
91
- out;
92
- hash;
93
- wants = new Map();
94
- constructor(store, out, hash) {
95
- this.store = store;
96
- this.out = out;
97
- this.hash = hash;
98
- }
99
- /**
100
- * Ask the swarm for a block. Resolves with the verified bytes, or `null` on
101
- * timeout (caller then falls back to origin). Returns immediately if we
102
- * already hold it; concurrent wants for the same hash share one request.
103
- */
104
- want(hash, timeoutMs) {
105
- return new Promise((resolve) => {
106
- void this.store.get(hash).then((local) => {
107
- if (local)
108
- return resolve(local);
109
- const existing = this.wants.get(hash);
110
- if (existing) {
111
- existing.resolvers.push(resolve);
112
- return;
113
- }
114
- const timer = setTimeout(() => {
115
- const w = this.wants.get(hash);
116
- this.wants.delete(hash);
117
- this.out("*", encodeCancel(hash));
118
- w?.resolvers.forEach((r) => r(null));
119
- }, timeoutMs);
120
- this.wants.set(hash, { resolvers: [resolve], timer });
121
- this.out("*", encodeWant(hash));
122
- });
123
- });
124
- }
125
- /** Tell the swarm we now hold `hash` (so peers wanting it can pull from us). */
126
- announce(hash) {
127
- this.out("*", encodeHave(hash));
128
- }
129
- /** Handle one inbound frame from `peer`. */
130
- async handleFrame(peer, frame) {
131
- let f;
132
- try {
133
- f = decode(frame);
134
- }
135
- catch {
136
- return; // malformed — ignore
137
- }
138
- switch (f.type) {
139
- case FrameType.WANT: {
140
- const bytes = await this.store.get(f.hash);
141
- if (bytes)
142
- this.out(peer, encodeBlock(f.hash, bytes));
143
- return;
144
- }
145
- case FrameType.HAVE: {
146
- // A peer announced a block we're still waiting for → pull from them.
147
- if (this.wants.has(f.hash))
148
- this.out(peer, encodeWant(f.hash));
149
- return;
150
- }
151
- case FrameType.BLOCK: {
152
- const pending = this.wants.get(f.hash);
153
- if (!pending || !f.payload)
154
- return; // unsolicited / empty — ignore
155
- // Integrity: a peer can't poison the store. Drop a mismatch and
156
- // keep waiting (the timeout will fall back to origin).
157
- const got = await this.hash(f.payload);
158
- if (got !== f.hash)
159
- return;
160
- await this.store.put(f.hash, f.payload);
161
- this.wants.delete(f.hash);
162
- clearTimeout(pending.timer);
163
- this.out("*", encodeCancel(f.hash)); // others can stop sending
164
- pending.resolvers.forEach((r) => r(f.payload));
165
- return;
166
- }
167
- case FrameType.CANCEL:
168
- // We serve WANTs synchronously, so there's nothing in flight to
169
- // cancel yet. Reserved for chunked-send cancellation later.
170
- return;
171
- }
172
- }
173
- /** Drop all pending waits (resolve null) — on teardown. */
174
- close() {
175
- for (const [, w] of this.wants) {
176
- clearTimeout(w.timer);
177
- w.resolvers.forEach((r) => r(null));
178
- }
179
- this.wants.clear();
180
- }
181
- }
182
-
183
- /**
184
- * `ShardCache` — a Cache-API-backed store for file-shard bytes. Shards are
185
- * content-addressed (the key IS the SHA-256 of the ciphertext), so caching them
186
- * is uniquely safe: a cache hit is self-validating, immutable, and shareable
187
- * across files. The Cache API (rather than IndexedDB) is the right home for
188
- * large binary blobs — it streams, it's purpose-built for response bodies, and
189
- * it has its own eviction.
190
- *
191
- * Keys are SYNTHETIC absolute URLs (`https://shard.muhkoo.local/<hash>`) rather
192
- * than the real shard endpoint, so the cache is addressable by hash alone — the
193
- * sync engine can re-PUT a deferred shard knowing only its hash, without
194
- * reconstructing the accelerator URL. We never fetch these synthetic URLs; they
195
- * exist only as cache keys.
196
- *
197
- * Every method degrades to a no-op / null where the Cache API is unavailable
198
- * (Node, Workers, insecure contexts), so callers don't need to branch.
199
- */
200
- const CACHE_NAME = "muhkoo-shards-v1";
201
- class ShardCache {
202
- static available() {
203
- try {
204
- return typeof caches !== "undefined";
205
- }
206
- catch {
207
- return false;
208
- }
209
- }
210
- key(hash) {
211
- return `https://shard.muhkoo.local/${encodeURIComponent(hash)}`;
212
- }
213
- open() {
214
- return caches.open(CACHE_NAME);
215
- }
216
- /** Cached shard bytes, or `null` on miss / when unavailable. */
217
- async get(hash) {
218
- if (!ShardCache.available())
219
- return null;
220
- try {
221
- const cache = await this.open();
222
- const res = await cache.match(this.key(hash));
223
- if (!res)
224
- return null;
225
- return new Uint8Array(await res.arrayBuffer());
226
- }
227
- catch {
228
- return null;
229
- }
230
- }
231
- /** Store shard bytes under their hash. Best-effort. */
232
- async put(hash, bytes) {
233
- if (!ShardCache.available())
234
- return;
235
- try {
236
- const cache = await this.open();
237
- await cache.put(this.key(hash), new Response(bytes, {
238
- headers: { "content-type": "application/octet-stream" },
239
- }));
240
- }
241
- catch {
242
- /* quota / unavailable — degrade to online-only for this shard */
243
- }
244
- }
245
- /** Whether a shard is present in the cache. */
246
- async has(hash) {
247
- if (!ShardCache.available())
248
- return false;
249
- try {
250
- const cache = await this.open();
251
- return Boolean(await cache.match(this.key(hash)));
252
- }
253
- catch {
254
- return false;
255
- }
256
- }
257
- }
258
-
259
- /**
260
- * Byte conversion helpers usable in both Node and the browser.
261
- *
262
- * Replaces Node's `Buffer` API for the small set of operations we actually
263
- * need in the crypto layer: hex/base64/base64url encode + decode, and array
264
- * concatenation. Lets `DoubleRatchet`, `KeyStore`, etc. compile against
265
- * platforms without `Buffer` (browsers, CF Workers).
266
- */
267
- const HEX_CHARS = "0123456789abcdef";
268
- /** Encode a byte array as a lowercase hex string. */
269
- function toHex(bytes) {
270
- let out = "";
271
- for (let i = 0; i < bytes.length; i++) {
272
- const b = bytes[i];
273
- out += HEX_CHARS[b >> 4] + HEX_CHARS[b & 0x0f];
274
- }
275
- return out;
276
- }
277
-
278
- /**
279
- * HTTP client for the open content-addressed shard store.
280
- *
281
- * Each shard is named by the lowercase hex SHA-256 of its ciphertext bytes.
282
- * The server verifies the hash on PUT, so the address authenticates the
283
- * contents — there's no benefit to ACLing PUT/GET, and making them open
284
- * enables cross-user dedup down the line.
285
- *
286
- * Wire protocol:
287
- * PUT /api/shards/:hash body: shard bytes (application/octet-stream)
288
- * 200 { ok: true, hash, size, dedup: boolean }
289
- * 400 if hash doesn't match body
290
- *
291
- * GET /api/shards/:hash 200 with shard bytes (application/octet-stream)
292
- * 404 if absent
293
- *
294
- * HEAD /api/shards/:hash 200 if present (no body) — used by callers to
295
- * skip a PUT they'd otherwise dedup-rewrite. The
296
- * server can ignore the optimization if it wants.
297
- */
298
- /**
299
- * Compute the lowercase hex SHA-256 of `bytes`. Available via WebCrypto in
300
- * every runtime this SDK targets.
301
- */
302
- async function shardHash(bytes) {
303
- const subtle = globalThis.crypto?.subtle;
304
- if (!subtle) {
305
- throw new Error("shardHash: `globalThis.crypto.subtle` is unavailable in this runtime.");
306
- }
307
- const digest = await subtle.digest("SHA-256", bytes);
308
- return toHex(new Uint8Array(digest));
309
- }
310
-
311
- /**
312
- * Dedicated Web Worker entry that hosts a {@link ./blockEngine.BlockEngine}. The
313
- * blockstore is the Cache-API-backed {@link ../../offline/cache/ShardCache}
314
- * (the Cache API is available in workers and origin-scoped, so it's the *same*
315
- * store the main thread reads), and hashing uses {@link
316
- * ../../storage/transport/ShardClient.shardHash}.
317
- *
318
- * Message protocol (see {@link ./engineClient.WorkerEngineHost}):
319
- * in : {t:"frame",peer,data} | {t:"want",id,hash,timeout} | {t:"announce",hash} | {t:"close"}
320
- * out: {t:"out",target,data} | {t:"want-result",id,data}
321
- *
322
- * Consumers instantiate this with a bundler-resolved URL, e.g.
323
- * new Worker(new URL("@muhkoo/connect/dist/.../blockEngine.worker.js", import.meta.url), { type: "module" })
324
- * and pass `() => that worker` as `workerFactory` to the client's p2p options.
325
- */
326
- const ctx = self;
327
- const engine = new BlockEngine(new ShardCache(), (target, frame) => {
328
- const buf = frame.buffer;
329
- ctx.postMessage({ t: "out", target, data: buf }, [buf]);
330
- }, shardHash);
331
- ctx.onmessage = (e) => {
332
- const m = e.data;
333
- if (m.t === "frame") {
334
- void engine.handleFrame(m.peer, new Uint8Array(m.data));
335
- }
336
- else if (m.t === "want") {
337
- void engine.want(m.hash, m.timeout).then((bytes) => {
338
- const buf = bytes ? bytes.buffer : null;
339
- ctx.postMessage({ t: "want-result", id: m.id, data: buf }, buf ? [buf] : []);
340
- });
341
- }
342
- else if (m.t === "announce") {
343
- engine.announce(m.hash);
344
- }
345
- else if (m.t === "close") {
346
- engine.close();
347
- }
348
- };
349
- //# sourceMappingURL=blockEngine.worker.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"blockEngine.worker.js","sources":["../../src/p2p/worker/protocol.ts","../../src/p2p/worker/blockEngine.ts","../../src/offline/cache/ShardCache.ts","../../src/utilities/bytes.ts","../../src/storage/transport/ShardClient.ts","../../src/p2p/worker/blockEngine.worker.ts"],"sourcesContent":["/**\n * Bitswap-lite wire protocol for peer block exchange. Four frame types over a\n * {@link ../transport/PeerTransport} data channel:\n *\n * WANT(hash) — \"does anyone have this block?\" / \"send it to me\"\n * HAVE(hash) — \"I hold this block\" (announce / reply to a WANT)\n * BLOCK(hash, bytes) — the block itself (verified by hash on receipt)\n * CANCEL(hash) — \"stop, I got it elsewhere\"\n *\n * Frames are compact binary so a 4 MiB shard isn't base64-bloated through JSON:\n *\n * byte 0 : type (0 WANT | 1 HAVE | 2 BLOCK | 3 CANCEL)\n * bytes 1..33 : 32-byte raw SHA-256 of the block (our shard hash, hex-decoded)\n * bytes 33.. : payload (BLOCK only)\n *\n * Blocks are AES-GCM ciphertext, so a frame carries nothing readable; the hash\n * is the capability check (the receiver re-hashes the payload and drops a\n * mismatch). See {@link ./blockEngine}.\n */\n\nexport enum FrameType {\n WANT = 0,\n HAVE = 1,\n BLOCK = 2,\n CANCEL = 3,\n}\n\nexport interface DecodedFrame {\n type: FrameType;\n /** Lowercase hex SHA-256 (matches `shardHash`). */\n hash: string;\n /** Present only for {@link FrameType.BLOCK}. */\n payload?: Uint8Array;\n}\n\nconst HASH_BYTES = 32;\nconst HEADER = 1 + HASH_BYTES; // type + hash\n\nfunction hexToBytes(hex: string): Uint8Array {\n if (hex.length !== HASH_BYTES * 2) {\n throw new Error(`protocol: expected a ${HASH_BYTES}-byte hex hash, got ${hex.length / 2} bytes`);\n }\n const out = new Uint8Array(HASH_BYTES);\n for (let i = 0; i < HASH_BYTES; i++) out[i] = parseInt(hex.substr(i * 2, 2), 16);\n return out;\n}\n\nconst HEX = Array.from({ length: 256 }, (_, i) => i.toString(16).padStart(2, \"0\"));\nfunction bytesToHex(bytes: Uint8Array): string {\n let s = \"\";\n for (let i = 0; i < bytes.length; i++) s += HEX[bytes[i]];\n return s;\n}\n\nfunction header(type: FrameType, hash: string, extra = 0): { buf: Uint8Array; offset: number } {\n const buf = new Uint8Array(HEADER + extra);\n buf[0] = type;\n buf.set(hexToBytes(hash), 1);\n return { buf, offset: HEADER };\n}\n\nexport function encodeWant(hash: string): Uint8Array {\n return header(FrameType.WANT, hash).buf;\n}\n\nexport function encodeHave(hash: string): Uint8Array {\n return header(FrameType.HAVE, hash).buf;\n}\n\nexport function encodeCancel(hash: string): Uint8Array {\n return header(FrameType.CANCEL, hash).buf;\n}\n\nexport function encodeBlock(hash: string, payload: Uint8Array): Uint8Array {\n const { buf, offset } = header(FrameType.BLOCK, hash, payload.length);\n buf.set(payload, offset);\n return buf;\n}\n\nexport function decode(frame: Uint8Array): DecodedFrame {\n if (frame.length < HEADER) throw new Error(\"protocol: frame too short\");\n const type = frame[0] as FrameType;\n const hash = bytesToHex(frame.subarray(1, HEADER));\n if (type === FrameType.BLOCK) {\n // Copy the payload out so it doesn't alias a transferred/pooled buffer.\n return { type, hash, payload: frame.slice(HEADER) };\n }\n return { type, hash };\n}\n","/**\n * `BlockEngine` — the bitswap-lite want/have ledger. Host-agnostic: it speaks\n * only to a {@link BlockStore} (get/put/has) and an `out` sink that ships\n * encoded frames to a peer (or `\"*\"` to broadcast). The host wires `out` and\n * `handleFrame` to a real transport — on the main thread or inside the Web\n * Worker (see {@link ./engineClient}) — so this class is pure and unit-testable.\n *\n * It never sees keys: blocks are AES-GCM ciphertext, addressed by SHA-256. The\n * one trust check is integrity — an inbound BLOCK is re-hashed and dropped on\n * mismatch, so a malicious peer can't poison the store (the requester just\n * falls back to origin).\n */\n\nimport {\n FrameType,\n decode,\n encodeWant,\n encodeHave,\n encodeBlock,\n encodeCancel,\n} from \"./protocol\";\n\nexport interface BlockStore {\n get(hash: string): Promise<Uint8Array | null>;\n put(hash: string, bytes: Uint8Array): Promise<void>;\n has(hash: string): Promise<boolean>;\n}\n\n/** Ship an encoded frame to one peer, or `\"*\"` for every connected peer. */\nexport type OutSink = (target: string | \"*\", frame: Uint8Array) => void;\n\n/** Lowercase hex SHA-256 of `bytes` — injected so tests can stub it. */\nexport type Hasher = (bytes: Uint8Array) => Promise<string>;\n\ninterface PendingWant {\n resolvers: Array<(b: Uint8Array | null) => void>;\n timer: ReturnType<typeof setTimeout>;\n}\n\nexport class BlockEngine {\n private readonly wants = new Map<string, PendingWant>();\n\n constructor(\n private readonly store: BlockStore,\n private readonly out: OutSink,\n private readonly hash: Hasher,\n ) {}\n\n /**\n * Ask the swarm for a block. Resolves with the verified bytes, or `null` on\n * timeout (caller then falls back to origin). Returns immediately if we\n * already hold it; concurrent wants for the same hash share one request.\n */\n want(hash: string, timeoutMs: number): Promise<Uint8Array | null> {\n return new Promise((resolve) => {\n void this.store.get(hash).then((local) => {\n if (local) return resolve(local);\n const existing = this.wants.get(hash);\n if (existing) {\n existing.resolvers.push(resolve);\n return;\n }\n const timer = setTimeout(() => {\n const w = this.wants.get(hash);\n this.wants.delete(hash);\n this.out(\"*\", encodeCancel(hash));\n w?.resolvers.forEach((r) => r(null));\n }, timeoutMs);\n this.wants.set(hash, { resolvers: [resolve], timer });\n this.out(\"*\", encodeWant(hash));\n });\n });\n }\n\n /** Tell the swarm we now hold `hash` (so peers wanting it can pull from us). */\n announce(hash: string): void {\n this.out(\"*\", encodeHave(hash));\n }\n\n /** Handle one inbound frame from `peer`. */\n async handleFrame(peer: string, frame: Uint8Array): Promise<void> {\n let f;\n try {\n f = decode(frame);\n } catch {\n return; // malformed — ignore\n }\n switch (f.type) {\n case FrameType.WANT: {\n const bytes = await this.store.get(f.hash);\n if (bytes) this.out(peer, encodeBlock(f.hash, bytes));\n return;\n }\n case FrameType.HAVE: {\n // A peer announced a block we're still waiting for → pull from them.\n if (this.wants.has(f.hash)) this.out(peer, encodeWant(f.hash));\n return;\n }\n case FrameType.BLOCK: {\n const pending = this.wants.get(f.hash);\n if (!pending || !f.payload) return; // unsolicited / empty — ignore\n // Integrity: a peer can't poison the store. Drop a mismatch and\n // keep waiting (the timeout will fall back to origin).\n const got = await this.hash(f.payload);\n if (got !== f.hash) return;\n await this.store.put(f.hash, f.payload);\n this.wants.delete(f.hash);\n clearTimeout(pending.timer);\n this.out(\"*\", encodeCancel(f.hash)); // others can stop sending\n pending.resolvers.forEach((r) => r(f.payload!));\n return;\n }\n case FrameType.CANCEL:\n // We serve WANTs synchronously, so there's nothing in flight to\n // cancel yet. Reserved for chunked-send cancellation later.\n return;\n }\n }\n\n /** Drop all pending waits (resolve null) — on teardown. */\n close(): void {\n for (const [, w] of this.wants) {\n clearTimeout(w.timer);\n w.resolvers.forEach((r) => r(null));\n }\n this.wants.clear();\n }\n}\n\nexport default BlockEngine;\n","/**\n * `ShardCache` — a Cache-API-backed store for file-shard bytes. Shards are\n * content-addressed (the key IS the SHA-256 of the ciphertext), so caching them\n * is uniquely safe: a cache hit is self-validating, immutable, and shareable\n * across files. The Cache API (rather than IndexedDB) is the right home for\n * large binary blobs — it streams, it's purpose-built for response bodies, and\n * it has its own eviction.\n *\n * Keys are SYNTHETIC absolute URLs (`https://shard.muhkoo.local/<hash>`) rather\n * than the real shard endpoint, so the cache is addressable by hash alone — the\n * sync engine can re-PUT a deferred shard knowing only its hash, without\n * reconstructing the accelerator URL. We never fetch these synthetic URLs; they\n * exist only as cache keys.\n *\n * Every method degrades to a no-op / null where the Cache API is unavailable\n * (Node, Workers, insecure contexts), so callers don't need to branch.\n */\n\nconst CACHE_NAME = \"muhkoo-shards-v1\";\n\nexport class ShardCache {\n static available(): boolean {\n try {\n return typeof caches !== \"undefined\";\n } catch {\n return false;\n }\n }\n\n private key(hash: string): string {\n return `https://shard.muhkoo.local/${encodeURIComponent(hash)}`;\n }\n\n private open(): Promise<Cache> {\n return caches.open(CACHE_NAME);\n }\n\n /** Cached shard bytes, or `null` on miss / when unavailable. */\n async get(hash: string): Promise<Uint8Array | null> {\n if (!ShardCache.available()) return null;\n try {\n const cache = await this.open();\n const res = await cache.match(this.key(hash));\n if (!res) return null;\n return new Uint8Array(await res.arrayBuffer());\n } catch {\n return null;\n }\n }\n\n /** Store shard bytes under their hash. Best-effort. */\n async put(hash: string, bytes: Uint8Array): Promise<void> {\n if (!ShardCache.available()) return;\n try {\n const cache = await this.open();\n await cache.put(\n this.key(hash),\n new Response(bytes as BodyInit, {\n headers: { \"content-type\": \"application/octet-stream\" },\n }),\n );\n } catch {\n /* quota / unavailable — degrade to online-only for this shard */\n }\n }\n\n /** Whether a shard is present in the cache. */\n async has(hash: string): Promise<boolean> {\n if (!ShardCache.available()) return false;\n try {\n const cache = await this.open();\n return Boolean(await cache.match(this.key(hash)));\n } catch {\n return false;\n }\n }\n}\n\nexport default ShardCache;\n","/**\n * Byte conversion helpers usable in both Node and the browser.\n *\n * Replaces Node's `Buffer` API for the small set of operations we actually\n * need in the crypto layer: hex/base64/base64url encode + decode, and array\n * concatenation. Lets `DoubleRatchet`, `KeyStore`, etc. compile against\n * platforms without `Buffer` (browsers, CF Workers).\n */\n\nconst HEX_CHARS = \"0123456789abcdef\";\n\n/** Encode a byte array as a lowercase hex string. */\nexport function toHex(bytes: Uint8Array): string {\n let out = \"\";\n for (let i = 0; i < bytes.length; i++) {\n const b = bytes[i];\n out += HEX_CHARS[b >> 4] + HEX_CHARS[b & 0x0f];\n }\n return out;\n}\n\n/** Decode a hex string into a byte array. Accepts upper/lowercase; rejects odd-length input. */\nexport function fromHex(hex: string): Uint8Array<ArrayBuffer> {\n if (hex.length % 2 !== 0) {\n throw new Error(`fromHex: input length must be even (got ${hex.length})`);\n }\n const ab = new ArrayBuffer(hex.length / 2);\n const out = new Uint8Array(ab);\n for (let i = 0; i < out.length; i++) {\n const byte = parseInt(hex.substring(i * 2, i * 2 + 2), 16);\n if (Number.isNaN(byte)) {\n throw new Error(`fromHex: invalid hex character at offset ${i * 2}`);\n }\n out[i] = byte;\n }\n return out;\n}\n\n/** Encode a byte array as a standard base64 string. */\nexport function toBase64(bytes: Uint8Array): string {\n let binary = \"\";\n for (let i = 0; i < bytes.length; i++) {\n binary += String.fromCharCode(bytes[i]);\n }\n return btoa(binary);\n}\n\n/** Decode a standard base64 string to bytes. */\nexport function fromBase64(b64: string): Uint8Array<ArrayBuffer> {\n const binary = atob(b64);\n const ab = new ArrayBuffer(binary.length);\n const out = new Uint8Array(ab);\n for (let i = 0; i < binary.length; i++) out[i] = binary.charCodeAt(i);\n return out;\n}\n\n/** Encode a byte array as a base64url string (no padding). */\nexport function toBase64Url(bytes: Uint8Array): string {\n return toBase64(bytes).replace(/\\+/g, \"-\").replace(/\\//g, \"_\").replace(/=+$/, \"\");\n}\n\n/** Decode a base64url string (with or without padding) to bytes. */\nexport function fromBase64Url(b64url: string): Uint8Array<ArrayBuffer> {\n const padded = b64url.replace(/-/g, \"+\").replace(/_/g, \"/\") +\n \"=\".repeat((4 - (b64url.length % 4)) % 4);\n return fromBase64(padded);\n}\n\n/** Concatenate any number of byte arrays into a single Uint8Array. */\nexport function concatBytes(...arrays: (Uint8Array | ArrayBuffer)[]): Uint8Array<ArrayBuffer> {\n let total = 0;\n for (const a of arrays) total += a.byteLength;\n const ab = new ArrayBuffer(total);\n const out = new Uint8Array(ab);\n let offset = 0;\n for (const a of arrays) {\n const view = a instanceof Uint8Array ? a : new Uint8Array(a);\n out.set(view, offset);\n offset += view.length;\n }\n return out;\n}\n\n/** Encode a UTF-8 string as bytes. Thin wrapper around TextEncoder for symmetry. */\nexport function utf8Encode(s: string): Uint8Array<ArrayBuffer> {\n // TextEncoder.encode returns Uint8Array<ArrayBufferLike>; widen to the\n // ArrayBuffer variant explicitly so WebCrypto's BufferSource type accepts it.\n const u8 = new TextEncoder().encode(s);\n const ab = new ArrayBuffer(u8.byteLength);\n const out = new Uint8Array(ab);\n out.set(u8);\n return out;\n}\n\n/** Decode UTF-8 bytes to a string. */\nexport function utf8Decode(bytes: Uint8Array): string {\n return new TextDecoder().decode(bytes);\n}\n","/**\n * HTTP client for the open content-addressed shard store.\n *\n * Each shard is named by the lowercase hex SHA-256 of its ciphertext bytes.\n * The server verifies the hash on PUT, so the address authenticates the\n * contents — there's no benefit to ACLing PUT/GET, and making them open\n * enables cross-user dedup down the line.\n *\n * Wire protocol:\n * PUT /api/shards/:hash body: shard bytes (application/octet-stream)\n * 200 { ok: true, hash, size, dedup: boolean }\n * 400 if hash doesn't match body\n *\n * GET /api/shards/:hash 200 with shard bytes (application/octet-stream)\n * 404 if absent\n *\n * HEAD /api/shards/:hash 200 if present (no body) — used by callers to\n * skip a PUT they'd otherwise dedup-rewrite. The\n * server can ignore the optimization if it wants.\n */\n\nimport { toHex } from \"../../utilities/bytes\";\n\n/** Peer head-start (ms) before origin joins the race in {@link ShardClient.getShard}. */\nconst HEDGE_MS = 500;\n\n/**\n * A content-addressed byte cache for shards (the offline layer's `ShardCache`\n * satisfies this structurally). Declared here as a minimal interface so the\n * storage layer doesn't import the offline module — keeping the dependency\n * arrow one-way (offline → storage).\n */\nexport interface ShardByteCache {\n get(hash: string): Promise<Uint8Array | null>;\n put(hash: string, bytes: Uint8Array): Promise<void>;\n}\n\n/**\n * A peer block source (the p2p layer's `PeerExchange` satisfies this\n * structurally). Consulted between the local cache and origin: a block fetched\n * from a peer is verified by hash before use. Declared here so the storage layer\n * doesn't import the p2p module (dependency arrow stays p2p → storage).\n */\nexport interface PeerBlockSource {\n getBlock(hash: string, opts?: { timeoutMs?: number }): Promise<Uint8Array | null>;\n announce(hash: string): void;\n}\n\nexport interface ShardClientOptions {\n /** Base URL of the accelerator (e.g. `https://muhkoo-accelerator.workers.dev`). */\n baseUrl: string;\n /**\n * Path prefix for the shard endpoints, joined to `baseUrl` before the\n * hash. Defaults to `/api/shards`, which targets a global shard store\n * (e.g. a dedicated `ShardStoreDO`). Pass a room-scoped value like\n * `/api/spaces/{roomId}/shards` to keep shards bound to a specific\n * `SharedSpaceDO` — that's the path the chat application uses today.\n *\n * Leading slash optional; trailing slash stripped. The final URL is\n * always `${baseUrl}${normalizedPrefix}/${encodeURIComponent(hash)}`.\n */\n pathPrefix?: string;\n /**\n * Optional custom fetch implementation. Defaults to `globalThis.fetch`.\n * Provided so callers can inject retries / auth / instrumentation.\n */\n fetch?: typeof fetch;\n /**\n * Optional offline byte cache. When present, GETs are served cache-first and\n * populated on success, and PUTs write the cache before the network so a\n * just-uploaded file reads back offline. See {@link ShardByteCache}.\n */\n cache?: ShardByteCache;\n /**\n * Called when a PUT can't reach the network (offline). Lets the caller queue\n * the upload for replay on reconnect; the bytes are already in {@link cache}\n * keyed by `hash`, so only the hash needs recording.\n */\n deferUpload?: (hash: string) => Promise<void>;\n /**\n * Optional peer block source. When present, GETs try peers between the local\n * cache and origin (peer blocks are verified by hash); PUTs announce the new\n * block to peers. Best-effort — any failure falls through to origin.\n */\n peers?: PeerBlockSource;\n /**\n * When true, {@link ShardClient.putShard} returns as soon as the shard is\n * written to {@link cache} and announced to {@link peers} — the origin PUT is\n * fired in the BACKGROUND rather than awaited. This makes a large write's\n * manifest available immediately (so a peer/worker can start pulling the\n * shards over P2P right away) while origin catches up as a durable fallback.\n * On a background-PUT failure the shard is handed to {@link deferUpload} (if\n * present) for replay. Requires a {@link cache} so the bytes survive locally\n * until the background upload lands. No-op without a cache.\n */\n backgroundUpload?: boolean;\n}\n\n/**\n * Compute the lowercase hex SHA-256 of `bytes`. Available via WebCrypto in\n * every runtime this SDK targets.\n */\nexport async function shardHash(bytes: Uint8Array): Promise<string> {\n const subtle = (globalThis.crypto as Crypto | undefined)?.subtle;\n if (!subtle) {\n throw new Error(\"shardHash: `globalThis.crypto.subtle` is unavailable in this runtime.\");\n }\n const digest = await subtle.digest(\"SHA-256\", bytes as BufferSource);\n return toHex(new Uint8Array(digest));\n}\n\nexport class ShardClient {\n private readonly baseUrl: string;\n private readonly pathPrefix: string;\n private readonly fetchFn: typeof fetch;\n private readonly cache?: ShardByteCache;\n private readonly deferUpload?: (hash: string) => Promise<void>;\n private readonly peers?: PeerBlockSource;\n private readonly backgroundUpload: boolean;\n\n constructor(opts: ShardClientOptions) {\n if (!opts?.baseUrl) throw new Error(\"ShardClient: `baseUrl` is required\");\n // Strip trailing slash so url concatenation stays clean.\n this.baseUrl = opts.baseUrl.replace(/\\/+$/, \"\");\n // Normalize prefix to leading-slash + no-trailing-slash form so the\n // URL builder can blindly append `/{hash}`.\n const rawPrefix = opts.pathPrefix ?? \"/api/shards\";\n const withSlash = rawPrefix.startsWith(\"/\") ? rawPrefix : `/${rawPrefix}`;\n this.pathPrefix = withSlash.replace(/\\/+$/, \"\");\n const f = opts.fetch ?? globalThis.fetch;\n if (typeof f !== \"function\") {\n throw new Error(\"ShardClient: `globalThis.fetch` is unavailable; pass an explicit fetch.\");\n }\n this.fetchFn = f.bind(globalThis);\n this.cache = opts.cache;\n this.deferUpload = opts.deferUpload;\n this.peers = opts.peers;\n // Background upload needs a local cache to hold the bytes until the PUT\n // lands; without one it would race the reader against a not-yet-uploaded\n // origin, so we quietly ignore the flag when there's no cache.\n this.backgroundUpload = Boolean(opts.backgroundUpload && opts.cache);\n }\n\n /** Build the URL for a given shard hash — exposed for callers that want to log or pre-resolve. */\n urlForHash(hash: string): string {\n return `${this.baseUrl}${this.pathPrefix}/${encodeURIComponent(hash)}`;\n }\n\n /**\n * Upload a shard. The server verifies SHA-256(body) matches `:hash` and\n * rejects with 400 if not, so the caller is the source of truth on\n * naming. Re-uploading an existing shard is idempotent — the server\n * returns `dedup: true` in that case.\n */\n async putShard(hash: string, bytes: Uint8Array): Promise<{ dedup: boolean }> {\n const url = this.urlForHash(hash);\n // Write the cache first so a just-uploaded file reads back even if the\n // network PUT then fails (offline). The bytes are content-addressed, so\n // this is safe to keep regardless of upload outcome.\n if (this.cache) await this.cache.put(hash, bytes);\n // Tell the swarm we now hold this block, so peers can pull it from us.\n if (this.peers) this.peers.announce(hash);\n // Background mode: the bytes are cached + announced, so return now and let\n // origin catch up asynchronously (a peer can already pull this block). The\n // PUT is enqueued through a bounded worker pool so a big file doesn't fire\n // hundreds of concurrent uploads at once.\n if (this.backgroundUpload) {\n this.enqueueBackgroundUpload(url, hash);\n return { dedup: false };\n }\n let response: Response;\n try {\n response = await this.fetchFn(url, {\n method: \"PUT\",\n headers: { \"content-type\": \"application/octet-stream\" },\n // BufferSource is what fetch's body expects; a Uint8Array satisfies it.\n body: bytes as BodyInit,\n });\n } catch (err) {\n // Network unreachable (offline). If a defer hook is wired, queue the\n // upload for replay and report success — the bytes are cached.\n if (this.deferUpload) {\n await this.deferUpload(hash);\n return { dedup: false };\n }\n throw err;\n }\n if (!response.ok) {\n const text = await safeText(response);\n throw new Error(`ShardClient.putShard(${hash}) failed (${response.status}): ${text}`);\n }\n let dedup = false;\n try {\n const json = (await response.json()) as { dedup?: boolean };\n dedup = Boolean(json?.dedup);\n } catch {\n // Server returned 200 with no body — treat as fresh upload.\n }\n return { dedup };\n }\n\n // ── bounded background-upload pool (backgroundUpload mode) ──\n private bgActive = 0;\n private readonly bgQueue: Array<() => void> = [];\n private static readonly BG_CONCURRENCY = 6;\n\n /** Queue a shard's origin PUT to run in the background under a concurrency\n * cap. Bytes are re-read from {@link cache} at upload time (not held in the\n * queue) so a large file's backlog doesn't pin a second copy in memory. */\n private enqueueBackgroundUpload(url: string, hash: string): void {\n const run = async () => {\n this.bgActive++;\n try {\n const bytes = this.cache ? await this.cache.get(hash) : null;\n if (bytes) await this.uploadToOrigin(url, hash, bytes);\n } catch {\n // Origin unreachable/failed — queue for replay if we can; otherwise\n // the block is still cached + peer-served, so a later re-PUT covers it.\n if (this.deferUpload) { try { await this.deferUpload(hash); } catch { /* best-effort */ } }\n } finally {\n this.bgActive--;\n const next = this.bgQueue.shift();\n if (next) next();\n }\n };\n if (this.bgActive < ShardClient.BG_CONCURRENCY) void run();\n else this.bgQueue.push(() => void run());\n }\n\n /** PUT the shard bytes to origin, verifying the server accepted them.\n * Used by the background-upload path; throws on network/HTTP failure. */\n private async uploadToOrigin(url: string, hash: string, bytes: Uint8Array): Promise<void> {\n const response = await this.fetchFn(url, {\n method: \"PUT\",\n headers: { \"content-type\": \"application/octet-stream\" },\n body: bytes as BodyInit,\n });\n if (!response.ok) {\n const text = await safeText(response);\n throw new Error(`ShardClient.putShard(${hash}) failed (${response.status}): ${text}`);\n }\n }\n\n /**\n * Fetch a shard by hash. Throws if the server returns a non-200; returns\n * `null` for 404 so callers can distinguish \"missing\" from \"errored\".\n */\n async getShard(hash: string): Promise<Uint8Array | null> {\n // Cache-first: a hit is content-verified by construction (key == hash).\n if (this.cache) {\n const cached = await this.cache.get(hash);\n if (cached) return cached;\n }\n // No peers → straight to origin.\n if (!this.peers) return this.fetchFromOrigin(hash);\n\n // HEDGED RACE: prefer a peer (saves origin egress) but never let a slow\n // or missing peer stall delivery — give the peer a short head start, then\n // start origin in parallel and take whichever returns the block first.\n const peers = this.peers;\n return new Promise<Uint8Array | null>((resolve, reject) => {\n let done = false;\n let originStarted = false;\n let timer: ReturnType<typeof setTimeout> | null = null;\n const win = (b: Uint8Array | null) => {\n if (done) return;\n done = true;\n if (timer) clearTimeout(timer);\n resolve(b);\n };\n const fail = (e: unknown) => {\n if (!done) { done = true; reject(e); }\n };\n const startOrigin = () => {\n if (originStarted || done) return;\n originStarted = true;\n if (timer) { clearTimeout(timer); timer = null; }\n this.fetchFromOrigin(hash).then(win, fail);\n };\n timer = setTimeout(startOrigin, HEDGE_MS);\n peers.getBlock(hash).then(\n (b) => {\n if (b) {\n if (this.cache) void this.cache.put(hash, b);\n win(b); // peer delivered first\n } else {\n startOrigin(); // peer miss → don't wait out the hedge\n }\n },\n () => startOrigin(), // peer error → origin now\n );\n });\n }\n\n /** Fetch a shard from origin. Returns `null` for 404 (or offline when a\n * cache is present, so RS recovery can proceed); throws otherwise. */\n private async fetchFromOrigin(hash: string): Promise<Uint8Array | null> {\n const url = this.urlForHash(hash);\n let response: Response;\n try {\n response = await this.fetchFn(url, { method: \"GET\" });\n } catch (err) {\n if (this.cache) return null;\n throw err;\n }\n if (response.status === 404) return null;\n if (!response.ok) {\n const text = await safeText(response);\n throw new Error(`ShardClient.getShard(${hash}) failed (${response.status}): ${text}`);\n }\n const bytes = new Uint8Array(await response.arrayBuffer());\n if (this.cache) void this.cache.put(hash, bytes);\n return bytes;\n }\n\n /**\n * Check whether a shard exists. Lets the caller skip a PUT they'd otherwise\n * dedup-rewrite. The server can implement this as a HEAD or return a small\n * JSON body — we don't depend on either.\n */\n async hasShard(hash: string): Promise<boolean> {\n const url = this.urlForHash(hash);\n const response = await this.fetchFn(url, { method: \"HEAD\" });\n return response.status === 200;\n }\n}\n\nasync function safeText(response: Response): Promise<string> {\n try { return await response.text(); } catch { return \"\"; }\n}\n","/**\n * Dedicated Web Worker entry that hosts a {@link ./blockEngine.BlockEngine}. The\n * blockstore is the Cache-API-backed {@link ../../offline/cache/ShardCache}\n * (the Cache API is available in workers and origin-scoped, so it's the *same*\n * store the main thread reads), and hashing uses {@link\n * ../../storage/transport/ShardClient.shardHash}.\n *\n * Message protocol (see {@link ./engineClient.WorkerEngineHost}):\n * in : {t:\"frame\",peer,data} | {t:\"want\",id,hash,timeout} | {t:\"announce\",hash} | {t:\"close\"}\n * out: {t:\"out\",target,data} | {t:\"want-result\",id,data}\n *\n * Consumers instantiate this with a bundler-resolved URL, e.g.\n * new Worker(new URL(\"@muhkoo/connect/dist/.../blockEngine.worker.js\", import.meta.url), { type: \"module\" })\n * and pass `() => that worker` as `workerFactory` to the client's p2p options.\n */\n\nimport { BlockEngine } from \"./blockEngine\";\nimport { ShardCache } from \"../../offline/cache/ShardCache\";\nimport { shardHash } from \"../../storage/transport/ShardClient\";\n\n/** Minimal worker-scope shape (avoids needing the WebWorker tsconfig lib). */\ninterface WorkerScope {\n onmessage: ((e: MessageEvent) => void) | null;\n postMessage(message: unknown, transfer?: Transferable[]): void;\n}\nconst ctx = self as unknown as WorkerScope;\n\nconst engine = new BlockEngine(\n new ShardCache(),\n (target, frame) => {\n const buf = frame.buffer as ArrayBuffer;\n ctx.postMessage({ t: \"out\", target, data: buf }, [buf]);\n },\n shardHash,\n);\n\nctx.onmessage = (e: MessageEvent) => {\n const m = e.data as\n | { t: \"frame\"; peer: string; data: ArrayBuffer }\n | { t: \"want\"; id: number; hash: string; timeout: number }\n | { t: \"announce\"; hash: string }\n | { t: \"close\" };\n\n if (m.t === \"frame\") {\n void engine.handleFrame(m.peer, new Uint8Array(m.data));\n } else if (m.t === \"want\") {\n void engine.want(m.hash, m.timeout).then((bytes) => {\n const buf = bytes ? (bytes.buffer as ArrayBuffer) : null;\n ctx.postMessage({ t: \"want-result\", id: m.id, data: buf }, buf ? [buf] : []);\n });\n } else if (m.t === \"announce\") {\n engine.announce(m.hash);\n } else if (m.t === \"close\") {\n engine.close();\n }\n};\n"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;AAkBG;AAEH,IAAY,SAKX;AALD,CAAA,UAAY,SAAS,EAAA;AACjB,IAAA,SAAA,CAAA,SAAA,CAAA,MAAA,CAAA,GAAA,CAAA,CAAA,GAAA,MAAQ;AACR,IAAA,SAAA,CAAA,SAAA,CAAA,MAAA,CAAA,GAAA,CAAA,CAAA,GAAA,MAAQ;AACR,IAAA,SAAA,CAAA,SAAA,CAAA,OAAA,CAAA,GAAA,CAAA,CAAA,GAAA,OAAS;AACT,IAAA,SAAA,CAAA,SAAA,CAAA,QAAA,CAAA,GAAA,CAAA,CAAA,GAAA,QAAU;AACd,CAAC,EALW,SAAS,KAAT,SAAS,GAAA,EAAA,CAAA,CAAA;AAerB,MAAM,UAAU,GAAG,EAAE;AACrB,MAAM,MAAM,GAAG,CAAC,GAAG,UAAU,CAAC;AAE9B,SAAS,UAAU,CAAC,GAAW,EAAA;IAC3B,IAAI,GAAG,CAAC,MAAM,KAAK,UAAU,GAAG,CAAC,EAAE;AAC/B,QAAA,MAAM,IAAI,KAAK,CAAC,CAAA,qBAAA,EAAwB,UAAU,CAAA,oBAAA,EAAuB,GAAG,CAAC,MAAM,GAAG,CAAC,CAAA,MAAA,CAAQ,CAAC;IACpG;AACA,IAAA,MAAM,GAAG,GAAG,IAAI,UAAU,CAAC,UAAU,CAAC;IACtC,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,UAAU,EAAE,CAAC,EAAE;AAAE,QAAA,GAAG,CAAC,CAAC,CAAC,GAAG,QAAQ,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC;AAChF,IAAA,OAAO,GAAG;AACd;AAEA,MAAM,GAAG,GAAG,KAAK,CAAC,IAAI,CAAC,EAAE,MAAM,EAAE,GAAG,EAAE,EAAE,CAAC,CAAC,EAAE,CAAC,KAAK,CAAC,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC,QAAQ,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC;AAClF,SAAS,UAAU,CAAC,KAAiB,EAAA;IACjC,IAAI,CAAC,GAAG,EAAE;AACV,IAAA,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,KAAK,CAAC,MAAM,EAAE,CAAC,EAAE;QAAE,CAAC,IAAI,GAAG,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;AACzD,IAAA,OAAO,CAAC;AACZ;AAEA,SAAS,MAAM,CAAC,IAAe,EAAE,IAAY,EAAE,KAAK,GAAG,CAAC,EAAA;IACpD,MAAM,GAAG,GAAG,IAAI,UAAU,CAAC,MAAM,GAAG,KAAK,CAAC;AAC1C,IAAA,GAAG,CAAC,CAAC,CAAC,GAAG,IAAI;IACb,GAAG,CAAC,GAAG,CAAC,UAAU,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;AAC5B,IAAA,OAAO,EAAE,GAAG,EAAE,MAAM,EAAE,MAAM,EAAE;AAClC;AAEM,SAAU,UAAU,CAAC,IAAY,EAAA;IACnC,OAAO,MAAM,CAAC,SAAS,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC,GAAG;AAC3C;AAEM,SAAU,UAAU,CAAC,IAAY,EAAA;IACnC,OAAO,MAAM,CAAC,SAAS,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC,GAAG;AAC3C;AAEM,SAAU,YAAY,CAAC,IAAY,EAAA;IACrC,OAAO,MAAM,CAAC,SAAS,CAAC,MAAM,EAAE,IAAI,CAAC,CAAC,GAAG;AAC7C;AAEM,SAAU,WAAW,CAAC,IAAY,EAAE,OAAmB,EAAA;AACzD,IAAA,MAAM,EAAE,GAAG,EAAE,MAAM,EAAE,GAAG,MAAM,CAAC,SAAS,CAAC,KAAK,EAAE,IAAI,EAAE,OAAO,CAAC,MAAM,CAAC;AACrE,IAAA,GAAG,CAAC,GAAG,CAAC,OAAO,EAAE,MAAM,CAAC;AACxB,IAAA,OAAO,GAAG;AACd;AAEM,SAAU,MAAM,CAAC,KAAiB,EAAA;AACpC,IAAA,IAAI,KAAK,CAAC,MAAM,GAAG,MAAM;AAAE,QAAA,MAAM,IAAI,KAAK,CAAC,2BAA2B,CAAC;AACvE,IAAA,MAAM,IAAI,GAAG,KAAK,CAAC,CAAC,CAAc;AAClC,IAAA,MAAM,IAAI,GAAG,UAAU,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC,EAAE,MAAM,CAAC,CAAC;AAClD,IAAA,IAAI,IAAI,KAAK,SAAS,CAAC,KAAK,EAAE;;AAE1B,QAAA,OAAO,EAAE,IAAI,EAAE,IAAI,EAAE,OAAO,EAAE,KAAK,CAAC,KAAK,CAAC,MAAM,CAAC,EAAE;IACvD;AACA,IAAA,OAAO,EAAE,IAAI,EAAE,IAAI,EAAE;AACzB;;ACxFA;;;;;;;;;;;AAWG;MA4BU,WAAW,CAAA;AAIC,IAAA,KAAA;AACA,IAAA,GAAA;AACA,IAAA,IAAA;AALJ,IAAA,KAAK,GAAG,IAAI,GAAG,EAAuB;AAEvD,IAAA,WAAA,CACqB,KAAiB,EACjB,GAAY,EACZ,IAAY,EAAA;QAFZ,IAAA,CAAA,KAAK,GAAL,KAAK;QACL,IAAA,CAAA,GAAG,GAAH,GAAG;QACH,IAAA,CAAA,IAAI,GAAJ,IAAI;IACtB;AAEH;;;;AAIG;IACH,IAAI,CAAC,IAAY,EAAE,SAAiB,EAAA;AAChC,QAAA,OAAO,IAAI,OAAO,CAAC,CAAC,OAAO,KAAI;AAC3B,YAAA,KAAK,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,CAAC,KAAK,KAAI;AACrC,gBAAA,IAAI,KAAK;AAAE,oBAAA,OAAO,OAAO,CAAC,KAAK,CAAC;gBAChC,MAAM,QAAQ,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,IAAI,CAAC;gBACrC,IAAI,QAAQ,EAAE;AACV,oBAAA,QAAQ,CAAC,SAAS,CAAC,IAAI,CAAC,OAAO,CAAC;oBAChC;gBACJ;AACA,gBAAA,MAAM,KAAK,GAAG,UAAU,CAAC,MAAK;oBAC1B,MAAM,CAAC,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,IAAI,CAAC;AAC9B,oBAAA,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,IAAI,CAAC;oBACvB,IAAI,CAAC,GAAG,CAAC,GAAG,EAAE,YAAY,CAAC,IAAI,CAAC,CAAC;AACjC,oBAAA,CAAC,EAAE,SAAS,CAAC,OAAO,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,IAAI,CAAC,CAAC;gBACxC,CAAC,EAAE,SAAS,CAAC;AACb,gBAAA,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,IAAI,EAAE,EAAE,SAAS,EAAE,CAAC,OAAO,CAAC,EAAE,KAAK,EAAE,CAAC;gBACrD,IAAI,CAAC,GAAG,CAAC,GAAG,EAAE,UAAU,CAAC,IAAI,CAAC,CAAC;AACnC,YAAA,CAAC,CAAC;AACN,QAAA,CAAC,CAAC;IACN;;AAGA,IAAA,QAAQ,CAAC,IAAY,EAAA;QACjB,IAAI,CAAC,GAAG,CAAC,GAAG,EAAE,UAAU,CAAC,IAAI,CAAC,CAAC;IACnC;;AAGA,IAAA,MAAM,WAAW,CAAC,IAAY,EAAE,KAAiB,EAAA;AAC7C,QAAA,IAAI,CAAC;AACL,QAAA,IAAI;AACA,YAAA,CAAC,GAAG,MAAM,CAAC,KAAK,CAAC;QACrB;AAAE,QAAA,MAAM;AACJ,YAAA,OAAO;QACX;AACA,QAAA,QAAQ,CAAC,CAAC,IAAI;AACV,YAAA,KAAK,SAAS,CAAC,IAAI,EAAE;AACjB,gBAAA,MAAM,KAAK,GAAG,MAAM,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC;AAC1C,gBAAA,IAAI,KAAK;AAAE,oBAAA,IAAI,CAAC,GAAG,CAAC,IAAI,EAAE,WAAW,CAAC,CAAC,CAAC,IAAI,EAAE,KAAK,CAAC,CAAC;gBACrD;YACJ;AACA,YAAA,KAAK,SAAS,CAAC,IAAI,EAAE;;gBAEjB,IAAI,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC;AAAE,oBAAA,IAAI,CAAC,GAAG,CAAC,IAAI,EAAE,UAAU,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC;gBAC9D;YACJ;AACA,YAAA,KAAK,SAAS,CAAC,KAAK,EAAE;AAClB,gBAAA,MAAM,OAAO,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC;AACtC,gBAAA,IAAI,CAAC,OAAO,IAAI,CAAC,CAAC,CAAC,OAAO;AAAE,oBAAA,OAAO;;;gBAGnC,MAAM,GAAG,GAAG,MAAM,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,OAAO,CAAC;AACtC,gBAAA,IAAI,GAAG,KAAK,CAAC,CAAC,IAAI;oBAAE;AACpB,gBAAA,MAAM,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC,OAAO,CAAC;gBACvC,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,IAAI,CAAC;AACzB,gBAAA,YAAY,CAAC,OAAO,CAAC,KAAK,CAAC;AAC3B,gBAAA,IAAI,CAAC,GAAG,CAAC,GAAG,EAAE,YAAY,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC;AACpC,gBAAA,OAAO,CAAC,SAAS,CAAC,OAAO,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,OAAQ,CAAC,CAAC;gBAC/C;YACJ;YACA,KAAK,SAAS,CAAC,MAAM;;;gBAGjB;;IAEZ;;IAGA,KAAK,GAAA;QACD,KAAK,MAAM,GAAG,CAAC,CAAC,IAAI,IAAI,CAAC,KAAK,EAAE;AAC5B,YAAA,YAAY,CAAC,CAAC,CAAC,KAAK,CAAC;AACrB,YAAA,CAAC,CAAC,SAAS,CAAC,OAAO,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,IAAI,CAAC,CAAC;QACvC;AACA,QAAA,IAAI,CAAC,KAAK,CAAC,KAAK,EAAE;IACtB;AACH;;AC/HD;;;;;;;;;;;;;;;;AAgBG;AAEH,MAAM,UAAU,GAAG,kBAAkB;MAExB,UAAU,CAAA;AACnB,IAAA,OAAO,SAAS,GAAA;AACZ,QAAA,IAAI;AACA,YAAA,OAAO,OAAO,MAAM,KAAK,WAAW;QACxC;AAAE,QAAA,MAAM;AACJ,YAAA,OAAO,KAAK;QAChB;IACJ;AAEQ,IAAA,GAAG,CAAC,IAAY,EAAA;AACpB,QAAA,OAAO,8BAA8B,kBAAkB,CAAC,IAAI,CAAC,EAAE;IACnE;IAEQ,IAAI,GAAA;AACR,QAAA,OAAO,MAAM,CAAC,IAAI,CAAC,UAAU,CAAC;IAClC;;IAGA,MAAM,GAAG,CAAC,IAAY,EAAA;AAClB,QAAA,IAAI,CAAC,UAAU,CAAC,SAAS,EAAE;AAAE,YAAA,OAAO,IAAI;AACxC,QAAA,IAAI;AACA,YAAA,MAAM,KAAK,GAAG,MAAM,IAAI,CAAC,IAAI,EAAE;AAC/B,YAAA,MAAM,GAAG,GAAG,MAAM,KAAK,CAAC,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;AAC7C,YAAA,IAAI,CAAC,GAAG;AAAE,gBAAA,OAAO,IAAI;YACrB,OAAO,IAAI,UAAU,CAAC,MAAM,GAAG,CAAC,WAAW,EAAE,CAAC;QAClD;AAAE,QAAA,MAAM;AACJ,YAAA,OAAO,IAAI;QACf;IACJ;;AAGA,IAAA,MAAM,GAAG,CAAC,IAAY,EAAE,KAAiB,EAAA;AACrC,QAAA,IAAI,CAAC,UAAU,CAAC,SAAS,EAAE;YAAE;AAC7B,QAAA,IAAI;AACA,YAAA,MAAM,KAAK,GAAG,MAAM,IAAI,CAAC,IAAI,EAAE;AAC/B,YAAA,MAAM,KAAK,CAAC,GAAG,CACX,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,EACd,IAAI,QAAQ,CAAC,KAAiB,EAAE;AAC5B,gBAAA,OAAO,EAAE,EAAE,cAAc,EAAE,0BAA0B,EAAE;AAC1D,aAAA,CAAC,CACL;QACL;AAAE,QAAA,MAAM;;QAER;IACJ;;IAGA,MAAM,GAAG,CAAC,IAAY,EAAA;AAClB,QAAA,IAAI,CAAC,UAAU,CAAC,SAAS,EAAE;AAAE,YAAA,OAAO,KAAK;AACzC,QAAA,IAAI;AACA,YAAA,MAAM,KAAK,GAAG,MAAM,IAAI,CAAC,IAAI,EAAE;AAC/B,YAAA,OAAO,OAAO,CAAC,MAAM,KAAK,CAAC,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC,CAAC;QACrD;AAAE,QAAA,MAAM;AACJ,YAAA,OAAO,KAAK;QAChB;IACJ;AACH;;AC5ED;;;;;;;AAOG;AAEH,MAAM,SAAS,GAAG,kBAAkB;AAEpC;AACM,SAAU,KAAK,CAAC,KAAiB,EAAA;IACrC,IAAI,GAAG,GAAG,EAAE;AACZ,IAAA,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,KAAK,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE;AACrC,QAAA,MAAM,CAAC,GAAG,KAAK,CAAC,CAAC,CAAC;AAClB,QAAA,GAAG,IAAI,SAAS,CAAC,CAAC,IAAI,CAAC,CAAC,GAAG,SAAS,CAAC,CAAC,GAAG,IAAI,CAAC;IAChD;AACA,IAAA,OAAO,GAAG;AACZ;;ACnBA;;;;;;;;;;;;;;;;;;;AAmBG;AA+EH;;;AAGG;AACI,eAAe,SAAS,CAAC,KAAiB,EAAA;AAC7C,IAAA,MAAM,MAAM,GAAI,UAAU,CAAC,MAA6B,EAAE,MAAM;IAChE,IAAI,CAAC,MAAM,EAAE;AACT,QAAA,MAAM,IAAI,KAAK,CAAC,uEAAuE,CAAC;IAC5F;IACA,MAAM,MAAM,GAAG,MAAM,MAAM,CAAC,MAAM,CAAC,SAAS,EAAE,KAAqB,CAAC;IACpE,OAAO,KAAK,CAAC,IAAI,UAAU,CAAC,MAAM,CAAC,CAAC;AACxC;;AC7GA;;;;;;;;;;;;;;AAcG;AAWH,MAAM,GAAG,GAAG,IAA8B;AAE1C,MAAM,MAAM,GAAG,IAAI,WAAW,CAC1B,IAAI,UAAU,EAAE,EAChB,CAAC,MAAM,EAAE,KAAK,KAAI;AACd,IAAA,MAAM,GAAG,GAAG,KAAK,CAAC,MAAqB;AACvC,IAAA,GAAG,CAAC,WAAW,CAAC,EAAE,CAAC,EAAE,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,GAAG,EAAE,EAAE,CAAC,GAAG,CAAC,CAAC;AAC3D,CAAC,EACD,SAAS,CACZ;AAED,GAAG,CAAC,SAAS,GAAG,CAAC,CAAe,KAAI;AAChC,IAAA,MAAM,CAAC,GAAG,CAAC,CAAC,IAIQ;AAEpB,IAAA,IAAI,CAAC,CAAC,CAAC,KAAK,OAAO,EAAE;AACjB,QAAA,KAAK,MAAM,CAAC,WAAW,CAAC,CAAC,CAAC,IAAI,EAAE,IAAI,UAAU,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC;IAC3D;AAAO,SAAA,IAAI,CAAC,CAAC,CAAC,KAAK,MAAM,EAAE;AACvB,QAAA,KAAK,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC,CAAC,KAAK,KAAI;AAC/C,YAAA,MAAM,GAAG,GAAG,KAAK,GAAI,KAAK,CAAC,MAAsB,GAAG,IAAI;AACxD,YAAA,GAAG,CAAC,WAAW,CAAC,EAAE,CAAC,EAAE,aAAa,EAAE,EAAE,EAAE,CAAC,CAAC,EAAE,EAAE,IAAI,EAAE,GAAG,EAAE,EAAE,GAAG,GAAG,CAAC,GAAG,CAAC,GAAG,EAAE,CAAC;AAChF,QAAA,CAAC,CAAC;IACN;AAAO,SAAA,IAAI,CAAC,CAAC,CAAC,KAAK,UAAU,EAAE;AAC3B,QAAA,MAAM,CAAC,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC;IAC3B;AAAO,SAAA,IAAI,CAAC,CAAC,CAAC,KAAK,OAAO,EAAE;QACxB,MAAM,CAAC,KAAK,EAAE;IAClB;AACJ,CAAC"}