rwsdk 1.0.3 → 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
|
-
//
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
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
|
|
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 () => {
|
|
@@ -60,8 +60,8 @@ export const ssrBridgeWrapPlugin = () => {
|
|
|
60
60
|
if (exportStart !== -1)
|
|
61
61
|
break;
|
|
62
62
|
}
|
|
63
|
-
const banner = `
|
|
64
|
-
const footer = `return { renderHtmlStream, ssrLoadModule, ssrWebpackRequire, ssrGetModuleExport, createThenableFromReadableStream };\n})();`;
|
|
63
|
+
const banner = `const { renderHtmlStream, ssrLoadModule, ssrWebpackRequire, ssrGetModuleExport, createThenableFromReadableStream } = (function() {`;
|
|
64
|
+
const footer = `return { renderHtmlStream, ssrLoadModule, ssrWebpackRequire, ssrGetModuleExport, createThenableFromReadableStream };\n})();\nexport { renderHtmlStream, ssrLoadModule, ssrWebpackRequire, ssrGetModuleExport, createThenableFromReadableStream };`;
|
|
65
65
|
// Insert banner after the last import (or at the beginning if no imports)
|
|
66
66
|
const insertIndex = lastImportEnd === -1 ? 0 : lastImportEnd;
|
|
67
67
|
s.appendLeft(insertIndex, banner + "\n");
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "rwsdk",
|
|
3
|
-
"version": "1.0.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
202
|
+
"wrangler": "^4.77.0"
|
|
203
203
|
},
|
|
204
204
|
"packageManager": "pnpm@10.31.0",
|
|
205
205
|
"devDependencies": {
|
|
206
|
-
"@cloudflare/vite-plugin": "1.
|
|
206
|
+
"@cloudflare/vite-plugin": "1.30.1",
|
|
207
|
+
"wrangler": "^4.77.0",
|
|
207
208
|
"capnweb": "~0.5.0",
|
|
208
|
-
"@types/debug": "~4.1.
|
|
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": "~
|
|
217
|
+
"typescript": "~6.0.2",
|
|
217
218
|
"vite": "~7.3.1",
|
|
218
|
-
"vitest": "~4.
|
|
219
|
+
"vitest": "~4.1.2"
|
|
219
220
|
}
|
|
220
221
|
}
|