poe-code 5.0.3 → 6.0.0
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/dist/index.js +1859 -1313
- package/dist/index.js.map +4 -4
- package/dist/metafile.json +1 -1
- package/dist/providers/poe-agent.js +14 -2
- package/dist/providers/poe-agent.js.map +4 -4
- package/package.json +1 -1
- package/packages/safejs/dist/interp/async.js +31 -9
- package/packages/safejs/dist/interp/cancel.d.ts +7 -2
- package/packages/safejs/dist/interp/cancel.js +128 -92
- package/packages/safejs/dist/interp/exceptions.js +7 -3
- package/packages/safejs/dist/interp/globals/collections.js +2 -0
- package/packages/safejs/dist/interp/globals/console-json.js +4 -0
- package/packages/safejs/dist/interp/globals/error.js +1 -5
- package/packages/safejs/dist/interp/globals/math.js +2 -4
- package/packages/safejs/dist/interp/globals/misc.js +5 -0
- package/packages/safejs/dist/interp/globals/object-array.js +25 -0
- package/packages/safejs/dist/interp/globals/regex.js +1 -1
- package/packages/safejs/dist/interp/host-bridge.js +8 -5
- package/packages/safejs/dist/interp/interpreter.js +26 -32
- package/packages/safejs/dist/interp/iteration.d.ts +3 -3
- package/packages/safejs/dist/interp/iteration.js +20 -9
- package/packages/safejs/dist/interp/methods/array.js +1 -0
- package/packages/safejs/dist/interp/methods/function.d.ts +1 -1
- package/packages/safejs/dist/interp/methods/function.js +17 -1
- package/packages/safejs/dist/interp/methods/generator.js +1 -0
- package/packages/safejs/dist/interp/methods/map.js +1 -0
- package/packages/safejs/dist/interp/methods/number.js +1 -0
- package/packages/safejs/dist/interp/methods/regex.js +1 -0
- package/packages/safejs/dist/interp/methods/set.js +1 -0
- package/packages/safejs/dist/interp/methods/string.js +1 -0
- package/packages/safejs/dist/interp/promise-tracker.d.ts +12 -2
- package/packages/safejs/dist/interp/promise-tracker.js +65 -7
- package/packages/safejs/dist/interp/promise.d.ts +7 -5
- package/packages/safejs/dist/interp/promise.js +531 -148
- package/packages/safejs/dist/interp/values.d.ts +6 -0
- package/packages/safejs/dist/interp/values.js +25 -5
- package/packages/safejs/dist/lint/rules/AS001.js +0 -5
- package/packages/safejs/dist/snapshot/dump-format.d.ts +1 -1
- package/packages/safejs/dist/snapshot/dump-format.js +1 -1
- package/packages/safejs/dist/snapshot/replay-data.js +3 -0
- package/packages/toolcraft-design/dist/explorer/detail-content.d.ts +6 -0
- package/packages/toolcraft-design/dist/explorer/detail-content.js +32 -0
- package/packages/toolcraft-design/dist/explorer/layout.d.ts +1 -0
- package/packages/toolcraft-design/dist/explorer/layout.js +8 -0
- package/packages/toolcraft-design/dist/explorer/reducer.js +8 -19
- package/packages/toolcraft-design/dist/explorer/render/detail.d.ts +1 -1
- package/packages/toolcraft-design/dist/explorer/render/detail.js +8 -36
- package/packages/toolcraft-design/dist/explorer/render/pane.d.ts +1 -1
- package/packages/toolcraft-design/dist/explorer/render/pane.js +1 -8
|
@@ -46,8 +46,11 @@ function wrapCallerInjectedFunction(name, value, options, state) {
|
|
|
46
46
|
const callable = value;
|
|
47
47
|
return createSandboxClosure({
|
|
48
48
|
...(isAsyncFunction(callable) ? { async: true } : {}),
|
|
49
|
+
cancellationSignal: options.signal,
|
|
49
50
|
call: (args, context) => {
|
|
50
51
|
try {
|
|
52
|
+
if (options.signal?.aborted)
|
|
53
|
+
throw readAbortReason(options.signal);
|
|
51
54
|
const stackFrames = context?.stack ?? [];
|
|
52
55
|
const callbacks = {
|
|
53
56
|
journal: options.hostCalls,
|
|
@@ -183,12 +186,12 @@ function executeHostCall(record, restored, invoke, stackFrames, options, span, c
|
|
|
183
186
|
}
|
|
184
187
|
})();
|
|
185
188
|
void promise.catch(() => undefined);
|
|
186
|
-
return createSandboxPromise(promise);
|
|
189
|
+
return createSandboxPromise(promise, { hostCall: record, hostCallJournal: hostCalls });
|
|
187
190
|
}
|
|
188
191
|
if (replayed !== undefined) {
|
|
189
192
|
restoreReplayedHostState(record, replayed, onReplay);
|
|
190
193
|
if (record.asynchronous)
|
|
191
|
-
return createReplayedHostCallResult(replayed);
|
|
194
|
+
return createReplayedHostCallResult(replayed, record, hostCalls);
|
|
192
195
|
if (replayed.status === "rejected")
|
|
193
196
|
throw replayed.reason;
|
|
194
197
|
return replayed.value;
|
|
@@ -197,7 +200,7 @@ function executeHostCall(record, restored, invoke, stackFrames, options, span, c
|
|
|
197
200
|
record.policy === "read-side-effect" &&
|
|
198
201
|
record.lifecycle === "consumed" &&
|
|
199
202
|
record.outcome !== undefined) {
|
|
200
|
-
return createReplayedHostCallResult(record.outcome);
|
|
203
|
+
return createReplayedHostCallResult(record.outcome, record, hostCalls);
|
|
201
204
|
}
|
|
202
205
|
if (restored &&
|
|
203
206
|
record.policy === "read-side-effect" &&
|
|
@@ -285,12 +288,12 @@ function restoreReplayedHostState(record, outcome, onReplay) {
|
|
|
285
288
|
throw error;
|
|
286
289
|
}
|
|
287
290
|
}
|
|
288
|
-
function createReplayedHostCallResult(outcome) {
|
|
291
|
+
function createReplayedHostCallResult(outcome, record, hostCalls) {
|
|
289
292
|
const promise = outcome.status === "fulfilled"
|
|
290
293
|
? Promise.resolve(outcome.value)
|
|
291
294
|
: Promise.reject(outcome.reason);
|
|
292
295
|
promise.catch(() => undefined);
|
|
293
|
-
return createSandboxPromise(promise);
|
|
296
|
+
return createSandboxPromise(promise, { hostCall: record, hostCallJournal: hostCalls });
|
|
294
297
|
}
|
|
295
298
|
function createHostCallPromise(record, outcomePromise, hostCalls) {
|
|
296
299
|
const promise = outcomePromise.then((outcome) => {
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import { promiseReplayContext } from "./promise-replay.js";
|
|
2
|
+
import { assertPromiseExecutionAllowed } from "./promise-tracker.js";
|
|
2
3
|
import { SandboxJobQueue, suspendJob } from "./jobs.js";
|
|
4
|
+
import { withCancellationSignal } from "./cancel.js";
|
|
3
5
|
import { attachErrorSpan, formatErrorStack, readErrorSpan, replaceErrorStack } from "../error/shape.js";
|
|
4
6
|
import { evaluateArrowFunctionExpression, evaluateFunctionExpression, evaluateAwaitExpression, emitResumeBreakpoint, createInterpretedClosure, normalizeClosureResult } from "./async.js";
|
|
5
7
|
import { HostCallResumabilityError } from "./host-call.js";
|
|
@@ -9,7 +11,7 @@ import { callArrayMethod, getArrayMember, isArrayMethodName } from "./methods/ar
|
|
|
9
11
|
import { getFunctionMember } from "./methods/function.js";
|
|
10
12
|
import { callMapMethod, getMapMember, isMapMethodName } from "./methods/map.js";
|
|
11
13
|
import { callNumberMethod, getNumberMember, isNumberMethodName } from "./methods/number.js";
|
|
12
|
-
import { getPromiseMember } from "./promise.js";
|
|
14
|
+
import { getPromiseMember, isSandboxPromiseConstructor } from "./promise.js";
|
|
13
15
|
import { getSandboxIterator } from "./iteration.js";
|
|
14
16
|
import { assertCollectionMutable } from "./running-state.js";
|
|
15
17
|
import { getGeneratorMember } from "./methods/generator.js";
|
|
@@ -108,7 +110,7 @@ export async function interpret(node, options = {}) {
|
|
|
108
110
|
generatorYield: options.generatorYield,
|
|
109
111
|
resumeTarget: { nodeId: options.snapshot?.resumeNodeId }
|
|
110
112
|
};
|
|
111
|
-
const evaluation = await jobs.run(() => evaluateNode(node, context));
|
|
113
|
+
const evaluation = await withCancellationSignal(options.signal, () => jobs.run(() => evaluateNode(node, context)));
|
|
112
114
|
await jobs.drain();
|
|
113
115
|
const snapshot = scope.snapshot();
|
|
114
116
|
reconcileDataBudget(budget, stats, scope, "hasValue" in evaluation && evaluation.hasValue ? evaluation.value : undefined);
|
|
@@ -154,6 +156,7 @@ async function evaluateNode(node, context) {
|
|
|
154
156
|
const replayWait = promiseReplayContext.getStore()?.beforeNode(node.nodeId);
|
|
155
157
|
if (replayWait !== undefined)
|
|
156
158
|
await suspendJob(replayWait);
|
|
159
|
+
assertPromiseExecutionAllowed();
|
|
157
160
|
context.budget.visitNode();
|
|
158
161
|
context.stats.nodeVisits += 1;
|
|
159
162
|
if (node.nodeId !== undefined && context.resumeTarget?.nodeId === node.nodeId) {
|
|
@@ -1597,7 +1600,7 @@ async function evaluateMemberExpression(node, context) {
|
|
|
1597
1600
|
return {
|
|
1598
1601
|
kind: "normal",
|
|
1599
1602
|
hasValue: true,
|
|
1600
|
-
value: getPromiseMember(member.
|
|
1603
|
+
value: getPromiseMember(member.property, context.budget)
|
|
1601
1604
|
};
|
|
1602
1605
|
}
|
|
1603
1606
|
if (isSandboxRegex(member.object)) {
|
|
@@ -1634,35 +1637,19 @@ async function evaluateNewExpression(node, context) {
|
|
|
1634
1637
|
return callee;
|
|
1635
1638
|
}
|
|
1636
1639
|
const name = getConstructorName(node.callee);
|
|
1637
|
-
if (!isSandboxClosure(callee.value) || callee.value.construct === undefined) {
|
|
1638
|
-
throw new TypeError(`${name} is not a constructor.`);
|
|
1639
|
-
}
|
|
1640
1640
|
const args = await evaluateCallArguments(node.arguments, context);
|
|
1641
1641
|
if (!args.ok) {
|
|
1642
1642
|
return args.result;
|
|
1643
1643
|
}
|
|
1644
|
-
|
|
1645
|
-
|
|
1646
|
-
try {
|
|
1647
|
-
return {
|
|
1648
|
-
kind: "normal",
|
|
1649
|
-
hasValue: true,
|
|
1650
|
-
value: await wrapHostResult(callee.value.construct(args.value, {
|
|
1651
|
-
stack,
|
|
1652
|
-
thisValue: undefined,
|
|
1653
|
-
span: node.span
|
|
1654
|
-
}), stack)
|
|
1655
|
-
};
|
|
1656
|
-
}
|
|
1657
|
-
catch (error) {
|
|
1658
|
-
if (isFatalSandboxError(error)) {
|
|
1659
|
-
throw error;
|
|
1660
|
-
}
|
|
1661
|
-
throw captureException(error, stack);
|
|
1662
|
-
}
|
|
1663
|
-
finally {
|
|
1664
|
-
leaveCall();
|
|
1644
|
+
if (!isSandboxClosure(callee.value) || callee.value.construct === undefined) {
|
|
1645
|
+
throw new TypeError(`${name} is not a constructor.`);
|
|
1665
1646
|
}
|
|
1647
|
+
const stack = [...context.callStack, formatStackFrame(node, callee.value.name ?? name)];
|
|
1648
|
+
return {
|
|
1649
|
+
kind: "normal",
|
|
1650
|
+
hasValue: true,
|
|
1651
|
+
value: await invokeSandboxClosure(callee.value, args.value, context, stack, node.span, undefined, true)
|
|
1652
|
+
};
|
|
1666
1653
|
}
|
|
1667
1654
|
function getConstructorName(callee) {
|
|
1668
1655
|
if (callee.type === "Identifier") {
|
|
@@ -1839,7 +1826,7 @@ async function evaluateMemberCallExpression(node, context) {
|
|
|
1839
1826
|
return evaluateResolvedCallExpression(node, memberValue, context, member.object);
|
|
1840
1827
|
}
|
|
1841
1828
|
if (isSandboxPromise(member.object)) {
|
|
1842
|
-
return evaluateResolvedCallExpression(node, getPromiseMember(member.
|
|
1829
|
+
return evaluateResolvedCallExpression(node, getPromiseMember(member.property, context.budget), context, member.object);
|
|
1843
1830
|
}
|
|
1844
1831
|
if (isSandboxRegex(member.object) && isRegexMethodName(member.property)) {
|
|
1845
1832
|
return evaluateResolvedCallExpression(node, getRegexMember(member.object, member.property), context, member.object);
|
|
@@ -2021,6 +2008,10 @@ function applyBinaryOperator(node, left, right, context) {
|
|
|
2021
2008
|
case ">>>":
|
|
2022
2009
|
return toNumber(left) >>> toNumber(right);
|
|
2023
2010
|
case "instanceof":
|
|
2011
|
+
while (isSandboxClosure(right) && right.boundTarget !== undefined)
|
|
2012
|
+
right = right.boundTarget;
|
|
2013
|
+
if (isSandboxPromiseConstructor(right))
|
|
2014
|
+
return isSandboxPromise(left);
|
|
2024
2015
|
if (isSandboxMapConstructor(right) && isSandboxMap(left)) {
|
|
2025
2016
|
return true;
|
|
2026
2017
|
}
|
|
@@ -2322,13 +2313,16 @@ function createSetMethodOptions(context) {
|
|
|
2322
2313
|
}
|
|
2323
2314
|
function createFunctionMethodOptions(context) {
|
|
2324
2315
|
return {
|
|
2325
|
-
callClosure: (closure, args, stack, thisValue) => invokeSandboxClosure(closure, args, context, stack, undefined, thisValue)
|
|
2316
|
+
callClosure: (closure, args, stack, thisValue, construct) => invokeSandboxClosure(closure, args, context, stack, undefined, thisValue, construct)
|
|
2326
2317
|
};
|
|
2327
2318
|
}
|
|
2328
|
-
async function invokeSandboxClosure(callee, args, context, stack, span, thisValue = undefined) {
|
|
2319
|
+
async function invokeSandboxClosure(callee, args, context, stack, span, thisValue = undefined, construct = false) {
|
|
2329
2320
|
const leaveCall = context.budget.enterCall();
|
|
2330
2321
|
try {
|
|
2331
|
-
const
|
|
2322
|
+
const invoke = construct ? callee.construct : callee.call;
|
|
2323
|
+
if (invoke === undefined)
|
|
2324
|
+
throw new TypeError("Value is not a constructor.");
|
|
2325
|
+
const result = Reflect.apply(invoke, undefined, [
|
|
2332
2326
|
args,
|
|
2333
2327
|
{
|
|
2334
2328
|
stack,
|
|
@@ -2339,7 +2333,7 @@ async function invokeSandboxClosure(callee, args, context, stack, span, thisValu
|
|
|
2339
2333
|
if (isSandboxPromise(result) && result.synchronousPrefix !== undefined) {
|
|
2340
2334
|
await result.synchronousPrefix;
|
|
2341
2335
|
}
|
|
2342
|
-
return callee.async === true
|
|
2336
|
+
return !construct && callee.async === true
|
|
2343
2337
|
? normalizeClosureResult(wrapHostResult(result, stack), context.budget)
|
|
2344
2338
|
: await wrapHostResult(result, stack);
|
|
2345
2339
|
}
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { type SandboxValue } from "./values.js";
|
|
2
2
|
export type SandboxIterator = {
|
|
3
3
|
readonly generator?: true;
|
|
4
|
-
next(value?: SandboxValue): Promise<IteratorResult<SandboxValue>>;
|
|
5
|
-
return?(value?: SandboxValue): Promise<IteratorResult<SandboxValue>>;
|
|
6
|
-
throw?(error?: SandboxValue): Promise<IteratorResult<SandboxValue>>;
|
|
4
|
+
next(value?: SandboxValue): IteratorResult<SandboxValue> | Promise<IteratorResult<SandboxValue>>;
|
|
5
|
+
return?(value?: SandboxValue): IteratorResult<SandboxValue> | Promise<IteratorResult<SandboxValue>>;
|
|
6
|
+
throw?(error?: SandboxValue): IteratorResult<SandboxValue> | Promise<IteratorResult<SandboxValue>>;
|
|
7
7
|
};
|
|
8
8
|
export declare function getSandboxIterator(value: SandboxValue): SandboxIterator | undefined;
|
|
@@ -47,22 +47,33 @@ function generatorIterator(generator) {
|
|
|
47
47
|
};
|
|
48
48
|
}
|
|
49
49
|
function syncIterator(iterator) {
|
|
50
|
-
const
|
|
50
|
+
const next = iterator.next;
|
|
51
|
+
const invoke = (method, args) => {
|
|
51
52
|
const leaveRunning = enterRunningState(iterator);
|
|
52
53
|
try {
|
|
53
|
-
return
|
|
54
|
+
return Reflect.apply(method, iterator, args);
|
|
54
55
|
}
|
|
55
56
|
finally {
|
|
56
57
|
leaveRunning();
|
|
57
58
|
}
|
|
58
59
|
};
|
|
59
60
|
return {
|
|
60
|
-
next: (
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
61
|
+
next: (...args) => invoke(next, args),
|
|
62
|
+
get return() {
|
|
63
|
+
const method = iterator.return;
|
|
64
|
+
if (method === undefined || method === null)
|
|
65
|
+
return undefined;
|
|
66
|
+
if (typeof method !== "function")
|
|
67
|
+
throw new TypeError("Iterator return must be callable.");
|
|
68
|
+
return (...args) => invoke(method, args);
|
|
69
|
+
},
|
|
70
|
+
get throw() {
|
|
71
|
+
const method = iterator.throw;
|
|
72
|
+
if (method === undefined || method === null)
|
|
73
|
+
return undefined;
|
|
74
|
+
if (typeof method !== "function")
|
|
75
|
+
throw new TypeError("Iterator throw must be callable.");
|
|
76
|
+
return (...args) => invoke(method, args);
|
|
77
|
+
}
|
|
67
78
|
};
|
|
68
79
|
}
|
|
@@ -45,6 +45,7 @@ export function getArrayMember(value, property, options) {
|
|
|
45
45
|
}
|
|
46
46
|
if (isArrayMethodName(property)) {
|
|
47
47
|
return createSandboxClosure({
|
|
48
|
+
sandbox: true,
|
|
48
49
|
name: `Array#${property}`,
|
|
49
50
|
call: (args, context) => callArrayMethod(value, property, args, options, context?.stack ?? [])
|
|
50
51
|
});
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { type SandboxClosure, type SandboxValue } from "../values.js";
|
|
2
2
|
export type FunctionMethodOptions = {
|
|
3
|
-
callClosure: (closure: SandboxClosure, args: readonly SandboxValue[], stack: readonly string[], thisValue: SandboxValue) => Promise<SandboxValue> | SandboxValue;
|
|
3
|
+
callClosure: (closure: SandboxClosure, args: readonly SandboxValue[], stack: readonly string[], thisValue: SandboxValue, construct?: boolean) => Promise<SandboxValue> | SandboxValue;
|
|
4
4
|
};
|
|
5
5
|
export declare function getFunctionMember(target: SandboxClosure, property: string | number, options: FunctionMethodOptions): SandboxValue | undefined;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { createSandboxClosure } from "../values.js";
|
|
2
|
-
const functionMethodNames = new Set(["apply", "call"]);
|
|
2
|
+
const functionMethodNames = new Set(["apply", "bind", "call"]);
|
|
3
3
|
export function getFunctionMember(target, property, options) {
|
|
4
4
|
const propertyValue = target.properties?.[String(property)];
|
|
5
5
|
if (propertyValue !== undefined || Object.hasOwn(target.properties ?? {}, String(property))) {
|
|
@@ -9,6 +9,7 @@ export function getFunctionMember(target, property, options) {
|
|
|
9
9
|
return undefined;
|
|
10
10
|
}
|
|
11
11
|
return createSandboxClosure({
|
|
12
|
+
sandbox: true,
|
|
12
13
|
name: `Function#${property}`,
|
|
13
14
|
call: (args, context) => callFunctionMethod(target, property, args, options, context?.stack ?? [])
|
|
14
15
|
});
|
|
@@ -18,6 +19,21 @@ function isFunctionMethodName(property) {
|
|
|
18
19
|
}
|
|
19
20
|
function callFunctionMethod(target, methodName, args, options, stack) {
|
|
20
21
|
const thisValue = args[0];
|
|
22
|
+
if (methodName === "bind") {
|
|
23
|
+
const boundArgs = args.slice(1);
|
|
24
|
+
return createSandboxClosure({
|
|
25
|
+
sandbox: true,
|
|
26
|
+
name: `bound ${target.name ?? ""}`,
|
|
27
|
+
boundTarget: target,
|
|
28
|
+
retainedValues: () => [target, thisValue, ...boundArgs],
|
|
29
|
+
call: (callArgs, context) => options.callClosure(target, [...boundArgs, ...callArgs], context?.stack ?? [], thisValue),
|
|
30
|
+
...(target.construct === undefined
|
|
31
|
+
? {}
|
|
32
|
+
: {
|
|
33
|
+
construct: (callArgs, context) => options.callClosure(target, [...boundArgs, ...callArgs], context?.stack ?? [], undefined, true)
|
|
34
|
+
})
|
|
35
|
+
});
|
|
36
|
+
}
|
|
21
37
|
if (methodName === "call") {
|
|
22
38
|
return options.callClosure(target, args.slice(1), stack, thisValue);
|
|
23
39
|
}
|
|
@@ -1,16 +1,26 @@
|
|
|
1
1
|
import type { ErrorSourceSpan } from "../error/shape.js";
|
|
2
|
+
import { SandboxError } from "./budget.js";
|
|
2
3
|
import type { SandboxPromise, SandboxValue } from "./values.js";
|
|
3
4
|
export declare class SandboxPromiseRejectionTracker {
|
|
4
5
|
private readonly records;
|
|
5
6
|
private readonly recordsByPromise;
|
|
7
|
+
private fatalRejection;
|
|
8
|
+
private readonly fatalWaiters;
|
|
9
|
+
get failure(): SandboxError | undefined;
|
|
10
|
+
onFatalRejection(interrupt: (error: SandboxError) => void): () => void;
|
|
11
|
+
interruptOnFatalRejection(pending: Promise<SandboxValue>): Promise<SandboxValue>;
|
|
6
12
|
observe(promise: SandboxPromise): void;
|
|
7
|
-
track(promise: SandboxPromise): void;
|
|
13
|
+
track(promise: SandboxPromise, outcome?: Promise<SandboxValue>): void;
|
|
8
14
|
findUnhandledRejection(): Promise<{
|
|
9
15
|
reason: SandboxValue | undefined;
|
|
10
16
|
span: ErrorSourceSpan | undefined;
|
|
11
17
|
} | undefined>;
|
|
12
18
|
}
|
|
19
|
+
export declare function assertPromiseExecutionAllowed(): void;
|
|
20
|
+
export declare function onFatalPromiseRejection(interrupt: (error: SandboxError) => void): (() => void) | undefined;
|
|
21
|
+
export declare function interruptOnFatalPromiseRejection(pending: Promise<SandboxValue>): Promise<SandboxValue>;
|
|
22
|
+
export declare function withFatalPromiseCleanup<TResult>(callback: () => TResult): TResult;
|
|
13
23
|
export declare function createSandboxPromiseRejectionTracker(): SandboxPromiseRejectionTracker;
|
|
14
24
|
export declare function observeSandboxPromise(promise: SandboxPromise): void;
|
|
15
|
-
export declare function trackSandboxPromise(promise: SandboxPromise): void;
|
|
25
|
+
export declare function trackSandboxPromise(promise: SandboxPromise, outcome?: Promise<SandboxValue>): void;
|
|
16
26
|
export declare function withSandboxPromiseRejectionTracker<TResult>(tracker: SandboxPromiseRejectionTracker, callback: () => TResult): TResult;
|
|
@@ -1,32 +1,71 @@
|
|
|
1
1
|
import { AsyncLocalStorage } from "node:async_hooks";
|
|
2
|
+
import { SandboxError } from "./budget.js";
|
|
2
3
|
export class SandboxPromiseRejectionTracker {
|
|
3
4
|
records = new Set();
|
|
4
5
|
recordsByPromise = new WeakMap();
|
|
6
|
+
fatalRejection;
|
|
7
|
+
fatalWaiters = new Set();
|
|
8
|
+
get failure() {
|
|
9
|
+
return this.fatalRejection;
|
|
10
|
+
}
|
|
11
|
+
onFatalRejection(interrupt) {
|
|
12
|
+
if (this.fatalRejection === undefined)
|
|
13
|
+
this.fatalWaiters.add(interrupt);
|
|
14
|
+
else
|
|
15
|
+
interrupt(this.fatalRejection);
|
|
16
|
+
return () => this.fatalWaiters.delete(interrupt);
|
|
17
|
+
}
|
|
18
|
+
interruptOnFatalRejection(pending) {
|
|
19
|
+
return new Promise((resolve, reject) => {
|
|
20
|
+
const detach = this.onFatalRejection(reject);
|
|
21
|
+
pending.then((value) => {
|
|
22
|
+
detach();
|
|
23
|
+
resolve(value);
|
|
24
|
+
}, (reason) => {
|
|
25
|
+
detach();
|
|
26
|
+
reject(reason);
|
|
27
|
+
});
|
|
28
|
+
});
|
|
29
|
+
}
|
|
5
30
|
observe(promise) {
|
|
6
31
|
const record = this.recordsByPromise.get(promise);
|
|
7
32
|
if (record !== undefined) {
|
|
8
33
|
record.observed = true;
|
|
9
34
|
}
|
|
10
35
|
}
|
|
11
|
-
track(promise) {
|
|
12
|
-
|
|
36
|
+
track(promise, outcome) {
|
|
37
|
+
const existing = this.recordsByPromise.get(promise);
|
|
38
|
+
if (existing !== undefined && outcome === undefined) {
|
|
13
39
|
return;
|
|
14
40
|
}
|
|
15
|
-
const record = {
|
|
41
|
+
const record = existing ?? {
|
|
16
42
|
observed: false,
|
|
17
43
|
promise,
|
|
18
44
|
rejected: false
|
|
19
45
|
};
|
|
20
46
|
this.records.add(record);
|
|
21
47
|
this.recordsByPromise.set(promise, record);
|
|
22
|
-
|
|
48
|
+
const pending = outcome ?? promise.promise;
|
|
49
|
+
pending.then(() => undefined, (reason) => {
|
|
50
|
+
if (this.fatalRejection === undefined &&
|
|
51
|
+
reason instanceof SandboxError &&
|
|
52
|
+
(reason.code === "budgetExceeded" || reason.code === "reentry")) {
|
|
53
|
+
this.fatalRejection = reason;
|
|
54
|
+
for (const interrupt of this.fatalWaiters)
|
|
55
|
+
interrupt(reason);
|
|
56
|
+
this.fatalWaiters.clear();
|
|
57
|
+
}
|
|
58
|
+
if (record.rejected)
|
|
59
|
+
return;
|
|
23
60
|
record.rejected = true;
|
|
24
61
|
record.rejectionReason = reason;
|
|
25
62
|
});
|
|
26
|
-
|
|
63
|
+
pending.catch(() => undefined);
|
|
27
64
|
}
|
|
28
65
|
async findUnhandledRejection() {
|
|
29
66
|
await flushPromiseJobs();
|
|
67
|
+
if (this.fatalRejection !== undefined)
|
|
68
|
+
throw this.fatalRejection;
|
|
30
69
|
for (const record of this.records) {
|
|
31
70
|
if (record.rejected && !record.observed) {
|
|
32
71
|
return {
|
|
@@ -39,14 +78,33 @@ export class SandboxPromiseRejectionTracker {
|
|
|
39
78
|
}
|
|
40
79
|
}
|
|
41
80
|
const activePromiseTracker = new AsyncLocalStorage();
|
|
81
|
+
const fatalRejectionCleanup = new AsyncLocalStorage();
|
|
82
|
+
export function assertPromiseExecutionAllowed() {
|
|
83
|
+
const failure = activePromiseTracker.getStore()?.failure;
|
|
84
|
+
if (failure !== undefined && fatalRejectionCleanup.getStore() !== true)
|
|
85
|
+
throw failure;
|
|
86
|
+
}
|
|
87
|
+
export function onFatalPromiseRejection(interrupt) {
|
|
88
|
+
if (fatalRejectionCleanup.getStore() === true)
|
|
89
|
+
return undefined;
|
|
90
|
+
return activePromiseTracker.getStore()?.onFatalRejection(interrupt);
|
|
91
|
+
}
|
|
92
|
+
export function interruptOnFatalPromiseRejection(pending) {
|
|
93
|
+
if (fatalRejectionCleanup.getStore() === true)
|
|
94
|
+
return pending;
|
|
95
|
+
return activePromiseTracker.getStore()?.interruptOnFatalRejection(pending) ?? pending;
|
|
96
|
+
}
|
|
97
|
+
export function withFatalPromiseCleanup(callback) {
|
|
98
|
+
return fatalRejectionCleanup.run(true, callback);
|
|
99
|
+
}
|
|
42
100
|
export function createSandboxPromiseRejectionTracker() {
|
|
43
101
|
return new SandboxPromiseRejectionTracker();
|
|
44
102
|
}
|
|
45
103
|
export function observeSandboxPromise(promise) {
|
|
46
104
|
activePromiseTracker.getStore()?.observe(promise);
|
|
47
105
|
}
|
|
48
|
-
export function trackSandboxPromise(promise) {
|
|
49
|
-
activePromiseTracker.getStore()?.track(promise);
|
|
106
|
+
export function trackSandboxPromise(promise, outcome) {
|
|
107
|
+
activePromiseTracker.getStore()?.track(promise, outcome);
|
|
50
108
|
}
|
|
51
109
|
export function withSandboxPromiseRejectionTracker(tracker, callback) {
|
|
52
110
|
return activePromiseTracker.run(tracker, callback);
|
|
@@ -1,14 +1,16 @@
|
|
|
1
|
-
import type
|
|
2
|
-
import { type SandboxClosure, type
|
|
1
|
+
import { type Budget } from "./budget.js";
|
|
2
|
+
import { type SandboxClosure, type SandboxPromise, type SandboxValue } from "./values.js";
|
|
3
3
|
export type PromiseGlobals = {
|
|
4
|
-
Promise:
|
|
4
|
+
Promise: SandboxClosure;
|
|
5
5
|
};
|
|
6
|
+
export declare function isSandboxPromiseConstructor(value: unknown): value is SandboxClosure;
|
|
6
7
|
export declare function createPromiseGlobals(options: {
|
|
7
8
|
budget: Budget;
|
|
8
9
|
}): PromiseGlobals;
|
|
9
|
-
export declare function getPromiseMember(
|
|
10
|
+
export declare function getPromiseMember(property: string | number, budget: Budget): SandboxValue;
|
|
10
11
|
export declare function resolveSandboxValue(value: SandboxValue | Promise<SandboxValue> | PromiseLike<SandboxValue>, options?: {
|
|
11
12
|
budget?: Budget;
|
|
13
|
+
self?: SandboxPromise;
|
|
12
14
|
}): Promise<SandboxValue>;
|
|
13
|
-
export declare function consumeSettledHostCall(value: SandboxPromise):
|
|
15
|
+
export declare function consumeSettledHostCall(value: SandboxPromise): undefined;
|
|
14
16
|
export declare function getThenable(value: SandboxValue): SandboxClosure | undefined;
|