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.
Files changed (49) hide show
  1. package/dist/index.js +1859 -1313
  2. package/dist/index.js.map +4 -4
  3. package/dist/metafile.json +1 -1
  4. package/dist/providers/poe-agent.js +14 -2
  5. package/dist/providers/poe-agent.js.map +4 -4
  6. package/package.json +1 -1
  7. package/packages/safejs/dist/interp/async.js +31 -9
  8. package/packages/safejs/dist/interp/cancel.d.ts +7 -2
  9. package/packages/safejs/dist/interp/cancel.js +128 -92
  10. package/packages/safejs/dist/interp/exceptions.js +7 -3
  11. package/packages/safejs/dist/interp/globals/collections.js +2 -0
  12. package/packages/safejs/dist/interp/globals/console-json.js +4 -0
  13. package/packages/safejs/dist/interp/globals/error.js +1 -5
  14. package/packages/safejs/dist/interp/globals/math.js +2 -4
  15. package/packages/safejs/dist/interp/globals/misc.js +5 -0
  16. package/packages/safejs/dist/interp/globals/object-array.js +25 -0
  17. package/packages/safejs/dist/interp/globals/regex.js +1 -1
  18. package/packages/safejs/dist/interp/host-bridge.js +8 -5
  19. package/packages/safejs/dist/interp/interpreter.js +26 -32
  20. package/packages/safejs/dist/interp/iteration.d.ts +3 -3
  21. package/packages/safejs/dist/interp/iteration.js +20 -9
  22. package/packages/safejs/dist/interp/methods/array.js +1 -0
  23. package/packages/safejs/dist/interp/methods/function.d.ts +1 -1
  24. package/packages/safejs/dist/interp/methods/function.js +17 -1
  25. package/packages/safejs/dist/interp/methods/generator.js +1 -0
  26. package/packages/safejs/dist/interp/methods/map.js +1 -0
  27. package/packages/safejs/dist/interp/methods/number.js +1 -0
  28. package/packages/safejs/dist/interp/methods/regex.js +1 -0
  29. package/packages/safejs/dist/interp/methods/set.js +1 -0
  30. package/packages/safejs/dist/interp/methods/string.js +1 -0
  31. package/packages/safejs/dist/interp/promise-tracker.d.ts +12 -2
  32. package/packages/safejs/dist/interp/promise-tracker.js +65 -7
  33. package/packages/safejs/dist/interp/promise.d.ts +7 -5
  34. package/packages/safejs/dist/interp/promise.js +531 -148
  35. package/packages/safejs/dist/interp/values.d.ts +6 -0
  36. package/packages/safejs/dist/interp/values.js +25 -5
  37. package/packages/safejs/dist/lint/rules/AS001.js +0 -5
  38. package/packages/safejs/dist/snapshot/dump-format.d.ts +1 -1
  39. package/packages/safejs/dist/snapshot/dump-format.js +1 -1
  40. package/packages/safejs/dist/snapshot/replay-data.js +3 -0
  41. package/packages/toolcraft-design/dist/explorer/detail-content.d.ts +6 -0
  42. package/packages/toolcraft-design/dist/explorer/detail-content.js +32 -0
  43. package/packages/toolcraft-design/dist/explorer/layout.d.ts +1 -0
  44. package/packages/toolcraft-design/dist/explorer/layout.js +8 -0
  45. package/packages/toolcraft-design/dist/explorer/reducer.js +8 -19
  46. package/packages/toolcraft-design/dist/explorer/render/detail.d.ts +1 -1
  47. package/packages/toolcraft-design/dist/explorer/render/detail.js +8 -36
  48. package/packages/toolcraft-design/dist/explorer/render/pane.d.ts +1 -1
  49. package/packages/toolcraft-design/dist/explorer/render/pane.js +1 -8
@@ -1,139 +1,423 @@
1
- import { createSubsetErrorValue } from "./exceptions.js";
1
+ import { SandboxError } from "./budget.js";
2
+ import { coerceThrownValue, createSubsetErrorValue } from "./exceptions.js";
3
+ import { getSandboxIterator } from "./iteration.js";
2
4
  import { runPromiseJob } from "./jobs.js";
3
5
  import { observeSandboxPromise } from "./promise-tracker.js";
4
6
  import { createSandboxClosure, createSandboxPromise, isSandboxClosure, isSandboxPromise } from "./values.js";
7
+ const promiseConstructors = new WeakSet();
8
+ const intrinsicPromiseConstructors = new WeakMap();
9
+ const promisePrototypes = new WeakMap();
10
+ export function isSandboxPromiseConstructor(value) {
11
+ return isSandboxClosure(value) && promiseConstructors.has(value);
12
+ }
5
13
  export function createPromiseGlobals(options) {
6
- return {
7
- Promise: {
14
+ promisePrototypes.delete(options.budget);
15
+ const prototype = getPromisePrototype(options.budget);
16
+ const construct = async ([executor], context) => {
17
+ if (!isSandboxClosure(executor))
18
+ throw new TypeError("Promise executor must be a function.");
19
+ let fulfill;
20
+ let reject;
21
+ const pending = createSandboxPromise(new Promise((resolve, rejectPromise) => {
22
+ fulfill = resolve;
23
+ reject = rejectPromise;
24
+ }), { span: context?.span });
25
+ let settled = false;
26
+ const settle = (state, value) => {
27
+ if (settled)
28
+ return;
29
+ settled = true;
30
+ try {
31
+ if (state === "rejected") {
32
+ reject(budgetSandboxValue(value, options.budget));
33
+ }
34
+ else if (value === pending) {
35
+ reject(createSubsetErrorValue("TypeError", "Promise cannot resolve to itself.", context?.stack ?? [], options.budget));
36
+ }
37
+ else {
38
+ fulfill(resolveSandboxValue(value, { budget: options.budget, self: pending }));
39
+ }
40
+ }
41
+ catch (error) {
42
+ reject(error);
43
+ }
44
+ };
45
+ try {
46
+ const result = executor.call([
47
+ createSandboxClosure({
48
+ sandbox: true,
49
+ name: "resolve",
50
+ retainedValues: () => [pending],
51
+ call: ([value]) => {
52
+ settle("fulfilled", value);
53
+ return undefined;
54
+ }
55
+ }),
56
+ createSandboxClosure({
57
+ sandbox: true,
58
+ name: "reject",
59
+ retainedValues: () => [pending],
60
+ call: ([reason]) => {
61
+ settle("rejected", reason);
62
+ return undefined;
63
+ }
64
+ })
65
+ ], { stack: context?.stack ?? [], thisValue: undefined });
66
+ if (executor.async !== true)
67
+ await result;
68
+ else if (isSandboxPromise(result) && result.synchronousPrefix !== undefined)
69
+ await result.synchronousPrefix;
70
+ else if (isPromiseLike(result))
71
+ createSandboxPromise(Promise.resolve(result));
72
+ }
73
+ catch (error) {
74
+ if (error instanceof SandboxError &&
75
+ (error.code === "budgetExceeded" || error.code === "reentry")) {
76
+ observeSandboxPromise(pending);
77
+ throw error;
78
+ }
79
+ settle("rejected", error);
80
+ }
81
+ return pending;
82
+ };
83
+ const promiseConstructor = createSandboxClosure({
84
+ sandbox: true,
85
+ name: "Promise",
86
+ construct,
87
+ call: () => {
88
+ throw new TypeError("Constructor Promise requires 'new'.");
89
+ },
90
+ properties: {
91
+ prototype,
8
92
  all: createSandboxClosure({
9
- async: true,
10
- call: ([values]) => createSandboxPromise(settleIterable(values, (entries) => Promise.all(entries), options.budget)),
93
+ sandbox: true,
94
+ call: ([values], context) => settleIterable(values, "all", options.budget, context === undefined ? promiseConstructor : context.thisValue, context),
11
95
  name: "all"
12
96
  }),
13
97
  race: createSandboxClosure({
14
- async: true,
15
- call: ([values]) => createSandboxPromise(settleIterable(values, (entries) => Promise.race(entries), options.budget)),
98
+ sandbox: true,
99
+ call: ([values], context) => settleIterable(values, "race", options.budget, context === undefined ? promiseConstructor : context.thisValue, context),
16
100
  name: "race"
17
101
  }),
18
102
  allSettled: createSandboxClosure({
19
- async: true,
20
- call: ([values]) => createSandboxPromise(settleIterable(values, (entries) => Promise.allSettled(entries).then((results) => budgetSandboxValue(results.map((result) => result.status === "fulfilled"
21
- ? {
22
- status: "fulfilled",
23
- value: result.value
24
- }
25
- : {
26
- reason: result.reason,
27
- status: "rejected"
28
- }), options.budget)), options.budget)),
103
+ sandbox: true,
104
+ call: ([values], context) => settleIterable(values, "allSettled", options.budget, context === undefined ? promiseConstructor : context.thisValue, context),
29
105
  name: "allSettled"
30
106
  }),
31
107
  any: createSandboxClosure({
32
- async: true,
33
- call: ([values]) => createSandboxPromise(settleIterable(values, (entries) => Promise.any(entries).catch((error) => {
34
- const aggregate = createSubsetErrorValue("AggregateError", "All promises were rejected", [], options.budget);
35
- aggregate.errors = Array.isArray(error.errors)
36
- ? error.errors
37
- : [];
38
- throw budgetSandboxValue(aggregate, options.budget);
39
- }), options.budget)),
108
+ sandbox: true,
109
+ call: ([values], context) => settleIterable(values, "any", options.budget, context === undefined ? promiseConstructor : context.thisValue, context),
40
110
  name: "any"
41
111
  }),
42
112
  resolve: createSandboxClosure({
43
- async: true,
44
- call: ([value]) => isSandboxPromise(value)
45
- ? value
46
- : createSandboxPromise(resolveSandboxValue(value, { budget: options.budget })),
113
+ sandbox: true,
114
+ call: ([value], context) => {
115
+ const constructor = context === undefined ? promiseConstructor : context.thisValue;
116
+ if (typeof constructor !== "object" || constructor === null) {
117
+ throw new TypeError("Promise.resolve requires an object receiver.");
118
+ }
119
+ if (isSandboxPromise(value) &&
120
+ getPromiseMember("constructor", options.budget) === constructor) {
121
+ return value;
122
+ }
123
+ return isSandboxPromiseConstructor(constructor)
124
+ ? createSandboxPromise(resolveSandboxValue(value, { budget: options.budget }))
125
+ : settleConstructedPromise(constructor, value, "fulfilled", options.budget, context);
126
+ },
47
127
  name: "resolve"
48
128
  }),
49
129
  reject: createSandboxClosure({
50
- async: true,
51
- call: ([reason], context) => createRejectedSandboxPromise(reason, options.budget, context?.span),
130
+ sandbox: true,
131
+ call: ([reason], context) => {
132
+ const constructor = context === undefined ? promiseConstructor : context.thisValue;
133
+ return isSandboxPromiseConstructor(constructor)
134
+ ? createRejectedSandboxPromise(reason, options.budget, context?.span)
135
+ : settleConstructedPromise(constructor, reason, "rejected", options.budget, context);
136
+ },
52
137
  name: "reject"
53
138
  })
54
139
  }
55
- };
140
+ });
141
+ Object.defineProperty(prototype, "constructor", {
142
+ value: promiseConstructor,
143
+ writable: true,
144
+ configurable: true
145
+ });
146
+ promiseConstructors.add(promiseConstructor);
147
+ intrinsicPromiseConstructors.set(options.budget, promiseConstructor);
148
+ return { Promise: promiseConstructor };
56
149
  }
57
- export function getPromiseMember(target, property, budget) {
58
- if (property === "then") {
59
- return createSandboxClosure({
60
- async: true,
61
- call: ([onFulfilled, onRejected]) => {
62
- observeSandboxPromise(target);
63
- const chained = createSandboxPromise(target.promise.then((value) => runPromiseReaction(onFulfilled, value, "fulfilled", budget, chained), (reason) => runPromiseReaction(onRejected, reason, "rejected", budget, chained)));
64
- return chained;
65
- },
66
- name: "then"
150
+ export function getPromiseMember(property, budget) {
151
+ const prototype = getPromisePrototype(budget);
152
+ return Object.hasOwn(prototype, property) ? prototype[property] : undefined;
153
+ }
154
+ async function settleConstructedPromise(constructor, value, state, budget, context) {
155
+ const capability = await createPromiseCapability(constructor, budget, context);
156
+ await callPromiseClosure(state === "fulfilled" ? capability.resolve : capability.reject, [value], undefined, budget, context);
157
+ return capability.promise;
158
+ }
159
+ async function createPromiseCapability(constructor, budget, context) {
160
+ if (!isSandboxClosure(constructor) || constructor.construct === undefined) {
161
+ throw new TypeError("Promise method requires a constructor receiver.");
162
+ }
163
+ let resolve;
164
+ let reject;
165
+ const executor = createSandboxClosure({
166
+ sandbox: true,
167
+ retainedValues: () => [resolve, reject],
168
+ call: (args) => {
169
+ if (resolve !== undefined || reject !== undefined) {
170
+ throw new TypeError("Promise capability is already initialized.");
171
+ }
172
+ [resolve, reject] = args;
173
+ return undefined;
174
+ }
175
+ });
176
+ const leaveCall = budget.enterCall();
177
+ try {
178
+ const promise = await constructor.construct([executor], {
179
+ stack: context?.stack ?? [],
180
+ thisValue: undefined,
181
+ ...(context?.span === undefined ? {} : { span: context.span })
67
182
  });
183
+ if (!isSandboxClosure(resolve) || !isSandboxClosure(reject)) {
184
+ throw new TypeError("Promise capability requires callable resolve and reject functions.");
185
+ }
186
+ return { promise, resolve, reject };
187
+ }
188
+ finally {
189
+ leaveCall();
190
+ }
191
+ }
192
+ async function callPromiseClosure(callback, args, thisValue, budget, context) {
193
+ const leaveCall = budget.enterCall();
194
+ try {
195
+ const stack = context?.stack ?? [];
196
+ const values = args.map((value) => value instanceof Error && !(value instanceof SandboxError)
197
+ ? coerceThrownValue(value, budget, stack)
198
+ : value);
199
+ let result = callback.call(values, { stack, thisValue });
200
+ if (callback.async !== true)
201
+ result = await result;
202
+ else if (isPromiseLike(result))
203
+ result = createSandboxPromise(Promise.resolve(result));
204
+ if (isSandboxPromise(result) && result.synchronousPrefix !== undefined) {
205
+ await result.synchronousPrefix;
206
+ }
207
+ return result;
208
+ }
209
+ finally {
210
+ leaveCall();
68
211
  }
69
- if (property === "catch") {
70
- return createSandboxClosure({
71
- async: true,
72
- call: ([onRejected]) => {
212
+ }
213
+ function getPromisePrototype(budget) {
214
+ const existing = promisePrototypes.get(budget);
215
+ if (existing !== undefined)
216
+ return existing;
217
+ const prototype = {
218
+ then: createSandboxClosure({
219
+ sandbox: true,
220
+ call: ([onFulfilled, onRejected], context) => {
221
+ const target = context?.thisValue;
222
+ if (!isSandboxPromise(target))
223
+ throw new TypeError("Promise.then requires a promise receiver.");
224
+ validatePromiseConstructorProperty(target, prototype);
73
225
  observeSandboxPromise(target);
74
- const chained = createSandboxPromise(target.promise.then((value) => runPromiseReaction(undefined, value, "fulfilled", budget, chained), (reason) => runPromiseReaction(onRejected, reason, "rejected", budget, chained)));
226
+ const chained = createSandboxPromise(target.promise.then((value) => {
227
+ consumeSettledHostCall(target);
228
+ return runPromiseReaction(onFulfilled, value, "fulfilled", budget, chained);
229
+ }, (reason) => {
230
+ consumeSettledHostCall(target);
231
+ return runPromiseReaction(onRejected, reason, "rejected", budget, chained);
232
+ }));
75
233
  return chained;
76
234
  },
235
+ name: "then"
236
+ }),
237
+ catch: createSandboxClosure({
238
+ sandbox: true,
239
+ call: ([onRejected], context) => {
240
+ const target = context?.thisValue;
241
+ const then = readPromiseReceiverProperty(target, "then", prototype);
242
+ if (!isSandboxClosure(then))
243
+ throw new TypeError("Promise.catch requires a callable then.");
244
+ return then.call([undefined, onRejected], {
245
+ stack: context?.stack ?? [],
246
+ thisValue: target
247
+ });
248
+ },
77
249
  name: "catch"
78
- });
79
- }
80
- if (property === "finally") {
81
- return createSandboxClosure({
82
- async: true,
83
- call: ([onFinally]) => {
84
- observeSandboxPromise(target);
85
- const chained = createSandboxPromise(target.promise.then((value) => runPromiseFinally(onFinally, value, "fulfilled", budget, chained), (reason) => runPromiseFinally(onFinally, reason, "rejected", budget, chained)));
86
- return chained;
250
+ }),
251
+ finally: createSandboxClosure({
252
+ sandbox: true,
253
+ call: ([onFinally], context) => {
254
+ const target = context?.thisValue;
255
+ if (typeof target !== "object" || target === null) {
256
+ throw new TypeError("Promise.finally requires an object receiver.");
257
+ }
258
+ validatePromiseConstructorProperty(target, prototype);
259
+ const then = readPromiseReceiverProperty(target, "then", prototype);
260
+ if (!isSandboxClosure(then))
261
+ throw new TypeError("Promise.finally requires a callable then.");
262
+ const handlers = isSandboxClosure(onFinally)
263
+ ? ["fulfilled", "rejected"].map((state) => createSandboxClosure({
264
+ sandbox: true,
265
+ retainedValues: () => [onFinally],
266
+ call: async ([value]) => {
267
+ const result = await callPromiseClosure(onFinally, [], undefined, budget, context);
268
+ const pending = isSandboxPromise(result) &&
269
+ getPromiseMember("constructor", budget) ===
270
+ intrinsicPromiseConstructors.get(budget)
271
+ ? result
272
+ : createSandboxPromise(resolveSandboxValue(result, { budget }));
273
+ const cleanupThen = getPromiseMember("then", budget);
274
+ if (!isSandboxClosure(cleanupThen))
275
+ throw new TypeError("Promise cleanup requires a callable then.");
276
+ return callPromiseClosure(cleanupThen, [
277
+ createSandboxClosure({
278
+ sandbox: true,
279
+ retainedValues: () => [value],
280
+ call: () => {
281
+ if (state === "rejected")
282
+ throw value;
283
+ return value;
284
+ }
285
+ })
286
+ ], pending, budget, context);
287
+ }
288
+ }))
289
+ : [onFinally, onFinally];
290
+ return then.call(handlers, { stack: context?.stack ?? [], thisValue: target });
87
291
  },
88
292
  name: "finally"
89
- });
293
+ })
294
+ };
295
+ for (const name of Object.keys(prototype)) {
296
+ Object.defineProperty(prototype, name, { enumerable: false });
90
297
  }
91
- return undefined;
298
+ promisePrototypes.set(budget, prototype);
299
+ return prototype;
92
300
  }
93
- function settleIterable(iterable, settle, budget) {
94
- return Promise.resolve().then(() => settle(toHostPromiseArray(iterable, budget)).then((value) => budgetSandboxValue(value, budget)));
301
+ function readPromiseReceiverProperty(receiver, property, prototype) {
302
+ const properties = isSandboxPromise(receiver)
303
+ ? prototype
304
+ : isSandboxClosure(receiver)
305
+ ? receiver.properties
306
+ : receiver;
307
+ return typeof properties === "object" &&
308
+ properties !== null &&
309
+ Object.hasOwn(properties, property)
310
+ ? properties[property]
311
+ : undefined;
95
312
  }
96
- function toHostPromiseArray(iterable, budget) {
97
- const iterator = getPromiseIterator(iterable);
98
- if (iterator === undefined) {
99
- throw new TypeError("Promise helpers require an iterable.");
100
- }
101
- const entries = [];
102
- while (true) {
103
- const next = readIteratorNext(iterator);
104
- if (next.done === true) {
105
- return entries;
106
- }
107
- entries.push(resolveSandboxValue(next.value, { budget }));
108
- }
109
- }
110
- function getPromiseIterator(iterable) {
111
- if (typeof iterable === "string") {
112
- return iterable[Symbol.iterator]();
113
- }
114
- if ((typeof iterable !== "object" && typeof iterable !== "function") || iterable === null) {
115
- return undefined;
116
- }
117
- const iteratorMethod = iterable[Symbol.iterator];
118
- if (typeof iteratorMethod !== "function") {
119
- return undefined;
120
- }
121
- const iterator = Reflect.apply(iteratorMethod, iterable, []);
122
- if ((typeof iterator !== "object" && typeof iterator !== "function") || iterator === null) {
123
- throw new TypeError("Promise helper iterable returned a non-object iterator.");
313
+ function validatePromiseConstructorProperty(receiver, prototype) {
314
+ const constructor = readPromiseReceiverProperty(receiver, "constructor", prototype);
315
+ if (constructor !== undefined && (typeof constructor !== "object" || constructor === null)) {
316
+ throw new TypeError("Promise constructor property must be an object.");
124
317
  }
125
- return iterator;
126
318
  }
127
- function readIteratorNext(iterator) {
128
- const nextMethod = iterator.next;
129
- if (typeof nextMethod !== "function") {
130
- throw new TypeError("Promise helper iterator requires a next method.");
319
+ async function settleIterable(iterable, method, budget, constructor, context) {
320
+ const capability = await createPromiseCapability(constructor, budget, context);
321
+ const prototype = getPromisePrototype(budget);
322
+ const values = [];
323
+ let remaining = 1;
324
+ const complete = async () => {
325
+ if (remaining !== 0 || method === "race")
326
+ return;
327
+ if (method === "any") {
328
+ const error = createSubsetErrorValue("AggregateError", "All promises were rejected", [], budget);
329
+ error.errors = values;
330
+ await callPromiseClosure(capability.reject, [budgetSandboxValue(error, budget)], undefined, budget, context);
331
+ }
332
+ else {
333
+ await callPromiseClosure(capability.resolve, [budgetSandboxValue(values, budget)], undefined, budget, context);
334
+ }
335
+ };
336
+ try {
337
+ const promiseResolve = readPromiseReceiverProperty(constructor, "resolve", prototype);
338
+ if (!isSandboxClosure(promiseResolve))
339
+ throw new TypeError("Promise constructor requires a callable resolve.");
340
+ const iterator = getSandboxIterator(iterable);
341
+ if (iterator === undefined)
342
+ throw new TypeError("Promise helpers require an iterable.");
343
+ let index = 0;
344
+ while (true) {
345
+ budget.visitNode();
346
+ const next = iterator.generator
347
+ ? await iterator.next()
348
+ : iterator.next();
349
+ if (typeof next !== "object" || next === null)
350
+ throw new TypeError("Iterator result must be an object.");
351
+ if (next.done)
352
+ break;
353
+ const value = next.value;
354
+ try {
355
+ budget.allocateArrayLength(index + 1);
356
+ const entryIndex = index++;
357
+ if (method !== "race")
358
+ values.push(undefined);
359
+ const entry = await callPromiseClosure(promiseResolve, [value], constructor, budget, context);
360
+ let called = false;
361
+ const handlers = ["fulfilled", "rejected"].map((state) => {
362
+ if (method === "race" || (method === "any" && state === "fulfilled"))
363
+ return state === "fulfilled" ? capability.resolve : capability.reject;
364
+ if (method === "all" && state === "rejected")
365
+ return capability.reject;
366
+ return createSandboxClosure({
367
+ sandbox: true,
368
+ retainedValues: () => [
369
+ capability.promise,
370
+ capability.resolve,
371
+ capability.reject,
372
+ values
373
+ ],
374
+ call: async ([settlement]) => {
375
+ if (called)
376
+ return undefined;
377
+ called = true;
378
+ values[entryIndex] =
379
+ method === "allSettled"
380
+ ? state === "fulfilled"
381
+ ? { status: state, value: settlement }
382
+ : { status: state, reason: settlement }
383
+ : settlement;
384
+ remaining--;
385
+ await complete();
386
+ return undefined;
387
+ }
388
+ });
389
+ });
390
+ remaining++;
391
+ const then = readPromiseReceiverProperty(entry, "then", prototype);
392
+ if (!isSandboxClosure(then))
393
+ throw new TypeError("Promise resolver result requires a callable then.");
394
+ await callPromiseClosure(then, handlers, entry, budget, context);
395
+ }
396
+ catch (error) {
397
+ try {
398
+ const closed = iterator.return?.();
399
+ if (iterator.generator)
400
+ await closed;
401
+ }
402
+ catch {
403
+ throw error;
404
+ }
405
+ throw error;
406
+ }
407
+ }
408
+ remaining--;
409
+ await complete();
131
410
  }
132
- const next = Reflect.apply(nextMethod, iterator, []);
133
- if (typeof next !== "object" || next === null) {
134
- throw new TypeError("Iterator result must be an object.");
411
+ catch (error) {
412
+ if (error instanceof SandboxError &&
413
+ (error.code === "budgetExceeded" || error.code === "reentry")) {
414
+ if (isSandboxPromise(capability.promise))
415
+ observeSandboxPromise(capability.promise);
416
+ throw error;
417
+ }
418
+ await callPromiseClosure(capability.reject, [error], undefined, budget, context);
135
419
  }
136
- return next;
420
+ return capability.promise;
137
421
  }
138
422
  function schedulePromise(promise, budget) {
139
423
  return Promise.resolve().then(() => promise.then((value) => resolveSandboxValue(value, { budget }), (reason) => Promise.reject(budgetSandboxValue(reason, budget))));
@@ -149,7 +433,12 @@ function budgetSandboxValue(value, budget) {
149
433
  return value;
150
434
  }
151
435
  export function resolveSandboxValue(value, options = {}) {
152
- return Promise.resolve().then(() => resolveSandboxValueNow(value, options, new WeakSet()));
436
+ try {
437
+ return resolveSandboxValueNow(value, options);
438
+ }
439
+ catch (error) {
440
+ return Promise.reject(error);
441
+ }
153
442
  }
154
443
  // A host call result is consumed once, when it settles. Observing the same promise
155
444
  // again is ordinary JavaScript and must not be reported as a double consumption.
@@ -159,15 +448,18 @@ export function consumeSettledHostCall(value) {
159
448
  }
160
449
  value.hostCallJournal?.consume(value.hostCall);
161
450
  }
162
- function resolveSandboxValueNow(value, options, seenThenables) {
451
+ function resolveSandboxValueNow(value, options) {
163
452
  if (isPromiseLike(value)) {
164
- return Promise.resolve(value).then((resolved) => resolveSandboxValueNow(resolved, options, seenThenables), (reason) => Promise.reject(budgetIfNeeded(reason, options.budget)));
453
+ return Promise.resolve(value).then((resolved) => resolveSandboxValueNow(resolved, options), (reason) => Promise.reject(budgetIfNeeded(reason, options.budget)));
165
454
  }
166
455
  if (isSandboxPromise(value)) {
456
+ if (options.budget !== undefined) {
457
+ return resolvePromiseResult(value, options.budget, options.self);
458
+ }
167
459
  observeSandboxPromise(value);
168
460
  return value.promise.then((resolved) => {
169
461
  consumeSettledHostCall(value);
170
- return resolveSandboxValueNow(resolved, options, seenThenables);
462
+ return resolveSandboxValueNow(resolved, options);
171
463
  }, (reason) => {
172
464
  consumeSettledHostCall(value);
173
465
  return Promise.reject(budgetIfNeeded(reason, options.budget));
@@ -175,21 +467,14 @@ function resolveSandboxValueNow(value, options, seenThenables) {
175
467
  }
176
468
  const then = getThenable(value);
177
469
  if (then !== undefined) {
178
- return resolveThenable(value, then, options, seenThenables);
470
+ return resolveThenable(value, then, options);
179
471
  }
180
472
  return Promise.resolve(budgetIfNeeded(value, options.budget));
181
473
  }
182
- function resolveThenable(value, then, options, seenThenables) {
474
+ function resolveThenable(value, then, options) {
183
475
  if (typeof value !== "object" || value === null) {
184
476
  return Promise.resolve(budgetIfNeeded(value, options.budget));
185
477
  }
186
- if (seenThenables.has(value)) {
187
- return Promise.reject({
188
- message: "Promise cannot resolve to itself.",
189
- name: "TypeError"
190
- });
191
- }
192
- seenThenables.add(value);
193
478
  return new Promise((resolve, reject) => {
194
479
  let settlement;
195
480
  let completed = false;
@@ -200,7 +485,9 @@ function resolveThenable(value, then, options, seenThenables) {
200
485
  completed = true;
201
486
  try {
202
487
  if (settlement.state === "fulfilled") {
203
- resolve(resolveSandboxValueNow(settlement.value, options, seenThenables));
488
+ resolve(isSandboxPromise(settlement.value) || getThenable(settlement.value) !== undefined
489
+ ? resolveSandboxValueNow(settlement.value, options)
490
+ : budgetIfNeeded(settlement.value, options.budget));
204
491
  }
205
492
  else {
206
493
  reject(budgetIfNeeded(settlement.value, options.budget));
@@ -219,6 +506,7 @@ function resolveThenable(value, then, options, seenThenables) {
219
506
  };
220
507
  callInPromiseJob(then, [
221
508
  createSandboxClosure({
509
+ sandbox: true,
222
510
  call: ([resolved]) => {
223
511
  recordSettlement("fulfilled", resolved);
224
512
  return undefined;
@@ -226,56 +514,151 @@ function resolveThenable(value, then, options, seenThenables) {
226
514
  name: "resolve"
227
515
  }),
228
516
  createSandboxClosure({
517
+ sandbox: true,
229
518
  call: ([reason]) => {
230
519
  recordSettlement("rejected", reason);
231
520
  return undefined;
232
521
  },
233
522
  name: "reject"
234
523
  })
235
- ], value).then(() => {
236
- invocationPending = false;
237
- complete();
238
- }, (error) => {
239
- invocationPending = false;
240
- recordSettlement("rejected", error);
241
- complete();
242
- });
243
- }).then((resolved) => budgetIfNeeded(resolved, options.budget));
524
+ ], value, {
525
+ fulfilled: () => {
526
+ invocationPending = false;
527
+ complete();
528
+ },
529
+ rejected: (error) => {
530
+ invocationPending = false;
531
+ if (error instanceof SandboxError &&
532
+ (error.code === "budgetExceeded" || error.code === "reentry")) {
533
+ completed = true;
534
+ reject(error);
535
+ return;
536
+ }
537
+ recordSettlement("rejected", error);
538
+ complete();
539
+ }
540
+ }).catch(reject);
541
+ });
244
542
  }
245
543
  function runPromiseReaction(handler, value, state, budget, self) {
246
- if (!isSandboxClosure(handler)) {
247
- return state === "fulfilled"
248
- ? Promise.resolve(value)
249
- : Promise.reject(budgetSandboxValue(value, budget));
250
- }
251
- return callInPromiseJob(handler, [value]).then(({ value: result }) => resolveReactionResult(result, budget, self));
252
- }
253
- function runPromiseFinally(handler, value, state, budget, self) {
254
- if (!isSandboxClosure(handler)) {
255
- return runPromiseReaction(undefined, value, state, budget);
256
- }
257
- return callInPromiseJob(handler, []).then(({ value: result }) => resolveReactionResult(result, budget, self).then(() => runPromiseReaction(undefined, value, state, budget)));
544
+ return new Promise((resolve, reject) => {
545
+ if (state === "rejected" &&
546
+ value instanceof SandboxError &&
547
+ (value.code === "budgetExceeded" || value.code === "reentry")) {
548
+ reject(value);
549
+ return;
550
+ }
551
+ const argument = state === "rejected" && value instanceof Error ? coerceThrownValue(value, budget, []) : value;
552
+ const fulfilled = (result) => {
553
+ if (isPromiseLike(result)) {
554
+ resolve(resolvePromiseResult(result, budget, self));
555
+ }
556
+ else if (isSelfResolution(result, self)) {
557
+ reject(createSubsetErrorValue("TypeError", "Promise cannot resolve to itself.", [], budget));
558
+ }
559
+ else if (isSandboxPromise(result) || getThenable(result) !== undefined) {
560
+ resolve(resolvePromiseResult(result, budget, self));
561
+ }
562
+ else {
563
+ resolve(budgetSandboxValue(result, budget));
564
+ }
565
+ };
566
+ if (isSandboxClosure(handler)) {
567
+ callInPromiseJob(handler, [argument], undefined, { fulfilled, rejected: reject }).catch(reject);
568
+ }
569
+ else {
570
+ runPromiseJob(() => {
571
+ if (state === "fulfilled")
572
+ fulfilled(value);
573
+ else
574
+ reject(budgetSandboxValue(value, budget));
575
+ }).catch(reject);
576
+ }
577
+ });
258
578
  }
259
- function callInPromiseJob(handler, args, thisValue = undefined) {
579
+ function callInPromiseJob(handler, args, thisValue = undefined, completion) {
260
580
  return runPromiseJob(async () => {
261
- let result = handler.call(args, { stack: [], thisValue });
262
- if (handler.async !== true)
263
- result = await result;
264
- if (isSandboxPromise(result) && result.synchronousPrefix !== undefined) {
265
- await result.synchronousPrefix;
581
+ try {
582
+ let result = handler.call(args, { stack: [], thisValue });
583
+ if (handler.async !== true)
584
+ result = await result;
585
+ if (isSandboxPromise(result) && result.synchronousPrefix !== undefined) {
586
+ await result.synchronousPrefix;
587
+ }
588
+ completion?.fulfilled(result);
589
+ return { value: result };
590
+ }
591
+ catch (error) {
592
+ if (completion === undefined)
593
+ throw error;
594
+ completion.rejected(error);
595
+ return { value: undefined };
266
596
  }
267
- return { value: result };
268
597
  });
269
598
  }
270
- function resolveReactionResult(result, budget, self) {
271
- return Promise.resolve(result).then((resolved) => {
272
- if (isSelfResolution(resolved, self)) {
273
- return Promise.reject({
274
- message: "Promise cannot resolve to itself.",
275
- name: "TypeError"
276
- });
277
- }
278
- return resolveSandboxValue(resolved, { budget });
599
+ function resolvePromiseResult(result, budget, self) {
600
+ if (isPromiseLike(result)) {
601
+ return Promise.resolve(result).then((resolved) => resolvePromiseResult(resolved, budget, self));
602
+ }
603
+ if (isSelfResolution(result, self)) {
604
+ return Promise.reject(createSubsetErrorValue("TypeError", "Promise cannot resolve to itself.", [], budget));
605
+ }
606
+ if (!isSandboxPromise(result))
607
+ return resolveSandboxValue(result, { budget, self });
608
+ const then = getPromiseMember("then", budget);
609
+ if (!isSandboxClosure(then))
610
+ return Promise.resolve(result);
611
+ return new Promise((resolve, reject) => {
612
+ let settled = false;
613
+ const settle = (state, value) => {
614
+ if (settled)
615
+ return;
616
+ settled = true;
617
+ try {
618
+ if (state === "rejected")
619
+ reject(budgetSandboxValue(value, budget));
620
+ else if (isSandboxPromise(value) || getThenable(value) !== undefined) {
621
+ resolve(resolvePromiseResult(value, budget, self));
622
+ }
623
+ else {
624
+ resolve(budgetSandboxValue(value, budget));
625
+ }
626
+ }
627
+ catch (error) {
628
+ reject(error);
629
+ }
630
+ };
631
+ runPromiseJob(async () => {
632
+ try {
633
+ const completion = await callPromiseClosure(then, [
634
+ createSandboxClosure({
635
+ sandbox: true,
636
+ call: ([value]) => {
637
+ settle("fulfilled", value);
638
+ return undefined;
639
+ }
640
+ }),
641
+ createSandboxClosure({
642
+ sandbox: true,
643
+ call: ([reason]) => {
644
+ settle("rejected", reason);
645
+ return undefined;
646
+ }
647
+ })
648
+ ], result, budget);
649
+ if (isSandboxPromise(completion)) {
650
+ completion.promise.catch((reason) => {
651
+ if (reason instanceof SandboxError &&
652
+ (reason.code === "budgetExceeded" || reason.code === "reentry")) {
653
+ reject(reason);
654
+ }
655
+ });
656
+ }
657
+ }
658
+ catch (error) {
659
+ settle("rejected", error);
660
+ }
661
+ }).catch(reject);
279
662
  });
280
663
  }
281
664
  function isSelfResolution(result, self) {