@tanstack/react-start-rsc 0.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/esm/ClientSlot.js +19 -0
- package/dist/esm/ClientSlot.js.map +1 -0
- package/dist/esm/CompositeComponent.js +93 -0
- package/dist/esm/CompositeComponent.js.map +1 -0
- package/dist/esm/ReplayableStream.js +147 -0
- package/dist/esm/ReplayableStream.js.map +1 -0
- package/dist/esm/RscNodeRenderer.js +46 -0
- package/dist/esm/RscNodeRenderer.js.map +1 -0
- package/dist/esm/ServerComponentTypes.js +22 -0
- package/dist/esm/ServerComponentTypes.js.map +1 -0
- package/dist/esm/SlotContext.js +30 -0
- package/dist/esm/SlotContext.js.map +1 -0
- package/dist/esm/awaitLazyElements.js +41 -0
- package/dist/esm/awaitLazyElements.js.map +1 -0
- package/dist/esm/createCompositeComponent.js +205 -0
- package/dist/esm/createCompositeComponent.js.map +1 -0
- package/dist/esm/createCompositeComponent.stub.js +15 -0
- package/dist/esm/createCompositeComponent.stub.js.map +1 -0
- package/dist/esm/createRscProxy.js +138 -0
- package/dist/esm/createRscProxy.js.map +1 -0
- package/dist/esm/createServerComponentFromStream.js +74 -0
- package/dist/esm/createServerComponentFromStream.js.map +1 -0
- package/dist/esm/entry/rsc.js +21 -0
- package/dist/esm/entry/rsc.js.map +1 -0
- package/dist/esm/flight.js +56 -0
- package/dist/esm/flight.js.map +1 -0
- package/dist/esm/flight.rsc.js +2 -0
- package/dist/esm/flight.stub.js +15 -0
- package/dist/esm/flight.stub.js.map +1 -0
- package/dist/esm/index.js +7 -0
- package/dist/esm/index.rsc.js +6 -0
- package/dist/esm/plugin/vite.js +172 -0
- package/dist/esm/plugin/vite.js.map +1 -0
- package/dist/esm/reactSymbols.js +8 -0
- package/dist/esm/reactSymbols.js.map +1 -0
- package/dist/esm/renderServerComponent.js +58 -0
- package/dist/esm/renderServerComponent.js.map +1 -0
- package/dist/esm/renderServerComponent.stub.js +16 -0
- package/dist/esm/renderServerComponent.stub.js.map +1 -0
- package/dist/esm/serialization.client.js +21 -0
- package/dist/esm/serialization.client.js.map +1 -0
- package/dist/esm/serialization.server.js +121 -0
- package/dist/esm/serialization.server.js.map +1 -0
- package/dist/esm/slotUsageSanitizer.js +33 -0
- package/dist/esm/slotUsageSanitizer.js.map +1 -0
- package/dist/esm/src/ClientSlot.d.ts +5 -0
- package/dist/esm/src/CompositeComponent.d.ts +28 -0
- package/dist/esm/src/ReplayableStream.d.ts +76 -0
- package/dist/esm/src/RscNodeRenderer.d.ts +7 -0
- package/dist/esm/src/ServerComponentTypes.d.ts +99 -0
- package/dist/esm/src/SlotContext.d.ts +21 -0
- package/dist/esm/src/awaitLazyElements.d.ts +17 -0
- package/dist/esm/src/createCompositeComponent.d.ts +32 -0
- package/dist/esm/src/createCompositeComponent.stub.d.ts +9 -0
- package/dist/esm/src/createRscProxy.d.ts +18 -0
- package/dist/esm/src/createServerComponentFromStream.d.ts +24 -0
- package/dist/esm/src/entry/rsc.d.ts +7 -0
- package/dist/esm/src/flight.d.ts +41 -0
- package/dist/esm/src/flight.rsc.d.ts +17 -0
- package/dist/esm/src/flight.stub.d.ts +8 -0
- package/dist/esm/src/index.d.ts +7 -0
- package/dist/esm/src/index.rsc.d.ts +6 -0
- package/dist/esm/src/plugin/vite.d.ts +9 -0
- package/dist/esm/src/reactSymbols.d.ts +3 -0
- package/dist/esm/src/renderServerComponent.d.ts +33 -0
- package/dist/esm/src/renderServerComponent.stub.d.ts +9 -0
- package/dist/esm/src/rscSsrHandler.d.ts +24 -0
- package/dist/esm/src/serialization.client.d.ts +11 -0
- package/dist/esm/src/serialization.server.d.ts +10 -0
- package/dist/esm/src/slotUsageSanitizer.d.ts +1 -0
- package/dist/esm/src/types.d.ts +13 -0
- package/dist/plugin/entry/rsc.tsx +23 -0
- package/package.json +108 -0
- package/src/ClientSlot.tsx +34 -0
- package/src/CompositeComponent.tsx +165 -0
- package/src/ReplayableStream.ts +249 -0
- package/src/RscNodeRenderer.tsx +76 -0
- package/src/ServerComponentTypes.ts +226 -0
- package/src/SlotContext.tsx +42 -0
- package/src/awaitLazyElements.ts +91 -0
- package/src/createCompositeComponent.stub.ts +20 -0
- package/src/createCompositeComponent.ts +338 -0
- package/src/createRscProxy.tsx +294 -0
- package/src/createServerComponentFromStream.ts +105 -0
- package/src/entry/rsc.tsx +23 -0
- package/src/entry/virtual-modules.d.ts +12 -0
- package/src/flight.rsc.ts +17 -0
- package/src/flight.stub.ts +15 -0
- package/src/flight.ts +68 -0
- package/src/global.d.ts +75 -0
- package/src/index.rsc.ts +25 -0
- package/src/index.ts +26 -0
- package/src/plugin/vite.ts +241 -0
- package/src/reactSymbols.ts +6 -0
- package/src/renderServerComponent.stub.ts +26 -0
- package/src/renderServerComponent.ts +110 -0
- package/src/rscSsrHandler.ts +39 -0
- package/src/serialization.client.ts +43 -0
- package/src/serialization.server.ts +193 -0
- package/src/slotUsageSanitizer.ts +62 -0
- package/src/types.ts +15 -0
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { useSlotContext } from "./SlotContext.js";
|
|
3
|
+
import { Fragment, jsx } from "react/jsx-runtime";
|
|
4
|
+
//#region src/ClientSlot.tsx
|
|
5
|
+
function ClientSlot({ slot, args }) {
|
|
6
|
+
const ctx = useSlotContext();
|
|
7
|
+
if (!ctx) throw new Error("ClientSlot must be rendered within SlotProvider");
|
|
8
|
+
const impl = ctx.implementations[slot];
|
|
9
|
+
if (impl === void 0) {
|
|
10
|
+
if (ctx.strict) throw new Error(`Missing slot implementation for "${slot}"`);
|
|
11
|
+
return null;
|
|
12
|
+
}
|
|
13
|
+
if (typeof impl !== "function") return /* @__PURE__ */ jsx(Fragment, { children: impl });
|
|
14
|
+
return /* @__PURE__ */ jsx(Fragment, { children: impl(...args) });
|
|
15
|
+
}
|
|
16
|
+
//#endregion
|
|
17
|
+
export { ClientSlot };
|
|
18
|
+
|
|
19
|
+
//# sourceMappingURL=ClientSlot.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ClientSlot.js","names":[],"sources":["../../src/ClientSlot.tsx"],"sourcesContent":["'use client'\n\nimport { useSlotContext } from './SlotContext'\n\nexport interface ClientSlotProps {\n slot: string\n args: Array<unknown>\n}\n\nexport function ClientSlot({ slot, args }: ClientSlotProps) {\n const ctx = useSlotContext()\n\n if (!ctx) {\n throw new Error('ClientSlot must be rendered within SlotProvider')\n }\n\n const impl = ctx.implementations[slot]\n\n // No implementation provided\n if (impl === undefined) {\n if (ctx.strict) {\n throw new Error(`Missing slot implementation for \"${slot}\"`)\n }\n return null\n }\n\n // For children slot or any non-function value, render directly\n if (typeof impl !== 'function') {\n return <>{impl}</>\n }\n\n // Render function with args\n return <>{impl(...args)}</>\n}\n"],"mappings":";;;;AASA,SAAgB,WAAW,EAAE,MAAM,QAAyB;CAC1D,MAAM,MAAM,gBAAgB;AAE5B,KAAI,CAAC,IACH,OAAM,IAAI,MAAM,kDAAkD;CAGpE,MAAM,OAAO,IAAI,gBAAgB;AAGjC,KAAI,SAAS,KAAA,GAAW;AACtB,MAAI,IAAI,OACN,OAAM,IAAI,MAAM,oCAAoC,KAAK,GAAG;AAE9D,SAAO;;AAIT,KAAI,OAAO,SAAS,WAClB,QAAO,oBAAA,UAAA,EAAA,UAAG,MAAQ,CAAA;AAIpB,QAAO,oBAAA,UAAA,EAAA,UAAG,KAAK,GAAG,KAAK,EAAI,CAAA"}
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { RSC_PROXY_GET_TREE, RSC_PROXY_PATH, SERVER_COMPONENT_CSS_HREFS, SERVER_COMPONENT_JS_PRELOADS, SERVER_COMPONENT_STREAM } from "./ServerComponentTypes.js";
|
|
3
|
+
import { SlotProvider } from "./SlotContext.js";
|
|
4
|
+
import { Suspense } from "react";
|
|
5
|
+
import ReactDOM from "react-dom";
|
|
6
|
+
import { Fragment, jsx } from "react/jsx-runtime";
|
|
7
|
+
//#region src/CompositeComponent.tsx
|
|
8
|
+
function splitSlotProps(props) {
|
|
9
|
+
const { children, strict, ...slotProps } = props ?? {};
|
|
10
|
+
return {
|
|
11
|
+
implementations: {
|
|
12
|
+
...slotProps,
|
|
13
|
+
children
|
|
14
|
+
},
|
|
15
|
+
strict: strict === true
|
|
16
|
+
};
|
|
17
|
+
}
|
|
18
|
+
function EmptyFallback() {
|
|
19
|
+
return null;
|
|
20
|
+
}
|
|
21
|
+
function CompositeRenderInner({ getTree, path, slotProps }) {
|
|
22
|
+
let tree = getTree();
|
|
23
|
+
for (const key of path) {
|
|
24
|
+
if (tree === null || tree === void 0) return null;
|
|
25
|
+
if (typeof tree !== "object") return null;
|
|
26
|
+
tree = tree[key];
|
|
27
|
+
}
|
|
28
|
+
if (tree === null || tree === void 0) return null;
|
|
29
|
+
const { implementations, strict } = splitSlotProps(slotProps);
|
|
30
|
+
return /* @__PURE__ */ jsx(SlotProvider, {
|
|
31
|
+
implementations,
|
|
32
|
+
strict,
|
|
33
|
+
children: tree
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
function CompositeRenderComponent({ getTree, path, slotProps, cssHrefs, jsPreloads }) {
|
|
37
|
+
for (const href of cssHrefs ?? []) ReactDOM.preinit(href, {
|
|
38
|
+
as: "style",
|
|
39
|
+
precedence: "high"
|
|
40
|
+
});
|
|
41
|
+
if (jsPreloads) for (const href of jsPreloads) ReactDOM.preloadModule(href);
|
|
42
|
+
return /* @__PURE__ */ jsx(Fragment, { children: /* @__PURE__ */ jsx(Suspense, {
|
|
43
|
+
fallback: /* @__PURE__ */ jsx(EmptyFallback, {}),
|
|
44
|
+
children: /* @__PURE__ */ jsx(CompositeRenderInner, {
|
|
45
|
+
getTree,
|
|
46
|
+
path,
|
|
47
|
+
slotProps
|
|
48
|
+
})
|
|
49
|
+
}) });
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Renders composite RSC data with slot support.
|
|
53
|
+
*
|
|
54
|
+
* Use this component to render data from `createCompositeComponent`.
|
|
55
|
+
* Pass slot implementations as props to fill in ClientSlot placeholders.
|
|
56
|
+
*
|
|
57
|
+
* @example
|
|
58
|
+
* ```tsx
|
|
59
|
+
* const src = await createCompositeComponent((props) => (
|
|
60
|
+
* <div>
|
|
61
|
+
* <header>{props.header('Dashboard')}</header>
|
|
62
|
+
* <main>{props.children}</main>
|
|
63
|
+
* </div>
|
|
64
|
+
* ))
|
|
65
|
+
*
|
|
66
|
+
* // In route component
|
|
67
|
+
* return (
|
|
68
|
+
* <CompositeComponent src={src} header={(title) => <h1>{title}</h1>}>
|
|
69
|
+
* <p>Main content</p>
|
|
70
|
+
* </CompositeComponent>
|
|
71
|
+
* )
|
|
72
|
+
* ```
|
|
73
|
+
*/
|
|
74
|
+
function CompositeComponent(props) {
|
|
75
|
+
const { src, ...slotProps } = props;
|
|
76
|
+
if (!src[SERVER_COMPONENT_STREAM]) throw new Error("[tanstack/start] <CompositeComponent> missing RSC stream on src");
|
|
77
|
+
const cssHrefs = src[SERVER_COMPONENT_CSS_HREFS];
|
|
78
|
+
const jsPreloads = src[SERVER_COMPONENT_JS_PRELOADS];
|
|
79
|
+
const path = src[RSC_PROXY_PATH] ?? [];
|
|
80
|
+
const getTree = src[RSC_PROXY_GET_TREE];
|
|
81
|
+
if (!getTree) throw new Error("[tanstack/start] <CompositeComponent> missing getTree on RSC src. Make sure src comes from createCompositeComponent().");
|
|
82
|
+
return /* @__PURE__ */ jsx(CompositeRenderComponent, {
|
|
83
|
+
getTree,
|
|
84
|
+
path,
|
|
85
|
+
slotProps,
|
|
86
|
+
cssHrefs,
|
|
87
|
+
jsPreloads
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
//#endregion
|
|
91
|
+
export { CompositeComponent };
|
|
92
|
+
|
|
93
|
+
//# sourceMappingURL=CompositeComponent.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"CompositeComponent.js","names":[],"sources":["../../src/CompositeComponent.tsx"],"sourcesContent":["'use client'\n\nimport { Suspense } from 'react'\nimport ReactDOM from 'react-dom'\n\nimport { SlotProvider } from './SlotContext'\nimport {\n RSC_PROXY_GET_TREE,\n RSC_PROXY_PATH,\n SERVER_COMPONENT_CSS_HREFS,\n SERVER_COMPONENT_JS_PRELOADS,\n SERVER_COMPONENT_STREAM,\n} from './ServerComponentTypes'\nimport type { AnyCompositeComponent } from './ServerComponentTypes'\nimport type { SlotImplementations } from './types'\n\nfunction splitSlotProps(props?: Record<string, unknown>): {\n implementations: SlotImplementations\n strict: boolean\n} {\n const safeProps = props ?? {}\n const { children, strict, ...slotProps } = safeProps as {\n children?: unknown\n strict?: unknown\n [key: string]: unknown\n }\n\n return {\n implementations: { ...slotProps, children },\n strict: strict === true,\n }\n}\n\nfunction EmptyFallback() {\n return null\n}\n\nfunction CompositeRenderInner({\n getTree,\n path,\n slotProps,\n}: {\n getTree: () => unknown\n path: Array<string>\n slotProps?: Record<string, unknown>\n}): React.ReactNode {\n let tree: unknown = getTree()\n\n for (const key of path) {\n if (tree === null || tree === undefined) return null\n if (typeof tree !== 'object') return null\n tree = (tree as Record<string, unknown>)[key]\n }\n\n if (tree === null || tree === undefined) return null\n\n const { implementations, strict } = splitSlotProps(slotProps)\n\n return (\n <SlotProvider implementations={implementations} strict={strict}>\n {tree as React.ReactNode}\n </SlotProvider>\n )\n}\n\nfunction CompositeRenderComponent({\n getTree,\n path,\n slotProps,\n cssHrefs,\n jsPreloads,\n}: {\n getTree: () => unknown\n path: Array<string>\n slotProps?: Record<string, unknown>\n cssHrefs?: ReadonlySet<string>\n jsPreloads?: ReadonlySet<string>\n}): React.ReactNode {\n for (const href of cssHrefs ?? []) {\n ReactDOM.preinit(href, { as: 'style', precedence: 'high' })\n }\n\n if (jsPreloads) {\n for (const href of jsPreloads) {\n ReactDOM.preloadModule(href)\n }\n }\n\n return (\n <>\n <Suspense fallback={<EmptyFallback />}>\n <CompositeRenderInner\n getTree={getTree}\n path={path}\n slotProps={slotProps}\n />\n </Suspense>\n </>\n )\n}\n\n/**\n * Renders composite RSC data with slot support.\n *\n * Use this component to render data from `createCompositeComponent`.\n * Pass slot implementations as props to fill in ClientSlot placeholders.\n *\n * @example\n * ```tsx\n * const src = await createCompositeComponent((props) => (\n * <div>\n * <header>{props.header('Dashboard')}</header>\n * <main>{props.children}</main>\n * </div>\n * ))\n *\n * // In route component\n * return (\n * <CompositeComponent src={src} header={(title) => <h1>{title}</h1>}>\n * <p>Main content</p>\n * </CompositeComponent>\n * )\n * ```\n */\nexport function CompositeComponent<TComp extends AnyCompositeComponent>(\n props: CompositeComponentProps<TComp>,\n): TComp['~types']['return'] {\n const { src, ...slotProps } = props\n\n const stream = src[SERVER_COMPONENT_STREAM]\n\n if (!stream) {\n throw new Error(\n '[tanstack/start] <CompositeComponent> missing RSC stream on src',\n )\n }\n\n const cssHrefs = src[SERVER_COMPONENT_CSS_HREFS]\n const jsPreloads = src[SERVER_COMPONENT_JS_PRELOADS]\n\n const path = src[RSC_PROXY_PATH] ?? []\n\n const getTree = src[RSC_PROXY_GET_TREE]\n\n if (!getTree) {\n throw new Error(\n '[tanstack/start] <CompositeComponent> missing getTree on RSC src. ' +\n 'Make sure src comes from createCompositeComponent().',\n )\n }\n\n return (\n <CompositeRenderComponent\n getTree={getTree}\n path={path}\n slotProps={slotProps}\n cssHrefs={cssHrefs}\n jsPreloads={jsPreloads}\n />\n )\n}\n\nexport type CompositeComponentProps<TComp extends AnyCompositeComponent> = {\n src: TComp\n} & TComp['~types']['props']\n"],"mappings":";;;;;;;AAgBA,SAAS,eAAe,OAGtB;CAEA,MAAM,EAAE,UAAU,QAAQ,GAAG,cADX,SAAS,EAAE;AAO7B,QAAO;EACL,iBAAiB;GAAE,GAAG;GAAW;GAAU;EAC3C,QAAQ,WAAW;EACpB;;AAGH,SAAS,gBAAgB;AACvB,QAAO;;AAGT,SAAS,qBAAqB,EAC5B,SACA,MACA,aAKkB;CAClB,IAAI,OAAgB,SAAS;AAE7B,MAAK,MAAM,OAAO,MAAM;AACtB,MAAI,SAAS,QAAQ,SAAS,KAAA,EAAW,QAAO;AAChD,MAAI,OAAO,SAAS,SAAU,QAAO;AACrC,SAAQ,KAAiC;;AAG3C,KAAI,SAAS,QAAQ,SAAS,KAAA,EAAW,QAAO;CAEhD,MAAM,EAAE,iBAAiB,WAAW,eAAe,UAAU;AAE7D,QACE,oBAAC,cAAD;EAA+B;EAAyB;YACrD;EACY,CAAA;;AAInB,SAAS,yBAAyB,EAChC,SACA,MACA,WACA,UACA,cAOkB;AAClB,MAAK,MAAM,QAAQ,YAAY,EAAE,CAC/B,UAAS,QAAQ,MAAM;EAAE,IAAI;EAAS,YAAY;EAAQ,CAAC;AAG7D,KAAI,WACF,MAAK,MAAM,QAAQ,WACjB,UAAS,cAAc,KAAK;AAIhC,QACE,oBAAA,UAAA,EAAA,UACE,oBAAC,UAAD;EAAU,UAAU,oBAAC,eAAD,EAAiB,CAAA;YACnC,oBAAC,sBAAD;GACW;GACH;GACK;GACX,CAAA;EACO,CAAA,EACV,CAAA;;;;;;;;;;;;;;;;;;;;;;;;;AA2BP,SAAgB,mBACd,OAC2B;CAC3B,MAAM,EAAE,KAAK,GAAG,cAAc;AAI9B,KAAI,CAFW,IAAI,yBAGjB,OAAM,IAAI,MACR,kEACD;CAGH,MAAM,WAAW,IAAI;CACrB,MAAM,aAAa,IAAI;CAEvB,MAAM,OAAO,IAAI,mBAAmB,EAAE;CAEtC,MAAM,UAAU,IAAI;AAEpB,KAAI,CAAC,QACH,OAAM,IAAI,MACR,yHAED;AAGH,QACE,oBAAC,0BAAD;EACW;EACH;EACK;EACD;EACE;EACZ,CAAA"}
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
//#region src/ReplayableStream.ts
|
|
2
|
+
var REPLAYABLE_STREAM_MARKER = Symbol.for("tanstack.rsc.ReplayableStream");
|
|
3
|
+
var ReplayableStream = class {
|
|
4
|
+
constructor(source, options = {}) {
|
|
5
|
+
this.source = source;
|
|
6
|
+
this.options = options;
|
|
7
|
+
this[REPLAYABLE_STREAM_MARKER] = true;
|
|
8
|
+
this.chunks = [];
|
|
9
|
+
this.done = false;
|
|
10
|
+
this.error = null;
|
|
11
|
+
this.waiter = null;
|
|
12
|
+
this.aborted = false;
|
|
13
|
+
this.released = false;
|
|
14
|
+
this.sourceReader = null;
|
|
15
|
+
this.abortListener = null;
|
|
16
|
+
this.abortSignal = options.signal;
|
|
17
|
+
this.start();
|
|
18
|
+
}
|
|
19
|
+
start() {
|
|
20
|
+
const signal = this.abortSignal;
|
|
21
|
+
if (signal?.aborted) {
|
|
22
|
+
this.handleAbort();
|
|
23
|
+
return;
|
|
24
|
+
}
|
|
25
|
+
const onAbort = () => this.handleAbort();
|
|
26
|
+
this.abortListener = onAbort;
|
|
27
|
+
signal?.addEventListener("abort", onAbort, { once: true });
|
|
28
|
+
const reader = this.source.getReader();
|
|
29
|
+
this.sourceReader = reader;
|
|
30
|
+
const pump = async () => {
|
|
31
|
+
try {
|
|
32
|
+
while (!this.aborted && !this.released) {
|
|
33
|
+
const { done, value } = await reader.read();
|
|
34
|
+
if (done) break;
|
|
35
|
+
if (this.aborted || this.released) break;
|
|
36
|
+
this.chunks.push(value);
|
|
37
|
+
this.notify();
|
|
38
|
+
}
|
|
39
|
+
this.done = true;
|
|
40
|
+
} catch (err) {
|
|
41
|
+
if (!this.aborted && !this.released) this.error = err;
|
|
42
|
+
this.done = true;
|
|
43
|
+
} finally {
|
|
44
|
+
this.detachAbortListener();
|
|
45
|
+
try {
|
|
46
|
+
reader.releaseLock();
|
|
47
|
+
} catch {}
|
|
48
|
+
if (this.sourceReader === reader) this.sourceReader = null;
|
|
49
|
+
this.notify();
|
|
50
|
+
}
|
|
51
|
+
};
|
|
52
|
+
pump();
|
|
53
|
+
}
|
|
54
|
+
detachAbortListener() {
|
|
55
|
+
const signal = this.abortSignal;
|
|
56
|
+
const listener = this.abortListener;
|
|
57
|
+
if (signal && listener) signal.removeEventListener("abort", listener);
|
|
58
|
+
this.abortListener = null;
|
|
59
|
+
}
|
|
60
|
+
cancelSource(reason) {
|
|
61
|
+
const reader = this.sourceReader;
|
|
62
|
+
this.sourceReader = null;
|
|
63
|
+
try {
|
|
64
|
+
reader?.cancel(reason).catch(() => {});
|
|
65
|
+
} catch {}
|
|
66
|
+
}
|
|
67
|
+
handleAbort() {
|
|
68
|
+
if (this.aborted) return;
|
|
69
|
+
this.aborted = true;
|
|
70
|
+
this.done = true;
|
|
71
|
+
const reason = this.abortSignal?.reason ?? /* @__PURE__ */ new Error("ReplayableStream aborted");
|
|
72
|
+
this.detachAbortListener();
|
|
73
|
+
this.abortSignal = void 0;
|
|
74
|
+
this.cancelSource(reason);
|
|
75
|
+
this.chunks = [];
|
|
76
|
+
this.released = true;
|
|
77
|
+
this.notify();
|
|
78
|
+
}
|
|
79
|
+
notify() {
|
|
80
|
+
if (this.waiter) {
|
|
81
|
+
this.waiter.resolve();
|
|
82
|
+
this.waiter = null;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
wait() {
|
|
86
|
+
if (this.done || this.released) return Promise.resolve();
|
|
87
|
+
if (!this.waiter) this.waiter = Promise.withResolvers();
|
|
88
|
+
return this.waiter.promise;
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* Explicitly release buffered chunks.
|
|
92
|
+
* Call this when you know no more replay streams will be created.
|
|
93
|
+
* After calling release(), createReplayStream() will return empty streams.
|
|
94
|
+
*/
|
|
95
|
+
release() {
|
|
96
|
+
if (this.released) return;
|
|
97
|
+
this.released = true;
|
|
98
|
+
this.chunks = [];
|
|
99
|
+
this.detachAbortListener();
|
|
100
|
+
this.abortSignal = void 0;
|
|
101
|
+
this.cancelSource(/* @__PURE__ */ new Error("ReplayableStream released"));
|
|
102
|
+
this.notify();
|
|
103
|
+
}
|
|
104
|
+
/**
|
|
105
|
+
* Check if the stream data has been released
|
|
106
|
+
*/
|
|
107
|
+
isReleased() {
|
|
108
|
+
return this.released;
|
|
109
|
+
}
|
|
110
|
+
/**
|
|
111
|
+
* Create an independent replay stream. Each call returns a fresh reader
|
|
112
|
+
* that starts from the beginning of the buffered data.
|
|
113
|
+
*
|
|
114
|
+
* If the stream has been released, returns a stream that closes immediately.
|
|
115
|
+
*/
|
|
116
|
+
createReplayStream() {
|
|
117
|
+
if (this.released) return new ReadableStream({ start(controller) {
|
|
118
|
+
controller.close();
|
|
119
|
+
} });
|
|
120
|
+
let index = 0;
|
|
121
|
+
return new ReadableStream({
|
|
122
|
+
pull: async (controller) => {
|
|
123
|
+
while (true) {
|
|
124
|
+
if (this.released) {
|
|
125
|
+
controller.close();
|
|
126
|
+
return;
|
|
127
|
+
}
|
|
128
|
+
if (index < this.chunks.length) {
|
|
129
|
+
controller.enqueue(this.chunks[index++]);
|
|
130
|
+
return;
|
|
131
|
+
}
|
|
132
|
+
if (this.done) {
|
|
133
|
+
if (this.error && !this.aborted) controller.error(this.error);
|
|
134
|
+
else controller.close();
|
|
135
|
+
return;
|
|
136
|
+
}
|
|
137
|
+
await this.wait();
|
|
138
|
+
}
|
|
139
|
+
},
|
|
140
|
+
cancel: () => {}
|
|
141
|
+
});
|
|
142
|
+
}
|
|
143
|
+
};
|
|
144
|
+
//#endregion
|
|
145
|
+
export { ReplayableStream };
|
|
146
|
+
|
|
147
|
+
//# sourceMappingURL=ReplayableStream.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ReplayableStream.js","names":[],"sources":["../../src/ReplayableStream.ts"],"sourcesContent":["/**\n * ReplayableStream is used for React Server Components (RSC) / Flight streams.\n *\n * In this package the same Flight payload may need to be:\n * - decoded for SSR (render path), and/or\n * - serialized for transport to the client for client-side decoding.\n *\n * Call sites:\n * - `src/createServerComponent.ts`: wraps the produced Flight stream once.\n * - `src/serialization.ts`: uses `createReplayStream()` to transport a fresh stream\n * to the client via `RawStream`.\n *\n * Constraints:\n * - Consumption order isn't fixed: SSR decode might start first or transport might\n * start first depending on the request path.\n * - Sometimes only one happens (e.g. when client calls server function directly).\n *\n * Why not just `ReadableStream.tee()`?\n * - tee() must be called up-front before the stream is consumed/locked and only\n * creates two live branches (no late \"replay from byte 0\").\n * - If one branch is slower or never consumed, the runtime may buffer internally\n * to keep branches consistent, which can retain large Flight payloads longer\n * than intended and makes cleanup less explicit.\n *\n * ReplayableStream reads once, buffers explicitly, can mint replay streams on\n * demand, and centralizes cancellation so aborting can stop upstream work and\n * free buffered data deterministically.\n *\n * Memory Management:\n * - Memory is released when the abort signal fires (request cancelled)\n * - Call `release()` to force immediate cleanup if no more replays are needed\n * - No automatic release: replays can be created at unpredictable times (SSR decode\n * finishes before serialization starts), so we can't safely auto-release\n */\n\nexport interface ReplayableStreamOptions {\n signal?: AbortSignal\n}\n\n// Use Symbol.for to ensure the same symbol across different module instances\nexport const REPLAYABLE_STREAM_MARKER = Symbol.for(\n 'tanstack.rsc.ReplayableStream',\n)\n\nexport class ReplayableStream<T = Uint8Array> {\n // Marker for cross-environment instance checking\n readonly [REPLAYABLE_STREAM_MARKER] = true\n\n private chunks: Array<T> = []\n private done = false\n private error: unknown = null\n private waiter: PromiseWithResolvers<void> | null = null\n private aborted = false\n private released = false\n\n private sourceReader: ReadableStreamDefaultReader<T> | null = null\n private abortSignal: AbortSignal | undefined\n private abortListener: (() => void) | null = null\n\n constructor(\n private source: ReadableStream<T>,\n private options: ReplayableStreamOptions = {},\n ) {\n this.abortSignal = options.signal\n this.start()\n }\n\n private start(): void {\n const signal = this.abortSignal\n\n if (signal?.aborted) {\n this.handleAbort()\n return\n }\n\n const onAbort = () => this.handleAbort()\n this.abortListener = onAbort\n signal?.addEventListener('abort', onAbort, { once: true })\n\n const reader = this.source.getReader()\n this.sourceReader = reader\n\n const pump = async () => {\n try {\n // Keep reading until upstream ends or we are stopped.\n while (!this.aborted && !this.released) {\n const { done, value } = await reader.read()\n if (done) break\n // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition\n if (this.aborted || this.released) break\n this.chunks.push(value)\n this.notify()\n }\n this.done = true\n } catch (err) {\n if (!this.aborted && !this.released) {\n this.error = err\n }\n this.done = true\n } finally {\n this.detachAbortListener()\n try {\n reader.releaseLock()\n } catch {\n // Ignore\n }\n if (this.sourceReader === reader) {\n this.sourceReader = null\n }\n this.notify()\n }\n }\n\n pump()\n }\n\n private detachAbortListener(): void {\n const signal = this.abortSignal\n const listener = this.abortListener\n if (signal && listener) {\n signal.removeEventListener('abort', listener)\n }\n this.abortListener = null\n }\n\n private cancelSource(reason: unknown): void {\n const reader = this.sourceReader\n this.sourceReader = null\n\n try {\n reader?.cancel(reason).catch(() => {})\n } catch {\n // Ignore\n }\n }\n\n private handleAbort(): void {\n if (this.aborted) return\n this.aborted = true\n this.done = true\n\n // Try to stop upstream work immediately.\n // Cancellation during an abort can throw synchronously in some runtimes\n // (eg when the underlying cancel algorithm throws). Never let that escape\n // an AbortSignal event handler.\n const reason =\n this.abortSignal?.reason ?? new Error('ReplayableStream aborted')\n\n this.detachAbortListener()\n this.abortSignal = undefined\n this.cancelSource(reason)\n\n this.chunks = [] // Free memory immediately on abort\n this.released = true\n this.notify()\n }\n\n private notify(): void {\n if (this.waiter) {\n this.waiter.resolve()\n this.waiter = null\n }\n }\n\n private wait(): Promise<void> {\n if (this.done || this.released) return Promise.resolve()\n if (!this.waiter) {\n this.waiter = Promise.withResolvers<void>()\n }\n return this.waiter.promise\n }\n\n /**\n * Explicitly release buffered chunks.\n * Call this when you know no more replay streams will be created.\n * After calling release(), createReplayStream() will return empty streams.\n */\n release(): void {\n if (this.released) return\n\n this.released = true\n this.chunks = []\n\n // Release should also stop upstream work and wake any waiters.\n // This is important when a Flight payload is never consumed again\n // (eg. cached loader data that evicts) to avoid retaining upstream resources.\n this.detachAbortListener()\n this.abortSignal = undefined\n this.cancelSource(new Error('ReplayableStream released'))\n this.notify()\n }\n\n /**\n * Check if the stream data has been released\n */\n isReleased(): boolean {\n return this.released\n }\n\n /**\n * Create an independent replay stream. Each call returns a fresh reader\n * that starts from the beginning of the buffered data.\n *\n * If the stream has been released, returns a stream that closes immediately.\n */\n createReplayStream(): ReadableStream<T> {\n if (this.released) {\n return new ReadableStream<T>({\n start(controller) {\n controller.close()\n },\n })\n }\n\n let index = 0\n\n return new ReadableStream<T>({\n pull: async (controller) => {\n // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition\n while (true) {\n if (this.released) {\n controller.close()\n return\n }\n\n if (index < this.chunks.length) {\n controller.enqueue(this.chunks[index++] as T)\n return\n }\n\n if (this.done) {\n if (this.error && !this.aborted) {\n controller.error(this.error)\n } else {\n controller.close()\n }\n return\n }\n\n await this.wait()\n }\n },\n cancel: () => {\n // No-op: consumers canceling a replay should not cancel upstream.\n // Upstream cancellation is controlled by abort/release.\n },\n })\n }\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { RSC_PROXY_GET_TREE, RSC_PROXY_PATH, SERVER_COMPONENT_CSS_HREFS, SERVER_COMPONENT_JS_PRELOADS } from "./ServerComponentTypes.js";
|
|
3
|
+
import { Suspense } from "react";
|
|
4
|
+
import ReactDOM from "react-dom";
|
|
5
|
+
import { Fragment, jsx } from "react/jsx-runtime";
|
|
6
|
+
//#region src/RscNodeRenderer.tsx
|
|
7
|
+
function EmptyFallback() {
|
|
8
|
+
return null;
|
|
9
|
+
}
|
|
10
|
+
function RscNodeRenderInner({ getTree, path }) {
|
|
11
|
+
let tree = getTree();
|
|
12
|
+
for (const key of path) {
|
|
13
|
+
if (tree === null || tree === void 0) return null;
|
|
14
|
+
if (typeof tree !== "object") return null;
|
|
15
|
+
tree = tree[key];
|
|
16
|
+
}
|
|
17
|
+
if (tree === null || tree === void 0) return null;
|
|
18
|
+
return tree;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Renders a renderable RSC proxy without slot support.
|
|
22
|
+
* Used internally by the renderable proxy's $$typeof/type masquerade.
|
|
23
|
+
*/
|
|
24
|
+
function RscNodeRenderer({ data }) {
|
|
25
|
+
const cssHrefs = data[SERVER_COMPONENT_CSS_HREFS];
|
|
26
|
+
const jsPreloads = data[SERVER_COMPONENT_JS_PRELOADS];
|
|
27
|
+
const path = data[RSC_PROXY_PATH] ?? [];
|
|
28
|
+
const getTree = data[RSC_PROXY_GET_TREE];
|
|
29
|
+
if (!getTree) throw new Error("[tanstack/start] RscNodeRenderer missing getTree on RSC data.");
|
|
30
|
+
for (const href of cssHrefs ?? []) ReactDOM.preinit(href, {
|
|
31
|
+
as: "style",
|
|
32
|
+
precedence: "high"
|
|
33
|
+
});
|
|
34
|
+
if (jsPreloads) for (const href of jsPreloads) ReactDOM.preloadModule(href);
|
|
35
|
+
return /* @__PURE__ */ jsx(Fragment, { children: /* @__PURE__ */ jsx(Suspense, {
|
|
36
|
+
fallback: /* @__PURE__ */ jsx(EmptyFallback, {}),
|
|
37
|
+
children: /* @__PURE__ */ jsx(RscNodeRenderInner, {
|
|
38
|
+
getTree,
|
|
39
|
+
path
|
|
40
|
+
})
|
|
41
|
+
}) });
|
|
42
|
+
}
|
|
43
|
+
//#endregion
|
|
44
|
+
export { RscNodeRenderer };
|
|
45
|
+
|
|
46
|
+
//# sourceMappingURL=RscNodeRenderer.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"RscNodeRenderer.js","names":[],"sources":["../../src/RscNodeRenderer.tsx"],"sourcesContent":["'use client'\n\nimport { Suspense } from 'react'\nimport ReactDOM from 'react-dom'\n\nimport {\n RSC_PROXY_GET_TREE,\n RSC_PROXY_PATH,\n SERVER_COMPONENT_CSS_HREFS,\n SERVER_COMPONENT_JS_PRELOADS,\n} from './ServerComponentTypes'\n\nfunction EmptyFallback() {\n return null\n}\n\nfunction RscNodeRenderInner({\n getTree,\n path,\n}: {\n getTree: () => unknown\n path: Array<string>\n}): React.ReactNode {\n let tree: unknown = getTree()\n\n for (const key of path) {\n if (tree === null || tree === undefined) return null\n if (typeof tree !== 'object') return null\n tree = (tree as Record<string, unknown>)[key]\n }\n\n if (tree === null || tree === undefined) return null\n\n // No SlotProvider - just return the tree directly\n return tree as React.ReactNode\n}\n\n/**\n * Renders a renderable RSC proxy without slot support.\n * Used internally by the renderable proxy's $$typeof/type masquerade.\n */\nexport function RscNodeRenderer({ data }: { data: any }): React.ReactNode {\n const cssHrefs = data[SERVER_COMPONENT_CSS_HREFS] as\n | ReadonlySet<string>\n | undefined\n const jsPreloads = data[SERVER_COMPONENT_JS_PRELOADS] as\n | ReadonlySet<string>\n | undefined\n\n const path = (data[RSC_PROXY_PATH] as Array<string> | undefined) ?? []\n\n const getTree = data[RSC_PROXY_GET_TREE] as (() => unknown) | undefined\n if (!getTree) {\n throw new Error(\n '[tanstack/start] RscNodeRenderer missing getTree on RSC data.',\n )\n }\n\n for (const href of cssHrefs ?? []) {\n ReactDOM.preinit(href, { as: 'style', precedence: 'high' })\n }\n\n if (jsPreloads) {\n for (const href of jsPreloads) {\n ReactDOM.preloadModule(href)\n }\n }\n\n return (\n <>\n <Suspense fallback={<EmptyFallback />}>\n <RscNodeRenderInner getTree={getTree} path={path} />\n </Suspense>\n </>\n )\n}\n"],"mappings":";;;;;;AAYA,SAAS,gBAAgB;AACvB,QAAO;;AAGT,SAAS,mBAAmB,EAC1B,SACA,QAIkB;CAClB,IAAI,OAAgB,SAAS;AAE7B,MAAK,MAAM,OAAO,MAAM;AACtB,MAAI,SAAS,QAAQ,SAAS,KAAA,EAAW,QAAO;AAChD,MAAI,OAAO,SAAS,SAAU,QAAO;AACrC,SAAQ,KAAiC;;AAG3C,KAAI,SAAS,QAAQ,SAAS,KAAA,EAAW,QAAO;AAGhD,QAAO;;;;;;AAOT,SAAgB,gBAAgB,EAAE,QAAwC;CACxE,MAAM,WAAW,KAAK;CAGtB,MAAM,aAAa,KAAK;CAIxB,MAAM,OAAQ,KAAK,mBAAiD,EAAE;CAEtE,MAAM,UAAU,KAAK;AACrB,KAAI,CAAC,QACH,OAAM,IAAI,MACR,gEACD;AAGH,MAAK,MAAM,QAAQ,YAAY,EAAE,CAC/B,UAAS,QAAQ,MAAM;EAAE,IAAI;EAAS,YAAY;EAAQ,CAAC;AAG7D,KAAI,WACF,MAAK,MAAM,QAAQ,WACjB,UAAS,cAAc,KAAK;AAIhC,QACE,oBAAA,UAAA,EAAA,UACE,oBAAC,UAAD;EAAU,UAAU,oBAAC,eAAD,EAAiB,CAAA;YACnC,oBAAC,oBAAD;GAA6B;GAAe;GAAQ,CAAA;EAC3C,CAAA,EACV,CAAA"}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
//#region src/ServerComponentTypes.ts
|
|
2
|
+
var SERVER_COMPONENT_STREAM = Symbol.for("tanstack.rsc.stream");
|
|
3
|
+
var SERVER_COMPONENT_CSS_HREFS = Symbol.for("tanstack.rsc.cssHrefs");
|
|
4
|
+
var SERVER_COMPONENT_JS_PRELOADS = Symbol.for("tanstack.rsc.jsPreloads");
|
|
5
|
+
var RSC_PROXY_PATH = Symbol.for("tanstack.rsc.path");
|
|
6
|
+
var RSC_PROXY_GET_TREE = Symbol.for("tanstack.rsc.getTree");
|
|
7
|
+
var RENDERABLE_RSC = Symbol.for("tanstack.rsc.renderable");
|
|
8
|
+
var RSC_SLOT_USAGES = Symbol.for("tanstack.rsc.slotUsages");
|
|
9
|
+
var RSC_SLOT_USAGES_STREAM = Symbol.for("tanstack.rsc.slotUsages.stream");
|
|
10
|
+
/**
|
|
11
|
+
* Type guard to check if a value is a ServerComponent (Proxy with attached stream).
|
|
12
|
+
* The value can be either an object (proxy target) or a function (stub for server functions).
|
|
13
|
+
*/
|
|
14
|
+
function isServerComponent(value) {
|
|
15
|
+
if (value === null || value === void 0) return false;
|
|
16
|
+
if (typeof value !== "object" && typeof value !== "function") return false;
|
|
17
|
+
return SERVER_COMPONENT_STREAM in value && value[SERVER_COMPONENT_STREAM] !== void 0;
|
|
18
|
+
}
|
|
19
|
+
//#endregion
|
|
20
|
+
export { RENDERABLE_RSC, RSC_PROXY_GET_TREE, RSC_PROXY_PATH, RSC_SLOT_USAGES, RSC_SLOT_USAGES_STREAM, SERVER_COMPONENT_CSS_HREFS, SERVER_COMPONENT_JS_PRELOADS, SERVER_COMPONENT_STREAM, isServerComponent };
|
|
21
|
+
|
|
22
|
+
//# sourceMappingURL=ServerComponentTypes.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ServerComponentTypes.js","names":[],"sources":["../../src/ServerComponentTypes.ts"],"sourcesContent":["import type {\n Constrain,\n LooseAsyncReturnType,\n LooseReturnType,\n ValidateSerializable,\n} from '@tanstack/router-core'\nimport type { ComponentProps, ComponentType } from 'react'\n\nexport interface ServerComponentStream {\n createReplayStream: () => ReadableStream<Uint8Array>\n}\n\n// Symbol to attach stream to component for serialization\nexport const SERVER_COMPONENT_STREAM = Symbol.for('tanstack.rsc.stream')\n\n// Symbol to attach collected CSS hrefs to component\nexport const SERVER_COMPONENT_CSS_HREFS = Symbol.for('tanstack.rsc.cssHrefs')\n\n// Symbol to attach collected JS modulepreload hrefs to component\nexport const SERVER_COMPONENT_JS_PRELOADS = Symbol.for(\n 'tanstack.rsc.jsPreloads',\n)\n\n// Symbol to attach a nested selection path to the RSC data proxy\nexport const RSC_PROXY_PATH = Symbol.for('tanstack.rsc.path')\n\n// Symbol to attach the root tree getter to every nested proxy\nexport const RSC_PROXY_GET_TREE = Symbol.for('tanstack.rsc.getTree')\n\n// Symbol to mark a proxy as \"renderable\" (for renderServerComponent output)\n// When true: from renderServerComponent (directly renderable)\n// When false/undefined: from createCompositeComponent (needs <CompositeComponent src={...} />)\nexport const RENDERABLE_RSC = Symbol.for('tanstack.rsc.renderable')\n\n// Dev-only: collected slot usage data for devtools (client-side cache)\nexport const RSC_SLOT_USAGES = Symbol.for('tanstack.rsc.slotUsages')\n\n// Dev-only: stream of slot usage preview events for devtools\nexport const RSC_SLOT_USAGES_STREAM = Symbol.for(\n 'tanstack.rsc.slotUsages.stream',\n)\n\nexport type RscSlotUsageEvent = {\n slot: string\n // Raw args passed to the slot call (must be serializable by the transport)\n args?: Array<any>\n}\n\n/**\n * Type guard to check if a value is a ServerComponent (Proxy with attached stream).\n * The value can be either an object (proxy target) or a function (stub for server functions).\n */\nexport function isServerComponent(\n value: unknown,\n): value is AnyCompositeComponent {\n if (value === null || value === undefined) return false\n if (typeof value !== 'object' && typeof value !== 'function') return false\n return (\n SERVER_COMPONENT_STREAM in value &&\n (value as any)[SERVER_COMPONENT_STREAM] !== undefined\n )\n}\n\n/**\n * Type guard to check if a value is a RenderableRsc (renderable proxy from renderServerComponent).\n * The value can be either an object (proxy target) or a function (stub for server functions).\n */\nexport function isRenderableRsc(value: unknown): boolean {\n if (value === null || value === undefined) return false\n if (typeof value !== 'object' && typeof value !== 'function') return false\n return RENDERABLE_RSC in value && (value as any)[RENDERABLE_RSC] === true\n}\n\nexport type ValidateCompositeComponent<TComp> = Constrain<\n TComp,\n (\n props: ValidateCompositeComponentProps<TComp>,\n ) => ValidateCompositeComponentReturnType<TComp>\n>\n\nexport type ValidateCompositeComponentProps<TComp> = unknown extends TComp\n ? TComp\n : ValidateCompositeComponentPropsObject<CompositeComponentProps<TComp>>\n\nexport type ValidateCompositeComponentPropsObject<TProps> =\n unknown extends TProps\n ? TProps\n : {\n [TKey in keyof TProps]: ValidateCompositeComponentProp<TProps[TKey]>\n }\n\nexport type CompositeComponentProps<TComp> = TComp extends (\n props: infer TProps,\n) => any\n ? TProps\n : unknown\n\nexport type ValidateCompositeComponentProp<TProp> = TProp extends (\n ...args: Array<any>\n) => any\n ? (...args: ValidateReactSerializable<Parameters<TProp>>) => React.ReactNode\n : TProp extends ComponentType<any>\n ? ComponentType<ValidateReactSerializable<ComponentProps<TProp>>>\n : TProp extends React.ReactNode\n ? TProp\n : React.ReactNode\n\nexport type ValidateReactSerializable<T> = ValidateSerializable<\n T,\n ReactSerializable\n>\n\nexport type ReactSerializable =\n | number\n | string\n | bigint\n | boolean\n | null\n | undefined\n | React.ReactNode\n\nexport type ValidateCompositeComponentReturnType<TComp> = unknown extends TComp\n ? React.ReactNode\n : ValidateCompositeComponentResult<LooseReturnType<TComp>>\n\nexport type ValidateCompositeComponentResult<TNode> =\n ValidateServerComponentResult<TNode>\n\nexport type ValidateServerComponentResult<TNode> =\n TNode extends Promise<any>\n ? ValidateCompositeComponentPromiseResult<TNode>\n : TNode extends React.ReactNode\n ? TNode\n : TNode extends (...args: Array<any>) => any\n ? React.ReactNode\n : TNode extends object\n ? ValidateCompositeComponentObjectResult<TNode>\n : React.ReactNode\n\nexport type ValidateCompositeComponentPromiseResult<TPromise> =\n TPromise extends Promise<infer T>\n ? Promise<ValidateCompositeComponentResult<T>>\n : never\n\nexport type ValidateCompositeComponentObjectResult<TObject> = {\n [TKey in keyof TObject]: ValidateCompositeComponentResult<TObject[TKey]>\n}\n\nexport type CompositeComponentResult<TComp> = CompositeComponentBuilder<\n TComp,\n LooseAsyncReturnType<TComp>\n>\n\nexport type CompositeComponentBuilder<TComp, TReturn> =\n TReturn extends React.ReactNode\n ? CompositeComponent<TComp, TReturn>\n : {\n [TKey in keyof TReturn]: CompositeComponentBuilder<TComp, TReturn[TKey]>\n }\n\nexport interface CompositeComponent<in out TComp, in out TReturn> {\n '~types': {\n props: CompositeComponentProps<TComp>\n return: TReturn\n }\n\n [SERVER_COMPONENT_STREAM]?: ServerComponentStream\n\n /**\n * Root decoded tree getter.\n */\n [RSC_PROXY_GET_TREE]?: () => unknown\n /**\n * Nested selection path (eg ['content','Stats']).\n * Used by <CompositeComponent/> to render a sub-tree.\n */\n [RSC_PROXY_PATH]?: Array<string>\n /**\n * CSS hrefs collected from the RSC stream.\n * Can be used for preloading in <head> or emitting 103 Early Hints.\n */\n [SERVER_COMPONENT_CSS_HREFS]?: ReadonlySet<string>\n\n /**\n * JS hrefs collected from the RSC stream.\n * Emitted as modulepreload links only if the decoded tree is rendered in SSR.\n */\n [SERVER_COMPONENT_JS_PRELOADS]?: ReadonlySet<string>\n\n /**\n * Dev-only: async stream of slot usage preview events.\n * Used by devtools to show slot names and previewed call args without\n * buffering/draining the Flight stream.\n */\n [RSC_SLOT_USAGES_STREAM]?: ReadableStream<RscSlotUsageEvent>\n}\n\nexport type ValidateRenderableServerComponent<TNode> =\n ValidateServerComponentResult<TNode>\n\nexport type RenderableServerComponentBuilder<T> = T extends React.ReactNode\n ? RenderableServerComponent<T>\n : { [TKey in keyof T]: RenderableServerComponentBuilder<T[TKey]> }\n\nexport type RenderableServerComponent<TNode extends React.ReactNode> = TNode &\n RenderableServerComponentAttributes<TNode>\n\nexport interface RenderableServerComponentAttributes<TNode> {\n '~types': {\n node: TNode\n }\n [SERVER_COMPONENT_STREAM]: ServerComponentStream\n [RENDERABLE_RSC]: true\n}\n\ndeclare module '@tanstack/router-core' {\n export interface SerializableExtensions {\n CompositeComponent: AnyCompositeComponent\n RenderableServerComponent: AnyRenderableServerComponent\n }\n}\n\nexport type AnyCompositeComponent = CompositeComponent<any, any>\n\nexport type AnyRenderableServerComponent =\n RenderableServerComponentAttributes<any>\n"],"mappings":";AAaA,IAAa,0BAA0B,OAAO,IAAI,sBAAsB;AAGxE,IAAa,6BAA6B,OAAO,IAAI,wBAAwB;AAG7E,IAAa,+BAA+B,OAAO,IACjD,0BACD;AAGD,IAAa,iBAAiB,OAAO,IAAI,oBAAoB;AAG7D,IAAa,qBAAqB,OAAO,IAAI,uBAAuB;AAKpE,IAAa,iBAAiB,OAAO,IAAI,0BAA0B;AAGnE,IAAa,kBAAkB,OAAO,IAAI,0BAA0B;AAGpE,IAAa,yBAAyB,OAAO,IAC3C,iCACD;;;;;AAYD,SAAgB,kBACd,OACgC;AAChC,KAAI,UAAU,QAAQ,UAAU,KAAA,EAAW,QAAO;AAClD,KAAI,OAAO,UAAU,YAAY,OAAO,UAAU,WAAY,QAAO;AACrE,QACE,2BAA2B,SAC1B,MAAc,6BAA6B,KAAA"}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { createContext, use } from "react";
|
|
3
|
+
import { jsx } from "react/jsx-runtime";
|
|
4
|
+
//#region src/SlotContext.tsx
|
|
5
|
+
var SlotContext = createContext(null);
|
|
6
|
+
/**
|
|
7
|
+
* Hook to access slot implementations from within ClientSlot.
|
|
8
|
+
*/
|
|
9
|
+
function useSlotContext() {
|
|
10
|
+
return use(SlotContext);
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* SlotProvider - makes slot implementations available to ClientSlot components.
|
|
14
|
+
*
|
|
15
|
+
* Must wrap the decoded RSC content so that ClientSlot components can
|
|
16
|
+
* access their slot implementations via React Context.
|
|
17
|
+
*/
|
|
18
|
+
function SlotProvider({ implementations, strict, children }) {
|
|
19
|
+
return /* @__PURE__ */ jsx(SlotContext, {
|
|
20
|
+
value: {
|
|
21
|
+
implementations,
|
|
22
|
+
strict: strict ?? false
|
|
23
|
+
},
|
|
24
|
+
children
|
|
25
|
+
});
|
|
26
|
+
}
|
|
27
|
+
//#endregion
|
|
28
|
+
export { SlotProvider, useSlotContext };
|
|
29
|
+
|
|
30
|
+
//# sourceMappingURL=SlotContext.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"SlotContext.js","names":[],"sources":["../../src/SlotContext.tsx"],"sourcesContent":["'use client'\n\nimport { createContext, use } from 'react'\nimport type { SlotImplementations } from './types'\n\nexport interface SlotContextValue {\n implementations: SlotImplementations\n strict: boolean\n}\n\nconst SlotContext = createContext<SlotContextValue | null>(null)\n\n/**\n * Hook to access slot implementations from within ClientSlot.\n */\nexport function useSlotContext(): SlotContextValue | null {\n return use(SlotContext)\n}\n\nexport interface SlotProviderProps {\n implementations: SlotImplementations\n strict?: boolean\n children?: React.ReactNode\n}\n\n/**\n * SlotProvider - makes slot implementations available to ClientSlot components.\n *\n * Must wrap the decoded RSC content so that ClientSlot components can\n * access their slot implementations via React Context.\n */\nexport function SlotProvider({\n implementations,\n strict,\n children,\n}: SlotProviderProps) {\n return (\n <SlotContext value={{ implementations, strict: strict ?? false }}>\n {children}\n </SlotContext>\n )\n}\n"],"mappings":";;;;AAUA,IAAM,cAAc,cAAuC,KAAK;;;;AAKhE,SAAgB,iBAA0C;AACxD,QAAO,IAAI,YAAY;;;;;;;;AAezB,SAAgB,aAAa,EAC3B,iBACA,QACA,YACoB;AACpB,QACE,oBAAC,aAAD;EAAa,OAAO;GAAE;GAAiB,QAAQ,UAAU;GAAO;EAC7D;EACW,CAAA"}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { ReactElement, ReactLazy, ReactSuspense } from "./reactSymbols.js";
|
|
2
|
+
//#region src/awaitLazyElements.ts
|
|
3
|
+
/**
|
|
4
|
+
* Yields pending lazy element payloads from a tree, stopping at Suspense boundaries.
|
|
5
|
+
* Also collects CSS hrefs from <link rel="stylesheet" data-rsc-css-href> elements.
|
|
6
|
+
*/
|
|
7
|
+
function* findPendingLazyPayloads(obj, seen = /* @__PURE__ */ new Set(), cssCollector) {
|
|
8
|
+
if (!obj || typeof obj !== "object") return;
|
|
9
|
+
if (seen.has(obj)) return;
|
|
10
|
+
seen.add(obj);
|
|
11
|
+
const el = obj;
|
|
12
|
+
if (el.$$typeof === ReactElement && el.type === ReactSuspense) return;
|
|
13
|
+
if (el.$$typeof === ReactElement && el.type === "link" && el.props?.rel === "stylesheet") {
|
|
14
|
+
const cssHref = el.props["data-rsc-css-href"];
|
|
15
|
+
if (cssHref && cssCollector) cssCollector(cssHref);
|
|
16
|
+
}
|
|
17
|
+
if (el.$$typeof === ReactLazy) {
|
|
18
|
+
const payload = el._payload;
|
|
19
|
+
if (payload && typeof payload === "object" && (payload.status === "pending" || payload.status === "blocked") && typeof payload.then === "function") yield payload;
|
|
20
|
+
}
|
|
21
|
+
if (Array.isArray(obj)) for (const item of obj) yield* findPendingLazyPayloads(item, seen, cssCollector);
|
|
22
|
+
else for (const key of Object.keys(obj)) if (key !== "_owner" && key !== "_store") yield* findPendingLazyPayloads(el[key], seen, cssCollector);
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Wait for all lazy elements in a tree to be resolved.
|
|
26
|
+
* This ensures client component chunks are fully loaded before rendering,
|
|
27
|
+
* preventing Suspense boundaries from flashing during SWR navigation.
|
|
28
|
+
*
|
|
29
|
+
* Also collects CSS hrefs from <link rel="stylesheet" data-rsc-css-href>
|
|
30
|
+
* elements for preloading in <head>.
|
|
31
|
+
*
|
|
32
|
+
* @param tree - The tree to process
|
|
33
|
+
* @param cssCollector - Optional callback to collect CSS hrefs (server-only)
|
|
34
|
+
*/
|
|
35
|
+
async function awaitLazyElements(tree, cssCollector) {
|
|
36
|
+
for (const payload of findPendingLazyPayloads(tree, /* @__PURE__ */ new Set(), cssCollector)) await Promise.resolve(payload).catch(() => {});
|
|
37
|
+
}
|
|
38
|
+
//#endregion
|
|
39
|
+
export { awaitLazyElements };
|
|
40
|
+
|
|
41
|
+
//# sourceMappingURL=awaitLazyElements.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"awaitLazyElements.js","names":[],"sources":["../../src/awaitLazyElements.ts"],"sourcesContent":["import { ReactElement, ReactLazy, ReactSuspense } from './reactSymbols'\n\n/**\n * Optional callback for collecting CSS hrefs during tree traversal.\n * Only called server-side when processing <link rel=\"stylesheet\" data-rsc-css-href>\n */\nexport type CssHrefCollector = (href: string) => void\n\n/**\n * Yields pending lazy element payloads from a tree, stopping at Suspense boundaries.\n * Also collects CSS hrefs from <link rel=\"stylesheet\" data-rsc-css-href> elements.\n */\nfunction* findPendingLazyPayloads(\n obj: unknown,\n seen = new Set(),\n cssCollector?: CssHrefCollector,\n): Generator<PromiseLike<unknown>> {\n if (!obj || typeof obj !== 'object') return\n if (seen.has(obj)) return\n seen.add(obj)\n\n const el = obj as any\n\n // Stop at Suspense boundaries - lazy elements inside are intentionally deferred\n if (el.$$typeof === ReactElement && el.type === ReactSuspense) {\n return\n }\n\n // Collect CSS hrefs from <link rel=\"stylesheet\" data-rsc-css-href>\n // The active RSC bundler adapter injects these for CSS module imports\n if (\n el.$$typeof === ReactElement &&\n el.type === 'link' &&\n el.props?.rel === 'stylesheet'\n ) {\n const cssHref = el.props['data-rsc-css-href'] as string | undefined\n if (cssHref && cssCollector) {\n cssCollector(cssHref)\n }\n }\n\n // Yield pending lazy element payload\n if (el.$$typeof === ReactLazy) {\n const payload = el._payload\n if (\n payload &&\n typeof payload === 'object' &&\n (payload.status === 'pending' || payload.status === 'blocked') &&\n typeof payload.then === 'function'\n ) {\n yield payload\n }\n }\n\n // Recurse into children\n if (Array.isArray(obj)) {\n for (const item of obj) {\n yield* findPendingLazyPayloads(item, seen, cssCollector)\n }\n } else {\n for (const key of Object.keys(obj)) {\n if (key !== '_owner' && key !== '_store') {\n yield* findPendingLazyPayloads(el[key], seen, cssCollector)\n }\n }\n }\n}\n\n/**\n * Wait for all lazy elements in a tree to be resolved.\n * This ensures client component chunks are fully loaded before rendering,\n * preventing Suspense boundaries from flashing during SWR navigation.\n *\n * Also collects CSS hrefs from <link rel=\"stylesheet\" data-rsc-css-href>\n * elements for preloading in <head>.\n *\n * @param tree - The tree to process\n * @param cssCollector - Optional callback to collect CSS hrefs (server-only)\n */\nexport async function awaitLazyElements(\n tree: unknown,\n cssCollector?: CssHrefCollector,\n): Promise<void> {\n for (const payload of findPendingLazyPayloads(\n tree,\n new Set(),\n cssCollector,\n )) {\n await Promise.resolve(payload).catch(() => {})\n }\n}\n"],"mappings":";;;;;;AAYA,UAAU,wBACR,KACA,uBAAO,IAAI,KAAK,EAChB,cACiC;AACjC,KAAI,CAAC,OAAO,OAAO,QAAQ,SAAU;AACrC,KAAI,KAAK,IAAI,IAAI,CAAE;AACnB,MAAK,IAAI,IAAI;CAEb,MAAM,KAAK;AAGX,KAAI,GAAG,aAAa,gBAAgB,GAAG,SAAS,cAC9C;AAKF,KACE,GAAG,aAAa,gBAChB,GAAG,SAAS,UACZ,GAAG,OAAO,QAAQ,cAClB;EACA,MAAM,UAAU,GAAG,MAAM;AACzB,MAAI,WAAW,aACb,cAAa,QAAQ;;AAKzB,KAAI,GAAG,aAAa,WAAW;EAC7B,MAAM,UAAU,GAAG;AACnB,MACE,WACA,OAAO,YAAY,aAClB,QAAQ,WAAW,aAAa,QAAQ,WAAW,cACpD,OAAO,QAAQ,SAAS,WAExB,OAAM;;AAKV,KAAI,MAAM,QAAQ,IAAI,CACpB,MAAK,MAAM,QAAQ,IACjB,QAAO,wBAAwB,MAAM,MAAM,aAAa;KAG1D,MAAK,MAAM,OAAO,OAAO,KAAK,IAAI,CAChC,KAAI,QAAQ,YAAY,QAAQ,SAC9B,QAAO,wBAAwB,GAAG,MAAM,MAAM,aAAa;;;;;;;;;;;;;AAiBnE,eAAsB,kBACpB,MACA,cACe;AACf,MAAK,MAAM,WAAW,wBACpB,sBACA,IAAI,KAAK,EACT,aACD,CACC,OAAM,QAAQ,QAAQ,QAAQ,CAAC,YAAY,GAAG"}
|