rwsdk 1.0.0-alpha.6 → 1.0.0-alpha.8

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 (56) hide show
  1. package/dist/lib/e2e/browser.d.mts +10 -0
  2. package/dist/lib/e2e/browser.mjs +107 -0
  3. package/dist/lib/e2e/dev.d.mts +8 -0
  4. package/dist/lib/e2e/dev.mjs +232 -0
  5. package/dist/lib/e2e/environment.d.mts +14 -0
  6. package/dist/lib/e2e/environment.mjs +201 -0
  7. package/dist/lib/e2e/index.d.mts +7 -0
  8. package/dist/lib/e2e/index.mjs +7 -0
  9. package/dist/lib/e2e/release.d.mts +56 -0
  10. package/dist/lib/e2e/release.mjs +537 -0
  11. package/dist/lib/e2e/tarball.d.mts +14 -0
  12. package/dist/lib/e2e/tarball.mjs +189 -0
  13. package/dist/lib/e2e/testHarness.d.mts +98 -0
  14. package/dist/lib/e2e/testHarness.mjs +393 -0
  15. package/dist/lib/e2e/types.d.mts +31 -0
  16. package/dist/lib/e2e/types.mjs +1 -0
  17. package/dist/lib/smokeTests/browser.mjs +3 -94
  18. package/dist/lib/smokeTests/development.mjs +2 -223
  19. package/dist/lib/smokeTests/environment.d.mts +4 -11
  20. package/dist/lib/smokeTests/environment.mjs +10 -158
  21. package/dist/lib/smokeTests/release.d.mts +2 -49
  22. package/dist/lib/smokeTests/release.mjs +3 -503
  23. package/dist/runtime/lib/injectHtmlAtMarker.d.ts +11 -0
  24. package/dist/runtime/lib/injectHtmlAtMarker.js +90 -0
  25. package/dist/runtime/lib/realtime/worker.d.ts +1 -1
  26. package/dist/runtime/lib/router.js +8 -2
  27. package/dist/runtime/lib/router.test.js +85 -1
  28. package/dist/runtime/lib/rwContext.d.ts +22 -0
  29. package/dist/runtime/lib/rwContext.js +1 -0
  30. package/dist/runtime/render/assembleDocument.d.ts +6 -0
  31. package/dist/runtime/render/assembleDocument.js +22 -0
  32. package/dist/runtime/render/createThenableFromReadableStream.d.ts +1 -0
  33. package/dist/runtime/render/createThenableFromReadableStream.js +9 -0
  34. package/dist/runtime/render/normalizeActionResult.d.ts +1 -0
  35. package/dist/runtime/render/normalizeActionResult.js +43 -0
  36. package/dist/runtime/render/preloads.d.ts +2 -2
  37. package/dist/runtime/render/preloads.js +2 -3
  38. package/dist/runtime/render/{renderRscThenableToHtmlStream.d.ts → renderDocumentHtmlStream.d.ts} +3 -3
  39. package/dist/runtime/render/renderDocumentHtmlStream.js +39 -0
  40. package/dist/runtime/render/renderHtmlStream.d.ts +7 -0
  41. package/dist/runtime/render/renderHtmlStream.js +31 -0
  42. package/dist/runtime/render/renderToRscStream.d.ts +2 -3
  43. package/dist/runtime/render/renderToRscStream.js +2 -41
  44. package/dist/runtime/render/renderToStream.d.ts +2 -1
  45. package/dist/runtime/render/renderToStream.js +15 -8
  46. package/dist/runtime/render/stylesheets.d.ts +2 -2
  47. package/dist/runtime/render/stylesheets.js +2 -3
  48. package/dist/runtime/ssrBridge.d.ts +2 -1
  49. package/dist/runtime/ssrBridge.js +2 -1
  50. package/dist/runtime/worker.d.ts +1 -0
  51. package/dist/runtime/worker.js +11 -6
  52. package/dist/vite/configPlugin.mjs +2 -2
  53. package/package.json +8 -4
  54. package/dist/runtime/render/renderRscThenableToHtmlStream.js +0 -54
  55. package/dist/runtime/render/transformRscToHtmlStream.d.ts +0 -8
  56. package/dist/runtime/render/transformRscToHtmlStream.js +0 -19
@@ -1,6 +1,6 @@
1
1
  import { describe, it, expect } from "vitest";
2
2
  import React from "react";
3
- import { matchPath, defineRoutes, route, render, layout } from "./router";
3
+ import { matchPath, defineRoutes, route, render, layout, prefix, } from "./router";
4
4
  describe("matchPath", () => {
5
5
  // Test case 1: Static paths
6
6
  it("should match static paths", () => {
@@ -209,6 +209,90 @@ describe("defineRoutes - Request Handling Behavior", () => {
209
209
  expect(await response.text()).toBe("Rendered: Element");
210
210
  });
211
211
  });
212
+ describe("Prefix Handling", () => {
213
+ it("should only run middleware within the specified prefix", async () => {
214
+ const executionOrder = [];
215
+ const prefixedMiddleware = () => {
216
+ executionOrder.push("prefixedMiddleware");
217
+ };
218
+ const PageComponent = () => {
219
+ executionOrder.push("PageComponent");
220
+ return React.createElement("div", {}, "Page");
221
+ };
222
+ const AdminPageComponent = () => {
223
+ executionOrder.push("AdminPageComponent");
224
+ return React.createElement("div", {}, "Admin Page");
225
+ };
226
+ const router = defineRoutes([
227
+ ...prefix("/admin", [
228
+ prefixedMiddleware,
229
+ route("/", AdminPageComponent),
230
+ ]),
231
+ route("/", PageComponent),
232
+ ]);
233
+ const deps = createMockDependencies();
234
+ // Test 1: Request to a path outside the prefix
235
+ deps.mockRequestInfo.request = new Request("http://localhost:3000/");
236
+ const request1 = new Request("http://localhost:3000/");
237
+ await router.handle({
238
+ request: request1,
239
+ renderPage: deps.mockRenderPage,
240
+ getRequestInfo: deps.getRequestInfo,
241
+ onError: deps.onError,
242
+ runWithRequestInfoOverrides: deps.mockRunWithRequestInfoOverrides,
243
+ rscActionHandler: deps.mockRscActionHandler,
244
+ });
245
+ expect(executionOrder).toEqual(["PageComponent"]);
246
+ // Reset execution order
247
+ executionOrder.length = 0;
248
+ // Test 2: Request to a path inside the prefix
249
+ deps.mockRequestInfo.request = new Request("http://localhost:3000/admin/");
250
+ const request2 = new Request("http://localhost:3000/admin/");
251
+ await router.handle({
252
+ request: request2,
253
+ renderPage: deps.mockRenderPage,
254
+ getRequestInfo: deps.getRequestInfo,
255
+ onError: deps.onError,
256
+ runWithRequestInfoOverrides: deps.mockRunWithRequestInfoOverrides,
257
+ rscActionHandler: deps.mockRscActionHandler,
258
+ });
259
+ expect(executionOrder).toEqual([
260
+ "prefixedMiddleware",
261
+ "AdminPageComponent",
262
+ ]);
263
+ });
264
+ it("should short-circuit from a prefixed middleware", async () => {
265
+ const executionOrder = [];
266
+ const prefixedMiddleware = () => {
267
+ executionOrder.push("prefixedMiddleware");
268
+ return new Response("From prefixed middleware");
269
+ };
270
+ const AdminPageComponent = () => {
271
+ executionOrder.push("AdminPageComponent");
272
+ return React.createElement("div", {}, "Admin Page");
273
+ };
274
+ const router = defineRoutes([
275
+ ...prefix("/admin", [
276
+ prefixedMiddleware,
277
+ route("/", AdminPageComponent),
278
+ ]),
279
+ ]);
280
+ const deps = createMockDependencies();
281
+ // Request to a path inside the prefix
282
+ deps.mockRequestInfo.request = new Request("http://localhost:3000/admin/");
283
+ const request = new Request("http://localhost:3000/admin/");
284
+ const response = await router.handle({
285
+ request,
286
+ renderPage: deps.mockRenderPage,
287
+ getRequestInfo: deps.getRequestInfo,
288
+ onError: deps.onError,
289
+ runWithRequestInfoOverrides: deps.mockRunWithRequestInfoOverrides,
290
+ rscActionHandler: deps.mockRscActionHandler,
291
+ });
292
+ expect(executionOrder).toEqual(["prefixedMiddleware"]);
293
+ expect(await response.text()).toBe("From prefixed middleware");
294
+ });
295
+ });
212
296
  describe("RSC Action Handling", () => {
213
297
  it("should handle RSC actions before the first route definition", async () => {
214
298
  const executionOrder = [];
@@ -0,0 +1,22 @@
1
+ import { type RequestInfo } from "../requestInfo/types.js";
2
+ import { type Kysely } from "kysely";
3
+ export type RwContext = {
4
+ nonce: string;
5
+ Document: React.FC<DocumentProps<any>>;
6
+ rscPayload: boolean;
7
+ ssr: boolean;
8
+ layouts?: React.FC<LayoutProps<any>>[];
9
+ databases: Map<string, Kysely<any>>;
10
+ scriptsToBeLoaded: Set<string>;
11
+ entryScripts: Set<string>;
12
+ inlineScripts: Set<string>;
13
+ pageRouteResolved: PromiseWithResolvers<void> | undefined;
14
+ actionResult?: unknown;
15
+ };
16
+ export type DocumentProps<T extends RequestInfo = RequestInfo> = T & {
17
+ children: React.ReactNode;
18
+ };
19
+ export type LayoutProps<T extends RequestInfo = RequestInfo> = {
20
+ children?: React.ReactNode;
21
+ requestInfo?: T;
22
+ };
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,6 @@
1
+ import { type RequestInfo } from "../requestInfo/types.js";
2
+ export declare const assembleDocument: ({ requestInfo, pageElement, shouldSSR, }: {
3
+ requestInfo: RequestInfo;
4
+ pageElement: React.ReactNode;
5
+ shouldSSR: boolean;
6
+ }) => import("react/jsx-runtime.js").JSX.Element;
@@ -0,0 +1,22 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { Preloads } from "./preloads.js";
3
+ import { Stylesheets } from "./stylesheets.js";
4
+ // Note: This is a server component, even though it doesn't have the "use server"
5
+ // directive. It's intended to be imported and used within the RSC render pass.
6
+ export const assembleDocument = ({ requestInfo, pageElement, shouldSSR, }) => {
7
+ // todo(justinvdm, 18 Jun 2025): We can build on this later to allow users
8
+ // surface context. e.g:
9
+ // * we assign `user: requestInfo.clientCtx` here
10
+ // * user populates requestInfo.clientCtx on worker side
11
+ // * user can import a read only `import { clientCtx } from "rwsdk/client"`
12
+ // on client side
13
+ const clientContext = {
14
+ rw: {
15
+ ssr: shouldSSR,
16
+ },
17
+ };
18
+ const Document = requestInfo.rw.Document;
19
+ return (_jsxs(Document, { ...requestInfo, children: [_jsx("script", { nonce: requestInfo.rw.nonce, dangerouslySetInnerHTML: {
20
+ __html: `globalThis.__RWSDK_CONTEXT = ${JSON.stringify(clientContext)}`,
21
+ } }), _jsx(Stylesheets, { requestInfo: requestInfo }), _jsx(Preloads, { requestInfo: requestInfo }), _jsx("div", { id: "hydrate-root", children: pageElement })] }));
22
+ };
@@ -0,0 +1 @@
1
+ export declare const createThenableFromReadableStream: (stream: ReadableStream) => Thenable<T>;
@@ -0,0 +1,9 @@
1
+ import { createModuleMap } from "./createModuleMap.js";
2
+ import ReactServerDom from "react-server-dom-webpack/client.edge";
3
+ const { createFromReadableStream } = ReactServerDom;
4
+ export const createThenableFromReadableStream = (stream) => createFromReadableStream(stream, {
5
+ serverConsumerManifest: {
6
+ moduleMap: createModuleMap(),
7
+ moduleLoading: null,
8
+ },
9
+ });
@@ -0,0 +1 @@
1
+ export declare const normalizeActionResult: (actionResult: any) => any;
@@ -0,0 +1,43 @@
1
+ // context(justinvdm, 24 Mar 2025): React flight limits chunks to 28 bytes, so we need to rechunk
2
+ // the stream to avoid losing data
3
+ function rechunkStream(stream, maxChunkSize = 28) {
4
+ const reader = stream.getReader();
5
+ return new ReadableStream({
6
+ async pull(controller) {
7
+ let buffer = new Uint8Array(0);
8
+ try {
9
+ while (true) {
10
+ const { done, value } = await reader.read();
11
+ if (done && buffer.length === 0) {
12
+ controller.close();
13
+ return;
14
+ }
15
+ if (value) {
16
+ buffer = new Uint8Array([...buffer, ...value]);
17
+ }
18
+ while (buffer.length >= maxChunkSize || (done && buffer.length > 0)) {
19
+ const chunk = buffer.slice(0, maxChunkSize);
20
+ buffer = buffer.slice(maxChunkSize);
21
+ controller.enqueue(chunk);
22
+ }
23
+ if (done) {
24
+ controller.close();
25
+ return;
26
+ }
27
+ }
28
+ }
29
+ catch (error) {
30
+ controller.error(error);
31
+ }
32
+ },
33
+ });
34
+ }
35
+ export const normalizeActionResult = (actionResult) => {
36
+ if (actionResult instanceof Response) {
37
+ return null;
38
+ }
39
+ if (actionResult instanceof ReadableStream) {
40
+ return rechunkStream(actionResult);
41
+ }
42
+ return actionResult;
43
+ };
@@ -1,6 +1,6 @@
1
1
  import type { RequestInfo } from "../requestInfo/types.js";
2
2
  import type { Manifest, ManifestChunk } from "../lib/manifest.js";
3
3
  export declare function findScriptForModule(id: string, manifest: Manifest): ManifestChunk | undefined;
4
- export declare const Preloads: ({ requestInfo }: {
4
+ export declare const Preloads: ({ requestInfo, }: {
5
5
  requestInfo: RequestInfo;
6
- }) => import("react/jsx-runtime.js").JSX.Element;
6
+ }) => Promise<import("react/jsx-runtime.js").JSX.Element>;
@@ -1,5 +1,4 @@
1
1
  import { jsx as _jsx, Fragment as _Fragment } from "react/jsx-runtime";
2
- import { use } from "react";
3
2
  import { getManifest } from "../lib/manifest.js";
4
3
  export function findScriptForModule(id, manifest) {
5
4
  const visited = new Set();
@@ -27,8 +26,8 @@ export function findScriptForModule(id, manifest) {
27
26
  }
28
27
  return find(id);
29
28
  }
30
- export const Preloads = ({ requestInfo }) => {
31
- const manifest = use(getManifest());
29
+ export const Preloads = async ({ requestInfo, }) => {
30
+ const manifest = await getManifest();
32
31
  const allScripts = new Set();
33
32
  for (const scriptId of requestInfo.rw.scriptsToBeLoaded) {
34
33
  const script = findScriptForModule(scriptId, manifest);
@@ -1,9 +1,9 @@
1
1
  import { type DocumentProps } from "../lib/router.js";
2
2
  import { type RequestInfo } from "../requestInfo/types.js";
3
- export declare const renderRscThenableToHtmlStream: ({ thenable, Document, requestInfo, shouldSSR, onError, }: {
4
- thenable: any;
3
+ export declare const renderDocumentHtmlStream: ({ rscPayloadStream, Document, requestInfo, shouldSSR, onError, }: {
4
+ rscPayloadStream: ReadableStream;
5
5
  Document: React.FC<DocumentProps>;
6
6
  requestInfo: RequestInfo;
7
7
  shouldSSR: boolean;
8
8
  onError: (error: unknown) => void;
9
- }) => Promise<import("react-dom/server.js").ReactDOMServerReadableStream>;
9
+ }) => Promise<ReadableStream<Uint8Array<ArrayBufferLike>>>;
@@ -0,0 +1,39 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { Preloads } from "./preloads.js";
3
+ import { Stylesheets } from "./stylesheets.js";
4
+ import { renderHtmlStream, createThenableFromReadableStream, } from "rwsdk/__ssr_bridge";
5
+ import { injectHtmlAtMarker } from "../lib/injectHtmlAtMarker.js";
6
+ export const renderDocumentHtmlStream = async ({ rscPayloadStream, Document, requestInfo, shouldSSR, onError, }) => {
7
+ // Extract the app node from the RSC payload
8
+ const rscAppThenable = createThenableFromReadableStream(rscPayloadStream);
9
+ const { node: appNode } = (await rscAppThenable);
10
+ // todo(justinvdm, 18 Jun 2025): We can build on this later to allow users
11
+ // surface context. e.g:
12
+ // * we assign `user: requestInfo.clientCtx` here
13
+ // * user populates requestInfo.clientCtx on worker side
14
+ // * user can import a read only `import { clientCtx } from "rwsdk/client"`
15
+ // on client side
16
+ const clientContext = {
17
+ rw: {
18
+ ssr: shouldSSR,
19
+ },
20
+ };
21
+ // Create the outer document with a marker for injection
22
+ const documentElement = (_jsxs(Document, { ...requestInfo, children: [_jsx("script", { nonce: requestInfo.rw.nonce, dangerouslySetInnerHTML: {
23
+ __html: `globalThis.__RWSDK_CONTEXT = ${JSON.stringify(clientContext)}`,
24
+ } }), _jsx(Stylesheets, { requestInfo: requestInfo }), _jsx(Preloads, { requestInfo: requestInfo }), _jsx("div", { id: "hydrate-root", dangerouslySetInnerHTML: { __html: "<!-- RWSDK_INJECT_APP_HTML -->" } })] }));
25
+ const outerHtmlStream = await renderHtmlStream({
26
+ node: documentElement,
27
+ requestInfo,
28
+ onError,
29
+ identifierPrefix: "__RWSDK_DOCUMENT__",
30
+ });
31
+ const appHtmlStream = await renderHtmlStream({
32
+ node: appNode,
33
+ requestInfo,
34
+ onError,
35
+ });
36
+ // Stitch the streams together
37
+ const stitchedStream = injectHtmlAtMarker(outerHtmlStream, appHtmlStream, "<!-- RWSDK_INJECT_APP_HTML -->");
38
+ return stitchedStream;
39
+ };
@@ -0,0 +1,7 @@
1
+ import { type RequestInfo } from "../requestInfo/types.js";
2
+ export declare const renderHtmlStream: ({ node, identifierPrefix, requestInfo, onError, }: {
3
+ node: React.ReactNode;
4
+ requestInfo: RequestInfo;
5
+ onError: (error: unknown) => void;
6
+ identifierPrefix?: string;
7
+ }) => Promise<import("react-dom/server.js").ReactDOMServerReadableStream>;
@@ -0,0 +1,31 @@
1
+ import { renderToReadableStream } from "react-dom/server.edge";
2
+ export const renderHtmlStream = async ({ node, identifierPrefix, requestInfo, onError, }) => {
3
+ return await renderToReadableStream(node, {
4
+ nonce: requestInfo.rw.nonce,
5
+ identifierPrefix,
6
+ onError(error, { componentStack }) {
7
+ try {
8
+ if (!error) {
9
+ error = new Error(`A falsy value was thrown during rendering: ${String(error)}.`);
10
+ }
11
+ const message = error
12
+ ? (error.stack ?? error.message ?? error)
13
+ : error;
14
+ const wrappedMessage = `${message}\n\nComponent stack:${componentStack}`;
15
+ if (error instanceof Error) {
16
+ const wrappedError = new Error(wrappedMessage);
17
+ wrappedError.stack = error.stack;
18
+ error = wrappedError;
19
+ }
20
+ else {
21
+ error = new Error(wrappedMessage);
22
+ error.stack = componentStack;
23
+ }
24
+ onError(error);
25
+ }
26
+ catch {
27
+ onError(error);
28
+ }
29
+ },
30
+ });
31
+ };
@@ -1,5 +1,4 @@
1
- export declare const renderToRscStream: (app: {
2
- node: React.ReactElement;
3
- actionResult: any;
1
+ export declare const renderToRscStream: ({ input, onError, }: {
2
+ input: any;
4
3
  onError?: (error: unknown) => void;
5
4
  }) => ReadableStream;
@@ -1,46 +1,7 @@
1
1
  import { renderToReadableStream as baseRenderToRscStream } from "react-server-dom-webpack/server.edge";
2
2
  import { createClientManifest } from "./createClientManifest.js";
3
- // context(justinvdm, 24 Mar 2025): React flight limits chunks to 28 bytes, so we need to rechunk
4
- // the stream to avoid losing data
5
- function rechunkStream(stream, maxChunkSize = 28) {
6
- const reader = stream.getReader();
7
- return new ReadableStream({
8
- async pull(controller) {
9
- let buffer = new Uint8Array(0);
10
- try {
11
- while (true) {
12
- const { done, value } = await reader.read();
13
- if (done && buffer.length === 0) {
14
- controller.close();
15
- return;
16
- }
17
- if (value) {
18
- buffer = new Uint8Array([...buffer, ...value]);
19
- }
20
- while (buffer.length >= maxChunkSize || (done && buffer.length > 0)) {
21
- const chunk = buffer.slice(0, maxChunkSize);
22
- buffer = buffer.slice(maxChunkSize);
23
- controller.enqueue(chunk);
24
- }
25
- if (done) {
26
- controller.close();
27
- return;
28
- }
29
- }
30
- }
31
- catch (error) {
32
- controller.error(error);
33
- }
34
- },
35
- });
36
- }
37
- export const renderToRscStream = (app) => {
38
- const { node, onError } = app;
39
- let { actionResult } = app;
40
- if (actionResult instanceof ReadableStream) {
41
- actionResult = rechunkStream(actionResult);
42
- }
43
- return baseRenderToRscStream({ node, actionResult }, createClientManifest(), {
3
+ export const renderToRscStream = ({ input, onError, }) => {
4
+ return baseRenderToRscStream(input, createClientManifest(), {
44
5
  onError,
45
6
  });
46
7
  };
@@ -2,8 +2,9 @@ import { ReactElement, FC } from "react";
2
2
  import { DocumentProps } from "../lib/router";
3
3
  export interface RenderToStreamOptions {
4
4
  Document?: FC<DocumentProps>;
5
+ ssr?: boolean;
5
6
  injectRSCPayload?: boolean;
6
7
  onError?: (error: unknown) => void;
7
8
  }
8
9
  export declare const IdentityDocument: FC<DocumentProps>;
9
- export declare const renderToStream: (element: ReactElement, { Document, injectRSCPayload: shouldInjectRSCPayload, onError, }?: RenderToStreamOptions) => Promise<ReadableStream>;
10
+ export declare const renderToStream: (element: ReactElement, { ssr: shouldSSR, Document, injectRSCPayload: shouldInjectRSCPayload, onError, }?: RenderToStreamOptions) => Promise<ReadableStream>;
@@ -1,27 +1,34 @@
1
1
  import { Fragment as _Fragment, jsx as _jsx } from "react/jsx-runtime";
2
2
  import { renderToRscStream } from "./renderToRscStream";
3
- import { transformRscToHtmlStream } from "./transformRscToHtmlStream";
4
3
  import { requestInfo } from "../requestInfo/worker";
5
4
  import { injectRSCPayload } from "rsc-html-stream/server";
5
+ import { renderDocumentHtmlStream } from "./renderDocumentHtmlStream";
6
6
  export const IdentityDocument = ({ children }) => (_jsx(_Fragment, { children: children }));
7
- export const renderToStream = async (element, { Document = IdentityDocument, injectRSCPayload: shouldInjectRSCPayload = false, onError = () => { }, } = {}) => {
7
+ export const renderToStream = async (element, { ssr: shouldSSR = true, Document = IdentityDocument, injectRSCPayload: shouldInjectRSCPayload = true, onError = () => { }, } = {}) => {
8
8
  let rscStream = renderToRscStream({
9
- node: element,
10
- actionResult: null,
9
+ input: {
10
+ node: element,
11
+ actionResult: undefined,
12
+ },
11
13
  onError,
12
14
  });
15
+ let injectRSCStream;
13
16
  if (shouldInjectRSCPayload) {
14
17
  const [rscPayloadStream1, rscPayloadStream2] = rscStream.tee();
15
18
  rscStream = rscPayloadStream1;
16
- rscStream = rscStream.pipeThrough(injectRSCPayload(rscPayloadStream2, {
19
+ injectRSCStream = injectRSCPayload(rscPayloadStream2, {
17
20
  nonce: requestInfo.rw.nonce,
18
- }));
21
+ });
19
22
  }
20
- const htmlStream = await transformRscToHtmlStream({
21
- stream: rscStream,
23
+ let htmlStream = await renderDocumentHtmlStream({
24
+ rscPayloadStream: rscStream,
22
25
  Document,
23
26
  requestInfo,
27
+ shouldSSR,
24
28
  onError,
25
29
  });
30
+ if (injectRSCStream) {
31
+ htmlStream = htmlStream.pipeThrough(injectRSCStream);
32
+ }
26
33
  return htmlStream;
27
34
  };
@@ -1,4 +1,4 @@
1
1
  import { type RequestInfo } from "../requestInfo/types.js";
2
- export declare const Stylesheets: ({ requestInfo }: {
2
+ export declare const Stylesheets: ({ requestInfo, }: {
3
3
  requestInfo: RequestInfo;
4
- }) => import("react/jsx-runtime.js").JSX.Element;
4
+ }) => Promise<import("react/jsx-runtime.js").JSX.Element>;
@@ -1,5 +1,4 @@
1
1
  import { jsx as _jsx, Fragment as _Fragment } from "react/jsx-runtime";
2
- import { use } from "react";
3
2
  import { getManifest } from "../lib/manifest.js";
4
3
  const findCssForModule = (scriptId, manifest) => {
5
4
  const css = new Set();
@@ -22,8 +21,8 @@ const findCssForModule = (scriptId, manifest) => {
22
21
  inner(scriptId);
23
22
  return Array.from(css);
24
23
  };
25
- export const Stylesheets = ({ requestInfo }) => {
26
- const manifest = use(getManifest());
24
+ export const Stylesheets = async ({ requestInfo, }) => {
25
+ const manifest = await getManifest();
27
26
  const allStylesheets = new Set();
28
27
  for (const scriptId of requestInfo.rw.scriptsToBeLoaded) {
29
28
  const css = findCssForModule(scriptId, manifest);
@@ -1,2 +1,3 @@
1
- export { renderRscThenableToHtmlStream } from "./render/renderRscThenableToHtmlStream";
1
+ export { renderHtmlStream } from "./render/renderHtmlStream";
2
+ export { createThenableFromReadableStream } from "./render/createThenableFromReadableStream";
2
3
  export { ssrLoadModule, ssrGetModuleExport, ssrWebpackRequire, } from "./imports/ssr";
@@ -7,5 +7,6 @@
7
7
  // import it through this bridge module, using the bare import path
8
8
  // `rwsdk/__ssr_bridge`. We have bundler logic (ssrBridgePlugin) that looks out
9
9
  // for imports to it.
10
- export { renderRscThenableToHtmlStream } from "./render/renderRscThenableToHtmlStream";
10
+ export { renderHtmlStream } from "./render/renderHtmlStream";
11
+ export { createThenableFromReadableStream } from "./render/createThenableFromReadableStream";
11
12
  export { ssrLoadModule, ssrGetModuleExport, ssrWebpackRequire, } from "./imports/ssr";
@@ -1,6 +1,7 @@
1
1
  import React from "react";
2
2
  import { RequestInfo, DefaultAppContext } from "./requestInfo/types";
3
3
  import { Route } from "./lib/router";
4
+ export * from "./requestInfo/types";
4
5
  declare global {
5
6
  type Env = {
6
7
  ASSETS: Fetcher;
@@ -1,5 +1,6 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
- import { transformRscToHtmlStream } from "./render/transformRscToHtmlStream";
2
+ import { renderDocumentHtmlStream } from "./render/renderDocumentHtmlStream";
3
+ import { normalizeActionResult } from "./render/normalizeActionResult";
3
4
  import { renderToRscStream } from "./render/renderToRscStream";
4
5
  import { rscActionHandler } from "./register/worker";
5
6
  import { injectRSCPayload } from "rsc-html-stream/server";
@@ -8,6 +9,7 @@ import { getRequestInfo, runWithRequestInfo, runWithRequestInfoOverrides, } from
8
9
  import { defineRoutes } from "./lib/router";
9
10
  import { generateNonce } from "./lib/utils";
10
11
  import { ssrWebpackRequire } from "./imports/worker";
12
+ export * from "./requestInfo/types";
11
13
  export const defineApp = (routes) => {
12
14
  return {
13
15
  fetch: async (request, env, cf) => {
@@ -79,12 +81,14 @@ export const defineApp = (routes) => {
79
81
  status: 500,
80
82
  });
81
83
  }
82
- const actionResult = requestInfo.rw.actionResult;
84
+ const actionResult = normalizeActionResult(requestInfo.rw.actionResult);
83
85
  const pageElement = createPageElement(requestInfo, Page);
84
86
  const { rscPayload: shouldInjectRSCPayload } = rw;
85
87
  let rscPayloadStream = renderToRscStream({
86
- node: pageElement,
87
- actionResult: actionResult instanceof Response ? null : actionResult,
88
+ input: {
89
+ node: pageElement,
90
+ actionResult,
91
+ },
88
92
  onError,
89
93
  });
90
94
  if (isRSCRequest) {
@@ -104,11 +108,12 @@ export const defineApp = (routes) => {
104
108
  nonce: rw.nonce,
105
109
  });
106
110
  }
107
- let html = await transformRscToHtmlStream({
108
- stream: rscPayloadStream,
111
+ let html = await renderDocumentHtmlStream({
112
+ rscPayloadStream: rscPayloadStream,
109
113
  Document: rw.Document,
110
114
  requestInfo: requestInfo,
111
115
  onError,
116
+ shouldSSR: rw.ssr,
112
117
  });
113
118
  if (injectRSCPayloadStream) {
114
119
  html = html.pipeThrough(injectRSCPayloadStream);
@@ -148,8 +148,8 @@ export const configPlugin = ({ silent, projectRootDir, workerEntryPathname, clie
148
148
  // original `export` statement from the bundle to prevent syntax
149
149
  // errors.
150
150
  inlineDynamicImports: true,
151
- banner: `export const { renderRscThenableToHtmlStream, ssrWebpackRequire, ssrGetModuleExport } = (function() {`,
152
- footer: `return { renderRscThenableToHtmlStream, ssrWebpackRequire, ssrGetModuleExport };\n})();`,
151
+ banner: `export const { renderHtmlStream, ssrWebpackRequire, ssrGetModuleExport, createThenableFromReadableStream } = (function() {`,
152
+ footer: `return { renderHtmlStream, ssrWebpackRequire, ssrGetModuleExport, createThenableFromReadableStream };\n})();`,
153
153
  },
154
154
  plugins: [
155
155
  {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "rwsdk",
3
- "version": "1.0.0-alpha.6",
3
+ "version": "1.0.0-alpha.8",
4
4
  "description": "Build fast, server-driven webapps on Cloudflare with SSR, RSC, and realtime",
5
5
  "type": "module",
6
6
  "bin": {
@@ -90,6 +90,10 @@
90
90
  "./realtime/durableObject": {
91
91
  "types": "./dist/runtime/lib/realtime/durableObject.d.ts",
92
92
  "default": "./dist/runtime/lib/realtime/durableObject.js"
93
+ },
94
+ "./e2e": {
95
+ "types": "./dist/lib/e2e/index.d.mts",
96
+ "default": "./dist/lib/e2e/index.mjs"
93
97
  }
94
98
  },
95
99
  "keywords": [
@@ -159,9 +163,9 @@
159
163
  },
160
164
  "peerDependencies": {
161
165
  "@cloudflare/vite-plugin": "^1.12.4",
162
- "react": "19.2.0-canary-3fb190f7-20250908",
163
- "react-dom": "19.2.0-canary-3fb190f7-20250908",
164
- "react-server-dom-webpack": "19.2.0-canary-3fb190f7-20250908",
166
+ "react": "19.2.0-canary-3fb190f7-20250908 <20.0.0",
167
+ "react-dom": "19.2.0-canary-3fb190f7-20250908 <20.0.0",
168
+ "react-server-dom-webpack": ">=19.2.0-canary-3fb190f7-20250908 <20.0.0",
165
169
  "vite": "^6.2.6 || 7.x",
166
170
  "wrangler": "^4.35.0"
167
171
  },