@tanstack/react-start-client 1.111.10

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 (129) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +33 -0
  3. package/dist/cjs/Meta.cjs +14 -0
  4. package/dist/cjs/Meta.cjs.map +1 -0
  5. package/dist/cjs/Meta.d.cts +1 -0
  6. package/dist/cjs/Scripts.cjs +12 -0
  7. package/dist/cjs/Scripts.cjs.map +1 -0
  8. package/dist/cjs/Scripts.d.cts +1 -0
  9. package/dist/cjs/StartClient.cjs +24 -0
  10. package/dist/cjs/StartClient.cjs.map +1 -0
  11. package/dist/cjs/StartClient.d.cts +4 -0
  12. package/dist/cjs/createIsomorphicFn.cjs +7 -0
  13. package/dist/cjs/createIsomorphicFn.cjs.map +1 -0
  14. package/dist/cjs/createIsomorphicFn.d.cts +12 -0
  15. package/dist/cjs/createMiddleware.cjs +34 -0
  16. package/dist/cjs/createMiddleware.cjs.map +1 -0
  17. package/dist/cjs/createMiddleware.d.cts +129 -0
  18. package/dist/cjs/createServerFn.cjs +369 -0
  19. package/dist/cjs/createServerFn.cjs.map +1 -0
  20. package/dist/cjs/createServerFn.d.cts +137 -0
  21. package/dist/cjs/envOnly.cjs +7 -0
  22. package/dist/cjs/envOnly.cjs.map +1 -0
  23. package/dist/cjs/envOnly.d.cts +4 -0
  24. package/dist/cjs/headers.cjs +30 -0
  25. package/dist/cjs/headers.cjs.map +1 -0
  26. package/dist/cjs/headers.d.cts +5 -0
  27. package/dist/cjs/index.cjs +31 -0
  28. package/dist/cjs/index.cjs.map +1 -0
  29. package/dist/cjs/index.d.cts +14 -0
  30. package/dist/cjs/json.cjs +14 -0
  31. package/dist/cjs/json.cjs.map +1 -0
  32. package/dist/cjs/json.d.cts +2 -0
  33. package/dist/cjs/registerGlobalMiddleware.cjs +9 -0
  34. package/dist/cjs/registerGlobalMiddleware.cjs.map +1 -0
  35. package/dist/cjs/registerGlobalMiddleware.d.cts +5 -0
  36. package/dist/cjs/renderRSC.cjs +29 -0
  37. package/dist/cjs/renderRSC.cjs.map +1 -0
  38. package/dist/cjs/renderRSC.d.cts +2 -0
  39. package/dist/cjs/routesManifest.d.cts +0 -0
  40. package/dist/cjs/serializer.cjs +152 -0
  41. package/dist/cjs/serializer.cjs.map +1 -0
  42. package/dist/cjs/serializer.d.cts +2 -0
  43. package/dist/cjs/ssr-client.cjs +114 -0
  44. package/dist/cjs/ssr-client.cjs.map +1 -0
  45. package/dist/cjs/ssr-client.d.cts +65 -0
  46. package/dist/cjs/tests/createIsomorphicFn.test-d.d.cts +1 -0
  47. package/dist/cjs/tests/createServerFn.test-d.d.cts +1 -0
  48. package/dist/cjs/tests/createServerMiddleware.test-d.d.cts +1 -0
  49. package/dist/cjs/tests/envOnly.test-d.d.cts +1 -0
  50. package/dist/cjs/tests/json.test.d.cts +1 -0
  51. package/dist/cjs/tests/transformer.test.d.cts +1 -0
  52. package/dist/cjs/useServerFn.cjs +26 -0
  53. package/dist/cjs/useServerFn.cjs.map +1 -0
  54. package/dist/cjs/useServerFn.d.cts +1 -0
  55. package/dist/esm/Meta.d.ts +1 -0
  56. package/dist/esm/Meta.js +14 -0
  57. package/dist/esm/Meta.js.map +1 -0
  58. package/dist/esm/Scripts.d.ts +1 -0
  59. package/dist/esm/Scripts.js +12 -0
  60. package/dist/esm/Scripts.js.map +1 -0
  61. package/dist/esm/StartClient.d.ts +4 -0
  62. package/dist/esm/StartClient.js +24 -0
  63. package/dist/esm/StartClient.js.map +1 -0
  64. package/dist/esm/createIsomorphicFn.d.ts +12 -0
  65. package/dist/esm/createIsomorphicFn.js +7 -0
  66. package/dist/esm/createIsomorphicFn.js.map +1 -0
  67. package/dist/esm/createMiddleware.d.ts +129 -0
  68. package/dist/esm/createMiddleware.js +34 -0
  69. package/dist/esm/createMiddleware.js.map +1 -0
  70. package/dist/esm/createServerFn.d.ts +137 -0
  71. package/dist/esm/createServerFn.js +347 -0
  72. package/dist/esm/createServerFn.js.map +1 -0
  73. package/dist/esm/envOnly.d.ts +4 -0
  74. package/dist/esm/envOnly.js +7 -0
  75. package/dist/esm/envOnly.js.map +1 -0
  76. package/dist/esm/headers.d.ts +5 -0
  77. package/dist/esm/headers.js +30 -0
  78. package/dist/esm/headers.js.map +1 -0
  79. package/dist/esm/index.d.ts +14 -0
  80. package/dist/esm/index.js +31 -0
  81. package/dist/esm/index.js.map +1 -0
  82. package/dist/esm/json.d.ts +2 -0
  83. package/dist/esm/json.js +14 -0
  84. package/dist/esm/json.js.map +1 -0
  85. package/dist/esm/registerGlobalMiddleware.d.ts +5 -0
  86. package/dist/esm/registerGlobalMiddleware.js +9 -0
  87. package/dist/esm/registerGlobalMiddleware.js.map +1 -0
  88. package/dist/esm/renderRSC.d.ts +2 -0
  89. package/dist/esm/renderRSC.js +29 -0
  90. package/dist/esm/renderRSC.js.map +1 -0
  91. package/dist/esm/routesManifest.d.ts +0 -0
  92. package/dist/esm/serializer.d.ts +2 -0
  93. package/dist/esm/serializer.js +152 -0
  94. package/dist/esm/serializer.js.map +1 -0
  95. package/dist/esm/ssr-client.d.ts +65 -0
  96. package/dist/esm/ssr-client.js +114 -0
  97. package/dist/esm/ssr-client.js.map +1 -0
  98. package/dist/esm/tests/createIsomorphicFn.test-d.d.ts +1 -0
  99. package/dist/esm/tests/createServerFn.test-d.d.ts +1 -0
  100. package/dist/esm/tests/createServerMiddleware.test-d.d.ts +1 -0
  101. package/dist/esm/tests/envOnly.test-d.d.ts +1 -0
  102. package/dist/esm/tests/json.test.d.ts +1 -0
  103. package/dist/esm/tests/transformer.test.d.ts +1 -0
  104. package/dist/esm/useServerFn.d.ts +1 -0
  105. package/dist/esm/useServerFn.js +26 -0
  106. package/dist/esm/useServerFn.js.map +1 -0
  107. package/package.json +68 -0
  108. package/src/Meta.tsx +10 -0
  109. package/src/Scripts.tsx +8 -0
  110. package/src/StartClient.tsx +21 -0
  111. package/src/createIsomorphicFn.ts +36 -0
  112. package/src/createMiddleware.ts +517 -0
  113. package/src/createServerFn.ts +790 -0
  114. package/src/envOnly.ts +9 -0
  115. package/src/headers.ts +50 -0
  116. package/src/index.tsx +74 -0
  117. package/src/json.ts +15 -0
  118. package/src/registerGlobalMiddleware.ts +9 -0
  119. package/src/renderRSC.tsx +91 -0
  120. package/src/routesManifest.ts +0 -0
  121. package/src/serializer.ts +177 -0
  122. package/src/ssr-client.tsx +221 -0
  123. package/src/tests/createIsomorphicFn.test-d.ts +72 -0
  124. package/src/tests/createServerFn.test-d.tsx +390 -0
  125. package/src/tests/createServerMiddleware.test-d.ts +611 -0
  126. package/src/tests/envOnly.test-d.ts +34 -0
  127. package/src/tests/json.test.ts +37 -0
  128. package/src/tests/transformer.test.tsx +147 -0
  129. package/src/useServerFn.ts +30 -0
package/src/envOnly.ts ADDED
@@ -0,0 +1,9 @@
1
+ type EnvOnlyFn = <TFn extends (...args: Array<any>) => any>(fn: TFn) => TFn
2
+
3
+ // A function that will only be available in the server build
4
+ // If called on the client, it will throw an error
5
+ export const serverOnly: EnvOnlyFn = (fn) => fn
6
+
7
+ // A function that will only be available in the client build
8
+ // If called on the server, it will throw an error
9
+ export const clientOnly: EnvOnlyFn = (fn) => fn
package/src/headers.ts ADDED
@@ -0,0 +1,50 @@
1
+ import { splitSetCookieString } from 'cookie-es'
2
+ import type { OutgoingHttpHeaders } from 'node:http2'
3
+ // A utility function to turn HeadersInit into an object
4
+ export function headersInitToObject(
5
+ headers: HeadersInit,
6
+ ): Record<keyof OutgoingHttpHeaders, string> {
7
+ const obj: Record<keyof OutgoingHttpHeaders, string> = {}
8
+ const headersInstance = new Headers(headers)
9
+ for (const [key, value] of headersInstance.entries()) {
10
+ obj[key] = value
11
+ }
12
+ return obj
13
+ }
14
+
15
+ type AnyHeaders =
16
+ | Headers
17
+ | HeadersInit
18
+ | Record<string, string>
19
+ | Array<[string, string]>
20
+ | OutgoingHttpHeaders
21
+ | undefined
22
+
23
+ // Helper function to convert various HeaderInit types to a Headers instance
24
+ function toHeadersInstance(init: AnyHeaders) {
25
+ if (init instanceof Headers) {
26
+ return new Headers(init)
27
+ } else if (Array.isArray(init)) {
28
+ return new Headers(init)
29
+ } else if (typeof init === 'object') {
30
+ return new Headers(init as HeadersInit)
31
+ } else {
32
+ return new Headers()
33
+ }
34
+ }
35
+
36
+ // Function to merge headers with proper overrides
37
+ export function mergeHeaders(...headers: Array<AnyHeaders>) {
38
+ return headers.reduce((acc: Headers, header) => {
39
+ const headersInstance = toHeadersInstance(header)
40
+ for (const [key, value] of headersInstance.entries()) {
41
+ if (key === 'set-cookie') {
42
+ const splitCookies = splitSetCookieString(value)
43
+ splitCookies.forEach((cookie) => acc.append('set-cookie', cookie))
44
+ } else {
45
+ acc.set(key, value)
46
+ }
47
+ }
48
+ return acc
49
+ }, new Headers())
50
+ }
package/src/index.tsx ADDED
@@ -0,0 +1,74 @@
1
+ /// <reference types="vinxi/types/client" />
2
+ export {
3
+ createIsomorphicFn,
4
+ type IsomorphicFn,
5
+ type ServerOnlyFn,
6
+ type ClientOnlyFn,
7
+ type IsomorphicFnBase,
8
+ } from './createIsomorphicFn'
9
+ export {
10
+ createServerFn,
11
+ type JsonResponse,
12
+ type ServerFn as FetchFn,
13
+ type ServerFnCtx as FetchFnCtx,
14
+ type CompiledFetcherFnOptions,
15
+ type CompiledFetcherFn,
16
+ type Fetcher,
17
+ type RscStream,
18
+ type FetcherData,
19
+ type FetcherBaseOptions,
20
+ type ServerFn,
21
+ type ServerFnCtx,
22
+ } from './createServerFn'
23
+ export {
24
+ createMiddleware,
25
+ type IntersectAllValidatorInputs,
26
+ type IntersectAllValidatorOutputs,
27
+ type MiddlewareServerFn,
28
+ type AnyMiddleware,
29
+ type MiddlewareOptions,
30
+ type MiddlewareWithTypes,
31
+ type MiddlewareValidator,
32
+ type MiddlewareServer,
33
+ type MiddlewareAfterClient,
34
+ type MiddlewareAfterMiddleware,
35
+ type MiddlewareAfterServer,
36
+ type Middleware,
37
+ type MiddlewareClientFnOptions,
38
+ type MiddlewareClientFnResult,
39
+ type MiddlewareClientNextFn,
40
+ type ClientResultWithContext,
41
+ type AssignAllClientContextBeforeNext,
42
+ type AssignAllMiddleware,
43
+ type AssignAllServerContext,
44
+ type MiddlewareAfterValidator,
45
+ type MiddlewareClientFn,
46
+ type MiddlewareServerFnResult,
47
+ type MiddlewareClient,
48
+ type MiddlewareServerFnOptions,
49
+ type MiddlewareServerNextFn,
50
+ type ServerResultWithContext,
51
+ } from './createMiddleware'
52
+ export {
53
+ registerGlobalMiddleware,
54
+ globalMiddleware,
55
+ } from './registerGlobalMiddleware'
56
+ export { serverOnly, clientOnly } from './envOnly'
57
+ export { json } from './json'
58
+ export { Meta } from './Meta'
59
+ export { Scripts } from './Scripts'
60
+ export { StartClient } from './StartClient'
61
+ export { mergeHeaders } from './headers'
62
+ export { renderRsc } from './renderRSC'
63
+ export { useServerFn } from './useServerFn'
64
+ export {
65
+ type DehydratedRouter,
66
+ type ClientExtractedBaseEntry,
67
+ type StartSsrGlobal,
68
+ type ClientExtractedEntry,
69
+ type SsrMatch,
70
+ type ClientExtractedPromise,
71
+ type ClientExtractedStream,
72
+ type ResolvePromiseState,
73
+ } from './ssr-client'
74
+ export { startSerializer } from './serializer'
package/src/json.ts ADDED
@@ -0,0 +1,15 @@
1
+ import { mergeHeaders } from './headers'
2
+ import type { JsonResponse } from './createServerFn'
3
+
4
+ export function json<TData>(
5
+ payload: TData,
6
+ init?: ResponseInit,
7
+ ): JsonResponse<TData> {
8
+ return new Response(JSON.stringify(payload), {
9
+ ...init,
10
+ headers: mergeHeaders(
11
+ { 'content-type': 'application/json' },
12
+ init?.headers,
13
+ ),
14
+ })
15
+ }
@@ -0,0 +1,9 @@
1
+ import type { AnyMiddleware } from './createMiddleware'
2
+
3
+ export const globalMiddleware: Array<AnyMiddleware> = []
4
+
5
+ export function registerGlobalMiddleware(options: {
6
+ middleware: Array<AnyMiddleware>
7
+ }) {
8
+ globalMiddleware.push(...options.middleware)
9
+ }
@@ -0,0 +1,91 @@
1
+ // TODO: RSCs
2
+ // // @ts-expect-error
3
+ // import * as reactDom from '@vinxi/react-server-dom/client'
4
+ import { isValidElement } from 'react'
5
+ import invariant from 'tiny-invariant'
6
+ import type React from 'react'
7
+
8
+ export function renderRsc(input: any): React.JSX.Element {
9
+ if (isValidElement(input)) {
10
+ return input
11
+ }
12
+
13
+ if (typeof input === 'object' && !input.state) {
14
+ input.state = {
15
+ status: 'pending',
16
+ promise: Promise.resolve()
17
+ .then(() => {
18
+ let element
19
+
20
+ // We're in node
21
+ // TODO: RSCs
22
+ // if (reactDom.createFromNodeStream) {
23
+ // const stream = await import('node:stream')
24
+
25
+ // let body: any = input
26
+
27
+ // // Unwrap the response
28
+ // if (input instanceof Response) {
29
+ // body = input.body
30
+ // }
31
+
32
+ // // Convert ReadableStream to NodeJS stream.Readable
33
+ // if (body instanceof ReadableStream) {
34
+ // body = stream.Readable.fromWeb(body as any)
35
+ // }
36
+
37
+ // if (stream.Readable.isReadable(body)) {
38
+ // // body = copyStreamToRaw(body)
39
+ // } else if (input.text) {
40
+ // // create a readable stream by awaiting the text method
41
+ // body = new stream.Readable({
42
+ // async read() {
43
+ // input.text().then((value: any) => {
44
+ // this.push(value)
45
+ // this.push(null)
46
+ // })
47
+ // },
48
+ // })
49
+ // } else {
50
+ // console.error('input', input)
51
+ // throw new Error('Unexpected rsc input type 👆')
52
+ // }
53
+
54
+ // element = await reactDom.createFromNodeStream(body)
55
+ // } else {
56
+ // // We're in the browser
57
+ // if (input.body instanceof ReadableStream) {
58
+ // input = input.body
59
+ // }
60
+
61
+ // if (input instanceof ReadableStream) {
62
+ // element = await reactDom.createFromReadableStream(input)
63
+ // }
64
+
65
+ // if (input instanceof Response) {
66
+ // // copy to the response body to cache the raw data
67
+ // element = await reactDom.createFromFetch(input)
68
+ // }
69
+ // }
70
+
71
+ // return element
72
+
73
+ invariant(false, 'renderRSC() is coming soon!')
74
+ })
75
+ .then((element) => {
76
+ input.state.value = element
77
+ input.state.status = 'success'
78
+ })
79
+ .catch((err) => {
80
+ input.state.status = 'error'
81
+ input.state.error = err
82
+ }),
83
+ }
84
+ }
85
+
86
+ if (input.state.status === 'pending') {
87
+ throw input.state.promise
88
+ }
89
+
90
+ return input.state.value
91
+ }
File without changes
@@ -0,0 +1,177 @@
1
+ import { isPlainObject } from '@tanstack/router-core'
2
+ import type { StartSerializer } from '@tanstack/router-core'
3
+
4
+ export const startSerializer: StartSerializer = {
5
+ stringify: (value: any) =>
6
+ JSON.stringify(value, function replacer(key, val) {
7
+ const ogVal = this[key]
8
+ const serializer = serializers.find((t) => t.stringifyCondition(ogVal))
9
+
10
+ if (serializer) {
11
+ return serializer.stringify(ogVal)
12
+ }
13
+
14
+ return val
15
+ }),
16
+ parse: (value: string) =>
17
+ JSON.parse(value, function parser(key, val) {
18
+ const ogVal = this[key]
19
+ if (isPlainObject(ogVal)) {
20
+ const serializer = serializers.find((t) => t.parseCondition(ogVal))
21
+
22
+ if (serializer) {
23
+ return serializer.parse(ogVal)
24
+ }
25
+ }
26
+
27
+ return val
28
+ }),
29
+ encode: (value: any) => {
30
+ // When encoding, dive first
31
+ if (Array.isArray(value)) {
32
+ return value.map((v) => startSerializer.encode(v))
33
+ }
34
+
35
+ if (isPlainObject(value)) {
36
+ return Object.fromEntries(
37
+ Object.entries(value).map(([key, v]) => [
38
+ key,
39
+ startSerializer.encode(v),
40
+ ]),
41
+ )
42
+ }
43
+
44
+ const serializer = serializers.find((t) => t.stringifyCondition(value))
45
+ if (serializer) {
46
+ return serializer.stringify(value)
47
+ }
48
+
49
+ return value
50
+ },
51
+ decode: (value: any) => {
52
+ // Attempt transform first
53
+ if (isPlainObject(value)) {
54
+ const serializer = serializers.find((t) => t.parseCondition(value))
55
+ if (serializer) {
56
+ return serializer.parse(value)
57
+ }
58
+ }
59
+
60
+ if (Array.isArray(value)) {
61
+ return value.map((v) => startSerializer.decode(v))
62
+ }
63
+
64
+ if (isPlainObject(value)) {
65
+ return Object.fromEntries(
66
+ Object.entries(value).map(([key, v]) => [
67
+ key,
68
+ startSerializer.decode(v),
69
+ ]),
70
+ )
71
+ }
72
+
73
+ return value
74
+ },
75
+ }
76
+
77
+ const createSerializer = <TKey extends string, TInput, TSerialized>(
78
+ key: TKey,
79
+ check: (value: any) => value is TInput,
80
+ toValue: (value: TInput) => TSerialized,
81
+ fromValue: (value: TSerialized) => TInput,
82
+ ) => ({
83
+ key,
84
+ stringifyCondition: check,
85
+ stringify: (value: any) => ({ [`$${key}`]: toValue(value) }),
86
+ parseCondition: (value: any) => Object.hasOwn(value, `$${key}`),
87
+ parse: (value: any) => fromValue(value[`$${key}`]),
88
+ })
89
+
90
+ // Keep these ordered by predicted frequency
91
+ // Make sure to keep DefaultSerializable in sync with these serializers
92
+ // Also, make sure that they are unit tested in serializer.test.tsx
93
+ const serializers = [
94
+ createSerializer(
95
+ // Key
96
+ 'undefined',
97
+ // Check
98
+ (v): v is undefined => v === undefined,
99
+ // To
100
+ () => 0,
101
+ // From
102
+ () => undefined,
103
+ ),
104
+ createSerializer(
105
+ // Key
106
+ 'date',
107
+ // Check
108
+ (v): v is Date => v instanceof Date,
109
+ // To
110
+ (v) => v.toISOString(),
111
+ // From
112
+ (v) => new Date(v),
113
+ ),
114
+ createSerializer(
115
+ // Key
116
+ 'error',
117
+ // Check
118
+ (v): v is Error => v instanceof Error,
119
+ // To
120
+ (v) => ({
121
+ ...v,
122
+ message: v.message,
123
+ stack: process.env.NODE_ENV === 'development' ? v.stack : undefined,
124
+ cause: v.cause,
125
+ }),
126
+ // From
127
+ (v) => Object.assign(new Error(v.message), v),
128
+ ),
129
+ createSerializer(
130
+ // Key
131
+ 'formData',
132
+ // Check
133
+ (v): v is FormData => v instanceof FormData,
134
+ // To
135
+ (v) => {
136
+ const entries: Record<
137
+ string,
138
+ Array<FormDataEntryValue> | FormDataEntryValue
139
+ > = {}
140
+ v.forEach((value, key) => {
141
+ const entry = entries[key]
142
+ if (entry !== undefined) {
143
+ if (Array.isArray(entry)) {
144
+ entry.push(value)
145
+ } else {
146
+ entries[key] = [entry, value]
147
+ }
148
+ } else {
149
+ entries[key] = value
150
+ }
151
+ })
152
+ return entries
153
+ },
154
+ // From
155
+ (v) => {
156
+ const formData = new FormData()
157
+ Object.entries(v).forEach(([key, value]) => {
158
+ if (Array.isArray(value)) {
159
+ value.forEach((val) => formData.append(key, val))
160
+ } else {
161
+ formData.append(key, value)
162
+ }
163
+ })
164
+ return formData
165
+ },
166
+ ),
167
+ createSerializer(
168
+ // Key
169
+ 'bigint',
170
+ // Check
171
+ (v): v is bigint => typeof v === 'bigint',
172
+ // To
173
+ (v) => v.toString(),
174
+ // From
175
+ (v) => BigInt(v),
176
+ ),
177
+ ] as const
@@ -0,0 +1,221 @@
1
+ import { isPlainObject } from '@tanstack/router-core'
2
+
3
+ import invariant from 'tiny-invariant'
4
+
5
+ import { startSerializer } from './serializer'
6
+ import type {
7
+ AnyRouter,
8
+ ControllablePromise,
9
+ MakeRouteMatch,
10
+ } from '@tanstack/react-router'
11
+
12
+ import type { DeferredPromiseState, Manifest } from '@tanstack/router-core'
13
+
14
+ declare global {
15
+ interface Window {
16
+ __TSR_SSR__?: StartSsrGlobal
17
+ }
18
+ }
19
+
20
+ export interface StartSsrGlobal {
21
+ matches: Array<SsrMatch>
22
+ streamedValues: Record<
23
+ string,
24
+ {
25
+ value: any
26
+ parsed: any
27
+ }
28
+ >
29
+ cleanScripts: () => void
30
+ dehydrated?: any
31
+ initMatch: (match: SsrMatch) => void
32
+ resolvePromise: (opts: {
33
+ matchId: string
34
+ id: number
35
+ promiseState: DeferredPromiseState<any>
36
+ }) => void
37
+ injectChunk: (opts: { matchId: string; id: number; chunk: string }) => void
38
+ closeStream: (opts: { matchId: string; id: number }) => void
39
+ }
40
+
41
+ export interface SsrMatch {
42
+ id: string
43
+ __beforeLoadContext: string
44
+ loaderData?: string
45
+ error?: string
46
+ extracted?: Array<ClientExtractedEntry>
47
+ updatedAt: MakeRouteMatch['updatedAt']
48
+ status: MakeRouteMatch['status']
49
+ }
50
+
51
+ export type ClientExtractedEntry =
52
+ | ClientExtractedStream
53
+ | ClientExtractedPromise
54
+
55
+ export interface ClientExtractedPromise extends ClientExtractedBaseEntry {
56
+ type: 'promise'
57
+ value?: ControllablePromise<any>
58
+ }
59
+
60
+ export interface ClientExtractedStream extends ClientExtractedBaseEntry {
61
+ type: 'stream'
62
+ value?: ReadableStream & { controller?: ReadableStreamDefaultController }
63
+ }
64
+
65
+ export interface ClientExtractedBaseEntry {
66
+ type: string
67
+ path: Array<string>
68
+ }
69
+
70
+ export interface ResolvePromiseState {
71
+ matchId: string
72
+ id: number
73
+ promiseState: DeferredPromiseState<any>
74
+ }
75
+
76
+ export interface DehydratedRouter {
77
+ manifest: Manifest | undefined
78
+ dehydratedData: any
79
+ }
80
+
81
+ export function hydrate(router: AnyRouter) {
82
+ invariant(
83
+ window.__TSR_SSR__?.dehydrated,
84
+ 'Expected to find a dehydrated data on window.__TSR_SSR__.dehydrated... but we did not. Please file an issue!',
85
+ )
86
+
87
+ const { manifest, dehydratedData } = startSerializer.parse(
88
+ window.__TSR_SSR__.dehydrated,
89
+ ) as DehydratedRouter
90
+
91
+ router.ssr = {
92
+ manifest,
93
+ serializer: startSerializer,
94
+ }
95
+
96
+ router.clientSsr = {
97
+ getStreamedValue: <T,>(key: string): T | undefined => {
98
+ if (router.isServer) {
99
+ return undefined
100
+ }
101
+
102
+ const streamedValue = window.__TSR_SSR__?.streamedValues[key]
103
+
104
+ if (!streamedValue) {
105
+ return
106
+ }
107
+
108
+ if (!streamedValue.parsed) {
109
+ streamedValue.parsed = router.ssr!.serializer.parse(streamedValue.value)
110
+ }
111
+
112
+ return streamedValue.parsed
113
+ },
114
+ }
115
+
116
+ // Hydrate the router state
117
+ const matches = router.matchRoutes(router.state.location)
118
+ // kick off loading the route chunks
119
+ const routeChunkPromise = Promise.all(
120
+ matches.map((match) => {
121
+ const route = router.looseRoutesById[match.routeId]!
122
+ return router.loadRouteChunk(route)
123
+ }),
124
+ )
125
+ matches.forEach((match) => {
126
+ const route = router.looseRoutesById[match.routeId]!
127
+
128
+ // Right after hydration and before the first render, we need to rehydrate each match
129
+ // This includes rehydrating the loaderData and also using the beforeLoadContext
130
+ // to reconstruct any context that was serialized on the server
131
+
132
+ const dehydratedMatch = window.__TSR_SSR__!.matches.find(
133
+ (d) => d.id === match.id,
134
+ )
135
+
136
+ if (dehydratedMatch) {
137
+ Object.assign(match, dehydratedMatch)
138
+
139
+ const parentMatch = matches[match.index - 1]
140
+ const parentContext = parentMatch?.context ?? router.options.context ?? {}
141
+
142
+ // Handle beforeLoadContext
143
+ if (dehydratedMatch.__beforeLoadContext) {
144
+ match.__beforeLoadContext = router.ssr!.serializer.parse(
145
+ dehydratedMatch.__beforeLoadContext,
146
+ ) as any
147
+
148
+ match.context = {
149
+ ...parentContext,
150
+ ...match.__routeContext,
151
+ ...match.__beforeLoadContext,
152
+ }
153
+ }
154
+
155
+ // Handle loaderData
156
+ if (dehydratedMatch.loaderData) {
157
+ match.loaderData = router.ssr!.serializer.parse(
158
+ dehydratedMatch.loaderData,
159
+ )
160
+ }
161
+
162
+ // Handle error
163
+ if (dehydratedMatch.error) {
164
+ match.error = router.ssr!.serializer.parse(dehydratedMatch.error)
165
+ }
166
+
167
+ // Handle extracted
168
+ ;(match as unknown as SsrMatch).extracted?.forEach((ex) => {
169
+ deepMutableSetByPath(match, ['loaderData', ...ex.path], ex.value)
170
+ })
171
+ } else {
172
+ Object.assign(match, {
173
+ status: 'success',
174
+ updatedAt: Date.now(),
175
+ })
176
+ }
177
+
178
+ const assetContext = {
179
+ matches: router.state.matches,
180
+ match,
181
+ params: match.params,
182
+ loaderData: match.loaderData,
183
+ }
184
+ const headFnContent = route.options.head?.(assetContext)
185
+
186
+ const scripts = route.options.scripts?.(assetContext)
187
+
188
+ match.meta = headFnContent?.meta
189
+ match.links = headFnContent?.links
190
+ match.headScripts = headFnContent?.scripts
191
+ match.scripts = scripts
192
+
193
+ return match
194
+ })
195
+
196
+ router.__store.setState((s) => {
197
+ return {
198
+ ...s,
199
+ matches,
200
+ }
201
+ })
202
+
203
+ // Allow the user to handle custom hydration data
204
+ router.options.hydrate?.(dehydratedData)
205
+ return routeChunkPromise
206
+ }
207
+
208
+ function deepMutableSetByPath<T>(obj: T, path: Array<string>, value: any) {
209
+ // mutable set by path retaining array and object references
210
+ if (path.length === 1) {
211
+ ;(obj as any)[path[0]!] = value
212
+ }
213
+
214
+ const [key, ...rest] = path
215
+
216
+ if (Array.isArray(obj)) {
217
+ deepMutableSetByPath(obj[Number(key)], rest, value)
218
+ } else if (isPlainObject(obj)) {
219
+ deepMutableSetByPath((obj as any)[key!], rest, value)
220
+ }
221
+ }