rwsdk 0.3.6 → 0.3.7-test.20250910150814

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.
@@ -20,6 +20,7 @@ export const fetchTransport = (transportContext) => {
20
20
  }
21
21
  const fetchPromise = fetch(url, {
22
22
  method: "POST",
23
+ redirect: "manual",
23
24
  body: args != null ? await encodeReply(args) : null,
24
25
  });
25
26
  // If there's a response handler, check the response first
@@ -17,6 +17,7 @@ export type RwContext = {
17
17
  databases: Map<string, Kysely<any>>;
18
18
  scriptsToBeLoaded: Set<string>;
19
19
  pageRouteResolved: PromiseWithResolvers<void> | undefined;
20
+ actionResult?: unknown;
20
21
  };
21
22
  export type RouteMiddleware<T extends RequestInfo = RequestInfo> = (requestInfo: T) => MaybePromise<React.JSX.Element | Response | void>;
22
23
  type RouteFunction<T extends RequestInfo = RequestInfo> = (requestInfo: T) => MaybePromise<Response>;
@@ -32,12 +33,13 @@ export type RouteDefinition<T extends RequestInfo = RequestInfo> = {
32
33
  export declare function matchPath<T extends RequestInfo = RequestInfo>(routePath: string, requestPath: string): T["params"] | null;
33
34
  export declare function defineRoutes<T extends RequestInfo = RequestInfo>(routes: Route<T>[]): {
34
35
  routes: Route<T>[];
35
- handle: ({ request, renderPage, getRequestInfo, onError, runWithRequestInfoOverrides, }: {
36
+ handle: ({ request, renderPage, getRequestInfo, onError, runWithRequestInfoOverrides, rscActionHandler, }: {
36
37
  request: Request;
37
38
  renderPage: (requestInfo: T, Page: React.FC, onError: (error: unknown) => void) => Promise<Response>;
38
39
  getRequestInfo: () => T;
39
40
  onError: (error: unknown) => void;
40
41
  runWithRequestInfoOverrides: <Result>(overrides: Partial<T>, fn: () => Promise<Result>) => Promise<Result>;
42
+ rscActionHandler: (request: Request) => Promise<unknown>;
41
43
  }) => Response | Promise<Response>;
42
44
  };
43
45
  export declare function route<T extends RequestInfo = RequestInfo>(path: string, handler: RouteHandler<T>): RouteDefinition<T>;
@@ -64,49 +64,15 @@ export function defineRoutes(routes) {
64
64
  const flattenedRoutes = flattenRoutes(routes);
65
65
  return {
66
66
  routes: flattenedRoutes,
67
- async handle({ request, renderPage, getRequestInfo, onError, runWithRequestInfoOverrides, }) {
67
+ async handle({ request, renderPage, getRequestInfo, onError, runWithRequestInfoOverrides, rscActionHandler, }) {
68
68
  const url = new URL(request.url);
69
69
  let path = url.pathname;
70
70
  // Must end with a trailing slash.
71
71
  if (path !== "/" && !path.endsWith("/")) {
72
72
  path = path + "/";
73
73
  }
74
- // Flow below; helpers are declared after the main flow for readability
75
- // 1) Global middlewares: run any middleware functions encountered (Response short-circuits, element renders)
76
- // 2) Route matching: skip non-matching route definitions; stop at first match
77
- // 3) Route-specific middlewares: run middlewares attached to the matched route
78
- // 4) Final component: render the matched route's final component (layouts apply only here)
79
- for (const route of flattenedRoutes) {
80
- // 1) Global middlewares (encountered before a match)
81
- if (typeof route === "function") {
82
- const result = await route(getRequestInfo());
83
- const handled = await handleMiddlewareResult(result);
84
- if (handled) {
85
- return handled;
86
- }
87
- continue;
88
- }
89
- // 2) Route matching (skip if not matched)
90
- const params = matchPath(route.path, path);
91
- if (!params) {
92
- continue;
93
- }
94
- // Found a match: 3) route middlewares, then 4) final component, then stop
95
- return await runWithRequestInfoOverrides({ params }, async () => {
96
- const { routeMiddlewares, componentHandler } = parseHandlers(route.handler);
97
- // 3) Route-specific middlewares
98
- const mwHandled = await handleRouteMiddlewares(routeMiddlewares);
99
- if (mwHandled) {
100
- return mwHandled;
101
- }
102
- // 4) Final component (always last item)
103
- return await handleRouteComponent(componentHandler, route.layouts || []);
104
- });
105
- }
106
- // No route matched and no middleware handled the request
107
- // todo(peterp, 2025-01-28): Allow the user to define their own "not found" route.
108
- return new Response("Not Found", { status: 404 });
109
74
  // --- Helpers ---
75
+ // (Hoisted for readability)
110
76
  function parseHandlers(handler) {
111
77
  const handlers = Array.isArray(handler) ? handler : [handler];
112
78
  const routeMiddlewares = handlers.slice(0, Math.max(handlers.length - 1, 0));
@@ -130,38 +96,60 @@ export function defineRoutes(routes) {
130
96
  }
131
97
  return undefined;
132
98
  }
133
- // Note: We no longer have separate global pass or match-only pass;
134
- // the outer single pass above handles both behaviors correctly.
135
- async function handleRouteMiddlewares(mws) {
136
- for (const mw of mws) {
137
- const result = await mw(getRequestInfo());
138
- const handled = await handleMiddlewareResult(result);
139
- if (handled)
140
- return handled;
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;
141
108
  }
142
- return undefined;
143
109
  }
144
- async function handleRouteComponent(component, layouts) {
145
- if (isRouteComponent(component)) {
146
- const requestInfo = getRequestInfo();
147
- const WrappedComponent = wrapWithLayouts(wrapHandlerToThrowResponses(component), layouts, requestInfo);
148
- if (!isClientReference(component)) {
149
- // context(justinvdm, 31 Jul 2025): We now know we're dealing with a page route,
150
- // so we create a deferred so that we can signal when we're done determining whether
151
- // we're returning a response or a react element
152
- requestInfo.rw.pageRouteResolved = Promise.withResolvers();
153
- }
154
- return await renderPage(requestInfo, WrappedComponent, onError);
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
+ const params = matchPath(route.path, path);
117
+ if (!params) {
118
+ continue;
155
119
  }
156
- // If the last handler is not a component, handle as middleware result (no layouts)
157
- const tailResult = await component(getRequestInfo());
158
- const handledTail = await handleMiddlewareResult(tailResult);
159
- if (handledTail)
160
- return handledTail;
161
- return new Response("Response not returned from route handler", {
162
- status: 500,
120
+ // Found a match: run route-specific middlewares, then the final component
121
+ return await runWithRequestInfoOverrides({ params }, async () => {
122
+ const { routeMiddlewares, componentHandler } = parseHandlers(route.handler);
123
+ // 3a. Route-specific middlewares
124
+ for (const mw of routeMiddlewares) {
125
+ const result = await mw(getRequestInfo());
126
+ const handled = await handleMiddlewareResult(result);
127
+ if (handled) {
128
+ return handled;
129
+ }
130
+ }
131
+ // 3b. Final component/handler
132
+ if (isRouteComponent(componentHandler)) {
133
+ const requestInfo = getRequestInfo();
134
+ const WrappedComponent = wrapWithLayouts(wrapHandlerToThrowResponses(componentHandler), route.layouts || [], requestInfo);
135
+ if (!isClientReference(componentHandler)) {
136
+ requestInfo.rw.pageRouteResolved = Promise.withResolvers();
137
+ }
138
+ return await renderPage(requestInfo, WrappedComponent, onError);
139
+ }
140
+ // Handle non-component final handler (e.g., returns new Response)
141
+ const tailResult = await componentHandler(getRequestInfo());
142
+ const handledTail = await handleMiddlewareResult(tailResult);
143
+ if (handledTail) {
144
+ return handledTail;
145
+ }
146
+ return new Response("Response not returned from route handler", {
147
+ status: 500,
148
+ });
163
149
  });
164
150
  }
151
+ // No route matched
152
+ return new Response("Not Found", { status: 404 });
165
153
  },
166
154
  };
167
155
  }
@@ -12,4 +12,5 @@ export interface RequestInfo<Params = any, AppContext = DefaultAppContext> {
12
12
  response: ResponseInit & {
13
13
  headers: Headers;
14
14
  };
15
+ isAction: boolean;
15
16
  }
@@ -33,6 +33,7 @@ export const defineApp = (routes) => {
33
33
  const url = new URL(request.url);
34
34
  const isRSCRequest = url.searchParams.has("__rsc") ||
35
35
  request.headers.get("accept")?.includes("text/x-component");
36
+ const isAction = url.searchParams.has("__rsc_action_id");
36
37
  const userHeaders = new Headers();
37
38
  const rw = {
38
39
  Document: DefaultDocument,
@@ -55,6 +56,7 @@ export const defineApp = (routes) => {
55
56
  ctx: {},
56
57
  rw,
57
58
  response: userResponseInit,
59
+ isAction,
58
60
  };
59
61
  const createPageElement = (requestInfo, Page) => {
60
62
  let pageElement;
@@ -77,11 +79,7 @@ export const defineApp = (routes) => {
77
79
  status: 500,
78
80
  });
79
81
  }
80
- let actionResult = undefined;
81
- const isRSCActionHandler = url.searchParams.has("__rsc_action_id");
82
- if (isRSCActionHandler) {
83
- actionResult = await rscActionHandler(request);
84
- }
82
+ const actionResult = requestInfo.rw.actionResult;
85
83
  const pageElement = createPageElement(requestInfo, Page);
86
84
  const { rscPayload: shouldInjectRSCPayload } = rw;
87
85
  let rscPayloadStream = renderToRscStream({
@@ -131,6 +129,7 @@ export const defineApp = (routes) => {
131
129
  getRequestInfo: getRequestInfo,
132
130
  runWithRequestInfoOverrides,
133
131
  onError: reject,
132
+ rscActionHandler,
134
133
  }));
135
134
  }
136
135
  catch (e) {
@@ -32,6 +32,9 @@ export async function transformClientComponents(code, normalizedId, ctx) {
32
32
  // - local: "MyComponent" (the original name)
33
33
  // - exported: "CustomName" (the alias name)
34
34
  // - alias: "CustomName" (to generate MyComponent_CustomName)
35
+ if (!exportInfo.name) {
36
+ return;
37
+ }
35
38
  const hasAlias = exportInfo.alias && exportInfo.originalName;
36
39
  processedExports.push({
37
40
  local: exportInfo.originalName || exportInfo.name, // Use originalName if available
@@ -243,6 +243,63 @@ export { Slot, Slot as Root }
243
243
  const Slot = registerClientReference("/test/file.tsx", "Slot");
244
244
  const Slot_Root = registerClientReference("/test/file.tsx", "Root");
245
245
  export { Slot, Slot_Root as Root };
246
+ `);
247
+ });
248
+ it("handles a large number of named exports from a single module", async () => {
249
+ const code = `"use client";
250
+ import * as React from "react";
251
+ const SidebarContext = React.createContext(null);
252
+ function SidebarProvider() { return jsx("div", {}); }
253
+ function useSidebar() {}
254
+ function Sidebar() { return jsx("div", {}); }
255
+ function SidebarTrigger() { return jsx("div", {}); }
256
+ function SidebarRail() { return jsx("div", {}); }
257
+ function SidebarInset() { return jsx("div", {}); }
258
+ function SidebarInput() { return jsx("div", {}); }
259
+ function SidebarHeader() { return jsx("div", {}); }
260
+ function SidebarFooter() { return jsx("div", {}); }
261
+ function SidebarSeparator() { return jsx("div", {}); }
262
+ function SidebarContent() { return jsx("div", {}); }
263
+ function SidebarGroup() { return jsx("div", {}); }
264
+ function SidebarGroupLabel() { return jsx("div", {}); }
265
+ function SidebarGroupAction() { return jsx("div", {}); }
266
+ function SidebarGroupContent() { return jsx("div", {}); }
267
+ function SidebarMenu() { return jsx("div", {}); }
268
+ function SidebarMenuItem() { return jsx("div", {}); }
269
+ function SidebarMenuButton() { return jsx("div", {}); }
270
+ function SidebarMenuAction() { return jsx("div", {}); }
271
+ function SidebarMenuBadge() { return jsx("div", {}); }
272
+ function SidebarMenuSkeleton() { return jsx("div", {}); }
273
+ function SidebarMenuSub() { return jsx("div", {}); }
274
+ function SidebarMenuSubItem() { return jsx("div", {}); }
275
+ function SidebarMenuSubButton() { return jsx("div", {}); }
276
+ export { Sidebar, SidebarContent, SidebarFooter, SidebarGroup, SidebarGroupAction, SidebarGroupContent, SidebarGroupLabel, SidebarHeader, SidebarInput, SidebarInset, SidebarMenu, SidebarMenuAction, SidebarMenuBadge, SidebarMenuButton, SidebarMenuItem, SidebarMenuSkeleton, SidebarMenuSub, SidebarMenuSubButton, SidebarMenuSubItem, SidebarProvider, SidebarRail, SidebarSeparator, SidebarTrigger, useSidebar, };`;
277
+ expect((await transform(code)) ?? "").toEqual(`import { registerClientReference } from "rwsdk/worker";
278
+ const Sidebar = registerClientReference("/test/file.tsx", "Sidebar");
279
+ const SidebarContent = registerClientReference("/test/file.tsx", "SidebarContent");
280
+ const SidebarFooter = registerClientReference("/test/file.tsx", "SidebarFooter");
281
+ const SidebarGroup = registerClientReference("/test/file.tsx", "SidebarGroup");
282
+ const SidebarGroupAction = registerClientReference("/test/file.tsx", "SidebarGroupAction");
283
+ const SidebarGroupContent = registerClientReference("/test/file.tsx", "SidebarGroupContent");
284
+ const SidebarGroupLabel = registerClientReference("/test/file.tsx", "SidebarGroupLabel");
285
+ const SidebarHeader = registerClientReference("/test/file.tsx", "SidebarHeader");
286
+ const SidebarInput = registerClientReference("/test/file.tsx", "SidebarInput");
287
+ const SidebarInset = registerClientReference("/test/file.tsx", "SidebarInset");
288
+ const SidebarMenu = registerClientReference("/test/file.tsx", "SidebarMenu");
289
+ const SidebarMenuAction = registerClientReference("/test/file.tsx", "SidebarMenuAction");
290
+ const SidebarMenuBadge = registerClientReference("/test/file.tsx", "SidebarMenuBadge");
291
+ const SidebarMenuButton = registerClientReference("/test/file.tsx", "SidebarMenuButton");
292
+ const SidebarMenuItem = registerClientReference("/test/file.tsx", "SidebarMenuItem");
293
+ const SidebarMenuSkeleton = registerClientReference("/test/file.tsx", "SidebarMenuSkeleton");
294
+ const SidebarMenuSub = registerClientReference("/test/file.tsx", "SidebarMenuSub");
295
+ const SidebarMenuSubButton = registerClientReference("/test/file.tsx", "SidebarMenuSubButton");
296
+ const SidebarMenuSubItem = registerClientReference("/test/file.tsx", "SidebarMenuSubItem");
297
+ const SidebarProvider = registerClientReference("/test/file.tsx", "SidebarProvider");
298
+ const SidebarRail = registerClientReference("/test/file.tsx", "SidebarRail");
299
+ const SidebarSeparator = registerClientReference("/test/file.tsx", "SidebarSeparator");
300
+ const SidebarTrigger = registerClientReference("/test/file.tsx", "SidebarTrigger");
301
+ const useSidebar = registerClientReference("/test/file.tsx", "useSidebar");
302
+ export { Sidebar, SidebarContent, SidebarFooter, SidebarGroup, SidebarGroupAction, SidebarGroupContent, SidebarGroupLabel, SidebarHeader, SidebarInput, SidebarInset, SidebarMenu, SidebarMenuAction, SidebarMenuBadge, SidebarMenuButton, SidebarMenuItem, SidebarMenuSkeleton, SidebarMenuSub, SidebarMenuSubButton, SidebarMenuSubItem, SidebarProvider, SidebarRail, SidebarSeparator, SidebarTrigger, useSidebar };
246
303
  `);
247
304
  });
248
305
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "rwsdk",
3
- "version": "0.3.6",
3
+ "version": "0.3.7-test.20250910150814",
4
4
  "description": "Build fast, server-driven webapps on Cloudflare with SSR, RSC, and realtime",
5
5
  "type": "module",
6
6
  "bin": {