wasm-idle 1.0.7 → 1.0.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.
Files changed (59) hide show
  1. package/README.md +48 -11
  2. package/dist/playground/applicationAssets.js +18 -0
  3. package/dist/playground/assetBridge.js +7 -0
  4. package/dist/playground/assets.d.ts +70 -0
  5. package/dist/playground/assets.js +120 -0
  6. package/dist/playground/cachedClang.js +43 -5
  7. package/dist/playground/clang.d.ts +5 -0
  8. package/dist/playground/clang.js +96 -0
  9. package/dist/playground/clangAssetIntegrity.d.ts +6 -0
  10. package/dist/playground/clangAssetIntegrity.js +3 -2
  11. package/dist/playground/clangWorkerProtocol.d.ts +12 -1
  12. package/dist/playground/fennel.d.ts +9 -0
  13. package/dist/playground/fennel.js +30 -0
  14. package/dist/playground/fennelBootstrap.d.ts +31 -0
  15. package/dist/playground/fennelBootstrap.js +118 -0
  16. package/dist/playground/grain.d.ts +9 -0
  17. package/dist/playground/grain.js +127 -0
  18. package/dist/playground/hy.d.ts +21 -0
  19. package/dist/playground/hy.js +24 -0
  20. package/dist/playground/index.d.ts +1 -1
  21. package/dist/playground/index.js +49 -0
  22. package/dist/playground/lua.d.ts +19 -0
  23. package/dist/playground/lua.js +64 -39
  24. package/dist/playground/objectivec.d.ts +3 -1
  25. package/dist/playground/objectivec.js +6 -2
  26. package/dist/playground/options.js +1 -1
  27. package/dist/playground/postgresql.d.ts +36 -0
  28. package/dist/playground/postgresql.js +543 -0
  29. package/dist/playground/python.d.ts +5 -0
  30. package/dist/playground/python.js +13 -0
  31. package/dist/playground/rescript.d.ts +5 -0
  32. package/dist/playground/rescript.js +58 -0
  33. package/dist/playground/rescriptPreflight.d.ts +26 -0
  34. package/dist/playground/rescriptPreflight.js +381 -0
  35. package/dist/playground/staticRuntimeModuleVersion.d.ts +1 -1
  36. package/dist/playground/staticRuntimeModuleVersion.js +1 -1
  37. package/dist/playground/staticRuntimePreflightExecute.d.ts +2 -1
  38. package/dist/playground/staticRuntimePreflightExecute.js +9 -0
  39. package/dist/playground/staticRuntimePreflightProtocol.d.ts +1 -1
  40. package/dist/playground/v.d.ts +46 -0
  41. package/dist/playground/v.js +703 -0
  42. package/dist/playground/wasmFennelVersion.d.ts +23 -0
  43. package/dist/playground/wasmFennelVersion.js +16 -0
  44. package/dist/playground/wasmGrainVersion.d.ts +24 -0
  45. package/dist/playground/wasmGrainVersion.js +25 -0
  46. package/dist/playground/wasmHyVersion.d.ts +7 -0
  47. package/dist/playground/wasmHyVersion.js +15 -0
  48. package/dist/playground/wasmReScriptVersion.d.ts +20 -0
  49. package/dist/playground/wasmReScriptVersion.js +20 -0
  50. package/dist/playground/worker/cachedClangCompile.js +37 -4
  51. package/dist/playground/worker/clang.js +35 -7
  52. package/dist/playground/worker/lua.js +48 -11
  53. package/dist/playground/worker/postgresql.d.ts +1 -0
  54. package/dist/playground/worker/postgresql.js +205 -0
  55. package/dist/playground/worker/python.js +106 -9
  56. package/dist/playground/worker/staticRuntimePreflight.js +2 -1
  57. package/dist/playground/worker/v.d.ts +1 -0
  58. package/dist/playground/worker/v.js +106 -0
  59. package/package.json +18 -7
@@ -0,0 +1,543 @@
1
+ import { resolvePostgresqlRuntimeModuleUrl } from './assets';
2
+ import { WorkerSession } from './workerSession';
3
+ import { reportWorkerProgress } from './workerProgress';
4
+ import { BusyError, CancelledError, DEFAULT_WORKSPACE_LIMITS, DiagnosticLimitError, OutputLimitError, RuntimeConfigurationError, TimeoutError, resolveExecutionLimits, validateExecutionWorkspace } from '@wasm-idle/core';
5
+ const OUTPUT_ENCODER = new TextEncoder();
6
+ const abortReason = (signal, phase) => signal.reason !== undefined
7
+ ? signal.reason
8
+ : new DOMException(phase === 'startup'
9
+ ? 'PostgreSQL runtime startup aborted'
10
+ : 'PostgreSQL execution aborted', 'AbortError');
11
+ class Postgresql {
12
+ output = null;
13
+ worker = null;
14
+ begin = 0;
15
+ elapse = 0;
16
+ uid = 0;
17
+ exit = true;
18
+ moduleUrl = '';
19
+ oncompilerdiagnostic;
20
+ activeOperation = null;
21
+ disposed = false;
22
+ disposePromise = null;
23
+ disposeCancellation = new CancelledError('PostgreSQL sandbox disposed', {
24
+ phase: 'dispose',
25
+ runtimeId: 'POSTGRESQL',
26
+ recoverable: false
27
+ });
28
+ workerSession = new WorkerSession({
29
+ label: 'PostgreSQL',
30
+ onDispose: (worker) => {
31
+ if (this.worker !== worker)
32
+ return;
33
+ delete this.worker;
34
+ this.exit = true;
35
+ }
36
+ });
37
+ beginOperation(phase) {
38
+ if (this.disposed) {
39
+ throw new RuntimeConfigurationError('PostgreSQL sandbox is disposed', {
40
+ phase: 'dispose',
41
+ runtimeId: 'POSTGRESQL'
42
+ });
43
+ }
44
+ if (this.activeOperation) {
45
+ throw new BusyError('PostgreSQL runtime already has an active operation', {
46
+ runtimeId: 'POSTGRESQL',
47
+ phase: this.activeOperation.phase
48
+ });
49
+ }
50
+ const operation = {
51
+ token: Symbol(phase),
52
+ phase,
53
+ cancelled: false,
54
+ cleanedUp: false,
55
+ cleanups: []
56
+ };
57
+ this.activeOperation = operation;
58
+ return operation;
59
+ }
60
+ isOperationActive(operation) {
61
+ return this.activeOperation?.token === operation.token && !operation.cancelled;
62
+ }
63
+ releaseOperation(operation) {
64
+ if (this.activeOperation?.token !== operation.token)
65
+ return false;
66
+ this.activeOperation = null;
67
+ return true;
68
+ }
69
+ cleanupOperation(operation) {
70
+ if (operation.cleanedUp)
71
+ return;
72
+ operation.cleanedUp = true;
73
+ const cleanups = operation.cleanups.splice(0);
74
+ for (const cleanup of cleanups) {
75
+ try {
76
+ cleanup();
77
+ }
78
+ catch {
79
+ // Caller-owned lifecycle cleanup must not replace the operation result.
80
+ }
81
+ }
82
+ }
83
+ releaseBeforeSession(operation, reason) {
84
+ const outcome = operation.cancelled ? operation.cancellationReason : reason;
85
+ this.releaseOperation(operation);
86
+ this.cleanupOperation(operation);
87
+ return outcome;
88
+ }
89
+ bindPreSessionAbort(operation, signal) {
90
+ if (!signal || !this.isOperationActive(operation))
91
+ return () => undefined;
92
+ let registered = false;
93
+ let unbound = false;
94
+ const unbind = () => {
95
+ if (unbound)
96
+ return;
97
+ unbound = true;
98
+ if (registered)
99
+ signal.removeEventListener('abort', onAbort);
100
+ };
101
+ const onAbort = () => {
102
+ if (!this.isOperationActive(operation))
103
+ return;
104
+ let reason;
105
+ try {
106
+ reason = abortReason(signal, operation.phase);
107
+ }
108
+ catch (error) {
109
+ reason = error;
110
+ }
111
+ operation.cancelled = true;
112
+ operation.cancellationReason = reason;
113
+ this.releaseOperation(operation);
114
+ this.cleanupOperation(operation);
115
+ };
116
+ operation.cleanups.push(unbind);
117
+ try {
118
+ registered = true;
119
+ signal.addEventListener('abort', onAbort, { once: true });
120
+ if (signal.aborted)
121
+ onAbort();
122
+ }
123
+ catch (error) {
124
+ if (this.isOperationActive(operation)) {
125
+ operation.cancelled = true;
126
+ operation.cancellationReason = error;
127
+ this.releaseOperation(operation);
128
+ this.cleanupOperation(operation);
129
+ }
130
+ }
131
+ return unbind;
132
+ }
133
+ bindAbortSignal(operation, signal) {
134
+ if (!signal || !this.isOperationActive(operation))
135
+ return;
136
+ let registered = false;
137
+ const onAbort = () => {
138
+ if (!this.isOperationActive(operation))
139
+ return;
140
+ let reason;
141
+ try {
142
+ reason = abortReason(signal, operation.phase);
143
+ }
144
+ catch (error) {
145
+ reason = error;
146
+ }
147
+ this.cancelOperation(operation, reason);
148
+ };
149
+ operation.cleanups.push(() => {
150
+ if (registered)
151
+ signal.removeEventListener('abort', onAbort);
152
+ });
153
+ try {
154
+ registered = true;
155
+ signal.addEventListener('abort', onAbort, { once: true });
156
+ if (signal.aborted)
157
+ onAbort();
158
+ }
159
+ catch (error) {
160
+ this.cancelOperation(operation, error);
161
+ }
162
+ }
163
+ bindOperationTimeout(operation, timeoutMs) {
164
+ if (!this.isOperationActive(operation))
165
+ return;
166
+ let timeout;
167
+ operation.cleanups.push(() => {
168
+ if (timeout !== undefined)
169
+ clearTimeout(timeout);
170
+ });
171
+ try {
172
+ timeout = setTimeout(() => {
173
+ if (!this.isOperationActive(operation))
174
+ return;
175
+ const label = operation.phase === 'startup' ? 'startup' : 'execution';
176
+ this.cancelOperation(operation, new TimeoutError(`PostgreSQL ${label} timed out after ${timeoutMs} ms`, {
177
+ phase: operation.phase,
178
+ runtimeId: 'POSTGRESQL',
179
+ timeoutMs
180
+ }));
181
+ }, timeoutMs);
182
+ if (operation.cleanedUp)
183
+ clearTimeout(timeout);
184
+ }
185
+ catch (error) {
186
+ this.cancelOperation(operation, error);
187
+ }
188
+ }
189
+ cancelOperation(operation, reason) {
190
+ if (!this.isOperationActive(operation))
191
+ return;
192
+ operation.cancelled = true;
193
+ operation.cancellationReason = reason;
194
+ this.activeOperation = null;
195
+ this.uid += 1;
196
+ this.exit = true;
197
+ this.workerSession.terminate(reason);
198
+ this.cleanupOperation(operation);
199
+ }
200
+ load(runtimeAssets = '', _code = '', _log = true, _args = [], options = {}, progress) {
201
+ let activeOperation;
202
+ try {
203
+ activeOperation = this.beginOperation('startup');
204
+ }
205
+ catch (error) {
206
+ return Promise.reject(error);
207
+ }
208
+ let limits;
209
+ let signal;
210
+ let unbindPreSessionAbort = () => undefined;
211
+ try {
212
+ signal = options.signal;
213
+ unbindPreSessionAbort = this.bindPreSessionAbort(activeOperation, signal);
214
+ if (!this.isOperationActive(activeOperation)) {
215
+ return Promise.reject(this.releaseBeforeSession(activeOperation, 'PostgreSQL runtime startup cancelled'));
216
+ }
217
+ limits = resolveExecutionLimits(options.limits);
218
+ if (!this.isOperationActive(activeOperation) || signal?.aborted) {
219
+ return Promise.reject(this.releaseBeforeSession(activeOperation, 'PostgreSQL runtime startup cancelled'));
220
+ }
221
+ unbindPreSessionAbort();
222
+ }
223
+ catch (error) {
224
+ return Promise.reject(this.releaseBeforeSession(activeOperation, error));
225
+ }
226
+ if (!this.isOperationActive(activeOperation)) {
227
+ return Promise.reject(this.releaseBeforeSession(activeOperation, 'PostgreSQL runtime startup cancelled'));
228
+ }
229
+ const loading = this.workerSession.load(async (resolve, reject) => {
230
+ const resolveOperation = () => {
231
+ if (!this.releaseOperation(activeOperation))
232
+ return;
233
+ resolve();
234
+ this.cleanupOperation(activeOperation);
235
+ };
236
+ const rejectOperation = (reason) => {
237
+ if (!this.releaseOperation(activeOperation))
238
+ return;
239
+ reject(reason);
240
+ this.cleanupOperation(activeOperation);
241
+ };
242
+ try {
243
+ if (!this.isOperationActive(activeOperation))
244
+ return;
245
+ const currentUrl = typeof window !== 'undefined' ? window.location.href : '';
246
+ const nextModuleUrl = resolvePostgresqlRuntimeModuleUrl(runtimeAssets, currentUrl);
247
+ if (!this.isOperationActive(activeOperation))
248
+ return;
249
+ if (this.worker && this.moduleUrl !== nextModuleUrl)
250
+ this.workerSession.reset();
251
+ this.moduleUrl = nextModuleUrl;
252
+ if (!this.isOperationActive(activeOperation))
253
+ return;
254
+ if (!this.worker) {
255
+ const WorkerConstructor = (await import('./worker/postgresql?worker')).default;
256
+ if (!this.isOperationActive(activeOperation))
257
+ return;
258
+ const worker = new WorkerConstructor();
259
+ if (!this.isOperationActive(activeOperation)) {
260
+ try {
261
+ worker.terminate();
262
+ }
263
+ catch {
264
+ // The unattached worker is already detached.
265
+ }
266
+ return;
267
+ }
268
+ this.worker = worker;
269
+ this.workerSession.attach(worker);
270
+ let handler;
271
+ handler = (event) => {
272
+ if (!this.isOperationActive(activeOperation) ||
273
+ this.worker !== worker ||
274
+ worker.onmessage !== handler) {
275
+ return;
276
+ }
277
+ try {
278
+ if (event.data?.load) {
279
+ progress?.set?.(1);
280
+ if (!this.isOperationActive(activeOperation) ||
281
+ this.worker !== worker ||
282
+ worker.onmessage !== handler) {
283
+ return;
284
+ }
285
+ resolveOperation();
286
+ return;
287
+ }
288
+ if (event.data?.error !== undefined) {
289
+ rejectOperation(event.data.error);
290
+ }
291
+ }
292
+ catch (error) {
293
+ rejectOperation(error);
294
+ }
295
+ };
296
+ worker.onmessage = handler;
297
+ worker.postMessage({
298
+ load: true,
299
+ moduleUrl: this.moduleUrl,
300
+ log: _log
301
+ });
302
+ }
303
+ else {
304
+ const worker = this.worker;
305
+ progress?.set?.(1);
306
+ if (!this.isOperationActive(activeOperation) || this.worker !== worker)
307
+ return;
308
+ resolveOperation();
309
+ }
310
+ }
311
+ catch (error) {
312
+ rejectOperation(error);
313
+ }
314
+ });
315
+ const timeoutMs = Math.min(2_147_483_647, limits.assetTimeoutMs + limits.startupTimeoutMs);
316
+ this.bindOperationTimeout(activeOperation, timeoutMs);
317
+ this.bindAbortSignal(activeOperation, signal);
318
+ return loading.finally(() => {
319
+ this.releaseOperation(activeOperation);
320
+ this.cleanupOperation(activeOperation);
321
+ });
322
+ }
323
+ write() { }
324
+ eof() { }
325
+ run(code, prepare, _log = true, _prog, _args = [], options = {}) {
326
+ let activeOperation;
327
+ try {
328
+ activeOperation = this.beginOperation('execute');
329
+ }
330
+ catch (error) {
331
+ return Promise.reject(error);
332
+ }
333
+ let limits;
334
+ let signal;
335
+ let unbindPreSessionAbort = () => undefined;
336
+ let workspace;
337
+ let stdin;
338
+ try {
339
+ signal = options.signal;
340
+ unbindPreSessionAbort = this.bindPreSessionAbort(activeOperation, signal);
341
+ if (!this.isOperationActive(activeOperation)) {
342
+ return Promise.reject(this.releaseBeforeSession(activeOperation, 'PostgreSQL execution cancelled'));
343
+ }
344
+ limits = resolveExecutionLimits(options.limits);
345
+ workspace = validateExecutionWorkspace(code, options.workspaceFiles ?? [], options.activePath ?? 'main.sql', {
346
+ ...options.workspaceLimits,
347
+ maxFileBytes: Math.min(options.workspaceLimits?.maxFileBytes ??
348
+ DEFAULT_WORKSPACE_LIMITS.maxFileBytes, limits.maxWorkspaceBytes),
349
+ maxTotalBytes: Math.min(options.workspaceLimits?.maxTotalBytes ??
350
+ DEFAULT_WORKSPACE_LIMITS.maxTotalBytes, limits.maxWorkspaceBytes)
351
+ });
352
+ stdin = options.stdin;
353
+ if (!this.isOperationActive(activeOperation) || signal?.aborted) {
354
+ return Promise.reject(this.releaseBeforeSession(activeOperation, 'PostgreSQL execution cancelled'));
355
+ }
356
+ unbindPreSessionAbort();
357
+ }
358
+ catch (error) {
359
+ return Promise.reject(this.releaseBeforeSession(activeOperation, error));
360
+ }
361
+ if (!this.isOperationActive(activeOperation)) {
362
+ return Promise.reject(this.releaseBeforeSession(activeOperation, 'PostgreSQL execution cancelled'));
363
+ }
364
+ const worker = this.worker;
365
+ if (!worker) {
366
+ return Promise.reject(this.releaseBeforeSession(activeOperation, 'Worker not loaded'));
367
+ }
368
+ this.exit = false;
369
+ const running = new Promise((resolve, reject) => {
370
+ const runUid = ++this.uid;
371
+ let diagnosticCount = 0;
372
+ let outputBytes = 0;
373
+ const workerOperation = this.workerSession.beginRun(worker, reject);
374
+ const timeoutMs = Math.min(2_147_483_647, limits.compileTimeoutMs + limits.runTimeoutMs);
375
+ this.bindOperationTimeout(activeOperation, timeoutMs);
376
+ this.bindAbortSignal(activeOperation, signal);
377
+ if (!this.isOperationActive(activeOperation))
378
+ return;
379
+ let handler;
380
+ const ownsRun = () => this.isOperationActive(activeOperation) &&
381
+ this.worker === worker &&
382
+ worker.onmessage === handler &&
383
+ runUid === this.uid;
384
+ const claimRun = () => {
385
+ if (!ownsRun())
386
+ return false;
387
+ this.elapse = Date.now() - this.begin;
388
+ this.exit = true;
389
+ this.workerSession.complete(workerOperation);
390
+ this.releaseOperation(activeOperation);
391
+ this.cleanupOperation(activeOperation);
392
+ try {
393
+ if (worker.onmessage === handler)
394
+ worker.onmessage = null;
395
+ }
396
+ catch {
397
+ // Handler cleanup must not replace the execution result.
398
+ }
399
+ return true;
400
+ };
401
+ const failRun = (reason, disposeWorker = false) => {
402
+ if (disposeWorker) {
403
+ if (!ownsRun())
404
+ return;
405
+ this.cancelOperation(activeOperation, reason);
406
+ return;
407
+ }
408
+ if (!claimRun())
409
+ return;
410
+ reject(reason);
411
+ };
412
+ handler = (event) => {
413
+ if (this.worker !== worker ||
414
+ worker.onmessage !== handler ||
415
+ !this.isOperationActive(activeOperation)) {
416
+ return;
417
+ }
418
+ try {
419
+ const { output, results, error, diagnostic, progress } = event.data;
420
+ reportWorkerProgress(_prog, progress);
421
+ if (!ownsRun())
422
+ return;
423
+ if (typeof output === 'string' && output.length > 0) {
424
+ const actual = outputBytes + OUTPUT_ENCODER.encode(output).byteLength;
425
+ if (actual > limits.maxOutputBytes) {
426
+ failRun(new OutputLimitError(`PostgreSQL output exceeded ${limits.maxOutputBytes} bytes`, {
427
+ actual,
428
+ limit: limits.maxOutputBytes,
429
+ phase: 'execute',
430
+ runtimeId: 'POSTGRESQL'
431
+ }), true);
432
+ return;
433
+ }
434
+ outputBytes = actual;
435
+ this.output?.(output);
436
+ if (!ownsRun())
437
+ return;
438
+ }
439
+ if (diagnostic !== undefined) {
440
+ const actual = diagnosticCount + 1;
441
+ if (actual > limits.maxDiagnostics) {
442
+ failRun(new DiagnosticLimitError(`PostgreSQL diagnostics exceeded ${limits.maxDiagnostics} messages`, {
443
+ actual,
444
+ limit: limits.maxDiagnostics,
445
+ phase: 'execute',
446
+ runtimeId: 'POSTGRESQL'
447
+ }), true);
448
+ return;
449
+ }
450
+ diagnosticCount = actual;
451
+ this.oncompilerdiagnostic?.(diagnostic);
452
+ if (!ownsRun())
453
+ return;
454
+ }
455
+ if (results !== undefined) {
456
+ if (!claimRun())
457
+ return;
458
+ resolve(results);
459
+ return;
460
+ }
461
+ if (error !== undefined)
462
+ failRun(error);
463
+ }
464
+ catch (error) {
465
+ failRun(error, true);
466
+ }
467
+ };
468
+ worker.onmessage = handler;
469
+ this.begin = Date.now();
470
+ try {
471
+ worker.postMessage({
472
+ code,
473
+ prepare,
474
+ activePath: workspace.activePath ?? 'main.sql',
475
+ workspaceFiles: workspace.workspaceFiles,
476
+ stdin,
477
+ log: _log
478
+ });
479
+ }
480
+ catch (error) {
481
+ failRun(error);
482
+ }
483
+ });
484
+ return running.finally(() => {
485
+ if (this.releaseOperation(activeOperation))
486
+ this.exit = true;
487
+ this.cleanupOperation(activeOperation);
488
+ });
489
+ }
490
+ kill() {
491
+ this.terminate();
492
+ }
493
+ terminate(reason = 'Process terminated') {
494
+ if (this.disposed)
495
+ return;
496
+ const activeOperation = this.activeOperation;
497
+ if (activeOperation) {
498
+ this.cancelOperation(activeOperation, reason);
499
+ return;
500
+ }
501
+ this.uid += 1;
502
+ this.workerSession.terminate(reason);
503
+ this.exit = true;
504
+ }
505
+ async clear() {
506
+ if (this.disposed)
507
+ return;
508
+ if (this.activeOperation) {
509
+ this.terminate();
510
+ return;
511
+ }
512
+ const worker = this.worker;
513
+ if (worker) {
514
+ try {
515
+ worker.onmessage = null;
516
+ }
517
+ catch {
518
+ // Idle handler cleanup is best effort.
519
+ }
520
+ }
521
+ }
522
+ dispose() {
523
+ if (this.disposePromise)
524
+ return this.disposePromise;
525
+ this.disposed = true;
526
+ this.disposePromise = Promise.resolve();
527
+ const activeOperation = this.activeOperation;
528
+ delete this.worker;
529
+ this.moduleUrl = '';
530
+ this.output = null;
531
+ this.oncompilerdiagnostic = undefined;
532
+ if (activeOperation) {
533
+ this.cancelOperation(activeOperation, this.disposeCancellation);
534
+ }
535
+ else {
536
+ this.uid += 1;
537
+ this.workerSession.terminate(this.disposeCancellation);
538
+ this.exit = true;
539
+ }
540
+ return this.disposePromise;
541
+ }
542
+ }
543
+ export default Postgresql;
@@ -31,6 +31,11 @@ declare class Python implements Sandbox {
31
31
  private ownsRuntimeAssetCache;
32
32
  private disposed;
33
33
  private disposal?;
34
+ private workerExtensionKey?;
35
+ /** Language executed by the shared Pyodide worker; subclasses run other Python-hosted languages. */
36
+ protected readonly workerLanguage: 'python' | 'hy';
37
+ /** Extra worker load configuration for Python-hosted languages, keyed for worker reuse. */
38
+ protected workerLoadExtension(_runtimeAssets: string | PlaygroundRuntimeAssets, _currentUrl: string): Record<string, unknown> | undefined;
34
39
  private readonly workerSession;
35
40
  private beginOperation;
36
41
  private isOperationActive;
@@ -34,6 +34,13 @@ class Python {
34
34
  ownsRuntimeAssetCache = true;
35
35
  disposed = false;
36
36
  disposal;
37
+ workerExtensionKey;
38
+ /** Language executed by the shared Pyodide worker; subclasses run other Python-hosted languages. */
39
+ workerLanguage = 'python';
40
+ /** Extra worker load configuration for Python-hosted languages, keyed for worker reuse. */
41
+ workerLoadExtension(_runtimeAssets, _currentUrl) {
42
+ return undefined;
43
+ }
37
44
  workerSession = new WorkerSession({
38
45
  label: 'Python',
39
46
  onDispose: (worker) => {
@@ -206,10 +213,13 @@ class Python {
206
213
  this.waitingForInput = false;
207
214
  this.pendingEof = false;
208
215
  const assetConfig = resolveRuntimeAssetConfig('python', runtimeAssets, typeof window !== 'undefined' ? window.location.href : '', options.persistentCache);
216
+ const extension = this.workerLoadExtension(runtimeAssets, typeof window !== 'undefined' ? window.location.href : '');
217
+ const extensionKey = JSON.stringify(extension ?? null);
209
218
  if (!this.isOperationActive(operation))
210
219
  return;
211
220
  const needsWorkerReset = !this.worker ||
212
221
  !this.assetBridge ||
222
+ this.workerExtensionKey !== extensionKey ||
213
223
  !this.assetBridge.matches(assetConfig, limits.maxAssetBytes);
214
224
  if (needsWorkerReset && this.worker)
215
225
  this.workerSession.reset();
@@ -226,6 +236,7 @@ class Python {
226
236
  return;
227
237
  }
228
238
  this.worker = worker;
239
+ this.workerExtensionKey = extensionKey;
229
240
  this.workerSession.attach(worker);
230
241
  const assetBridge = new WorkerAssetBridge(worker, 'python', assetConfig, progress, limits.maxAssetBytes, false, this.runtimeAssetCache);
231
242
  if (!this.isOperationActive(operation) || this.worker !== worker) {
@@ -263,6 +274,7 @@ class Python {
263
274
  load: true,
264
275
  log,
265
276
  code,
277
+ ...(extension ? { extension } : {}),
266
278
  assets: {
267
279
  baseUrl: assetConfig.baseUrl,
268
280
  maxAssetBytes: limits.maxAssetBytes,
@@ -464,6 +476,7 @@ class Python {
464
476
  worker.postMessage({
465
477
  code,
466
478
  prepare,
479
+ language: this.workerLanguage,
467
480
  buffer: this.buffer,
468
481
  debugBuffer: this.debugBuffer,
469
482
  watchBuffer: this.watchBuffer,
@@ -0,0 +1,5 @@
1
+ import { StaticWorkerRuntimeSandbox } from './staticWorkerRuntime';
2
+ declare class ReScript extends StaticWorkerRuntimeSandbox {
3
+ constructor();
4
+ }
5
+ export default ReScript;
@@ -0,0 +1,58 @@
1
+ import { resolveReScriptRuntimeAssetConfig } from './assets';
2
+ import { preflightStaticRuntimeAssetsInWorker } from './staticRuntimePreflight';
3
+ import { StaticWorkerRuntimeSandbox } from './staticWorkerRuntime';
4
+ import { RuntimeConfigurationError } from '@wasm-idle/core';
5
+ class ReScript extends StaticWorkerRuntimeSandbox {
6
+ constructor() {
7
+ super({
8
+ languageId: 'RESCRIPT',
9
+ displayName: 'ReScript',
10
+ defaultActivePath: 'Main.res',
11
+ stdin: {
12
+ mode: 'streaming',
13
+ sourceHintPattern: /\b(?:readLineSync|readFileSync)\b|\/dev\/stdin\b/
14
+ },
15
+ inlineVerifiedWorker: true,
16
+ runtimePreflightDelivery: 'transfer-owned',
17
+ resolveRuntimeAssets(runtimeAssets, currentUrl) {
18
+ const resolved = resolveReScriptRuntimeAssetConfig(runtimeAssets, currentUrl);
19
+ const profile = resolved.preflightProfile;
20
+ if (!/^[a-f0-9]{64}$/u.test(resolved.manifestFingerprint || '') ||
21
+ profile.manifestFingerprint !== resolved.manifestFingerprint ||
22
+ !resolved.workerReceipt) {
23
+ throw new RuntimeConfigurationError('ReScript runtime requires a manifest fingerprint and worker receipt.', { runtimeId: 'RESCRIPT' });
24
+ }
25
+ return resolved;
26
+ },
27
+ async preflightRuntimeAssets(urls, context) {
28
+ const profile = urls.preflightProfile;
29
+ const loadedByAsset = new Map();
30
+ const totalDownloadBytes = (profile.manifestReceipt?.bytes ?? 0) + (profile.compilerReceipt?.bytes ?? 0);
31
+ const payload = await preflightStaticRuntimeAssetsInWorker({
32
+ runtimeId: 'RESCRIPT',
33
+ displayName: 'ReScript',
34
+ baseUrl: urls.baseUrl,
35
+ manifestUrl: urls.manifestUrl || '',
36
+ profile,
37
+ limits: context.limits,
38
+ persistentCache: context.persistentCache,
39
+ signal: context.signal,
40
+ reportProgress(progress) {
41
+ if (progress.kind === 'asset') {
42
+ loadedByAsset.set(progress.progress.assetKey, progress.progress.loadedBytes);
43
+ const loadedBytes = [...loadedByAsset.values()].reduce((total, loaded) => total + loaded, 0);
44
+ const fraction = totalDownloadBytes > 0 ? loadedBytes / totalDownloadBytes : 0;
45
+ context.reportProgress(0.04 + Math.min(1, fraction) * 0.11, `Preflighting ReScript asset ${progress.progress.assetKey}`);
46
+ return;
47
+ }
48
+ const { loadedBytes, totalBytes } = progress;
49
+ const fraction = totalBytes > 0 ? loadedBytes / totalBytes : 0;
50
+ context.reportProgress(0.15 + Math.min(1, fraction) * 0.02, 'Decompressing verified ReScript compiler');
51
+ }
52
+ });
53
+ return context.createOwnedDelivery(payload);
54
+ }
55
+ });
56
+ }
57
+ }
58
+ export default ReScript;