@dcasegr/skein 0.0.0-stage → 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (176) hide show
  1. package/README.md +522 -2
  2. package/bin/skein-host.js +22 -0
  3. package/bin/skein.js +8 -0
  4. package/dist/client/admin-cli.d.ts +10 -0
  5. package/dist/client/admin-cli.js +370 -0
  6. package/dist/client/admin.d.ts +139 -0
  7. package/dist/client/admin.js +256 -0
  8. package/dist/client/bundle.d.ts +9 -0
  9. package/dist/client/bundle.js +34 -0
  10. package/dist/client/cli.d.ts +44 -0
  11. package/dist/client/cli.js +306 -0
  12. package/dist/client/client.d.ts +161 -0
  13. package/dist/client/client.js +237 -0
  14. package/dist/client/config.d.ts +18 -0
  15. package/dist/client/config.js +31 -0
  16. package/dist/client/conversation.d.ts +62 -0
  17. package/dist/client/conversation.js +113 -0
  18. package/dist/client/raw.d.ts +94 -0
  19. package/dist/client/raw.js +154 -0
  20. package/dist/client/target.d.ts +34 -0
  21. package/dist/client/target.js +158 -0
  22. package/dist/dev/cli.d.ts +8 -0
  23. package/dist/dev/cli.js +248 -0
  24. package/dist/dev/scan.d.ts +15 -0
  25. package/dist/dev/scan.js +173 -0
  26. package/dist/envelope-cbor.d.ts +110 -0
  27. package/dist/envelope-cbor.js +160 -0
  28. package/dist/envelope.d.ts +38 -0
  29. package/dist/envelope.js +101 -0
  30. package/dist/host/arc.d.ts +131 -0
  31. package/dist/host/arc.js +425 -0
  32. package/dist/host/billing.d.ts +102 -0
  33. package/dist/host/billing.js +167 -0
  34. package/dist/host/boot.d.ts +246 -0
  35. package/dist/host/boot.js +643 -0
  36. package/dist/host/cli.d.ts +57 -0
  37. package/dist/host/cli.js +1253 -0
  38. package/dist/host/clock.d.ts +3 -0
  39. package/dist/host/clock.js +8 -0
  40. package/dist/host/control.d.ts +45 -0
  41. package/dist/host/control.js +170 -0
  42. package/dist/host/cron.d.ts +99 -0
  43. package/dist/host/cron.js +196 -0
  44. package/dist/host/deliver.d.ts +53 -0
  45. package/dist/host/deliver.js +251 -0
  46. package/dist/host/deploy.d.ts +29 -0
  47. package/dist/host/deploy.js +55 -0
  48. package/dist/host/door-answer.d.ts +24 -0
  49. package/dist/host/door-answer.js +21 -0
  50. package/dist/host/events.d.ts +89 -0
  51. package/dist/host/events.js +222 -0
  52. package/dist/host/fake-arcade.d.ts +40 -0
  53. package/dist/host/fake-arcade.js +149 -0
  54. package/dist/host/fake-discovery.d.ts +4 -0
  55. package/dist/host/fake-discovery.js +58 -0
  56. package/dist/host/feeds.d.ts +127 -0
  57. package/dist/host/feeds.js +422 -0
  58. package/dist/host/frontdoor.d.ts +92 -0
  59. package/dist/host/frontdoor.js +172 -0
  60. package/dist/host/genesis.d.ts +259 -0
  61. package/dist/host/genesis.js +353 -0
  62. package/dist/host/handles.d.ts +40 -0
  63. package/dist/host/handles.js +88 -0
  64. package/dist/host/hostenv.d.ts +30 -0
  65. package/dist/host/hostenv.js +83 -0
  66. package/dist/host/image-chain.d.ts +127 -0
  67. package/dist/host/image-chain.js +440 -0
  68. package/dist/host/install.d.ts +22 -0
  69. package/dist/host/install.js +139 -0
  70. package/dist/host/instances.d.ts +227 -0
  71. package/dist/host/instances.js +430 -0
  72. package/dist/host/kernel.d.ts +246 -0
  73. package/dist/host/kernel.js +603 -0
  74. package/dist/host/liveness.d.ts +65 -0
  75. package/dist/host/liveness.js +155 -0
  76. package/dist/host/manifest.d.ts +160 -0
  77. package/dist/host/manifest.js +597 -0
  78. package/dist/host/p2p.d.ts +337 -0
  79. package/dist/host/p2p.js +1002 -0
  80. package/dist/host/packet.d.ts +121 -0
  81. package/dist/host/packet.js +562 -0
  82. package/dist/host/plan.d.ts +154 -0
  83. package/dist/host/plan.js +418 -0
  84. package/dist/host/providers.d.ts +238 -0
  85. package/dist/host/providers.js +565 -0
  86. package/dist/host/roster.d.ts +72 -0
  87. package/dist/host/roster.js +172 -0
  88. package/dist/host/router.d.ts +773 -0
  89. package/dist/host/router.js +2389 -0
  90. package/dist/host/signer.d.ts +49 -0
  91. package/dist/host/signer.js +167 -0
  92. package/dist/host/store-sqlite.d.ts +39 -0
  93. package/dist/host/store-sqlite.js +170 -0
  94. package/dist/host/stores.d.ts +43 -0
  95. package/dist/host/stores.js +342 -0
  96. package/dist/host/sync.d.ts +121 -0
  97. package/dist/host/sync.js +467 -0
  98. package/dist/host/testbeef.d.ts +21 -0
  99. package/dist/host/testbeef.js +37 -0
  100. package/dist/host/testhost.d.ts +149 -0
  101. package/dist/host/testhost.js +211 -0
  102. package/dist/host/vcdiff.d.ts +9 -0
  103. package/dist/host/vcdiff.js +210 -0
  104. package/dist/host/wasm-kernel.d.ts +87 -0
  105. package/dist/host/wasm-kernel.js +296 -0
  106. package/dist/host/wasm-worker.d.ts +1 -0
  107. package/dist/host/wasm-worker.js +186 -0
  108. package/dist/peers/cron.d.ts +33 -0
  109. package/dist/peers/cron.js +75 -0
  110. package/dist/peers/infer-main.d.ts +2 -0
  111. package/dist/peers/infer-main.js +74 -0
  112. package/dist/peers/infer.d.ts +163 -0
  113. package/dist/peers/infer.js +367 -0
  114. package/dist/runtime/beef.d.ts +28 -0
  115. package/dist/runtime/beef.js +107 -0
  116. package/dist/runtime/bitcoin.d.ts +36 -0
  117. package/dist/runtime/bitcoin.js +141 -0
  118. package/dist/runtime/cid.d.ts +14 -0
  119. package/dist/runtime/cid.js +51 -0
  120. package/dist/runtime/dispatch.d.ts +92 -0
  121. package/dist/runtime/dispatch.js +142 -0
  122. package/dist/runtime/envelope.d.ts +60 -0
  123. package/dist/runtime/envelope.js +101 -0
  124. package/dist/runtime/heads.d.ts +25 -0
  125. package/dist/runtime/heads.js +37 -0
  126. package/dist/runtime/identity.d.ts +28 -0
  127. package/dist/runtime/identity.js +67 -0
  128. package/dist/runtime/index-store.d.ts +152 -0
  129. package/dist/runtime/index-store.js +715 -0
  130. package/dist/runtime/log.d.ts +62 -0
  131. package/dist/runtime/log.js +104 -0
  132. package/dist/runtime/programs.d.ts +30 -0
  133. package/dist/runtime/programs.js +61 -0
  134. package/dist/runtime/records.d.ts +135 -0
  135. package/dist/runtime/records.js +149 -0
  136. package/dist/runtime/sqlite.d.ts +33 -0
  137. package/dist/runtime/sqlite.js +604 -0
  138. package/dist/runtime/store.d.ts +141 -0
  139. package/dist/runtime/store.js +24 -0
  140. package/dist/runtime/syscalls.d.ts +9 -0
  141. package/dist/runtime/syscalls.js +41 -0
  142. package/dist/runtime/tree.d.ts +60 -0
  143. package/dist/runtime/tree.js +196 -0
  144. package/dist/runtime/types.d.ts +100 -0
  145. package/dist/runtime/types.js +7 -0
  146. package/dist/testapps.d.ts +101 -0
  147. package/dist/testapps.js +275 -0
  148. package/dist/testkit.d.ts +25 -0
  149. package/dist/testkit.js +48 -0
  150. package/dist/wallet.d.ts +35 -0
  151. package/dist/wallet.js +87 -0
  152. package/images/README.md +68 -0
  153. package/images/default/README.md +38 -0
  154. package/images/default/bin/frontdoor.cid +1 -0
  155. package/images/default/bin/frontdoor.json +5 -0
  156. package/images/default/bin/messagebox.cid +1 -0
  157. package/images/default/bin/messagebox.json +8 -0
  158. package/images/default/bin/wallet.cid +1 -0
  159. package/images/default/bin/wallet.json +14 -0
  160. package/images/default/etc/config.json +1 -0
  161. package/images/default/etc/dispatch.json +82 -0
  162. package/images/host/etc/apps.json +8 -0
  163. package/kernel-zig/README.md +1247 -0
  164. package/kernel-zig/web/client.js +65 -0
  165. package/kernel-zig/web/kernel.d.ts +59 -0
  166. package/kernel-zig/web/kernel.js +329 -0
  167. package/kernel-zig/web/store.js +67 -0
  168. package/kernel-zig/web/worker.js +79 -0
  169. package/kernel-zig/zig-out/web/skein-kernel.wasm +0 -0
  170. package/package.json +77 -4
  171. package/wasm/README.md +35 -0
  172. package/wasm/frontdoor.wasm +0 -0
  173. package/wasm/messagebox.wasm +0 -0
  174. package/wasm/resolve.wasm +0 -0
  175. package/wasm/wallet.wasm +0 -0
  176. package/wasm/wire-probe.wasm +0 -0
@@ -0,0 +1,211 @@
1
+ // A test host (#40): a router over a temporary host.db and SKEIN_HOME,
2
+ // instances (mailbox instances among them) with keys of their own (the signer's stand-in). With no
3
+ // host skein its own origin's BRC-169 requests are answered by a fixture over
4
+ // host.db (fake-discovery.ts); `hostSkein()` makes the real one (#113): the host
5
+ // skein with the onboarding app installed, which then answers them.
6
+ import { existsSync, statSync } from "node:fs";
7
+ import * as fs from "node:fs/promises";
8
+ import { tmpdir } from "node:os";
9
+ import { join } from "node:path";
10
+ import { fileURLToPath } from "node:url";
11
+ import { PrivateKey } from "@bsv/sdk";
12
+ import * as dagCbor from "@ipld/dag-cbor";
13
+ import { dirBundles } from "../client/client.js";
14
+ import { RawBox, signClaim } from "../client/raw.js";
15
+ import { InferPeer } from "../peers/infer.js";
16
+ import { ephemeralWallet } from "../wallet.js";
17
+ import { CHAT_APP, installApps, ONBOARD_APP, SHELL_APP } from "../testapps.js";
18
+ import { fakeDiscovery } from "./fake-discovery.js";
19
+ import { imageSource } from "./boot.js";
20
+ import { HostDb } from "./instances.js";
21
+ import { Router } from "./router.js";
22
+ import { Signer } from "./signer.js";
23
+ const ROOT = join(fileURLToPath(new URL(".", import.meta.url)), "../..");
24
+ export const until = async (what, f, ms = 30_000) => {
25
+ for (const end = Date.now() + ms; Date.now() < end;) {
26
+ const v = await f();
27
+ if (v)
28
+ return v;
29
+ await new Promise((r) => setTimeout(r, 50));
30
+ }
31
+ throw new Error(`timed out: ${what}`);
32
+ };
33
+ /**
34
+ * The messages an instance took in, in log order (#68): a mail entry of its
35
+ * own (a host that admits one directly), or a message a request's thread
36
+ * routed — the `admit` of the front door's answer on that thread's updates.
37
+ */
38
+ export async function messagesIn(store) {
39
+ const byInput = new Map();
40
+ for await (const t of store.edges.query({ kind: "thread" })) {
41
+ const o = await store.get(t);
42
+ if (o.args?.request && o.input)
43
+ byInput.set(o.input.toString(), t);
44
+ }
45
+ const out = [];
46
+ for await (const { cid, entry } of store.log.entries(1)) {
47
+ const mail = entry.mail;
48
+ if (mail) {
49
+ out.push(await store.get(mail));
50
+ continue;
51
+ }
52
+ const t = byInput.get(cid.toString());
53
+ if (!t)
54
+ continue;
55
+ for await (const u of store.chains.history(t)) {
56
+ if (u.equals(t))
57
+ continue;
58
+ const up = await store.get(u);
59
+ if (!up.result?.stdout?.length)
60
+ continue;
61
+ let a;
62
+ try {
63
+ a = dagCbor.decode(up.result.stdout);
64
+ }
65
+ catch {
66
+ continue;
67
+ } // stdout that is not dag-cbor admitted nothing
68
+ for (const x of a.admit ?? [])
69
+ if (x.mail)
70
+ out.push(x.mail);
71
+ }
72
+ }
73
+ return out;
74
+ }
75
+ /** A host with instances (mailbox instances among them), each instance's key its own (the signer's stand-in). */
76
+ export async function testHost(t, o = {}) {
77
+ const home = await fs.mkdtemp(join(tmpdir(), "skein-router-"));
78
+ // #96: o.lmdb — every instance a set of one LMDB environment, <home>/store (host.db `lmdb:<dir>`), else a file each.
79
+ // #158: o.rocks — every instance a set of one RocksDB database, <home>/store-rocksdb, run by one `skein-kernel host` process.
80
+ // The settled split: o.sqlite (or SKEIN_TEST_STORE=sqlite) — every instance a set of one SQLite database, <home>/store.sqlite, on the wasm kernel.
81
+ const sqlite = o.sqlite ?? process.env.SKEIN_TEST_STORE === "sqlite";
82
+ const storeOf = (handle) => (sqlite ? `sqlite:${join(home, "store.sqlite")}` : o.rocks ? `rocksdb:${join(home, "store-rocksdb")}` : o.lmdb ? `lmdb:${join(home, "store")}` : join(home, "instances", handle, "runtime.db"));
83
+ t.after(() => fs.rm(home, { recursive: true, force: true }));
84
+ const db = new HostDb(join(home, "host.db"));
85
+ const keys = new Map();
86
+ const keyOf = (h) => { let k = keys.get(h); if (!k) {
87
+ k = PrivateKey.fromRandom();
88
+ keys.set(h, k);
89
+ } return k; };
90
+ const ownerKey = o.ownerKey ?? PrivateKey.fromRandom();
91
+ const owner = ephemeralWallet(ownerKey), ownerId = ownerKey.toPublicKey().toString();
92
+ const lines = [];
93
+ const providerMaster = PrivateKey.fromRandom();
94
+ const router = new Router({
95
+ db, walletFor: (row) => ephemeralWallet(keyOf(row.handle)), owner: ownerId, infer: o.infer, home,
96
+ idleMs: o.idleMs ?? 0, http: o.http, ownerMessagebox: o.ownerMessagebox, genesis: o.genesis, now: o.now, ledgerMs: 60_000, kernel: { env: { SKEIN_HOME: home } },
97
+ // A broadcaster (#58, #65): its Arcade, and so a `status` provider.
98
+ ...(o.arc ? { arc: o.arc } : {}), ...(o.arcRetry ? { arcRetry: o.arcRetry } : {}),
99
+ // The host's headers feed (#102).
100
+ ...(o.headersFeed ? { headersFeed: o.headersFeed } : {}),
101
+ // #130: what the host bills (billing.ts); none: it bills no one.
102
+ ...(o.billing ? { billing: o.billing } : {}),
103
+ ...(o.lmdb ? { lmdbStore: join(home, "store") } : {}),
104
+ ...(o.rocks ? { rocksStore: join(home, "store-rocksdb") } : {}),
105
+ // The settled split: the wasm kernel over the host's one SQLite database (SKEIN_TEST_STORE=sqlite: every test host so).
106
+ ...(sqlite ? { sqliteStore: join(home, "store.sqlite") } : {}),
107
+ // #113: with no host skein, discovery over host.db (a fixture); hostSkein() makes the real server.
108
+ discovery: fakeDiscovery(db, () => router),
109
+ // The host's providers (#70) under keys of its own, as `skein-host run` derives them.
110
+ providerKeyFor: (name) => new Signer(providerMaster).providerKey(name),
111
+ log: (s, l) => { lines.push(`[${s}] ${l}`); if (process.env.VERBOSE)
112
+ console.log(`[${s}] ${l}`); },
113
+ });
114
+ t.after(async () => { await router.stop(); db.close(); });
115
+ await router.listen(0);
116
+ const base = `http://127.0.0.1:${router.port}`;
117
+ const h = {
118
+ home, db, router, base, owner, ownerKey, ownerId, lines, keyOf,
119
+ /**
120
+ * An instance's row (host.db kind `agent`: not a mailbox instance), its
121
+ * genesis at first hydration (the owner's mailbox must exist first to be
122
+ * named in it). Its identity key (hex).
123
+ */
124
+ instance(handle) { db.add(handle, { store: storeOf(handle), identity: keyOf(handle).toPublicKey().toString() }); return keyOf(handle).toPublicKey().toString(); },
125
+ /** The same as `instance`, by its old name (for tests written before the rename). */
126
+ agent(handle) { return h.instance(handle); },
127
+ /**
128
+ * An instance booted from an image (#89; default: the default image,
129
+ * images/default): no owner in its genesis, the claim row from anyone.
130
+ * Claim it with a message from the owner's wallet in box `claim`
131
+ * (src/testapps.ts sendPlan of planClaim, #127): its sender owns it.
132
+ */
133
+ async image(handle, dir = join(ROOT, "images/default")) {
134
+ db.add(handle, { store: storeOf(handle), identity: keyOf(handle).toPublicKey().toString() });
135
+ return await router.bootRow(handle, await imageSource(dir), { image: true });
136
+ },
137
+ /** A mailbox instance's row (a fixture's: its genesis at first hydration; the product path is the manager's create, Router.createInstance image `mailbox`). */
138
+ mailbox(handle, whose) { db.add(handle, { kind: "mailbox", owner: whose, store: storeOf(handle), identity: keyOf(handle).toPublicKey().toString() }); },
139
+ /**
140
+ * The host skein (#90, #113, #142): `host` from the host image (the
141
+ * default image with the onboarding app), its genesis naming the owner
142
+ * (the operator's key here), the instance manager and the certifier in
143
+ * its address book; the onboarding app's config at birth over its
144
+ * manifest's (default: domain `localhost`, origin this router's). Its own
145
+ * origin's BRC-169 requests are then the app's.
146
+ */
147
+ async hostSkein(config = {}) {
148
+ const c = await router.createInstance("host", ownerId, { host: true, appConfig: { onboard: { onboard: { domain: "localhost", origin: base, ...config } } } });
149
+ await router.hydrate("host");
150
+ await router.settled();
151
+ return c;
152
+ },
153
+ origin: (handle) => router.originOf(handle),
154
+ storeSize(handle) { const p = db.get(handle).store; return statSync(p).size + (existsSync(`${p}-wal`) ? statSync(`${p}-wal`).size : 0); },
155
+ /**
156
+ * Install apps into an instance as the owner (#83: a genesis has no shell,
157
+ * no `run`, no `chat`): by default the shell app and the chat app, through
158
+ * the owner's messages (src/testapps.ts installApps, #124). Hydrates the instance first.
159
+ */
160
+ async install(handle, apps = [SHELL_APP, CHAT_APP]) {
161
+ await router.hydrate(handle);
162
+ await installApps({ home, port: router.port, owner, settled: () => router.settled() }, handle, apps);
163
+ },
164
+ async entries(handle) { const k = (await router.hydrate(handle)).kernel; return (await k.store.get((await k.store.log.tip()))).n + 1; },
165
+ };
166
+ return h;
167
+ }
168
+ /**
169
+ * A small world on a test host (the explorer's and the store reader's tests):
170
+ * the owner's mailbox, the inference peer's, and instance `alpha` with the shell
171
+ * app and the chat app installed (#83); `dir` imported into alpha (objects →
172
+ * `main`); a chat whose turn makes one bash
173
+ * tool call (`ls | head -3`) and answers "README and **src**." into the
174
+ * owner's mailbox. The peer answers from a script, over its raw transport.
175
+ */
176
+ export async function chatWorld(t, dir, o = {}) {
177
+ const inferKey = PrivateKey.fromRandom(), inferId = inferKey.toPublicKey().toString();
178
+ const h = await testHost(t, { infer: inferId, ...o });
179
+ h.mailbox("david", h.ownerId);
180
+ h.mailbox("infer", inferId);
181
+ const alpha = h.instance("alpha");
182
+ await h.router.start();
183
+ await h.install("alpha");
184
+ const reply = (message) => new Response(JSON.stringify({ choices: [{ message: { role: "assistant", ...message } }], usage: { prompt_tokens: 10, completion_tokens: 5 }, model: "qwen38" }), { status: 200 });
185
+ const script = [
186
+ reply({ content: "", tool_calls: [{ id: "call-1", type: "function", function: { name: "bash", arguments: JSON.stringify({ cmd: "ls | head -3" }) } }] }),
187
+ reply({ content: "README and **src**." }),
188
+ ];
189
+ const w = ephemeralWallet(inferKey);
190
+ const peer = new InferPeer({
191
+ wallet: w, providers: { ripper: { baseUrl: "http://ripper.test/v1" } }, log: (l) => h.lines.push(`[infer] ${l}`),
192
+ fetch: (async () => script.shift() ?? new Response("no more", { status: 500 })),
193
+ raw: {
194
+ inbox: new RawBox(w, `${h.base}/@infer`),
195
+ outbox: (url) => new RawBox(w, url),
196
+ addressOf: (k) => { const r = h.db.list().find((x) => x.identity === k && x.kind !== "mailbox"); return r && h.origin(r.handle); },
197
+ },
198
+ });
199
+ const owner = new RawBox(h.owner, `${h.base}/@alpha`);
200
+ const { root, bundles } = await dirBundles(dir);
201
+ for (const b of bundles)
202
+ await owner.send(alpha, "objects", b);
203
+ const chat = (await owner.send(alpha, "chat", { text: "What is here?" })).id;
204
+ const mine = new RawBox(h.owner, `${h.base}/@david`);
205
+ await until("alpha's answer", async () => {
206
+ await peer.poll();
207
+ return (await mine.list("chat")).find((m) => m.value.text === "README and **src**.");
208
+ }, 60_000);
209
+ await h.router.settled();
210
+ return { h, alpha, root, chat, store: h.db.get("alpha").store };
211
+ }
@@ -0,0 +1,9 @@
1
+ export declare class VcdiffError extends Error {
2
+ }
3
+ /** apply(delta, source): the target bytes. */
4
+ export declare function vcdiffDecode(delta: Uint8Array, source?: Uint8Array): Uint8Array;
5
+ /**
6
+ * A minimal encoder (tests and `pack --patch`): one window over the whole
7
+ * source, the longest common prefix and suffix as COPYs, the middle as an ADD.
8
+ */
9
+ export declare function vcdiffEncode(target: Uint8Array, source?: Uint8Array): Uint8Array;
@@ -0,0 +1,210 @@
1
+ // RFC 3284 VCDIFF, decode only: what an `ordfs/patch` output carries (gib's
2
+ // file edits; ~/Work/bsv/gib-cli docs/plans/ordfs-formats.html). The profile
3
+ // gib's writers emit is the plain one — header D6 C3 C4 00, no secondary
4
+ // compression, no custom code table — and this decoder takes exactly that
5
+ // (plus xdelta3's application header and Adler-32 window checksum, which it
6
+ // skips/verifies), any number of windows, VCD_SOURCE and VCD_TARGET.
7
+ export class VcdiffError extends Error {
8
+ }
9
+ const NOOP = 0, ADD = 1, RUN = 2, COPY = 3;
10
+ const S_NEAR = 4, S_SAME = 3;
11
+ /** The default code table (RFC 3284 §5.6): 256 entries of one or two instructions. */
12
+ const TABLE = (() => {
13
+ const t = [];
14
+ const i = (type, size = 0, mode = 0) => ({ type, size, mode });
15
+ const none = i(NOOP);
16
+ t.push([i(RUN, 0), none]);
17
+ for (let s = 0; s <= 17; s++)
18
+ t.push([i(ADD, s), none]);
19
+ for (let m = 0; m <= 8; m++) {
20
+ t.push([i(COPY, 0, m), none]);
21
+ for (let s = 4; s <= 18; s++)
22
+ t.push([i(COPY, s, m), none]);
23
+ }
24
+ for (let m = 0; m <= 5; m++)
25
+ for (let a = 1; a <= 4; a++)
26
+ for (let c = 4; c <= 6; c++)
27
+ t.push([i(ADD, a), i(COPY, c, m)]);
28
+ for (let m = 6; m <= 8; m++)
29
+ for (let a = 1; a <= 4; a++)
30
+ t.push([i(ADD, a), i(COPY, 4, m)]);
31
+ for (let m = 0; m <= 8; m++)
32
+ t.push([i(COPY, 4, m), i(ADD, 1)]);
33
+ if (t.length !== 256)
34
+ throw new Error("vcdiff code table");
35
+ return t;
36
+ })();
37
+ class Reader {
38
+ b;
39
+ at;
40
+ end;
41
+ constructor(b, at = 0, end = b.length) { this.b = b; this.at = at; this.end = end; }
42
+ byte() {
43
+ if (this.at >= this.end)
44
+ throw new VcdiffError("vcdiff: truncated");
45
+ return this.b[this.at++];
46
+ }
47
+ int() {
48
+ let v = 0;
49
+ for (let n = 0; n < 9; n++) {
50
+ const x = this.byte();
51
+ v = v * 128 + (x & 0x7f);
52
+ if (v > Number.MAX_SAFE_INTEGER)
53
+ throw new VcdiffError("vcdiff: integer overflow");
54
+ if (!(x & 0x80))
55
+ return v;
56
+ }
57
+ throw new VcdiffError("vcdiff: integer too long");
58
+ }
59
+ take(n) {
60
+ if (this.at + n > this.end)
61
+ throw new VcdiffError("vcdiff: truncated");
62
+ const s = this.b.subarray(this.at, this.at + n);
63
+ this.at += n;
64
+ return s;
65
+ }
66
+ }
67
+ function adler32(b) {
68
+ let a = 1, s = 0;
69
+ for (const x of b) {
70
+ a = (a + x) % 65521;
71
+ s = (s + a) % 65521;
72
+ }
73
+ return ((s << 16) | a) >>> 0;
74
+ }
75
+ /** apply(delta, source): the target bytes. */
76
+ export function vcdiffDecode(delta, source = new Uint8Array(0)) {
77
+ const r = new Reader(delta);
78
+ if (r.byte() !== 0xd6 || r.byte() !== 0xc3 || r.byte() !== 0xc4)
79
+ throw new VcdiffError("vcdiff: bad magic");
80
+ if (r.byte() !== 0)
81
+ throw new VcdiffError("vcdiff: unsupported version");
82
+ const hdr = r.byte();
83
+ if (hdr & 0x01)
84
+ throw new VcdiffError("vcdiff: secondary compression is not supported");
85
+ if (hdr & 0x02)
86
+ throw new VcdiffError("vcdiff: custom code tables are not supported");
87
+ if (hdr & ~0x07)
88
+ throw new VcdiffError("vcdiff: unknown header bits");
89
+ if (hdr & 0x04)
90
+ r.take(r.int()); // xdelta3's application header
91
+ const out = [];
92
+ let target = new Uint8Array(0);
93
+ while (r.at < r.end) {
94
+ const win = r.byte();
95
+ if (win & ~0x07)
96
+ throw new VcdiffError("vcdiff: unknown window bits");
97
+ if ((win & 0x03) === 0x03)
98
+ throw new VcdiffError("vcdiff: VCD_SOURCE and VCD_TARGET together");
99
+ let seg = new Uint8Array(0);
100
+ if (win & 0x03) {
101
+ const len = r.int(), pos = r.int();
102
+ const from = win & 0x01 ? source : target;
103
+ if (pos + len > from.length)
104
+ throw new VcdiffError("vcdiff: source segment out of range");
105
+ seg = from.subarray(pos, pos + len);
106
+ }
107
+ const encLen = r.int();
108
+ const encEnd = r.at + encLen;
109
+ if (encEnd > r.end)
110
+ throw new VcdiffError("vcdiff: truncated window");
111
+ const tlen = r.int();
112
+ if (r.byte() !== 0)
113
+ throw new VcdiffError("vcdiff: compressed sections are not supported");
114
+ const dlen = r.int(), ilen = r.int(), alen = r.int();
115
+ const sum = win & 0x04 ? (r.byte() << 24 | r.byte() << 16 | r.byte() << 8 | r.byte()) >>> 0 : undefined;
116
+ const data = new Reader(delta, r.at, r.at + dlen);
117
+ r.take(dlen);
118
+ const inst = new Reader(delta, r.at, r.at + ilen);
119
+ r.take(ilen);
120
+ const addr = new Reader(delta, r.at, r.at + alen);
121
+ r.take(alen);
122
+ if (r.at !== encEnd)
123
+ throw new VcdiffError("vcdiff: window length mismatch");
124
+ const t = new Uint8Array(tlen);
125
+ let tp = 0;
126
+ const near = new Array(S_NEAR).fill(0), same = new Array(S_SAME * 256).fill(0);
127
+ let nextNear = 0;
128
+ const cache = (a) => { near[nextNear] = a; nextNear = (nextNear + 1) % S_NEAR; same[a % (S_SAME * 256)] = a; };
129
+ const decodeAddr = (here, mode) => {
130
+ let a;
131
+ if (mode === 0)
132
+ a = addr.int();
133
+ else if (mode === 1)
134
+ a = here - addr.int();
135
+ else if (mode - 2 < S_NEAR)
136
+ a = near[mode - 2] + addr.int();
137
+ else
138
+ a = same[(mode - 2 - S_NEAR) * 256 + addr.byte()];
139
+ cache(a);
140
+ return a;
141
+ };
142
+ const exec = (x) => {
143
+ if (x.type === NOOP)
144
+ return;
145
+ const size = x.size || inst.int();
146
+ if (tp + size > tlen)
147
+ throw new VcdiffError("vcdiff: instruction overruns the target window");
148
+ if (x.type === ADD) {
149
+ t.set(data.take(size), tp);
150
+ tp += size;
151
+ return;
152
+ }
153
+ if (x.type === RUN) {
154
+ t.fill(data.byte(), tp, tp + size);
155
+ tp += size;
156
+ return;
157
+ }
158
+ const a = decodeAddr(seg.length + tp, x.mode);
159
+ if (a < 0 || a >= seg.length + tp)
160
+ throw new VcdiffError("vcdiff: copy address out of range");
161
+ for (let k = 0; k < size; k++) {
162
+ const s = a + k;
163
+ t[tp++] = s < seg.length ? seg[s] : t[s - seg.length];
164
+ }
165
+ };
166
+ while (inst.at < inst.end) {
167
+ const [x, y] = TABLE[inst.byte()];
168
+ exec(x);
169
+ exec(y);
170
+ }
171
+ if (tp !== tlen || data.at !== data.end || addr.at !== addr.end)
172
+ throw new VcdiffError("vcdiff: window does not add up");
173
+ if (sum !== undefined && adler32(t) !== sum)
174
+ throw new VcdiffError("vcdiff: window checksum mismatch");
175
+ out.push(t);
176
+ target = Buffer.concat(out);
177
+ }
178
+ return new Uint8Array(Buffer.concat(out));
179
+ }
180
+ /**
181
+ * A minimal encoder (tests and `pack --patch`): one window over the whole
182
+ * source, the longest common prefix and suffix as COPYs, the middle as an ADD.
183
+ */
184
+ export function vcdiffEncode(target, source = new Uint8Array(0)) {
185
+ const int = (v) => { const o = [v & 0x7f]; v = Math.floor(v / 128); while (v > 0) {
186
+ o.unshift((v & 0x7f) | 0x80);
187
+ v = Math.floor(v / 128);
188
+ } return o; };
189
+ let pre = 0;
190
+ while (pre < source.length && pre < target.length && source[pre] === target[pre])
191
+ pre++;
192
+ let suf = 0;
193
+ while (suf < source.length - pre && suf < target.length - pre && source[source.length - 1 - suf] === target[target.length - 1 - suf])
194
+ suf++;
195
+ const mid = target.subarray(pre, target.length - suf);
196
+ const inst = [], addr = [];
197
+ if (pre) {
198
+ inst.push(19, ...int(pre));
199
+ addr.push(...int(0));
200
+ } // COPY size 0 (explicit), mode 0 (VCD_SELF)
201
+ if (mid.length)
202
+ inst.push(1, ...int(mid.length)); // ADD size 0 (explicit)
203
+ if (suf) {
204
+ inst.push(19, ...int(suf));
205
+ addr.push(...int(source.length - suf));
206
+ }
207
+ const body = Buffer.concat([Buffer.from([...int(target.length), 0, ...int(mid.length), ...int(inst.length), ...int(addr.length)]), mid, Buffer.from(inst), Buffer.from(addr)]);
208
+ const win = source.length ? [0x01, ...int(source.length), ...int(0)] : [0x00];
209
+ return new Uint8Array(Buffer.concat([Buffer.from([0xd6, 0xc3, 0xc4, 0x00, 0x00, ...win, ...int(body.length)]), body]));
210
+ }
@@ -0,0 +1,87 @@
1
+ import { Worker } from "node:worker_threads";
2
+ import type { CID } from "multiformats/cid";
3
+ import { KernelSurface, type HostChainTip, type KernelOptions } from "./kernel.ts";
4
+ /** The wasm kernel: $SKEIN_KERNEL_WASM, else kernel-zig's `zig build web` output (the package ships it there). */
5
+ export declare const KERNEL_WASM: string;
6
+ /** The pinned modules (the boundary programs): $SKEIN_WASM_DIR, else the repo's (the package's) wasm/. */
7
+ export declare const WASM_DIR: string;
8
+ /** One worker holding one machine: frames in, replies and the kernel's notices and requests out. */
9
+ declare class Machine {
10
+ readonly worker: Worker;
11
+ private nextId;
12
+ private waiting;
13
+ readonly ready: Promise<void>;
14
+ readonly done: Promise<number>;
15
+ gone: boolean;
16
+ private control;
17
+ private ctrl;
18
+ private data;
19
+ constructor(module: WebAssembly.Module, w: {
20
+ db: string;
21
+ set: string;
22
+ mode: "instance" | "chain";
23
+ network?: string;
24
+ }, o: {
25
+ onNotify(op: string, v: Uint8Array): void;
26
+ onRequest(op: string, v: Uint8Array): Promise<Uint8Array>;
27
+ log?(line: string): void;
28
+ });
29
+ call(op: string, v?: unknown): Promise<unknown>;
30
+ }
31
+ export interface WasmHostOptions {
32
+ /** The host's network for the header chain (SKEIN_NETWORK: main, test, regtest; default main). */
33
+ network?: string;
34
+ log?(line: string): void;
35
+ }
36
+ /** The host's machines over one SQLite database (one per database per node process: acquire/release). */
37
+ export declare class WasmHost {
38
+ private static all;
39
+ private static compiled?;
40
+ /** The wasm kernel, compiled once per node process. */
41
+ static module(): Promise<WebAssembly.Module>;
42
+ static acquire(db: string, o?: WasmHostOptions): WasmHost;
43
+ readonly db: string;
44
+ readonly o: WasmHostOptions;
45
+ private holders;
46
+ private views;
47
+ private chain?;
48
+ private recent;
49
+ private constructor();
50
+ /** Hydrate an instance: its machine on a worker of its own (the store's pinned modules installed). */
51
+ open(o: KernelOptions): Promise<WasmKernel>;
52
+ /** The host chain's holder (#156): a machine over the `host/chain` set, no instance. */
53
+ private holder;
54
+ /**
55
+ * #156: a run of headers into the host chain. The same run asked for again while it is in flight or
56
+ * just answered (every instance on one feed asks for the same event) is one call: its answer shared.
57
+ */
58
+ chainHeaders(raws: Uint8Array[]): Promise<HostChainTip>;
59
+ /** #156: an image's chain/headers (the tree `root`, whose blocks the set `set` holds) into the empty host chain. */
60
+ chainImport(set: string, root: CID): Promise<HostChainTip>;
61
+ /** #156: the host chain as it stands, or undefined (empty). */
62
+ chainTip(): Promise<HostChainTip | undefined>;
63
+ /** One holder less; the last stops every machine. */
64
+ release(): Promise<void>;
65
+ }
66
+ /** One instance's machine, as the router holds it. */
67
+ export declare class WasmKernel extends KernelSurface {
68
+ readonly o: KernelOptions;
69
+ readonly host: WasmHost;
70
+ readonly machine: Machine;
71
+ private wire?;
72
+ /** Its stop has begun (the next open of the handle waits for the worker to end). */
73
+ stopping: boolean;
74
+ constructor(host: WasmHost, o: KernelOptions, module: WebAssembly.Module);
75
+ private onNotify;
76
+ get pid(): number | undefined;
77
+ call(op: string, v?: unknown): Promise<unknown>;
78
+ running(identity: string): Promise<void>;
79
+ /** #156: the host chain is the host's: its holder's. */
80
+ chainHeaders(raws: Uint8Array[]): Promise<HostChainTip>;
81
+ chainImport(root: CID): Promise<HostChainTip>;
82
+ chainTip(): Promise<HostChainTip | undefined>;
83
+ /** Stop the machine: what its worker runs finishes, its store is closed, the worker ends. */
84
+ stop(): Promise<void>;
85
+ exited(): Promise<void>;
86
+ }
87
+ export {};