wasm-idle 0.6.1 → 0.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (33) hide show
  1. package/README.md +77 -11
  2. package/dist/playground/assets.d.ts +43 -0
  3. package/dist/playground/assets.js +164 -0
  4. package/dist/playground/elixir.d.ts +32 -0
  5. package/dist/playground/elixir.js +173 -0
  6. package/dist/playground/go.d.ts +31 -0
  7. package/dist/playground/go.js +166 -0
  8. package/dist/playground/index.js +36 -1
  9. package/dist/playground/ocaml.d.ts +32 -0
  10. package/dist/playground/ocaml.js +341 -0
  11. package/dist/playground/options.d.ts +4 -0
  12. package/dist/playground/tinygo.d.ts +75 -0
  13. package/dist/playground/tinygo.js +510 -0
  14. package/dist/playground/wasmElixirVersion.d.ts +1 -0
  15. package/dist/playground/wasmElixirVersion.js +1 -0
  16. package/dist/playground/wasmGoVersion.d.ts +1 -0
  17. package/dist/playground/wasmGoVersion.js +1 -0
  18. package/dist/playground/wasmOcamlVersion.d.ts +1 -0
  19. package/dist/playground/wasmOcamlVersion.js +1 -0
  20. package/dist/playground/wasmRustVersion.d.ts +1 -1
  21. package/dist/playground/wasmRustVersion.js +1 -1
  22. package/dist/playground/wasmTinyGoVersion.d.ts +1 -0
  23. package/dist/playground/wasmTinyGoVersion.js +1 -0
  24. package/dist/playground/worker/elixir.d.ts +1 -0
  25. package/dist/playground/worker/elixir.js +317 -0
  26. package/dist/playground/worker/go.d.ts +1 -0
  27. package/dist/playground/worker/go.js +163 -0
  28. package/dist/playground/worker/ocaml.d.ts +1 -0
  29. package/dist/playground/worker/ocaml.js +449 -0
  30. package/dist/playground/worker/tinygo.d.ts +1 -0
  31. package/dist/playground/worker/tinygo.js +137 -0
  32. package/dist/terminal/Terminal.svelte +69 -29
  33. package/package.json +33 -21
@@ -0,0 +1,449 @@
1
+ import { waitForBufferedStdin } from '../stdinBuffer';
2
+ let moduleUrl = '';
3
+ let manifestUrl = '';
4
+ let loadedModuleUrl = '';
5
+ let loadedManifestUrl = '';
6
+ let compilerPromise = null;
7
+ let manifestPromise = null;
8
+ let compiledResult = null;
9
+ let compiledCacheKey = '';
10
+ let stdinBufferOcaml = null;
11
+ function appendTrailingNewline(text) {
12
+ return text.endsWith('\n') ? text : `${text}\n`;
13
+ }
14
+ function normalizeAssetLoader(programSource, runtimePromiseKey) {
15
+ let normalizedSource = programSource.includes('($=>async a=>{')
16
+ ? programSource.replace('($=>async a=>{', `globalThis.${runtimePromiseKey}=($=>async a=>{`)
17
+ : programSource;
18
+ const assetLoaderPattern = /function ([A-Za-z$_][\w$]*)\(([A-Za-z$_][\w$]*)\)\{const ([A-Za-z$_][\w$]*)=([A-Za-z$_][\w$]*)\?new URL\(\2,\4\):\2;return fetch\(\3\)\}/;
19
+ const matchedLoader = normalizedSource.match(assetLoaderPattern);
20
+ if (!matchedLoader) {
21
+ return normalizedSource;
22
+ }
23
+ const loaderName = matchedLoader[1] || 'loadAsset';
24
+ const argumentName = matchedLoader[2] || 'assetPath';
25
+ const resolvedName = matchedLoader[3] || 'resolvedUrl';
26
+ const baseName = matchedLoader[4] || 'scriptUrl';
27
+ return normalizedSource.replace(assetLoaderPattern, `function ${loaderName}(${argumentName}){if(globalThis.__wasm_of_js_of_ocaml_resolve_asset){const resolvedAsset=globalThis.__wasm_of_js_of_ocaml_resolve_asset(${argumentName});if(resolvedAsset)return fetch(resolvedAsset)}const ${resolvedName}=${baseName}?new URL(${argumentName},${baseName}):${argumentName};return fetch(${resolvedName})}`);
28
+ }
29
+ function rewriteAbsoluteBundleUrl(url, currentManifestUrl) {
30
+ if (!url || /^[a-z]+:/i.test(url)) {
31
+ return url;
32
+ }
33
+ const manifestLocation = new URL(currentManifestUrl, self.location.href);
34
+ const basePath = manifestLocation.pathname.replace(/\/wasm-of-js-of-ocaml\/browser-native-bundle\/browser-native-manifest\.v1\.json$/, '');
35
+ return new URL(`${basePath}${url}`, manifestLocation.origin).toString();
36
+ }
37
+ function rewriteManifest(manifest, currentManifestUrl) {
38
+ return {
39
+ ...manifest,
40
+ findlibConf: rewriteAbsoluteBundleUrl(manifest.findlibConf, currentManifestUrl),
41
+ tools: {
42
+ ocamlc: rewriteAbsoluteBundleUrl(manifest.tools.ocamlc, currentManifestUrl),
43
+ js_of_ocaml: rewriteAbsoluteBundleUrl(manifest.tools.js_of_ocaml, currentManifestUrl),
44
+ wasm_of_ocaml: rewriteAbsoluteBundleUrl(manifest.tools.wasm_of_ocaml, currentManifestUrl)
45
+ },
46
+ ...(manifest.binaryenTools
47
+ ? {
48
+ binaryenTools: {
49
+ wasm_opt: rewriteAbsoluteBundleUrl(manifest.binaryenTools.wasm_opt, currentManifestUrl),
50
+ wasm_merge: rewriteAbsoluteBundleUrl(manifest.binaryenTools.wasm_merge, currentManifestUrl),
51
+ wasm_metadce: rewriteAbsoluteBundleUrl(manifest.binaryenTools.wasm_metadce, currentManifestUrl)
52
+ }
53
+ }
54
+ : {}),
55
+ ...(manifest.runtimePack
56
+ ? {
57
+ runtimePack: {
58
+ ...manifest.runtimePack,
59
+ asset: rewriteAbsoluteBundleUrl(manifest.runtimePack.asset, currentManifestUrl),
60
+ index: rewriteAbsoluteBundleUrl(manifest.runtimePack.index, currentManifestUrl)
61
+ }
62
+ }
63
+ : {}),
64
+ ocamlLibFiles: manifest.ocamlLibFiles.map((file) => ({
65
+ ...file,
66
+ ...(file.url ? { url: rewriteAbsoluteBundleUrl(file.url, currentManifestUrl) } : {})
67
+ })),
68
+ packages: manifest.packages.map((manifestPackage) => ({
69
+ ...manifestPackage,
70
+ files: manifestPackage.files.map((file) => ({
71
+ ...file,
72
+ ...(file.url ? { url: rewriteAbsoluteBundleUrl(file.url, currentManifestUrl) } : {})
73
+ }))
74
+ }))
75
+ };
76
+ }
77
+ async function loadCompiler(nextModuleUrl) {
78
+ if (!nextModuleUrl) {
79
+ throw new Error('OCaml runtime is not configured. Set runtimeAssets.ocaml.moduleUrl and runtimeAssets.ocaml.manifestUrl or sync the bundled wasm-of-js-of-ocaml assets.');
80
+ }
81
+ if (loadedModuleUrl === nextModuleUrl && compilerPromise) {
82
+ return await compilerPromise;
83
+ }
84
+ loadedModuleUrl = nextModuleUrl;
85
+ compiledResult = null;
86
+ compiledCacheKey = '';
87
+ compilerPromise = (async () => {
88
+ const module = (await import(/* @vite-ignore */ nextModuleUrl));
89
+ if (typeof module.compile !== 'function') {
90
+ throw new Error('wasm-of-js-of-ocaml bundle must export compile');
91
+ }
92
+ if (typeof module.createBrowserWorkerSystemDispatcher !== 'function') {
93
+ throw new Error('wasm-of-js-of-ocaml bundle must export createBrowserWorkerSystemDispatcher');
94
+ }
95
+ return module;
96
+ })();
97
+ return await compilerPromise;
98
+ }
99
+ async function loadManifest(nextManifestUrl) {
100
+ if (!nextManifestUrl) {
101
+ throw new Error('OCaml runtime is not configured. Set runtimeAssets.ocaml.moduleUrl and runtimeAssets.ocaml.manifestUrl or sync the bundled wasm-of-js-of-ocaml assets.');
102
+ }
103
+ if (loadedManifestUrl === nextManifestUrl && manifestPromise) {
104
+ return await manifestPromise;
105
+ }
106
+ loadedManifestUrl = nextManifestUrl;
107
+ compiledResult = null;
108
+ compiledCacheKey = '';
109
+ manifestPromise = (async () => {
110
+ const response = await fetch(nextManifestUrl, { cache: 'no-store' });
111
+ if (!response.ok) {
112
+ throw new Error(`failed to fetch OCaml manifest: ${response.status}`);
113
+ }
114
+ return rewriteManifest((await response.json()), nextManifestUrl);
115
+ })();
116
+ return await manifestPromise;
117
+ }
118
+ function getJsArtifact(result) {
119
+ const programArtifact = result.artifacts.find((artifact) => artifact.kind === 'js' && typeof artifact.data === 'string');
120
+ if (!programArtifact || typeof programArtifact.data !== 'string') {
121
+ throw new Error('OCaml compile result did not include a JavaScript artifact');
122
+ }
123
+ return programArtifact;
124
+ }
125
+ async function executeCompileResult(result, log = false) {
126
+ const programArtifact = getJsArtifact(result);
127
+ const assetFiles = result.artifacts.filter((artifact) => artifact.kind === 'wasm' || artifact.kind === 'asset');
128
+ const assetResolverKey = '__wasm_of_js_of_ocaml_resolve_asset';
129
+ const runtimePromiseKey = '__wasm_of_js_of_ocaml_runtime_promise';
130
+ const stdinHookKey = '__wasmIdleOcamlReadStdin';
131
+ const sourceDir = programArtifact.path.replace(/\/[^/]+$/, '');
132
+ const createdObjectUrls = [];
133
+ const stdinEncoder = new TextEncoder();
134
+ let stdinChunkOcaml = new Uint8Array(0);
135
+ let stdinChunkOffsetOcaml = 0;
136
+ const originalConsole = globalThis.console;
137
+ const originalFetch = globalThis.fetch.bind(globalThis);
138
+ const originalInstantiate = WebAssembly.instantiate.bind(WebAssembly);
139
+ const originalInstantiateStreaming = WebAssembly.instantiateStreaming
140
+ ? WebAssembly.instantiateStreaming.bind(WebAssembly)
141
+ : undefined;
142
+ const runtimeGlobal = globalThis;
143
+ const hadProcess = Object.prototype.hasOwnProperty.call(runtimeGlobal, 'process');
144
+ const hadRequire = Object.prototype.hasOwnProperty.call(runtimeGlobal, 'require');
145
+ const hadModule = Object.prototype.hasOwnProperty.call(runtimeGlobal, 'module');
146
+ const hadExports = Object.prototype.hasOwnProperty.call(runtimeGlobal, 'exports');
147
+ const hadFs = Object.prototype.hasOwnProperty.call(runtimeGlobal, 'fs');
148
+ const originalProcess = runtimeGlobal.process;
149
+ const originalRequire = runtimeGlobal.require;
150
+ const originalModule = runtimeGlobal.module;
151
+ const originalExports = runtimeGlobal.exports;
152
+ const originalFs = runtimeGlobal.fs;
153
+ const hadStdinHook = Object.prototype.hasOwnProperty.call(runtimeGlobal, stdinHookKey);
154
+ const originalStdinHook = runtimeGlobal[stdinHookKey];
155
+ const assetEntries = assetFiles
156
+ .filter((artifact) => artifact.data instanceof Uint8Array)
157
+ .map((assetFile) => {
158
+ const copiedAssetData = new Uint8Array(assetFile.data.byteLength);
159
+ copiedAssetData.set(assetFile.data);
160
+ const objectUrl = URL.createObjectURL(new Blob([copiedAssetData], {
161
+ type: assetFile.path.endsWith('.wasm')
162
+ ? 'application/wasm'
163
+ : 'application/octet-stream'
164
+ }));
165
+ createdObjectUrls.push(objectUrl);
166
+ const relativeFromSourceDir = assetFile.path.startsWith(`${sourceDir}/`)
167
+ ? assetFile.path.slice(sourceDir.length + 1)
168
+ : assetFile.path.replace(/^\/+/, '');
169
+ return {
170
+ path: assetFile.path,
171
+ basename: assetFile.path.split('/').at(-1) || assetFile.path,
172
+ relativeFromSourceDir,
173
+ objectUrl
174
+ };
175
+ });
176
+ const resolveAsset = (requestedAsset) => {
177
+ const candidates = [String(requestedAsset)];
178
+ try {
179
+ candidates.push(new URL(String(requestedAsset), self.location.href).pathname);
180
+ }
181
+ catch {
182
+ // ignore
183
+ }
184
+ for (const assetEntry of assetEntries) {
185
+ if (candidates.some((candidate) => candidate === assetEntry.path ||
186
+ candidate === assetEntry.relativeFromSourceDir ||
187
+ candidate.endsWith(`/${assetEntry.relativeFromSourceDir}`) ||
188
+ candidate.endsWith(`/${assetEntry.basename}`))) {
189
+ return assetEntry.objectUrl;
190
+ }
191
+ }
192
+ return null;
193
+ };
194
+ const readOcamlStdinBytes = (requestedBytes) => {
195
+ if (!stdinBufferOcaml) {
196
+ return null;
197
+ }
198
+ if (requestedBytes <= 0) {
199
+ return new Uint8Array(0);
200
+ }
201
+ while (stdinChunkOffsetOcaml >= stdinChunkOcaml.length) {
202
+ const chunk = waitForBufferedStdin(stdinBufferOcaml, () => postMessage({ buffer: true }));
203
+ if (chunk == null) {
204
+ stdinChunkOcaml = new Uint8Array(0);
205
+ stdinChunkOffsetOcaml = 0;
206
+ return null;
207
+ }
208
+ stdinChunkOcaml = stdinEncoder.encode(chunk);
209
+ stdinChunkOffsetOcaml = 0;
210
+ }
211
+ const end = Math.min(stdinChunkOffsetOcaml + requestedBytes, stdinChunkOcaml.length);
212
+ const bytes = stdinChunkOcaml.slice(stdinChunkOffsetOcaml, end);
213
+ stdinChunkOffsetOcaml = end;
214
+ return bytes;
215
+ };
216
+ globalThis.console = {
217
+ ...originalConsole,
218
+ log: (...args) => {
219
+ postMessage({
220
+ output: appendTrailingNewline(args.map((value) => String(value)).join(' '))
221
+ });
222
+ originalConsole.log(...args);
223
+ },
224
+ info: (...args) => {
225
+ postMessage({
226
+ output: appendTrailingNewline(args.map((value) => String(value)).join(' '))
227
+ });
228
+ originalConsole.info(...args);
229
+ },
230
+ warn: (...args) => {
231
+ postMessage({
232
+ output: appendTrailingNewline(args.map((value) => String(value)).join(' '))
233
+ });
234
+ originalConsole.warn(...args);
235
+ },
236
+ error: (...args) => {
237
+ postMessage({
238
+ output: appendTrailingNewline(args.map((value) => String(value)).join(' '))
239
+ });
240
+ originalConsole.error(...args);
241
+ }
242
+ };
243
+ WebAssembly.instantiate = (async (source, importObject) => {
244
+ return await originalInstantiate(source, importObject);
245
+ });
246
+ if (originalInstantiateStreaming) {
247
+ WebAssembly.instantiateStreaming = (async (source, importObject) => {
248
+ return await originalInstantiateStreaming(source, importObject);
249
+ });
250
+ }
251
+ globalThis[assetResolverKey] = resolveAsset;
252
+ globalThis.fetch = (async (input, init) => {
253
+ const requestUrl = typeof input === 'string' ? input : input instanceof URL ? input.toString() : input.url;
254
+ const resolvedAssetUrl = resolveAsset(requestUrl);
255
+ if (resolvedAssetUrl) {
256
+ return await originalFetch(resolvedAssetUrl, init);
257
+ }
258
+ return await originalFetch(input, init);
259
+ });
260
+ runtimeGlobal.process = undefined;
261
+ runtimeGlobal[stdinHookKey] = (requestedBytes = Number.MAX_SAFE_INTEGER) => {
262
+ const encoded = readOcamlStdinBytes(requestedBytes);
263
+ if (encoded == null) {
264
+ return null;
265
+ }
266
+ let byteString = '';
267
+ for (const byte of encoded) {
268
+ byteString += String.fromCharCode(byte);
269
+ }
270
+ return byteString;
271
+ };
272
+ const stdinFsShim = {
273
+ readSync(fileDescriptor, buffer, offset = 0, length = buffer.byteLength - offset) {
274
+ if (fileDescriptor !== 0 || !stdinBufferOcaml) {
275
+ return 0;
276
+ }
277
+ const encoded = readOcamlStdinBytes(length);
278
+ if (encoded == null) {
279
+ return 0;
280
+ }
281
+ buffer.set(encoded, offset);
282
+ return encoded.byteLength;
283
+ }
284
+ };
285
+ runtimeGlobal.fs = stdinFsShim;
286
+ runtimeGlobal.require = (specifier) => {
287
+ if (specifier === 'fs' || specifier === 'node:fs') {
288
+ return stdinFsShim;
289
+ }
290
+ throw new Error(`unsupported OCaml runtime require: ${specifier}`);
291
+ };
292
+ runtimeGlobal.module = { exports: {} };
293
+ runtimeGlobal.exports = runtimeGlobal.module.exports;
294
+ try {
295
+ let normalizedSource = normalizeAssetLoader(programArtifact.data, runtimePromiseKey);
296
+ const stdinReadReplacement = 'read($1,$2,$3,$4){var e=globalThis.__wasmIdleOcamlReadStdin?globalThis.__wasmIdleOcamlReadStdin($3):null;if(e==null)return 0;var h=e.length,f=new Uint8Array(h),i=0;for(;i<h;i++)f[i]=e.charCodeAt(i);var g=f.length<$3?f.length:$3;$1.set(f.subarray(0,g),$2);return g}';
297
+ const patchedSource = normalizedSource.replace(/read\(([A-Za-z$_][\w$]*),([A-Za-z$_][\w$]*),([A-Za-z$_][\w$]*),([A-Za-z$_][\w$]*)\)\{cz\(\4,mc,aha,ow\)\}/, stdinReadReplacement);
298
+ normalizedSource =
299
+ patchedSource !== normalizedSource
300
+ ? patchedSource
301
+ : normalizedSource.replace(/read\(([A-Za-z$_][\w$]*),([A-Za-z$_][\w$]*),([A-Za-z$_][\w$]*),([A-Za-z$_][\w$]*)\)\{[^{}]+\}(?=seek\([A-Za-z$_][\w$]*,[A-Za-z$_][\w$]*,[A-Za-z$_][\w$]*\)\{[^{}]+\}pos\(\)\{return-1\}close\(\)\{this\.log=undefined\}check_stream_semantics\([A-Za-z$_][\w$]*\)\{\})/, stdinReadReplacement);
302
+ normalizedSource = normalizedSource
303
+ .replace(/\bfs\.readSync\(/g, 'globalThis.fs.readSync(')
304
+ .replace(/read:\(([^)]*)\)=>[A-Za-z$_][\w$]*\.readSync\(/g, 'read:($1)=>globalThis.fs.readSync(');
305
+ if (log && normalizedSource === programArtifact.data) {
306
+ console.warn('[wasm-idle:ocaml-worker] stdin runtime patch did not match generated JS');
307
+ }
308
+ new Function(`${normalizedSource}\n//# sourceURL=${programArtifact.path}`)();
309
+ const runtimePromise = globalThis[runtimePromiseKey];
310
+ if (runtimePromise instanceof Promise) {
311
+ await runtimePromise;
312
+ }
313
+ postMessage({ results: true });
314
+ }
315
+ finally {
316
+ globalThis.console = originalConsole;
317
+ globalThis.fetch = originalFetch;
318
+ WebAssembly.instantiate = originalInstantiate;
319
+ if (originalInstantiateStreaming) {
320
+ WebAssembly.instantiateStreaming = originalInstantiateStreaming;
321
+ }
322
+ delete globalThis[runtimePromiseKey];
323
+ delete globalThis[assetResolverKey];
324
+ if (!hadProcess) {
325
+ delete runtimeGlobal.process;
326
+ }
327
+ else {
328
+ runtimeGlobal.process = originalProcess;
329
+ }
330
+ if (!hadRequire) {
331
+ delete runtimeGlobal.require;
332
+ }
333
+ else {
334
+ runtimeGlobal.require = originalRequire;
335
+ }
336
+ if (!hadModule) {
337
+ delete runtimeGlobal.module;
338
+ }
339
+ else {
340
+ runtimeGlobal.module = originalModule;
341
+ }
342
+ if (!hadExports) {
343
+ delete runtimeGlobal.exports;
344
+ }
345
+ else {
346
+ runtimeGlobal.exports = originalExports;
347
+ }
348
+ if (!hadFs) {
349
+ delete runtimeGlobal.fs;
350
+ }
351
+ else {
352
+ runtimeGlobal.fs = originalFs;
353
+ }
354
+ if (!hadStdinHook) {
355
+ delete runtimeGlobal[stdinHookKey];
356
+ }
357
+ else {
358
+ runtimeGlobal[stdinHookKey] = originalStdinHook;
359
+ }
360
+ for (const objectUrl of createdObjectUrls) {
361
+ URL.revokeObjectURL(objectUrl);
362
+ }
363
+ }
364
+ }
365
+ self.onmessage = async (event) => {
366
+ const { load, moduleUrl: nextModuleUrl, manifestUrl: nextManifestUrl, code, prepare, target = 'wasm', log = true, buffer } = event.data;
367
+ try {
368
+ if (load) {
369
+ moduleUrl = nextModuleUrl;
370
+ manifestUrl = nextManifestUrl;
371
+ await Promise.all([loadCompiler(moduleUrl), loadManifest(manifestUrl)]);
372
+ postMessage({ load: true });
373
+ return;
374
+ }
375
+ stdinBufferOcaml = buffer ? new Int32Array(buffer) : null;
376
+ if (log) {
377
+ console.log(`[wasm-idle:ocaml-worker] compile start prepare=${prepare} target=${target}`);
378
+ }
379
+ postMessage({ progress: { stage: 'compile-bootstrap', percent: 10 } });
380
+ const [compilerModule, manifest] = await Promise.all([
381
+ loadCompiler(moduleUrl),
382
+ loadManifest(manifestUrl)
383
+ ]);
384
+ postMessage({ progress: { stage: 'compile-ready', percent: 25 } });
385
+ const compileKey = `${target}\n${code}`;
386
+ let compiledFresh = false;
387
+ if (!compiledResult || compiledCacheKey !== compileKey) {
388
+ const result = await compilerModule.compile({
389
+ files: {
390
+ 'main.ml': code
391
+ },
392
+ entry: 'main.ml',
393
+ target,
394
+ effectsMode: 'cps'
395
+ }, {
396
+ system: compilerModule.createBrowserWorkerSystemDispatcher({
397
+ manifest
398
+ }),
399
+ toolchainRoot: '/static/toolchain'
400
+ });
401
+ compiledResult = result;
402
+ compiledCacheKey = compileKey;
403
+ compiledFresh = true;
404
+ }
405
+ const result = compiledResult;
406
+ if (log) {
407
+ console.log(`[wasm-idle:ocaml-worker] compile settled success=${result.success} diagnostics=${result.diagnostics?.length || 0}`);
408
+ }
409
+ if (compiledFresh && result.stdout) {
410
+ postMessage({ output: result.stdout });
411
+ }
412
+ if ((compiledFresh || !result.success) && result.stderr) {
413
+ postMessage({ output: result.stderr });
414
+ }
415
+ for (const diagnostic of result.diagnostics || []) {
416
+ postMessage({
417
+ diagnostic: {
418
+ fileName: diagnostic.file ?? null,
419
+ lineNumber: Math.max(1, Number(diagnostic.line || 1)),
420
+ columnNumber: typeof diagnostic.column === 'number'
421
+ ? Math.max(1, diagnostic.column)
422
+ : undefined,
423
+ severity: diagnostic.severity === 'warning' || diagnostic.severity === 'other'
424
+ ? diagnostic.severity
425
+ : 'error',
426
+ message: String(diagnostic.message || '')
427
+ }
428
+ });
429
+ }
430
+ postMessage({ progress: { stage: 'compile-finished', percent: 90 } });
431
+ if (!result.success) {
432
+ throw new Error(result.stderr ||
433
+ result.diagnostics?.map((diagnostic) => diagnostic.message || '').join('\n') ||
434
+ 'OCaml compilation failed');
435
+ }
436
+ if (prepare) {
437
+ postMessage({ results: true });
438
+ return;
439
+ }
440
+ postMessage({ progress: { stage: 'runtime-start', percent: 95 } });
441
+ await executeCompileResult(result, log);
442
+ }
443
+ catch (error) {
444
+ if (log) {
445
+ console.error('[wasm-idle:ocaml-worker] failed', error);
446
+ }
447
+ postMessage({ error: error?.message || String(error) });
448
+ }
449
+ };
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,137 @@
1
+ import { ConsoleStdout, Fd, WASI, WASIProcExit, wasi } from '@bjorn3/browser_wasi_shim';
2
+ import { waitForBufferedStdin } from '../stdinBuffer';
3
+ self.document = {
4
+ querySelectorAll() {
5
+ return [];
6
+ }
7
+ };
8
+ const encoder = new TextEncoder();
9
+ const decoder = new TextDecoder();
10
+ let stdinBufferTinyGo = null;
11
+ let stdinChunkTinyGo = new Uint8Array(0);
12
+ let stdinChunkOffsetTinyGo = 0;
13
+ class TinyGoStdin extends Fd {
14
+ fd_fdstat_get() {
15
+ const fdstat = new wasi.Fdstat(wasi.FILETYPE_CHARACTER_DEVICE, 0);
16
+ fdstat.fs_rights_base = BigInt(wasi.RIGHTS_FD_READ);
17
+ fdstat.fs_rights_inherited = 0n;
18
+ return { ret: wasi.ERRNO_SUCCESS, fdstat };
19
+ }
20
+ fd_filestat_get() {
21
+ return {
22
+ ret: wasi.ERRNO_SUCCESS,
23
+ filestat: new wasi.Filestat(0n, wasi.FILETYPE_CHARACTER_DEVICE, 0n)
24
+ };
25
+ }
26
+ fd_read(size) {
27
+ if (stdinBufferTinyGo === null) {
28
+ return {
29
+ ret: wasi.ERRNO_SUCCESS,
30
+ data: new Uint8Array(0)
31
+ };
32
+ }
33
+ if (stdinChunkOffsetTinyGo >= stdinChunkTinyGo.length) {
34
+ const nextChunk = waitForBufferedStdin(stdinBufferTinyGo, () => postMessage({ buffer: true }));
35
+ if (nextChunk === null) {
36
+ stdinChunkTinyGo = new Uint8Array(0);
37
+ stdinChunkOffsetTinyGo = 0;
38
+ return {
39
+ ret: wasi.ERRNO_SUCCESS,
40
+ data: new Uint8Array(0)
41
+ };
42
+ }
43
+ stdinChunkTinyGo = encoder.encode(nextChunk);
44
+ stdinChunkOffsetTinyGo = 0;
45
+ }
46
+ const end = Math.min(stdinChunkOffsetTinyGo + size, stdinChunkTinyGo.length);
47
+ const data = stdinChunkTinyGo.slice(stdinChunkOffsetTinyGo, end);
48
+ stdinChunkOffsetTinyGo = end;
49
+ return {
50
+ ret: wasi.ERRNO_SUCCESS,
51
+ data
52
+ };
53
+ }
54
+ }
55
+ self.onmessage = async (event) => {
56
+ const { load, artifact, buffer, args = [], log } = event.data;
57
+ try {
58
+ if (load) {
59
+ postMessage({ load: true });
60
+ return;
61
+ }
62
+ const bytes = artifact instanceof Uint8Array
63
+ ? artifact
64
+ : artifact instanceof ArrayBuffer
65
+ ? new Uint8Array(artifact)
66
+ : ArrayBuffer.isView(artifact)
67
+ ? new Uint8Array(artifact.buffer, artifact.byteOffset, artifact.byteLength)
68
+ : null;
69
+ if (!bytes) {
70
+ throw new Error('TinyGo worker expected a wasm artifact');
71
+ }
72
+ stdinBufferTinyGo = new Int32Array(buffer);
73
+ stdinChunkTinyGo = new Uint8Array(0);
74
+ stdinChunkOffsetTinyGo = 0;
75
+ const stdout = new ConsoleStdout((chunk) => {
76
+ const text = decoder.decode(chunk);
77
+ if (text)
78
+ postMessage({ output: text });
79
+ });
80
+ const stderr = new ConsoleStdout((chunk) => {
81
+ const text = decoder.decode(chunk);
82
+ if (text)
83
+ postMessage({ output: text });
84
+ });
85
+ const wasiRuntime = new WASI(args, ['USER=jungol'], [new TinyGoStdin(), stdout, stderr]);
86
+ const wasmModule = (await WebAssembly.instantiate(bytes, {
87
+ wasi_snapshot_preview1: wasiRuntime.wasiImport,
88
+ wasi_unstable: wasiRuntime.wasiImport
89
+ }));
90
+ const instance = typeof WebAssembly.Instance === 'function' && wasmModule instanceof WebAssembly.Instance
91
+ ? wasmModule
92
+ : 'instance' in wasmModule
93
+ ? wasmModule.instance
94
+ : wasmModule;
95
+ const exportsObject = instance.exports;
96
+ let exitCode = 0;
97
+ try {
98
+ if (typeof exportsObject._start === 'function') {
99
+ exitCode = wasiRuntime.start(instance);
100
+ }
101
+ else if (typeof exportsObject.memory === 'object' &&
102
+ typeof exportsObject.main === 'function') {
103
+ wasiRuntime.initialize(instance);
104
+ if (typeof exportsObject._initialize !== 'function' &&
105
+ typeof exportsObject.__wasm_call_ctors === 'function') {
106
+ exportsObject.__wasm_call_ctors();
107
+ }
108
+ const mainResult = exportsObject.main();
109
+ exitCode = typeof mainResult === 'number' ? mainResult : 0;
110
+ }
111
+ else {
112
+ throw new Error('TinyGo worker expected a WASI artifact with _start or a reactor artifact with main');
113
+ }
114
+ }
115
+ catch (error) {
116
+ if (error instanceof WASIProcExit) {
117
+ exitCode = error.code;
118
+ }
119
+ else {
120
+ throw error;
121
+ }
122
+ }
123
+ if (log) {
124
+ console.log(`[wasm-idle:tinygo-worker] wasi run complete exitCode=${String(exitCode)}`);
125
+ }
126
+ if (exitCode !== 0) {
127
+ throw new Error(`TinyGo program exited with code ${exitCode}`);
128
+ }
129
+ postMessage({ results: true });
130
+ }
131
+ catch (error) {
132
+ if (log) {
133
+ console.error('[wasm-idle:tinygo-worker] failed', error);
134
+ }
135
+ postMessage({ error: error?.message || String(error) });
136
+ }
137
+ };