@poe-platform/safe-js 0.1.22 → 0.1.23
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 +8 -2
- package/dist/safe-js/chunks/{chunk-2M6MNQBY.js → chunk-MXUOEOBE.js} +109 -11
- package/dist/safe-js/chunks/chunk-MXUOEOBE.js.map +7 -0
- package/dist/safe-js/chunks/{chunk-BO5CZWZM.js → chunk-X32RVLSM.js} +2 -2
- 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/extensions.d.ts +2 -0
- 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-bridge.d.ts +4 -0
- package/dist/safe-js/interp/host-capabilities.d.ts +7 -0
- package/dist/safe-js/realm.d.ts +2 -0
- package/package.json +2 -2
- package/dist/safe-js/chunks/chunk-2M6MNQBY.js.map +0 -7
- /package/dist/safe-js/chunks/{chunk-BO5CZWZM.js.map → chunk-X32RVLSM.js.map} +0 -0
package/README.md
CHANGED
|
@@ -116,9 +116,9 @@ This prints `2`. Evaluations share declarations, closures and object identity wi
|
|
|
116
116
|
| --- | --- |
|
|
117
117
|
| `extensions` | Explicit `defineExtension(...)` registrations; `[]`. Setup runs once, on first evaluation, not on construction or unused close. |
|
|
118
118
|
| `grants` | Granted capability names; `[]`. Every requested capability must be granted before any extension setup runs. |
|
|
119
|
-
| `limits` | Positive integer caps: `extensions: 32`, `hostObjects: 1024`, `callbacks: 1024`, `cleanups: 1024`, `nestedEvaluations: 16`. Collection budgets also apply. |
|
|
119
|
+
| `limits` | Positive integer caps: `extensions: 32`, `hostObjects: 1024`, `callbacks: 1024`, `guestReferences: 1024`, `cleanups: 1024`, `nestedEvaluations: 16`. Collection budgets also apply. |
|
|
120
120
|
|
|
121
|
-
Ordinary host arguments/results are still copied. To preserve live native identity, explicitly create a host object. A guest function crossing to the host becomes an opaque callback: invoke it with `realm.invokeCallback(callback, { thisValue?, args? })`, then `realm.releaseCallback(callback)` when no longer needed. Callbacks and live objects cannot cross realms or survive close.
|
|
121
|
+
Ordinary host arguments/results are still copied. To preserve live native identity, explicitly create a host object. A guest function crossing to the host becomes an opaque callback: invoke it with `realm.invokeCallback(callback, { thisValue?, args? })`, then `realm.releaseCallback(callback)` when no longer needed. Callbacks and live objects cannot cross realms or survive close. For deferred arguments that must preserve guest identity, opt into retained references as described below.
|
|
122
122
|
|
|
123
123
|
<details>
|
|
124
124
|
<summary>Trusted extensions and live host objects</summary>
|
|
@@ -163,9 +163,15 @@ The manifest requires `version: 1` and a nonempty `name`. Optional `capabilities
|
|
|
163
163
|
| `createHostObject({ properties?, methods? })` | Create a realm-owned capability. Properties declare synchronous `get`/`set` functions; methods are host functions. Undeclared members expose no native prototype. |
|
|
164
164
|
| `invokeCallback(callback, { thisValue?, args? })` | Invoke a captured guest function with the realm's state, cancellation and budgets. Same operation as on the realm. |
|
|
165
165
|
| `releaseCallback(callback)` | Revoke the callback and release its retained guest state. |
|
|
166
|
+
| `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. |
|
|
167
|
+
| `releaseGuestReference(reference)` | Revoke one reference and release its retained state. Also available on the realm. |
|
|
166
168
|
| `nestedOperation(fn)` | During setup, mark a host operation authorized to run nested source. Requires declared and granted `source:nested`. |
|
|
167
169
|
| `evaluateNested(source)` | Only inside that extension's authorized operation. Completes before the enclosing call returns to guest code, shares scope/budgets, and propagates errors. Parallel nested evaluations and ordinary source reentry are rejected. |
|
|
168
170
|
|
|
171
|
+
For a timer-shaped `schedule(callback, delay, ...args)`, register `context.retainGuestArguments(schedule, 2)`. The host receives normal callback/delay values and opaque `GuestReference` handles for the remaining arguments. Pass those handles to `context.invokeCallback(callback, { args })` to recover the original guest objects and observe mutations made after scheduling. References also work as callback receivers and host return values, including cycles, closures, primitives and live host objects.
|
|
172
|
+
|
|
173
|
+
Release each reference when the host no longer needs it; returning it does not release it. Retained graphs count against data budgets and `limits.guestReferences`. Synchronous native failure releases references captured for that call; asynchronous operations must release theirs in host cleanup. Close revokes all remaining references. Handles cannot be inspected, used in another realm, or serialized into replay/error data. Unmarked operations still copy values.
|
|
174
|
+
|
|
169
175
|
Live objects do not support native prototypes, property-descriptor manipulation or 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.
|
|
170
176
|
|
|
171
177
|
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 clocks/random generators and telemetry are rejected in this mode rather than silently ignored.
|
|
@@ -6211,6 +6211,25 @@ function copyFloat32Storage(value, state) {
|
|
|
6211
6211
|
var hostObjects = /* @__PURE__ */ new WeakMap();
|
|
6212
6212
|
var guestObjects = /* @__PURE__ */ new WeakMap();
|
|
6213
6213
|
var guestCallbacks = /* @__PURE__ */ new WeakMap();
|
|
6214
|
+
var guestReferences = /* @__PURE__ */ new WeakMap();
|
|
6215
|
+
function createGuestReference(root, owner, assertActive) {
|
|
6216
|
+
const reference = Object.freeze(/* @__PURE__ */ Object.create(null));
|
|
6217
|
+
guestReferences.set(reference, { root, owner, assertActive });
|
|
6218
|
+
return reference;
|
|
6219
|
+
}
|
|
6220
|
+
function readGuestReference(reference, owner) {
|
|
6221
|
+
const state = typeof reference === "object" && reference !== null ? guestReferences.get(reference) : void 0;
|
|
6222
|
+
if (state === void 0 || state.owner !== owner)
|
|
6223
|
+
throw new TypeError("Foreign or invalid guest reference.");
|
|
6224
|
+
state.assertActive();
|
|
6225
|
+
if (state.root === void 0) throw new TypeError("Guest reference is revoked.");
|
|
6226
|
+
return state.root[0];
|
|
6227
|
+
}
|
|
6228
|
+
function revokeGuestReference(reference, owner) {
|
|
6229
|
+
const state = guestReferences.get(reference);
|
|
6230
|
+
if (state === void 0 || state.owner !== owner) throw new TypeError("Foreign guest reference.");
|
|
6231
|
+
state.root = void 0;
|
|
6232
|
+
}
|
|
6214
6233
|
function createLiveHostObject(definition, controller) {
|
|
6215
6234
|
const input = readDataRecord(definition, "Host object definition");
|
|
6216
6235
|
if (Object.keys(input).some((key) => key !== "properties" && key !== "methods"))
|
|
@@ -6254,9 +6273,10 @@ function isGuestHostObject(value) {
|
|
|
6254
6273
|
return typeof value === "object" && value !== null && guestObjects.has(value);
|
|
6255
6274
|
}
|
|
6256
6275
|
function isLiveCapability(value) {
|
|
6257
|
-
return (typeof value === "object" && value !== null || typeof value === "function") && (hostObjects.has(value) || guestObjects.has(value) || guestCallbacks.has(value));
|
|
6276
|
+
return (typeof value === "object" && value !== null || typeof value === "function") && (hostObjects.has(value) || guestObjects.has(value) || guestCallbacks.has(value) || guestReferences.has(value));
|
|
6258
6277
|
}
|
|
6259
6278
|
function importHostCapability(value, owner) {
|
|
6279
|
+
if (guestReferences.has(value)) return readGuestReference(value, owner);
|
|
6260
6280
|
const object = hostObjects.get(value);
|
|
6261
6281
|
if (object !== void 0) {
|
|
6262
6282
|
if (object.controller.owner !== owner) throw new TypeError("Foreign realm host capability.");
|
|
@@ -28043,7 +28063,7 @@ function wrapCallerInjectedFunction(name, value, options, state) {
|
|
|
28043
28063
|
seen: /* @__PURE__ */ new WeakMap(),
|
|
28044
28064
|
restored: []
|
|
28045
28065
|
};
|
|
28046
|
-
const
|
|
28066
|
+
const copyArguments = (values) => deepCopyFromSandbox([...values], {
|
|
28047
28067
|
compilation,
|
|
28048
28068
|
unwrapHostObject: options.realm === void 0 ? void 0 : (object) => exportHostCapability(object, options.realm.owner),
|
|
28049
28069
|
wrapClosure: (closure) => options.realm?.wrapCallback(closure) ?? wrapSandboxClosureForHost(
|
|
@@ -28054,13 +28074,21 @@ function wrapCallerInjectedFunction(name, value, options, state) {
|
|
|
28054
28074
|
callbacks
|
|
28055
28075
|
)
|
|
28056
28076
|
});
|
|
28077
|
+
const captured = options.realm?.captureArguments(callable, args, copyArguments);
|
|
28078
|
+
const hostArgs = captured?.args ?? copyArguments(args);
|
|
28057
28079
|
const hostCalls = options.hostCalls;
|
|
28058
28080
|
const operation = options.operation ?? bindingName;
|
|
28059
28081
|
const moduleId = options.moduleId ?? "<bindings>";
|
|
28060
28082
|
const policy = readHostOperationPolicy(value) ?? readRegisteredPendingHostCallPolicy(moduleId, operation) ?? "re-issue";
|
|
28061
28083
|
if (hostCalls === void 0) {
|
|
28062
28084
|
if (options.realm !== void 0) {
|
|
28063
|
-
|
|
28085
|
+
let result;
|
|
28086
|
+
try {
|
|
28087
|
+
result = options.realm.invoke(callable, () => Reflect.apply(callable, void 0, hostArgs));
|
|
28088
|
+
} catch (error) {
|
|
28089
|
+
captured.rollback();
|
|
28090
|
+
throw error;
|
|
28091
|
+
}
|
|
28064
28092
|
if (options.realm.awaitResult(callable)) {
|
|
28065
28093
|
return Promise.resolve(result).then((value2) => copyHostResultToSandbox(value2, stackFrames, options));
|
|
28066
28094
|
}
|
|
@@ -30095,6 +30123,7 @@ var RealmState = class {
|
|
|
30095
30123
|
extensions: 32,
|
|
30096
30124
|
hostObjects: 1024,
|
|
30097
30125
|
callbacks: 1024,
|
|
30126
|
+
guestReferences: 1024,
|
|
30098
30127
|
cleanups: 1024,
|
|
30099
30128
|
nestedEvaluations: 16
|
|
30100
30129
|
};
|
|
@@ -30131,6 +30160,7 @@ var RealmState = class {
|
|
|
30131
30160
|
owner: this,
|
|
30132
30161
|
assertActive: this.assertOpen,
|
|
30133
30162
|
wrapCallback: this.wrapCallback,
|
|
30163
|
+
captureArguments: this.captureArguments,
|
|
30134
30164
|
invoke: this.invokeHost,
|
|
30135
30165
|
awaitResult: (operation) => this.nestedOperations.has(operation)
|
|
30136
30166
|
};
|
|
@@ -30170,7 +30200,7 @@ var RealmState = class {
|
|
|
30170
30200
|
}
|
|
30171
30201
|
options.signal?.addEventListener("abort", this.abort, { once: true });
|
|
30172
30202
|
if (options.signal?.aborted) this.abort();
|
|
30173
|
-
this.budget.setRetainedValues(this, this.
|
|
30203
|
+
this.budget.setRetainedValues(this, this.retainedRoots);
|
|
30174
30204
|
this.tracker.onFatalRejection((error) => this.poison(error));
|
|
30175
30205
|
} catch (error) {
|
|
30176
30206
|
this.compilation.dispose();
|
|
@@ -30194,6 +30224,8 @@ var RealmState = class {
|
|
|
30194
30224
|
pendingCallbacks = /* @__PURE__ */ new Set();
|
|
30195
30225
|
callbackCache = /* @__PURE__ */ new WeakMap();
|
|
30196
30226
|
hostObjects = /* @__PURE__ */ new Set();
|
|
30227
|
+
guestReferences = /* @__PURE__ */ new Map();
|
|
30228
|
+
retainedOperations = /* @__PURE__ */ new WeakMap();
|
|
30197
30229
|
nestedOperations = /* @__PURE__ */ new WeakMap();
|
|
30198
30230
|
convertedModules = /* @__PURE__ */ new Map();
|
|
30199
30231
|
nativeConversions = { seen: /* @__PURE__ */ new WeakMap() };
|
|
@@ -30213,10 +30245,56 @@ var RealmState = class {
|
|
|
30213
30245
|
signal: this.controller.signal,
|
|
30214
30246
|
realm: this.bridge
|
|
30215
30247
|
});
|
|
30216
|
-
|
|
30248
|
+
retainedRoots = () => [
|
|
30217
30249
|
...this.callbacks.values(),
|
|
30218
|
-
...Array.from(this.pendingCallbacks, (pending) => pending.closure)
|
|
30250
|
+
...Array.from(this.pendingCallbacks, (pending) => pending.closure),
|
|
30251
|
+
...this.guestReferences.values()
|
|
30219
30252
|
];
|
|
30253
|
+
captureArguments = (operation, args, copy) => {
|
|
30254
|
+
const from = this.retainedOperations.get(operation)?.from ?? args.length;
|
|
30255
|
+
const values = copy(args.slice(0, from));
|
|
30256
|
+
const captured = [];
|
|
30257
|
+
const rollback = () => {
|
|
30258
|
+
for (const reference of captured) {
|
|
30259
|
+
revokeGuestReference(reference, this);
|
|
30260
|
+
this.guestReferences.delete(reference);
|
|
30261
|
+
}
|
|
30262
|
+
};
|
|
30263
|
+
try {
|
|
30264
|
+
for (const value of args.slice(from)) {
|
|
30265
|
+
this.checkCollection(
|
|
30266
|
+
this.guestReferences.size + 1,
|
|
30267
|
+
this.limits.guestReferences,
|
|
30268
|
+
"guest reference"
|
|
30269
|
+
);
|
|
30270
|
+
const root = [value];
|
|
30271
|
+
const reference = createGuestReference(root, this, this.assertOpen);
|
|
30272
|
+
this.guestReferences.set(reference, root);
|
|
30273
|
+
captured.push(reference);
|
|
30274
|
+
values.push(reference);
|
|
30275
|
+
}
|
|
30276
|
+
if (captured.length > 0)
|
|
30277
|
+
this.budget.reconcileDataUsage(
|
|
30278
|
+
measureSandboxData([...this.scope?.retainedValues() ?? [], ...this.retainedRoots()])
|
|
30279
|
+
);
|
|
30280
|
+
return { args: values, rollback };
|
|
30281
|
+
} catch (error) {
|
|
30282
|
+
rollback();
|
|
30283
|
+
if (error instanceof SandboxError) this.poison(error);
|
|
30284
|
+
throw error;
|
|
30285
|
+
}
|
|
30286
|
+
};
|
|
30287
|
+
releaseGuestReference = (reference) => {
|
|
30288
|
+
readGuestReference(reference, this);
|
|
30289
|
+
revokeGuestReference(reference, this);
|
|
30290
|
+
this.guestReferences.delete(reference);
|
|
30291
|
+
if (this.active === void 0)
|
|
30292
|
+
reconcileCompiledValues(
|
|
30293
|
+
this.budget,
|
|
30294
|
+
[...this.scope?.retainedValues() ?? [], ...this.retainedRoots()],
|
|
30295
|
+
this.compilation
|
|
30296
|
+
);
|
|
30297
|
+
};
|
|
30220
30298
|
assertOpen = () => {
|
|
30221
30299
|
if (this.failure !== void 0) throw this.failure.reason;
|
|
30222
30300
|
if (this.closed) throw new Error("SafeJS realm is closed; capabilities are revoked.");
|
|
@@ -30384,7 +30462,7 @@ var RealmState = class {
|
|
|
30384
30462
|
});
|
|
30385
30463
|
try {
|
|
30386
30464
|
this.budget.reconcileDataUsage(
|
|
30387
|
-
measureSandboxData([...this.scope?.retainedValues() ?? [], ...this.
|
|
30465
|
+
measureSandboxData([...this.scope?.retainedValues() ?? [], ...this.retainedRoots()])
|
|
30388
30466
|
);
|
|
30389
30467
|
} catch (error) {
|
|
30390
30468
|
this.poison(error);
|
|
@@ -30399,7 +30477,7 @@ var RealmState = class {
|
|
|
30399
30477
|
if (this.active === void 0)
|
|
30400
30478
|
reconcileCompiledValues(
|
|
30401
30479
|
this.budget,
|
|
30402
|
-
[...this.scope?.retainedValues() ?? [], ...this.
|
|
30480
|
+
[...this.scope?.retainedValues() ?? [], ...this.retainedRoots()],
|
|
30403
30481
|
this.compilation
|
|
30404
30482
|
);
|
|
30405
30483
|
};
|
|
@@ -30458,7 +30536,7 @@ var RealmState = class {
|
|
|
30458
30536
|
if (!this.closed && this.active === void 0)
|
|
30459
30537
|
reconcileCompiledValues(
|
|
30460
30538
|
this.budget,
|
|
30461
|
-
[...this.scope?.retainedValues() ?? [], ...this.
|
|
30539
|
+
[...this.scope?.retainedValues() ?? [], ...this.retainedRoots()],
|
|
30462
30540
|
this.compilation
|
|
30463
30541
|
);
|
|
30464
30542
|
}
|
|
@@ -30475,6 +30553,23 @@ var RealmState = class {
|
|
|
30475
30553
|
createHostObject: this.createHostObject,
|
|
30476
30554
|
invokeCallback: this.invokeCallback,
|
|
30477
30555
|
releaseCallback: this.releaseCallback,
|
|
30556
|
+
releaseGuestReference: this.releaseGuestReference,
|
|
30557
|
+
retainGuestArguments: (operation, from) => {
|
|
30558
|
+
this.assertOpen();
|
|
30559
|
+
if (!extension.manifest.capabilities?.includes("guest:retain"))
|
|
30560
|
+
throw new TypeError("Retaining arguments requires the guest:retain grant.");
|
|
30561
|
+
if (typeof operation !== "function")
|
|
30562
|
+
throw new TypeError("Retained operation must be a function.");
|
|
30563
|
+
if (!Number.isSafeInteger(from) || from < 0)
|
|
30564
|
+
throw new TypeError("Argument index must be a non-negative safe integer.");
|
|
30565
|
+
if (this.scope !== void 0)
|
|
30566
|
+
throw new TypeError("Retained operations must be registered during setup.");
|
|
30567
|
+
const previous = this.retainedOperations.get(operation);
|
|
30568
|
+
if (previous !== void 0 && (previous.extension !== extension || previous.from !== from))
|
|
30569
|
+
throw new TypeError("Conflicting retained operation declaration.");
|
|
30570
|
+
this.retainedOperations.set(operation, { extension, from });
|
|
30571
|
+
return operation;
|
|
30572
|
+
},
|
|
30478
30573
|
nestedOperation: (operation) => {
|
|
30479
30574
|
this.assertOpen();
|
|
30480
30575
|
if (!extension.manifest.capabilities?.includes("source:nested"))
|
|
@@ -30637,7 +30732,7 @@ var RealmState = class {
|
|
|
30637
30732
|
if (!this.closed)
|
|
30638
30733
|
reconcileCompiledValues(
|
|
30639
30734
|
this.budget,
|
|
30640
|
-
[...this.scope?.retainedValues() ?? [], ...this.
|
|
30735
|
+
[...this.scope?.retainedValues() ?? [], ...this.retainedRoots()],
|
|
30641
30736
|
this.compilation
|
|
30642
30737
|
);
|
|
30643
30738
|
return result;
|
|
@@ -30671,6 +30766,8 @@ var RealmState = class {
|
|
|
30671
30766
|
this.callbacks.clear();
|
|
30672
30767
|
for (const object of this.hostObjects) revokeHostObject(object, this);
|
|
30673
30768
|
this.hostObjects.clear();
|
|
30769
|
+
for (const reference of this.guestReferences.keys()) revokeGuestReference(reference, this);
|
|
30770
|
+
this.guestReferences.clear();
|
|
30674
30771
|
this.budget.setRetainedValues(this, void 0);
|
|
30675
30772
|
this.disposal = (async () => {
|
|
30676
30773
|
const errors = [];
|
|
@@ -30726,6 +30823,7 @@ function createRealm(options = {}) {
|
|
|
30726
30823
|
evaluate: state.evaluate,
|
|
30727
30824
|
invokeCallback: state.invokeCallback,
|
|
30728
30825
|
releaseCallback: state.releaseCallback,
|
|
30826
|
+
releaseGuestReference: state.releaseGuestReference,
|
|
30729
30827
|
close: state.close
|
|
30730
30828
|
});
|
|
30731
30829
|
}
|
|
@@ -31920,4 +32018,4 @@ export {
|
|
|
31920
32018
|
FileSnapshotBackend,
|
|
31921
32019
|
run
|
|
31922
32020
|
};
|
|
31923
|
-
//# sourceMappingURL=chunk-
|
|
32021
|
+
//# sourceMappingURL=chunk-MXUOEOBE.js.map
|