@panyam/tsappkit 0.6.3 → 0.6.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +202 -0
- package/dist/wasmhost/index.d.mts +76 -2
- package/dist/wasmhost/index.d.ts +76 -2
- package/dist/wasmhost/index.js +237 -18
- package/dist/wasmhost/index.js.map +1 -1
- package/dist/wasmhost/index.mjs +236 -19
- package/dist/wasmhost/index.mjs.map +1 -1
- package/dist/wasmhost/worker.js +82 -1
- package/dist/wasmhost/worker.js.map +1 -1
- package/package.json +2 -2
- package/src/wasmhost/cache.ts +111 -0
- package/src/wasmhost/client.ts +171 -23
- package/src/wasmhost/index.ts +3 -1
- package/src/wasmhost/lane.ts +171 -0
- package/src/wasmhost/protocol.ts +15 -1
- package/src/wasmhost/worker.ts +22 -3
package/dist/wasmhost/index.js
CHANGED
|
@@ -11,6 +11,8 @@ var Channel = class {
|
|
|
11
11
|
const m = ev.data;
|
|
12
12
|
if ("exited" in m) {
|
|
13
13
|
this.fail(new Error(`wasm host: ${m.exited}`));
|
|
14
|
+
} else if ("chunk" in m) {
|
|
15
|
+
this.pending.get(m.id)?.onChunk?.(m);
|
|
14
16
|
} else if ("id" in m) {
|
|
15
17
|
this.pending.get(m.id)?.resolve(m);
|
|
16
18
|
this.pending.delete(m.id);
|
|
@@ -18,13 +20,18 @@ var Channel = class {
|
|
|
18
20
|
});
|
|
19
21
|
worker.addEventListener("error", (ev) => this.fail(new Error(`wasm host worker: ${ev.message}`)));
|
|
20
22
|
}
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
23
|
+
/** Sends req and returns its id with the final reply; chunks before it go to onChunk. */
|
|
24
|
+
start(req, transfer, onChunk) {
|
|
25
|
+
const id = this.next++;
|
|
26
|
+
if (this.dead) return { id, done: Promise.reject(this.dead) };
|
|
27
|
+
const done = new Promise((resolve, reject) => {
|
|
28
|
+
this.pending.set(id, { resolve, reject, onChunk });
|
|
26
29
|
this.worker.postMessage({ ...req, id }, transfer);
|
|
27
30
|
});
|
|
31
|
+
return { id, done };
|
|
32
|
+
}
|
|
33
|
+
call(req, transfer) {
|
|
34
|
+
return this.start(req, transfer).done;
|
|
28
35
|
}
|
|
29
36
|
fail(err) {
|
|
30
37
|
this.dead = err;
|
|
@@ -33,6 +40,10 @@ var Channel = class {
|
|
|
33
40
|
}
|
|
34
41
|
};
|
|
35
42
|
var channels = /* @__PURE__ */ new WeakMap();
|
|
43
|
+
function endWorker(worker, err) {
|
|
44
|
+
channels.get(worker)?.fail(err);
|
|
45
|
+
worker.terminate();
|
|
46
|
+
}
|
|
36
47
|
function channel(worker) {
|
|
37
48
|
let c = channels.get(worker);
|
|
38
49
|
if (!c) {
|
|
@@ -54,13 +65,22 @@ function startWorker(opts) {
|
|
|
54
65
|
const m = ev.data;
|
|
55
66
|
if (!("ready" in m)) return;
|
|
56
67
|
worker.removeEventListener("message", onMessage);
|
|
57
|
-
if (m.ready)
|
|
58
|
-
|
|
68
|
+
if (m.ready) {
|
|
69
|
+
resolve(worker);
|
|
70
|
+
return;
|
|
71
|
+
}
|
|
72
|
+
worker.terminate();
|
|
73
|
+
reject(new Error(`wasm host failed to load: ${m.error}`));
|
|
59
74
|
};
|
|
60
75
|
worker.addEventListener("message", onMessage);
|
|
61
|
-
worker.addEventListener(
|
|
62
|
-
|
|
63
|
-
|
|
76
|
+
worker.addEventListener(
|
|
77
|
+
"error",
|
|
78
|
+
(ev) => {
|
|
79
|
+
worker.terminate();
|
|
80
|
+
reject(new Error(`wasm host worker: ${ev.message}`));
|
|
81
|
+
},
|
|
82
|
+
{ once: true }
|
|
83
|
+
);
|
|
64
84
|
});
|
|
65
85
|
}
|
|
66
86
|
var NULL_BODY = /* @__PURE__ */ new Set([101, 103, 204, 205, 304]);
|
|
@@ -68,21 +88,108 @@ function workerFetch(worker) {
|
|
|
68
88
|
const c = channel(worker);
|
|
69
89
|
return async (input, init) => {
|
|
70
90
|
const req = new Request(input, init);
|
|
91
|
+
const signal = req.signal;
|
|
92
|
+
if (signal.aborted) throw abortReason(signal);
|
|
71
93
|
const buf = new Uint8Array(await req.arrayBuffer());
|
|
72
94
|
const body = buf.byteLength ? buf : null;
|
|
73
95
|
const headers = {};
|
|
74
96
|
req.headers.forEach((v, k) => headers[k] = v);
|
|
75
97
|
const u = new URL(req.url);
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
98
|
+
return new Promise((resolve, reject) => {
|
|
99
|
+
let stream;
|
|
100
|
+
let settled = false;
|
|
101
|
+
const fail = (err) => {
|
|
102
|
+
if (settled) return;
|
|
103
|
+
settled = true;
|
|
104
|
+
if (stream) stream.error(err);
|
|
105
|
+
else reject(err);
|
|
106
|
+
};
|
|
107
|
+
const { id, done } = c.start(
|
|
108
|
+
{ kind: "http", method: req.method, url: u.pathname + u.search, headers, body },
|
|
109
|
+
body ? [body.buffer] : [],
|
|
110
|
+
(chunk) => {
|
|
111
|
+
if (settled) return;
|
|
112
|
+
if (!stream) {
|
|
113
|
+
const status = chunk.status ?? 200;
|
|
114
|
+
const rs = new ReadableStream({ start: (ctl) => void (stream = ctl) });
|
|
115
|
+
resolve(new Response(NULL_BODY.has(status) ? null : rs, { status, headers: chunk.headers }));
|
|
116
|
+
}
|
|
117
|
+
if (chunk.body.byteLength) stream.enqueue(chunk.body);
|
|
118
|
+
}
|
|
119
|
+
);
|
|
120
|
+
const onAbort = () => {
|
|
121
|
+
void c.call({ kind: "cancel", target: id }, []).catch(() => {
|
|
122
|
+
});
|
|
123
|
+
fail(abortReason(signal));
|
|
124
|
+
};
|
|
125
|
+
signal.addEventListener("abort", onAbort, { once: true });
|
|
126
|
+
done.then(
|
|
127
|
+
(reply) => {
|
|
128
|
+
signal.removeEventListener("abort", onAbort);
|
|
129
|
+
if (!reply.ok) return fail(new Error(reply.error));
|
|
130
|
+
if (settled) return;
|
|
131
|
+
settled = true;
|
|
132
|
+
if (stream) {
|
|
133
|
+
if (reply.body?.byteLength) stream.enqueue(reply.body);
|
|
134
|
+
stream.close();
|
|
135
|
+
return;
|
|
136
|
+
}
|
|
137
|
+
const status = reply.status ?? 200;
|
|
138
|
+
const resBody = NULL_BODY.has(status) ? null : reply.body ?? null;
|
|
139
|
+
resolve(new Response(resBody, { status, headers: reply.headers }));
|
|
140
|
+
},
|
|
141
|
+
(err) => {
|
|
142
|
+
signal.removeEventListener("abort", onAbort);
|
|
143
|
+
fail(err);
|
|
144
|
+
}
|
|
145
|
+
);
|
|
146
|
+
});
|
|
84
147
|
};
|
|
85
148
|
}
|
|
149
|
+
function abortReason(signal) {
|
|
150
|
+
return signal.reason ?? new DOMException("This request was aborted.", "AbortError");
|
|
151
|
+
}
|
|
152
|
+
function oneShotFetch(opts) {
|
|
153
|
+
return async (input, init) => {
|
|
154
|
+
const worker = await startWorker(opts);
|
|
155
|
+
let res;
|
|
156
|
+
try {
|
|
157
|
+
res = await workerFetch(worker)(input, init);
|
|
158
|
+
} catch (err) {
|
|
159
|
+
worker.terminate();
|
|
160
|
+
throw err;
|
|
161
|
+
}
|
|
162
|
+
return untilBodyEnds(res, () => worker.terminate());
|
|
163
|
+
};
|
|
164
|
+
}
|
|
165
|
+
function untilBodyEnds(res, end) {
|
|
166
|
+
if (!res.body) {
|
|
167
|
+
end();
|
|
168
|
+
return res;
|
|
169
|
+
}
|
|
170
|
+
const reader = res.body.getReader();
|
|
171
|
+
const body = new ReadableStream({
|
|
172
|
+
async pull(ctl) {
|
|
173
|
+
try {
|
|
174
|
+
const { done, value } = await reader.read();
|
|
175
|
+
if (done) {
|
|
176
|
+
ctl.close();
|
|
177
|
+
end();
|
|
178
|
+
} else {
|
|
179
|
+
ctl.enqueue(value);
|
|
180
|
+
}
|
|
181
|
+
} catch (err) {
|
|
182
|
+
end();
|
|
183
|
+
ctl.error(err);
|
|
184
|
+
}
|
|
185
|
+
},
|
|
186
|
+
cancel(reason) {
|
|
187
|
+
end();
|
|
188
|
+
return reader.cancel(reason);
|
|
189
|
+
}
|
|
190
|
+
});
|
|
191
|
+
return new Response(body, { status: res.status, statusText: res.statusText, headers: res.headers });
|
|
192
|
+
}
|
|
86
193
|
function mountFiles(worker, name, files) {
|
|
87
194
|
return sendFiles(worker, "mount", name, files);
|
|
88
195
|
}
|
|
@@ -153,10 +260,122 @@ async function readEntry(entry, out) {
|
|
|
153
260
|
}
|
|
154
261
|
}
|
|
155
262
|
|
|
263
|
+
// src/wasmhost/lane.ts
|
|
264
|
+
function startLane(opts, lane = {}) {
|
|
265
|
+
let closed = false;
|
|
266
|
+
let current;
|
|
267
|
+
let failed = false;
|
|
268
|
+
const boot = () => {
|
|
269
|
+
failed = false;
|
|
270
|
+
const p = startWorker(opts).then(async (w) => {
|
|
271
|
+
if (lane.warm) {
|
|
272
|
+
try {
|
|
273
|
+
await lane.warm(workerFetch(w));
|
|
274
|
+
} catch (err) {
|
|
275
|
+
endWorker(w, err instanceof Error ? err : new Error(String(err)));
|
|
276
|
+
throw err;
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
return w;
|
|
280
|
+
});
|
|
281
|
+
p.catch(() => {
|
|
282
|
+
if (current === p) failed = true;
|
|
283
|
+
});
|
|
284
|
+
current = p;
|
|
285
|
+
return p;
|
|
286
|
+
};
|
|
287
|
+
const replace = (old, why) => {
|
|
288
|
+
endWorker(old, why);
|
|
289
|
+
return boot();
|
|
290
|
+
};
|
|
291
|
+
let inFlight = 0;
|
|
292
|
+
const checkMemory = async (w, of) => {
|
|
293
|
+
let bytes;
|
|
294
|
+
try {
|
|
295
|
+
bytes = await workerMemory(w);
|
|
296
|
+
} catch {
|
|
297
|
+
return;
|
|
298
|
+
}
|
|
299
|
+
if (bytes <= lane.maxMemoryBytes || inFlight !== 0 || current !== of || closed) return;
|
|
300
|
+
lane.onRestart?.({ memoryBytes: bytes });
|
|
301
|
+
void replace(w, new Error("wasm host lane: restarted past its memory limit")).catch(() => {
|
|
302
|
+
});
|
|
303
|
+
};
|
|
304
|
+
boot();
|
|
305
|
+
return {
|
|
306
|
+
async fetch(input, init) {
|
|
307
|
+
if (closed) throw new Error("wasm host lane: closed");
|
|
308
|
+
const signal = init?.signal ?? (input instanceof Request ? input.signal : void 0);
|
|
309
|
+
if (signal?.aborted) throw abortReason2(signal);
|
|
310
|
+
if (failed) boot();
|
|
311
|
+
const booting = current;
|
|
312
|
+
const w = await booting;
|
|
313
|
+
if (signal?.aborted) throw abortReason2(signal);
|
|
314
|
+
inFlight++;
|
|
315
|
+
let ended = false;
|
|
316
|
+
const end = () => {
|
|
317
|
+
if (ended) return;
|
|
318
|
+
ended = true;
|
|
319
|
+
inFlight--;
|
|
320
|
+
if (inFlight === 0 && lane.maxMemoryBytes !== void 0 && current === booting) void checkMemory(w, booting);
|
|
321
|
+
};
|
|
322
|
+
const run = workerFetch(w)(input, init);
|
|
323
|
+
if (!signal) {
|
|
324
|
+
return run.then(
|
|
325
|
+
(res) => untilBodyEnds(res, end),
|
|
326
|
+
(err) => {
|
|
327
|
+
end();
|
|
328
|
+
throw err;
|
|
329
|
+
}
|
|
330
|
+
);
|
|
331
|
+
}
|
|
332
|
+
return new Promise((resolve, reject) => {
|
|
333
|
+
const onAbort = () => {
|
|
334
|
+
end();
|
|
335
|
+
reject(abortReason2(signal));
|
|
336
|
+
if (current === booting) void replace(w, new Error("wasm host lane: restarted after an abort")).catch(() => {
|
|
337
|
+
});
|
|
338
|
+
};
|
|
339
|
+
signal.addEventListener("abort", onAbort, { once: true });
|
|
340
|
+
run.then(
|
|
341
|
+
// A streamed body is still coming from the busy worker, so an abort until it ends must
|
|
342
|
+
// still end the worker.
|
|
343
|
+
(res) => resolve(
|
|
344
|
+
untilBodyEnds(res, () => {
|
|
345
|
+
signal.removeEventListener("abort", onAbort);
|
|
346
|
+
end();
|
|
347
|
+
})
|
|
348
|
+
),
|
|
349
|
+
(err) => {
|
|
350
|
+
signal.removeEventListener("abort", onAbort);
|
|
351
|
+
end();
|
|
352
|
+
reject(err);
|
|
353
|
+
}
|
|
354
|
+
);
|
|
355
|
+
});
|
|
356
|
+
},
|
|
357
|
+
worker: () => current,
|
|
358
|
+
async restart() {
|
|
359
|
+
const old = await current.catch(() => void 0);
|
|
360
|
+
await (old ? replace(old, new Error("wasm host lane: restarted")) : boot());
|
|
361
|
+
},
|
|
362
|
+
close() {
|
|
363
|
+
closed = true;
|
|
364
|
+
void current.then((w) => endWorker(w, new Error("wasm host lane: closed")), () => {
|
|
365
|
+
});
|
|
366
|
+
}
|
|
367
|
+
};
|
|
368
|
+
}
|
|
369
|
+
function abortReason2(signal) {
|
|
370
|
+
return signal.reason ?? new DOMException("This request was aborted.", "AbortError");
|
|
371
|
+
}
|
|
372
|
+
|
|
156
373
|
exports.addFiles = addFiles;
|
|
157
374
|
exports.filesFromDrop = filesFromDrop;
|
|
158
375
|
exports.filesFromFileList = filesFromFileList;
|
|
159
376
|
exports.mountFiles = mountFiles;
|
|
377
|
+
exports.oneShotFetch = oneShotFetch;
|
|
378
|
+
exports.startLane = startLane;
|
|
160
379
|
exports.startWorker = startWorker;
|
|
161
380
|
exports.unmountFiles = unmountFiles;
|
|
162
381
|
exports.workerFetch = workerFetch;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/wasmhost/client.ts","../../src/wasmhost/drop.ts"],"names":[],"mappings":"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","file":"index.js","sourcesContent":["// The page's side of a wasm host worker (goapplib issue 32). startWorker loads the app's Go wasm in\n// a Web Worker; workerFetch is a fetch to hand a Connect transport, so the app's generated clients\n// talk to the worker exactly as they would to a server; mountFiles pushes files in before the\n// requests that read them.\nimport type { Files, HostReply, HostRequest, HostStatus } from \"./protocol\";\n\nexport type { Files } from \"./protocol\";\n\n/** Where the worker's pieces are served, and the global name the Go side exports under. */\nexport interface StartWorkerOptions {\n /** The built worker script, @panyam/tsappkit/wasmhost/worker.js, as your bundler serves it. */\n worker: string | URL;\n /** The Go wasm build. */\n wasm: string | URL;\n /** Go's wasm_exec.js, from `$(go env GOROOT)/lib/wasm`, matching the Go that built the wasm. */\n exec: string | URL;\n /** The ns the Go program passed to wasmhost.Serve. */\n ns: string;\n}\n\ntype Unsent = HostRequest extends infer R ? (R extends HostRequest ? Omit<R, \"id\"> : never) : never;\n\nclass Channel {\n private next = 1;\n private pending = new Map<number, { resolve: (r: HostReply) => void; reject: (e: Error) => void }>();\n private dead: Error | null = null;\n\n constructor(private worker: Worker) {\n worker.addEventListener(\"message\", (ev: MessageEvent<HostReply | HostStatus>) => {\n const m = ev.data;\n if (\"exited\" in m) {\n this.fail(new Error(`wasm host: ${m.exited}`));\n } else if (\"id\" in m) {\n this.pending.get(m.id)?.resolve(m);\n this.pending.delete(m.id);\n }\n });\n worker.addEventListener(\"error\", (ev: ErrorEvent) => this.fail(new Error(`wasm host worker: ${ev.message}`)));\n }\n\n call(req: Unsent, transfer: Transferable[]): Promise<HostReply> {\n if (this.dead) return Promise.reject(this.dead);\n return new Promise((resolve, reject) => {\n const id = this.next++;\n this.pending.set(id, { resolve, reject });\n this.worker.postMessage({ ...req, id }, transfer);\n });\n }\n\n private fail(err: Error) {\n this.dead = err;\n for (const p of this.pending.values()) p.reject(err);\n this.pending.clear();\n }\n}\n\nconst channels = new WeakMap<Worker, Channel>();\n\nfunction channel(worker: Worker): Channel {\n let c = channels.get(worker);\n if (!c) {\n c = new Channel(worker);\n channels.set(worker, c);\n }\n return c;\n}\n\n/**\n * Starts the worker and resolves with it once the Go program has installed its exports, or rejects\n * with why it couldn't load. Relative URLs resolve against the page, not the worker script.\n */\nexport function startWorker(opts: StartWorkerOptions): Promise<Worker> {\n const abs = (u: string | URL) => new URL(u, location.href).href;\n const url = new URL(abs(opts.worker));\n url.searchParams.set(\"wasm\", abs(opts.wasm));\n url.searchParams.set(\"exec\", abs(opts.exec));\n url.searchParams.set(\"ns\", opts.ns);\n const worker = new Worker(url);\n channel(worker);\n return new Promise((resolve, reject) => {\n const onMessage = (ev: MessageEvent<HostStatus | HostReply>) => {\n const m = ev.data;\n if (!(\"ready\" in m)) return;\n worker.removeEventListener(\"message\", onMessage);\n if (m.ready) resolve(worker);\n else reject(new Error(`wasm host failed to load: ${m.error}`));\n };\n worker.addEventListener(\"message\", onMessage);\n worker.addEventListener(\"error\", (ev: ErrorEvent) => reject(new Error(`wasm host worker: ${ev.message}`)), {\n once: true,\n });\n });\n}\n\n// Statuses whose Response must have no body.\nconst NULL_BODY = new Set([101, 103, 204, 205, 304]);\n\n/**\n * A fetch that answers in the worker, for `createConnectTransport({ baseUrl, fetch: workerFetch(worker) })`.\n * Only the path and query reach the worker, so baseUrl can be anything absolute, such as\n * location.origin. It rejects if the worker reports an error (no handler, a failed build, a handler\n * panic) and resolves with any HTTP status the handler chose.\n */\nexport function workerFetch(worker: Worker): (input: RequestInfo | URL, init?: RequestInit) => Promise<Response> {\n const c = channel(worker);\n return async (input, init) => {\n const req = new Request(input, init);\n const buf = new Uint8Array(await req.arrayBuffer());\n const body = buf.byteLength ? buf : null;\n const headers: Record<string, string> = {};\n req.headers.forEach((v, k) => (headers[k] = v));\n const u = new URL(req.url);\n const reply = await c.call(\n { kind: \"http\", method: req.method, url: u.pathname + u.search, headers, body },\n body ? [body.buffer] : [],\n );\n if (!reply.ok) throw new Error(reply.error);\n const status = reply.status ?? 200;\n const resBody = NULL_BODY.has(status) ? null : ((reply.body ?? null) as BodyInit | null);\n return new Response(resBody, { status, headers: reply.headers });\n };\n}\n\n/**\n * Replaces the mount `name` with `files`. The buffers are transferred to the worker, not copied, so\n * each Uint8Array is empty here afterwards; copy first anything the page still needs. Resolves once\n * the worker has mounted them (and, for a rebuild-mode host, rebuilt its handler).\n */\nexport function mountFiles(worker: Worker, name: string, files: Files): Promise<void> {\n return sendFiles(worker, \"mount\", name, files);\n}\n\n/**\n * Adds `files` to the mount `name`, keeping the files it already holds; a file at a path the mount\n * already has is replaced, and a missing mount is created. This suits a page that brings files in a\n * piece at a time, since it only sends what's new. The worker checks the whole batch first, so a\n * rejection (a path that is already a directory, say) leaves the mount as it was. Buffers are\n * transferred as with mountFiles.\n */\nexport function addFiles(worker: Worker, name: string, files: Files): Promise<void> {\n return sendFiles(worker, \"add\", name, files);\n}\n\nasync function sendFiles(worker: Worker, kind: \"mount\" | \"add\", name: string, files: Files): Promise<void> {\n const transfer = new Set<ArrayBuffer>();\n for (const b of Object.values(files)) {\n // A tag check rather than instanceof, which fails for a buffer from another realm (an iframe),\n // and a SharedArrayBuffer can't be transferred.\n if (Object.prototype.toString.call(b.buffer) === \"[object ArrayBuffer]\") transfer.add(b.buffer as ArrayBuffer);\n }\n const reply = await channel(worker).call({ kind, name, files }, [...transfer]);\n if (!reply.ok) throw new Error(reply.error);\n}\n\n/**\n * The bytes of linear memory the worker's wasm holds. Wasm memory grows and never shrinks, so this\n * is the peak the engine has needed so far, which is what an app weighs before giving a browser a\n * bigger job.\n */\nexport async function workerMemory(worker: Worker): Promise<number> {\n const reply = await channel(worker).call({ kind: \"stats\" }, []);\n if (!reply.ok) throw new Error(reply.error);\n return reply.memoryBytes ?? 0;\n}\n\n/** Removes the mount `name`. Removing a name that isn't mounted succeeds. */\nexport async function unmountFiles(worker: Worker, name: string): Promise<void> {\n const reply = await channel(worker).call({ kind: \"unmount\", name }, []);\n if (!reply.ok) throw new Error(reply.error);\n}\n","// Turning what a user drops or picks into the {path: bytes} map mountFiles takes. The bytes are\n// read here, before any request, because the Go side can't wait on File.arrayBuffer() mid-request.\nimport type { Files } from \"./protocol\";\n\n// The parts of the File and Directory Entries API used here. Chromium, Firefox and Safari all ship\n// it under the webkit prefix.\ninterface Entry {\n isFile: boolean;\n isDirectory: boolean;\n fullPath: string;\n}\ninterface FileEntry extends Entry {\n file(ok: (f: File) => void, err: (e: unknown) => void): void;\n}\ninterface DirectoryEntry extends Entry {\n createReader(): { readEntries(ok: (es: Entry[]) => void, err: (e: unknown) => void): void };\n}\n\n/**\n * Reads a drop into files keyed by path. A dropped folder keeps its own name as the first path\n * element (\"design/board.edn\"), and dropped loose files sit at the top. Entries the browser can't\n * read as files or folders are skipped.\n */\nexport async function filesFromDrop(dt: DataTransfer): Promise<Files> {\n const entries: Entry[] = [];\n const loose: File[] = [];\n for (const item of Array.from(dt.items)) {\n if (item.kind !== \"file\") continue;\n const entry = (item as DataTransferItem & { webkitGetAsEntry?: () => Entry | null }).webkitGetAsEntry?.();\n if (entry) entries.push(entry);\n else {\n const f = item.getAsFile();\n if (f) loose.push(f);\n }\n }\n const out: Files = {};\n await Promise.all([\n ...entries.map((e) => readEntry(e, out)),\n ...loose.map(async (f) => {\n out[f.name] = new Uint8Array(await f.arrayBuffer());\n }),\n ]);\n return out;\n}\n\n/**\n * Reads an `<input type=\"file\" webkitdirectory>` (or `multiple`) selection into files keyed by\n * path. Folder picks use each file's webkitRelativePath, which starts with the folder's name.\n */\nexport async function filesFromFileList(list: FileList | File[]): Promise<Files> {\n const out: Files = {};\n await Promise.all(\n Array.from(list).map(async (f) => {\n out[f.webkitRelativePath || f.name] = new Uint8Array(await f.arrayBuffer());\n }),\n );\n return out;\n}\n\nasync function readEntry(entry: Entry, out: Files): Promise<void> {\n if (entry.isFile) {\n const f = await new Promise<File>((ok, err) => (entry as FileEntry).file(ok, err));\n out[entry.fullPath.replace(/^\\/+/, \"\")] = new Uint8Array(await f.arrayBuffer());\n return;\n }\n if (!entry.isDirectory) return;\n const reader = (entry as DirectoryEntry).createReader();\n // readEntries returns a batch at a time (100 in Chromium) and an empty batch at the end.\n for (;;) {\n const batch = await new Promise<Entry[]>((ok, err) => reader.readEntries(ok, err));\n if (batch.length === 0) return;\n await Promise.all(batch.map((e) => readEntry(e, out)));\n }\n}\n"]}
|
|
1
|
+
{"version":3,"sources":["../../src/wasmhost/client.ts","../../src/wasmhost/drop.ts","../../src/wasmhost/lane.ts"],"names":["abortReason"],"mappings":"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","file":"index.js","sourcesContent":["// The page's side of a wasm host worker (goapplib issue 32). startWorker loads the app's Go wasm in\n// a Web Worker; workerFetch is a fetch to hand a Connect transport, so the app's generated clients\n// talk to the worker exactly as they would to a server; mountFiles pushes files in before the\n// requests that read them.\nimport type { Files, HostChunk, HostReply, HostRequest, HostStatus } from \"./protocol\";\n\nexport type { Files } from \"./protocol\";\n\n/** Where the worker's pieces are served, and the global name the Go side exports under. */\nexport interface StartWorkerOptions {\n /** The built worker script, @panyam/tsappkit/wasmhost/worker.js, as your bundler serves it. */\n worker: string | URL;\n /** The Go wasm build. */\n wasm: string | URL;\n /** Go's wasm_exec.js, from `$(go env GOROOT)/lib/wasm`, matching the Go that built the wasm. */\n exec: string | URL;\n /** The ns the Go program passed to wasmhost.Serve. */\n ns: string;\n}\n\ntype Unsent = HostRequest extends infer R ? (R extends HostRequest ? Omit<R, \"id\"> : never) : never;\n\nclass Channel {\n private next = 1;\n private pending = new Map<\n number,\n { resolve: (r: HostReply) => void; reject: (e: Error) => void; onChunk?: (c: HostChunk) => void }\n >();\n private dead: Error | null = null;\n\n constructor(private worker: Worker) {\n worker.addEventListener(\"message\", (ev: MessageEvent<HostReply | HostStatus | HostChunk>) => {\n const m = ev.data;\n if (\"exited\" in m) {\n this.fail(new Error(`wasm host: ${m.exited}`));\n } else if (\"chunk\" in m) {\n this.pending.get(m.id)?.onChunk?.(m);\n } else if (\"id\" in m) {\n this.pending.get(m.id)?.resolve(m);\n this.pending.delete(m.id);\n }\n });\n worker.addEventListener(\"error\", (ev: ErrorEvent) => this.fail(new Error(`wasm host worker: ${ev.message}`)));\n }\n\n /** Sends req and returns its id with the final reply; chunks before it go to onChunk. */\n start(req: Unsent, transfer: Transferable[], onChunk?: (c: HostChunk) => void): { id: number; done: Promise<HostReply> } {\n const id = this.next++;\n if (this.dead) return { id, done: Promise.reject(this.dead) };\n const done = new Promise<HostReply>((resolve, reject) => {\n this.pending.set(id, { resolve, reject, onChunk });\n this.worker.postMessage({ ...req, id }, transfer);\n });\n return { id, done };\n }\n\n call(req: Unsent, transfer: Transferable[]): Promise<HostReply> {\n return this.start(req, transfer).done;\n }\n\n fail(err: Error) {\n this.dead = err;\n for (const p of this.pending.values()) p.reject(err);\n this.pending.clear();\n }\n}\n\nconst channels = new WeakMap<Worker, Channel>();\n\n/**\n * Terminates worker and rejects every request still waiting on it with `err`, which terminate\n * alone wouldn't do: a terminated worker never replies, so its pending requests would wait forever.\n * For lane.ts; not exported from the package.\n */\nexport function endWorker(worker: Worker, err: Error): void {\n channels.get(worker)?.fail(err);\n worker.terminate();\n}\n\nfunction channel(worker: Worker): Channel {\n let c = channels.get(worker);\n if (!c) {\n c = new Channel(worker);\n channels.set(worker, c);\n }\n return c;\n}\n\n/**\n * Starts the worker and resolves with it once the Go program has installed its exports, or rejects\n * with why it couldn't load (and terminates it, since it can't answer anything). Relative URLs\n * resolve against the page, not the worker script.\n */\nexport function startWorker(opts: StartWorkerOptions): Promise<Worker> {\n const abs = (u: string | URL) => new URL(u, location.href).href;\n const url = new URL(abs(opts.worker));\n url.searchParams.set(\"wasm\", abs(opts.wasm));\n url.searchParams.set(\"exec\", abs(opts.exec));\n url.searchParams.set(\"ns\", opts.ns);\n const worker = new Worker(url);\n channel(worker);\n return new Promise((resolve, reject) => {\n const onMessage = (ev: MessageEvent<HostStatus | HostReply>) => {\n const m = ev.data;\n if (!(\"ready\" in m)) return;\n worker.removeEventListener(\"message\", onMessage);\n if (m.ready) {\n resolve(worker);\n return;\n }\n worker.terminate();\n reject(new Error(`wasm host failed to load: ${m.error}`));\n };\n worker.addEventListener(\"message\", onMessage);\n worker.addEventListener(\n \"error\",\n (ev: ErrorEvent) => {\n worker.terminate();\n reject(new Error(`wasm host worker: ${ev.message}`));\n },\n { once: true },\n );\n });\n}\n\n// Statuses whose Response must have no body.\nconst NULL_BODY = new Set([101, 103, 204, 205, 304]);\n\n/**\n * A fetch that answers in the worker, for `createConnectTransport({ baseUrl, fetch: workerFetch(worker) })`.\n * Only the path and query reach the worker, so baseUrl can be anything absolute, such as\n * location.origin. It rejects if the worker reports an error (no handler, a failed build, a handler\n * panic) and resolves with any HTTP status the handler chose.\n *\n * A handler that flushes (http.Flusher) streams: the Response resolves at its first flush, and its\n * body delivers each flush as it happens, even from a Go loop that never yields, then the rest\n * when the handler returns. Aborting the request (init.signal) rejects it, or errors a body that's\n * already streaming, and cancels the handler's context; a handler sees that the next time it checks\n * ctx, which a loop that never yields to JS doesn't get to do (a lane ends such a worker instead).\n * Once the body is streaming, Chrome's text() and json() report the abort as \"TypeError: Failed to\n * fetch\" rather than an AbortError, so tell an abort from a failure by signal.aborted.\n */\nexport function workerFetch(worker: Worker): (input: RequestInfo | URL, init?: RequestInit) => Promise<Response> {\n const c = channel(worker);\n return async (input, init) => {\n const req = new Request(input, init);\n const signal = req.signal;\n if (signal.aborted) throw abortReason(signal);\n const buf = new Uint8Array(await req.arrayBuffer());\n const body = buf.byteLength ? buf : null;\n const headers: Record<string, string> = {};\n req.headers.forEach((v, k) => (headers[k] = v));\n const u = new URL(req.url);\n\n return new Promise<Response>((resolve, reject) => {\n let stream: ReadableStreamDefaultController<Uint8Array> | undefined;\n let settled = false;\n const fail = (err: unknown) => {\n if (settled) return;\n settled = true;\n if (stream) stream.error(err);\n else reject(err);\n };\n const { id, done } = c.start(\n { kind: \"http\", method: req.method, url: u.pathname + u.search, headers, body },\n body ? [body.buffer] : [],\n (chunk) => {\n if (settled) return;\n if (!stream) {\n const status = chunk.status ?? 200;\n const rs = new ReadableStream<Uint8Array>({ start: (ctl) => void (stream = ctl) });\n resolve(new Response(NULL_BODY.has(status) ? null : rs, { status, headers: chunk.headers }));\n }\n if (chunk.body.byteLength) stream!.enqueue(chunk.body);\n },\n );\n const onAbort = () => {\n void c.call({ kind: \"cancel\", target: id }, []).catch(() => {});\n fail(abortReason(signal));\n };\n signal.addEventListener(\"abort\", onAbort, { once: true });\n done.then(\n (reply) => {\n signal.removeEventListener(\"abort\", onAbort);\n if (!reply.ok) return fail(new Error(reply.error));\n if (settled) return;\n settled = true;\n if (stream) {\n if (reply.body?.byteLength) stream.enqueue(reply.body);\n stream.close();\n return;\n }\n const status = reply.status ?? 200;\n const resBody = NULL_BODY.has(status) ? null : ((reply.body ?? null) as BodyInit | null);\n resolve(new Response(resBody, { status, headers: reply.headers }));\n },\n (err) => {\n signal.removeEventListener(\"abort\", onAbort);\n fail(err);\n },\n );\n });\n };\n}\n\nfunction abortReason(signal: AbortSignal): unknown {\n return signal.reason ?? new DOMException(\"This request was aborted.\", \"AbortError\");\n}\n\n/**\n * A fetch like workerFetch's, except that each call starts a fresh worker from `opts`, sends it that\n * one request, and terminates it once the response is in, whether the request succeeded or not.\n * Ending the worker is the only way to give its wasm memory back to the browser, since wasm memory\n * never shrinks, so route a request with a big transient peak (an ingest that parses a large input\n * on the way to a small result) through this, and have it leave its result in the shared Cache\n * (Go's wasmhost.BrowserCache) for the long-lived worker to load. Every worker for the same `ns`\n * sees the same cache.\n *\n * Each call pays for a worker start and a wasm instantiate, a few hundred ms, so it's for jobs\n * that take seconds, not for ordinary requests. The worker ends when the response's body has been\n * read to the end, fails, or is cancelled, so a streamed body isn't cut short; read or cancel the\n * body, or the worker stays up.\n */\nexport function oneShotFetch(opts: StartWorkerOptions): (input: RequestInfo | URL, init?: RequestInit) => Promise<Response> {\n return async (input, init) => {\n const worker = await startWorker(opts);\n let res: Response;\n try {\n res = await workerFetch(worker)(input, init);\n } catch (err) {\n worker.terminate();\n throw err;\n }\n return untilBodyEnds(res, () => worker.terminate());\n };\n}\n\n/**\n * res with `end` called once its body has been read to the end, has failed, or has been\n * cancelled (or at once, for a response without a body). For oneShotFetch and lanes, which must\n * not let go of a worker while its streamed body is still arriving. Not exported from the package.\n */\nexport function untilBodyEnds(res: Response, end: () => void): Response {\n if (!res.body) {\n end();\n return res;\n }\n const reader = res.body.getReader();\n const body = new ReadableStream<Uint8Array>({\n async pull(ctl) {\n try {\n const { done, value } = await reader.read();\n if (done) {\n ctl.close();\n end();\n } else {\n ctl.enqueue(value);\n }\n } catch (err) {\n end();\n ctl.error(err);\n }\n },\n cancel(reason) {\n end();\n return reader.cancel(reason);\n },\n });\n return new Response(body, { status: res.status, statusText: res.statusText, headers: res.headers });\n}\n\n/**\n * Replaces the mount `name` with `files`. The buffers are transferred to the worker, not copied, so\n * each Uint8Array is empty here afterwards; copy first anything the page still needs. Resolves once\n * the worker has mounted them (and, for a rebuild-mode host, rebuilt its handler).\n */\nexport function mountFiles(worker: Worker, name: string, files: Files): Promise<void> {\n return sendFiles(worker, \"mount\", name, files);\n}\n\n/**\n * Adds `files` to the mount `name`, keeping the files it already holds; a file at a path the mount\n * already has is replaced, and a missing mount is created. This suits a page that brings files in a\n * piece at a time, since it only sends what's new. The worker checks the whole batch first, so a\n * rejection (a path that is already a directory, say) leaves the mount as it was. Buffers are\n * transferred as with mountFiles.\n */\nexport function addFiles(worker: Worker, name: string, files: Files): Promise<void> {\n return sendFiles(worker, \"add\", name, files);\n}\n\nasync function sendFiles(worker: Worker, kind: \"mount\" | \"add\", name: string, files: Files): Promise<void> {\n const transfer = new Set<ArrayBuffer>();\n for (const b of Object.values(files)) {\n // A tag check rather than instanceof, which fails for a buffer from another realm (an iframe),\n // and a SharedArrayBuffer can't be transferred.\n if (Object.prototype.toString.call(b.buffer) === \"[object ArrayBuffer]\") transfer.add(b.buffer as ArrayBuffer);\n }\n const reply = await channel(worker).call({ kind, name, files }, [...transfer]);\n if (!reply.ok) throw new Error(reply.error);\n}\n\n/**\n * The bytes of linear memory the worker's wasm holds. Wasm memory grows and never shrinks, so this\n * is the peak the engine has needed so far, which is what an app weighs before giving a browser a\n * bigger job.\n */\nexport async function workerMemory(worker: Worker): Promise<number> {\n const reply = await channel(worker).call({ kind: \"stats\" }, []);\n if (!reply.ok) throw new Error(reply.error);\n return reply.memoryBytes ?? 0;\n}\n\n/** Removes the mount `name`. Removing a name that isn't mounted succeeds. */\nexport async function unmountFiles(worker: Worker, name: string): Promise<void> {\n const reply = await channel(worker).call({ kind: \"unmount\", name }, []);\n if (!reply.ok) throw new Error(reply.error);\n}\n","// Turning what a user drops or picks into the {path: bytes} map mountFiles takes. The bytes are\n// read here, before any request, because the Go side can't wait on File.arrayBuffer() mid-request.\nimport type { Files } from \"./protocol\";\n\n// The parts of the File and Directory Entries API used here. Chromium, Firefox and Safari all ship\n// it under the webkit prefix.\ninterface Entry {\n isFile: boolean;\n isDirectory: boolean;\n fullPath: string;\n}\ninterface FileEntry extends Entry {\n file(ok: (f: File) => void, err: (e: unknown) => void): void;\n}\ninterface DirectoryEntry extends Entry {\n createReader(): { readEntries(ok: (es: Entry[]) => void, err: (e: unknown) => void): void };\n}\n\n/**\n * Reads a drop into files keyed by path. A dropped folder keeps its own name as the first path\n * element (\"design/board.edn\"), and dropped loose files sit at the top. Entries the browser can't\n * read as files or folders are skipped.\n */\nexport async function filesFromDrop(dt: DataTransfer): Promise<Files> {\n const entries: Entry[] = [];\n const loose: File[] = [];\n for (const item of Array.from(dt.items)) {\n if (item.kind !== \"file\") continue;\n const entry = (item as DataTransferItem & { webkitGetAsEntry?: () => Entry | null }).webkitGetAsEntry?.();\n if (entry) entries.push(entry);\n else {\n const f = item.getAsFile();\n if (f) loose.push(f);\n }\n }\n const out: Files = {};\n await Promise.all([\n ...entries.map((e) => readEntry(e, out)),\n ...loose.map(async (f) => {\n out[f.name] = new Uint8Array(await f.arrayBuffer());\n }),\n ]);\n return out;\n}\n\n/**\n * Reads an `<input type=\"file\" webkitdirectory>` (or `multiple`) selection into files keyed by\n * path. Folder picks use each file's webkitRelativePath, which starts with the folder's name.\n */\nexport async function filesFromFileList(list: FileList | File[]): Promise<Files> {\n const out: Files = {};\n await Promise.all(\n Array.from(list).map(async (f) => {\n out[f.webkitRelativePath || f.name] = new Uint8Array(await f.arrayBuffer());\n }),\n );\n return out;\n}\n\nasync function readEntry(entry: Entry, out: Files): Promise<void> {\n if (entry.isFile) {\n const f = await new Promise<File>((ok, err) => (entry as FileEntry).file(ok, err));\n out[entry.fullPath.replace(/^\\/+/, \"\")] = new Uint8Array(await f.arrayBuffer());\n return;\n }\n if (!entry.isDirectory) return;\n const reader = (entry as DirectoryEntry).createReader();\n // readEntries returns a batch at a time (100 in Chromium) and an empty batch at the end.\n for (;;) {\n const batch = await new Promise<Entry[]>((ok, err) => reader.readEntries(ok, err));\n if (batch.length === 0) return;\n await Promise.all(batch.map((e) => readEntry(e, out)));\n }\n}\n","// Lanes (goapplib issue 80): more than one worker for one wasm service, with the app routing\n// requests by kind, so a quick request never waits behind a long one. Go's wasm target runs one\n// thread per worker, and a job that never yields keeps that thread busy, so neither a second\n// request nor an abort can reach it. A lane holds one worker it can replace: aborting a request\n// on it terminates the worker, which is the only way to stop a job that never yields, and starts\n// a fresh one, warmed from the shared Cache by the app's warm function.\nimport { endWorker, startWorker, untilBodyEnds, workerFetch, workerMemory, type StartWorkerOptions } from \"./client\";\n\ntype Fetch = (input: RequestInfo | URL, init?: RequestInit) => Promise<Response>;\n\nexport interface LaneOptions {\n /**\n * Runs on each new worker before the lane hands it a request: typically a request that loads\n * the service's state from the cache (wasmhost.BrowserCache), so a replacement worker answers\n * like the one it replaced. A warm that throws ends that worker, and the error is what the next\n * request gets; the request after it starts another worker.\n */\n warm?: (fetch: Fetch) => Promise<unknown>;\n /**\n * A memory limit for the lane's worker, in bytes of wasm memory (workerMemory). Wasm memory never\n * shrinks, so a long-lived worker creeps up through requests that each allocate and drop some.\n * When a request finishes (its body read to the end, or failed) and the lane has nothing else\n * in flight, the lane asks the worker how much it holds, and past this limit replaces it with a\n * fresh one, warmed as on an abort. It never interrupts a request, so a lane that's never idle\n * never restarts, and a response whose body nobody reads keeps the lane busy. Size it above what\n * the warmed state needs, or the lane restarts after every request.\n */\n maxMemoryBytes?: number;\n /** Called when the lane replaces its worker for passing maxMemoryBytes, with what it held. */\n onRestart?: (why: { memoryBytes: number }) => void;\n}\n\n/** One replaceable worker. See startLane. */\nexport interface Lane {\n /**\n * A fetch for a Connect transport, like workerFetch, answered by the lane's current worker.\n * Aborting a request (init.signal) rejects it with the signal's reason at once (or errors its\n * body, if it's already streaming), terminates the worker, and starts and warms a new one; other\n * requests still waiting on the old worker are rejected too. A request made meanwhile waits for\n * the new worker.\n */\n fetch: Fetch;\n /** The lane's current worker, once it's started and warmed. */\n worker(): Promise<Worker>;\n /** Replaces the worker, as an abort does, and resolves once the new one is warm. */\n restart(): Promise<void>;\n /** Terminates the worker. Requests after this reject. */\n close(): void;\n}\n\n/**\n * Starts a lane: a worker from `opts`, warmed with `lane.warm`, that the lane replaces whenever a\n * request on it is aborted. Give each kind of request its own lane (a \"serve\" lane for quick\n * queries, a \"jobs\" lane for long jobs) by building each client's transport on that lane's fetch.\n * Every lane of one ns shares the same Cache, which is how a replacement or a second lane gets the\n * state without rebuilding it.\n */\nexport function startLane(opts: StartWorkerOptions, lane: LaneOptions = {}): Lane {\n let closed = false;\n let current!: Promise<Worker>;\n let failed = false;\n\n const boot = () => {\n failed = false;\n const p: Promise<Worker> = startWorker(opts).then(async (w) => {\n if (lane.warm) {\n try {\n await lane.warm(workerFetch(w));\n } catch (err) {\n endWorker(w, err instanceof Error ? err : new Error(String(err)));\n throw err;\n }\n }\n return w;\n });\n p.catch(() => {\n if (current === p) failed = true;\n });\n current = p;\n return p;\n };\n\n const replace = (old: Worker, why: Error) => {\n endWorker(old, why);\n return boot();\n };\n\n let inFlight = 0;\n // Runs when the lane goes idle. A request that starts while workerMemory is out means the lane\n // isn't idle any more, so the restart waits for the next idle moment.\n const checkMemory = async (w: Worker, of: Promise<Worker>) => {\n let bytes: number;\n try {\n bytes = await workerMemory(w);\n } catch {\n return;\n }\n if (bytes <= lane.maxMemoryBytes! || inFlight !== 0 || current !== of || closed) return;\n lane.onRestart?.({ memoryBytes: bytes });\n void replace(w, new Error(\"wasm host lane: restarted past its memory limit\")).catch(() => {});\n };\n\n boot();\n\n return {\n async fetch(input, init) {\n if (closed) throw new Error(\"wasm host lane: closed\");\n const signal = init?.signal ?? (input instanceof Request ? input.signal : undefined);\n if (signal?.aborted) throw abortReason(signal);\n if (failed) boot();\n const booting = current;\n const w = await booting;\n if (signal?.aborted) throw abortReason(signal);\n inFlight++;\n let ended = false;\n const end = () => {\n if (ended) return;\n ended = true;\n inFlight--;\n if (inFlight === 0 && lane.maxMemoryBytes !== undefined && current === booting) void checkMemory(w, booting);\n };\n const run = workerFetch(w)(input, init);\n if (!signal) {\n return run.then(\n (res) => untilBodyEnds(res, end),\n (err) => {\n end();\n throw err;\n },\n );\n }\n return new Promise<Response>((resolve, reject) => {\n const onAbort = () => {\n end();\n reject(abortReason(signal));\n if (current === booting) void replace(w, new Error(\"wasm host lane: restarted after an abort\")).catch(() => {});\n };\n signal.addEventListener(\"abort\", onAbort, { once: true });\n run.then(\n // A streamed body is still coming from the busy worker, so an abort until it ends must\n // still end the worker.\n (res) =>\n resolve(\n untilBodyEnds(res, () => {\n signal.removeEventListener(\"abort\", onAbort);\n end();\n }),\n ),\n (err) => {\n signal.removeEventListener(\"abort\", onAbort);\n end();\n reject(err);\n },\n );\n });\n },\n worker: () => current,\n async restart() {\n const old = await current.catch(() => undefined);\n await (old ? replace(old, new Error(\"wasm host lane: restarted\")) : boot());\n },\n close() {\n closed = true;\n void current.then((w) => endWorker(w, new Error(\"wasm host lane: closed\")), () => {});\n },\n };\n}\n\nfunction abortReason(signal: AbortSignal): unknown {\n return signal.reason ?? new DOMException(\"This request was aborted.\", \"AbortError\");\n}\n"]}
|