wasm-idle 1.1.0 → 1.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.
Files changed (145) hide show
  1. package/README.md +140 -37
  2. package/dist/playground/assetBridge.js +106 -31
  3. package/dist/playground/assets.d.ts +2 -2
  4. package/dist/playground/c3.js +22 -0
  5. package/dist/playground/c3Memory.d.ts +19 -0
  6. package/dist/playground/c3Memory.js +117 -0
  7. package/dist/playground/cachedClang.js +143 -34
  8. package/dist/playground/clangAssetIntegrity.js +6 -6
  9. package/dist/playground/clangWorkerProtocol.d.ts +12 -1
  10. package/dist/playground/cobol.d.ts +3 -0
  11. package/dist/playground/cobol.js +10 -1
  12. package/dist/playground/commonlispAssets.d.ts +2 -2
  13. package/dist/playground/compiledArtifactCache.d.ts +3 -1
  14. package/dist/playground/compiledArtifactCache.js +31 -5
  15. package/dist/playground/container/ContainerExplorer.svelte +307 -0
  16. package/dist/playground/container/ContainerExplorer.svelte.d.ts +14 -0
  17. package/dist/playground/container/binding.d.ts +26 -0
  18. package/dist/playground/container/binding.js +247 -0
  19. package/dist/playground/container/clang-runtime.d.ts +38 -0
  20. package/dist/playground/container/clang-runtime.js +343 -0
  21. package/dist/playground/container/client.d.ts +15 -0
  22. package/dist/playground/container/client.js +189 -0
  23. package/dist/playground/container/explorer.d.ts +31 -0
  24. package/dist/playground/container/explorer.js +112 -0
  25. package/dist/playground/container/owner.worker.js +390 -0
  26. package/dist/playground/container/protocol.d.ts +73 -0
  27. package/dist/playground/container/protocol.js +28 -0
  28. package/dist/playground/container/release.d.ts +31 -0
  29. package/dist/playground/container/release.js +612 -0
  30. package/dist/playground/container/runtime-adapter.d.ts +73 -0
  31. package/dist/playground/container/runtime-adapter.js +474 -0
  32. package/dist/playground/container/shell-control.d.ts +49 -0
  33. package/dist/playground/container/shell-control.js +303 -0
  34. package/dist/playground/container/workspace.d.ts +8 -0
  35. package/dist/playground/container/workspace.js +29 -0
  36. package/dist/playground/d.d.ts +6 -1
  37. package/dist/playground/d.js +83 -5
  38. package/dist/playground/dotnet.js +6 -8
  39. package/dist/playground/fortran.d.ts +3 -0
  40. package/dist/playground/fortran.js +10 -1
  41. package/dist/playground/go.js +14 -3
  42. package/dist/playground/goDebugInstrumentation.js +184 -54
  43. package/dist/playground/java.js +3 -2
  44. package/dist/playground/javaRuntimeErrors.d.ts +9 -0
  45. package/dist/playground/javaRuntimeErrors.js +60 -0
  46. package/dist/playground/lean4Assets.d.ts +4 -4
  47. package/dist/playground/lfortran.js +10 -0
  48. package/dist/playground/lfortranAssets.d.ts +2 -2
  49. package/dist/playground/nim.js +22 -0
  50. package/dist/playground/objectivec.d.ts +3 -0
  51. package/dist/playground/objectivec.js +11 -2
  52. package/dist/playground/options.d.ts +2 -0
  53. package/dist/playground/python.js +8 -0
  54. package/dist/playground/runtimeAssetCache.d.ts +1 -32
  55. package/dist/playground/runtimeAssetCache.js +1 -193
  56. package/dist/playground/runtimeSession.d.ts +2 -2
  57. package/dist/playground/runtimeSession.js +11 -11
  58. package/dist/playground/rust.d.ts +6 -0
  59. package/dist/playground/rust.js +103 -6
  60. package/dist/playground/staticStdinRing.d.ts +2 -1
  61. package/dist/playground/staticStdinRing.js +21 -10
  62. package/dist/playground/staticWorkerRuntime.d.ts +15 -1
  63. package/dist/playground/staticWorkerRuntime.js +119 -17
  64. package/dist/playground/tinygo.d.ts +3 -0
  65. package/dist/playground/tinygo.js +81 -6
  66. package/dist/playground/v.d.ts +3 -0
  67. package/dist/playground/v.js +10 -1
  68. package/dist/playground/wasmAwkVersion.d.ts +13 -13
  69. package/dist/playground/wasmAwkVersion.js +4 -4
  70. package/dist/playground/wasmBqnVersion.d.ts +2 -2
  71. package/dist/playground/wasmBqnVersion.js +2 -2
  72. package/dist/playground/wasmC3Version.d.ts +2 -2
  73. package/dist/playground/wasmC3Version.js +2 -2
  74. package/dist/playground/wasmClojureScriptVersion.d.ts +2 -2
  75. package/dist/playground/wasmClojureScriptVersion.js +2 -2
  76. package/dist/playground/wasmCommonLispVersion.d.ts +2 -2
  77. package/dist/playground/wasmCommonLispVersion.js +2 -2
  78. package/dist/playground/wasmDIntegrity.d.ts +5 -5
  79. package/dist/playground/wasmDIntegrity.js +5 -5
  80. package/dist/playground/wasmDVersion.d.ts +16 -1
  81. package/dist/playground/wasmDVersion.js +16 -1
  82. package/dist/playground/wasmDebugVersion.d.ts +1 -1
  83. package/dist/playground/wasmDebugVersion.js +1 -1
  84. package/dist/playground/wasmForthVersion.d.ts +2 -2
  85. package/dist/playground/wasmForthVersion.js +2 -2
  86. package/dist/playground/wasmGleamVersion.d.ts +2 -2
  87. package/dist/playground/wasmGleamVersion.js +2 -2
  88. package/dist/playground/wasmGrainVersion.d.ts +2 -2
  89. package/dist/playground/wasmGrainVersion.js +2 -2
  90. package/dist/playground/wasmJVersion.d.ts +2 -2
  91. package/dist/playground/wasmJVersion.js +2 -2
  92. package/dist/playground/wasmJanetVersion.js +2 -2
  93. package/dist/playground/wasmJuliaVersion.js +2 -2
  94. package/dist/playground/wasmLean4Version.d.ts +5 -5
  95. package/dist/playground/wasmLean4Version.js +5 -5
  96. package/dist/playground/wasmLfortranVersion.d.ts +2 -2
  97. package/dist/playground/wasmLfortranVersion.js +2 -2
  98. package/dist/playground/wasmNimVersion.d.ts +9 -9
  99. package/dist/playground/wasmNimVersion.js +6 -6
  100. package/dist/playground/wasmOcamlVersion.d.ts +2 -2
  101. package/dist/playground/wasmOcamlVersion.js +1 -1
  102. package/dist/playground/wasmPascalVersion.js +2 -2
  103. package/dist/playground/wasmPerlVersion.js +2 -2
  104. package/dist/playground/wasmPrologVersion.d.ts +2 -2
  105. package/dist/playground/wasmPrologVersion.js +2 -2
  106. package/dist/playground/wasmReScriptVersion.d.ts +2 -2
  107. package/dist/playground/wasmReScriptVersion.js +2 -2
  108. package/dist/playground/wasmRustVersion.d.ts +8 -8
  109. package/dist/playground/wasmRustVersion.js +7 -7
  110. package/dist/playground/wasmTclVersion.d.ts +4 -4
  111. package/dist/playground/wasmTclVersion.js +2 -2
  112. package/dist/playground/wasmTinyGoVersion.d.ts +14 -14
  113. package/dist/playground/wasmTinyGoVersion.js +14 -14
  114. package/dist/playground/wasmTypeScriptVersion.d.ts +5 -5
  115. package/dist/playground/wasmTypeScriptVersion.js +5 -5
  116. package/dist/playground/worker/cachedClangCompile.js +52 -4
  117. package/dist/playground/worker/cachedClangExecute.js +6 -5
  118. package/dist/playground/worker/clang.js +32 -10
  119. package/dist/playground/worker/cobol.js +2 -2
  120. package/dist/playground/worker/d.js +12 -5
  121. package/dist/playground/worker/fortran.js +2 -2
  122. package/dist/playground/worker/go.js +16 -5
  123. package/dist/playground/worker/haskell.js +10 -7
  124. package/dist/playground/worker/java.js +75 -10
  125. package/dist/playground/worker/lua.js +2 -3
  126. package/dist/playground/worker/php.js +15 -9
  127. package/dist/playground/worker/python.js +46 -9
  128. package/dist/playground/worker/pythonExecution.js +17 -1
  129. package/dist/playground/worker/pythonWorkspaceFiles.d.ts +6 -0
  130. package/dist/playground/worker/pythonWorkspaceFiles.js +26 -0
  131. package/dist/playground/worker/pythonWorkspaceImports.d.ts +4 -0
  132. package/dist/playground/worker/pythonWorkspaceImports.js +129 -0
  133. package/dist/playground/worker/r.js +13 -6
  134. package/dist/playground/worker/rust.js +34 -2
  135. package/dist/playground/worker/sqlite.js +4 -3
  136. package/dist/playground/worker/tinygo.js +11 -3
  137. package/dist/playground/worker/typescript.js +2 -3
  138. package/dist/playground/worker/v.js +2 -2
  139. package/dist/playground/worker/wat.js +2 -3
  140. package/dist/playground/worker/zig.js +20 -6
  141. package/dist/playground/workerSession.d.ts +1 -28
  142. package/dist/playground/workerSession.js +1 -164
  143. package/package.json +20 -13
  144. package/dist/playground/worker/python.runtime.cases.js +0 -374
  145. /package/dist/playground/{worker/python.runtime.cases.d.ts → container/owner.worker.d.ts} +0 -0
@@ -0,0 +1,38 @@
1
+ import type { Container } from '@wasm-idle/container';
2
+ import { type SandboxExecutionOptions } from '../options';
3
+ import type { ProgressLike } from '@wasm-idle/core';
4
+ import type { ContainerWorkerEvent } from './protocol';
5
+ import type { WebsiteRuntimeAdapter } from './runtime-adapter';
6
+ type Profile = {
7
+ container: Container;
8
+ prepareCommand(invocation: unknown, signal: AbortSignal): Promise<void>;
9
+ };
10
+ /** The Run button uses the same real Clang command and artifacts as Bash. Debug uses its qualified backend. */
11
+ export declare class WebsiteClangRuntime {
12
+ #private;
13
+ private readonly profile;
14
+ private readonly files;
15
+ private readonly emit;
16
+ constructor(profile: Profile, files: WebsiteRuntimeAdapter, emit: (event: ContainerWorkerEvent) => void);
17
+ private queue;
18
+ private key;
19
+ private drain;
20
+ private success;
21
+ private headerKey;
22
+ private discardHeader;
23
+ prepare(language: string, code: string, args: string[], options: SandboxExecutionOptions, signal: AbortSignal, progress: ProgressLike | undefined, requestId: number): Promise<boolean>;
24
+ run(language: string, code: string, args: string[], options: SandboxExecutionOptions, signal: AbortSignal, progress: ProgressLike | undefined, requestId: number): Promise<boolean>;
25
+ private flush;
26
+ write(language: string, text: string): Promise<void>;
27
+ eof(language: string): Promise<void>;
28
+ stop(language: string): Promise<void>;
29
+ clear(language: string): void;
30
+ inspect(): {
31
+ language: string;
32
+ phase: "run" | "prepare";
33
+ pid: number | undefined;
34
+ filesystem: string;
35
+ }[];
36
+ dispose(): Promise<void>;
37
+ }
38
+ export {};
@@ -0,0 +1,343 @@
1
+ import { resolveSandboxExecutionArgs } from '../options';
2
+ /** The Run button uses the same real Clang command and artifacts as Bash. Debug uses its qualified backend. */
3
+ export class WebsiteClangRuntime {
4
+ profile;
5
+ files;
6
+ emit;
7
+ #active = new Map();
8
+ #prepared = new Map();
9
+ #input = new Map();
10
+ #header;
11
+ constructor(profile, files, emit) {
12
+ this.profile = profile;
13
+ this.files = files;
14
+ this.emit = emit;
15
+ }
16
+ queue(language) {
17
+ let value = this.#input.get(language);
18
+ if (!value)
19
+ this.#input.set(language, (value = { chunks: [], size: 0, eof: false }));
20
+ return value;
21
+ }
22
+ key(language, code, args, options) {
23
+ return JSON.stringify([
24
+ language,
25
+ code,
26
+ args,
27
+ options.cppVersion,
28
+ options.cVersion,
29
+ options.compileArgs,
30
+ options.activePath,
31
+ options.env,
32
+ options.workspaceFiles
33
+ ]);
34
+ }
35
+ async drain(process, language, requestId, diagnostics = false, quiet = false) {
36
+ const output = async (stream, stderr) => {
37
+ const reader = stream.getReader(), decoder = new TextDecoder();
38
+ let pending = '';
39
+ try {
40
+ for (;;) {
41
+ const { value, done } = await reader.read();
42
+ const text = done ? decoder.decode() : decoder.decode(value, { stream: true });
43
+ if (text && !quiet)
44
+ this.emit({ language, name: 'output', data: text, requestId });
45
+ if (stderr && diagnostics) {
46
+ pending += text;
47
+ const lines = pending.split('\n');
48
+ pending = lines.pop() ?? '';
49
+ for (const line of lines) {
50
+ const match = /^(.*?):(\d+):(\d+): (fatal error|error|warning|note): (.*)$/.exec(line);
51
+ if (match)
52
+ this.emit({
53
+ language,
54
+ name: 'diagnostic',
55
+ requestId,
56
+ data: {
57
+ fileName: match[1].replace(`/workspace/${language}/`, '/workspace/'),
58
+ lineNumber: Number(match[2]),
59
+ columnNumber: Number(match[3]),
60
+ severity: match[4].includes('error')
61
+ ? 'error'
62
+ : match[4] === 'warning'
63
+ ? 'warning'
64
+ : 'other',
65
+ message: match[5]
66
+ }
67
+ });
68
+ }
69
+ if (pending.length > 65536)
70
+ pending = '';
71
+ }
72
+ if (done)
73
+ break;
74
+ }
75
+ }
76
+ finally {
77
+ reader.releaseLock();
78
+ }
79
+ };
80
+ const [result] = await Promise.all([
81
+ process.exited,
82
+ output(process.stdout, false),
83
+ output(process.stderr, true)
84
+ ]);
85
+ return result;
86
+ }
87
+ success(result, signal) {
88
+ if (signal.aborted)
89
+ throw signal.reason ?? new DOMException('Command cancelled', 'AbortError');
90
+ if (result.reason !== 'exited')
91
+ throw new Error(`Container command ended with ${result.reason}`);
92
+ return result.code === 0;
93
+ }
94
+ headerKey(language, code, compileArgs, options, cwd, standard) {
95
+ // A textual prefix and the fixed system headers are the only admitted inputs.
96
+ // Custom arguments, environment paths, or workspace headers can alter declarations.
97
+ if (language !== 'CPP' ||
98
+ compileArgs.length ||
99
+ Object.keys(options.env ?? {}).length ||
100
+ options.workspaceFiles?.some((file) => file.path !== options.activePath && !/\.(?:c|cc|cpp|cxx|wasm)$/i.test(file.path)))
101
+ return;
102
+ let rest = code.replace(/^\uFEFF/, '');
103
+ for (;;) {
104
+ rest = rest.replace(/^\s+/, '');
105
+ if (rest.startsWith('//')) {
106
+ const end = rest.indexOf('\n');
107
+ if (end < 0 || rest.slice(0, end).trimEnd().endsWith('\\'))
108
+ return;
109
+ rest = rest.slice(end + 1);
110
+ }
111
+ else if (rest.startsWith('/*')) {
112
+ const end = rest.indexOf('*/', 2);
113
+ if (end < 0)
114
+ return;
115
+ rest = rest.slice(end + 2);
116
+ }
117
+ else
118
+ break;
119
+ }
120
+ if (!/^#[ \t]*include[ \t]*<bits\/stdc\+\+\.h>/.test(rest))
121
+ return;
122
+ return JSON.stringify([cwd, standard, '-fno-exceptions']);
123
+ }
124
+ discardHeader() {
125
+ if (!this.#header)
126
+ return;
127
+ try {
128
+ this.profile.container.fs.unlink(this.#header.path);
129
+ }
130
+ catch {
131
+ /* Already removed. */
132
+ }
133
+ this.#header = undefined;
134
+ }
135
+ async prepare(language, code, args, options, signal, progress, requestId) {
136
+ if (this.#active.has(language))
137
+ throw new Error(`${language} already has an active command`);
138
+ const input = this.files.inputs(language, code, options);
139
+ const cwd = this.files.directory(language), artifact = `${cwd}/program.wasm`;
140
+ const job = { language, phase: 'prepare', signal };
141
+ this.#active.set(language, job);
142
+ this.#prepared.delete(language);
143
+ try {
144
+ progress?.report?.({
145
+ kind: 'activity',
146
+ phase: 'initializing',
147
+ label: 'Preparing the Clang toolchain'
148
+ });
149
+ await this.profile.prepareCommand({ path: '/usr/local/bin/clang' }, signal);
150
+ const { compileArgs } = resolveSandboxExecutionArgs(language, args, options);
151
+ const standard = language === 'CPP'
152
+ ? (options.cppVersion ?? 'CPP20').replace(/^CPP/, 'c++').toLowerCase()
153
+ : options.cVersion?.toLowerCase();
154
+ // An opaque response file or -x override can select C++ for a C editor.
155
+ // Keep its default source mode C while enabling the C++ driver's link libraries.
156
+ const override = compileArgs.some((arg) => arg.startsWith('@') || arg.startsWith('-x'));
157
+ const common = [
158
+ ...(language === 'CPP' || override ? ['--driver-mode=g++'] : []),
159
+ ...(language === 'C' && override ? ['-x', 'c'] : []),
160
+ ...(standard ? [`-std=${standard}`] : []),
161
+ '-fno-color-diagnostics',
162
+ '-fno-exceptions',
163
+ ...compileArgs
164
+ ];
165
+ const compile = async (argv, quiet = false) => {
166
+ job.process = await this.profile.container.spawn('clang', argv, {
167
+ cwd,
168
+ signal,
169
+ env: options.env
170
+ });
171
+ const drained = this.drain(job.process, language, requestId, !quiet, quiet);
172
+ await job.process.stdin.getWriter().close();
173
+ const result = await drained;
174
+ if (job.stopped)
175
+ throw new DOMException('Command cancelled', 'AbortError');
176
+ return this.success(result, signal);
177
+ };
178
+ const headerKey = this.headerKey(language, input.code, compileArgs, input.options, cwd, standard);
179
+ if (headerKey && this.#header?.key !== headerKey)
180
+ this.discardHeader();
181
+ let header = headerKey === this.#header?.key ? this.#header : undefined;
182
+ if (header) {
183
+ // Restore owned bytes instead of trusting a writable temporary file.
184
+ try {
185
+ try {
186
+ this.profile.container.fs.unlink(header.path);
187
+ }
188
+ catch {
189
+ /* Already removed. */
190
+ }
191
+ this.profile.container.fs.writeFile(header.path, header.bytes);
192
+ }
193
+ catch {
194
+ this.discardHeader();
195
+ header = undefined;
196
+ }
197
+ }
198
+ let okay = await compile([
199
+ ...common,
200
+ ...(header ? ['-include-pch', header.path] : []),
201
+ input.options.activePath,
202
+ '-lc-printscan-long-double',
203
+ '-o',
204
+ artifact
205
+ ]);
206
+ if (header && okay)
207
+ progress?.set?.(95, 'Compiled with precompiled <bits/stdc++.h>');
208
+ else if (header) {
209
+ this.discardHeader();
210
+ okay = await compile([
211
+ ...common,
212
+ input.options.activePath,
213
+ '-lc-printscan-long-double',
214
+ '-o',
215
+ artifact
216
+ ]);
217
+ }
218
+ if (okay && headerKey && !this.#header) {
219
+ const path = `/tmp/wasm-idle-stdcpp-${crypto.randomUUID()}.pch`;
220
+ try {
221
+ if (await compile([
222
+ ...common,
223
+ '-c',
224
+ '-x',
225
+ 'c++-header',
226
+ '/usr/share/wasm-sysroot/include/bits/stdc++.h',
227
+ '-o',
228
+ path
229
+ ], true)) {
230
+ const bytes = this.profile.container.fs.readFile(path);
231
+ if (bytes.byteLength > 0 && bytes.byteLength <= 32 * 1024 * 1024)
232
+ this.#header = { key: headerKey, path, bytes };
233
+ }
234
+ }
235
+ catch (error) {
236
+ if (signal.aborted || job.stopped)
237
+ throw error;
238
+ }
239
+ finally {
240
+ if (this.#header?.path !== path) {
241
+ try {
242
+ this.profile.container.fs.unlink(path);
243
+ }
244
+ catch {
245
+ /* No usable header. */
246
+ }
247
+ }
248
+ }
249
+ }
250
+ if (okay)
251
+ this.#prepared.set(language, this.key(language, input.code, args, options));
252
+ return okay;
253
+ }
254
+ finally {
255
+ if (this.#active.get(language) === job)
256
+ this.#active.delete(language);
257
+ }
258
+ }
259
+ async run(language, code, args, options, signal, progress, requestId) {
260
+ const input = this.files.inputs(language, code, options);
261
+ if (this.#prepared.get(language) !== this.key(language, input.code, args, options) &&
262
+ !(await this.prepare(language, code, args, options, signal, progress, requestId)))
263
+ return false;
264
+ const cwd = this.files.directory(language), job = { language, phase: 'run', signal };
265
+ if (this.#active.has(language))
266
+ throw new Error(`${language} already has an active command`);
267
+ this.#active.set(language, job);
268
+ try {
269
+ const { programArgs } = resolveSandboxExecutionArgs(language, args, options);
270
+ job.process = await this.profile.container.spawn('wasm-run', [`${cwd}/program.wasm`, ...programArgs], { cwd, signal, env: options.env });
271
+ job.writer = job.process.stdin.getWriter();
272
+ progress?.report?.({
273
+ kind: 'ready',
274
+ state: 'running',
275
+ reason: 'started',
276
+ label: 'Container program started'
277
+ });
278
+ const drained = this.drain(job.process, language, requestId);
279
+ if (options.stdin !== undefined) {
280
+ await job.writer.write(new TextEncoder().encode(options.stdin));
281
+ await job.writer.close();
282
+ }
283
+ else
284
+ await this.flush(language);
285
+ return this.success(await drained, signal);
286
+ }
287
+ finally {
288
+ job.writer?.releaseLock();
289
+ this.#input.delete(language);
290
+ if (this.#active.get(language) === job)
291
+ this.#active.delete(language);
292
+ }
293
+ }
294
+ async flush(language) {
295
+ const writer = this.#active.get(language)?.writer;
296
+ if (!writer)
297
+ return;
298
+ const queue = this.queue(language);
299
+ for (const text of queue.chunks.splice(0)) {
300
+ const bytes = new TextEncoder().encode(text);
301
+ await writer.write(bytes);
302
+ queue.size -= bytes.byteLength;
303
+ }
304
+ if (queue.eof) {
305
+ queue.eof = false;
306
+ await writer.close();
307
+ }
308
+ }
309
+ async write(language, text) {
310
+ const q = this.queue(language);
311
+ q.size += new TextEncoder().encode(text).length;
312
+ if (q.size > 8 * 1024 * 1024)
313
+ throw new Error('Pending stdin exceeds its limit');
314
+ q.chunks.push(text);
315
+ await this.flush(language);
316
+ }
317
+ async eof(language) {
318
+ this.queue(language).eof = true;
319
+ await this.flush(language);
320
+ }
321
+ async stop(language) {
322
+ this.#input.delete(language);
323
+ const job = this.#active.get(language);
324
+ if (job)
325
+ job.stopped = true;
326
+ await job?.process?.terminate();
327
+ }
328
+ clear(language) {
329
+ this.#input.delete(language);
330
+ }
331
+ inspect() {
332
+ return [...this.#active.values()].map((job) => ({
333
+ language: job.language,
334
+ phase: job.phase,
335
+ pid: job.process?.pid,
336
+ filesystem: 'live'
337
+ }));
338
+ }
339
+ async dispose() {
340
+ await Promise.allSettled([...this.#active.keys()].map((language) => this.stop(language)));
341
+ this.discardHeader();
342
+ }
343
+ }
@@ -0,0 +1,15 @@
1
+ import { type ContainerWorkerCallOptions, type ContainerWorkerEvent, type ContainerWorkerInitialization } from './protocol';
2
+ export interface ContainerWorkerClient {
3
+ call<T = unknown>(method: string, args?: unknown[], options?: ContainerWorkerCallOptions): Promise<T>;
4
+ onEvent(listener: (event: ContainerWorkerEvent) => void): () => void;
5
+ dispose(): Promise<void>;
6
+ }
7
+ type OwnerWorker = Pick<Worker, 'postMessage' | 'addEventListener' | 'removeEventListener' | 'terminate'>;
8
+ interface ClientOptions extends ContainerWorkerInitialization {
9
+ /** Injection is used only by deterministic transport tests. */
10
+ workerFactory?: () => OwnerWorker;
11
+ initializationTimeoutMs?: number;
12
+ cleanupDeadlineMs?: number;
13
+ }
14
+ export declare function createContainerWorkerClient(options: ClientOptions): ContainerWorkerClient;
15
+ export {};
@@ -0,0 +1,189 @@
1
+ import { CONTAINER_WORKER_PROTOCOL, OWNER_CLEANUP_DEADLINE_MS, snapshotContainerArguments } from './protocol';
2
+ export function createContainerWorkerClient(options) {
3
+ const channel = crypto.randomUUID();
4
+ const worker = options.workerFactory?.() ??
5
+ new Worker(new URL('./owner.worker.ts', import.meta.url), {
6
+ type: 'module',
7
+ name: 'wasm-idle-container-owner'
8
+ });
9
+ const listeners = new Set();
10
+ const pending = new Map();
11
+ let nextId = 1, disposed = false, disposal;
12
+ let retirementError;
13
+ const cleanupDeadlineMs = options.cleanupDeadlineMs ?? OWNER_CLEANUP_DEADLINE_MS;
14
+ const abortError = () => new DOMException('Container operation cancelled', 'AbortError');
15
+ const post = (data) => worker.postMessage(data);
16
+ const retire = (error) => {
17
+ if (disposed)
18
+ return;
19
+ retirementError = error;
20
+ disposed = true;
21
+ worker.removeEventListener('message', message);
22
+ worker.removeEventListener('error', crash);
23
+ worker.removeEventListener('messageerror', crash);
24
+ worker.terminate();
25
+ for (const entry of pending.values()) {
26
+ entry.cleanup();
27
+ entry.reject(error);
28
+ }
29
+ pending.clear();
30
+ listeners.clear();
31
+ };
32
+ const crash = (event) => {
33
+ if (event.type === 'messageerror') {
34
+ retire(new Error('The container owner Worker message could not be decoded'));
35
+ return;
36
+ }
37
+ const failure = event;
38
+ retire(failure.error ?? new Error(failure.message || 'The container owner Worker crashed'));
39
+ };
40
+ const message = (event) => {
41
+ const data = event.data;
42
+ if (!data || data.version !== CONTAINER_WORKER_PROTOCOL || data.channel !== channel)
43
+ return;
44
+ if (data.type === 'event') {
45
+ for (const listener of listeners) {
46
+ try {
47
+ listener(data.event);
48
+ }
49
+ catch (error) {
50
+ console.error('Container UI event handler failed', error);
51
+ }
52
+ }
53
+ return;
54
+ }
55
+ const entry = pending.get(data.id);
56
+ if (!entry)
57
+ return;
58
+ if (data.type === 'progress') {
59
+ try {
60
+ if (data.progress.kind === 'set')
61
+ entry.options?.progress?.set?.(data.progress.value, data.progress.stage);
62
+ else
63
+ entry.options?.progress?.report?.(data.progress.event);
64
+ }
65
+ catch (error) {
66
+ console.error('Container UI progress handler failed', error);
67
+ }
68
+ return;
69
+ }
70
+ entry.cleanup();
71
+ pending.delete(data.id);
72
+ if (data.type === 'error') {
73
+ const error = Object.assign(new Error(data.error.message), {
74
+ name: data.error.name,
75
+ code: data.error.code
76
+ });
77
+ entry.reject(error);
78
+ }
79
+ else
80
+ entry.resolve(data.value);
81
+ };
82
+ worker.addEventListener('message', message);
83
+ worker.addEventListener('error', crash);
84
+ worker.addEventListener('messageerror', crash);
85
+ const initialized = new Promise((resolve, reject) => {
86
+ const timer = setTimeout(() => retire(new Error('Container owner initialization timed out')), options.initializationTimeoutMs ?? 60_000);
87
+ pending.set(0, { resolve: () => resolve(), reject, cleanup: () => clearTimeout(timer) });
88
+ try {
89
+ post({
90
+ version: CONTAINER_WORKER_PROTOCOL,
91
+ channel,
92
+ id: 0,
93
+ type: 'init',
94
+ settings: snapshotContainerArguments({
95
+ runtimeAssets: options.runtimeAssets,
96
+ applicationRootURL: options.applicationRootURL,
97
+ workspaceId: options.workspaceId
98
+ })
99
+ });
100
+ }
101
+ catch (error) {
102
+ retire(error);
103
+ }
104
+ });
105
+ void initialized.catch(() => { });
106
+ const call = async (method, args = [], settings = {}) => {
107
+ if (disposed)
108
+ throw retirementError;
109
+ if (settings.signal?.aborted)
110
+ throw abortError();
111
+ // Snapshot before the first await; caller mutation never changes a pending request.
112
+ const values = snapshotContainerArguments(args);
113
+ await initialized;
114
+ if (disposed)
115
+ throw retirementError;
116
+ if (settings.signal?.aborted)
117
+ throw abortError();
118
+ const id = nextId++;
119
+ return await new Promise((resolve, reject) => {
120
+ const cancel = () => {
121
+ const entry = pending.get(id);
122
+ if (!entry || entry.cancelTimer)
123
+ return;
124
+ try {
125
+ post({ version: CONTAINER_WORKER_PROTOCOL, channel, id, type: 'cancel' });
126
+ }
127
+ catch (error) {
128
+ retire(error);
129
+ return;
130
+ }
131
+ entry.cancelTimer = setTimeout(() => retire(new Error('Container owner did not finish cancellation before its cleanup deadline')), cleanupDeadlineMs);
132
+ };
133
+ pending.set(id, {
134
+ resolve: (value) => settings.signal?.aborted ? reject(abortError()) : resolve(value),
135
+ reject,
136
+ options: settings,
137
+ cleanup() {
138
+ settings.signal?.removeEventListener('abort', cancel);
139
+ const timer = pending.get(id)?.cancelTimer;
140
+ if (timer)
141
+ clearTimeout(timer);
142
+ }
143
+ });
144
+ settings.signal?.addEventListener('abort', cancel, { once: true });
145
+ try {
146
+ post({
147
+ version: CONTAINER_WORKER_PROTOCOL,
148
+ channel,
149
+ id,
150
+ type: 'call',
151
+ method,
152
+ args: values
153
+ });
154
+ }
155
+ catch (error) {
156
+ const entry = pending.get(id);
157
+ entry?.cleanup();
158
+ pending.delete(id);
159
+ reject(error);
160
+ }
161
+ if (settings.signal?.aborted)
162
+ cancel();
163
+ });
164
+ };
165
+ return {
166
+ call,
167
+ onEvent(listener) {
168
+ listeners.add(listener);
169
+ return () => listeners.delete(listener);
170
+ },
171
+ dispose() {
172
+ return (disposal ??= (async () => {
173
+ if (disposed)
174
+ return;
175
+ const timer = setTimeout(() => retire(new Error('Container owner disposal deadline expired')), cleanupDeadlineMs);
176
+ try {
177
+ await call('dispose');
178
+ }
179
+ catch {
180
+ /* Retiring the Worker is the final cleanup boundary. */
181
+ }
182
+ finally {
183
+ clearTimeout(timer);
184
+ retire(new Error('Container owner has been disposed'));
185
+ }
186
+ })());
187
+ }
188
+ };
189
+ }
@@ -0,0 +1,31 @@
1
+ import type { ContainerWorkspaceEntry } from './protocol';
2
+ export interface ExplorerEntry extends ContainerWorkspaceEntry {
3
+ name: string;
4
+ parent: string;
5
+ implicit?: boolean;
6
+ }
7
+ export interface ExplorerModel {
8
+ readonly entries: ReadonlyMap<string, ExplorerEntry>;
9
+ readonly children: ReadonlyMap<string, readonly ExplorerEntry[]>;
10
+ }
11
+ export interface ExplorerRow {
12
+ entry: ExplorerEntry;
13
+ depth: number;
14
+ expanded: boolean;
15
+ }
16
+ export declare const EXPLORER_PAGE_SIZE = 256;
17
+ /** Build directory entries without following symbolic links or changing POSIX path spelling. */
18
+ export declare function buildExplorerModel(entries: readonly ContainerWorkspaceEntry[]): ExplorerModel;
19
+ /** Missing or removed folders fall back to the nearest actual directory, then /workspace. */
20
+ export declare function resolveExplorerDirectory(model: ExplorerModel, requested: string): string;
21
+ export declare function explorerBreadcrumbs(directory: string): {
22
+ path: string;
23
+ name: string;
24
+ }[];
25
+ /** Flatten only expanded branches and retain a single page of rows, even in large workspaces. */
26
+ export declare function visibleExplorerRows(model: ExplorerModel, directory: string, expanded: ReadonlySet<string>, requestedOffset?: number): {
27
+ rows: ExplorerRow[];
28
+ total: number;
29
+ offset: number;
30
+ remaining: number;
31
+ };