@poe-platform/safe-js 0.1.720 → 0.1.722

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 (88) hide show
  1. package/README.md +50 -7
  2. package/dist/config-mutations/types.d.ts +3 -1
  3. package/dist/safe-js/chunks/{chunk-B2SQLK4O.js → chunk-3QQYJ4RQ.js} +3 -2
  4. package/dist/safe-js/chunks/chunk-3QQYJ4RQ.js.map +7 -0
  5. package/dist/safe-js/chunks/{chunk-OP3H3ZDN.js → chunk-55RIIUGG.js} +23 -7
  6. package/dist/safe-js/chunks/chunk-55RIIUGG.js.map +7 -0
  7. package/dist/safe-js/chunks/{chunk-NJM2Q74Z.js → chunk-7SBQ7WTV.js} +10 -11
  8. package/dist/safe-js/chunks/chunk-7SBQ7WTV.js.map +7 -0
  9. package/dist/safe-js/chunks/{chunk-KP6I45ET.js → chunk-BNIEPGDU.js} +9885 -8523
  10. package/dist/safe-js/chunks/{chunk-KP6I45ET.js.map → chunk-BNIEPGDU.js.map} +4 -4
  11. package/dist/safe-js/chunks/{chunk-HYD4K4HN.js → chunk-D3GC5QPZ.js} +63 -1
  12. package/dist/safe-js/chunks/chunk-D3GC5QPZ.js.map +7 -0
  13. package/dist/safe-js/chunks/{chunk-KK5NCR5R.js → chunk-EH5A2EQI.js} +501 -150
  14. package/dist/safe-js/chunks/chunk-EH5A2EQI.js.map +7 -0
  15. package/dist/safe-js/chunks/{chunk-APKV5Y6T.js → chunk-FA5CQO6V.js} +2 -2
  16. package/dist/safe-js/chunks/{chunk-RML35GIF.js → chunk-GCOLKRMY.js} +4 -4
  17. package/dist/safe-js/chunks/{chunk-U6KZ2DQ2.js → chunk-K3ZRBAG5.js} +3 -3
  18. package/dist/safe-js/chunks/{chunk-BGJXQN4A.js → chunk-QL2ALAE6.js} +4 -4
  19. package/dist/safe-js/chunks/{chunk-IDQ67DY2.js → chunk-RYVHEOHM.js} +1131 -101
  20. package/dist/safe-js/chunks/chunk-RYVHEOHM.js.map +7 -0
  21. package/dist/safe-js/chunks/{chunk-A7CADQAD.js → chunk-S2GFVXSI.js} +3 -3
  22. package/dist/safe-js/chunks/{chunk-XMA6FOJU.js → chunk-WF7HYDJ4.js} +4 -5
  23. package/dist/safe-js/chunks/{chunk-XMA6FOJU.js.map → chunk-WF7HYDJ4.js.map} +2 -2
  24. package/dist/safe-js/chunks/{chunk-NEJ44T4Y.js → chunk-XHSOM5CG.js} +2 -2
  25. package/dist/safe-js/chunks/{cli-runtime-PO6DQDHP.js → cli-runtime-6QPGM3TX.js} +14 -14
  26. package/dist/safe-js/chunks/{dump-TRGFSCJ7.js → dump-TU6SYRFD.js} +4 -4
  27. package/dist/safe-js/chunks/{fs-4T5MZW5J.js → fs-HTZT32MQ.js} +4 -4
  28. package/dist/safe-js/chunks/harness-RYAMI444.js +10 -0
  29. package/dist/safe-js/chunks/mcp-DO2IRFGQ.js +11 -0
  30. package/dist/safe-js/chunks/migration-file-K7UH5ZG6.js +12 -0
  31. package/dist/safe-js/chunks/restore-7LRP4BM3.js +13 -0
  32. package/dist/safe-js/chunks/run-6SETM7SV.js +17 -0
  33. package/dist/safe-js/chunks/runtime-modules-MCQS2SHQ.js +10 -0
  34. package/dist/safe-js/cli.js +2 -2
  35. package/dist/safe-js/core.js +7 -7
  36. package/dist/safe-js/extensions.d.ts +3 -1
  37. package/dist/safe-js/index.js +14 -14
  38. package/dist/safe-js/interp/async.d.ts +4 -1
  39. package/dist/safe-js/interp/budget.d.ts +15 -6
  40. package/dist/safe-js/interp/collection-iterator.d.ts +1 -1
  41. package/dist/safe-js/interp/host-bridge.d.ts +1 -0
  42. package/dist/safe-js/interp/host-capabilities.d.ts +13 -4
  43. package/dist/safe-js/interp/interpreter.d.ts +5 -1
  44. package/dist/safe-js/interp/intl-numberformat.d.ts +2 -2
  45. package/dist/safe-js/interp/jobs.d.ts +8 -0
  46. package/dist/safe-js/interp/scope.d.ts +13 -3
  47. package/dist/safe-js/interp/typed-array.d.ts +5 -2
  48. package/dist/safe-js/interp/values.d.ts +1 -0
  49. package/dist/safe-js/modules/registry.d.ts +4 -1
  50. package/dist/safe-js/modules/source-graph.d.ts +19 -1
  51. package/dist/safe-js/parse/compact-spans.d.ts +18 -0
  52. package/dist/safe-js/parse/dynamic-source.d.ts +3 -1
  53. package/dist/safe-js/parse/function-source.d.ts +1 -1
  54. package/dist/safe-js/parse/parser.d.ts +8 -3
  55. package/dist/safe-js/parse/tokenizer.d.ts +4 -0
  56. package/dist/safe-js/realm.d.ts +17 -3
  57. package/dist/safe-js/restore.d.ts +1 -0
  58. package/dist/safe-js/run.d.ts +10 -0
  59. package/dist/safe-js/snapshot/guest-heap.d.ts +1 -0
  60. package/dist/toolcraft-design/prompts/primitives/note.d.ts +1 -1
  61. package/package.json +2 -2
  62. package/dist/safe-js/chunks/chunk-B2SQLK4O.js.map +0 -7
  63. package/dist/safe-js/chunks/chunk-HYD4K4HN.js.map +0 -7
  64. package/dist/safe-js/chunks/chunk-IDQ67DY2.js.map +0 -7
  65. package/dist/safe-js/chunks/chunk-KK5NCR5R.js.map +0 -7
  66. package/dist/safe-js/chunks/chunk-NJM2Q74Z.js.map +0 -7
  67. package/dist/safe-js/chunks/chunk-OP3H3ZDN.js.map +0 -7
  68. package/dist/safe-js/chunks/harness-DLEQ2Z3O.js +0 -10
  69. package/dist/safe-js/chunks/mcp-TNVEFJ6K.js +0 -11
  70. package/dist/safe-js/chunks/migration-file-IDIUFLKY.js +0 -12
  71. package/dist/safe-js/chunks/restore-P27AUZR2.js +0 -13
  72. package/dist/safe-js/chunks/run-KGAEHYMV.js +0 -17
  73. package/dist/safe-js/chunks/runtime-modules-KICK45EE.js +0 -10
  74. /package/dist/safe-js/chunks/{chunk-APKV5Y6T.js.map → chunk-FA5CQO6V.js.map} +0 -0
  75. /package/dist/safe-js/chunks/{chunk-RML35GIF.js.map → chunk-GCOLKRMY.js.map} +0 -0
  76. /package/dist/safe-js/chunks/{chunk-U6KZ2DQ2.js.map → chunk-K3ZRBAG5.js.map} +0 -0
  77. /package/dist/safe-js/chunks/{chunk-BGJXQN4A.js.map → chunk-QL2ALAE6.js.map} +0 -0
  78. /package/dist/safe-js/chunks/{chunk-A7CADQAD.js.map → chunk-S2GFVXSI.js.map} +0 -0
  79. /package/dist/safe-js/chunks/{chunk-NEJ44T4Y.js.map → chunk-XHSOM5CG.js.map} +0 -0
  80. /package/dist/safe-js/chunks/{cli-runtime-PO6DQDHP.js.map → cli-runtime-6QPGM3TX.js.map} +0 -0
  81. /package/dist/safe-js/chunks/{dump-TRGFSCJ7.js.map → dump-TU6SYRFD.js.map} +0 -0
  82. /package/dist/safe-js/chunks/{fs-4T5MZW5J.js.map → fs-HTZT32MQ.js.map} +0 -0
  83. /package/dist/safe-js/chunks/{harness-DLEQ2Z3O.js.map → harness-RYAMI444.js.map} +0 -0
  84. /package/dist/safe-js/chunks/{mcp-TNVEFJ6K.js.map → mcp-DO2IRFGQ.js.map} +0 -0
  85. /package/dist/safe-js/chunks/{migration-file-IDIUFLKY.js.map → migration-file-K7UH5ZG6.js.map} +0 -0
  86. /package/dist/safe-js/chunks/{restore-P27AUZR2.js.map → restore-7LRP4BM3.js.map} +0 -0
  87. /package/dist/safe-js/chunks/{run-KGAEHYMV.js.map → run-6SETM7SV.js.map} +0 -0
  88. /package/dist/safe-js/chunks/{runtime-modules-KICK45EE.js.map → runtime-modules-MCQS2SHQ.js.map} +0 -0
@@ -1,10 +1,10 @@
1
1
  import {
2
2
  restore
3
- } from "./chunk-NJM2Q74Z.js";
3
+ } from "./chunk-7SBQ7WTV.js";
4
4
  import {
5
5
  attachDumpController,
6
6
  createDumpController
7
- } from "./chunk-A7CADQAD.js";
7
+ } from "./chunk-S2GFVXSI.js";
8
8
  import {
9
9
  getOwnErrorCode,
10
10
  hasOwnErrorCode
@@ -12,7 +12,7 @@ import {
12
12
  import {
13
13
  hashParsedAst,
14
14
  hashSource
15
- } from "./chunk-U6KZ2DQ2.js";
15
+ } from "./chunk-K3ZRBAG5.js";
16
16
  import {
17
17
  DUMP_FORMAT_VERSION,
18
18
  EXECUTION_SEMANTICS,
@@ -21,6 +21,9 @@ import {
21
21
  PromiseReplay,
22
22
  SandboxJobQueue,
23
23
  Scope,
24
+ arrayBufferDetached,
25
+ arrayBufferLength,
26
+ arrayBufferOptions,
24
27
  attachExecutionControl,
25
28
  attachSourceLoader,
26
29
  awaitSandboxValue,
@@ -30,8 +33,10 @@ import {
30
33
  consumeSettledHostCall,
31
34
  copyHostValueToSandbox,
32
35
  createBuiltinBindings,
36
+ createEvalSource,
33
37
  createGuestReference,
34
38
  createHostCallbackContext,
39
+ createImmutableEmptyModuleEnvironment,
35
40
  createLiveHostObject,
36
41
  createModuleEnvironment,
37
42
  createModuleNamespace,
@@ -44,15 +49,19 @@ import {
44
49
  decodeReplayData,
45
50
  deepCopyFromSandbox,
46
51
  deepCopyToSandbox,
52
+ denyGuestStringCompilation,
47
53
  encodeReplayData,
48
54
  enterRunningState,
49
55
  enterSnapshotRun,
50
56
  exportHostCapability,
51
57
  getExtensionSetup,
58
+ getRealmGlobalObject,
52
59
  guestProxyStates,
60
+ hostObjectGuestRoot,
53
61
  inMemoryRunSnapshots,
54
62
  interpret,
55
63
  invokeBuiltinClosure,
64
+ isSandboxArrayBuffer,
56
65
  isSandboxClosure,
57
66
  isSandboxCollectionIterator,
58
67
  isSandboxMap,
@@ -75,6 +84,7 @@ import {
75
84
  releaseObjectPrototype,
76
85
  resolveModuleImports,
77
86
  resolveModuleNamespace,
87
+ retainValues,
78
88
  revokeGuestCallback,
79
89
  revokeGuestReference,
80
90
  revokeHostObject,
@@ -90,7 +100,7 @@ import {
90
100
  withSandboxPromiseRejectionTracker,
91
101
  wrapCallerInjectedBindings,
92
102
  wrapCancelableBindings
93
- } from "./chunk-KP6I45ET.js";
103
+ } from "./chunk-BNIEPGDU.js";
94
104
  import {
95
105
  Budget,
96
106
  CompileScope,
@@ -105,20 +115,21 @@ import {
105
115
  parseSourceModule,
106
116
  replaceErrorStack,
107
117
  safeAddEvent
108
- } from "./chunk-IDQ67DY2.js";
118
+ } from "./chunk-RYVHEOHM.js";
109
119
 
110
120
  // packages/safe-js/src/realm.ts
111
- import { AsyncLocalStorage } from "node:async_hooks";
121
+ import { AsyncLocalStorage as AsyncLocalStorage2 } from "node:async_hooks";
112
122
  import { types } from "node:util";
113
123
 
114
124
  // packages/safe-js/src/modules/source-graph.ts
125
+ import { AsyncLocalStorage } from "node:async_hooks";
115
126
  var ambiguous = /* @__PURE__ */ Symbol("ambiguous module export");
116
127
  var sourceRecords = /* @__PURE__ */ new WeakSet();
117
128
  var SourceModuleGraph = class {
118
129
  constructor(options) {
119
130
  this.options = options;
120
131
  this.jobs = options.jobs ?? new SandboxJobQueue();
121
- attachSourceLoader(options.modules, this.import.bind(this));
132
+ attachSourceLoader(options.modules, this.import.bind(this), { preserveImmutableNamespaces: true });
122
133
  options.budget?.setRetainedValues(this, () => [...this.records.values()].flatMap((record) => [record.id, record.source, ...record.parsed.requests, ...record.scope.retainedDataRoots()]));
123
134
  }
124
135
  options;
@@ -126,60 +137,169 @@ var SourceModuleGraph = class {
126
137
  records = /* @__PURE__ */ new Map();
127
138
  requests = /* @__PURE__ */ new Map();
128
139
  requestDataSize = 0;
140
+ lookupDataSize = 0;
129
141
  pendingImports = /* @__PURE__ */ new Set();
142
+ fulfilledImports = 0;
143
+ rejectedImports = 0;
144
+ importGroup = new AsyncLocalStorage();
130
145
  jobs;
131
146
  evaluationStack = [];
132
147
  evaluationIndex = 0;
133
148
  close() {
134
149
  this.options.budget?.setRetainedValues(this, void 0);
135
150
  this.options.budget?.setRetainedDataUsage(this.requests, 0);
151
+ for (const record of this.records.values()) {
152
+ record.explicitExports.clear();
153
+ record.localImports.clear();
154
+ record.starExports.length = 0;
155
+ }
156
+ this.options.budget?.setRetainedDataUsage(this.records, 0);
157
+ this.lookupDataSize = 0;
136
158
  this.requestDataSize = 0;
137
159
  this.records.clear();
138
160
  this.requests.clear();
139
161
  }
162
+ sourceModuleStatus() {
163
+ return Object.freeze({
164
+ pendingImports: this.pendingImports.size,
165
+ preparedModules: this.records.size,
166
+ fulfilledImports: this.fulfilledImports,
167
+ rejectedImports: this.rejectedImports
168
+ });
169
+ }
140
170
  import(specifier, referrer) {
171
+ const group = this.importGroup.getStore();
172
+ const cancelDeadline = this.startImportDeadline();
141
173
  const pending = (async () => {
142
174
  const entry = await this.load(specifier, referrer);
143
175
  if (!isRecord(entry)) return entry;
144
176
  await this.loadGraph(entry);
145
- await this.link(entry);
177
+ if (this.options.serializePreparation) await this.jobs.run(() => this.link(entry));
178
+ else await this.link(entry);
146
179
  await this.evaluate(entry);
147
180
  await this.jobs.drain();
148
181
  return this.namespace(entry);
149
182
  })();
150
183
  this.pendingImports.add(pending);
151
- void pending.then(() => this.pendingImports.delete(pending), () => this.pendingImports.delete(pending));
184
+ group?.add(pending);
185
+ const release = () => {
186
+ cancelDeadline();
187
+ this.pendingImports.delete(pending);
188
+ group?.delete(pending);
189
+ };
190
+ void pending.then(
191
+ () => {
192
+ this.fulfilledImports++;
193
+ release();
194
+ },
195
+ () => {
196
+ this.rejectedImports++;
197
+ release();
198
+ }
199
+ );
152
200
  return pending;
153
201
  }
154
- async settle() {
202
+ startImportDeadline() {
203
+ const policy = this.options.importDeadline;
204
+ const signal = this.options.signal;
205
+ if (policy === void 0 || signal?.aborted) return () => {
206
+ };
207
+ const deadline = Date.now() + policy.timeoutMs;
208
+ let expired = false;
209
+ const expire = (reason) => {
210
+ if (expired) return;
211
+ expired = true;
212
+ cancel();
213
+ policy.abort(reason);
214
+ };
215
+ const releaseDeadline = this.options.budget?.acquireDeadline(deadline, expire);
216
+ const timer = setTimeout(() => {
217
+ expire(new SandboxError({
218
+ budget: "deadline",
219
+ current: Date.now(),
220
+ limit: deadline
221
+ }));
222
+ }, policy.timeoutMs);
223
+ const cancel = () => {
224
+ clearTimeout(timer);
225
+ releaseDeadline?.();
226
+ signal?.removeEventListener("abort", cancel);
227
+ };
228
+ signal?.addEventListener("abort", cancel, { once: true });
229
+ return cancel;
230
+ }
231
+ withoutImportGroup(task) {
232
+ return this.importGroup.exit(task);
233
+ }
234
+ withImportGroup(task) {
235
+ const group = /* @__PURE__ */ new Set();
236
+ return this.importGroup.run(group, async () => {
237
+ const result = await task();
238
+ await this.settle(group);
239
+ return result;
240
+ });
241
+ }
242
+ async settle(pendingImports = this.pendingImports) {
155
243
  do {
156
- await Promise.allSettled([...this.pendingImports]);
244
+ await Promise.allSettled([...pendingImports]);
157
245
  await this.jobs.drain();
158
- } while (this.pendingImports.size > 0);
246
+ } while (pendingImports.size > 0);
159
247
  }
160
248
  async evaluateSource(source, beforeEvaluation) {
161
- const entry = this.register(source);
249
+ const entry = this.options.serializePreparation ? await this.jobs.run(() => this.register(source)) : this.register(source);
162
250
  await this.loadGraph(entry);
163
- await this.link(entry);
251
+ if (this.options.serializePreparation) await this.jobs.run(() => this.link(entry));
252
+ else await this.link(entry);
164
253
  beforeEvaluation?.(entry.parsed.module);
165
254
  await this.evaluate(entry);
166
255
  await this.jobs.drain();
167
256
  return this.namespace(entry);
168
257
  }
169
258
  register(source) {
259
+ this.options.assertActive?.();
260
+ this.options.signal?.throwIfAborted();
170
261
  const known = this.records.get(source.id);
171
262
  if (known !== void 0) {
172
263
  if (known.source !== source.source) throw new TypeError(`Source identity '${source.id}' changed within the graph.`);
173
264
  return known;
174
265
  }
175
266
  this.options.budget?.chargeDataUsage(source.source.length + source.id.length + 1);
176
- const parsed = parseSourceModule(source.source, source.id, this.options.compilation?.owner ?? this.options.compileOwner);
267
+ const parsed = parseSourceModule(source.source, source.id, this.options.compilation?.owner ?? this.options.compileOwner, { sharedPositions: true, compactAst: true });
268
+ const explicitExports = /* @__PURE__ */ new Map();
269
+ const localImports = /* @__PURE__ */ new Map();
270
+ const starExports = [];
271
+ for (const entry of parsed.exports) {
272
+ this.options.budget?.visitNode();
273
+ if (entry.exported === void 0) {
274
+ const lookupDataSize = this.lookupDataSize + 1;
275
+ this.options.budget?.setRetainedDataUsage(this.records, lookupDataSize);
276
+ this.lookupDataSize = lookupDataSize;
277
+ starExports.push(entry);
278
+ } else if (!explicitExports.has(entry.exported)) {
279
+ const lookupDataSize = this.lookupDataSize + entry.exported.length + 2;
280
+ this.options.budget?.setRetainedDataUsage(this.records, lookupDataSize);
281
+ this.lookupDataSize = lookupDataSize;
282
+ explicitExports.set(entry.exported, entry);
283
+ }
284
+ }
285
+ for (const entry of parsed.imports) {
286
+ this.options.budget?.visitNode();
287
+ if (!localImports.has(entry.local)) {
288
+ const lookupDataSize = this.lookupDataSize + entry.local.length + 2;
289
+ this.options.budget?.setRetainedDataUsage(this.records, lookupDataSize);
290
+ this.lookupDataSize = lookupDataSize;
291
+ localImports.set(entry.local, entry);
292
+ }
293
+ }
177
294
  const importMeta = /* @__PURE__ */ Object.create(null);
178
295
  setSandboxPrototype(importMeta, null);
179
296
  const record = {
180
297
  id: source.id,
181
298
  source: source.source,
182
299
  parsed,
300
+ explicitExports,
301
+ localImports,
302
+ starExports,
183
303
  scope: new Scope({ this: void 0 }, this.options.scope, importMeta, { functionBoundary: true }),
184
304
  dependencies: /* @__PURE__ */ new Map()
185
305
  };
@@ -205,7 +325,7 @@ var SourceModuleGraph = class {
205
325
  if (source === void 0) throw new TypeError(`Source resolver denied '${specifier}' from '${referrer}'.`);
206
326
  if (typeof source.id !== "string" || source.id.length === 0 || typeof source.source !== "string")
207
327
  throw new TypeError("Source resolver must supply a nonempty identity and source text.");
208
- return this.register(source);
328
+ return this.options.serializePreparation ? this.jobs.run(() => this.register(source)) : this.register(source);
209
329
  })();
210
330
  this.requests.set(key, request);
211
331
  return request;
@@ -315,24 +435,17 @@ var SourceModuleGraph = class {
315
435
  const leave = this.options.budget?.enterCall();
316
436
  try {
317
437
  visited.add(key);
318
- const explicit = record.parsed.exports.find((entry) => {
319
- this.options.budget?.visitNode();
320
- return entry.exported === name;
321
- });
438
+ const explicit = record.explicitExports.get(name);
322
439
  if (explicit?.local !== void 0) {
323
- const imported = record.parsed.imports.find((entry) => {
324
- this.options.budget?.visitNode();
325
- return entry.local === explicit.local;
326
- });
440
+ const imported = record.localImports.get(explicit.local);
327
441
  if (imported === void 0 || imported.namespace === true) return { record, name: explicit.local };
328
442
  return this.resolveDependency(record, imported.request, imported.imported, visited);
329
443
  }
330
444
  if (explicit?.request !== void 0) return this.resolveDependency(record, explicit.request, explicit.imported, visited, explicit.namespace);
331
445
  if (name === "default") return void 0;
332
446
  let resolved;
333
- for (const entry of record.parsed.exports) {
447
+ for (const entry of record.starExports) {
334
448
  this.options.budget?.visitNode();
335
- if (entry.exported !== void 0) continue;
336
449
  const candidate = this.resolveDependency(record, entry.request, name, visited);
337
450
  if (candidate === ambiguous) return ambiguous;
338
451
  if (candidate === void 0) continue;
@@ -459,7 +572,7 @@ var SourceModuleGraph = class {
459
572
  async execute(record, modulePhase, onSuspend) {
460
573
  const result = await interpret(
461
574
  { type: "BlockStatement", body: record.parsed.module.body, span: record.parsed.module.span },
462
- { ...this.options, scope: record.scope, useScopeDirectly: true, jobs: this.jobs, nested: true, modulePhase, onSuspend }
575
+ { ...this.options, scope: record.scope, useScopeDirectly: true, captureSnapshot: false, jobs: this.jobs, nested: true, modulePhase, onSuspend }
463
576
  );
464
577
  this.stats.nodeVisits += result.stats.nodeVisits;
465
578
  if (!result.ok) throw new Error(result.error.message);
@@ -488,9 +601,15 @@ function createReplayableRandom(options = {}) {
488
601
  }
489
602
 
490
603
  // packages/safe-js/src/realm.ts
604
+ var retainedArrayPrototype = Array.prototype;
605
+ var nativeRetainedArrayPrototype = Object.setPrototypeOf;
606
+ var nativeRetainedArrayAppend = Function.prototype.call.bind(Array.prototype.push);
607
+ var freezeSourceReference = Object.freeze;
491
608
  var RealmState = class {
492
609
  constructor(options, queue = new SandboxJobQueue()) {
493
610
  this.options = options;
611
+ if (options.classicScripts !== void 0 && typeof options.classicScripts !== "boolean")
612
+ throw new TypeError("Realm classicScripts must be a boolean.");
494
613
  this.queue = queue;
495
614
  const limitInput = readDataRecord(options.limits ?? {}, "Realm limits");
496
615
  this.limits = {
@@ -524,6 +643,8 @@ var RealmState = class {
524
643
  if (this.extensions.length > this.limits.extensions)
525
644
  throw new RangeError("Realm extension limit exceeded.");
526
645
  this.globals = readDataRecord(options.bindings ?? {}, "Realm bindings");
646
+ if (options.classicScripts && ["this", "globalThis"].some((name) => Object.hasOwn(this.globals, name)))
647
+ throw new TypeError("Classic Script receiver bindings cannot be replaced.");
527
648
  this.modules = readModules(options.modules);
528
649
  const grants = new Set(readStringList(options.grants ?? [], "Realm grants"));
529
650
  for (const extension of this.extensions) getExtensionSetup(extension);
@@ -536,7 +657,7 @@ var RealmState = class {
536
657
  this.consoleExtension = extensionName;
537
658
  }
538
659
  this.budget = options.budget ?? new Budget({ maxCallDepth: 1e3 });
539
- this.lease = this.budget.acquireCompileOwner(true);
660
+ this.lease = this.budget.acquireRealmOwner();
540
661
  this.compilation = new CompileScope(this.lease.owner);
541
662
  this.bridge = {
542
663
  owner: this,
@@ -570,6 +691,8 @@ var RealmState = class {
570
691
  );
571
692
  for (const extension of this.extensions) {
572
693
  const manifest = extension.manifest;
694
+ if (options.classicScripts && manifest.globals?.some((name) => name === "this" || name === "globalThis"))
695
+ throw new TypeError("Classic Script receiver bindings cannot be replaced.");
573
696
  if (names.has(manifest.name))
574
697
  throw new TypeError(`Duplicate extension '${manifest.name}'.`);
575
698
  names.add(manifest.name);
@@ -609,7 +732,7 @@ var RealmState = class {
609
732
  lease;
610
733
  compilation;
611
734
  controller = new AbortController();
612
- phase = new AsyncLocalStorage();
735
+ phase = new AsyncLocalStorage2();
613
736
  queue;
614
737
  tracker = createSandboxPromiseRejectionTracker();
615
738
  bridge;
@@ -621,6 +744,8 @@ var RealmState = class {
621
744
  resources;
622
745
  callbacks = /* @__PURE__ */ new Map();
623
746
  pendingCallbacks = /* @__PURE__ */ new Set();
747
+ pendingWork = /* @__PURE__ */ new Set();
748
+ releaseReconciliation;
624
749
  callbackCache = /* @__PURE__ */ new WeakMap();
625
750
  hostObjects = /* @__PURE__ */ new Set();
626
751
  guestReferences = /* @__PURE__ */ new Map();
@@ -646,11 +771,17 @@ var RealmState = class {
646
771
  signal: this.controller.signal,
647
772
  realm: this.bridge
648
773
  });
649
- retainedRoots = () => [
650
- ...this.callbacks.values(),
651
- ...Array.from(this.pendingCallbacks, (pending) => pending.closure),
652
- ...this.guestReferences.values()
653
- ];
774
+ retainedRoots = () => {
775
+ const roots = nativeRetainedArrayPrototype([], null);
776
+ for (const object of this.hostObjects) {
777
+ const root = hostObjectGuestRoot(object);
778
+ if (root !== void 0) nativeRetainedArrayAppend(roots, root);
779
+ }
780
+ for (const closure of this.callbacks.values()) nativeRetainedArrayAppend(roots, closure);
781
+ for (const pending of this.pendingCallbacks) nativeRetainedArrayAppend(roots, pending.closure);
782
+ for (const reference of this.guestReferences.values()) nativeRetainedArrayAppend(roots, reference);
783
+ return nativeRetainedArrayPrototype(roots, retainedArrayPrototype);
784
+ };
654
785
  captureArguments = (operation, args, copy) => {
655
786
  const from = this.retainedOperations.get(operation)?.from ?? args.length;
656
787
  const values = copy(args.slice(0, from));
@@ -676,7 +807,7 @@ var RealmState = class {
676
807
  }
677
808
  if (captured.length > 0)
678
809
  this.budget.reconcileDataUsage(
679
- measureSandboxData([...this.scope?.retainedDataRoots() ?? [], ...this.retainedRoots()])
810
+ measureSandboxData([...this.scope?.retainedDataRoots() ?? [], ...this.retainedRoots(), ...this.budget.retainedValues()])
680
811
  );
681
812
  return { args: values, rollback };
682
813
  } catch (error) {
@@ -685,6 +816,40 @@ var RealmState = class {
685
816
  throw error;
686
817
  }
687
818
  };
819
+ createArrayBufferReference = (buffer) => {
820
+ this.assertOpen();
821
+ if (!isSandboxArrayBuffer(buffer) || arrayBufferDetached(buffer) || arrayBufferOptions(buffer) !== void 0 || Reflect.ownKeys(buffer).length !== 0)
822
+ throw new TypeError(
823
+ "Live buffer references require a fixed, attached, plain ArrayBuffer without metadata."
824
+ );
825
+ let reference;
826
+ try {
827
+ this.budget.allocateArrayLength(arrayBufferLength(buffer));
828
+ this.checkCollection(
829
+ this.guestReferences.size + 1,
830
+ this.limits.guestReferences,
831
+ "guest reference"
832
+ );
833
+ const root = [buffer];
834
+ reference = createGuestReference(root, this, this.assertOpen);
835
+ this.guestReferences.set(reference, root);
836
+ this.budget.reconcileDataUsage(
837
+ measureSandboxData([
838
+ ...this.scope?.retainedDataRoots() ?? [],
839
+ ...this.retainedRoots(),
840
+ ...this.budget.retainedValues()
841
+ ])
842
+ );
843
+ return reference;
844
+ } catch (error) {
845
+ if (reference !== void 0) {
846
+ revokeGuestReference(reference, this);
847
+ this.guestReferences.delete(reference);
848
+ }
849
+ if (error instanceof SandboxError) this.poison(error);
850
+ throw error;
851
+ }
852
+ };
688
853
  releaseGuestReference = (reference) => {
689
854
  readGuestReference(reference, this);
690
855
  revokeGuestReference(reference, this);
@@ -713,6 +878,34 @@ var RealmState = class {
713
878
  void this.dispose().catch(() => void 0);
714
879
  });
715
880
  }
881
+ trackWork(pending) {
882
+ this.pendingWork.add(pending);
883
+ void pending.then(
884
+ () => {
885
+ this.pendingWork.delete(pending);
886
+ },
887
+ () => {
888
+ this.pendingWork.delete(pending);
889
+ }
890
+ );
891
+ return pending;
892
+ }
893
+ reconcileWhenIdle() {
894
+ if (this.closed || this.active !== void 0 || this.pendingCallbacks.size > 0) return;
895
+ this.releaseReconciliation?.();
896
+ this.releaseReconciliation = void 0;
897
+ try {
898
+ reconcileCompiledValues(
899
+ this.budget,
900
+ [...this.scope?.retainedDataRoots() ?? [], ...this.retainedRoots()],
901
+ this.compilation
902
+ );
903
+ } catch (error) {
904
+ this.poison(error);
905
+ void this.dispose().catch(() => void 0);
906
+ throw error;
907
+ }
908
+ }
716
909
  chargeWork = (units = 1) => {
717
910
  this.assertOpen();
718
911
  if (!Number.isSafeInteger(units) || units < 0)
@@ -811,6 +1004,7 @@ var RealmState = class {
811
1004
  owner: this,
812
1005
  assertActive: this.assertOpen,
813
1006
  chargeWork: this.chargeWork,
1007
+ chargeGuestData: (units) => this.budget.chargeDataUsage(units),
814
1008
  checkLength: (length) => this.budget.allocateArrayLength(length),
815
1009
  checkString: (value) => {
816
1010
  this.budget.allocateString(value);
@@ -880,7 +1074,7 @@ var RealmState = class {
880
1074
  });
881
1075
  try {
882
1076
  this.budget.reconcileDataUsage(
883
- measureSandboxData([...this.scope?.retainedDataRoots() ?? [], ...this.retainedRoots()])
1077
+ measureSandboxData([...this.scope?.retainedDataRoots() ?? [], ...this.retainedRoots(), ...this.budget.retainedValues()])
884
1078
  );
885
1079
  } catch (error) {
886
1080
  this.poison(error);
@@ -913,12 +1107,19 @@ var RealmState = class {
913
1107
  };
914
1108
  invokeCallback = this.executeCallback.bind(this);
915
1109
  async executeCallback(callback, options = {}, completeSynchronous) {
916
- if (this.closed || this.failure !== void 0) await this.dispose();
1110
+ if ((this.closed || this.failure !== void 0) && this.options.callbackScheduling !== "after-prefix")
1111
+ await this.dispose();
917
1112
  this.assertOpen();
918
1113
  const closure = readGuestCallback(callback, this);
919
1114
  this.checkCollection(this.pendingCallbacks.size + 1, this.limits.callbacks, "pending callback");
920
- const record = { closure };
1115
+ const scheduled = this.options.callbackScheduling === "after-prefix";
1116
+ const record = {
1117
+ closure,
1118
+ prefixComplete: false
1119
+ };
921
1120
  this.pendingCallbacks.add(record);
1121
+ if (scheduled) this.releaseReconciliation ??= this.budget.deferReconciliation();
1122
+ const rejectionOwner = scheduled ? this.tracker.startOperation() : void 0;
922
1123
  const invoke = async () => {
923
1124
  this.assertOpen();
924
1125
  readGuestCallback(callback, this);
@@ -949,6 +1150,7 @@ var RealmState = class {
949
1150
  if (isSandboxPromise(value) && value.synchronousPrefix !== void 0)
950
1151
  await value.synchronousPrefix;
951
1152
  this.assertOpen();
1153
+ record.prefixComplete = true;
952
1154
  completeSynchronous?.();
953
1155
  const settled = await suspendJob(settlement);
954
1156
  return this.exportValue(settled);
@@ -958,25 +1160,37 @@ var RealmState = class {
958
1160
  }
959
1161
  };
960
1162
  try {
961
- const active = this.active !== void 0;
962
- const pending = withSandboxPromiseRejectionTracker(
963
- this.tracker,
964
- () => runResources.run(
965
- this.resources,
966
- () => withCancellationSignal(
967
- this.controller.signal,
968
- () => active && this.phase.getStore()?.active ? runAsyncPrefix(invoke) : this.queue.run(invoke)
1163
+ const active = this.active !== void 0 || scheduled && this.phase.getStore()?.active === true;
1164
+ const execute = () => this.tracker.withOperation(
1165
+ rejectionOwner,
1166
+ () => withSandboxPromiseRejectionTracker(
1167
+ this.tracker,
1168
+ () => runResources.run(
1169
+ this.resources,
1170
+ () => withCancellationSignal(
1171
+ this.controller.signal,
1172
+ () => active && this.phase.getStore()?.active ? runAsyncPrefix(invoke, completeSynchronous === void 0 && this.phase.getStore()?.extension !== void 0) : this.queue.run(invoke)
1173
+ )
969
1174
  )
970
1175
  )
971
1176
  );
972
- record.promise = active ? pending : this.perform(() => pending);
973
- return await record.promise;
1177
+ const pending = scheduled && this.sourceGraph !== void 0 ? this.sourceGraph.withoutImportGroup(execute) : execute();
1178
+ record.promise = scheduled ? this.trackWork(pending) : active ? pending : this.perform(() => pending);
1179
+ const result = await record.promise;
1180
+ if (scheduled) {
1181
+ await this.checkUnhandledRejection(rejectionOwner);
1182
+ this.assertOpen();
1183
+ }
1184
+ return result;
974
1185
  } catch (error) {
975
- if (error instanceof SandboxError) this.poison(error);
1186
+ if (scheduled || error instanceof SandboxError) this.poison(error);
1187
+ if (scheduled) void this.dispose().catch(() => void 0);
976
1188
  throw error;
977
1189
  } finally {
1190
+ this.tracker.finishOperation(rejectionOwner);
978
1191
  this.pendingCallbacks.delete(record);
979
- if (!this.closed && this.active === void 0)
1192
+ if (scheduled) this.reconcileWhenIdle();
1193
+ else if (!this.closed && this.active === void 0)
980
1194
  reconcileCompiledValues(
981
1195
  this.budget,
982
1196
  [...this.scope?.retainedDataRoots() ?? [], ...this.retainedRoots()],
@@ -997,6 +1211,12 @@ var RealmState = class {
997
1211
  },
998
1212
  chargeWork: this.chargeWork,
999
1213
  createHostObject: this.createHostObject,
1214
+ createArrayBufferReference: (buffer) => {
1215
+ this.assertOpen();
1216
+ if (!extension.manifest.capabilities?.includes("array-buffer:share"))
1217
+ throw new TypeError("Live buffers require the array-buffer:share grant.");
1218
+ return this.createArrayBufferReference(buffer);
1219
+ },
1000
1220
  startCallback: this.startCallback,
1001
1221
  invokeCallback: this.invokeCallback,
1002
1222
  releaseCallback: this.releaseCallback,
@@ -1081,71 +1301,129 @@ var RealmState = class {
1081
1301
  const bindings = wrapCallerInjectedBindings(this.globals, this.bridgeOptions());
1082
1302
  this.scope = new Scope(this.builtinBindings, void 0, void 0, { chargeData: false }).child(
1083
1303
  bindings,
1084
- { functionBoundary: true }
1304
+ this.options.classicScripts ? { globalEnvironment: true } : { functionBoundary: true }
1085
1305
  );
1306
+ if (this.options.classicScripts)
1307
+ this.scope.declare("this", "const", getRealmGlobalObject(this.budget));
1308
+ if (this.options.stringCompilation === "deny") denyGuestStringCompilation(this.scope);
1086
1309
  }
1087
- async evaluateRaw(source, filename = "<realm>", nested = false, sourceType) {
1310
+ sourceModuleStatus = () => {
1311
+ this.assertOpen();
1312
+ return this.sourceGraph?.sourceModuleStatus() ?? Object.freeze({
1313
+ pendingImports: 0,
1314
+ preparedModules: 0,
1315
+ fulfilledImports: 0,
1316
+ rejectedImports: 0
1317
+ });
1318
+ };
1319
+ ensureSourceGraph() {
1320
+ this.assertOpen();
1321
+ this.initialize();
1322
+ if (this.sourceGraph !== void 0) return this.sourceGraph;
1323
+ const moduleOptions = { ...this.bridgeOptions(), wrappedModules: this.convertedModules };
1324
+ const modules = Object.keys(this.modules).length === 0 && this.convertedModules.size === 0 ? createImmutableEmptyModuleEnvironment(moduleOptions) : createModuleEnvironment(this.modules, moduleOptions);
1325
+ return this.sourceGraph ??= new SourceModuleGraph({
1326
+ resolver: this.options.sourceResolver ?? (() => void 0),
1327
+ scope: this.scope,
1328
+ modules,
1329
+ budget: this.budget,
1330
+ compilation: this.compilation,
1331
+ signal: this.controller.signal,
1332
+ jobs: this.queue,
1333
+ assertActive: this.assertOpen,
1334
+ surfaceUnhandledThrows: true,
1335
+ serializePreparation: this.options.callbackScheduling === "after-prefix",
1336
+ ...this.options.sourceImportTimeoutMs === void 0 ? {} : {
1337
+ importDeadline: {
1338
+ timeoutMs: this.options.sourceImportTimeoutMs,
1339
+ abort: (reason) => {
1340
+ this.poison(reason);
1341
+ void this.close().catch(() => void 0);
1342
+ }
1343
+ }
1344
+ }
1345
+ });
1346
+ }
1347
+ async evaluateRaw(source, filename = "<realm>", nested = false, sourceType, reportUnhandledThrows = false) {
1088
1348
  this.assertOpen();
1089
1349
  if (typeof source !== "string") throw new TypeError("Realm source must be a string.");
1090
1350
  if (sourceType === "module") {
1351
+ const prepare = () => {
1352
+ this.ensureSourceGraph();
1353
+ };
1354
+ const scheduled = this.options.callbackScheduling === "after-prefix";
1355
+ if (scheduled) await this.queue.run(prepare);
1356
+ else prepare();
1357
+ const graph = this.sourceGraph;
1358
+ const before = graph.stats.nodeVisits;
1359
+ const evaluate = async () => {
1360
+ const namespace2 = await graph.evaluateSource({ id: filename, source }, (module) => {
1361
+ this.sourceModuleHash = hashParsedAst(module);
1362
+ });
1363
+ if (!scheduled) await graph.settle();
1364
+ return namespace2;
1365
+ };
1366
+ const namespace = scheduled ? await graph.withImportGroup(evaluate) : await evaluate();
1367
+ return { ok: true, returnValue: namespace, snapshot: { bindings: {} }, stats: { nodeVisits: graph.stats.nodeVisits - before, currentDataSize: this.budget.currentDataSize, peakDataSize: this.budget.peakDataSize } };
1368
+ }
1369
+ const script = this.options.classicScripts ? createEvalSource(source, {}, this.lease.owner, filename, { compactAst: true }) : void 0;
1370
+ const sourceReference = script && this.options.sourceResolver !== void 0 ? freezeSourceReference({ referrer: filename }) : void 0;
1371
+ const releaseSource = script ? retainValues(this.budget, () => [script.source, sourceReference]) : void 0;
1372
+ try {
1373
+ if (script) this.budget.chargeDataUsage(measureSandboxData([script.source]));
1374
+ if (sourceReference) this.budget.chargeDataUsage(measureSandboxData([sourceReference]));
1375
+ const module = script?.node ?? parseExecutableModule(source, filename, this.lease.owner);
1091
1376
  this.initialize();
1092
- this.sourceGraph ??= new SourceModuleGraph({
1093
- resolver: this.options.sourceResolver ?? (() => void 0),
1094
- scope: this.scope,
1095
- modules: createModuleEnvironment(this.modules, { ...this.bridgeOptions(), wrappedModules: this.convertedModules }),
1096
- budget: this.budget,
1097
- compilation: this.compilation,
1098
- signal: this.controller.signal,
1099
- jobs: this.queue,
1100
- assertActive: this.assertOpen,
1101
- surfaceUnhandledThrows: true
1102
- });
1103
- const before = this.sourceGraph.stats.nodeVisits;
1104
- const namespace = await this.sourceGraph.evaluateSource({ id: filename, source }, (module2) => {
1105
- this.sourceModuleHash = hashParsedAst(module2);
1377
+ const moduleOptions = { ...this.bridgeOptions(), wrappedModules: this.convertedModules };
1378
+ const moduleEnvironment = script && Object.keys(this.modules).length === 0 && this.convertedModules.size === 0 ? createImmutableEmptyModuleEnvironment(moduleOptions) : createModuleEnvironment(this.modules, moduleOptions);
1379
+ if (sourceReference)
1380
+ attachSourceLoader(
1381
+ moduleEnvironment,
1382
+ (specifier, referrer) => this.ensureSourceGraph().import(specifier, referrer),
1383
+ { preserveImmutableNamespaces: true }
1384
+ );
1385
+ const imports = script ? {} : resolveModuleImports(module, this.modules, {
1386
+ environment: moduleEnvironment,
1387
+ ...this.bridgeOptions(),
1388
+ wrappedModules: this.convertedModules
1106
1389
  });
1107
- await this.sourceGraph.settle();
1108
- return { ok: true, returnValue: namespace, snapshot: { bindings: {} }, stats: { nodeVisits: this.sourceGraph.stats.nodeVisits - before, currentDataSize: this.budget.currentDataSize, peakDataSize: this.budget.peakDataSize } };
1109
- }
1110
- const module = parseExecutableModule(source, filename, this.lease.owner);
1111
- this.initialize();
1112
- const moduleEnvironment = createModuleEnvironment(this.modules, { ...this.bridgeOptions(), wrappedModules: this.convertedModules });
1113
- const imports = resolveModuleImports(module, this.modules, {
1114
- environment: moduleEnvironment,
1115
- ...this.bridgeOptions(),
1116
- wrappedModules: this.convertedModules
1117
- });
1118
- this.scope.moduleEnvironment = moduleEnvironment;
1119
- for (const [name, value] of Object.entries(imports)) {
1120
- const binding = this.scope.lookup(name);
1121
- if (!binding.found) this.scope.declare(name, "const", value);
1122
- else if (binding.value !== value) throw new TypeError(`Conflicting import '${name}'.`);
1123
- }
1124
- const result = await interpret(
1125
- {
1126
- type: "BlockStatement",
1127
- body: module.body.filter((statement) => statement.type !== "ImportDeclaration"),
1128
- span: module.span
1129
- },
1130
- {
1131
- scope: this.scope,
1132
- useScopeDirectly: true,
1133
- budget: this.budget,
1134
- compilation: this.compilation,
1135
- signal: this.controller.signal,
1136
- surfaceUnhandledThrows: true,
1137
- jobs: this.queue,
1138
- nested,
1139
- assertActive: this.assertOpen
1390
+ this.scope.moduleEnvironment = moduleEnvironment;
1391
+ for (const [name, value] of Object.entries(imports)) {
1392
+ const binding = this.scope.lookup(name);
1393
+ if (!binding.found) this.scope.declare(name, "const", value);
1394
+ else if (binding.value !== value) throw new TypeError(`Conflicting import '${name}'.`);
1140
1395
  }
1141
- );
1142
- this.assertOpen();
1143
- return result;
1396
+ const result = await interpret(
1397
+ {
1398
+ type: "BlockStatement",
1399
+ body: module.body.filter((statement) => statement.type !== "ImportDeclaration"),
1400
+ span: module.span
1401
+ },
1402
+ {
1403
+ ...script ? { script: { strict: script.strict } } : {},
1404
+ sourceReference,
1405
+ scope: this.scope,
1406
+ useScopeDirectly: true,
1407
+ budget: this.budget,
1408
+ compilation: this.compilation,
1409
+ signal: this.controller.signal,
1410
+ surfaceUnhandledThrows: true,
1411
+ reportUnhandledThrows: script !== void 0 && reportUnhandledThrows,
1412
+ jobs: this.queue,
1413
+ nested,
1414
+ assertActive: this.assertOpen
1415
+ }
1416
+ );
1417
+ this.assertOpen();
1418
+ return result;
1419
+ } finally {
1420
+ releaseSource?.();
1421
+ }
1144
1422
  }
1145
1423
  evaluateNested = async (source, extension) => {
1146
1424
  this.assertOpen();
1147
1425
  const phase = this.phase.getStore();
1148
- if (!phase?.active || phase.extension !== extension || phase.evaluating || this.active === void 0)
1426
+ if (!phase?.active || phase.extension !== extension || phase.evaluating || this.active === void 0 && !(this.options.callbackScheduling === "after-prefix" && this.pendingCallbacks.size > 0))
1149
1427
  throw new SandboxError("reentry");
1150
1428
  const leave = this.budget.enterCall();
1151
1429
  phase.evaluating = true;
@@ -1168,39 +1446,65 @@ var RealmState = class {
1168
1446
  }
1169
1447
  };
1170
1448
  evaluate = async (source, options = {}) => this.perform(async () => {
1171
- const result = await this.evaluateRaw(source, options.filename, false, options.sourceType);
1449
+ const discard = Object.getOwnPropertyDescriptor(options, "discardResult");
1450
+ if (discard && (!("value" in discard) || discard.value !== void 0 && typeof discard.value !== "boolean"))
1451
+ throw new TypeError("Realm evaluation discardResult must be an own-data boolean.");
1452
+ const scheduled = this.options.callbackScheduling === "after-prefix";
1453
+ const reportUnhandledThrows = options.sourceType !== "module" && this.options.classicScriptErrors === "report";
1454
+ const result = scheduled && options.sourceType !== "module" ? await this.queue.run(() => this.evaluateRaw(source, options.filename, true, void 0, reportUnhandledThrows)) : await this.evaluateRaw(source, options.filename, false, options.sourceType, reportUnhandledThrows);
1172
1455
  if (!result.ok) {
1173
- await this.dispose();
1174
- return { ok: false, error: result.error, stats: result.stats };
1456
+ const recoverable = reportUnhandledThrows && result.error.code === "UNCAUGHT_EXCEPTION";
1457
+ if (!scheduled && !recoverable) await this.dispose();
1458
+ return { ok: false, error: result.error, stats: result.stats, ...recoverable ? { recoverable: true } : {} };
1175
1459
  }
1176
- return { ok: true, returnValue: this.exportValue(result.returnValue), stats: result.stats };
1177
- });
1178
- async perform(task) {
1179
- if (this.closed || this.failure !== void 0) await this.dispose();
1460
+ return { ok: true, ...discard?.value === true ? {} : { returnValue: this.exportValue(result.returnValue) }, stats: result.stats };
1461
+ }, (result) => !result.ok && !result.recoverable);
1462
+ async checkUnhandledRejection(owner) {
1463
+ const unhandled = await this.tracker.findUnhandledRejection(owner);
1464
+ if (unhandled !== void 0) {
1465
+ const error = new Error(
1466
+ `Unhandled guest promise rejection: ${describeThrownValue(unhandled.reason)}`
1467
+ );
1468
+ error.name = "UnhandledRejectionError";
1469
+ throw error;
1470
+ }
1471
+ }
1472
+ async perform(task, failed) {
1473
+ if ((this.closed || this.failure !== void 0) && this.options.callbackScheduling !== "after-prefix")
1474
+ await this.dispose();
1180
1475
  this.assertOpen();
1181
1476
  if (this.active !== void 0) throw new SandboxError("reentry");
1477
+ const scheduled = this.options.callbackScheduling === "after-prefix";
1478
+ if (scheduled && (this.phase.getStore()?.active || [...this.pendingCallbacks].some((record) => !record.prefixComplete)))
1479
+ throw new SandboxError("reentry");
1480
+ const rejectionOwner = scheduled ? this.tracker.startOperation() : void 0;
1182
1481
  const pending = Promise.resolve().then(
1183
- () => withSandboxPromiseRejectionTracker(
1184
- this.tracker,
1185
- () => runResources.run(
1186
- this.resources,
1187
- () => withCancellationSignal(this.controller.signal, task)
1482
+ () => this.tracker.withOperation(
1483
+ rejectionOwner,
1484
+ () => withSandboxPromiseRejectionTracker(
1485
+ this.tracker,
1486
+ () => runResources.run(
1487
+ this.resources,
1488
+ () => withCancellationSignal(this.controller.signal, task)
1489
+ )
1188
1490
  )
1189
1491
  )
1190
1492
  );
1191
1493
  this.active = pending;
1494
+ if (scheduled) this.trackWork(pending);
1192
1495
  try {
1193
1496
  const result = await pending;
1194
- await this.queue.drain();
1195
- const unhandled = await this.tracker.findUnhandledRejection();
1196
- if (unhandled !== void 0) {
1197
- const error = new Error(
1198
- `Unhandled guest promise rejection: ${describeThrownValue(unhandled.reason)}`
1199
- );
1200
- error.name = "UnhandledRejectionError";
1201
- throw error;
1497
+ if (scheduled && failed?.(result)) {
1498
+ this.closed = true;
1499
+ this.controller.abort(new Error("SafeJS source evaluation failed."));
1500
+ await this.dispose();
1501
+ return result;
1202
1502
  }
1503
+ if (scheduled) this.assertOpen();
1504
+ await this.queue.drain();
1505
+ await this.checkUnhandledRejection(rejectionOwner);
1203
1506
  if (this.failure !== void 0) throw this.failure.reason;
1507
+ if (scheduled) this.assertOpen();
1204
1508
  if (!this.closed)
1205
1509
  reconcileCompiledValues(
1206
1510
  this.budget,
@@ -1218,12 +1522,22 @@ var RealmState = class {
1218
1522
  }
1219
1523
  throw reason;
1220
1524
  } finally {
1221
- this.active = void 0;
1525
+ this.tracker.finishOperation(rejectionOwner);
1526
+ if (this.active === pending) this.active = void 0;
1527
+ if (scheduled) this.reconcileWhenIdle();
1222
1528
  }
1223
1529
  }
1224
1530
  close = () => {
1225
1531
  this.closed = true;
1226
1532
  this.controller.abort(new Error("SafeJS realm is closed."));
1533
+ if (this.options.callbackScheduling === "after-prefix") {
1534
+ const disposal = this.dispose();
1535
+ if (this.phase.getStore()?.active) {
1536
+ void disposal.catch(() => void 0);
1537
+ return Promise.reject(new SandboxError("reentry"));
1538
+ }
1539
+ return disposal;
1540
+ }
1227
1541
  if (this.phase.getStore()?.active) return this.dispose();
1228
1542
  return Promise.allSettled([
1229
1543
  this.active,
@@ -1235,6 +1549,21 @@ var RealmState = class {
1235
1549
  this.closed = true;
1236
1550
  this.controller.abort(new Error("SafeJS realm is closed."));
1237
1551
  this.options.signal?.removeEventListener("abort", this.abort);
1552
+ if (this.options.callbackScheduling === "after-prefix") {
1553
+ this.disposal = Promise.resolve().then(async () => {
1554
+ while (this.pendingWork.size > 0) await Promise.allSettled([...this.pendingWork]);
1555
+ await this.sourceGraph?.settle();
1556
+ await this.queue.settle();
1557
+ this.releaseReconciliation?.();
1558
+ this.releaseReconciliation = void 0;
1559
+ await this.releaseResources();
1560
+ }).finally(() => this.queue.finish());
1561
+ return this.disposal;
1562
+ }
1563
+ this.disposal = this.releaseResources().finally(() => this.queue.finish());
1564
+ return this.disposal;
1565
+ }
1566
+ async releaseResources() {
1238
1567
  for (const callback of this.callbacks.keys()) revokeGuestCallback(callback, this);
1239
1568
  this.callbacks.clear();
1240
1569
  for (const object of this.hostObjects) revokeHostObject(object, this);
@@ -1246,28 +1575,25 @@ var RealmState = class {
1246
1575
  this.budget.setRetainedValues(this, void 0);
1247
1576
  this.sourceGraph?.close();
1248
1577
  releaseObjectPrototype(this.budget);
1249
- this.disposal = (async () => {
1250
- const errors = [];
1251
- for (const cleanup of this.cleanups.splice(0).reverse()) {
1252
- try {
1253
- await cleanup();
1254
- } catch (error) {
1255
- errors.push(error);
1256
- }
1578
+ const errors = [];
1579
+ for (const cleanup of this.cleanups.splice(0).reverse()) {
1580
+ try {
1581
+ await cleanup();
1582
+ } catch (error) {
1583
+ errors.push(error);
1257
1584
  }
1258
- this.scope = void 0;
1259
- this.convertedModules.clear();
1260
- for (const key of Object.keys(this.globals)) delete this.globals[key];
1261
- for (const key of Object.keys(this.modules)) delete this.modules[key];
1262
- for (const key of Object.keys(this.builtinBindings))
1263
- Reflect.deleteProperty(this.builtinBindings, key);
1264
- this.nativeConversions.seen = /* @__PURE__ */ new WeakMap();
1265
- reconcileCompiledValues(this.budget, [], this.compilation);
1266
- this.compilation.dispose();
1267
- this.lease.release();
1268
- if (errors.length > 0) throw new AggregateError(errors, "Realm cleanup failed.");
1269
- })().finally(() => this.queue.finish());
1270
- return this.disposal;
1585
+ }
1586
+ this.scope = void 0;
1587
+ this.convertedModules.clear();
1588
+ for (const key of Object.keys(this.globals)) delete this.globals[key];
1589
+ for (const key of Object.keys(this.modules)) delete this.modules[key];
1590
+ for (const key of Object.keys(this.builtinBindings))
1591
+ Reflect.deleteProperty(this.builtinBindings, key);
1592
+ this.nativeConversions.seen = /* @__PURE__ */ new WeakMap();
1593
+ reconcileCompiledValues(this.budget, [], this.compilation);
1594
+ this.compilation.dispose();
1595
+ this.lease.release();
1596
+ if (errors.length > 0) throw new AggregateError(errors, "Realm cleanup failed.");
1271
1597
  }
1272
1598
  };
1273
1599
  function readModules(input) {
@@ -1296,12 +1622,17 @@ function assertNames(actual, expected, label) {
1296
1622
  function createRealm(options = {}) {
1297
1623
  const state = new RealmState(readRealmOptions(options));
1298
1624
  return Object.freeze(attachExecutionControl({
1625
+ stringCompilation: state.options.stringCompilation ?? "allow",
1626
+ classicScriptErrors: state.options.classicScriptErrors ?? "fatal",
1627
+ supportsDiscardResult: true,
1628
+ sourceImportTimeoutMs: state.options.sourceImportTimeoutMs,
1299
1629
  extensions: Object.freeze(state.extensions.map((extension) => extension.manifest)),
1300
1630
  evaluate: state.evaluate,
1301
1631
  startCallback: state.startCallback,
1302
1632
  invokeCallback: state.invokeCallback,
1303
1633
  releaseCallback: state.releaseCallback,
1304
1634
  releaseGuestReference: state.releaseGuestReference,
1635
+ sourceModuleStatus: state.sourceModuleStatus,
1305
1636
  close: state.close
1306
1637
  }, state.queue));
1307
1638
  }
@@ -1311,6 +1642,7 @@ async function runWithExtensions(source, options, jobs) {
1311
1642
  "Live extension runs do not support snapshots or entryPointArgs; use a persistent realm."
1312
1643
  );
1313
1644
  const state = new RealmState(readRealmOptions(options, true), jobs);
1645
+ if (options.sourceLocation !== void 0) state.resources.sourceLocation = options.sourceLocation;
1314
1646
  try {
1315
1647
  const result = await state.perform(() => state.evaluateRaw(source, options.filename, false, options.sourceType));
1316
1648
  if (result.ok && options.sourceType !== "module") encodeReplayData(result.returnValue);
@@ -1352,10 +1684,25 @@ function readRealmOptions(value, oneShot = false) {
1352
1684
  "clock",
1353
1685
  "limits"
1354
1686
  ]);
1687
+ if (!oneShot) supported.add("stringCompilation");
1688
+ if (!oneShot) supported.add("classicScripts");
1689
+ if (!oneShot) supported.add("classicScriptErrors");
1690
+ if (!oneShot) supported.add("callbackScheduling");
1691
+ if (!oneShot) supported.add("sourceImportTimeoutMs");
1355
1692
  for (const [key, entry] of Object.entries(options)) {
1356
- if (supported.has(key) || oneShot && (key === "filename" || key === "sourceType" || entry === void 0)) continue;
1693
+ if (supported.has(key) || oneShot && (key === "filename" || key === "sourceType" || key === "sourceLocation" || entry === void 0)) continue;
1357
1694
  throw new TypeError(`Unsupported ${oneShot ? "extension-run" : "realm"} option '${key}'.`);
1358
1695
  }
1696
+ if (options.callbackScheduling !== void 0 && options.callbackScheduling !== "after-prefix")
1697
+ throw new TypeError("Realm callbackScheduling must be 'after-prefix'.");
1698
+ if (options.sourceImportTimeoutMs !== void 0 && (!Number.isSafeInteger(options.sourceImportTimeoutMs) || Number(options.sourceImportTimeoutMs) < 1 || Number(options.sourceImportTimeoutMs) > 2147483647))
1699
+ throw new TypeError("Realm sourceImportTimeoutMs must be an integer between 1 and 2147483647.");
1700
+ if (options.stringCompilation !== void 0 && options.stringCompilation !== "allow" && options.stringCompilation !== "deny")
1701
+ throw new TypeError("Realm stringCompilation must be 'allow' or 'deny'.");
1702
+ if (options.classicScriptErrors !== void 0 && options.classicScriptErrors !== "fatal" && options.classicScriptErrors !== "report")
1703
+ throw new TypeError("Realm classicScriptErrors must be 'fatal' or 'report'.");
1704
+ if (options.classicScriptErrors === "report" && options.classicScripts !== true)
1705
+ throw new TypeError("Reporting classic Script errors requires classicScripts:true.");
1359
1706
  return options;
1360
1707
  }
1361
1708
 
@@ -1876,7 +2223,10 @@ function run(source, options = {}) {
1876
2223
  const operation = budget.acquireCompileOwner(true);
1877
2224
  const compilation = new CompileScope(operation.owner);
1878
2225
  try {
1879
- const restoredSnapshot = options.snapshot === void 0 ? void 0 : restore(options.snapshot, { source }, operation.owner);
2226
+ const restoredSnapshot = options.snapshot === void 0 ? void 0 : restore(options.snapshot, {
2227
+ source,
2228
+ ...options.importSpecifiers ? { importSpecifiers: options.importSpecifiers } : {}
2229
+ }, operation.owner);
1880
2230
  const promiseReplay = new PromiseReplay(restoredSnapshot?.promiseReplay);
1881
2231
  return await promiseReplayContext.run(promiseReplay, async () => {
1882
2232
  const deactivateOtelSink = activateOtelSink(options.otelSink);
@@ -1900,15 +2250,16 @@ function run(source, options = {}) {
1900
2250
  const functionSourceText = executionSemantics === "jobs-v8" || executionSemantics === EXECUTION_SEMANTICS;
1901
2251
  runResources.getStore().functionSourceText = functionSourceText;
1902
2252
  runResources.getStore().hostDataMetadata = executionSemantics === EXECUTION_SEMANTICS;
2253
+ if (options.sourceLocation !== void 0) runResources.getStore().sourceLocation = options.sourceLocation;
1903
2254
  const convertInitialInput = (convert) => executionSemantics === "jobs-v6" ? convert() : promiseReplayContext.exit(convert);
1904
2255
  if (restoredSnapshot !== void 0) {
1905
2256
  leaveSnapshotRun = enterSnapshotRun(options.snapshot ?? restoredSnapshot);
1906
2257
  }
1907
2258
  promiseReplay.attachBudget(budget);
1908
2259
  const filename = options.filename ?? "<input>";
1909
- const module = parseExecutableModule(source, filename, operation.owner);
2260
+ const module = parseExecutableModule(source, filename, operation.owner, options.importSpecifiers);
1910
2261
  promiseReplay.validateNodes(module);
1911
- const sourceHash = findRegexLiteral(module) === void 0 ? hashSource(source, operation.owner, functionSourceText) : hashParsedAst(module, functionSourceText);
2262
+ const sourceHash = findRegexLiteral(module) === void 0 && options.importSpecifiers === void 0 ? hashSource(source, operation.owner, functionSourceText) : hashParsedAst(module, functionSourceText);
1912
2263
  const hostCalls = new HostCallJournal(
1913
2264
  sourceHash,
1914
2265
  readHostCallSnapshot(restoredSnapshot),
@@ -2447,4 +2798,4 @@ export {
2447
2798
  UnhandledRejectionError,
2448
2799
  run
2449
2800
  };
2450
- //# sourceMappingURL=chunk-KK5NCR5R.js.map
2801
+ //# sourceMappingURL=chunk-EH5A2EQI.js.map