octane 0.1.22 → 0.1.24

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.
@@ -3182,8 +3182,12 @@ function buildUniversalHmrBlocksAst(state, origin) {
3182
3182
  return { prelude: [], tail: [] };
3183
3183
  }
3184
3184
  if (state.hmrDialect === 'webpack') {
3185
- const hot = importMetaMember('webpackHot', origin);
3186
- const prelude = [];
3185
+ // Rspack only guarantees that the `import.meta.webpackHot` root is lowered.
3186
+ // Keep `.data` and the HMR methods on an ordinary local so Rsbuild's React
3187
+ // transform cannot turn a deeper meta-property chain into `undefined`.
3188
+ const hotName = allocName(state, '__octaneWebpackHot');
3189
+ const hot = generatedIdentifier(hotName, origin);
3190
+ const prelude = [generatedConst(hotName, importMetaMember('webpackHot', origin), origin)];
3187
3191
  const tail = [];
3188
3192
  if (disposals.length === 0) {
3189
3193
  const data = generatedIdentifier('data', origin);
@@ -41,7 +41,12 @@ import {
41
41
  import { print as esrapPrint } from 'esrap';
42
42
  import esrapTsx from 'esrap/languages/tsx';
43
43
  import { buildFatSegments } from './fat-segments.js';
44
- import { analyzeHookDependencies, applyHookDependencies, isInvariantLiteral } from './hook-deps.js';
44
+ import {
45
+ METHOD_DEP_IMPORT,
46
+ analyzeHookDependencies,
47
+ applyHookDependencies,
48
+ isInvariantLiteral,
49
+ } from './hook-deps.js';
45
50
  import { compileUniversal, UNIVERSAL_COMPILER_RUNTIME_IMPORTS } from './compile-universal.js';
46
51
  import {
47
52
  expandDomRendererRegionsAst,
@@ -4404,6 +4409,31 @@ export function hasOwnValueReturn(node) {
4404
4409
  return walk(body.body || []);
4405
4410
  }
4406
4411
 
4412
+ /**
4413
+ * Whether a statement list always completes abruptly, so control can never fall
4414
+ * past its end. Lets an outputless `@{ … }` body drop the tail return it would
4415
+ * otherwise synthesize, because the body's own returns already cover every path.
4416
+ *
4417
+ * Deliberately syntactic: `return`, `throw`, a block that ends abruptly, and an
4418
+ * if/else whose arms both do. Anything subtler keeps the tail, which is always
4419
+ * safe — the runtime reads a fallen-through `undefined` as "this body already
4420
+ * emitted its template", so the tail must stay wherever reachability is unproven.
4421
+ */
4422
+ function alwaysCompletesAbruptly(statements) {
4423
+ const last = statements[statements.length - 1];
4424
+ if (!last) return false;
4425
+ if (last.type === 'ReturnStatement' || last.type === 'ThrowStatement') return true;
4426
+ if (last.type === 'BlockStatement') return alwaysCompletesAbruptly(last.body || []);
4427
+ if (last.type === 'IfStatement') {
4428
+ return (
4429
+ !!last.alternate &&
4430
+ alwaysCompletesAbruptly([last.consequent]) &&
4431
+ alwaysCompletesAbruptly([last.alternate])
4432
+ );
4433
+ }
4434
+ return false;
4435
+ }
4436
+
4407
4437
  /**
4408
4438
  * A mixed shorthand's early return must always reach renderReturnedValue. The
4409
4439
  * runtime reserves `undefined` for a compiled-void body that already emitted its
@@ -6039,12 +6069,16 @@ function compileInternal(source, filename, options, analyzedAst, mode, bundlerMe
6039
6069
  rendererBoundaryPreparation?.universalUnits,
6040
6070
  ),
6041
6071
  });
6072
+ let hookDepHelperNeeded = false;
6042
6073
  ast = applyHookDependencies(ast, {
6043
6074
  filename,
6044
6075
  hookRuntimeModules: hookRuntimeModulesForCompile(
6045
6076
  options,
6046
6077
  rendererBoundaryPreparation?.universalUnits,
6047
6078
  ),
6079
+ onRuntimeHelper: () => {
6080
+ hookDepHelperNeeded = true;
6081
+ },
6048
6082
  });
6049
6083
  const hmrOption = options && options.hmr;
6050
6084
  const hmrDialect = hmrOption === true ? 'vite' : hmrOption || false;
@@ -6177,6 +6211,7 @@ function compileInternal(source, filename, options, analyzedAst, mode, bundlerMe
6177
6211
  // single module print must carry a loc (OCTANE_COMPILE_ASSERT_LOC).
6178
6212
  _moduleOrigin: ast.body.find((n) => n?.loc != null) ?? ast,
6179
6213
  };
6214
+ if (hookDepHelperNeeded) ctx.runtimeNeeded.add(METHOD_DEP_IMPORT);
6180
6215
  {
6181
6216
  const imports = collectOctaneImportBindings(ast.body);
6182
6217
  ctx.octaneImportLocals = imports.locals;
@@ -6797,7 +6832,18 @@ function compileInternal(source, filename, options, analyzedAst, mode, bundlerMe
6797
6832
  // it. Persist that canonical identity again for the next update. This keeps
6798
6833
  // working across any number of edits; accept callbacks in webpack are error
6799
6834
  // handlers, not Vite-style callbacks carrying the new module namespace.
6800
- const webpackHot = () => b.member(importMeta(), 'webpackHot');
6835
+ // Keep every access after the recognized `import.meta.webpackHot` root on a
6836
+ // local. Rspack's React/Rsbuild transform pipeline otherwise treats deeper
6837
+ // expressions such as `import.meta.webpackHot.data` as unsupported even
6838
+ // though it lowers the root itself to `module.hot`.
6839
+ const webpackHotName = allocCompilerName(ctx, '_$webpackHot');
6840
+ const webpackHot = () => b.id(webpackHotName);
6841
+ hmrNodes.push(
6842
+ inheritOriginLoc(
6843
+ b.const(webpackHotName, b.member(importMeta(), 'webpackHot')),
6844
+ moduleOrigin,
6845
+ ),
6846
+ );
6801
6847
  const previousComponent = (name, optional) =>
6802
6848
  b.member(
6803
6849
  b.member(
@@ -7097,9 +7143,13 @@ function compileServer(source, filename, options, analyzedAst = null) {
7097
7143
  // Mirror the client transform exactly. Effects are server no-ops, but
7098
7144
  // useMemo/useCallback execute during SSR and must receive the same inferred
7099
7145
  // dependency shape as hydration's client compile.
7146
+ let hookDepHelperNeeded = false;
7100
7147
  ast = applyHookDependencies(ast, {
7101
7148
  filename,
7102
7149
  hookRuntimeModules: hookRuntimeModulesForCompile(options),
7150
+ onRuntimeHelper: () => {
7151
+ hookDepHelperNeeded = true;
7152
+ },
7103
7153
  });
7104
7154
  const ctx = {
7105
7155
  filename,
@@ -7143,6 +7193,7 @@ function compileServer(source, filename, options, analyzedAst = null) {
7143
7193
  // Scaffolding without a more precise authored construct maps here.
7144
7194
  _moduleOrigin: ast.body.find((n) => n?.loc != null) ?? ast,
7145
7195
  };
7196
+ if (hookDepHelperNeeded) ctx.runtimeNeeded.add(METHOD_DEP_IMPORT);
7146
7197
  {
7147
7198
  const imports = collectOctaneImportBindings(ast.body);
7148
7199
  ctx.octaneImportLocals = imports.locals;
@@ -10562,10 +10613,20 @@ function compileFunctionBody(node, ctx, name, parentNs = 'html', cssHash = null,
10562
10613
  const shellOrigin = node.loc ? node : node.id?.loc ? node.id : prevFnOrigin;
10563
10614
  let plan = null;
10564
10615
  let returnedExpression = null;
10565
- if (returnedOutput) {
10566
- const rendered = jsxNodes[0];
10616
+ if (returnedOutput && jsxNodes.length === 0) {
10617
+ // A `@{ … }` body can carry value returns with NO trailing output node —
10618
+ // `@{ … return null }` while a component is being written, or a React-shaped
10619
+ // `return <jsx>` inside the block. There is no template to lower: the body's
10620
+ // own returns are the whole output, so the tail is a plain `null` covering
10621
+ // the fall-through path. When the body provably never falls through, that
10622
+ // tail is unreachable and is dropped. Statement returns already normalized
10623
+ // to `?? null`.
10624
+ if (!alwaysCompletesAbruptly(rewrittenStatements)) {
10625
+ returnedExpression = b.literal(null, 'null', node);
10626
+ }
10627
+ } else if (returnedOutput) {
10567
10628
  returnedExpression = lowerReturnJsx(
10568
- rewriteHookCalls(rendered, ctx, name, options?.localHookSlots === true),
10629
+ rewriteHookCalls(jsxNodes[0], ctx, name, options?.localHookSlots === true),
10569
10630
  ctx,
10570
10631
  inlinedSubs,
10571
10632
  cssHash,
@@ -16678,12 +16739,10 @@ function planJsx(
16678
16739
  ctx.runtimeNeeded.add('queueRefDetach'); // unmount-detach of a spread-supplied ref
16679
16740
  }
16680
16741
  if (b.kind === 'ref') {
16681
- ctx.runtimeNeeded.add('attachRef');
16682
16742
  ctx.runtimeNeeded.add('queueRefAttach'); // deferred mount attach (commit-phase timing)
16683
16743
  ctx.runtimeNeeded.add('queueRefDetach'); // deferred unmount detach (same phasing)
16684
16744
  }
16685
16745
  if (b.kind === 'fragmentRef') {
16686
- ctx.runtimeNeeded.add('attachRef');
16687
16746
  ctx.runtimeNeeded.add('mountFragmentRef');
16688
16747
  ctx.runtimeNeeded.add('queueRefAttach'); // deferred update re-attach
16689
16748
  ctx.runtimeNeeded.add('queueRefDetach'); // deferred update/unmount detach
@@ -18251,18 +18310,18 @@ function emitBindingMount(bind, elVar, bag) {
18251
18310
  // bound element rides along as the cleanup target, so a callback ref
18252
18311
  // shared across elements (ref={registerItem} on every @for row)
18253
18312
  // releases ITS row's React-19 cleanup, not another row's.
18254
- // Both deferred closures read through the captured `_b` (committed by the
18255
- // time attach/cleanup run); `_ref$` must be a LIVE read — updates re-point it.
18313
+ // Both deferred operations retain the bound element. `_ref$` must be a LIVE
18314
+ // cleanup read because updates re-point it. Assigning both bag locals inside
18315
+ // the queue call evaluates each mount value once while avoiding throwaway
18316
+ // temporaries; Suspense still receives the exact ref/target pair.
18256
18317
  return st(
18257
18318
  b.block([
18258
- b.const('_r', bind.expr),
18259
- b.stmt(b.assignment('=', local(`_ref$${bind.id}`), b.id('_r'))),
18260
- b.stmt(b.assignment('=', local(`_el$${bind.id}`), el())),
18261
18319
  b.stmt(
18262
18320
  b.call(
18263
18321
  '_$queueRefAttach',
18264
18322
  b.id('__s'),
18265
- b.arrow([], b.call('_$attachRef', b.id('_r'), bagFieldNode(bag, `_el$${bind.id}`))),
18323
+ b.assignment('=', local(`_ref$${bind.id}`), bind.expr),
18324
+ b.assignment('=', local(`_el$${bind.id}`), el()),
18266
18325
  ),
18267
18326
  ),
18268
18327
  cleanupsPush(
@@ -18286,22 +18345,13 @@ function emitBindingMount(bind, elVar, bag) {
18286
18345
  // the user's ref, and registers a single cleanup that detaches
18287
18346
  // the ref + destroys the instance on unmount.
18288
18347
  return st(
18289
- b.block([
18290
- b.const('_r', bind.expr),
18291
- b.stmt(
18292
- b.assignment(
18293
- '=',
18294
- local(`_fi$${bind.id}`),
18295
- b.call(
18296
- '_$mountFragmentRef',
18297
- b.id('__s'),
18298
- el(),
18299
- hostVarNode(bind.endElVar),
18300
- b.id('_r'),
18301
- ),
18302
- ),
18348
+ b.stmt(
18349
+ b.assignment(
18350
+ '=',
18351
+ local(`_fi$${bind.id}`),
18352
+ b.call('_$mountFragmentRef', b.id('__s'), el(), hostVarNode(bind.endElVar), bind.expr),
18303
18353
  ),
18304
- ]),
18354
+ ),
18305
18355
  );
18306
18356
  }
18307
18357
  }
@@ -18561,13 +18611,7 @@ function emitBindingUpdate(bind, bag) {
18561
18611
  ),
18562
18612
  b.if(
18563
18613
  b.binary('!=', b.id('_r'), nullNode()),
18564
- b.stmt(
18565
- b.call(
18566
- '_$queueRefAttach',
18567
- b.id('__s'),
18568
- b.arrow([], b.call('_$attachRef', b.id('_r'), F('_el'))),
18569
- ),
18570
- ),
18614
+ b.stmt(b.call('_$queueRefAttach', b.id('__s'), b.id('_r'), F('_el'))),
18571
18615
  null,
18572
18616
  ),
18573
18617
  b.stmt(b.assignment('=', F('_ref'), b.id('_r'))),
@@ -18600,13 +18644,7 @@ function emitBindingUpdate(bind, bag) {
18600
18644
  ),
18601
18645
  b.if(
18602
18646
  b.binary('!=', b.id('_r'), nullNode()),
18603
- b.stmt(
18604
- b.call(
18605
- '_$queueRefAttach',
18606
- b.id('__s'),
18607
- b.arrow([], b.call('_$attachRef', b.id('_r'), fi())),
18608
- ),
18609
- ),
18647
+ b.stmt(b.call('_$queueRefAttach', b.id('__s'), b.id('_r'), fi())),
18610
18648
  null,
18611
18649
  ),
18612
18650
  b.stmt(b.assignment('=', cur(), b.id('_r'))),
@@ -819,10 +819,78 @@ function staticMemberInfo(node) {
819
819
  return {
820
820
  node: dependencyNode,
821
821
  root,
822
+ name: current.property.name,
822
823
  path: `${current.optional ? '?' : ''}.${current.property.name}`,
823
824
  };
824
825
  }
825
826
 
827
+ // Directive prologues that declare "this body executes in another context, not
828
+ // during render" — a nested function carrying one contributes only its ROOT
829
+ // captures to an inferred dependency array, because hoisting its member reads
830
+ // to render time would run getters in a context where they may be illegal
831
+ // (issue #542: TypeGPU's `.$` is only readable inside `'use gpu'` shader code)
832
+ // and at a moment the program never performs them.
833
+ //
834
+ // This is a deliberate ALLOWLIST, not "any directive": directives that mark
835
+ // same-context compiler hints (`'use strict'`, React Compiler's `'use memo'` /
836
+ // `'use no memo'`, `'use signals'`) or reserved module/function markers with
837
+ // their own semantics (`'use server'`, `'use client'`, `'use cache'`,
838
+ // `'use workflow'`/`'use step'`) must NOT truncate. Extend the set as more
839
+ // other-context directives appear in the ecosystem.
840
+ //
841
+ // 'use gpu' — TypeGPU shader functions (transpiled, run on the GPU).
842
+ // 'worklet' — react-native-reanimated / react-native-worklets-core bodies,
843
+ // serialized and executed on a separate UI-thread runtime.
844
+ const OPAQUE_EXECUTION_DIRECTIVES = new Set(['use gpu', 'worklet']);
845
+
846
+ // A directive prologue is the run of leading string-literal expression
847
+ // statements. Parsers implementing ESTree stamp `directive` on those
848
+ // statements; fall back to the literal value where they don't.
849
+ function hasOpaqueExecutionDirective(fn) {
850
+ if (fn.body?.type !== 'BlockStatement') return false;
851
+ for (const statement of fn.body.body || []) {
852
+ if (statement.type !== 'ExpressionStatement') return false;
853
+ const expression = unwrapValue(statement.expression);
854
+ const value =
855
+ statement.directive ??
856
+ (expression?.type === 'Literal' && typeof expression.value === 'string'
857
+ ? expression.value
858
+ : null);
859
+ if (value === null) return false;
860
+ if (OPAQUE_EXECUTION_DIRECTIVES.has(value)) return true;
861
+ }
862
+ return false;
863
+ }
864
+
865
+ // The `octane` runtime export inferred method-call dependencies compile to.
866
+ // Both emitters alias it: the full compiler through `ctx.runtimeNeeded` (its
867
+ // `_$`-prefixed rtAlias convention is baked into methodDepNode below) and the
868
+ // surgical pass through its own helper-import allocator.
869
+ export const METHOD_DEP_IMPORT = '__methodDep';
870
+
871
+ // The emitted dependency expression for a one-level method call:
872
+ // `_$__methodDep(root, 'name')` — own property ? member value : receiver (see
873
+ // the runtime helper's contract in src/method-dep.ts). The call node carries
874
+ // the authored member's source range so source maps and the surgical pass's
875
+ // offset expectations stay anchored to the authored expression, while the
876
+ // cloned root identifier keeps its own authored position.
877
+ function methodDepNode(dependency) {
878
+ // Every synthesized node is stamped with the authored member's origin — the
879
+ // bundler print path asserts a loc on each printed node, including the
880
+ // helper's callee identifier.
881
+ const call = b.call(
882
+ b.id(`_$${METHOD_DEP_IMPORT}`, dependency.node),
883
+ { ...dependency.method.root },
884
+ b.literal(dependency.method.name, JSON.stringify(dependency.method.name), dependency.node),
885
+ );
886
+ return {
887
+ ...call,
888
+ start: dependency.node.start,
889
+ end: dependency.node.end,
890
+ loc: dependency.node.loc,
891
+ };
892
+ }
893
+
826
894
  function collectDependencies(expression, callbackScope, analysis) {
827
895
  const dependencies = [];
828
896
  const seen = new Set();
@@ -863,6 +931,48 @@ function collectDependencies(expression, callbackScope, analysis) {
863
931
  }
864
932
  }
865
933
 
934
+ // A one-level member CALLED as a method. The member value alone cannot
935
+ // witness a changed receiver when the method is inherited (issue #542:
936
+ // `count.toFixed` is `Number.prototype.toFixed` on every render), and the
937
+ // receiver alone would defeat memoization for own function properties on
938
+ // per-render containers (`props.onChange(...)`). Record the pair and let the
939
+ // emitted `__methodDep(root, 'name')` helper pick the comparable value at
940
+ // runtime. Deeper callees (`a.b.c(...)`) never reach here: their receiver
941
+ // path is recorded by the ordinary member walk, which cannot capture the
942
+ // method itself, so they were never exposed to the stale-method hazard.
943
+ function addMethodCall(info) {
944
+ const scope = analysis.nodeScopes.get(info.root);
945
+ const binding = scope ? resolveBinding(scope, info.root.name) : null;
946
+ if (
947
+ binding === null ||
948
+ binding.imported ||
949
+ binding.dependencyInvariant ||
950
+ (callbackScope !== null && scopeIsWithin(binding.scope, callbackScope))
951
+ ) {
952
+ return;
953
+ }
954
+ // Distinct from the plain-read key: `x.m` read as a value elsewhere in the
955
+ // callback still contributes its own member dependency.
956
+ const key = `b${binding.id}${info.path}()`;
957
+ if (!seen.has(key)) {
958
+ seen.add(key);
959
+ dependencies.push({
960
+ node: info.node,
961
+ key,
962
+ binding,
963
+ method: { root: info.root, name: info.name },
964
+ });
965
+ }
966
+ }
967
+
968
+ // Depth of enclosing functions whose directive prologue declares another
969
+ // execution context (see OPAQUE_EXECUTION_DIRECTIVES). Inside one, member
970
+ // chains truncate to their root bindings: the body's property reads happen
971
+ // in that other context, so hoisting them into a render-time dependency
972
+ // array would evaluate getters in a context where they may be illegal
973
+ // (TypeGPU's `.$`) and at a time the program never reads them.
974
+ let opaqueDepth = 0;
975
+
866
976
  function walk(node) {
867
977
  if (!node || typeof node !== 'object') return;
868
978
  if (Array.isArray(node)) {
@@ -878,13 +988,44 @@ function collectDependencies(expression, callbackScope, analysis) {
878
988
  case 'Identifier':
879
989
  addIdentifier(node);
880
990
  return;
991
+ case 'CallExpression': {
992
+ if (opaqueDepth > 0) {
993
+ // Roots only: the callee's receiver chain collapses through the
994
+ // MemberExpression case below; no method-pair dependency either,
995
+ // since it would read the member at render.
996
+ walk(node.callee);
997
+ walk(node.arguments);
998
+ return;
999
+ }
1000
+ // Optional spellings land here too: `a?.b(x)` and `a.b?.(x)` are a
1001
+ // CallExpression under a ChainExpression whose callee is a bare
1002
+ // (possibly optional) MemberExpression, which staticMemberInfo accepts.
1003
+ const callee = unwrapValue(node.callee);
1004
+ const info =
1005
+ callee?.type === 'MemberExpression' || callee?.type === 'ChainExpression'
1006
+ ? staticMemberInfo(callee)
1007
+ : null;
1008
+ if (info) addMethodCall(info);
1009
+ else walk(node.callee);
1010
+ walk(node.arguments);
1011
+ return;
1012
+ }
881
1013
  case 'ChainExpression': {
1014
+ if (opaqueDepth > 0) {
1015
+ walk(node.expression);
1016
+ return;
1017
+ }
882
1018
  const info = staticMemberInfo(node);
883
1019
  if (info) addStaticMember(info);
884
1020
  else walk(node.expression);
885
1021
  return;
886
1022
  }
887
1023
  case 'MemberExpression': {
1024
+ if (opaqueDepth > 0) {
1025
+ walk(node.object);
1026
+ if (node.computed) walk(node.property);
1027
+ return;
1028
+ }
888
1029
  const info = staticMemberInfo(node);
889
1030
  if (info) addStaticMember(info);
890
1031
  else {
@@ -913,10 +1054,14 @@ function collectDependencies(expression, callbackScope, analysis) {
913
1054
  return;
914
1055
  case 'FunctionDeclaration':
915
1056
  case 'FunctionExpression':
916
- case 'ArrowFunctionExpression':
1057
+ case 'ArrowFunctionExpression': {
1058
+ const opaque = hasOpaqueExecutionDirective(node);
1059
+ if (opaque) opaqueDepth++;
917
1060
  for (const param of node.params || []) walkPatternExpression(param);
918
1061
  walk(node.body);
1062
+ if (opaque) opaqueDepth--;
919
1063
  return;
1064
+ }
920
1065
  case 'ImportDeclaration':
921
1066
  case 'ExportAllDeclaration':
922
1067
  case 'MetaProperty':
@@ -1163,7 +1308,7 @@ function rebuildWithHookMetadata(ast, analysis, inferred, insertDeps) {
1163
1308
  args.splice(result.depsIndex, 0, {
1164
1309
  ...b.array(
1165
1310
  result.dependencies.map((/** @type {any} */ dependency) =>
1166
- cloneDependency(dependency.node),
1311
+ dependency.method ? methodDepNode(dependency) : cloneDependency(dependency.node),
1167
1312
  ),
1168
1313
  ),
1169
1314
  start: node.start,
@@ -1197,8 +1342,22 @@ export function annotateHookCalls(ast, options = {}) {
1197
1342
  * dependency arrays inserted at each candidate call. Copy-on-write — the input
1198
1343
  * AST is never modified; callers must use the returned module.
1199
1344
  */
1200
- /** @param {any} ast @param {{ onlyImported?: boolean, hookRuntimeModules?: readonly string[], filename?: string }} [options] */
1345
+ /** @param {any} ast @param {{ onlyImported?: boolean, hookRuntimeModules?: readonly string[], filename?: string, onRuntimeHelper?: (name: string) => void }} [options] */
1201
1346
  export function applyHookDependencies(ast, options = {}) {
1202
1347
  const { analysis, inferred } = analyzeInternal(ast, options);
1348
+ // The inserted `_$__methodDep(...)` calls need their aliased runtime import;
1349
+ // the caller owns import assembly, so report the requirement rather than
1350
+ // splicing an ImportDeclaration into a module whose runtime request
1351
+ // ('octane' vs 'octane/server') this pass cannot know.
1352
+ if (options.onRuntimeHelper !== undefined) {
1353
+ outer: for (const result of inferred.values()) {
1354
+ for (const dependency of result.dependencies) {
1355
+ if (dependency.method) {
1356
+ options.onRuntimeHelper(METHOD_DEP_IMPORT);
1357
+ break outer;
1358
+ }
1359
+ }
1360
+ }
1361
+ }
1203
1362
  return rebuildWithHookMetadata(ast, analysis, inferred, true).ast;
1204
1363
  }
@@ -16,7 +16,7 @@
16
16
 
17
17
  import { parseModule } from '@tsrx/core';
18
18
  import { HOOK_NAMES, hookSlotHash } from './compile.js';
19
- import { annotateHookCalls } from './hook-deps.js';
19
+ import { METHOD_DEP_IMPORT, annotateHookCalls } from './hook-deps.js';
20
20
  import { assertStrongMode } from './strong-mode.js';
21
21
 
22
22
  // Build a cheap import-presence gate. Precise call identity is annotated by the
@@ -940,9 +940,16 @@ function walk(node, owner, st) {
940
940
  // The dependency callback is already the final user argument. Insert
941
941
  // both the generated array and slot in one edit so equal-position edit
942
942
  // ordering cannot reverse them. Dependency nodes retain original source
943
- // offsets, preserving arbitrary TS syntax byte-for-byte.
943
+ // offsets, preserving arbitrary TS syntax byte-for-byte. Method-call
944
+ // dependencies are the one synthesized form: the helper call's root is
945
+ // a bare identifier and its name a JSON string, so no arbitrary TS
946
+ // syntax needs reprinting there either.
944
947
  const deps = inferred.dependencies
945
- .map((dependency) => st.source.slice(dependency.node.start, dependency.node.end))
948
+ .map((dependency) =>
949
+ dependency.method
950
+ ? `${requireParallelHelper(st, METHOD_DEP_IMPORT)}(${dependency.method.root.name}, ${JSON.stringify(dependency.method.name)})`
951
+ : st.source.slice(dependency.node.start, dependency.node.end),
952
+ )
946
953
  .join(', ');
947
954
  st.edits.push({
948
955
  pos: node.arguments[node.arguments.length - 1].end,
package/dist/index.d.ts CHANGED
@@ -3,3 +3,4 @@ export { initializeHydrationEventCapture } from './hydration/event-capture.js';
3
3
  export { createRoot, hydrateRoot, flushSync, act, type Root, type RootOptions, useState, useLinkedState, type LinkedStatePrevious, type LinkedStateOptions, useReducer, useEffect, useLayoutEffect, useInsertionEffect, useMemo, useCallback, useRef, useId, useImperativeHandle, useEffectEvent, useSyncExternalStore, useDeferredValue, useTransition, useActionState, useFormStatus, useOptimistic, useDebugValue, type FormStatus, startTransition, requestFormReset, memo, lazy, preload, preinit, preconnect, prefetchDNS, createContext, use, useContext, type Context, type ForeignHostContext, Suspense, ErrorBoundary, Hydrate, Activity, ViewTransition, addTransitionType, ViewTransition as unstable_ViewTransition, addTransitionType as unstable_addTransitionType, ViewTransitionPseudoElement, type ViewTransitionProps, type ViewTransitionInstance, Fragment, createPortal, type PortalDescriptor, createElement, cloneElement, isValidElement, isChildrenBlock, Children, type ElementDescriptor, type ComponentBody, type OctaneNode, TsrxErrorBoundary, __useStateWithGetter, __useLinkedStateWithGetter, __useReducerWithGetter, __createVoidRoot, bindRendererRegionOwner, EXTERNAL_HYDRATION_PROMISE, HYDRATION_RANGE_BOUNDARY, createHostContextRequest, __vtSeen, template, clone, drainFrag, bag0, bag1, bag2, bag3, bag4, bag5, bag6, bag7, bag8, bag9, bag10, bag11, bag12, bag13, bag14, bag15, bag16, bagOf, evt0, evt0u, evt1, evt1u, evt2, evt2u, evtN, evtNu, devEventListener, htext, htextSwap, child, sibling, setText, setScriptText, setHTML, setDangerouslySetInnerHTML, setDangerouslySetInnerHTMLSources, markDangerouslySetInnerHTMLChildren, setAttribute, setStringData, setBooleanAttribute, setAriaAttribute, setClassName, setClassAttr, normalizeClass, setStyle, setSpread, snapshotSpread, setHostPropSources, queueNativeChangeDiagnostic, markNativeChangeDiagnosticStatic, setFormAction, setValue, setFormControlSources, setChecked, setCheckedCheckable, setSelectValue, setDefaultValue, setDefaultValueUncontrolled, setDefaultChecked, setAutoFocus, attachRef, queueRefAttach, queueRefDetach, injectStyle, headBlock, namespaceHead, namespaceHeadElement, delegateEvents, delegateCaptureEvents, forBlock, mapSlot, ifBlock, tryBlock, switchBlock, activityBlock, componentSlot, componentSlotVoid, componentSlotLite, compilerCacheContext, markSingleRoot, markSingleRoot as __s, markChildrenBlock, createScopedValue, createScopedElement, childSlot, positionalChildren, textSlot, textHole, childTextHole, hostComponent, renderBlock, portal, hookSlots, withSlot, useBatch, warmMemo, warmChild, puMiss, puTake0, puTake1, puTake2, puTake3, puTake4, puPub, provideContext, mountFragmentRef, FragmentInstance, hmr, HMR, hasPendingWork, type Scope, type Block, drainPassiveEffects, setIsOctaneActEnvironment, setTransitionFallbackTimeout, getTransitionFallbackTimeout, } from './runtime.js';
4
4
  export type { HydrateOptions, HydrateProps, HydrateWhen, HydrationInteractionEvent, HydrationInteractionEvents, HydrationPrefetchContext, HydrationPrefetchFunction, HydrationPrefetchStrategy, HydrationPrefetchWaitReason, HydrationStrategy, HydrationWhen, } from './hydration/types.js';
5
5
  export { __serverRpc } from './server-rpc-client.js';
6
+ export { __methodDep } from './method-dep.js';
package/dist/index.js CHANGED
@@ -180,6 +180,7 @@ import {
180
180
  getTransitionFallbackTimeout
181
181
  } from "./runtime.js";
182
182
  import { __serverRpc } from "./server-rpc-client.js";
183
+ import { __methodDep } from "./method-dep.js";
183
184
  export {
184
185
  Activity,
185
186
  Children,
@@ -195,6 +196,7 @@ export {
195
196
  ViewTransition,
196
197
  ViewTransitionPseudoElement,
197
198
  __createVoidRoot,
199
+ __methodDep,
198
200
  markSingleRoot2 as __s,
199
201
  __serverRpc,
200
202
  __useLinkedStateWithGetter,
@@ -0,0 +1 @@
1
+ export declare function __methodDep(receiver: unknown, name: string): unknown;
@@ -0,0 +1,11 @@
1
+ const hasOwnProp = Object.prototype.hasOwnProperty;
2
+ function __methodDep(receiver, name) {
3
+ if ((typeof receiver !== "object" || receiver === null) && typeof receiver !== "function") {
4
+ return receiver;
5
+ }
6
+ if (hasOwnProp.call(receiver, name)) return receiver[name];
7
+ return name in receiver ? receiver : void 0;
8
+ }
9
+ export {
10
+ __methodDep
11
+ };
package/dist/runtime.d.ts CHANGED
@@ -385,14 +385,13 @@ export declare function flushSync<T>(fn: () => T): T;
385
385
  * Compiler-emitted on a host element's ref MOUNT. Defers the attach until commit
386
386
  * (drainRefAttaches) so the node is connected when a callback ref fires and
387
387
  * ref.current is set before layout effects run. Each entry records its owning
388
- * `block` plus an enqueue-order `seq`; drainRefAttaches sorts with
389
- * comparePostOrder (post-order via the parentBlock chain, seq as tiebreak) for
390
- * child-before-parent ordering, matching effect ordering. Ref identity UPDATES
391
- * queue here too (paired with a queueRefDetach of the old ref), so within one
392
- * commit every detach drains before every attach — a ref hopping between
393
- * elements never ends null, whichever binding updates first.
394
- */
395
- export declare function queueRefAttach(scope: Scope, fn: () => void): void;
388
+ * `block`; drainRefAttaches preserves enqueue order for disjoint subtrees and
389
+ * only reorders ancestor/descendant pairs for child-before-parent ordering.
390
+ * Ref identity UPDATES queue here too (paired with a queueRefDetach of the old
391
+ * ref), so within one commit every detach drains before every attach — a ref
392
+ * hopping between elements never ends null, whichever binding updates first.
393
+ */
394
+ export declare function queueRefAttach(scope: Scope, ref: any, el: Element | FragmentInstance): void;
396
395
  /**
397
396
  * Queue a teardown ref detach for commit (compiled `ref` binding / spread-ref /
398
397
  * hostComponent / fragment-ref unmount cleanups, and the de-opt teardown walk).
package/dist/runtime.js CHANGED
@@ -1018,6 +1018,9 @@ let passiveScheduled = false;
1018
1018
  let commitSeq = 0;
1019
1019
  const storeSyncQueue = [];
1020
1020
  const refAttachQueue = [];
1021
+ function attachLiveFragmentRef(instance) {
1022
+ attachRef(instance._currentRef, instance);
1023
+ }
1021
1024
  let WIP_CAPTURE = null;
1022
1025
  function createOffscreenCapture() {
1023
1026
  return {
@@ -1474,10 +1477,10 @@ function flushSync(fn) {
1474
1477
  }
1475
1478
  }
1476
1479
  const LAYOUT_CASCADE_LIMIT = 50;
1477
- function queueRefAttach(scope, fn) {
1480
+ function queueRefAttach(scope, ref, el) {
1478
1481
  (WIP_CAPTURE !== null ? WIP_CAPTURE.refs : refAttachQueue).push({
1479
- fn,
1480
- seq: commitSeq++,
1482
+ ref,
1483
+ el,
1481
1484
  block: scope.block
1482
1485
  });
1483
1486
  }
@@ -1532,13 +1535,13 @@ function drainRefDetaches() {
1532
1535
  function drainRefAttaches() {
1533
1536
  if (refAttachQueue.length === 0) return;
1534
1537
  const q = refAttachQueue.splice(0);
1535
- q.sort((a, b) => comparePostOrder(a.block, a.seq, b.block, b.seq));
1538
+ q.sort((a, b) => comparePostOrder(a.block, 0, b.block, 0));
1536
1539
  for (const r of q) {
1537
1540
  if (blockSubtreeDisposed(r.block)) continue;
1538
1541
  try {
1539
1542
  REF_CALLBACK_DEPTH++;
1540
1543
  try {
1541
- r.fn();
1544
+ attachRef(r.ref, r.el);
1542
1545
  } finally {
1543
1546
  REF_CALLBACK_DEPTH--;
1544
1547
  }
@@ -1558,6 +1561,30 @@ function blockSubtreeDisposed(block) {
1558
1561
  }
1559
1562
  return false;
1560
1563
  }
1564
+ function refAttachWasDiscarded(uncommitted, el, ref) {
1565
+ return uncommitted !== null && uncommitted.get(el) === ref;
1566
+ }
1567
+ function discardSubtreeRefAttachesFrom(queue, root, uncommitted) {
1568
+ let write = 0;
1569
+ for (let read = 0; read < queue.length; read++) {
1570
+ const entry = queue[read];
1571
+ const owner = entry.block;
1572
+ if (owner === root || owner !== null && blockIsAncestorOf(root, owner)) {
1573
+ uncommitted.set(
1574
+ entry.el,
1575
+ entry.ref === attachLiveFragmentRef ? entry.el._currentRef : entry.ref
1576
+ );
1577
+ continue;
1578
+ }
1579
+ queue[write++] = entry;
1580
+ }
1581
+ queue.length = write;
1582
+ }
1583
+ function discardSubtreeRefAttaches(root, uncommitted = /* @__PURE__ */ new Map()) {
1584
+ discardSubtreeRefAttachesFrom(refAttachQueue, root, uncommitted);
1585
+ if (WIP_CAPTURE !== null) discardSubtreeRefAttachesFrom(WIP_CAPTURE.refs, root, uncommitted);
1586
+ return uncommitted;
1587
+ }
1561
1588
  function commitEffects() {
1562
1589
  if (effectEventQueue.length === 0 && effectEventCommitActions.length === 0 && effectQueues[INSERTION].length === 0 && effectQueues[LAYOUT].length === 0 && refDetachQueue.length === 0 && refAttachQueue.length === 0 && storeSyncQueue.length === 0 && activeFragments.size === 0 && !hasControlledSyncs()) {
1563
1590
  if ((effectQueues[PASSIVE].length > 0 || pendingPassiveUnmounts.length > 0) && !passiveScheduled) {
@@ -6017,7 +6044,7 @@ function isFocusable(el) {
6017
6044
  function mountFragmentRef(scope, startMarker, endMarker, ref) {
6018
6045
  const fi = new FragmentInstance(scope.block, startMarker, endMarker);
6019
6046
  fi._currentRef = ref;
6020
- queueRefAttach(scope, () => attachRef(fi._currentRef, fi));
6047
+ queueRefAttach(scope, attachLiveFragmentRef, fi);
6021
6048
  (scope.cleanups ??= []).push(() => {
6022
6049
  queueRefDetach(fi._currentRef, fi);
6023
6050
  fi._destroy();
@@ -6552,7 +6579,7 @@ function setSpread(el, value, prev, mountScope, skipDangerouslySetInnerHTML = fa
6552
6579
  const pv = prev ? prev[k] : void 0;
6553
6580
  if (k === "ref") {
6554
6581
  if (v === pv) continue;
6555
- if (mountScope) queueRefAttach(mountScope, () => attachRef(v, el));
6582
+ if (mountScope) queueRefAttach(mountScope, v, el);
6556
6583
  else attachRef(v, el);
6557
6584
  continue;
6558
6585
  }
@@ -8875,7 +8902,7 @@ function positionalChildren(children) {
8875
8902
  }
8876
8903
  function applyDeoptProp(el, name, v, ownerBlock) {
8877
8904
  if (name === "ref") {
8878
- if (v != null) queueRefAttach(ownerBlock, () => attachRef(v, el));
8905
+ if (v != null) queueRefAttach(ownerBlock, v, el);
8879
8906
  } else if (name === "className" || name === "class") {
8880
8907
  setDeoptClass(el, v);
8881
8908
  } else if (name === "style") {
@@ -9000,7 +9027,7 @@ function applyHostProps(el, props, scope, state) {
9000
9027
  if (name === "ref") {
9001
9028
  if (v !== state.ref) {
9002
9029
  if (state.ref != null) queueRefDetach(state.ref, el);
9003
- if (v != null) queueRefAttach(scope, () => attachRef(v, el));
9030
+ if (v != null) queueRefAttach(scope, v, el);
9004
9031
  state.ref = v;
9005
9032
  }
9006
9033
  } else if (name === "className" || name === "class") {
@@ -11067,17 +11094,19 @@ function hideTryContentAndMountPending(state, resumeThenable) {
11067
11094
  }
11068
11095
  if (state.tryBlock) {
11069
11096
  const persistent = state.tryBlock;
11097
+ const uncommittedRefs = discardSubtreeRefAttaches(persistent);
11070
11098
  deactivateScope(persistent, false);
11071
11099
  if (state.parentBlock.disposed || persistent.disposed || state.tryBlock !== persistent) {
11072
11100
  return false;
11073
11101
  }
11074
11102
  if (state.detachedRefs === null) {
11075
11103
  state.detachedRefs = [];
11076
- detachSubtreeRefs(persistent, state.detachedRefs, true, false);
11104
+ detachSubtreeRefs(persistent, state.detachedRefs, true, false, uncommittedRefs);
11077
11105
  }
11078
11106
  if (state.parentBlock.disposed || persistent.disposed || state.tryBlock !== persistent) {
11079
11107
  return false;
11080
11108
  }
11109
+ discardSubtreeRefAttaches(persistent, uncommittedRefs);
11081
11110
  persistent.inactive = true;
11082
11111
  }
11083
11112
  if (!mountPendingBody(state)) return false;
@@ -11363,7 +11392,7 @@ function queueCurrentHiddenRefs(state) {
11363
11392
  collectVisibleSubtreeRefs(state.tryBlock, refs);
11364
11393
  for (let i = 0; i < refs.length; i++) {
11365
11394
  const entry = refs[i];
11366
- queueRefAttach(entry.scope, () => attachRef(entry.ref, entry.el));
11395
+ queueRefAttach(entry.scope, entry.ref, entry.el);
11367
11396
  }
11368
11397
  }
11369
11398
  function attemptHiddenReveal(state, scheduledMode) {
@@ -11488,14 +11517,12 @@ function compareStagedRevealDomOrder(a, b) {
11488
11517
  if (position & Node.DOCUMENT_POSITION_PRECEDING) return 1;
11489
11518
  return 0;
11490
11519
  }
11491
- function rebaseOffscreenCaptureSeq(capture) {
11520
+ function rebaseOffscreenEffectSeq(capture) {
11492
11521
  const effects = capture.effects[INSERTION].concat(
11493
11522
  capture.effects[LAYOUT],
11494
11523
  capture.effects[PASSIVE]
11495
11524
  ).sort((a, b) => a.seq - b.seq);
11496
11525
  for (let i = 0; i < effects.length; i++) effects[i].seq = commitSeq++;
11497
- const refs = capture.refs.slice().sort((a, b) => a.seq - b.seq);
11498
- for (let i = 0; i < refs.length; i++) refs[i].seq = commitSeq++;
11499
11526
  }
11500
11527
  function flushStagedReveals() {
11501
11528
  if (flushingStagedReveals) return;
@@ -11521,7 +11548,7 @@ function flushStagedReveals() {
11521
11548
  const deferEffects = batch.every((state) => state.stagedCapture !== null);
11522
11549
  if (deferEffects) {
11523
11550
  batch.sort(compareStagedRevealDomOrder);
11524
- for (const state of batch) rebaseOffscreenCaptureSeq(state.stagedCapture);
11551
+ for (const state of batch) rebaseOffscreenEffectSeq(state.stagedCapture);
11525
11552
  }
11526
11553
  const previousDeferral = deferringStagedRevealEffects;
11527
11554
  deferringStagedRevealEffects = deferEffects;
@@ -12515,9 +12542,9 @@ function forEachSubtreeChild(scope, visit, includeHiddenTry = true) {
12515
12542
  }
12516
12543
  }
12517
12544
  }
12518
- function detachSubtreeRefs(scope, out, shouldDetach = true, includeHiddenTry = true) {
12545
+ function detachSubtreeRefs(scope, out, shouldDetach = true, includeHiddenTry = true, uncommitted = null) {
12519
12546
  const deoptRoot = scope.deoptNode;
12520
- if (deoptRoot != null) detachDeoptTreeRefs(deoptRoot, out, shouldDetach, scope);
12547
+ if (deoptRoot != null) detachDeoptTreeRefs(deoptRoot, out, shouldDetach, scope, uncommitted);
12521
12548
  const rm = scope.refFields;
12522
12549
  if (rm !== null) {
12523
12550
  const bag = scope.slots[0];
@@ -12529,19 +12556,25 @@ function detachSubtreeRefs(scope, out, shouldDetach = true, includeHiddenTry = t
12529
12556
  if (ref == null) continue;
12530
12557
  const el = bag[rm[j + 2]];
12531
12558
  out.push({ ref, el, scope });
12532
- if (shouldDetach) attachRef(ref, null, el);
12559
+ if (shouldDetach && !refAttachWasDiscarded(uncommitted, el, ref)) {
12560
+ attachRef(ref, null, el);
12561
+ }
12533
12562
  } else if (kind === "s") {
12534
12563
  const ref = bag[rm[j + 1]]?.ref;
12535
12564
  if (ref == null) continue;
12536
12565
  const el = bag[rm[j + 2]];
12537
12566
  if (el == null) continue;
12538
12567
  out.push({ ref, el, scope });
12539
- if (shouldDetach) attachRef(ref, null, el);
12568
+ if (shouldDetach && !refAttachWasDiscarded(uncommitted, el, ref)) {
12569
+ attachRef(ref, null, el);
12570
+ }
12540
12571
  } else {
12541
12572
  const fi = bag[rm[j + 1]];
12542
12573
  if (fi == null || fi._currentRef == null) continue;
12543
12574
  out.push({ ref: fi._currentRef, el: fi, scope });
12544
- if (shouldDetach) attachRef(fi._currentRef, null, fi);
12575
+ if (shouldDetach && !refAttachWasDiscarded(uncommitted, fi, fi._currentRef)) {
12576
+ attachRef(fi._currentRef, null, fi);
12577
+ }
12545
12578
  }
12546
12579
  }
12547
12580
  }
@@ -12552,27 +12585,31 @@ function detachSubtreeRefs(scope, out, shouldDetach = true, includeHiddenTry = t
12552
12585
  if (s === null || typeof s !== "object") continue;
12553
12586
  if (s.ref != null && s.anchor !== void 0 && s.el instanceof Element) {
12554
12587
  out.push({ ref: s.ref, el: s.el, scope });
12555
- if (shouldDetach) attachRef(s.ref, null, s.el);
12588
+ if (shouldDetach && !refAttachWasDiscarded(uncommitted, s.el, s.ref)) {
12589
+ attachRef(s.ref, null, s.el);
12590
+ }
12556
12591
  }
12557
12592
  if (s.__kind === "childSlot" && s.hostNode != null) {
12558
- detachDeoptTreeRefs(s.hostNode, out, shouldDetach, scope);
12593
+ detachDeoptTreeRefs(s.hostNode, out, shouldDetach, scope, uncommitted);
12559
12594
  }
12560
12595
  }
12561
12596
  forEachSubtreeChild(
12562
12597
  scope,
12563
- (child2) => detachSubtreeRefs(child2, out, shouldDetach, includeHiddenTry),
12598
+ (child2) => detachSubtreeRefs(child2, out, shouldDetach, includeHiddenTry, uncommitted),
12564
12599
  includeHiddenTry
12565
12600
  );
12566
12601
  }
12567
12602
  function collectVisibleSubtreeRefs(scope, out) {
12568
12603
  detachSubtreeRefs(scope, out, false, false);
12569
12604
  }
12570
- function detachDeoptTreeRefs(node, out, shouldDetach = true, ownerScope) {
12605
+ function detachDeoptTreeRefs(node, out, shouldDetach = true, ownerScope, uncommitted = null) {
12571
12606
  const ref = getDeoptDesc(node)?.props?.ref;
12572
12607
  if (ref != null) {
12573
12608
  if (out !== null) {
12574
12609
  out.push({ ref, el: node, scope: ownerScope });
12575
- if (shouldDetach) attachRef(ref, null, node);
12610
+ if (shouldDetach && !refAttachWasDiscarded(uncommitted, node, ref)) {
12611
+ attachRef(ref, null, node);
12612
+ }
12576
12613
  } else {
12577
12614
  queueRefDetach(ref, node);
12578
12615
  }
@@ -12584,7 +12621,7 @@ function detachDeoptTreeRefs(node, out, shouldDetach = true, ownerScope) {
12584
12621
  c = nodeAfterPortalRange(c, rangeEnd);
12585
12622
  continue;
12586
12623
  }
12587
- detachDeoptTreeRefs(c, out, shouldDetach, ownerScope);
12624
+ detachDeoptTreeRefs(c, out, shouldDetach, ownerScope, uncommitted);
12588
12625
  c = c.nextSibling;
12589
12626
  }
12590
12627
  }
@@ -551,11 +551,15 @@ export interface RenderOptions {
551
551
  }
552
552
  export declare function setSsrSuspenseTimeout(ms: number): void;
553
553
  export declare function getSsrSuspenseTimeout(): number;
554
- type SuspenseOutcome = {
554
+ type SuspenseResult = {
555
555
  value: unknown;
556
556
  } | {
557
557
  reason: unknown;
558
558
  };
559
+ type SuspenseOutcome = SuspenseResult & {
560
+ /** Thenable whose settlement produced this string-keyed cached result. */
561
+ thenable: PromiseLike<unknown>;
562
+ };
559
563
  type ResolvedMap = Map<string, SuspenseOutcome> & {
560
564
  /** Render-local stable ids for non-primitive control/list keys. */
561
565
  asyncIdentities: Map<unknown, number>;
@@ -571,7 +575,7 @@ type ResolvedMap = Map<string, SuspenseOutcome> & {
571
575
  site: ServerHookSlot | undefined;
572
576
  frame: Frame | null;
573
577
  }>;
574
- resolvedT: Map<PromiseLike<unknown>, SuspenseOutcome>;
578
+ resolvedT: Map<PromiseLike<unknown>, SuspenseResult>;
575
579
  warm: Map<ServerHookSlot, {
576
580
  deps: unknown[];
577
581
  value: unknown;
@@ -2258,6 +2258,13 @@ function use(usable, siteKey) {
2258
2258
  const resolved = RESOLVED;
2259
2259
  if (resolved !== null && resolved.has(key)) {
2260
2260
  const entry = resolved.get(key);
2261
+ const thenable = usable;
2262
+ if (entry.thenable !== thenable) {
2263
+ try {
2264
+ thenable.then(NOOP, NOOP);
2265
+ } catch {
2266
+ }
2267
+ }
2261
2268
  if ("reason" in entry) {
2262
2269
  recordHydrationRejection(serial, entry.reason);
2263
2270
  throw entry.reason;
@@ -3124,11 +3131,11 @@ async function settleSuspended(suspended, resolved, timeoutMs, signal) {
3124
3131
  if (resolved.has(key)) return;
3125
3132
  const isPu = key.charCodeAt(0) === 124 && key.startsWith("|pu#");
3126
3133
  try {
3127
- const outcome = { value: await promise };
3134
+ const outcome = { value: await promise, thenable: promise };
3128
3135
  resolved.set(key, outcome);
3129
3136
  if (isPu) pu.resolvedT.set(promise, outcome);
3130
3137
  } catch (reason) {
3131
- const outcome = { reason };
3138
+ const outcome = { reason, thenable: promise };
3132
3139
  resolved.set(key, outcome);
3133
3140
  if (isPu) pu.resolvedT.set(promise, outcome);
3134
3141
  }
@@ -3147,11 +3154,13 @@ async function settleFirstOfWave(suspended, resolved, timeoutMs, signal) {
3147
3154
  (async () => {
3148
3155
  try {
3149
3156
  const value = await promise;
3150
- if (!resolved.has(key)) resolved.set(key, { value });
3151
- if (isPu && !pu.resolvedT.has(promise)) pu.resolvedT.set(promise, { value });
3157
+ const outcome = { value, thenable: promise };
3158
+ if (!resolved.has(key)) resolved.set(key, outcome);
3159
+ if (isPu && !pu.resolvedT.has(promise)) pu.resolvedT.set(promise, outcome);
3152
3160
  } catch (reason) {
3153
- if (!resolved.has(key)) resolved.set(key, { reason });
3154
- if (isPu && !pu.resolvedT.has(promise)) pu.resolvedT.set(promise, { reason });
3161
+ const outcome = { reason, thenable: promise };
3162
+ if (!resolved.has(key)) resolved.set(key, outcome);
3163
+ if (isPu && !pu.resolvedT.has(promise)) pu.resolvedT.set(promise, outcome);
3155
3164
  }
3156
3165
  })()
3157
3166
  );
@@ -3296,11 +3305,13 @@ function recordHostedStratum(suspended, resolved) {
3296
3305
  const isPu = key.startsWith("|pu#");
3297
3306
  try {
3298
3307
  const value = await promise;
3299
- if (!resolved.has(key)) resolved.set(key, { value });
3300
- if (isPu && !pu.resolvedT.has(promise)) pu.resolvedT.set(promise, { value });
3308
+ const outcome = { value, thenable: promise };
3309
+ if (!resolved.has(key)) resolved.set(key, outcome);
3310
+ if (isPu && !pu.resolvedT.has(promise)) pu.resolvedT.set(promise, outcome);
3301
3311
  } catch (reason) {
3302
- if (!resolved.has(key)) resolved.set(key, { reason });
3303
- if (isPu && !pu.resolvedT.has(promise)) pu.resolvedT.set(promise, { reason });
3312
+ const outcome = { reason, thenable: promise };
3313
+ if (!resolved.has(key)) resolved.set(key, outcome);
3314
+ if (isPu && !pu.resolvedT.has(promise)) pu.resolvedT.set(promise, outcome);
3304
3315
  }
3305
3316
  })()
3306
3317
  );
@@ -21,4 +21,5 @@
21
21
  * should call them.
22
22
  */
23
23
  export { executeServerFunction } from './rpc.js';
24
+ export { __methodDep } from '../method-dep.js';
24
25
  export { renderToString, renderToStaticMarkup, renderToPipeableStream, renderToReadableStream, type RenderResult, type RenderOptions, type StreamOptions, type StreamInjectionSource, setSsrSuspenseTimeout, getSsrSuspenseTimeout, EXTERNAL_HYDRATION_PROMISE, HYDRATION_RANGE_BOUNDARY, useState, useLinkedState, useReducer, __useStateWithGetter, __useLinkedStateWithGetter, __useReducerWithGetter, type LinkedStatePrevious, type LinkedStateOptions, useEffect, useLayoutEffect, useInsertionEffect, useImperativeHandle, useMemo, useCallback, useRef, useId, useEffectEvent, useTransition, useDeferredValue, useSyncExternalStore, useActionState, useFormStatus, useOptimistic, useDebugValue, memo, lazy, hookSlots, withSlot, startTransition, flushSync, isChildrenBlock, isValidElement, cloneElement, Children, createPortal, requestFormReset, preload, preinit, preconnect, prefetchDNS, Suspense, ErrorBoundary, Hydrate, Fragment, Activity, ViewTransition, ViewTransition as unstable_ViewTransition, addTransitionType, addTransitionType as unstable_addTransitionType, createContext, use, useContext, ssrIsSuspense, type Context, type FormStatus, markChildrenBlock, createElement, createScopedValue, createScopedElement, positionalChildren, escapeHtml, escapeAttr, ssrText, ssrTextPre, ssrChild, ssrChildText, ssrAttr, normalizeClass, ssrStyle, ssrClass, ssrAttrs, ssrSnapshotSpread, ssrSpread, ssrInnerHtml, ssrScriptInnerHtml, ssrChildrenSources, ssrVoidContent, ssrValueAttr, ssrCheckedAttr, ssrInputAttrs, ssrTextareaValue, ssrTextareaValueSources, ssrSelectAttrs, ssrSelectScope, ssrSelectScopeSources, ssrOptionValueSources, ssrOption, ssrElement, ssrComponent, ssrComponentNS, ssrInNamespace, ssrBlock, ssrActivity, ssrForBlock, mapSlot, ssrControl, ssrArm, ssrTry, ssrPortal, injectStyle, ssrHeadEl, namespaceHead, namespaceHeadElement, puMemo, puBatch, warmMemo, warmChild, } from '../runtime.server.js';
@@ -1,4 +1,5 @@
1
1
  import { executeServerFunction } from "./rpc.js";
2
+ import { __methodDep } from "../method-dep.js";
2
3
  import {
3
4
  renderToString,
4
5
  renderToStaticMarkup,
@@ -122,6 +123,7 @@ export {
122
123
  Hydrate,
123
124
  Suspense,
124
125
  ViewTransition,
126
+ __methodDep,
125
127
  __useLinkedStateWithGetter,
126
128
  __useReducerWithGetter,
127
129
  __useStateWithGetter,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "octane",
3
- "version": "0.1.22",
3
+ "version": "0.1.24",
4
4
  "license": "MIT",
5
5
  "type": "module",
6
6
  "sideEffects": false,