octane 0.16.3 → 0.16.4

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 (71) hide show
  1. package/README.md +1 -1
  2. package/dist/cjs/dom-focused-move.cjs +4 -2
  3. package/dist/cjs/error-codes.client.generated.cjs +5 -0
  4. package/dist/cjs/runtime/drivers.cjs +7 -4
  5. package/dist/cjs/runtime/effects.cjs +22 -0
  6. package/dist/cjs/runtime/engine.cjs +50 -7
  7. package/dist/cjs/runtime/hooks.cjs +6 -0
  8. package/dist/cjs/runtime/suspense.cjs +7 -2
  9. package/dist/cjs/runtime/transitions.cjs +14 -4
  10. package/dist/cjs/runtime.server.cjs +1 -2
  11. package/dist/cjs/version.cjs +1 -1
  12. package/dist/compiler/arm-exits.js +48 -1
  13. package/dist/compiler/bare-holes.js +84 -0
  14. package/dist/compiler/bundler.js +29 -22
  15. package/dist/compiler/compile-universal.js +114 -16
  16. package/dist/compiler/compile-valdi.js +161 -39
  17. package/dist/compiler/compile.js +245 -145
  18. package/dist/compiler/descriptor-lowering.js +29 -0
  19. package/dist/compiler/for-headers.js +9 -3
  20. package/dist/compiler/hook-deps.js +5 -2
  21. package/dist/compiler/hook-methods.js +5 -2
  22. package/dist/compiler/native-attribute-reads.js +10 -4
  23. package/dist/compiler/native-read-diagnostics.js +4 -1
  24. package/dist/compiler/server-context.js +5 -2
  25. package/dist/compiler/signal-attempt-reads.js +12 -6
  26. package/dist/compiler/volar.js +126 -15
  27. package/dist/context-identity.d.ts +15 -0
  28. package/dist/dom-focused-move.d.ts +6 -0
  29. package/dist/dom-focused-move.js +2 -1
  30. package/dist/error-codes.client.generated.d.ts +1 -0
  31. package/dist/error-codes.client.generated.js +5 -0
  32. package/dist/node/dom-focused-move.js +2 -1
  33. package/dist/node/error-codes.client.generated.js +5 -0
  34. package/dist/node/react/index.js +2 -0
  35. package/dist/node/react/react-compat.js +16 -2
  36. package/dist/node/react/server.js +2 -0
  37. package/dist/node/react/shared.js +2 -0
  38. package/dist/node/runtime/drivers.js +8 -4
  39. package/dist/node/runtime/effects.js +21 -0
  40. package/dist/node/runtime/engine.js +49 -7
  41. package/dist/node/runtime/hooks.js +5 -0
  42. package/dist/node/runtime/suspense.js +7 -2
  43. package/dist/node/runtime/transitions.js +16 -4
  44. package/dist/node/runtime.server.js +5 -3
  45. package/dist/node/tsrx-boundary.js +3 -1
  46. package/dist/node/version.js +1 -1
  47. package/dist/react/index.js +2 -0
  48. package/dist/react/react-compat-shared.d.ts +1 -1
  49. package/dist/react/react-compat.js +16 -2
  50. package/dist/react/server.js +2 -0
  51. package/dist/react/shared.d.ts +8 -0
  52. package/dist/react/shared.js +2 -0
  53. package/dist/runtime/drivers.js +8 -4
  54. package/dist/runtime/effects.d.ts +1 -0
  55. package/dist/runtime/effects.js +21 -0
  56. package/dist/runtime/engine.d.ts +10 -1
  57. package/dist/runtime/engine.js +49 -7
  58. package/dist/runtime/hooks.d.ts +14 -7
  59. package/dist/runtime/hooks.js +5 -0
  60. package/dist/runtime/suspense.d.ts +6 -5
  61. package/dist/runtime/suspense.js +7 -2
  62. package/dist/runtime/transitions.d.ts +11 -7
  63. package/dist/runtime/transitions.js +16 -4
  64. package/dist/runtime.server.d.ts +13 -18
  65. package/dist/runtime.server.js +5 -3
  66. package/dist/strong.d.ts +91 -0
  67. package/dist/tsrx-boundary.d.ts +8 -0
  68. package/dist/tsrx-boundary.js +3 -1
  69. package/dist/universal-core.d.ts +6 -0
  70. package/dist/version.js +1 -1
  71. package/package.json +6 -3
package/README.md CHANGED
@@ -21,7 +21,7 @@ To use the compiler in Node, including through the Vite, Rspack, or Rsbuild
21
21
  integrations, install its optional parser peer in your project:
22
22
 
23
23
  ```bash
24
- pnpm add @tsrx/oxc@0.20.0
24
+ pnpm add @tsrx/oxc@0.23.0
25
25
  ```
26
26
 
27
27
  Applications that use only the Octane runtime do not need this peer.
@@ -19,7 +19,8 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
19
19
  var dom_focused_move_exports = {};
20
20
  __export(dom_focused_move_exports, {
21
21
  isChildOf: () => isChildOf,
22
- moveNativeNodeBefore: () => moveNativeNodeBefore
22
+ moveNativeNodeBefore: () => moveNativeNodeBefore,
23
+ nodeProto: () => nodeProto
23
24
  });
24
25
  module.exports = __toCommonJS(dom_focused_move_exports);
25
26
  function moveNativeNodeBefore(parent, node, anchor, focused, contentEditable) {
@@ -96,5 +97,6 @@ function containsFocus(node, focused) {
96
97
  // Annotate the CommonJS export names for ESM import in node:
97
98
  0 && (module.exports = {
98
99
  isChildOf,
99
- moveNativeNodeBefore
100
+ moveNativeNodeBefore,
101
+ nodeProto
100
102
  });
@@ -1128,6 +1128,11 @@ function formatClientError(code, ...args) {
1128
1128
  "This container already has a behavior root; pass replace: true to replace it.",
1129
1129
  args
1130
1130
  );
1131
+ case 362:
1132
+ return (0, import_error_message.formatDevErrorMessage)(
1133
+ "A hook was called outside a component render. If the call is inside a component, the app probably loads more than one copy of Octane, possibly with the same version. Check that the renderer (`createRoot`), the hooks, and `octane/internal/client` all resolve to one installed `octane` package; a custom bundler may need to deduplicate `octane` and its subpaths.",
1134
+ args
1135
+ );
1131
1136
  default:
1132
1137
  return (0, import_error_message.formatUnknownDevErrorMessage)(code);
1133
1138
  }
@@ -100,7 +100,7 @@ module.exports = __toCommonJS(drivers_exports);
100
100
  var import_hydration_flag = require("../hydration-flag.cjs");
101
101
  var import_event_names = require("../event-names.cjs");
102
102
  var import_devtools_hook = require("../devtools-hook.cjs");
103
- var import_error_message_no_arguments = require("../error-message-no-arguments.cjs");
103
+ var import_error_codes_client_generated = require("../error-codes.client.generated.cjs");
104
104
  var import_native_read_client = require("../signals/native-read-client.cjs");
105
105
  var import_read_protocol = require("../signals/read-protocol.cjs");
106
106
  var import_owner_context = require("../signals/owner-context.cjs");
@@ -232,7 +232,10 @@ function installEffectDrivers() {
232
232
  schedulePassiveFlush = import_effects.scheduleRegisteredPassiveFlush;
233
233
  }
234
234
  let NATIVE_READ_DRIVER = null;
235
- let resolveSlot = (slot) => slot;
235
+ let resolveSlot = (slot) => {
236
+ if (__octaneDev && slot !== void 0) (0, import_hooks.checkHookOwner)();
237
+ return slot;
238
+ };
236
239
  let CONTROLLED_RESTORE_DRIVER = null;
237
240
  let drainControlledSyncs = noop;
238
241
  let publishManualFormPending = noop;
@@ -277,7 +280,7 @@ let reportUncaughtError = () => false;
277
280
  let publishInlineCaughtErrorReports = noop;
278
281
  let SIGNAL_BINDINGS_ENABLED = false;
279
282
  function enableSignalBindings(abi = 1, potentialOnly = false) {
280
- if (abi !== 1) throw new TypeError(__octaneDev ? "Unsupported Octane signal binding ABI." : (0, import_error_message_no_arguments.formatNoArgumentErrorMessage)(74));
283
+ if (abi !== 1) throw new TypeError((0, import_error_codes_client_generated.formatClientError)(74));
281
284
  SIGNAL_BINDINGS_ENABLED = true;
282
285
  import_owner_context.signalOwnerUse.active = true;
283
286
  SIGNAL_CHILD = import_signal_bindings.bindImplicitSignalChild;
@@ -448,7 +451,7 @@ function useEffectEvent(fn, slot) {
448
451
  const cell = s;
449
452
  return ((...args) => {
450
453
  if (import_render.CURRENT_SCOPE !== null && import_engine.EFFECT_EVENT_LIFECYCLE_DEPTH === 0) {
451
- throw new Error(__octaneDev ? "A function wrapped in useEffectEvent can't be called during rendering." : (0, import_error_message_no_arguments.formatNoArgumentErrorMessage)(11));
454
+ throw new Error((0, import_error_codes_client_generated.formatClientError)(11));
452
455
  }
453
456
  EFFECT_EVENT_INVOCATION_DEPTH++;
454
457
  try {
@@ -41,6 +41,7 @@ __export(effects_exports, {
41
41
  nativeEffectPublicationCurrent: () => nativeEffectPublicationCurrent,
42
42
  passiveScheduled: () => passiveScheduled,
43
43
  pendingPassiveUnmounts: () => pendingPassiveUnmounts,
44
+ postOrderHostRefs: () => postOrderHostRefs,
44
45
  releaseDeferredPassives: () => releaseDeferredPassives,
45
46
  renderWritesForbidden: () => renderWritesForbidden,
46
47
  restoreDeferredPassives: () => restoreDeferredPassives,
@@ -49,6 +50,7 @@ __export(effects_exports, {
49
50
  });
50
51
  module.exports = __toCommonJS(effects_exports);
51
52
  var import_engine = require("./engine.cjs");
53
+ var import_dom_focused_move = require("../dom-focused-move.cjs");
52
54
  var import_hydration_flag = require("../hydration-flag.cjs");
53
55
  var import_profiling = require("../profiling.cjs");
54
56
  var import_drivers = require("./drivers.cjs");
@@ -169,6 +171,25 @@ function compareEffectPostOrder(a, b) {
169
171
  function compareRefPostOrder(a, b) {
170
172
  return comparePostOrder(a.block, 0, b.block, 0);
171
173
  }
174
+ function postOrderHostRefs(q) {
175
+ for (let i = 1; i < q.length; i++) {
176
+ const entry = q[i];
177
+ let j = i;
178
+ while (j > 0 && q[j - 1].block === entry.block && compareHostRefs(q[j - 1], entry) > 0) {
179
+ q[j] = q[j - 1];
180
+ j--;
181
+ }
182
+ q[j] = entry;
183
+ }
184
+ }
185
+ function compareHostRefs(a, b) {
186
+ const from = refPosition(a);
187
+ const position = (0, import_dom_focused_move.nodeProto)(from).compareDocumentPosition.call(from, refPosition(b));
188
+ return position & 16 || (position & 10) === 2 ? 1 : position === 0 ? 0 : -1;
189
+ }
190
+ function refPosition(entry) {
191
+ return entry.el._startMarker ?? entry.el;
192
+ }
172
193
  function fireEffectCleanup(e) {
173
194
  const slot = e.scope.hooks?.get(e.slot);
174
195
  if (slot === void 0 || slot.revision !== e.revision) return;
@@ -369,6 +390,7 @@ function schedulePostPaint(cb) {
369
390
  nativeEffectPublicationCurrent,
370
391
  passiveScheduled,
371
392
  pendingPassiveUnmounts,
393
+ postOrderHostRefs,
372
394
  releaseDeferredPassives,
373
395
  renderWritesForbidden,
374
396
  restoreDeferredPassives,
@@ -126,6 +126,7 @@ __export(engine_exports, {
126
126
  retireRendererSignalOwner: () => retireRendererSignalOwner,
127
127
  retireRootBlock: () => retireRootBlock,
128
128
  retiringHostsSince: () => retiringHostsSince,
129
+ retryRootRequest: () => retryRootRequest,
129
130
  retryTransitionBatch: () => retryTransitionBatch,
130
131
  retryTransitionHolder: () => retryTransitionHolder,
131
132
  rollbackRootRender: () => rollbackRootRender,
@@ -145,6 +146,7 @@ __export(engine_exports, {
145
146
  setIsOctaneActEnvironment: () => setIsOctaneActEnvironment,
146
147
  settleTransitionFalls: () => settleTransitionFalls,
147
148
  spawnDeferredSwap: () => spawnDeferredSwap,
149
+ suspendingTransitionHold: () => suspendingTransitionHold,
148
150
  syncFlush: () => syncFlush,
149
151
  tickTransitionCount: () => tickTransitionCount,
150
152
  undoCreatedInRootRender: () => undoCreatedInRootRender,
@@ -383,20 +385,41 @@ let TRANSITION_PENDING_COUNT = 0;
383
385
  let TRANSITION_HOLDERS = null;
384
386
  let WOKEN_TRANSITION_HOLDERS = null;
385
387
  let RETRIED_TRANSITION_BATCHES = null;
386
- function holdTransitionBatch(holder, batch) {
388
+ let RETRIED_ROOT_REQUESTS = null;
389
+ function suspendingTransitionHold(holder) {
387
390
  const holders = TRANSITION_HOLDERS ??= /* @__PURE__ */ new Map();
388
391
  let hold = holders.get(holder);
389
- if (hold === void 0) holders.set(holder, hold = { batches: /* @__PURE__ */ new Set(), suspends: true });
392
+ if (hold === void 0)
393
+ holders.set(holder, hold = { batches: /* @__PURE__ */ new Set(), suspends: true, request: -1 });
390
394
  else if (!hold.suspends) {
391
395
  hold.suspends = true;
392
396
  for (const held of hold.batches) held.suspended++;
393
397
  }
398
+ return hold;
399
+ }
400
+ function holdTransitionBatch(holder, batch) {
401
+ const hold = suspendingTransitionHold(holder);
394
402
  if (hold.batches.has(batch)) return;
395
403
  hold.batches.add(batch);
396
404
  batch.pendingHolds = (batch.pendingHolds ?? 0) + 1;
397
405
  if (hold.suspends) batch.suspended++;
398
406
  (0, import_transitions.raiseHeldTransitionBatch)(batch);
399
407
  }
408
+ function retryRootRequest(owner, generation) {
409
+ if (owner.disposed || owner.generation !== generation) return false;
410
+ if (inFlush || import_render.CURRENT_BLOCK !== null) {
411
+ (RETRIED_ROOT_REQUESTS ??= []).push(owner, generation);
412
+ scheduleTransitionTask();
413
+ return true;
414
+ }
415
+ TRANSITION_DEPTH++;
416
+ try {
417
+ owner.retry();
418
+ } finally {
419
+ TRANSITION_DEPTH--;
420
+ }
421
+ return true;
422
+ }
400
423
  function retryTransitionBatch(batch, render, fall) {
401
424
  if (inFlush || import_render.CURRENT_BLOCK !== null) {
402
425
  (RETRIED_TRANSITION_BATCHES ??= []).push(batch, render, fall);
@@ -423,6 +446,13 @@ function unsuspendTransitionHolder(holder) {
423
446
  (RETRIED_TRANSITION_BATCHES ??= []).push(batch, true, false);
424
447
  scheduleTransitionTask();
425
448
  }
449
+ if (hold.request !== -1) {
450
+ (RETRIED_ROOT_REQUESTS ??= []).push(
451
+ holder.parentBlock.idState.renderOwner,
452
+ hold.request
453
+ );
454
+ scheduleTransitionTask();
455
+ }
426
456
  }
427
457
  let TRANSITION_HOOK_PUBLISH_DEPTH = 0;
428
458
  function scheduleHookPublication(block) {
@@ -552,6 +582,11 @@ function retryWokenTransitions() {
552
582
  retried[i + 2]
553
583
  );
554
584
  }
585
+ const requests = RETRIED_ROOT_REQUESTS;
586
+ RETRIED_ROOT_REQUESTS = null;
587
+ if (requests !== null)
588
+ for (let i = 0; i < requests.length; i += 2)
589
+ retryRootRequest(requests[i], requests[i + 1]);
555
590
  }
556
591
  let COMMITTING_OWNER = null;
557
592
  let COMMITTING_GENERATION = 0;
@@ -1259,7 +1294,8 @@ function scheduleTransitionTask() {
1259
1294
  if (transitionTask !== id) return;
1260
1295
  TRANSITION_TASK_ACTIVE = true;
1261
1296
  try {
1262
- if (WOKEN_TRANSITION_HOLDERS || RETRIED_TRANSITION_BATCHES) retryWokenTransitions();
1297
+ if (WOKEN_TRANSITION_HOLDERS || RETRIED_TRANSITION_BATCHES || RETRIED_ROOT_REQUESTS)
1298
+ retryWokenTransitions();
1263
1299
  adoptTransitionQueue(true);
1264
1300
  flush();
1265
1301
  } finally {
@@ -1276,7 +1312,8 @@ function adoptTransitionQueue(drain) {
1276
1312
  if (drain === true) import_drivers.TRANSITION_ROOT_DRIVER.armFalls();
1277
1313
  else scheduleTransitionTask();
1278
1314
  }
1279
- if (WOKEN_TRANSITION_HOLDERS || RETRIED_TRANSITION_BATCHES) scheduleTransitionTask();
1315
+ if (WOKEN_TRANSITION_HOLDERS || RETRIED_TRANSITION_BATCHES || RETRIED_ROOT_REQUESTS)
1316
+ scheduleTransitionTask();
1280
1317
  }
1281
1318
  function settleTransitionFalls() {
1282
1319
  const armed = ARMED_TRANSITION_FALLS;
@@ -2288,6 +2325,7 @@ function drainQueuedRefAttaches() {
2288
2325
  break;
2289
2326
  }
2290
2327
  }
2328
+ (0, import_effects.postOrderHostRefs)(q);
2291
2329
  }
2292
2330
  for (const r of q) {
2293
2331
  if ((0, import_refs.blockSubtreeDisposed)(r.block)) continue;
@@ -2965,10 +3003,10 @@ function countLayoutSnapshotChange(cell, block) {
2965
3003
  let DID_WARN_UNCACHED_SNAPSHOT = false;
2966
3004
  function useSyncExternalStore(subscribe, getSnapshot, serverSnapshotOrSlot = void 0, lastSlot) {
2967
3005
  const count = arguments.length;
2968
- let slot = count > 4 ? arguments[count - 1] : count === 4 ? lastSlot : serverSnapshotOrSlot;
3006
+ const getServerSnapshot = typeof serverSnapshotOrSlot === "function" ? serverSnapshotOrSlot : void 0;
3007
+ let slot = count > 4 ? arguments[count - 1] : count === 4 ? lastSlot : getServerSnapshot ? void 0 : serverSnapshotOrSlot;
2969
3008
  slot = (0, import_drivers.resolveSlot)(slot);
2970
3009
  if (slot === void 0) (0, import_hooks.missingSlot)("useSyncExternalStore");
2971
- const getServerSnapshot = count >= 4 ? serverSnapshotOrSlot : void 0;
2972
3010
  const subs = (0, import_hooks.usesSubslots)(slot);
2973
3011
  const readSnapshot = import_hydration_flag.hydrating && import_hydration.CURSOR !== null && getServerSnapshot !== void 0 ? getServerSnapshot : getSnapshot;
2974
3012
  const value = readSnapshot();
@@ -3008,7 +3046,10 @@ function useSyncExternalStore(subscribe, getSnapshot, serverSnapshotOrSlot = voi
3008
3046
  (0, import_hooks.ensureHooks)(scope).set(subs.inst, inst);
3009
3047
  (0, import_hooks.enqueueStoreSync)(inst, value, subscribe);
3010
3048
  } else {
3011
- inst.getSnapshot = getSnapshot;
3049
+ if (inst.getSnapshot !== getSnapshot) {
3050
+ journalRootProperty(inst, "getSnapshot", inst.getSnapshot);
3051
+ inst.getSnapshot = getSnapshot;
3052
+ }
3012
3053
  if (!Object.is(value, inst.value) || subscribe !== inst.subscribe) {
3013
3054
  (0, import_hooks.enqueueStoreSync)(inst, value, subscribe);
3014
3055
  }
@@ -3819,6 +3860,7 @@ function makeRoot(container, rootBlock, currentBody, currentKey, idState, output
3819
3860
  retireRendererSignalOwner,
3820
3861
  retireRootBlock,
3821
3862
  retiringHostsSince,
3863
+ retryRootRequest,
3822
3864
  retryTransitionBatch,
3823
3865
  retryTransitionHolder,
3824
3866
  rollbackRootRender,
@@ -3838,6 +3880,7 @@ function makeRoot(container, rootBlock, currentBody, currentKey, idState, output
3838
3880
  setIsOctaneActEnvironment,
3839
3881
  settleTransitionFalls,
3840
3882
  spawnDeferredSwap,
3883
+ suspendingTransitionHold,
3841
3884
  syncFlush,
3842
3885
  tickTransitionCount,
3843
3886
  undoCreatedInRootRender,
@@ -26,6 +26,7 @@ __export(hooks_exports, {
26
26
  adoptMemoEntry: () => adoptMemoEntry,
27
27
  callWithReceiver: () => callWithReceiver,
28
28
  cancelHiddenRevealAction: () => cancelHiddenRevealAction,
29
+ checkHookOwner: () => checkHookOwner,
29
30
  commitHookCells: () => commitHookCells,
30
31
  compilerMemoRegion: () => compilerMemoRegion,
31
32
  compilerMemoWitness: () => compilerMemoWitness,
@@ -120,6 +121,9 @@ function hookSlots(count) {
120
121
  function missingSlot(name) {
121
122
  throw new Error((0, import_error_codes_client_generated.formatClientError)(15, name));
122
123
  }
124
+ function checkHookOwner() {
125
+ if (import_render.CURRENT_SCOPE === null) throw new Error((0, import_error_codes_client_generated.formatClientError)(362));
126
+ }
123
127
  const slotStack = [];
124
128
  function manualHook(fn, name) {
125
129
  function provider() {
@@ -139,6 +143,7 @@ function appendSlotKey(key, slot) {
139
143
  }
140
144
  function resolveCustomSlot(slot) {
141
145
  const n = slotStack.length;
146
+ if (__octaneDev && (slot !== void 0 || n !== 0)) checkHookOwner();
142
147
  if (n === 0) return slot;
143
148
  if (slot === void 0 && n === 1) return slotStack[0];
144
149
  const resolved = (0, import_hook_slot_cache.resolveHookPath)(slotStack, slot, false);
@@ -911,6 +916,7 @@ function useId(slot) {
911
916
  adoptMemoEntry,
912
917
  callWithReceiver,
913
918
  cancelHiddenRevealAction,
919
+ checkHookOwner,
914
920
  commitHookCells,
915
921
  compilerMemoRegion,
916
922
  compilerMemoWitness,
@@ -787,9 +787,12 @@ function mountErrorBoundary(state, hydration) {
787
787
  return state.block;
788
788
  };
789
789
  state.block = body;
790
+ if (freshBoundary) hydration.suspend(() => renderErrorBody(state, body, hydration));
791
+ else renderErrorBody(state, body, hydration);
792
+ }
793
+ function renderErrorBody(state, body, hydration) {
790
794
  try {
791
- if (freshBoundary) hydration.suspend(() => hydration.renderTryBody(body, true));
792
- else if (hydration !== null) hydration.renderTryBody(body, true);
795
+ if (hydration !== null) hydration.renderTryBody(body, true);
793
796
  else (0, import_signal_owners.renderBlock)(body);
794
797
  state.hasResolved = true;
795
798
  } catch (error) {
@@ -815,6 +818,8 @@ function switchErrorToCatchInner(state, error, reportInline) {
815
818
  const previous = state.block;
816
819
  if (previous !== null) {
817
820
  state.block = null;
821
+ const transaction = import_render.ROOT_RENDER_TRANSACTION;
822
+ if (transaction !== null && !transaction.aborted) dropBodyOps(transaction, previous);
818
823
  (0, import_block.unmountBlock)(previous, hydration === null);
819
824
  if (state.parentBlock.disposed || state.block !== null) return;
820
825
  }
@@ -287,6 +287,8 @@ function releaseTransitionHookHolder(holder, retry = true) {
287
287
  if (unblocked || settled) (0, import_engine.retryTransitionBatch)(batch, unblocked, settled);
288
288
  } else if (settled) finishTransitionHookBatch(batch);
289
289
  }
290
+ if (hold.request !== -1 && hold.suspends && retry)
291
+ return (0, import_engine.retryRootRequest)(holder.parentBlock.idState.renderOwner, hold.request) || hold.batches.size !== 0;
290
292
  return true;
291
293
  }
292
294
  const HELD_TRANSITIONS = /* @__PURE__ */ new Set();
@@ -379,12 +381,14 @@ function noteTransitionBatch(transaction, batch) {
379
381
  function dropTransitionRender(transaction) {
380
382
  const holders = transaction.holders;
381
383
  transaction.holders = null;
384
+ const owner = transaction.owner;
382
385
  const origin = import_engine.TRANSITION_ORIGIN;
383
- const batches = origin === null ? transaction.batches : transaction.batches?.filter((batch) => batch.origin === origin) ?? null;
384
- if (batches === null || batches.length === 0 || transaction.owner.disposed) return false;
386
+ const batches = (origin === null ? transaction.batches : transaction.batches?.filter((batch) => batch.origin === origin)) ?? [];
387
+ const request = transaction.rootRequest && origin !== null && origin === owner.current && origin.currentRenderMode === "transition";
388
+ if (batches.length === 0 && !request || owner.disposed) return false;
385
389
  let live = false;
386
390
  for (const holder of holders)
387
- if (holder.__kind !== "trySlotSlot" || holder.transitionHeld)
391
+ if (holder.__kind === "trySlotSlot" ? holder.transitionHeld : batches.length !== 0)
388
392
  live = true;
389
393
  if (!live) return false;
390
394
  if (typeof __OCTANE_PROFILE_ENABLED__ !== "undefined" && __OCTANE_PROFILE_ENABLED__)
@@ -399,7 +403,11 @@ function dropTransitionRender(transaction) {
399
403
  for (const holder of holders) {
400
404
  if (holder.__kind === "trySlotSlot") {
401
405
  if (!holder.transitionHeld) continue;
402
- } else if (import_engine.TRANSITION_HOLDERS?.has(holder) !== true) (0, import_engine.tickTransitionCount)(1);
406
+ if (request) (0, import_engine.suspendingTransitionHold)(holder).request = owner.generation;
407
+ } else {
408
+ if (batches.length === 0) continue;
409
+ if (import_engine.TRANSITION_HOLDERS?.has(holder) !== true) (0, import_engine.tickTransitionCount)(1);
410
+ }
403
411
  for (const batch of batches) (0, import_engine.holdTransitionBatch)(holder, batch);
404
412
  }
405
413
  for (const other of import_engine.ROOT_RENDER_TRANSACTIONS)
@@ -425,6 +433,8 @@ function holdsUnrenderedTransition(state) {
425
433
  const hold = import_engine.TRANSITION_HOLDERS.get(state);
426
434
  if (hold === void 0) return false;
427
435
  const transaction = import_render.ROOT_RENDER_TRANSACTION;
436
+ if (hold.request !== -1 && (transaction === null || !transaction.rootRequest) && state.parentBlock.idState.renderOwner.generation === hold.request)
437
+ return true;
428
438
  for (const batch of hold.batches)
429
439
  if (transaction === null || batch.stamp !== transaction.createdStamp) return true;
430
440
  return false;
@@ -4894,8 +4894,7 @@ function useDeferredValue(value, initialValueOrSlot = void 0, _slot) {
4894
4894
  function useSyncExternalStore(_subscribe, getSnapshot, serverSnapshotOrSlot = void 0, _slot) {
4895
4895
  if (SHELL_WITNESS !== null && __octaneDev)
4896
4896
  witnessShellHook("useSyncExternalStore", useSyncExternalStore);
4897
- const getServerSnapshot = arguments.length >= 4 ? serverSnapshotOrSlot : void 0;
4898
- return getServerSnapshot ? getServerSnapshot() : getSnapshot();
4897
+ return typeof serverSnapshotOrSlot === "function" ? serverSnapshotOrSlot() : getSnapshot();
4899
4898
  }
4900
4899
  function useActionState(_action, initialState) {
4901
4900
  return [initialState, NOOP, false];
@@ -21,7 +21,7 @@ __export(version_exports, {
21
21
  version: () => version
22
22
  });
23
23
  module.exports = __toCommonJS(version_exports);
24
- const version = "0.16.3";
24
+ const version = "0.16.4";
25
25
  // Annotate the CommonJS export names for ESM import in node:
26
26
  0 && (module.exports = {
27
27
  version
@@ -82,6 +82,50 @@ const LOOP_TYPES = new Set([
82
82
  'DoWhileStatement',
83
83
  ]);
84
84
 
85
+ /**
86
+ * Replace each exit that a directive arm's setup owns, anywhere in `ast`, with
87
+ * `exit(node)`, copy-on-write. Nested functions and `@{ … }` blocks own their
88
+ * jumps, and a nested directive's arms are arms of their own. The type-only
89
+ * view uses this; each compiler walks its arms itself.
90
+ */
91
+ export function mapArmExits(ast, exit) {
92
+ const visit = (node, arm, loop, breakable) => {
93
+ if (Array.isArray(node)) {
94
+ let out = null;
95
+ for (let i = 0; i < node.length; i++) {
96
+ const child = node[i];
97
+ const mapped =
98
+ child !== null && typeof child === 'object' ? visit(child, arm, loop, breakable) : child;
99
+ if (out === null && mapped !== child) out = node.slice(0, i);
100
+ if (out !== null) out.push(mapped);
101
+ }
102
+ return out ?? node;
103
+ }
104
+ const type = node.type;
105
+ if (arm && armJump(node, loop, breakable) === 'exit') return exit(node);
106
+ if (DIRECTIVE_TYPES.has(type)) {
107
+ arm = true;
108
+ loop = breakable = false;
109
+ } else if (FUNCTION_TYPES.has(type) || type === 'JSXCodeBlock') {
110
+ arm = false;
111
+ } else if (LOOP_TYPES.has(type)) {
112
+ loop = breakable = true;
113
+ } else if (type === 'SwitchStatement') {
114
+ breakable = true;
115
+ }
116
+ let out = null;
117
+ for (const key in node) {
118
+ if (WALK_SKIP_KEYS.has(key)) continue;
119
+ const child = node[key];
120
+ if (child === null || typeof child !== 'object') continue;
121
+ const mapped = visit(child, arm, loop, breakable);
122
+ if (mapped !== child) (out ??= { ...node })[key] = mapped;
123
+ }
124
+ return out ?? node;
125
+ };
126
+ return visit(ast, false, false, false);
127
+ }
128
+
85
129
  // The template region a statement sits in. Outside every template, jumps are
86
130
  // ordinary JavaScript and the parser resolves them.
87
131
  const OUTSIDE = 0;
@@ -125,7 +169,10 @@ export function assertTemplateJumps(ast, source, filename) {
125
169
  : region === BLOCK && !(type === 'ContinueStatement' ? loop : breakable);
126
170
  if (leaves) {
127
171
  const at = node.loc ? ` (${file}:${node.loc.start.line}:${node.loc.start.column})` : '';
128
- throw new Error((region === BLOCK ? BLOCK_JUMP_MESSAGE : ARM_LABEL_MESSAGE) + at);
172
+ throw Object.assign(
173
+ new Error((region === BLOCK ? BLOCK_JUMP_MESSAGE : ARM_LABEL_MESSAGE) + at),
174
+ { code: region === BLOCK ? 'OCTANE_BLOCK_JUMP' : 'OCTANE_ARM_LABELED_JUMP' },
175
+ );
129
176
  }
130
177
  return;
131
178
  }
@@ -0,0 +1,84 @@
1
+ /**
2
+ * A `@{ … }` body and every directive arm hold setup statements, then one
3
+ * output node: an element, a fragment, a `@{ … }` block, or a directive. In
4
+ * statement position `{children}` is therefore a JavaScript block around a
5
+ * dead expression, not a hole, and the body renders nothing in its place. It
6
+ * reads as a hole, so assertNoBareHoles rejects it once on the authored module,
7
+ * ahead of every compiler. Braces around one expression statement do nothing
8
+ * in JavaScript, so setup code never needs this shape. `@case` arms are the
9
+ * exception: the parser reads a `{` there as a hole, and it renders.
10
+ */
11
+
12
+ const BARE_HOLE_MESSAGE =
13
+ '`{…}` in a `@{ … }` body or directive arm is a JavaScript block, not a hole, so it ' +
14
+ 'renders nothing. Wrap it in a fragment to render it, as in `<>{children}</>`, or ' +
15
+ 'remove the braces to keep it as setup code.';
16
+
17
+ const WALK_SKIP_KEYS = new Set(['type', 'loc', 'start', 'end', 'range', 'metadata', 'parent']);
18
+
19
+ /**
20
+ * Reject the first block statement holding a single expression statement among
21
+ * the statements of a `@{ … }` body or an `@if`/`@else`, `@for`/`@empty`, or
22
+ * `@try`/`@pending`/`@catch` arm. Runs once per module, on the authored AST,
23
+ * for every target.
24
+ */
25
+ export function assertNoBareHoles(ast, source, filename) {
26
+ if (!/@(?:\{|(?:if|for|try)\b)/.test(source)) return;
27
+ const file = filename.split(/[\\/]/).pop();
28
+ const check = (body) => {
29
+ if (body == null) return;
30
+ // An `@else if` chain continues as a plain IfStatement.
31
+ if (body.type === 'IfStatement') {
32
+ check(body.consequent);
33
+ check(body.alternate);
34
+ return;
35
+ }
36
+ // The compilers also accept an arm that is a single node, or an `@else`
37
+ // that is a JSXIfExpression, which the walk below checks on its own.
38
+ if (!Array.isArray(body.body)) return;
39
+ for (const statement of body.body) {
40
+ if (
41
+ statement.type === 'BlockStatement' &&
42
+ statement.body.length === 1 &&
43
+ statement.body[0].type === 'ExpressionStatement'
44
+ ) {
45
+ const at = statement.loc
46
+ ? ` (${file}:${statement.loc.start.line}:${statement.loc.start.column})`
47
+ : '';
48
+ throw Object.assign(new Error(BARE_HOLE_MESSAGE + at), { code: 'OCTANE_BARE_HOLE' });
49
+ }
50
+ }
51
+ };
52
+ const visit = (node) => {
53
+ if (Array.isArray(node)) {
54
+ for (const child of node) {
55
+ if (child !== null && typeof child === 'object') visit(child);
56
+ }
57
+ return;
58
+ }
59
+ switch (node.type) {
60
+ case 'JSXCodeBlock':
61
+ check(node);
62
+ break;
63
+ case 'JSXIfExpression':
64
+ check(node.consequent);
65
+ check(node.alternate);
66
+ break;
67
+ case 'JSXForExpression':
68
+ check(node.body);
69
+ check(node.empty);
70
+ break;
71
+ case 'JSXTryExpression':
72
+ check(node.block);
73
+ check(node.pending);
74
+ check(node.handler?.body);
75
+ break;
76
+ }
77
+ for (const key in node) {
78
+ if (WALK_SKIP_KEYS.has(key)) continue;
79
+ const child = node[key];
80
+ if (child !== null && typeof child === 'object') visit(child);
81
+ }
82
+ };
83
+ visit(ast);
84
+ }
@@ -316,10 +316,10 @@ function normalizeHmrDialect(value) {
316
316
  /**
317
317
  * Classify direct function exports whose production TSRX body has no renderable
318
318
  * JavaScript return. A direct `memo(LocalComponent)` export preserves that
319
- * contract, as do null-only early-return guards that compile to template
320
- * control flow. Bundler adapters attach this fact after compiling the exact
321
- * source they loaded. Re-exports and indirect bindings remain deliberately
322
- * unknown.
319
+ * contract, as does a default export of such a binding, and so do null-only
320
+ * early-return guards that compile to template control flow. Bundler adapters
321
+ * attach this fact after compiling the exact source they loaded. Re-exports and
322
+ * indirect bindings remain deliberately unknown.
323
323
  */
324
324
  export function findVoidComponentExports(source, id) {
325
325
  let ast;
@@ -357,6 +357,17 @@ export function findVoidComponentExports(source, id) {
357
357
  if (declaration) declarations.push(declaration);
358
358
  }
359
359
 
360
+ // The local a direct `memo(Local)` call wraps, or null. The imported memo
361
+ // identity is lexical proof; method calls, comparators, and arbitrary
362
+ // wrappers stay unknown.
363
+ const memoTarget = (node) =>
364
+ node?.type === 'CallExpression' &&
365
+ node.callee?.type === 'Identifier' &&
366
+ memoLocals.has(node.callee.name) &&
367
+ node.arguments?.length === 1 &&
368
+ node.arguments[0]?.type === 'Identifier'
369
+ ? node.arguments[0].name
370
+ : null;
360
371
  const voidBindings = new Set();
361
372
  const memoDependents = new Map();
362
373
  const hasLowerableJsxReturnBranches = createJsxReturnBranchClassifier(ast.body || []);
@@ -375,10 +386,9 @@ export function findVoidComponentExports(source, id) {
375
386
  continue;
376
387
  }
377
388
  if (declaration.type !== 'VariableDeclaration' || declaration.kind !== 'const') continue;
378
- // Resolve only the exact, immutable `const Export = memo(Local)` form. The
379
- // imported memo identity is lexical proof; method calls, comparators, and
380
- // arbitrary wrappers stay unknown. Index the reverse edges once so a chain
381
- // declared outermost-first does not rescan every declaration per link.
389
+ // Resolve only the exact, immutable `const Export = memo(Local)` form.
390
+ // Index the reverse edges once so a chain declared outermost-first does
391
+ // not rescan every declaration per link.
382
392
  for (const item of declaration.declarations || []) {
383
393
  const init = item.init;
384
394
  if (
@@ -389,17 +399,9 @@ export function findVoidComponentExports(source, id) {
389
399
  ) {
390
400
  voidBindings.add(item.id.name);
391
401
  }
392
- if (
393
- item.id?.type !== 'Identifier' ||
394
- reassigned.has(item.id) ||
395
- init?.type !== 'CallExpression' ||
396
- init.callee?.type !== 'Identifier' ||
397
- !memoLocals.has(init.callee.name) ||
398
- init.arguments?.length !== 1 ||
399
- init.arguments[0]?.type !== 'Identifier'
400
- )
401
- continue;
402
- const target = init.arguments[0].name;
402
+ const target =
403
+ item.id?.type === 'Identifier' && !reassigned.has(item.id) ? memoTarget(init) : null;
404
+ if (target === null) continue;
403
405
  let dependents = memoDependents.get(target);
404
406
  if (dependents === undefined) memoDependents.set(target, (dependents = []));
405
407
  dependents.push(item.id.name);
@@ -418,12 +420,17 @@ export function findVoidComponentExports(source, id) {
418
420
  for (const node of ast.body || []) {
419
421
  if (node.type === 'ExportDefaultDeclaration') {
420
422
  const declaration = node.declaration;
423
+ // React's `export default Local` and `export default memo(Local)` export
424
+ // a local binding's value, so they carry that binding's proof.
421
425
  if (
422
- (declaration?.type === 'FunctionDeclaration' ||
426
+ ((declaration?.type === 'FunctionDeclaration' ||
423
427
  declaration?.type === 'FunctionExpression' ||
424
428
  declaration?.type === 'ArrowFunctionExpression') &&
425
- (declaration.id == null || !reassigned.has(declaration.id)) &&
426
- isVoidFunction(declaration)
429
+ (declaration.id == null || !reassigned.has(declaration.id)) &&
430
+ isVoidFunction(declaration)) ||
431
+ voidBindings.has(
432
+ declaration?.type === 'Identifier' ? declaration.name : memoTarget(declaration),
433
+ )
427
434
  ) {
428
435
  exports.push('default');
429
436
  }