rwsdk 1.0.0-beta.2-test.20250930092748 → 1.0.0-beta.20

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 (68) hide show
  1. package/dist/lib/constants.d.mts +1 -0
  2. package/dist/lib/constants.mjs +1 -0
  3. package/dist/lib/e2e/constants.d.mts +14 -0
  4. package/dist/lib/e2e/constants.mjs +74 -0
  5. package/dist/lib/e2e/dev.mjs +0 -1
  6. package/dist/lib/e2e/environment.d.mts +1 -1
  7. package/dist/lib/e2e/environment.mjs +118 -18
  8. package/dist/lib/e2e/index.d.mts +1 -0
  9. package/dist/lib/e2e/index.mjs +1 -0
  10. package/dist/lib/e2e/poll.d.mts +1 -1
  11. package/dist/lib/e2e/testHarness.d.mts +36 -3
  12. package/dist/lib/e2e/testHarness.mjs +192 -116
  13. package/dist/runtime/client/client.d.ts +1 -0
  14. package/dist/runtime/client/client.js +2 -0
  15. package/dist/runtime/client/navigation.d.ts +8 -0
  16. package/dist/runtime/client/navigation.js +39 -31
  17. package/dist/runtime/entries/clientSSR.d.ts +1 -0
  18. package/dist/runtime/entries/clientSSR.js +3 -0
  19. package/dist/runtime/entries/router.d.ts +1 -0
  20. package/dist/runtime/entries/worker.d.ts +2 -0
  21. package/dist/runtime/entries/worker.js +2 -0
  22. package/dist/runtime/lib/db/createDb.d.ts +1 -2
  23. package/dist/runtime/lib/db/createDb.js +4 -0
  24. package/dist/runtime/lib/manifest.d.ts +1 -1
  25. package/dist/runtime/lib/manifest.js +7 -4
  26. package/dist/runtime/lib/realtime/client.js +8 -2
  27. package/dist/runtime/lib/router.d.ts +16 -21
  28. package/dist/runtime/lib/router.js +67 -1
  29. package/dist/runtime/lib/router.test.js +241 -0
  30. package/dist/runtime/lib/{rwContext.d.ts → types.d.ts} +1 -0
  31. package/dist/runtime/render/renderDocumentHtmlStream.d.ts +1 -1
  32. package/dist/runtime/render/renderToStream.d.ts +4 -2
  33. package/dist/runtime/render/renderToStream.js +21 -2
  34. package/dist/runtime/render/renderToString.d.ts +3 -1
  35. package/dist/runtime/requestInfo/types.d.ts +4 -1
  36. package/dist/runtime/requestInfo/utils.d.ts +9 -0
  37. package/dist/runtime/requestInfo/utils.js +44 -0
  38. package/dist/runtime/requestInfo/worker.js +3 -2
  39. package/dist/runtime/script.d.ts +1 -3
  40. package/dist/runtime/script.js +1 -10
  41. package/dist/runtime/state.d.ts +3 -0
  42. package/dist/runtime/state.js +13 -0
  43. package/dist/runtime/worker.js +25 -0
  44. package/dist/scripts/debug-sync.mjs +18 -20
  45. package/dist/scripts/worker-run.d.mts +1 -1
  46. package/dist/scripts/worker-run.mjs +50 -113
  47. package/dist/vite/buildApp.mjs +34 -2
  48. package/dist/vite/directiveModulesDevPlugin.mjs +1 -1
  49. package/dist/vite/envResolvers.d.mts +11 -0
  50. package/dist/vite/envResolvers.mjs +20 -0
  51. package/dist/vite/hmrStabilityPlugin.d.mts +2 -0
  52. package/dist/vite/hmrStabilityPlugin.mjs +68 -0
  53. package/dist/vite/knownDepsResolverPlugin.d.mts +0 -6
  54. package/dist/vite/knownDepsResolverPlugin.mjs +1 -12
  55. package/dist/vite/linkerPlugin.d.mts +2 -1
  56. package/dist/vite/linkerPlugin.mjs +11 -3
  57. package/dist/vite/linkerPlugin.test.mjs +15 -0
  58. package/dist/vite/redwoodPlugin.mjs +7 -9
  59. package/dist/vite/runDirectivesScan.mjs +57 -12
  60. package/dist/vite/ssrBridgePlugin.mjs +104 -34
  61. package/dist/vite/staleDepRetryPlugin.d.mts +2 -0
  62. package/dist/vite/staleDepRetryPlugin.mjs +69 -0
  63. package/dist/vite/statePlugin.d.mts +4 -0
  64. package/dist/vite/statePlugin.mjs +62 -0
  65. package/package.json +13 -7
  66. package/dist/vite/manifestPlugin.d.mts +0 -4
  67. package/dist/vite/manifestPlugin.mjs +0 -63
  68. /package/dist/runtime/lib/{rwContext.js → types.js} +0 -0
@@ -1,33 +1,28 @@
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
- };
3
+ import type { DocumentProps, LayoutProps } from "./types.js";
22
4
  export type RouteMiddleware<T extends RequestInfo = RequestInfo> = (requestInfo: T) => MaybePromise<React.JSX.Element | Response | void>;
23
5
  type RouteFunction<T extends RequestInfo = RequestInfo> = (requestInfo: T) => MaybePromise<Response>;
24
6
  type MaybePromise<T> = T | Promise<T>;
25
7
  type RouteComponent<T extends RequestInfo = RequestInfo> = (requestInfo: T) => MaybePromise<React.JSX.Element | Response | void>;
26
8
  type RouteHandler<T extends RequestInfo = RequestInfo> = RouteFunction<T> | RouteComponent<T> | [...RouteMiddleware<T>[], RouteFunction<T> | RouteComponent<T>];
9
+ declare const METHOD_VERBS: readonly ["delete", "get", "head", "patch", "post", "put"];
10
+ export type MethodVerb = (typeof METHOD_VERBS)[number];
11
+ export type MethodHandlers<T extends RequestInfo = RequestInfo> = {
12
+ [K in MethodVerb]?: RouteHandler<T>;
13
+ } & {
14
+ config?: {
15
+ disable405?: true;
16
+ disableOptions?: true;
17
+ };
18
+ custom?: {
19
+ [method: string]: RouteHandler<T>;
20
+ };
21
+ };
27
22
  export type Route<T extends RequestInfo = RequestInfo> = RouteMiddleware<T> | RouteDefinition<T> | Array<Route<T>>;
28
23
  export type RouteDefinition<T extends RequestInfo = RequestInfo> = {
29
24
  path: string;
30
- handler: RouteHandler<T>;
25
+ handler: RouteHandler<T> | MethodHandlers<T>;
31
26
  layouts?: React.FC<LayoutProps<T>>[];
32
27
  };
33
28
  export declare function matchPath<T extends RequestInfo = RequestInfo>(routePath: string, requestPath: string): T["params"] | null;
@@ -42,7 +37,7 @@ export declare function defineRoutes<T extends RequestInfo = RequestInfo>(routes
42
37
  rscActionHandler: (request: Request) => Promise<unknown>;
43
38
  }) => Response | Promise<Response>;
44
39
  };
45
- export declare function route<T extends RequestInfo = RequestInfo>(path: string, handler: RouteHandler<T>): RouteDefinition<T>;
40
+ export declare function route<T extends RequestInfo = RequestInfo>(path: string, handler: RouteHandler<T> | MethodHandlers<T>): RouteDefinition<T>;
46
41
  export declare function index<T extends RequestInfo = RequestInfo>(handler: RouteHandler<T>): RouteDefinition<T>;
47
42
  export declare function prefix<T extends RequestInfo = RequestInfo>(prefixPath: string, routes: Route<T>[]): Route<T>[];
48
43
  export declare const wrapHandlerToThrowResponses: <T extends RequestInfo = RequestInfo>(handler: RouteFunction<T> | RouteComponent<T>) => RouteHandler<T>;
@@ -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());
@@ -169,6 +225,16 @@ export function route(path, handler) {
169
225
  if (!path.endsWith("/")) {
170
226
  path = path + "/";
171
227
  }
228
+ // Normalize custom method keys to lowercase
229
+ if (isMethodHandlers(handler) && handler.custom) {
230
+ handler = {
231
+ ...handler,
232
+ custom: Object.fromEntries(Object.entries(handler.custom).map(([method, methodHandler]) => [
233
+ method.toLowerCase(),
234
+ methodHandler,
235
+ ])),
236
+ };
237
+ }
172
238
  return {
173
239
  path,
174
240
  handler,
@@ -71,6 +71,8 @@ describe("defineRoutes - Request Handling Behavior", () => {
71
71
  ssr: true,
72
72
  databases: new Map(),
73
73
  scriptsToBeLoaded: new Set(),
74
+ entryScripts: new Set(),
75
+ inlineScripts: new Set(),
74
76
  pageRouteResolved: undefined,
75
77
  },
76
78
  cf: {},
@@ -593,6 +595,245 @@ describe("defineRoutes - Request Handling Behavior", () => {
593
595
  expect(extractedParams).toEqual({ id: "123" });
594
596
  });
595
597
  });
598
+ describe("HTTP Method Routing", () => {
599
+ it("should route GET request to get handler", async () => {
600
+ const router = defineRoutes([
601
+ route("/test/", {
602
+ get: () => new Response("GET Response"),
603
+ post: () => new Response("POST Response"),
604
+ }),
605
+ ]);
606
+ const deps = createMockDependencies();
607
+ deps.mockRequestInfo.request = new Request("http://localhost:3000/test/", {
608
+ method: "GET",
609
+ });
610
+ const request = new Request("http://localhost:3000/test/", {
611
+ method: "GET",
612
+ });
613
+ const response = await router.handle({
614
+ request,
615
+ renderPage: deps.mockRenderPage,
616
+ getRequestInfo: deps.getRequestInfo,
617
+ onError: deps.onError,
618
+ runWithRequestInfoOverrides: deps.mockRunWithRequestInfoOverrides,
619
+ rscActionHandler: deps.mockRscActionHandler,
620
+ });
621
+ expect(await response.text()).toBe("GET Response");
622
+ });
623
+ it("should route POST request to post handler", async () => {
624
+ const router = defineRoutes([
625
+ route("/test/", {
626
+ get: () => new Response("GET Response"),
627
+ post: () => new Response("POST Response"),
628
+ }),
629
+ ]);
630
+ const deps = createMockDependencies();
631
+ deps.mockRequestInfo.request = new Request("http://localhost:3000/test/", {
632
+ method: "POST",
633
+ });
634
+ const request = new Request("http://localhost:3000/test/", {
635
+ method: "POST",
636
+ });
637
+ const response = await router.handle({
638
+ request,
639
+ renderPage: deps.mockRenderPage,
640
+ getRequestInfo: deps.getRequestInfo,
641
+ onError: deps.onError,
642
+ runWithRequestInfoOverrides: deps.mockRunWithRequestInfoOverrides,
643
+ rscActionHandler: deps.mockRscActionHandler,
644
+ });
645
+ expect(await response.text()).toBe("POST Response");
646
+ });
647
+ it("should return 405 for unsupported method with Allow header", async () => {
648
+ const router = defineRoutes([
649
+ route("/test/", {
650
+ get: () => new Response("GET Response"),
651
+ post: () => new Response("POST Response"),
652
+ }),
653
+ ]);
654
+ const deps = createMockDependencies();
655
+ deps.mockRequestInfo.request = new Request("http://localhost:3000/test/", {
656
+ method: "DELETE",
657
+ });
658
+ const request = new Request("http://localhost:3000/test/", {
659
+ method: "DELETE",
660
+ });
661
+ const response = await router.handle({
662
+ request,
663
+ renderPage: deps.mockRenderPage,
664
+ getRequestInfo: deps.getRequestInfo,
665
+ onError: deps.onError,
666
+ runWithRequestInfoOverrides: deps.mockRunWithRequestInfoOverrides,
667
+ rscActionHandler: deps.mockRscActionHandler,
668
+ });
669
+ expect(response.status).toBe(405);
670
+ expect(await response.text()).toBe("Method Not Allowed");
671
+ expect(response.headers.get("Allow")).toBe("GET, OPTIONS, POST");
672
+ });
673
+ it("should handle OPTIONS request with Allow header", async () => {
674
+ const router = defineRoutes([
675
+ route("/test/", {
676
+ get: () => new Response("GET Response"),
677
+ post: () => new Response("POST Response"),
678
+ }),
679
+ ]);
680
+ const deps = createMockDependencies();
681
+ deps.mockRequestInfo.request = new Request("http://localhost:3000/test/", {
682
+ method: "OPTIONS",
683
+ });
684
+ const request = new Request("http://localhost:3000/test/", {
685
+ method: "OPTIONS",
686
+ });
687
+ const response = await router.handle({
688
+ request,
689
+ renderPage: deps.mockRenderPage,
690
+ getRequestInfo: deps.getRequestInfo,
691
+ onError: deps.onError,
692
+ runWithRequestInfoOverrides: deps.mockRunWithRequestInfoOverrides,
693
+ rscActionHandler: deps.mockRscActionHandler,
694
+ });
695
+ expect(response.status).toBe(204);
696
+ expect(response.headers.get("Allow")).toBe("GET, OPTIONS, POST");
697
+ });
698
+ it("should support custom methods (case-insensitive)", async () => {
699
+ const router = defineRoutes([
700
+ route("/test/", {
701
+ custom: {
702
+ report: () => new Response("REPORT Response"),
703
+ },
704
+ }),
705
+ ]);
706
+ const deps = createMockDependencies();
707
+ deps.mockRequestInfo.request = new Request("http://localhost:3000/test/", {
708
+ method: "REPORT",
709
+ });
710
+ const request = new Request("http://localhost:3000/test/", {
711
+ method: "REPORT",
712
+ });
713
+ const response = await router.handle({
714
+ request,
715
+ renderPage: deps.mockRenderPage,
716
+ getRequestInfo: deps.getRequestInfo,
717
+ onError: deps.onError,
718
+ runWithRequestInfoOverrides: deps.mockRunWithRequestInfoOverrides,
719
+ rscActionHandler: deps.mockRscActionHandler,
720
+ });
721
+ expect(await response.text()).toBe("REPORT Response");
722
+ });
723
+ it("should normalize custom method keys to lowercase", async () => {
724
+ const router = defineRoutes([
725
+ route("/test/", {
726
+ custom: {
727
+ REPORT: () => new Response("REPORT Response"),
728
+ },
729
+ }),
730
+ ]);
731
+ const deps = createMockDependencies();
732
+ deps.mockRequestInfo.request = new Request("http://localhost:3000/test/", {
733
+ method: "report",
734
+ });
735
+ const request = new Request("http://localhost:3000/test/", {
736
+ method: "report",
737
+ });
738
+ const response = await router.handle({
739
+ request,
740
+ renderPage: deps.mockRenderPage,
741
+ getRequestInfo: deps.getRequestInfo,
742
+ onError: deps.onError,
743
+ runWithRequestInfoOverrides: deps.mockRunWithRequestInfoOverrides,
744
+ rscActionHandler: deps.mockRscActionHandler,
745
+ });
746
+ expect(await response.text()).toBe("REPORT Response");
747
+ });
748
+ it("should disable 405 when config.disable405 is true", async () => {
749
+ const router = defineRoutes([
750
+ route("/test/", {
751
+ get: () => new Response("GET Response"),
752
+ config: {
753
+ disable405: true,
754
+ },
755
+ }),
756
+ ]);
757
+ const deps = createMockDependencies();
758
+ deps.mockRequestInfo.request = new Request("http://localhost:3000/test/", {
759
+ method: "POST",
760
+ });
761
+ const request = new Request("http://localhost:3000/test/", {
762
+ method: "POST",
763
+ });
764
+ const response = await router.handle({
765
+ request,
766
+ renderPage: deps.mockRenderPage,
767
+ getRequestInfo: deps.getRequestInfo,
768
+ onError: deps.onError,
769
+ runWithRequestInfoOverrides: deps.mockRunWithRequestInfoOverrides,
770
+ rscActionHandler: deps.mockRscActionHandler,
771
+ });
772
+ expect(response.status).toBe(404);
773
+ expect(await response.text()).toBe("Not Found");
774
+ });
775
+ it("should disable OPTIONS when config.disableOptions is true", async () => {
776
+ const router = defineRoutes([
777
+ route("/test/", {
778
+ get: () => new Response("GET Response"),
779
+ post: () => new Response("POST Response"),
780
+ config: {
781
+ disableOptions: true,
782
+ },
783
+ }),
784
+ ]);
785
+ const deps = createMockDependencies();
786
+ deps.mockRequestInfo.request = new Request("http://localhost:3000/test/", {
787
+ method: "OPTIONS",
788
+ });
789
+ const request = new Request("http://localhost:3000/test/", {
790
+ method: "OPTIONS",
791
+ });
792
+ const response = await router.handle({
793
+ request,
794
+ renderPage: deps.mockRenderPage,
795
+ getRequestInfo: deps.getRequestInfo,
796
+ onError: deps.onError,
797
+ runWithRequestInfoOverrides: deps.mockRunWithRequestInfoOverrides,
798
+ rscActionHandler: deps.mockRscActionHandler,
799
+ });
800
+ expect(response.status).toBe(405);
801
+ expect(await response.text()).toBe("Method Not Allowed");
802
+ expect(response.headers.get("Allow")).toBe("GET, POST");
803
+ });
804
+ it("should support middleware arrays in method handlers", async () => {
805
+ const executionOrder = [];
806
+ const authMiddleware = () => {
807
+ executionOrder.push("authMiddleware");
808
+ };
809
+ const getHandler = () => {
810
+ executionOrder.push("getHandler");
811
+ return new Response("GET Response");
812
+ };
813
+ const router = defineRoutes([
814
+ route("/test/", {
815
+ get: [authMiddleware, getHandler],
816
+ }),
817
+ ]);
818
+ const deps = createMockDependencies();
819
+ deps.mockRequestInfo.request = new Request("http://localhost:3000/test/", {
820
+ method: "GET",
821
+ });
822
+ const request = new Request("http://localhost:3000/test/", {
823
+ method: "GET",
824
+ });
825
+ const response = await router.handle({
826
+ request,
827
+ renderPage: deps.mockRenderPage,
828
+ getRequestInfo: deps.getRequestInfo,
829
+ onError: deps.onError,
830
+ runWithRequestInfoOverrides: deps.mockRunWithRequestInfoOverrides,
831
+ rscActionHandler: deps.mockRscActionHandler,
832
+ });
833
+ expect(executionOrder).toEqual(["authMiddleware", "getHandler"]);
834
+ expect(await response.text()).toBe("GET Response");
835
+ });
836
+ });
596
837
  describe("Edge Cases", () => {
597
838
  it("should handle middleware-only apps with RSC actions", async () => {
598
839
  const executionOrder = [];
@@ -1,4 +1,5 @@
1
1
  import { type Kysely } from "kysely";
2
+ import React from "react";
2
3
  import { type RequestInfo } from "../requestInfo/types.js";
3
4
  export type RwContext = {
4
5
  nonce: string;
@@ -1,4 +1,4 @@
1
- import { type DocumentProps } from "../lib/router.js";
1
+ import { type DocumentProps } from "../lib/types.js";
2
2
  import { type RequestInfo } from "../requestInfo/types.js";
3
3
  export declare const renderDocumentHtmlStream: ({ rscPayloadStream, Document, requestInfo, shouldSSR, onError, }: {
4
4
  rscPayloadStream: ReadableStream;
@@ -1,10 +1,12 @@
1
1
  import { FC, ReactElement } from "react";
2
- import { DocumentProps } from "../lib/router";
2
+ import { DocumentProps } from "../lib/types.js";
3
+ import { type PartialRequestInfo } from "../requestInfo/types";
3
4
  export interface RenderToStreamOptions {
4
5
  Document?: FC<DocumentProps>;
5
6
  ssr?: boolean;
6
7
  injectRSCPayload?: boolean;
7
8
  onError?: (error: unknown) => void;
9
+ requestInfo?: PartialRequestInfo;
8
10
  }
9
11
  export declare const IdentityDocument: FC<DocumentProps>;
10
- export declare const renderToStream: (element: ReactElement, { ssr: shouldSSR, Document, injectRSCPayload: shouldInjectRSCPayload, onError, }?: RenderToStreamOptions) => Promise<ReadableStream>;
12
+ export declare const renderToStream: (element: ReactElement, { ssr: shouldSSR, Document, injectRSCPayload: shouldInjectRSCPayload, requestInfo: givenRequestInfo, onError, }?: RenderToStreamOptions) => Promise<ReadableStream>;
@@ -1,10 +1,29 @@
1
1
  import { Fragment as _Fragment, jsx as _jsx } from "react/jsx-runtime";
2
2
  import { injectRSCPayload } from "rsc-html-stream/server";
3
- import { requestInfo } from "../requestInfo/worker";
3
+ import { constructWithDefaultRequestInfo } from "../requestInfo/utils";
4
+ import { getRequestInfo } from "../requestInfo/worker";
4
5
  import { renderDocumentHtmlStream } from "./renderDocumentHtmlStream";
5
6
  import { renderToRscStream } from "./renderToRscStream";
6
7
  export const IdentityDocument = ({ children }) => (_jsx(_Fragment, { children: children }));
7
- export const renderToStream = async (element, { ssr: shouldSSR = true, Document = IdentityDocument, injectRSCPayload: shouldInjectRSCPayload = true, onError = () => { }, } = {}) => {
8
+ export const renderToStream = async (element, { ssr: shouldSSR = true, Document = IdentityDocument, injectRSCPayload: shouldInjectRSCPayload = true, requestInfo: givenRequestInfo, onError = () => { }, } = {}) => {
9
+ // Try to get the context requestInfo from the async store.
10
+ let contextRequestInfo;
11
+ try {
12
+ contextRequestInfo = getRequestInfo();
13
+ }
14
+ catch (e) {
15
+ // No requestInfo detected from store.
16
+ }
17
+ // Construct requestInfo with defaults where overrides take precedence.
18
+ // If provided, `givenRequestInfo` will override values from context requestInfo if it exists
19
+ const requestInfo = constructWithDefaultRequestInfo({
20
+ ...contextRequestInfo,
21
+ ...givenRequestInfo,
22
+ rw: {
23
+ ...contextRequestInfo?.rw,
24
+ ...givenRequestInfo?.rw,
25
+ },
26
+ });
8
27
  let rscStream = renderToRscStream({
9
28
  input: {
10
29
  node: element,
@@ -1,7 +1,9 @@
1
1
  import { FC, ReactElement } from "react";
2
- import { DocumentProps } from "../lib/router";
2
+ import { DocumentProps } from "../lib/types.js";
3
+ import { type PartialRequestInfo } from "../requestInfo/types";
3
4
  export interface RenderToStringOptions {
4
5
  Document?: FC<DocumentProps>;
5
6
  injectRSCPayload?: boolean;
7
+ requestInfo?: PartialRequestInfo;
6
8
  }
7
9
  export declare const renderToString: (element: ReactElement, options?: RenderToStringOptions) => Promise<string>;
@@ -1,4 +1,4 @@
1
- import { RwContext } from "../lib/router";
1
+ import { RwContext } from "../lib/types.js";
2
2
  export interface DefaultAppContext {
3
3
  }
4
4
  export interface RequestInfo<Params = any, AppContext = DefaultAppContext> {
@@ -12,3 +12,6 @@ export interface RequestInfo<Params = any, AppContext = DefaultAppContext> {
12
12
  };
13
13
  isAction: boolean;
14
14
  }
15
+ export type PartialRequestInfo<Params = any, AppContext = DefaultAppContext> = Omit<Partial<RequestInfo<Params, AppContext>>, "rw"> & {
16
+ rw?: Partial<RwContext>;
17
+ };
@@ -0,0 +1,9 @@
1
+ import { FC } from "react";
2
+ import { type DocumentProps } from "../lib/types";
3
+ import { type PartialRequestInfo, type RequestInfo } from "./types";
4
+ export declare const DefaultRequestInfoDocument: FC<DocumentProps>;
5
+ /**
6
+ * Constructs a generic requestInfo that can be used as defaults.
7
+ * Allows for passing in overrides to initialize with defaults.
8
+ */
9
+ export declare const constructWithDefaultRequestInfo: (overrides?: PartialRequestInfo) => RequestInfo;
@@ -0,0 +1,44 @@
1
+ import { Fragment as _Fragment, jsx as _jsx } from "react/jsx-runtime";
2
+ import { generateNonce } from "../lib/utils";
3
+ export const DefaultRequestInfoDocument = ({ children }) => (_jsx(_Fragment, { children: children }));
4
+ /**
5
+ * Constructs a generic requestInfo that can be used as defaults.
6
+ * Allows for passing in overrides to initialize with defaults.
7
+ */
8
+ export const constructWithDefaultRequestInfo = (overrides = {}) => {
9
+ const { rw: rwOverrides, ...otherRequestInfoOverrides } = overrides;
10
+ const defaultRequestInfo = {
11
+ request: new Request("http://localhost/"),
12
+ params: {},
13
+ ctx: {},
14
+ cf: {
15
+ waitUntil: () => { },
16
+ passThroughOnException: () => { },
17
+ props: {},
18
+ },
19
+ response: {
20
+ status: 200,
21
+ headers: new Headers(),
22
+ },
23
+ isAction: false,
24
+ rw: {
25
+ Document: DefaultRequestInfoDocument,
26
+ nonce: generateNonce(),
27
+ rscPayload: true,
28
+ ssr: true,
29
+ databases: new Map(),
30
+ scriptsToBeLoaded: new Set(),
31
+ entryScripts: new Set(),
32
+ inlineScripts: new Set(),
33
+ pageRouteResolved: undefined,
34
+ },
35
+ };
36
+ return {
37
+ ...defaultRequestInfo,
38
+ ...otherRequestInfoOverrides,
39
+ rw: {
40
+ ...defaultRequestInfo.rw,
41
+ ...rwOverrides,
42
+ },
43
+ };
44
+ };
@@ -1,6 +1,7 @@
1
1
  import { AsyncLocalStorage } from "async_hooks";
2
- const requestInfoDeferred = Promise.withResolvers();
3
- const requestInfoStore = new AsyncLocalStorage();
2
+ import { defineRwState } from "rwsdk/__state";
3
+ const requestInfoDeferred = defineRwState("requestInfoDeferred", () => Promise.withResolvers());
4
+ const requestInfoStore = defineRwState("requestInfoStore", () => new AsyncLocalStorage());
4
5
  const requestInfoBase = {};
5
6
  const REQUEST_INFO_KEYS = ["request", "params", "ctx", "rw", "cf", "response"];
6
7
  REQUEST_INFO_KEYS.forEach((key) => {
@@ -1,5 +1,3 @@
1
1
  export declare const defineScript: (fn: ({ env }: {
2
2
  env: Env;
3
- }) => Promise<unknown>) => {
4
- fetch: (request: Request, env: Env, cf: ExecutionContext) => Promise<Response>;
5
- };
3
+ }) => Promise<unknown>) => () => Promise<unknown>;
@@ -1,11 +1,2 @@
1
1
  import { env } from "cloudflare:workers";
2
- import { defineApp } from "./worker";
3
- export const defineScript = (fn) => {
4
- const app = defineApp([
5
- async () => {
6
- await fn({ env: env });
7
- return new Response("Done!");
8
- },
9
- ]);
10
- return app;
11
- };
2
+ export const defineScript = (fn) => () => fn({ env: env });
@@ -0,0 +1,3 @@
1
+ export declare function defineRwState<T>(key: string, initializer: () => T): T;
2
+ export declare function getRwState<T>(key: string): T | undefined;
3
+ export declare function setRwState<T>(key: string, value: T): void;
@@ -0,0 +1,13 @@
1
+ const state = {};
2
+ export function defineRwState(key, initializer) {
3
+ if (!(key in state)) {
4
+ state[key] = initializer();
5
+ }
6
+ return state[key];
7
+ }
8
+ export function getRwState(key) {
9
+ return state[key];
10
+ }
11
+ export function setRwState(key, value) {
12
+ state[key] = value;
13
+ }
@@ -23,6 +23,29 @@ export const defineApp = (routes) => {
23
23
  url.pathname = url.pathname.slice("/assets/".length);
24
24
  return env.ASSETS.fetch(new Request(url.toString(), request));
25
25
  }
26
+ else if (import.meta.env.VITE_IS_DEV_SERVER &&
27
+ new URL(request.url).pathname === "/__worker-run") {
28
+ const url = new URL(request.url);
29
+ const scriptPath = url.searchParams.get("script");
30
+ if (!scriptPath) {
31
+ return new Response("Missing 'script' query parameter", {
32
+ status: 400,
33
+ });
34
+ }
35
+ try {
36
+ const scriptModule = await import(/* @vite-ignore */ scriptPath);
37
+ if (scriptModule.default) {
38
+ await scriptModule.default(request, env, cf);
39
+ }
40
+ return new Response("Script executed successfully");
41
+ }
42
+ catch (e) {
43
+ console.error(`Error executing script: ${scriptPath}\n\n${e.stack}`);
44
+ return new Response(`Error executing script: ${e.message}`, {
45
+ status: 500,
46
+ });
47
+ }
48
+ }
26
49
  else if (import.meta.env.VITE_IS_DEV_SERVER &&
27
50
  request.url.includes("/__vite_preamble__")) {
28
51
  return new Response('import RefreshRuntime from "/@react-refresh"; RefreshRuntime.injectIntoGlobalHook(window); window.$RefreshReg$ = () => {}; window.$RefreshSig$ = () => (type) => type; window.__vite_plugin_react_preamble_installed__ = true;', {
@@ -43,6 +66,8 @@ export const defineApp = (routes) => {
43
66
  ssr: true,
44
67
  databases: new Map(),
45
68
  scriptsToBeLoaded: new Set(),
69
+ entryScripts: new Set(),
70
+ inlineScripts: new Set(),
46
71
  pageRouteResolved: undefined,
47
72
  };
48
73
  const userResponseInit = {