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
@@ -0,0 +1,153 @@
1
+ import { assertSandboxDataDepth } from "../../graph-depth.js";
2
+ import { isSandboxDate } from "../date.js";
3
+ import { isSandboxErrorConstructorInstance } from "../exceptions.js";
4
+ import { isFloat32Array } from "../float32.js";
5
+ import { hasHostObjectMember, isGuestHostObject } from "../host-capabilities.js";
6
+ import { getSandboxPrototype, installObjectPrototype, isGuestClosure, markDescriptorObject, materializeFunctionProperties } from "../object-model.js";
7
+ import { sandboxString } from "../string-coercion.js";
8
+ import { createSandboxClosure, isSandboxClosure, isSandboxGenerator, isSandboxMap, isSandboxPromise, isSandboxRegex, isSandboxSet } from "../values.js";
9
+ export function createObjectGlobal(methods, budget) {
10
+ const construct = ([value]) => {
11
+ if (value === null || value === undefined) {
12
+ budget.chargeDataUsage(1);
13
+ return Object.create(null);
14
+ }
15
+ if (typeof value !== "object")
16
+ throw new TypeError("Object primitive boxing is not supported.");
17
+ return value;
18
+ };
19
+ const constructor = createSandboxClosure({
20
+ guest: true,
21
+ sandbox: true,
22
+ name: "Object",
23
+ length: 1,
24
+ call: construct,
25
+ construct
26
+ });
27
+ const properties = materializeFunctionProperties(constructor);
28
+ const prototype = properties.prototype;
29
+ Object.defineProperty(properties, "prototype", { writable: false });
30
+ for (const [name, method] of Object.entries(methods)) {
31
+ Object.defineProperty(properties, name, { value: method, writable: true, configurable: true });
32
+ }
33
+ const prototypeMethods = {
34
+ toString: createSandboxClosure({
35
+ sandbox: true,
36
+ name: "toString",
37
+ length: 0,
38
+ call: (_args, context) => budget.allocateString(`[object ${typeTag(context?.thisValue)}]`)
39
+ }),
40
+ valueOf: createSandboxClosure({
41
+ sandbox: true,
42
+ name: "valueOf",
43
+ length: 0,
44
+ call: (_args, context) => {
45
+ const value = requireReceiver(context?.thisValue);
46
+ if (typeof value !== "object")
47
+ throw new TypeError("Object primitive boxing is not supported.");
48
+ return value;
49
+ }
50
+ }),
51
+ hasOwnProperty: createSandboxClosure({
52
+ sandbox: true,
53
+ name: "hasOwnProperty",
54
+ length: 1,
55
+ call: async ([key], context) => hasOwnSandboxProperty(requireReceiver(context?.thisValue), await sandboxString(key, budget, context), false)
56
+ }),
57
+ propertyIsEnumerable: createSandboxClosure({
58
+ sandbox: true,
59
+ name: "propertyIsEnumerable",
60
+ length: 1,
61
+ call: async ([key], context) => hasOwnSandboxProperty(requireReceiver(context?.thisValue), await sandboxString(key, budget, context), true)
62
+ }),
63
+ isPrototypeOf: createSandboxClosure({
64
+ sandbox: true,
65
+ name: "isPrototypeOf",
66
+ length: 1,
67
+ call: ([value], context) => {
68
+ if (typeof value !== "object" || value === null)
69
+ return false;
70
+ const receiver = requireReceiver(context?.thisValue);
71
+ let depth = 0;
72
+ for (let current = getSandboxPrototype(value, budget); current !== null; current = getSandboxPrototype(current, budget)) {
73
+ budget.visitNode();
74
+ assertSandboxDataDepth(depth++);
75
+ if (current === receiver)
76
+ return true;
77
+ }
78
+ return false;
79
+ }
80
+ })
81
+ };
82
+ for (const [name, method] of Object.entries(prototypeMethods)) {
83
+ Object.defineProperty(prototype, name, { value: method, writable: true, configurable: true });
84
+ }
85
+ markDescriptorObject(prototype);
86
+ installObjectPrototype(budget, prototype, constructor);
87
+ return constructor;
88
+ }
89
+ function requireReceiver(value) {
90
+ if (value === null || value === undefined)
91
+ throw new TypeError("Object method requires a non-null receiver.");
92
+ return value;
93
+ }
94
+ export function hasOwnSandboxProperty(value, key, enumerable) {
95
+ requireReceiver(value);
96
+ if (isGuestHostObject(value))
97
+ return hasHostObjectMember(value, key, enumerable);
98
+ let properties;
99
+ if (isGuestClosure(value))
100
+ properties = materializeFunctionProperties(value);
101
+ else if (isSandboxClosure(value)) {
102
+ if (key === "length" || key === "name")
103
+ return !enumerable;
104
+ properties = value.properties ?? Object.create(null);
105
+ }
106
+ else if (isSandboxMap(value) ||
107
+ isSandboxSet(value) ||
108
+ isSandboxPromise(value) ||
109
+ isSandboxGenerator(value))
110
+ return false;
111
+ else if (isSandboxRegex(value))
112
+ return key === "lastIndex" && !enumerable;
113
+ else
114
+ properties = Object(value);
115
+ const descriptor = Object.getOwnPropertyDescriptor(properties, key);
116
+ return descriptor !== undefined && (!enumerable || descriptor.enumerable === true);
117
+ }
118
+ function typeTag(value) {
119
+ if (value === undefined)
120
+ return "Undefined";
121
+ if (value === null)
122
+ return "Null";
123
+ if (typeof value === "string")
124
+ return "String";
125
+ if (typeof value === "number")
126
+ return "Number";
127
+ if (typeof value === "boolean")
128
+ return "Boolean";
129
+ if (isSandboxClosure(value)) {
130
+ while (value.boundTarget !== undefined)
131
+ value = value.boundTarget;
132
+ return value.generator ? "GeneratorFunction" : value.async ? "AsyncFunction" : "Function";
133
+ }
134
+ if (Array.isArray(value))
135
+ return "Array";
136
+ if (isSandboxDate(value))
137
+ return "Date";
138
+ if (isSandboxErrorConstructorInstance(value, "Error"))
139
+ return "Error";
140
+ if (isSandboxRegex(value))
141
+ return "RegExp";
142
+ if (isSandboxMap(value))
143
+ return "Map";
144
+ if (isSandboxSet(value))
145
+ return "Set";
146
+ if (isSandboxPromise(value))
147
+ return "Promise";
148
+ if (isSandboxGenerator(value))
149
+ return "Generator";
150
+ if (isFloat32Array(value))
151
+ return "Float32Array";
152
+ return "Object";
153
+ }
@@ -1,6 +1,8 @@
1
1
  import { createConsoleJsonGlobals } from "./globals/console-json.js";
2
+ import type { RunClock } from "../run.js";
2
3
  export declare function createBuiltinBindings(options: Parameters<typeof createConsoleJsonGlobals>[0] & {
3
4
  random?: () => number;
5
+ clock?: RunClock;
4
6
  }): {
5
7
  RegExp: import("./values.js").SandboxClosure;
6
8
  Promise: import("./values.js").SandboxClosure;
@@ -9,7 +11,7 @@ export declare function createBuiltinBindings(options: Parameters<typeof createC
9
11
  parseFloat: import("./values.js").SandboxClosure;
10
12
  isNaN: import("./values.js").SandboxClosure;
11
13
  isFinite: import("./values.js").SandboxClosure;
12
- Object: import("./values.js").SandboxObject;
14
+ Object: import("./values.js").SandboxClosure;
13
15
  Array: import("./values.js").SandboxClosure;
14
16
  String: import("./values.js").SandboxClosure;
15
17
  Number: import("./values.js").SandboxClosure;
@@ -24,6 +26,7 @@ export declare function createBuiltinBindings(options: Parameters<typeof createC
24
26
  SyntaxError: import("./values.js").SandboxClosure;
25
27
  AggregateError: import("./values.js").SandboxClosure;
26
28
  Float32Array: import("./values.js").SandboxClosure;
29
+ Date: import("./values.js").SandboxClosure;
27
30
  Map: import("./values.js").SandboxClosure;
28
31
  Set: import("./values.js").SandboxClosure;
29
32
  JSON: import("./values.js").SandboxObject;
@@ -7,11 +7,13 @@ import { createRegexGlobals } from "./globals/regex.js";
7
7
  import { createMiscGlobals } from "./globals/misc.js";
8
8
  import { createObjectArrayGlobals } from "./globals/object-array.js";
9
9
  import { createPromiseGlobals } from "./promise.js";
10
+ import { createDateGlobal } from "./globals/date.js";
10
11
  export function createBuiltinBindings(options) {
11
12
  return {
12
13
  ...createConsoleJsonGlobals(options),
13
14
  ...createCollectionGlobals(options),
14
15
  Float32Array: createFloat32ArrayGlobal(options.budget),
16
+ Date: createDateGlobal(options),
15
17
  ...createErrorGlobals(options),
16
18
  ...createMathGlobals({ random: options.random }),
17
19
  ...createObjectArrayGlobals(options),
@@ -12,6 +12,10 @@ export type RealmBridge = {
12
12
  wrapCallback(closure: SandboxClosure): (...args: readonly unknown[]) => Promise<unknown>;
13
13
  invoke(operation: CallerInjectedFunction, call: () => unknown): unknown;
14
14
  awaitResult(operation: CallerInjectedFunction): boolean;
15
+ captureArguments(operation: CallerInjectedFunction, args: readonly SandboxValue[], copy: (values: readonly SandboxValue[]) => unknown[]): {
16
+ args: unknown[];
17
+ rollback(): void;
18
+ };
15
19
  };
16
20
  export type HostBridgeOptions = {
17
21
  realm?: RealmBridge;
@@ -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,8 @@
1
1
  import { type HostOperation } from "../extensions.js";
2
2
  import type { SandboxClosure, SandboxObject, SandboxValue } from "./values.js";
3
+ import type { SandboxIterator } from "./iteration.js";
3
4
  declare const hostObjectBrand: unique symbol;
5
+ declare const guestReferenceBrand: unique symbol;
4
6
  declare const sandboxHostObjectBrand: unique symbol;
5
7
  type SandboxHostObject = SandboxObject & {
6
8
  readonly [sandboxHostObjectBrand]: true;
@@ -8,18 +10,37 @@ type SandboxHostObject = SandboxObject & {
8
10
  export type HostObject = Readonly<Record<string, never>> & {
9
11
  readonly [hostObjectBrand]: true;
10
12
  };
13
+ export type GuestReference = Readonly<Record<string, never>> & {
14
+ readonly [guestReferenceBrand]: true;
15
+ };
16
+ export type HostObjectIndexedDefinition = {
17
+ length(): number;
18
+ get(index: number): unknown;
19
+ maxLength: number;
20
+ };
11
21
  export type HostObjectDefinition = {
22
+ indexed?: HostObjectIndexedDefinition;
23
+ named?: HostObjectNamedDefinition;
12
24
  properties?: Record<string, {
13
25
  get?: () => unknown;
14
26
  set?: (value: unknown) => void;
15
27
  }>;
16
28
  methods?: Record<string, HostOperation>;
17
29
  };
30
+ export type HostObjectNamedDefinition = {
31
+ keys(): readonly string[];
32
+ get(name: string): unknown;
33
+ maxKeys: number;
34
+ maxKeyCodeUnits: number;
35
+ enumerable?: boolean;
36
+ };
18
37
  export type HostObjectController = {
19
38
  owner: object;
20
39
  assertActive(): void;
21
40
  chargeWork(units?: number): void;
22
- read(operation: () => unknown): SandboxValue;
41
+ checkLength(length: number): void;
42
+ checkTemporaryDataSize(size: number): void;
43
+ read(operation: () => unknown, validate?: (value: unknown) => unknown): SandboxValue;
23
44
  write(operation: (value: unknown) => void, value: SandboxValue): void;
24
45
  method(operation: HostOperation): SandboxClosure;
25
46
  };
@@ -28,6 +49,9 @@ type GuestCallbackState = {
28
49
  closure?: SandboxClosure;
29
50
  assertActive(): void;
30
51
  };
52
+ export declare function createGuestReference(root: [SandboxValue], owner: object, assertActive: () => void): GuestReference;
53
+ export declare function readGuestReference(reference: unknown, owner: object): SandboxValue;
54
+ export declare function revokeGuestReference(reference: GuestReference, owner: object): void;
31
55
  export declare function createLiveHostObject(definition: HostObjectDefinition, controller: HostObjectController): HostObject;
32
56
  export declare function isGuestHostObject(value: unknown): value is SandboxHostObject;
33
57
  export declare function isLiveCapability(value: unknown): boolean;
@@ -40,4 +64,7 @@ export declare function revokeHostObject(value: HostObject, owner: object): void
40
64
  export declare function getHostObjectMember(value: SandboxObject, key: string): SandboxValue;
41
65
  export declare function setHostObjectMember(value: SandboxObject, key: string, entry: SandboxValue): void;
42
66
  export declare function getHostObjectKeys(value: SandboxObject): string[];
67
+ export declare function hasHostObjectMember(value: SandboxObject, key: string, enumerableOnly?: boolean): boolean;
68
+ export declare function measureHostObjectData(value: SandboxObject): number;
69
+ export declare function getHostObjectIterator(value: SandboxObject): SandboxIterator | undefined;
43
70
  export {};
@@ -1,11 +1,83 @@
1
1
  import { readDataRecord } from "../extensions.js";
2
+ import { types } from "node:util";
3
+ const MAX_INDEXED_LENGTH = 65_536;
4
+ const MAX_NAMED_KEYS = 65_536;
5
+ const MAX_NAMED_KEY_CODE_UNITS = 1_048_576;
2
6
  const hostObjects = new WeakMap();
3
7
  const guestObjects = new WeakMap();
4
8
  const guestCallbacks = new WeakMap();
9
+ const guestReferences = new WeakMap();
10
+ export function createGuestReference(root, owner, assertActive) {
11
+ const reference = Object.freeze(Object.create(null));
12
+ guestReferences.set(reference, { root, owner, assertActive });
13
+ return reference;
14
+ }
15
+ export function readGuestReference(reference, owner) {
16
+ const state = typeof reference === "object" && reference !== null
17
+ ? guestReferences.get(reference)
18
+ : undefined;
19
+ if (state === undefined || state.owner !== owner)
20
+ throw new TypeError("Foreign or invalid guest reference.");
21
+ state.assertActive();
22
+ if (state.root === undefined)
23
+ throw new TypeError("Guest reference is revoked.");
24
+ return state.root[0];
25
+ }
26
+ export function revokeGuestReference(reference, owner) {
27
+ const state = guestReferences.get(reference);
28
+ if (state === undefined || state.owner !== owner)
29
+ throw new TypeError("Foreign guest reference.");
30
+ state.root = undefined;
31
+ }
5
32
  export function createLiveHostObject(definition, controller) {
6
33
  const input = readDataRecord(definition, "Host object definition");
7
- if (Object.keys(input).some((key) => key !== "properties" && key !== "methods"))
34
+ if (Object.keys(input).some((key) => !["properties", "methods", "indexed", "named"].includes(key)))
8
35
  throw new TypeError("Unknown host object definition field.");
36
+ let indexed;
37
+ if (input.indexed !== undefined) {
38
+ const data = readDataRecord(input.indexed, "Indexed host capability");
39
+ if (Object.keys(data).some((key) => !["length", "get", "maxLength"].includes(key)))
40
+ throw new TypeError("Unknown indexed host capability field.");
41
+ if (typeof data.length !== "function" || typeof data.get !== "function")
42
+ throw new TypeError("Indexed length and get must be synchronous functions.");
43
+ if (typeof data.maxLength !== "number" ||
44
+ !Number.isInteger(data.maxLength) ||
45
+ data.maxLength < 1 ||
46
+ data.maxLength > MAX_INDEXED_LENGTH)
47
+ throw new RangeError(`Indexed maxLength must be an integer from 1 to ${MAX_INDEXED_LENGTH}.`);
48
+ indexed = {
49
+ length: data.length,
50
+ get: data.get,
51
+ maxLength: data.maxLength
52
+ };
53
+ }
54
+ let named;
55
+ if (input.named !== undefined) {
56
+ const data = readDataRecord(input.named, "Named host capability");
57
+ if (Object.keys(data).some((key) => !["keys", "get", "maxKeys", "maxKeyCodeUnits", "enumerable"].includes(key)))
58
+ throw new TypeError("Unknown named host capability field.");
59
+ if (typeof data.keys !== "function" || typeof data.get !== "function")
60
+ throw new TypeError("Named keys and get must be synchronous functions.");
61
+ if (typeof data.maxKeys !== "number" ||
62
+ !Number.isInteger(data.maxKeys) ||
63
+ data.maxKeys < 1 ||
64
+ data.maxKeys > MAX_NAMED_KEYS)
65
+ throw new RangeError(`Named maxKeys must be an integer from 1 to ${MAX_NAMED_KEYS}.`);
66
+ if (typeof data.maxKeyCodeUnits !== "number" ||
67
+ !Number.isInteger(data.maxKeyCodeUnits) ||
68
+ data.maxKeyCodeUnits < 1 ||
69
+ data.maxKeyCodeUnits > MAX_NAMED_KEY_CODE_UNITS)
70
+ throw new RangeError(`Named maxKeyCodeUnits must be an integer from 1 to ${MAX_NAMED_KEY_CODE_UNITS}.`);
71
+ if (data.enumerable !== undefined && typeof data.enumerable !== "boolean")
72
+ throw new TypeError("Named enumerable must be a boolean.");
73
+ named = {
74
+ keys: data.keys,
75
+ get: data.get,
76
+ maxKeys: data.maxKeys,
77
+ maxKeyCodeUnits: data.maxKeyCodeUnits,
78
+ enumerable: data.enumerable
79
+ };
80
+ }
9
81
  const properties = new Map();
10
82
  for (const [name, inputProperty] of Object.entries(readDataRecord(input.properties ?? {}, "Host properties"))) {
11
83
  const property = readDataRecord(inputProperty, `Host property '${name}'`);
@@ -26,6 +98,8 @@ export function createLiveHostObject(definition, controller) {
26
98
  for (const name of [...properties.keys(), ...Object.keys(operations)]) {
27
99
  if (["constructor", "prototype", "__proto__"].includes(name))
28
100
  throw new TypeError(`Reserved host member '${name}'.`);
101
+ if (indexed !== undefined && (name === "length" || canonicalIndex(name) !== undefined))
102
+ throw new TypeError(`Conflicting indexed host member '${name}'.`);
29
103
  }
30
104
  controller.assertActive();
31
105
  controller.chargeWork(properties.size + Object.keys(operations).length + 1);
@@ -35,7 +109,7 @@ export function createLiveHostObject(definition, controller) {
35
109
  name,
36
110
  controller.method(operation)
37
111
  ]));
38
- const state = { host, guest, controller, properties, methods };
112
+ const state = { host, guest, controller, properties, methods, indexed, named };
39
113
  hostObjects.set(host, state);
40
114
  guestObjects.set(guest, state);
41
115
  return host;
@@ -45,9 +119,14 @@ export function isGuestHostObject(value) {
45
119
  }
46
120
  export function isLiveCapability(value) {
47
121
  return (((typeof value === "object" && value !== null) || typeof value === "function") &&
48
- (hostObjects.has(value) || guestObjects.has(value) || guestCallbacks.has(value)));
122
+ (hostObjects.has(value) ||
123
+ guestObjects.has(value) ||
124
+ guestCallbacks.has(value) ||
125
+ guestReferences.has(value)));
49
126
  }
50
127
  export function importHostCapability(value, owner) {
128
+ if (guestReferences.has(value))
129
+ return readGuestReference(value, owner);
51
130
  const object = hostObjects.get(value);
52
131
  if (object !== undefined) {
53
132
  if (object.controller.owner !== owner)
@@ -97,15 +176,32 @@ export function revokeHostObject(value, owner) {
97
176
  throw new TypeError("Foreign host object.");
98
177
  state.properties.clear();
99
178
  state.methods.clear();
179
+ state.indexed = undefined;
180
+ state.named = undefined;
100
181
  }
101
182
  export function getHostObjectMember(value, key) {
102
183
  const state = guestObjects.get(value);
103
184
  state.controller.assertActive();
104
185
  state.controller.chargeWork();
186
+ if (state.indexed !== undefined) {
187
+ if (key === "length")
188
+ return indexedLength(state);
189
+ const index = canonicalIndex(key);
190
+ if (index !== undefined) {
191
+ if (index >= state.indexed.maxLength || index >= indexedLength(state))
192
+ return undefined;
193
+ return state.controller.read(() => state.indexed.get(index));
194
+ }
195
+ }
105
196
  const property = state.properties.get(key);
106
197
  if (property !== undefined)
107
198
  return property.get === undefined ? undefined : state.controller.read(property.get);
108
- return state.methods.get(key);
199
+ const method = state.methods.get(key);
200
+ if (method !== undefined)
201
+ return method;
202
+ if (state.named !== undefined && namedKeys(state).includes(key))
203
+ return state.controller.read(() => state.named.get(key));
204
+ return undefined;
109
205
  }
110
206
  export function setHostObjectMember(value, key, entry) {
111
207
  const state = guestObjects.get(value);
@@ -119,5 +215,124 @@ export function setHostObjectMember(value, key, entry) {
119
215
  export function getHostObjectKeys(value) {
120
216
  const state = guestObjects.get(value);
121
217
  state.controller.assertActive();
122
- return [...state.properties.keys(), ...state.methods.keys()];
218
+ const length = state.indexed === undefined ? 0 : indexedLength(state);
219
+ const names = state.named === undefined || state.named.enumerable === false
220
+ ? []
221
+ : namedKeys(state).filter((key) => !state.properties.has(key) &&
222
+ !state.methods.has(key) &&
223
+ !(state.indexed !== undefined &&
224
+ (key === "length" || canonicalIndex(key) !== undefined)));
225
+ const size = length + state.properties.size + state.methods.size + names.length;
226
+ state.controller.checkLength(size);
227
+ state.controller.chargeWork(size + 1);
228
+ return [
229
+ ...Array.from({ length }, (_entry, index) => String(index)),
230
+ ...state.properties.keys(),
231
+ ...state.methods.keys(),
232
+ ...names
233
+ ];
234
+ }
235
+ export function hasHostObjectMember(value, key, enumerableOnly = false) {
236
+ const state = guestObjects.get(value);
237
+ state.controller.assertActive();
238
+ state.controller.chargeWork();
239
+ if (state.indexed !== undefined) {
240
+ if (key === "length")
241
+ return !enumerableOnly;
242
+ const index = canonicalIndex(key);
243
+ if (index !== undefined)
244
+ return index < state.indexed.maxLength && index < indexedLength(state);
245
+ }
246
+ if (state.properties.has(key) || state.methods.has(key))
247
+ return true;
248
+ return (state.named !== undefined &&
249
+ !(enumerableOnly && state.named.enumerable === false) &&
250
+ namedKeys(state).includes(key));
251
+ }
252
+ export function measureHostObjectData(value) {
253
+ const state = guestObjects.get(value);
254
+ let size = state.indexed === undefined ? 0 : 16;
255
+ if (state.named !== undefined)
256
+ size += 24;
257
+ for (const key of state.properties.keys())
258
+ size += key.length + 1;
259
+ for (const key of state.methods.keys())
260
+ size += key.length + 1;
261
+ return size;
262
+ }
263
+ export function getHostObjectIterator(value) {
264
+ const state = guestObjects.get(value);
265
+ state.controller.assertActive();
266
+ if (state.indexed === undefined)
267
+ return undefined;
268
+ let index = 0;
269
+ let exhausted = false;
270
+ return {
271
+ next: () => {
272
+ state.controller.assertActive();
273
+ state.controller.chargeWork();
274
+ if (exhausted)
275
+ return { done: true, value: undefined };
276
+ if (index >= indexedLength(state)) {
277
+ exhausted = true;
278
+ return { done: true, value: undefined };
279
+ }
280
+ const position = index++;
281
+ return { done: false, value: state.controller.read(() => state.indexed.get(position)) };
282
+ }
283
+ };
284
+ }
285
+ function indexedLength(state) {
286
+ state.controller.chargeWork();
287
+ const length = state.controller.read(state.indexed.length);
288
+ if (typeof length !== "number" ||
289
+ !Number.isInteger(length) ||
290
+ length < 0 ||
291
+ length > state.indexed.maxLength)
292
+ throw new RangeError("Indexed length must be a non-negative integer within maxLength.");
293
+ state.controller.checkLength(length);
294
+ return length;
295
+ }
296
+ function canonicalIndex(key) {
297
+ const index = Number(key);
298
+ return Number.isInteger(index) && index >= 0 && index < 0xffffffff && String(index) === key
299
+ ? index
300
+ : undefined;
301
+ }
302
+ function namedKeys(state) {
303
+ const named = state.named;
304
+ return state.controller.read(named.keys, (value) => {
305
+ if (!Array.isArray(value) || types.isProxy(value))
306
+ throw new TypeError("Named keys must be a dense own-data array of strings, not a proxy.");
307
+ const length = Object.getOwnPropertyDescriptor(value, "length").value;
308
+ if (length > named.maxKeys)
309
+ throw new RangeError("Named keys exceed maxKeys.");
310
+ state.controller.checkLength(length);
311
+ state.controller.chargeWork(length + 1);
312
+ if (Reflect.ownKeys(value).length !== length + 1)
313
+ throw new TypeError("Named keys must contain only dense own-data indices and length.");
314
+ const result = [];
315
+ const seen = new Set();
316
+ let units = 0;
317
+ for (let index = 0; index < length; index++) {
318
+ const descriptor = Object.getOwnPropertyDescriptor(value, String(index));
319
+ if (descriptor === undefined ||
320
+ !("value" in descriptor) ||
321
+ typeof descriptor.value !== "string")
322
+ throw new TypeError("Named keys require dense own string data, not accessors or sparse arrays.");
323
+ const key = descriptor.value;
324
+ units += key.length;
325
+ if (units > named.maxKeyCodeUnits)
326
+ throw new RangeError("Named keys exceed maximum UTF-16 code units.");
327
+ state.controller.chargeWork(key.length);
328
+ if (seen.has(key))
329
+ throw new TypeError("Named keys must be distinct.");
330
+ if (["constructor", "prototype", "__proto__"].includes(key))
331
+ throw new TypeError(`Reserved named host member '${key}'.`);
332
+ seen.add(key);
333
+ result.push(key);
334
+ }
335
+ state.controller.checkTemporaryDataSize(1 + length + units);
336
+ return result;
337
+ });
123
338
  }