rwsdk 1.0.4 → 1.0.5

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.
@@ -206,7 +206,6 @@ export function defineRoutes(routes) {
206
206
  throw error;
207
207
  }
208
208
  // --- Main flow ---
209
- let firstRouteDefinitionEncountered = false;
210
209
  let actionHandled = false;
211
210
  const handleAction = async () => {
212
211
  // Handle RSC actions once per request, based on the incoming URL.
@@ -241,17 +240,6 @@ export function defineRoutes(routes) {
241
240
  currentRouteIndex++;
242
241
  continue;
243
242
  }
244
- // This is a RouteDefinition (route.type === "definition").
245
- // The first time we see one, we handle any RSC actions.
246
- if (!firstRouteDefinitionEncountered) {
247
- firstRouteDefinitionEncountered = true;
248
- try {
249
- await handleAction();
250
- }
251
- catch (error) {
252
- return await executeExceptHandlers(error, currentRouteIndex);
253
- }
254
- }
255
243
  let params = null;
256
244
  if (route.isStatic) {
257
245
  if (route.path === path) {
@@ -310,6 +298,9 @@ export function defineRoutes(routes) {
310
298
  return handled;
311
299
  }
312
300
  }
301
+ // All global and route-specific middlewares have run, so it's
302
+ // safe to handle any pending RSC action before rendering.
303
+ await handleAction();
313
304
  // Final component/handler
314
305
  if (isRouteComponent(componentHandler)) {
315
306
  const requestInfo = getRequestInfo();
@@ -365,14 +356,13 @@ Route handlers must return one of:
365
356
  }
366
357
  }
367
358
  // If we've gotten this far, no route was matched.
368
- // We still need to handle a possible action if the app has no route definitions at all.
369
- if (!firstRouteDefinitionEncountered) {
370
- try {
371
- await handleAction();
372
- }
373
- catch (error) {
374
- return await executeExceptHandlers(error, compiledRoutes.length - 1);
375
- }
359
+ // All global middlewares have already executed, so it's safe to handle
360
+ // any pending RSC action before returning the 404 response.
361
+ try {
362
+ await handleAction();
363
+ }
364
+ catch (error) {
365
+ return await executeExceptHandlers(error, compiledRoutes.length - 1);
376
366
  }
377
367
  return new Response("Not Found", { status: 404 });
378
368
  }
@@ -404,7 +404,7 @@ describe("defineRoutes - Request Handling Behavior", () => {
404
404
  });
405
405
  });
406
406
  describe("RSC Action Handling", () => {
407
- it("should handle RSC actions before the first route definition", async () => {
407
+ it("should handle RSC actions after global middleware but before page rendering", async () => {
408
408
  const executionOrder = [];
409
409
  const middleware1 = (requestInfo) => {
410
410
  executionOrder.push("middleware1");
@@ -473,6 +473,96 @@ describe("defineRoutes - Request Handling Behavior", () => {
473
473
  expect(executionOrder).toEqual(["rscActionHandler", "PageComponent2"]);
474
474
  // Should only call action handler once, even though there are multiple routes
475
475
  });
476
+ // Regression test for issue #1110:
477
+ // When a route definition appears before global middleware in the flattened
478
+ // route list (e.g. an API route defined before a session-setup middleware),
479
+ // the RSC action handler was incorrectly fired at the first route definition
480
+ // encountered – before those later middlewares had run. Interruptors
481
+ // (registered via serverAction/serverQuery) execute inside rscActionHandler,
482
+ // so they would see an incomplete request context (e.g. ctx.user not yet set).
483
+ it("should run all global middleware before rscActionHandler even when a route definition appears first", async () => {
484
+ const executionOrder = [];
485
+ // A route definition that appears BEFORE global middleware
486
+ const apiRoute = route("/api/data/", async () => new Response(JSON.stringify({ data: "ok" })));
487
+ // Global middleware that sets up context (e.g. session / auth)
488
+ const globalMiddleware = async (requestInfo) => {
489
+ executionOrder.push("globalMiddleware");
490
+ requestInfo.ctx.user = { id: 1 };
491
+ };
492
+ const PageComponent = () => {
493
+ executionOrder.push("PageComponent");
494
+ return React.createElement("div", {}, "Page");
495
+ };
496
+ const router = defineRoutes([
497
+ apiRoute,
498
+ globalMiddleware, // global middleware defined AFTER a route definition
499
+ route("/test/", PageComponent),
500
+ ]);
501
+ const deps = createMockDependencies();
502
+ deps.mockRequestInfo.request = new Request("http://localhost:3000/test/?__rsc_action_id=test");
503
+ deps.mockRscActionHandler = async (request) => {
504
+ executionOrder.push("rscActionHandler");
505
+ return { actionResult: "test-result" };
506
+ };
507
+ const request = new Request("http://localhost:3000/test/?__rsc_action_id=test");
508
+ await router.handle({
509
+ request,
510
+ renderPage: deps.mockRenderPage,
511
+ getRequestInfo: deps.getRequestInfo,
512
+ onError: deps.onError,
513
+ runWithRequestInfoOverrides: deps.mockRunWithRequestInfoOverrides,
514
+ rscActionHandler: deps.mockRscActionHandler,
515
+ });
516
+ // globalMiddleware must complete before rscActionHandler runs so that
517
+ // interruptors inside the action have access to ctx.user.
518
+ expect(executionOrder).toEqual([
519
+ "globalMiddleware",
520
+ "rscActionHandler",
521
+ "PageComponent",
522
+ ]);
523
+ });
524
+ it("should handle RSC actions after route-specific middleware", async () => {
525
+ const executionOrder = [];
526
+ const globalMiddleware = async (requestInfo) => {
527
+ executionOrder.push("globalMiddleware");
528
+ requestInfo.ctx.user = { id: 1 };
529
+ };
530
+ const requireAdmin = async (requestInfo) => {
531
+ executionOrder.push("requireAdmin");
532
+ if (!requestInfo.ctx.user?.isAdmin) {
533
+ return new Response("Forbidden", { status: 403 });
534
+ }
535
+ };
536
+ const AdminPage = () => {
537
+ executionOrder.push("AdminPage");
538
+ return React.createElement("div", {}, "Admin");
539
+ };
540
+ const router = defineRoutes([
541
+ globalMiddleware,
542
+ route("/admin/", [requireAdmin, AdminPage]),
543
+ ]);
544
+ const deps = createMockDependencies();
545
+ deps.mockRequestInfo.request = new Request("http://localhost:3000/admin/?__rsc_action_id=test");
546
+ deps.mockRscActionHandler = async (request) => {
547
+ executionOrder.push("rscActionHandler");
548
+ return { actionResult: "test-result" };
549
+ };
550
+ const request = new Request("http://localhost:3000/admin/?__rsc_action_id=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
+ // requireAdmin should short-circuit before the action runs
560
+ expect(executionOrder).toEqual([
561
+ "globalMiddleware",
562
+ "requireAdmin",
563
+ ]);
564
+ expect(response.status).toBe(403);
565
+ });
476
566
  });
477
567
  describe("Page Route Matching and Rendering", () => {
478
568
  it("should match the first route that matches the path", async () => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "rwsdk",
3
- "version": "1.0.4",
3
+ "version": "1.0.5",
4
4
  "description": "Build fast, server-driven webapps on Cloudflare with SSR, RSC, and realtime",
5
5
  "type": "module",
6
6
  "bin": {
@@ -154,7 +154,7 @@
154
154
  "license": "MIT",
155
155
  "dependencies": {
156
156
  "@ast-grep/napi": "~0.41.0",
157
- "@cloudflare/workers-types": "~4.20260307.1",
157
+ "@cloudflare/workers-types": "~4.20260331.1",
158
158
  "@mdx-js/mdx": "~3.1.1",
159
159
  "@puppeteer/browsers": "~2.13.0",
160
160
  "@types/decompress": "~4.2.7",
@@ -172,11 +172,11 @@
172
172
  "execa": "~9.6.1",
173
173
  "find-up": "~8.0.0",
174
174
  "fs-extra": "~11.3.4",
175
- "get-port": "^7.1.0",
175
+ "get-port": "^7.2.0",
176
176
  "glob": "~13.0.6",
177
177
  "ignore": "~7.0.5",
178
178
  "jsonc-parser": "~3.3.1",
179
- "kysely": "~0.28.12",
179
+ "kysely": "~0.28.15",
180
180
  "kysely-do": "~0.0.1-rc.1",
181
181
  "lodash": "~4.17.23",
182
182
  "magic-string": "~0.30.21",
@@ -199,13 +199,14 @@
199
199
  "react-dom": ">=19.2.0-0 <19.3.0 || >=19.3.0-0 <20.0.0",
200
200
  "react-server-dom-webpack": ">=19.2.0-0 <19.3.0 || >=19.3.0-0 <20.0.0",
201
201
  "vite": "^6.2.6 || 7.x",
202
- "wrangler": "^4.71.0"
202
+ "wrangler": "^4.77.0"
203
203
  },
204
204
  "packageManager": "pnpm@10.31.0",
205
205
  "devDependencies": {
206
- "@cloudflare/vite-plugin": "1.26.1",
206
+ "@cloudflare/vite-plugin": "1.30.1",
207
+ "wrangler": "^4.77.0",
207
208
  "capnweb": "~0.5.0",
208
- "@types/debug": "~4.1.12",
209
+ "@types/debug": "~4.1.13",
209
210
  "@types/js-beautify": "~1.14.3",
210
211
  "@types/lodash": "~4.17.24",
211
212
  "@types/node": "~25.3.5",
@@ -213,8 +214,8 @@
213
214
  "js-beautify": "~1.15.4",
214
215
  "semver": "~7.7.4",
215
216
  "tsx": "~4.21.0",
216
- "typescript": "~5.9.3",
217
+ "typescript": "~6.0.2",
217
218
  "vite": "~7.3.1",
218
- "vitest": "~4.0.18"
219
+ "vitest": "~4.1.2"
219
220
  }
220
221
  }