@rangojs/router 0.16.0 → 0.18.0

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 (309) hide show
  1. package/README.md +255 -89
  2. package/dist/testing/vitest.js +252 -0
  3. package/dist/types/browser/cookie-name.d.ts +6 -0
  4. package/dist/types/browser/history-state.d.ts +16 -0
  5. package/dist/types/browser/navigation-transaction.d.ts +6 -0
  6. package/dist/types/browser/partial-update.d.ts +11 -0
  7. package/dist/types/browser/react/ScrollRestoration.d.ts +6 -6
  8. package/dist/types/browser/rsc-router.d.ts +1 -1
  9. package/dist/types/browser/scroll-restoration.d.ts +7 -0
  10. package/dist/types/cache/cache-exec-scope.d.ts +18 -1
  11. package/dist/types/cache/cache-key-utils.d.ts +17 -2
  12. package/dist/types/cache/cache-policy.d.ts +17 -0
  13. package/dist/types/cache/cache-scope.d.ts +132 -19
  14. package/dist/types/cache/cache-tag.d.ts +92 -7
  15. package/dist/types/cache/cf/cf-base64.d.ts +6 -0
  16. package/dist/types/cache/cf/cf-cache-constants.d.ts +11 -0
  17. package/dist/types/cache/cf/cf-cache-store.d.ts +185 -44
  18. package/dist/types/cache/cf/cf-cache-types.d.ts +31 -4
  19. package/dist/types/cache/cf/cf-shell-frame.d.ts +95 -0
  20. package/dist/types/cache/cf/cf-tag-marker-memo.d.ts +34 -0
  21. package/dist/types/cache/handle-capture.d.ts +41 -3
  22. package/dist/types/cache/handle-snapshot.d.ts +54 -13
  23. package/dist/types/cache/index.d.ts +1 -0
  24. package/dist/types/cache/isolate-tag-memo.d.ts +149 -0
  25. package/dist/types/cache/segment-codec.d.ts +10 -3
  26. package/dist/types/cache/shell-memo.d.ts +178 -0
  27. package/dist/types/cache/shell-snapshot.d.ts +110 -21
  28. package/dist/types/cache/tag-invalidation.d.ts +30 -3
  29. package/dist/types/cache/taint.d.ts +1 -1
  30. package/dist/types/cache/types.d.ts +220 -44
  31. package/dist/types/cache/vercel/vercel-cache-store.d.ts +111 -13
  32. package/dist/types/client.d.ts +4 -4
  33. package/dist/types/client.rsc.d.ts +4 -5
  34. package/dist/types/components/DefaultDocument.d.ts +5 -2
  35. package/dist/types/context-var.d.ts +6 -5
  36. package/dist/types/escape-script.d.ts +1 -1
  37. package/dist/types/handles/MetaTags.d.ts +5 -4
  38. package/dist/types/handles/Scripts.d.ts +8 -7
  39. package/dist/types/handles/meta.d.ts +2 -2
  40. package/dist/types/handles/script.d.ts +17 -16
  41. package/dist/types/href-client.d.ts +1 -1
  42. package/dist/types/html.d.ts +12 -0
  43. package/dist/types/index.d.ts +1 -1
  44. package/dist/types/index.rsc.d.ts +3 -2
  45. package/dist/types/loader-store.d.ts +7 -4
  46. package/dist/types/prerender/build-shell-capture.d.ts +11 -4
  47. package/dist/types/reverse.d.ts +36 -4
  48. package/dist/types/route-definition/dsl-helpers.d.ts +3 -3
  49. package/dist/types/route-definition/helpers-types.d.ts +33 -9
  50. package/dist/types/route-definition/resolve-handler-use.d.ts +23 -1
  51. package/dist/types/route-types.d.ts +1 -1
  52. package/dist/types/router/debug-manifest.d.ts +3 -2
  53. package/dist/types/router/error-handling.d.ts +20 -0
  54. package/dist/types/router/handler-context.d.ts +6 -0
  55. package/dist/types/router/instrument.d.ts +8 -0
  56. package/dist/types/router/match-middleware/background-revalidation.d.ts +46 -5
  57. package/dist/types/router/middleware-types.d.ts +9 -3
  58. package/dist/types/router/router-context.d.ts +3 -1
  59. package/dist/types/router/router-interfaces.d.ts +24 -2
  60. package/dist/types/router/router-options.d.ts +8 -5
  61. package/dist/types/router/segment-resolution/fresh.d.ts +10 -0
  62. package/dist/types/router/segment-resolution/loader-cache.d.ts +66 -1
  63. package/dist/types/router/segment-resolution/loader-mask.d.ts +9 -19
  64. package/dist/types/router/segment-resolution/loader-snapshot.d.ts +23 -12
  65. package/dist/types/router/segment-resolution/mask-nested.d.ts +67 -26
  66. package/dist/types/router/segment-resolution/revalidation.d.ts +6 -2
  67. package/dist/types/router/segment-wrappers.d.ts +1 -1
  68. package/dist/types/rsc/full-payload.d.ts +14 -0
  69. package/dist/types/rsc/handler.d.ts +1 -1
  70. package/dist/types/rsc/response-route-handler.d.ts +1 -1
  71. package/dist/types/rsc/rsc-rendering.d.ts +1 -2
  72. package/dist/types/rsc/shell-build-manifest.d.ts +8 -0
  73. package/dist/types/rsc/shell-capture-constants.d.ts +61 -6
  74. package/dist/types/rsc/shell-capture.d.ts +151 -19
  75. package/dist/types/rsc/shell-serve.d.ts +140 -17
  76. package/dist/types/rsc/ssr-setup.d.ts +6 -6
  77. package/dist/types/rsc/types.d.ts +17 -0
  78. package/dist/types/server/context.d.ts +139 -15
  79. package/dist/types/server/cookie-store.d.ts +21 -0
  80. package/dist/types/server/handle-store.d.ts +26 -2
  81. package/dist/types/server/request-context.d.ts +182 -53
  82. package/dist/types/ssr/index.d.ts +24 -0
  83. package/dist/types/ssr/ssr-root.d.ts +9 -8
  84. package/dist/types/static-handler.d.ts +4 -1
  85. package/dist/types/testing/flight.entry.d.ts +7 -0
  86. package/dist/types/testing/generated-routes.d.ts +2 -2
  87. package/dist/types/testing/internal/context.d.ts +1 -1
  88. package/dist/types/testing/render-route.d.ts +32 -6
  89. package/dist/types/testing/run-loader.d.ts +18 -3
  90. package/dist/types/testing/serve-shell-request.d.ts +132 -0
  91. package/dist/types/testing/shell-status.d.ts +23 -10
  92. package/dist/types/testing/vitest-stubs/plugin-rsc.d.ts +28 -5
  93. package/dist/types/testing/vitest.d.ts +35 -1
  94. package/dist/types/theme/ThemeScript.d.ts +3 -3
  95. package/dist/types/types/cache-types.d.ts +14 -10
  96. package/dist/types/types/handler-context.d.ts +28 -1
  97. package/dist/types/types/loader-types.d.ts +8 -6
  98. package/dist/types/urls/pattern-types.d.ts +22 -18
  99. package/dist/types/urls/response-types.d.ts +3 -12
  100. package/dist/types/use-loader.d.ts +12 -3
  101. package/dist/types/vite/plugins/use-cache-transform.d.ts +33 -2
  102. package/dist/types/vite/rango.d.ts +1 -1
  103. package/dist/types/vite/router-discovery.d.ts +29 -2
  104. package/dist/vite/index.js +1420 -1348
  105. package/package.json +2 -2
  106. package/skills/api-client/SKILL.md +22 -10
  107. package/skills/breadcrumbs/SKILL.md +75 -22
  108. package/skills/bundle-analysis/SKILL.md +22 -22
  109. package/skills/cache-guide/SKILL.md +306 -124
  110. package/skills/caching/SKILL.md +530 -98
  111. package/skills/catalog.json +16 -16
  112. package/skills/client-urls/SKILL.md +65 -38
  113. package/skills/cloudflare/SKILL.md +87 -20
  114. package/skills/cloudflare/references/d1-and-local-dev.md +15 -2
  115. package/skills/cloudflare/references/streaming-and-deploy.md +10 -3
  116. package/skills/cloudflare/references/webhooks-and-crypto.md +9 -2
  117. package/skills/comparison/SKILL.md +5 -0
  118. package/skills/comparison/references/framework-comparison.md +75 -57
  119. package/skills/composability/SKILL.md +38 -29
  120. package/skills/css/SKILL.md +10 -13
  121. package/skills/debug-manifest/SKILL.md +74 -31
  122. package/skills/defer-hydration/SKILL.md +52 -31
  123. package/skills/deployment-caching/SKILL.md +11 -3
  124. package/skills/document-cache/SKILL.md +98 -28
  125. package/skills/fonts/SKILL.md +16 -8
  126. package/skills/handler-use/SKILL.md +55 -17
  127. package/skills/hooks/SKILL.md +28 -20
  128. package/skills/hooks/data.md +23 -3
  129. package/skills/hooks/handle-and-actions.md +34 -17
  130. package/skills/hooks/navigation.md +39 -10
  131. package/skills/hooks/outlets.md +13 -6
  132. package/skills/hooks/state.md +39 -15
  133. package/skills/hooks/urls.md +7 -5
  134. package/skills/host-router/SKILL.md +64 -12
  135. package/skills/i18n/SKILL.md +60 -34
  136. package/skills/intercept/SKILL.md +161 -26
  137. package/skills/layout/SKILL.md +106 -50
  138. package/skills/links/SKILL.md +51 -19
  139. package/skills/loader/SKILL.md +386 -128
  140. package/skills/middleware/SKILL.md +129 -26
  141. package/skills/migrate-nextjs/SKILL.md +156 -70
  142. package/skills/migrate-nextjs/backend-host-swap.md +5 -2
  143. package/skills/migrate-react-router/SKILL.md +51 -39
  144. package/skills/migrate-react-router/component-migration.md +28 -7
  145. package/skills/migrate-react-router/data-and-actions.md +21 -6
  146. package/skills/migrate-react-router/route-mapping.md +19 -10
  147. package/skills/mime-routes/SKILL.md +12 -7
  148. package/skills/observability/SKILL.md +92 -14
  149. package/skills/parallel/SKILL.md +88 -47
  150. package/skills/ppr/SKILL.md +866 -379
  151. package/skills/prerender/SKILL.md +210 -108
  152. package/skills/rango/SKILL.md +115 -65
  153. package/skills/react-compiler/SKILL.md +6 -5
  154. package/skills/response-routes/SKILL.md +133 -25
  155. package/skills/route/SKILL.md +170 -60
  156. package/skills/router-setup/SKILL.md +420 -240
  157. package/skills/scripts/SKILL.md +39 -21
  158. package/skills/server-actions/SKILL.md +231 -59
  159. package/skills/shell-manifest/SKILL.md +28 -14
  160. package/skills/streams-and-websockets/SKILL.md +32 -14
  161. package/skills/tailwind/SKILL.md +37 -33
  162. package/skills/testing/SKILL.md +73 -33
  163. package/skills/testing/cache-prerender.md +60 -33
  164. package/skills/testing/client-components.md +59 -6
  165. package/skills/testing/e2e-parity.md +12 -2
  166. package/skills/testing/flight.md +19 -11
  167. package/skills/testing/handles.md +8 -5
  168. package/skills/testing/loader.md +26 -6
  169. package/skills/testing/middleware.md +8 -3
  170. package/skills/testing/predicates.md +120 -0
  171. package/skills/testing/render-handler.md +70 -22
  172. package/skills/testing/response-routes.md +11 -4
  173. package/skills/testing/reverse-and-types.md +20 -14
  174. package/skills/testing/serve-shell-request.md +188 -0
  175. package/skills/testing/server-actions.md +12 -3
  176. package/skills/testing/server-tree.md +9 -0
  177. package/skills/testing/setup.md +9 -5
  178. package/skills/theme/SKILL.md +155 -30
  179. package/skills/typesafety/SKILL.md +20 -9
  180. package/skills/typesafety/env-and-bindings.md +137 -50
  181. package/skills/typesafety/generated-files-and-cli.md +26 -14
  182. package/skills/typesafety/params-and-search.md +44 -19
  183. package/skills/typesafety/route-types.md +50 -14
  184. package/skills/use-cache/SKILL.md +224 -83
  185. package/skills/vercel/SKILL.md +35 -13
  186. package/skills/view-transitions/SKILL.md +42 -21
  187. package/src/__augment-tests__/augmented.check.ts +26 -1
  188. package/src/browser/cookie-name.ts +23 -0
  189. package/src/browser/history-state.ts +23 -0
  190. package/src/browser/index.ts +2 -2
  191. package/src/browser/navigation-bridge.ts +3 -4
  192. package/src/browser/navigation-transaction.ts +22 -2
  193. package/src/browser/partial-update.ts +35 -25
  194. package/src/browser/react/ScrollRestoration.tsx +6 -6
  195. package/src/browser/rsc-router.tsx +6 -1
  196. package/src/browser/scroll-restoration.ts +53 -6
  197. package/src/cache/cache-exec-scope.ts +37 -4
  198. package/src/cache/cache-key-utils.ts +23 -8
  199. package/src/cache/cache-policy.ts +22 -0
  200. package/src/cache/cache-runtime.ts +264 -68
  201. package/src/cache/cache-scope.ts +419 -43
  202. package/src/cache/cache-tag.ts +375 -11
  203. package/src/cache/cf/cf-base64.ts +36 -4
  204. package/src/cache/cf/cf-cache-constants.ts +12 -0
  205. package/src/cache/cf/cf-cache-store.ts +990 -348
  206. package/src/cache/cf/cf-cache-types.ts +32 -4
  207. package/src/cache/cf/cf-shell-frame.ts +258 -0
  208. package/src/cache/cf/cf-tag-marker-memo.ts +143 -39
  209. package/src/cache/document-cache.ts +15 -0
  210. package/src/cache/handle-capture.ts +90 -10
  211. package/src/cache/handle-snapshot.ts +133 -28
  212. package/src/cache/index.ts +2 -0
  213. package/src/cache/isolate-tag-memo.ts +308 -0
  214. package/src/cache/segment-codec.ts +26 -7
  215. package/src/cache/shell-memo.ts +341 -0
  216. package/src/cache/shell-snapshot.ts +217 -27
  217. package/src/cache/tag-invalidation.ts +108 -22
  218. package/src/cache/taint.ts +1 -1
  219. package/src/cache/types.ts +225 -44
  220. package/src/cache/vercel/vercel-cache-store.ts +545 -61
  221. package/src/client.rsc.tsx +10 -9
  222. package/src/client.tsx +5 -4
  223. package/src/components/DefaultDocument.tsx +7 -2
  224. package/src/context-var.ts +6 -5
  225. package/src/escape-script.ts +1 -1
  226. package/src/handles/MetaTags.tsx +13 -11
  227. package/src/handles/Scripts.tsx +11 -10
  228. package/src/handles/meta.ts +2 -2
  229. package/src/handles/script.ts +17 -16
  230. package/src/href-client.ts +1 -1
  231. package/src/html.ts +12 -0
  232. package/src/index.rsc.ts +5 -0
  233. package/src/index.ts +1 -0
  234. package/src/loader-store.ts +7 -4
  235. package/src/prerender/build-shell-capture.ts +73 -22
  236. package/src/reverse.ts +44 -4
  237. package/src/route-definition/dsl-helpers.ts +121 -33
  238. package/src/route-definition/helpers-types.ts +33 -9
  239. package/src/route-definition/resolve-handler-use.ts +121 -6
  240. package/src/route-types.ts +0 -3
  241. package/src/router/debug-manifest.ts +10 -2
  242. package/src/router/error-handling.ts +51 -15
  243. package/src/router/handler-context.ts +71 -28
  244. package/src/router/instrument.ts +54 -2
  245. package/src/router/intercept-resolution.ts +141 -43
  246. package/src/router/loader-resolution.ts +115 -39
  247. package/src/router/match-api.ts +40 -26
  248. package/src/router/match-handlers.ts +5 -0
  249. package/src/router/match-middleware/background-revalidation.ts +161 -96
  250. package/src/router/match-middleware/cache-lookup.ts +110 -10
  251. package/src/router/match-middleware/cache-store.ts +48 -101
  252. package/src/router/middleware-types.ts +9 -6
  253. package/src/router/middleware.ts +75 -13
  254. package/src/router/prerender-match.ts +59 -19
  255. package/src/router/route-snapshot.ts +11 -1
  256. package/src/router/router-context.ts +2 -0
  257. package/src/router/router-interfaces.ts +25 -2
  258. package/src/router/router-options.ts +8 -5
  259. package/src/router/segment-resolution/fresh.ts +62 -21
  260. package/src/router/segment-resolution/helpers.ts +4 -2
  261. package/src/router/segment-resolution/loader-cache.ts +407 -76
  262. package/src/router/segment-resolution/loader-mask.ts +12 -22
  263. package/src/router/segment-resolution/loader-snapshot.ts +208 -56
  264. package/src/router/segment-resolution/mask-nested.ts +138 -44
  265. package/src/router/segment-resolution/revalidation.ts +61 -16
  266. package/src/router/segment-wrappers.ts +4 -0
  267. package/src/rsc/full-payload.ts +25 -1
  268. package/src/rsc/handler.ts +14 -2
  269. package/src/rsc/progressive-enhancement.ts +3 -2
  270. package/src/rsc/render-pipeline.ts +3 -0
  271. package/src/rsc/response-cache-serve.ts +9 -6
  272. package/src/rsc/response-route-handler.ts +11 -3
  273. package/src/rsc/rsc-rendering.ts +608 -225
  274. package/src/rsc/shell-build-manifest.ts +27 -9
  275. package/src/rsc/shell-capture-constants.ts +73 -6
  276. package/src/rsc/shell-capture.ts +1068 -296
  277. package/src/rsc/shell-serve.ts +316 -34
  278. package/src/rsc/ssr-setup.ts +14 -8
  279. package/src/rsc/types.ts +18 -0
  280. package/src/server/context.ts +300 -32
  281. package/src/server/cookie-store.ts +87 -61
  282. package/src/server/handle-store.ts +232 -7
  283. package/src/server/request-context.ts +297 -67
  284. package/src/ssr/index.tsx +136 -44
  285. package/src/ssr/ssr-root.tsx +9 -8
  286. package/src/static-handler.ts +4 -1
  287. package/src/testing/flight-runtime.d.ts +21 -0
  288. package/src/testing/flight.entry.ts +14 -0
  289. package/src/testing/generated-routes.ts +2 -2
  290. package/src/testing/internal/context.ts +1 -1
  291. package/src/testing/internal/flight-client-globals.ts +7 -5
  292. package/src/testing/render-route.tsx +85 -24
  293. package/src/testing/run-loader.ts +68 -6
  294. package/src/testing/serve-shell-request.ts +378 -0
  295. package/src/testing/shell-status.ts +34 -12
  296. package/src/testing/vitest-stubs/plugin-rsc.ts +123 -20
  297. package/src/testing/vitest.ts +62 -1
  298. package/src/theme/ThemeProvider.tsx +7 -6
  299. package/src/theme/ThemeScript.tsx +3 -3
  300. package/src/types/cache-types.ts +14 -10
  301. package/src/types/handler-context.ts +28 -1
  302. package/src/types/loader-types.ts +8 -6
  303. package/src/urls/pattern-types.ts +22 -18
  304. package/src/urls/response-types.ts +3 -23
  305. package/src/use-loader.tsx +12 -3
  306. package/src/vite/discovery/shell-prerender-phase.ts +9 -0
  307. package/src/vite/plugins/use-cache-transform.ts +118 -74
  308. package/src/vite/rango.ts +7 -3
  309. package/src/vite/router-discovery.ts +14 -4
@@ -1,3 +1,5 @@
1
+ import { getCurrentLoaderBodyId, isInsideLoaderScope } from "./context.js";
2
+
1
3
  /**
2
4
  * Handle data structure: handleName -> segmentId -> entries[]
3
5
  *
@@ -45,6 +47,24 @@ function createLateHandlePushError(
45
47
  return error;
46
48
  }
47
49
 
50
+ /**
51
+ * Per-slot flags, parallel to one handle/segment values array. Allocated on
52
+ * the array's first flagged slot; positions past its length are unflagged.
53
+ */
54
+ interface SlotTag {
55
+ /** Pushed inside a DSL loader scope; captureHandles drops it. */
56
+ loader?: boolean;
57
+ /** A loader-cache replay of this loader's recorded push (pushReplayed). */
58
+ replayOf?: string;
59
+ /** Pushed by this loader's live run after it replaced its replayed slots. */
60
+ liveOf?: string;
61
+ /** Pushed by this loader's body and kept in records (push `owner`). */
62
+ owner?: string;
63
+ }
64
+
65
+ // Shared by every plain DSL-loader push; tags are never mutated in place.
66
+ const LOADER_SLOT: SlotTag = Object.freeze({ loader: true });
67
+
48
68
  function cloneHandleData(data: HandleData): HandleData {
49
69
  const clone: HandleData = {};
50
70
  for (const handleName in data) {
@@ -116,8 +136,35 @@ export interface HandleStore {
116
136
  * Push handle data for a specific handle and segment.
117
137
  * Multiple pushes to the same handle/segment accumulate in an array.
118
138
  * Each push triggers an emission on the stream.
139
+ *
140
+ * `loaderPush` overrides the push-time DSL-loader tag
141
+ * (isInsideLoaderScope()) that getDataForSegment(id, true) excludes. The
142
+ * PPR shell capture passes false for the bake-lane pushes its record keeps
143
+ * (shell-capture.ts deriveShellCaptureContext), with `owner` set to the
144
+ * pushing loader's id so the record can restore them through pushReplayed
145
+ * (getRecordOwners).
119
146
  */
120
- push(handleName: string, segmentId: string, data: unknown): void;
147
+ push(
148
+ handleName: string,
149
+ segmentId: string,
150
+ data: unknown,
151
+ loaderPush?: boolean,
152
+ owner?: string,
153
+ ): void;
154
+
155
+ /**
156
+ * push() a loader-cache HIT's replayed value, recorded from `loaderId`'s
157
+ * body (loader-cache.ts replayLoaderHandles). If that loader then runs live
158
+ * in this request, its first push removes every value replayed for it and
159
+ * takes the first one's position (later live pushes follow it), so the
160
+ * handle output stays live without a duplicate.
161
+ */
162
+ pushReplayed(
163
+ handleName: string,
164
+ segmentId: string,
165
+ data: unknown,
166
+ loaderId: string,
167
+ ): void;
121
168
 
122
169
  /**
123
170
  * Get all collected handle data after all handlers have settled.
@@ -151,8 +198,23 @@ export interface HandleStore {
151
198
  /**
152
199
  * Get handle data for a specific segment (for caching).
153
200
  * Returns data in format: { handleName: [values...] }
201
+ *
202
+ * `excludeLoaderPushes` drops values tagged as DSL-loader pushes at push
203
+ * time (see push); see captureHandles.
204
+ */
205
+ getDataForSegment(
206
+ segmentId: string,
207
+ excludeLoaderPushes?: boolean,
208
+ ): Record<string, unknown[]>;
209
+
210
+ /**
211
+ * The push `owner` of each value in getDataForSegment(segmentId, true),
212
+ * index-aligned per handle (null: no owner). Handles without an owned
213
+ * value are omitted; undefined when none has one.
154
214
  */
155
- getDataForSegment(segmentId: string): Record<string, unknown[]>;
215
+ getRecordOwners(
216
+ segmentId: string,
217
+ ): Record<string, (string | null)[]> | undefined;
156
218
 
157
219
  /**
158
220
  * Replay cached handle data back into the store (for cache hits).
@@ -186,6 +248,88 @@ export interface HandleStore {
186
248
  */
187
249
  export function createHandleStore(): HandleStore {
188
250
  const data: HandleData = {};
251
+ // SlotTag arrays, keyed by the per-handle/segment values array. Positional
252
+ // (not value identity) so primitive values are covered too.
253
+ // replaySegmentData installs fresh arrays, so restored values are untagged.
254
+ const slotTags = new WeakMap<unknown[], (SlotTag | undefined)[]>();
255
+ // Replayed loaders that have not pushed live yet: loaderId -> the arrays
256
+ // holding their replayed slots.
257
+ const pendingReplays = new Map<string, Set<unknown[]>>();
258
+ // Replayed loaders whose live run already replaced those slots.
259
+ const replacedReplays = new Set<string>();
260
+
261
+ // Aligned to `values` so a mid-array insert or removal keeps positions.
262
+ function tagsFor(values: unknown[]): (SlotTag | undefined)[] {
263
+ let tags = slotTags.get(values);
264
+ if (!tags) slotTags.set(values, (tags = []));
265
+ if (tags.length < values.length) tags.length = values.length;
266
+ return tags;
267
+ }
268
+
269
+ function insertSlot(
270
+ values: unknown[],
271
+ at: number | undefined,
272
+ value: unknown,
273
+ tag: SlotTag | undefined,
274
+ ): void {
275
+ if (at === undefined || at >= values.length) {
276
+ values.push(value);
277
+ if (tag) tagsFor(values)[values.length - 1] = tag;
278
+ return;
279
+ }
280
+ const tags = tagsFor(values);
281
+ values.splice(at, 0, value);
282
+ tags.splice(at, 0, tag);
283
+ }
284
+
285
+ // Remove `loaderId`'s replayed slots; returns the first one's position.
286
+ function removeReplayedSlots(
287
+ values: unknown[],
288
+ loaderId: string,
289
+ ): number | undefined {
290
+ const tags = slotTags.get(values);
291
+ if (!tags) return undefined;
292
+ let first: number | undefined;
293
+ let kept = 0;
294
+ for (let i = 0; i < values.length; i++) {
295
+ if (tags[i]?.replayOf === loaderId) {
296
+ first ??= kept;
297
+ continue;
298
+ }
299
+ values[kept] = values[i];
300
+ tags[kept] = tags[i];
301
+ kept++;
302
+ }
303
+ values.length = kept;
304
+ tags.length = kept;
305
+ return first;
306
+ }
307
+
308
+ // Where a live push by a replayed loader lands in `values`: its first push
309
+ // removes all of its replayed slots and takes the first one's position in
310
+ // this array; later pushes follow its previous live push. undefined appends.
311
+ function liveReplacementIndex(
312
+ loaderId: string,
313
+ values: unknown[],
314
+ ): number | undefined {
315
+ const pending = pendingReplays.get(loaderId);
316
+ if (pending) {
317
+ pendingReplays.delete(loaderId);
318
+ replacedReplays.add(loaderId);
319
+ let at: number | undefined;
320
+ for (const replayed of pending) {
321
+ const first = removeReplayedSlots(replayed, loaderId);
322
+ if (replayed === values) at = first;
323
+ }
324
+ return at;
325
+ }
326
+ const tags = slotTags.get(values);
327
+ if (!tags) return undefined;
328
+ for (let i = tags.length - 1; i >= 0; i--) {
329
+ if (tags[i]?.liveOf === loaderId) return i + 1;
330
+ }
331
+ return undefined;
332
+ }
189
333
 
190
334
  // Settlement barriers: `settled` (handler lane) resolves when sealed AND
191
335
  // handler inflight === 0. `fullySettled` additionally waits for the
@@ -320,7 +464,13 @@ export function createHandleStore(): HandleStore {
320
464
  });
321
465
  },
322
466
 
323
- push(handleName: string, segmentId: string, value: unknown): void {
467
+ push(
468
+ handleName: string,
469
+ segmentId: string,
470
+ value: unknown,
471
+ loaderPush?: boolean,
472
+ owner?: string,
473
+ ): void {
324
474
  if (completed) {
325
475
  const error = createLateHandlePushError(handleName, segmentId);
326
476
  if (this.onError) this.onError(error);
@@ -333,13 +483,58 @@ export function createHandleStore(): HandleStore {
333
483
  if (!data[handleName][segmentId]) {
334
484
  data[handleName][segmentId] = [];
335
485
  }
336
- data[handleName][segmentId].push(value);
486
+ const values = data[handleName][segmentId];
487
+ const loader = (loaderPush ?? isInsideLoaderScope()) || undefined;
488
+ let at: number | undefined;
489
+ let liveOf: string | undefined;
490
+ if (pendingReplays.size > 0 || replacedReplays.size > 0) {
491
+ const bodyId = getCurrentLoaderBodyId();
492
+ if (
493
+ bodyId !== undefined &&
494
+ (pendingReplays.has(bodyId) || replacedReplays.has(bodyId))
495
+ ) {
496
+ at = liveReplacementIndex(bodyId, values);
497
+ liveOf = bodyId;
498
+ }
499
+ }
500
+ insertSlot(
501
+ values,
502
+ at,
503
+ value,
504
+ liveOf || owner
505
+ ? { loader, liveOf, owner }
506
+ : loader
507
+ ? LOADER_SLOT
508
+ : undefined,
509
+ );
337
510
 
338
511
  // Bump the version; each consumer's cursor decides when to clone+yield.
339
512
  version++;
340
513
  signalEmission();
341
514
  },
342
515
 
516
+ pushReplayed(
517
+ handleName: string,
518
+ segmentId: string,
519
+ value: unknown,
520
+ loaderId: string,
521
+ ): void {
522
+ const before = data[handleName]?.[segmentId]?.length ?? 0;
523
+ // Through this.push: active captures see the replay like a live push.
524
+ this.push(handleName, segmentId, value);
525
+ const values = data[handleName]?.[segmentId];
526
+ // A diverting capture kept it out of the store.
527
+ if (!values || values.length !== before + 1) return;
528
+ const tags = tagsFor(values);
529
+ tags[values.length - 1] = {
530
+ ...tags[values.length - 1],
531
+ replayOf: loaderId,
532
+ };
533
+ let arrays = pendingReplays.get(loaderId);
534
+ if (!arrays) pendingReplays.set(loaderId, (arrays = new Set()));
535
+ arrays.add(values);
536
+ },
537
+
343
538
  getData(): Promise<HandleData> {
344
539
  sealInternal();
345
540
  return this.settled.then(() => cloneHandleData(data));
@@ -417,12 +612,42 @@ export function createHandleStore(): HandleStore {
417
612
  }
418
613
  },
419
614
 
420
- getDataForSegment(segmentId: string): Record<string, unknown[]> {
615
+ getDataForSegment(
616
+ segmentId: string,
617
+ excludeLoaderPushes?: boolean,
618
+ ): Record<string, unknown[]> {
421
619
  const result: Record<string, unknown[]> = {};
422
620
  for (const handleName in data) {
423
- if (data[handleName][segmentId]) {
424
- result[handleName] = [...data[handleName][segmentId]];
621
+ const values = data[handleName][segmentId];
622
+ if (!values) continue;
623
+ const tags = excludeLoaderPushes ? slotTags.get(values) : undefined;
624
+ if (!tags) {
625
+ result[handleName] = [...values];
626
+ continue;
627
+ }
628
+ const kept = values.filter((_, i) => !tags[i]?.loader);
629
+ if (kept.length > 0) result[handleName] = kept;
630
+ }
631
+ return result;
632
+ },
633
+
634
+ getRecordOwners(
635
+ segmentId: string,
636
+ ): Record<string, (string | null)[]> | undefined {
637
+ let result: Record<string, (string | null)[]> | undefined;
638
+ for (const handleName in data) {
639
+ const values = data[handleName][segmentId];
640
+ const tags = values && slotTags.get(values);
641
+ if (!tags) continue;
642
+ const owners: (string | null)[] = [];
643
+ let owned = false;
644
+ for (let i = 0; i < values.length; i++) {
645
+ if (tags[i]?.loader) continue;
646
+ const owner = tags[i]?.owner ?? null;
647
+ if (owner) owned = true;
648
+ owners.push(owner);
425
649
  }
650
+ if (owned) (result ??= {})[handleName] = owners;
426
651
  }
427
652
  return result;
428
653
  },