poe-code 14.0.6 → 14.0.7

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 (48) hide show
  1. package/dist/index.js +454 -39
  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-BWVASPJX.js} +420 -33
  6. package/packages/safe-js/dist/chunks/chunk-BWVASPJX.js.map +7 -0
  7. package/packages/safe-js/dist/chunks/{chunk-QSSQJNKE.js → chunk-ETKM3KPA.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/date.d.ts +19 -0
  16. package/packages/safe-js/dist/interp/date.js +129 -0
  17. package/packages/safe-js/dist/interp/globals/console-json.js +5 -1
  18. package/packages/safe-js/dist/interp/globals/date.d.ts +10 -0
  19. package/packages/safe-js/dist/interp/globals/date.js +95 -0
  20. package/packages/safe-js/dist/interp/globals/object-array.d.ts +2 -1
  21. package/packages/safe-js/dist/interp/globals/object-array.js +10 -1
  22. package/packages/safe-js/dist/interp/globals.d.ts +3 -0
  23. package/packages/safe-js/dist/interp/globals.js +2 -0
  24. package/packages/safe-js/dist/interp/host-bridge.d.ts +4 -0
  25. package/packages/safe-js/dist/interp/host-bridge.js +21 -2
  26. package/packages/safe-js/dist/interp/host-call.js +4 -0
  27. package/packages/safe-js/dist/interp/host-capabilities.d.ts +7 -0
  28. package/packages/safe-js/dist/interp/host-capabilities.js +29 -1
  29. package/packages/safe-js/dist/interp/interpreter.js +19 -2
  30. package/packages/safe-js/dist/interp/string-coercion.js +3 -0
  31. package/packages/safe-js/dist/interp/values.d.ts +1 -1
  32. package/packages/safe-js/dist/interp/values.js +21 -0
  33. package/packages/safe-js/dist/lint/rules/known-globals.d.ts +1 -1
  34. package/packages/safe-js/dist/lint/rules/known-globals.js +1 -0
  35. package/packages/safe-js/dist/parse/parser.js +4 -2
  36. package/packages/safe-js/dist/realm.d.ts +4 -1
  37. package/packages/safe-js/dist/realm.js +74 -8
  38. package/packages/safe-js/dist/run.d.ts +2 -0
  39. package/packages/safe-js/dist/run.js +1 -1
  40. package/packages/safe-js/dist/snapshot/dump-format.js +8 -3
  41. package/packages/safe-js/dist/snapshot/replay-data.d.ts +4 -1
  42. package/packages/safe-js/dist/snapshot/replay-data.js +11 -0
  43. package/packages/safe-js/dist/snapshot/restore.js +6 -0
  44. package/packages/safe-js/dist/snapshot/serialize.d.ts +5 -2
  45. package/packages/safe-js/dist/snapshot/serialize.js +8 -2
  46. package/packages/safe-js/dist/snapshot/validation.js +18 -1
  47. package/packages/safe-js/dist/chunks/chunk-2M6MNQBY.js.map +0 -7
  48. /package/packages/safe-js/dist/chunks/{chunk-QSSQJNKE.js.map → chunk-ETKM3KPA.js.map} +0 -0
@@ -1,4 +1,5 @@
1
1
  import { normalizeClosureResult } from "./async.js";
2
+ import { copyNativeDate } from "./date.js";
2
3
  import { exportHostCapability, importHostCapability, isLiveCapability } from "./host-capabilities.js";
3
4
  import { attachErrorSpan, replaceErrorStack } from "../error/shape.js";
4
5
  import { SandboxError } from "./budget.js";
@@ -77,11 +78,13 @@ function wrapCallerInjectedFunction(name, value, options, state) {
77
78
  seen: new WeakMap(),
78
79
  restored: []
79
80
  };
80
- const hostArgs = deepCopyFromSandbox([...args], {
81
+ const copyArguments = (values) => deepCopyFromSandbox([...values], {
81
82
  compilation,
82
83
  unwrapHostObject: options.realm === undefined ? undefined : object => exportHostCapability(object, options.realm.owner),
83
84
  wrapClosure: (closure) => options.realm?.wrapCallback(closure) ?? wrapSandboxClosureForHost(closure, stackFrames, options.budget, operationLease.owner, callbacks)
84
85
  });
86
+ const captured = options.realm?.captureArguments(callable, args, copyArguments);
87
+ const hostArgs = captured?.args ?? copyArguments(args);
85
88
  const hostCalls = options.hostCalls;
86
89
  const operation = options.operation ?? bindingName;
87
90
  const moduleId = options.moduleId ?? "<bindings>";
@@ -90,7 +93,14 @@ function wrapCallerInjectedFunction(name, value, options, state) {
90
93
  "re-issue";
91
94
  if (hostCalls === undefined) {
92
95
  if (options.realm !== undefined) {
93
- const result = options.realm.invoke(callable, () => Reflect.apply(callable, undefined, hostArgs));
96
+ let result;
97
+ try {
98
+ result = options.realm.invoke(callable, () => Reflect.apply(callable, undefined, hostArgs));
99
+ }
100
+ catch (error) {
101
+ captured.rollback();
102
+ throw error;
103
+ }
94
104
  if (options.realm.awaitResult(callable)) {
95
105
  return Promise.resolve(result).then(value => copyHostResultToSandbox(value, stackFrames, options));
96
106
  }
@@ -658,6 +668,15 @@ export function copyHostValueToSandbox(value, stackFrames, options, state, path)
658
668
  : path
659
669
  }, state);
660
670
  }
671
+ const date = copyNativeDate(value);
672
+ if (date !== undefined) {
673
+ const existing = state.seen.get(value);
674
+ if (existing !== undefined)
675
+ return existing;
676
+ budget.chargeDataUsage(9);
677
+ state.seen.set(value, date);
678
+ return date;
679
+ }
661
680
  if (isFloat32Array(value)) {
662
681
  const existing = state.seen.get(value);
663
682
  if (existing !== undefined)
@@ -1,5 +1,6 @@
1
1
  import { createHash, randomUUID } from "node:crypto";
2
2
  import { float32DataProperties, float32Storage, isFloat32Array } from "./float32.js";
3
+ import { copyNativeDate, serializedDateTime } from "./date.js";
3
4
  import { cloneSandboxValue, createSandboxClosure, defineOwnDataProperty, isArrayIndexKey, measureSandboxData } from "./values.js";
4
5
  import { CompileScope } from "./regex/compile-guard.js";
5
6
  import { decodeReplayData, encodeReplayData } from "../snapshot/replay-data.js";
@@ -581,6 +582,9 @@ function normalize(value, seen) {
581
582
  throw new TypeError("Host call arguments cannot contain cycles.");
582
583
  seen.add(value);
583
584
  try {
585
+ const date = copyNativeDate(value);
586
+ if (date !== undefined)
587
+ return Object.assign(Object.create(null), { $type: "date", time: serializedDateTime(date) });
584
588
  if (isFloat32Array(value)) {
585
589
  const storage = float32Storage(value);
586
590
  const properties = Object.create(null);
@@ -1,6 +1,7 @@
1
1
  import { type HostOperation } from "../extensions.js";
2
2
  import type { SandboxClosure, SandboxObject, SandboxValue } from "./values.js";
3
3
  declare const hostObjectBrand: unique symbol;
4
+ declare const guestReferenceBrand: unique symbol;
4
5
  declare const sandboxHostObjectBrand: unique symbol;
5
6
  type SandboxHostObject = SandboxObject & {
6
7
  readonly [sandboxHostObjectBrand]: true;
@@ -8,6 +9,9 @@ type SandboxHostObject = SandboxObject & {
8
9
  export type HostObject = Readonly<Record<string, never>> & {
9
10
  readonly [hostObjectBrand]: true;
10
11
  };
12
+ export type GuestReference = Readonly<Record<string, never>> & {
13
+ readonly [guestReferenceBrand]: true;
14
+ };
11
15
  export type HostObjectDefinition = {
12
16
  properties?: Record<string, {
13
17
  get?: () => unknown;
@@ -28,6 +32,9 @@ type GuestCallbackState = {
28
32
  closure?: SandboxClosure;
29
33
  assertActive(): void;
30
34
  };
35
+ export declare function createGuestReference(root: [SandboxValue], owner: object, assertActive: () => void): GuestReference;
36
+ export declare function readGuestReference(reference: unknown, owner: object): SandboxValue;
37
+ export declare function revokeGuestReference(reference: GuestReference, owner: object): void;
31
38
  export declare function createLiveHostObject(definition: HostObjectDefinition, controller: HostObjectController): HostObject;
32
39
  export declare function isGuestHostObject(value: unknown): value is SandboxHostObject;
33
40
  export declare function isLiveCapability(value: unknown): boolean;
@@ -2,6 +2,29 @@ import { readDataRecord } from "../extensions.js";
2
2
  const hostObjects = new WeakMap();
3
3
  const guestObjects = new WeakMap();
4
4
  const guestCallbacks = new WeakMap();
5
+ const guestReferences = new WeakMap();
6
+ export function createGuestReference(root, owner, assertActive) {
7
+ const reference = Object.freeze(Object.create(null));
8
+ guestReferences.set(reference, { root, owner, assertActive });
9
+ return reference;
10
+ }
11
+ export function readGuestReference(reference, owner) {
12
+ const state = typeof reference === "object" && reference !== null
13
+ ? guestReferences.get(reference)
14
+ : undefined;
15
+ if (state === undefined || state.owner !== owner)
16
+ throw new TypeError("Foreign or invalid guest reference.");
17
+ state.assertActive();
18
+ if (state.root === undefined)
19
+ throw new TypeError("Guest reference is revoked.");
20
+ return state.root[0];
21
+ }
22
+ export function revokeGuestReference(reference, owner) {
23
+ const state = guestReferences.get(reference);
24
+ if (state === undefined || state.owner !== owner)
25
+ throw new TypeError("Foreign guest reference.");
26
+ state.root = undefined;
27
+ }
5
28
  export function createLiveHostObject(definition, controller) {
6
29
  const input = readDataRecord(definition, "Host object definition");
7
30
  if (Object.keys(input).some((key) => key !== "properties" && key !== "methods"))
@@ -45,9 +68,14 @@ export function isGuestHostObject(value) {
45
68
  }
46
69
  export function isLiveCapability(value) {
47
70
  return (((typeof value === "object" && value !== null) || typeof value === "function") &&
48
- (hostObjects.has(value) || guestObjects.has(value) || guestCallbacks.has(value)));
71
+ (hostObjects.has(value) ||
72
+ guestObjects.has(value) ||
73
+ guestCallbacks.has(value) ||
74
+ guestReferences.has(value)));
49
75
  }
50
76
  export function importHostCapability(value, owner) {
77
+ if (guestReferences.has(value))
78
+ return readGuestReference(value, owner);
51
79
  const object = hostObjects.get(value);
52
80
  if (object !== undefined) {
53
81
  if (object.controller.owner !== owner)
@@ -27,6 +27,8 @@ import { isSandboxErrorConstructorInstance } from "./globals/error.js";
27
27
  import { isSandboxMapConstructor, isSandboxSetConstructor } from "./globals/collections.js";
28
28
  import { getFloat32Member, isFloat32ArrayConstructor, setFloat32Member } from "./globals/float32array.js";
29
29
  import { isFloat32Array } from "./float32.js";
30
+ import { dateString, dateTime, isSandboxDate } from "./date.js";
31
+ import { getDateMember, getDatePrototype, isDateConstructor } from "./globals/date.js";
30
32
  import { createSandboxRegex, allocateProducedSandboxValue, isSandboxClosure, isSandboxGenerator, isSandboxMap, isSandboxPromise, isSandboxRegex, isSandboxSet, reconcileCompiledValues } from "./values.js";
31
33
  import { Scope } from "./scope.js";
32
34
  import { hoistVarDeclarations } from "./var-hoist.js";
@@ -1648,6 +1650,8 @@ function getPropertyValue(target, property, context) {
1648
1650
  return undefined;
1649
1651
  if (isFloat32Array(target))
1650
1652
  return getFloat32Member(target, property, context.budget);
1653
+ if (isSandboxDate(target))
1654
+ return getDateMember(property, context.budget, context.compilation?.owner);
1651
1655
  if (isSandboxMap(target))
1652
1656
  return getMapMember(target, property, createMapMethodOptions(context));
1653
1657
  if (isSandboxSet(target))
@@ -1864,6 +1868,9 @@ async function evaluateMemberCallExpression(node, context) {
1864
1868
  if (isSandboxMap(member.object)) {
1865
1869
  return evaluatePrimitiveMemberCall(node, "Map", member.property, getMapMember(member.object, member.property, createMapMethodOptions(context)), context);
1866
1870
  }
1871
+ if (isSandboxDate(member.object)) {
1872
+ return evaluateResolvedCallExpression(node, getDateMember(member.property, context.budget, context.compilation?.owner), context, member.object);
1873
+ }
1867
1874
  if (isFloat32Array(member.object)) {
1868
1875
  return evaluateResolvedCallExpression(node, getFloat32Member(member.object, member.property, context.budget), context, member.object);
1869
1876
  }
@@ -2076,6 +2083,8 @@ function applyBinaryOperator(node, left, right, context) {
2076
2083
  }
2077
2084
  if (isFloat32ArrayConstructor(right))
2078
2085
  return isFloat32Array(left);
2086
+ if (isDateConstructor(right))
2087
+ return isSandboxDate(left) && getDatePrototype(left, context.budget, context.compilation?.owner) !== null;
2079
2088
  if (isSandboxSetConstructor(right) && isSandboxSet(left)) {
2080
2089
  return true;
2081
2090
  }
@@ -2141,8 +2150,8 @@ function applyAdditionOperator(left, right, context) {
2141
2150
  return toNumber(leftPrimitive) + toNumber(rightPrimitive);
2142
2151
  }
2143
2152
  function compareRelational(left, right, operator) {
2144
- const leftPrimitive = toPrimitive(left);
2145
- const rightPrimitive = toPrimitive(right);
2153
+ const leftPrimitive = isSandboxDate(left) ? dateTime(left) : toPrimitive(left);
2154
+ const rightPrimitive = isSandboxDate(right) ? dateTime(right) : toPrimitive(right);
2146
2155
  if (typeof leftPrimitive === "string" && typeof rightPrimitive === "string") {
2147
2156
  switch (operator) {
2148
2157
  case "<":
@@ -2225,6 +2234,8 @@ function toPrimitive(value) {
2225
2234
  return toString(value);
2226
2235
  }
2227
2236
  async function toNumericPrimitive(value, context) {
2237
+ if (isSandboxDate(value))
2238
+ return dateTime(value);
2228
2239
  if (isPrimitiveCoercionType(getCoercionType(value))) {
2229
2240
  return value;
2230
2241
  }
@@ -2247,6 +2258,8 @@ async function toNumericPrimitive(value, context) {
2247
2258
  return toString(value);
2248
2259
  }
2249
2260
  function toNumber(value) {
2261
+ if (isSandboxDate(value))
2262
+ return dateTime(value);
2250
2263
  if (typeof value === "number") {
2251
2264
  return value;
2252
2265
  }
@@ -2265,6 +2278,8 @@ function toNumber(value) {
2265
2278
  return toNumber(toPrimitive(value));
2266
2279
  }
2267
2280
  function toString(value) {
2281
+ if (isSandboxDate(value))
2282
+ return dateString(value);
2268
2283
  if (Array.isArray(value)) {
2269
2284
  return value
2270
2285
  .map((entry) => (entry === null || entry === undefined ? "" : toString(entry)))
@@ -2323,6 +2338,8 @@ function getArrayMemberValue(target, property, context) {
2323
2338
  return getArrayMember(target, property, createArrayMethodOptions(context));
2324
2339
  }
2325
2340
  export function setSandboxProperty(target, property, value, budget) {
2341
+ if (isSandboxDate(target))
2342
+ throw new TypeError("Date own properties are not supported.");
2326
2343
  if (isGuestHostObject(target)) {
2327
2344
  setHostObjectMember(target, String(property), value);
2328
2345
  return;
@@ -1,4 +1,5 @@
1
1
  import { sandboxErrorTypes } from "../error/shape.js";
2
+ import { dateString, isSandboxDate } from "./date.js";
2
3
  import { float32Storage, isFloat32Array } from "./float32.js";
3
4
  import { isSandboxClosure, isSandboxPromise } from "./values.js";
4
5
  export function sandboxString(value, budget, context, joining = new Set()) {
@@ -41,6 +42,8 @@ async function stringifyObject(value, budget, context, joining) {
41
42
  }
42
43
  }
43
44
  async function defaultToString(value, budget, context, joining) {
45
+ if (isSandboxDate(value))
46
+ return budget.allocateString(dateString(value));
44
47
  if (Array.isArray(value) || isFloat32Array(value)) {
45
48
  if (Object.hasOwn(value, "join")) {
46
49
  const join = ownDataValue(value, "join");
@@ -12,7 +12,7 @@ declare const sandboxRegexPattern: unique symbol;
12
12
  declare const sandboxSetBrand: unique symbol;
13
13
  declare const sandboxRetainedValues: unique symbol;
14
14
  export type SandboxPrimitive = string | number | boolean | null | undefined;
15
- export type SandboxValue = SandboxPrimitive | Float32Array | SandboxObject | SandboxArray | SandboxClosure | SandboxGenerator | SandboxMap | SandboxSet | SandboxPromise | SandboxRegex;
15
+ export type SandboxValue = SandboxPrimitive | Date | Float32Array | SandboxObject | SandboxArray | SandboxClosure | SandboxGenerator | SandboxMap | SandboxSet | SandboxPromise | SandboxRegex;
16
16
  export type SandboxObject = {
17
17
  [key: string]: SandboxValue;
18
18
  };
@@ -1,4 +1,5 @@
1
1
  import { bindOtelSpan, getBoundOtelSpan } from "../observability/otel.js";
2
+ import { copyNativeDate, exportDate, isSandboxDate } from "./date.js";
2
3
  import { getHostObjectKeys, getHostObjectMember, isGuestHostObject, isLiveCapability } from "./host-capabilities.js";
3
4
  import { types as nodeTypes } from "node:util";
4
5
  import { CompileScope, RegexCompileGuard, regexCompiledData } from "./regex/compile-guard.js";
@@ -225,6 +226,10 @@ export function measureSandboxData(values, options = {}) {
225
226
  assertSandboxDataDepth(depth);
226
227
  seen.add(value);
227
228
  usage += 1;
229
+ if (isSandboxDate(value)) {
230
+ usage += 8;
231
+ return;
232
+ }
228
233
  if (isGuestHostObject(value)) {
229
234
  for (const key of getHostObjectKeys(value))
230
235
  usage += key.length + 1;
@@ -435,6 +440,14 @@ function copyToSandbox(value, state, path = "<root>", cloneSandboxCollections =
435
440
  }
436
441
  return sandboxPromise;
437
442
  }
443
+ if (nodeTypes.isDate(value)) {
444
+ const existing = state.seen.get(value);
445
+ if (existing !== undefined)
446
+ return existing;
447
+ const copy = copyNativeDate(value);
448
+ state.seen.set(value, copy);
449
+ return copy;
450
+ }
438
451
  if (isFloat32Array(value)) {
439
452
  const existing = state.seen.get(value);
440
453
  if (existing !== undefined)
@@ -567,6 +580,14 @@ function copyFromSandbox(value, state, path = "<root>", options, depth = 0) {
567
580
  Object.preventExtensions(copy);
568
581
  return copy;
569
582
  }
583
+ if (isSandboxDate(value)) {
584
+ const existing = state.seen.get(value);
585
+ if (existing !== undefined)
586
+ return existing;
587
+ const copy = exportDate(value);
588
+ state.seen.set(value, copy);
589
+ return copy;
590
+ }
570
591
  if (isSandboxClosure(value)) {
571
592
  if (options.wrapClosure === undefined) {
572
593
  throw new TypeError("Sandbox closures cannot cross into host values without an explicit wrapper.");
@@ -1 +1 @@
1
- export declare const KNOWN_RUNTIME_GLOBALS: readonly ["AggregateError", "Array", "Boolean", "Error", "Infinity", "isFinite", "isNaN", "JSON", "Map", "Float32Array", "Math", "NaN", "Number", "Object", "parseFloat", "parseInt", "Promise", "RangeError", "ReferenceError", "RegExp", "Set", "String", "structuredClone", "SyntaxError", "TypeError", "console"];
1
+ export declare const KNOWN_RUNTIME_GLOBALS: readonly ["AggregateError", "Array", "Boolean", "Date", "Error", "Infinity", "isFinite", "isNaN", "JSON", "Map", "Float32Array", "Math", "NaN", "Number", "Object", "parseFloat", "parseInt", "Promise", "RangeError", "ReferenceError", "RegExp", "Set", "String", "structuredClone", "SyntaxError", "TypeError", "console"];
@@ -2,6 +2,7 @@ export const KNOWN_RUNTIME_GLOBALS = [
2
2
  "AggregateError",
3
3
  "Array",
4
4
  "Boolean",
5
+ "Date",
5
6
  "Error",
6
7
  "Infinity",
7
8
  "isFinite",
@@ -1832,8 +1832,10 @@ class Parser {
1832
1832
  }
1833
1833
  break;
1834
1834
  }
1835
- this.expectPunctuator("(");
1836
- const args = this.parseArguments();
1835
+ const optional = this.consumePunctuator("?.");
1836
+ if (optional !== undefined)
1837
+ throw new DisallowedSyntaxError("new optional chain", optional.start);
1838
+ const args = this.consumePunctuator("(") === undefined ? [] : this.parseArguments();
1837
1839
  const end = this.previousToken();
1838
1840
  return {
1839
1841
  node: {
@@ -4,15 +4,17 @@ import { type CallerInjectedBinding } from "./interp/host-bridge.js";
4
4
  import { type CallbackOptions, type SafeJSExtension } from "./extensions.js";
5
5
  import { type ModuleRegistry } from "./modules/registry.js";
6
6
  import type { ConsoleSink } from "./interp/globals/console-json.js";
7
- import type { RunOptions, RunResult } from "./run.js";
7
+ import type { RunClock, RunOptions, RunResult } from "./run.js";
8
8
  export type RealmLimits = {
9
9
  extensions?: number;
10
10
  hostObjects?: number;
11
11
  callbacks?: number;
12
+ guestReferences?: number;
12
13
  cleanups?: number;
13
14
  nestedEvaluations?: number;
14
15
  };
15
16
  export type RealmOptions = {
17
+ clock?: RunClock;
16
18
  bindings?: Record<string, CallerInjectedBinding>;
17
19
  modules?: ModuleRegistry;
18
20
  extensions?: readonly SafeJSExtension[];
@@ -37,6 +39,7 @@ export type SafeJSRealm = {
37
39
  }): Promise<RealmResult>;
38
40
  invokeCallback(callback: unknown, options?: CallbackOptions): Promise<unknown>;
39
41
  releaseCallback(callback: unknown): void;
42
+ releaseGuestReference(reference: unknown): void;
40
43
  close(): Promise<void>;
41
44
  };
42
45
  export declare function createRealm(options?: RealmOptions): SafeJSRealm;
@@ -10,7 +10,7 @@ import { enterRunningState } from "./interp/running-state.js";
10
10
  import { runResources } from "./interp/resources.js";
11
11
  import { createSandboxPromiseRejectionTracker, withSandboxPromiseRejectionTracker } from "./interp/promise-tracker.js";
12
12
  import { copyHostValueToSandbox, wrapCallerInjectedBindings } from "./interp/host-bridge.js";
13
- import { createLiveHostObject, exportHostCapability, readGuestCallback, registerGuestCallback, revokeGuestCallback, revokeHostObject } from "./interp/host-capabilities.js";
13
+ import { createLiveHostObject, createGuestReference, exportHostCapability, readGuestReference, readGuestCallback, registerGuestCallback, revokeGuestCallback, revokeHostObject, revokeGuestReference } from "./interp/host-capabilities.js";
14
14
  import { deepCopyFromSandbox, isSandboxClosure, measureSandboxData, reconcileCompiledValues } from "./interp/values.js";
15
15
  import { getExtensionSetup, readDataRecord, readStringList } from "./extensions.js";
16
16
  import { resolveModuleImports } from "./modules/registry.js";
@@ -36,6 +36,8 @@ class RealmState {
36
36
  pendingCallbacks = new Set();
37
37
  callbackCache = new WeakMap();
38
38
  hostObjects = new Set();
39
+ guestReferences = new Map();
40
+ retainedOperations = new WeakMap();
39
41
  nestedOperations = new WeakMap();
40
42
  convertedModules = new Map();
41
43
  nativeConversions = { seen: new WeakMap() };
@@ -56,6 +58,7 @@ class RealmState {
56
58
  extensions: 32,
57
59
  hostObjects: 1024,
58
60
  callbacks: 1024,
61
+ guestReferences: 1024,
59
62
  cleanups: 1024,
60
63
  nestedEvaluations: 16
61
64
  };
@@ -94,6 +97,7 @@ class RealmState {
94
97
  owner: this,
95
98
  assertActive: this.assertOpen,
96
99
  wrapCallback: this.wrapCallback,
100
+ captureArguments: this.captureArguments,
97
101
  invoke: this.invokeHost,
98
102
  awaitResult: (operation) => this.nestedOperations.has(operation)
99
103
  };
@@ -102,6 +106,7 @@ class RealmState {
102
106
  budget: this.budget,
103
107
  compileOwner: this.lease.owner,
104
108
  sink: options.sink,
109
+ clock: options.clock,
105
110
  random: createReplayableRandom({ seed: options.randomSeed }).next
106
111
  });
107
112
  const names = new Set();
@@ -134,7 +139,7 @@ class RealmState {
134
139
  options.signal?.addEventListener("abort", this.abort, { once: true });
135
140
  if (options.signal?.aborted)
136
141
  this.abort();
137
- this.budget.setRetainedValues(this, this.retainedCallbacks);
142
+ this.budget.setRetainedValues(this, this.retainedRoots);
138
143
  this.tracker.onFatalRejection((error) => this.poison(error));
139
144
  }
140
145
  catch (error) {
@@ -149,10 +154,48 @@ class RealmState {
149
154
  signal: this.controller.signal,
150
155
  realm: this.bridge
151
156
  });
152
- retainedCallbacks = () => [
157
+ retainedRoots = () => [
153
158
  ...this.callbacks.values(),
154
- ...Array.from(this.pendingCallbacks, (pending) => pending.closure)
159
+ ...Array.from(this.pendingCallbacks, (pending) => pending.closure),
160
+ ...this.guestReferences.values()
155
161
  ];
162
+ captureArguments = (operation, args, copy) => {
163
+ const from = this.retainedOperations.get(operation)?.from ?? args.length;
164
+ const values = copy(args.slice(0, from));
165
+ const captured = [];
166
+ const rollback = () => {
167
+ for (const reference of captured) {
168
+ revokeGuestReference(reference, this);
169
+ this.guestReferences.delete(reference);
170
+ }
171
+ };
172
+ try {
173
+ for (const value of args.slice(from)) {
174
+ this.checkCollection(this.guestReferences.size + 1, this.limits.guestReferences, "guest reference");
175
+ const root = [value];
176
+ const reference = createGuestReference(root, this, this.assertOpen);
177
+ this.guestReferences.set(reference, root);
178
+ captured.push(reference);
179
+ values.push(reference);
180
+ }
181
+ if (captured.length > 0)
182
+ this.budget.reconcileDataUsage(measureSandboxData([...(this.scope?.retainedValues() ?? []), ...this.retainedRoots()]));
183
+ return { args: values, rollback };
184
+ }
185
+ catch (error) {
186
+ rollback();
187
+ if (error instanceof SandboxError)
188
+ this.poison(error);
189
+ throw error;
190
+ }
191
+ };
192
+ releaseGuestReference = (reference) => {
193
+ readGuestReference(reference, this);
194
+ revokeGuestReference(reference, this);
195
+ this.guestReferences.delete(reference);
196
+ if (this.active === undefined)
197
+ reconcileCompiledValues(this.budget, [...(this.scope?.retainedValues() ?? []), ...this.retainedRoots()], this.compilation);
198
+ };
156
199
  assertOpen = () => {
157
200
  if (this.failure !== undefined)
158
201
  throw this.failure.reason;
@@ -321,7 +364,7 @@ class RealmState {
321
364
  }
322
365
  });
323
366
  try {
324
- this.budget.reconcileDataUsage(measureSandboxData([...(this.scope?.retainedValues() ?? []), ...this.retainedCallbacks()]));
367
+ this.budget.reconcileDataUsage(measureSandboxData([...(this.scope?.retainedValues() ?? []), ...this.retainedRoots()]));
325
368
  }
326
369
  catch (error) {
327
370
  this.poison(error);
@@ -334,7 +377,7 @@ class RealmState {
334
377
  revokeGuestCallback(callback, this);
335
378
  this.callbacks.delete(callback);
336
379
  if (this.active === undefined)
337
- reconcileCompiledValues(this.budget, [...(this.scope?.retainedValues() ?? []), ...this.retainedCallbacks()], this.compilation);
380
+ reconcileCompiledValues(this.budget, [...(this.scope?.retainedValues() ?? []), ...this.retainedRoots()], this.compilation);
338
381
  };
339
382
  invokeCallback = async (callback, options = {}) => {
340
383
  if (this.closed || this.failure !== undefined)
@@ -384,7 +427,7 @@ class RealmState {
384
427
  finally {
385
428
  this.pendingCallbacks.delete(record);
386
429
  if (!this.closed && this.active === undefined)
387
- reconcileCompiledValues(this.budget, [...(this.scope?.retainedValues() ?? []), ...this.retainedCallbacks()], this.compilation);
430
+ reconcileCompiledValues(this.budget, [...(this.scope?.retainedValues() ?? []), ...this.retainedRoots()], this.compilation);
388
431
  }
389
432
  };
390
433
  initialize() {
@@ -400,6 +443,24 @@ class RealmState {
400
443
  createHostObject: this.createHostObject,
401
444
  invokeCallback: this.invokeCallback,
402
445
  releaseCallback: this.releaseCallback,
446
+ releaseGuestReference: this.releaseGuestReference,
447
+ retainGuestArguments: (operation, from) => {
448
+ this.assertOpen();
449
+ if (!extension.manifest.capabilities?.includes("guest:retain"))
450
+ throw new TypeError("Retaining arguments requires the guest:retain grant.");
451
+ if (typeof operation !== "function")
452
+ throw new TypeError("Retained operation must be a function.");
453
+ if (!Number.isSafeInteger(from) || from < 0)
454
+ throw new TypeError("Argument index must be a non-negative safe integer.");
455
+ if (this.scope !== undefined)
456
+ throw new TypeError("Retained operations must be registered during setup.");
457
+ const previous = this.retainedOperations.get(operation);
458
+ if (previous !== undefined &&
459
+ (previous.extension !== extension || previous.from !== from))
460
+ throw new TypeError("Conflicting retained operation declaration.");
461
+ this.retainedOperations.set(operation, { extension, from });
462
+ return operation;
463
+ },
403
464
  nestedOperation: (operation) => {
404
465
  this.assertOpen();
405
466
  if (!extension.manifest.capabilities?.includes("source:nested"))
@@ -552,7 +613,7 @@ class RealmState {
552
613
  if (this.failure !== undefined)
553
614
  throw this.failure.reason;
554
615
  if (!this.closed)
555
- reconcileCompiledValues(this.budget, [...(this.scope?.retainedValues() ?? []), ...this.retainedCallbacks()], this.compilation);
616
+ reconcileCompiledValues(this.budget, [...(this.scope?.retainedValues() ?? []), ...this.retainedRoots()], this.compilation);
556
617
  return result;
557
618
  }
558
619
  catch (error) {
@@ -591,6 +652,9 @@ class RealmState {
591
652
  for (const object of this.hostObjects)
592
653
  revokeHostObject(object, this);
593
654
  this.hostObjects.clear();
655
+ for (const reference of this.guestReferences.keys())
656
+ revokeGuestReference(reference, this);
657
+ this.guestReferences.clear();
594
658
  this.budget.setRetainedValues(this, undefined);
595
659
  this.disposal = (async () => {
596
660
  const errors = [];
@@ -651,6 +715,7 @@ export function createRealm(options = {}) {
651
715
  evaluate: state.evaluate,
652
716
  invokeCallback: state.invokeCallback,
653
717
  releaseCallback: state.releaseCallback,
718
+ releaseGuestReference: state.releaseGuestReference,
654
719
  close: state.close
655
720
  });
656
721
  }
@@ -702,6 +767,7 @@ function readRealmOptions(value, oneShot = false) {
702
767
  "signal",
703
768
  "sink",
704
769
  "randomSeed",
770
+ "clock",
705
771
  "limits"
706
772
  ]);
707
773
  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 = {
@@ -109,7 +109,7 @@ export function run(source, options = {}) {
109
109
  signal: options.signal,
110
110
  lifecycle
111
111
  }));
112
- const builtinBindings = createBuiltinBindings({ compileOwner: operation.owner, budget, hostCalls, sink: options.sink, random: random?.generator.next });
112
+ const builtinBindings = createBuiltinBindings({ compileOwner: operation.owner, budget, hostCalls, sink: options.sink, random: random?.generator.next, clock: options.clock });
113
113
  const importMeta = convertInitialInput(() => deepCopyToSandbox(options.importMeta ?? {}));
114
114
  if (restoredSnapshot?.migration !== undefined) {
115
115
  if (Object.hasOwn(importMeta, "migration"))
@@ -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.");