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
|
-
//
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
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
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
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
|
-
//
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
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
|
}
|
package/dist/runtime/worker.js
CHANGED
|
@@ -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
|
-
|
|
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
|