poe-code 14.0.6 → 14.0.8

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 (54) hide show
  1. package/dist/index.js +944 -143
  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-2M6MNQBY.js → chunk-MO3YD4ID.js} +888 -133
  6. package/packages/safe-js/dist/chunks/chunk-MO3YD4ID.js.map +7 -0
  7. package/packages/safe-js/dist/chunks/{chunk-QSSQJNKE.js → chunk-RXLDJ6LQ.js} +2 -2
  8. package/packages/safe-js/dist/cli.js +2 -2
  9. package/packages/safe-js/dist/core.d.ts +1 -1
  10. package/packages/safe-js/dist/core.js +1 -1
  11. package/packages/safe-js/dist/extensions.d.ts +2 -0
  12. package/packages/safe-js/dist/index.d.ts +1 -1
  13. package/packages/safe-js/dist/index.js +2 -2
  14. package/packages/safe-js/dist/index.js.map +1 -1
  15. package/packages/safe-js/dist/interp/async.js +1 -0
  16. package/packages/safe-js/dist/interp/date.d.ts +19 -0
  17. package/packages/safe-js/dist/interp/date.js +129 -0
  18. package/packages/safe-js/dist/interp/globals/console-json.js +5 -1
  19. package/packages/safe-js/dist/interp/globals/date.d.ts +10 -0
  20. package/packages/safe-js/dist/interp/globals/date.js +95 -0
  21. package/packages/safe-js/dist/interp/globals/object-array.d.ts +4 -3
  22. package/packages/safe-js/dist/interp/globals/object-array.js +42 -5
  23. package/packages/safe-js/dist/interp/globals/object.d.ts +4 -0
  24. package/packages/safe-js/dist/interp/globals/object.js +153 -0
  25. package/packages/safe-js/dist/interp/globals.d.ts +4 -1
  26. package/packages/safe-js/dist/interp/globals.js +2 -0
  27. package/packages/safe-js/dist/interp/host-bridge.d.ts +4 -0
  28. package/packages/safe-js/dist/interp/host-bridge.js +21 -2
  29. package/packages/safe-js/dist/interp/host-call.js +4 -0
  30. package/packages/safe-js/dist/interp/host-capabilities.d.ts +28 -1
  31. package/packages/safe-js/dist/interp/host-capabilities.js +220 -5
  32. package/packages/safe-js/dist/interp/interpreter.js +46 -11
  33. package/packages/safe-js/dist/interp/iteration.js +3 -0
  34. package/packages/safe-js/dist/interp/object-model.d.ts +3 -1
  35. package/packages/safe-js/dist/interp/object-model.js +39 -9
  36. package/packages/safe-js/dist/interp/string-coercion.js +3 -0
  37. package/packages/safe-js/dist/interp/values.d.ts +3 -1
  38. package/packages/safe-js/dist/interp/values.js +34 -6
  39. package/packages/safe-js/dist/lint/rules/known-globals.d.ts +1 -1
  40. package/packages/safe-js/dist/lint/rules/known-globals.js +1 -0
  41. package/packages/safe-js/dist/parse/parser.js +4 -2
  42. package/packages/safe-js/dist/realm.d.ts +4 -1
  43. package/packages/safe-js/dist/realm.js +89 -10
  44. package/packages/safe-js/dist/run.d.ts +2 -0
  45. package/packages/safe-js/dist/run.js +3 -1
  46. package/packages/safe-js/dist/snapshot/dump-format.js +8 -3
  47. package/packages/safe-js/dist/snapshot/replay-data.d.ts +4 -1
  48. package/packages/safe-js/dist/snapshot/replay-data.js +11 -0
  49. package/packages/safe-js/dist/snapshot/restore.js +6 -0
  50. package/packages/safe-js/dist/snapshot/serialize.d.ts +5 -2
  51. package/packages/safe-js/dist/snapshot/serialize.js +8 -2
  52. package/packages/safe-js/dist/snapshot/validation.js +18 -1
  53. package/packages/safe-js/dist/chunks/chunk-2M6MNQBY.js.map +0 -7
  54. /package/packages/safe-js/dist/chunks/{chunk-QSSQJNKE.js.map → chunk-RXLDJ6LQ.js.map} +0 -0
@@ -3,6 +3,7 @@ import { types } from "node:util";
3
3
  import { Budget, SandboxError } from "./interp/budget.js";
4
4
  import { CompileScope } from "./interp/regex/compile-guard.js";
5
5
  import { createBuiltinBindings } from "./interp/globals.js";
6
+ import { releaseObjectPrototype } from "./interp/object-model.js";
6
7
  import { interpret, Scope } from "./interp/interpreter.js";
7
8
  import { SandboxJobQueue, runAsyncPrefix, suspendJob } from "./interp/jobs.js";
8
9
  import { withCancellationSignal, awaitSandboxValue } from "./interp/cancel.js";
@@ -10,7 +11,7 @@ import { enterRunningState } from "./interp/running-state.js";
10
11
  import { runResources } from "./interp/resources.js";
11
12
  import { createSandboxPromiseRejectionTracker, withSandboxPromiseRejectionTracker } from "./interp/promise-tracker.js";
12
13
  import { copyHostValueToSandbox, wrapCallerInjectedBindings } from "./interp/host-bridge.js";
13
- import { createLiveHostObject, exportHostCapability, readGuestCallback, registerGuestCallback, revokeGuestCallback, revokeHostObject } from "./interp/host-capabilities.js";
14
+ import { createLiveHostObject, createGuestReference, exportHostCapability, readGuestReference, readGuestCallback, registerGuestCallback, revokeGuestCallback, revokeHostObject, revokeGuestReference } from "./interp/host-capabilities.js";
14
15
  import { deepCopyFromSandbox, isSandboxClosure, measureSandboxData, reconcileCompiledValues } from "./interp/values.js";
15
16
  import { getExtensionSetup, readDataRecord, readStringList } from "./extensions.js";
16
17
  import { resolveModuleImports } from "./modules/registry.js";
@@ -36,6 +37,8 @@ class RealmState {
36
37
  pendingCallbacks = new Set();
37
38
  callbackCache = new WeakMap();
38
39
  hostObjects = new Set();
40
+ guestReferences = new Map();
41
+ retainedOperations = new WeakMap();
39
42
  nestedOperations = new WeakMap();
40
43
  convertedModules = new Map();
41
44
  nativeConversions = { seen: new WeakMap() };
@@ -56,6 +59,7 @@ class RealmState {
56
59
  extensions: 32,
57
60
  hostObjects: 1024,
58
61
  callbacks: 1024,
62
+ guestReferences: 1024,
59
63
  cleanups: 1024,
60
64
  nestedEvaluations: 16
61
65
  };
@@ -94,6 +98,7 @@ class RealmState {
94
98
  owner: this,
95
99
  assertActive: this.assertOpen,
96
100
  wrapCallback: this.wrapCallback,
101
+ captureArguments: this.captureArguments,
97
102
  invoke: this.invokeHost,
98
103
  awaitResult: (operation) => this.nestedOperations.has(operation)
99
104
  };
@@ -102,6 +107,7 @@ class RealmState {
102
107
  budget: this.budget,
103
108
  compileOwner: this.lease.owner,
104
109
  sink: options.sink,
110
+ clock: options.clock,
105
111
  random: createReplayableRandom({ seed: options.randomSeed }).next
106
112
  });
107
113
  const names = new Set();
@@ -134,10 +140,11 @@ class RealmState {
134
140
  options.signal?.addEventListener("abort", this.abort, { once: true });
135
141
  if (options.signal?.aborted)
136
142
  this.abort();
137
- this.budget.setRetainedValues(this, this.retainedCallbacks);
143
+ this.budget.setRetainedValues(this, this.retainedRoots);
138
144
  this.tracker.onFatalRejection((error) => this.poison(error));
139
145
  }
140
146
  catch (error) {
147
+ releaseObjectPrototype(this.budget);
141
148
  this.compilation.dispose();
142
149
  this.lease.release();
143
150
  throw error;
@@ -149,10 +156,48 @@ class RealmState {
149
156
  signal: this.controller.signal,
150
157
  realm: this.bridge
151
158
  });
152
- retainedCallbacks = () => [
159
+ retainedRoots = () => [
153
160
  ...this.callbacks.values(),
154
- ...Array.from(this.pendingCallbacks, (pending) => pending.closure)
161
+ ...Array.from(this.pendingCallbacks, (pending) => pending.closure),
162
+ ...this.guestReferences.values()
155
163
  ];
164
+ captureArguments = (operation, args, copy) => {
165
+ const from = this.retainedOperations.get(operation)?.from ?? args.length;
166
+ const values = copy(args.slice(0, from));
167
+ const captured = [];
168
+ const rollback = () => {
169
+ for (const reference of captured) {
170
+ revokeGuestReference(reference, this);
171
+ this.guestReferences.delete(reference);
172
+ }
173
+ };
174
+ try {
175
+ for (const value of args.slice(from)) {
176
+ this.checkCollection(this.guestReferences.size + 1, this.limits.guestReferences, "guest reference");
177
+ const root = [value];
178
+ const reference = createGuestReference(root, this, this.assertOpen);
179
+ this.guestReferences.set(reference, root);
180
+ captured.push(reference);
181
+ values.push(reference);
182
+ }
183
+ if (captured.length > 0)
184
+ this.budget.reconcileDataUsage(measureSandboxData([...(this.scope?.retainedValues() ?? []), ...this.retainedRoots()]));
185
+ return { args: values, rollback };
186
+ }
187
+ catch (error) {
188
+ rollback();
189
+ if (error instanceof SandboxError)
190
+ this.poison(error);
191
+ throw error;
192
+ }
193
+ };
194
+ releaseGuestReference = (reference) => {
195
+ readGuestReference(reference, this);
196
+ revokeGuestReference(reference, this);
197
+ this.guestReferences.delete(reference);
198
+ if (this.active === undefined)
199
+ reconcileCompiledValues(this.budget, [...(this.scope?.retainedValues() ?? []), ...this.retainedRoots()], this.compilation);
200
+ };
156
201
  assertOpen = () => {
157
202
  if (this.failure !== undefined)
158
203
  throw this.failure.reason;
@@ -267,13 +312,23 @@ class RealmState {
267
312
  owner: this,
268
313
  assertActive: this.assertOpen,
269
314
  chargeWork: this.chargeWork,
270
- read: (operation) => {
315
+ checkLength: (length) => this.budget.allocateArrayLength(length),
316
+ checkTemporaryDataSize: (size) => {
317
+ const temporary = {};
318
+ try {
319
+ this.budget.setRetainedDataUsage(temporary, size);
320
+ }
321
+ finally {
322
+ this.budget.setRetainedDataUsage(temporary, 0);
323
+ }
324
+ },
325
+ read: (operation, validate) => {
271
326
  const value = this.invokeHost(operation, operation);
272
327
  if (types.isPromise(value)) {
273
328
  void Promise.resolve(value).catch(() => undefined);
274
329
  throw new TypeError("Live property getters must be synchronous.");
275
330
  }
276
- return this.importValue(value);
331
+ return this.importValue(validate === undefined ? value : validate(value));
277
332
  },
278
333
  write: (operation, value) => {
279
334
  const result = this.invokeHost(operation, () => operation(this.exportValue(value)));
@@ -321,7 +376,7 @@ class RealmState {
321
376
  }
322
377
  });
323
378
  try {
324
- this.budget.reconcileDataUsage(measureSandboxData([...(this.scope?.retainedValues() ?? []), ...this.retainedCallbacks()]));
379
+ this.budget.reconcileDataUsage(measureSandboxData([...(this.scope?.retainedValues() ?? []), ...this.retainedRoots()]));
325
380
  }
326
381
  catch (error) {
327
382
  this.poison(error);
@@ -334,7 +389,7 @@ class RealmState {
334
389
  revokeGuestCallback(callback, this);
335
390
  this.callbacks.delete(callback);
336
391
  if (this.active === undefined)
337
- reconcileCompiledValues(this.budget, [...(this.scope?.retainedValues() ?? []), ...this.retainedCallbacks()], this.compilation);
392
+ reconcileCompiledValues(this.budget, [...(this.scope?.retainedValues() ?? []), ...this.retainedRoots()], this.compilation);
338
393
  };
339
394
  invokeCallback = async (callback, options = {}) => {
340
395
  if (this.closed || this.failure !== undefined)
@@ -384,7 +439,7 @@ class RealmState {
384
439
  finally {
385
440
  this.pendingCallbacks.delete(record);
386
441
  if (!this.closed && this.active === undefined)
387
- reconcileCompiledValues(this.budget, [...(this.scope?.retainedValues() ?? []), ...this.retainedCallbacks()], this.compilation);
442
+ reconcileCompiledValues(this.budget, [...(this.scope?.retainedValues() ?? []), ...this.retainedRoots()], this.compilation);
388
443
  }
389
444
  };
390
445
  initialize() {
@@ -400,6 +455,24 @@ class RealmState {
400
455
  createHostObject: this.createHostObject,
401
456
  invokeCallback: this.invokeCallback,
402
457
  releaseCallback: this.releaseCallback,
458
+ releaseGuestReference: this.releaseGuestReference,
459
+ retainGuestArguments: (operation, from) => {
460
+ this.assertOpen();
461
+ if (!extension.manifest.capabilities?.includes("guest:retain"))
462
+ throw new TypeError("Retaining arguments requires the guest:retain grant.");
463
+ if (typeof operation !== "function")
464
+ throw new TypeError("Retained operation must be a function.");
465
+ if (!Number.isSafeInteger(from) || from < 0)
466
+ throw new TypeError("Argument index must be a non-negative safe integer.");
467
+ if (this.scope !== undefined)
468
+ throw new TypeError("Retained operations must be registered during setup.");
469
+ const previous = this.retainedOperations.get(operation);
470
+ if (previous !== undefined &&
471
+ (previous.extension !== extension || previous.from !== from))
472
+ throw new TypeError("Conflicting retained operation declaration.");
473
+ this.retainedOperations.set(operation, { extension, from });
474
+ return operation;
475
+ },
403
476
  nestedOperation: (operation) => {
404
477
  this.assertOpen();
405
478
  if (!extension.manifest.capabilities?.includes("source:nested"))
@@ -552,7 +625,7 @@ class RealmState {
552
625
  if (this.failure !== undefined)
553
626
  throw this.failure.reason;
554
627
  if (!this.closed)
555
- reconcileCompiledValues(this.budget, [...(this.scope?.retainedValues() ?? []), ...this.retainedCallbacks()], this.compilation);
628
+ reconcileCompiledValues(this.budget, [...(this.scope?.retainedValues() ?? []), ...this.retainedRoots()], this.compilation);
556
629
  return result;
557
630
  }
558
631
  catch (error) {
@@ -591,7 +664,11 @@ class RealmState {
591
664
  for (const object of this.hostObjects)
592
665
  revokeHostObject(object, this);
593
666
  this.hostObjects.clear();
667
+ for (const reference of this.guestReferences.keys())
668
+ revokeGuestReference(reference, this);
669
+ this.guestReferences.clear();
594
670
  this.budget.setRetainedValues(this, undefined);
671
+ releaseObjectPrototype(this.budget);
595
672
  this.disposal = (async () => {
596
673
  const errors = [];
597
674
  for (const cleanup of this.cleanups.splice(0).reverse()) {
@@ -651,6 +728,7 @@ export function createRealm(options = {}) {
651
728
  evaluate: state.evaluate,
652
729
  invokeCallback: state.invokeCallback,
653
730
  releaseCallback: state.releaseCallback,
731
+ releaseGuestReference: state.releaseGuestReference,
654
732
  close: state.close
655
733
  });
656
734
  }
@@ -702,6 +780,7 @@ function readRealmOptions(value, oneShot = false) {
702
780
  "signal",
703
781
  "sink",
704
782
  "randomSeed",
783
+ "clock",
705
784
  "limits"
706
785
  ]);
707
786
  for (const [key, entry] of Object.entries(options)) {
@@ -63,6 +63,8 @@ export type RunClockSnapshot = {
63
63
  next: number;
64
64
  };
65
65
  export type RunClock = {
66
+ now?: () => number;
67
+ restore?: (snapshot: RunClockSnapshot) => void;
66
68
  snapshot: () => RunClockSnapshot | undefined;
67
69
  };
68
70
  export type RunRandom = {
@@ -14,6 +14,7 @@ import { enterSnapshotRun } from "./interp/running-state.js";
14
14
  import { createSandboxPromiseRejectionTracker, observeSandboxPromise, withSandboxPromiseRejectionTracker } from "./interp/promise-tracker.js";
15
15
  import { HostCallJournal } from "./interp/host-call.js";
16
16
  import { createBuiltinBindings } from "./interp/globals.js";
17
+ import { releaseObjectPrototype } from "./interp/object-model.js";
17
18
  import { declareHostOperation, wrapCallerInjectedBindings } from "./interp/host-bridge.js";
18
19
  import { interpret, Scope } from "./interp/interpreter.js";
19
20
  import { consumeSettledHostCall } from "./interp/promise.js";
@@ -109,7 +110,7 @@ export function run(source, options = {}) {
109
110
  signal: options.signal,
110
111
  lifecycle
111
112
  }));
112
- const builtinBindings = createBuiltinBindings({ compileOwner: operation.owner, budget, hostCalls, sink: options.sink, random: random?.generator.next });
113
+ const builtinBindings = createBuiltinBindings({ compileOwner: operation.owner, budget, hostCalls, sink: options.sink, random: random?.generator.next, clock: options.clock });
113
114
  const importMeta = convertInitialInput(() => deepCopyToSandbox(options.importMeta ?? {}));
114
115
  if (restoredSnapshot?.migration !== undefined) {
115
116
  if (Object.hasOwn(importMeta, "migration"))
@@ -349,6 +350,7 @@ export function run(source, options = {}) {
349
350
  });
350
351
  }
351
352
  finally {
353
+ releaseObjectPrototype(budget);
352
354
  compilation.dispose();
353
355
  operation.release();
354
356
  }
@@ -8,6 +8,7 @@ import { serializeArguments } from "./arguments.js";
8
8
  import { requiresArrayEntries, serializeArray } from "./arrays.js";
9
9
  import { float32DataProperties, isFloat32Array } from "../interp/float32.js";
10
10
  import { encodeFloat32Storage } from "./float32array.js";
11
+ import { isSandboxDate, serializedDateTime } from "../interp/date.js";
11
12
  const SKIP_VALUE = Symbol("SafeJS.skip-dump-value");
12
13
  export function serializeSafeJSSnapshot(snapshot) {
13
14
  const replayError = Object.getOwnPropertyDescriptor(snapshot, "replayError");
@@ -72,7 +73,7 @@ function serializeDumpValue(value, path, state) {
72
73
  if (hasGuestObjectState(value)) {
73
74
  throw new TypeError("Guest function properties and prototype links cannot be serialized.");
74
75
  }
75
- if (isFloat32Array(value))
76
+ if (isSandboxDate(value) || isFloat32Array(value))
76
77
  return serializeHeapReference(value, path, state);
77
78
  if (Array.isArray(value)) {
78
79
  const reference = serializeHeapReference(value, path, state);
@@ -97,7 +98,10 @@ function serializeHeapReference(value, path, state) {
97
98
  }
98
99
  if (!state.serializedHeapIds.has(id)) {
99
100
  state.serializedHeapIds.add(id);
100
- if (isFloat32Array(value)) {
101
+ if (isSandboxDate(value)) {
102
+ state.heap[String(id)] = { kind: "date", time: serializedDateTime(value) };
103
+ }
104
+ else if (isFloat32Array(value)) {
101
105
  const storage = encodeFloat32Storage(value, id, state.float32Buffers, (id) => ({
102
106
  kind: "ref",
103
107
  id
@@ -159,6 +163,7 @@ function indexHeapContainers(snapshot) {
159
163
  for (const [value, stat] of stats.entries()) {
160
164
  if (stat.count > 1 ||
161
165
  stat.cyclic ||
166
+ isSandboxDate(value) ||
162
167
  isFloat32Array(value) ||
163
168
  (Array.isArray(value) && requiresArrayEntries(value)) ||
164
169
  isSandboxArguments(value) ||
@@ -173,7 +178,7 @@ function collectContainerStats(value, stats, ancestors) {
173
178
  if (value === null || typeof value !== "object") {
174
179
  return;
175
180
  }
176
- if (!Array.isArray(value) && !isPlainObject(value) && !isFloat32Array(value)) {
181
+ if (!Array.isArray(value) && !isPlainObject(value) && !isFloat32Array(value) && !isSandboxDate(value)) {
177
182
  return;
178
183
  }
179
184
  let stat = stats.get(value);
@@ -24,7 +24,10 @@ type Properties = Record<string, {
24
24
  enumerable: boolean;
25
25
  writable: boolean;
26
26
  }>;
27
- type DataNode = (Float32Data<Atom> & {
27
+ type DataNode = {
28
+ kind: "date";
29
+ time: number | null;
30
+ } | (Float32Data<Atom> & {
28
31
  properties: Properties;
29
32
  extensible: boolean;
30
33
  }) | {
@@ -3,6 +3,7 @@ import { hasGuestObjectState } from "../interp/object-model.js";
3
3
  import { CompileScope } from "../interp/regex/compile-guard.js";
4
4
  import { float32DataProperties, isFloat32Array } from "../interp/float32.js";
5
5
  import { decodeFloat32Storage, encodeFloat32Storage } from "./float32array.js";
6
+ import { isSandboxDate, restoreDateTime, serializedDateTime } from "../interp/date.js";
6
7
  import { sandboxErrorNames, sandboxErrorTypes } from "../error/shape.js";
7
8
  import { createSandboxArguments, createSandboxClosure, createSandboxMap, createSandboxRegex, createSandboxSet, isSandboxArguments, isSandboxClosure, isSandboxGenerator, isSandboxMap, isSandboxPromise, isSandboxRegex, isSandboxSet } from "../interp/values.js";
8
9
  import { serializeArguments } from "./arguments.js";
@@ -63,6 +64,9 @@ export function encodeReplayData(value, options = {}) {
63
64
  properties: child(entry.properties, "properties")
64
65
  };
65
66
  }
67
+ else if (isSandboxDate(entry)) {
68
+ nodes[id] = { kind: "date", time: serializedDateTime(entry) };
69
+ }
66
70
  else if (isFloat32Array(entry)) {
67
71
  const storage = encodeFloat32Storage(entry, id, float32Buffers, (id) => ({
68
72
  tag: "ref",
@@ -231,6 +235,13 @@ export function decodeReplayData(input, options = {}, parent) {
231
235
  options.onCapabilityRestored?.(capability, copy);
232
236
  return copy;
233
237
  }
238
+ if (kind === "date") {
239
+ if (Object.keys(node).length !== 2)
240
+ throw new TypeError("Invalid serialized Date fields.");
241
+ const result = restoreDateTime(own(node, "time"));
242
+ restored.set(id, result);
243
+ return result;
244
+ }
234
245
  if (kind === "float32array") {
235
246
  if (typeof node.extensible !== "boolean")
236
247
  throw new TypeError("Invalid Float32Array extensibility.");
@@ -1,6 +1,7 @@
1
1
  import { Budget, SandboxError } from "../interp/budget.js";
2
2
  import { CompileScope } from "../interp/regex/compile-guard.js";
3
3
  import { decodeFloat32Storage } from "./float32array.js";
4
+ import { restoreDateTime } from "../interp/date.js";
4
5
  import { sandboxErrorTypes } from "../error/shape.js";
5
6
  import { SnapshotMismatchError } from "../restore.js";
6
7
  import { Scope, setSandboxProperty } from "../interp/interpreter.js";
@@ -339,6 +340,11 @@ function restoreHeapValue(id, state) {
339
340
  if (serialized === undefined) {
340
341
  throw new Error(`Snapshot references unknown heap value ${id}.`);
341
342
  }
343
+ if (serialized.kind === "date") {
344
+ const value = restoreDateTime(serialized.time);
345
+ state.heapValueById.set(id, value);
346
+ return value;
347
+ }
342
348
  if (serialized.kind === "float32array") {
343
349
  const value = decodeFloat32Storage(serialized, (reference) => deserializeValue(reference, state));
344
350
  state.heapValueById.set(id, value);
@@ -40,7 +40,10 @@ export type SerializedReferenceValue = {
40
40
  kind: "ref";
41
41
  id: number;
42
42
  };
43
- export type SerializedHeapValue = (Float32Data<SerializedReferenceValue> & {
43
+ export type SerializedHeapValue = {
44
+ kind: "date";
45
+ time: number | null;
46
+ } | (Float32Data<SerializedReferenceValue> & {
44
47
  entries: Record<string, SerializedSnapshotValue>;
45
48
  }) | SerializedArguments<SerializedSnapshotValue> | SerializedArray<SerializedSnapshotValue> | {
46
49
  kind: "object";
@@ -72,7 +75,7 @@ export type RuntimePromiseValue = {
72
75
  id: SnapshotId;
73
76
  promise?: Promise<unknown>;
74
77
  };
75
- export type RuntimeSnapshotValue = Float32Array | boolean | null | number | string | undefined | RuntimeClosureValue | SandboxGenerator | RuntimePromiseValue | SandboxMap | SandboxRegex | SandboxSet | RuntimeSnapshotValue[] | {
78
+ export type RuntimeSnapshotValue = Date | Float32Array | boolean | null | number | string | undefined | RuntimeClosureValue | SandboxGenerator | RuntimePromiseValue | SandboxMap | SandboxRegex | SandboxSet | RuntimeSnapshotValue[] | {
76
79
  [key: string]: RuntimeSnapshotValue;
77
80
  };
78
81
  export type RuntimeScopeFrame = {
@@ -6,6 +6,7 @@ import { serializeArguments } from "./arguments.js";
6
6
  import { requiresArrayEntries, serializeArray } from "./arrays.js";
7
7
  import { float32DataProperties, isFloat32Array } from "../interp/float32.js";
8
8
  import { encodeFloat32Storage } from "./float32array.js";
9
+ import { isSandboxDate, serializedDateTime } from "../interp/date.js";
9
10
  import { isSandboxArguments, isSandboxGenerator, isSandboxMap, isSandboxRegex, isSandboxSet } from "../interp/values.js";
10
11
  export class UnsnapshotableValueError extends Error {
11
12
  path;
@@ -161,7 +162,7 @@ function serializeValue(value, path, state) {
161
162
  if (isSandboxRegex(value)) {
162
163
  return { kind: "regex", source: value.source, flags: value.flags, lastIndex: value.lastIndex };
163
164
  }
164
- if (isSandboxMap(value) || isSandboxSet(value) || isFloat32Array(value)) {
165
+ if (isSandboxDate(value) || isSandboxMap(value) || isSandboxSet(value) || isFloat32Array(value)) {
165
166
  const reference = serializeHeapReference(value, path, state);
166
167
  if (reference === undefined) {
167
168
  throw new TypeError(`Cannot serialize collection without a heap reference at ${path}.`);
@@ -190,7 +191,10 @@ function serializeHeapReference(value, path, state) {
190
191
  }
191
192
  if (!state.serializedHeapIds.has(id)) {
192
193
  state.serializedHeapIds.add(id);
193
- if (isFloat32Array(value)) {
194
+ if (isSandboxDate(value)) {
195
+ state.heap[String(id)] = { kind: "date", time: serializedDateTime(value) };
196
+ }
197
+ else if (isFloat32Array(value)) {
194
198
  const storage = encodeFloat32Storage(value, id, state.float32Buffers, (id) => ({
195
199
  kind: "ref",
196
200
  id
@@ -297,6 +301,7 @@ function indexHeapContainers(input) {
297
301
  for (const [value, stat] of stats.entries()) {
298
302
  if (stat.count > 1 ||
299
303
  stat.cyclic ||
304
+ isSandboxDate(value) ||
300
305
  isFloat32Array(value) ||
301
306
  (Array.isArray(value) && requiresArrayEntries(value)) ||
302
307
  sandboxErrorTypes.has(value) ||
@@ -319,6 +324,7 @@ function collectContainerStats(value, stats, ancestors) {
319
324
  }
320
325
  if (!Array.isArray(value) &&
321
326
  !isPlainObject(value) &&
327
+ !isSandboxDate(value) &&
322
328
  !isFloat32Array(value) &&
323
329
  !isSandboxMap(value) &&
324
330
  !isSandboxSet(value)) {
@@ -3,6 +3,7 @@ import { types } from "node:util";
3
3
  import { DUMP_FORMAT_VERSION } from "./dump-format.js";
4
4
  import { MAX_DATA_DEPTH } from "../graph-depth.js";
5
5
  import { validateFloat32Storage } from "./float32array.js";
6
+ import { restoreDateTime } from "../interp/date.js";
6
7
  import { hasGuestObjectState } from "../interp/object-model.js";
7
8
  const DEFAULT_MAX_DEPTH = MAX_DATA_DEPTH;
8
9
  const DEFAULT_MAX_ENTRIES = 100_000;
@@ -80,6 +81,10 @@ function validateDumpHeap(root, state) {
80
81
  addUnique(heapIds, id, path);
81
82
  const entry = requireRecord(value, path);
82
83
  validateErrorType(entry, path);
84
+ if (entry.kind === "date") {
85
+ validateDateRecord(entry, path);
86
+ continue;
87
+ }
83
88
  if (entry.kind === "float32array") {
84
89
  validateFloat32Storage(entry);
85
90
  requireRecord(entry.entries, `${path}.entries`);
@@ -402,7 +407,7 @@ function validateGeneratorShape(record, path, state) {
402
407
  function validateHeapValue(value, path, state) {
403
408
  const record = requireRecord(value, path);
404
409
  validateErrorType(record, path);
405
- if (!["arguments", "array", "object", "map", "set", "float32array"].includes(String(record.kind)))
410
+ if (!["arguments", "array", "object", "map", "set", "float32array", "date"].includes(String(record.kind)))
406
411
  fail("unknownTag", `${path}.kind`, "unknown heap tag");
407
412
  validateValue(record, path, 1, state);
408
413
  if (record.kind === "arguments")
@@ -411,6 +416,8 @@ function validateHeapValue(value, path, state) {
411
416
  validateArrayHeap(record, path, state);
412
417
  if (record.kind === "object")
413
418
  requireRecord(record.entries, `${path}.entries`);
419
+ if (record.kind === "date")
420
+ validateDateRecord(record, path);
414
421
  if (record.kind === "float32array") {
415
422
  validateFloat32Storage(record);
416
423
  requireRecord(record.entries, `${path}.entries`);
@@ -425,6 +432,16 @@ function validateHeapValue(value, path, state) {
425
432
  if (record.kind === "set")
426
433
  requireArray(record.values, `${path}.values`, state);
427
434
  }
435
+ function validateDateRecord(record, path) {
436
+ if (Object.keys(record).length !== 2)
437
+ fail("invalidValue", path, "invalid Date fields");
438
+ try {
439
+ restoreDateTime(record.time);
440
+ }
441
+ catch {
442
+ fail("invalidValue", `${path}.time`, "invalid Date epoch");
443
+ }
444
+ }
428
445
  function validateArrayHeap(record, path, state) {
429
446
  if (Object.hasOwn(record, "items")) {
430
447
  requireArray(record.items, `${path}.items`, state);