rwsdk 1.0.0-beta.5 → 1.0.0-beta.50

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 (157) hide show
  1. package/bin/rw-scripts.mjs +13 -13
  2. package/dist/lib/constants.d.mts +1 -0
  3. package/dist/lib/constants.mjs +7 -4
  4. package/dist/lib/e2e/browser.mjs +6 -2
  5. package/dist/lib/e2e/constants.d.mts +4 -0
  6. package/dist/lib/e2e/constants.mjs +49 -12
  7. package/dist/lib/e2e/dev.mjs +49 -57
  8. package/dist/lib/e2e/environment.d.mts +2 -0
  9. package/dist/lib/e2e/environment.mjs +201 -64
  10. package/dist/lib/e2e/index.d.mts +2 -0
  11. package/dist/lib/e2e/index.mjs +2 -0
  12. package/dist/lib/e2e/poll.d.mts +1 -1
  13. package/dist/lib/e2e/release.d.mts +1 -0
  14. package/dist/lib/e2e/release.mjs +57 -52
  15. package/dist/lib/e2e/tarball.mjs +2 -34
  16. package/dist/lib/e2e/testHarness.d.mts +39 -3
  17. package/dist/lib/e2e/testHarness.mjs +239 -92
  18. package/dist/lib/e2e/utils.d.mts +1 -0
  19. package/dist/lib/e2e/utils.mjs +15 -0
  20. package/dist/lib/normalizeModulePath.mjs +1 -1
  21. package/dist/runtime/client/client.d.ts +64 -2
  22. package/dist/runtime/client/client.js +156 -15
  23. package/dist/runtime/client/navigation.d.ts +45 -0
  24. package/dist/runtime/client/navigation.js +68 -14
  25. package/dist/runtime/client/navigationCache.d.ts +68 -0
  26. package/dist/runtime/client/navigationCache.js +294 -0
  27. package/dist/runtime/client/navigationCache.test.js +469 -0
  28. package/dist/runtime/client/types.d.ts +26 -5
  29. package/dist/runtime/client/types.js +8 -1
  30. package/dist/runtime/entries/no-react-server-ssr-bridge.d.ts +0 -0
  31. package/dist/runtime/entries/no-react-server-ssr-bridge.js +2 -0
  32. package/dist/runtime/entries/no-react-server.js +3 -1
  33. package/dist/runtime/entries/react-server-only.js +1 -1
  34. package/dist/runtime/entries/router.d.ts +1 -0
  35. package/dist/runtime/entries/routerClient.d.ts +1 -0
  36. package/dist/runtime/entries/routerClient.js +1 -0
  37. package/dist/runtime/entries/worker.d.ts +4 -0
  38. package/dist/runtime/entries/worker.js +4 -0
  39. package/dist/runtime/imports/__mocks__/use-client-lookup.d.ts +6 -0
  40. package/dist/runtime/imports/__mocks__/use-client-lookup.js +6 -0
  41. package/dist/runtime/lib/db/SqliteDurableObject.d.ts +2 -2
  42. package/dist/runtime/lib/db/SqliteDurableObject.js +2 -2
  43. package/dist/runtime/lib/db/createDb.d.ts +1 -2
  44. package/dist/runtime/lib/db/createDb.js +4 -0
  45. package/dist/runtime/lib/db/typeInference/builders/alterTable.d.ts +13 -3
  46. package/dist/runtime/lib/db/typeInference/builders/columnDefinition.d.ts +35 -21
  47. package/dist/runtime/lib/db/typeInference/builders/createTable.d.ts +9 -2
  48. package/dist/runtime/lib/db/typeInference/database.d.ts +16 -2
  49. package/dist/runtime/lib/db/typeInference/typetests/alterTable.typetest.js +80 -5
  50. package/dist/runtime/lib/db/typeInference/typetests/createTable.typetest.js +104 -2
  51. package/dist/runtime/lib/db/typeInference/typetests/testUtils.d.ts +1 -0
  52. package/dist/runtime/lib/db/typeInference/utils.d.ts +59 -9
  53. package/dist/runtime/lib/links.d.ts +21 -7
  54. package/dist/runtime/lib/links.js +84 -26
  55. package/dist/runtime/lib/links.test.d.ts +1 -0
  56. package/dist/runtime/lib/links.test.js +20 -0
  57. package/dist/runtime/lib/manifest.d.ts +1 -1
  58. package/dist/runtime/lib/manifest.js +7 -4
  59. package/dist/runtime/lib/realtime/client.js +28 -6
  60. package/dist/runtime/lib/realtime/worker.d.ts +1 -1
  61. package/dist/runtime/lib/router.d.ts +154 -35
  62. package/dist/runtime/lib/router.js +491 -105
  63. package/dist/runtime/lib/router.test.js +611 -1
  64. package/dist/runtime/lib/stitchDocumentAndAppStreams.d.ts +66 -0
  65. package/dist/runtime/lib/stitchDocumentAndAppStreams.js +302 -35
  66. package/dist/runtime/lib/stitchDocumentAndAppStreams.test.d.ts +1 -0
  67. package/dist/runtime/lib/stitchDocumentAndAppStreams.test.js +418 -0
  68. package/dist/runtime/lib/{rwContext.d.ts → types.d.ts} +1 -0
  69. package/dist/runtime/lib/types.js +1 -0
  70. package/dist/runtime/register/client.d.ts +1 -1
  71. package/dist/runtime/register/client.js +10 -3
  72. package/dist/runtime/register/worker.js +13 -4
  73. package/dist/runtime/render/normalizeActionResult.js +8 -1
  74. package/dist/runtime/render/renderDocumentHtmlStream.d.ts +1 -1
  75. package/dist/runtime/render/renderToStream.d.ts +4 -2
  76. package/dist/runtime/render/renderToStream.js +53 -24
  77. package/dist/runtime/render/renderToString.d.ts +3 -6
  78. package/dist/runtime/requestInfo/types.d.ts +5 -1
  79. package/dist/runtime/requestInfo/utils.d.ts +9 -0
  80. package/dist/runtime/requestInfo/utils.js +45 -0
  81. package/dist/runtime/requestInfo/worker.d.ts +0 -1
  82. package/dist/runtime/requestInfo/worker.js +5 -11
  83. package/dist/runtime/script.d.ts +1 -3
  84. package/dist/runtime/script.js +1 -10
  85. package/dist/runtime/server.d.ts +52 -0
  86. package/dist/runtime/server.js +88 -0
  87. package/dist/runtime/state.d.ts +3 -0
  88. package/dist/runtime/state.js +13 -0
  89. package/dist/runtime/worker.d.ts +3 -1
  90. package/dist/runtime/worker.js +45 -2
  91. package/dist/scripts/debug-sync.mjs +18 -20
  92. package/dist/scripts/worker-run.d.mts +1 -1
  93. package/dist/scripts/worker-run.mjs +59 -113
  94. package/dist/use-synced-state/SyncedStateServer.d.mts +36 -0
  95. package/dist/use-synced-state/SyncedStateServer.mjs +196 -0
  96. package/dist/use-synced-state/__tests__/SyncStateServer.test.d.mts +1 -0
  97. package/dist/use-synced-state/__tests__/SyncStateServer.test.mjs +116 -0
  98. package/dist/use-synced-state/__tests__/useSyncState.test.d.ts +1 -0
  99. package/dist/use-synced-state/__tests__/useSyncState.test.js +115 -0
  100. package/dist/use-synced-state/__tests__/useSyncedState.test.d.ts +1 -0
  101. package/dist/use-synced-state/__tests__/useSyncedState.test.js +115 -0
  102. package/dist/use-synced-state/__tests__/worker.test.d.mts +1 -0
  103. package/dist/use-synced-state/__tests__/worker.test.mjs +70 -0
  104. package/dist/use-synced-state/client-core.d.ts +29 -0
  105. package/dist/use-synced-state/client-core.js +103 -0
  106. package/dist/use-synced-state/client.d.ts +3 -0
  107. package/dist/use-synced-state/client.js +4 -0
  108. package/dist/use-synced-state/constants.d.mts +1 -0
  109. package/dist/use-synced-state/constants.mjs +1 -0
  110. package/dist/use-synced-state/useSyncedState.d.ts +21 -0
  111. package/dist/use-synced-state/useSyncedState.js +64 -0
  112. package/dist/use-synced-state/worker.d.mts +14 -0
  113. package/dist/use-synced-state/worker.mjs +135 -0
  114. package/dist/vite/buildApp.mjs +34 -2
  115. package/dist/vite/cloudflarePreInitPlugin.d.mts +11 -0
  116. package/dist/vite/cloudflarePreInitPlugin.mjs +40 -0
  117. package/dist/vite/configPlugin.mjs +9 -14
  118. package/dist/vite/constants.d.mts +1 -0
  119. package/dist/vite/constants.mjs +1 -0
  120. package/dist/vite/createDirectiveLookupPlugin.mjs +10 -7
  121. package/dist/vite/devServerTimingPlugin.mjs +4 -0
  122. package/dist/vite/diagnosticAssetGraphPlugin.d.mts +4 -0
  123. package/dist/vite/diagnosticAssetGraphPlugin.mjs +41 -0
  124. package/dist/vite/directiveModulesDevPlugin.mjs +9 -1
  125. package/dist/vite/directivesPlugin.mjs +4 -4
  126. package/dist/vite/envResolvers.d.mts +11 -0
  127. package/dist/vite/envResolvers.mjs +20 -0
  128. package/dist/vite/getViteEsbuild.mjs +2 -1
  129. package/dist/vite/hmrStabilityPlugin.d.mts +2 -0
  130. package/dist/vite/hmrStabilityPlugin.mjs +73 -0
  131. package/dist/vite/injectVitePreamblePlugin.mjs +0 -4
  132. package/dist/vite/knownDepsResolverPlugin.d.mts +0 -6
  133. package/dist/vite/knownDepsResolverPlugin.mjs +25 -17
  134. package/dist/vite/linkerPlugin.d.mts +2 -1
  135. package/dist/vite/linkerPlugin.mjs +11 -3
  136. package/dist/vite/linkerPlugin.test.mjs +15 -0
  137. package/dist/vite/miniflareHMRPlugin.mjs +6 -38
  138. package/dist/vite/moveStaticAssetsPlugin.mjs +35 -4
  139. package/dist/vite/redwoodPlugin.mjs +9 -11
  140. package/dist/vite/redwoodPlugin.test.mjs +4 -4
  141. package/dist/vite/runDirectivesScan.mjs +75 -19
  142. package/dist/vite/ssrBridgePlugin.mjs +132 -40
  143. package/dist/vite/ssrBridgeWrapPlugin.d.mts +2 -0
  144. package/dist/vite/ssrBridgeWrapPlugin.mjs +85 -0
  145. package/dist/vite/staleDepRetryPlugin.d.mts +2 -0
  146. package/dist/vite/staleDepRetryPlugin.mjs +74 -0
  147. package/dist/vite/statePlugin.d.mts +4 -0
  148. package/dist/vite/statePlugin.mjs +62 -0
  149. package/dist/vite/transformClientComponents.test.mjs +32 -0
  150. package/dist/vite/transformJsxScriptTagsPlugin.mjs +0 -5
  151. package/dist/vite/transformServerFunctions.mjs +66 -4
  152. package/dist/vite/transformServerFunctions.test.mjs +35 -0
  153. package/dist/vite/virtualPlugin.mjs +6 -7
  154. package/package.json +41 -19
  155. package/dist/vite/manifestPlugin.d.mts +0 -4
  156. package/dist/vite/manifestPlugin.mjs +0 -63
  157. /package/dist/runtime/{lib/rwContext.js → client/navigationCache.test.d.ts} +0 -0
@@ -1,38 +1,96 @@
1
+ export function linkFor() {
2
+ return createLinkFunction();
3
+ }
4
+ export function createLinks(_app) {
5
+ return linkFor();
6
+ }
7
+ // Implementation
1
8
  export function defineLinks(routes) {
2
- // Validate routes at runtime
9
+ // If no routes provided, this is the app type overload
10
+ // At runtime, we can't distinguish, but the type system ensures
11
+ // this only happens when called as defineLinks<App>()
12
+ // We delegate to linkFor which handles app types correctly
13
+ if (routes === undefined) {
14
+ // This branch is only reachable when called as defineLinks<App>()
15
+ // The return type is AppLink<App> due to the overload
16
+ // We use linkFor internally which doesn't need runtime route validation
17
+ return linkFor();
18
+ }
19
+ // Original implementation for route arrays
3
20
  routes.forEach((route) => {
4
21
  if (typeof route !== "string") {
5
- throw new Error(`Invalid route: ${route}. Routes must be strings.`);
22
+ throw new Error(`RedwoodSDK: Invalid route: ${route}. Routes must be string literals. Ensure you're passing an array of route paths.`);
6
23
  }
7
24
  });
8
- return (path, params) => {
25
+ const link = createLinkFunction();
26
+ return ((path, params) => {
9
27
  if (!routes.includes(path)) {
10
- throw new Error(`Invalid route: ${path}`);
28
+ throw new Error(`RedwoodSDK: Invalid route: ${path}. This route is not included in the routes array passed to defineLinks(). Check for typos or ensure the route is defined in your router.`);
11
29
  }
12
- if (!params)
30
+ return link(path, params);
31
+ });
32
+ }
33
+ const TOKEN_REGEX = /:([a-zA-Z0-9_]+)|\*/g;
34
+ function createLinkFunction() {
35
+ return ((path, params) => {
36
+ const expectsParams = hasRouteParameters(path);
37
+ if (!params || Object.keys(params).length === 0) {
38
+ if (expectsParams) {
39
+ throw new Error(`RedwoodSDK: Route ${path} requires an object of parameters (e.g., link("${path}", { id: "123" })).`);
40
+ }
13
41
  return path;
14
- let result = path;
15
- // Replace named parameters
16
- for (const [key, value] of Object.entries(params)) {
17
- if (key.startsWith("$")) {
18
- // Replace each star with its corresponding $ parameter
19
- const starIndex = parseInt(key.slice(1));
20
- const stars = result.match(/\*/g) || [];
21
- if (starIndex >= stars.length) {
22
- throw new Error(`Parameter ${key} has no corresponding * in route`);
23
- }
24
- // Replace the nth star with the value
25
- let count = 0;
26
- result = result.replace(/\*/g, (match) => count++ === starIndex ? value : match);
42
+ }
43
+ return interpolate(path, params);
44
+ });
45
+ }
46
+ function hasRouteParameters(path) {
47
+ TOKEN_REGEX.lastIndex = 0;
48
+ const result = TOKEN_REGEX.test(path);
49
+ TOKEN_REGEX.lastIndex = 0;
50
+ return result;
51
+ }
52
+ function interpolate(template, params) {
53
+ let result = "";
54
+ let lastIndex = 0;
55
+ let wildcardIndex = 0;
56
+ const consumed = new Set();
57
+ TOKEN_REGEX.lastIndex = 0;
58
+ let match;
59
+ while ((match = TOKEN_REGEX.exec(template)) !== null) {
60
+ result += template.slice(lastIndex, match.index);
61
+ if (match[1]) {
62
+ const name = match[1];
63
+ const value = params[name];
64
+ if (value === undefined) {
65
+ throw new Error(`RedwoodSDK: Missing parameter "${name}" for route ${template}. Ensure you're providing all required parameters in the params object.`);
27
66
  }
28
- else {
29
- // Handle named parameters
30
- if (typeof value !== "string") {
31
- throw new Error(`Parameter ${key} must be a string`);
32
- }
33
- result = result.replace(`:${key}`, value);
67
+ result += encodeURIComponent(value);
68
+ consumed.add(name);
69
+ }
70
+ else {
71
+ const key = `$${wildcardIndex}`;
72
+ const value = params[key];
73
+ if (value === undefined) {
74
+ throw new Error(`RedwoodSDK: Missing parameter "${key}" for route ${template}. Wildcard routes use $0, $1, etc. as parameter keys.`);
34
75
  }
76
+ result += encodeWildcardValue(value);
77
+ consumed.add(key);
78
+ wildcardIndex += 1;
79
+ }
80
+ lastIndex = TOKEN_REGEX.lastIndex;
81
+ }
82
+ result += template.slice(lastIndex);
83
+ for (const key of Object.keys(params)) {
84
+ if (!consumed.has(key)) {
85
+ throw new Error(`RedwoodSDK: Parameter "${key}" is not used by route ${template}. Check your params object for typos or remove unused parameters.`);
35
86
  }
36
- return result;
37
- };
87
+ }
88
+ TOKEN_REGEX.lastIndex = 0;
89
+ return result;
90
+ }
91
+ function encodeWildcardValue(value) {
92
+ return value
93
+ .split("/")
94
+ .map((segment) => encodeURIComponent(segment))
95
+ .join("/");
38
96
  }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,20 @@
1
+ import { describe, expect, it } from "vitest";
2
+ import { defineLinks } from "./links";
3
+ const link = defineLinks(["/", "/users/:id", "/files/*"]);
4
+ describe("link helpers", () => {
5
+ it("returns static routes without parameters", () => {
6
+ expect(link("/")).toBe("/");
7
+ });
8
+ it("replaces named parameters with encoded values", () => {
9
+ expect(link("/users/:id", { id: "user id" })).toBe("/users/user%20id");
10
+ });
11
+ it("replaces wildcard parameters preserving path segments", () => {
12
+ expect(link("/files/*", { $0: "docs/Guide Document.md" })).toBe("/files/docs/Guide%20Document.md");
13
+ });
14
+ it("throws when parameters are missing", () => {
15
+ expect(() => link("/users/:id")).toThrowError(/requires an object of parameters/i);
16
+ });
17
+ it("throws when extra parameters are supplied", () => {
18
+ expect(() => link("/users/:id", { id: "123", extra: "value" })).toThrowError(/is not used by route/i);
19
+ });
20
+ });
@@ -8,4 +8,4 @@ export interface ManifestChunk {
8
8
  css?: string[];
9
9
  assets?: string[];
10
10
  }
11
- export declare const getManifest: () => Promise<Manifest>;
11
+ export declare function getManifest(): Promise<Manifest>;
@@ -1,14 +1,17 @@
1
1
  let manifest;
2
- export const getManifest = async () => {
2
+ export async function getManifest() {
3
3
  if (manifest) {
4
4
  return manifest;
5
5
  }
6
6
  if (import.meta.env.VITE_IS_DEV_SERVER) {
7
+ // In dev, there's no manifest, so we can use an empty object.
7
8
  manifest = {};
8
9
  }
9
10
  else {
10
- const { default: prodManifest } = await import("virtual:rwsdk:manifest.js");
11
- manifest = prodManifest;
11
+ // context(justinvdm, 2 Oct 2025): In production, the manifest is a
12
+ // placeholder string that will be replaced by the linker plugin with the
13
+ // actual manifest JSON object.
14
+ manifest = "__RWSDK_MANIFEST_PLACEHOLDER__";
12
15
  }
13
16
  return manifest;
14
- };
17
+ }
@@ -1,7 +1,15 @@
1
- import { createFromReadableStream } from "react-server-dom-webpack/client.browser";
1
+ // context(justinvdm, 14 Aug 2025): `react-server-dom-webpack` uses this globa ___webpack_require__ global,
2
+ // so we need to import our client entry point (which sets it), before importing
3
+ // prettier-ignore
2
4
  import { initClient } from "../../client/client";
3
- import { packMessage, unpackMessage, } from "./protocol";
5
+ // prettier-ignore
6
+ import { isActionResponse, } from "../../client/types";
7
+ // prettier-ignore
8
+ import { createFromReadableStream } from "react-server-dom-webpack/client.browser";
9
+ // prettier-ignore
4
10
  import { MESSAGE_TYPE } from "./shared";
11
+ // prettier-ignore
12
+ import { packMessage, unpackMessage, } from "./protocol";
5
13
  const DEFAULT_KEY = "default";
6
14
  export const initRealtimeClient = ({ key = DEFAULT_KEY, handleResponse, } = {}) => {
7
15
  const transport = realtimeTransport({ key, handleResponse });
@@ -50,7 +58,7 @@ export const realtimeTransport = ({ key = DEFAULT_KEY, handleResponse, }) => (tr
50
58
  }
51
59
  return ws;
52
60
  };
53
- const realtimeCallServer = async (id, args) => {
61
+ const realtimeCallServer = async (id, args, _source, _method) => {
54
62
  try {
55
63
  const socket = ensureWs();
56
64
  const { encodeReply } = await import("react-server-dom-webpack/client.browser");
@@ -74,7 +82,7 @@ export const realtimeTransport = ({ key = DEFAULT_KEY, handleResponse, }) => (tr
74
82
  }
75
83
  catch (e) {
76
84
  console.error("[Realtime] Error calling server", e);
77
- return null;
85
+ return undefined;
78
86
  }
79
87
  };
80
88
  const processResponse = async (response) => {
@@ -91,13 +99,27 @@ export const realtimeTransport = ({ key = DEFAULT_KEY, handleResponse, }) => (tr
91
99
  streamForRsc = response.body;
92
100
  }
93
101
  if (!shouldContinue) {
94
- return null;
102
+ return undefined;
95
103
  }
96
104
  const rscPayload = createFromReadableStream(streamForRsc, {
97
105
  callServer: realtimeCallServer,
98
106
  });
99
107
  transportContext.setRscPayload(rscPayload);
100
- return (await rscPayload).actionResult;
108
+ const rawActionResult = (await rscPayload).actionResult;
109
+ if (isActionResponse(rawActionResult)) {
110
+ const actionResponse = rawActionResult.__rw_action_response;
111
+ const handledByHook = transportContext.onActionResponse?.(actionResponse) === true;
112
+ if (!handledByHook) {
113
+ const location = actionResponse.headers["location"];
114
+ const isRedirect = actionResponse.status >= 300 && actionResponse.status < 400;
115
+ if (location && isRedirect) {
116
+ window.location.href = location;
117
+ return undefined;
118
+ }
119
+ }
120
+ return rawActionResult;
121
+ }
122
+ return rawActionResult;
101
123
  }
102
124
  catch (err) {
103
125
  throw err;
@@ -1,3 +1,3 @@
1
1
  import type { RealtimeDurableObject } from "./durableObject";
2
2
  export { renderRealtimeClients } from "./renderRealtimeClients";
3
- export declare const realtimeRoute: (getDurableObjectNamespace: (env: Cloudflare.Env) => DurableObjectNamespace<RealtimeDurableObject>) => import("../router").RouteDefinition<import("../../worker").RequestInfo<any, import("../../worker").DefaultAppContext>>;
3
+ export declare const realtimeRoute: (getDurableObjectNamespace: (env: Cloudflare.Env) => DurableObjectNamespace<RealtimeDurableObject>) => import("../router").RouteDefinition<"/__realtime", import("../../worker").RequestInfo<any, import("../../worker").DefaultAppContext>>;
@@ -1,37 +1,52 @@
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;
3
+ import type { DocumentProps, LayoutProps } from "./types.js";
4
+ type MaybePromise<T> = T | Promise<T>;
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
+ export type ExceptHandler<T extends RequestInfo = RequestInfo> = {
10
+ __rwExcept: true;
11
+ handler: (error: unknown, requestInfo: T) => MaybePromise<React.JSX.Element | Response | void>;
10
12
  };
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;
13
+ type RouteFunction<T extends RequestInfo = RequestInfo> = BivariantRouteHandler<T, MaybePromise<Response>>;
14
+ type RouteComponent<T extends RequestInfo = RequestInfo> = BivariantRouteHandler<T, MaybePromise<React.JSX.Element | Response | void>>;
15
+ type RouteHandler<T extends RequestInfo = RequestInfo> = RouteFunction<T> | RouteComponent<T> | readonly [...RouteMiddleware<T>[], RouteFunction<T> | RouteComponent<T>];
16
+ declare const METHOD_VERBS: readonly ["delete", "get", "head", "patch", "post", "put"];
17
+ export type MethodVerb = (typeof METHOD_VERBS)[number];
18
+ export type MethodHandlers<T extends RequestInfo = RequestInfo> = {
19
+ [K in MethodVerb]?: RouteHandler<T>;
20
+ } & {
21
+ config?: {
22
+ disable405?: true;
23
+ disableOptions?: true;
24
+ };
25
+ custom?: {
26
+ [method: string]: RouteHandler<T>;
27
+ };
21
28
  };
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>;
24
- 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> = {
29
+ export type Route<T extends RequestInfo = RequestInfo> = RouteMiddleware<T> | RouteDefinition<string, T> | ExceptHandler<T> | readonly Route<T>[];
30
+ type NormalizedRouteDefinition<T extends RequestInfo = RequestInfo> = {
29
31
  path: string;
30
- handler: RouteHandler<T>;
32
+ handler: RouteHandler<T> | MethodHandlers<T>;
31
33
  layouts?: React.FC<LayoutProps<T>>[];
32
34
  };
35
+ export type RouteDefinition<Path extends string = string, T extends RequestInfo = RequestInfo> = NormalizedRouteDefinition<T> & {
36
+ readonly __rwPath?: Path;
37
+ };
38
+ type TrimTrailingSlash<S extends string> = S extends `${infer Head}/` ? TrimTrailingSlash<Head> : S;
39
+ type TrimLeadingSlash<S extends string> = S extends `/${infer Rest}` ? TrimLeadingSlash<Rest> : S;
40
+ type NormalizePrefix<Prefix extends string> = TrimTrailingSlash<TrimLeadingSlash<Prefix>> extends "" ? "" : `/${TrimTrailingSlash<TrimLeadingSlash<Prefix>>}`;
41
+ type NormalizePath<Path extends string> = TrimTrailingSlash<Path> extends "/" ? "/" : `/${TrimTrailingSlash<TrimLeadingSlash<Path>>}`;
42
+ type JoinPaths<Prefix extends string, Path extends string> = NormalizePrefix<Prefix> extends "" ? NormalizePath<Path> : Path extends "/" ? NormalizePrefix<Prefix> : `${NormalizePrefix<Prefix>}${NormalizePath<Path>}`;
43
+ type PrefixedRouteValue<Prefix extends string, Value> = Value extends RouteDefinition<infer Path, infer Req> ? RouteDefinition<JoinPaths<Prefix, Path>, Req> : Value extends ExceptHandler<any> ? Value : Value extends readonly Route<any>[] ? PrefixedRouteArray<Prefix, Value> : Value;
44
+ type PrefixedRouteArray<Prefix extends string, Routes extends readonly Route<any>[]> = Routes extends readonly [] ? [] : Routes extends readonly [infer Head, ...infer Tail] ? readonly [
45
+ PrefixedRouteValue<Prefix, Head>,
46
+ ...PrefixedRouteArray<Prefix, Tail extends readonly Route<any>[] ? Tail : []>
47
+ ] : ReadonlyArray<PrefixedRouteValue<Prefix, Routes[number]>>;
33
48
  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>[]): {
49
+ export declare function defineRoutes<T extends RequestInfo = RequestInfo>(routes: readonly Route<T>[]): {
35
50
  routes: Route<T>[];
36
51
  handle: ({ request, renderPage, getRequestInfo, onError, runWithRequestInfoOverrides, rscActionHandler, }: {
37
52
  request: Request;
@@ -42,20 +57,124 @@ export declare function defineRoutes<T extends RequestInfo = RequestInfo>(routes
42
57
  rscActionHandler: (request: Request) => Promise<unknown>;
43
58
  }) => Response | Promise<Response>;
44
59
  };
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>[];
60
+ /**
61
+ * Defines a route handler for a path pattern.
62
+ *
63
+ * Supports three types of path patterns:
64
+ * - Static: /about, /contact
65
+ * - Parameters: /users/:id, /posts/:postId/edit
66
+ * - Wildcards: /files/\*, /api/\*\/download
67
+ *
68
+ * @example
69
+ * // Static route
70
+ * route("/about", () => <AboutPage />)
71
+ *
72
+ * @example
73
+ * // Route with parameters
74
+ * route("/users/:id", ({ params }) => {
75
+ * return <UserProfile userId={params.id} />
76
+ * })
77
+ *
78
+ * @example
79
+ * // Route with wildcards
80
+ * route("/files/*", ({ params }) => {
81
+ * const filePath = params.$0
82
+ * return <FileViewer path={filePath} />
83
+ * })
84
+ *
85
+ * @example
86
+ * // Method-based routing
87
+ * route("/api/users", {
88
+ * get: () => Response.json(users),
89
+ * post: ({ request }) => Response.json({ status: "created" }, { status: 201 }),
90
+ * delete: () => new Response(null, { status: 204 }),
91
+ * })
92
+ *
93
+ * @example
94
+ * // Route with middleware array
95
+ * route("/admin", [isAuthenticated, isAdmin, () => <AdminDashboard />])
96
+ */
97
+ export declare function route<Path extends string, T extends RequestInfo = RequestInfo>(path: Path, handler: RouteHandler<T> | MethodHandlers<T>): RouteDefinition<NormalizePath<Path>, T>;
98
+ /**
99
+ * Defines a route handler for the root path "/".
100
+ *
101
+ * @example
102
+ * // Homepage
103
+ * index(() => <HomePage />)
104
+ *
105
+ * @example
106
+ * // With middleware
107
+ * index([logRequest, () => <HomePage />])
108
+ */
109
+ export declare function index<T extends RequestInfo = RequestInfo>(handler: RouteHandler<T>): RouteDefinition<"/", T>;
110
+ /**
111
+ * Defines an error handler that catches errors from routes, middleware, and RSC actions.
112
+ *
113
+ * @example
114
+ * // Global error handler
115
+ * except((error, requestInfo) => {
116
+ * console.error(error);
117
+ * return new Response("Internal Server Error", { status: 500 });
118
+ * })
119
+ *
120
+ * @example
121
+ * // Error handler that returns a React component
122
+ * except((error) => {
123
+ * return <ErrorPage error={error} />;
124
+ * })
125
+ */
126
+ export declare function except<T extends RequestInfo = RequestInfo>(handler: (error: unknown, requestInfo: T) => MaybePromise<React.JSX.Element | Response | void>): ExceptHandler<T>;
127
+ 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
128
  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
129
  /**
52
- * @param options - Configuration options for rendering.
130
+ * Wraps routes with a layout component.
131
+ *
132
+ * @example
133
+ * // Define a layout component
134
+ * function BlogLayout({ children }: { children?: React.ReactNode }) {
135
+ * return (
136
+ * <div>
137
+ * <nav>Blog Navigation</nav>
138
+ * <main>{children}</main>
139
+ * </div>
140
+ * )
141
+ * }
142
+ *
143
+ * // Apply layout to routes
144
+ * const blogRoutes = layout(BlogLayout, [
145
+ * route("/", () => <BlogIndex />),
146
+ * route("/post/:id", ({ params }) => <BlogPost id={params.id} />),
147
+ * ])
148
+ */
149
+ export declare function layout<T extends RequestInfo = RequestInfo, Routes extends readonly Route<T>[] = readonly Route<T>[]>(LayoutComponent: React.FC<LayoutProps<T>>, routes: Routes): Routes;
150
+ /**
151
+ * Wraps routes with a Document component and configures rendering options.
152
+ *
53
153
  * @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.
154
+ * @param options.ssr - Disable sever side rendering for all these routes. This only allow client side rendering, which requires `rscPayload` to be enabled.
155
+ *
156
+ * @example
157
+ * // Basic usage
158
+ * defineApp([
159
+ * render(Document, [
160
+ * route("/", () => <HomePage />),
161
+ * route("/about", () => <AboutPage />),
162
+ * ]),
163
+ * ])
164
+ *
165
+ * @example
166
+ * // With custom rendering options
167
+ * render(Document, [
168
+ * route("/", () => <HomePage />),
169
+ * ], {
170
+ * rscPayload: true,
171
+ * ssr: true,
172
+ * })
55
173
  */
56
- options?: {
174
+ type RenderedRoutes<T extends RequestInfo, Routes extends readonly Route<T>[]> = readonly [RouteMiddleware<T>, ...Routes];
175
+ 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
176
  rscPayload?: boolean;
58
177
  ssr?: boolean;
59
- }): Route<T>[];
178
+ }): RenderedRoutes<T, Routes>;
60
179
  export declare const isClientReference: (value: any) => boolean;
61
180
  export {};