@dcasegr/skein 0.0.0-stage → 0.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +525 -2
- package/bin/skein-host.js +22 -0
- package/bin/skein.js +8 -0
- package/dist/client/admin-cli.d.ts +10 -0
- package/dist/client/admin-cli.js +370 -0
- package/dist/client/admin.d.ts +139 -0
- package/dist/client/admin.js +256 -0
- package/dist/client/bundle.d.ts +9 -0
- package/dist/client/bundle.js +34 -0
- package/dist/client/cli.d.ts +44 -0
- package/dist/client/cli.js +306 -0
- package/dist/client/client.d.ts +161 -0
- package/dist/client/client.js +237 -0
- package/dist/client/config.d.ts +18 -0
- package/dist/client/config.js +31 -0
- package/dist/client/conversation.d.ts +62 -0
- package/dist/client/conversation.js +113 -0
- package/dist/client/raw.d.ts +94 -0
- package/dist/client/raw.js +154 -0
- package/dist/client/target.d.ts +34 -0
- package/dist/client/target.js +158 -0
- package/dist/dev/cli.d.ts +8 -0
- package/dist/dev/cli.js +248 -0
- package/dist/dev/scan.d.ts +15 -0
- package/dist/dev/scan.js +173 -0
- package/dist/envelope-cbor.d.ts +110 -0
- package/dist/envelope-cbor.js +160 -0
- package/dist/envelope.d.ts +38 -0
- package/dist/envelope.js +101 -0
- package/dist/host/arc.d.ts +131 -0
- package/dist/host/arc.js +425 -0
- package/dist/host/billing.d.ts +102 -0
- package/dist/host/billing.js +167 -0
- package/dist/host/boot.d.ts +246 -0
- package/dist/host/boot.js +643 -0
- package/dist/host/cli.d.ts +57 -0
- package/dist/host/cli.js +1253 -0
- package/dist/host/clock.d.ts +3 -0
- package/dist/host/clock.js +8 -0
- package/dist/host/control.d.ts +45 -0
- package/dist/host/control.js +170 -0
- package/dist/host/cron.d.ts +99 -0
- package/dist/host/cron.js +196 -0
- package/dist/host/deliver.d.ts +53 -0
- package/dist/host/deliver.js +251 -0
- package/dist/host/deploy.d.ts +29 -0
- package/dist/host/deploy.js +55 -0
- package/dist/host/door-answer.d.ts +24 -0
- package/dist/host/door-answer.js +21 -0
- package/dist/host/events.d.ts +89 -0
- package/dist/host/events.js +222 -0
- package/dist/host/fake-arcade.d.ts +40 -0
- package/dist/host/fake-arcade.js +149 -0
- package/dist/host/fake-discovery.d.ts +4 -0
- package/dist/host/fake-discovery.js +58 -0
- package/dist/host/feeds.d.ts +127 -0
- package/dist/host/feeds.js +422 -0
- package/dist/host/frontdoor.d.ts +92 -0
- package/dist/host/frontdoor.js +172 -0
- package/dist/host/genesis.d.ts +259 -0
- package/dist/host/genesis.js +353 -0
- package/dist/host/handles.d.ts +40 -0
- package/dist/host/handles.js +88 -0
- package/dist/host/hostenv.d.ts +30 -0
- package/dist/host/hostenv.js +83 -0
- package/dist/host/image-chain.d.ts +127 -0
- package/dist/host/image-chain.js +440 -0
- package/dist/host/install.d.ts +22 -0
- package/dist/host/install.js +139 -0
- package/dist/host/instances.d.ts +227 -0
- package/dist/host/instances.js +430 -0
- package/dist/host/kernel.d.ts +246 -0
- package/dist/host/kernel.js +603 -0
- package/dist/host/liveness.d.ts +65 -0
- package/dist/host/liveness.js +155 -0
- package/dist/host/manifest.d.ts +160 -0
- package/dist/host/manifest.js +597 -0
- package/dist/host/p2p.d.ts +337 -0
- package/dist/host/p2p.js +1002 -0
- package/dist/host/packet.d.ts +121 -0
- package/dist/host/packet.js +562 -0
- package/dist/host/plan.d.ts +154 -0
- package/dist/host/plan.js +418 -0
- package/dist/host/providers.d.ts +238 -0
- package/dist/host/providers.js +565 -0
- package/dist/host/roster.d.ts +72 -0
- package/dist/host/roster.js +172 -0
- package/dist/host/router.d.ts +782 -0
- package/dist/host/router.js +2411 -0
- package/dist/host/signer.d.ts +49 -0
- package/dist/host/signer.js +167 -0
- package/dist/host/store-sqlite.d.ts +39 -0
- package/dist/host/store-sqlite.js +170 -0
- package/dist/host/stores.d.ts +43 -0
- package/dist/host/stores.js +342 -0
- package/dist/host/sync.d.ts +121 -0
- package/dist/host/sync.js +467 -0
- package/dist/host/testbeef.d.ts +21 -0
- package/dist/host/testbeef.js +37 -0
- package/dist/host/testhost.d.ts +149 -0
- package/dist/host/testhost.js +211 -0
- package/dist/host/vcdiff.d.ts +9 -0
- package/dist/host/vcdiff.js +210 -0
- package/dist/host/wasm-kernel.d.ts +87 -0
- package/dist/host/wasm-kernel.js +296 -0
- package/dist/host/wasm-worker.d.ts +1 -0
- package/dist/host/wasm-worker.js +186 -0
- package/dist/peers/cron.d.ts +33 -0
- package/dist/peers/cron.js +75 -0
- package/dist/peers/infer-main.d.ts +2 -0
- package/dist/peers/infer-main.js +74 -0
- package/dist/peers/infer.d.ts +163 -0
- package/dist/peers/infer.js +367 -0
- package/dist/runtime/beef.d.ts +28 -0
- package/dist/runtime/beef.js +107 -0
- package/dist/runtime/bitcoin.d.ts +36 -0
- package/dist/runtime/bitcoin.js +141 -0
- package/dist/runtime/cid.d.ts +14 -0
- package/dist/runtime/cid.js +51 -0
- package/dist/runtime/dispatch.d.ts +92 -0
- package/dist/runtime/dispatch.js +142 -0
- package/dist/runtime/envelope.d.ts +60 -0
- package/dist/runtime/envelope.js +101 -0
- package/dist/runtime/heads.d.ts +25 -0
- package/dist/runtime/heads.js +37 -0
- package/dist/runtime/identity.d.ts +28 -0
- package/dist/runtime/identity.js +67 -0
- package/dist/runtime/index-store.d.ts +152 -0
- package/dist/runtime/index-store.js +715 -0
- package/dist/runtime/log.d.ts +62 -0
- package/dist/runtime/log.js +104 -0
- package/dist/runtime/programs.d.ts +30 -0
- package/dist/runtime/programs.js +61 -0
- package/dist/runtime/records.d.ts +135 -0
- package/dist/runtime/records.js +149 -0
- package/dist/runtime/sqlite.d.ts +33 -0
- package/dist/runtime/sqlite.js +604 -0
- package/dist/runtime/store.d.ts +141 -0
- package/dist/runtime/store.js +24 -0
- package/dist/runtime/syscalls.d.ts +9 -0
- package/dist/runtime/syscalls.js +41 -0
- package/dist/runtime/tree.d.ts +60 -0
- package/dist/runtime/tree.js +196 -0
- package/dist/runtime/types.d.ts +100 -0
- package/dist/runtime/types.js +7 -0
- package/dist/testapps.d.ts +101 -0
- package/dist/testapps.js +275 -0
- package/dist/testkit.d.ts +25 -0
- package/dist/testkit.js +48 -0
- package/dist/wallet.d.ts +35 -0
- package/dist/wallet.js +87 -0
- package/images/README.md +68 -0
- package/images/default/README.md +38 -0
- package/images/default/bin/frontdoor.cid +1 -0
- package/images/default/bin/frontdoor.json +5 -0
- package/images/default/bin/messagebox.cid +1 -0
- package/images/default/bin/messagebox.json +8 -0
- package/images/default/bin/wallet.cid +1 -0
- package/images/default/bin/wallet.json +14 -0
- package/images/default/etc/config.json +1 -0
- package/images/default/etc/dispatch.json +82 -0
- package/images/host/etc/apps.json +8 -0
- package/kernel-zig/README.md +1247 -0
- package/kernel-zig/web/client.js +65 -0
- package/kernel-zig/web/kernel.d.ts +59 -0
- package/kernel-zig/web/kernel.js +329 -0
- package/kernel-zig/web/store.js +67 -0
- package/kernel-zig/web/worker.js +79 -0
- package/kernel-zig/zig-out/web/skein-kernel.wasm +0 -0
- package/package.json +77 -4
- package/wasm/README.md +35 -0
- package/wasm/frontdoor.wasm +0 -0
- package/wasm/messagebox.wasm +0 -0
- package/wasm/resolve.wasm +0 -0
- package/wasm/wallet.wasm +0 -0
- package/wasm/wire-probe.wasm +0 -0
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
// The page's side of the kernel's Worker (issue #35, worker.js): method calls
|
|
2
|
+
// as promises, the kernel's notifications as a callback, and its blocking
|
|
3
|
+
// requests answered by an async handler — the answer written into the shared
|
|
4
|
+
// buffer the worker waits on. The page must be cross-origin isolated
|
|
5
|
+
// (COOP: same-origin, COEP: require-corp) for SharedArrayBuffer.
|
|
6
|
+
|
|
7
|
+
const enc = new TextEncoder();
|
|
8
|
+
|
|
9
|
+
export class KernelWorker {
|
|
10
|
+
/**
|
|
11
|
+
* `wasm`: the kernel's URL or bytes. `onRequest(op, bytes)` → Promise<Uint8Array>
|
|
12
|
+
* (throw for an error answer); `onNotify(op, v)`.
|
|
13
|
+
*/
|
|
14
|
+
constructor({ wasm, workerUrl = new URL("./worker.js", import.meta.url), onRequest, onNotify } = {}) {
|
|
15
|
+
if (!globalThis.crossOriginIsolated && typeof window !== "undefined") throw new Error("the kernel's worker needs a cross-origin isolated page (COOP/COEP)");
|
|
16
|
+
this.worker = new Worker(workerUrl, { type: "module" });
|
|
17
|
+
this.control = new SharedArrayBuffer(12);
|
|
18
|
+
this.ctrl = new Int32Array(this.control);
|
|
19
|
+
this.answer = new SharedArrayBuffer(1 << 16, { maxByteLength: 1 << 30 });
|
|
20
|
+
this.next = 1;
|
|
21
|
+
this.waiting = new Map();
|
|
22
|
+
this.onRequest = onRequest;
|
|
23
|
+
this.onNotify = onNotify;
|
|
24
|
+
this.worker.onmessage = (e) => this.onMessage(e.data);
|
|
25
|
+
this.worker.onerror = (e) => { for (const w of this.waiting.values()) w.reject(new Error(`kernel worker: ${e.message}`)); this.waiting.clear(); };
|
|
26
|
+
this.ready = this.call("init", typeof wasm === "string" ? new URL(wasm, globalThis.location?.href).href : wasm, this.control, this.answer);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
call(method, ...args) {
|
|
30
|
+
const id = this.next++;
|
|
31
|
+
return new Promise((resolve, reject) => {
|
|
32
|
+
this.waiting.set(id, { resolve, reject });
|
|
33
|
+
this.worker.postMessage({ id, method, args });
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
async onMessage(m) {
|
|
38
|
+
if (m.id !== undefined) {
|
|
39
|
+
const w = this.waiting.get(m.id);
|
|
40
|
+
if (!w) return;
|
|
41
|
+
this.waiting.delete(m.id);
|
|
42
|
+
if (m.error !== undefined) w.reject(new Error(m.error)); else w.resolve(m.result);
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
if (m.notify !== undefined) { try { this.onNotify?.(m.notify, m.v); } catch (e) { console.error(e); } return; }
|
|
46
|
+
if (m.request !== undefined) {
|
|
47
|
+
let status = 0, bytes;
|
|
48
|
+
try {
|
|
49
|
+
if (!this.onRequest) throw new Error(`this host answers no ${m.request}`);
|
|
50
|
+
bytes = await this.onRequest(m.request, m.v);
|
|
51
|
+
} catch (e) {
|
|
52
|
+
status = 1;
|
|
53
|
+
bytes = enc.encode(e?.message ?? String(e));
|
|
54
|
+
}
|
|
55
|
+
if (bytes.length > this.answer.byteLength) this.answer.grow(bytes.length);
|
|
56
|
+
new Uint8Array(this.answer, 0, bytes.length).set(bytes);
|
|
57
|
+
this.ctrl[1] = status;
|
|
58
|
+
this.ctrl[2] = bytes.length;
|
|
59
|
+
Atomics.store(this.ctrl, 0, 1);
|
|
60
|
+
Atomics.notify(this.ctrl, 0);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
terminate() { this.worker.terminate(); }
|
|
65
|
+
}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
// Types for kernel.js (the wasm kernel's dependency-free shim), for the TypeScript host that loads it.
|
|
2
|
+
|
|
3
|
+
/** A store the kernel's `skein_store` imports reach by id. */
|
|
4
|
+
export interface ShimStore {
|
|
5
|
+
getBlock(cid: Uint8Array): Uint8Array | undefined;
|
|
6
|
+
hasBlock(cid: Uint8Array): boolean;
|
|
7
|
+
putBlock(cid: Uint8Array, bytes: Uint8Array): boolean;
|
|
8
|
+
getPointer(name: string): Uint8Array | undefined;
|
|
9
|
+
setPointer(name: string, cid: Uint8Array): boolean;
|
|
10
|
+
begin(): void;
|
|
11
|
+
commit(): boolean;
|
|
12
|
+
rollback(): void;
|
|
13
|
+
entries?(): Iterable<[Uint8Array, Uint8Array]>;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export interface Peers {
|
|
17
|
+
request?(op: string, v: Uint8Array): { ok: Uint8Array } | { error: string };
|
|
18
|
+
notify?(op: string, v: Uint8Array | string): void;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export declare class MemoryStore implements ShimStore {
|
|
22
|
+
constructor(o?: { readOnly?: boolean });
|
|
23
|
+
blocks: Map<string, Uint8Array>;
|
|
24
|
+
pointers: Map<string, Uint8Array>;
|
|
25
|
+
getBlock(cid: Uint8Array): Uint8Array | undefined;
|
|
26
|
+
hasBlock(cid: Uint8Array): boolean;
|
|
27
|
+
putBlock(cid: Uint8Array, bytes: Uint8Array): boolean;
|
|
28
|
+
getPointer(name: string): Uint8Array | undefined;
|
|
29
|
+
setPointer(name: string, cid: Uint8Array): boolean;
|
|
30
|
+
begin(): void;
|
|
31
|
+
commit(): boolean;
|
|
32
|
+
rollback(): void;
|
|
33
|
+
entries(): Iterable<[Uint8Array, Uint8Array]>;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export declare function codecOf(cid: Uint8Array): number;
|
|
37
|
+
|
|
38
|
+
export declare class Kernel {
|
|
39
|
+
constructor(module: WebAssembly.Module, o?: { stores?: Record<number, ShimStore>; peers?: Peers });
|
|
40
|
+
stores: Record<number, ShimStore>;
|
|
41
|
+
stats: { compiled: number; instances: number; hostCalls: number };
|
|
42
|
+
x: Record<string, (...a: number[]) => number> & { memory: WebAssembly.Memory };
|
|
43
|
+
push(bytes: Uint8Array | string): [number, number];
|
|
44
|
+
release(p: [number, number]): void;
|
|
45
|
+
result(n: number): Uint8Array;
|
|
46
|
+
check(r: number): number;
|
|
47
|
+
open(id: number): void;
|
|
48
|
+
modulesList(): Uint8Array;
|
|
49
|
+
putBlock(cid: Uint8Array, bytes: Uint8Array): void;
|
|
50
|
+
getBlock(cid: Uint8Array): Uint8Array | undefined;
|
|
51
|
+
call(frame: Uint8Array): Uint8Array;
|
|
52
|
+
admit(frame: Uint8Array): Uint8Array;
|
|
53
|
+
start(): Uint8Array;
|
|
54
|
+
drain(): Uint8Array;
|
|
55
|
+
state(): Uint8Array;
|
|
56
|
+
replay(src: number, dst: number): string;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export declare function replayInto(kernel: Kernel, srcId: number, dstId: number): string;
|
|
@@ -0,0 +1,329 @@
|
|
|
1
|
+
// The browser build's shim (issue #35): the JS around zig-out/web/skein-kernel.wasm.
|
|
2
|
+
// Dependency-free and synchronous, so it runs wherever V8 does (a Worker in the
|
|
3
|
+
// browser, node for the tests). It gives the kernel what it imports:
|
|
4
|
+
//
|
|
5
|
+
// skein_engine V8 as the kernel's engine: compile the (fuel-instrumented)
|
|
6
|
+
// program modules it hands over, instantiate them with an
|
|
7
|
+
// import object whose every function calls back into the
|
|
8
|
+
// kernel (skein_host_call), run them, trap → code, read and
|
|
9
|
+
// write their memory and their fuel counter
|
|
10
|
+
// skein_store stores by id: key→bytes + named pointers (MemoryStore here,
|
|
11
|
+
// IdbStore in store.js), with begin/commit/rollback
|
|
12
|
+
// skein_peer the host: request(op, v) answered synchronously (wallet:
|
|
13
|
+
// the signer — a Worker blocks on a SharedArrayBuffer while
|
|
14
|
+
// the page answers: worker.js), notify(op, v) (emit: a
|
|
15
|
+
// message or an event to carry out; say, panic)
|
|
16
|
+
//
|
|
17
|
+
// and wraps the kernel's exports as methods (Kernel below). Values cross as
|
|
18
|
+
// dag-cbor bytes; decoding them is the caller's (the page bundles
|
|
19
|
+
// @ipld/dag-cbor; this file needs nothing).
|
|
20
|
+
|
|
21
|
+
const enc = new TextEncoder();
|
|
22
|
+
const dec = new TextDecoder();
|
|
23
|
+
|
|
24
|
+
/** Thrown through a program's frames when the kernel aborts it (exit, a park, a fatal host error). */
|
|
25
|
+
class Abort extends Error {}
|
|
26
|
+
/** The kernel itself failed inside a host call: rethrown out of `run`, never reported as the program's trap. */
|
|
27
|
+
class KernelFault extends Error {
|
|
28
|
+
constructor(cause) { super(`kernel fault: ${cause?.message ?? cause}`); this.cause = cause; }
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const key = (b) => { let s = ""; for (let i = 0; i < b.length; i++) s += String.fromCharCode(b[i]); return s; };
|
|
32
|
+
const unkey = (s) => { const b = new Uint8Array(s.length); for (let i = 0; i < s.length; i++) b[i] = s.charCodeAt(i); return b; };
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* A store in memory: blocks by CID, named pointers, and a transaction buffer
|
|
36
|
+
* (reads see it; commit applies it, rollback drops it). `onCommit(batch)` is
|
|
37
|
+
* told every applied batch in order — IdbStore makes it durable there.
|
|
38
|
+
*/
|
|
39
|
+
export class MemoryStore {
|
|
40
|
+
constructor({ readOnly = false } = {}) {
|
|
41
|
+
this.blocks = new Map();
|
|
42
|
+
this.pointers = new Map();
|
|
43
|
+
this.readOnly = readOnly;
|
|
44
|
+
this.tx = null;
|
|
45
|
+
}
|
|
46
|
+
/** Every block (cid bytes, bytes) and pointer, e.g. to write the store out. */
|
|
47
|
+
*entries() { for (const [k, v] of this.blocks) yield [unkey(k), v]; }
|
|
48
|
+
pointer(name) { return this.pointers.get(name); }
|
|
49
|
+
getBlock(cid) {
|
|
50
|
+
const k = key(cid);
|
|
51
|
+
if (this.tx?.blocks.has(k)) return this.tx.blocks.get(k);
|
|
52
|
+
return this.blocks.get(k);
|
|
53
|
+
}
|
|
54
|
+
hasBlock(cid) { return this.getBlock(cid) !== undefined; }
|
|
55
|
+
putBlock(cid, bytes) {
|
|
56
|
+
if (this.readOnly) return false;
|
|
57
|
+
const k = key(cid);
|
|
58
|
+
if (this.hasBlock(cid)) return true;
|
|
59
|
+
if (this.tx) this.tx.blocks.set(k, bytes);
|
|
60
|
+
else this.apply({ blocks: new Map([[k, bytes]]), pointers: new Map() });
|
|
61
|
+
return true;
|
|
62
|
+
}
|
|
63
|
+
getPointer(name) { return this.tx?.pointers.has(name) ? this.tx.pointers.get(name) : this.pointers.get(name); }
|
|
64
|
+
setPointer(name, cid) {
|
|
65
|
+
if (this.readOnly) return false;
|
|
66
|
+
if (this.tx) this.tx.pointers.set(name, cid);
|
|
67
|
+
else this.apply({ blocks: new Map(), pointers: new Map([[name, cid]]) });
|
|
68
|
+
return true;
|
|
69
|
+
}
|
|
70
|
+
begin() { this.tx = { blocks: new Map(), pointers: new Map() }; }
|
|
71
|
+
commit() { const t = this.tx; this.tx = null; if (t) this.apply(t); return true; }
|
|
72
|
+
rollback() { this.tx = null; }
|
|
73
|
+
apply(batch) {
|
|
74
|
+
for (const [k, v] of batch.blocks) this.blocks.set(k, v);
|
|
75
|
+
for (const [k, v] of batch.pointers) this.pointers.set(k, v);
|
|
76
|
+
this.onCommit?.(batch);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/** A codec of a binary CID (v1: version varint, codec varint), e.g. 0x55 raw. */
|
|
81
|
+
export function codecOf(cid) {
|
|
82
|
+
let i = 0;
|
|
83
|
+
const varint = () => { let x = 0, s = 0; for (;;) { const b = cid[i++]; x += (b & 0x7f) * 2 ** s; if (!(b & 0x80)) return x; s += 7; } };
|
|
84
|
+
varint();
|
|
85
|
+
return varint();
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* The kernel: one wasm instance of skein-kernel.wasm, with its stores (by id)
|
|
90
|
+
* and its host (`peers`: request(op, bytes) → {ok: bytes} | {error: string},
|
|
91
|
+
* notify(op, bytes)). One runtime per instance (skein_open); replay uses two stores.
|
|
92
|
+
*/
|
|
93
|
+
export class Kernel {
|
|
94
|
+
static async load(source, o) {
|
|
95
|
+
const bytes = source instanceof Uint8Array || source instanceof ArrayBuffer ? source : new Uint8Array(await (await fetch(source)).arrayBuffer());
|
|
96
|
+
const module = source instanceof WebAssembly.Module ? source : await WebAssembly.compile(bytes);
|
|
97
|
+
return new Kernel(module, o);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
constructor(module, { stores = {}, peers = {} } = {}) {
|
|
101
|
+
this.stores = stores;
|
|
102
|
+
this.peers = peers;
|
|
103
|
+
this.modules = [];
|
|
104
|
+
this.instances = new Map();
|
|
105
|
+
this.nextInstance = 1;
|
|
106
|
+
this.engineError = "";
|
|
107
|
+
this.held = new Uint8Array();
|
|
108
|
+
this.stats = { compiled: 0, instances: 0, hostCalls: 0 };
|
|
109
|
+
this.instance = new WebAssembly.Instance(module, this.imports());
|
|
110
|
+
this.x = this.instance.exports;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
// ---------------------------------------------------------------- kernel memory
|
|
114
|
+
|
|
115
|
+
get mem() { return new Uint8Array(this.x.memory.buffer); }
|
|
116
|
+
// A wasm32 address arrives in JS as a signed i32: above 2 GiB it is negative (#83: a store with the
|
|
117
|
+
// shell app's modules takes the kernel past it). `>>> 0` reads every address and length unsigned.
|
|
118
|
+
bytesAt(ptr, len) { return new Uint8Array(this.x.memory.buffer, ptr >>> 0, len >>> 0); }
|
|
119
|
+
str(ptr, len) { return dec.decode(this.bytesAt(ptr, len)); }
|
|
120
|
+
/** Copy bytes into a fresh kernel buffer: [ptr, len]; free with release. */
|
|
121
|
+
push(bytes) {
|
|
122
|
+
const b = typeof bytes === "string" ? enc.encode(bytes) : bytes;
|
|
123
|
+
const p = b.length ? this.x.skein_alloc(b.length) : 0;
|
|
124
|
+
if (b.length && !p) throw new Error("kernel: out of memory");
|
|
125
|
+
if (b.length) this.bytesAt(p, b.length).set(b);
|
|
126
|
+
return [p, b.length];
|
|
127
|
+
}
|
|
128
|
+
release([p, n]) { if (n) this.x.skein_free(p, n); }
|
|
129
|
+
/** The kernel's result buffer, copied out. */
|
|
130
|
+
result(n) { return n < 0 ? new Uint8Array() : this.bytesAt(this.x.skein_result(), n).slice(); }
|
|
131
|
+
hold(b) { this.held = b; return b.length; }
|
|
132
|
+
|
|
133
|
+
imports() {
|
|
134
|
+
const k = this;
|
|
135
|
+
const store = (id) => { const s = k.stores[id]; if (!s) throw new Error(`kernel: no store ${id}`); return s; };
|
|
136
|
+
const guest = (h) => { const g = k.instances.get(h); if (!g) throw new Error(`kernel: no instance ${h}`); return g; };
|
|
137
|
+
return {
|
|
138
|
+
skein_store: {
|
|
139
|
+
get(id, c, n) { const b = store(id).getBlock(k.bytesAt(c, n)); if (b === undefined) return -1; return k.hold(b); },
|
|
140
|
+
take(p) { k.bytesAt(p, k.held.length).set(k.held); },
|
|
141
|
+
has(id, c, n) { return store(id).hasBlock(k.bytesAt(c, n)) ? 1 : 0; },
|
|
142
|
+
put(id, c, cn, b, bn) { return store(id).putBlock(k.bytesAt(c, cn).slice(), k.bytesAt(b, bn).slice()) ? 0 : -1; },
|
|
143
|
+
begin(id) { store(id).begin(); },
|
|
144
|
+
commit(id) { return store(id).commit() ? 0 : -1; },
|
|
145
|
+
rollback(id) { store(id).rollback(); },
|
|
146
|
+
pointer_get(id, p, n) { const c = store(id).getPointer(k.str(p, n)); if (c === undefined) return -1; return k.hold(c); },
|
|
147
|
+
pointer_set(id, p, n, c, cn) { return store(id).setPointer(k.str(p, n), k.bytesAt(c, cn).slice()) ? 0 : -1; },
|
|
148
|
+
},
|
|
149
|
+
skein_engine: {
|
|
150
|
+
compile(p, n, d, dn) { return k.compile(k.bytesAt(p, n), k.bytesAt(d, dn).slice()); },
|
|
151
|
+
instantiate(h) { return k.instantiate(h); },
|
|
152
|
+
run(inst, hasStart, e, en) { return k.run(inst, hasStart !== 0, k.str(e, en)); },
|
|
153
|
+
release(inst) { k.instances.delete(inst); },
|
|
154
|
+
fuel_get(inst) { return guest(inst).exports.__skein_fuel.value; },
|
|
155
|
+
fuel_set(inst, v) { guest(inst).exports.__skein_fuel.value = v; },
|
|
156
|
+
mem_read(inst, at, len, p) { k.bytesAt(p, len).set(new Uint8Array(guest(inst).exports.memory.buffer, at >>> 0, len >>> 0)); },
|
|
157
|
+
mem_write(inst, at, len, p) { new Uint8Array(guest(inst).exports.memory.buffer, at >>> 0, len >>> 0).set(k.bytesAt(p, len)); },
|
|
158
|
+
error_len() { return enc.encode(k.engineError).length; },
|
|
159
|
+
error_take(p) { const b = enc.encode(k.engineError); k.bytesAt(p, b.length).set(b); },
|
|
160
|
+
},
|
|
161
|
+
skein_peer: {
|
|
162
|
+
request(o, on, v, vn) {
|
|
163
|
+
const op = k.str(o, on);
|
|
164
|
+
let r;
|
|
165
|
+
try { r = k.peers.request ? k.peers.request(op, k.bytesAt(v, vn).slice()) : { error: `this host answers no ${op}` }; } catch (e) { r = { error: e?.message ?? String(e) }; }
|
|
166
|
+
if (r.ok) return k.hold(r.ok);
|
|
167
|
+
return -k.hold(enc.encode(String(r.error ?? "no answer"))) - 1;
|
|
168
|
+
},
|
|
169
|
+
notify(o, on, v, vn) {
|
|
170
|
+
const op = k.str(o, on);
|
|
171
|
+
const b = k.bytesAt(v, vn).slice();
|
|
172
|
+
try { k.peers.notify?.(op, op === "say" || op === "panic" ? dec.decode(b) : b); } catch { /* the host's problem */ }
|
|
173
|
+
},
|
|
174
|
+
take(p) { k.bytesAt(p, k.held.length).set(k.held); },
|
|
175
|
+
},
|
|
176
|
+
};
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
// ---------------------------------------------------------------- the engine: programs on V8
|
|
180
|
+
|
|
181
|
+
compile(bytes, desc) {
|
|
182
|
+
try {
|
|
183
|
+
const module = new WebAssembly.Module(bytes);
|
|
184
|
+
const kinds = [];
|
|
185
|
+
for (let i = 0; i < desc.length; i += 5) kinds.push({ result: desc[i], first: desc[i + 1] | (desc[i + 2] << 8) | (desc[i + 3] << 16) | (desc[i + 4] << 24) });
|
|
186
|
+
this.modules.push({ module, kinds, names: WebAssembly.Module.imports(module).filter((x) => x.kind === "function") });
|
|
187
|
+
this.stats.compiled++;
|
|
188
|
+
return this.modules.length - 1;
|
|
189
|
+
} catch (e) {
|
|
190
|
+
this.engineError = e?.message ?? String(e);
|
|
191
|
+
return -1;
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
instantiate(h) {
|
|
196
|
+
const m = this.modules[h];
|
|
197
|
+
const id = this.nextInstance++;
|
|
198
|
+
const imports = {};
|
|
199
|
+
const fns = [];
|
|
200
|
+
const k = this;
|
|
201
|
+
m.names.forEach((imp, i) => {
|
|
202
|
+
const kind = m.kinds[i] ?? { result: 0, first: i };
|
|
203
|
+
if (kind.first !== i) { (imports[imp.module] ??= {})[imp.name] = fns[kind.first]; return; }
|
|
204
|
+
const index = i, result = kind.result;
|
|
205
|
+
const f = function (...args) {
|
|
206
|
+
const x = k.x;
|
|
207
|
+
const a = new BigInt64Array(x.memory.buffer, x.skein_args() >>> 0, 16);
|
|
208
|
+
const n = Math.min(args.length, 16);
|
|
209
|
+
for (let j = 0; j < n; j++) a[j] = typeof args[j] === "bigint" ? args[j] : BigInt(Math.trunc(args[j]));
|
|
210
|
+
const g = k.instances.get(id);
|
|
211
|
+
const memLen = g?.exports.memory ? g.exports.memory.buffer.byteLength : 0;
|
|
212
|
+
let r;
|
|
213
|
+
k.stats.hostCalls++;
|
|
214
|
+
try { r = x.skein_host_call(id, index, n, memLen); } catch (e) { throw new KernelFault(e); }
|
|
215
|
+
if (r !== 0) throw new Abort("skein: abort");
|
|
216
|
+
const v = new BigInt64Array(x.memory.buffer, x.skein_args() >>> 0, 1)[0];
|
|
217
|
+
return result === 2 ? v : result === 1 ? Number(BigInt.asIntN(32, v)) : undefined;
|
|
218
|
+
};
|
|
219
|
+
fns[i] = f;
|
|
220
|
+
(imports[imp.module] ??= {})[imp.name] = f;
|
|
221
|
+
});
|
|
222
|
+
try {
|
|
223
|
+
const inst = new WebAssembly.Instance(m.module, imports);
|
|
224
|
+
this.instances.set(id, inst);
|
|
225
|
+
this.stats.instances++;
|
|
226
|
+
return id;
|
|
227
|
+
} catch (e) {
|
|
228
|
+
this.engineError = e?.message ?? String(e);
|
|
229
|
+
return -1;
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
run(id, hasStart, entry) {
|
|
234
|
+
const inst = this.instances.get(id);
|
|
235
|
+
try {
|
|
236
|
+
if (hasStart) inst.exports.__skein_start();
|
|
237
|
+
const f = inst.exports[entry];
|
|
238
|
+
if (typeof f !== "function") { this.engineError = `no ${entry}`; return -2; }
|
|
239
|
+
f();
|
|
240
|
+
return 0;
|
|
241
|
+
} catch (e) {
|
|
242
|
+
if (e instanceof KernelFault) throw e;
|
|
243
|
+
if (e instanceof Abort) return 1;
|
|
244
|
+
if (inst.exports.__skein_oog?.value) return 3;
|
|
245
|
+
if (e instanceof WebAssembly.RuntimeError || e instanceof RangeError) { this.engineError = e.message; return 2; }
|
|
246
|
+
this.engineError = e?.message ?? String(e);
|
|
247
|
+
return -1;
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
// ---------------------------------------------------------------- the kernel's exports
|
|
252
|
+
|
|
253
|
+
/** Open store `id` and a runtime over it. */
|
|
254
|
+
open(id) { this.check(this.x.skein_open(id)); }
|
|
255
|
+
/** A call's return value: a failure (< 0) throws with the kernel's message. */
|
|
256
|
+
check(r) { if (r < 0) throw new Error(`kernel: ${dec.decode(this.result(-r - 1))}`); return r; }
|
|
257
|
+
/** The pinned modules and files: dag-cbor [{name, file, cid}]. */
|
|
258
|
+
modulesList() { return this.result(this.x.skein_modules()); }
|
|
259
|
+
putBlock(cid, bytes) {
|
|
260
|
+
const c = this.push(cid), b = this.push(bytes);
|
|
261
|
+
try { this.check(this.x.skein_put_block(c[0], c[1], b[0], b[1])); } finally { this.release(c); this.release(b); }
|
|
262
|
+
}
|
|
263
|
+
getBlock(cid) {
|
|
264
|
+
const c = this.push(cid);
|
|
265
|
+
try { const n = this.x.skein_get_block(c[0], c[1]); return n === -1 ? undefined : this.result(this.check(n)); } finally { this.release(c); }
|
|
266
|
+
}
|
|
267
|
+
/**
|
|
268
|
+
* A serve op as a frame {op, v} (dag-cbor) → the reply frame (dag-cbor {ok} | {error, rejected}).
|
|
269
|
+
* Among them the kernel's `call` (#40): {op: "call", v: {program, fn, arg, caller?, now?}} → ok {ok, result | error, fuel}.
|
|
270
|
+
*/
|
|
271
|
+
call(frame) { const f = this.push(frame); try { return this.result(this.x.skein_call(f[0], f[1])); } finally { this.release(f); } }
|
|
272
|
+
/** serve's admit ({entry, envelope?, body?}, dag-cbor) → the reply frame; not processed until drain. */
|
|
273
|
+
admit(frame) { const f = this.push(frame); try { return this.result(this.x.skein_admit(f[0], f[1])); } finally { this.release(f); } }
|
|
274
|
+
start() { return this.result(this.x.skein_start()); }
|
|
275
|
+
drain() { return this.result(this.x.skein_drain()); }
|
|
276
|
+
state() { return this.result(this.x.skein_state()); }
|
|
277
|
+
/** `skein-kernel replay`: store `src`'s log into store `dst` (its raw blocks put there first: replayInto). The JSON report. */
|
|
278
|
+
replay(src, dst) {
|
|
279
|
+
return dec.decode(this.result(this.check(this.x.skein_replay(src, dst))));
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
/**
|
|
284
|
+
* replay.zig's order: the destination gets every raw block of the source
|
|
285
|
+
* (the modules it ran), then the kernel copies the log and runs it.
|
|
286
|
+
*/
|
|
287
|
+
export function replayInto(kernel, srcId, dstId) {
|
|
288
|
+
const src = kernel.stores[srcId], dst = kernel.stores[dstId];
|
|
289
|
+
for (const [cid, bytes] of src.entries()) if (codecOf(cid) === 0x55) dst.putBlock(cid, bytes);
|
|
290
|
+
return kernel.replay(srcId, dstId);
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
/** Blocks as one binary stream: [u32 cid length][cid][u32 length][bytes]…, then the pointers as [u32 name length][name][u32 cid length][cid]… after a zero u32. */
|
|
294
|
+
export function readBundle(buf, store) {
|
|
295
|
+
const d = new DataView(buf.buffer, buf.byteOffset, buf.byteLength);
|
|
296
|
+
let i = 0;
|
|
297
|
+
const u32 = () => { const v = d.getUint32(i, true); i += 4; return v; };
|
|
298
|
+
for (;;) {
|
|
299
|
+
const cn = u32();
|
|
300
|
+
if (cn === 0) break;
|
|
301
|
+
const cid = buf.subarray(i, i + cn); i += cn;
|
|
302
|
+
const bn = u32();
|
|
303
|
+
const bytes = buf.subarray(i, i + bn); i += bn;
|
|
304
|
+
store.blocks.set(key(cid), bytes.slice());
|
|
305
|
+
}
|
|
306
|
+
while (i < buf.length) {
|
|
307
|
+
const nn = u32();
|
|
308
|
+
const name = dec.decode(buf.subarray(i, i + nn)); i += nn;
|
|
309
|
+
const cn = u32();
|
|
310
|
+
store.pointers.set(name, buf.slice(i, i + cn)); i += cn;
|
|
311
|
+
}
|
|
312
|
+
return store;
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
export function writeBundle(store) {
|
|
316
|
+
const parts = [];
|
|
317
|
+
let size = 4;
|
|
318
|
+
for (const [cid, bytes] of store.entries()) { parts.push(cid, bytes); size += 8 + cid.length + bytes.length; }
|
|
319
|
+
const ptrs = [...store.pointers].map(([n, c]) => [enc.encode(n), c]);
|
|
320
|
+
for (const [n, c] of ptrs) size += 8 + n.length + c.length;
|
|
321
|
+
const out = new Uint8Array(size);
|
|
322
|
+
const d = new DataView(out.buffer);
|
|
323
|
+
let i = 0;
|
|
324
|
+
const put = (b) => { d.setUint32(i, b.length, true); i += 4; out.set(b, i); i += b.length; };
|
|
325
|
+
for (let j = 0; j < parts.length; j += 2) { put(parts[j]); put(parts[j + 1]); }
|
|
326
|
+
d.setUint32(i, 0, true); i += 4;
|
|
327
|
+
for (const [n, c] of ptrs) { put(n); put(c); }
|
|
328
|
+
return out;
|
|
329
|
+
}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
// The instance store over IndexedDB (issue #35): the kernel's key→bytes map
|
|
2
|
+
// and its `state` pointer (issue #30), one database per instance with two
|
|
3
|
+
// object stores, `blocks` (CID bytes → bytes) and `pointers` (name → CID
|
|
4
|
+
// bytes). The kernel reads synchronously, so the whole store is loaded into
|
|
5
|
+
// memory at open (a MemoryStore) and every committed batch is written behind,
|
|
6
|
+
// one readwrite transaction per batch, created at the commit — IndexedDB runs
|
|
7
|
+
// transactions over the same stores in creation order, so what is durable is
|
|
8
|
+
// always a prefix of what was committed, and the pointer never names a state
|
|
9
|
+
// whose blocks are not written. `flushed()` resolves once everything committed
|
|
10
|
+
// so far is durable: the host acknowledges an admission (to the messagebox it
|
|
11
|
+
// came from) only after that.
|
|
12
|
+
|
|
13
|
+
import { MemoryStore } from "./kernel.js";
|
|
14
|
+
|
|
15
|
+
const req = (r) => new Promise((resolve, reject) => { r.onsuccess = () => resolve(r.result); r.onerror = () => reject(r.error); });
|
|
16
|
+
|
|
17
|
+
export class IdbStore extends MemoryStore {
|
|
18
|
+
/** Open (or create) the database `name` and load it. */
|
|
19
|
+
static async open(name, { idb = globalThis.indexedDB } = {}) {
|
|
20
|
+
const open = idb.open(name, 1);
|
|
21
|
+
open.onupgradeneeded = () => {
|
|
22
|
+
const db = open.result;
|
|
23
|
+
if (!db.objectStoreNames.contains("blocks")) db.createObjectStore("blocks");
|
|
24
|
+
if (!db.objectStoreNames.contains("pointers")) db.createObjectStore("pointers");
|
|
25
|
+
};
|
|
26
|
+
const db = await req(open);
|
|
27
|
+
const s = new IdbStore();
|
|
28
|
+
s.db = db;
|
|
29
|
+
s.name = name;
|
|
30
|
+
const t = db.transaction(["blocks", "pointers"], "readonly");
|
|
31
|
+
const [keys, values, pkeys, pvalues] = await Promise.all([
|
|
32
|
+
req(t.objectStore("blocks").getAllKeys()), req(t.objectStore("blocks").getAll()),
|
|
33
|
+
req(t.objectStore("pointers").getAllKeys()), req(t.objectStore("pointers").getAll()),
|
|
34
|
+
]);
|
|
35
|
+
for (let i = 0; i < keys.length; i++) s.blocks.set(keyOf(new Uint8Array(keys[i])), new Uint8Array(values[i]));
|
|
36
|
+
for (let i = 0; i < pkeys.length; i++) s.pointers.set(pkeys[i], new Uint8Array(pvalues[i]));
|
|
37
|
+
s.last = Promise.resolve();
|
|
38
|
+
s.failed = null;
|
|
39
|
+
s.onCommit = (batch) => s.writeBehind(batch);
|
|
40
|
+
return s;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
writeBehind(batch) {
|
|
44
|
+
const t = this.db.transaction(["blocks", "pointers"], "readwrite");
|
|
45
|
+
const blocks = t.objectStore("blocks"), pointers = t.objectStore("pointers");
|
|
46
|
+
for (const [k, v] of batch.blocks) blocks.put(v, unkeyOf(k));
|
|
47
|
+
for (const [k, v] of batch.pointers) pointers.put(v, k);
|
|
48
|
+
const done = new Promise((resolve, reject) => {
|
|
49
|
+
t.oncomplete = () => resolve();
|
|
50
|
+
t.onerror = t.onabort = () => { this.failed ??= t.error ?? new Error("IndexedDB write failed"); reject(this.failed); };
|
|
51
|
+
});
|
|
52
|
+
done.catch(() => {});
|
|
53
|
+
this.last = this.last.then(() => done);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/** Everything committed so far is durable (or the first write failure). */
|
|
57
|
+
async flushed() { await this.last; if (this.failed) throw this.failed; }
|
|
58
|
+
|
|
59
|
+
close() { this.db.close(); }
|
|
60
|
+
|
|
61
|
+
/** Delete the database `name`. */
|
|
62
|
+
static async remove(name, { idb = globalThis.indexedDB } = {}) { await req(idb.deleteDatabase(name)); }
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// MemoryStore's map key: the CID's bytes as a latin1 string (kernel.js).
|
|
66
|
+
function keyOf(b) { let s = ""; for (let i = 0; i < b.length; i++) s += String.fromCharCode(b[i]); return s; }
|
|
67
|
+
function unkeyOf(s) { const b = new Uint8Array(s.length); for (let i = 0; i < s.length; i++) b[i] = s.charCodeAt(i); return b; }
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
// The kernel's thread in the browser (issue #35): a module Worker holding the
|
|
2
|
+
// wasm kernel (kernel.js) and its stores. A Worker, because the kernel runs
|
|
3
|
+
// programs synchronously — `new WebAssembly.Module` of a program, and its
|
|
4
|
+
// host calls — which a page's main thread may not do for large modules, and
|
|
5
|
+
// because the kernel's requests to its host (wallet: the signer) must be
|
|
6
|
+
// answered while it waits: it posts the request and blocks on a
|
|
7
|
+
// SharedArrayBuffer (Atomics.wait) until the page has written the answer
|
|
8
|
+
// (client.js). That needs a cross-origin isolated page (COOP/COEP headers).
|
|
9
|
+
//
|
|
10
|
+
// Messages in: {id, method, args} → {id, result} | {id, error}.
|
|
11
|
+
// Messages out: {notify: op, v} (emit, say, panic) and
|
|
12
|
+
// {request: op, v} (answered through the shared buffer).
|
|
13
|
+
|
|
14
|
+
import { codecOf, Kernel, MemoryStore, readBundle, replayInto, writeBundle } from "./kernel.js";
|
|
15
|
+
import { IdbStore } from "./store.js";
|
|
16
|
+
|
|
17
|
+
const dec = new TextDecoder();
|
|
18
|
+
let kernel = null;
|
|
19
|
+
const stores = {};
|
|
20
|
+
let ctrl = null; // Int32Array over the control buffer: [0] ready flag, [1] status (0 ok, 1 error), [2] length
|
|
21
|
+
let data = null; // the answer's bytes (a growable SharedArrayBuffer)
|
|
22
|
+
|
|
23
|
+
function request(op, v) {
|
|
24
|
+
Atomics.store(ctrl, 0, 0);
|
|
25
|
+
postMessage({ request: op, v });
|
|
26
|
+
Atomics.wait(ctrl, 0, 0);
|
|
27
|
+
const bytes = new Uint8Array(data, 0, ctrl[2]).slice();
|
|
28
|
+
return ctrl[1] === 0 ? { ok: bytes } : { error: dec.decode(bytes) };
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const flushAll = async () => { for (const s of Object.values(stores)) await s.flushed?.(); };
|
|
32
|
+
|
|
33
|
+
const methods = {
|
|
34
|
+
async init(wasm, control, answer) {
|
|
35
|
+
ctrl = new Int32Array(control);
|
|
36
|
+
data = answer;
|
|
37
|
+
const bytes = typeof wasm === "string" ? new Uint8Array(await (await fetch(wasm)).arrayBuffer()) : wasm;
|
|
38
|
+
kernel = new Kernel(await WebAssembly.compile(bytes), { stores, peers: { request, notify: (op, v) => postMessage({ notify: op, v }) } });
|
|
39
|
+
return true;
|
|
40
|
+
},
|
|
41
|
+
/** Store `id` over the IndexedDB database `name` (loaded). */
|
|
42
|
+
async openIdb(id, name) { stores[id] = await IdbStore.open(name); return stores[id].blocks.size; },
|
|
43
|
+
/** Store `id` in memory, from a bundle (kernel.js readBundle) or empty. */
|
|
44
|
+
openMemory(id, bundle, readOnly = false) { stores[id] = new MemoryStore({ readOnly }); if (bundle) readBundle(new Uint8Array(bundle), stores[id]); return stores[id].blocks.size; },
|
|
45
|
+
async removeIdb(name) { await IdbStore.remove(name); return true; },
|
|
46
|
+
closeStore(id) { stores[id]?.close?.(); delete stores[id]; return true; },
|
|
47
|
+
/** The store as a bundle (to write it out: equiv); `records` leaves out the raw blocks (the modules, which a replay installs). */
|
|
48
|
+
async bundle(id, records = false) {
|
|
49
|
+
await stores[id].flushed?.();
|
|
50
|
+
if (!records) return writeBundle(stores[id]);
|
|
51
|
+
const s = new MemoryStore();
|
|
52
|
+
for (const [cid, bytes] of stores[id].entries()) if (codecOf(cid) !== 0x55) s.blocks.set(String.fromCharCode(...cid), bytes);
|
|
53
|
+
for (const [n, c] of stores[id].pointers) s.pointers.set(n, c);
|
|
54
|
+
return writeBundle(s);
|
|
55
|
+
},
|
|
56
|
+
open(id) { kernel.open(id); return true; },
|
|
57
|
+
modules() { return kernel.modulesList(); },
|
|
58
|
+
hasBlock(id, cid) { return stores[id].hasBlock(cid); },
|
|
59
|
+
putBlock(cid, bytes) { kernel.putBlock(cid, bytes); return true; },
|
|
60
|
+
getBlock(cid) { return kernel.getBlock(cid); },
|
|
61
|
+
call(frame) { return kernel.call(frame); },
|
|
62
|
+
/** The one call in: admitted and durable before the answer (the host then acknowledges its source). */
|
|
63
|
+
async admit(frame) { const r = kernel.admit(frame); await flushAll(); return r; },
|
|
64
|
+
async start() { const r = kernel.start(); await flushAll(); return r; },
|
|
65
|
+
async drain() { const r = kernel.drain(); await flushAll(); return r; },
|
|
66
|
+
state() { return kernel.state(); },
|
|
67
|
+
async replay(src, dst) { const t = performance.now(); const out = replayInto(kernel, src, dst); await flushAll(); return { report: out, ms: performance.now() - t, stats: kernel.stats }; },
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
onmessage = async (e) => {
|
|
71
|
+
const { id, method, args } = e.data;
|
|
72
|
+
try {
|
|
73
|
+
const f = methods[method];
|
|
74
|
+
if (!f) throw new Error(`no method ${method}`);
|
|
75
|
+
postMessage({ id, result: await f(...(args ?? [])) });
|
|
76
|
+
} catch (err) {
|
|
77
|
+
postMessage({ id, error: err?.stack ?? String(err) });
|
|
78
|
+
}
|
|
79
|
+
};
|
|
Binary file
|