@volter/tabnode 0.5.28 → 0.5.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/dist/assets/{runtime-worker-C6ulimZ4.js → runtime-worker-Dv_vTkXr.js} +138 -78
- package/dist/index.mjs +139 -79
- package/dist/shims/async_hooks.d.ts +12 -21
- package/package.json +1 -1
- package/src/runtime.ts +94 -3
- package/src/shims/async_hooks.ts +83 -83
|
@@ -6521,99 +6521,85 @@ forGuestRealm(() => {
|
|
|
6521
6521
|
const realm = globalThis;
|
|
6522
6522
|
if (!realm.__browserRuntimeHeldWork) takeFromHost(globalThis, "__browserRuntimeHeldWork", ownHeldWork);
|
|
6523
6523
|
});
|
|
6524
|
-
|
|
6525
|
-
|
|
6526
|
-
|
|
6527
|
-
|
|
6528
|
-
|
|
6529
|
-
|
|
6530
|
-
|
|
6531
|
-
|
|
6532
|
-
|
|
6533
|
-
|
|
6534
|
-
|
|
6535
|
-
|
|
6536
|
-
|
|
6537
|
-
|
|
6538
|
-
|
|
6539
|
-
|
|
6540
|
-
|
|
6541
|
-
|
|
6542
|
-
|
|
6524
|
+
const ROOT_FRAME = /* @__PURE__ */ new Map();
|
|
6525
|
+
let currentFrame = ROOT_FRAME;
|
|
6526
|
+
const nativeQueueMicrotask = globalThis.queueMicrotask.bind(globalThis);
|
|
6527
|
+
let resetQueued = false;
|
|
6528
|
+
const resetFrame = () => {
|
|
6529
|
+
resetQueued = false;
|
|
6530
|
+
currentFrame = ROOT_FRAME;
|
|
6531
|
+
};
|
|
6532
|
+
const resumeFrame = (frame) => {
|
|
6533
|
+
if (frame === currentFrame) return;
|
|
6534
|
+
currentFrame = frame;
|
|
6535
|
+
if (!resetQueued) {
|
|
6536
|
+
resetQueued = true;
|
|
6537
|
+
nativeQueueMicrotask(resetFrame);
|
|
6538
|
+
}
|
|
6539
|
+
};
|
|
6540
|
+
const withFrame = (frame, callback) => {
|
|
6541
|
+
const previous = currentFrame;
|
|
6542
|
+
currentFrame = frame;
|
|
6543
|
+
try {
|
|
6544
|
+
return callback();
|
|
6545
|
+
} finally {
|
|
6546
|
+
currentFrame = previous;
|
|
6543
6547
|
}
|
|
6544
|
-
}
|
|
6545
|
-
const __substrateAsyncLocalStorages = /* @__PURE__ */ new Set();
|
|
6548
|
+
};
|
|
6546
6549
|
class AsyncLocalStorage {
|
|
6547
|
-
constructor() {
|
|
6548
|
-
__publicField(this, "store");
|
|
6549
|
-
__substrateAsyncLocalStorages.add(this);
|
|
6550
|
-
}
|
|
6551
6550
|
static snapshot() {
|
|
6552
|
-
const captured =
|
|
6553
|
-
|
|
6554
|
-
return (callback, ...args) => {
|
|
6555
|
-
const previous = /* @__PURE__ */ new Map();
|
|
6556
|
-
for (const [storage, store] of captured) {
|
|
6557
|
-
previous.set(storage, storage.store);
|
|
6558
|
-
storage.store = store;
|
|
6559
|
-
}
|
|
6560
|
-
try {
|
|
6561
|
-
return callback(...args);
|
|
6562
|
-
} finally {
|
|
6563
|
-
for (const [storage, store] of previous) storage.store = store;
|
|
6564
|
-
}
|
|
6565
|
-
};
|
|
6551
|
+
const captured = currentFrame;
|
|
6552
|
+
return (callback, ...args) => withFrame(captured, () => callback(...args));
|
|
6566
6553
|
}
|
|
6567
6554
|
static bind(callback) {
|
|
6568
6555
|
const snapshot = AsyncLocalStorage.snapshot();
|
|
6569
6556
|
return (...args) => snapshot(callback, ...args);
|
|
6570
6557
|
}
|
|
6571
6558
|
disable() {
|
|
6559
|
+
if (!currentFrame.has(this)) return;
|
|
6560
|
+
const next2 = new Map(currentFrame);
|
|
6561
|
+
next2.delete(this);
|
|
6562
|
+
currentFrame = next2;
|
|
6572
6563
|
}
|
|
6573
6564
|
getStore() {
|
|
6574
|
-
return this
|
|
6565
|
+
return currentFrame.get(this);
|
|
6575
6566
|
}
|
|
6576
|
-
/**
|
|
6577
|
-
* A store entered for an async callback used to be left the moment the
|
|
6578
|
-
* callback returned its promise, so the first `await` inside a Server Action
|
|
6579
|
-
* lost Next's request store and `cookies()` was "outside a request scope". A
|
|
6580
|
-
* tab has no async_hooks and no AsyncContext, and an `await` of a native
|
|
6581
|
-
* promise bypasses any `then` a patch could install, so the continuations
|
|
6582
|
-
* cannot be followed. What can be done honestly is keep the store current
|
|
6583
|
-
* until the callback's promise settles: exact for one run at a time, and
|
|
6584
|
-
* last-entered-wins where two async runs overlap.
|
|
6585
|
-
*/
|
|
6586
6567
|
run(store, callback, ...args) {
|
|
6587
|
-
const
|
|
6588
|
-
this
|
|
6589
|
-
|
|
6590
|
-
try {
|
|
6591
|
-
result = callback(...args);
|
|
6592
|
-
} catch (error) {
|
|
6593
|
-
this.store = prev;
|
|
6594
|
-
throw error;
|
|
6595
|
-
}
|
|
6596
|
-
if (result && typeof result.then === "function") {
|
|
6597
|
-
const restore = () => {
|
|
6598
|
-
if (this.store === store) this.store = prev;
|
|
6599
|
-
};
|
|
6600
|
-
result.then(restore, restore);
|
|
6601
|
-
return result;
|
|
6602
|
-
}
|
|
6603
|
-
this.store = prev;
|
|
6604
|
-
return result;
|
|
6568
|
+
const next2 = new Map(currentFrame);
|
|
6569
|
+
next2.set(this, store);
|
|
6570
|
+
return withFrame(next2, () => callback(...args));
|
|
6605
6571
|
}
|
|
6606
6572
|
exit(callback) {
|
|
6607
|
-
const
|
|
6608
|
-
this
|
|
6609
|
-
|
|
6610
|
-
return callback();
|
|
6611
|
-
} finally {
|
|
6612
|
-
this.store = prev;
|
|
6613
|
-
}
|
|
6573
|
+
const next2 = new Map(currentFrame);
|
|
6574
|
+
next2.delete(this);
|
|
6575
|
+
return withFrame(next2, callback);
|
|
6614
6576
|
}
|
|
6615
6577
|
enterWith(store) {
|
|
6616
|
-
|
|
6578
|
+
const next2 = new Map(currentFrame);
|
|
6579
|
+
next2.set(this, store);
|
|
6580
|
+
currentFrame = next2;
|
|
6581
|
+
}
|
|
6582
|
+
}
|
|
6583
|
+
class AsyncResource {
|
|
6584
|
+
constructor(_type2, _options3) {
|
|
6585
|
+
__publicField(this, "frame", currentFrame);
|
|
6586
|
+
}
|
|
6587
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
6588
|
+
runInAsyncScope(fn2, thisArg, ...args) {
|
|
6589
|
+
return withFrame(this.frame, () => fn2.apply(thisArg, args));
|
|
6590
|
+
}
|
|
6591
|
+
emitDestroy() {
|
|
6592
|
+
return this;
|
|
6593
|
+
}
|
|
6594
|
+
asyncId() {
|
|
6595
|
+
return 0;
|
|
6596
|
+
}
|
|
6597
|
+
triggerAsyncId() {
|
|
6598
|
+
return 0;
|
|
6599
|
+
}
|
|
6600
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
6601
|
+
static bind(fn2, _type2) {
|
|
6602
|
+
return AsyncLocalStorage.bind(fn2);
|
|
6617
6603
|
}
|
|
6618
6604
|
}
|
|
6619
6605
|
forGuestRealm(() => {
|
|
@@ -6640,6 +6626,14 @@ forGuestRealm(() => {
|
|
|
6640
6626
|
};
|
|
6641
6627
|
takeFromHost(Promise.prototype, "then", carriedThen);
|
|
6642
6628
|
takeFromHost(globalThis, "__substrateCarried", carried);
|
|
6629
|
+
takeFromHost(globalThis, "__substrateContext", () => currentFrame);
|
|
6630
|
+
takeFromHost(globalThis, "__substrateResume", (frame, value) => {
|
|
6631
|
+
resumeFrame(frame);
|
|
6632
|
+
return value;
|
|
6633
|
+
});
|
|
6634
|
+
takeFromHost(globalThis, "__substrateRestore", (frame) => {
|
|
6635
|
+
resumeFrame(frame);
|
|
6636
|
+
});
|
|
6643
6637
|
});
|
|
6644
6638
|
function createHook(_callbacks) {
|
|
6645
6639
|
return {
|
|
@@ -78526,6 +78520,72 @@ function collectDirectEval(node, replacements) {
|
|
|
78526
78520
|
replacements.push([source.start, source.start, "__substrateEvalSource("]);
|
|
78527
78521
|
replacements.push([source.end, source.end, ")"]);
|
|
78528
78522
|
}
|
|
78523
|
+
const ASYNC_FRAME = "__substrateAsyncFrame";
|
|
78524
|
+
const FOLLOW_MARK = "/*substrate*/";
|
|
78525
|
+
const FOLLOW_TAKE = `const ${ASYNC_FRAME}=__substrateContext();`;
|
|
78526
|
+
const FOLLOW_RESTORE = `__substrateRestore(${ASYNC_FRAME});`;
|
|
78527
|
+
const FOLLOW_RESUME_FRAMED = `__substrateResume(${ASYNC_FRAME},`;
|
|
78528
|
+
const FOLLOW_RESUME_TAKEN = "__substrateResume(__substrateContext(),";
|
|
78529
|
+
function __substrateFollowAwaits(code2) {
|
|
78530
|
+
if (!/\bawait\b/.test(code2)) return code2;
|
|
78531
|
+
let ast;
|
|
78532
|
+
try {
|
|
78533
|
+
ast = parse3(code2, { ecmaVersion: "latest", sourceType: "script", allowReturnOutsideFunction: true, allowHashBang: true, allowAwaitOutsideFunction: true });
|
|
78534
|
+
} catch {
|
|
78535
|
+
return code2;
|
|
78536
|
+
}
|
|
78537
|
+
const edits = [];
|
|
78538
|
+
const insert = (at3, text) => {
|
|
78539
|
+
edits.push([at3, at3, text]);
|
|
78540
|
+
};
|
|
78541
|
+
const isFunction = (node) => node.type === "FunctionDeclaration" || node.type === "FunctionExpression" || node.type === "ArrowFunctionExpression";
|
|
78542
|
+
const visit = (node, framed, parent) => {
|
|
78543
|
+
if (isFunction(node)) {
|
|
78544
|
+
framed = node.async && node.body.type === "BlockStatement";
|
|
78545
|
+
if (framed) {
|
|
78546
|
+
const directives = node.body.body.filter((statement) => statement.directive !== void 0);
|
|
78547
|
+
const at3 = directives.length ? directives[directives.length - 1].end : node.body.start + 1;
|
|
78548
|
+
insert(at3, FOLLOW_TAKE);
|
|
78549
|
+
}
|
|
78550
|
+
} else if (node.type === "AwaitExpression") {
|
|
78551
|
+
insert(node.start, framed ? FOLLOW_RESUME_FRAMED : FOLLOW_RESUME_TAKEN);
|
|
78552
|
+
insert(node.end, `${FOLLOW_MARK})`);
|
|
78553
|
+
} else if (framed && node.type === "CatchClause") {
|
|
78554
|
+
insert(node.body.start + 1, FOLLOW_RESTORE);
|
|
78555
|
+
} else if (framed && node.type === "TryStatement" && node.finalizer) {
|
|
78556
|
+
insert(node.finalizer.start + 1, FOLLOW_RESTORE);
|
|
78557
|
+
} else if (framed && node.type === "ForOfStatement" && node.await) {
|
|
78558
|
+
const whole = (parent == null ? void 0 : parent.type) === "LabeledStatement" ? parent : node;
|
|
78559
|
+
insert(whole.start, `{${FOLLOW_MARK}`);
|
|
78560
|
+
insert(whole.end, `;${FOLLOW_RESTORE}${FOLLOW_MARK}}`);
|
|
78561
|
+
if (node.body.type === "BlockStatement") insert(node.body.start + 1, FOLLOW_RESTORE);
|
|
78562
|
+
else {
|
|
78563
|
+
insert(node.body.start, `{${FOLLOW_MARK}${FOLLOW_RESTORE}`);
|
|
78564
|
+
insert(node.body.end, `${FOLLOW_MARK}}`);
|
|
78565
|
+
}
|
|
78566
|
+
}
|
|
78567
|
+
for (const key of Object.keys(node)) {
|
|
78568
|
+
if (key === "type" || key === "start" || key === "end" || key === "loc" || key === "range") continue;
|
|
78569
|
+
const child = node[key];
|
|
78570
|
+
if (!child || typeof child !== "object") continue;
|
|
78571
|
+
if (Array.isArray(child)) {
|
|
78572
|
+
for (const item of child) if (item && typeof item.type === "string") visit(item, framed, node);
|
|
78573
|
+
} else if (typeof child.type === "string") visit(child, framed, node);
|
|
78574
|
+
}
|
|
78575
|
+
};
|
|
78576
|
+
visit(ast, false, void 0);
|
|
78577
|
+
return edits.length ? applyReplacements(code2, edits) : code2;
|
|
78578
|
+
}
|
|
78579
|
+
function __substrateUnfollowAwaits(source) {
|
|
78580
|
+
if (!source.includes("__substrate")) return source;
|
|
78581
|
+
return source.replaceAll(`;${FOLLOW_RESTORE}${FOLLOW_MARK}}`, "").replaceAll(`{${FOLLOW_MARK}${FOLLOW_RESTORE}`, "").replaceAll(`{${FOLLOW_MARK}`, "").replaceAll(`${FOLLOW_MARK}}`, "").replaceAll(`${FOLLOW_MARK})`, "").replaceAll(FOLLOW_TAKE, "").replaceAll(FOLLOW_RESTORE, "").replaceAll(FOLLOW_RESUME_FRAMED, "").replaceAll(FOLLOW_RESUME_TAKEN, "");
|
|
78582
|
+
}
|
|
78583
|
+
forGuestRealm(() => {
|
|
78584
|
+
const native = Function.prototype.toString;
|
|
78585
|
+
takeFromHost(Function.prototype, "toString", function toString2() {
|
|
78586
|
+
return __substrateUnfollowAwaits(native.call(this));
|
|
78587
|
+
});
|
|
78588
|
+
});
|
|
78529
78589
|
function applyReplacements(code2, replacements) {
|
|
78530
78590
|
replacements.sort((a, b) => b[0] - a[0] || b[1] - a[1]);
|
|
78531
78591
|
let out = code2;
|
|
@@ -78549,10 +78609,10 @@ function __substratePrepareBody(rawCode, resolvedPath, format2, process2) {
|
|
|
78549
78609
|
} else {
|
|
78550
78610
|
code2 = __substrateRewriteDynamicImportsInScript(code2);
|
|
78551
78611
|
}
|
|
78552
|
-
return code2;
|
|
78612
|
+
return __substrateFollowAwaits(code2);
|
|
78553
78613
|
}
|
|
78554
78614
|
const PREPARED_MODULES_DIR = "/.tabnode/prepared";
|
|
78555
|
-
const PREPARED_MODULES_FORMAT = "tabnode-prepared-
|
|
78615
|
+
const PREPARED_MODULES_FORMAT = "tabnode-prepared-2";
|
|
78556
78616
|
function preparedModuleKey(rawCode, resolvedPath) {
|
|
78557
78617
|
var _a3;
|
|
78558
78618
|
const extension = (_a3 = /\.(js|cjs|mjs)$/u.exec(resolvedPath)) == null ? void 0 : _a3[1];
|
package/dist/index.mjs
CHANGED
|
@@ -6202,99 +6202,85 @@ const bufferBinding = {
|
|
|
6202
6202
|
setBufferPrototype,
|
|
6203
6203
|
createFromString
|
|
6204
6204
|
};
|
|
6205
|
-
|
|
6206
|
-
|
|
6207
|
-
|
|
6208
|
-
|
|
6209
|
-
|
|
6210
|
-
|
|
6211
|
-
|
|
6212
|
-
|
|
6213
|
-
|
|
6214
|
-
|
|
6215
|
-
|
|
6216
|
-
|
|
6217
|
-
|
|
6218
|
-
|
|
6219
|
-
|
|
6220
|
-
|
|
6221
|
-
|
|
6222
|
-
|
|
6223
|
-
|
|
6205
|
+
const ROOT_FRAME = /* @__PURE__ */ new Map();
|
|
6206
|
+
let currentFrame = ROOT_FRAME;
|
|
6207
|
+
const nativeQueueMicrotask = globalThis.queueMicrotask.bind(globalThis);
|
|
6208
|
+
let resetQueued = false;
|
|
6209
|
+
const resetFrame = () => {
|
|
6210
|
+
resetQueued = false;
|
|
6211
|
+
currentFrame = ROOT_FRAME;
|
|
6212
|
+
};
|
|
6213
|
+
const resumeFrame = (frame) => {
|
|
6214
|
+
if (frame === currentFrame) return;
|
|
6215
|
+
currentFrame = frame;
|
|
6216
|
+
if (!resetQueued) {
|
|
6217
|
+
resetQueued = true;
|
|
6218
|
+
nativeQueueMicrotask(resetFrame);
|
|
6219
|
+
}
|
|
6220
|
+
};
|
|
6221
|
+
const withFrame = (frame, callback) => {
|
|
6222
|
+
const previous = currentFrame;
|
|
6223
|
+
currentFrame = frame;
|
|
6224
|
+
try {
|
|
6225
|
+
return callback();
|
|
6226
|
+
} finally {
|
|
6227
|
+
currentFrame = previous;
|
|
6224
6228
|
}
|
|
6225
|
-
}
|
|
6226
|
-
const __substrateAsyncLocalStorages = /* @__PURE__ */ new Set();
|
|
6229
|
+
};
|
|
6227
6230
|
class AsyncLocalStorage {
|
|
6228
|
-
constructor() {
|
|
6229
|
-
__publicField(this, "store");
|
|
6230
|
-
__substrateAsyncLocalStorages.add(this);
|
|
6231
|
-
}
|
|
6232
6231
|
static snapshot() {
|
|
6233
|
-
const captured =
|
|
6234
|
-
|
|
6235
|
-
return (callback, ...args) => {
|
|
6236
|
-
const previous = /* @__PURE__ */ new Map();
|
|
6237
|
-
for (const [storage, store] of captured) {
|
|
6238
|
-
previous.set(storage, storage.store);
|
|
6239
|
-
storage.store = store;
|
|
6240
|
-
}
|
|
6241
|
-
try {
|
|
6242
|
-
return callback(...args);
|
|
6243
|
-
} finally {
|
|
6244
|
-
for (const [storage, store] of previous) storage.store = store;
|
|
6245
|
-
}
|
|
6246
|
-
};
|
|
6232
|
+
const captured = currentFrame;
|
|
6233
|
+
return (callback, ...args) => withFrame(captured, () => callback(...args));
|
|
6247
6234
|
}
|
|
6248
6235
|
static bind(callback) {
|
|
6249
6236
|
const snapshot = AsyncLocalStorage.snapshot();
|
|
6250
6237
|
return (...args) => snapshot(callback, ...args);
|
|
6251
6238
|
}
|
|
6252
6239
|
disable() {
|
|
6240
|
+
if (!currentFrame.has(this)) return;
|
|
6241
|
+
const next = new Map(currentFrame);
|
|
6242
|
+
next.delete(this);
|
|
6243
|
+
currentFrame = next;
|
|
6253
6244
|
}
|
|
6254
6245
|
getStore() {
|
|
6255
|
-
return this
|
|
6246
|
+
return currentFrame.get(this);
|
|
6256
6247
|
}
|
|
6257
|
-
/**
|
|
6258
|
-
* A store entered for an async callback used to be left the moment the
|
|
6259
|
-
* callback returned its promise, so the first `await` inside a Server Action
|
|
6260
|
-
* lost Next's request store and `cookies()` was "outside a request scope". A
|
|
6261
|
-
* tab has no async_hooks and no AsyncContext, and an `await` of a native
|
|
6262
|
-
* promise bypasses any `then` a patch could install, so the continuations
|
|
6263
|
-
* cannot be followed. What can be done honestly is keep the store current
|
|
6264
|
-
* until the callback's promise settles: exact for one run at a time, and
|
|
6265
|
-
* last-entered-wins where two async runs overlap.
|
|
6266
|
-
*/
|
|
6267
6248
|
run(store, callback, ...args) {
|
|
6268
|
-
const
|
|
6269
|
-
this
|
|
6270
|
-
|
|
6271
|
-
try {
|
|
6272
|
-
result = callback(...args);
|
|
6273
|
-
} catch (error) {
|
|
6274
|
-
this.store = prev;
|
|
6275
|
-
throw error;
|
|
6276
|
-
}
|
|
6277
|
-
if (result && typeof result.then === "function") {
|
|
6278
|
-
const restore = () => {
|
|
6279
|
-
if (this.store === store) this.store = prev;
|
|
6280
|
-
};
|
|
6281
|
-
result.then(restore, restore);
|
|
6282
|
-
return result;
|
|
6283
|
-
}
|
|
6284
|
-
this.store = prev;
|
|
6285
|
-
return result;
|
|
6249
|
+
const next = new Map(currentFrame);
|
|
6250
|
+
next.set(this, store);
|
|
6251
|
+
return withFrame(next, () => callback(...args));
|
|
6286
6252
|
}
|
|
6287
6253
|
exit(callback) {
|
|
6288
|
-
const
|
|
6289
|
-
this
|
|
6290
|
-
|
|
6291
|
-
return callback();
|
|
6292
|
-
} finally {
|
|
6293
|
-
this.store = prev;
|
|
6294
|
-
}
|
|
6254
|
+
const next = new Map(currentFrame);
|
|
6255
|
+
next.delete(this);
|
|
6256
|
+
return withFrame(next, callback);
|
|
6295
6257
|
}
|
|
6296
6258
|
enterWith(store) {
|
|
6297
|
-
|
|
6259
|
+
const next = new Map(currentFrame);
|
|
6260
|
+
next.set(this, store);
|
|
6261
|
+
currentFrame = next;
|
|
6262
|
+
}
|
|
6263
|
+
}
|
|
6264
|
+
class AsyncResource {
|
|
6265
|
+
constructor(_type2, _options2) {
|
|
6266
|
+
__publicField(this, "frame", currentFrame);
|
|
6267
|
+
}
|
|
6268
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
6269
|
+
runInAsyncScope(fn, thisArg, ...args) {
|
|
6270
|
+
return withFrame(this.frame, () => fn.apply(thisArg, args));
|
|
6271
|
+
}
|
|
6272
|
+
emitDestroy() {
|
|
6273
|
+
return this;
|
|
6274
|
+
}
|
|
6275
|
+
asyncId() {
|
|
6276
|
+
return 0;
|
|
6277
|
+
}
|
|
6278
|
+
triggerAsyncId() {
|
|
6279
|
+
return 0;
|
|
6280
|
+
}
|
|
6281
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
6282
|
+
static bind(fn, _type2) {
|
|
6283
|
+
return AsyncLocalStorage.bind(fn);
|
|
6298
6284
|
}
|
|
6299
6285
|
}
|
|
6300
6286
|
forGuestRealm(() => {
|
|
@@ -6321,6 +6307,14 @@ forGuestRealm(() => {
|
|
|
6321
6307
|
};
|
|
6322
6308
|
takeFromHost(Promise.prototype, "then", carriedThen);
|
|
6323
6309
|
takeFromHost(globalThis, "__substrateCarried", carried);
|
|
6310
|
+
takeFromHost(globalThis, "__substrateContext", () => currentFrame);
|
|
6311
|
+
takeFromHost(globalThis, "__substrateResume", (frame, value) => {
|
|
6312
|
+
resumeFrame(frame);
|
|
6313
|
+
return value;
|
|
6314
|
+
});
|
|
6315
|
+
takeFromHost(globalThis, "__substrateRestore", (frame) => {
|
|
6316
|
+
resumeFrame(frame);
|
|
6317
|
+
});
|
|
6324
6318
|
});
|
|
6325
6319
|
function createHook(_callbacks) {
|
|
6326
6320
|
return {
|
|
@@ -38184,6 +38178,72 @@ function collectDirectEval(node, replacements) {
|
|
|
38184
38178
|
replacements.push([source.start, source.start, "__substrateEvalSource("]);
|
|
38185
38179
|
replacements.push([source.end, source.end, ")"]);
|
|
38186
38180
|
}
|
|
38181
|
+
const ASYNC_FRAME = "__substrateAsyncFrame";
|
|
38182
|
+
const FOLLOW_MARK = "/*substrate*/";
|
|
38183
|
+
const FOLLOW_TAKE = `const ${ASYNC_FRAME}=__substrateContext();`;
|
|
38184
|
+
const FOLLOW_RESTORE = `__substrateRestore(${ASYNC_FRAME});`;
|
|
38185
|
+
const FOLLOW_RESUME_FRAMED = `__substrateResume(${ASYNC_FRAME},`;
|
|
38186
|
+
const FOLLOW_RESUME_TAKEN = "__substrateResume(__substrateContext(),";
|
|
38187
|
+
function __substrateFollowAwaits(code2) {
|
|
38188
|
+
if (!/\bawait\b/.test(code2)) return code2;
|
|
38189
|
+
let ast;
|
|
38190
|
+
try {
|
|
38191
|
+
ast = parse3(code2, { ecmaVersion: "latest", sourceType: "script", allowReturnOutsideFunction: true, allowHashBang: true, allowAwaitOutsideFunction: true });
|
|
38192
|
+
} catch {
|
|
38193
|
+
return code2;
|
|
38194
|
+
}
|
|
38195
|
+
const edits = [];
|
|
38196
|
+
const insert = (at2, text) => {
|
|
38197
|
+
edits.push([at2, at2, text]);
|
|
38198
|
+
};
|
|
38199
|
+
const isFunction = (node) => node.type === "FunctionDeclaration" || node.type === "FunctionExpression" || node.type === "ArrowFunctionExpression";
|
|
38200
|
+
const visit = (node, framed, parent) => {
|
|
38201
|
+
if (isFunction(node)) {
|
|
38202
|
+
framed = node.async && node.body.type === "BlockStatement";
|
|
38203
|
+
if (framed) {
|
|
38204
|
+
const directives = node.body.body.filter((statement) => statement.directive !== void 0);
|
|
38205
|
+
const at2 = directives.length ? directives[directives.length - 1].end : node.body.start + 1;
|
|
38206
|
+
insert(at2, FOLLOW_TAKE);
|
|
38207
|
+
}
|
|
38208
|
+
} else if (node.type === "AwaitExpression") {
|
|
38209
|
+
insert(node.start, framed ? FOLLOW_RESUME_FRAMED : FOLLOW_RESUME_TAKEN);
|
|
38210
|
+
insert(node.end, `${FOLLOW_MARK})`);
|
|
38211
|
+
} else if (framed && node.type === "CatchClause") {
|
|
38212
|
+
insert(node.body.start + 1, FOLLOW_RESTORE);
|
|
38213
|
+
} else if (framed && node.type === "TryStatement" && node.finalizer) {
|
|
38214
|
+
insert(node.finalizer.start + 1, FOLLOW_RESTORE);
|
|
38215
|
+
} else if (framed && node.type === "ForOfStatement" && node.await) {
|
|
38216
|
+
const whole = (parent == null ? void 0 : parent.type) === "LabeledStatement" ? parent : node;
|
|
38217
|
+
insert(whole.start, `{${FOLLOW_MARK}`);
|
|
38218
|
+
insert(whole.end, `;${FOLLOW_RESTORE}${FOLLOW_MARK}}`);
|
|
38219
|
+
if (node.body.type === "BlockStatement") insert(node.body.start + 1, FOLLOW_RESTORE);
|
|
38220
|
+
else {
|
|
38221
|
+
insert(node.body.start, `{${FOLLOW_MARK}${FOLLOW_RESTORE}`);
|
|
38222
|
+
insert(node.body.end, `${FOLLOW_MARK}}`);
|
|
38223
|
+
}
|
|
38224
|
+
}
|
|
38225
|
+
for (const key of Object.keys(node)) {
|
|
38226
|
+
if (key === "type" || key === "start" || key === "end" || key === "loc" || key === "range") continue;
|
|
38227
|
+
const child = node[key];
|
|
38228
|
+
if (!child || typeof child !== "object") continue;
|
|
38229
|
+
if (Array.isArray(child)) {
|
|
38230
|
+
for (const item of child) if (item && typeof item.type === "string") visit(item, framed, node);
|
|
38231
|
+
} else if (typeof child.type === "string") visit(child, framed, node);
|
|
38232
|
+
}
|
|
38233
|
+
};
|
|
38234
|
+
visit(ast, false, void 0);
|
|
38235
|
+
return edits.length ? applyReplacements(code2, edits) : code2;
|
|
38236
|
+
}
|
|
38237
|
+
function __substrateUnfollowAwaits(source) {
|
|
38238
|
+
if (!source.includes("__substrate")) return source;
|
|
38239
|
+
return source.replaceAll(`;${FOLLOW_RESTORE}${FOLLOW_MARK}}`, "").replaceAll(`{${FOLLOW_MARK}${FOLLOW_RESTORE}`, "").replaceAll(`{${FOLLOW_MARK}`, "").replaceAll(`${FOLLOW_MARK}}`, "").replaceAll(`${FOLLOW_MARK})`, "").replaceAll(FOLLOW_TAKE, "").replaceAll(FOLLOW_RESTORE, "").replaceAll(FOLLOW_RESUME_FRAMED, "").replaceAll(FOLLOW_RESUME_TAKEN, "");
|
|
38240
|
+
}
|
|
38241
|
+
forGuestRealm(() => {
|
|
38242
|
+
const native = Function.prototype.toString;
|
|
38243
|
+
takeFromHost(Function.prototype, "toString", function toString2() {
|
|
38244
|
+
return __substrateUnfollowAwaits(native.call(this));
|
|
38245
|
+
});
|
|
38246
|
+
});
|
|
38187
38247
|
function applyReplacements(code2, replacements) {
|
|
38188
38248
|
replacements.sort((a, b) => b[0] - a[0] || b[1] - a[1]);
|
|
38189
38249
|
let out = code2;
|
|
@@ -38207,10 +38267,10 @@ function __substratePrepareBody(rawCode, resolvedPath, format2, process2) {
|
|
|
38207
38267
|
} else {
|
|
38208
38268
|
code2 = __substrateRewriteDynamicImportsInScript(code2);
|
|
38209
38269
|
}
|
|
38210
|
-
return code2;
|
|
38270
|
+
return __substrateFollowAwaits(code2);
|
|
38211
38271
|
}
|
|
38212
38272
|
const PREPARED_MODULES_DIR = "/.tabnode/prepared";
|
|
38213
|
-
const PREPARED_MODULES_FORMAT = "tabnode-prepared-
|
|
38273
|
+
const PREPARED_MODULES_FORMAT = "tabnode-prepared-2";
|
|
38214
38274
|
function preparedModuleKey(rawCode, resolvedPath) {
|
|
38215
38275
|
var _a2;
|
|
38216
38276
|
const extension = (_a2 = /\.(js|cjs|mjs)$/u.exec(resolvedPath)) == null ? void 0 : _a2[1];
|
|
@@ -40169,7 +40229,7 @@ class WorkerRuntime {
|
|
|
40169
40229
|
this.worker = new Worker(
|
|
40170
40230
|
new URL(
|
|
40171
40231
|
/* @vite-ignore */
|
|
40172
|
-
"/assets/runtime-worker-
|
|
40232
|
+
"/assets/runtime-worker-Dv_vTkXr.js",
|
|
40173
40233
|
import.meta.url
|
|
40174
40234
|
),
|
|
40175
40235
|
{ type: "module" }
|
|
@@ -1,35 +1,26 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* async_hooks shim
|
|
2
|
+
* async_hooks shim: AsyncLocalStorage and AsyncResource carry context the way
|
|
3
|
+
* Node's do; the hooks themselves are not available in a browser.
|
|
3
4
|
*/
|
|
4
|
-
export declare class AsyncResource {
|
|
5
|
-
constructor(_type: string, _options?: object);
|
|
6
|
-
runInAsyncScope<T>(fn: (...args: any[]) => T, thisArg?: unknown, ...args: any[]): T;
|
|
7
|
-
emitDestroy(): this;
|
|
8
|
-
asyncId(): number;
|
|
9
|
-
triggerAsyncId(): number;
|
|
10
|
-
static bind<T extends (...args: any[]) => any>(fn: T, _type?: string): T;
|
|
11
|
-
}
|
|
12
5
|
export declare class AsyncLocalStorage<T> {
|
|
13
|
-
private store;
|
|
14
|
-
constructor();
|
|
15
6
|
static snapshot(): (callback: (...args: unknown[]) => unknown, ...args: unknown[]) => unknown;
|
|
16
7
|
static bind(callback: (...args: unknown[]) => unknown): (...args: unknown[]) => unknown;
|
|
17
8
|
disable(): void;
|
|
18
9
|
getStore(): T | undefined;
|
|
19
|
-
/**
|
|
20
|
-
* A store entered for an async callback used to be left the moment the
|
|
21
|
-
* callback returned its promise, so the first `await` inside a Server Action
|
|
22
|
-
* lost Next's request store and `cookies()` was "outside a request scope". A
|
|
23
|
-
* tab has no async_hooks and no AsyncContext, and an `await` of a native
|
|
24
|
-
* promise bypasses any `then` a patch could install, so the continuations
|
|
25
|
-
* cannot be followed. What can be done honestly is keep the store current
|
|
26
|
-
* until the callback's promise settles: exact for one run at a time, and
|
|
27
|
-
* last-entered-wins where two async runs overlap.
|
|
28
|
-
*/
|
|
29
10
|
run<R>(store: T, callback: (...args: unknown[]) => R, ...args: unknown[]): R;
|
|
30
11
|
exit<R>(callback: () => R): R;
|
|
31
12
|
enterWith(store: T): void;
|
|
32
13
|
}
|
|
14
|
+
/** Runs its callbacks in the context it was made in, as Node's does. */
|
|
15
|
+
export declare class AsyncResource {
|
|
16
|
+
private readonly frame;
|
|
17
|
+
constructor(_type: string, _options?: object);
|
|
18
|
+
runInAsyncScope<T>(fn: (...args: any[]) => T, thisArg?: unknown, ...args: any[]): T;
|
|
19
|
+
emitDestroy(): this;
|
|
20
|
+
asyncId(): number;
|
|
21
|
+
triggerAsyncId(): number;
|
|
22
|
+
static bind<T extends (...args: any[]) => any>(fn: T, _type?: string): T;
|
|
23
|
+
}
|
|
33
24
|
export interface AsyncHook {
|
|
34
25
|
enable(): this;
|
|
35
26
|
disable(): this;
|
package/package.json
CHANGED
package/src/runtime.ts
CHANGED
|
@@ -436,6 +436,97 @@ function collectDirectEval(node: any, replacements: Array<[number, number, strin
|
|
|
436
436
|
replacements.push([source.end, source.end, ")"]);
|
|
437
437
|
}
|
|
438
438
|
|
|
439
|
+
/**
|
|
440
|
+
* Node's AsyncLocalStorage context follows an `await`; in a tab nothing can
|
|
441
|
+
* observe one (see shims/async_hooks), so the body follows it itself. An async
|
|
442
|
+
* function takes its frame on entry and puts it back wherever it resumes:
|
|
443
|
+
* after each `await`, at the start of a `catch` or `finally` (a rejected
|
|
444
|
+
* `await` resumes there), and in and after a `for await`. An async arrow with
|
|
445
|
+
* an expression body takes the frame at each `await`, which is the same
|
|
446
|
+
* frame. Playwright names each API call from a zone it keeps in one; a zone
|
|
447
|
+
* left over from another call made its errors lose `locator.click:`. Only
|
|
448
|
+
* insertions, and no line breaks, so a stack frame keeps its line.
|
|
449
|
+
*/
|
|
450
|
+
const ASYNC_FRAME = '__substrateAsyncFrame';
|
|
451
|
+
// Every fragment the pass inserts is one of these strings or ends in the
|
|
452
|
+
// marker, so a function's source can be given back as it was written (below).
|
|
453
|
+
const FOLLOW_MARK = '/*substrate*/';
|
|
454
|
+
const FOLLOW_TAKE = `const ${ASYNC_FRAME}=__substrateContext();`;
|
|
455
|
+
const FOLLOW_RESTORE = `__substrateRestore(${ASYNC_FRAME});`;
|
|
456
|
+
const FOLLOW_RESUME_FRAMED = `__substrateResume(${ASYNC_FRAME},`;
|
|
457
|
+
const FOLLOW_RESUME_TAKEN = '__substrateResume(__substrateContext(),';
|
|
458
|
+
function __substrateFollowAwaits(code: string): string {
|
|
459
|
+
if (!/\bawait\b/.test(code)) return code;
|
|
460
|
+
let ast;
|
|
461
|
+
try {
|
|
462
|
+
ast = acorn.parse(code, { ecmaVersion: "latest", sourceType: "script", allowReturnOutsideFunction: true, allowHashBang: true, allowAwaitOutsideFunction: true });
|
|
463
|
+
} catch {
|
|
464
|
+
return code;
|
|
465
|
+
}
|
|
466
|
+
const edits: Array<[number, number, string]> = [];
|
|
467
|
+
const insert = (at: number, text: string): void => { edits.push([at, at, text]); };
|
|
468
|
+
const isFunction = (node: any): boolean => node.type === 'FunctionDeclaration' || node.type === 'FunctionExpression' || node.type === 'ArrowFunctionExpression';
|
|
469
|
+
// `framed`: the nearest function is async and holds the frame in ASYNC_FRAME.
|
|
470
|
+
const visit = (node: any, framed: boolean, parent: any): void => {
|
|
471
|
+
if (isFunction(node)) {
|
|
472
|
+
framed = node.async && node.body.type === 'BlockStatement';
|
|
473
|
+
if (framed) {
|
|
474
|
+
const directives = node.body.body.filter((statement: any) => statement.directive !== undefined);
|
|
475
|
+
const at = directives.length ? directives[directives.length - 1].end : node.body.start + 1;
|
|
476
|
+
insert(at, FOLLOW_TAKE);
|
|
477
|
+
}
|
|
478
|
+
} else if (node.type === 'AwaitExpression') {
|
|
479
|
+
insert(node.start, framed ? FOLLOW_RESUME_FRAMED : FOLLOW_RESUME_TAKEN);
|
|
480
|
+
insert(node.end, `${FOLLOW_MARK})`);
|
|
481
|
+
} else if (framed && node.type === 'CatchClause') {
|
|
482
|
+
insert(node.body.start + 1, FOLLOW_RESTORE);
|
|
483
|
+
} else if (framed && node.type === 'TryStatement' && node.finalizer) {
|
|
484
|
+
insert(node.finalizer.start + 1, FOLLOW_RESTORE);
|
|
485
|
+
} else if (framed && node.type === 'ForOfStatement' && node.await) {
|
|
486
|
+
// The loop's own wrap first: where its end is the body's, an insertion
|
|
487
|
+
// made later lands before it, so the body's brace closes inside.
|
|
488
|
+
const whole = parent?.type === 'LabeledStatement' ? parent : node;
|
|
489
|
+
insert(whole.start, `{${FOLLOW_MARK}`);
|
|
490
|
+
insert(whole.end, `;${FOLLOW_RESTORE}${FOLLOW_MARK}}`);
|
|
491
|
+
if (node.body.type === 'BlockStatement') insert(node.body.start + 1, FOLLOW_RESTORE);
|
|
492
|
+
else { insert(node.body.start, `{${FOLLOW_MARK}${FOLLOW_RESTORE}`); insert(node.body.end, `${FOLLOW_MARK}}`); }
|
|
493
|
+
}
|
|
494
|
+
for (const key of Object.keys(node)) {
|
|
495
|
+
if (key === 'type' || key === 'start' || key === 'end' || key === 'loc' || key === 'range') continue;
|
|
496
|
+
const child = node[key];
|
|
497
|
+
if (!child || typeof child !== 'object') continue;
|
|
498
|
+
if (Array.isArray(child)) {
|
|
499
|
+
for (const item of child) if (item && typeof item.type === 'string') visit(item, framed, node);
|
|
500
|
+
} else if (typeof child.type === 'string') visit(child, framed, node);
|
|
501
|
+
}
|
|
502
|
+
};
|
|
503
|
+
visit(ast, false, undefined);
|
|
504
|
+
return edits.length ? applyReplacements(code, edits) : code;
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
/**
|
|
508
|
+
* A function's source as it was written. Playwright sends the source of the
|
|
509
|
+
* function a test evaluates to the browser, where the pass's calls do not
|
|
510
|
+
* exist; the pass only inserts, so removing its fragments gives the text back.
|
|
511
|
+
*/
|
|
512
|
+
function __substrateUnfollowAwaits(source: string): string {
|
|
513
|
+
if (!source.includes('__substrate')) return source;
|
|
514
|
+
return source
|
|
515
|
+
.replaceAll(`;${FOLLOW_RESTORE}${FOLLOW_MARK}}`, '')
|
|
516
|
+
.replaceAll(`{${FOLLOW_MARK}${FOLLOW_RESTORE}`, '')
|
|
517
|
+
.replaceAll(`{${FOLLOW_MARK}`, '')
|
|
518
|
+
.replaceAll(`${FOLLOW_MARK}}`, '')
|
|
519
|
+
.replaceAll(`${FOLLOW_MARK})`, '')
|
|
520
|
+
.replaceAll(FOLLOW_TAKE, '')
|
|
521
|
+
.replaceAll(FOLLOW_RESTORE, '')
|
|
522
|
+
.replaceAll(FOLLOW_RESUME_FRAMED, '')
|
|
523
|
+
.replaceAll(FOLLOW_RESUME_TAKEN, '');
|
|
524
|
+
}
|
|
525
|
+
forGuestRealm(() => {
|
|
526
|
+
const native = Function.prototype.toString;
|
|
527
|
+
takeFromHost(Function.prototype, 'toString', function toString(this: unknown): string { return __substrateUnfollowAwaits(native.call(this)); });
|
|
528
|
+
});
|
|
529
|
+
|
|
439
530
|
/** Applies insertions and replacements from the end, so earlier offsets hold. */
|
|
440
531
|
function applyReplacements(code: string, replacements: Array<[number, number, string]>): string {
|
|
441
532
|
replacements.sort((a, b) => b[0] - a[0] || b[1] - a[1]);
|
|
@@ -448,7 +539,7 @@ Object.defineProperty(globalThis, "__substrateEvalSource", {
|
|
|
448
539
|
configurable: true,
|
|
449
540
|
value: (source: unknown): unknown => typeof source === "string" ? __substrateRewriteDynamicImportsInScript(source) : source,
|
|
450
541
|
});
|
|
451
|
-
/** A file's source made a body: shebang stripped, types stripped, ESM lowered, dynamic imports rewritten. */
|
|
542
|
+
/** A file's source made a body: shebang stripped, types stripped, ESM lowered, dynamic imports rewritten, awaits followed. */
|
|
452
543
|
function __substratePrepareBody(rawCode: string, resolvedPath: string, format: string | undefined, process: unknown): string {
|
|
453
544
|
let code = rawCode;
|
|
454
545
|
|
|
@@ -487,7 +578,7 @@ function __substratePrepareBody(rawCode: string, resolvedPath: string, format: s
|
|
|
487
578
|
code = __substrateRewriteDynamicImportsInScript(code);
|
|
488
579
|
}
|
|
489
580
|
|
|
490
|
-
return code;
|
|
581
|
+
return __substrateFollowAwaits(code);
|
|
491
582
|
}
|
|
492
583
|
|
|
493
584
|
/**
|
|
@@ -499,7 +590,7 @@ function __substratePrepareBody(rawCode: string, resolvedPath: string, format: s
|
|
|
499
590
|
* place of the passes when the file it read hashes to one.
|
|
500
591
|
*/
|
|
501
592
|
export const PREPARED_MODULES_DIR = '/.tabnode/prepared';
|
|
502
|
-
const PREPARED_MODULES_FORMAT = 'tabnode-prepared-
|
|
593
|
+
const PREPARED_MODULES_FORMAT = 'tabnode-prepared-2';
|
|
503
594
|
/** The name a prepared body goes under: the hash of the file as read, and how it is compiled. Undefined for a file no body is prepared for. */
|
|
504
595
|
export function preparedModuleKey(rawCode: string, resolvedPath: string): string | undefined {
|
|
505
596
|
const extension = /\.(js|cjs|mjs)$/u.exec(resolvedPath)?.[1];
|
package/src/shims/async_hooks.ts
CHANGED
|
@@ -1,109 +1,105 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* async_hooks shim
|
|
2
|
+
* async_hooks shim: AsyncLocalStorage and AsyncResource carry context the way
|
|
3
|
+
* Node's do; the hooks themselves are not available in a browser.
|
|
3
4
|
*/
|
|
4
5
|
|
|
5
6
|
import { forGuestRealm, takeFromHost } from '../host-globals';
|
|
6
7
|
|
|
7
|
-
export class AsyncResource {
|
|
8
|
-
constructor(_type: string, _options?: object) {}
|
|
9
|
-
|
|
10
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
11
|
-
runInAsyncScope<T>(fn: (...args: any[]) => T, thisArg?: unknown, ...args: any[]): T {
|
|
12
|
-
return fn.apply(thisArg, args);
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
emitDestroy(): this { return this; }
|
|
16
|
-
asyncId(): number { return 0; }
|
|
17
|
-
triggerAsyncId(): number { return 0; }
|
|
18
|
-
|
|
19
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
20
|
-
static bind<T extends (...args: any[]) => any>(fn: T, _type?: string): T {
|
|
21
|
-
return fn;
|
|
22
|
-
}
|
|
23
|
-
}
|
|
24
|
-
|
|
25
8
|
/**
|
|
26
|
-
*
|
|
27
|
-
*
|
|
28
|
-
*
|
|
29
|
-
*
|
|
30
|
-
*
|
|
31
|
-
*
|
|
9
|
+
* The context a continuation runs in: every storage's store, as one frame.
|
|
10
|
+
* `run` makes a new frame for its callback and puts the old one back when the
|
|
11
|
+
* callback returns, as Node's does; a frame is never changed after it is
|
|
12
|
+
* made, so a snapshot is the frame itself.
|
|
13
|
+
*
|
|
14
|
+
* A tab has no async hooks and no AsyncContext, and an `await` of a native
|
|
15
|
+
* promise bypasses any `then` a patch installs. The engine follows it at the
|
|
16
|
+
* source instead: each module body it compiles captures its async function's
|
|
17
|
+
* frame on entry and restores it where the function resumes (after each
|
|
18
|
+
* `await`, in a `catch` or `finally`, around a `for await`), through
|
|
19
|
+
* `__substrateResume` and `__substrateRestore` below. A continuation's frame
|
|
20
|
+
* holds only for the turn it runs in; the next turn starts from the root, as
|
|
21
|
+
* a macrotask of Node's does. Code the engine did not compile (its own) runs
|
|
22
|
+
* in whichever frame is current when it is called.
|
|
32
23
|
*/
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
24
|
+
type ContextFrame = ReadonlyMap<AsyncLocalStorage<unknown>, unknown>;
|
|
25
|
+
const ROOT_FRAME: ContextFrame = new Map();
|
|
26
|
+
let currentFrame: ContextFrame = ROOT_FRAME;
|
|
27
|
+
const nativeQueueMicrotask = globalThis.queueMicrotask.bind(globalThis);
|
|
28
|
+
let resetQueued = false;
|
|
29
|
+
const resetFrame = (): void => { resetQueued = false; currentFrame = ROOT_FRAME; };
|
|
30
|
+
/** A resumed continuation's frame, current until the turn it runs in ends. */
|
|
31
|
+
const resumeFrame = (frame: ContextFrame): void => {
|
|
32
|
+
if (frame === currentFrame) return;
|
|
33
|
+
currentFrame = frame;
|
|
34
|
+
if (!resetQueued) { resetQueued = true; nativeQueueMicrotask(resetFrame); }
|
|
35
|
+
};
|
|
36
|
+
const withFrame = <R>(frame: ContextFrame, callback: () => R): R => {
|
|
37
|
+
const previous = currentFrame;
|
|
38
|
+
currentFrame = frame;
|
|
39
|
+
try {
|
|
40
|
+
return callback();
|
|
41
|
+
} finally {
|
|
42
|
+
currentFrame = previous;
|
|
40
43
|
}
|
|
44
|
+
};
|
|
41
45
|
|
|
46
|
+
export class AsyncLocalStorage<T> {
|
|
42
47
|
static snapshot() {
|
|
43
|
-
const captured =
|
|
44
|
-
|
|
45
|
-
return (callback: (...args: unknown[]) => unknown, ...args: unknown[]) => {
|
|
46
|
-
const previous = new Map<AsyncLocalStorage<unknown>, unknown>();
|
|
47
|
-
for (const [storage, store] of captured) { previous.set(storage, storage.store); storage.store = store; }
|
|
48
|
-
try {
|
|
49
|
-
return callback(...args);
|
|
50
|
-
} finally {
|
|
51
|
-
for (const [storage, store] of previous) storage.store = store;
|
|
52
|
-
}
|
|
53
|
-
};
|
|
48
|
+
const captured = currentFrame;
|
|
49
|
+
return (callback: (...args: unknown[]) => unknown, ...args: unknown[]) => withFrame(captured, () => callback(...args));
|
|
54
50
|
}
|
|
55
51
|
static bind(callback: (...args: unknown[]) => unknown) {
|
|
56
52
|
const snapshot = AsyncLocalStorage.snapshot();
|
|
57
53
|
return (...args: unknown[]) => snapshot(callback, ...args);
|
|
58
54
|
}
|
|
59
55
|
|
|
60
|
-
disable(): void {
|
|
56
|
+
disable(): void {
|
|
57
|
+
if (!currentFrame.has(this as AsyncLocalStorage<unknown>)) return;
|
|
58
|
+
const next = new Map(currentFrame);
|
|
59
|
+
next.delete(this as AsyncLocalStorage<unknown>);
|
|
60
|
+
currentFrame = next;
|
|
61
|
+
}
|
|
61
62
|
|
|
62
63
|
getStore(): T | undefined {
|
|
63
|
-
return this
|
|
64
|
+
return currentFrame.get(this as AsyncLocalStorage<unknown>) as T | undefined;
|
|
64
65
|
}
|
|
65
66
|
|
|
66
|
-
/**
|
|
67
|
-
* A store entered for an async callback used to be left the moment the
|
|
68
|
-
* callback returned its promise, so the first `await` inside a Server Action
|
|
69
|
-
* lost Next's request store and `cookies()` was "outside a request scope". A
|
|
70
|
-
* tab has no async_hooks and no AsyncContext, and an `await` of a native
|
|
71
|
-
* promise bypasses any `then` a patch could install, so the continuations
|
|
72
|
-
* cannot be followed. What can be done honestly is keep the store current
|
|
73
|
-
* until the callback's promise settles: exact for one run at a time, and
|
|
74
|
-
* last-entered-wins where two async runs overlap.
|
|
75
|
-
*/
|
|
76
67
|
run<R>(store: T, callback: (...args: unknown[]) => R, ...args: unknown[]): R {
|
|
77
|
-
const
|
|
78
|
-
this
|
|
79
|
-
|
|
80
|
-
try {
|
|
81
|
-
result = callback(...args);
|
|
82
|
-
} catch (error) {
|
|
83
|
-
this.store = prev;
|
|
84
|
-
throw error;
|
|
85
|
-
}
|
|
86
|
-
if (result && typeof (result as { then?: unknown }).then === 'function') {
|
|
87
|
-
const restore = () => { if (this.store === store) this.store = prev; };
|
|
88
|
-
(result as unknown as Promise<unknown>).then(restore, restore);
|
|
89
|
-
return result;
|
|
90
|
-
}
|
|
91
|
-
this.store = prev;
|
|
92
|
-
return result;
|
|
68
|
+
const next = new Map(currentFrame);
|
|
69
|
+
next.set(this as AsyncLocalStorage<unknown>, store);
|
|
70
|
+
return withFrame(next, () => callback(...args));
|
|
93
71
|
}
|
|
94
72
|
|
|
95
73
|
exit<R>(callback: () => R): R {
|
|
96
|
-
const
|
|
97
|
-
this
|
|
98
|
-
|
|
99
|
-
return callback();
|
|
100
|
-
} finally {
|
|
101
|
-
this.store = prev;
|
|
102
|
-
}
|
|
74
|
+
const next = new Map(currentFrame);
|
|
75
|
+
next.delete(this as AsyncLocalStorage<unknown>);
|
|
76
|
+
return withFrame(next, callback);
|
|
103
77
|
}
|
|
104
78
|
|
|
105
79
|
enterWith(store: T): void {
|
|
106
|
-
|
|
80
|
+
const next = new Map(currentFrame);
|
|
81
|
+
next.set(this as AsyncLocalStorage<unknown>, store);
|
|
82
|
+
currentFrame = next;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/** Runs its callbacks in the context it was made in, as Node's does. */
|
|
87
|
+
export class AsyncResource {
|
|
88
|
+
private readonly frame = currentFrame;
|
|
89
|
+
constructor(_type: string, _options?: object) {}
|
|
90
|
+
|
|
91
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
92
|
+
runInAsyncScope<T>(fn: (...args: any[]) => T, thisArg?: unknown, ...args: any[]): T {
|
|
93
|
+
return withFrame(this.frame, () => fn.apply(thisArg, args));
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
emitDestroy(): this { return this; }
|
|
97
|
+
asyncId(): number { return 0; }
|
|
98
|
+
triggerAsyncId(): number { return 0; }
|
|
99
|
+
|
|
100
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
101
|
+
static bind<T extends (...args: any[]) => any>(fn: T, _type?: string): T {
|
|
102
|
+
return AsyncLocalStorage.bind(fn) as T;
|
|
107
103
|
}
|
|
108
104
|
}
|
|
109
105
|
|
|
@@ -112,10 +108,10 @@ export class AsyncLocalStorage<T> {
|
|
|
112
108
|
* schedules: a timer, an immediate, a microtask, a nextTick, a promise
|
|
113
109
|
* callback. A tab has no async hooks, so each of those is wrapped once to
|
|
114
110
|
* capture the stores current when it was scheduled and restore them when it
|
|
115
|
-
* runs. A native `await` is the one continuation this cannot follow
|
|
116
|
-
*
|
|
117
|
-
* server renderer schedules its work on immediates and promises, and
|
|
118
|
-
* Next's request store through them.
|
|
111
|
+
* runs. A native `await` is the one continuation this cannot follow: the
|
|
112
|
+
* compiled module body follows it, through the three globals installed here.
|
|
113
|
+
* React's server renderer schedules its work on immediates and promises, and
|
|
114
|
+
* read Next's request store through them.
|
|
119
115
|
*
|
|
120
116
|
* The wrapping is a guest's, not the realm's. It used to run at load, so
|
|
121
117
|
* importing the engine into a Node process replaced that process's own
|
|
@@ -145,6 +141,10 @@ forGuestRealm(() => {
|
|
|
145
141
|
): Promise<TFulfilled | TRejected> { return then.call(this, carried(onFulfilled), carried(onRejected)) as Promise<TFulfilled | TRejected>; };
|
|
146
142
|
takeFromHost(Promise.prototype, 'then', carriedThen);
|
|
147
143
|
takeFromHost(globalThis, '__substrateCarried', carried);
|
|
144
|
+
// An async function's frame, taken on entry; put back where it resumes.
|
|
145
|
+
takeFromHost(globalThis, '__substrateContext', () => currentFrame);
|
|
146
|
+
takeFromHost(globalThis, '__substrateResume', <V>(frame: ContextFrame, value: V): V => { resumeFrame(frame); return value; });
|
|
147
|
+
takeFromHost(globalThis, '__substrateRestore', (frame: ContextFrame): void => { resumeFrame(frame); });
|
|
148
148
|
});
|
|
149
149
|
|
|
150
150
|
export interface AsyncHook {
|