@hara-lang/hta 0.1.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/worker.mjs ADDED
@@ -0,0 +1,344 @@
1
+ import { decodeHta, encodeHta, HTA_MAX_FRAME_BYTES } from "./index.js";
2
+ import { createBrowserProvider } from "./provider-browser.mjs";
3
+ import { createProviderLifecycle, HTA_PROVIDER_EVENT, providerErrorCode } from "./provider-common.mjs";
4
+
5
+ let instance;
6
+ let abiVersion;
7
+ let backend;
8
+ let lifecycle;
9
+ const requests = new Map();
10
+ const tasks = new Map();
11
+ const hostTasks = new Map();
12
+ const externrefTable = [];
13
+ let activeOperation = null;
14
+
15
+ const wasmImports = {
16
+ env: {
17
+ hara_random_fill(pointer, length) {
18
+ const memory = instance?.exports.memory;
19
+ if (!memory) throw new Error("hta/memory-unavailable");
20
+ try {
21
+ globalThis.crypto.getRandomValues(new Uint8Array(memory.buffer, pointer, length));
22
+ return 0;
23
+ } catch {
24
+ return 1;
25
+ }
26
+ },
27
+ hara_time_ms() {
28
+ return BigInt(Math.trunc(Date.now()));
29
+ },
30
+ hara_time_ns() {
31
+ return BigInt(Math.trunc(performance.now() * 1_000_000));
32
+ }
33
+ },
34
+ "__wbindgen_placeholder__": {
35
+ __wbindgen_describe() {},
36
+ __wbindgen_object_drop_ref(index) {
37
+ externrefTable[index] = undefined;
38
+ },
39
+ __wbg_getRandomValues_eb590f34c5dc8fa0(pointer, length) {
40
+ const memory = instance?.exports.memory;
41
+ if (!memory) throw new Error("hta/memory-unavailable");
42
+ crypto.getRandomValues(new Uint8Array(memory.buffer, pointer, length));
43
+ },
44
+ __wbg___wbindgen_throw_bb96b2010945f0bc(pointer, length) {
45
+ const bytes = instance ? new Uint8Array(instance.exports.memory.buffer, pointer, length) : [];
46
+ throw new Error(instance ? new TextDecoder().decode(bytes) : "wasm error");
47
+ }
48
+ },
49
+ "__wbindgen_externref_xform__": {
50
+ __wbindgen_externref_table_grow(size) {
51
+ const previous = externrefTable.length;
52
+ externrefTable.length += size;
53
+ return previous;
54
+ },
55
+ __wbindgen_externref_table_set_null(index) {
56
+ externrefTable[index] = undefined;
57
+ }
58
+ }
59
+ };
60
+
61
+ self.addEventListener("message", event => {
62
+ return receive(event.data);
63
+ });
64
+
65
+ async function receive(message) {
66
+ try {
67
+ if (message.type === "init") {
68
+ await initialize(message);
69
+ return;
70
+ }
71
+ if (backend?.kind === "provider") {
72
+ await backend.provider.handle(message);
73
+ if (message.type === "close") {
74
+ self.postMessage({type:"closed"});
75
+ self.close();
76
+ }
77
+ return;
78
+ }
79
+ if (backend?.kind !== "wasm") throw new Error("hta/worker-not-initialized");
80
+ if (message.type === "call") {
81
+ const session = requestSession(message.frame);
82
+ const [operation] = decodeHta(message.frame);
83
+ activeOperation = String(operation);
84
+ const task = Number(callFrame(instance.exports.hta_start, message.frame));
85
+ if (task <= 0) throw new Error("hta/start-failed");
86
+ requests.set(message.id, task);
87
+ tasks.set(task, { id: message.id, session, operation: String(operation) });
88
+ lifecycle?.emit(HTA_PROVIDER_EVENT.CALL_ENTER, {
89
+ request: Number(message.id),
90
+ operation: String(operation)
91
+ });
92
+ pump();
93
+ } else if (message.type === "delivery") {
94
+ const hostTask = hostTasks.get(message.call);
95
+ const task = hostTask?.task;
96
+ if (hostTask === undefined || !tasks.has(task)) {
97
+ hostTasks.delete(message.call);
98
+ pump();
99
+ return;
100
+ }
101
+ hostTasks.delete(message.call);
102
+ callFrame(
103
+ instance.exports.hta_deliver,
104
+ encodeHta([message.call, message.ok ? 0 : 1, decodeHta(message.frame)])
105
+ );
106
+ pump();
107
+ } else if (message.type === "cancel") {
108
+ const task = requests.get(message.id);
109
+ if (task !== undefined) {
110
+ const request = tasks.get(task);
111
+ const calls = [...hostTasks.entries()]
112
+ .filter(([, hostTask]) => hostTask.task === task)
113
+ .map(([call, hostTask]) => ({
114
+ call,
115
+ task,
116
+ session: hostTask.session,
117
+ mount: hostTask.mount,
118
+ service: hostTask.service,
119
+ method: hostTask.method
120
+ }));
121
+ if (calls.length) self.postMessage({ type: "host-cancel", calls });
122
+ const cancelStatus = instance.exports.hta_cancel(BigInt(task));
123
+ const dropStatus = instance.exports.hta_drop_task(BigInt(task));
124
+ requests.delete(message.id);
125
+ tasks.delete(task);
126
+ removeHostTasks(task);
127
+ if (cancelStatus !== 0) throw new Error(`hta/cancel-failed: ${cancelStatus}`);
128
+ if (dropStatus !== 0) throw new Error(`hta/drop-task-failed: ${dropStatus}`);
129
+ lifecycle?.emit(HTA_PROVIDER_EVENT.CANCEL, {
130
+ request: Number(message.id),
131
+ operation: request?.operation
132
+ });
133
+ pump();
134
+ }
135
+ } else if (message.type === "release") {
136
+ const status = callFrame(instance.exports.hta_release, message.frame);
137
+ if (Number(status) !== 0) throw new Error(`hta/handle-release-failed: ${status}`);
138
+ lifecycle?.emit(HTA_PROVIDER_EVENT.RELEASE, { status: "ok" });
139
+ pump();
140
+ } else if (message.type === "close") {
141
+ closeWasm();
142
+ }
143
+ } catch (error) {
144
+ lifecycle?.emit(HTA_PROVIDER_EVENT.FAILURE, {
145
+ status: "error",
146
+ code: providerErrorCode(error)
147
+ });
148
+ const operation = activeOperation ? ` (${activeOperation})` : "";
149
+ self.postMessage({ type: "fatal", error: { message: `${String(error?.message ?? error)}${operation}` } });
150
+ }
151
+ }
152
+
153
+ async function initialize(message) {
154
+ if (backend) throw new Error("hta/worker-already-initialized");
155
+ if (message.backend === "provider") {
156
+ if (typeof message.providerUrl !== "string") throw new Error("hta/provider-missing");
157
+ const providerModule = await import(message.providerUrl);
158
+ const call = providerModule.default ?? providerModule.call ?? providerModule.provider;
159
+ if (typeof call !== "function") throw new Error("hta/provider-invalid");
160
+ const close = providerModule.close ?? providerModule.closeAll;
161
+ if (close !== undefined && typeof close !== "function") throw new Error("hta/provider-close-invalid");
162
+ if (providerModule.release !== undefined && typeof providerModule.release !== "function") {
163
+ throw new Error("hta/provider-release-invalid");
164
+ }
165
+ backend = {
166
+ kind: "provider",
167
+ provider: createBrowserProvider(call, {
168
+ scope: self,
169
+ errorCode: message.errorCode,
170
+ close: close === undefined ? undefined : () => close(),
171
+ release: providerModule.release,
172
+ onEvent: message.instrumentation
173
+ ? event => self.postMessage({ type: "provider-event", event })
174
+ : undefined
175
+ })
176
+ };
177
+ self.postMessage({ type: "ready" });
178
+ return;
179
+ }
180
+
181
+ const bytes = message.moduleBytes ?? await (await fetch(message.moduleUrl)).arrayBuffer();
182
+ const libraryBytes = message.libraryBytes
183
+ ?? (message.libraryUrl ? await (await fetch(message.libraryUrl)).arrayBuffer() : null);
184
+ const imports = { ...wasmImports };
185
+ if (libraryBytes) {
186
+ const library = (await WebAssembly.instantiate(libraryBytes, imports)).instance;
187
+ imports["hara/library"] = {};
188
+ for (const entry of WebAssembly.Module.imports(await WebAssembly.compile(bytes))) {
189
+ if (entry.module !== "hara/library") continue;
190
+ const exported = library.exports[entry.name];
191
+ if (exported === undefined) throw new Error(`hta/library-export-missing: ${entry.name}`);
192
+ imports["hara/library"][entry.name] = exported;
193
+ }
194
+ }
195
+ instance = (await WebAssembly.instantiate(bytes, imports)).instance;
196
+ required();
197
+ lifecycle = createProviderLifecycle({
198
+ origin: "browser-wasm",
199
+ onEvent: message.instrumentation
200
+ ? event => self.postMessage({ type: "provider-event", event })
201
+ : undefined
202
+ });
203
+ lifecycle.emit(HTA_PROVIDER_EVENT.START);
204
+ backend = { kind: "wasm" };
205
+ self.postMessage({ type: "ready" });
206
+ }
207
+
208
+ function required() {
209
+ for (const name of [
210
+ "memory", "hta_abi_version", "hta_alloc", "hta_dealloc", "hta_start",
211
+ "hta_next_event", "hta_deliver", "hta_cancel", "hta_drop_task", "hta_release"
212
+ ]) {
213
+ if (!(name in instance.exports)) throw new Error(`hta/export-missing: ${name}`);
214
+ }
215
+ abiVersion = instance.exports.hta_abi_version();
216
+ if (abiVersion !== 1 && abiVersion !== 2 && abiVersion !== 3 && abiVersion !== 4) {
217
+ throw new Error("hta/version-unsupported");
218
+ }
219
+ }
220
+
221
+ function callFrame(fn, frame) {
222
+ const bytes = frame instanceof Uint8Array ? frame : new Uint8Array(frame);
223
+ if (bytes.length > HTA_MAX_FRAME_BYTES) throw new Error("hta/value-too-large: frame exceeds 64 MiB");
224
+ const pointer = Number(instance.exports.hta_alloc(bytes.length));
225
+ if (!Number.isSafeInteger(pointer) || pointer < 0 || pointer + bytes.length > instance.exports.memory.buffer.byteLength) {
226
+ throw new Error("hta/memory-unavailable");
227
+ }
228
+ new Uint8Array(instance.exports.memory.buffer, pointer, bytes.length).set(bytes);
229
+ try {
230
+ return fn(pointer, bytes.length);
231
+ } finally {
232
+ instance.exports.hta_dealloc(pointer, bytes.length);
233
+ }
234
+ }
235
+
236
+ function next() {
237
+ const packed = instance.exports.hta_next_event();
238
+ if (packed === 0n) return null;
239
+ const pointer = Number(packed >> 32n);
240
+ const size = Number(packed & 0xffff_ffffn);
241
+ if (!Number.isSafeInteger(pointer) || !Number.isSafeInteger(size) || size === 0
242
+ || size > HTA_MAX_FRAME_BYTES || pointer + size > instance.exports.memory.buffer.byteLength) {
243
+ if (Number.isSafeInteger(pointer) && Number.isSafeInteger(size) && pointer >= 0 && size >= 0) {
244
+ instance.exports.hta_dealloc(pointer, size);
245
+ }
246
+ throw new Error("hta/event-memory-invalid");
247
+ }
248
+ const frame = new Uint8Array(instance.exports.memory.buffer, pointer, size).slice();
249
+ instance.exports.hta_dealloc(pointer, size);
250
+ return decodeHta(frame);
251
+ }
252
+
253
+ function requestSession(frame) {
254
+ const [target, args] = decodeHta(frame);
255
+ return typeof target === "string"
256
+ && ["session/eval", "session/eval-vm", "session/prepare-vm", "session/invoke-vm",
257
+ "session/eval-bound", "session/trace-eval", "session/complete"].includes(target)
258
+ && typeof args?.[0] === "string"
259
+ ? args[0]
260
+ : "ROOT";
261
+ }
262
+
263
+ function pump() {
264
+ for (let event; (event = next()) !== null;) {
265
+ const kind = Number(event[0]);
266
+ if (kind === 0 || kind === 1) {
267
+ const task = Number(event[1]);
268
+ const request = tasks.get(task);
269
+ if (!request) continue;
270
+ tasks.delete(task);
271
+ requests.delete(request.id);
272
+ removeHostTasks(task);
273
+ instance.exports.hta_drop_task(BigInt(task));
274
+ self.postMessage({ type: "result", id: request.id, ok: kind === 0, frame: encodeHta(event[2]) });
275
+ lifecycle?.emit(kind === 0 ? HTA_PROVIDER_EVENT.CALL_RETURN : HTA_PROVIDER_EVENT.CALL_ERROR, {
276
+ request: request.id,
277
+ operation: request.operation,
278
+ status: kind === 0 ? "ok" : "error"
279
+ });
280
+ } else if (kind === 2) {
281
+ const call = Number(event[1]);
282
+ const task = Number(event[2]);
283
+ hostTasks.set(call, {
284
+ task,
285
+ session: event[3],
286
+ mount: event[4] ?? null,
287
+ service: String(abiVersion >= 2 ? event[5] : event[3]),
288
+ method: String(abiVersion >= 2 ? event[6] : event[4])
289
+ });
290
+ const request = tasks.get(task);
291
+ lifecycle?.emit(HTA_PROVIDER_EVENT.HOST_CALL, {
292
+ request: request?.id,
293
+ task,
294
+ call,
295
+ service: String(abiVersion >= 2 ? event[5] : event[3]),
296
+ method: String(abiVersion >= 2 ? event[6] : event[4]),
297
+ status: "enter"
298
+ });
299
+ if (abiVersion >= 2) {
300
+ self.postMessage({
301
+ type: "host-call", call, task, session: event[3], mount: event[4] ?? null,
302
+ service: event[5], method: event[6], frame: encodeHta(event[7])
303
+ });
304
+ } else {
305
+ const request = tasks.get(task);
306
+ self.postMessage({
307
+ type: "host-call", call, task, session: request?.session ?? "ROOT", mount: null,
308
+ service: event[3], method: event[4], frame: encodeHta(event[5])
309
+ });
310
+ }
311
+ } else {
312
+ throw new Error(`hta/event-unknown: ${kind}`);
313
+ }
314
+ }
315
+ }
316
+
317
+ function removeHostTasks(task) {
318
+ for (const [call, pendingTask] of hostTasks) {
319
+ if (pendingTask.task === task) hostTasks.delete(call);
320
+ }
321
+ }
322
+
323
+ function closeWasm() {
324
+ const calls = [...hostTasks.entries()].map(([call, hostTask]) => ({
325
+ call,
326
+ task: hostTask.task,
327
+ session: hostTask.session,
328
+ mount: hostTask.mount,
329
+ service: hostTask.service,
330
+ method: hostTask.method
331
+ }));
332
+ if (calls.length) self.postMessage({ type: "host-cancel", calls });
333
+ for (const [task] of tasks) {
334
+ instance.exports.hta_cancel(BigInt(task));
335
+ instance.exports.hta_drop_task(BigInt(task));
336
+ }
337
+ requests.clear();
338
+ tasks.clear();
339
+ hostTasks.clear();
340
+ lifecycle?.emit(HTA_PROVIDER_EVENT.TERMINAL, { status: "ok" });
341
+ lifecycle?.shutdown({ status: "ok" });
342
+ self.postMessage({type:"closed"});
343
+ self.close();
344
+ }