@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 +25 -2
- package/dist/safe-js/chunks/{chunk-6V2VGVEH.js → chunk-D4J7UXVE.js} +2 -2
- package/dist/safe-js/chunks/{chunk-MCF7GT3X.js → chunk-MO3YD4ID.js} +100 -18
- package/dist/safe-js/chunks/chunk-MO3YD4ID.js.map +7 -0
- package/dist/safe-js/cli.js +2 -2
- package/dist/safe-js/core.d.ts +1 -1
- package/dist/safe-js/core.js +1 -1
- package/dist/safe-js/index.d.ts +1 -1
- package/dist/safe-js/index.js +2 -2
- package/dist/safe-js/index.js.map +1 -1
- package/dist/safe-js/interp/host-capabilities.d.ts +10 -1
- package/package.json +2 -2
- package/dist/safe-js/chunks/chunk-MCF7GT3X.js.map +0 -7
- /package/dist/safe-js/chunks/{chunk-6V2VGVEH.js.map → chunk-D4J7UXVE.js.map} +0 -0
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`
|
|
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
|
-
|
|
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-
|
|
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-
|
|
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) =>
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
|
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 &&
|
|
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
|
|
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) ||
|
|
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 (
|
|
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
|
-
|
|
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 (
|
|
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 (
|
|
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 (
|
|
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 (
|
|
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-
|
|
32678
|
+
//# sourceMappingURL=chunk-MO3YD4ID.js.map
|