rwsdk 1.0.0-beta.4 → 1.0.0-beta.40
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.
- package/dist/lib/constants.d.mts +1 -0
- package/dist/lib/constants.mjs +7 -4
- package/dist/lib/e2e/browser.mjs +6 -2
- package/dist/lib/e2e/constants.d.mts +4 -0
- package/dist/lib/e2e/constants.mjs +49 -12
- package/dist/lib/e2e/dev.mjs +37 -49
- package/dist/lib/e2e/environment.d.mts +2 -0
- package/dist/lib/e2e/environment.mjs +201 -64
- package/dist/lib/e2e/index.d.mts +1 -0
- package/dist/lib/e2e/index.mjs +1 -0
- package/dist/lib/e2e/poll.d.mts +1 -1
- package/dist/lib/e2e/release.d.mts +1 -0
- package/dist/lib/e2e/release.mjs +16 -32
- package/dist/lib/e2e/tarball.mjs +2 -34
- package/dist/lib/e2e/testHarness.d.mts +34 -3
- package/dist/lib/e2e/testHarness.mjs +219 -90
- package/dist/lib/e2e/utils.d.mts +1 -0
- package/dist/lib/e2e/utils.mjs +15 -0
- package/dist/runtime/client/client.d.ts +35 -0
- package/dist/runtime/client/client.js +35 -0
- package/dist/runtime/client/navigation.d.ts +49 -0
- package/dist/runtime/client/navigation.js +80 -31
- package/dist/runtime/entries/clientSSR.d.ts +1 -0
- package/dist/runtime/entries/clientSSR.js +3 -0
- package/dist/runtime/entries/no-react-server-ssr-bridge.d.ts +0 -0
- package/dist/runtime/entries/no-react-server-ssr-bridge.js +2 -0
- package/dist/runtime/entries/router.d.ts +1 -0
- package/dist/runtime/entries/routerClient.d.ts +1 -0
- package/dist/runtime/entries/routerClient.js +1 -0
- package/dist/runtime/entries/worker.d.ts +2 -0
- package/dist/runtime/entries/worker.js +2 -0
- package/dist/runtime/imports/__mocks__/use-client-lookup.d.ts +6 -0
- package/dist/runtime/imports/__mocks__/use-client-lookup.js +6 -0
- package/dist/runtime/lib/db/SqliteDurableObject.d.ts +2 -2
- package/dist/runtime/lib/db/SqliteDurableObject.js +2 -2
- package/dist/runtime/lib/db/createDb.d.ts +1 -2
- package/dist/runtime/lib/db/createDb.js +4 -0
- package/dist/runtime/lib/db/typeInference/builders/alterTable.d.ts +13 -3
- package/dist/runtime/lib/db/typeInference/builders/columnDefinition.d.ts +35 -21
- package/dist/runtime/lib/db/typeInference/builders/createTable.d.ts +9 -2
- package/dist/runtime/lib/db/typeInference/database.d.ts +16 -2
- package/dist/runtime/lib/db/typeInference/typetests/alterTable.typetest.js +80 -5
- package/dist/runtime/lib/db/typeInference/typetests/createTable.typetest.js +104 -2
- package/dist/runtime/lib/db/typeInference/typetests/testUtils.d.ts +1 -0
- package/dist/runtime/lib/db/typeInference/utils.d.ts +59 -9
- package/dist/runtime/lib/links.d.ts +21 -7
- package/dist/runtime/lib/links.js +82 -24
- package/dist/runtime/lib/links.test.js +20 -0
- package/dist/runtime/lib/manifest.d.ts +1 -1
- package/dist/runtime/lib/manifest.js +7 -4
- package/dist/runtime/lib/realtime/client.js +8 -2
- package/dist/runtime/lib/realtime/worker.d.ts +1 -1
- package/dist/runtime/lib/router.d.ts +153 -36
- package/dist/runtime/lib/router.js +169 -20
- package/dist/runtime/lib/router.test.js +241 -0
- package/dist/runtime/lib/stitchDocumentAndAppStreams.d.ts +66 -0
- package/dist/runtime/lib/stitchDocumentAndAppStreams.js +302 -35
- package/dist/runtime/lib/stitchDocumentAndAppStreams.test.d.ts +1 -0
- package/dist/runtime/lib/stitchDocumentAndAppStreams.test.js +418 -0
- package/dist/runtime/lib/{rwContext.d.ts → types.d.ts} +1 -0
- package/dist/runtime/lib/types.js +1 -0
- package/dist/runtime/render/renderDocumentHtmlStream.d.ts +1 -1
- package/dist/runtime/render/renderToStream.d.ts +4 -2
- package/dist/runtime/render/renderToStream.js +53 -24
- package/dist/runtime/render/renderToString.d.ts +3 -6
- package/dist/runtime/requestInfo/types.d.ts +4 -1
- package/dist/runtime/requestInfo/utils.d.ts +9 -0
- package/dist/runtime/requestInfo/utils.js +44 -0
- package/dist/runtime/requestInfo/worker.d.ts +0 -1
- package/dist/runtime/requestInfo/worker.js +3 -10
- package/dist/runtime/script.d.ts +1 -3
- package/dist/runtime/script.js +1 -10
- package/dist/runtime/state.d.ts +3 -0
- package/dist/runtime/state.js +13 -0
- package/dist/runtime/worker.d.ts +3 -1
- package/dist/runtime/worker.js +32 -0
- package/dist/scripts/debug-sync.mjs +18 -20
- package/dist/scripts/worker-run.d.mts +1 -1
- package/dist/scripts/worker-run.mjs +59 -113
- package/dist/use-synced-state/SyncedStateServer.d.mts +21 -0
- package/dist/use-synced-state/SyncedStateServer.mjs +128 -0
- package/dist/use-synced-state/__tests__/SyncStateServer.test.d.mts +1 -0
- package/dist/use-synced-state/__tests__/SyncStateServer.test.mjs +109 -0
- package/dist/use-synced-state/__tests__/useSyncState.test.d.ts +1 -0
- package/dist/use-synced-state/__tests__/useSyncState.test.js +115 -0
- package/dist/use-synced-state/__tests__/useSyncedState.test.d.ts +1 -0
- package/dist/use-synced-state/__tests__/useSyncedState.test.js +115 -0
- package/dist/use-synced-state/__tests__/worker.test.d.mts +1 -0
- package/dist/use-synced-state/__tests__/worker.test.mjs +69 -0
- package/dist/use-synced-state/client-core.d.ts +26 -0
- package/dist/use-synced-state/client-core.js +39 -0
- package/dist/use-synced-state/client.d.ts +3 -0
- package/dist/use-synced-state/client.js +4 -0
- package/dist/use-synced-state/constants.d.mts +1 -0
- package/dist/use-synced-state/constants.mjs +1 -0
- package/dist/use-synced-state/useSyncedState.d.ts +20 -0
- package/dist/use-synced-state/useSyncedState.js +58 -0
- package/dist/use-synced-state/worker.d.mts +13 -0
- package/dist/use-synced-state/worker.mjs +69 -0
- package/dist/vite/buildApp.mjs +34 -2
- package/dist/vite/cloudflarePreInitPlugin.d.mts +11 -0
- package/dist/vite/cloudflarePreInitPlugin.mjs +40 -0
- package/dist/vite/configPlugin.mjs +9 -14
- package/dist/vite/constants.d.mts +1 -0
- package/dist/vite/constants.mjs +1 -0
- package/dist/vite/createDirectiveLookupPlugin.mjs +10 -7
- package/dist/vite/devServerTimingPlugin.mjs +4 -0
- package/dist/vite/diagnosticAssetGraphPlugin.d.mts +4 -0
- package/dist/vite/diagnosticAssetGraphPlugin.mjs +41 -0
- package/dist/vite/directiveModulesDevPlugin.mjs +9 -1
- package/dist/vite/directivesPlugin.mjs +4 -4
- package/dist/vite/envResolvers.d.mts +11 -0
- package/dist/vite/envResolvers.mjs +20 -0
- package/dist/vite/getViteEsbuild.mjs +2 -1
- package/dist/vite/hmrStabilityPlugin.d.mts +2 -0
- package/dist/vite/hmrStabilityPlugin.mjs +73 -0
- package/dist/vite/injectVitePreamblePlugin.mjs +0 -4
- package/dist/vite/knownDepsResolverPlugin.d.mts +0 -6
- package/dist/vite/knownDepsResolverPlugin.mjs +25 -17
- package/dist/vite/linkerPlugin.d.mts +2 -1
- package/dist/vite/linkerPlugin.mjs +11 -3
- package/dist/vite/linkerPlugin.test.mjs +15 -0
- package/dist/vite/miniflareHMRPlugin.mjs +6 -38
- package/dist/vite/moveStaticAssetsPlugin.mjs +35 -4
- package/dist/vite/redwoodPlugin.mjs +8 -10
- package/dist/vite/runDirectivesScan.mjs +72 -18
- package/dist/vite/ssrBridgePlugin.mjs +132 -40
- package/dist/vite/ssrBridgeWrapPlugin.d.mts +2 -0
- package/dist/vite/ssrBridgeWrapPlugin.mjs +85 -0
- package/dist/vite/staleDepRetryPlugin.d.mts +2 -0
- package/dist/vite/staleDepRetryPlugin.mjs +74 -0
- package/dist/vite/statePlugin.d.mts +4 -0
- package/dist/vite/statePlugin.mjs +62 -0
- package/dist/vite/transformJsxScriptTagsPlugin.mjs +0 -5
- package/dist/vite/virtualPlugin.mjs +6 -7
- package/package.json +27 -10
- package/dist/vite/manifestPlugin.d.mts +0 -4
- package/dist/vite/manifestPlugin.mjs +0 -63
- /package/dist/runtime/lib/{rwContext.js → links.test.d.ts} +0 -0
|
@@ -1,37 +1,48 @@
|
|
|
1
|
-
import type { Kysely } from "kysely";
|
|
2
1
|
import React from "react";
|
|
3
2
|
import { RequestInfo } from "../requestInfo/types";
|
|
4
|
-
|
|
5
|
-
children: React.ReactNode;
|
|
6
|
-
};
|
|
7
|
-
export type LayoutProps<T extends RequestInfo = RequestInfo> = {
|
|
8
|
-
children?: React.ReactNode;
|
|
9
|
-
requestInfo?: T;
|
|
10
|
-
};
|
|
11
|
-
export type RwContext = {
|
|
12
|
-
nonce: string;
|
|
13
|
-
Document: React.FC<DocumentProps<any>>;
|
|
14
|
-
rscPayload: boolean;
|
|
15
|
-
ssr: boolean;
|
|
16
|
-
layouts?: React.FC<LayoutProps<any>>[];
|
|
17
|
-
databases: Map<string, Kysely<any>>;
|
|
18
|
-
scriptsToBeLoaded: Set<string>;
|
|
19
|
-
pageRouteResolved: PromiseWithResolvers<void> | undefined;
|
|
20
|
-
actionResult?: unknown;
|
|
21
|
-
};
|
|
22
|
-
export type RouteMiddleware<T extends RequestInfo = RequestInfo> = (requestInfo: T) => MaybePromise<React.JSX.Element | Response | void>;
|
|
23
|
-
type RouteFunction<T extends RequestInfo = RequestInfo> = (requestInfo: T) => MaybePromise<Response>;
|
|
3
|
+
import type { DocumentProps, LayoutProps } from "./types.js";
|
|
24
4
|
type MaybePromise<T> = T | Promise<T>;
|
|
25
|
-
type
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
export type
|
|
5
|
+
type BivariantRouteHandler<T extends RequestInfo, R> = {
|
|
6
|
+
bivarianceHack(requestInfo: T): R;
|
|
7
|
+
}["bivarianceHack"];
|
|
8
|
+
export type RouteMiddleware<T extends RequestInfo = RequestInfo> = BivariantRouteHandler<T, MaybePromise<React.JSX.Element | Response | void>>;
|
|
9
|
+
type RouteFunction<T extends RequestInfo = RequestInfo> = BivariantRouteHandler<T, MaybePromise<Response>>;
|
|
10
|
+
type RouteComponent<T extends RequestInfo = RequestInfo> = BivariantRouteHandler<T, MaybePromise<React.JSX.Element | Response | void>>;
|
|
11
|
+
type RouteHandler<T extends RequestInfo = RequestInfo> = RouteFunction<T> | RouteComponent<T> | readonly [...RouteMiddleware<T>[], RouteFunction<T> | RouteComponent<T>];
|
|
12
|
+
declare const METHOD_VERBS: readonly ["delete", "get", "head", "patch", "post", "put"];
|
|
13
|
+
export type MethodVerb = (typeof METHOD_VERBS)[number];
|
|
14
|
+
export type MethodHandlers<T extends RequestInfo = RequestInfo> = {
|
|
15
|
+
[K in MethodVerb]?: RouteHandler<T>;
|
|
16
|
+
} & {
|
|
17
|
+
config?: {
|
|
18
|
+
disable405?: true;
|
|
19
|
+
disableOptions?: true;
|
|
20
|
+
};
|
|
21
|
+
custom?: {
|
|
22
|
+
[method: string]: RouteHandler<T>;
|
|
23
|
+
};
|
|
24
|
+
};
|
|
25
|
+
export type Route<T extends RequestInfo = RequestInfo> = RouteMiddleware<T> | RouteDefinition<string, T> | readonly Route<T>[];
|
|
26
|
+
type NormalizedRouteDefinition<T extends RequestInfo = RequestInfo> = {
|
|
29
27
|
path: string;
|
|
30
|
-
handler: RouteHandler<T>;
|
|
28
|
+
handler: RouteHandler<T> | MethodHandlers<T>;
|
|
31
29
|
layouts?: React.FC<LayoutProps<T>>[];
|
|
32
30
|
};
|
|
31
|
+
export type RouteDefinition<Path extends string = string, T extends RequestInfo = RequestInfo> = NormalizedRouteDefinition<T> & {
|
|
32
|
+
readonly __rwPath?: Path;
|
|
33
|
+
};
|
|
34
|
+
type TrimTrailingSlash<S extends string> = S extends `${infer Head}/` ? TrimTrailingSlash<Head> : S;
|
|
35
|
+
type TrimLeadingSlash<S extends string> = S extends `/${infer Rest}` ? TrimLeadingSlash<Rest> : S;
|
|
36
|
+
type NormalizePrefix<Prefix extends string> = TrimTrailingSlash<TrimLeadingSlash<Prefix>> extends "" ? "" : `/${TrimTrailingSlash<TrimLeadingSlash<Prefix>>}`;
|
|
37
|
+
type NormalizePath<Path extends string> = TrimTrailingSlash<Path> extends "/" ? "/" : `/${TrimTrailingSlash<TrimLeadingSlash<Path>>}`;
|
|
38
|
+
type JoinPaths<Prefix extends string, Path extends string> = NormalizePrefix<Prefix> extends "" ? NormalizePath<Path> : Path extends "/" ? NormalizePrefix<Prefix> : `${NormalizePrefix<Prefix>}${NormalizePath<Path>}`;
|
|
39
|
+
type PrefixedRouteValue<Prefix extends string, Value> = Value extends RouteDefinition<infer Path, infer Req> ? RouteDefinition<JoinPaths<Prefix, Path>, Req> : Value extends readonly Route<any>[] ? PrefixedRouteArray<Prefix, Value> : Value;
|
|
40
|
+
type PrefixedRouteArray<Prefix extends string, Routes extends readonly Route<any>[]> = Routes extends readonly [] ? [] : Routes extends readonly [infer Head, ...infer Tail] ? readonly [
|
|
41
|
+
PrefixedRouteValue<Prefix, Head>,
|
|
42
|
+
...PrefixedRouteArray<Prefix, Tail extends readonly Route<any>[] ? Tail : []>
|
|
43
|
+
] : ReadonlyArray<PrefixedRouteValue<Prefix, Routes[number]>>;
|
|
33
44
|
export declare function matchPath<T extends RequestInfo = RequestInfo>(routePath: string, requestPath: string): T["params"] | null;
|
|
34
|
-
export declare function defineRoutes<T extends RequestInfo = RequestInfo>(routes: Route<T>[]): {
|
|
45
|
+
export declare function defineRoutes<T extends RequestInfo = RequestInfo>(routes: readonly Route<T>[]): {
|
|
35
46
|
routes: Route<T>[];
|
|
36
47
|
handle: ({ request, renderPage, getRequestInfo, onError, runWithRequestInfoOverrides, rscActionHandler, }: {
|
|
37
48
|
request: Request;
|
|
@@ -42,20 +53,126 @@ export declare function defineRoutes<T extends RequestInfo = RequestInfo>(routes
|
|
|
42
53
|
rscActionHandler: (request: Request) => Promise<unknown>;
|
|
43
54
|
}) => Response | Promise<Response>;
|
|
44
55
|
};
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
56
|
+
/**
|
|
57
|
+
* Defines a route handler for a path pattern.
|
|
58
|
+
*
|
|
59
|
+
* Supports three types of path patterns:
|
|
60
|
+
* - Static: /about, /contact
|
|
61
|
+
* - Parameters: /users/:id, /posts/:postId/edit
|
|
62
|
+
* - Wildcards: /files/\*, /api/\*\/download
|
|
63
|
+
*
|
|
64
|
+
* @example
|
|
65
|
+
* // Static route
|
|
66
|
+
* route("/about", () => <AboutPage />)
|
|
67
|
+
*
|
|
68
|
+
* @example
|
|
69
|
+
* // Route with parameters
|
|
70
|
+
* route("/users/:id", ({ params }) => {
|
|
71
|
+
* return <UserProfile userId={params.id} />
|
|
72
|
+
* })
|
|
73
|
+
*
|
|
74
|
+
* @example
|
|
75
|
+
* // Route with wildcards
|
|
76
|
+
* route("/files/*", ({ params }) => {
|
|
77
|
+
* const filePath = params.$0
|
|
78
|
+
* return <FileViewer path={filePath} />
|
|
79
|
+
* })
|
|
80
|
+
*
|
|
81
|
+
* @example
|
|
82
|
+
* // Method-based routing
|
|
83
|
+
* route("/api/users", {
|
|
84
|
+
* get: () => Response.json(users),
|
|
85
|
+
* post: ({ request }) => Response.json({ status: "created" }, { status: 201 }),
|
|
86
|
+
* delete: () => new Response(null, { status: 204 }),
|
|
87
|
+
* })
|
|
88
|
+
*
|
|
89
|
+
* @example
|
|
90
|
+
* // Route with middleware array
|
|
91
|
+
* route("/admin", [isAuthenticated, isAdmin, () => <AdminDashboard />])
|
|
92
|
+
*/
|
|
93
|
+
export declare function route<Path extends string, T extends RequestInfo = RequestInfo>(path: Path, handler: RouteHandler<T> | MethodHandlers<T>): RouteDefinition<NormalizePath<Path>, T>;
|
|
94
|
+
/**
|
|
95
|
+
* Defines a route handler for the root path "/".
|
|
96
|
+
*
|
|
97
|
+
* @example
|
|
98
|
+
* // Homepage
|
|
99
|
+
* index(() => <HomePage />)
|
|
100
|
+
*
|
|
101
|
+
* @example
|
|
102
|
+
* // With middleware
|
|
103
|
+
* index([logRequest, () => <HomePage />])
|
|
104
|
+
*/
|
|
105
|
+
export declare function index<T extends RequestInfo = RequestInfo>(handler: RouteHandler<T>): RouteDefinition<"/", T>;
|
|
106
|
+
/**
|
|
107
|
+
* Prefixes a group of routes with a path.
|
|
108
|
+
*
|
|
109
|
+
* @example
|
|
110
|
+
* // Organize blog routes under /blog
|
|
111
|
+
* const blogRoutes = [
|
|
112
|
+
* route("/", () => <BlogIndex />),
|
|
113
|
+
* route("/post/:id", ({ params }) => <BlogPost id={params.id} />),
|
|
114
|
+
* route("/admin", [isAuthenticated, () => <BlogAdmin />]),
|
|
115
|
+
* ]
|
|
116
|
+
*
|
|
117
|
+
* // In worker.tsx
|
|
118
|
+
* defineApp([
|
|
119
|
+
* render(Document, [
|
|
120
|
+
* route("/", () => <HomePage />),
|
|
121
|
+
* prefix("/blog", blogRoutes),
|
|
122
|
+
* ]),
|
|
123
|
+
* ])
|
|
124
|
+
*/
|
|
125
|
+
export declare function prefix<Prefix extends string, T extends RequestInfo = RequestInfo, Routes extends readonly Route<T>[] = readonly Route<T>[]>(prefixPath: Prefix, routes: Routes): PrefixedRouteArray<Prefix, Routes>;
|
|
48
126
|
export declare const wrapHandlerToThrowResponses: <T extends RequestInfo = RequestInfo>(handler: RouteFunction<T> | RouteComponent<T>) => RouteHandler<T>;
|
|
49
|
-
export declare function layout<T extends RequestInfo = RequestInfo>(LayoutComponent: React.FC<LayoutProps<T>>, routes: Route<T>[]): Route<T>[];
|
|
50
|
-
export declare function render<T extends RequestInfo = RequestInfo>(Document: React.FC<DocumentProps<T>>, routes: Route<T>[],
|
|
51
127
|
/**
|
|
52
|
-
*
|
|
128
|
+
* Wraps routes with a layout component.
|
|
129
|
+
*
|
|
130
|
+
* @example
|
|
131
|
+
* // Define a layout component
|
|
132
|
+
* function BlogLayout({ children }: { children: React.ReactNode }) {
|
|
133
|
+
* return (
|
|
134
|
+
* <div>
|
|
135
|
+
* <nav>Blog Navigation</nav>
|
|
136
|
+
* <main>{children}</main>
|
|
137
|
+
* </div>
|
|
138
|
+
* )
|
|
139
|
+
* }
|
|
140
|
+
*
|
|
141
|
+
* // Apply layout to routes
|
|
142
|
+
* const blogRoutes = layout(BlogLayout, [
|
|
143
|
+
* route("/", () => <BlogIndex />),
|
|
144
|
+
* route("/post/:id", ({ params }) => <BlogPost id={params.id} />),
|
|
145
|
+
* ])
|
|
146
|
+
*/
|
|
147
|
+
export declare function layout<T extends RequestInfo = RequestInfo, Routes extends readonly Route<T>[] = readonly Route<T>[]>(LayoutComponent: React.FC<LayoutProps<T>>, routes: Routes): Routes;
|
|
148
|
+
/**
|
|
149
|
+
* Wraps routes with a Document component and configures rendering options.
|
|
150
|
+
*
|
|
53
151
|
* @param options.rscPayload - Toggle the RSC payload that's appended to the Document. Disabling this will mean that interactivity no longer works.
|
|
54
|
-
* @param options.ssr - Disable sever side rendering for all these routes. This only allow client side rendering
|
|
152
|
+
* @param options.ssr - Disable sever side rendering for all these routes. This only allow client side rendering, which requires `rscPayload` to be enabled.
|
|
153
|
+
*
|
|
154
|
+
* @example
|
|
155
|
+
* // Basic usage
|
|
156
|
+
* defineApp([
|
|
157
|
+
* render(Document, [
|
|
158
|
+
* route("/", () => <HomePage />),
|
|
159
|
+
* route("/about", () => <AboutPage />),
|
|
160
|
+
* ]),
|
|
161
|
+
* ])
|
|
162
|
+
*
|
|
163
|
+
* @example
|
|
164
|
+
* // With custom rendering options
|
|
165
|
+
* render(Document, [
|
|
166
|
+
* route("/", () => <HomePage />),
|
|
167
|
+
* ], {
|
|
168
|
+
* rscPayload: true,
|
|
169
|
+
* ssr: true,
|
|
170
|
+
* })
|
|
55
171
|
*/
|
|
56
|
-
|
|
172
|
+
type RenderedRoutes<T extends RequestInfo, Routes extends readonly Route<T>[]> = readonly [RouteMiddleware<T>, ...Routes];
|
|
173
|
+
export declare function render<T extends RequestInfo = RequestInfo, Routes extends readonly Route<T>[] = readonly Route<T>[]>(Document: React.FC<DocumentProps<T>>, routes: Routes, options?: {
|
|
57
174
|
rscPayload?: boolean;
|
|
58
175
|
ssr?: boolean;
|
|
59
|
-
}):
|
|
176
|
+
}): RenderedRoutes<T, Routes>;
|
|
60
177
|
export declare const isClientReference: (value: any) => boolean;
|
|
61
178
|
export {};
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import React from "react";
|
|
2
2
|
import { isValidElementType } from "react-is";
|
|
3
|
+
const METHOD_VERBS = ["delete", "get", "head", "patch", "post", "put"];
|
|
3
4
|
export function matchPath(routePath, requestPath) {
|
|
4
5
|
// Check for invalid pattern: multiple colons in a segment (e.g., /:param1:param2/)
|
|
5
6
|
if (routePath.includes(":")) {
|
|
@@ -60,6 +61,38 @@ function flattenRoutes(routes) {
|
|
|
60
61
|
return [...acc, route];
|
|
61
62
|
}, []);
|
|
62
63
|
}
|
|
64
|
+
function isMethodHandlers(handler) {
|
|
65
|
+
return (typeof handler === "object" && handler !== null && !Array.isArray(handler));
|
|
66
|
+
}
|
|
67
|
+
function handleOptionsRequest(methodHandlers) {
|
|
68
|
+
const methods = new Set([
|
|
69
|
+
...(methodHandlers.config?.disableOptions ? [] : ["OPTIONS"]),
|
|
70
|
+
...METHOD_VERBS.filter((verb) => methodHandlers[verb]).map((verb) => verb.toUpperCase()),
|
|
71
|
+
...Object.keys(methodHandlers.custom ?? {}).map((method) => method.toUpperCase()),
|
|
72
|
+
]);
|
|
73
|
+
return new Response(null, {
|
|
74
|
+
status: 204,
|
|
75
|
+
headers: {
|
|
76
|
+
Allow: Array.from(methods).sort().join(", "),
|
|
77
|
+
},
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
function handleMethodNotAllowed(methodHandlers) {
|
|
81
|
+
const optionsResponse = handleOptionsRequest(methodHandlers);
|
|
82
|
+
return new Response("Method Not Allowed", {
|
|
83
|
+
status: 405,
|
|
84
|
+
headers: optionsResponse.headers,
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
function getHandlerForMethod(methodHandlers, method) {
|
|
88
|
+
const lowerMethod = method.toLowerCase();
|
|
89
|
+
// Check standard method verbs
|
|
90
|
+
if (METHOD_VERBS.includes(lowerMethod)) {
|
|
91
|
+
return methodHandlers[lowerMethod];
|
|
92
|
+
}
|
|
93
|
+
// Check custom methods (already normalized to lowercase)
|
|
94
|
+
return methodHandlers.custom?.[lowerMethod];
|
|
95
|
+
}
|
|
63
96
|
export function defineRoutes(routes) {
|
|
64
97
|
const flattenedRoutes = flattenRoutes(routes);
|
|
65
98
|
return {
|
|
@@ -125,9 +158,32 @@ export function defineRoutes(routes) {
|
|
|
125
158
|
if (!params) {
|
|
126
159
|
continue; // Not a match, keep going.
|
|
127
160
|
}
|
|
161
|
+
// Resolve handler if method-based routing
|
|
162
|
+
let handler;
|
|
163
|
+
if (isMethodHandlers(route.handler)) {
|
|
164
|
+
const requestMethod = request.method;
|
|
165
|
+
// Handle OPTIONS request
|
|
166
|
+
if (requestMethod === "OPTIONS" &&
|
|
167
|
+
!route.handler.config?.disableOptions) {
|
|
168
|
+
return handleOptionsRequest(route.handler);
|
|
169
|
+
}
|
|
170
|
+
// Try to find handler for the request method
|
|
171
|
+
handler = getHandlerForMethod(route.handler, requestMethod);
|
|
172
|
+
if (!handler) {
|
|
173
|
+
// Method not supported for this route
|
|
174
|
+
if (!route.handler.config?.disable405) {
|
|
175
|
+
return handleMethodNotAllowed(route.handler);
|
|
176
|
+
}
|
|
177
|
+
// If 405 is disabled, continue to next route
|
|
178
|
+
continue;
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
else {
|
|
182
|
+
handler = route.handler;
|
|
183
|
+
}
|
|
128
184
|
// Found a match: run route-specific middlewares, then the final component, then stop.
|
|
129
185
|
return await runWithRequestInfoOverrides({ params }, async () => {
|
|
130
|
-
const { routeMiddlewares, componentHandler } = parseHandlers(
|
|
186
|
+
const { routeMiddlewares, componentHandler } = parseHandlers(handler);
|
|
131
187
|
// Route-specific middlewares
|
|
132
188
|
for (const mw of routeMiddlewares) {
|
|
133
189
|
const result = await mw(getRequestInfo());
|
|
@@ -165,20 +221,99 @@ export function defineRoutes(routes) {
|
|
|
165
221
|
},
|
|
166
222
|
};
|
|
167
223
|
}
|
|
224
|
+
/**
|
|
225
|
+
* Defines a route handler for a path pattern.
|
|
226
|
+
*
|
|
227
|
+
* Supports three types of path patterns:
|
|
228
|
+
* - Static: /about, /contact
|
|
229
|
+
* - Parameters: /users/:id, /posts/:postId/edit
|
|
230
|
+
* - Wildcards: /files/\*, /api/\*\/download
|
|
231
|
+
*
|
|
232
|
+
* @example
|
|
233
|
+
* // Static route
|
|
234
|
+
* route("/about", () => <AboutPage />)
|
|
235
|
+
*
|
|
236
|
+
* @example
|
|
237
|
+
* // Route with parameters
|
|
238
|
+
* route("/users/:id", ({ params }) => {
|
|
239
|
+
* return <UserProfile userId={params.id} />
|
|
240
|
+
* })
|
|
241
|
+
*
|
|
242
|
+
* @example
|
|
243
|
+
* // Route with wildcards
|
|
244
|
+
* route("/files/*", ({ params }) => {
|
|
245
|
+
* const filePath = params.$0
|
|
246
|
+
* return <FileViewer path={filePath} />
|
|
247
|
+
* })
|
|
248
|
+
*
|
|
249
|
+
* @example
|
|
250
|
+
* // Method-based routing
|
|
251
|
+
* route("/api/users", {
|
|
252
|
+
* get: () => Response.json(users),
|
|
253
|
+
* post: ({ request }) => Response.json({ status: "created" }, { status: 201 }),
|
|
254
|
+
* delete: () => new Response(null, { status: 204 }),
|
|
255
|
+
* })
|
|
256
|
+
*
|
|
257
|
+
* @example
|
|
258
|
+
* // Route with middleware array
|
|
259
|
+
* route("/admin", [isAuthenticated, isAdmin, () => <AdminDashboard />])
|
|
260
|
+
*/
|
|
168
261
|
export function route(path, handler) {
|
|
169
|
-
|
|
170
|
-
|
|
262
|
+
let normalizedPath = path;
|
|
263
|
+
if (!normalizedPath.endsWith("/")) {
|
|
264
|
+
normalizedPath = normalizedPath + "/";
|
|
265
|
+
}
|
|
266
|
+
// Normalize custom method keys to lowercase
|
|
267
|
+
if (isMethodHandlers(handler) && handler.custom) {
|
|
268
|
+
handler = {
|
|
269
|
+
...handler,
|
|
270
|
+
custom: Object.fromEntries(Object.entries(handler.custom).map(([method, methodHandler]) => [
|
|
271
|
+
method.toLowerCase(),
|
|
272
|
+
methodHandler,
|
|
273
|
+
])),
|
|
274
|
+
};
|
|
171
275
|
}
|
|
172
276
|
return {
|
|
173
|
-
path,
|
|
277
|
+
path: normalizedPath,
|
|
174
278
|
handler,
|
|
279
|
+
__rwPath: normalizedPath,
|
|
175
280
|
};
|
|
176
281
|
}
|
|
282
|
+
/**
|
|
283
|
+
* Defines a route handler for the root path "/".
|
|
284
|
+
*
|
|
285
|
+
* @example
|
|
286
|
+
* // Homepage
|
|
287
|
+
* index(() => <HomePage />)
|
|
288
|
+
*
|
|
289
|
+
* @example
|
|
290
|
+
* // With middleware
|
|
291
|
+
* index([logRequest, () => <HomePage />])
|
|
292
|
+
*/
|
|
177
293
|
export function index(handler) {
|
|
178
294
|
return route("/", handler);
|
|
179
295
|
}
|
|
296
|
+
/**
|
|
297
|
+
* Prefixes a group of routes with a path.
|
|
298
|
+
*
|
|
299
|
+
* @example
|
|
300
|
+
* // Organize blog routes under /blog
|
|
301
|
+
* const blogRoutes = [
|
|
302
|
+
* route("/", () => <BlogIndex />),
|
|
303
|
+
* route("/post/:id", ({ params }) => <BlogPost id={params.id} />),
|
|
304
|
+
* route("/admin", [isAuthenticated, () => <BlogAdmin />]),
|
|
305
|
+
* ]
|
|
306
|
+
*
|
|
307
|
+
* // In worker.tsx
|
|
308
|
+
* defineApp([
|
|
309
|
+
* render(Document, [
|
|
310
|
+
* route("/", () => <HomePage />),
|
|
311
|
+
* prefix("/blog", blogRoutes),
|
|
312
|
+
* ]),
|
|
313
|
+
* ])
|
|
314
|
+
*/
|
|
180
315
|
export function prefix(prefixPath, routes) {
|
|
181
|
-
|
|
316
|
+
const prefixed = routes.map((r) => {
|
|
182
317
|
if (typeof r === "function") {
|
|
183
318
|
const middleware = (requestInfo) => {
|
|
184
319
|
const url = new URL(requestInfo.request.url);
|
|
@@ -193,13 +328,14 @@ export function prefix(prefixPath, routes) {
|
|
|
193
328
|
// Recursively process nested route arrays
|
|
194
329
|
return prefix(prefixPath, r);
|
|
195
330
|
}
|
|
196
|
-
|
|
331
|
+
const routeDef = r;
|
|
197
332
|
return {
|
|
198
|
-
path: prefixPath +
|
|
199
|
-
handler:
|
|
200
|
-
...(
|
|
333
|
+
path: prefixPath + routeDef.path,
|
|
334
|
+
handler: routeDef.handler,
|
|
335
|
+
...(routeDef.layouts && { layouts: routeDef.layouts }),
|
|
201
336
|
};
|
|
202
337
|
});
|
|
338
|
+
return prefixed;
|
|
203
339
|
}
|
|
204
340
|
function wrapWithLayouts(Component, layouts = [], requestInfo) {
|
|
205
341
|
if (layouts.length === 0) {
|
|
@@ -242,8 +378,27 @@ export const wrapHandlerToThrowResponses = (handler) => {
|
|
|
242
378
|
ComponentWrappedToThrowResponses.__rwsdk_route_component = true;
|
|
243
379
|
return ComponentWrappedToThrowResponses;
|
|
244
380
|
};
|
|
381
|
+
/**
|
|
382
|
+
* Wraps routes with a layout component.
|
|
383
|
+
*
|
|
384
|
+
* @example
|
|
385
|
+
* // Define a layout component
|
|
386
|
+
* function BlogLayout({ children }: { children: React.ReactNode }) {
|
|
387
|
+
* return (
|
|
388
|
+
* <div>
|
|
389
|
+
* <nav>Blog Navigation</nav>
|
|
390
|
+
* <main>{children}</main>
|
|
391
|
+
* </div>
|
|
392
|
+
* )
|
|
393
|
+
* }
|
|
394
|
+
*
|
|
395
|
+
* // Apply layout to routes
|
|
396
|
+
* const blogRoutes = layout(BlogLayout, [
|
|
397
|
+
* route("/", () => <BlogIndex />),
|
|
398
|
+
* route("/post/:id", ({ params }) => <BlogPost id={params.id} />),
|
|
399
|
+
* ])
|
|
400
|
+
*/
|
|
245
401
|
export function layout(LayoutComponent, routes) {
|
|
246
|
-
// Attach layouts directly to route definitions
|
|
247
402
|
return routes.map((route) => {
|
|
248
403
|
if (typeof route === "function") {
|
|
249
404
|
// Pass through middleware as-is
|
|
@@ -253,20 +408,14 @@ export function layout(LayoutComponent, routes) {
|
|
|
253
408
|
// Recursively process nested route arrays
|
|
254
409
|
return layout(LayoutComponent, route);
|
|
255
410
|
}
|
|
256
|
-
|
|
411
|
+
const routeDef = route;
|
|
257
412
|
return {
|
|
258
|
-
...
|
|
259
|
-
layouts: [LayoutComponent, ...(
|
|
413
|
+
...routeDef,
|
|
414
|
+
layouts: [LayoutComponent, ...(routeDef.layouts || [])],
|
|
260
415
|
};
|
|
261
416
|
});
|
|
262
417
|
}
|
|
263
|
-
export function render(Document, routes,
|
|
264
|
-
/**
|
|
265
|
-
* @param options - Configuration options for rendering.
|
|
266
|
-
* @param options.rscPayload - Toggle the RSC payload that's appended to the Document. Disabling this will mean that interactivity no longer works.
|
|
267
|
-
* @param options.ssr - Disable sever side rendering for all these routes. This only allow client side rendering`, which requires `rscPayload` to be enabled.
|
|
268
|
-
*/
|
|
269
|
-
options = {}) {
|
|
418
|
+
export function render(Document, routes, options = {}) {
|
|
270
419
|
options = {
|
|
271
420
|
rscPayload: true,
|
|
272
421
|
ssr: true,
|