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.
Files changed (139) hide show
  1. package/dist/lib/constants.d.mts +1 -0
  2. package/dist/lib/constants.mjs +7 -4
  3. package/dist/lib/e2e/browser.mjs +6 -2
  4. package/dist/lib/e2e/constants.d.mts +4 -0
  5. package/dist/lib/e2e/constants.mjs +49 -12
  6. package/dist/lib/e2e/dev.mjs +37 -49
  7. package/dist/lib/e2e/environment.d.mts +2 -0
  8. package/dist/lib/e2e/environment.mjs +201 -64
  9. package/dist/lib/e2e/index.d.mts +1 -0
  10. package/dist/lib/e2e/index.mjs +1 -0
  11. package/dist/lib/e2e/poll.d.mts +1 -1
  12. package/dist/lib/e2e/release.d.mts +1 -0
  13. package/dist/lib/e2e/release.mjs +16 -32
  14. package/dist/lib/e2e/tarball.mjs +2 -34
  15. package/dist/lib/e2e/testHarness.d.mts +34 -3
  16. package/dist/lib/e2e/testHarness.mjs +219 -90
  17. package/dist/lib/e2e/utils.d.mts +1 -0
  18. package/dist/lib/e2e/utils.mjs +15 -0
  19. package/dist/runtime/client/client.d.ts +35 -0
  20. package/dist/runtime/client/client.js +35 -0
  21. package/dist/runtime/client/navigation.d.ts +49 -0
  22. package/dist/runtime/client/navigation.js +80 -31
  23. package/dist/runtime/entries/clientSSR.d.ts +1 -0
  24. package/dist/runtime/entries/clientSSR.js +3 -0
  25. package/dist/runtime/entries/no-react-server-ssr-bridge.d.ts +0 -0
  26. package/dist/runtime/entries/no-react-server-ssr-bridge.js +2 -0
  27. package/dist/runtime/entries/router.d.ts +1 -0
  28. package/dist/runtime/entries/routerClient.d.ts +1 -0
  29. package/dist/runtime/entries/routerClient.js +1 -0
  30. package/dist/runtime/entries/worker.d.ts +2 -0
  31. package/dist/runtime/entries/worker.js +2 -0
  32. package/dist/runtime/imports/__mocks__/use-client-lookup.d.ts +6 -0
  33. package/dist/runtime/imports/__mocks__/use-client-lookup.js +6 -0
  34. package/dist/runtime/lib/db/SqliteDurableObject.d.ts +2 -2
  35. package/dist/runtime/lib/db/SqliteDurableObject.js +2 -2
  36. package/dist/runtime/lib/db/createDb.d.ts +1 -2
  37. package/dist/runtime/lib/db/createDb.js +4 -0
  38. package/dist/runtime/lib/db/typeInference/builders/alterTable.d.ts +13 -3
  39. package/dist/runtime/lib/db/typeInference/builders/columnDefinition.d.ts +35 -21
  40. package/dist/runtime/lib/db/typeInference/builders/createTable.d.ts +9 -2
  41. package/dist/runtime/lib/db/typeInference/database.d.ts +16 -2
  42. package/dist/runtime/lib/db/typeInference/typetests/alterTable.typetest.js +80 -5
  43. package/dist/runtime/lib/db/typeInference/typetests/createTable.typetest.js +104 -2
  44. package/dist/runtime/lib/db/typeInference/typetests/testUtils.d.ts +1 -0
  45. package/dist/runtime/lib/db/typeInference/utils.d.ts +59 -9
  46. package/dist/runtime/lib/links.d.ts +21 -7
  47. package/dist/runtime/lib/links.js +82 -24
  48. package/dist/runtime/lib/links.test.js +20 -0
  49. package/dist/runtime/lib/manifest.d.ts +1 -1
  50. package/dist/runtime/lib/manifest.js +7 -4
  51. package/dist/runtime/lib/realtime/client.js +8 -2
  52. package/dist/runtime/lib/realtime/worker.d.ts +1 -1
  53. package/dist/runtime/lib/router.d.ts +153 -36
  54. package/dist/runtime/lib/router.js +169 -20
  55. package/dist/runtime/lib/router.test.js +241 -0
  56. package/dist/runtime/lib/stitchDocumentAndAppStreams.d.ts +66 -0
  57. package/dist/runtime/lib/stitchDocumentAndAppStreams.js +302 -35
  58. package/dist/runtime/lib/stitchDocumentAndAppStreams.test.d.ts +1 -0
  59. package/dist/runtime/lib/stitchDocumentAndAppStreams.test.js +418 -0
  60. package/dist/runtime/lib/{rwContext.d.ts → types.d.ts} +1 -0
  61. package/dist/runtime/lib/types.js +1 -0
  62. package/dist/runtime/render/renderDocumentHtmlStream.d.ts +1 -1
  63. package/dist/runtime/render/renderToStream.d.ts +4 -2
  64. package/dist/runtime/render/renderToStream.js +53 -24
  65. package/dist/runtime/render/renderToString.d.ts +3 -6
  66. package/dist/runtime/requestInfo/types.d.ts +4 -1
  67. package/dist/runtime/requestInfo/utils.d.ts +9 -0
  68. package/dist/runtime/requestInfo/utils.js +44 -0
  69. package/dist/runtime/requestInfo/worker.d.ts +0 -1
  70. package/dist/runtime/requestInfo/worker.js +3 -10
  71. package/dist/runtime/script.d.ts +1 -3
  72. package/dist/runtime/script.js +1 -10
  73. package/dist/runtime/state.d.ts +3 -0
  74. package/dist/runtime/state.js +13 -0
  75. package/dist/runtime/worker.d.ts +3 -1
  76. package/dist/runtime/worker.js +32 -0
  77. package/dist/scripts/debug-sync.mjs +18 -20
  78. package/dist/scripts/worker-run.d.mts +1 -1
  79. package/dist/scripts/worker-run.mjs +59 -113
  80. package/dist/use-synced-state/SyncedStateServer.d.mts +21 -0
  81. package/dist/use-synced-state/SyncedStateServer.mjs +128 -0
  82. package/dist/use-synced-state/__tests__/SyncStateServer.test.d.mts +1 -0
  83. package/dist/use-synced-state/__tests__/SyncStateServer.test.mjs +109 -0
  84. package/dist/use-synced-state/__tests__/useSyncState.test.d.ts +1 -0
  85. package/dist/use-synced-state/__tests__/useSyncState.test.js +115 -0
  86. package/dist/use-synced-state/__tests__/useSyncedState.test.d.ts +1 -0
  87. package/dist/use-synced-state/__tests__/useSyncedState.test.js +115 -0
  88. package/dist/use-synced-state/__tests__/worker.test.d.mts +1 -0
  89. package/dist/use-synced-state/__tests__/worker.test.mjs +69 -0
  90. package/dist/use-synced-state/client-core.d.ts +26 -0
  91. package/dist/use-synced-state/client-core.js +39 -0
  92. package/dist/use-synced-state/client.d.ts +3 -0
  93. package/dist/use-synced-state/client.js +4 -0
  94. package/dist/use-synced-state/constants.d.mts +1 -0
  95. package/dist/use-synced-state/constants.mjs +1 -0
  96. package/dist/use-synced-state/useSyncedState.d.ts +20 -0
  97. package/dist/use-synced-state/useSyncedState.js +58 -0
  98. package/dist/use-synced-state/worker.d.mts +13 -0
  99. package/dist/use-synced-state/worker.mjs +69 -0
  100. package/dist/vite/buildApp.mjs +34 -2
  101. package/dist/vite/cloudflarePreInitPlugin.d.mts +11 -0
  102. package/dist/vite/cloudflarePreInitPlugin.mjs +40 -0
  103. package/dist/vite/configPlugin.mjs +9 -14
  104. package/dist/vite/constants.d.mts +1 -0
  105. package/dist/vite/constants.mjs +1 -0
  106. package/dist/vite/createDirectiveLookupPlugin.mjs +10 -7
  107. package/dist/vite/devServerTimingPlugin.mjs +4 -0
  108. package/dist/vite/diagnosticAssetGraphPlugin.d.mts +4 -0
  109. package/dist/vite/diagnosticAssetGraphPlugin.mjs +41 -0
  110. package/dist/vite/directiveModulesDevPlugin.mjs +9 -1
  111. package/dist/vite/directivesPlugin.mjs +4 -4
  112. package/dist/vite/envResolvers.d.mts +11 -0
  113. package/dist/vite/envResolvers.mjs +20 -0
  114. package/dist/vite/getViteEsbuild.mjs +2 -1
  115. package/dist/vite/hmrStabilityPlugin.d.mts +2 -0
  116. package/dist/vite/hmrStabilityPlugin.mjs +73 -0
  117. package/dist/vite/injectVitePreamblePlugin.mjs +0 -4
  118. package/dist/vite/knownDepsResolverPlugin.d.mts +0 -6
  119. package/dist/vite/knownDepsResolverPlugin.mjs +25 -17
  120. package/dist/vite/linkerPlugin.d.mts +2 -1
  121. package/dist/vite/linkerPlugin.mjs +11 -3
  122. package/dist/vite/linkerPlugin.test.mjs +15 -0
  123. package/dist/vite/miniflareHMRPlugin.mjs +6 -38
  124. package/dist/vite/moveStaticAssetsPlugin.mjs +35 -4
  125. package/dist/vite/redwoodPlugin.mjs +8 -10
  126. package/dist/vite/runDirectivesScan.mjs +72 -18
  127. package/dist/vite/ssrBridgePlugin.mjs +132 -40
  128. package/dist/vite/ssrBridgeWrapPlugin.d.mts +2 -0
  129. package/dist/vite/ssrBridgeWrapPlugin.mjs +85 -0
  130. package/dist/vite/staleDepRetryPlugin.d.mts +2 -0
  131. package/dist/vite/staleDepRetryPlugin.mjs +74 -0
  132. package/dist/vite/statePlugin.d.mts +4 -0
  133. package/dist/vite/statePlugin.mjs +62 -0
  134. package/dist/vite/transformJsxScriptTagsPlugin.mjs +0 -5
  135. package/dist/vite/virtualPlugin.mjs +6 -7
  136. package/package.json +27 -10
  137. package/dist/vite/manifestPlugin.d.mts +0 -4
  138. package/dist/vite/manifestPlugin.mjs +0 -63
  139. /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
- export type DocumentProps<T extends RequestInfo = RequestInfo> = T & {
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 RouteComponent<T extends RequestInfo = RequestInfo> = (requestInfo: T) => MaybePromise<React.JSX.Element | Response | void>;
26
- type RouteHandler<T extends RequestInfo = RequestInfo> = RouteFunction<T> | RouteComponent<T> | [...RouteMiddleware<T>[], RouteFunction<T> | RouteComponent<T>];
27
- export type Route<T extends RequestInfo = RequestInfo> = RouteMiddleware<T> | RouteDefinition<T> | Array<Route<T>>;
28
- export type RouteDefinition<T extends RequestInfo = RequestInfo> = {
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
- export declare function route<T extends RequestInfo = RequestInfo>(path: string, handler: RouteHandler<T>): RouteDefinition<T>;
46
- export declare function index<T extends RequestInfo = RequestInfo>(handler: RouteHandler<T>): RouteDefinition<T>;
47
- export declare function prefix<T extends RequestInfo = RequestInfo>(prefixPath: string, routes: Route<T>[]): Route<T>[];
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
- * @param options - Configuration options for rendering.
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`, which requires `rscPayload` to be enabled.
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
- options?: {
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
- }): Route<T>[];
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(route.handler);
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
- if (!path.endsWith("/")) {
170
- path = path + "/";
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
- return routes.map((r) => {
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
- // For RouteDefinition objects, update the path and preserve layouts
331
+ const routeDef = r;
197
332
  return {
198
- path: prefixPath + r.path,
199
- handler: r.handler,
200
- ...(r.layouts && { layouts: r.layouts }),
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
- // For RouteDefinition objects, prepend the layout so outer layouts come first
411
+ const routeDef = route;
257
412
  return {
258
- ...route,
259
- layouts: [LayoutComponent, ...(route.layouts || [])],
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,