@tanstack/router-core 1.132.0-alpha.8 → 1.132.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.
Files changed (92) hide show
  1. package/dist/cjs/Matches.cjs +2 -1
  2. package/dist/cjs/Matches.cjs.map +1 -1
  3. package/dist/cjs/fileRoute.d.cts +3 -3
  4. package/dist/cjs/index.cjs +7 -2
  5. package/dist/cjs/index.cjs.map +1 -1
  6. package/dist/cjs/index.d.cts +8 -4
  7. package/dist/cjs/load-matches.cjs +5 -3
  8. package/dist/cjs/load-matches.cjs.map +1 -1
  9. package/dist/cjs/location.d.cts +38 -0
  10. package/dist/cjs/path.cjs +27 -64
  11. package/dist/cjs/path.cjs.map +1 -1
  12. package/dist/cjs/path.d.cts +6 -7
  13. package/dist/cjs/process-route-tree.cjs +144 -0
  14. package/dist/cjs/process-route-tree.cjs.map +1 -0
  15. package/dist/cjs/process-route-tree.d.cts +10 -0
  16. package/dist/cjs/redirect.cjs +1 -1
  17. package/dist/cjs/redirect.cjs.map +1 -1
  18. package/dist/cjs/rewrite.cjs +63 -0
  19. package/dist/cjs/rewrite.cjs.map +1 -0
  20. package/dist/cjs/rewrite.d.cts +22 -0
  21. package/dist/cjs/route.cjs.map +1 -1
  22. package/dist/cjs/route.d.cts +55 -42
  23. package/dist/cjs/router.cjs +77 -184
  24. package/dist/cjs/router.cjs.map +1 -1
  25. package/dist/cjs/router.d.cts +68 -37
  26. package/dist/cjs/scroll-restoration.cjs.map +1 -1
  27. package/dist/cjs/scroll-restoration.d.cts +9 -0
  28. package/dist/cjs/ssr/createRequestHandler.cjs +4 -1
  29. package/dist/cjs/ssr/createRequestHandler.cjs.map +1 -1
  30. package/dist/cjs/ssr/serializer/transformer.cjs.map +1 -1
  31. package/dist/cjs/ssr/serializer/transformer.d.cts +10 -8
  32. package/dist/cjs/ssr/server.d.cts +0 -5
  33. package/dist/cjs/ssr/ssr-server.cjs +5 -2
  34. package/dist/cjs/ssr/ssr-server.cjs.map +1 -1
  35. package/dist/cjs/ssr/ssr-server.d.cts +4 -1
  36. package/dist/cjs/utils.cjs +68 -46
  37. package/dist/cjs/utils.cjs.map +1 -1
  38. package/dist/esm/Matches.js +2 -1
  39. package/dist/esm/Matches.js.map +1 -1
  40. package/dist/esm/fileRoute.d.ts +3 -3
  41. package/dist/esm/index.d.ts +8 -4
  42. package/dist/esm/index.js +8 -3
  43. package/dist/esm/index.js.map +1 -1
  44. package/dist/esm/load-matches.js +5 -3
  45. package/dist/esm/load-matches.js.map +1 -1
  46. package/dist/esm/location.d.ts +38 -0
  47. package/dist/esm/path.d.ts +6 -7
  48. package/dist/esm/path.js +27 -64
  49. package/dist/esm/path.js.map +1 -1
  50. package/dist/esm/process-route-tree.d.ts +10 -0
  51. package/dist/esm/process-route-tree.js +144 -0
  52. package/dist/esm/process-route-tree.js.map +1 -0
  53. package/dist/esm/redirect.js +1 -1
  54. package/dist/esm/redirect.js.map +1 -1
  55. package/dist/esm/rewrite.d.ts +22 -0
  56. package/dist/esm/rewrite.js +63 -0
  57. package/dist/esm/rewrite.js.map +1 -0
  58. package/dist/esm/route.d.ts +55 -42
  59. package/dist/esm/route.js.map +1 -1
  60. package/dist/esm/router.d.ts +68 -37
  61. package/dist/esm/router.js +79 -186
  62. package/dist/esm/router.js.map +1 -1
  63. package/dist/esm/scroll-restoration.d.ts +9 -0
  64. package/dist/esm/scroll-restoration.js.map +1 -1
  65. package/dist/esm/ssr/createRequestHandler.js +4 -1
  66. package/dist/esm/ssr/createRequestHandler.js.map +1 -1
  67. package/dist/esm/ssr/serializer/transformer.d.ts +10 -8
  68. package/dist/esm/ssr/serializer/transformer.js.map +1 -1
  69. package/dist/esm/ssr/server.d.ts +0 -5
  70. package/dist/esm/ssr/ssr-server.d.ts +4 -1
  71. package/dist/esm/ssr/ssr-server.js +5 -2
  72. package/dist/esm/ssr/ssr-server.js.map +1 -1
  73. package/dist/esm/utils.js +68 -46
  74. package/dist/esm/utils.js.map +1 -1
  75. package/package.json +3 -3
  76. package/src/Matches.ts +2 -1
  77. package/src/fileRoute.ts +16 -6
  78. package/src/index.ts +11 -6
  79. package/src/load-matches.ts +29 -19
  80. package/src/location.ts +38 -0
  81. package/src/path.ts +44 -82
  82. package/src/process-route-tree.ts +233 -0
  83. package/src/redirect.ts +1 -1
  84. package/src/rewrite.ts +70 -0
  85. package/src/route.ts +214 -80
  86. package/src/router.ts +208 -329
  87. package/src/scroll-restoration.ts +1 -1
  88. package/src/ssr/createRequestHandler.ts +4 -1
  89. package/src/ssr/serializer/transformer.ts +17 -17
  90. package/src/ssr/server.ts +5 -5
  91. package/src/ssr/ssr-server.ts +8 -5
  92. package/src/utils.ts +83 -61
@@ -4,37 +4,38 @@ import { ParsePathnameCache } from './path.cjs';
4
4
  import { SearchParser, SearchSerializer } from './searchParams.cjs';
5
5
  import { AnyRedirect, ResolvedRedirect } from './redirect.cjs';
6
6
  import { HistoryLocation, HistoryState, ParsedHistoryState, RouterHistory } from '@tanstack/history';
7
- import { Awaitable, ControlledPromise, NoInfer, NonNullableUpdater, PickAsRequired, Updater } from './utils.cjs';
7
+ import { Awaitable, Constrain, ControlledPromise, NoInfer, NonNullableUpdater, PickAsRequired, Updater } from './utils.cjs';
8
8
  import { ParsedLocation } from './location.cjs';
9
- import { AnyContext, AnyRoute, AnyRouteWithContext, MakeRemountDepsOptionsUnion, RouteMask } from './route.cjs';
9
+ import { AnyContext, AnyRoute, AnyRouteWithContext, MakeRemountDepsOptionsUnion, RouteLike, RouteMask } from './route.cjs';
10
10
  import { FullSearchSchema, RouteById, RoutePaths, RoutesById, RoutesByPath } from './routeInfo.cjs';
11
11
  import { AnyRouteMatch, MakeRouteMatchUnion, MatchRouteOptions } from './Matches.cjs';
12
12
  import { BuildLocationFn, CommitLocationOptions, NavigateFn } from './RouterProvider.cjs';
13
13
  import { Manifest } from './manifest.cjs';
14
14
  import { AnySchema } from './validators.cjs';
15
15
  import { NavigateOptions, ResolveRelativePath, ToOptions } from './link.cjs';
16
- import { AnySerializationAdapter } from './ssr/serializer/transformer.cjs';
17
- import { AnyRouterConfig } from './config.cjs';
16
+ import { AnySerializationAdapter, ValidateSerializableInput } from './ssr/serializer/transformer.cjs';
18
17
  export type ControllablePromise<T = any> = Promise<T> & {
19
18
  resolve: (value: T) => void;
20
19
  reject: (value?: any) => void;
21
20
  };
22
21
  export type InjectedHtmlEntry = Promise<string>;
23
- export interface DefaultRegister {
24
- router: AnyRouter;
25
- config: AnyRouterConfig;
26
- ssr: SSROption;
22
+ export interface Register {
27
23
  }
28
- export interface Register extends DefaultRegister {
29
- }
30
- export type RegisteredRouter = Register['router'];
24
+ export type RegisteredRouter<TRegister = Register> = TRegister extends {
25
+ router: infer TRouter;
26
+ } ? TRouter : AnyRouter;
27
+ export type RegisteredConfigType<TRegister, TKey> = TRegister extends {
28
+ config: infer TConfig;
29
+ } ? TConfig extends {
30
+ '~types': infer TTypes;
31
+ } ? TKey extends keyof TTypes ? TTypes[TKey] : unknown : unknown : unknown;
31
32
  export type DefaultRemountDepsFn<TRouteTree extends AnyRoute> = (opts: MakeRemountDepsOptionsUnion<TRouteTree>) => any;
32
33
  export interface DefaultRouterOptionsExtensions {
33
34
  }
34
35
  export interface RouterOptionsExtensions extends DefaultRouterOptionsExtensions {
35
36
  }
36
37
  export type SSROption = boolean | 'data-only';
37
- export interface RouterOptions<TRouteTree extends AnyRoute, TTrailingSlashOption extends TrailingSlashOption, TDefaultStructuralSharingOption extends boolean = false, TRouterHistory extends RouterHistory = RouterHistory, TDehydrated extends Record<string, any> = Record<string, any>> extends RouterOptionsExtensions {
38
+ export interface RouterOptions<TRouteTree extends AnyRoute, TTrailingSlashOption extends TrailingSlashOption, TDefaultStructuralSharingOption extends boolean = false, TRouterHistory extends RouterHistory = RouterHistory, TDehydrated = undefined> extends RouterOptionsExtensions {
38
39
  /**
39
40
  * The history object that will be used to manage the browser history.
40
41
  *
@@ -178,6 +179,18 @@ export interface RouterOptions<TRouteTree extends AnyRoute, TTrailingSlashOption
178
179
  /**
179
180
  * The basepath for then entire router. This is useful for mounting a router instance at a subpath.
180
181
  *
182
+ * @deprecated - use `rewrite.input` with the new `rewriteBasepath` utility instead:
183
+ * ```ts
184
+ * const router = createRouter({
185
+ * routeTree,
186
+ * rewrite: rewriteBasepath('/basepath')
187
+ * // Or wrap existing rewrite functionality
188
+ * rewrite: rewriteBasepath('/basepath', {
189
+ * output: ({ url }) => {...},
190
+ * input: ({ url }) => {...},
191
+ * })
192
+ * })
193
+ * ```
181
194
  * @default '/'
182
195
  * @link [API Docs](https://tanstack.com/router/latest/docs/framework/react/api/router/RouterOptionsType#basepath-property)
183
196
  */
@@ -193,6 +206,7 @@ export interface RouterOptions<TRouteTree extends AnyRoute, TTrailingSlashOption
193
206
  * @link [Guide](https://tanstack.com/router/latest/docs/framework/react/guide/router-context)
194
207
  */
195
208
  context?: InferRouterContext<TRouteTree>;
209
+ additionalContext?: any;
196
210
  /**
197
211
  * A function that will be called when the router is dehydrated.
198
212
  *
@@ -201,7 +215,7 @@ export interface RouterOptions<TRouteTree extends AnyRoute, TTrailingSlashOption
201
215
  * @link [API Docs](https://tanstack.com/router/latest/docs/framework/react/api/router/RouterOptionsType#dehydrate-method)
202
216
  * @link [Guide](https://tanstack.com/router/latest/docs/framework/react/guide/external-data-loading#critical-dehydrationhydration)
203
217
  */
204
- dehydrate?: () => Awaitable<TDehydrated>;
218
+ dehydrate?: () => Constrain<TDehydrated, ValidateSerializableInput<Register, TDehydrated>>;
205
219
  /**
206
220
  * A function that will be called when the router is hydrated.
207
221
  *
@@ -328,7 +342,44 @@ export interface RouterOptions<TRouteTree extends AnyRoute, TTrailingSlashOption
328
342
  */
329
343
  disableGlobalCatchBoundary?: boolean;
330
344
  serializationAdapters?: ReadonlyArray<AnySerializationAdapter>;
345
+ /**
346
+ * Configures how the router will rewrite the location between the actual href and the internal href of the router.
347
+ *
348
+ * @default undefined
349
+ * @description You can provide a custom rewrite pair (in/out) or use the utilities like `rewriteBasepath` as a convenience for common use cases, or even do both!
350
+ * This is useful for basepath rewriting, shifting data from the origin to the path (for things like )
351
+ */
352
+ rewrite?: LocationRewrite;
353
+ origin?: string;
354
+ ssr?: {
355
+ nonce?: string;
356
+ };
331
357
  }
358
+ export type LocationRewrite = {
359
+ /**
360
+ * A function that will be called to rewrite the URL before it is interpreted by the router from the history instance.
361
+ * Utilities like `rewriteBasepath` are provided as a convenience for common use cases.
362
+ *
363
+ * @default undefined
364
+ */
365
+ input?: LocationRewriteFunction;
366
+ /**
367
+ * A function that will be called to rewrite the URL before it is committed to the actual history instance from the router.
368
+ * Utilities like `rewriteBasepath` are provided as a convenience for common use cases.
369
+ *
370
+ * @default undefined
371
+ */
372
+ output?: LocationRewriteFunction;
373
+ };
374
+ /**
375
+ * A function that will be called to rewrite the URL.
376
+ *
377
+ * @param url The URL to rewrite.
378
+ * @returns The rewritten URL (as a URL instance or full href string) or undefined if no rewrite is needed.
379
+ */
380
+ export type LocationRewriteFunction = ({ url, }: {
381
+ url: URL;
382
+ }) => undefined | string | URL;
332
383
  export interface RouterState<in out TRouteTree extends AnyRoute = AnyRoute, in out TRouteMatch = MakeRouteMatchUnion> {
333
384
  status: 'pending' | 'idle';
334
385
  loadedAt: number;
@@ -408,7 +459,7 @@ export type RouterContextOptions<TRouteTree extends AnyRoute> = AnyContext exten
408
459
  } : {
409
460
  context: InferRouterContext<TRouteTree>;
410
461
  };
411
- export type RouterConstructorOptions<TRouteTree extends AnyRoute, TTrailingSlashOption extends TrailingSlashOption, TDefaultStructuralSharingOption extends boolean, TRouterHistory extends RouterHistory, TDehydrated extends Record<string, any>> = Omit<RouterOptions<TRouteTree, TTrailingSlashOption, TDefaultStructuralSharingOption, TRouterHistory, TDehydrated>, 'context'> & RouterContextOptions<TRouteTree>;
462
+ export type RouterConstructorOptions<TRouteTree extends AnyRoute, TTrailingSlashOption extends TrailingSlashOption, TDefaultStructuralSharingOption extends boolean, TRouterHistory extends RouterHistory, TDehydrated extends Record<string, any>> = Omit<RouterOptions<TRouteTree, TTrailingSlashOption, TDefaultStructuralSharingOption, TRouterHistory, TDehydrated>, 'context' | 'serializationAdapters' | 'defaultSsr'> & RouterContextOptions<TRouteTree>;
412
463
  export type PreloadRouteFn<TRouteTree extends AnyRoute, TTrailingSlashOption extends TrailingSlashOption, TDefaultStructuralSharingOption extends boolean, TRouterHistory extends RouterHistory> = <TFrom extends RoutePaths<TRouteTree> | string = string, TTo extends string | undefined = undefined, TMaskFrom extends RoutePaths<TRouteTree> | string = TFrom, TMaskTo extends string = ''>(opts: NavigateOptions<RouterCore<TRouteTree, TTrailingSlashOption, TDefaultStructuralSharingOption, TRouterHistory>, TFrom, TTo, TMaskFrom, TMaskTo>) => Promise<Array<AnyRouteMatch> | undefined>;
413
464
  export type MatchRouteFn<TRouteTree extends AnyRoute, TTrailingSlashOption extends TrailingSlashOption, TDefaultStructuralSharingOption extends boolean, TRouterHistory extends RouterHistory> = <TFrom extends RoutePaths<TRouteTree> = '/', TTo extends string | undefined = undefined, TResolved = ResolveRelativePath<TFrom, NoInfer<TTo>>>(location: ToOptions<RouterCore<TRouteTree, TTrailingSlashOption, TDefaultStructuralSharingOption, TRouterHistory>, TFrom, TTo>, opts?: MatchRouteOptions) => false | RouteById<TRouteTree, TResolved>['types']['allParams'];
414
465
  export type UpdateFn<TRouteTree extends AnyRoute, TTrailingSlashOption extends TrailingSlashOption, TDefaultStructuralSharingOption extends boolean, TRouterHistory extends RouterHistory, TDehydrated extends Record<string, any>> = (newOptions: RouterConstructorOptions<TRouteTree, TTrailingSlashOption, TDefaultStructuralSharingOption, TRouterHistory, TDehydrated>) => void;
@@ -495,6 +546,8 @@ export declare class RouterCore<in out TRouteTree extends AnyRoute, in out TTrai
495
546
  __store: Store<RouterState<TRouteTree>>;
496
547
  options: PickAsRequired<RouterOptions<TRouteTree, TTrailingSlashOption, TDefaultStructuralSharingOption, TRouterHistory, TDehydrated>, 'stringifySearch' | 'parseSearch' | 'context'>;
497
548
  history: TRouterHistory;
549
+ rewrite?: LocationRewrite;
550
+ origin?: string;
498
551
  latestLocation: ParsedLocation<FullSearchSchema<TRouteTree>>;
499
552
  basepath: string;
500
553
  routeTree: TRouteTree;
@@ -556,31 +609,9 @@ export declare class PathParamError extends Error {
556
609
  }
557
610
  export declare function lazyFn<T extends Record<string, (...args: Array<any>) => any>, TKey extends keyof T = 'default'>(fn: () => Promise<T>, key?: TKey): (...args: Parameters<T[TKey]>) => Promise<Awaited<ReturnType<T[TKey]>>>;
558
611
  export declare function getInitialRouterState(location: ParsedLocation): RouterState<any>;
559
- interface RouteLike {
560
- id: string;
561
- isRoot?: boolean;
562
- path?: string;
563
- fullPath: string;
564
- rank?: number;
565
- parentRoute?: RouteLike;
566
- children?: Array<RouteLike>;
567
- options?: {
568
- caseSensitive?: boolean;
569
- };
570
- }
571
- export type ProcessRouteTreeResult<TRouteLike extends RouteLike> = {
572
- routesById: Record<string, TRouteLike>;
573
- routesByPath: Record<string, TRouteLike>;
574
- flatRoutes: Array<TRouteLike>;
575
- };
576
- export declare function processRouteTree<TRouteLike extends RouteLike>({ routeTree, initRoute, }: {
577
- routeTree: TRouteLike;
578
- initRoute?: (route: TRouteLike, index: number) => void;
579
- }): ProcessRouteTreeResult<TRouteLike>;
580
- export declare function getMatchedRoutes<TRouteLike extends RouteLike>({ pathname, routePathname, basepath, caseSensitive, routesByPath, routesById, flatRoutes, parseCache, }: {
612
+ export declare function getMatchedRoutes<TRouteLike extends RouteLike>({ pathname, routePathname, caseSensitive, routesByPath, routesById, flatRoutes, parseCache, }: {
581
613
  pathname: string;
582
614
  routePathname?: string;
583
- basepath: string;
584
615
  caseSensitive?: boolean;
585
616
  routesByPath: Record<string, TRouteLike>;
586
617
  routesById: Record<string, TRouteLike>;
@@ -1 +1 @@
1
- {"version":3,"file":"scroll-restoration.cjs","sources":["../../src/scroll-restoration.ts"],"sourcesContent":["import { functionalUpdate } from './utils'\nimport type { AnyRouter } from './router'\nimport type { ParsedLocation } from './location'\nimport type { NonNullableUpdater } from './utils'\nimport type { HistoryLocation } from '@tanstack/history'\n\nexport type ScrollRestorationEntry = { scrollX: number; scrollY: number }\n\nexport type ScrollRestorationByElement = Record<string, ScrollRestorationEntry>\n\nexport type ScrollRestorationByKey = Record<string, ScrollRestorationByElement>\n\nexport type ScrollRestorationCache = {\n state: ScrollRestorationByKey\n set: (updater: NonNullableUpdater<ScrollRestorationByKey>) => void\n}\nexport type ScrollRestorationOptions = {\n getKey?: (location: ParsedLocation) => string\n scrollBehavior?: ScrollToOptions['behavior']\n}\n\nfunction getSafeSessionStorage() {\n try {\n if (\n typeof window !== 'undefined' &&\n typeof window.sessionStorage === 'object'\n ) {\n return window.sessionStorage\n }\n } catch {\n // silent\n }\n return undefined\n}\n\nexport const storageKey = 'tsr-scroll-restoration-v1_3'\n\nconst throttle = (fn: (...args: Array<any>) => void, wait: number) => {\n let timeout: any\n return (...args: Array<any>) => {\n if (!timeout) {\n timeout = setTimeout(() => {\n fn(...args)\n timeout = null\n }, wait)\n }\n }\n}\n\nfunction createScrollRestorationCache(): ScrollRestorationCache | null {\n const safeSessionStorage = getSafeSessionStorage()\n if (!safeSessionStorage) {\n return null\n }\n\n const persistedState = safeSessionStorage.getItem(storageKey)\n let state: ScrollRestorationByKey = persistedState\n ? JSON.parse(persistedState)\n : {}\n\n return {\n state,\n // This setter is simply to make sure that we set the sessionStorage right\n // after the state is updated. It doesn't necessarily need to be a functional\n // update.\n set: (updater) => (\n (state = functionalUpdate(updater, state) || state),\n safeSessionStorage.setItem(storageKey, JSON.stringify(state))\n ),\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 */\n\nexport const defaultGetScrollRestorationKey = (location: ParsedLocation) => {\n return location.state.__TSR_key! || location.href\n}\n\nexport function 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\nlet ignoreScroll = false\n\n// NOTE: This function must remain pure and not use any outside variables\n// unless they are passed in as arguments. Why? Because we need to be able to\n// toString() it into a script tag to execute as early as possible in the browser\n// during SSR. Additionally, we also call it from within the router lifecycle\nexport function restoreScroll({\n storageKey,\n key,\n behavior,\n shouldScrollRestoration,\n scrollToTopSelectors,\n location,\n}: {\n storageKey: string\n key?: string\n behavior?: ScrollToOptions['behavior']\n shouldScrollRestoration?: boolean\n scrollToTopSelectors?: Array<string | (() => Element | null | undefined)>\n location?: HistoryLocation\n}) {\n let byKey: ScrollRestorationByKey\n\n try {\n byKey = JSON.parse(sessionStorage.getItem(storageKey) || '{}')\n } catch (error) {\n console.error(error)\n return\n }\n\n const resolvedKey = key || window.history.state?.__TSR_key\n const elementEntries = byKey[resolvedKey]\n\n //\n ignoreScroll = true\n\n //\n scroll: {\n // If we have a cached entry for this location state,\n // we always need to prefer that over the hash scroll.\n if (\n shouldScrollRestoration &&\n elementEntries &&\n Object.keys(elementEntries).length > 0\n ) {\n for (const elementSelector in elementEntries) {\n const entry = elementEntries[elementSelector]!\n if (elementSelector === 'window') {\n window.scrollTo({\n top: entry.scrollY,\n left: entry.scrollX,\n behavior,\n })\n } else if (elementSelector) {\n const element = document.querySelector(elementSelector)\n if (element) {\n element.scrollLeft = entry.scrollX\n element.scrollTop = entry.scrollY\n }\n }\n }\n\n break scroll\n }\n\n // If we don't have a cached entry for the hash,\n // Which means we've never seen this location before,\n // we need to check if there is a hash in the URL.\n // If there is, we need to scroll it's ID into view.\n const hash = (location ?? window.location).hash.split('#', 2)[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\n break scroll\n }\n\n // If there is no cached entry for the hash and there is no hash in the URL,\n // we need to scroll to the top of the page for every scrollToTop element\n const scrollOptions = { top: 0, left: 0, behavior }\n window.scrollTo(scrollOptions)\n if (scrollToTopSelectors) {\n for (const selector of scrollToTopSelectors) {\n if (selector === 'window') continue\n const element =\n typeof selector === 'function'\n ? selector()\n : document.querySelector(selector)\n if (element) element.scrollTo(scrollOptions)\n }\n }\n }\n\n //\n ignoreScroll = false\n}\n\nexport function setupScrollRestoration(router: AnyRouter, force?: boolean) {\n if (!scrollRestorationCache && !router.isServer) {\n return\n }\n const shouldScrollRestoration =\n force ?? router.options.scrollRestoration ?? false\n\n if (shouldScrollRestoration) {\n router.isScrollRestoring = true\n }\n\n if (\n router.isServer ||\n router.isScrollRestorationSetup ||\n !scrollRestorationCache\n ) {\n return\n }\n\n router.isScrollRestorationSetup = true\n\n //\n ignoreScroll = false\n\n const getKey =\n router.options.getScrollRestorationKey || defaultGetScrollRestorationKey\n\n window.history.scrollRestoration = 'manual'\n\n // // Create a MutationObserver to monitor DOM changes\n // const mutationObserver = new MutationObserver(() => {\n // ;ignoreScroll = true\n // requestAnimationFrame(() => {\n // ;ignoreScroll = false\n\n // // Attempt to restore scroll position on each dom\n // // mutation until the user scrolls. We do this\n // // because dynamic content may come in at different\n // // ticks after the initial render and we want to\n // // keep up with that content as much as possible.\n // // As soon as the user scrolls, we no longer need\n // // to attempt router.\n // // console.log('mutation observer restoreScroll')\n // restoreScroll(\n // storageKey,\n // getKey(router.state.location),\n // router.options.scrollRestorationBehavior,\n // )\n // })\n // })\n\n // const observeDom = () => {\n // // Observe changes to the entire document\n // mutationObserver.observe(document, {\n // childList: true, // Detect added or removed child nodes\n // subtree: true, // Monitor all descendants\n // characterData: true, // Detect text content changes\n // })\n // }\n\n // const unobserveDom = () => {\n // mutationObserver.disconnect()\n // }\n\n // observeDom()\n\n const onScroll = (event: Event) => {\n // unobserveDom()\n\n if (ignoreScroll || !router.isScrollRestoring) {\n return\n }\n\n let elementSelector = ''\n\n if (event.target === document || event.target === window) {\n elementSelector = 'window'\n } else {\n const attrId = (event.target as Element).getAttribute(\n 'data-scroll-restoration-id',\n )\n\n if (attrId) {\n elementSelector = `[data-scroll-restoration-id=\"${attrId}\"]`\n } else {\n elementSelector = getCssSelector(event.target)\n }\n }\n\n const restoreKey = getKey(router.state.location)\n\n scrollRestorationCache.set((state) => {\n const keyEntry = (state[restoreKey] ||= {} as ScrollRestorationByElement)\n\n const elementEntry = (keyEntry[elementSelector] ||=\n {} as ScrollRestorationEntry)\n\n if (elementSelector === 'window') {\n elementEntry.scrollX = window.scrollX || 0\n elementEntry.scrollY = window.scrollY || 0\n } else if (elementSelector) {\n const element = document.querySelector(elementSelector)\n if (element) {\n elementEntry.scrollX = element.scrollLeft || 0\n elementEntry.scrollY = element.scrollTop || 0\n }\n }\n\n return state\n })\n }\n\n // Throttle the scroll event to avoid excessive updates\n if (typeof document !== 'undefined') {\n document.addEventListener('scroll', throttle(onScroll, 100), true)\n }\n\n router.subscribe('onRendered', (event) => {\n // unobserveDom()\n\n const cacheKey = getKey(event.toLocation)\n\n // If the user doesn't want to restore the scroll position,\n // we don't need to do anything.\n if (!router.resetNextScroll) {\n router.resetNextScroll = true\n return\n }\n if (typeof router.options.scrollRestoration === 'function') {\n const shouldRestore = router.options.scrollRestoration({\n location: router.latestLocation,\n })\n if (!shouldRestore) {\n return\n }\n }\n\n restoreScroll({\n storageKey,\n key: cacheKey,\n behavior: router.options.scrollRestorationBehavior,\n shouldScrollRestoration: router.isScrollRestoring,\n scrollToTopSelectors: router.options.scrollToTopSelectors,\n location: router.history.location,\n })\n\n if (router.isScrollRestoring) {\n // Mark the location as having been seen\n scrollRestorationCache.set((state) => {\n state[cacheKey] ||= {} as ScrollRestorationByElement\n\n return state\n })\n }\n })\n}\n\n/**\n * @internal\n * Handles hash-based scrolling after navigation completes.\n * To be used in framework-specific <Transitioner> components during the onResolved event.\n *\n * Provides hash scrolling for programmatic navigation when default browser handling is prevented.\n * @param router The router instance containing current location and state\n */\nexport function handleHashScroll(router: AnyRouter) {\n if (typeof document !== 'undefined' && (document as any).querySelector) {\n const hashScrollIntoViewOptions =\n router.state.location.state.__hashScrollIntoViewOptions ?? true\n\n if (hashScrollIntoViewOptions && router.state.location.hash !== '') {\n const el = document.getElementById(router.state.location.hash)\n if (el) {\n el.scrollIntoView(hashScrollIntoViewOptions)\n }\n }\n }\n}\n"],"names":["functionalUpdate","storageKey"],"mappings":"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"}
1
+ {"version":3,"file":"scroll-restoration.cjs","sources":["../../src/scroll-restoration.ts"],"sourcesContent":["import { functionalUpdate } from './utils'\nimport type { AnyRouter } from './router'\nimport type { ParsedLocation } from './location'\nimport type { NonNullableUpdater } from './utils'\nimport type { HistoryLocation } from '@tanstack/history'\n\nexport type ScrollRestorationEntry = { scrollX: number; scrollY: number }\n\nexport type ScrollRestorationByElement = Record<string, ScrollRestorationEntry>\n\nexport type ScrollRestorationByKey = Record<string, ScrollRestorationByElement>\n\nexport type ScrollRestorationCache = {\n state: ScrollRestorationByKey\n set: (updater: NonNullableUpdater<ScrollRestorationByKey>) => void\n}\nexport type ScrollRestorationOptions = {\n getKey?: (location: ParsedLocation) => string\n scrollBehavior?: ScrollToOptions['behavior']\n}\n\nfunction getSafeSessionStorage() {\n try {\n if (\n typeof window !== 'undefined' &&\n typeof window.sessionStorage === 'object'\n ) {\n return window.sessionStorage\n }\n } catch {\n // silent\n }\n return undefined\n}\n\nexport const storageKey = 'tsr-scroll-restoration-v1_3'\n\nconst throttle = (fn: (...args: Array<any>) => void, wait: number) => {\n let timeout: any\n return (...args: Array<any>) => {\n if (!timeout) {\n timeout = setTimeout(() => {\n fn(...args)\n timeout = null\n }, wait)\n }\n }\n}\n\nfunction createScrollRestorationCache(): ScrollRestorationCache | null {\n const safeSessionStorage = getSafeSessionStorage()\n if (!safeSessionStorage) {\n return null\n }\n\n const persistedState = safeSessionStorage.getItem(storageKey)\n let state: ScrollRestorationByKey = persistedState\n ? JSON.parse(persistedState)\n : {}\n\n return {\n state,\n // This setter is simply to make sure that we set the sessionStorage right\n // after the state is updated. It doesn't necessarily need to be a functional\n // update.\n set: (updater) => (\n (state = functionalUpdate(updater, state) || state),\n safeSessionStorage.setItem(storageKey, JSON.stringify(state))\n ),\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 */\n\nexport const defaultGetScrollRestorationKey = (location: ParsedLocation) => {\n return location.state.__TSR_key! || location.href\n}\n\nexport function 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\nlet ignoreScroll = false\n\n// NOTE: This function must remain pure and not use any outside variables\n// unless they are passed in as arguments. Why? Because we need to be able to\n// toString() it into a script tag to execute as early as possible in the browser\n// during SSR. Additionally, we also call it from within the router lifecycle\nexport function restoreScroll({\n storageKey,\n key,\n behavior,\n shouldScrollRestoration,\n scrollToTopSelectors,\n location,\n}: {\n storageKey: string\n key?: string\n behavior?: ScrollToOptions['behavior']\n shouldScrollRestoration?: boolean\n scrollToTopSelectors?: Array<string | (() => Element | null | undefined)>\n location?: HistoryLocation\n}) {\n let byKey: ScrollRestorationByKey\n\n try {\n byKey = JSON.parse(sessionStorage.getItem(storageKey) || '{}')\n } catch (error) {\n console.error(error)\n return\n }\n\n const resolvedKey = key || window.history.state?.__TSR_key\n const elementEntries = byKey[resolvedKey]\n\n //\n ignoreScroll = true\n\n //\n scroll: {\n // If we have a cached entry for this location state,\n // we always need to prefer that over the hash scroll.\n if (\n shouldScrollRestoration &&\n elementEntries &&\n Object.keys(elementEntries).length > 0\n ) {\n for (const elementSelector in elementEntries) {\n const entry = elementEntries[elementSelector]!\n if (elementSelector === 'window') {\n window.scrollTo({\n top: entry.scrollY,\n left: entry.scrollX,\n behavior,\n })\n } else if (elementSelector) {\n const element = document.querySelector(elementSelector)\n if (element) {\n element.scrollLeft = entry.scrollX\n element.scrollTop = entry.scrollY\n }\n }\n }\n\n break scroll\n }\n\n // If we don't have a cached entry for the hash,\n // Which means we've never seen this location before,\n // we need to check if there is a hash in the URL.\n // If there is, we need to scroll it's ID into view.\n const hash = (location ?? window.location).hash.split('#', 2)[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\n break scroll\n }\n\n // If there is no cached entry for the hash and there is no hash in the URL,\n // we need to scroll to the top of the page for every scrollToTop element\n const scrollOptions = { top: 0, left: 0, behavior }\n window.scrollTo(scrollOptions)\n if (scrollToTopSelectors) {\n for (const selector of scrollToTopSelectors) {\n if (selector === 'window') continue\n const element =\n typeof selector === 'function'\n ? selector()\n : document.querySelector(selector)\n if (element) element.scrollTo(scrollOptions)\n }\n }\n }\n\n //\n ignoreScroll = false\n}\n\nexport function setupScrollRestoration(router: AnyRouter, force?: boolean) {\n if (!scrollRestorationCache && !router.isServer) {\n return\n }\n const shouldScrollRestoration =\n force ?? router.options.scrollRestoration ?? false\n\n if (shouldScrollRestoration) {\n router.isScrollRestoring = true\n }\n\n if (\n router.isServer ||\n router.isScrollRestorationSetup ||\n !scrollRestorationCache\n ) {\n return\n }\n\n router.isScrollRestorationSetup = true\n\n //\n ignoreScroll = false\n\n const getKey =\n router.options.getScrollRestorationKey || defaultGetScrollRestorationKey\n\n window.history.scrollRestoration = 'manual'\n\n // // Create a MutationObserver to monitor DOM changes\n // const mutationObserver = new MutationObserver(() => {\n // ;ignoreScroll = true\n // requestAnimationFrame(() => {\n // ;ignoreScroll = false\n\n // // Attempt to restore scroll position on each dom\n // // mutation until the user scrolls. We do this\n // // because dynamic content may come in at different\n // // ticks after the initial render and we want to\n // // keep up with that content as much as possible.\n // // As soon as the user scrolls, we no longer need\n // // to attempt router.\n // // console.log('mutation observer restoreScroll')\n // restoreScroll(\n // storageKey,\n // getKey(router.state.location),\n // router.options.scrollRestorationBehavior,\n // )\n // })\n // })\n\n // const observeDom = () => {\n // // Observe changes to the entire document\n // mutationObserver.observe(document, {\n // childList: true, // Detect added or removed child nodes\n // subtree: true, // Monitor all descendants\n // characterData: true, // Detect text content changes\n // })\n // }\n\n // const unobserveDom = () => {\n // mutationObserver.disconnect()\n // }\n\n // observeDom()\n\n const onScroll = (event: Event) => {\n // unobserveDom()\n\n if (ignoreScroll || !router.isScrollRestoring) {\n return\n }\n\n let elementSelector = ''\n\n if (event.target === document || event.target === window) {\n elementSelector = 'window'\n } else {\n const attrId = (event.target as Element).getAttribute(\n 'data-scroll-restoration-id',\n )\n\n if (attrId) {\n elementSelector = `[data-scroll-restoration-id=\"${attrId}\"]`\n } else {\n elementSelector = getCssSelector(event.target)\n }\n }\n\n const restoreKey = getKey(router.state.location)\n\n scrollRestorationCache.set((state) => {\n const keyEntry = (state[restoreKey] ||= {} as ScrollRestorationByElement)\n\n const elementEntry = (keyEntry[elementSelector] ||=\n {} as ScrollRestorationEntry)\n\n if (elementSelector === 'window') {\n elementEntry.scrollX = window.scrollX || 0\n elementEntry.scrollY = window.scrollY || 0\n } else if (elementSelector) {\n const element = document.querySelector(elementSelector)\n if (element) {\n elementEntry.scrollX = element.scrollLeft || 0\n elementEntry.scrollY = element.scrollTop || 0\n }\n }\n\n return state\n })\n }\n\n // Throttle the scroll event to avoid excessive updates\n if (typeof document !== 'undefined') {\n document.addEventListener('scroll', throttle(onScroll, 100), true)\n }\n\n router.subscribe('onRendered', (event) => {\n // unobserveDom()\n\n const cacheKey = getKey(event.toLocation)\n\n // If the user doesn't want to restore the scroll position,\n // we don't need to do anything.\n if (!router.resetNextScroll) {\n router.resetNextScroll = true\n return\n }\n if (typeof router.options.scrollRestoration === 'function') {\n const shouldRestore = router.options.scrollRestoration({\n location: router.latestLocation,\n })\n if (!shouldRestore) {\n return\n }\n }\n\n restoreScroll({\n storageKey,\n key: cacheKey,\n behavior: router.options.scrollRestorationBehavior,\n shouldScrollRestoration: router.isScrollRestoring,\n scrollToTopSelectors: router.options.scrollToTopSelectors,\n location: router.history.location,\n })\n\n if (router.isScrollRestoring) {\n // Mark the location as having been seen\n scrollRestorationCache.set((state) => {\n state[cacheKey] ||= {} as ScrollRestorationByElement\n\n return state\n })\n }\n })\n}\n\n/**\n * @private\n * Handles hash-based scrolling after navigation completes.\n * To be used in framework-specific <Transitioner> components during the onResolved event.\n *\n * Provides hash scrolling for programmatic navigation when default browser handling is prevented.\n * @param router The router instance containing current location and state\n */\nexport function handleHashScroll(router: AnyRouter) {\n if (typeof document !== 'undefined' && (document as any).querySelector) {\n const hashScrollIntoViewOptions =\n router.state.location.state.__hashScrollIntoViewOptions ?? true\n\n if (hashScrollIntoViewOptions && router.state.location.hash !== '') {\n const el = document.getElementById(router.state.location.hash)\n if (el) {\n el.scrollIntoView(hashScrollIntoViewOptions)\n }\n }\n }\n}\n"],"names":["functionalUpdate","storageKey"],"mappings":"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"}
@@ -35,3 +35,12 @@ export declare function restoreScroll({ storageKey, key, behavior, shouldScrollR
35
35
  location?: HistoryLocation;
36
36
  }): void;
37
37
  export declare function setupScrollRestoration(router: AnyRouter, force?: boolean): void;
38
+ /**
39
+ * @private
40
+ * Handles hash-based scrolling after navigation completes.
41
+ * To be used in framework-specific <Transitioner> components during the onResolved event.
42
+ *
43
+ * Provides hash scrolling for programmatic navigation when default browser handling is prevented.
44
+ * @param router The router instance containing current location and state
45
+ */
46
+ export declare function handleHashScroll(router: AnyRouter): void;
@@ -10,7 +10,10 @@ function createRequestHandler({
10
10
  }) {
11
11
  return async (cb) => {
12
12
  const router = createRouter();
13
- ssrServer.attachRouterServerSsrUtils(router, await getRouterManifest?.());
13
+ ssrServer.attachRouterServerSsrUtils({
14
+ router,
15
+ manifest: await getRouterManifest?.()
16
+ });
14
17
  const url = new URL(request.url, "http://localhost");
15
18
  const href = url.href.replace(url.origin, "");
16
19
  const history$1 = history.createMemoryHistory({
@@ -1 +1 @@
1
- {"version":3,"file":"createRequestHandler.cjs","sources":["../../../src/ssr/createRequestHandler.ts"],"sourcesContent":["import { createMemoryHistory } from '@tanstack/history'\nimport { mergeHeaders } from './headers'\nimport { attachRouterServerSsrUtils } from './ssr-server'\nimport type { HandlerCallback } from './handlerCallback'\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\n attachRouterServerSsrUtils(router, await getRouterManifest?.())\n\n const url = new URL(request.url, 'http://localhost')\n\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 })\n\n await router.load()\n\n await router.serverSsr?.dehydrate()\n\n const responseHeaders = getRequestHeaders({\n router,\n })\n\n return cb({\n request,\n router,\n responseHeaders,\n } as any)\n }\n}\n\nfunction getRequestHeaders(opts: { router: AnyRouter }): Headers {\n let headers = mergeHeaders(\n {\n 'Content-Type': 'text/html; charset=UTF-8',\n },\n ...opts.router.state.matches.map((match) => {\n return match.headers\n }),\n )\n\n // Handle Redirects\n const { redirect } = opts.router.state\n\n if (redirect) {\n headers = mergeHeaders(headers, redirect.headers)\n }\n\n return headers\n}\n"],"names":["attachRouterServerSsrUtils","history","createMemoryHistory","headers","mergeHeaders"],"mappings":";;;;;AAWO,SAAS,qBAAgD;AAAA,EAC9D;AAAA,EACA;AAAA,EACA;AACF,GAI4B;AAC1B,SAAO,OAAO,OAAO;AACnB,UAAM,SAAS,aAAA;AAEfA,yCAA2B,QAAQ,MAAM,qBAAqB;AAE9D,UAAM,MAAM,IAAI,IAAI,QAAQ,KAAK,kBAAkB;AAEnD,UAAM,OAAO,IAAI,KAAK,QAAQ,IAAI,QAAQ,EAAE;AAG5C,UAAMC,YAAUC,QAAAA,oBAAoB;AAAA,MAClC,gBAAgB,CAAC,IAAI;AAAA,IAAA,CACtB;AAGD,WAAO,OAAO;AAAA,MAAA,SACZD;AAAAA,IAAA,CACD;AAED,UAAM,OAAO,KAAA;AAEb,UAAM,OAAO,WAAW,UAAA;AAExB,UAAM,kBAAkB,kBAAkB;AAAA,MACxC;AAAA,IAAA,CACD;AAED,WAAO,GAAG;AAAA,MACR;AAAA,MACA;AAAA,MACA;AAAA,IAAA,CACM;AAAA,EACV;AACF;AAEA,SAAS,kBAAkB,MAAsC;AAC/D,MAAIE,YAAUC,QAAAA;AAAAA,IACZ;AAAA,MACE,gBAAgB;AAAA,IAAA;AAAA,IAElB,GAAG,KAAK,OAAO,MAAM,QAAQ,IAAI,CAAC,UAAU;AAC1C,aAAO,MAAM;AAAA,IACf,CAAC;AAAA,EAAA;AAIH,QAAM,EAAE,SAAA,IAAa,KAAK,OAAO;AAEjC,MAAI,UAAU;AACZD,gBAAUC,QAAAA,aAAaD,WAAS,SAAS,OAAO;AAAA,EAClD;AAEA,SAAOA;AACT;;"}
1
+ {"version":3,"file":"createRequestHandler.cjs","sources":["../../../src/ssr/createRequestHandler.ts"],"sourcesContent":["import { createMemoryHistory } from '@tanstack/history'\nimport { mergeHeaders } from './headers'\nimport { attachRouterServerSsrUtils } from './ssr-server'\nimport type { HandlerCallback } from './handlerCallback'\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\n attachRouterServerSsrUtils({\n router,\n manifest: await getRouterManifest?.(),\n })\n\n const url = new URL(request.url, 'http://localhost')\n\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 })\n\n await router.load()\n\n await router.serverSsr?.dehydrate()\n\n const responseHeaders = getRequestHeaders({\n router,\n })\n\n return cb({\n request,\n router,\n responseHeaders,\n } as any)\n }\n}\n\nfunction getRequestHeaders(opts: { router: AnyRouter }): Headers {\n let headers = mergeHeaders(\n {\n 'Content-Type': 'text/html; charset=UTF-8',\n },\n ...opts.router.state.matches.map((match) => {\n return match.headers\n }),\n )\n\n // Handle Redirects\n const { redirect } = opts.router.state\n\n if (redirect) {\n headers = mergeHeaders(headers, redirect.headers)\n }\n\n return headers\n}\n"],"names":["attachRouterServerSsrUtils","history","createMemoryHistory","headers","mergeHeaders"],"mappings":";;;;;AAWO,SAAS,qBAAgD;AAAA,EAC9D;AAAA,EACA;AAAA,EACA;AACF,GAI4B;AAC1B,SAAO,OAAO,OAAO;AACnB,UAAM,SAAS,aAAA;AAEfA,yCAA2B;AAAA,MACzB;AAAA,MACA,UAAU,MAAM,oBAAA;AAAA,IAAoB,CACrC;AAED,UAAM,MAAM,IAAI,IAAI,QAAQ,KAAK,kBAAkB;AAEnD,UAAM,OAAO,IAAI,KAAK,QAAQ,IAAI,QAAQ,EAAE;AAG5C,UAAMC,YAAUC,QAAAA,oBAAoB;AAAA,MAClC,gBAAgB,CAAC,IAAI;AAAA,IAAA,CACtB;AAGD,WAAO,OAAO;AAAA,MAAA,SACZD;AAAAA,IAAA,CACD;AAED,UAAM,OAAO,KAAA;AAEb,UAAM,OAAO,WAAW,UAAA;AAExB,UAAM,kBAAkB,kBAAkB;AAAA,MACxC;AAAA,IAAA,CACD;AAED,WAAO,GAAG;AAAA,MACR;AAAA,MACA;AAAA,MACA;AAAA,IAAA,CACM;AAAA,EACV;AACF;AAEA,SAAS,kBAAkB,MAAsC;AAC/D,MAAIE,YAAUC,QAAAA;AAAAA,IACZ;AAAA,MACE,gBAAgB;AAAA,IAAA;AAAA,IAElB,GAAG,KAAK,OAAO,MAAM,QAAQ,IAAI,CAAC,UAAU;AAC1C,aAAO,MAAM;AAAA,IACf,CAAC;AAAA,EAAA;AAIH,QAAM,EAAE,SAAA,IAAa,KAAK,OAAO;AAEjC,MAAI,UAAU;AACZD,gBAAUC,QAAAA,aAAaD,WAAS,SAAS,OAAO;AAAA,EAClD;AAEA,SAAOA;AACT;;"}
@@ -1 +1 @@
1
- {"version":3,"file":"transformer.cjs","sources":["../../../../src/ssr/serializer/transformer.ts"],"sourcesContent":["import { createPlugin } from 'seroval'\nimport { GLOBAL_TSR } from '../constants'\nimport type { Plugin, SerovalNode } from 'seroval'\nimport type { Register, SSROption } from '../../router'\nimport type { LooseReturnType } from '../../utils'\nimport type { AnyRoute, ResolveAllSSR } from '../../route'\n\nexport type Serializable =\n | number\n | string\n | boolean\n | null\n | undefined\n | bigint\n | Date\n\nexport function createSerializationAdapter<\n TInput = unknown,\n TOutput = unknown /* we need to check that this type is actually serializable taking into account all seroval native types and any custom plugin WE=router/start add!!! */,\n>(\n opts: CreateSerializationAdapterOptions<TInput, TOutput>,\n): SerializationAdapter<TInput, TOutput> {\n return opts as unknown as SerializationAdapter<TInput, TOutput>\n}\n\nexport interface CreateSerializationAdapterOptions<TInput, TOutput> {\n key: string\n test: (value: unknown) => value is TInput\n toSerializable: (value: TInput) => ValidateSerializable<TOutput, Serializable>\n fromSerializable: (value: TOutput) => TInput\n}\n\nexport type ValidateSerializable<T, TSerializable> = T extends TSerializable\n ? T\n : T extends (...args: Array<any>) => any\n ? 'Function is not serializable'\n : T extends Promise<any>\n ? ValidateSerializablePromise<T, TSerializable>\n : T extends ReadableStream<any>\n ? ValidateReadableStream<T, TSerializable>\n : T extends Set<any>\n ? ValidateSerializableSet<T, TSerializable>\n : T extends Map<any, any>\n ? ValidateSerializableMap<T, TSerializable>\n : {\n [K in keyof T]: ValidateSerializable<T[K], TSerializable>\n }\n\nexport type ValidateSerializablePromise<T, TSerializable> =\n T extends Promise<infer TAwaited>\n ? Promise<ValidateSerializable<TAwaited, TSerializable>>\n : never\n\nexport type ValidateReadableStream<T, TSerializable> =\n T extends ReadableStream<infer TStreamed>\n ? ReadableStream<ValidateSerializable<TStreamed, TSerializable>>\n : never\n\nexport type ValidateSerializableSet<T, TSerializable> =\n T extends Set<infer TItem>\n ? Set<ValidateSerializable<TItem, TSerializable>>\n : never\n\nexport type ValidateSerializableMap<T, TSerializable> =\n T extends Map<infer TKey, infer TValue>\n ? Map<\n ValidateSerializable<TKey, TSerializable>,\n ValidateSerializable<TValue, TSerializable>\n >\n : never\n\nexport type RegisteredReadableStream =\n unknown extends SerializerExtensions['ReadableStream']\n ? never\n : SerializerExtensions['ReadableStream']\n\nexport interface DefaultSerializerExtensions {\n ReadableStream: unknown\n}\n\nexport interface SerializerExtensions extends DefaultSerializerExtensions {}\n\nexport interface SerializationAdapter<TInput, TOutput> {\n '~types': SerializationAdapterTypes<TInput, TOutput>\n key: string\n test: (value: unknown) => value is TInput\n toSerializable: (value: TInput) => TOutput\n fromSerializable: (value: TOutput) => TInput\n makePlugin: (options: { didRun: boolean }) => Plugin<TInput, SerovalNode>\n}\n\nexport interface SerializationAdapterTypes<TInput, TOutput> {\n input: TInput\n output: TOutput\n}\n\nexport type AnySerializationAdapter = SerializationAdapter<any, any>\n\nexport function makeSsrSerovalPlugin<TInput, TOutput>(\n serializationAdapter: SerializationAdapter<TInput, TOutput>,\n options: { didRun: boolean },\n) {\n return createPlugin<TInput, SerovalNode>({\n tag: '$TSR/t/' + serializationAdapter.key,\n test: serializationAdapter.test,\n parse: {\n stream(value, ctx) {\n return ctx.parse(serializationAdapter.toSerializable(value))\n },\n },\n serialize(node, ctx) {\n options.didRun = true\n return (\n GLOBAL_TSR +\n '.t.get(\"' +\n serializationAdapter.key +\n '\")(' +\n ctx.serialize(node) +\n ')'\n )\n },\n // we never deserialize on the server during SSR\n deserialize: undefined as never,\n })\n}\n\nexport function makeSerovalPlugin<TInput, TOutput>(\n serializationAdapter: SerializationAdapter<TInput, TOutput>,\n) {\n return createPlugin<TInput, SerovalNode>({\n tag: '$TSR/t/' + serializationAdapter.key,\n test: serializationAdapter.test,\n parse: {\n sync(value, ctx) {\n return ctx.parse(serializationAdapter.toSerializable(value))\n },\n async async(value, ctx) {\n return await ctx.parse(serializationAdapter.toSerializable(value))\n },\n stream(value, ctx) {\n return ctx.parse(serializationAdapter.toSerializable(value))\n },\n },\n // we don't generate JS code outside of SSR (for now)\n serialize: undefined as never,\n deserialize(node, ctx) {\n return serializationAdapter.fromSerializable(\n ctx.deserialize(node) as TOutput,\n )\n },\n })\n}\n\nexport type ValidateSerializableInput<\n TRegister extends Register,\n T,\n> = ValidateSerializable<T, RegisteredSerializableInput<TRegister>>\n\nexport type RegisteredSerializableInput<TRegister extends Register> =\n | (unknown extends RegisteredSerializationAdapters<TRegister>\n ? never\n : RegisteredSerializationAdapters<TRegister> extends ReadonlyArray<AnySerializationAdapter>\n ? RegisteredSerializationAdapters<TRegister>[number]['~types']['input']\n : never)\n | Serializable\n\nexport type RegisteredSerializationAdapters<TRegister extends Register> =\n TRegister['config']['~types']['serializationAdapters']\n\nexport type ValidateSerializableInputResult<\n TRegister extends Register,\n T,\n> = ValidateSerializableResult<T, RegisteredSerializableInput<TRegister>>\n\nexport type ValidateSerializableResult<T, TSerializable> =\n T extends TSerializable\n ? T\n : unknown extends SerializerExtensions['ReadableStream']\n ? { [K in keyof T]: ValidateSerializableResult<T[K], TSerializable> }\n : T extends SerializerExtensions['ReadableStream']\n ? ReadableStream\n : { [K in keyof T]: ValidateSerializableResult<T[K], TSerializable> }\n\nexport type RegisteredSSROption<TRegister extends Register> =\n unknown extends TRegister['config']['~types']['defaultSsr']\n ? SSROption\n : TRegister['config']['~types']['defaultSsr']\n\nexport type ValidateSerializableLifecycleResult<\n TRegister extends Register,\n TParentRoute extends AnyRoute,\n TSSR,\n TFn,\n> = false extends TRegister['ssr']\n ? any\n : ValidateSerializableLifecycleResultSSR<\n TRegister,\n TParentRoute,\n TSSR,\n TFn\n > extends infer TInput\n ? TInput\n : never\n\nexport type ValidateSerializableLifecycleResultSSR<\n TRegister extends Register,\n TParentRoute extends AnyRoute,\n TSSR,\n TFn,\n> =\n ResolveAllSSR<TParentRoute, TSSR> extends false\n ? any\n : RegisteredSSROption<TRegister> extends false\n ? any\n : ValidateSerializableInput<TRegister, LooseReturnType<TFn>>\n"],"names":["createPlugin","GLOBAL_TSR"],"mappings":";;;;AAgBO,SAAS,2BAId,MACuC;AACvC,SAAO;AACT;AA2EO,SAAS,qBACd,sBACA,SACA;AACA,SAAOA,qBAAkC;AAAA,IACvC,KAAK,YAAY,qBAAqB;AAAA,IACtC,MAAM,qBAAqB;AAAA,IAC3B,OAAO;AAAA,MACL,OAAO,OAAO,KAAK;AACjB,eAAO,IAAI,MAAM,qBAAqB,eAAe,KAAK,CAAC;AAAA,MAC7D;AAAA,IAAA;AAAA,IAEF,UAAU,MAAM,KAAK;AACnB,cAAQ,SAAS;AACjB,aACEC,UAAAA,aACA,aACA,qBAAqB,MACrB,QACA,IAAI,UAAU,IAAI,IAClB;AAAA,IAEJ;AAAA;AAAA,IAEA,aAAa;AAAA,EAAA,CACd;AACH;AAEO,SAAS,kBACd,sBACA;AACA,SAAOD,qBAAkC;AAAA,IACvC,KAAK,YAAY,qBAAqB;AAAA,IACtC,MAAM,qBAAqB;AAAA,IAC3B,OAAO;AAAA,MACL,KAAK,OAAO,KAAK;AACf,eAAO,IAAI,MAAM,qBAAqB,eAAe,KAAK,CAAC;AAAA,MAC7D;AAAA,MACA,MAAM,MAAM,OAAO,KAAK;AACtB,eAAO,MAAM,IAAI,MAAM,qBAAqB,eAAe,KAAK,CAAC;AAAA,MACnE;AAAA,MACA,OAAO,OAAO,KAAK;AACjB,eAAO,IAAI,MAAM,qBAAqB,eAAe,KAAK,CAAC;AAAA,MAC7D;AAAA,IAAA;AAAA;AAAA,IAGF,WAAW;AAAA,IACX,YAAY,MAAM,KAAK;AACrB,aAAO,qBAAqB;AAAA,QAC1B,IAAI,YAAY,IAAI;AAAA,MAAA;AAAA,IAExB;AAAA,EAAA,CACD;AACH;;;;"}
1
+ {"version":3,"file":"transformer.cjs","sources":["../../../../src/ssr/serializer/transformer.ts"],"sourcesContent":["import { createPlugin } from 'seroval'\nimport { GLOBAL_TSR } from '../constants'\nimport type { Plugin, SerovalNode } from 'seroval'\nimport type { RegisteredConfigType, SSROption } from '../../router'\nimport type { LooseReturnType } from '../../utils'\nimport type { AnyRoute, ResolveAllSSR } from '../../route'\n\nexport type Serializable =\n | number\n | string\n | boolean\n | null\n | undefined\n | bigint\n | Date\n\nexport function createSerializationAdapter<\n TInput = unknown,\n TOutput = unknown /* we need to check that this type is actually serializable taking into account all seroval native types and any custom plugin WE=router/start add!!! */,\n>(\n opts: CreateSerializationAdapterOptions<TInput, TOutput>,\n): SerializationAdapter<TInput, TOutput> {\n return opts as unknown as SerializationAdapter<TInput, TOutput>\n}\n\nexport interface CreateSerializationAdapterOptions<TInput, TOutput> {\n key: string\n test: (value: unknown) => value is TInput\n toSerializable: (value: TInput) => ValidateSerializable<TOutput, Serializable>\n fromSerializable: (value: TOutput) => TInput\n}\n\nexport type ValidateSerializable<T, TSerializable> = T extends TSerializable\n ? T\n : T extends (...args: Array<any>) => any\n ? 'Function is not serializable'\n : T extends Promise<any>\n ? ValidateSerializablePromise<T, TSerializable>\n : T extends ReadableStream<any>\n ? ValidateReadableStream<T, TSerializable>\n : T extends Set<any>\n ? ValidateSerializableSet<T, TSerializable>\n : T extends Map<any, any>\n ? ValidateSerializableMap<T, TSerializable>\n : {\n [K in keyof T]: ValidateSerializable<T[K], TSerializable>\n }\n\nexport type ValidateSerializablePromise<T, TSerializable> =\n T extends Promise<infer TAwaited>\n ? Promise<ValidateSerializable<TAwaited, TSerializable>>\n : never\n\nexport type ValidateReadableStream<T, TSerializable> =\n T extends ReadableStream<infer TStreamed>\n ? ReadableStream<ValidateSerializable<TStreamed, TSerializable>>\n : never\n\nexport type ValidateSerializableSet<T, TSerializable> =\n T extends Set<infer TItem>\n ? Set<ValidateSerializable<TItem, TSerializable>>\n : never\n\nexport type ValidateSerializableMap<T, TSerializable> =\n T extends Map<infer TKey, infer TValue>\n ? Map<\n ValidateSerializable<TKey, TSerializable>,\n ValidateSerializable<TValue, TSerializable>\n >\n : never\n\nexport type RegisteredReadableStream =\n unknown extends SerializerExtensions['ReadableStream']\n ? never\n : SerializerExtensions['ReadableStream']\n\nexport interface DefaultSerializerExtensions {\n ReadableStream: unknown\n}\n\nexport interface SerializerExtensions extends DefaultSerializerExtensions {}\n\nexport interface SerializationAdapter<TInput, TOutput> {\n '~types': SerializationAdapterTypes<TInput, TOutput>\n key: string\n test: (value: unknown) => value is TInput\n toSerializable: (value: TInput) => TOutput\n fromSerializable: (value: TOutput) => TInput\n makePlugin: (options: { didRun: boolean }) => Plugin<TInput, SerovalNode>\n}\n\nexport interface SerializationAdapterTypes<TInput, TOutput> {\n input: TInput\n output: TOutput\n}\n\nexport type AnySerializationAdapter = SerializationAdapter<any, any>\n\nexport function makeSsrSerovalPlugin<TInput, TOutput>(\n serializationAdapter: SerializationAdapter<TInput, TOutput>,\n options: { didRun: boolean },\n) {\n return createPlugin<TInput, SerovalNode>({\n tag: '$TSR/t/' + serializationAdapter.key,\n test: serializationAdapter.test,\n parse: {\n stream(value, ctx) {\n return ctx.parse(serializationAdapter.toSerializable(value))\n },\n },\n serialize(node, ctx) {\n options.didRun = true\n return (\n GLOBAL_TSR +\n '.t.get(\"' +\n serializationAdapter.key +\n '\")(' +\n ctx.serialize(node) +\n ')'\n )\n },\n // we never deserialize on the server during SSR\n deserialize: undefined as never,\n })\n}\n\nexport function makeSerovalPlugin<TInput, TOutput>(\n serializationAdapter: SerializationAdapter<TInput, TOutput>,\n) {\n return createPlugin<TInput, SerovalNode>({\n tag: '$TSR/t/' + serializationAdapter.key,\n test: serializationAdapter.test,\n parse: {\n sync(value, ctx) {\n return ctx.parse(serializationAdapter.toSerializable(value))\n },\n async async(value, ctx) {\n return await ctx.parse(serializationAdapter.toSerializable(value))\n },\n stream(value, ctx) {\n return ctx.parse(serializationAdapter.toSerializable(value))\n },\n },\n // we don't generate JS code outside of SSR (for now)\n serialize: undefined as never,\n deserialize(node, ctx) {\n return serializationAdapter.fromSerializable(\n ctx.deserialize(node) as TOutput,\n )\n },\n })\n}\n\nexport type ValidateSerializableInput<TRegister, T> = ValidateSerializable<\n T,\n RegisteredSerializableInput<TRegister>\n>\n\nexport type RegisteredSerializableInput<TRegister> =\n | (unknown extends RegisteredSerializationAdapters<TRegister>\n ? never\n : RegisteredSerializationAdapters<TRegister> extends ReadonlyArray<AnySerializationAdapter>\n ? RegisteredSerializationAdapters<TRegister>[number]['~types']['input']\n : never)\n | Serializable\n\nexport type RegisteredSerializationAdapters<TRegister> = RegisteredConfigType<\n TRegister,\n 'serializationAdapters'\n>\n\nexport type ValidateSerializableInputResult<TRegister, T> =\n ValidateSerializableResult<T, RegisteredSerializableInput<TRegister>>\n\nexport type ValidateSerializableResult<T, TSerializable> =\n T extends TSerializable\n ? T\n : unknown extends SerializerExtensions['ReadableStream']\n ? { [K in keyof T]: ValidateSerializableResult<T[K], TSerializable> }\n : T extends SerializerExtensions['ReadableStream']\n ? ReadableStream\n : { [K in keyof T]: ValidateSerializableResult<T[K], TSerializable> }\n\nexport type RegisteredSSROption<TRegister> =\n unknown extends RegisteredConfigType<TRegister, 'defaultSsr'>\n ? SSROption\n : RegisteredConfigType<TRegister, 'defaultSsr'>\n\nexport type ValidateSerializableLifecycleResult<\n TRegister,\n TParentRoute extends AnyRoute,\n TSSR,\n TFn,\n> = false extends (TRegister extends { ssr: infer TSSR } ? TSSR : never)\n ? any\n : ValidateSerializableLifecycleResultSSR<\n TRegister,\n TParentRoute,\n TSSR,\n TFn\n > extends infer TInput\n ? TInput\n : never\n\nexport type ValidateSerializableLifecycleResultSSR<\n TRegister,\n TParentRoute extends AnyRoute,\n TSSR,\n TFn,\n> =\n ResolveAllSSR<TParentRoute, TSSR> extends false\n ? any\n : RegisteredSSROption<TRegister> extends false\n ? any\n : ValidateSerializableInput<TRegister, LooseReturnType<TFn>>\n"],"names":["createPlugin","GLOBAL_TSR"],"mappings":";;;;AAgBO,SAAS,2BAId,MACuC;AACvC,SAAO;AACT;AA2EO,SAAS,qBACd,sBACA,SACA;AACA,SAAOA,qBAAkC;AAAA,IACvC,KAAK,YAAY,qBAAqB;AAAA,IACtC,MAAM,qBAAqB;AAAA,IAC3B,OAAO;AAAA,MACL,OAAO,OAAO,KAAK;AACjB,eAAO,IAAI,MAAM,qBAAqB,eAAe,KAAK,CAAC;AAAA,MAC7D;AAAA,IAAA;AAAA,IAEF,UAAU,MAAM,KAAK;AACnB,cAAQ,SAAS;AACjB,aACEC,UAAAA,aACA,aACA,qBAAqB,MACrB,QACA,IAAI,UAAU,IAAI,IAClB;AAAA,IAEJ;AAAA;AAAA,IAEA,aAAa;AAAA,EAAA,CACd;AACH;AAEO,SAAS,kBACd,sBACA;AACA,SAAOD,qBAAkC;AAAA,IACvC,KAAK,YAAY,qBAAqB;AAAA,IACtC,MAAM,qBAAqB;AAAA,IAC3B,OAAO;AAAA,MACL,KAAK,OAAO,KAAK;AACf,eAAO,IAAI,MAAM,qBAAqB,eAAe,KAAK,CAAC;AAAA,MAC7D;AAAA,MACA,MAAM,MAAM,OAAO,KAAK;AACtB,eAAO,MAAM,IAAI,MAAM,qBAAqB,eAAe,KAAK,CAAC;AAAA,MACnE;AAAA,MACA,OAAO,OAAO,KAAK;AACjB,eAAO,IAAI,MAAM,qBAAqB,eAAe,KAAK,CAAC;AAAA,MAC7D;AAAA,IAAA;AAAA;AAAA,IAGF,WAAW;AAAA,IACX,YAAY,MAAM,KAAK;AACrB,aAAO,qBAAqB;AAAA,QAC1B,IAAI,YAAY,IAAI;AAAA,MAAA;AAAA,IAExB;AAAA,EAAA,CACD;AACH;;;;"}
@@ -1,5 +1,5 @@
1
1
  import { Plugin, SerovalNode } from 'seroval';
2
- import { Register, SSROption } from '../../router.cjs';
2
+ import { RegisteredConfigType, SSROption } from '../../router.cjs';
3
3
  import { LooseReturnType } from '../../utils.cjs';
4
4
  import { AnyRoute, ResolveAllSSR } from '../../route.cjs';
5
5
  export type Serializable = number | string | boolean | null | undefined | bigint | Date;
@@ -42,15 +42,17 @@ export declare function makeSsrSerovalPlugin<TInput, TOutput>(serializationAdapt
42
42
  didRun: boolean;
43
43
  }): Plugin<TInput, SerovalNode>;
44
44
  export declare function makeSerovalPlugin<TInput, TOutput>(serializationAdapter: SerializationAdapter<TInput, TOutput>): Plugin<TInput, SerovalNode>;
45
- export type ValidateSerializableInput<TRegister extends Register, T> = ValidateSerializable<T, RegisteredSerializableInput<TRegister>>;
46
- export type RegisteredSerializableInput<TRegister extends Register> = (unknown extends RegisteredSerializationAdapters<TRegister> ? never : RegisteredSerializationAdapters<TRegister> extends ReadonlyArray<AnySerializationAdapter> ? RegisteredSerializationAdapters<TRegister>[number]['~types']['input'] : never) | Serializable;
47
- export type RegisteredSerializationAdapters<TRegister extends Register> = TRegister['config']['~types']['serializationAdapters'];
48
- export type ValidateSerializableInputResult<TRegister extends Register, T> = ValidateSerializableResult<T, RegisteredSerializableInput<TRegister>>;
45
+ export type ValidateSerializableInput<TRegister, T> = ValidateSerializable<T, RegisteredSerializableInput<TRegister>>;
46
+ export type RegisteredSerializableInput<TRegister> = (unknown extends RegisteredSerializationAdapters<TRegister> ? never : RegisteredSerializationAdapters<TRegister> extends ReadonlyArray<AnySerializationAdapter> ? RegisteredSerializationAdapters<TRegister>[number]['~types']['input'] : never) | Serializable;
47
+ export type RegisteredSerializationAdapters<TRegister> = RegisteredConfigType<TRegister, 'serializationAdapters'>;
48
+ export type ValidateSerializableInputResult<TRegister, T> = ValidateSerializableResult<T, RegisteredSerializableInput<TRegister>>;
49
49
  export type ValidateSerializableResult<T, TSerializable> = T extends TSerializable ? T : unknown extends SerializerExtensions['ReadableStream'] ? {
50
50
  [K in keyof T]: ValidateSerializableResult<T[K], TSerializable>;
51
51
  } : T extends SerializerExtensions['ReadableStream'] ? ReadableStream : {
52
52
  [K in keyof T]: ValidateSerializableResult<T[K], TSerializable>;
53
53
  };
54
- export type RegisteredSSROption<TRegister extends Register> = unknown extends TRegister['config']['~types']['defaultSsr'] ? SSROption : TRegister['config']['~types']['defaultSsr'];
55
- export type ValidateSerializableLifecycleResult<TRegister extends Register, TParentRoute extends AnyRoute, TSSR, TFn> = false extends TRegister['ssr'] ? any : ValidateSerializableLifecycleResultSSR<TRegister, TParentRoute, TSSR, TFn> extends infer TInput ? TInput : never;
56
- export type ValidateSerializableLifecycleResultSSR<TRegister extends Register, TParentRoute extends AnyRoute, TSSR, TFn> = ResolveAllSSR<TParentRoute, TSSR> extends false ? any : RegisteredSSROption<TRegister> extends false ? any : ValidateSerializableInput<TRegister, LooseReturnType<TFn>>;
54
+ export type RegisteredSSROption<TRegister> = unknown extends RegisteredConfigType<TRegister, 'defaultSsr'> ? SSROption : RegisteredConfigType<TRegister, 'defaultSsr'>;
55
+ export type ValidateSerializableLifecycleResult<TRegister, TParentRoute extends AnyRoute, TSSR, TFn> = false extends (TRegister extends {
56
+ ssr: infer TSSR;
57
+ } ? TSSR : never) ? any : ValidateSerializableLifecycleResultSSR<TRegister, TParentRoute, TSSR, TFn> extends infer TInput ? TInput : never;
58
+ export type ValidateSerializableLifecycleResultSSR<TRegister, TParentRoute extends AnyRoute, TSSR, TFn> = ResolveAllSSR<TParentRoute, TSSR> extends false ? any : RegisteredSSROption<TRegister> extends false ? any : ValidateSerializableInput<TRegister, LooseReturnType<TFn>>;
@@ -4,8 +4,3 @@ export { defineHandlerCallback } from './handlerCallback.cjs';
4
4
  export type { HandlerCallback } from './handlerCallback.cjs';
5
5
  export { transformPipeableStreamWithRouter, transformStreamWithRouter, transformReadableStreamWithRouter, } from './transformStreamWithRouter.cjs';
6
6
  export { attachRouterServerSsrUtils } from './ssr-server.cjs';
7
- declare module '../router' {
8
- interface Register {
9
- ssr: true;
10
- }
11
- }
@@ -27,7 +27,10 @@ function dehydrateMatch(match) {
27
27
  }
28
28
  return dehydratedMatch;
29
29
  }
30
- function attachRouterServerSsrUtils(router, manifest) {
30
+ function attachRouterServerSsrUtils({
31
+ router,
32
+ manifest
33
+ }) {
31
34
  router.ssr = {
32
35
  manifest
33
36
  };
@@ -56,7 +59,7 @@ function attachRouterServerSsrUtils(router, manifest) {
56
59
  injectScript: (getScript) => {
57
60
  return router.serverSsr.injectHtml(async () => {
58
61
  const script = await getScript();
59
- return `<script class='$tsr'>${getInitialScript()}${script};$_TSR.c()<\/script>`;
62
+ return `<script ${router.options.ssr?.nonce ? `nonce='${router.options.ssr.nonce}'` : ""} class='$tsr'>${getInitialScript()}${script};$_TSR.c()<\/script>`;
60
63
  });
61
64
  },
62
65
  dehydrate: async () => {
@@ -1 +1 @@
1
- {"version":3,"file":"ssr-server.cjs","sources":["../../../src/ssr/ssr-server.ts"],"sourcesContent":["import { crossSerializeStream, getCrossReferenceHeader } from 'seroval'\nimport invariant from 'tiny-invariant'\nimport { createControlledPromise } from '../utils'\nimport minifiedTsrBootStrapScript from './tsrScript?script-string'\nimport { GLOBAL_TSR } from './constants'\nimport { defaultSerovalPlugins } from './serializer/seroval-plugins'\nimport { makeSsrSerovalPlugin } from './serializer/transformer'\nimport type { AnyRouter } from '../router'\nimport type { DehydratedMatch } from './ssr-client'\nimport type { DehydratedRouter } from './client'\nimport type { AnyRouteMatch } from '../Matches'\nimport type { Manifest } from '../manifest'\nimport type { AnySerializationAdapter } from './serializer/transformer'\n\ndeclare module '../router' {\n interface ServerSsr {\n setRenderFinished: () => void\n }\n interface RouterEvents {\n onInjectedHtml: {\n type: 'onInjectedHtml'\n promise: Promise<string>\n }\n }\n}\n\nconst SCOPE_ID = 'tsr'\n\nexport function dehydrateMatch(match: AnyRouteMatch): DehydratedMatch {\n const dehydratedMatch: DehydratedMatch = {\n i: match.id,\n u: match.updatedAt,\n s: match.status,\n }\n\n const properties = [\n ['__beforeLoadContext', 'b'],\n ['loaderData', 'l'],\n ['error', 'e'],\n ['ssr', 'ssr'],\n ] as const\n\n for (const [key, shorthand] of properties) {\n if (match[key] !== undefined) {\n dehydratedMatch[shorthand] = match[key]\n }\n }\n return dehydratedMatch\n}\n\nexport function attachRouterServerSsrUtils(\n router: AnyRouter,\n manifest: Manifest | undefined,\n) {\n router.ssr = {\n manifest,\n }\n let initialScriptSent = false\n const getInitialScript = () => {\n if (initialScriptSent) {\n return ''\n }\n initialScriptSent = true\n return `${getCrossReferenceHeader(SCOPE_ID)};${minifiedTsrBootStrapScript};`\n }\n let _dehydrated = false\n const listeners: Array<() => void> = []\n\n router.serverSsr = {\n injectedHtml: [],\n injectHtml: (getHtml) => {\n const promise = Promise.resolve().then(getHtml)\n router.serverSsr!.injectedHtml.push(promise)\n router.emit({\n type: 'onInjectedHtml',\n promise,\n })\n\n return promise.then(() => {})\n },\n injectScript: (getScript) => {\n return router.serverSsr!.injectHtml(async () => {\n const script = await getScript()\n return `<script class='$tsr'>${getInitialScript()}${script};$_TSR.c()</script>`\n })\n },\n dehydrate: async () => {\n invariant(!_dehydrated, 'router is already dehydrated!')\n let matchesToDehydrate = router.state.matches\n if (router.isShell()) {\n // In SPA mode we only want to dehydrate the root match\n matchesToDehydrate = matchesToDehydrate.slice(0, 1)\n }\n const matches = matchesToDehydrate.map(dehydrateMatch)\n\n const dehydratedRouter: DehydratedRouter = {\n manifest: router.ssr!.manifest,\n matches,\n }\n const lastMatchId = matchesToDehydrate[matchesToDehydrate.length - 1]?.id\n if (lastMatchId) {\n dehydratedRouter.lastMatchId = lastMatchId\n }\n dehydratedRouter.dehydratedData = await router.options.dehydrate?.()\n _dehydrated = true\n\n const p = createControlledPromise<string>()\n const trackPlugins = { didRun: false }\n const plugins =\n (\n router.options.serializationAdapters as\n | Array<AnySerializationAdapter>\n | undefined\n )?.map((t) => makeSsrSerovalPlugin(t, trackPlugins)) ?? []\n crossSerializeStream(dehydratedRouter, {\n refs: new Map(),\n plugins: [...plugins, ...defaultSerovalPlugins],\n onSerialize: (data, initial) => {\n let serialized = initial ? GLOBAL_TSR + '.router=' + data : data\n if (trackPlugins.didRun) {\n serialized = GLOBAL_TSR + '.p(()=>' + serialized + ')'\n }\n router.serverSsr!.injectScript(() => serialized)\n },\n scopeId: SCOPE_ID,\n onDone: () => p.resolve(''),\n onError: (err) => p.reject(err),\n })\n // make sure the stream is kept open until the promise is resolved\n router.serverSsr!.injectHtml(() => p)\n },\n isDehydrated() {\n return _dehydrated\n },\n onRenderFinished: (listener) => listeners.push(listener),\n setRenderFinished: () => {\n listeners.forEach((l) => l())\n },\n }\n}\n"],"names":["getCrossReferenceHeader","minifiedTsrBootStrapScript","createControlledPromise","makeSsrSerovalPlugin","crossSerializeStream","defaultSerovalPlugins","GLOBAL_TSR"],"mappings":"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"}
1
+ {"version":3,"file":"ssr-server.cjs","sources":["../../../src/ssr/ssr-server.ts"],"sourcesContent":["import { crossSerializeStream, getCrossReferenceHeader } from 'seroval'\nimport invariant from 'tiny-invariant'\nimport { createControlledPromise } from '../utils'\nimport minifiedTsrBootStrapScript from './tsrScript?script-string'\nimport { GLOBAL_TSR } from './constants'\nimport { defaultSerovalPlugins } from './serializer/seroval-plugins'\nimport { makeSsrSerovalPlugin } from './serializer/transformer'\nimport type { AnyRouter } from '../router'\nimport type { DehydratedMatch } from './ssr-client'\nimport type { DehydratedRouter } from './client'\nimport type { AnyRouteMatch } from '../Matches'\nimport type { Manifest } from '../manifest'\nimport type { AnySerializationAdapter } from './serializer/transformer'\n\ndeclare module '../router' {\n interface ServerSsr {\n setRenderFinished: () => void\n }\n interface RouterEvents {\n onInjectedHtml: {\n type: 'onInjectedHtml'\n promise: Promise<string>\n }\n }\n}\n\nconst SCOPE_ID = 'tsr'\n\nexport function dehydrateMatch(match: AnyRouteMatch): DehydratedMatch {\n const dehydratedMatch: DehydratedMatch = {\n i: match.id,\n u: match.updatedAt,\n s: match.status,\n }\n\n const properties = [\n ['__beforeLoadContext', 'b'],\n ['loaderData', 'l'],\n ['error', 'e'],\n ['ssr', 'ssr'],\n ] as const\n\n for (const [key, shorthand] of properties) {\n if (match[key] !== undefined) {\n dehydratedMatch[shorthand] = match[key]\n }\n }\n return dehydratedMatch\n}\n\nexport function attachRouterServerSsrUtils({\n router,\n manifest,\n}: {\n router: AnyRouter\n manifest: Manifest | undefined\n}) {\n router.ssr = {\n manifest,\n }\n let initialScriptSent = false\n const getInitialScript = () => {\n if (initialScriptSent) {\n return ''\n }\n initialScriptSent = true\n return `${getCrossReferenceHeader(SCOPE_ID)};${minifiedTsrBootStrapScript};`\n }\n let _dehydrated = false\n const listeners: Array<() => void> = []\n\n router.serverSsr = {\n injectedHtml: [],\n injectHtml: (getHtml) => {\n const promise = Promise.resolve().then(getHtml)\n router.serverSsr!.injectedHtml.push(promise)\n router.emit({\n type: 'onInjectedHtml',\n promise,\n })\n\n return promise.then(() => {})\n },\n injectScript: (getScript) => {\n return router.serverSsr!.injectHtml(async () => {\n const script = await getScript()\n return `<script ${router.options.ssr?.nonce ? `nonce='${router.options.ssr.nonce}'` : ''} class='$tsr'>${getInitialScript()}${script};$_TSR.c()</script>`\n })\n },\n dehydrate: async () => {\n invariant(!_dehydrated, 'router is already dehydrated!')\n let matchesToDehydrate = router.state.matches\n if (router.isShell()) {\n // In SPA mode we only want to dehydrate the root match\n matchesToDehydrate = matchesToDehydrate.slice(0, 1)\n }\n const matches = matchesToDehydrate.map(dehydrateMatch)\n\n const dehydratedRouter: DehydratedRouter = {\n manifest: router.ssr!.manifest,\n matches,\n }\n const lastMatchId = matchesToDehydrate[matchesToDehydrate.length - 1]?.id\n if (lastMatchId) {\n dehydratedRouter.lastMatchId = lastMatchId\n }\n dehydratedRouter.dehydratedData = await router.options.dehydrate?.()\n _dehydrated = true\n\n const p = createControlledPromise<string>()\n const trackPlugins = { didRun: false }\n const plugins =\n (\n router.options.serializationAdapters as\n | Array<AnySerializationAdapter>\n | undefined\n )?.map((t) => makeSsrSerovalPlugin(t, trackPlugins)) ?? []\n crossSerializeStream(dehydratedRouter, {\n refs: new Map(),\n plugins: [...plugins, ...defaultSerovalPlugins],\n onSerialize: (data, initial) => {\n let serialized = initial ? GLOBAL_TSR + '.router=' + data : data\n if (trackPlugins.didRun) {\n serialized = GLOBAL_TSR + '.p(()=>' + serialized + ')'\n }\n router.serverSsr!.injectScript(() => serialized)\n },\n scopeId: SCOPE_ID,\n onDone: () => p.resolve(''),\n onError: (err) => p.reject(err),\n })\n // make sure the stream is kept open until the promise is resolved\n router.serverSsr!.injectHtml(() => p)\n },\n isDehydrated() {\n return _dehydrated\n },\n onRenderFinished: (listener) => listeners.push(listener),\n setRenderFinished: () => {\n listeners.forEach((l) => l())\n },\n }\n}\n"],"names":["getCrossReferenceHeader","minifiedTsrBootStrapScript","createControlledPromise","makeSsrSerovalPlugin","crossSerializeStream","defaultSerovalPlugins","GLOBAL_TSR"],"mappings":"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"}
@@ -14,4 +14,7 @@ declare module '../router' {
14
14
  }
15
15
  }
16
16
  export declare function dehydrateMatch(match: AnyRouteMatch): DehydratedMatch;
17
- export declare function attachRouterServerSsrUtils(router: AnyRouter, manifest: Manifest | undefined): void;
17
+ export declare function attachRouterServerSsrUtils({ router, manifest, }: {
18
+ router: AnyRouter;
19
+ manifest: Manifest | undefined;
20
+ }): void;
@@ -12,44 +12,54 @@ function functionalUpdate(updater, previous) {
12
12
  }
13
13
  return updater;
14
14
  }
15
+ const hasOwn = Object.prototype.hasOwnProperty;
15
16
  function replaceEqualDeep(prev, _next) {
16
17
  if (prev === _next) {
17
18
  return prev;
18
19
  }
19
20
  const next = _next;
20
21
  const array = isPlainArray(prev) && isPlainArray(next);
21
- if (array || isSimplePlainObject(prev) && isSimplePlainObject(next)) {
22
- const prevItems = array ? prev : Object.keys(prev).concat(
23
- Object.getOwnPropertySymbols(prev)
24
- );
25
- const prevSize = prevItems.length;
26
- const nextItems = array ? next : Object.keys(next).concat(
27
- Object.getOwnPropertySymbols(next)
28
- );
29
- const nextSize = nextItems.length;
30
- const copy = array ? [] : {};
31
- let equalItems = 0;
32
- for (let i = 0; i < nextSize; i++) {
33
- const key = array ? i : nextItems[i];
34
- if ((!array && prevItems.includes(key) || array) && prev[key] === void 0 && next[key] === void 0) {
35
- copy[key] = void 0;
36
- equalItems++;
37
- } else {
38
- copy[key] = replaceEqualDeep(prev[key], next[key]);
39
- if (copy[key] === prev[key] && prev[key] !== void 0) {
40
- equalItems++;
41
- }
42
- }
22
+ if (!array && !(isPlainObject(prev) && isPlainObject(next))) return next;
23
+ const prevItems = array ? prev : getEnumerableOwnKeys(prev);
24
+ if (!prevItems) return next;
25
+ const nextItems = array ? next : getEnumerableOwnKeys(next);
26
+ if (!nextItems) return next;
27
+ const prevSize = prevItems.length;
28
+ const nextSize = nextItems.length;
29
+ const copy = array ? new Array(nextSize) : {};
30
+ let equalItems = 0;
31
+ for (let i = 0; i < nextSize; i++) {
32
+ const key = array ? i : nextItems[i];
33
+ const p = prev[key];
34
+ const n = next[key];
35
+ if (p === n) {
36
+ copy[key] = p;
37
+ if (array ? i < prevSize : hasOwn.call(prev, key)) equalItems++;
38
+ continue;
39
+ }
40
+ if (p === null || n === null || typeof p !== "object" || typeof n !== "object") {
41
+ copy[key] = n;
42
+ continue;
43
43
  }
44
- return prevSize === nextSize && equalItems === prevSize ? prev : copy;
44
+ const v = replaceEqualDeep(p, n);
45
+ copy[key] = v;
46
+ if (v === p) equalItems++;
45
47
  }
46
- return next;
48
+ return prevSize === nextSize && equalItems === prevSize ? prev : copy;
47
49
  }
48
- function isSimplePlainObject(o) {
49
- return (
50
- // all the checks from isPlainObject are more likely to hit so we perform them first
51
- isPlainObject(o) && Object.getOwnPropertyNames(o).length === Object.keys(o).length
52
- );
50
+ function getEnumerableOwnKeys(o) {
51
+ const keys = [];
52
+ const names = Object.getOwnPropertyNames(o);
53
+ for (const name of names) {
54
+ if (!Object.prototype.propertyIsEnumerable.call(o, name)) return false;
55
+ keys.push(name);
56
+ }
57
+ const symbols = Object.getOwnPropertySymbols(o);
58
+ for (const symbol of symbols) {
59
+ if (!Object.prototype.propertyIsEnumerable.call(o, symbol)) return false;
60
+ keys.push(symbol);
61
+ }
62
+ return keys;
53
63
  }
54
64
  function isPlainObject(o) {
55
65
  if (!hasObjectPrototype(o)) {
@@ -74,13 +84,6 @@ function hasObjectPrototype(o) {
74
84
  function isPlainArray(value) {
75
85
  return Array.isArray(value) && value.length === Object.keys(value).length;
76
86
  }
77
- function getObjectKeys(obj, ignoreUndefined) {
78
- let keys = Object.keys(obj);
79
- if (ignoreUndefined) {
80
- keys = keys.filter((key) => obj[key] !== void 0);
81
- }
82
- return keys;
83
- }
84
87
  function deepEqual(a, b, opts) {
85
88
  if (a === b) {
86
89
  return true;
@@ -88,20 +91,39 @@ function deepEqual(a, b, opts) {
88
91
  if (typeof a !== typeof b) {
89
92
  return false;
90
93
  }
94
+ if (Array.isArray(a) && Array.isArray(b)) {
95
+ if (a.length !== b.length) return false;
96
+ for (let i = 0, l = a.length; i < l; i++) {
97
+ if (!deepEqual(a[i], b[i], opts)) return false;
98
+ }
99
+ return true;
100
+ }
91
101
  if (isPlainObject(a) && isPlainObject(b)) {
92
102
  const ignoreUndefined = opts?.ignoreUndefined ?? true;
93
- const aKeys = getObjectKeys(a, ignoreUndefined);
94
- const bKeys = getObjectKeys(b, ignoreUndefined);
95
- if (!opts?.partial && aKeys.length !== bKeys.length) {
96
- return false;
103
+ if (opts?.partial) {
104
+ for (const k in b) {
105
+ if (!ignoreUndefined || b[k] !== void 0) {
106
+ if (!deepEqual(a[k], b[k], opts)) return false;
107
+ }
108
+ }
109
+ return true;
97
110
  }
98
- return bKeys.every((key) => deepEqual(a[key], b[key], opts));
99
- }
100
- if (Array.isArray(a) && Array.isArray(b)) {
101
- if (a.length !== b.length) {
102
- return false;
111
+ let aCount = 0;
112
+ if (!ignoreUndefined) {
113
+ aCount = Object.keys(a).length;
114
+ } else {
115
+ for (const k in a) {
116
+ if (a[k] !== void 0) aCount++;
117
+ }
118
+ }
119
+ let bCount = 0;
120
+ for (const k in b) {
121
+ if (!ignoreUndefined || b[k] !== void 0) {
122
+ bCount++;
123
+ if (bCount > aCount || !deepEqual(a[k], b[k], opts)) return false;
124
+ }
103
125
  }
104
- return !a.some((item, index) => !deepEqual(item, b[index], opts));
126
+ return aCount === bCount;
105
127
  }
106
128
  return false;
107
129
  }