@tanstack/router-core 1.121.33 → 1.121.39

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 (94) hide show
  1. package/dist/cjs/index.d.cts +1 -1
  2. package/dist/cjs/router.cjs +5 -0
  3. package/dist/cjs/router.cjs.map +1 -1
  4. package/dist/cjs/router.d.cts +2 -2
  5. package/dist/cjs/scroll-restoration.cjs +1 -1
  6. package/dist/cjs/scroll-restoration.cjs.map +1 -1
  7. package/dist/cjs/serializer.cjs +146 -0
  8. package/dist/cjs/serializer.cjs.map +1 -0
  9. package/dist/cjs/serializer.d.cts +7 -1
  10. package/dist/cjs/ssr/client.cjs +12 -0
  11. package/dist/cjs/ssr/client.cjs.map +1 -0
  12. package/dist/cjs/ssr/client.d.cts +6 -0
  13. package/dist/cjs/ssr/createRequestHandler.cjs +50 -0
  14. package/dist/cjs/ssr/createRequestHandler.cjs.map +1 -0
  15. package/dist/cjs/ssr/createRequestHandler.d.cts +9 -0
  16. package/dist/cjs/ssr/handlerCallback.cjs +7 -0
  17. package/dist/cjs/ssr/handlerCallback.cjs.map +1 -0
  18. package/dist/cjs/ssr/handlerCallback.d.cts +9 -0
  19. package/dist/cjs/ssr/headers.cjs +39 -0
  20. package/dist/cjs/ssr/headers.cjs.map +1 -0
  21. package/dist/cjs/ssr/headers.d.cts +5 -0
  22. package/dist/cjs/ssr/json.cjs +14 -0
  23. package/dist/cjs/ssr/json.cjs.map +1 -0
  24. package/dist/cjs/ssr/json.d.cts +4 -0
  25. package/dist/cjs/ssr/server.cjs +17 -0
  26. package/dist/cjs/ssr/server.cjs.map +1 -0
  27. package/dist/cjs/ssr/server.d.cts +8 -0
  28. package/dist/cjs/ssr/ssr-client.cjs +131 -0
  29. package/dist/cjs/ssr/ssr-client.cjs.map +1 -0
  30. package/dist/cjs/ssr/ssr-client.d.cts +68 -0
  31. package/dist/cjs/ssr/ssr-server.cjs +248 -0
  32. package/dist/cjs/ssr/ssr-server.cjs.map +1 -0
  33. package/dist/cjs/ssr/ssr-server.d.cts +32 -0
  34. package/dist/cjs/ssr/transformStreamWithRouter.cjs +183 -0
  35. package/dist/cjs/ssr/transformStreamWithRouter.cjs.map +1 -0
  36. package/dist/cjs/ssr/transformStreamWithRouter.d.cts +6 -0
  37. package/dist/cjs/ssr/tsrScript.cjs +4 -0
  38. package/dist/cjs/ssr/tsrScript.cjs.map +1 -0
  39. package/dist/cjs/ssr/tsrScript.d.cts +1 -0
  40. package/dist/esm/index.d.ts +1 -1
  41. package/dist/esm/router.d.ts +2 -2
  42. package/dist/esm/router.js +5 -0
  43. package/dist/esm/router.js.map +1 -1
  44. package/dist/esm/scroll-restoration.js +1 -1
  45. package/dist/esm/scroll-restoration.js.map +1 -1
  46. package/dist/esm/serializer.d.ts +7 -1
  47. package/dist/esm/serializer.js +146 -0
  48. package/dist/esm/serializer.js.map +1 -0
  49. package/dist/esm/ssr/client.d.ts +6 -0
  50. package/dist/esm/ssr/client.js +12 -0
  51. package/dist/esm/ssr/client.js.map +1 -0
  52. package/dist/esm/ssr/createRequestHandler.d.ts +9 -0
  53. package/dist/esm/ssr/createRequestHandler.js +50 -0
  54. package/dist/esm/ssr/createRequestHandler.js.map +1 -0
  55. package/dist/esm/ssr/handlerCallback.d.ts +9 -0
  56. package/dist/esm/ssr/handlerCallback.js +7 -0
  57. package/dist/esm/ssr/handlerCallback.js.map +1 -0
  58. package/dist/esm/ssr/headers.d.ts +5 -0
  59. package/dist/esm/ssr/headers.js +39 -0
  60. package/dist/esm/ssr/headers.js.map +1 -0
  61. package/dist/esm/ssr/json.d.ts +4 -0
  62. package/dist/esm/ssr/json.js +14 -0
  63. package/dist/esm/ssr/json.js.map +1 -0
  64. package/dist/esm/ssr/server.d.ts +8 -0
  65. package/dist/esm/ssr/server.js +17 -0
  66. package/dist/esm/ssr/server.js.map +1 -0
  67. package/dist/esm/ssr/ssr-client.d.ts +68 -0
  68. package/dist/esm/ssr/ssr-client.js +131 -0
  69. package/dist/esm/ssr/ssr-client.js.map +1 -0
  70. package/dist/esm/ssr/ssr-server.d.ts +32 -0
  71. package/dist/esm/ssr/ssr-server.js +248 -0
  72. package/dist/esm/ssr/ssr-server.js.map +1 -0
  73. package/dist/esm/ssr/transformStreamWithRouter.d.ts +6 -0
  74. package/dist/esm/ssr/transformStreamWithRouter.js +183 -0
  75. package/dist/esm/ssr/transformStreamWithRouter.js.map +1 -0
  76. package/dist/esm/ssr/tsrScript.d.ts +1 -0
  77. package/dist/esm/ssr/tsrScript.js +5 -0
  78. package/dist/esm/ssr/tsrScript.js.map +1 -0
  79. package/package.json +29 -2
  80. package/src/index.ts +1 -0
  81. package/src/router.ts +8 -5
  82. package/src/scroll-restoration.ts +1 -1
  83. package/src/serializer.ts +174 -1
  84. package/src/ssr/client.ts +15 -0
  85. package/src/ssr/createRequestHandler.ts +74 -0
  86. package/src/ssr/handlerCallback.ts +15 -0
  87. package/src/ssr/headers.ts +51 -0
  88. package/src/ssr/json.ts +18 -0
  89. package/src/ssr/server.ts +23 -0
  90. package/src/ssr/ssr-client.ts +244 -0
  91. package/src/ssr/ssr-server.ts +345 -0
  92. package/src/ssr/transformStreamWithRouter.ts +258 -0
  93. package/src/ssr/tsrScript.ts +91 -0
  94. package/src/vite-env.d.ts +4 -0
@@ -0,0 +1 @@
1
+ {"version":3,"file":"transformStreamWithRouter.js","sources":["../../../src/ssr/transformStreamWithRouter.ts"],"sourcesContent":["import { ReadableStream } from 'node:stream/web'\nimport { Readable } from 'node:stream'\nimport { createControlledPromise } from '../utils'\nimport type { AnyRouter } from '../router'\n\nexport function transformReadableStreamWithRouter(\n router: AnyRouter,\n routerStream: ReadableStream,\n) {\n return transformStreamWithRouter(router, routerStream)\n}\n\nexport function transformPipeableStreamWithRouter(\n router: AnyRouter,\n routerStream: Readable,\n) {\n return Readable.fromWeb(\n transformStreamWithRouter(router, Readable.toWeb(routerStream)),\n )\n}\n\n// regex pattern for matching closing body and html tags\nconst patternBodyStart = /(<body)/\nconst patternBodyEnd = /(<\\/body>)/\nconst patternHtmlEnd = /(<\\/html>)/\nconst patternHeadStart = /(<head.*?>)/\n// regex pattern for matching closing tags\nconst patternClosingTag = /(<\\/[a-zA-Z][\\w:.-]*?>)/g\n\nconst textDecoder = new TextDecoder()\n\ntype ReadablePassthrough = {\n stream: ReadableStream\n write: (chunk: string) => void\n end: (chunk?: string) => void\n destroy: (error: unknown) => void\n destroyed: boolean\n}\n\nfunction createPassthrough() {\n let controller: ReadableStreamDefaultController<any>\n const encoder = new TextEncoder()\n const stream = new ReadableStream({\n start(c) {\n controller = c\n },\n })\n\n const res: ReadablePassthrough = {\n stream,\n write: (chunk) => {\n controller.enqueue(encoder.encode(chunk))\n },\n end: (chunk) => {\n if (chunk) {\n controller.enqueue(encoder.encode(chunk))\n }\n controller.close()\n res.destroyed = true\n },\n destroy: (error) => {\n controller.error(error)\n },\n destroyed: false,\n }\n\n return res\n}\n\nasync function readStream(\n stream: ReadableStream,\n opts: {\n onData?: (chunk: ReadableStreamReadValueResult<any>) => void\n onEnd?: () => void\n onError?: (error: unknown) => void\n },\n) {\n try {\n const reader = stream.getReader()\n let chunk\n while (!(chunk = await reader.read()).done) {\n opts.onData?.(chunk)\n }\n opts.onEnd?.()\n } catch (error) {\n opts.onError?.(error)\n }\n}\n\nexport function transformStreamWithRouter(\n router: AnyRouter,\n appStream: ReadableStream,\n) {\n const finalPassThrough = createPassthrough()\n\n let isAppRendering = true as boolean\n let routerStreamBuffer = ''\n let pendingClosingTags = ''\n let bodyStarted = false as boolean\n let headStarted = false as boolean\n let leftover = ''\n let leftoverHtml = ''\n\n function getBufferedRouterStream() {\n const html = routerStreamBuffer\n routerStreamBuffer = ''\n return html\n }\n\n function decodeChunk(chunk: unknown): string {\n if (chunk instanceof Uint8Array) {\n return textDecoder.decode(chunk)\n }\n return String(chunk)\n }\n\n const injectedHtmlDonePromise = createControlledPromise<void>()\n\n let processingCount = 0\n\n // Process any already-injected HTML\n router.serverSsr!.injectedHtml.forEach((promise) => {\n handleInjectedHtml(promise)\n })\n\n // Listen for any new injected HTML\n const stopListeningToInjectedHtml = router.subscribe(\n 'onInjectedHtml',\n (e) => {\n handleInjectedHtml(e.promise)\n },\n )\n\n function handleInjectedHtml(promise: Promise<string>) {\n processingCount++\n\n promise\n .then((html) => {\n if (!bodyStarted) {\n routerStreamBuffer += html\n } else {\n finalPassThrough.write(html)\n }\n })\n .catch(injectedHtmlDonePromise.reject)\n .finally(() => {\n processingCount--\n\n if (!isAppRendering && processingCount === 0) {\n stopListeningToInjectedHtml()\n injectedHtmlDonePromise.resolve()\n }\n })\n }\n\n injectedHtmlDonePromise\n .then(() => {\n const finalHtml =\n leftoverHtml + getBufferedRouterStream() + pendingClosingTags\n\n finalPassThrough.end(finalHtml)\n })\n .catch((err) => {\n console.error('Error reading routerStream:', err)\n finalPassThrough.destroy(err)\n })\n\n // Transform the appStream\n readStream(appStream, {\n onData: (chunk) => {\n const text = decodeChunk(chunk.value)\n\n let chunkString = leftover + text\n const bodyEndMatch = chunkString.match(patternBodyEnd)\n const htmlEndMatch = chunkString.match(patternHtmlEnd)\n\n if (!bodyStarted) {\n const bodyStartMatch = chunkString.match(patternBodyStart)\n if (bodyStartMatch) {\n bodyStarted = true\n }\n }\n\n if (!headStarted) {\n const headStartMatch = chunkString.match(patternHeadStart)\n if (headStartMatch) {\n headStarted = true\n const index = headStartMatch.index!\n const headTag = headStartMatch[0]\n const remaining = chunkString.slice(index + headTag.length)\n finalPassThrough.write(\n chunkString.slice(0, index) + headTag + getBufferedRouterStream(),\n )\n // make sure to only write `remaining` until the next closing tag\n chunkString = remaining\n }\n }\n\n if (!bodyStarted) {\n finalPassThrough.write(chunkString)\n leftover = ''\n return\n }\n\n // If either the body end or html end is in the chunk,\n // We need to get all of our data in asap\n if (\n bodyEndMatch &&\n htmlEndMatch &&\n bodyEndMatch.index! < htmlEndMatch.index!\n ) {\n const bodyEndIndex = bodyEndMatch.index!\n pendingClosingTags = chunkString.slice(bodyEndIndex)\n\n finalPassThrough.write(\n chunkString.slice(0, bodyEndIndex) + getBufferedRouterStream(),\n )\n\n leftover = ''\n return\n }\n\n let result: RegExpExecArray | null\n let lastIndex = 0\n while ((result = patternClosingTag.exec(chunkString)) !== null) {\n lastIndex = result.index + result[0].length\n }\n\n if (lastIndex > 0) {\n const processed =\n chunkString.slice(0, lastIndex) +\n getBufferedRouterStream() +\n leftoverHtml\n\n finalPassThrough.write(processed)\n leftover = chunkString.slice(lastIndex)\n } else {\n leftover = chunkString\n leftoverHtml += getBufferedRouterStream()\n }\n },\n onEnd: () => {\n // Mark the app as done rendering\n isAppRendering = false\n\n // If there are no pending promises, resolve the injectedHtmlDonePromise\n if (processingCount === 0) {\n injectedHtmlDonePromise.resolve()\n }\n },\n onError: (error) => {\n console.error('Error reading appStream:', error)\n finalPassThrough.destroy(error)\n },\n })\n\n return finalPassThrough.stream\n}\n"],"names":[],"mappings":"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"}
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,5 @@
1
+ const minifiedTsrBootStrapScript = 'const __TSR_SSR__={matches:[],streamedValues:{},initMatch:o=>(__TSR_SSR__.matches.push(o),o.extracted?.forEach(l=>{if(l.type==="stream"){let r;l.value=new ReadableStream({start(e){r={enqueue:t=>{try{e.enqueue(t)}catch{}},close:()=>{try{e.close()}catch{}}}}}),l.value.controller=r}else{let r,e;l.value=new Promise((t,n)=>{e=n,r=t}),l.value.reject=e,l.value.resolve=r}}),!0),resolvePromise:({matchId:o,id:l,promiseState:r})=>{const e=__TSR_SSR__.matches.find(t=>t.id===o);if(e){const t=e.extracted?.[l];if(t&&t.type==="promise"&&t.value&&r.status==="success")return t.value.resolve(r.data),!0}return!1},injectChunk:({matchId:o,id:l,chunk:r})=>{const e=__TSR_SSR__.matches.find(t=>t.id===o);if(e){const t=e.extracted?.[l];if(t&&t.type==="stream"&&t.value?.controller)return t.value.controller.enqueue(new TextEncoder().encode(r.toString())),!0}return!1},closeStream:({matchId:o,id:l})=>{const r=__TSR_SSR__.matches.find(e=>e.id===o);if(r){const e=r.extracted?.[l];if(e&&e.type==="stream"&&e.value?.controller)return e.value.controller.close(),!0}return!1},cleanScripts:()=>{document.querySelectorAll(".tsr-once").forEach(o=>{o.remove()})}};window.__TSR_SSR__=__TSR_SSR__;\n';
2
+ export {
3
+ minifiedTsrBootStrapScript as default
4
+ };
5
+ //# sourceMappingURL=tsrScript.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tsrScript.js","sources":["../../../src/ssr/tsrScript.ts?script-string"],"sourcesContent":["import type { ControllablePromise } from '../router'\nimport type { TsrSsrGlobal } from './ssr-client'\n\nconst __TSR_SSR__: TsrSsrGlobal = {\n matches: [],\n streamedValues: {},\n initMatch: (match) => {\n __TSR_SSR__.matches.push(match)\n\n match.extracted?.forEach((ex) => {\n if (ex.type === 'stream') {\n let controller\n ex.value = new ReadableStream({\n start(c) {\n controller = {\n enqueue: (chunk: unknown) => {\n try {\n c.enqueue(chunk)\n } catch {}\n },\n close: () => {\n try {\n c.close()\n } catch {}\n },\n }\n },\n })\n ex.value.controller = controller\n } else {\n let resolve: ControllablePromise['reject'] | undefined\n let reject: ControllablePromise['reject'] | undefined\n\n ex.value = new Promise((_resolve, _reject) => {\n reject = _reject\n resolve = _resolve\n }) as ControllablePromise\n ex.value.reject = reject!\n ex.value.resolve = resolve!\n }\n })\n\n return true\n },\n resolvePromise: ({ matchId, id, promiseState }) => {\n const match = __TSR_SSR__.matches.find((m) => m.id === matchId)\n if (match) {\n const ex = match.extracted?.[id]\n if (\n ex &&\n ex.type === 'promise' &&\n ex.value &&\n promiseState.status === 'success'\n ) {\n ex.value.resolve(promiseState.data)\n return true\n }\n }\n return false\n },\n injectChunk: ({ matchId, id, chunk }) => {\n const match = __TSR_SSR__.matches.find((m) => m.id === matchId)\n\n if (match) {\n const ex = match.extracted?.[id]\n if (ex && ex.type === 'stream' && ex.value?.controller) {\n ex.value.controller.enqueue(new TextEncoder().encode(chunk.toString()))\n return true\n }\n }\n return false\n },\n closeStream: ({ matchId, id }) => {\n const match = __TSR_SSR__.matches.find((m) => m.id === matchId)\n if (match) {\n const ex = match.extracted?.[id]\n if (ex && ex.type === 'stream' && ex.value?.controller) {\n ex.value.controller.close()\n return true\n }\n }\n return false\n },\n cleanScripts: () => {\n document.querySelectorAll('.tsr-once').forEach((el) => {\n el.remove()\n })\n },\n}\n\nwindow.__TSR_SSR__ = __TSR_SSR__\n"],"names":[],"mappings":"AAAA,MAAA,6BAAe;"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tanstack/router-core",
3
- "version": "1.121.33",
3
+ "version": "1.121.39",
4
4
  "description": "Modern and scalable routing for React applications",
5
5
  "author": "Tanner Linsley",
6
6
  "license": "MIT",
@@ -33,6 +33,26 @@
33
33
  "default": "./dist/cjs/index.cjs"
34
34
  }
35
35
  },
36
+ "./ssr/server": {
37
+ "import": {
38
+ "types": "./dist/esm/ssr/server.d.ts",
39
+ "default": "./dist/esm/ssr/server.js"
40
+ },
41
+ "require": {
42
+ "types": "./dist/cjs/ssr/server.d.cts",
43
+ "default": "./dist/cjs/ssr/server.cjs"
44
+ }
45
+ },
46
+ "./ssr/client": {
47
+ "import": {
48
+ "types": "./dist/esm/ssr/client.d.ts",
49
+ "default": "./dist/esm/ssr/client.js"
50
+ },
51
+ "require": {
52
+ "types": "./dist/cjs/ssr/client.d.cts",
53
+ "default": "./dist/cjs/ssr/client.cjs"
54
+ }
55
+ },
36
56
  "./package.json": "./package.json"
37
57
  },
38
58
  "sideEffects": false,
@@ -45,8 +65,15 @@
45
65
  },
46
66
  "dependencies": {
47
67
  "@tanstack/store": "^0.7.0",
68
+ "cookie-es": "^1.2.2",
69
+ "jsesc": "^3.1.0",
48
70
  "tiny-invariant": "^1.3.3",
49
- "@tanstack/history": "1.121.21"
71
+ "tiny-warning": "^1.0.3",
72
+ "@tanstack/history": "1.121.34"
73
+ },
74
+ "devDependencies": {
75
+ "@types/jsesc": "^3.0.3",
76
+ "esbuild": "^0.25.0"
50
77
  },
51
78
  "scripts": {}
52
79
  }
package/src/index.ts CHANGED
@@ -66,6 +66,7 @@ export type {
66
66
  } from './fileRoute'
67
67
 
68
68
  export type {
69
+ TsrSerializer,
69
70
  StartSerializer,
70
71
  Serializable,
71
72
  SerializerParse,
package/src/router.ts CHANGED
@@ -77,7 +77,7 @@ import type {
77
77
  NavigateFn,
78
78
  } from './RouterProvider'
79
79
  import type { Manifest } from './manifest'
80
- import type { StartSerializer } from './serializer'
80
+ import type { TsrSerializer } from './serializer'
81
81
  import type { AnySchema, AnyValidator } from './validators'
82
82
  import type { NavigateOptions, ResolveRelativePath, ToOptions } from './link'
83
83
  import type { NotFoundError } from './not-found'
@@ -1016,7 +1016,8 @@ export class RouterCore<
1016
1016
  if (__tempLocation && (!__tempKey || __tempKey === this.tempLocationKey)) {
1017
1017
  // Sync up the location keys
1018
1018
  const parsedTempLocation = parse(__tempLocation) as any
1019
- parsedTempLocation.state.key = location.state.key
1019
+ parsedTempLocation.state.key = location.state.key // TODO: Remove in v2 - use __TSR_key instead
1020
+ parsedTempLocation.state.__TSR_key = location.state.__TSR_key
1020
1021
 
1021
1022
  delete parsedTempLocation.state.__tempLocation
1022
1023
 
@@ -1616,7 +1617,8 @@ export class RouterCore<
1616
1617
  // temporarily add the previous values to the next state so they don't affect
1617
1618
  // the comparison
1618
1619
  const ignoredProps = [
1619
- 'key',
1620
+ 'key', // TODO: Remove in v2 - use __TSR_key instead
1621
+ '__TSR_key',
1620
1622
  '__TSR_index',
1621
1623
  '__hashScrollIntoViewOptions',
1622
1624
  ] as const
@@ -1657,7 +1659,8 @@ export class RouterCore<
1657
1659
  ...nextHistory.state,
1658
1660
  __tempKey: undefined!,
1659
1661
  __tempLocation: undefined!,
1660
- key: undefined!,
1662
+ __TSR_key: undefined!,
1663
+ key: undefined!, // TODO: Remove in v2 - use __TSR_key instead
1661
1664
  },
1662
1665
  },
1663
1666
  },
@@ -2854,7 +2857,7 @@ export class RouterCore<
2854
2857
 
2855
2858
  ssr?: {
2856
2859
  manifest: Manifest | undefined
2857
- serializer: StartSerializer
2860
+ serializer: TsrSerializer
2858
2861
  }
2859
2862
 
2860
2863
  serverSsr?: {
@@ -79,7 +79,7 @@ export const scrollRestorationCache = createScrollRestorationCache()
79
79
  */
80
80
 
81
81
  export const defaultGetScrollRestorationKey = (location: ParsedLocation) => {
82
- return location.state.key! || location.href
82
+ return location.state.__TSR_key! || location.href
83
83
  }
84
84
 
85
85
  export function getCssSelector(el: any): string {
package/src/serializer.ts CHANGED
@@ -1,10 +1,17 @@
1
- export interface StartSerializer {
1
+ import { isPlainObject } from './utils'
2
+
3
+ export interface TsrSerializer {
2
4
  stringify: (obj: unknown) => string
3
5
  parse: (str: string) => unknown
4
6
  encode: <T>(value: T) => T
5
7
  decode: <T>(value: T) => T
6
8
  }
7
9
 
10
+ /**
11
+ * @deprecated This is re-export of TsrSerializer which is the generic Router serializer interface. Going forward StartSerializer will be used specifically as a Tanstack Start serializer interface.
12
+ */
13
+ export interface StartSerializer extends TsrSerializer {}
14
+
8
15
  export type SerializerStringifyBy<T, TSerializable> = T extends TSerializable
9
16
  ? T
10
17
  : T extends (...args: Array<any>) => any
@@ -30,3 +37,169 @@ export type Serializable = Date | undefined | Error | FormData | bigint
30
37
  export type SerializerStringify<T> = SerializerStringifyBy<T, Serializable>
31
38
 
32
39
  export type SerializerParse<T> = SerializerParseBy<T, Serializable>
40
+ export const tsrSerializer: TsrSerializer = {
41
+ stringify: (value: any) =>
42
+ JSON.stringify(value, function replacer(key, val) {
43
+ const ogVal = this[key]
44
+ const serializer = serializers.find((t) => t.stringifyCondition(ogVal))
45
+
46
+ if (serializer) {
47
+ return serializer.stringify(ogVal)
48
+ }
49
+
50
+ return val
51
+ }),
52
+ parse: (value: string) =>
53
+ JSON.parse(value, function parser(key, val) {
54
+ const ogVal = this[key]
55
+ if (isPlainObject(ogVal)) {
56
+ const serializer = serializers.find((t) => t.parseCondition(ogVal))
57
+
58
+ if (serializer) {
59
+ return serializer.parse(ogVal)
60
+ }
61
+ }
62
+
63
+ return val
64
+ }),
65
+ encode: (value: any) => {
66
+ // When encoding, dive first
67
+ if (Array.isArray(value)) {
68
+ return value.map((v) => tsrSerializer.encode(v))
69
+ }
70
+
71
+ if (isPlainObject(value)) {
72
+ return Object.fromEntries(
73
+ Object.entries(value).map(([key, v]) => [key, tsrSerializer.encode(v)]),
74
+ )
75
+ }
76
+
77
+ const serializer = serializers.find((t) => t.stringifyCondition(value))
78
+ if (serializer) {
79
+ return serializer.stringify(value)
80
+ }
81
+
82
+ return value
83
+ },
84
+ decode: (value: any) => {
85
+ // Attempt transform first
86
+ if (isPlainObject(value)) {
87
+ const serializer = serializers.find((t) => t.parseCondition(value))
88
+ if (serializer) {
89
+ return serializer.parse(value)
90
+ }
91
+ }
92
+
93
+ if (Array.isArray(value)) {
94
+ return value.map((v) => tsrSerializer.decode(v))
95
+ }
96
+
97
+ if (isPlainObject(value)) {
98
+ return Object.fromEntries(
99
+ Object.entries(value).map(([key, v]) => [key, tsrSerializer.decode(v)]),
100
+ )
101
+ }
102
+
103
+ return value
104
+ },
105
+ }
106
+ const createSerializer = <TKey extends string, TInput, TSerialized>(
107
+ key: TKey,
108
+ check: (value: any) => value is TInput,
109
+ toValue: (value: TInput) => TSerialized,
110
+ fromValue: (value: TSerialized) => TInput,
111
+ ) => ({
112
+ key,
113
+ stringifyCondition: check,
114
+ stringify: (value: any) => ({ [`$${key}`]: toValue(value) }),
115
+ parseCondition: (value: any) => Object.hasOwn(value, `$${key}`),
116
+ parse: (value: any) => fromValue(value[`$${key}`]),
117
+ })
118
+ // Keep these ordered by predicted frequency
119
+ // Make sure to keep DefaultSerializable in sync with these serializers
120
+ // Also, make sure that they are unit tested in serializer.test.tsx
121
+ const serializers = [
122
+ createSerializer(
123
+ // Key
124
+ 'undefined',
125
+ // Check
126
+ (v): v is undefined => v === undefined,
127
+ // To
128
+ () => 0,
129
+ // From
130
+ () => undefined,
131
+ ),
132
+ createSerializer(
133
+ // Key
134
+ 'date',
135
+ // Check
136
+ (v): v is Date => v instanceof Date,
137
+ // To
138
+ (v) => v.toISOString(),
139
+ // From
140
+ (v) => new Date(v),
141
+ ),
142
+ createSerializer(
143
+ // Key
144
+ 'error',
145
+ // Check
146
+ (v): v is Error => v instanceof Error,
147
+ // To
148
+ (v) => ({
149
+ ...v,
150
+ message: v.message,
151
+ stack: process.env.NODE_ENV === 'development' ? v.stack : undefined,
152
+ cause: v.cause,
153
+ }),
154
+ // From
155
+ (v) => Object.assign(new Error(v.message), v),
156
+ ),
157
+ createSerializer(
158
+ // Key
159
+ 'formData',
160
+ // Check
161
+ (v): v is FormData => v instanceof FormData,
162
+ // To
163
+ (v) => {
164
+ const entries: Record<
165
+ string,
166
+ Array<FormDataEntryValue> | FormDataEntryValue
167
+ > = {}
168
+ v.forEach((value, key) => {
169
+ const entry = entries[key]
170
+ if (entry !== undefined) {
171
+ if (Array.isArray(entry)) {
172
+ entry.push(value)
173
+ } else {
174
+ entries[key] = [entry, value]
175
+ }
176
+ } else {
177
+ entries[key] = value
178
+ }
179
+ })
180
+ return entries
181
+ },
182
+ // From
183
+ (v) => {
184
+ const formData = new FormData()
185
+ Object.entries(v).forEach(([key, value]) => {
186
+ if (Array.isArray(value)) {
187
+ value.forEach((val) => formData.append(key, val))
188
+ } else {
189
+ formData.append(key, value)
190
+ }
191
+ })
192
+ return formData
193
+ },
194
+ ),
195
+ createSerializer(
196
+ // Key
197
+ 'bigint',
198
+ // Check
199
+ (v): v is bigint => typeof v === 'bigint',
200
+ // To
201
+ (v) => v.toString(),
202
+ // From
203
+ (v) => BigInt(v),
204
+ ),
205
+ ] as const
@@ -0,0 +1,15 @@
1
+ export { mergeHeaders, headersInitToObject } from './headers'
2
+ export { json } from './json'
3
+ export type { JsonResponse } from './json'
4
+ export { hydrate } from './ssr-client'
5
+ export type {
6
+ DehydratedRouter,
7
+ ClientExtractedBaseEntry,
8
+ TsrSsrGlobal,
9
+ ClientExtractedEntry,
10
+ SsrMatch,
11
+ ClientExtractedPromise,
12
+ ClientExtractedStream,
13
+ ResolvePromiseState,
14
+ } from './ssr-client'
15
+ export { tsrSerializer } from '../serializer'
@@ -0,0 +1,74 @@
1
+ import { createMemoryHistory } from '@tanstack/history'
2
+ import { mergeHeaders } from './headers'
3
+ import { attachRouterServerSsrUtils, dehydrateRouter } from './ssr-server'
4
+ import type { HandlerCallback } from './handlerCallback'
5
+ import type { AnyRouter } from '../router'
6
+ import type { Manifest } from '../manifest'
7
+
8
+ export type RequestHandler<TRouter extends AnyRouter> = (
9
+ cb: HandlerCallback<TRouter>,
10
+ ) => Promise<Response>
11
+
12
+ export function createRequestHandler<TRouter extends AnyRouter>({
13
+ createRouter,
14
+ request,
15
+ getRouterManifest,
16
+ }: {
17
+ createRouter: () => TRouter
18
+ request: Request
19
+ getRouterManifest?: () => Manifest | Promise<Manifest>
20
+ }): RequestHandler<TRouter> {
21
+ return async (cb) => {
22
+ const router = createRouter()
23
+
24
+ attachRouterServerSsrUtils(router, await getRouterManifest?.())
25
+
26
+ const url = new URL(request.url, 'http://localhost')
27
+
28
+ const href = url.href.replace(url.origin, '')
29
+
30
+ // Create a history for the router
31
+ const history = createMemoryHistory({
32
+ initialEntries: [href],
33
+ })
34
+
35
+ // Update the router with the history and context
36
+ router.update({
37
+ history,
38
+ })
39
+
40
+ await router.load()
41
+
42
+ dehydrateRouter(router)
43
+
44
+ const responseHeaders = getRequestHeaders({
45
+ router,
46
+ })
47
+
48
+ return cb({
49
+ request,
50
+ router,
51
+ responseHeaders,
52
+ } as any)
53
+ }
54
+ }
55
+
56
+ function getRequestHeaders(opts: { router: AnyRouter }): Headers {
57
+ let headers = mergeHeaders(
58
+ {
59
+ 'Content-Type': 'text/html; charset=UTF-8',
60
+ },
61
+ ...opts.router.state.matches.map((match) => {
62
+ return match.headers
63
+ }),
64
+ )
65
+
66
+ // Handle Redirects
67
+ const { redirect } = opts.router.state
68
+
69
+ if (redirect) {
70
+ headers = mergeHeaders(headers, redirect.headers)
71
+ }
72
+
73
+ return headers
74
+ }
@@ -0,0 +1,15 @@
1
+ import type { AnyRouter } from '../router'
2
+
3
+ export interface HandlerCallback<TRouter extends AnyRouter> {
4
+ (ctx: {
5
+ request: Request
6
+ router: TRouter
7
+ responseHeaders: Headers
8
+ }): Response | Promise<Response>
9
+ }
10
+
11
+ export function defineHandlerCallback<TRouter extends AnyRouter>(
12
+ handler: HandlerCallback<TRouter>,
13
+ ): HandlerCallback<TRouter> {
14
+ return handler
15
+ }
@@ -0,0 +1,51 @@
1
+ import { splitSetCookieString } from 'cookie-es'
2
+ import type { OutgoingHttpHeaders } from 'node:http2'
3
+
4
+ // A utility function to turn HeadersInit into an object
5
+ export function headersInitToObject(
6
+ headers: HeadersInit,
7
+ ): Record<keyof OutgoingHttpHeaders, string> {
8
+ const obj: Record<keyof OutgoingHttpHeaders, string> = {}
9
+ const headersInstance = new Headers(headers)
10
+ for (const [key, value] of headersInstance.entries()) {
11
+ obj[key] = value
12
+ }
13
+ return obj
14
+ }
15
+
16
+ type AnyHeaders =
17
+ | Headers
18
+ | HeadersInit
19
+ | Record<string, string>
20
+ | Array<[string, string]>
21
+ | OutgoingHttpHeaders
22
+ | undefined
23
+
24
+ // Helper function to convert various HeaderInit types to a Headers instance
25
+ function toHeadersInstance(init: AnyHeaders) {
26
+ if (init instanceof Headers) {
27
+ return new Headers(init)
28
+ } else if (Array.isArray(init)) {
29
+ return new Headers(init)
30
+ } else if (typeof init === 'object') {
31
+ return new Headers(init as HeadersInit)
32
+ } else {
33
+ return new Headers()
34
+ }
35
+ }
36
+
37
+ // Function to merge headers with proper overrides
38
+ export function mergeHeaders(...headers: Array<AnyHeaders>) {
39
+ return headers.reduce((acc: Headers, header) => {
40
+ const headersInstance = toHeadersInstance(header)
41
+ for (const [key, value] of headersInstance.entries()) {
42
+ if (key === 'set-cookie') {
43
+ const splitCookies = splitSetCookieString(value)
44
+ splitCookies.forEach((cookie) => acc.append('set-cookie', cookie))
45
+ } else {
46
+ acc.set(key, value)
47
+ }
48
+ }
49
+ return acc
50
+ }, new Headers())
51
+ }
@@ -0,0 +1,18 @@
1
+ import { mergeHeaders } from './headers'
2
+
3
+ export interface JsonResponse<TData> extends Response {
4
+ json: () => Promise<TData>
5
+ }
6
+
7
+ export function json<TData>(
8
+ payload: TData,
9
+ init?: ResponseInit,
10
+ ): JsonResponse<TData> {
11
+ return new Response(JSON.stringify(payload), {
12
+ ...init,
13
+ headers: mergeHeaders(
14
+ { 'content-type': 'application/json' },
15
+ init?.headers,
16
+ ),
17
+ })
18
+ }
@@ -0,0 +1,23 @@
1
+ export { createRequestHandler } from './createRequestHandler'
2
+ export type { RequestHandler } from './createRequestHandler'
3
+ export { defineHandlerCallback } from './handlerCallback'
4
+ export type { HandlerCallback } from './handlerCallback'
5
+ export {
6
+ transformPipeableStreamWithRouter,
7
+ transformStreamWithRouter,
8
+ transformReadableStreamWithRouter,
9
+ } from './transformStreamWithRouter'
10
+ export {
11
+ attachRouterServerSsrUtils,
12
+ dehydrateRouter,
13
+ extractAsyncLoaderData,
14
+ onMatchSettled,
15
+ replaceBy,
16
+ } from './ssr-server'
17
+ export type {
18
+ ServerExtractedBaseEntry,
19
+ ServerExtractedEntry,
20
+ ServerExtractedPromise,
21
+ ServerExtractedStream,
22
+ } from './ssr-server'
23
+ export * from './tsrScript'