@rangojs/router 0.0.0-experimental.b9cb8739 → 0.0.0-experimental.bd6e11bc

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 (285) hide show
  1. package/README.md +196 -43
  2. package/dist/bin/rango.js +277 -99
  3. package/dist/testing/vitest.js +48 -0
  4. package/dist/vite/index.js +2779 -1064
  5. package/dist/vite/index.js.bak +5448 -0
  6. package/dist/vite/plugins/cloudflare-protocol-loader-hook.mjs +76 -0
  7. package/package.json +57 -11
  8. package/skills/breadcrumbs/SKILL.md +3 -1
  9. package/skills/bundle-analysis/SKILL.md +159 -0
  10. package/skills/cache-guide/SKILL.md +243 -21
  11. package/skills/caching/SKILL.md +155 -6
  12. package/skills/composability/SKILL.md +27 -2
  13. package/skills/document-cache/SKILL.md +78 -55
  14. package/skills/handler-use/SKILL.md +364 -0
  15. package/skills/hooks/SKILL.md +229 -20
  16. package/skills/host-router/SKILL.md +45 -20
  17. package/skills/i18n/SKILL.md +276 -0
  18. package/skills/intercept/SKILL.md +46 -4
  19. package/skills/layout/SKILL.md +28 -7
  20. package/skills/links/SKILL.md +249 -17
  21. package/skills/loader/SKILL.md +273 -53
  22. package/skills/middleware/SKILL.md +49 -12
  23. package/skills/migrate-nextjs/SKILL.md +562 -0
  24. package/skills/migrate-react-router/SKILL.md +769 -0
  25. package/skills/mime-routes/SKILL.md +27 -0
  26. package/skills/observability/SKILL.md +137 -0
  27. package/skills/parallel/SKILL.md +197 -6
  28. package/skills/prerender/SKILL.md +123 -100
  29. package/skills/rango/SKILL.md +242 -22
  30. package/skills/react-compiler/SKILL.md +168 -0
  31. package/skills/response-routes/SKILL.md +66 -9
  32. package/skills/route/SKILL.md +88 -4
  33. package/skills/router-setup/SKILL.md +90 -5
  34. package/skills/server-actions/SKILL.md +751 -0
  35. package/skills/streams-and-websockets/SKILL.md +283 -0
  36. package/skills/testing/SKILL.md +716 -0
  37. package/skills/typesafety/SKILL.md +329 -27
  38. package/skills/use-cache/SKILL.md +34 -5
  39. package/skills/view-transitions/SKILL.md +294 -0
  40. package/src/__augment-tests__/augment.ts +81 -0
  41. package/src/__augment-tests__/augmented.check.ts +117 -0
  42. package/src/__internal.ts +1 -1
  43. package/src/browser/action-coordinator.ts +53 -36
  44. package/src/browser/app-shell.ts +52 -0
  45. package/src/browser/app-version.ts +14 -0
  46. package/src/browser/event-controller.ts +91 -70
  47. package/src/browser/history-state.ts +21 -0
  48. package/src/browser/index.ts +3 -3
  49. package/src/browser/navigation-bridge.ts +102 -16
  50. package/src/browser/navigation-client.ts +164 -59
  51. package/src/browser/navigation-store.ts +75 -17
  52. package/src/browser/navigation-transaction.ts +21 -37
  53. package/src/browser/partial-update.ts +139 -38
  54. package/src/browser/prefetch/cache.ts +175 -15
  55. package/src/browser/prefetch/fetch.ts +180 -33
  56. package/src/browser/prefetch/queue.ts +123 -20
  57. package/src/browser/prefetch/resource-ready.ts +77 -0
  58. package/src/browser/rango-state.ts +53 -13
  59. package/src/browser/react/Link.tsx +81 -9
  60. package/src/browser/react/NavigationProvider.tsx +110 -33
  61. package/src/browser/react/context.ts +7 -2
  62. package/src/browser/react/filter-segment-order.ts +51 -7
  63. package/src/browser/react/index.ts +3 -0
  64. package/src/browser/react/location-state-shared.ts +175 -4
  65. package/src/browser/react/location-state.ts +39 -13
  66. package/src/browser/react/use-handle.ts +23 -64
  67. package/src/browser/react/use-navigation.ts +22 -2
  68. package/src/browser/react/use-params.ts +20 -8
  69. package/src/browser/react/use-reverse.ts +106 -0
  70. package/src/browser/react/use-router.ts +43 -10
  71. package/src/browser/react/use-segments.ts +11 -8
  72. package/src/browser/response-adapter.ts +25 -0
  73. package/src/browser/rsc-router.tsx +191 -74
  74. package/src/browser/scroll-restoration.ts +41 -14
  75. package/src/browser/segment-reconciler.ts +36 -9
  76. package/src/browser/segment-structure-assert.ts +2 -2
  77. package/src/browser/server-action-bridge.ts +31 -36
  78. package/src/browser/types.ts +57 -5
  79. package/src/build/collect-fallback-refs.ts +107 -0
  80. package/src/build/generate-manifest.ts +65 -40
  81. package/src/build/generate-route-types.ts +5 -0
  82. package/src/build/index.ts +2 -0
  83. package/src/build/route-trie.ts +52 -25
  84. package/src/build/route-types/codegen.ts +4 -4
  85. package/src/build/route-types/include-resolution.ts +9 -2
  86. package/src/build/route-types/per-module-writer.ts +7 -4
  87. package/src/build/route-types/router-processing.ts +278 -88
  88. package/src/build/route-types/scan-filter.ts +9 -2
  89. package/src/build/route-types/source-scan.ts +118 -0
  90. package/src/build/runtime-discovery.ts +9 -20
  91. package/src/cache/cache-runtime.ts +15 -11
  92. package/src/cache/cache-scope.ts +76 -49
  93. package/src/cache/cf/cf-cache-store.ts +501 -18
  94. package/src/cache/cf/index.ts +5 -1
  95. package/src/cache/document-cache.ts +17 -7
  96. package/src/cache/index.ts +1 -0
  97. package/src/cache/taint.ts +55 -0
  98. package/src/client.rsc.tsx +3 -0
  99. package/src/client.tsx +94 -238
  100. package/src/context-var.ts +72 -2
  101. package/src/debug.ts +2 -2
  102. package/src/decode-loader-results.ts +36 -0
  103. package/src/errors.ts +30 -1
  104. package/src/handle.ts +65 -12
  105. package/src/host/index.ts +2 -2
  106. package/src/host/router.ts +129 -57
  107. package/src/host/types.ts +31 -2
  108. package/src/host/utils.ts +1 -1
  109. package/src/href-client.ts +140 -20
  110. package/src/index.rsc.ts +12 -5
  111. package/src/index.ts +61 -11
  112. package/src/loader-store.ts +500 -0
  113. package/src/loader.rsc.ts +2 -5
  114. package/src/loader.ts +3 -10
  115. package/src/missing-id-error.ts +68 -0
  116. package/src/outlet-context.ts +1 -1
  117. package/src/prerender/store.ts +5 -4
  118. package/src/prerender.ts +141 -80
  119. package/src/response-utils.ts +37 -0
  120. package/src/reverse.ts +65 -15
  121. package/src/route-content-wrapper.tsx +6 -28
  122. package/src/route-definition/dsl-helpers.ts +435 -260
  123. package/src/route-definition/helper-factories.ts +29 -139
  124. package/src/route-definition/helpers-types.ts +110 -34
  125. package/src/route-definition/index.ts +3 -0
  126. package/src/route-definition/redirect.ts +11 -3
  127. package/src/route-definition/resolve-handler-use.ts +155 -0
  128. package/src/route-definition/use-item-types.ts +32 -0
  129. package/src/route-map-builder.ts +7 -1
  130. package/src/route-types.ts +37 -41
  131. package/src/router/basename.ts +14 -0
  132. package/src/router/content-negotiation.ts +113 -1
  133. package/src/router/error-handling.ts +1 -1
  134. package/src/router/find-match.ts +4 -2
  135. package/src/router/handler-context.ts +77 -38
  136. package/src/router/intercept-resolution.ts +15 -22
  137. package/src/router/lazy-includes.ts +12 -9
  138. package/src/router/loader-resolution.ts +174 -22
  139. package/src/router/logging.ts +5 -2
  140. package/src/router/manifest.ts +31 -16
  141. package/src/router/match-api.ts +128 -192
  142. package/src/router/match-handlers.ts +63 -20
  143. package/src/router/match-middleware/background-revalidation.ts +30 -2
  144. package/src/router/match-middleware/cache-lookup.ts +136 -106
  145. package/src/router/match-middleware/cache-store.ts +54 -10
  146. package/src/router/match-middleware/intercept-resolution.ts +9 -7
  147. package/src/router/match-middleware/segment-resolution.ts +61 -5
  148. package/src/router/match-result.ts +125 -10
  149. package/src/router/metrics.ts +7 -2
  150. package/src/router/middleware-types.ts +21 -34
  151. package/src/router/middleware.ts +103 -90
  152. package/src/router/navigation-snapshot.ts +182 -0
  153. package/src/router/pattern-matching.ts +101 -17
  154. package/src/router/prerender-match.ts +110 -10
  155. package/src/router/preview-match.ts +32 -102
  156. package/src/router/request-classification.ts +286 -0
  157. package/src/router/revalidation.ts +58 -2
  158. package/src/router/route-snapshot.ts +245 -0
  159. package/src/router/router-context.ts +6 -1
  160. package/src/router/router-interfaces.ts +77 -28
  161. package/src/router/router-options.ts +76 -11
  162. package/src/router/router-registry.ts +2 -5
  163. package/src/router/segment-resolution/fresh.ts +223 -24
  164. package/src/router/segment-resolution/helpers.ts +29 -24
  165. package/src/router/segment-resolution/loader-cache.ts +1 -0
  166. package/src/router/segment-resolution/revalidation.ts +466 -285
  167. package/src/router/segment-resolution/view-transition-default.ts +36 -0
  168. package/src/router/segment-wrappers.ts +2 -0
  169. package/src/router/substitute-pattern-params.ts +56 -0
  170. package/src/router/telemetry.ts +99 -0
  171. package/src/router/trie-matching.ts +18 -13
  172. package/src/router/types.ts +9 -0
  173. package/src/router/url-params.ts +49 -0
  174. package/src/router.ts +91 -23
  175. package/src/rsc/handler-context.ts +2 -2
  176. package/src/rsc/handler.ts +440 -381
  177. package/src/rsc/helpers.ts +91 -43
  178. package/src/rsc/index.ts +1 -1
  179. package/src/rsc/loader-fetch.ts +23 -3
  180. package/src/rsc/manifest-init.ts +5 -1
  181. package/src/rsc/origin-guard.ts +28 -10
  182. package/src/rsc/progressive-enhancement.ts +18 -2
  183. package/src/rsc/response-route-handler.ts +46 -53
  184. package/src/rsc/rsc-rendering.ts +41 -48
  185. package/src/rsc/runtime-warnings.ts +9 -10
  186. package/src/rsc/server-action.ts +25 -37
  187. package/src/rsc/ssr-setup.ts +18 -2
  188. package/src/rsc/types.ts +17 -3
  189. package/src/search-params.ts +4 -4
  190. package/src/segment-content-promise.ts +67 -0
  191. package/src/segment-loader-promise.ts +122 -0
  192. package/src/segment-system.tsx +219 -67
  193. package/src/serialize.ts +243 -0
  194. package/src/server/context.ts +277 -61
  195. package/src/server/cookie-store.ts +28 -4
  196. package/src/server/handle-store.ts +19 -0
  197. package/src/server/loader-registry.ts +9 -8
  198. package/src/server/request-context.ts +204 -60
  199. package/src/ssr/index.tsx +9 -1
  200. package/src/static-handler.ts +19 -7
  201. package/src/testing/cache-status.ts +166 -0
  202. package/src/testing/collect-handle.ts +63 -0
  203. package/src/testing/dispatch.ts +440 -0
  204. package/src/testing/dom.entry.ts +22 -0
  205. package/src/testing/e2e/fixture.ts +154 -0
  206. package/src/testing/e2e/index.ts +149 -0
  207. package/src/testing/e2e/matchers.ts +51 -0
  208. package/src/testing/e2e/page-helpers.ts +272 -0
  209. package/src/testing/e2e/parity.ts +306 -0
  210. package/src/testing/e2e/server.ts +183 -0
  211. package/src/testing/flight-matchers.ts +104 -0
  212. package/src/testing/flight-runtime.d.ts +21 -0
  213. package/src/testing/flight.entry.ts +22 -0
  214. package/src/testing/flight.ts +182 -0
  215. package/src/testing/generated-routes.ts +223 -0
  216. package/src/testing/index.ts +106 -0
  217. package/src/testing/internal/context.ts +255 -0
  218. package/src/testing/render-route.tsx +565 -0
  219. package/src/testing/run-loader.ts +296 -0
  220. package/src/testing/run-middleware.ts +179 -0
  221. package/src/testing/vitest-stubs/cloudflare-email.ts +9 -0
  222. package/src/testing/vitest-stubs/cloudflare-workers.ts +21 -0
  223. package/src/testing/vitest-stubs/plugin-rsc.ts +16 -0
  224. package/src/testing/vitest-stubs/version.ts +5 -0
  225. package/src/testing/vitest.ts +183 -0
  226. package/src/types/cache-types.ts +4 -4
  227. package/src/types/global-namespace.ts +39 -26
  228. package/src/types/handler-context.ts +194 -72
  229. package/src/types/index.ts +1 -0
  230. package/src/types/loader-types.ts +41 -15
  231. package/src/types/request-scope.ts +126 -0
  232. package/src/types/route-entry.ts +19 -1
  233. package/src/types/segments.ts +37 -1
  234. package/src/urls/include-helper.ts +34 -67
  235. package/src/urls/index.ts +0 -3
  236. package/src/urls/path-helper-types.ts +50 -9
  237. package/src/urls/path-helper.ts +63 -63
  238. package/src/urls/pattern-types.ts +48 -19
  239. package/src/urls/response-types.ts +25 -22
  240. package/src/urls/type-extraction.ts +26 -116
  241. package/src/urls/urls-function.ts +1 -5
  242. package/src/use-loader.tsx +487 -44
  243. package/src/vite/debug.ts +185 -0
  244. package/src/vite/discovery/bundle-postprocess.ts +34 -37
  245. package/src/vite/discovery/discover-routers.ts +105 -51
  246. package/src/vite/discovery/discovery-errors.ts +194 -0
  247. package/src/vite/discovery/gate-state.ts +171 -0
  248. package/src/vite/discovery/prerender-collection.ts +188 -93
  249. package/src/vite/discovery/route-types-writer.ts +40 -84
  250. package/src/vite/discovery/self-gen-tracking.ts +27 -1
  251. package/src/vite/discovery/state.ts +46 -6
  252. package/src/vite/discovery/virtual-module-codegen.ts +13 -23
  253. package/src/vite/index.ts +6 -0
  254. package/src/vite/plugin-types.ts +111 -72
  255. package/src/vite/plugins/cjs-to-esm.ts +8 -7
  256. package/src/vite/plugins/client-ref-dedup.ts +16 -0
  257. package/src/vite/plugins/client-ref-hashing.ts +28 -5
  258. package/src/vite/plugins/cloudflare-protocol-loader-hook.d.mts +23 -0
  259. package/src/vite/plugins/cloudflare-protocol-loader-hook.mjs +76 -0
  260. package/src/vite/plugins/cloudflare-protocol-stub.ts +214 -0
  261. package/src/vite/plugins/expose-action-id.ts +55 -33
  262. package/src/vite/plugins/expose-id-utils.ts +24 -8
  263. package/src/vite/plugins/expose-ids/export-analysis.ts +100 -20
  264. package/src/vite/plugins/expose-ids/handler-transform.ts +12 -35
  265. package/src/vite/plugins/expose-ids/loader-transform.ts +3 -5
  266. package/src/vite/plugins/expose-ids/router-transform.ts +20 -3
  267. package/src/vite/plugins/expose-internal-ids.ts +544 -317
  268. package/src/vite/plugins/performance-tracks.ts +92 -0
  269. package/src/vite/plugins/refresh-cmd.ts +88 -26
  270. package/src/vite/plugins/use-cache-transform.ts +65 -50
  271. package/src/vite/plugins/version-injector.ts +39 -23
  272. package/src/vite/plugins/version-plugin.ts +72 -3
  273. package/src/vite/plugins/virtual-entries.ts +2 -2
  274. package/src/vite/rango.ts +265 -226
  275. package/src/vite/router-discovery.ts +920 -137
  276. package/src/vite/utils/ast-handler-extract.ts +15 -15
  277. package/src/vite/utils/banner.ts +4 -4
  278. package/src/vite/utils/bundle-analysis.ts +4 -2
  279. package/src/vite/utils/client-chunks.ts +190 -0
  280. package/src/vite/utils/forward-user-plugins.ts +193 -0
  281. package/src/vite/utils/manifest-utils.ts +21 -5
  282. package/src/vite/utils/package-resolution.ts +41 -1
  283. package/src/vite/utils/prerender-utils.ts +38 -5
  284. package/src/vite/utils/shared-utils.ts +109 -27
  285. package/src/browser/action-response-classifier.ts +0 -99
@@ -0,0 +1,255 @@
1
+ /**
2
+ * Shared internals for the consumer testing primitives.
3
+ *
4
+ * Builds a real RequestContext via the same createRequestContext the RSC
5
+ * handler uses, with test-friendly defaults, so loaders and middleware run
6
+ * with production-fidelity context (cookies, headers, get/set, use, reverse)
7
+ * instead of a hand-rolled mock.
8
+ */
9
+
10
+ import {
11
+ createRequestContext,
12
+ runWithRequestContext,
13
+ type RequestContext,
14
+ } from "../../server/request-context.js";
15
+ import { resolveLocationStateEntries } from "../../browser/react/location-state-shared.js";
16
+ import { createReverseFunction } from "../../router/handler-context.js";
17
+ import { normalizeBasename } from "../../router/basename.js";
18
+ import { contextSet, type ContextVar } from "../../context-var.js";
19
+ import type { ThemeConfig } from "../../theme/types.js";
20
+ import { resolveThemeConfig } from "../../theme/constants.js";
21
+ import type { SegmentCacheStore } from "../../cache/types.js";
22
+ import type { CacheProfile } from "../../cache/profile-registry.js";
23
+
24
+ const DEFAULT_ORIGIN = "http://localhost/";
25
+
26
+ /**
27
+ * Initializer for seeded context variables (as a prior middleware would have
28
+ * set). Either a plain object keyed by var name (the common, best-inferring
29
+ * form: `{ user: u }`) or a list of `[key, value]` tuples where the key may be a
30
+ * `createVar()` handle or a string (`[[userVar, u], ["flag", true]]`).
31
+ */
32
+ export type VarsInit =
33
+ | Record<string, unknown>
34
+ | ReadonlyArray<readonly [ContextVar<unknown> | string, unknown]>;
35
+
36
+ /** Normalize a Request | string | undefined into a concrete Request. */
37
+ export function toRequest(
38
+ request: Request | string | undefined,
39
+ init?: RequestInit,
40
+ ): Request {
41
+ if (request instanceof Request) return request;
42
+ if (typeof request === "string") {
43
+ return new Request(new URL(request, DEFAULT_ORIGIN), init);
44
+ }
45
+ return new Request(DEFAULT_ORIGIN, init);
46
+ }
47
+
48
+ /**
49
+ * Preload variables as if set by upstream middleware. Accepts entries keyed by
50
+ * either a ContextVar (from createVar) or a string, matching ctx.set().
51
+ */
52
+ export function seedVariables(
53
+ variables: Record<string, unknown>,
54
+ vars?: VarsInit,
55
+ ): Record<string, unknown> {
56
+ if (!vars) return variables;
57
+ // Array/iterable form -> use the tuples as-is; plain object -> its entries.
58
+ const entries: Iterable<readonly [ContextVar<unknown> | string, unknown]> =
59
+ Symbol.iterator in (vars as object)
60
+ ? (vars as ReadonlyArray<
61
+ readonly [ContextVar<unknown> | string, unknown]
62
+ >)
63
+ : Object.entries(vars as Record<string, unknown>);
64
+ for (const [key, value] of entries) {
65
+ contextSet(variables, key as ContextVar<unknown>, value);
66
+ }
67
+ return variables;
68
+ }
69
+
70
+ export interface CreateTestContextOptions<TEnv> {
71
+ env?: TEnv;
72
+ request?: Request | string;
73
+ requestInit?: RequestInit;
74
+ /** Backing store for ctx.get()/ctx.set(); pre-seeded from `vars`. */
75
+ variables?: Record<string, unknown>;
76
+ /** Variables a prior middleware would have set (object or [key, value] list). */
77
+ vars?: VarsInit;
78
+ /** Route name -> pattern map enabling ctx.reverse() without global state. */
79
+ routeMap?: Record<string, string>;
80
+ routeName?: string;
81
+ params?: Record<string, string>;
82
+ /**
83
+ * Router basename for this request (what the RSC handler stores on the
84
+ * context). Drives redirect() prefixing. Normalized exactly like
85
+ * createRouter({ basename }) (leading slash forced, trailing stripped, bare
86
+ * "/" -> undefined) so passing the same value your router takes yields the
87
+ * same redirect Location. Defaults to undefined (no basename).
88
+ */
89
+ basename?: string;
90
+ /**
91
+ * Cache store backing `use cache` functions invoked during the test, the
92
+ * same shape `createRouter({ cache })` resolves. Without it,
93
+ * registerCachedFunction bypasses (it checks for a store FIRST), so a cached
94
+ * function runs uncached and its taint/profile guards never fire. Wire one
95
+ * (e.g. `new MemorySegmentCacheStore()`) to exercise real cache behavior.
96
+ */
97
+ cacheStore?: SegmentCacheStore;
98
+ /**
99
+ * Cache profiles in the `createRouter({ cacheProfiles })` shape. Required for
100
+ * a `use cache: "profileName"` function to resolve its profile (an unknown
101
+ * profile throws), once a `cacheStore` is wired.
102
+ */
103
+ cacheProfiles?: Record<string, CacheProfile>;
104
+ /**
105
+ * Theme config in the same shape `createRouter({ theme })` takes (resolved
106
+ * internally). Without it `ctx.theme`/`ctx.setTheme` are inert (undefined),
107
+ * mirroring an app with no theme configured. Pass one (e.g. `true`, or
108
+ * `{ themes: [...] }`) to exercise a handler that reads them.
109
+ */
110
+ theme?: ThemeConfig | true;
111
+ }
112
+
113
+ export interface TestRequestContext<TEnv> {
114
+ ctx: RequestContext<TEnv>;
115
+ request: Request;
116
+ url: URL;
117
+ variables: Record<string, unknown>;
118
+ }
119
+
120
+ /**
121
+ * Create a real RequestContext for unit-testing loaders/middleware.
122
+ *
123
+ * The returned `ctx` must be ENTERED before use — wrap your call in
124
+ * `runWithRequestContext(ctx, fn)` (re-exported from `@rangojs/router/testing`)
125
+ * so that cookie/header mutations and `getRequestContext()` resolve. For the
126
+ * common case prefer {@link runInRequestContext}, which builds AND enters the
127
+ * context in a single call.
128
+ */
129
+ export function createTestRequestContext<TEnv>(
130
+ opts: CreateTestContextOptions<TEnv> = {},
131
+ ): TestRequestContext<TEnv> {
132
+ const request = toRequest(opts.request, opts.requestInit);
133
+ const url = new URL(request.url);
134
+ const variables = seedVariables(opts.variables ?? {}, opts.vars);
135
+ const ctx = createRequestContext<TEnv>({
136
+ env: (opts.env ?? {}) as TEnv,
137
+ request,
138
+ url,
139
+ variables,
140
+ themeConfig:
141
+ opts.theme === undefined ? undefined : resolveThemeConfig(opts.theme),
142
+ cacheStore: opts.cacheStore,
143
+ cacheProfiles: opts.cacheProfiles,
144
+ });
145
+ if (opts.basename !== undefined)
146
+ ctx._basename = normalizeBasename(opts.basename);
147
+ if (opts.params) ctx.params = opts.params;
148
+ if (opts.routeMap) {
149
+ ctx._routeName = opts.routeName;
150
+ ctx.reverse = createReverseFunction(
151
+ opts.routeMap,
152
+ opts.routeName,
153
+ opts.params ?? {},
154
+ ) as RequestContext<TEnv>["reverse"];
155
+ }
156
+ return { ctx, request, url, variables };
157
+ }
158
+
159
+ /**
160
+ * What a run accumulated on the request context, surfaced as PUBLIC values so a
161
+ * test never has to cast through the `@internal` `ctx.res` / `ctx.cookies()` to
162
+ * assert what an action produced.
163
+ */
164
+ export interface RunInRequestContextResult<T> {
165
+ /** The value `fn` returned (awaited if it returned a promise). */
166
+ result: T;
167
+ /**
168
+ * A Response carrying the status, headers, and Set-Cookie cookies the run set
169
+ * on the request context (via `cookies().set()`, `ctx.header()`, etc.).
170
+ * Assert Set-Cookie with `response.headers.getSetCookie()`. This is the
171
+ * accumulated side-channel, NOT a Response `fn` itself returned (that is
172
+ * `result`).
173
+ */
174
+ response: Response;
175
+ /**
176
+ * The effective cookie view after the run: request cookies merged with
177
+ * anything the run set or deleted (last-write-wins), as `{ name: value }`.
178
+ */
179
+ cookies: Record<string, string>;
180
+ /**
181
+ * Location state the run set via `ctx.setLocationState()` / `redirect({ state })`,
182
+ * resolved to the flat `{ key: value }` shape the client reads off
183
+ * `history.state` (empty object when none) — so a post-action flash ("Saved!")
184
+ * is assertable at the unit layer.
185
+ */
186
+ locationState: Record<string, unknown>;
187
+ }
188
+
189
+ /**
190
+ * Snapshot the observable effects a run left on `ctx` (cookies + location
191
+ * state). Reads the fields directly off the ctx object, so it works both inside
192
+ * and outside the AsyncLocalStorage scope (no `getRequestContext()`).
193
+ */
194
+ export function snapshotRunEffects<TEnv>(ctx: RequestContext<TEnv>): {
195
+ cookies: Record<string, string>;
196
+ locationState: Record<string, unknown>;
197
+ } {
198
+ return {
199
+ cookies: { ...ctx.cookies() },
200
+ locationState: resolveLocationStateEntries(ctx._locationState ?? []),
201
+ };
202
+ }
203
+
204
+ /**
205
+ * Build a seeded RequestContext (via {@link createTestRequestContext}) and run
206
+ * `fn` inside it, so code under test that calls `getRequestContext()`,
207
+ * `cookies()`, or reads/mutates request headers resolves exactly as in
208
+ * production.
209
+ *
210
+ * This is the entry point for the advanced cases the unit wrappers
211
+ * (`runLoader` / `runMiddleware`) do not model — most notably a server ACTION
212
+ * that authenticates off the request cookie or sets a session cookie / flash:
213
+ * an action has no loader context, so `runLoader` is the wrong shape, yet it
214
+ * still needs a real request context to read the cookie and resolve
215
+ * `getRequestContext()`.
216
+ *
217
+ * Returns `{ result, response, cookies, locationState }` so the action's OUTPUT
218
+ * (Set-Cookie, headers, flash) is assertable without casting through the
219
+ * `@internal` `ctx.res` / `ctx.cookies()`. `fn` may be async — the context
220
+ * stays active across its awaits (AsyncLocalStorage), and the snapshot is taken
221
+ * after it settles.
222
+ *
223
+ * @example
224
+ * ```ts
225
+ * const { result, cookies, response } = await runInRequestContext(
226
+ * () => loginAction(input),
227
+ * {
228
+ * env,
229
+ * request: new Request("https://app.test/", {
230
+ * headers: { Cookie: "sid=abc" },
231
+ * }),
232
+ * },
233
+ * );
234
+ * expect(cookies.session).toBe("new-token");
235
+ * expect(response.headers.getSetCookie()).toContainEqual(
236
+ * expect.stringContaining("session="),
237
+ * );
238
+ * ```
239
+ */
240
+ export async function runInRequestContext<T, TEnv = unknown>(
241
+ fn: (ctx: RequestContext<TEnv>) => T | Promise<T>,
242
+ opts: CreateTestContextOptions<TEnv> = {},
243
+ ): Promise<RunInRequestContextResult<T>> {
244
+ const { ctx } = createTestRequestContext<TEnv>(opts);
245
+ const result = (await runWithRequestContext(ctx, () => fn(ctx))) as T;
246
+ const { cookies, locationState } = snapshotRunEffects(ctx);
247
+ // Snapshot the accumulated response from the stub directly (status + headers,
248
+ // incl. Set-Cookie). The Response constructor copies the Headers, so this is
249
+ // an immutable snapshot independent of later ctx mutations.
250
+ const response = new Response(null, {
251
+ status: ctx.res.status,
252
+ headers: ctx.res.headers,
253
+ });
254
+ return { result, response, cookies, locationState };
255
+ }