rwsdk 1.0.0-alpha.4 → 1.0.0-alpha.6

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.
@@ -97,30 +97,38 @@ export function defineRoutes(routes) {
97
97
  return undefined;
98
98
  }
99
99
  // --- Main flow ---
100
- const globalMiddlewares = flattenedRoutes.filter((route) => typeof route === "function");
101
- const routeDefinitions = flattenedRoutes.filter((route) => typeof route !== "function");
102
- // 1. Run global middlewares
103
- for (const middleware of globalMiddlewares) {
104
- const result = await middleware(getRequestInfo());
105
- const handled = await handleMiddlewareResult(result);
106
- if (handled) {
107
- return handled;
100
+ let firstRouteDefinitionEncountered = false;
101
+ let actionHandled = false;
102
+ const handleAction = async () => {
103
+ if (!actionHandled && url.searchParams.has("__rsc_action_id")) {
104
+ getRequestInfo().rw.actionResult = await rscActionHandler(request);
105
+ actionHandled = true;
106
+ }
107
+ };
108
+ for (const route of flattenedRoutes) {
109
+ if (typeof route === "function") {
110
+ // This is a global middleware.
111
+ const result = await route(getRequestInfo());
112
+ const handled = await handleMiddlewareResult(result);
113
+ if (handled) {
114
+ return handled; // Short-circuit
115
+ }
116
+ continue;
117
+ }
118
+ // This is a RouteDefinition.
119
+ // The first time we see one, we handle any RSC actions.
120
+ if (!firstRouteDefinitionEncountered) {
121
+ firstRouteDefinitionEncountered = true;
122
+ await handleAction();
108
123
  }
109
- }
110
- // 2. Handle RSC actions
111
- if (url.searchParams.has("__rsc_action_id")) {
112
- getRequestInfo().rw.actionResult = await rscActionHandler(request);
113
- }
114
- // 3. Match and handle routes
115
- for (const route of routeDefinitions) {
116
124
  const params = matchPath(route.path, path);
117
125
  if (!params) {
118
- continue;
126
+ continue; // Not a match, keep going.
119
127
  }
120
- // Found a match: run route-specific middlewares, then the final component
128
+ // Found a match: run route-specific middlewares, then the final component, then stop.
121
129
  return await runWithRequestInfoOverrides({ params }, async () => {
122
130
  const { routeMiddlewares, componentHandler } = parseHandlers(route.handler);
123
- // 3a. Route-specific middlewares
131
+ // Route-specific middlewares
124
132
  for (const mw of routeMiddlewares) {
125
133
  const result = await mw(getRequestInfo());
126
134
  const handled = await handleMiddlewareResult(result);
@@ -128,7 +136,7 @@ export function defineRoutes(routes) {
128
136
  return handled;
129
137
  }
130
138
  }
131
- // 3b. Final component/handler
139
+ // Final component/handler
132
140
  if (isRouteComponent(componentHandler)) {
133
141
  const requestInfo = getRequestInfo();
134
142
  const WrappedComponent = wrapWithLayouts(wrapHandlerToThrowResponses(componentHandler), route.layouts || [], requestInfo);
@@ -148,7 +156,11 @@ export function defineRoutes(routes) {
148
156
  });
149
157
  });
150
158
  }
151
- // No route matched
159
+ // If we've gotten this far, no route was matched.
160
+ // We still need to handle a possible action if the app has no route definitions at all.
161
+ if (!firstRouteDefinitionEncountered) {
162
+ await handleAction();
163
+ }
152
164
  return new Response("Not Found", { status: 404 });
153
165
  },
154
166
  };
@@ -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 } from "./router";
3
4
  describe("matchPath", () => {
4
5
  // Test case 1: Static paths
5
6
  it("should match static paths", () => {
@@ -56,3 +57,507 @@ 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("RSC Action Handling", () => {
213
+ it("should handle RSC actions before the first route definition", async () => {
214
+ const executionOrder = [];
215
+ const middleware1 = (requestInfo) => {
216
+ executionOrder.push("middleware1");
217
+ };
218
+ const PageComponent = () => {
219
+ executionOrder.push("PageComponent");
220
+ return React.createElement("div", {}, "Page");
221
+ };
222
+ const router = defineRoutes([
223
+ middleware1,
224
+ route("/test/", PageComponent),
225
+ ]);
226
+ const deps = createMockDependencies();
227
+ deps.mockRequestInfo.request = new Request("http://localhost:3000/test/?__rsc_action_id=test");
228
+ deps.mockRscActionHandler = async (request) => {
229
+ executionOrder.push("rscActionHandler");
230
+ return { actionResult: "test-result" };
231
+ };
232
+ const request = new Request("http://localhost:3000/test/?__rsc_action_id=test");
233
+ await router.handle({
234
+ request,
235
+ renderPage: deps.mockRenderPage,
236
+ getRequestInfo: deps.getRequestInfo,
237
+ onError: deps.onError,
238
+ runWithRequestInfoOverrides: deps.mockRunWithRequestInfoOverrides,
239
+ rscActionHandler: deps.mockRscActionHandler,
240
+ });
241
+ expect(executionOrder).toEqual([
242
+ "middleware1",
243
+ "rscActionHandler",
244
+ "PageComponent",
245
+ ]);
246
+ expect(deps.mockRequestInfo.rw.actionResult).toEqual({
247
+ actionResult: "test-result",
248
+ });
249
+ });
250
+ it("should not handle RSC actions multiple times for multiple routes", async () => {
251
+ const executionOrder = [];
252
+ const PageComponent1 = () => {
253
+ executionOrder.push("PageComponent1");
254
+ return React.createElement("div", {}, "Page1");
255
+ };
256
+ const PageComponent2 = () => {
257
+ executionOrder.push("PageComponent2");
258
+ return React.createElement("div", {}, "Page2");
259
+ };
260
+ const router = defineRoutes([
261
+ route("/other/", PageComponent1),
262
+ route("/test/", PageComponent2),
263
+ ]);
264
+ const deps = createMockDependencies();
265
+ deps.mockRequestInfo.request = new Request("http://localhost:3000/test/?__rsc_action_id=test");
266
+ deps.mockRscActionHandler = async (request) => {
267
+ executionOrder.push("rscActionHandler");
268
+ return { actionResult: "test-result" };
269
+ };
270
+ const request = new Request("http://localhost:3000/test/?__rsc_action_id=test");
271
+ await router.handle({
272
+ request,
273
+ renderPage: deps.mockRenderPage,
274
+ getRequestInfo: deps.getRequestInfo,
275
+ onError: deps.onError,
276
+ runWithRequestInfoOverrides: deps.mockRunWithRequestInfoOverrides,
277
+ rscActionHandler: deps.mockRscActionHandler,
278
+ });
279
+ expect(executionOrder).toEqual(["rscActionHandler", "PageComponent2"]);
280
+ // Should only call action handler once, even though there are multiple routes
281
+ });
282
+ });
283
+ describe("Page Route Matching and Rendering", () => {
284
+ it("should match the first route that matches the path", async () => {
285
+ const executionOrder = [];
286
+ const PageComponent1 = () => {
287
+ executionOrder.push("PageComponent1");
288
+ return React.createElement("div", {}, "Page1");
289
+ };
290
+ const PageComponent2 = () => {
291
+ executionOrder.push("PageComponent2");
292
+ return React.createElement("div", {}, "Page2");
293
+ };
294
+ const router = defineRoutes([
295
+ route("/test/", PageComponent1),
296
+ route("/test/", PageComponent2), // This should never be reached
297
+ ]);
298
+ const deps = createMockDependencies();
299
+ deps.mockRequestInfo.request = new Request("http://localhost:3000/test/");
300
+ const request = new Request("http://localhost:3000/test/");
301
+ await router.handle({
302
+ request,
303
+ renderPage: deps.mockRenderPage,
304
+ getRequestInfo: deps.getRequestInfo,
305
+ onError: deps.onError,
306
+ runWithRequestInfoOverrides: deps.mockRunWithRequestInfoOverrides,
307
+ rscActionHandler: deps.mockRscActionHandler,
308
+ });
309
+ expect(executionOrder).toEqual(["PageComponent1"]);
310
+ });
311
+ it("should continue to next route if path does not match", async () => {
312
+ const executionOrder = [];
313
+ const PageComponent1 = () => {
314
+ executionOrder.push("PageComponent1");
315
+ return React.createElement("div", {}, "Page1");
316
+ };
317
+ const PageComponent2 = () => {
318
+ executionOrder.push("PageComponent2");
319
+ return React.createElement("div", {}, "Page2");
320
+ };
321
+ const router = defineRoutes([
322
+ route("/other/", PageComponent1),
323
+ route("/test/", PageComponent2),
324
+ ]);
325
+ const deps = createMockDependencies();
326
+ deps.mockRequestInfo.request = new Request("http://localhost:3000/test/");
327
+ const request = new Request("http://localhost:3000/test/");
328
+ await router.handle({
329
+ request,
330
+ renderPage: deps.mockRenderPage,
331
+ getRequestInfo: deps.getRequestInfo,
332
+ onError: deps.onError,
333
+ runWithRequestInfoOverrides: deps.mockRunWithRequestInfoOverrides,
334
+ rscActionHandler: deps.mockRscActionHandler,
335
+ });
336
+ expect(executionOrder).toEqual(["PageComponent2"]);
337
+ });
338
+ it("should return 404 when no routes match", async () => {
339
+ const PageComponent = () => React.createElement("div", {}, "Page");
340
+ const router = defineRoutes([route("/other/", PageComponent)]);
341
+ const deps = createMockDependencies();
342
+ deps.mockRequestInfo.request = new Request("http://localhost:3000/test/");
343
+ const request = new Request("http://localhost:3000/test/");
344
+ const response = await router.handle({
345
+ request,
346
+ renderPage: deps.mockRenderPage,
347
+ getRequestInfo: deps.getRequestInfo,
348
+ onError: deps.onError,
349
+ runWithRequestInfoOverrides: deps.mockRunWithRequestInfoOverrides,
350
+ rscActionHandler: deps.mockRscActionHandler,
351
+ });
352
+ expect(response.status).toBe(404);
353
+ expect(await response.text()).toBe("Not Found");
354
+ });
355
+ });
356
+ describe("Multiple Render Blocks with SSR Configuration", () => {
357
+ it("should short-circuit on first matching render block and not apply later configurations", async () => {
358
+ const executionOrder = [];
359
+ const ssrSettings = [];
360
+ const Document1 = () => React.createElement("html", {}, "Doc1");
361
+ const Document2 = () => React.createElement("html", {}, "Doc2");
362
+ const PageComponent1 = (requestInfo) => {
363
+ executionOrder.push("PageComponent1");
364
+ ssrSettings.push(requestInfo.rw.ssr);
365
+ return React.createElement("div", {}, "Page1");
366
+ };
367
+ const PageComponent2 = (requestInfo) => {
368
+ executionOrder.push("PageComponent2");
369
+ ssrSettings.push(requestInfo.rw.ssr);
370
+ return React.createElement("div", {}, "Page2");
371
+ };
372
+ const router = defineRoutes([
373
+ ...render(Document1, [route("/test/", PageComponent1)], { ssr: true }),
374
+ ...render(Document2, [route("/other/", PageComponent2)], {
375
+ ssr: false,
376
+ }),
377
+ ]);
378
+ const deps = createMockDependencies();
379
+ deps.mockRequestInfo.request = new Request("http://localhost:3000/test/");
380
+ const request = new Request("http://localhost:3000/test/");
381
+ await router.handle({
382
+ request,
383
+ renderPage: deps.mockRenderPage,
384
+ getRequestInfo: deps.getRequestInfo,
385
+ onError: deps.onError,
386
+ runWithRequestInfoOverrides: deps.mockRunWithRequestInfoOverrides,
387
+ rscActionHandler: deps.mockRscActionHandler,
388
+ });
389
+ expect(executionOrder).toEqual(["PageComponent1"]);
390
+ expect(ssrSettings).toEqual([true]);
391
+ // The second render block's ssr: false should not have been applied
392
+ expect(deps.mockRequestInfo.rw.Document).toBe(Document1);
393
+ });
394
+ });
395
+ describe("Route-Specific Middleware", () => {
396
+ it("should execute route-specific middleware before the component", async () => {
397
+ const executionOrder = [];
398
+ const globalMiddleware = (requestInfo) => {
399
+ executionOrder.push("globalMiddleware");
400
+ };
401
+ const routeMiddleware = (requestInfo) => {
402
+ executionOrder.push("routeMiddleware");
403
+ };
404
+ const PageComponent = () => {
405
+ executionOrder.push("PageComponent");
406
+ return React.createElement("div", {}, "Page");
407
+ };
408
+ const router = defineRoutes([
409
+ globalMiddleware,
410
+ route("/test/", [routeMiddleware, PageComponent]),
411
+ ]);
412
+ const deps = createMockDependencies();
413
+ deps.mockRequestInfo.request = new Request("http://localhost:3000/test/");
414
+ const request = new Request("http://localhost:3000/test/");
415
+ await router.handle({
416
+ request,
417
+ renderPage: deps.mockRenderPage,
418
+ getRequestInfo: deps.getRequestInfo,
419
+ onError: deps.onError,
420
+ runWithRequestInfoOverrides: deps.mockRunWithRequestInfoOverrides,
421
+ rscActionHandler: deps.mockRscActionHandler,
422
+ });
423
+ expect(executionOrder).toEqual([
424
+ "globalMiddleware",
425
+ "routeMiddleware",
426
+ "PageComponent",
427
+ ]);
428
+ });
429
+ it("should short-circuit if route-specific middleware returns a Response", async () => {
430
+ const executionOrder = [];
431
+ const routeMiddleware = (requestInfo) => {
432
+ executionOrder.push("routeMiddleware");
433
+ return new Response("Route Middleware Response");
434
+ };
435
+ const PageComponent = () => {
436
+ executionOrder.push("PageComponent");
437
+ return React.createElement("div", {}, "Page");
438
+ };
439
+ const router = defineRoutes([
440
+ route("/test/", [routeMiddleware, PageComponent]),
441
+ ]);
442
+ const deps = createMockDependencies();
443
+ deps.mockRequestInfo.request = new Request("http://localhost:3000/test/");
444
+ const request = new Request("http://localhost:3000/test/");
445
+ const response = await router.handle({
446
+ request,
447
+ renderPage: deps.mockRenderPage,
448
+ getRequestInfo: deps.getRequestInfo,
449
+ onError: deps.onError,
450
+ runWithRequestInfoOverrides: deps.mockRunWithRequestInfoOverrides,
451
+ rscActionHandler: deps.mockRscActionHandler,
452
+ });
453
+ expect(executionOrder).toEqual(["routeMiddleware"]);
454
+ expect(await response.text()).toBe("Route Middleware Response");
455
+ });
456
+ });
457
+ describe("Layout Handling", () => {
458
+ it("should wrap components with layouts", async () => {
459
+ const executionOrder = [];
460
+ const TestLayout = ({ children }) => {
461
+ executionOrder.push("TestLayout");
462
+ return React.createElement("div", { className: "layout" }, children);
463
+ };
464
+ const PageComponent = () => {
465
+ executionOrder.push("PageComponent");
466
+ return React.createElement("div", {}, "Page Content");
467
+ };
468
+ const router = defineRoutes([
469
+ ...layout(TestLayout, [route("/test/", PageComponent)]),
470
+ ]);
471
+ const deps = createMockDependencies();
472
+ deps.mockRequestInfo.request = new Request("http://localhost:3000/test/");
473
+ // Mock renderPage to track layout wrapping
474
+ deps.mockRenderPage = async (requestInfo, WrappedComponent, onError) => {
475
+ // The component should be wrapped with layouts
476
+ const element = React.createElement(WrappedComponent);
477
+ return new Response(`Rendered with layouts`);
478
+ };
479
+ const request = new Request("http://localhost:3000/test/");
480
+ const response = await router.handle({
481
+ request,
482
+ renderPage: deps.mockRenderPage,
483
+ getRequestInfo: deps.getRequestInfo,
484
+ onError: deps.onError,
485
+ runWithRequestInfoOverrides: deps.mockRunWithRequestInfoOverrides,
486
+ rscActionHandler: deps.mockRscActionHandler,
487
+ });
488
+ expect(await response.text()).toBe("Rendered with layouts");
489
+ });
490
+ });
491
+ describe("Parameter Extraction", () => {
492
+ it("should extract path parameters and make them available in request info", async () => {
493
+ let extractedParams = null;
494
+ const PageComponent = (requestInfo) => {
495
+ extractedParams = requestInfo.params;
496
+ return React.createElement("div", {}, `User: ${requestInfo.params.id}`);
497
+ };
498
+ const router = defineRoutes([route("/users/:id/", PageComponent)]);
499
+ const deps = createMockDependencies();
500
+ deps.mockRequestInfo.request = new Request("http://localhost:3000/users/123/");
501
+ const request = new Request("http://localhost:3000/users/123/");
502
+ await router.handle({
503
+ request,
504
+ renderPage: deps.mockRenderPage,
505
+ getRequestInfo: deps.getRequestInfo,
506
+ onError: deps.onError,
507
+ runWithRequestInfoOverrides: deps.mockRunWithRequestInfoOverrides,
508
+ rscActionHandler: deps.mockRscActionHandler,
509
+ });
510
+ expect(extractedParams).toEqual({ id: "123" });
511
+ });
512
+ });
513
+ describe("Edge Cases", () => {
514
+ it("should handle middleware-only apps with RSC actions", async () => {
515
+ const executionOrder = [];
516
+ const middleware1 = (requestInfo) => {
517
+ executionOrder.push("middleware1");
518
+ };
519
+ const middleware2 = (requestInfo) => {
520
+ executionOrder.push("middleware2");
521
+ return new Response("Middleware Response");
522
+ };
523
+ // No route definitions, only middleware
524
+ const router = defineRoutes([middleware1, middleware2]);
525
+ const deps = createMockDependencies();
526
+ deps.mockRequestInfo.request = new Request("http://localhost:3000/test/?__rsc_action_id=test");
527
+ deps.mockRscActionHandler = async (request) => {
528
+ executionOrder.push("rscActionHandler");
529
+ return { actionResult: "test-result" };
530
+ };
531
+ const request = new Request("http://localhost:3000/test/?__rsc_action_id=test");
532
+ const response = await router.handle({
533
+ request,
534
+ renderPage: deps.mockRenderPage,
535
+ getRequestInfo: deps.getRequestInfo,
536
+ onError: deps.onError,
537
+ runWithRequestInfoOverrides: deps.mockRunWithRequestInfoOverrides,
538
+ rscActionHandler: deps.mockRscActionHandler,
539
+ });
540
+ // Action should still be handled even with no route definitions
541
+ expect(executionOrder).toEqual(["middleware1", "middleware2"]);
542
+ expect(await response.text()).toBe("Middleware Response");
543
+ });
544
+ it("should handle trailing slash normalization", async () => {
545
+ const PageComponent = () => React.createElement("div", {}, "Page");
546
+ const router = defineRoutes([route("/test/", PageComponent)]);
547
+ const deps = createMockDependencies();
548
+ // Request without trailing slash should be normalized
549
+ deps.mockRequestInfo.request = new Request("http://localhost:3000/test");
550
+ const request = new Request("http://localhost:3000/test");
551
+ const response = await router.handle({
552
+ request,
553
+ renderPage: deps.mockRenderPage,
554
+ getRequestInfo: deps.getRequestInfo,
555
+ onError: deps.onError,
556
+ runWithRequestInfoOverrides: deps.mockRunWithRequestInfoOverrides,
557
+ rscActionHandler: deps.mockRscActionHandler,
558
+ });
559
+ expect(response.status).not.toBe(404);
560
+ expect(await response.text()).toBe("Rendered: Element");
561
+ });
562
+ });
563
+ });
@@ -14,7 +14,7 @@ export const generateVendorBarrelContent = (files, projectRootDir) => {
14
14
  const exports = "export default {\n" +
15
15
  [...files]
16
16
  .filter((file) => file.includes("node_modules"))
17
- .map((file, i) => ` '${normalizeModulePath(file, projectRootDir).slice(1)}': M${i},`)
17
+ .map((file, i) => ` '${normalizeModulePath(file, projectRootDir)}': M${i},`)
18
18
  .join("\n") +
19
19
  "\n};";
20
20
  return `${imports}\n\n${exports}`;
@@ -14,8 +14,8 @@ describe("directiveModulesDevPlugin helpers", () => {
14
14
  import * as M1 from '${projectRootDir}/node_modules/lib-b/component.tsx';
15
15
 
16
16
  export default {
17
- 'node_modules/lib-a/index.js': M0,
18
- 'node_modules/lib-b/component.tsx': M1,
17
+ '/node_modules/lib-a/index.js': M0,
18
+ '/node_modules/lib-b/component.tsx': M1,
19
19
  };`;
20
20
  expect(content).toEqual(expected);
21
21
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "rwsdk",
3
- "version": "1.0.0-alpha.4",
3
+ "version": "1.0.0-alpha.6",
4
4
  "description": "Build fast, server-driven webapps on Cloudflare with SSR, RSC, and realtime",
5
5
  "type": "module",
6
6
  "bin": {