rwsdk 1.0.0-alpha.1-test.20250911154541 → 1.0.0-alpha.10

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 (137) hide show
  1. package/dist/lib/e2e/browser.d.mts +10 -0
  2. package/dist/lib/e2e/browser.mjs +124 -0
  3. package/dist/lib/e2e/dev.d.mts +8 -0
  4. package/dist/lib/e2e/dev.mjs +232 -0
  5. package/dist/lib/e2e/environment.d.mts +14 -0
  6. package/dist/lib/e2e/environment.mjs +223 -0
  7. package/dist/lib/e2e/index.d.mts +7 -0
  8. package/dist/lib/e2e/index.mjs +7 -0
  9. package/dist/lib/e2e/release.d.mts +56 -0
  10. package/dist/lib/e2e/release.mjs +559 -0
  11. package/dist/lib/e2e/tarball.d.mts +13 -0
  12. package/dist/lib/e2e/tarball.mjs +99 -0
  13. package/dist/lib/e2e/testHarness.d.mts +123 -0
  14. package/dist/lib/e2e/testHarness.mjs +507 -0
  15. package/dist/lib/e2e/types.d.mts +32 -0
  16. package/dist/lib/getShortName.mjs +6 -1
  17. package/dist/lib/getShortName.test.d.mts +1 -0
  18. package/dist/lib/getShortName.test.mjs +25 -0
  19. package/dist/lib/hasPkgScript.d.mts +4 -1
  20. package/dist/lib/hasPkgScript.mjs +9 -6
  21. package/dist/lib/hasPkgScript.test.d.mts +1 -0
  22. package/dist/lib/hasPkgScript.test.mjs +33 -0
  23. package/dist/lib/jsonUtils.mjs +3 -0
  24. package/dist/lib/jsonUtils.test.d.mts +1 -0
  25. package/dist/lib/jsonUtils.test.mjs +90 -0
  26. package/dist/lib/normalizeModulePath.d.mts +5 -0
  27. package/dist/lib/normalizeModulePath.mjs +1 -1
  28. package/dist/lib/normalizeModulePath.test.d.mts +1 -0
  29. package/dist/lib/{normalizeModulePath.test.js → normalizeModulePath.test.mjs} +20 -1
  30. package/dist/lib/smokeTests/browser.mjs +3 -94
  31. package/dist/lib/smokeTests/development.mjs +2 -223
  32. package/dist/lib/smokeTests/environment.d.mts +4 -11
  33. package/dist/lib/smokeTests/environment.mjs +10 -158
  34. package/dist/lib/smokeTests/release.d.mts +2 -49
  35. package/dist/lib/smokeTests/release.mjs +3 -503
  36. package/dist/runtime/lib/injectHtmlAtMarker.d.ts +11 -0
  37. package/dist/runtime/lib/injectHtmlAtMarker.js +90 -0
  38. package/dist/runtime/lib/memoizeOnId.test.d.ts +1 -0
  39. package/dist/runtime/lib/memoizeOnId.test.js +49 -0
  40. package/dist/runtime/lib/realtime/protocol.test.d.ts +1 -0
  41. package/dist/runtime/lib/realtime/protocol.test.js +107 -0
  42. package/dist/runtime/lib/realtime/shared.test.d.ts +1 -0
  43. package/dist/runtime/lib/realtime/shared.test.js +18 -0
  44. package/dist/runtime/lib/realtime/validateUpgradeRequest.test.d.ts +1 -0
  45. package/dist/runtime/lib/realtime/validateUpgradeRequest.test.js +66 -0
  46. package/dist/runtime/lib/realtime/worker.d.ts +1 -1
  47. package/dist/runtime/lib/router.js +40 -22
  48. package/dist/runtime/lib/router.test.js +591 -2
  49. package/dist/runtime/lib/rwContext.d.ts +22 -0
  50. package/dist/runtime/lib/rwContext.js +1 -0
  51. package/dist/runtime/lib/turnstile/verifyTurnstileToken.d.ts +2 -1
  52. package/dist/runtime/lib/turnstile/verifyTurnstileToken.js +6 -6
  53. package/dist/runtime/lib/turnstile/verifyTurnstileToken.test.d.ts +1 -0
  54. package/dist/runtime/lib/turnstile/verifyTurnstileToken.test.js +49 -0
  55. package/dist/runtime/register/worker.d.ts +1 -1
  56. package/dist/runtime/register/worker.js +26 -21
  57. package/dist/runtime/render/assembleDocument.d.ts +6 -0
  58. package/dist/runtime/render/assembleDocument.js +22 -0
  59. package/dist/runtime/render/createThenableFromReadableStream.d.ts +1 -0
  60. package/dist/runtime/render/createThenableFromReadableStream.js +9 -0
  61. package/dist/runtime/render/normalizeActionResult.d.ts +1 -0
  62. package/dist/runtime/render/normalizeActionResult.js +43 -0
  63. package/dist/runtime/render/preloads.d.ts +2 -2
  64. package/dist/runtime/render/preloads.js +2 -3
  65. package/dist/runtime/render/{renderRscThenableToHtmlStream.d.ts → renderDocumentHtmlStream.d.ts} +3 -3
  66. package/dist/runtime/render/renderDocumentHtmlStream.js +39 -0
  67. package/dist/runtime/render/renderHtmlStream.d.ts +7 -0
  68. package/dist/runtime/render/renderHtmlStream.js +31 -0
  69. package/dist/runtime/render/renderToRscStream.d.ts +2 -3
  70. package/dist/runtime/render/renderToRscStream.js +2 -41
  71. package/dist/runtime/render/renderToStream.d.ts +2 -1
  72. package/dist/runtime/render/renderToStream.js +15 -8
  73. package/dist/runtime/render/stylesheets.d.ts +2 -2
  74. package/dist/runtime/render/stylesheets.js +2 -3
  75. package/dist/runtime/ssrBridge.d.ts +2 -1
  76. package/dist/runtime/ssrBridge.js +2 -1
  77. package/dist/runtime/worker.d.ts +1 -0
  78. package/dist/runtime/worker.js +11 -6
  79. package/dist/scripts/debug-sync.mjs +102 -133
  80. package/dist/vite/buildApp.d.mts +2 -1
  81. package/dist/vite/buildApp.mjs +9 -5
  82. package/dist/vite/checkIsUsingPrisma.d.mts +4 -0
  83. package/dist/vite/checkIsUsingPrisma.mjs +2 -2
  84. package/dist/vite/checkIsUsingPrisma.test.d.mts +1 -0
  85. package/dist/vite/checkIsUsingPrisma.test.mjs +30 -0
  86. package/dist/vite/configPlugin.mjs +35 -14
  87. package/dist/vite/createDirectiveLookupPlugin.d.mts +9 -0
  88. package/dist/vite/createDirectiveLookupPlugin.mjs +33 -29
  89. package/dist/vite/createDirectiveLookupPlugin.test.d.mts +1 -0
  90. package/dist/vite/createDirectiveLookupPlugin.test.mjs +40 -0
  91. package/dist/vite/directiveModulesDevPlugin.d.mts +4 -1
  92. package/dist/vite/directiveModulesDevPlugin.mjs +5 -4
  93. package/dist/vite/directiveModulesDevPlugin.test.d.mts +1 -0
  94. package/dist/vite/directiveModulesDevPlugin.test.mjs +59 -0
  95. package/dist/vite/directivesPlugin.d.mts +1 -0
  96. package/dist/vite/directivesPlugin.mjs +1 -1
  97. package/dist/vite/directivesPlugin.test.d.mts +1 -0
  98. package/dist/vite/directivesPlugin.test.mjs +24 -0
  99. package/dist/vite/ensureAliasArray.test.d.mts +1 -0
  100. package/dist/vite/ensureAliasArray.test.mjs +71 -0
  101. package/dist/vite/findSpecifiers.mjs +2 -1
  102. package/dist/vite/findSpecifiers.test.d.mts +1 -0
  103. package/dist/vite/findSpecifiers.test.mjs +202 -0
  104. package/dist/vite/findSsrSpecifiers.test.d.mts +1 -0
  105. package/dist/vite/findSsrSpecifiers.test.mjs +99 -0
  106. package/dist/vite/hasDirective.d.mts +6 -3
  107. package/dist/vite/hasDirective.mjs +43 -27
  108. package/dist/vite/hasDirective.test.d.mts +1 -0
  109. package/dist/vite/hasDirective.test.mjs +107 -0
  110. package/dist/vite/isJsFile.test.d.mts +1 -0
  111. package/dist/vite/isJsFile.test.mjs +38 -0
  112. package/dist/vite/{reactConditionsResolverPlugin.d.mts → knownDepsResolverPlugin.d.mts} +2 -2
  113. package/dist/vite/{reactConditionsResolverPlugin.mjs → knownDepsResolverPlugin.mjs} +28 -23
  114. package/dist/vite/linkerPlugin.d.mts +8 -0
  115. package/dist/vite/linkerPlugin.mjs +30 -22
  116. package/dist/vite/linkerPlugin.test.d.mts +1 -0
  117. package/dist/vite/linkerPlugin.test.mjs +41 -0
  118. package/dist/vite/miniflareHMRPlugin.d.mts +5 -0
  119. package/dist/vite/miniflareHMRPlugin.mjs +2 -2
  120. package/dist/vite/miniflareHMRPlugin.test.d.mts +1 -0
  121. package/dist/vite/miniflareHMRPlugin.test.mjs +42 -0
  122. package/dist/vite/redwoodPlugin.d.mts +7 -0
  123. package/dist/vite/redwoodPlugin.mjs +10 -5
  124. package/dist/vite/redwoodPlugin.test.d.mts +1 -0
  125. package/dist/vite/redwoodPlugin.test.mjs +34 -0
  126. package/dist/vite/runDirectivesScan.d.mts +21 -1
  127. package/dist/vite/runDirectivesScan.mjs +67 -52
  128. package/dist/vite/runDirectivesScan.test.d.mts +1 -0
  129. package/dist/vite/runDirectivesScan.test.mjs +73 -0
  130. package/dist/vite/ssrBridgePlugin.mjs +8 -1
  131. package/dist/vite/transformClientComponents.mjs +4 -3
  132. package/dist/vite/transformClientComponents.test.mjs +116 -58
  133. package/package.json +8 -4
  134. package/dist/runtime/render/renderRscThenableToHtmlStream.js +0 -54
  135. package/dist/runtime/render/transformRscToHtmlStream.d.ts +0 -8
  136. package/dist/runtime/render/transformRscToHtmlStream.js +0 -19
  137. /package/dist/lib/{normalizeModulePath.test.d.ts → e2e/types.mjs} +0 -0
@@ -1,5 +1,6 @@
1
1
  import { describe, it, expect } from "vitest";
2
- import { matchPath } from "./router";
2
+ import React from "react";
3
+ import { matchPath, defineRoutes, route, render, layout, prefix, } from "./router";
3
4
  describe("matchPath", () => {
4
5
  // Test case 1: Static paths
5
6
  it("should match static paths", () => {
@@ -22,7 +23,7 @@ describe("matchPath", () => {
22
23
  expect(matchPath("/files/*/", "/files/document.pdf/")).toEqual({
23
24
  $0: "document.pdf",
24
25
  });
25
- expect(matchPath("/data/*\/content/", "/data/archive/content/")).toEqual({
26
+ expect(matchPath("/data/*/content/", "/data/archive/content/")).toEqual({
26
27
  $0: "archive",
27
28
  });
28
29
  expect(matchPath("/assets/*/*/", "/assets/images/pic.png/")).toEqual({
@@ -56,3 +57,591 @@ describe("matchPath", () => {
56
57
  expect(() => matchPath("/type/**", "/type/a-thumb-drive")).toThrow();
57
58
  });
58
59
  });
60
+ describe("defineRoutes - Request Handling Behavior", () => {
61
+ // Helper to create mock dependencies using dependency injection
62
+ const createMockDependencies = () => {
63
+ const mockRequestInfo = {
64
+ request: new Request("http://localhost:3000/"),
65
+ params: {},
66
+ ctx: {},
67
+ headers: new Headers(),
68
+ rw: {
69
+ nonce: "test-nonce",
70
+ Document: () => React.createElement("html"),
71
+ rscPayload: true,
72
+ ssr: true,
73
+ databases: new Map(),
74
+ scriptsToBeLoaded: new Set(),
75
+ pageRouteResolved: undefined,
76
+ },
77
+ cf: {},
78
+ response: { headers: new Headers() },
79
+ isAction: false,
80
+ };
81
+ const mockRenderPage = async (requestInfo, Page, onError) => {
82
+ return new Response(`Rendered: ${Page.name || "Component"}`, {
83
+ headers: { "content-type": "text/html" },
84
+ });
85
+ };
86
+ const mockRscActionHandler = async (request) => {
87
+ return { actionResult: "test-action-result" };
88
+ };
89
+ const mockRunWithRequestInfoOverrides = async (overrides, fn) => {
90
+ // Merge overrides into the mock request info
91
+ Object.assign(mockRequestInfo, overrides);
92
+ return await fn();
93
+ };
94
+ return {
95
+ mockRequestInfo,
96
+ mockRenderPage,
97
+ mockRscActionHandler,
98
+ mockRunWithRequestInfoOverrides,
99
+ getRequestInfo: () => mockRequestInfo,
100
+ onError: (error) => {
101
+ throw error;
102
+ },
103
+ };
104
+ };
105
+ describe("Sequential Route Evaluation", () => {
106
+ it("should process routes in the exact order they are defined", async () => {
107
+ const executionOrder = [];
108
+ const middleware1 = (requestInfo) => {
109
+ executionOrder.push("middleware1");
110
+ };
111
+ const middleware2 = (requestInfo) => {
112
+ executionOrder.push("middleware2");
113
+ };
114
+ const PageComponent = () => {
115
+ executionOrder.push("PageComponent");
116
+ return React.createElement("div", {}, "Page");
117
+ };
118
+ const router = defineRoutes([
119
+ middleware1,
120
+ middleware2,
121
+ route("/test/", PageComponent),
122
+ ]);
123
+ const deps = createMockDependencies();
124
+ deps.mockRequestInfo.request = new Request("http://localhost:3000/test/");
125
+ const request = new Request("http://localhost:3000/test/");
126
+ await router.handle({
127
+ request,
128
+ renderPage: deps.mockRenderPage,
129
+ getRequestInfo: deps.getRequestInfo,
130
+ onError: deps.onError,
131
+ runWithRequestInfoOverrides: deps.mockRunWithRequestInfoOverrides,
132
+ rscActionHandler: deps.mockRscActionHandler,
133
+ });
134
+ expect(executionOrder).toEqual([
135
+ "middleware1",
136
+ "middleware2",
137
+ "PageComponent",
138
+ ]);
139
+ });
140
+ });
141
+ describe("Middleware Short-Circuiting", () => {
142
+ it("should stop processing when middleware returns a Response", async () => {
143
+ const executionOrder = [];
144
+ const middleware1 = (requestInfo) => {
145
+ executionOrder.push("middleware1");
146
+ };
147
+ const middleware2 = (requestInfo) => {
148
+ executionOrder.push("middleware2");
149
+ return new Response("Middleware2 Response", { status: 200 });
150
+ };
151
+ const middleware3 = (requestInfo) => {
152
+ executionOrder.push("middleware3");
153
+ };
154
+ const PageComponent = () => {
155
+ executionOrder.push("PageComponent");
156
+ return React.createElement("div", {}, "Page");
157
+ };
158
+ const router = defineRoutes([
159
+ middleware1,
160
+ middleware2,
161
+ middleware3,
162
+ route("/test/", PageComponent),
163
+ ]);
164
+ const deps = createMockDependencies();
165
+ deps.mockRequestInfo.request = new Request("http://localhost:3000/test/");
166
+ const request = new Request("http://localhost:3000/test/");
167
+ const response = await router.handle({
168
+ request,
169
+ renderPage: deps.mockRenderPage,
170
+ getRequestInfo: deps.getRequestInfo,
171
+ onError: deps.onError,
172
+ runWithRequestInfoOverrides: deps.mockRunWithRequestInfoOverrides,
173
+ rscActionHandler: deps.mockRscActionHandler,
174
+ });
175
+ expect(executionOrder).toEqual(["middleware1", "middleware2"]);
176
+ expect(await response.text()).toBe("Middleware2 Response");
177
+ expect(response.status).toBe(200);
178
+ });
179
+ it("should stop processing when middleware returns a JSX element", async () => {
180
+ const executionOrder = [];
181
+ const middleware1 = (requestInfo) => {
182
+ executionOrder.push("middleware1");
183
+ };
184
+ const middleware2 = (requestInfo) => {
185
+ executionOrder.push("middleware2");
186
+ return React.createElement("div", {}, "Middleware JSX");
187
+ };
188
+ const PageComponent = () => {
189
+ executionOrder.push("PageComponent");
190
+ return React.createElement("div", {}, "Page");
191
+ };
192
+ const router = defineRoutes([
193
+ middleware1,
194
+ middleware2,
195
+ route("/test/", PageComponent),
196
+ ]);
197
+ const deps = createMockDependencies();
198
+ deps.mockRequestInfo.request = new Request("http://localhost:3000/test/");
199
+ const request = new Request("http://localhost:3000/test/");
200
+ const response = await router.handle({
201
+ request,
202
+ renderPage: deps.mockRenderPage,
203
+ getRequestInfo: deps.getRequestInfo,
204
+ onError: deps.onError,
205
+ runWithRequestInfoOverrides: deps.mockRunWithRequestInfoOverrides,
206
+ rscActionHandler: deps.mockRscActionHandler,
207
+ });
208
+ expect(executionOrder).toEqual(["middleware1", "middleware2"]);
209
+ expect(await response.text()).toBe("Rendered: Element");
210
+ });
211
+ });
212
+ describe("Prefix Handling", () => {
213
+ it("should only run middleware within the specified prefix", async () => {
214
+ const executionOrder = [];
215
+ const prefixedMiddleware = () => {
216
+ executionOrder.push("prefixedMiddleware");
217
+ };
218
+ const PageComponent = () => {
219
+ executionOrder.push("PageComponent");
220
+ return React.createElement("div", {}, "Page");
221
+ };
222
+ const AdminPageComponent = () => {
223
+ executionOrder.push("AdminPageComponent");
224
+ return React.createElement("div", {}, "Admin Page");
225
+ };
226
+ const router = defineRoutes([
227
+ ...prefix("/admin", [
228
+ prefixedMiddleware,
229
+ route("/", AdminPageComponent),
230
+ ]),
231
+ route("/", PageComponent),
232
+ ]);
233
+ const deps = createMockDependencies();
234
+ // Test 1: Request to a path outside the prefix
235
+ deps.mockRequestInfo.request = new Request("http://localhost:3000/");
236
+ const request1 = new Request("http://localhost:3000/");
237
+ await router.handle({
238
+ request: request1,
239
+ renderPage: deps.mockRenderPage,
240
+ getRequestInfo: deps.getRequestInfo,
241
+ onError: deps.onError,
242
+ runWithRequestInfoOverrides: deps.mockRunWithRequestInfoOverrides,
243
+ rscActionHandler: deps.mockRscActionHandler,
244
+ });
245
+ expect(executionOrder).toEqual(["PageComponent"]);
246
+ // Reset execution order
247
+ executionOrder.length = 0;
248
+ // Test 2: Request to a path inside the prefix
249
+ deps.mockRequestInfo.request = new Request("http://localhost:3000/admin/");
250
+ const request2 = new Request("http://localhost:3000/admin/");
251
+ await router.handle({
252
+ request: request2,
253
+ renderPage: deps.mockRenderPage,
254
+ getRequestInfo: deps.getRequestInfo,
255
+ onError: deps.onError,
256
+ runWithRequestInfoOverrides: deps.mockRunWithRequestInfoOverrides,
257
+ rscActionHandler: deps.mockRscActionHandler,
258
+ });
259
+ expect(executionOrder).toEqual([
260
+ "prefixedMiddleware",
261
+ "AdminPageComponent",
262
+ ]);
263
+ });
264
+ it("should short-circuit from a prefixed middleware", async () => {
265
+ const executionOrder = [];
266
+ const prefixedMiddleware = () => {
267
+ executionOrder.push("prefixedMiddleware");
268
+ return new Response("From prefixed middleware");
269
+ };
270
+ const AdminPageComponent = () => {
271
+ executionOrder.push("AdminPageComponent");
272
+ return React.createElement("div", {}, "Admin Page");
273
+ };
274
+ const router = defineRoutes([
275
+ ...prefix("/admin", [
276
+ prefixedMiddleware,
277
+ route("/", AdminPageComponent),
278
+ ]),
279
+ ]);
280
+ const deps = createMockDependencies();
281
+ // Request to a path inside the prefix
282
+ deps.mockRequestInfo.request = new Request("http://localhost:3000/admin/");
283
+ const request = new Request("http://localhost:3000/admin/");
284
+ const response = await router.handle({
285
+ request,
286
+ renderPage: deps.mockRenderPage,
287
+ getRequestInfo: deps.getRequestInfo,
288
+ onError: deps.onError,
289
+ runWithRequestInfoOverrides: deps.mockRunWithRequestInfoOverrides,
290
+ rscActionHandler: deps.mockRscActionHandler,
291
+ });
292
+ expect(executionOrder).toEqual(["prefixedMiddleware"]);
293
+ expect(await response.text()).toBe("From prefixed middleware");
294
+ });
295
+ });
296
+ describe("RSC Action Handling", () => {
297
+ it("should handle RSC actions before the first route definition", async () => {
298
+ const executionOrder = [];
299
+ const middleware1 = (requestInfo) => {
300
+ executionOrder.push("middleware1");
301
+ };
302
+ const PageComponent = () => {
303
+ executionOrder.push("PageComponent");
304
+ return React.createElement("div", {}, "Page");
305
+ };
306
+ const router = defineRoutes([
307
+ middleware1,
308
+ route("/test/", PageComponent),
309
+ ]);
310
+ const deps = createMockDependencies();
311
+ deps.mockRequestInfo.request = new Request("http://localhost:3000/test/?__rsc_action_id=test");
312
+ deps.mockRscActionHandler = async (request) => {
313
+ executionOrder.push("rscActionHandler");
314
+ return { actionResult: "test-result" };
315
+ };
316
+ const request = new Request("http://localhost:3000/test/?__rsc_action_id=test");
317
+ await router.handle({
318
+ request,
319
+ renderPage: deps.mockRenderPage,
320
+ getRequestInfo: deps.getRequestInfo,
321
+ onError: deps.onError,
322
+ runWithRequestInfoOverrides: deps.mockRunWithRequestInfoOverrides,
323
+ rscActionHandler: deps.mockRscActionHandler,
324
+ });
325
+ expect(executionOrder).toEqual([
326
+ "middleware1",
327
+ "rscActionHandler",
328
+ "PageComponent",
329
+ ]);
330
+ expect(deps.mockRequestInfo.rw.actionResult).toEqual({
331
+ actionResult: "test-result",
332
+ });
333
+ });
334
+ it("should not handle RSC actions multiple times for multiple routes", async () => {
335
+ const executionOrder = [];
336
+ const PageComponent1 = () => {
337
+ executionOrder.push("PageComponent1");
338
+ return React.createElement("div", {}, "Page1");
339
+ };
340
+ const PageComponent2 = () => {
341
+ executionOrder.push("PageComponent2");
342
+ return React.createElement("div", {}, "Page2");
343
+ };
344
+ const router = defineRoutes([
345
+ route("/other/", PageComponent1),
346
+ route("/test/", PageComponent2),
347
+ ]);
348
+ const deps = createMockDependencies();
349
+ deps.mockRequestInfo.request = new Request("http://localhost:3000/test/?__rsc_action_id=test");
350
+ deps.mockRscActionHandler = async (request) => {
351
+ executionOrder.push("rscActionHandler");
352
+ return { actionResult: "test-result" };
353
+ };
354
+ const request = new Request("http://localhost:3000/test/?__rsc_action_id=test");
355
+ await router.handle({
356
+ request,
357
+ renderPage: deps.mockRenderPage,
358
+ getRequestInfo: deps.getRequestInfo,
359
+ onError: deps.onError,
360
+ runWithRequestInfoOverrides: deps.mockRunWithRequestInfoOverrides,
361
+ rscActionHandler: deps.mockRscActionHandler,
362
+ });
363
+ expect(executionOrder).toEqual(["rscActionHandler", "PageComponent2"]);
364
+ // Should only call action handler once, even though there are multiple routes
365
+ });
366
+ });
367
+ describe("Page Route Matching and Rendering", () => {
368
+ it("should match the first route that matches the path", async () => {
369
+ const executionOrder = [];
370
+ const PageComponent1 = () => {
371
+ executionOrder.push("PageComponent1");
372
+ return React.createElement("div", {}, "Page1");
373
+ };
374
+ const PageComponent2 = () => {
375
+ executionOrder.push("PageComponent2");
376
+ return React.createElement("div", {}, "Page2");
377
+ };
378
+ const router = defineRoutes([
379
+ route("/test/", PageComponent1),
380
+ route("/test/", PageComponent2), // This should never be reached
381
+ ]);
382
+ const deps = createMockDependencies();
383
+ deps.mockRequestInfo.request = new Request("http://localhost:3000/test/");
384
+ const request = new Request("http://localhost:3000/test/");
385
+ await router.handle({
386
+ request,
387
+ renderPage: deps.mockRenderPage,
388
+ getRequestInfo: deps.getRequestInfo,
389
+ onError: deps.onError,
390
+ runWithRequestInfoOverrides: deps.mockRunWithRequestInfoOverrides,
391
+ rscActionHandler: deps.mockRscActionHandler,
392
+ });
393
+ expect(executionOrder).toEqual(["PageComponent1"]);
394
+ });
395
+ it("should continue to next route if path does not match", async () => {
396
+ const executionOrder = [];
397
+ const PageComponent1 = () => {
398
+ executionOrder.push("PageComponent1");
399
+ return React.createElement("div", {}, "Page1");
400
+ };
401
+ const PageComponent2 = () => {
402
+ executionOrder.push("PageComponent2");
403
+ return React.createElement("div", {}, "Page2");
404
+ };
405
+ const router = defineRoutes([
406
+ route("/other/", PageComponent1),
407
+ route("/test/", PageComponent2),
408
+ ]);
409
+ const deps = createMockDependencies();
410
+ deps.mockRequestInfo.request = new Request("http://localhost:3000/test/");
411
+ const request = new Request("http://localhost:3000/test/");
412
+ await router.handle({
413
+ request,
414
+ renderPage: deps.mockRenderPage,
415
+ getRequestInfo: deps.getRequestInfo,
416
+ onError: deps.onError,
417
+ runWithRequestInfoOverrides: deps.mockRunWithRequestInfoOverrides,
418
+ rscActionHandler: deps.mockRscActionHandler,
419
+ });
420
+ expect(executionOrder).toEqual(["PageComponent2"]);
421
+ });
422
+ it("should return 404 when no routes match", async () => {
423
+ const PageComponent = () => React.createElement("div", {}, "Page");
424
+ const router = defineRoutes([route("/other/", PageComponent)]);
425
+ const deps = createMockDependencies();
426
+ deps.mockRequestInfo.request = new Request("http://localhost:3000/test/");
427
+ const request = new Request("http://localhost:3000/test/");
428
+ const response = await router.handle({
429
+ request,
430
+ renderPage: deps.mockRenderPage,
431
+ getRequestInfo: deps.getRequestInfo,
432
+ onError: deps.onError,
433
+ runWithRequestInfoOverrides: deps.mockRunWithRequestInfoOverrides,
434
+ rscActionHandler: deps.mockRscActionHandler,
435
+ });
436
+ expect(response.status).toBe(404);
437
+ expect(await response.text()).toBe("Not Found");
438
+ });
439
+ });
440
+ describe("Multiple Render Blocks with SSR Configuration", () => {
441
+ it("should short-circuit on first matching render block and not apply later configurations", async () => {
442
+ const executionOrder = [];
443
+ const ssrSettings = [];
444
+ const Document1 = () => React.createElement("html", {}, "Doc1");
445
+ const Document2 = () => React.createElement("html", {}, "Doc2");
446
+ const PageComponent1 = (requestInfo) => {
447
+ executionOrder.push("PageComponent1");
448
+ ssrSettings.push(requestInfo.rw.ssr);
449
+ return React.createElement("div", {}, "Page1");
450
+ };
451
+ const PageComponent2 = (requestInfo) => {
452
+ executionOrder.push("PageComponent2");
453
+ ssrSettings.push(requestInfo.rw.ssr);
454
+ return React.createElement("div", {}, "Page2");
455
+ };
456
+ const router = defineRoutes([
457
+ ...render(Document1, [route("/test/", PageComponent1)], { ssr: true }),
458
+ ...render(Document2, [route("/other/", PageComponent2)], {
459
+ ssr: false,
460
+ }),
461
+ ]);
462
+ const deps = createMockDependencies();
463
+ deps.mockRequestInfo.request = new Request("http://localhost:3000/test/");
464
+ const request = new Request("http://localhost:3000/test/");
465
+ await router.handle({
466
+ request,
467
+ renderPage: deps.mockRenderPage,
468
+ getRequestInfo: deps.getRequestInfo,
469
+ onError: deps.onError,
470
+ runWithRequestInfoOverrides: deps.mockRunWithRequestInfoOverrides,
471
+ rscActionHandler: deps.mockRscActionHandler,
472
+ });
473
+ expect(executionOrder).toEqual(["PageComponent1"]);
474
+ expect(ssrSettings).toEqual([true]);
475
+ // The second render block's ssr: false should not have been applied
476
+ expect(deps.mockRequestInfo.rw.Document).toBe(Document1);
477
+ });
478
+ });
479
+ describe("Route-Specific Middleware", () => {
480
+ it("should execute route-specific middleware before the component", async () => {
481
+ const executionOrder = [];
482
+ const globalMiddleware = (requestInfo) => {
483
+ executionOrder.push("globalMiddleware");
484
+ };
485
+ const routeMiddleware = (requestInfo) => {
486
+ executionOrder.push("routeMiddleware");
487
+ };
488
+ const PageComponent = () => {
489
+ executionOrder.push("PageComponent");
490
+ return React.createElement("div", {}, "Page");
491
+ };
492
+ const router = defineRoutes([
493
+ globalMiddleware,
494
+ route("/test/", [routeMiddleware, PageComponent]),
495
+ ]);
496
+ const deps = createMockDependencies();
497
+ deps.mockRequestInfo.request = new Request("http://localhost:3000/test/");
498
+ const request = new Request("http://localhost:3000/test/");
499
+ await router.handle({
500
+ request,
501
+ renderPage: deps.mockRenderPage,
502
+ getRequestInfo: deps.getRequestInfo,
503
+ onError: deps.onError,
504
+ runWithRequestInfoOverrides: deps.mockRunWithRequestInfoOverrides,
505
+ rscActionHandler: deps.mockRscActionHandler,
506
+ });
507
+ expect(executionOrder).toEqual([
508
+ "globalMiddleware",
509
+ "routeMiddleware",
510
+ "PageComponent",
511
+ ]);
512
+ });
513
+ it("should short-circuit if route-specific middleware returns a Response", async () => {
514
+ const executionOrder = [];
515
+ const routeMiddleware = (requestInfo) => {
516
+ executionOrder.push("routeMiddleware");
517
+ return new Response("Route Middleware Response");
518
+ };
519
+ const PageComponent = () => {
520
+ executionOrder.push("PageComponent");
521
+ return React.createElement("div", {}, "Page");
522
+ };
523
+ const router = defineRoutes([
524
+ route("/test/", [routeMiddleware, PageComponent]),
525
+ ]);
526
+ const deps = createMockDependencies();
527
+ deps.mockRequestInfo.request = new Request("http://localhost:3000/test/");
528
+ const request = new Request("http://localhost:3000/test/");
529
+ const response = await router.handle({
530
+ request,
531
+ renderPage: deps.mockRenderPage,
532
+ getRequestInfo: deps.getRequestInfo,
533
+ onError: deps.onError,
534
+ runWithRequestInfoOverrides: deps.mockRunWithRequestInfoOverrides,
535
+ rscActionHandler: deps.mockRscActionHandler,
536
+ });
537
+ expect(executionOrder).toEqual(["routeMiddleware"]);
538
+ expect(await response.text()).toBe("Route Middleware Response");
539
+ });
540
+ });
541
+ describe("Layout Handling", () => {
542
+ it("should wrap components with layouts", async () => {
543
+ const executionOrder = [];
544
+ const TestLayout = ({ children }) => {
545
+ executionOrder.push("TestLayout");
546
+ return React.createElement("div", { className: "layout" }, children);
547
+ };
548
+ const PageComponent = () => {
549
+ executionOrder.push("PageComponent");
550
+ return React.createElement("div", {}, "Page Content");
551
+ };
552
+ const router = defineRoutes([
553
+ ...layout(TestLayout, [route("/test/", PageComponent)]),
554
+ ]);
555
+ const deps = createMockDependencies();
556
+ deps.mockRequestInfo.request = new Request("http://localhost:3000/test/");
557
+ // Mock renderPage to track layout wrapping
558
+ deps.mockRenderPage = async (requestInfo, WrappedComponent, onError) => {
559
+ // The component should be wrapped with layouts
560
+ const element = React.createElement(WrappedComponent);
561
+ return new Response(`Rendered with layouts`);
562
+ };
563
+ const request = new Request("http://localhost:3000/test/");
564
+ const response = await router.handle({
565
+ request,
566
+ renderPage: deps.mockRenderPage,
567
+ getRequestInfo: deps.getRequestInfo,
568
+ onError: deps.onError,
569
+ runWithRequestInfoOverrides: deps.mockRunWithRequestInfoOverrides,
570
+ rscActionHandler: deps.mockRscActionHandler,
571
+ });
572
+ expect(await response.text()).toBe("Rendered with layouts");
573
+ });
574
+ });
575
+ describe("Parameter Extraction", () => {
576
+ it("should extract path parameters and make them available in request info", async () => {
577
+ let extractedParams = null;
578
+ const PageComponent = (requestInfo) => {
579
+ extractedParams = requestInfo.params;
580
+ return React.createElement("div", {}, `User: ${requestInfo.params.id}`);
581
+ };
582
+ const router = defineRoutes([route("/users/:id/", PageComponent)]);
583
+ const deps = createMockDependencies();
584
+ deps.mockRequestInfo.request = new Request("http://localhost:3000/users/123/");
585
+ const request = new Request("http://localhost:3000/users/123/");
586
+ await router.handle({
587
+ request,
588
+ renderPage: deps.mockRenderPage,
589
+ getRequestInfo: deps.getRequestInfo,
590
+ onError: deps.onError,
591
+ runWithRequestInfoOverrides: deps.mockRunWithRequestInfoOverrides,
592
+ rscActionHandler: deps.mockRscActionHandler,
593
+ });
594
+ expect(extractedParams).toEqual({ id: "123" });
595
+ });
596
+ });
597
+ describe("Edge Cases", () => {
598
+ it("should handle middleware-only apps with RSC actions", async () => {
599
+ const executionOrder = [];
600
+ const middleware1 = (requestInfo) => {
601
+ executionOrder.push("middleware1");
602
+ };
603
+ const middleware2 = (requestInfo) => {
604
+ executionOrder.push("middleware2");
605
+ return new Response("Middleware Response");
606
+ };
607
+ // No route definitions, only middleware
608
+ const router = defineRoutes([middleware1, middleware2]);
609
+ const deps = createMockDependencies();
610
+ deps.mockRequestInfo.request = new Request("http://localhost:3000/test/?__rsc_action_id=test");
611
+ deps.mockRscActionHandler = async (request) => {
612
+ executionOrder.push("rscActionHandler");
613
+ return { actionResult: "test-result" };
614
+ };
615
+ const request = new Request("http://localhost:3000/test/?__rsc_action_id=test");
616
+ const response = await router.handle({
617
+ request,
618
+ renderPage: deps.mockRenderPage,
619
+ getRequestInfo: deps.getRequestInfo,
620
+ onError: deps.onError,
621
+ runWithRequestInfoOverrides: deps.mockRunWithRequestInfoOverrides,
622
+ rscActionHandler: deps.mockRscActionHandler,
623
+ });
624
+ // Action should still be handled even with no route definitions
625
+ expect(executionOrder).toEqual(["middleware1", "middleware2"]);
626
+ expect(await response.text()).toBe("Middleware Response");
627
+ });
628
+ it("should handle trailing slash normalization", async () => {
629
+ const PageComponent = () => React.createElement("div", {}, "Page");
630
+ const router = defineRoutes([route("/test/", PageComponent)]);
631
+ const deps = createMockDependencies();
632
+ // Request without trailing slash should be normalized
633
+ deps.mockRequestInfo.request = new Request("http://localhost:3000/test");
634
+ const request = new Request("http://localhost:3000/test");
635
+ const response = await router.handle({
636
+ request,
637
+ renderPage: deps.mockRenderPage,
638
+ getRequestInfo: deps.getRequestInfo,
639
+ onError: deps.onError,
640
+ runWithRequestInfoOverrides: deps.mockRunWithRequestInfoOverrides,
641
+ rscActionHandler: deps.mockRscActionHandler,
642
+ });
643
+ expect(response.status).not.toBe(404);
644
+ expect(await response.text()).toBe("Rendered: Element");
645
+ });
646
+ });
647
+ });
@@ -0,0 +1,22 @@
1
+ import { type RequestInfo } from "../requestInfo/types.js";
2
+ import { type Kysely } from "kysely";
3
+ export type RwContext = {
4
+ nonce: string;
5
+ Document: React.FC<DocumentProps<any>>;
6
+ rscPayload: boolean;
7
+ ssr: boolean;
8
+ layouts?: React.FC<LayoutProps<any>>[];
9
+ databases: Map<string, Kysely<any>>;
10
+ scriptsToBeLoaded: Set<string>;
11
+ entryScripts: Set<string>;
12
+ inlineScripts: Set<string>;
13
+ pageRouteResolved: PromiseWithResolvers<void> | undefined;
14
+ actionResult?: unknown;
15
+ };
16
+ export type DocumentProps<T extends RequestInfo = RequestInfo> = T & {
17
+ children: React.ReactNode;
18
+ };
19
+ export type LayoutProps<T extends RequestInfo = RequestInfo> = {
20
+ children?: React.ReactNode;
21
+ requestInfo?: T;
22
+ };
@@ -0,0 +1 @@
1
+ export {};
@@ -1,4 +1,5 @@
1
- export declare const verifyTurnstileToken: ({ token, secretKey, }: {
1
+ export declare const verifyTurnstileToken: ({ token, secretKey, fetchFn, }: {
2
2
  token: string;
3
3
  secretKey: string;
4
+ fetchFn?: typeof fetch;
4
5
  }) => Promise<boolean>;
@@ -1,10 +1,10 @@
1
- export const verifyTurnstileToken = async ({ token, secretKey, }) => {
2
- const response = await fetch("https://challenges.cloudflare.com/turnstile/v0/siteverify", {
3
- method: "POST",
4
- headers: { "Content-Type": "application/x-www-form-urlencoded" },
5
- body: new URLSearchParams({ secret: secretKey, response: token }),
6
- });
1
+ export const verifyTurnstileToken = async ({ token, secretKey, fetchFn = fetch, }) => {
7
2
  try {
3
+ const response = await fetchFn("https://challenges.cloudflare.com/turnstile/v0/siteverify", {
4
+ method: "POST",
5
+ headers: { "Content-Type": "application/x-www-form-urlencoded" },
6
+ body: new URLSearchParams({ secret: secretKey, response: token }),
7
+ });
8
8
  const data = (await response.json());
9
9
  return data?.success === true;
10
10
  }