rwsdk 0.3.8 → 1.0.0-alpha.0

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.
@@ -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) {
@@ -146,7 +146,6 @@ export const miniflareHMRPlugin = (givenOptions) => [
146
146
  invalidateModule(ctx.server, environment, VIRTUAL_SSR_PREFIX + "/@id/virtual:use-server-lookup.js");
147
147
  invalidateModule(ctx.server, environment, VIRTUAL_SSR_PREFIX + "virtual:use-server-lookup.js");
148
148
  }
149
- // todo(justinvdm, 12 Dec 2024): Skip client references
150
149
  const modules = Array.from(ctx.server.environments[environment].moduleGraph.getModulesByFile(ctx.file) ?? []);
151
150
  const isWorkerUpdate = Boolean(modules);
152
151
  // The worker needs an update, but this is the client environment
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "rwsdk",
3
- "version": "0.3.8",
3
+ "version": "1.0.0-alpha.0",
4
4
  "description": "Build fast, server-driven webapps on Cloudflare with SSR, RSC, and realtime",
5
5
  "type": "module",
6
6
  "bin": {