@rangojs/router 0.18.0 → 0.19.1

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 (244) hide show
  1. package/dist/types/browser/event-controller.d.ts +3 -3
  2. package/dist/types/browser/history-state.d.ts +2 -0
  3. package/dist/types/browser/navigation-transaction.d.ts +20 -2
  4. package/dist/types/browser/partial-update.d.ts +16 -1
  5. package/dist/types/browser/prefetch/cache.d.ts +6 -6
  6. package/dist/types/browser/react/Link.d.ts +19 -9
  7. package/dist/types/browser/react/context.d.ts +2 -2
  8. package/dist/types/browser/react/location-state-shared.d.ts +95 -56
  9. package/dist/types/browser/react/location-state.d.ts +1 -1
  10. package/dist/types/browser/transition-when.d.ts +80 -0
  11. package/dist/types/browser/types.d.ts +104 -9
  12. package/dist/types/build/generate-manifest.d.ts +8 -0
  13. package/dist/types/cache/background-task.d.ts +24 -2
  14. package/dist/types/cache/cache-exec-scope.d.ts +37 -0
  15. package/dist/types/cache/cache-key-utils.d.ts +44 -13
  16. package/dist/types/cache/cache-policy.d.ts +1 -1
  17. package/dist/types/cache/cache-runtime.d.ts +7 -0
  18. package/dist/types/cache/cache-scope.d.ts +117 -35
  19. package/dist/types/cache/cache-tag.d.ts +48 -5
  20. package/dist/types/cache/cf/cf-cache-store.d.ts +55 -44
  21. package/dist/types/cache/cf/cf-shell-frame.d.ts +1 -1
  22. package/dist/types/cache/cf/cf-tag-marker-memo.d.ts +0 -15
  23. package/dist/types/cache/document-cache.d.ts +10 -0
  24. package/dist/types/cache/handle-snapshot.d.ts +40 -8
  25. package/dist/types/cache/invalidation-order.d.ts +23 -0
  26. package/dist/types/cache/request-tag-mask.d.ts +54 -0
  27. package/dist/types/cache/shell-snapshot.d.ts +88 -141
  28. package/dist/types/cache/tag-invalidation.d.ts +38 -14
  29. package/dist/types/cache/types.d.ts +68 -61
  30. package/dist/types/cache/vercel/vercel-cache-store.d.ts +23 -16
  31. package/dist/types/client-urls/navigation.d.ts +28 -2
  32. package/dist/types/client-urls/server-projection.d.ts +5 -2
  33. package/dist/types/client-urls/types.d.ts +5 -5
  34. package/dist/types/client.d.ts +1 -1
  35. package/dist/types/index.d.ts +1 -1
  36. package/dist/types/index.rsc.d.ts +1 -1
  37. package/dist/types/route-definition/helpers-types.d.ts +11 -11
  38. package/dist/types/route-name.d.ts +7 -0
  39. package/dist/types/router/error-handling.d.ts +1 -1
  40. package/dist/types/router/intercept-resolution.d.ts +2 -0
  41. package/dist/types/router/match-context.d.ts +5 -0
  42. package/dist/types/router/match-middleware/background-revalidation.d.ts +0 -4
  43. package/dist/types/router/router-options.d.ts +4 -1
  44. package/dist/types/router/segment-resolution/loader-cache.d.ts +18 -1
  45. package/dist/types/router/segment-resolution/loader-mask.d.ts +3 -4
  46. package/dist/types/router/segment-resolution/loader-snapshot.d.ts +14 -40
  47. package/dist/types/router/segment-resolution/mask-nested.d.ts +11 -3
  48. package/dist/types/router/segment-resolution/view-transition-default.d.ts +7 -5
  49. package/dist/types/router/transition-when.d.ts +24 -10
  50. package/dist/types/rsc/attach-transition-when.d.ts +18 -0
  51. package/dist/types/rsc/full-payload.d.ts +14 -7
  52. package/dist/types/rsc/response-cache-serve.d.ts +15 -12
  53. package/dist/types/rsc/shell-build-manifest.d.ts +4 -3
  54. package/dist/types/rsc/shell-capture-constants.d.ts +77 -20
  55. package/dist/types/rsc/shell-capture.d.ts +78 -81
  56. package/dist/types/rsc/shell-serve.d.ts +19 -48
  57. package/dist/types/rsc/types.d.ts +2 -0
  58. package/dist/types/segment-system.d.ts +7 -0
  59. package/dist/types/server/context.d.ts +126 -61
  60. package/dist/types/server/cookie-store.d.ts +30 -18
  61. package/dist/types/server/handle-store.d.ts +52 -1
  62. package/dist/types/server/request-context.d.ts +66 -45
  63. package/dist/types/server/request-headers.d.ts +44 -0
  64. package/dist/types/server.d.ts +1 -0
  65. package/dist/types/testing/e2e/index.d.ts +1 -1
  66. package/dist/types/testing/index.d.ts +3 -2
  67. package/dist/types/testing/internal/action-id.d.ts +7 -0
  68. package/dist/types/testing/location-state-key.d.ts +24 -0
  69. package/dist/types/testing/render-route.d.ts +17 -1
  70. package/dist/types/testing/run-transition-when.d.ts +44 -69
  71. package/dist/types/testing/serve-shell-request.d.ts +7 -6
  72. package/dist/types/testing/shell-status.d.ts +34 -22
  73. package/dist/types/transition-when-ref.d.ts +79 -0
  74. package/dist/types/types/cache-types.d.ts +15 -3
  75. package/dist/types/types/index.d.ts +1 -1
  76. package/dist/types/types/segments.d.ts +86 -71
  77. package/dist/types/urls/path-helper-types.d.ts +3 -4
  78. package/dist/types/urls/pattern-types.d.ts +3 -4
  79. package/dist/types/vite/plugins/transition-when-hoist.d.ts +45 -0
  80. package/dist/vite/index.js +962 -97
  81. package/package.json +1 -1
  82. package/skills/cache-guide/SKILL.md +76 -49
  83. package/skills/caching/SKILL.md +167 -69
  84. package/skills/client-urls/SKILL.md +1 -1
  85. package/skills/comparison/references/framework-comparison.md +8 -5
  86. package/skills/document-cache/SKILL.md +24 -0
  87. package/skills/hooks/SKILL.md +3 -1
  88. package/skills/hooks/navigation.md +23 -2
  89. package/skills/hooks/state.md +98 -6
  90. package/skills/intercept/SKILL.md +29 -12
  91. package/skills/loader/SKILL.md +52 -21
  92. package/skills/middleware/SKILL.md +18 -0
  93. package/skills/migrate-nextjs/SKILL.md +3 -3
  94. package/skills/observability/SKILL.md +6 -5
  95. package/skills/parallel/SKILL.md +1 -1
  96. package/skills/ppr/SKILL.md +129 -77
  97. package/skills/prerender/SKILL.md +3 -3
  98. package/skills/rango/SKILL.md +25 -6
  99. package/skills/route/SKILL.md +2 -0
  100. package/skills/testing/SKILL.md +27 -25
  101. package/skills/testing/cache-prerender.md +9 -9
  102. package/skills/testing/client-components.md +45 -3
  103. package/skills/testing/loader.md +3 -2
  104. package/skills/testing/middleware.md +1 -0
  105. package/skills/testing/predicates.md +49 -49
  106. package/skills/testing/render-handler.md +1 -0
  107. package/skills/testing/serve-shell-request.md +14 -0
  108. package/skills/testing/server-actions.md +9 -3
  109. package/skills/theme/SKILL.md +8 -4
  110. package/skills/typesafety/env-and-bindings.md +8 -0
  111. package/skills/use-cache/SKILL.md +67 -19
  112. package/skills/view-transitions/SKILL.md +150 -29
  113. package/src/__augment-tests__/augmented.check.ts +23 -0
  114. package/src/browser/event-controller.ts +4 -4
  115. package/src/browser/history-state.ts +52 -1
  116. package/src/browser/link-interceptor.ts +4 -0
  117. package/src/browser/navigation-bridge.ts +77 -18
  118. package/src/browser/navigation-client.ts +4 -3
  119. package/src/browser/navigation-store.ts +43 -1
  120. package/src/browser/navigation-transaction.ts +31 -11
  121. package/src/browser/partial-update.ts +146 -35
  122. package/src/browser/prefetch/cache.ts +6 -6
  123. package/src/browser/prefetch/fetch.ts +2 -2
  124. package/src/browser/react/Link.tsx +38 -33
  125. package/src/browser/react/NavigationProvider.tsx +2 -2
  126. package/src/browser/react/context.ts +2 -2
  127. package/src/browser/react/location-state-shared.ts +241 -49
  128. package/src/browser/react/location-state.ts +5 -2
  129. package/src/browser/rsc-router.tsx +4 -0
  130. package/src/browser/server-action-bridge.ts +84 -15
  131. package/src/browser/transition-when.ts +205 -0
  132. package/src/browser/types.ts +124 -10
  133. package/src/build/generate-manifest.ts +77 -1
  134. package/src/cache/background-task.ts +55 -2
  135. package/src/cache/cache-exec-scope.ts +54 -1
  136. package/src/cache/cache-key-utils.ts +56 -16
  137. package/src/cache/cache-policy.ts +9 -4
  138. package/src/cache/cache-runtime.ts +202 -144
  139. package/src/cache/cache-scope.ts +304 -112
  140. package/src/cache/cache-tag.ts +209 -13
  141. package/src/cache/cf/cf-cache-store.ts +152 -76
  142. package/src/cache/cf/cf-shell-frame.ts +2 -2
  143. package/src/cache/cf/cf-tag-marker-memo.ts +11 -100
  144. package/src/cache/document-cache.ts +53 -7
  145. package/src/cache/handle-snapshot.ts +52 -9
  146. package/src/cache/invalidation-order.ts +84 -0
  147. package/src/cache/request-tag-mask.ts +147 -0
  148. package/src/cache/shell-snapshot.ts +130 -348
  149. package/src/cache/tag-invalidation.ts +57 -51
  150. package/src/cache/types.ts +71 -67
  151. package/src/cache/vercel/vercel-cache-store.ts +61 -58
  152. package/src/client-urls/client-root.tsx +8 -4
  153. package/src/client-urls/client-urls.ts +8 -6
  154. package/src/client-urls/navigation.ts +55 -4
  155. package/src/client-urls/server-projection.ts +31 -10
  156. package/src/client-urls/types.ts +12 -5
  157. package/src/client.tsx +2 -0
  158. package/src/host/cookie-handler.ts +2 -1
  159. package/src/index.rsc.ts +3 -0
  160. package/src/index.ts +3 -0
  161. package/src/loader-redirect.tsx +7 -3
  162. package/src/prerender/build-shell-capture.ts +155 -146
  163. package/src/route-definition/dsl-helpers.ts +2 -0
  164. package/src/route-definition/helpers-types.ts +11 -11
  165. package/src/route-name.ts +12 -0
  166. package/src/router/content-negotiation.ts +4 -1
  167. package/src/router/error-handling.ts +5 -2
  168. package/src/router/handler-context.ts +10 -7
  169. package/src/router/intercept-resolution.ts +11 -1
  170. package/src/router/loader-resolution.ts +42 -5
  171. package/src/router/logging.ts +5 -3
  172. package/src/router/match-api.ts +62 -34
  173. package/src/router/match-context.ts +5 -0
  174. package/src/router/match-handlers.ts +9 -29
  175. package/src/router/match-middleware/background-revalidation.ts +3 -5
  176. package/src/router/match-middleware/cache-lookup.ts +35 -19
  177. package/src/router/match-middleware/cache-store.ts +15 -9
  178. package/src/router/match-result.ts +1 -0
  179. package/src/router/middleware.ts +2 -5
  180. package/src/router/navigation-snapshot.ts +6 -5
  181. package/src/router/prerender-match.ts +4 -0
  182. package/src/router/request-classification.ts +7 -4
  183. package/src/router/router-options.ts +4 -1
  184. package/src/router/segment-resolution/fresh.ts +0 -4
  185. package/src/router/segment-resolution/loader-cache.ts +152 -26
  186. package/src/router/segment-resolution/loader-mask.ts +3 -4
  187. package/src/router/segment-resolution/loader-snapshot.ts +15 -78
  188. package/src/router/segment-resolution/mask-nested.ts +47 -22
  189. package/src/router/segment-resolution/revalidation.ts +0 -3
  190. package/src/router/segment-resolution/view-transition-default.ts +6 -21
  191. package/src/router/telemetry.ts +6 -3
  192. package/src/router/transition-when.ts +34 -52
  193. package/src/router.ts +2 -1
  194. package/src/rsc/attach-transition-when.ts +47 -0
  195. package/src/rsc/full-payload.ts +30 -10
  196. package/src/rsc/handler.ts +12 -10
  197. package/src/rsc/loader-fetch.ts +2 -1
  198. package/src/rsc/manifest-init.ts +4 -0
  199. package/src/rsc/origin-guard.ts +8 -5
  200. package/src/rsc/progressive-enhancement.ts +7 -30
  201. package/src/rsc/response-cache-serve.ts +68 -80
  202. package/src/rsc/response-route-handler.ts +2 -2
  203. package/src/rsc/rsc-rendering.ts +206 -252
  204. package/src/rsc/server-action.ts +8 -20
  205. package/src/rsc/shell-build-manifest.ts +22 -20
  206. package/src/rsc/shell-capture-constants.ts +136 -20
  207. package/src/rsc/shell-capture.ts +352 -589
  208. package/src/rsc/shell-serve.ts +73 -109
  209. package/src/rsc/ssr-setup.ts +3 -2
  210. package/src/rsc/types.ts +2 -0
  211. package/src/segment-system.tsx +40 -7
  212. package/src/server/context.ts +263 -124
  213. package/src/server/cookie-store.ts +153 -127
  214. package/src/server/handle-store.ts +245 -27
  215. package/src/server/request-context.ts +134 -87
  216. package/src/server/request-headers.ts +134 -0
  217. package/src/server.ts +4 -0
  218. package/src/testing/dispatch.ts +4 -7
  219. package/src/testing/e2e/index.ts +1 -0
  220. package/src/testing/index.ts +5 -0
  221. package/src/testing/internal/action-id.ts +25 -0
  222. package/src/testing/internal/context.ts +1 -1
  223. package/src/testing/location-state-key.ts +45 -0
  224. package/src/testing/render-handler.ts +1 -1
  225. package/src/testing/render-route.tsx +109 -16
  226. package/src/testing/run-client-revalidate.ts +3 -19
  227. package/src/testing/run-loader.ts +5 -1
  228. package/src/testing/run-transition-when.ts +109 -172
  229. package/src/testing/serve-shell-request.ts +10 -7
  230. package/src/testing/shell-status.ts +60 -56
  231. package/src/transition-when-ref.ts +220 -0
  232. package/src/types/cache-types.ts +15 -3
  233. package/src/types/index.ts +3 -0
  234. package/src/types/segments.ts +94 -91
  235. package/src/urls/path-helper-types.ts +3 -4
  236. package/src/urls/pattern-types.ts +3 -4
  237. package/src/vite/discovery/discover-routers.ts +3 -0
  238. package/src/vite/discovery/shell-prerender-phase.ts +0 -2
  239. package/src/vite/discovery/virtual-module-codegen.ts +16 -6
  240. package/src/vite/plugins/transition-when-hoist.ts +1064 -0
  241. package/src/vite/rango.ts +2 -0
  242. package/src/vite/router-discovery.ts +75 -5
  243. package/dist/types/rsc/transition-gate.d.ts +0 -23
  244. package/src/rsc/transition-gate.ts +0 -86
@@ -17,14 +17,46 @@ import {
17
17
  } from "../server/request-context.js";
18
18
  import {
19
19
  getCurrentLoaderBodyId,
20
+ getLoaderRunIdentity,
20
21
  getLoaderBodyTags,
21
22
  isInsideLoaderScope,
23
+ useCacheLoaderIdentityError,
22
24
  type LoaderIdentityRead,
23
25
  type LoaderIdentityReadVerb,
26
+ type LoaderRunIdentity,
24
27
  } from "../server/context.js";
28
+ import {
29
+ getCacheExecScope,
30
+ isInsideIdentityExempt,
31
+ type CacheExecScope,
32
+ } from "./cache-exec-scope.js";
33
+ import type { ExecutionStart } from "./tag-invalidation.js";
25
34
 
26
35
  const cacheTagStorage = new AsyncLocalStorage<Set<string>>();
27
36
 
37
+ /**
38
+ * Each "use cache" tag scope's enclosing scope when it opened (#980). A tag
39
+ * recorded in a scope goes to every scope above it: an intermediate call can
40
+ * return a value holding a nested call still running, whose tags land after
41
+ * the intermediate call reported its own to its caller. Its caller's encode
42
+ * awaits the same promise, so the tags are in before it reads its own.
43
+ */
44
+ const scopeParents = new WeakMap<Set<string>, Set<string>>();
45
+
46
+ /** Add `tag` to `scope` and every scope enclosing it. */
47
+ function addToScopes(scope: Set<string> | undefined, tag: string): void {
48
+ for (let s = scope; s; s = scopeParents.get(s)) s.add(tag);
49
+ }
50
+
51
+ /**
52
+ * Call `fn` outside any "use cache" tag scope: detached work a cached body
53
+ * schedules (a stale entry's background refresh) records nothing onto the
54
+ * entry being filled.
55
+ */
56
+ export function outsideCacheTagScope<T>(fn: () => T): T {
57
+ return cacheTagStorage.exit(fn);
58
+ }
59
+
28
60
  /**
29
61
  * Tag owners (#957). A route cache() record replays its segments without
30
62
  * running their handlers or re-rendering their server components, so the
@@ -287,7 +319,7 @@ export function cacheTag(...tags: string[]): void {
287
319
  }
288
320
  continue;
289
321
  }
290
- store.add(normalized);
322
+ addToScopes(store, normalized);
291
323
  }
292
324
  return;
293
325
  }
@@ -457,21 +489,172 @@ export function recordedIdentityRead(
457
489
  return undefined;
458
490
  }
459
491
 
460
- /** `root`'s tags plus those of every set it links to, transitively. */
461
- export function flattenRecordedTags(root: Set<string>): Set<string> {
462
- const out = new Set<string>();
492
+ /**
493
+ * Loader value identity (#1011). Each loader execution's value promise maps to
494
+ * its LoaderRunIdentity (server/context.ts); a loader-cache binding's value
495
+ * carries its reads on its recorded-tag set instead (tagLoaderValue,
496
+ * markIdentityRead). On globalThis like identityReads: the executor that
497
+ * tracks a value and the reader that checks it must share the map.
498
+ */
499
+ const loaderRuns: WeakMap<object, LoaderRunIdentity> = ((globalThis as any)[
500
+ Symbol.for("rangojs-router:loader-runs")
501
+ ] ??= new WeakMap());
502
+
503
+ /**
504
+ * `value` is the promise of the execution `run` records, started by the
505
+ * current execution (if any), which reads it. Both loader runners call it
506
+ * (loader-resolution.ts createLoaderExecutor, request-context.ts
507
+ * createUseFunction).
508
+ */
509
+ export function trackLoaderRun(value: object, run: LoaderRunIdentity): void {
510
+ loaderRuns.set(value, run);
511
+ linkLoaderRun(value);
512
+ }
513
+
514
+ /** The current loader execution reads `value`: its record links the value's. */
515
+ function linkLoaderRun(value: object): void {
516
+ const reader = getLoaderRunIdentity();
517
+ const run = loaderRuns.get(value);
518
+ if (reader && run && reader !== run) (reader.reads ??= new Set()).add(run);
519
+ }
520
+
521
+ /**
522
+ * The identity read behind a loader value: its execution's or one it read
523
+ * (nearest first), or the one its recorded-tag set links to.
524
+ */
525
+ function loaderValueIdentityRead(
526
+ value: object,
527
+ ): LoaderIdentityRead | undefined {
528
+ const root = loaderRuns.get(value);
529
+ if (root) {
530
+ const queue = [root];
531
+ const seen = new Set<LoaderRunIdentity>();
532
+ for (let i = 0; i < queue.length; i++) {
533
+ const run = queue[i]!;
534
+ if (seen.has(run)) continue;
535
+ seen.add(run);
536
+ if (run.read) return run.read;
537
+ for (const linked of run.reads ?? []) queue.push(linked);
538
+ }
539
+ }
540
+ const tags = loaderValueTags.get(value);
541
+ return tags && recordedIdentityRead(tags);
542
+ }
543
+
544
+ /**
545
+ * A ctx.use(Loader) or getRequestContext().use(Loader) read that returns an
546
+ * already-started value (the request memo, a loader cache() binding's value).
547
+ * The current loader execution, if any, links it (it reads the value).
548
+ * Inside a "use cache" body the value is refused when its execution read
549
+ * request identity, as a body entered there would have been
550
+ * (guardIdentityRead). The check runs once the value settles, so a read the
551
+ * body makes after an await counts. Each enclosing execution also notes the
552
+ * value, and checks it again after encoding its result
553
+ * (assertLoaderReadsClean): a read that settles after the value (a nested
554
+ * promise in it) then fails the write instead.
555
+ */
556
+ export function readStartedLoaderValue<T>(
557
+ value: Promise<T>,
558
+ loaderId: string,
559
+ ): Promise<T> {
560
+ linkLoaderRun(value);
561
+ const scope = getCacheExecScope();
562
+ if (!scope || isInsideIdentityExempt()) return value;
563
+ for (let s: CacheExecScope | undefined = scope; s; s = s.parent) {
564
+ (s.loaderReads ??= new Map()).set(value, loaderId);
565
+ }
566
+ const checked = value.then((resolved) => {
567
+ assertLoaderValueClean(value, loaderId);
568
+ return resolved;
569
+ });
570
+ // A read the body never awaits must not crash the process as an unhandled
571
+ // rejection; the write check (assertLoaderReadsClean) still refuses it.
572
+ checked.catch(() => {});
573
+ return checked;
574
+ }
575
+
576
+ /** Throw when a loader value `scope` read carries an identity read. */
577
+ export function assertLoaderReadsClean(scope: CacheExecScope): void {
578
+ for (const [value, loaderId] of scope.loaderReads ?? []) {
579
+ assertLoaderValueClean(value, loaderId);
580
+ }
581
+ }
582
+
583
+ function assertLoaderValueClean(value: object, loaderId: string): void {
584
+ const read = loaderValueIdentityRead(value);
585
+ if (read) throw useCacheLoaderIdentityError(read, loaderId);
586
+ }
587
+
588
+ /** `root` and every set it links to, transitively. */
589
+ function linkedSets(root: Set<string>): Set<Set<string>> {
463
590
  const seen = new Set<Set<string>>();
464
591
  const pending = [root];
465
592
  for (let set = pending.pop(); set !== undefined; set = pending.pop()) {
466
593
  if (seen.has(set)) continue;
467
594
  seen.add(set);
468
- for (const tag of set) out.add(tag);
469
595
  for (const linked of tagLinks.get(set) ?? []) pending.push(linked);
470
596
  }
597
+ return seen;
598
+ }
599
+
600
+ /** `root`'s tags plus those of every set it links to, transitively. */
601
+ export function flattenRecordedTags(root: Set<string>): Set<string> {
602
+ const out = new Set<string>();
603
+ for (const set of linkedSets(root)) {
604
+ for (const tag of set) out.add(tag);
605
+ }
471
606
  return out;
472
607
  }
473
608
 
474
- /** recordRequestTags, also recording onto `owner`'s set (#957). */
609
+ /**
610
+ * Where each execution that records into a set started (#977): a loader
611
+ * execution (createLoaderExecutor), a loader-cache MISS or refresh, a
612
+ * loader-cache binding's value.
613
+ */
614
+ const tagSetStarts = new WeakMap<Set<string>, ExecutionStart>();
615
+
616
+ /** Record where the execution recording into `set` started. */
617
+ export function markTagSetStart(set: Set<string>, start: ExecutionStart): void {
618
+ tagSetStarts.set(set, start);
619
+ }
620
+
621
+ /**
622
+ * The earliest start among `root` and every set it links to: a value built
623
+ * from another execution's value is as old as the oldest of them. A
624
+ * loader-cache write gates on it (loader-cache.ts), because the loader value
625
+ * it stores can be a run a reader started before the binding executed, and
626
+ * the values that run read can be older still.
627
+ */
628
+ export function earliestRecordedStart(
629
+ root: Set<string>,
630
+ ): ExecutionStart | undefined {
631
+ let earliest: ExecutionStart | undefined;
632
+ for (const set of linkedSets(root)) {
633
+ const start = tagSetStarts.get(set);
634
+ if (!start) continue;
635
+ earliest = earliest
636
+ ? {
637
+ seq: Math.min(earliest.seq, start.seq),
638
+ at: Math.min(earliest.at, start.at),
639
+ }
640
+ : start;
641
+ }
642
+ return earliest;
643
+ }
644
+
645
+ /**
646
+ * recordRequestTags, also recording onto `owner`'s set (#957), the active
647
+ * loader tag set (#964), and the enclosing "use cache" execution's tag
648
+ * scope (#980).
649
+ *
650
+ * The last one is how a nested "use cache" call's tags reach the entry that
651
+ * bakes its value: the inner wrapper records its tags (a miss's, a hit's
652
+ * stored ones, a joined execution's) after its own scope closed, so the
653
+ * scope current then is the caller's. Without it an outer entry kept the
654
+ * inner value past updateTag() of an inner tag, and the same-request gate
655
+ * (tag-invalidation.ts invalidatedSince) did not see the outer execution
656
+ * as tagged by it.
657
+ */
475
658
  function recordOwnedTags(
476
659
  tags: Iterable<string> | undefined,
477
660
  ctx: RequestContext | undefined,
@@ -480,12 +663,14 @@ function recordOwnedTags(
480
663
  if (!tags || !ctx?._requestTags) return;
481
664
  const set = ctx._requestTags;
482
665
  const captured = activeTagSet();
666
+ const enclosing = cacheTagStorage.getStore();
483
667
  let owned: Set<string> | undefined;
484
668
  for (const tag of tags) {
485
669
  const normalized = normalizeTag(tag);
486
670
  if (normalized === null) continue;
487
671
  set.add(normalized);
488
672
  captured?.add(normalized);
673
+ addToScopes(enclosing, normalized);
489
674
  if (owner !== undefined) {
490
675
  owned ??= setFor(ownersFor(ctx).tags, owner);
491
676
  owned.add(normalized);
@@ -494,20 +679,31 @@ function recordOwnedTags(
494
679
  }
495
680
 
496
681
  /**
497
- * Run a function within a cache tag scope. Any cacheTag() calls inside `fn`
498
- * accumulate into the returned Set.
682
+ * Run a function within a cache tag scope. Any cacheTag() calls inside `fn`,
683
+ * and the tags a nested "use cache" call records (recordOwnedTags, #980),
684
+ * accumulate into the returned Set and every scope enclosing it
685
+ * (scopeParents).
499
686
  *
500
687
  * The returned Set is the LIVE reference - the caller must await `result`
501
688
  * before reading `tags`, because an async cached function may call cacheTag()
502
- * after an await boundary.
689
+ * after an await boundary. Pass `tagSet` to re-enter an execution's scope
690
+ * (its result's Flight encode, #980); it keeps the parent it opened under.
503
691
  *
504
692
  * @internal Used by cache-runtime.ts to wrap "use cache" execution.
505
693
  */
506
- export function runWithCacheTagScope<T>(fn: () => T): {
694
+ export function runWithCacheTagScope<T>(
695
+ fn: () => T,
696
+ tagSet?: Set<string>,
697
+ ): {
507
698
  result: T;
508
699
  tags: Set<string>;
509
700
  } {
510
- const tagSet = new Set<string>();
511
- const result = cacheTagStorage.run(tagSet, fn);
512
- return { result, tags: tagSet };
701
+ let scope = tagSet;
702
+ if (!scope) {
703
+ scope = new Set<string>();
704
+ const parent = cacheTagStorage.getStore();
705
+ if (parent) scopeParents.set(scope, parent);
706
+ }
707
+ const result = cacheTagStorage.run(scope, fn);
708
+ return { result, tags: scope };
513
709
  }