@benjavicente/router-core 1.168.9 → 1.169.2
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/cjs/Matches.cjs +1 -1
- package/dist/cjs/Matches.cjs.map +1 -1
- package/dist/cjs/config.cjs +1 -1
- package/dist/cjs/config.cjs.map +1 -1
- package/dist/cjs/defer.cjs +1 -1
- package/dist/cjs/defer.cjs.map +1 -1
- package/dist/cjs/hash-scroll.cjs +1 -1
- package/dist/cjs/hash-scroll.cjs.map +1 -1
- package/dist/cjs/index.cjs +4 -0
- package/dist/cjs/index.d.cts +3 -3
- package/dist/cjs/isServer/client.cjs +1 -1
- package/dist/cjs/isServer/client.cjs.map +1 -1
- package/dist/cjs/isServer/development.cjs +1 -1
- package/dist/cjs/isServer/development.cjs.map +1 -1
- package/dist/cjs/isServer/server.cjs +1 -1
- package/dist/cjs/isServer/server.cjs.map +1 -1
- package/dist/cjs/link.cjs +1 -1
- package/dist/cjs/link.cjs.map +1 -1
- package/dist/cjs/load-matches.cjs +26 -26
- package/dist/cjs/load-matches.cjs.map +1 -1
- package/dist/cjs/manifest.cjs +31 -0
- package/dist/cjs/manifest.cjs.map +1 -1
- package/dist/cjs/manifest.d.cts +8 -0
- package/dist/cjs/new-process-route-tree.cjs +55 -67
- package/dist/cjs/new-process-route-tree.cjs.map +1 -1
- package/dist/cjs/new-process-route-tree.d.cts +4 -26
- package/dist/cjs/not-found.cjs +1 -1
- package/dist/cjs/not-found.cjs.map +1 -1
- package/dist/cjs/path.cjs +1 -22
- package/dist/cjs/path.cjs.map +1 -1
- package/dist/cjs/root.cjs +1 -1
- package/dist/cjs/root.cjs.map +1 -1
- package/dist/cjs/route.cjs.map +1 -1
- package/dist/cjs/route.d.cts +6 -34
- package/dist/cjs/router.cjs +94 -80
- package/dist/cjs/router.cjs.map +1 -1
- package/dist/cjs/router.d.cts +6 -5
- package/dist/cjs/scroll-restoration-script/server.cjs +1 -1
- package/dist/cjs/scroll-restoration-script/server.cjs.map +1 -1
- package/dist/cjs/scroll-restoration.cjs +7 -7
- package/dist/cjs/scroll-restoration.cjs.map +1 -1
- package/dist/cjs/searchParams.cjs +2 -2
- package/dist/cjs/searchParams.cjs.map +1 -1
- package/dist/cjs/ssr/constants.cjs +2 -2
- package/dist/cjs/ssr/constants.cjs.map +1 -1
- package/dist/cjs/ssr/createRequestHandler.cjs +2 -2
- package/dist/cjs/ssr/createRequestHandler.cjs.map +1 -1
- package/dist/cjs/ssr/serializer/RawStream.cjs +50 -41
- package/dist/cjs/ssr/serializer/RawStream.cjs.map +1 -1
- package/dist/cjs/ssr/serializer/RawStream.d.cts +12 -4
- package/dist/cjs/ssr/serializer/ShallowErrorPlugin.cjs +1 -1
- package/dist/cjs/ssr/serializer/ShallowErrorPlugin.cjs.map +1 -1
- package/dist/cjs/ssr/serializer/ShallowErrorPlugin.d.cts +2 -2
- package/dist/cjs/ssr/serializer/seroval-plugins.cjs +1 -1
- package/dist/cjs/ssr/serializer/seroval-plugins.cjs.map +1 -1
- package/dist/cjs/ssr/serializer/seroval-plugins.d.cts +2 -1
- package/dist/cjs/ssr/serializer/transformer.cjs +16 -14
- package/dist/cjs/ssr/serializer/transformer.cjs.map +1 -1
- package/dist/cjs/ssr/serializer/transformer.d.cts +24 -23
- package/dist/cjs/ssr/ssr-client.cjs +9 -9
- package/dist/cjs/ssr/ssr-client.cjs.map +1 -1
- package/dist/cjs/ssr/ssr-server.cjs +110 -22
- package/dist/cjs/ssr/ssr-server.cjs.map +1 -1
- package/dist/cjs/ssr/ssr-server.d.cts +4 -2
- package/dist/cjs/ssr/transformStreamWithRouter.cjs +10 -7
- package/dist/cjs/ssr/transformStreamWithRouter.cjs.map +1 -1
- package/dist/cjs/stores.cjs +57 -57
- package/dist/cjs/stores.cjs.map +1 -1
- package/dist/cjs/stores.d.cts +16 -16
- package/dist/cjs/utils.cjs +12 -7
- package/dist/cjs/utils.cjs.map +1 -1
- package/dist/cjs/utils.d.cts +1 -0
- package/dist/esm/Matches.js +1 -1
- package/dist/esm/Matches.js.map +1 -1
- package/dist/esm/config.js +1 -1
- package/dist/esm/config.js.map +1 -1
- package/dist/esm/defer.js +1 -1
- package/dist/esm/defer.js.map +1 -1
- package/dist/esm/hash-scroll.js +1 -1
- package/dist/esm/hash-scroll.js.map +1 -1
- package/dist/esm/index.d.ts +3 -3
- package/dist/esm/index.js +3 -3
- package/dist/esm/isServer/client.js +1 -1
- package/dist/esm/isServer/client.js.map +1 -1
- package/dist/esm/isServer/development.js +1 -1
- package/dist/esm/isServer/development.js.map +1 -1
- package/dist/esm/isServer/server.js +1 -1
- package/dist/esm/isServer/server.js.map +1 -1
- package/dist/esm/link.js +1 -1
- package/dist/esm/link.js.map +1 -1
- package/dist/esm/load-matches.js +26 -26
- package/dist/esm/load-matches.js.map +1 -1
- package/dist/esm/manifest.d.ts +8 -0
- package/dist/esm/manifest.js +28 -1
- package/dist/esm/manifest.js.map +1 -1
- package/dist/esm/new-process-route-tree.d.ts +4 -26
- package/dist/esm/new-process-route-tree.js +55 -68
- package/dist/esm/new-process-route-tree.js.map +1 -1
- package/dist/esm/not-found.js +1 -1
- package/dist/esm/not-found.js.map +1 -1
- package/dist/esm/path.js +1 -22
- package/dist/esm/path.js.map +1 -1
- package/dist/esm/root.js +1 -1
- package/dist/esm/root.js.map +1 -1
- package/dist/esm/route.d.ts +6 -34
- package/dist/esm/route.js.map +1 -1
- package/dist/esm/router.d.ts +6 -5
- package/dist/esm/router.js +96 -82
- package/dist/esm/router.js.map +1 -1
- package/dist/esm/scroll-restoration-script/server.js +1 -1
- package/dist/esm/scroll-restoration-script/server.js.map +1 -1
- package/dist/esm/scroll-restoration.js +7 -7
- package/dist/esm/scroll-restoration.js.map +1 -1
- package/dist/esm/searchParams.js +2 -2
- package/dist/esm/searchParams.js.map +1 -1
- package/dist/esm/ssr/constants.js +2 -2
- package/dist/esm/ssr/constants.js.map +1 -1
- package/dist/esm/ssr/createRequestHandler.js +2 -2
- package/dist/esm/ssr/createRequestHandler.js.map +1 -1
- package/dist/esm/ssr/serializer/RawStream.d.ts +12 -4
- package/dist/esm/ssr/serializer/RawStream.js +50 -41
- package/dist/esm/ssr/serializer/RawStream.js.map +1 -1
- package/dist/esm/ssr/serializer/ShallowErrorPlugin.d.ts +2 -2
- package/dist/esm/ssr/serializer/ShallowErrorPlugin.js +1 -1
- package/dist/esm/ssr/serializer/ShallowErrorPlugin.js.map +1 -1
- package/dist/esm/ssr/serializer/seroval-plugins.d.ts +2 -1
- package/dist/esm/ssr/serializer/seroval-plugins.js +1 -1
- package/dist/esm/ssr/serializer/seroval-plugins.js.map +1 -1
- package/dist/esm/ssr/serializer/transformer.d.ts +24 -23
- package/dist/esm/ssr/serializer/transformer.js +16 -14
- package/dist/esm/ssr/serializer/transformer.js.map +1 -1
- package/dist/esm/ssr/ssr-client.js +9 -9
- package/dist/esm/ssr/ssr-client.js.map +1 -1
- package/dist/esm/ssr/ssr-server.d.ts +4 -2
- package/dist/esm/ssr/ssr-server.js +110 -22
- package/dist/esm/ssr/ssr-server.js.map +1 -1
- package/dist/esm/ssr/transformStreamWithRouter.js +10 -7
- package/dist/esm/ssr/transformStreamWithRouter.js.map +1 -1
- package/dist/esm/stores.d.ts +16 -16
- package/dist/esm/stores.js +58 -58
- package/dist/esm/stores.js.map +1 -1
- package/dist/esm/utils.d.ts +1 -0
- package/dist/esm/utils.js +12 -8
- package/dist/esm/utils.js.map +1 -1
- package/package.json +4 -8
- package/skills/router-core/auth-and-guards/SKILL.md +41 -0
- package/skills/router-core/ssr/SKILL.md +59 -5
- package/skills/router-core/type-safety/SKILL.md +37 -42
- package/src/hash-scroll.ts +1 -1
- package/src/index.ts +9 -2
- package/src/load-matches.ts +14 -15
- package/src/manifest.ts +46 -0
- package/src/new-process-route-tree.ts +93 -106
- package/src/not-found.ts +1 -1
- package/src/path.ts +1 -27
- package/src/route.ts +10 -36
- package/src/router.ts +151 -140
- package/src/scroll-restoration.ts +1 -1
- package/src/ssr/createRequestHandler.ts +4 -5
- package/src/ssr/serializer/RawStream.ts +65 -56
- package/src/ssr/serializer/ShallowErrorPlugin.ts +2 -2
- package/src/ssr/serializer/seroval-plugins.ts +2 -1
- package/src/ssr/serializer/transformer.ts +71 -76
- package/src/ssr/ssr-client.ts +8 -12
- package/src/ssr/ssr-server.ts +170 -14
- package/src/ssr/transformStreamWithRouter.ts +3 -0
- package/src/stores.ts +86 -86
- package/src/utils.ts +7 -0
- package/bin/intent.js +0 -25
package/dist/cjs/router.d.cts
CHANGED
|
@@ -477,8 +477,6 @@ export type GetMatchRoutesFn = (pathname: string) => {
|
|
|
477
477
|
matchedRoutes: ReadonlyArray<AnyRoute>;
|
|
478
478
|
/** exhaustive params, still in their string form */
|
|
479
479
|
routeParams: Record<string, string>;
|
|
480
|
-
/** partial params, parsed from routeParams during matching */
|
|
481
|
-
parsedParams: Record<string, unknown> | undefined;
|
|
482
480
|
foundRoute: AnyRoute | undefined;
|
|
483
481
|
parseError?: unknown;
|
|
484
482
|
};
|
|
@@ -520,7 +518,9 @@ export interface ServerSsr {
|
|
|
520
518
|
setRenderFinished: () => void;
|
|
521
519
|
cleanup: () => void;
|
|
522
520
|
onSerializationFinished: (listener: () => void) => void;
|
|
523
|
-
dehydrate: (
|
|
521
|
+
dehydrate: (opts?: {
|
|
522
|
+
requestAssets?: Array<RouterManagedTag>;
|
|
523
|
+
}) => Promise<void>;
|
|
524
524
|
takeBufferedScripts: () => RouterManagedTag | undefined;
|
|
525
525
|
/**
|
|
526
526
|
* Takes any buffered HTML that was injected.
|
|
@@ -609,6 +609,7 @@ export declare class RouterCore<in out TRouteTree extends AnyRoute, in out TTrai
|
|
|
609
609
|
routesByPath: RoutesByPath<TRouteTree>;
|
|
610
610
|
processedTree: ProcessedTree<TRouteTree, any, any>;
|
|
611
611
|
resolvePathCache: LRUCache<string, string>;
|
|
612
|
+
private routeBranchCache;
|
|
612
613
|
isServer: boolean;
|
|
613
614
|
pathParamsDecoder?: (encoded: string) => string;
|
|
614
615
|
protocolAllowlist: Set<string>;
|
|
@@ -637,8 +638,9 @@ export declare class RouterCore<in out TRouteTree extends AnyRoute, in out TTrai
|
|
|
637
638
|
* current router options, rewrite rules and search parser/stringifier.
|
|
638
639
|
*/
|
|
639
640
|
parseLocation: ParseLocationFn<TRouteTree>;
|
|
640
|
-
/** Resolve a path
|
|
641
|
+
/** Resolve a path using the router's trailing-slash policy. */
|
|
641
642
|
resolvePathWithBase: (from: string, path: string) => string;
|
|
643
|
+
private getRouteBranch;
|
|
642
644
|
get looseRoutesById(): Record<string, AnyRoute>;
|
|
643
645
|
matchRoutes: MatchRoutesFn;
|
|
644
646
|
private getParentContext;
|
|
@@ -729,6 +731,5 @@ export declare function getMatchedRoutes<TRouteLike extends RouteLike>({ pathnam
|
|
|
729
731
|
matchedRoutes: readonly TRouteLike[];
|
|
730
732
|
routeParams: Record<string, string>;
|
|
731
733
|
foundRoute: TRouteLike | undefined;
|
|
732
|
-
parsedParams: Record<string, unknown> | undefined;
|
|
733
734
|
};
|
|
734
735
|
export {};
|
|
@@ -3,7 +3,7 @@ const require_utils = require("../utils.cjs");
|
|
|
3
3
|
const require_scroll_restoration = require("../scroll-restoration.cjs");
|
|
4
4
|
const require_scroll_restoration_inline = require("../scroll-restoration-inline.cjs");
|
|
5
5
|
//#region src/scroll-restoration-script/server.ts
|
|
6
|
-
|
|
6
|
+
const defaultInlineScrollRestorationScript = `(${require_scroll_restoration_inline.default})(${require_utils.escapeHtml(JSON.stringify({
|
|
7
7
|
storageKey: require_scroll_restoration.storageKey,
|
|
8
8
|
shouldScrollRestoration: true
|
|
9
9
|
}))})`;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"server.cjs","names":[],"sources":["../../../src/scroll-restoration-script/server.ts"],"sourcesContent":["import minifiedScrollRestorationScript from '../scroll-restoration-inline?script-string'\nimport {\n defaultGetScrollRestorationKey,\n storageKey,\n} from '../scroll-restoration'\nimport { escapeHtml } from '../utils'\nimport type { AnyRouter } from '../router'\n\ntype InlineScrollRestorationScriptOptions = {\n storageKey: string\n key?: string\n behavior?: ScrollToOptions['behavior']\n shouldScrollRestoration?: boolean\n}\n\nconst defaultInlineScrollRestorationScript = `(${minifiedScrollRestorationScript})(${escapeHtml(\n JSON.stringify({\n storageKey,\n shouldScrollRestoration: true,\n } satisfies InlineScrollRestorationScriptOptions),\n)})`\n\nfunction getScrollRestorationScript(\n options: InlineScrollRestorationScriptOptions,\n) {\n if (\n options.storageKey === storageKey &&\n options.shouldScrollRestoration === true &&\n options.key === undefined &&\n options.behavior === undefined\n ) {\n return defaultInlineScrollRestorationScript\n }\n\n return `(${minifiedScrollRestorationScript})(${escapeHtml(JSON.stringify(options))})`\n}\n\nexport function getScrollRestorationScriptForRouter(router: AnyRouter) {\n if (\n typeof router.options.scrollRestoration === 'function' &&\n !router.options.scrollRestoration({ location: router.latestLocation })\n ) {\n return null\n }\n\n const getKey = router.options.getScrollRestorationKey\n if (!getKey) {\n return defaultInlineScrollRestorationScript\n }\n\n const location = router.latestLocation\n const userKey = getKey(location)\n const defaultKey = defaultGetScrollRestorationKey(location)\n\n if (userKey === defaultKey) {\n return defaultInlineScrollRestorationScript\n }\n\n return getScrollRestorationScript({\n storageKey,\n shouldScrollRestoration: true,\n key: userKey,\n })\n}\n"],"mappings":";;;;;AAeA,
|
|
1
|
+
{"version":3,"file":"server.cjs","names":[],"sources":["../../../src/scroll-restoration-script/server.ts"],"sourcesContent":["import minifiedScrollRestorationScript from '../scroll-restoration-inline?script-string'\nimport {\n defaultGetScrollRestorationKey,\n storageKey,\n} from '../scroll-restoration'\nimport { escapeHtml } from '../utils'\nimport type { AnyRouter } from '../router'\n\ntype InlineScrollRestorationScriptOptions = {\n storageKey: string\n key?: string\n behavior?: ScrollToOptions['behavior']\n shouldScrollRestoration?: boolean\n}\n\nconst defaultInlineScrollRestorationScript = `(${minifiedScrollRestorationScript})(${escapeHtml(\n JSON.stringify({\n storageKey,\n shouldScrollRestoration: true,\n } satisfies InlineScrollRestorationScriptOptions),\n)})`\n\nfunction getScrollRestorationScript(\n options: InlineScrollRestorationScriptOptions,\n) {\n if (\n options.storageKey === storageKey &&\n options.shouldScrollRestoration === true &&\n options.key === undefined &&\n options.behavior === undefined\n ) {\n return defaultInlineScrollRestorationScript\n }\n\n return `(${minifiedScrollRestorationScript})(${escapeHtml(JSON.stringify(options))})`\n}\n\nexport function getScrollRestorationScriptForRouter(router: AnyRouter) {\n if (\n typeof router.options.scrollRestoration === 'function' &&\n !router.options.scrollRestoration({ location: router.latestLocation })\n ) {\n return null\n }\n\n const getKey = router.options.getScrollRestorationKey\n if (!getKey) {\n return defaultInlineScrollRestorationScript\n }\n\n const location = router.latestLocation\n const userKey = getKey(location)\n const defaultKey = defaultGetScrollRestorationKey(location)\n\n if (userKey === defaultKey) {\n return defaultInlineScrollRestorationScript\n }\n\n return getScrollRestorationScript({\n storageKey,\n shouldScrollRestoration: true,\n key: userKey,\n })\n}\n"],"mappings":";;;;;AAeA,MAAM,uCAAuC,IAAI,kCAAA,QAAgC,IAAI,cAAA,WACnF,KAAK,UAAU;CACb,YAAA,2BAAA;CACA,yBAAyB;CAC1B,CAAgD,CAClD,CAAC;AAEF,SAAS,2BACP,SACA;AACA,KACE,QAAQ,eAAA,iCACR,QAAQ,4BAA4B,QACpC,QAAQ,QAAQ,KAAA,KAChB,QAAQ,aAAa,KAAA,EAErB,QAAO;AAGT,QAAO,IAAI,kCAAA,QAAgC,IAAI,cAAA,WAAW,KAAK,UAAU,QAAQ,CAAC,CAAC;;AAGrF,SAAgB,oCAAoC,QAAmB;AACrE,KACE,OAAO,OAAO,QAAQ,sBAAsB,cAC5C,CAAC,OAAO,QAAQ,kBAAkB,EAAE,UAAU,OAAO,gBAAgB,CAAC,CAEtE,QAAO;CAGT,MAAM,SAAS,OAAO,QAAQ;AAC9B,KAAI,CAAC,OACH,QAAO;CAGT,MAAM,WAAW,OAAO;CACxB,MAAM,UAAU,OAAO,SAAS;AAGhC,KAAI,YAFe,2BAAA,+BAA+B,SAAS,CAGzD,QAAO;AAGT,QAAO,2BAA2B;EAChC,YAAA,2BAAA;EACA,yBAAyB;EACzB,KAAK;EACN,CAAC"}
|
|
@@ -8,7 +8,7 @@ function getSafeSessionStorage() {
|
|
|
8
8
|
return;
|
|
9
9
|
}
|
|
10
10
|
}
|
|
11
|
-
|
|
11
|
+
const storageKey = "tsr-scroll-restoration-v1_3";
|
|
12
12
|
function createScrollRestorationCache() {
|
|
13
13
|
const safeSessionStorage = getSafeSessionStorage();
|
|
14
14
|
if (!safeSessionStorage) return null;
|
|
@@ -34,14 +34,14 @@ function createScrollRestorationCache() {
|
|
|
34
34
|
persist
|
|
35
35
|
};
|
|
36
36
|
}
|
|
37
|
-
|
|
37
|
+
const scrollRestorationCache = createScrollRestorationCache();
|
|
38
38
|
/**
|
|
39
39
|
* The default `getKey` function for `useScrollRestoration`.
|
|
40
40
|
* It returns the `key` from the location state or the `href` of the location.
|
|
41
41
|
*
|
|
42
42
|
* The `location.href` is used as a fallback to support the use case where the location state is not available like the initial render.
|
|
43
43
|
*/
|
|
44
|
-
|
|
44
|
+
const defaultGetScrollRestorationKey = (location) => {
|
|
45
45
|
return location.state.__TSR_key || location.href;
|
|
46
46
|
};
|
|
47
47
|
function getCssSelector(el) {
|
|
@@ -60,9 +60,9 @@ function getElementScrollRestorationEntry(router, options) {
|
|
|
60
60
|
if (!element) return;
|
|
61
61
|
return scrollRestorationCache?.state[restoreKey]?.[element instanceof Window ? windowScrollTarget : getCssSelector(element)];
|
|
62
62
|
}
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
63
|
+
let ignoreScroll = false;
|
|
64
|
+
const windowScrollTarget = "window";
|
|
65
|
+
const scrollRestorationIdAttribute = "data-scroll-restoration-id";
|
|
66
66
|
function setupScrollRestoration(router, force) {
|
|
67
67
|
if (!scrollRestorationCache && !(_benjavicente_router_core_isServer.isServer ?? router.isServer)) return;
|
|
68
68
|
const cache = scrollRestorationCache;
|
|
@@ -107,7 +107,7 @@ function setupScrollRestoration(router, force) {
|
|
|
107
107
|
trackedScrollEntries.clear();
|
|
108
108
|
});
|
|
109
109
|
window.addEventListener("pagehide", () => {
|
|
110
|
-
snapshotCurrentScrollTargets(getKey(router.stores.resolvedLocation.
|
|
110
|
+
snapshotCurrentScrollTargets(getKey(router.stores.resolvedLocation.get() ?? router.stores.location.get()));
|
|
111
111
|
cache.persist();
|
|
112
112
|
});
|
|
113
113
|
router.subscribe("onRendered", (event) => {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"scroll-restoration.cjs","names":[],"sources":["../../src/scroll-restoration.ts"],"sourcesContent":["import { isServer } from '@benjavicente/router-core/isServer'\nimport { functionalUpdate, isPlainObject } from './utils'\nimport type { AnyRouter } from './router'\nimport type { ParsedLocation } from './location'\nimport type { NonNullableUpdater } from './utils'\n\nexport type ScrollRestorationEntry = { scrollX: number; scrollY: number }\n\ntype ScrollRestorationByElement = Record<string, ScrollRestorationEntry>\n\ntype ScrollRestorationByKey = Record<string, ScrollRestorationByElement>\n\ntype ScrollRestorationCache = {\n readonly state: ScrollRestorationByKey\n set: (updater: NonNullableUpdater<ScrollRestorationByKey>) => void\n persist: () => void\n}\n\nexport type ScrollRestorationOptions = {\n getKey?: (location: ParsedLocation) => string\n scrollBehavior?: ScrollToOptions['behavior']\n}\n\nfunction getSafeSessionStorage() {\n try {\n return typeof window !== 'undefined' &&\n typeof window.sessionStorage === 'object'\n ? window.sessionStorage\n : undefined\n } catch {\n // silent\n return undefined\n }\n}\n\n// SessionStorage key used to store scroll positions across navigations.\nexport const storageKey = 'tsr-scroll-restoration-v1_3'\n\nfunction createScrollRestorationCache(): ScrollRestorationCache | null {\n const safeSessionStorage = getSafeSessionStorage()\n if (!safeSessionStorage) {\n return null\n }\n\n let state: ScrollRestorationByKey = {}\n\n try {\n const parsed = JSON.parse(safeSessionStorage.getItem(storageKey) || '{}')\n if (isPlainObject(parsed)) {\n state = parsed as ScrollRestorationByKey\n }\n } catch {\n // ignore invalid session storage payloads\n }\n\n const persist = () => {\n try {\n safeSessionStorage.setItem(storageKey, JSON.stringify(state))\n } catch {\n if (process.env.NODE_ENV !== 'production') {\n console.warn(\n '[ts-router] Could not persist scroll restoration state to sessionStorage.',\n )\n }\n }\n }\n\n return {\n get state() {\n return state\n },\n set: (updater) => {\n state = functionalUpdate(updater, state) || state\n },\n persist,\n }\n}\n\nexport const scrollRestorationCache = createScrollRestorationCache()\n\n/**\n * The default `getKey` function for `useScrollRestoration`.\n * It returns the `key` from the location state or the `href` of the location.\n *\n * The `location.href` is used as a fallback to support the use case where the location state is not available like the initial render.\n */\nexport const defaultGetScrollRestorationKey = (location: ParsedLocation) => {\n return location.state.__TSR_key! || location.href\n}\n\nfunction getCssSelector(el: any): string {\n const path = []\n let parent: HTMLElement\n while ((parent = el.parentNode)) {\n path.push(\n `${el.tagName}:nth-child(${Array.prototype.indexOf.call(parent.children, el) + 1})`,\n )\n el = parent\n }\n return `${path.reverse().join(' > ')}`.toLowerCase()\n}\n\nexport function getElementScrollRestorationEntry(\n router: AnyRouter,\n options: (\n | {\n id: string\n getElement?: () => Window | Element | undefined | null\n }\n | {\n id?: string\n getElement: () => Window | Element | undefined | null\n }\n ) & {\n getKey?: (location: ParsedLocation) => string\n },\n): ScrollRestorationEntry | undefined {\n const getKey = options.getKey || defaultGetScrollRestorationKey\n const restoreKey = getKey(router.latestLocation)\n\n if (options.id) {\n return scrollRestorationCache?.state[restoreKey]?.[\n `[${scrollRestorationIdAttribute}=\"${options.id}\"]`\n ]\n }\n\n const element = options.getElement?.()\n if (!element) {\n return\n }\n\n return scrollRestorationCache?.state[restoreKey]?.[\n element instanceof Window ? windowScrollTarget : getCssSelector(element)\n ]\n}\n\nlet ignoreScroll = false\nconst windowScrollTarget = 'window'\nconst scrollRestorationIdAttribute = 'data-scroll-restoration-id'\ntype ScrollTarget = typeof windowScrollTarget | Element\n\nexport function setupScrollRestoration(router: AnyRouter, force?: boolean) {\n if (!scrollRestorationCache && !(isServer ?? router.isServer)) {\n return\n }\n\n const cache = scrollRestorationCache\n\n const shouldScrollRestoration =\n force ?? router.options.scrollRestoration ?? false\n\n if (shouldScrollRestoration) {\n router.isScrollRestoring = true\n }\n\n if (\n (isServer ?? router.isServer) ||\n router.isScrollRestorationSetup ||\n !cache\n ) {\n return\n }\n\n router.isScrollRestorationSetup = true\n ignoreScroll = false\n\n const getKey =\n router.options.getScrollRestorationKey || defaultGetScrollRestorationKey\n const trackedScrollEntries = new Map<ScrollTarget, ScrollRestorationEntry>()\n\n window.history.scrollRestoration = 'manual'\n\n const onScroll = (event: Event) => {\n if (ignoreScroll || !router.isScrollRestoring) {\n return\n }\n\n if (event.target === document || event.target === window) {\n trackedScrollEntries.set(windowScrollTarget, {\n scrollX: window.scrollX || 0,\n scrollY: window.scrollY || 0,\n })\n } else {\n const target = event.target as Element\n trackedScrollEntries.set(target, {\n scrollX: target.scrollLeft || 0,\n scrollY: target.scrollTop || 0,\n })\n }\n }\n\n // Snapshot the current page's tracked scroll targets before navigation or unload.\n const snapshotCurrentScrollTargets = (restoreKey?: string) => {\n if (\n !router.isScrollRestoring ||\n !restoreKey ||\n trackedScrollEntries.size === 0 ||\n !cache\n ) {\n return\n }\n\n const keyEntry = (cache.state[restoreKey] ||=\n {} as ScrollRestorationByElement)\n\n for (const [target, position] of trackedScrollEntries) {\n let selector: string | undefined\n\n if (target === windowScrollTarget) {\n selector = windowScrollTarget\n } else if (target.isConnected) {\n const attrId = target.getAttribute(scrollRestorationIdAttribute)\n selector = attrId\n ? `[${scrollRestorationIdAttribute}=\"${attrId}\"]`\n : getCssSelector(target)\n }\n\n if (!selector) {\n continue\n }\n\n keyEntry[selector] = position\n }\n }\n\n document.addEventListener('scroll', onScroll, true)\n router.subscribe('onBeforeLoad', (event) => {\n snapshotCurrentScrollTargets(\n event.fromLocation ? getKey(event.fromLocation) : undefined,\n )\n trackedScrollEntries.clear()\n })\n window.addEventListener('pagehide', () => {\n snapshotCurrentScrollTargets(\n getKey(\n router.stores.resolvedLocation.state ?? router.stores.location.state,\n ),\n )\n cache.persist()\n })\n\n // Restore destination scroll after the new route has rendered.\n router.subscribe('onRendered', (event) => {\n const cacheKey = getKey(event.toLocation)\n const behavior = router.options.scrollRestorationBehavior\n const scrollToTopSelectors = router.options.scrollToTopSelectors\n trackedScrollEntries.clear()\n\n if (!router.resetNextScroll) {\n router.resetNextScroll = true\n return\n }\n\n if (\n typeof router.options.scrollRestoration === 'function' &&\n !router.options.scrollRestoration({ location: router.latestLocation })\n ) {\n return\n }\n\n ignoreScroll = true\n\n try {\n const elementEntries = router.isScrollRestoring\n ? cache.state[cacheKey]\n : undefined\n let restored = false\n\n if (elementEntries) {\n for (const elementSelector in elementEntries) {\n const entry = elementEntries[elementSelector]\n\n if (!isPlainObject(entry)) {\n continue\n }\n\n const { scrollX, scrollY } = entry as {\n scrollX?: unknown\n scrollY?: unknown\n }\n\n if (!Number.isFinite(scrollX) || !Number.isFinite(scrollY)) {\n continue\n }\n\n if (elementSelector === windowScrollTarget) {\n window.scrollTo({\n top: scrollY as number,\n left: scrollX as number,\n behavior,\n })\n restored = true\n } else if (elementSelector) {\n let element\n\n try {\n element = document.querySelector(elementSelector)\n } catch {\n continue\n }\n\n if (element) {\n element.scrollLeft = scrollX as number\n element.scrollTop = scrollY as number\n restored = true\n }\n }\n }\n }\n\n if (!restored) {\n const hash = router.history.location.hash.slice(1)\n\n if (hash) {\n const hashScrollIntoViewOptions =\n window.history.state?.__hashScrollIntoViewOptions ?? true\n\n if (hashScrollIntoViewOptions) {\n const el = document.getElementById(hash)\n if (el) {\n el.scrollIntoView(hashScrollIntoViewOptions)\n }\n }\n } else {\n const scrollOptions = {\n top: 0,\n left: 0,\n behavior,\n }\n\n window.scrollTo(scrollOptions)\n if (scrollToTopSelectors) {\n for (const selector of scrollToTopSelectors) {\n if (selector === windowScrollTarget) continue\n const element =\n typeof selector === 'function'\n ? selector()\n : document.querySelector(selector)\n if (element) {\n element.scrollTo(scrollOptions)\n }\n }\n }\n }\n }\n } finally {\n ignoreScroll = false\n }\n\n if (router.isScrollRestoring) {\n cache.set((state) => {\n state[cacheKey] ||= {} as ScrollRestorationByElement\n return state\n })\n }\n })\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"scroll-restoration.cjs","names":[],"sources":["../../src/scroll-restoration.ts"],"sourcesContent":["import { isServer } from '@benjavicente/router-core/isServer'\nimport { functionalUpdate, isPlainObject } from './utils'\nimport type { AnyRouter } from './router'\nimport type { ParsedLocation } from './location'\nimport type { NonNullableUpdater } from './utils'\n\nexport type ScrollRestorationEntry = { scrollX: number; scrollY: number }\n\ntype ScrollRestorationByElement = Record<string, ScrollRestorationEntry>\n\ntype ScrollRestorationByKey = Record<string, ScrollRestorationByElement>\n\ntype ScrollRestorationCache = {\n readonly state: ScrollRestorationByKey\n set: (updater: NonNullableUpdater<ScrollRestorationByKey>) => void\n persist: () => void\n}\n\nexport type ScrollRestorationOptions = {\n getKey?: (location: ParsedLocation) => string\n scrollBehavior?: ScrollToOptions['behavior']\n}\n\nfunction getSafeSessionStorage() {\n try {\n return typeof window !== 'undefined' &&\n typeof window.sessionStorage === 'object'\n ? window.sessionStorage\n : undefined\n } catch {\n // silent\n return undefined\n }\n}\n\n// SessionStorage key used to store scroll positions across navigations.\nexport const storageKey = 'tsr-scroll-restoration-v1_3'\n\nfunction createScrollRestorationCache(): ScrollRestorationCache | null {\n const safeSessionStorage = getSafeSessionStorage()\n if (!safeSessionStorage) {\n return null\n }\n\n let state: ScrollRestorationByKey = {}\n\n try {\n const parsed = JSON.parse(safeSessionStorage.getItem(storageKey) || '{}')\n if (isPlainObject(parsed)) {\n state = parsed as ScrollRestorationByKey\n }\n } catch {\n // ignore invalid session storage payloads\n }\n\n const persist = () => {\n try {\n safeSessionStorage.setItem(storageKey, JSON.stringify(state))\n } catch {\n if (process.env.NODE_ENV !== 'production') {\n console.warn(\n '[ts-router] Could not persist scroll restoration state to sessionStorage.',\n )\n }\n }\n }\n\n return {\n get state() {\n return state\n },\n set: (updater) => {\n state = functionalUpdate(updater, state) || state\n },\n persist,\n }\n}\n\nexport const scrollRestorationCache = createScrollRestorationCache()\n\n/**\n * The default `getKey` function for `useScrollRestoration`.\n * It returns the `key` from the location state or the `href` of the location.\n *\n * The `location.href` is used as a fallback to support the use case where the location state is not available like the initial render.\n */\nexport const defaultGetScrollRestorationKey = (location: ParsedLocation) => {\n return location.state.__TSR_key! || location.href\n}\n\nfunction getCssSelector(el: any): string {\n const path = []\n let parent: HTMLElement\n while ((parent = el.parentNode)) {\n path.push(\n `${el.tagName}:nth-child(${Array.prototype.indexOf.call(parent.children, el) + 1})`,\n )\n el = parent\n }\n return `${path.reverse().join(' > ')}`.toLowerCase()\n}\n\nexport function getElementScrollRestorationEntry(\n router: AnyRouter,\n options: (\n | {\n id: string\n getElement?: () => Window | Element | undefined | null\n }\n | {\n id?: string\n getElement: () => Window | Element | undefined | null\n }\n ) & {\n getKey?: (location: ParsedLocation) => string\n },\n): ScrollRestorationEntry | undefined {\n const getKey = options.getKey || defaultGetScrollRestorationKey\n const restoreKey = getKey(router.latestLocation)\n\n if (options.id) {\n return scrollRestorationCache?.state[restoreKey]?.[\n `[${scrollRestorationIdAttribute}=\"${options.id}\"]`\n ]\n }\n\n const element = options.getElement?.()\n if (!element) {\n return\n }\n\n return scrollRestorationCache?.state[restoreKey]?.[\n element instanceof Window ? windowScrollTarget : getCssSelector(element)\n ]\n}\n\nlet ignoreScroll = false\nconst windowScrollTarget = 'window'\nconst scrollRestorationIdAttribute = 'data-scroll-restoration-id'\ntype ScrollTarget = typeof windowScrollTarget | Element\n\nexport function setupScrollRestoration(router: AnyRouter, force?: boolean) {\n if (!scrollRestorationCache && !(isServer ?? router.isServer)) {\n return\n }\n\n const cache = scrollRestorationCache\n\n const shouldScrollRestoration =\n force ?? router.options.scrollRestoration ?? false\n\n if (shouldScrollRestoration) {\n router.isScrollRestoring = true\n }\n\n if (\n (isServer ?? router.isServer) ||\n router.isScrollRestorationSetup ||\n !cache\n ) {\n return\n }\n\n router.isScrollRestorationSetup = true\n ignoreScroll = false\n\n const getKey =\n router.options.getScrollRestorationKey || defaultGetScrollRestorationKey\n const trackedScrollEntries = new Map<ScrollTarget, ScrollRestorationEntry>()\n\n window.history.scrollRestoration = 'manual'\n\n const onScroll = (event: Event) => {\n if (ignoreScroll || !router.isScrollRestoring) {\n return\n }\n\n if (event.target === document || event.target === window) {\n trackedScrollEntries.set(windowScrollTarget, {\n scrollX: window.scrollX || 0,\n scrollY: window.scrollY || 0,\n })\n } else {\n const target = event.target as Element\n trackedScrollEntries.set(target, {\n scrollX: target.scrollLeft || 0,\n scrollY: target.scrollTop || 0,\n })\n }\n }\n\n // Snapshot the current page's tracked scroll targets before navigation or unload.\n const snapshotCurrentScrollTargets = (restoreKey?: string) => {\n if (\n !router.isScrollRestoring ||\n !restoreKey ||\n trackedScrollEntries.size === 0 ||\n !cache\n ) {\n return\n }\n\n const keyEntry = (cache.state[restoreKey] ||=\n {} as ScrollRestorationByElement)\n\n for (const [target, position] of trackedScrollEntries) {\n let selector: string | undefined\n\n if (target === windowScrollTarget) {\n selector = windowScrollTarget\n } else if (target.isConnected) {\n const attrId = target.getAttribute(scrollRestorationIdAttribute)\n selector = attrId\n ? `[${scrollRestorationIdAttribute}=\"${attrId}\"]`\n : getCssSelector(target)\n }\n\n if (!selector) {\n continue\n }\n\n keyEntry[selector] = position\n }\n }\n\n document.addEventListener('scroll', onScroll, true)\n router.subscribe('onBeforeLoad', (event) => {\n snapshotCurrentScrollTargets(\n event.fromLocation ? getKey(event.fromLocation) : undefined,\n )\n trackedScrollEntries.clear()\n })\n window.addEventListener('pagehide', () => {\n snapshotCurrentScrollTargets(\n getKey(\n router.stores.resolvedLocation.get() ?? router.stores.location.get(),\n ),\n )\n cache.persist()\n })\n\n // Restore destination scroll after the new route has rendered.\n router.subscribe('onRendered', (event) => {\n const cacheKey = getKey(event.toLocation)\n const behavior = router.options.scrollRestorationBehavior\n const scrollToTopSelectors = router.options.scrollToTopSelectors\n trackedScrollEntries.clear()\n\n if (!router.resetNextScroll) {\n router.resetNextScroll = true\n return\n }\n\n if (\n typeof router.options.scrollRestoration === 'function' &&\n !router.options.scrollRestoration({ location: router.latestLocation })\n ) {\n return\n }\n\n ignoreScroll = true\n\n try {\n const elementEntries = router.isScrollRestoring\n ? cache.state[cacheKey]\n : undefined\n let restored = false\n\n if (elementEntries) {\n for (const elementSelector in elementEntries) {\n const entry = elementEntries[elementSelector]\n\n if (!isPlainObject(entry)) {\n continue\n }\n\n const { scrollX, scrollY } = entry as {\n scrollX?: unknown\n scrollY?: unknown\n }\n\n if (!Number.isFinite(scrollX) || !Number.isFinite(scrollY)) {\n continue\n }\n\n if (elementSelector === windowScrollTarget) {\n window.scrollTo({\n top: scrollY as number,\n left: scrollX as number,\n behavior,\n })\n restored = true\n } else if (elementSelector) {\n let element\n\n try {\n element = document.querySelector(elementSelector)\n } catch {\n continue\n }\n\n if (element) {\n element.scrollLeft = scrollX as number\n element.scrollTop = scrollY as number\n restored = true\n }\n }\n }\n }\n\n if (!restored) {\n const hash = router.history.location.hash.slice(1)\n\n if (hash) {\n const hashScrollIntoViewOptions =\n window.history.state?.__hashScrollIntoViewOptions ?? true\n\n if (hashScrollIntoViewOptions) {\n const el = document.getElementById(hash)\n if (el) {\n el.scrollIntoView(hashScrollIntoViewOptions)\n }\n }\n } else {\n const scrollOptions = {\n top: 0,\n left: 0,\n behavior,\n }\n\n window.scrollTo(scrollOptions)\n if (scrollToTopSelectors) {\n for (const selector of scrollToTopSelectors) {\n if (selector === windowScrollTarget) continue\n const element =\n typeof selector === 'function'\n ? selector()\n : document.querySelector(selector)\n if (element) {\n element.scrollTo(scrollOptions)\n }\n }\n }\n }\n }\n } finally {\n ignoreScroll = false\n }\n\n if (router.isScrollRestoring) {\n cache.set((state) => {\n state[cacheKey] ||= {} as ScrollRestorationByElement\n return state\n })\n }\n })\n}\n"],"mappings":"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"}
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
const require_qss = require("./qss.cjs");
|
|
2
2
|
//#region src/searchParams.ts
|
|
3
3
|
/** Default `parseSearch` that strips leading '?' and JSON-parses values. */
|
|
4
|
-
|
|
4
|
+
const defaultParseSearch = parseSearchWith(JSON.parse);
|
|
5
5
|
/** Default `stringifySearch` using JSON.stringify for complex values. */
|
|
6
|
-
|
|
6
|
+
const defaultStringifySearch = stringifySearchWith(JSON.stringify, JSON.parse);
|
|
7
7
|
/**
|
|
8
8
|
* Build a `parseSearch` function using a provided JSON-like parser.
|
|
9
9
|
*
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"searchParams.cjs","names":[],"sources":["../../src/searchParams.ts"],"sourcesContent":["import { decode, encode } from './qss'\nimport type { AnySchema } from './validators'\n\n/** Default `parseSearch` that strips leading '?' and JSON-parses values. */\nexport const defaultParseSearch = parseSearchWith(JSON.parse)\n/** Default `stringifySearch` using JSON.stringify for complex values. */\nexport const defaultStringifySearch = stringifySearchWith(\n JSON.stringify,\n JSON.parse,\n)\n\n/**\n * Build a `parseSearch` function using a provided JSON-like parser.\n *\n * The returned function strips a leading `?`, decodes values, and attempts to\n * JSON-parse string values using the given `parser`.\n *\n * @param parser Function to parse a string value (e.g. `JSON.parse`).\n * @returns A `parseSearch` function compatible with `Router` options.\n * @link https://tanstack.com/router/latest/docs/framework/react/guide/custom-search-param-serialization\n */\nexport function parseSearchWith(parser: (str: string) => any) {\n return (searchStr: string): AnySchema => {\n if (searchStr[0] === '?') {\n searchStr = searchStr.substring(1)\n }\n\n const query: Record<string, unknown> = decode(searchStr)\n\n // Try to parse any query params that might be json\n for (const key in query) {\n const value = query[key]\n if (typeof value === 'string') {\n try {\n query[key] = parser(value)\n } catch (_err) {\n // silent\n }\n }\n }\n\n return query\n }\n}\n\n/**\n * Build a `stringifySearch` function using a provided serializer.\n *\n * Non-primitive values are serialized with `stringify`. If a `parser` is\n * supplied, string values that are parseable are re-serialized to ensure\n * symmetry with `parseSearch`.\n *\n * @param stringify Function to serialize a value (e.g. `JSON.stringify`).\n * @param parser Optional parser to detect parseable strings.\n * @returns A `stringifySearch` function compatible with `Router` options.\n * @link https://tanstack.com/router/latest/docs/framework/react/guide/custom-search-param-serialization\n */\nexport function stringifySearchWith(\n stringify: (search: any) => string,\n parser?: (str: string) => any,\n) {\n const hasParser = typeof parser === 'function'\n function stringifyValue(val: any) {\n if (typeof val === 'object' && val !== null) {\n try {\n return stringify(val)\n } catch (_err) {\n // silent\n }\n } else if (hasParser && typeof val === 'string') {\n try {\n // Check if it's a valid parseable string.\n // If it is, then stringify it again.\n parser(val)\n return stringify(val)\n } catch (_err) {\n // silent\n }\n }\n return val\n }\n\n return (search: Record<string, any>) => {\n const searchStr = encode(search, stringifyValue)\n return searchStr ? `?${searchStr}` : ''\n }\n}\n\nexport type SearchSerializer = (searchObj: Record<string, any>) => string\nexport type SearchParser = (searchStr: string) => Record<string, any>\n"],"mappings":";;;AAIA,
|
|
1
|
+
{"version":3,"file":"searchParams.cjs","names":[],"sources":["../../src/searchParams.ts"],"sourcesContent":["import { decode, encode } from './qss'\nimport type { AnySchema } from './validators'\n\n/** Default `parseSearch` that strips leading '?' and JSON-parses values. */\nexport const defaultParseSearch = parseSearchWith(JSON.parse)\n/** Default `stringifySearch` using JSON.stringify for complex values. */\nexport const defaultStringifySearch = stringifySearchWith(\n JSON.stringify,\n JSON.parse,\n)\n\n/**\n * Build a `parseSearch` function using a provided JSON-like parser.\n *\n * The returned function strips a leading `?`, decodes values, and attempts to\n * JSON-parse string values using the given `parser`.\n *\n * @param parser Function to parse a string value (e.g. `JSON.parse`).\n * @returns A `parseSearch` function compatible with `Router` options.\n * @link https://tanstack.com/router/latest/docs/framework/react/guide/custom-search-param-serialization\n */\nexport function parseSearchWith(parser: (str: string) => any) {\n return (searchStr: string): AnySchema => {\n if (searchStr[0] === '?') {\n searchStr = searchStr.substring(1)\n }\n\n const query: Record<string, unknown> = decode(searchStr)\n\n // Try to parse any query params that might be json\n for (const key in query) {\n const value = query[key]\n if (typeof value === 'string') {\n try {\n query[key] = parser(value)\n } catch (_err) {\n // silent\n }\n }\n }\n\n return query\n }\n}\n\n/**\n * Build a `stringifySearch` function using a provided serializer.\n *\n * Non-primitive values are serialized with `stringify`. If a `parser` is\n * supplied, string values that are parseable are re-serialized to ensure\n * symmetry with `parseSearch`.\n *\n * @param stringify Function to serialize a value (e.g. `JSON.stringify`).\n * @param parser Optional parser to detect parseable strings.\n * @returns A `stringifySearch` function compatible with `Router` options.\n * @link https://tanstack.com/router/latest/docs/framework/react/guide/custom-search-param-serialization\n */\nexport function stringifySearchWith(\n stringify: (search: any) => string,\n parser?: (str: string) => any,\n) {\n const hasParser = typeof parser === 'function'\n function stringifyValue(val: any) {\n if (typeof val === 'object' && val !== null) {\n try {\n return stringify(val)\n } catch (_err) {\n // silent\n }\n } else if (hasParser && typeof val === 'string') {\n try {\n // Check if it's a valid parseable string.\n // If it is, then stringify it again.\n parser(val)\n return stringify(val)\n } catch (_err) {\n // silent\n }\n }\n return val\n }\n\n return (search: Record<string, any>) => {\n const searchStr = encode(search, stringifyValue)\n return searchStr ? `?${searchStr}` : ''\n }\n}\n\nexport type SearchSerializer = (searchObj: Record<string, any>) => string\nexport type SearchParser = (searchStr: string) => Record<string, any>\n"],"mappings":";;;AAIA,MAAa,qBAAqB,gBAAgB,KAAK,MAAM;;AAE7D,MAAa,yBAAyB,oBACpC,KAAK,WACL,KAAK,MACN;;;;;;;;;;;AAYD,SAAgB,gBAAgB,QAA8B;AAC5D,SAAQ,cAAiC;AACvC,MAAI,UAAU,OAAO,IACnB,aAAY,UAAU,UAAU,EAAE;EAGpC,MAAM,QAAiC,YAAA,OAAO,UAAU;AAGxD,OAAK,MAAM,OAAO,OAAO;GACvB,MAAM,QAAQ,MAAM;AACpB,OAAI,OAAO,UAAU,SACnB,KAAI;AACF,UAAM,OAAO,OAAO,MAAM;YACnB,MAAM;;AAMnB,SAAO;;;;;;;;;;;;;;;AAgBX,SAAgB,oBACd,WACA,QACA;CACA,MAAM,YAAY,OAAO,WAAW;CACpC,SAAS,eAAe,KAAU;AAChC,MAAI,OAAO,QAAQ,YAAY,QAAQ,KACrC,KAAI;AACF,UAAO,UAAU,IAAI;WACd,MAAM;WAGN,aAAa,OAAO,QAAQ,SACrC,KAAI;AAGF,UAAO,IAAI;AACX,UAAO,UAAU,IAAI;WACd,MAAM;AAIjB,SAAO;;AAGT,SAAQ,WAAgC;EACtC,MAAM,YAAY,YAAA,OAAO,QAAQ,eAAe;AAChD,SAAO,YAAY,IAAI,cAAc"}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
//#region src/ssr/constants.ts
|
|
2
|
-
|
|
3
|
-
|
|
2
|
+
const GLOBAL_TSR = "$_TSR";
|
|
3
|
+
const TSR_SCRIPT_BARRIER_ID = "$tsr-stream-barrier";
|
|
4
4
|
//#endregion
|
|
5
5
|
exports.GLOBAL_TSR = GLOBAL_TSR;
|
|
6
6
|
exports.TSR_SCRIPT_BARRIER_ID = TSR_SCRIPT_BARRIER_ID;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"constants.cjs","names":[],"sources":["../../../src/ssr/constants.ts"],"sourcesContent":["export const GLOBAL_TSR = '$_TSR'\nexport declare const GLOBAL_SEROVAL: '$R'\nexport const TSR_SCRIPT_BARRIER_ID = '$tsr-stream-barrier'\n"],"mappings":";AAAA,
|
|
1
|
+
{"version":3,"file":"constants.cjs","names":[],"sources":["../../../src/ssr/constants.ts"],"sourcesContent":["export const GLOBAL_TSR = '$_TSR'\nexport declare const GLOBAL_SEROVAL: '$R'\nexport const TSR_SCRIPT_BARRIER_ID = '$tsr-stream-barrier'\n"],"mappings":";AAAA,MAAa,aAAa;AAE1B,MAAa,wBAAwB"}
|
|
@@ -33,8 +33,8 @@ function createRequestHandler({ createRouter, request, getRouterManifest }) {
|
|
|
33
33
|
};
|
|
34
34
|
}
|
|
35
35
|
function getRequestHeaders(opts) {
|
|
36
|
-
const matchHeaders = opts.router.stores.
|
|
37
|
-
const redirect = opts.router.stores.redirect.
|
|
36
|
+
const matchHeaders = opts.router.stores.matches.get().map((match) => match.headers);
|
|
37
|
+
const redirect = opts.router.stores.redirect.get();
|
|
38
38
|
if (redirect) matchHeaders.push(redirect.headers);
|
|
39
39
|
return require_headers.mergeHeaders({ "Content-Type": "text/html; charset=UTF-8" }, ...matchHeaders);
|
|
40
40
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"createRequestHandler.cjs","names":[],"sources":["../../../src/ssr/createRequestHandler.ts"],"sourcesContent":["import { createMemoryHistory } from '@benjavicente/history'\nimport { mergeHeaders } from './headers'\nimport {\n attachRouterServerSsrUtils,\n getNormalizedURL,\n getOrigin,\n} from './ssr-server'\nimport type { HandlerCallback } from './handlerCallback'\nimport type { AnyHeaders } from './headers'\nimport type { AnyRouter } from '../router'\nimport type { Manifest } from '../manifest'\n\nexport type RequestHandler<TRouter extends AnyRouter> = (\n cb: HandlerCallback<TRouter>,\n) => Promise<Response>\n\nexport function createRequestHandler<TRouter extends AnyRouter>({\n createRouter,\n request,\n getRouterManifest,\n}: {\n createRouter: () => TRouter\n request: Request\n getRouterManifest?: () => Manifest | Promise<Manifest>\n}): RequestHandler<TRouter> {\n return async (cb) => {\n const router = createRouter()\n // Track whether the callback will handle cleanup\n let cbWillCleanup = false\n\n try {\n attachRouterServerSsrUtils({\n router,\n manifest: await getRouterManifest?.(),\n })\n\n // normalizing and sanitizing the pathname here for server, so we always deal with the same format during SSR.\n const { url } = getNormalizedURL(request.url, 'http://localhost')\n const origin = getOrigin(request)\n const href = url.href.replace(url.origin, '')\n\n // Create a history for the router\n const history = createMemoryHistory({\n initialEntries: [href],\n })\n\n // Update the router with the history and context\n router.update({\n history,\n origin: router.options.origin ?? origin,\n })\n\n await router.load()\n\n await router.serverSsr?.dehydrate()\n\n const responseHeaders = getRequestHeaders({\n router,\n })\n\n // Mark that the callback will handle cleanup\n cbWillCleanup = true\n return cb({\n request,\n router,\n responseHeaders,\n })\n } finally {\n if (!cbWillCleanup) {\n // Clean up router SSR state if the callback won't handle it\n // (e.g., if an error occurred before the callback was invoked).\n // When the callback runs, it handles cleanup (either via transformStreamWithRouter\n // for streaming, or directly in renderRouterToString for non-streaming).\n router.serverSsr?.cleanup()\n }\n }\n }\n}\n\nfunction getRequestHeaders(opts: { router: AnyRouter }): Headers {\n const matchHeaders
|
|
1
|
+
{"version":3,"file":"createRequestHandler.cjs","names":[],"sources":["../../../src/ssr/createRequestHandler.ts"],"sourcesContent":["import { createMemoryHistory } from '@benjavicente/history'\nimport { mergeHeaders } from './headers'\nimport {\n attachRouterServerSsrUtils,\n getNormalizedURL,\n getOrigin,\n} from './ssr-server'\nimport type { HandlerCallback } from './handlerCallback'\nimport type { AnyHeaders } from './headers'\nimport type { AnyRouter } from '../router'\nimport type { Manifest } from '../manifest'\n\nexport type RequestHandler<TRouter extends AnyRouter> = (\n cb: HandlerCallback<TRouter>,\n) => Promise<Response>\n\nexport function createRequestHandler<TRouter extends AnyRouter>({\n createRouter,\n request,\n getRouterManifest,\n}: {\n createRouter: () => TRouter\n request: Request\n getRouterManifest?: () => Manifest | Promise<Manifest>\n}): RequestHandler<TRouter> {\n return async (cb) => {\n const router = createRouter()\n // Track whether the callback will handle cleanup\n let cbWillCleanup = false\n\n try {\n attachRouterServerSsrUtils({\n router,\n manifest: await getRouterManifest?.(),\n })\n\n // normalizing and sanitizing the pathname here for server, so we always deal with the same format during SSR.\n const { url } = getNormalizedURL(request.url, 'http://localhost')\n const origin = getOrigin(request)\n const href = url.href.replace(url.origin, '')\n\n // Create a history for the router\n const history = createMemoryHistory({\n initialEntries: [href],\n })\n\n // Update the router with the history and context\n router.update({\n history,\n origin: router.options.origin ?? origin,\n })\n\n await router.load()\n\n await router.serverSsr?.dehydrate()\n\n const responseHeaders = getRequestHeaders({\n router,\n })\n\n // Mark that the callback will handle cleanup\n cbWillCleanup = true\n return cb({\n request,\n router,\n responseHeaders,\n })\n } finally {\n if (!cbWillCleanup) {\n // Clean up router SSR state if the callback won't handle it\n // (e.g., if an error occurred before the callback was invoked).\n // When the callback runs, it handles cleanup (either via transformStreamWithRouter\n // for streaming, or directly in renderRouterToString for non-streaming).\n router.serverSsr?.cleanup()\n }\n }\n }\n}\n\nfunction getRequestHeaders(opts: { router: AnyRouter }): Headers {\n const matchHeaders = opts.router.stores.matches\n .get()\n .map<AnyHeaders>((match) => match.headers)\n\n // Handle Redirects\n const redirect = opts.router.stores.redirect.get()\n if (redirect) {\n matchHeaders.push(redirect.headers)\n }\n\n return mergeHeaders(\n {\n 'Content-Type': 'text/html; charset=UTF-8',\n },\n ...matchHeaders,\n )\n}\n"],"mappings":";;;;AAgBA,SAAgB,qBAAgD,EAC9D,cACA,SACA,qBAK0B;AAC1B,QAAO,OAAO,OAAO;EACnB,MAAM,SAAS,cAAc;EAE7B,IAAI,gBAAgB;AAEpB,MAAI;AACF,sBAAA,2BAA2B;IACzB;IACA,UAAU,MAAM,qBAAqB;IACtC,CAAC;GAGF,MAAM,EAAE,QAAQ,mBAAA,iBAAiB,QAAQ,KAAK,mBAAmB;GACjE,MAAM,SAAS,mBAAA,UAAU,QAAQ;GAIjC,MAAM,WAAA,GAAA,sBAAA,qBAA8B,EAClC,gBAAgB,CAJL,IAAI,KAAK,QAAQ,IAAI,QAAQ,GAAG,CAIrB,EACvB,CAAC;AAGF,UAAO,OAAO;IACZ;IACA,QAAQ,OAAO,QAAQ,UAAU;IAClC,CAAC;AAEF,SAAM,OAAO,MAAM;AAEnB,SAAM,OAAO,WAAW,WAAW;GAEnC,MAAM,kBAAkB,kBAAkB,EACxC,QACD,CAAC;AAGF,mBAAgB;AAChB,UAAO,GAAG;IACR;IACA;IACA;IACD,CAAC;YACM;AACR,OAAI,CAAC,cAKH,QAAO,WAAW,SAAS;;;;AAMnC,SAAS,kBAAkB,MAAsC;CAC/D,MAAM,eAAe,KAAK,OAAO,OAAO,QACrC,KAAK,CACL,KAAiB,UAAU,MAAM,QAAQ;CAG5C,MAAM,WAAW,KAAK,OAAO,OAAO,SAAS,KAAK;AAClD,KAAI,SACF,cAAa,KAAK,SAAS,QAAQ;AAGrC,QAAO,gBAAA,aACL,EACE,gBAAgB,4BACjB,EACD,GAAG,aACJ"}
|
|
@@ -19,8 +19,8 @@ var RawStream = class {
|
|
|
19
19
|
this.hint = options?.hint ?? "binary";
|
|
20
20
|
}
|
|
21
21
|
};
|
|
22
|
-
|
|
23
|
-
|
|
22
|
+
const BufferCtor = globalThis.Buffer;
|
|
23
|
+
const hasNodeBuffer = !!BufferCtor && typeof BufferCtor.from === "function";
|
|
24
24
|
function uint8ArrayToBase64(bytes) {
|
|
25
25
|
if (bytes.length === 0) return "";
|
|
26
26
|
if (hasNodeBuffer) return BufferCtor.from(bytes).toString("base64");
|
|
@@ -43,9 +43,9 @@ function base64ToUint8Array(base64) {
|
|
|
43
43
|
for (let i = 0; i < binary.length; i++) bytes[i] = binary.charCodeAt(i);
|
|
44
44
|
return bytes;
|
|
45
45
|
}
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
46
|
+
const RAW_STREAM_FACTORY_BINARY = Object.create(null);
|
|
47
|
+
const RAW_STREAM_FACTORY_TEXT = Object.create(null);
|
|
48
|
+
const RAW_STREAM_FACTORY_CONSTRUCTOR_BINARY = (stream) => new ReadableStream({ start(controller) {
|
|
49
49
|
stream.on({
|
|
50
50
|
next(base64) {
|
|
51
51
|
try {
|
|
@@ -62,8 +62,8 @@ var RAW_STREAM_FACTORY_CONSTRUCTOR_BINARY = (stream) => new ReadableStream({ sta
|
|
|
62
62
|
}
|
|
63
63
|
});
|
|
64
64
|
} });
|
|
65
|
-
|
|
66
|
-
|
|
65
|
+
const textEncoderForFactory = new TextEncoder();
|
|
66
|
+
const RAW_STREAM_FACTORY_CONSTRUCTOR_TEXT = (stream) => {
|
|
67
67
|
return new ReadableStream({ start(controller) {
|
|
68
68
|
stream.on({
|
|
69
69
|
next(value) {
|
|
@@ -83,8 +83,8 @@ var RAW_STREAM_FACTORY_CONSTRUCTOR_TEXT = (stream) => {
|
|
|
83
83
|
});
|
|
84
84
|
} });
|
|
85
85
|
};
|
|
86
|
-
|
|
87
|
-
|
|
86
|
+
const FACTORY_BINARY = `(s=>new ReadableStream({start(c){s.on({next(b){try{const d=atob(b),a=new Uint8Array(d.length);for(let i=0;i<d.length;i++)a[i]=d.charCodeAt(i);c.enqueue(a)}catch(_){}},throw(e){c.error(e)},return(){try{c.close()}catch(_){}}})}}))`;
|
|
87
|
+
const FACTORY_TEXT = `(s=>{const e=new TextEncoder();return new ReadableStream({start(c){s.on({next(v){try{if(typeof v==='string'){c.enqueue(e.encode(v))}else{const d=atob(v.$b64),a=new Uint8Array(d.length);for(let i=0;i<d.length;i++)a[i]=d.charCodeAt(i);c.enqueue(a)}}catch(_){}},throw(x){c.error(x)},return(){try{c.close()}catch(_){}}})}})})`;
|
|
88
88
|
function toBinaryStream(readable) {
|
|
89
89
|
const stream = (0, seroval.createStream)();
|
|
90
90
|
const reader = readable.getReader();
|
|
@@ -137,41 +137,49 @@ function toTextStream(readable) {
|
|
|
137
137
|
})();
|
|
138
138
|
return stream;
|
|
139
139
|
}
|
|
140
|
-
|
|
140
|
+
const RawStreamFactoryBinaryPlugin = /* @__PURE__ */ (0, seroval.createPlugin)({
|
|
141
141
|
tag: "tss/RawStreamFactory",
|
|
142
142
|
test(value) {
|
|
143
143
|
return value === RAW_STREAM_FACTORY_BINARY;
|
|
144
144
|
},
|
|
145
145
|
parse: {
|
|
146
|
-
sync() {
|
|
147
|
-
|
|
148
|
-
return Promise.resolve(void 0);
|
|
146
|
+
sync(_value, _ctx, _data) {
|
|
147
|
+
return {};
|
|
149
148
|
},
|
|
150
|
-
|
|
149
|
+
async async(_value, _ctx, _data) {
|
|
150
|
+
return {};
|
|
151
|
+
},
|
|
152
|
+
stream(_value, _ctx, _data) {
|
|
153
|
+
return {};
|
|
154
|
+
}
|
|
151
155
|
},
|
|
152
|
-
serialize() {
|
|
156
|
+
serialize(_node, _ctx, _data) {
|
|
153
157
|
return FACTORY_BINARY;
|
|
154
158
|
},
|
|
155
|
-
deserialize() {
|
|
159
|
+
deserialize(_node, _ctx, _data) {
|
|
156
160
|
return RAW_STREAM_FACTORY_BINARY;
|
|
157
161
|
}
|
|
158
162
|
});
|
|
159
|
-
|
|
163
|
+
const RawStreamFactoryTextPlugin = /* @__PURE__ */ (0, seroval.createPlugin)({
|
|
160
164
|
tag: "tss/RawStreamFactoryText",
|
|
161
165
|
test(value) {
|
|
162
166
|
return value === RAW_STREAM_FACTORY_TEXT;
|
|
163
167
|
},
|
|
164
168
|
parse: {
|
|
165
|
-
sync() {
|
|
166
|
-
|
|
167
|
-
return Promise.resolve(void 0);
|
|
169
|
+
sync(_value, _ctx, _data) {
|
|
170
|
+
return {};
|
|
168
171
|
},
|
|
169
|
-
|
|
172
|
+
async async(_value, _ctx, _data) {
|
|
173
|
+
return {};
|
|
174
|
+
},
|
|
175
|
+
stream(_value, _ctx, _data) {
|
|
176
|
+
return {};
|
|
177
|
+
}
|
|
170
178
|
},
|
|
171
|
-
serialize() {
|
|
179
|
+
serialize(_node, _ctx, _data) {
|
|
172
180
|
return FACTORY_TEXT;
|
|
173
181
|
},
|
|
174
|
-
deserialize() {
|
|
182
|
+
deserialize(_node, _ctx, _data) {
|
|
175
183
|
return RAW_STREAM_FACTORY_TEXT;
|
|
176
184
|
}
|
|
177
185
|
});
|
|
@@ -183,46 +191,46 @@ var RawStreamFactoryTextPlugin = (0, seroval.createPlugin)({
|
|
|
183
191
|
* - 'binary': Always base64 encode (default)
|
|
184
192
|
* - 'text': Try UTF-8 first, fallback to base64 for invalid UTF-8
|
|
185
193
|
*/
|
|
186
|
-
|
|
194
|
+
const RawStreamSSRPlugin = /* @__PURE__ */ (0, seroval.createPlugin)({
|
|
187
195
|
tag: "tss/RawStream",
|
|
188
196
|
extends: [RawStreamFactoryBinaryPlugin, RawStreamFactoryTextPlugin],
|
|
189
197
|
test(value) {
|
|
190
198
|
return value instanceof RawStream;
|
|
191
199
|
},
|
|
192
200
|
parse: {
|
|
193
|
-
sync(value, ctx) {
|
|
201
|
+
sync(value, ctx, _data) {
|
|
194
202
|
const factory = value.hint === "text" ? RAW_STREAM_FACTORY_TEXT : RAW_STREAM_FACTORY_BINARY;
|
|
195
203
|
return {
|
|
196
|
-
hint: value.hint,
|
|
204
|
+
hint: ctx.parse(value.hint),
|
|
197
205
|
factory: ctx.parse(factory),
|
|
198
206
|
stream: ctx.parse((0, seroval.createStream)())
|
|
199
207
|
};
|
|
200
208
|
},
|
|
201
|
-
async async(value, ctx) {
|
|
209
|
+
async async(value, ctx, _data) {
|
|
202
210
|
const factory = value.hint === "text" ? RAW_STREAM_FACTORY_TEXT : RAW_STREAM_FACTORY_BINARY;
|
|
203
211
|
const encodedStream = value.hint === "text" ? toTextStream(value.stream) : toBinaryStream(value.stream);
|
|
204
212
|
return {
|
|
205
|
-
hint: value.hint,
|
|
213
|
+
hint: await ctx.parse(value.hint),
|
|
206
214
|
factory: await ctx.parse(factory),
|
|
207
215
|
stream: await ctx.parse(encodedStream)
|
|
208
216
|
};
|
|
209
217
|
},
|
|
210
|
-
stream(value, ctx) {
|
|
218
|
+
stream(value, ctx, _data) {
|
|
211
219
|
const factory = value.hint === "text" ? RAW_STREAM_FACTORY_TEXT : RAW_STREAM_FACTORY_BINARY;
|
|
212
220
|
const encodedStream = value.hint === "text" ? toTextStream(value.stream) : toBinaryStream(value.stream);
|
|
213
221
|
return {
|
|
214
|
-
hint: value.hint,
|
|
222
|
+
hint: ctx.parse(value.hint),
|
|
215
223
|
factory: ctx.parse(factory),
|
|
216
224
|
stream: ctx.parse(encodedStream)
|
|
217
225
|
};
|
|
218
226
|
}
|
|
219
227
|
},
|
|
220
|
-
serialize(node, ctx) {
|
|
228
|
+
serialize(node, ctx, _data) {
|
|
221
229
|
return "(" + ctx.serialize(node.factory) + ")(" + ctx.serialize(node.stream) + ")";
|
|
222
230
|
},
|
|
223
|
-
deserialize(node, ctx) {
|
|
231
|
+
deserialize(node, ctx, _data) {
|
|
224
232
|
const stream = ctx.deserialize(node.stream);
|
|
225
|
-
return node.hint === "text" ? RAW_STREAM_FACTORY_CONSTRUCTOR_TEXT(stream) : RAW_STREAM_FACTORY_CONSTRUCTOR_BINARY(stream);
|
|
233
|
+
return ctx.deserialize(node.hint) === "text" ? RAW_STREAM_FACTORY_CONSTRUCTOR_TEXT(stream) : RAW_STREAM_FACTORY_CONSTRUCTOR_BINARY(stream);
|
|
226
234
|
}
|
|
227
235
|
});
|
|
228
236
|
/**
|
|
@@ -232,23 +240,24 @@ var RawStreamSSRPlugin = (0, seroval.createPlugin)({
|
|
|
232
240
|
*
|
|
233
241
|
* @param onRawStream Callback invoked when a RawStream is encountered during serialization
|
|
234
242
|
*/
|
|
243
|
+
/* @__NO_SIDE_EFFECTS__ */
|
|
235
244
|
function createRawStreamRPCPlugin(onRawStream) {
|
|
236
245
|
let nextStreamId = 1;
|
|
237
|
-
return (0, seroval.createPlugin)({
|
|
246
|
+
return /* @__PURE__ */ (0, seroval.createPlugin)({
|
|
238
247
|
tag: "tss/RawStream",
|
|
239
248
|
test(value) {
|
|
240
249
|
return value instanceof RawStream;
|
|
241
250
|
},
|
|
242
251
|
parse: {
|
|
243
|
-
async(value) {
|
|
252
|
+
async async(value, ctx, _data) {
|
|
244
253
|
const streamId = nextStreamId++;
|
|
245
254
|
onRawStream(streamId, value.stream);
|
|
246
|
-
return
|
|
255
|
+
return { streamId: await ctx.parse(streamId) };
|
|
247
256
|
},
|
|
248
|
-
stream(value) {
|
|
257
|
+
stream(value, ctx, _data) {
|
|
249
258
|
const streamId = nextStreamId++;
|
|
250
259
|
onRawStream(streamId, value.stream);
|
|
251
|
-
return { streamId };
|
|
260
|
+
return { streamId: ctx.parse(streamId) };
|
|
252
261
|
}
|
|
253
262
|
},
|
|
254
263
|
serialize() {
|
|
@@ -266,15 +275,15 @@ function createRawStreamRPCPlugin(onRawStream) {
|
|
|
266
275
|
* @param getOrCreateStream Function to get/create a stream by ID from frame decoder
|
|
267
276
|
*/
|
|
268
277
|
function createRawStreamDeserializePlugin(getOrCreateStream) {
|
|
269
|
-
return (0, seroval.createPlugin)({
|
|
278
|
+
return /* @__PURE__ */ (0, seroval.createPlugin)({
|
|
270
279
|
tag: "tss/RawStream",
|
|
271
280
|
test: () => false,
|
|
272
281
|
parse: {},
|
|
273
282
|
serialize() {
|
|
274
283
|
throw new Error("RawStreamDeserializePlugin.serialize should not be called. Client only deserializes.");
|
|
275
284
|
},
|
|
276
|
-
deserialize(node) {
|
|
277
|
-
return getOrCreateStream(node.streamId);
|
|
285
|
+
deserialize(node, ctx, _data) {
|
|
286
|
+
return getOrCreateStream(typeof ctx?.deserialize === "function" ? ctx.deserialize(node.streamId) : node.streamId);
|
|
278
287
|
}
|
|
279
288
|
});
|
|
280
289
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"RawStream.cjs","names":[],"sources":["../../../../src/ssr/serializer/RawStream.ts"],"sourcesContent":["import { createPlugin, createStream } from 'seroval'\nimport type { Plugin } from 'seroval'\n\n/**\n * Hint for RawStream encoding strategy during SSR serialization.\n * - 'binary': Always use base64 encoding (best for binary data like files, images)\n * - 'text': Try UTF-8 first, fallback to base64 (best for text-heavy data like RSC payloads)\n */\nexport type RawStreamHint = 'binary' | 'text'\n\n/**\n * Options for RawStream configuration.\n */\nexport interface RawStreamOptions {\n /**\n * Encoding hint for SSR serialization.\n * - 'binary' (default): Always use base64 encoding\n * - 'text': Try UTF-8 first, fallback to base64 for invalid UTF-8 chunks\n */\n hint?: RawStreamHint\n}\n\n/**\n * Marker class for ReadableStream<Uint8Array> that should be serialized\n * with base64 encoding (SSR) or binary framing (server functions).\n *\n * Wrap your binary streams with this to get efficient serialization:\n * ```ts\n * // For binary data (files, images, etc.)\n * return { data: new RawStream(file.stream()) }\n *\n * // For text-heavy data (RSC payloads, etc.)\n * return { data: new RawStream(rscStream, { hint: 'text' }) }\n * ```\n */\nexport class RawStream {\n public readonly hint: RawStreamHint\n\n constructor(\n public readonly stream: ReadableStream<Uint8Array>,\n options?: RawStreamOptions,\n ) {\n this.hint = options?.hint ?? 'binary'\n }\n}\n\n/**\n * Callback type for RPC plugin to register raw streams with multiplexer\n */\nexport type OnRawStreamCallback = (\n streamId: number,\n stream: ReadableStream<Uint8Array>,\n) => void\n\n// Base64 helpers used in both Node and browser.\n// In Node-like runtimes, prefer Buffer for speed and compatibility.\nconst BufferCtor: any = (globalThis as any).Buffer\nconst hasNodeBuffer = !!BufferCtor && typeof BufferCtor.from === 'function'\n\nfunction uint8ArrayToBase64(bytes: Uint8Array): string {\n if (bytes.length === 0) return ''\n\n if (hasNodeBuffer) {\n return BufferCtor.from(bytes).toString('base64')\n }\n\n // Browser fallback: chunked String.fromCharCode + btoa\n const CHUNK_SIZE = 0x8000 // 32KB chunks to avoid stack overflow\n const chunks: Array<string> = []\n for (let i = 0; i < bytes.length; i += CHUNK_SIZE) {\n const chunk = bytes.subarray(i, i + CHUNK_SIZE)\n chunks.push(String.fromCharCode.apply(null, chunk as any))\n }\n return btoa(chunks.join(''))\n}\n\nfunction base64ToUint8Array(base64: string): Uint8Array {\n if (base64.length === 0) return new Uint8Array(0)\n\n if (hasNodeBuffer) {\n const buf = BufferCtor.from(base64, 'base64')\n return new Uint8Array(buf.buffer, buf.byteOffset, buf.byteLength)\n }\n\n const binary = atob(base64)\n const bytes = new Uint8Array(binary.length)\n for (let i = 0; i < binary.length; i++) {\n bytes[i] = binary.charCodeAt(i)\n }\n return bytes\n}\n\n// Factory sentinels - use null-proto objects to avoid prototype surprises\nconst RAW_STREAM_FACTORY_BINARY: Record<string, never> = Object.create(null)\nconst RAW_STREAM_FACTORY_TEXT: Record<string, never> = Object.create(null)\n\n// Factory constructor for binary mode - converts seroval stream to ReadableStream<Uint8Array>\n// All chunks are base64 encoded strings\nconst RAW_STREAM_FACTORY_CONSTRUCTOR_BINARY = (\n stream: ReturnType<typeof createStream>,\n) =>\n new ReadableStream<Uint8Array>({\n start(controller) {\n stream.on({\n next(base64: string) {\n try {\n controller.enqueue(base64ToUint8Array(base64))\n } catch {\n // Stream may be closed\n }\n },\n throw(error: unknown) {\n controller.error(error)\n },\n return() {\n try {\n controller.close()\n } catch {\n // Stream may already be closed\n }\n },\n })\n },\n })\n\n// Factory constructor for text mode - converts seroval stream to ReadableStream<Uint8Array>\n// Chunks are either strings (UTF-8) or { $b64: string } (base64 fallback)\n// Use module-level TextEncoder to avoid per-factory allocation\nconst textEncoderForFactory = new TextEncoder()\nconst RAW_STREAM_FACTORY_CONSTRUCTOR_TEXT = (\n stream: ReturnType<typeof createStream>,\n) => {\n return new ReadableStream<Uint8Array>({\n start(controller) {\n stream.on({\n next(value: string | { $b64: string }) {\n try {\n if (typeof value === 'string') {\n controller.enqueue(textEncoderForFactory.encode(value))\n } else {\n controller.enqueue(base64ToUint8Array(value.$b64))\n }\n } catch {\n // Stream may be closed\n }\n },\n throw(error: unknown) {\n controller.error(error)\n },\n return() {\n try {\n controller.close()\n } catch {\n // Stream may already be closed\n }\n },\n })\n },\n })\n}\n\n// Minified factory function for binary mode - all chunks are base64 strings\n// This must be self-contained since it's injected into the HTML\nconst FACTORY_BINARY = `(s=>new ReadableStream({start(c){s.on({next(b){try{const d=atob(b),a=new Uint8Array(d.length);for(let i=0;i<d.length;i++)a[i]=d.charCodeAt(i);c.enqueue(a)}catch(_){}},throw(e){c.error(e)},return(){try{c.close()}catch(_){}}})}}))`\n\n// Minified factory function for text mode - chunks are string or {$b64: string}\n// Uses cached TextEncoder for performance\nconst FACTORY_TEXT = `(s=>{const e=new TextEncoder();return new ReadableStream({start(c){s.on({next(v){try{if(typeof v==='string'){c.enqueue(e.encode(v))}else{const d=atob(v.$b64),a=new Uint8Array(d.length);for(let i=0;i<d.length;i++)a[i]=d.charCodeAt(i);c.enqueue(a)}}catch(_){}},throw(x){c.error(x)},return(){try{c.close()}catch(_){}}})}})})`\n\n// Convert ReadableStream<Uint8Array> to seroval stream with base64-encoded chunks (binary mode)\nfunction toBinaryStream(readable: ReadableStream<Uint8Array>) {\n const stream = createStream()\n const reader = readable.getReader()\n\n // Use iterative loop instead of recursive async to avoid stack accumulation\n ;(async () => {\n try {\n while (true) {\n const { done, value } = await reader.read()\n if (done) {\n stream.return(undefined)\n break\n }\n stream.next(uint8ArrayToBase64(value))\n }\n } catch (error) {\n stream.throw(error)\n } finally {\n reader.releaseLock()\n }\n })()\n\n return stream\n}\n\n// Convert ReadableStream<Uint8Array> to seroval stream with UTF-8 first, base64 fallback (text mode)\nfunction toTextStream(readable: ReadableStream<Uint8Array>) {\n const stream = createStream()\n const reader = readable.getReader()\n const decoder = new TextDecoder('utf-8', { fatal: true })\n\n // Use iterative loop instead of recursive async to avoid stack accumulation\n ;(async () => {\n try {\n while (true) {\n const { done, value } = await reader.read()\n if (done) {\n // Flush any remaining bytes in the decoder\n try {\n const remaining = decoder.decode()\n if (remaining.length > 0) {\n stream.next(remaining)\n }\n } catch {\n // Ignore decode errors on flush\n }\n stream.return(undefined)\n break\n }\n\n try {\n // Try UTF-8 decode first\n const text = decoder.decode(value, { stream: true })\n if (text.length > 0) {\n stream.next(text)\n }\n } catch {\n // UTF-8 decode failed, fallback to base64\n stream.next({ $b64: uint8ArrayToBase64(value) })\n }\n }\n } catch (error) {\n stream.throw(error)\n } finally {\n reader.releaseLock()\n }\n })()\n\n return stream\n}\n\n// Factory plugin for binary mode\nconst RawStreamFactoryBinaryPlugin = createPlugin<\n Record<string, never>,\n undefined\n>({\n tag: 'tss/RawStreamFactory',\n test(value) {\n return value === RAW_STREAM_FACTORY_BINARY\n },\n parse: {\n sync() {\n return undefined\n },\n async() {\n return Promise.resolve(undefined)\n },\n stream() {\n return undefined\n },\n },\n serialize() {\n return FACTORY_BINARY\n },\n deserialize() {\n return RAW_STREAM_FACTORY_BINARY\n },\n})\n\n// Factory plugin for text mode\nconst RawStreamFactoryTextPlugin = createPlugin<\n Record<string, never>,\n undefined\n>({\n tag: 'tss/RawStreamFactoryText',\n test(value) {\n return value === RAW_STREAM_FACTORY_TEXT\n },\n parse: {\n sync() {\n return undefined\n },\n async() {\n return Promise.resolve(undefined)\n },\n stream() {\n return undefined\n },\n },\n serialize() {\n return FACTORY_TEXT\n },\n deserialize() {\n return RAW_STREAM_FACTORY_TEXT\n },\n})\n\n/**\n * SSR Plugin - uses base64 or UTF-8+base64 encoding for chunks, delegates to seroval's stream mechanism.\n * Used during SSR when serializing to JavaScript code for HTML injection.\n *\n * Supports two modes based on RawStream hint:\n * - 'binary': Always base64 encode (default)\n * - 'text': Try UTF-8 first, fallback to base64 for invalid UTF-8\n */\nexport const RawStreamSSRPlugin: Plugin<any, any> = createPlugin({\n tag: 'tss/RawStream',\n extends: [RawStreamFactoryBinaryPlugin, RawStreamFactoryTextPlugin],\n\n test(value: unknown) {\n return value instanceof RawStream\n },\n\n parse: {\n sync(value: RawStream, ctx) {\n // Sync parse not really supported for streams, return empty stream\n const factory =\n value.hint === 'text'\n ? RAW_STREAM_FACTORY_TEXT\n : RAW_STREAM_FACTORY_BINARY\n return {\n hint: value.hint,\n factory: ctx.parse(factory),\n stream: ctx.parse(createStream()),\n }\n },\n async async(value: RawStream, ctx) {\n const factory =\n value.hint === 'text'\n ? RAW_STREAM_FACTORY_TEXT\n : RAW_STREAM_FACTORY_BINARY\n const encodedStream =\n value.hint === 'text'\n ? toTextStream(value.stream)\n : toBinaryStream(value.stream)\n return {\n hint: value.hint,\n factory: await ctx.parse(factory),\n stream: await ctx.parse(encodedStream),\n }\n },\n stream(value: RawStream, ctx) {\n const factory =\n value.hint === 'text'\n ? RAW_STREAM_FACTORY_TEXT\n : RAW_STREAM_FACTORY_BINARY\n const encodedStream =\n value.hint === 'text'\n ? toTextStream(value.stream)\n : toBinaryStream(value.stream)\n return {\n hint: value.hint,\n factory: ctx.parse(factory),\n stream: ctx.parse(encodedStream),\n }\n },\n },\n\n serialize(node: { hint: RawStreamHint; factory: any; stream: any }, ctx) {\n return (\n '(' +\n ctx.serialize(node.factory) +\n ')(' +\n ctx.serialize(node.stream) +\n ')'\n )\n },\n\n deserialize(\n node: { hint: RawStreamHint; factory: any; stream: any },\n ctx,\n ): any {\n const stream: ReturnType<typeof createStream> = ctx.deserialize(node.stream)\n return node.hint === 'text'\n ? RAW_STREAM_FACTORY_CONSTRUCTOR_TEXT(stream)\n : RAW_STREAM_FACTORY_CONSTRUCTOR_BINARY(stream)\n },\n}) as Plugin<any, any>\n\n/**\n * Node type for RPC plugin serialization\n */\ninterface RawStreamRPCNode {\n streamId: number\n}\n\n/**\n * Creates an RPC plugin instance that registers raw streams with a multiplexer.\n * Used for server function responses where we want binary framing.\n * Note: RPC always uses binary framing regardless of hint.\n *\n * @param onRawStream Callback invoked when a RawStream is encountered during serialization\n */\nexport function createRawStreamRPCPlugin(\n onRawStream: OnRawStreamCallback,\n): Plugin<any, any> {\n // Own stream counter - sequential IDs starting at 1, independent of seroval internals\n let nextStreamId = 1\n\n return createPlugin({\n tag: 'tss/RawStream',\n\n test(value: unknown) {\n return value instanceof RawStream\n },\n\n parse: {\n async(value: RawStream) {\n const streamId = nextStreamId++\n onRawStream(streamId, value.stream)\n return Promise.resolve({ streamId })\n },\n stream(value: RawStream) {\n const streamId = nextStreamId++\n onRawStream(streamId, value.stream)\n return { streamId }\n },\n },\n\n serialize(): never {\n // RPC uses toCrossJSONStream which produces JSON nodes, not JS code.\n // This method is only called by crossSerialize* which we don't use.\n throw new Error(\n 'RawStreamRPCPlugin.serialize should not be called. RPC uses JSON serialization, not JS code generation.',\n )\n },\n\n deserialize(): never {\n // Client uses createRawStreamDeserializePlugin instead\n throw new Error(\n 'RawStreamRPCPlugin.deserialize should not be called. Use createRawStreamDeserializePlugin on client.',\n )\n },\n }) as Plugin<any, any>\n}\n\n/**\n * Creates a deserialize-only plugin for client-side stream reconstruction.\n * Used in serverFnFetcher to wire up streams from frame decoder.\n *\n * @param getOrCreateStream Function to get/create a stream by ID from frame decoder\n */\nexport function createRawStreamDeserializePlugin(\n getOrCreateStream: (id: number) => ReadableStream<Uint8Array>,\n): Plugin<any, any> {\n return createPlugin({\n tag: 'tss/RawStream',\n\n test: () => false, // Client never serializes RawStream\n\n parse: {}, // Client only deserializes, never parses\n\n serialize(): never {\n // Client never serializes RawStream back to server\n throw new Error(\n 'RawStreamDeserializePlugin.serialize should not be called. Client only deserializes.',\n )\n },\n\n deserialize(node: RawStreamRPCNode) {\n return getOrCreateStream(node.streamId)\n },\n }) as Plugin<any, any>\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"RawStream.cjs","names":[],"sources":["../../../../src/ssr/serializer/RawStream.ts"],"sourcesContent":["import { createPlugin, createStream } from 'seroval'\nimport type { PluginData, PluginInfo, SerovalNode } from 'seroval'\n\n/**\n * Hint for RawStream encoding strategy during SSR serialization.\n * - 'binary': Always use base64 encoding (best for binary data like files, images)\n * - 'text': Try UTF-8 first, fallback to base64 (best for text-heavy data like RSC payloads)\n */\nexport type RawStreamHint = 'binary' | 'text'\n\n/**\n * Options for RawStream configuration.\n */\nexport interface RawStreamOptions {\n /**\n * Encoding hint for SSR serialization.\n * - 'binary' (default): Always use base64 encoding\n * - 'text': Try UTF-8 first, fallback to base64 for invalid UTF-8 chunks\n */\n hint?: RawStreamHint\n}\n\n/**\n * Marker class for ReadableStream<Uint8Array> that should be serialized\n * with base64 encoding (SSR) or binary framing (server functions).\n *\n * Wrap your binary streams with this to get efficient serialization:\n * ```ts\n * // For binary data (files, images, etc.)\n * return { data: new RawStream(file.stream()) }\n *\n * // For text-heavy data (RSC payloads, etc.)\n * return { data: new RawStream(rscStream, { hint: 'text' }) }\n * ```\n */\nexport class RawStream {\n public readonly hint: RawStreamHint\n\n constructor(\n public readonly stream: ReadableStream<Uint8Array>,\n options?: RawStreamOptions,\n ) {\n this.hint = options?.hint ?? 'binary'\n }\n}\n\n/**\n * Callback type for RPC plugin to register raw streams with multiplexer\n */\nexport type OnRawStreamCallback = (\n streamId: number,\n stream: ReadableStream<Uint8Array>,\n) => void\n\n// Base64 helpers used in both Node and browser.\n// In Node-like runtimes, prefer Buffer for speed and compatibility.\nconst BufferCtor: any = (globalThis as any).Buffer\nconst hasNodeBuffer = !!BufferCtor && typeof BufferCtor.from === 'function'\n\nfunction uint8ArrayToBase64(bytes: Uint8Array): string {\n if (bytes.length === 0) return ''\n\n if (hasNodeBuffer) {\n return BufferCtor.from(bytes).toString('base64')\n }\n\n // Browser fallback: chunked String.fromCharCode + btoa\n const CHUNK_SIZE = 0x8000 // 32KB chunks to avoid stack overflow\n const chunks: Array<string> = []\n for (let i = 0; i < bytes.length; i += CHUNK_SIZE) {\n const chunk = bytes.subarray(i, i + CHUNK_SIZE)\n chunks.push(String.fromCharCode.apply(null, chunk as any))\n }\n return btoa(chunks.join(''))\n}\n\nfunction base64ToUint8Array(base64: string): Uint8Array {\n if (base64.length === 0) return new Uint8Array(0)\n\n if (hasNodeBuffer) {\n const buf = BufferCtor.from(base64, 'base64')\n return new Uint8Array(buf.buffer, buf.byteOffset, buf.byteLength)\n }\n\n const binary = atob(base64)\n const bytes = new Uint8Array(binary.length)\n for (let i = 0; i < binary.length; i++) {\n bytes[i] = binary.charCodeAt(i)\n }\n return bytes\n}\n\n// Factory sentinels - use null-proto objects to avoid prototype surprises\nconst RAW_STREAM_FACTORY_BINARY: Record<string, never> = Object.create(null)\nconst RAW_STREAM_FACTORY_TEXT: Record<string, never> = Object.create(null)\n\n// Factory constructor for binary mode - converts seroval stream to ReadableStream<Uint8Array>\n// All chunks are base64 encoded strings\nconst RAW_STREAM_FACTORY_CONSTRUCTOR_BINARY = (\n stream: ReturnType<typeof createStream>,\n) =>\n new ReadableStream<Uint8Array>({\n start(controller) {\n stream.on({\n next(base64: string) {\n try {\n controller.enqueue(base64ToUint8Array(base64))\n } catch {\n // Stream may be closed\n }\n },\n throw(error: unknown) {\n controller.error(error)\n },\n return() {\n try {\n controller.close()\n } catch {\n // Stream may already be closed\n }\n },\n })\n },\n })\n\n// Factory constructor for text mode - converts seroval stream to ReadableStream<Uint8Array>\n// Chunks are either strings (UTF-8) or { $b64: string } (base64 fallback)\n// Use module-level TextEncoder to avoid per-factory allocation\nconst textEncoderForFactory = new TextEncoder()\nconst RAW_STREAM_FACTORY_CONSTRUCTOR_TEXT = (\n stream: ReturnType<typeof createStream>,\n) => {\n return new ReadableStream<Uint8Array>({\n start(controller) {\n stream.on({\n next(value: string | { $b64: string }) {\n try {\n if (typeof value === 'string') {\n controller.enqueue(textEncoderForFactory.encode(value))\n } else {\n controller.enqueue(base64ToUint8Array(value.$b64))\n }\n } catch {\n // Stream may be closed\n }\n },\n throw(error: unknown) {\n controller.error(error)\n },\n return() {\n try {\n controller.close()\n } catch {\n // Stream may already be closed\n }\n },\n })\n },\n })\n}\n\n// Minified factory function for binary mode - all chunks are base64 strings\n// This must be self-contained since it's injected into the HTML\nconst FACTORY_BINARY = `(s=>new ReadableStream({start(c){s.on({next(b){try{const d=atob(b),a=new Uint8Array(d.length);for(let i=0;i<d.length;i++)a[i]=d.charCodeAt(i);c.enqueue(a)}catch(_){}},throw(e){c.error(e)},return(){try{c.close()}catch(_){}}})}}))`\n\n// Minified factory function for text mode - chunks are string or {$b64: string}\n// Uses cached TextEncoder for performance\nconst FACTORY_TEXT = `(s=>{const e=new TextEncoder();return new ReadableStream({start(c){s.on({next(v){try{if(typeof v==='string'){c.enqueue(e.encode(v))}else{const d=atob(v.$b64),a=new Uint8Array(d.length);for(let i=0;i<d.length;i++)a[i]=d.charCodeAt(i);c.enqueue(a)}}catch(_){}},throw(x){c.error(x)},return(){try{c.close()}catch(_){}}})}})})`\n\n// Convert ReadableStream<Uint8Array> to seroval stream with base64-encoded chunks (binary mode)\nfunction toBinaryStream(readable: ReadableStream<Uint8Array>) {\n const stream = createStream()\n const reader = readable.getReader()\n\n // Use iterative loop instead of recursive async to avoid stack accumulation\n ;(async () => {\n try {\n while (true) {\n const { done, value } = await reader.read()\n if (done) {\n stream.return(undefined)\n break\n }\n stream.next(uint8ArrayToBase64(value))\n }\n } catch (error) {\n stream.throw(error)\n } finally {\n reader.releaseLock()\n }\n })()\n\n return stream\n}\n\n// Convert ReadableStream<Uint8Array> to seroval stream with UTF-8 first, base64 fallback (text mode)\nfunction toTextStream(readable: ReadableStream<Uint8Array>) {\n const stream = createStream()\n const reader = readable.getReader()\n const decoder = new TextDecoder('utf-8', { fatal: true })\n\n // Use iterative loop instead of recursive async to avoid stack accumulation\n ;(async () => {\n try {\n while (true) {\n const { done, value } = await reader.read()\n if (done) {\n // Flush any remaining bytes in the decoder\n try {\n const remaining = decoder.decode()\n if (remaining.length > 0) {\n stream.next(remaining)\n }\n } catch {\n // Ignore decode errors on flush\n }\n stream.return(undefined)\n break\n }\n\n try {\n // Try UTF-8 decode first\n const text = decoder.decode(value, { stream: true })\n if (text.length > 0) {\n stream.next(text)\n }\n } catch {\n // UTF-8 decode failed, fallback to base64\n stream.next({ $b64: uint8ArrayToBase64(value) })\n }\n }\n } catch (error) {\n stream.throw(error)\n } finally {\n reader.releaseLock()\n }\n })()\n\n return stream\n}\n\n// Factory plugin for binary mode\nconst RawStreamFactoryBinaryPlugin = /* @__PURE__ */ createPlugin<\n Record<string, never>,\n PluginInfo\n>({\n tag: 'tss/RawStreamFactory',\n test(value) {\n return value === RAW_STREAM_FACTORY_BINARY\n },\n parse: {\n sync(_value, _ctx, _data) {\n return {}\n },\n async async(_value, _ctx, _data) {\n return {}\n },\n stream(_value, _ctx, _data) {\n return {}\n },\n },\n serialize(_node, _ctx, _data) {\n return FACTORY_BINARY\n },\n deserialize(_node, _ctx, _data) {\n return RAW_STREAM_FACTORY_BINARY\n },\n})\n\n// Factory plugin for text mode\nconst RawStreamFactoryTextPlugin = /* @__PURE__ */ createPlugin<\n Record<string, never>,\n PluginInfo\n>({\n tag: 'tss/RawStreamFactoryText',\n test(value) {\n return value === RAW_STREAM_FACTORY_TEXT\n },\n parse: {\n sync(_value, _ctx, _data) {\n return {}\n },\n async async(_value, _ctx, _data) {\n return {}\n },\n stream(_value, _ctx, _data) {\n return {}\n },\n },\n serialize(_node, _ctx, _data) {\n return FACTORY_TEXT\n },\n deserialize(_node, _ctx, _data) {\n return RAW_STREAM_FACTORY_TEXT\n },\n})\n\nexport interface RawStreamSSRNode extends PluginInfo {\n hint: SerovalNode\n factory: SerovalNode\n stream: SerovalNode\n}\n\nexport interface RawStreamRPCNode extends PluginInfo {\n streamId: SerovalNode\n}\n\n/**\n * SSR Plugin - uses base64 or UTF-8+base64 encoding for chunks, delegates to seroval's stream mechanism.\n * Used during SSR when serializing to JavaScript code for HTML injection.\n *\n * Supports two modes based on RawStream hint:\n * - 'binary': Always base64 encode (default)\n * - 'text': Try UTF-8 first, fallback to base64 for invalid UTF-8\n */\nexport const RawStreamSSRPlugin = /* @__PURE__ */ createPlugin<\n RawStream,\n RawStreamSSRNode\n>({\n tag: 'tss/RawStream',\n extends: [RawStreamFactoryBinaryPlugin, RawStreamFactoryTextPlugin],\n\n test(value: unknown) {\n return value instanceof RawStream\n },\n\n parse: {\n sync(value: RawStream, ctx, _data) {\n // Sync parse not really supported for streams, return empty stream\n const factory =\n value.hint === 'text'\n ? RAW_STREAM_FACTORY_TEXT\n : RAW_STREAM_FACTORY_BINARY\n return {\n hint: ctx.parse(value.hint),\n factory: ctx.parse(factory),\n stream: ctx.parse(createStream()),\n }\n },\n async async(value: RawStream, ctx, _data) {\n const factory =\n value.hint === 'text'\n ? RAW_STREAM_FACTORY_TEXT\n : RAW_STREAM_FACTORY_BINARY\n const encodedStream =\n value.hint === 'text'\n ? toTextStream(value.stream)\n : toBinaryStream(value.stream)\n return {\n hint: await ctx.parse(value.hint),\n factory: await ctx.parse(factory),\n stream: await ctx.parse(encodedStream),\n }\n },\n stream(value: RawStream, ctx, _data) {\n const factory =\n value.hint === 'text'\n ? RAW_STREAM_FACTORY_TEXT\n : RAW_STREAM_FACTORY_BINARY\n const encodedStream =\n value.hint === 'text'\n ? toTextStream(value.stream)\n : toBinaryStream(value.stream)\n return {\n hint: ctx.parse(value.hint),\n factory: ctx.parse(factory),\n stream: ctx.parse(encodedStream),\n }\n },\n },\n\n serialize(node: RawStreamSSRNode, ctx, _data) {\n return (\n '(' +\n ctx.serialize(node.factory) +\n ')(' +\n ctx.serialize(node.stream) +\n ')'\n )\n },\n\n deserialize(node: RawStreamSSRNode, ctx, _data): any {\n const stream: ReturnType<typeof createStream> = ctx.deserialize(node.stream)\n const hint = ctx.deserialize(node.hint)\n return hint === 'text'\n ? RAW_STREAM_FACTORY_CONSTRUCTOR_TEXT(stream)\n : RAW_STREAM_FACTORY_CONSTRUCTOR_BINARY(stream)\n },\n})\n\n/**\n * Creates an RPC plugin instance that registers raw streams with a multiplexer.\n * Used for server function responses where we want binary framing.\n * Note: RPC always uses binary framing regardless of hint.\n *\n * @param onRawStream Callback invoked when a RawStream is encountered during serialization\n */\n/* @__NO_SIDE_EFFECTS__ */\nexport function createRawStreamRPCPlugin(onRawStream: OnRawStreamCallback) {\n // Own stream counter - sequential IDs starting at 1, independent of seroval internals\n let nextStreamId = 1\n\n return /* @__PURE__ */ createPlugin<RawStream, RawStreamRPCNode>({\n tag: 'tss/RawStream',\n\n test(value: unknown) {\n return value instanceof RawStream\n },\n\n parse: {\n async async(value: RawStream, ctx, _data: PluginData) {\n const streamId = nextStreamId++\n onRawStream(streamId, value.stream)\n return { streamId: await ctx.parse(streamId) }\n },\n stream(value: RawStream, ctx, _data: PluginData) {\n const streamId = nextStreamId++\n onRawStream(streamId, value.stream)\n return { streamId: ctx.parse(streamId) }\n },\n },\n\n serialize(): never {\n // RPC uses toCrossJSONStream which produces JSON nodes, not JS code.\n // This method is only called by crossSerialize* which we don't use.\n throw new Error(\n 'RawStreamRPCPlugin.serialize should not be called. RPC uses JSON serialization, not JS code generation.',\n )\n },\n\n deserialize(): never {\n // Client uses createRawStreamDeserializePlugin instead\n throw new Error(\n 'RawStreamRPCPlugin.deserialize should not be called. Use createRawStreamDeserializePlugin on client.',\n )\n },\n })\n}\n\n/**\n * Creates a deserialize-only plugin for client-side stream reconstruction.\n * Used in serverFnFetcher to wire up streams from frame decoder.\n *\n * @param getOrCreateStream Function to get/create a stream by ID from frame decoder\n */\nexport function createRawStreamDeserializePlugin(\n getOrCreateStream: (id: number) => ReadableStream<Uint8Array>,\n) {\n return /* @__PURE__ */ createPlugin<any, RawStreamRPCNode>({\n tag: 'tss/RawStream',\n\n test: () => false, // Client never serializes RawStream\n\n parse: {}, // Client only deserializes, never parses\n\n serialize(): never {\n // Client never serializes RawStream back to server\n throw new Error(\n 'RawStreamDeserializePlugin.serialize should not be called. Client only deserializes.',\n )\n },\n\n deserialize(node, ctx, _data) {\n // In normal seroval usage, ctx.deserialize exists.\n // Some unit tests call plugin.deserialize directly with a minimal ctx.\n const id =\n typeof (ctx as any)?.deserialize === 'function'\n ? (ctx as any).deserialize(node.streamId)\n : (node as any).streamId\n return getOrCreateStream(id as number)\n },\n })\n}\n"],"mappings":"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"}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { PluginInfo, SerovalNode } from 'seroval';
|
|
2
2
|
/**
|
|
3
3
|
* Hint for RawStream encoding strategy during SSR serialization.
|
|
4
4
|
* - 'binary': Always use base64 encoding (best for binary data like files, images)
|
|
@@ -38,6 +38,14 @@ export declare class RawStream {
|
|
|
38
38
|
* Callback type for RPC plugin to register raw streams with multiplexer
|
|
39
39
|
*/
|
|
40
40
|
export type OnRawStreamCallback = (streamId: number, stream: ReadableStream<Uint8Array>) => void;
|
|
41
|
+
export interface RawStreamSSRNode extends PluginInfo {
|
|
42
|
+
hint: SerovalNode;
|
|
43
|
+
factory: SerovalNode;
|
|
44
|
+
stream: SerovalNode;
|
|
45
|
+
}
|
|
46
|
+
export interface RawStreamRPCNode extends PluginInfo {
|
|
47
|
+
streamId: SerovalNode;
|
|
48
|
+
}
|
|
41
49
|
/**
|
|
42
50
|
* SSR Plugin - uses base64 or UTF-8+base64 encoding for chunks, delegates to seroval's stream mechanism.
|
|
43
51
|
* Used during SSR when serializing to JavaScript code for HTML injection.
|
|
@@ -46,7 +54,7 @@ export type OnRawStreamCallback = (streamId: number, stream: ReadableStream<Uint
|
|
|
46
54
|
* - 'binary': Always base64 encode (default)
|
|
47
55
|
* - 'text': Try UTF-8 first, fallback to base64 for invalid UTF-8
|
|
48
56
|
*/
|
|
49
|
-
export declare const RawStreamSSRPlugin: Plugin<
|
|
57
|
+
export declare const RawStreamSSRPlugin: import('seroval').Plugin<RawStream, RawStreamSSRNode>;
|
|
50
58
|
/**
|
|
51
59
|
* Creates an RPC plugin instance that registers raw streams with a multiplexer.
|
|
52
60
|
* Used for server function responses where we want binary framing.
|
|
@@ -54,11 +62,11 @@ export declare const RawStreamSSRPlugin: Plugin<any, any>;
|
|
|
54
62
|
*
|
|
55
63
|
* @param onRawStream Callback invoked when a RawStream is encountered during serialization
|
|
56
64
|
*/
|
|
57
|
-
export declare function createRawStreamRPCPlugin(onRawStream: OnRawStreamCallback): Plugin<
|
|
65
|
+
export declare function createRawStreamRPCPlugin(onRawStream: OnRawStreamCallback): import('seroval').Plugin<RawStream, RawStreamRPCNode>;
|
|
58
66
|
/**
|
|
59
67
|
* Creates a deserialize-only plugin for client-side stream reconstruction.
|
|
60
68
|
* Used in serverFnFetcher to wire up streams from frame decoder.
|
|
61
69
|
*
|
|
62
70
|
* @param getOrCreateStream Function to get/create a stream by ID from frame decoder
|
|
63
71
|
*/
|
|
64
|
-
export declare function createRawStreamDeserializePlugin(getOrCreateStream: (id: number) => ReadableStream<Uint8Array>): Plugin<any,
|
|
72
|
+
export declare function createRawStreamDeserializePlugin(getOrCreateStream: (id: number) => ReadableStream<Uint8Array>): import('seroval').Plugin<any, RawStreamRPCNode>;
|