@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.
@@ -6521,99 +6521,85 @@ forGuestRealm(() => {
6521
6521
  const realm = globalThis;
6522
6522
  if (!realm.__browserRuntimeHeldWork) takeFromHost(globalThis, "__browserRuntimeHeldWork", ownHeldWork);
6523
6523
  });
6524
- class AsyncResource {
6525
- constructor(_type2, _options3) {
6526
- }
6527
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
6528
- runInAsyncScope(fn2, thisArg, ...args) {
6529
- return fn2.apply(thisArg, args);
6530
- }
6531
- emitDestroy() {
6532
- return this;
6533
- }
6534
- asyncId() {
6535
- return 0;
6536
- }
6537
- triggerAsyncId() {
6538
- return 0;
6539
- }
6540
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
6541
- static bind(fn2, _type2) {
6542
- return fn2;
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 = /* @__PURE__ */ new Map();
6553
- for (const storage of __substrateAsyncLocalStorages) captured.set(storage, storage.store);
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.store;
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 prev = this.store;
6588
- this.store = store;
6589
- let result;
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 prev = this.store;
6608
- this.store = void 0;
6609
- try {
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
- this.store = store;
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-1";
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
- class AsyncResource {
6206
- constructor(_type2, _options2) {
6207
- }
6208
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
6209
- runInAsyncScope(fn, thisArg, ...args) {
6210
- return fn.apply(thisArg, args);
6211
- }
6212
- emitDestroy() {
6213
- return this;
6214
- }
6215
- asyncId() {
6216
- return 0;
6217
- }
6218
- triggerAsyncId() {
6219
- return 0;
6220
- }
6221
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
6222
- static bind(fn, _type2) {
6223
- return fn;
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 = /* @__PURE__ */ new Map();
6234
- for (const storage of __substrateAsyncLocalStorages) captured.set(storage, storage.store);
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.store;
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 prev = this.store;
6269
- this.store = store;
6270
- let result;
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 prev = this.store;
6289
- this.store = void 0;
6290
- try {
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
- this.store = store;
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-1";
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-C6ulimZ4.js",
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 - Async tracking is not available in browser
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@volter/tabnode",
3
- "version": "0.5.28",
3
+ "version": "0.5.29",
4
4
  "description": "Node's own library, in a browser tab.",
5
5
  "type": "module",
6
6
  "license": "MIT",
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-1';
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];
@@ -1,109 +1,105 @@
1
1
  /**
2
- * async_hooks shim - Async tracking is not available in browser
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
- * `AsyncLocalStorage.snapshot()` captures every storage's current store and
27
- * answers a function that runs a call inside that capture, which Next's
28
- * request-store constructors use to carry a request's context into the work
29
- * they schedule. These storages are store slots with no such capture; every
30
- * storage registers itself, and a snapshot is the slots as they are, restored
31
- * around the call and put back after.
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
- const __substrateAsyncLocalStorages = new Set<AsyncLocalStorage<unknown>>();
34
-
35
- export class AsyncLocalStorage<T> {
36
- private store: T | undefined;
37
-
38
- constructor() {
39
- __substrateAsyncLocalStorages.add(this as AsyncLocalStorage<unknown>);
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 = new Map<AsyncLocalStorage<unknown>, unknown>();
44
- for (const storage of __substrateAsyncLocalStorages) captured.set(storage, storage.store);
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.store;
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 prev = this.store;
78
- this.store = store;
79
- let result;
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 prev = this.store;
97
- this.store = undefined;
98
- try {
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
- this.store = store;
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, which
116
- * `run` covers by holding its store until the callback settles. React's
117
- * server renderer schedules its work on immediates and promises, and read
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 {