@poe-platform/safe-js 0.1.28 → 0.1.29

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.
package/README.md CHANGED
@@ -212,7 +212,7 @@ The manifest requires `version: 1` and a nonempty `name`. Optional `capabilities
212
212
  | `signal` | Realm cancellation signal; aborted on close or failure. |
213
213
  | `onCleanup(fn)` | Register a sync/async disposer. Cleanup runs in reverse order, awaits every disposer, and reports failures without skipping the rest. |
214
214
  | `chargeWork(units = 1)` | Charge a nonnegative integer against the shared execution budget. Fatal exhaustion cannot be swallowed to continue execution. |
215
- | `createHostObject({ properties?, methods?, indexed? })` | Create a realm-owned capability. Properties declare synchronous `get`/`set` functions; methods are host functions. Optional `indexed` exposes a bounded live collection. Undeclared members expose no native prototype. |
215
+ | `createHostObject({ properties?, methods?, indexed?, named? })` | Create a realm-owned capability. Properties declare synchronous `get`/`set` functions; methods are host functions. Optional `indexed` and `named` expose bounded live members. Undeclared members expose no native prototype. |
216
216
  | `invokeCallback(callback, { thisValue?, args? })` | Invoke a captured guest function with the realm's state, cancellation and budgets. Same operation as on the realm. |
217
217
  | `releaseCallback(callback)` | Revoke the callback and release its retained guest state. |
218
218
  | `retainGuestArguments(operation, from)` | During setup, opt an operation into opaque argument references starting at the zero-based index `from`. Requires declared and granted `guest:retain`. Earlier arguments keep normal conversion; live host methods preserve the declaration. |
@@ -238,7 +238,30 @@ const collection = context.createHostObject({ indexed: {
238
238
 
239
239
  Saved collections observe current host contents. Index reads, `Object.keys`/`values`/`entries`, `Object.hasOwn`, `in`, `for...in`, `for...of`, array/object spread and `Array.from` use the live view. Enumerable keys include current indices and fixed members, but not `length`. `Array.from` preserves element identity and interleaves mapping with reads. Noncanonical and out-of-range indices never call `get`; fixed members cannot reuse `length` or canonical index names. Enumeration and traversal consume execution budgets, without eagerly allocating virtual properties.
240
240
 
241
- Indexed members and their `length` are read-only. Live objects reject deletion, freezing, native prototype access, property-descriptor manipulation and portable serialization. Realm state is not a checkpoint: snapshot/replay and live-capability error-data conversion are rejected. Extensions are trusted native code; grants are a registration contract, not OS isolation. Native work still needs host timeouts and external process supervision for hard limits. No DOM, timers or browser engine are bundled.
241
+ For changing named properties, add `named` to the same definition:
242
+
243
+ ```js
244
+ const named = {
245
+ keys: () => [...attributes.keys()],
246
+ get: name => attributes.get(name),
247
+ maxKeys: 256,
248
+ maxKeyCodeUnits: 8192,
249
+ enumerable: false
250
+ };
251
+ const attributesObject = context.createHostObject({ named });
252
+ ```
253
+
254
+ | Named option | Contract |
255
+ | --- | --- |
256
+ | `keys()` | Synchronous dense own-data array of distinct strings. Proxies, accessors, sparse arrays and reserved `constructor`/`prototype`/`__proto__` names reject. |
257
+ | `get(name)` | Synchronous value provider, called only for a currently present name. Existing host conversion and identity rules apply. |
258
+ | `maxKeys` | Required positive integer, at most 65,536. |
259
+ | `maxKeyCodeUnits` | Required positive aggregate key-length cap, at most 1,048,576 UTF-16 code units. Execution, array, string and data budgets also apply. |
260
+ | `enumerable` | Defaults to `true`. Set `false` to keep names readable and visible to `in`/`Object.hasOwn`, but omit them from keys/values/entries, object spread and `for...in`. |
261
+
262
+ Fixed properties/methods take precedence over names. With `indexed`, numeric indices and `length` remain indexed members. Enumeration deduplicates collisions; names removed by an earlier getter are skipped. Named-only objects are not iterable—combine `named` with `indexed` when you need numeric collection access and `for...of`.
263
+
264
+ Named properties, indexed members and indexed `length` are read-only. Live objects reject deletion, freezing, native prototype access, property-descriptor manipulation and portable serialization. Realm state is not a checkpoint: snapshot/replay and live-capability error-data conversion are rejected. Extensions are trusted native code; grants are a registration contract, not OS isolation. Native work still needs host timeouts and external process supervision for hard limits. No DOM, timers or browser engine are bundled.
242
265
 
243
266
  For one-shot use, `run(source, { extensions, grants, ... })` accepts the same realm options plus `filename`, returns data only, and closes resources before settling. Run-only features such as snapshots, `entryPointArgs`, `importMeta`, custom random generators and telemetry are rejected in this mode rather than silently ignored.
244
267
 
@@ -27,7 +27,7 @@ import {
27
27
  validateMigrationSemantics,
28
28
  validateSnapshotData,
29
29
  validateSnapshotMigration
30
- } from "./chunk-MCF7GT3X.js";
30
+ } from "./chunk-MO3YD4ID.js";
31
31
 
32
32
  // packages/safe-js/src/migrate.ts
33
33
  import { createHash } from "node:crypto";
@@ -8245,4 +8245,4 @@ export {
8245
8245
  parseMcpConfig,
8246
8246
  makeMcpModule
8247
8247
  };
8248
- //# sourceMappingURL=chunk-6V2VGVEH.js.map
8248
+ //# sourceMappingURL=chunk-D4J7UXVE.js.map
@@ -6331,7 +6331,10 @@ function serializedDateTime(value) {
6331
6331
  }
6332
6332
 
6333
6333
  // packages/safe-js/src/interp/host-capabilities.ts
6334
+ import { types as types3 } from "node:util";
6334
6335
  var MAX_INDEXED_LENGTH = 65536;
6336
+ var MAX_NAMED_KEYS = 65536;
6337
+ var MAX_NAMED_KEY_CODE_UNITS = 1048576;
6335
6338
  var hostObjects = /* @__PURE__ */ new WeakMap();
6336
6339
  var guestObjects = /* @__PURE__ */ new WeakMap();
6337
6340
  var guestCallbacks = /* @__PURE__ */ new WeakMap();
@@ -6356,7 +6359,7 @@ function revokeGuestReference(reference, owner) {
6356
6359
  }
6357
6360
  function createLiveHostObject(definition, controller) {
6358
6361
  const input = readDataRecord(definition, "Host object definition");
6359
- if (Object.keys(input).some((key) => key !== "properties" && key !== "methods" && key !== "indexed"))
6362
+ if (Object.keys(input).some((key) => !["properties", "methods", "indexed", "named"].includes(key)))
6360
6363
  throw new TypeError("Unknown host object definition field.");
6361
6364
  let indexed;
6362
6365
  if (input.indexed !== void 0) {
@@ -6373,6 +6376,31 @@ function createLiveHostObject(definition, controller) {
6373
6376
  maxLength: data.maxLength
6374
6377
  };
6375
6378
  }
6379
+ let named;
6380
+ if (input.named !== void 0) {
6381
+ const data = readDataRecord(input.named, "Named host capability");
6382
+ if (Object.keys(data).some(
6383
+ (key) => !["keys", "get", "maxKeys", "maxKeyCodeUnits", "enumerable"].includes(key)
6384
+ ))
6385
+ throw new TypeError("Unknown named host capability field.");
6386
+ if (typeof data.keys !== "function" || typeof data.get !== "function")
6387
+ throw new TypeError("Named keys and get must be synchronous functions.");
6388
+ if (typeof data.maxKeys !== "number" || !Number.isInteger(data.maxKeys) || data.maxKeys < 1 || data.maxKeys > MAX_NAMED_KEYS)
6389
+ throw new RangeError(`Named maxKeys must be an integer from 1 to ${MAX_NAMED_KEYS}.`);
6390
+ if (typeof data.maxKeyCodeUnits !== "number" || !Number.isInteger(data.maxKeyCodeUnits) || data.maxKeyCodeUnits < 1 || data.maxKeyCodeUnits > MAX_NAMED_KEY_CODE_UNITS)
6391
+ throw new RangeError(
6392
+ `Named maxKeyCodeUnits must be an integer from 1 to ${MAX_NAMED_KEY_CODE_UNITS}.`
6393
+ );
6394
+ if (data.enumerable !== void 0 && typeof data.enumerable !== "boolean")
6395
+ throw new TypeError("Named enumerable must be a boolean.");
6396
+ named = {
6397
+ keys: data.keys,
6398
+ get: data.get,
6399
+ maxKeys: data.maxKeys,
6400
+ maxKeyCodeUnits: data.maxKeyCodeUnits,
6401
+ enumerable: data.enumerable
6402
+ };
6403
+ }
6376
6404
  const properties = /* @__PURE__ */ new Map();
6377
6405
  for (const [name, inputProperty] of Object.entries(
6378
6406
  readDataRecord(input.properties ?? {}, "Host properties")
@@ -6405,7 +6433,7 @@ function createLiveHostObject(definition, controller) {
6405
6433
  controller.method(operation)
6406
6434
  ])
6407
6435
  );
6408
- const state = { host, guest, controller, properties, methods, indexed };
6436
+ const state = { host, guest, controller, properties, methods, indexed, named };
6409
6437
  hostObjects.set(host, state);
6410
6438
  guestObjects.set(guest, state);
6411
6439
  return host;
@@ -6463,6 +6491,7 @@ function revokeHostObject(value, owner) {
6463
6491
  state.properties.clear();
6464
6492
  state.methods.clear();
6465
6493
  state.indexed = void 0;
6494
+ state.named = void 0;
6466
6495
  }
6467
6496
  function getHostObjectMember(value, key) {
6468
6497
  const state = guestObjects.get(value);
@@ -6479,7 +6508,11 @@ function getHostObjectMember(value, key) {
6479
6508
  const property = state.properties.get(key);
6480
6509
  if (property !== void 0)
6481
6510
  return property.get === void 0 ? void 0 : state.controller.read(property.get);
6482
- return state.methods.get(key);
6511
+ const method = state.methods.get(key);
6512
+ if (method !== void 0) return method;
6513
+ if (state.named !== void 0 && namedKeys(state).includes(key))
6514
+ return state.controller.read(() => state.named.get(key));
6515
+ return void 0;
6483
6516
  }
6484
6517
  function setHostObjectMember(value, key, entry) {
6485
6518
  const state = guestObjects.get(value);
@@ -6493,13 +6526,17 @@ function getHostObjectKeys(value) {
6493
6526
  const state = guestObjects.get(value);
6494
6527
  state.controller.assertActive();
6495
6528
  const length = state.indexed === void 0 ? 0 : indexedLength(state);
6496
- const size = length + state.properties.size + state.methods.size;
6529
+ const names = state.named === void 0 || state.named.enumerable === false ? [] : namedKeys(state).filter(
6530
+ (key) => !state.properties.has(key) && !state.methods.has(key) && !(state.indexed !== void 0 && (key === "length" || canonicalIndex(key) !== void 0))
6531
+ );
6532
+ const size = length + state.properties.size + state.methods.size + names.length;
6497
6533
  state.controller.checkLength(size);
6498
6534
  state.controller.chargeWork(size + 1);
6499
6535
  return [
6500
6536
  ...Array.from({ length }, (_entry, index) => String(index)),
6501
6537
  ...state.properties.keys(),
6502
- ...state.methods.keys()
6538
+ ...state.methods.keys(),
6539
+ ...names
6503
6540
  ];
6504
6541
  }
6505
6542
  function hasHostObjectMember(value, key, enumerableOnly = false) {
@@ -6511,11 +6548,13 @@ function hasHostObjectMember(value, key, enumerableOnly = false) {
6511
6548
  const index = canonicalIndex(key);
6512
6549
  if (index !== void 0) return index < state.indexed.maxLength && index < indexedLength(state);
6513
6550
  }
6514
- return state.properties.has(key) || state.methods.has(key);
6551
+ if (state.properties.has(key) || state.methods.has(key)) return true;
6552
+ return state.named !== void 0 && !(enumerableOnly && state.named.enumerable === false) && namedKeys(state).includes(key);
6515
6553
  }
6516
6554
  function measureHostObjectData(value) {
6517
6555
  const state = guestObjects.get(value);
6518
6556
  let size = state.indexed === void 0 ? 0 : 16;
6557
+ if (state.named !== void 0) size += 24;
6519
6558
  for (const key of state.properties.keys()) size += key.length + 1;
6520
6559
  for (const key of state.methods.keys()) size += key.length + 1;
6521
6560
  return size;
@@ -6552,6 +6591,41 @@ function canonicalIndex(key) {
6552
6591
  const index = Number(key);
6553
6592
  return Number.isInteger(index) && index >= 0 && index < 4294967295 && String(index) === key ? index : void 0;
6554
6593
  }
6594
+ function namedKeys(state) {
6595
+ const named = state.named;
6596
+ return state.controller.read(named.keys, (value) => {
6597
+ if (!Array.isArray(value) || types3.isProxy(value))
6598
+ throw new TypeError("Named keys must be a dense own-data array of strings, not a proxy.");
6599
+ const length = Object.getOwnPropertyDescriptor(value, "length").value;
6600
+ if (length > named.maxKeys) throw new RangeError("Named keys exceed maxKeys.");
6601
+ state.controller.checkLength(length);
6602
+ state.controller.chargeWork(length + 1);
6603
+ if (Reflect.ownKeys(value).length !== length + 1)
6604
+ throw new TypeError("Named keys must contain only dense own-data indices and length.");
6605
+ const result = [];
6606
+ const seen = /* @__PURE__ */ new Set();
6607
+ let units = 0;
6608
+ for (let index = 0; index < length; index++) {
6609
+ const descriptor = Object.getOwnPropertyDescriptor(value, String(index));
6610
+ if (descriptor === void 0 || !("value" in descriptor) || typeof descriptor.value !== "string")
6611
+ throw new TypeError(
6612
+ "Named keys require dense own string data, not accessors or sparse arrays."
6613
+ );
6614
+ const key = descriptor.value;
6615
+ units += key.length;
6616
+ if (units > named.maxKeyCodeUnits)
6617
+ throw new RangeError("Named keys exceed maximum UTF-16 code units.");
6618
+ state.controller.chargeWork(key.length);
6619
+ if (seen.has(key)) throw new TypeError("Named keys must be distinct.");
6620
+ if (["constructor", "prototype", "__proto__"].includes(key))
6621
+ throw new TypeError(`Reserved named host member '${key}'.`);
6622
+ seen.add(key);
6623
+ result.push(key);
6624
+ }
6625
+ state.controller.checkTemporaryDataSize(1 + length + units);
6626
+ return result;
6627
+ });
6628
+ }
6555
6629
 
6556
6630
  // packages/safe-js/src/interp/values.ts
6557
6631
  import { types as nodeTypes } from "node:util";
@@ -6673,7 +6747,7 @@ async function flushPromiseJobs() {
6673
6747
  import { AsyncLocalStorage as AsyncLocalStorage2 } from "node:async_hooks";
6674
6748
 
6675
6749
  // packages/safe-js/src/snapshot/validation.ts
6676
- import { types as types3 } from "node:util";
6750
+ import { types as types4 } from "node:util";
6677
6751
 
6678
6752
  // packages/safe-js/src/interp/arguments.ts
6679
6753
  var sandboxArgumentsBrand = /* @__PURE__ */ Symbol("SandboxArguments");
@@ -7557,7 +7631,7 @@ function validateGenericValue(value, path, depth, state) {
7557
7631
  if (typeof value === "object" && value !== null && hasGuestObjectState(value)) {
7558
7632
  fail("invalidState", path, "guest function properties, prototype links and custom descriptors cannot be restored");
7559
7633
  }
7560
- if (state.dataPropertiesOnly && types3.isProxy(value)) {
7634
+ if (state.dataPropertiesOnly && types4.isProxy(value)) {
7561
7635
  fail("invalidType", path, "proxy objects are not snapshot data");
7562
7636
  }
7563
7637
  if (depth > state.limits.maxDepth)
@@ -29569,7 +29643,7 @@ function createReplayableRandom(options = {}) {
29569
29643
 
29570
29644
  // packages/safe-js/src/realm.ts
29571
29645
  import { AsyncLocalStorage as AsyncLocalStorage6 } from "node:async_hooks";
29572
- import { types as types4 } from "node:util";
29646
+ import { types as types5 } from "node:util";
29573
29647
 
29574
29648
  // packages/safe-js/src/interp/globals/console-json.ts
29575
29649
  function createConsoleJsonGlobals(options) {
@@ -30701,7 +30775,7 @@ var RealmState = class {
30701
30775
  throw new TypeError("Realm limits must be positive safe integers with supported names.");
30702
30776
  this.limits[name] = Number(value);
30703
30777
  }
30704
- if (options.extensions !== void 0 && (!Array.isArray(options.extensions) || types4.isProxy(options.extensions)))
30778
+ if (options.extensions !== void 0 && (!Array.isArray(options.extensions) || types5.isProxy(options.extensions)))
30705
30779
  throw new TypeError("Extensions must be a registration array.");
30706
30780
  const registrations = options.extensions ?? [];
30707
30781
  const extensions = [];
@@ -30921,7 +30995,7 @@ var RealmState = class {
30921
30995
  return this.phase.run(phase, () => {
30922
30996
  try {
30923
30997
  const result = call();
30924
- if (types4.isPromise(result) || phase.pending.size > 0 || phase.failure !== void 0) {
30998
+ if (types5.isPromise(result) || phase.pending.size > 0 || phase.failure !== void 0) {
30925
30999
  return Promise.resolve(result).then(
30926
31000
  async (value) => {
30927
31001
  await Promise.allSettled(phase.pending);
@@ -30977,17 +31051,25 @@ var RealmState = class {
30977
31051
  assertActive: this.assertOpen,
30978
31052
  chargeWork: this.chargeWork,
30979
31053
  checkLength: (length) => this.budget.allocateArrayLength(length),
30980
- read: (operation) => {
31054
+ checkTemporaryDataSize: (size) => {
31055
+ const temporary = {};
31056
+ try {
31057
+ this.budget.setRetainedDataUsage(temporary, size);
31058
+ } finally {
31059
+ this.budget.setRetainedDataUsage(temporary, 0);
31060
+ }
31061
+ },
31062
+ read: (operation, validate) => {
30981
31063
  const value = this.invokeHost(operation, operation);
30982
- if (types4.isPromise(value)) {
31064
+ if (types5.isPromise(value)) {
30983
31065
  void Promise.resolve(value).catch(() => void 0);
30984
31066
  throw new TypeError("Live property getters must be synchronous.");
30985
31067
  }
30986
- return this.importValue(value);
31068
+ return this.importValue(validate === void 0 ? value : validate(value));
30987
31069
  },
30988
31070
  write: (operation, value) => {
30989
31071
  const result = this.invokeHost(operation, () => operation(this.exportValue(value)));
30990
- if (types4.isPromise(result)) {
31072
+ if (types5.isPromise(result)) {
30991
31073
  void Promise.resolve(result).catch(() => void 0);
30992
31074
  throw new TypeError("Live property setters must be synchronous.");
30993
31075
  }
@@ -31180,7 +31262,7 @@ var RealmState = class {
31180
31262
  }
31181
31263
  });
31182
31264
  const output = getExtensionSetup(extension)(context);
31183
- if (types4.isPromise(output)) {
31265
+ if (types5.isPromise(output)) {
31184
31266
  void Promise.resolve(output).catch(() => void 0);
31185
31267
  throw new TypeError("Extension setup must be synchronous.");
31186
31268
  }
@@ -31368,7 +31450,7 @@ var RealmState = class {
31368
31450
  };
31369
31451
  function readModules(input) {
31370
31452
  const entries = (value, label) => {
31371
- if (types4.isMap(value) && !types4.isProxy(value)) {
31453
+ if (types5.isMap(value) && !types5.isProxy(value)) {
31372
31454
  const result = [...Map.prototype.entries.call(value)];
31373
31455
  if (result.length > 4096 || result.some(([key]) => typeof key !== "string" || key.length === 0))
31374
31456
  throw new TypeError(`${label} requires bounded string keys.`);
@@ -32593,4 +32675,4 @@ export {
32593
32675
  FileSnapshotBackend,
32594
32676
  run
32595
32677
  };
32596
- //# sourceMappingURL=chunk-MCF7GT3X.js.map
32678
+ //# sourceMappingURL=chunk-MO3YD4ID.js.map