poe-code 14.0.62 → 14.0.64

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 (49) hide show
  1. package/dist/index.js +977 -528
  2. package/dist/index.js.map +4 -4
  3. package/dist/metafile.json +1 -1
  4. package/package.json +1 -1
  5. package/packages/safe-js/dist/chunks/{chunk-F7TJSPDZ.js → chunk-3EXDJDQM.js} +2 -2
  6. package/packages/safe-js/dist/chunks/{chunk-VKGWGZBH.js → chunk-XO7B4P27.js} +945 -545
  7. package/packages/safe-js/dist/chunks/chunk-XO7B4P27.js.map +7 -0
  8. package/packages/safe-js/dist/cli.js +2 -2
  9. package/packages/safe-js/dist/core.js +1 -1
  10. package/packages/safe-js/dist/graph-depth.js +3 -0
  11. package/packages/safe-js/dist/index.js +2 -2
  12. package/packages/safe-js/dist/interp/boxed.d.ts +14 -0
  13. package/packages/safe-js/dist/interp/boxed.js +50 -0
  14. package/packages/safe-js/dist/interp/globals/collections.js +4 -4
  15. package/packages/safe-js/dist/interp/globals/console-json.js +15 -18
  16. package/packages/safe-js/dist/interp/globals/misc.js +1 -1
  17. package/packages/safe-js/dist/interp/globals/object-array.js +19 -14
  18. package/packages/safe-js/dist/interp/globals/object.js +10 -5
  19. package/packages/safe-js/dist/interp/globals/primitives.d.ts +8 -0
  20. package/packages/safe-js/dist/interp/globals/primitives.js +69 -0
  21. package/packages/safe-js/dist/interp/host-bridge.js +22 -0
  22. package/packages/safe-js/dist/interp/interpreter.js +175 -122
  23. package/packages/safe-js/dist/interp/iteration.d.ts +5 -3
  24. package/packages/safe-js/dist/interp/iteration.js +22 -1
  25. package/packages/safe-js/dist/interp/methods/array.js +6 -3
  26. package/packages/safe-js/dist/interp/methods/function.js +32 -15
  27. package/packages/safe-js/dist/interp/methods/number.d.ts +1 -0
  28. package/packages/safe-js/dist/interp/methods/number.js +4 -1
  29. package/packages/safe-js/dist/interp/methods/string.d.ts +2 -0
  30. package/packages/safe-js/dist/interp/methods/string.js +2 -2
  31. package/packages/safe-js/dist/interp/object-model.d.ts +5 -0
  32. package/packages/safe-js/dist/interp/object-model.js +66 -14
  33. package/packages/safe-js/dist/interp/promise.js +68 -61
  34. package/packages/safe-js/dist/interp/string-coercion.js +4 -1
  35. package/packages/safe-js/dist/interp/values.d.ts +1 -0
  36. package/packages/safe-js/dist/interp/values.js +78 -3
  37. package/packages/safe-js/dist/migrate.d.ts +2 -2
  38. package/packages/safe-js/dist/migration-file.d.ts +4 -4
  39. package/packages/safe-js/dist/snapshot/boxed.d.ts +16 -0
  40. package/packages/safe-js/dist/snapshot/boxed.js +52 -0
  41. package/packages/safe-js/dist/snapshot/dump-format.js +18 -5
  42. package/packages/safe-js/dist/snapshot/replay-data.d.ts +5 -0
  43. package/packages/safe-js/dist/snapshot/replay-data.js +24 -0
  44. package/packages/safe-js/dist/snapshot/restore.js +8 -0
  45. package/packages/safe-js/dist/snapshot/serialize.d.ts +4 -2
  46. package/packages/safe-js/dist/snapshot/serialize.js +18 -10
  47. package/packages/safe-js/dist/snapshot/validation.js +22 -1
  48. package/packages/safe-js/dist/chunks/chunk-VKGWGZBH.js.map +0 -7
  49. /package/packages/safe-js/dist/chunks/{chunk-F7TJSPDZ.js.map → chunk-3EXDJDQM.js.map} +0 -0
@@ -1,11 +1,15 @@
1
1
  import { assertSandboxDataDepth } from "../graph-depth.js";
2
2
  import { getHostObjectMember, isGuestHostObject, isLiveCapability } from "./host-capabilities.js";
3
+ import { boxedValue, isSandboxBox } from "./boxed.js";
3
4
  import { isSandboxClosure, isSandboxGenerator, isSandboxMap, isSandboxPromise, isSandboxRegex, isSandboxSet } from "./values.js";
4
5
  const guestClosures = new WeakSet();
5
6
  const functionProperties = new WeakMap();
6
7
  const prototypes = new WeakMap();
7
8
  const intrinsicPrototypes = new WeakMap();
9
+ const boxedPrototypes = new WeakMap();
10
+ const intrinsicPrototypeRoots = new WeakMap();
8
11
  const intrinsicConstructors = new WeakMap();
12
+ const initialBoxedMethods = new WeakMap();
9
13
  const descriptorObjects = new WeakSet();
10
14
  export function registerGuestClosure(closure) {
11
15
  guestClosures.add(closure);
@@ -61,28 +65,69 @@ export function getGuestFunctionProperty(closure, key) {
61
65
  export function installObjectPrototype(budget, prototype, constructor) {
62
66
  prototypes.set(prototype, null);
63
67
  intrinsicPrototypes.set(budget, prototype);
64
- const records = [prototype, materializeFunctionProperties(constructor)].map(value => ({
65
- value, descriptors: new Map(Object.entries(Object.getOwnPropertyDescriptors(value)))
68
+ registerIntrinsicPrototype(budget, prototype, constructor);
69
+ }
70
+ export function installBoxedPrototype(budget, prototype, constructor) {
71
+ let state = boxedPrototypes.get(budget);
72
+ if (state === undefined)
73
+ boxedPrototypes.set(budget, state = new Map());
74
+ state.set(typeof boxedValue(prototype), prototype);
75
+ initialBoxedMethods.set(prototype, new Map(Object.entries(Object.getOwnPropertyDescriptors(prototype)).map(([key, descriptor]) => [key, descriptor.value])));
76
+ registerIntrinsicPrototype(budget, prototype, constructor);
77
+ }
78
+ export function getBoxedPrototype(value, budget) {
79
+ return boxedPrototypes.get(budget)?.get(typeof value);
80
+ }
81
+ export function isDefaultBoxedMethod(value, key, budget) {
82
+ const prototype = getBoxedPrototype(value, budget);
83
+ return prototype !== undefined && initialBoxedMethods.get(prototype)?.has(key) === true &&
84
+ Object.getOwnPropertyDescriptor(prototype, key)?.value === initialBoxedMethods.get(prototype)?.get(key);
85
+ }
86
+ export function isIntrinsicConstructor(value) {
87
+ return intrinsicConstructors.has(value);
88
+ }
89
+ function registerIntrinsicPrototype(budget, prototype, constructor) {
90
+ let roots = intrinsicPrototypeRoots.get(budget);
91
+ if (roots === undefined)
92
+ intrinsicPrototypeRoots.set(budget, roots = new Set());
93
+ roots.add(prototype);
94
+ const records = [prototype, constructor].map(target => ({
95
+ target,
96
+ value: isGuestClosure(target) ? materializeFunctionProperties(target) : target,
97
+ prototype: getSandboxPrototype(target),
98
+ explicit: hasExplicitSandboxPrototype(target)
99
+ })).map(record => ({
100
+ ...record, descriptors: new Map(Object.entries(Object.getOwnPropertyDescriptors(record.value)))
66
101
  }));
67
- const unchanged = (before, after) => before !== undefined && after !== undefined && before.value === after.value &&
102
+ const unchanged = (before, after) => before !== undefined && after !== undefined && Object.is(before.value, after.value) &&
68
103
  before.writable === after.writable && before.configurable === after.configurable && before.enumerable === after.enumerable;
69
- intrinsicConstructors.set(constructor, () => records.every(({ value, descriptors }) => {
104
+ intrinsicConstructors.set(constructor, () => records.every(({ target, value, descriptors, prototype: parent, explicit }) => {
70
105
  const current = Object.getOwnPropertyDescriptors(value);
71
- return Object.keys(current).length === descriptors.size &&
106
+ return getSandboxPrototype(target) === parent && hasExplicitSandboxPrototype(target) === explicit &&
107
+ Object.keys(current).length === descriptors.size &&
72
108
  Object.keys(current).every(key => unchanged(descriptors.get(key), current[key]));
73
109
  }));
74
- budget.setRetainedValues(prototype, () => records.flatMap(({ value, descriptors }) => Object.entries(Object.getOwnPropertyDescriptors(value)).flatMap(([key, descriptor]) => unchanged(descriptors.get(key), descriptor) ? [] : [key, descriptor.value])));
110
+ budget.setRetainedValues(prototype, () => records.flatMap(({ target, value, descriptors, prototype: parent }) => [
111
+ ...(getSandboxPrototype(target) === parent ? [] : [getSandboxPrototype(target)]),
112
+ ...Object.entries(Object.getOwnPropertyDescriptors(value)).flatMap(([key, descriptor]) => unchanged(descriptors.get(key), descriptor) ? [] : [key, descriptor.value])
113
+ ]));
75
114
  }
76
115
  export function releaseObjectPrototype(budget) {
77
- const prototype = intrinsicPrototypes.get(budget);
78
- if (prototype !== undefined)
116
+ for (const prototype of intrinsicPrototypeRoots.get(budget) ?? [])
79
117
  budget.setRetainedValues(prototype, undefined);
118
+ intrinsicPrototypeRoots.delete(budget);
119
+ boxedPrototypes.delete(budget);
80
120
  intrinsicPrototypes.delete(budget);
81
121
  }
82
122
  export function getSandboxPrototype(value, budget) {
83
123
  if (prototypes.has(value))
84
124
  return prototypes.get(value) ?? null;
85
- return budget !== undefined && isPrototypeRecord(value)
125
+ if (budget !== undefined && isSandboxBox(value)) {
126
+ const prototype = getBoxedPrototype(boxedValue(value), budget);
127
+ if (prototype !== undefined && prototype !== value)
128
+ return prototype;
129
+ }
130
+ return budget !== undefined && !isSandboxClosure(value) && isPrototypeRecord(value)
86
131
  ? intrinsicPrototypes.get(budget) ?? null
87
132
  : null;
88
133
  }
@@ -95,9 +140,12 @@ export function getSandboxDataProperty(value, key, budget) {
95
140
  while (typeof current === "object" && current !== null) {
96
141
  if (isGuestHostObject(current))
97
142
  return getHostObjectMember(current, String(key));
98
- if (isGuestClosure(current))
99
- return getGuestFunctionProperty(current, String(key));
100
- if (isSandboxClosure(current)) {
143
+ if (isGuestClosure(current)) {
144
+ const entry = getGuestFunctionProperty(current, String(key));
145
+ if (entry !== undefined || Object.hasOwn(current.properties ?? {}, String(key)))
146
+ return entry;
147
+ }
148
+ else if (isSandboxClosure(current)) {
101
149
  const properties = current.properties;
102
150
  return properties !== undefined && Object.hasOwn(properties, String(key))
103
151
  ? properties[String(key)]
@@ -109,7 +157,7 @@ export function getSandboxDataProperty(value, key, budget) {
109
157
  isSandboxRegex(current) ||
110
158
  isSandboxGenerator(current))
111
159
  return undefined;
112
- if (Object.hasOwn(current, String(key)))
160
+ if (!isSandboxClosure(current) && Object.hasOwn(current, String(key)))
113
161
  return current[String(key)];
114
162
  current = getSandboxPrototype(current, budget);
115
163
  if (current !== null) {
@@ -124,7 +172,7 @@ export function setSandboxPrototype(value, prototype, budget) {
124
172
  throw new TypeError("Object.prototype has an immutable null prototype.");
125
173
  }
126
174
  if (!isPrototypeRecord(value) || (prototype !== null && !isPrototypeRecord(prototype))) {
127
- throw new TypeError("Prototype links require ordinary sandbox objects; callable and exotic prototype chains are not supported.");
175
+ throw new TypeError("Prototype links require ordinary sandbox objects or guest functions.");
128
176
  }
129
177
  if (prototypes.has(value) && getSandboxPrototype(value, budget) === prototype)
130
178
  return;
@@ -141,6 +189,8 @@ export function setSandboxPrototype(value, prototype, budget) {
141
189
  prototypes.set(value, prototype);
142
190
  }
143
191
  function isPrototypeRecord(value) {
192
+ if (isGuestClosure(value))
193
+ return true;
144
194
  if (isGuestHostObject(value))
145
195
  return false;
146
196
  if (isSandboxClosure(value) ||
@@ -167,6 +217,8 @@ export function hasGuestObjectState(value) {
167
217
  return true;
168
218
  if (functionProperties.has(value) || prototypes.has(value))
169
219
  return true;
220
+ if (isSandboxBox(value))
221
+ return false;
170
222
  return (descriptorObjects.has(value) &&
171
223
  Object.values(Object.getOwnPropertyDescriptors(value)).some((descriptor) => !descriptor.enumerable || !descriptor.configurable || !descriptor.writable));
172
224
  }
@@ -1,6 +1,7 @@
1
1
  import { SandboxError } from "./budget.js";
2
2
  import { coerceThrownValue, createSubsetErrorValue } from "./exceptions.js";
3
3
  import { getSandboxIterator } from "./iteration.js";
4
+ import { retainValues } from "./resources.js";
4
5
  import { runPromiseJob } from "./jobs.js";
5
6
  import { observeSandboxPromise } from "./promise-tracker.js";
6
7
  import { createSandboxClosure, createSandboxPromise, isSandboxClosure, isSandboxPromise } from "./values.js";
@@ -337,76 +338,82 @@ async function settleIterable(iterable, method, budget, constructor, context) {
337
338
  const promiseResolve = readPromiseReceiverProperty(constructor, "resolve", prototype);
338
339
  if (!isSandboxClosure(promiseResolve))
339
340
  throw new TypeError("Promise constructor requires a callable resolve.");
340
- const iterator = getSandboxIterator(iterable, budget);
341
+ const iterator = getSandboxIterator(iterable, budget, context);
341
342
  if (iterator === undefined)
342
343
  throw new TypeError("Promise helpers require an iterable.");
343
- let index = 0;
344
- while (true) {
345
- budget.visitNode();
346
- const next = iterator.generator
347
- ? await iterator.next()
348
- : iterator.next();
349
- if (typeof next !== "object" || next === null)
350
- throw new TypeError("Iterator result must be an object.");
351
- if (next.done)
352
- break;
353
- const value = next.value;
354
- try {
355
- budget.allocateArrayLength(index + 1);
356
- const entryIndex = index++;
357
- if (method !== "race")
358
- values.push(undefined);
359
- const entry = await callPromiseClosure(promiseResolve, [value], constructor, budget, context);
360
- let called = false;
361
- const handlers = ["fulfilled", "rejected"].map((state) => {
362
- if (method === "race" || (method === "any" && state === "fulfilled"))
363
- return state === "fulfilled" ? capability.resolve : capability.reject;
364
- if (method === "all" && state === "rejected")
365
- return capability.reject;
366
- return createSandboxClosure({
367
- sandbox: true,
368
- retainedValues: () => [
369
- capability.promise,
370
- capability.resolve,
371
- capability.reject,
372
- values
373
- ],
374
- call: async ([settlement]) => {
375
- if (called)
344
+ const releaseIterator = retainValues(budget, () => [iterator.retainedValue]);
345
+ try {
346
+ let index = 0;
347
+ while (true) {
348
+ budget.visitNode();
349
+ const next = iterator.generator || iterator.asynchronous
350
+ ? await iterator.next()
351
+ : iterator.next();
352
+ if (typeof next !== "object" || next === null)
353
+ throw new TypeError("Iterator result must be an object.");
354
+ if (next.done)
355
+ break;
356
+ const value = next.value;
357
+ try {
358
+ budget.allocateArrayLength(index + 1);
359
+ const entryIndex = index++;
360
+ if (method !== "race")
361
+ values.push(undefined);
362
+ const entry = await callPromiseClosure(promiseResolve, [value], constructor, budget, context);
363
+ let called = false;
364
+ const handlers = ["fulfilled", "rejected"].map((state) => {
365
+ if (method === "race" || (method === "any" && state === "fulfilled"))
366
+ return state === "fulfilled" ? capability.resolve : capability.reject;
367
+ if (method === "all" && state === "rejected")
368
+ return capability.reject;
369
+ return createSandboxClosure({
370
+ sandbox: true,
371
+ retainedValues: () => [
372
+ capability.promise,
373
+ capability.resolve,
374
+ capability.reject,
375
+ values
376
+ ],
377
+ call: async ([settlement]) => {
378
+ if (called)
379
+ return undefined;
380
+ called = true;
381
+ values[entryIndex] =
382
+ method === "allSettled"
383
+ ? state === "fulfilled"
384
+ ? { status: state, value: settlement }
385
+ : { status: state, reason: settlement }
386
+ : settlement;
387
+ remaining--;
388
+ await complete();
376
389
  return undefined;
377
- called = true;
378
- values[entryIndex] =
379
- method === "allSettled"
380
- ? state === "fulfilled"
381
- ? { status: state, value: settlement }
382
- : { status: state, reason: settlement }
383
- : settlement;
384
- remaining--;
385
- await complete();
386
- return undefined;
387
- }
390
+ }
391
+ });
388
392
  });
389
- });
390
- remaining++;
391
- const then = readPromiseReceiverProperty(entry, "then", prototype);
392
- if (!isSandboxClosure(then))
393
- throw new TypeError("Promise resolver result requires a callable then.");
394
- await callPromiseClosure(then, handlers, entry, budget, context);
395
- }
396
- catch (error) {
397
- try {
398
- const closed = iterator.return?.();
399
- if (iterator.generator)
400
- await closed;
393
+ remaining++;
394
+ const then = readPromiseReceiverProperty(entry, "then", prototype);
395
+ if (!isSandboxClosure(then))
396
+ throw new TypeError("Promise resolver result requires a callable then.");
397
+ await callPromiseClosure(then, handlers, entry, budget, context);
401
398
  }
402
- catch {
399
+ catch (error) {
400
+ try {
401
+ const closed = iterator.return?.();
402
+ if (iterator.generator || iterator.asynchronous)
403
+ await closed;
404
+ }
405
+ catch {
406
+ throw error;
407
+ }
403
408
  throw error;
404
409
  }
405
- throw error;
406
410
  }
411
+ remaining--;
412
+ await complete();
413
+ }
414
+ finally {
415
+ releaseIterator();
407
416
  }
408
- remaining--;
409
- await complete();
410
417
  }
411
418
  catch (error) {
412
419
  if (error instanceof SandboxError &&
@@ -9,6 +9,7 @@ import { getGuestFunctionProperties, getSandboxPrototype, hasExplicitSandboxProt
9
9
  import { getRegexMember } from "./methods/regex.js";
10
10
  import { retainValues } from "./resources.js";
11
11
  import { functionString } from "./function-string.js";
12
+ import { boxedValue, isSandboxBox } from "./boxed.js";
12
13
  import { isSandboxClosure, isSandboxPromise, isSandboxMap, isSandboxSet, isSandboxRegex, isSandboxGenerator } from "./values.js";
13
14
  const defaultStringHook = Symbol("defaultStringHook");
14
15
  const defaultValueHook = Symbol("defaultValueHook");
@@ -40,7 +41,7 @@ async function objectToPrimitive(value, budget, context, joining, hint) {
40
41
  result = await defaultToString(value, budget, context, joining);
41
42
  }
42
43
  else if (hook === defaultValueHook) {
43
- result = isSandboxDate(value) ? dateTime(value) : value;
44
+ result = isSandboxDate(value) ? dateTime(value) : isSandboxBox(value) ? boxedValue(value) : value;
44
45
  }
45
46
  else {
46
47
  if (!isSandboxClosure(hook))
@@ -97,6 +98,8 @@ function conversionHook(value, name, budget) {
97
98
  return undefined;
98
99
  }
99
100
  async function defaultToString(value, budget, context, joining) {
101
+ if (isSandboxBox(value))
102
+ return budget.allocateString(String(boxedValue(value)));
100
103
  if (isSandboxClosure(value))
101
104
  return budget.allocateString(functionString(value));
102
105
  if (isSandboxMap(value))
@@ -134,6 +134,7 @@ export declare function deepCopyToSandbox(value: unknown): SandboxValue;
134
134
  export declare function cloneSandboxValue(value: SandboxValue, options?: {
135
135
  compilation?: CompileScope;
136
136
  resetRegexLastIndex?: boolean;
137
+ structuredClone?: boolean;
137
138
  }): SandboxValue;
138
139
  export declare function allocateProducedSandboxValue(value: SandboxValue, budget: Budget): SandboxValue;
139
140
  export declare function measureSandboxData(values: Iterable<unknown>, options?: {
@@ -2,6 +2,7 @@ import { bindOtelSpan, getBoundOtelSpan } from "../observability/otel.js";
2
2
  import { isSandboxMap, isSandboxSet, sandboxMapBrand, sandboxSetBrand } from "./collection-brands.js";
3
3
  import { collectionIteratorState, isSandboxCollectionIterator, restoreSandboxCollectionIterator, snapshotCollectionIterator } from "./collection-iterator.js";
4
4
  import { copyNativeDate, exportDate, isSandboxDate } from "./date.js";
5
+ import { boxedDataProperties, boxedValue, createSandboxBox, isSandboxBox, nativeBoxedValue } from "./boxed.js";
5
6
  import { getHostObjectKeys, getHostObjectMember, hasHostObjectMember, measureHostObjectData, isGuestHostObject, isLiveCapability } from "./host-capabilities.js";
6
7
  import { types as nodeTypes } from "node:util";
7
8
  import { CompileScope, RegexCompileGuard, regexCompiledData } from "./regex/compile-guard.js";
@@ -13,7 +14,7 @@ import { invokeCancelableClosure, readPromiseCancellation, registerPromiseCancel
13
14
  import { parseRegex } from "./regex/parse.js";
14
15
  import { assertSandboxDataDepth } from "../graph-depth.js";
15
16
  import { sandboxErrorTypes } from "../error/shape.js";
16
- import { getGuestFunctionProperties, getSandboxPrototype, hasGuestObjectState, hasManagedDescriptors, isGuestClosure, registerGuestClosure } from "./object-model.js";
17
+ import { getGuestFunctionProperties, getSandboxPrototype, hasGuestObjectState, hasManagedDescriptors, isGuestClosure, isIntrinsicConstructor, registerGuestClosure } from "./object-model.js";
17
18
  import { copySandboxArgumentProperties, createSandboxArguments, getSandboxArgumentEntries, isSandboxArguments } from "./arguments.js";
18
19
  export { createSandboxArguments, isSandboxArguments } from "./arguments.js";
19
20
  export { isSandboxMap, isSandboxSet } from "./collection-brands.js";
@@ -243,6 +244,19 @@ export function measureSandboxData(values, options = {}) {
243
244
  assertSandboxDataDepth(depth);
244
245
  seen.add(value);
245
246
  usage += 1;
247
+ if (isSandboxBox(value)) {
248
+ const primitive = boxedValue(value);
249
+ usage += typeof primitive === "string" ? primitive.length : 8;
250
+ const prototype = getSandboxPrototype(value);
251
+ if (prototype !== null)
252
+ visit(prototype, depth + 1);
253
+ for (const [key, descriptor] of boxedDataProperties(value)) {
254
+ usage += key.length + 1;
255
+ if ("value" in descriptor)
256
+ visit(descriptor.value, depth + 1);
257
+ }
258
+ return;
259
+ }
246
260
  if (isSandboxDate(value)) {
247
261
  usage += 8;
248
262
  return;
@@ -311,8 +325,19 @@ export function measureSandboxData(values, options = {}) {
311
325
  if (isSandboxClosure(value)) {
312
326
  if (options.ignoreClosures)
313
327
  return;
314
- if (value.properties !== undefined)
315
- visit(value.properties, depth + 1);
328
+ if (value.properties !== undefined) {
329
+ if (isIntrinsicConstructor(value)) {
330
+ for (const [key, descriptor] of Object.entries(Object.getOwnPropertyDescriptors(value.properties))) {
331
+ if (key === "prototype" || key === "name" || key === "length")
332
+ continue;
333
+ usage += key.length + 1;
334
+ if ("value" in descriptor)
335
+ visit(descriptor.value, depth + 1);
336
+ }
337
+ }
338
+ else
339
+ visit(value.properties, depth + 1);
340
+ }
316
341
  if (!options.ignoreClosureCaptures)
317
342
  for (const retained of value[sandboxRetainedValues]?.() ?? [])
318
343
  visit(retained, depth + 1);
@@ -457,6 +482,26 @@ function copyToSandbox(value, state, path = "<root>", cloneSandboxCollections =
457
482
  if (typeof value === "object" && value !== null && hasGuestObjectState(value)) {
458
483
  throw new TypeError("Guest prototype links and custom descriptors cannot be copied as data.");
459
484
  }
485
+ const primitive = nativeBoxedValue(value);
486
+ if (primitive !== undefined) {
487
+ const original = value;
488
+ const existing = state.seen.get(original);
489
+ if (existing !== undefined)
490
+ return existing;
491
+ const copy = createSandboxBox(primitive);
492
+ state.seen.set(original, copy);
493
+ if (!state.structuredClone) {
494
+ for (const [key, descriptor] of boxedDataProperties(original)) {
495
+ if (!("value" in descriptor))
496
+ throw new TypeError("Boxed data cannot contain accessors.");
497
+ Object.defineProperty(copy, key, {
498
+ ...descriptor,
499
+ value: copyToSandbox(descriptor.value, state, joinPath(path, key), cloneSandboxCollections, depth + 1)
500
+ });
501
+ }
502
+ }
503
+ return copy;
504
+ }
460
505
  if (isSandboxMap(value)) {
461
506
  if (!cloneSandboxCollections)
462
507
  return value;
@@ -598,6 +643,22 @@ function copyFromSandbox(value, state, path = "<root>", options, depth = 0) {
598
643
  if (!isSandboxClosure(value) && hasGuestObjectState(value)) {
599
644
  throw new TypeError("Guest prototype links and custom descriptors cannot be copied as data.");
600
645
  }
646
+ if (isSandboxBox(value)) {
647
+ const existing = state.seen.get(value);
648
+ if (existing !== undefined)
649
+ return existing;
650
+ const copy = Object(boxedValue(value));
651
+ state.seen.set(value, copy);
652
+ for (const [key, descriptor] of boxedDataProperties(value)) {
653
+ if (!("value" in descriptor))
654
+ throw new TypeError("Boxed data cannot contain accessors.");
655
+ Object.defineProperty(copy, key, {
656
+ ...descriptor,
657
+ value: copyFromSandbox(descriptor.value, state, joinPath(path, key), options, depth + 1)
658
+ });
659
+ }
660
+ return copy;
661
+ }
601
662
  const regexBrand = Object.getOwnPropertyDescriptor(value, sandboxRegexBrand);
602
663
  if (regexBrand !== undefined) {
603
664
  if (!("value" in regexBrand) || regexBrand.value !== true) {
@@ -759,6 +820,20 @@ function allocateSandboxValue(value, budget, seen) {
759
820
  budget.allocateString(value);
760
821
  return;
761
822
  }
823
+ if (isSandboxBox(value)) {
824
+ if (seen.has(value))
825
+ return;
826
+ seen.add(value);
827
+ const primitive = boxedValue(value);
828
+ if (typeof primitive === "string")
829
+ budget.allocateString(primitive);
830
+ for (const [key, descriptor] of boxedDataProperties(value)) {
831
+ budget.allocateString(key);
832
+ if ("value" in descriptor)
833
+ allocateSandboxValue(descriptor.value, budget, seen);
834
+ }
835
+ return;
836
+ }
762
837
  if (isFloat32Array(value)) {
763
838
  if (seen.has(value))
764
839
  return;
@@ -13,14 +13,14 @@ export declare function inspectSnapshotMigration(snapshot: SafeJSSnapshot, optio
13
13
  checkpointDigest: string;
14
14
  sourceHash: string;
15
15
  executionSemantics: string;
16
- calls: (Omit<import("./index.js").HostCallRecord, "outcome" | "asynchronous"> & {
16
+ calls: (Omit<import("./index.js").HostCallRecord, "asynchronous" | "outcome"> & {
17
17
  asynchronous: boolean;
18
18
  outcome?: {
19
19
  status: "fulfilled" | "rejected";
20
20
  data: import("./snapshot/replay-data.js").ReplayData;
21
21
  };
22
22
  })[];
23
- unresolvedCalls: (Omit<import("./index.js").HostCallRecord, "outcome" | "asynchronous"> & {
23
+ unresolvedCalls: (Omit<import("./index.js").HostCallRecord, "asynchronous" | "outcome"> & {
24
24
  asynchronous: boolean;
25
25
  outcome?: {
26
26
  status: "fulfilled" | "rejected";
@@ -13,14 +13,14 @@ export declare function migrateSnapshotFile(options: SnapshotMigrationFileOption
13
13
  checkpointDigest: string;
14
14
  sourceHash: string;
15
15
  executionSemantics: string;
16
- calls: (Omit<import("./index.js").HostCallRecord, "outcome" | "asynchronous"> & {
16
+ calls: (Omit<import("./index.js").HostCallRecord, "asynchronous" | "outcome"> & {
17
17
  asynchronous: boolean;
18
18
  outcome?: {
19
19
  status: "fulfilled" | "rejected";
20
20
  data: import("./snapshot/replay-data.js").ReplayData;
21
21
  };
22
22
  })[];
23
- unresolvedCalls: (Omit<import("./index.js").HostCallRecord, "outcome" | "asynchronous"> & {
23
+ unresolvedCalls: (Omit<import("./index.js").HostCallRecord, "asynchronous" | "outcome"> & {
24
24
  asynchronous: boolean;
25
25
  outcome?: {
26
26
  status: "fulfilled" | "rejected";
@@ -35,14 +35,14 @@ export declare function migrateSnapshotFile(options: SnapshotMigrationFileOption
35
35
  checkpointDigest: string;
36
36
  sourceHash: string;
37
37
  executionSemantics: string;
38
- calls: (Omit<import("./index.js").HostCallRecord, "outcome" | "asynchronous"> & {
38
+ calls: (Omit<import("./index.js").HostCallRecord, "asynchronous" | "outcome"> & {
39
39
  asynchronous: boolean;
40
40
  outcome?: {
41
41
  status: "fulfilled" | "rejected";
42
42
  data: import("./snapshot/replay-data.js").ReplayData;
43
43
  };
44
44
  })[];
45
- unresolvedCalls: (Omit<import("./index.js").HostCallRecord, "outcome" | "asynchronous"> & {
45
+ unresolvedCalls: (Omit<import("./index.js").HostCallRecord, "asynchronous" | "outcome"> & {
46
46
  asynchronous: boolean;
47
47
  outcome?: {
48
48
  status: "fulfilled" | "rejected";
@@ -0,0 +1,16 @@
1
+ import { type SandboxBox } from "../interp/boxed.js";
2
+ import type { SandboxValue } from "../interp/values.js";
3
+ export type BoxedData<T> = {
4
+ kind: "boxed";
5
+ value: T;
6
+ properties: Record<string, {
7
+ value: T;
8
+ configurable: boolean;
9
+ enumerable: boolean;
10
+ writable: boolean;
11
+ }>;
12
+ extensible: boolean;
13
+ };
14
+ export declare function encodeBoxedData<T>(value: SandboxBox, encode: (value: SandboxValue, key: string) => T): BoxedData<T>;
15
+ export declare function restoreBoxedProperties<T>(value: SandboxBox, data: BoxedData<T>, decode: (value: T) => unknown): void;
16
+ export declare function validateBoxedProperties(data: Record<string, unknown>): void;
@@ -0,0 +1,52 @@
1
+ import { boxedDataProperties, boxedValue } from "../interp/boxed.js";
2
+ export function encodeBoxedData(value, encode) {
3
+ const properties = Object.create(null);
4
+ for (const [key, descriptor] of boxedDataProperties(value)) {
5
+ if (!("value" in descriptor))
6
+ throw new TypeError("Boxed data cannot contain accessors.");
7
+ properties[key] = {
8
+ value: encode(descriptor.value, key),
9
+ configurable: descriptor.configurable === true,
10
+ enumerable: descriptor.enumerable === true,
11
+ writable: descriptor.writable === true
12
+ };
13
+ }
14
+ return {
15
+ kind: "boxed",
16
+ value: encode(boxedValue(value), "<payload>"),
17
+ properties,
18
+ extensible: Object.isExtensible(value)
19
+ };
20
+ }
21
+ export function restoreBoxedProperties(value, data, decode) {
22
+ for (const [key, descriptor] of Object.entries(data.properties))
23
+ Object.defineProperty(value, key, { ...descriptor, value: decode(descriptor.value) });
24
+ if (!data.extensible)
25
+ Object.preventExtensions(value);
26
+ }
27
+ export function validateBoxedProperties(data) {
28
+ if (!Object.hasOwn(data, "kind") ||
29
+ data.kind !== "boxed" ||
30
+ !Object.hasOwn(data, "value") ||
31
+ !Object.hasOwn(data, "extensible") ||
32
+ !Object.hasOwn(data, "properties") ||
33
+ Object.keys(data).length !== 4 ||
34
+ typeof data.extensible !== "boolean" ||
35
+ typeof data.properties !== "object" ||
36
+ data.properties === null ||
37
+ Array.isArray(data.properties))
38
+ throw new TypeError("Invalid boxed primitive properties.");
39
+ for (const descriptor of Object.values(data.properties)) {
40
+ if (typeof descriptor !== "object" ||
41
+ descriptor === null ||
42
+ !Object.hasOwn(descriptor, "value") ||
43
+ !Object.hasOwn(descriptor, "writable") ||
44
+ !Object.hasOwn(descriptor, "enumerable") ||
45
+ !Object.hasOwn(descriptor, "configurable") ||
46
+ typeof descriptor.writable !== "boolean" ||
47
+ typeof descriptor.enumerable !== "boolean" ||
48
+ typeof descriptor.configurable !== "boolean" ||
49
+ Object.keys(descriptor).length !== 4)
50
+ throw new TypeError("Invalid boxed primitive descriptor.");
51
+ }
52
+ }
@@ -9,6 +9,8 @@ import { requiresArrayEntries, serializeArray } from "./arrays.js";
9
9
  import { float32DataProperties, isFloat32Array } from "../interp/float32.js";
10
10
  import { encodeFloat32Storage } from "./float32array.js";
11
11
  import { isSandboxDate, serializedDateTime } from "../interp/date.js";
12
+ import { boxedDataProperties, isSandboxBox } from "../interp/boxed.js";
13
+ import { encodeBoxedData } from "./boxed.js";
12
14
  const SKIP_VALUE = Symbol("SafeJS.skip-dump-value");
13
15
  export function serializeSafeJSSnapshot(snapshot) {
14
16
  const replayError = Object.getOwnPropertyDescriptor(snapshot, "replayError");
@@ -73,7 +75,7 @@ function serializeDumpValue(value, path, state) {
73
75
  if (hasGuestObjectState(value)) {
74
76
  throw new TypeError("Guest function properties and prototype links cannot be serialized.");
75
77
  }
76
- if (isSandboxDate(value) || isFloat32Array(value))
78
+ if (isSandboxBox(value) || isSandboxDate(value) || isFloat32Array(value))
77
79
  return serializeHeapReference(value, path, state);
78
80
  if (Array.isArray(value)) {
79
81
  const reference = serializeHeapReference(value, path, state);
@@ -98,7 +100,15 @@ function serializeHeapReference(value, path, state) {
98
100
  }
99
101
  if (!state.serializedHeapIds.has(id)) {
100
102
  state.serializedHeapIds.add(id);
101
- if (isSandboxDate(value)) {
103
+ if (isSandboxBox(value)) {
104
+ state.heap[String(id)] = encodeBoxedData(value, (entry, key) => {
105
+ if (Object.is(entry, -0))
106
+ return { kind: "number", value: "-0" };
107
+ const serialized = serializeDumpValue(entry, `${path}.${key}`, state);
108
+ return serialized === SKIP_VALUE ? { kind: "undefined" } : serialized;
109
+ });
110
+ }
111
+ else if (isSandboxDate(value)) {
102
112
  state.heap[String(id)] = { kind: "date", time: serializedDateTime(value) };
103
113
  }
104
114
  else if (isFloat32Array(value)) {
@@ -163,6 +173,7 @@ function indexHeapContainers(snapshot) {
163
173
  for (const [value, stat] of stats.entries()) {
164
174
  if (stat.count > 1 ||
165
175
  stat.cyclic ||
176
+ isSandboxBox(value) ||
166
177
  isSandboxDate(value) ||
167
178
  isFloat32Array(value) ||
168
179
  (Array.isArray(value) && requiresArrayEntries(value)) ||
@@ -200,9 +211,11 @@ function collectContainerStats(value, stats, ancestors) {
200
211
  }
201
212
  stat.expanded = true;
202
213
  ancestors.add(value);
203
- const entries = isSandboxArguments(value)
204
- ? getSandboxArgumentEntries(value).map(([, entry]) => entry)
205
- : getEnumerableDataValues(value);
214
+ const entries = isSandboxBox(value)
215
+ ? boxedDataProperties(value).map(([, descriptor]) => descriptor.value)
216
+ : isSandboxArguments(value)
217
+ ? getSandboxArgumentEntries(value).map(([, entry]) => entry)
218
+ : getEnumerableDataValues(value);
206
219
  for (const entry of entries) {
207
220
  collectContainerStats(entry, stats, ancestors);
208
221
  }