@tanstack/start-client-core 1.120.7 → 1.121.0-alpha.11

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 (36) hide show
  1. package/README.md +6 -27
  2. package/dist/cjs/createMiddleware.cjs +8 -5
  3. package/dist/cjs/createMiddleware.cjs.map +1 -1
  4. package/dist/cjs/createMiddleware.d.cts +125 -81
  5. package/dist/cjs/createServerFn.cjs +1 -0
  6. package/dist/cjs/createServerFn.cjs.map +1 -1
  7. package/dist/cjs/createServerFn.d.cts +6 -5
  8. package/dist/cjs/index.cjs +1 -0
  9. package/dist/cjs/index.cjs.map +1 -1
  10. package/dist/cjs/index.d.cts +2 -2
  11. package/dist/cjs/registerGlobalMiddleware.cjs.map +1 -1
  12. package/dist/cjs/registerGlobalMiddleware.d.cts +3 -3
  13. package/dist/cjs/ssr-client.cjs +62 -61
  14. package/dist/cjs/ssr-client.cjs.map +1 -1
  15. package/dist/cjs/ssr-client.d.cts +2 -1
  16. package/dist/esm/createMiddleware.d.ts +125 -81
  17. package/dist/esm/createMiddleware.js +8 -5
  18. package/dist/esm/createMiddleware.js.map +1 -1
  19. package/dist/esm/createServerFn.d.ts +6 -5
  20. package/dist/esm/createServerFn.js +1 -0
  21. package/dist/esm/createServerFn.js.map +1 -1
  22. package/dist/esm/index.d.ts +2 -2
  23. package/dist/esm/index.js +2 -1
  24. package/dist/esm/registerGlobalMiddleware.d.ts +3 -3
  25. package/dist/esm/registerGlobalMiddleware.js.map +1 -1
  26. package/dist/esm/ssr-client.d.ts +2 -1
  27. package/dist/esm/ssr-client.js +62 -61
  28. package/dist/esm/ssr-client.js.map +1 -1
  29. package/package.json +2 -2
  30. package/src/createMiddleware.ts +474 -363
  31. package/src/createServerFn.ts +31 -16
  32. package/src/index.tsx +22 -21
  33. package/src/registerGlobalMiddleware.ts +3 -3
  34. package/src/ssr-client.tsx +75 -69
  35. package/src/tests/createServerFn.test-d.ts +20 -13
  36. package/src/tests/createServerMiddleware.test-d.ts +186 -73
@@ -16,15 +16,17 @@ import type {
16
16
  } from '@tanstack/router-core'
17
17
  import type { Readable } from 'node:stream'
18
18
  import type {
19
- AnyMiddleware,
19
+ AnyFunctionMiddleware,
20
20
  AssignAllClientSendContext,
21
21
  AssignAllServerContext,
22
+ FunctionMiddlewareClientFnResult,
23
+ FunctionMiddlewareServerFnResult,
22
24
  IntersectAllValidatorInputs,
23
25
  IntersectAllValidatorOutputs,
24
- MiddlewareClientFnResult,
25
- MiddlewareServerFnResult,
26
26
  } from './createMiddleware'
27
27
 
28
+ type TODO = any
29
+
28
30
  export function createServerFn<
29
31
  TMethod extends Method,
30
32
  TServerFnResponseType extends ServerFnResponseType = 'data',
@@ -215,8 +217,8 @@ export function createServerFn<
215
217
  }
216
218
  }
217
219
 
218
- async function executeMiddleware(
219
- middlewares: Array<AnyMiddleware>,
220
+ export async function executeMiddleware(
221
+ middlewares: Array<AnyFunctionMiddleware>,
220
222
  env: 'client' | 'server',
221
223
  opts: ServerFnMiddlewareOptions,
222
224
  ): Promise<ServerFnMiddlewareResult> {
@@ -398,6 +400,7 @@ export type ServerFnReturnType<
398
400
  > = TServerFnResponseType extends 'raw'
399
401
  ? RawResponse | Promise<RawResponse>
400
402
  : Promise<SerializerStringify<TResponse>> | SerializerStringify<TResponse>
403
+
401
404
  export type ServerFn<
402
405
  TMethod,
403
406
  TServerFnResponseType extends ServerFnResponseType,
@@ -442,7 +445,7 @@ export type ServerFnBaseOptions<
442
445
  method: TMethod
443
446
  response?: TServerFnResponseType
444
447
  validateClient?: boolean
445
- middleware?: Constrain<TMiddlewares, ReadonlyArray<AnyMiddleware>>
448
+ middleware?: Constrain<TMiddlewares, ReadonlyArray<AnyFunctionMiddleware>>
446
449
  validator?: ConstrainValidator<TInput>
447
450
  extractedFn?: CompiledFetcherFn<TResponse, TServerFnResponseType>
448
451
  serverFn?: ServerFn<
@@ -485,7 +488,10 @@ export interface ServerFnMiddleware<
485
488
  TValidator,
486
489
  > {
487
490
  middleware: <const TNewMiddlewares = undefined>(
488
- middlewares: Constrain<TNewMiddlewares, ReadonlyArray<AnyMiddleware>>,
491
+ middlewares: Constrain<
492
+ TNewMiddlewares,
493
+ ReadonlyArray<AnyFunctionMiddleware>
494
+ >,
489
495
  ) => ServerFnAfterMiddleware<
490
496
  TMethod,
491
497
  TServerFnResponseType,
@@ -812,12 +818,12 @@ export function extractFormDataContext(formData: FormData) {
812
818
  }
813
819
 
814
820
  export function flattenMiddlewares(
815
- middlewares: Array<AnyMiddleware>,
816
- ): Array<AnyMiddleware> {
817
- const seen = new Set<AnyMiddleware>()
818
- const flattened: Array<AnyMiddleware> = []
821
+ middlewares: Array<AnyFunctionMiddleware>,
822
+ ): Array<AnyFunctionMiddleware> {
823
+ const seen = new Set<AnyFunctionMiddleware>()
824
+ const flattened: Array<AnyFunctionMiddleware> = []
819
825
 
820
- const recurse = (middleware: Array<AnyMiddleware>) => {
826
+ const recurse = (middleware: Array<AnyFunctionMiddleware>) => {
821
827
  middleware.forEach((m) => {
822
828
  if (m.options.middleware) {
823
829
  recurse(m.options.middleware)
@@ -929,7 +935,7 @@ export function execValidator(
929
935
 
930
936
  export function serverFnBaseToMiddleware(
931
937
  options: ServerFnBaseOptions<any, any, any, any, any>,
932
- ): AnyMiddleware {
938
+ ): AnyFunctionMiddleware {
933
939
  return {
934
940
  _types: undefined!,
935
941
  options: {
@@ -973,16 +979,25 @@ export function serverFnBaseToMiddleware(
973
979
  // but not before serializing the context
974
980
  const res = await options.extractedFn?.(payload)
975
981
 
976
- return next(res) as unknown as MiddlewareClientFnResult<any, any, any>
982
+ return next(res) as unknown as FunctionMiddlewareClientFnResult<
983
+ any,
984
+ any,
985
+ any
986
+ >
977
987
  },
978
988
  server: async ({ next, ...ctx }) => {
979
989
  // Execute the server function
980
- const result = await options.serverFn?.(ctx)
990
+ const result = await options.serverFn?.(ctx as TODO)
981
991
 
982
992
  return next({
983
993
  ...ctx,
984
994
  result,
985
- } as any) as unknown as MiddlewareServerFnResult<any, any, any, any>
995
+ } as any) as unknown as FunctionMiddlewareServerFnResult<
996
+ any,
997
+ any,
998
+ any,
999
+ any
1000
+ >
986
1001
  },
987
1002
  },
988
1003
  }
package/src/index.tsx CHANGED
@@ -25,30 +25,30 @@ export {
25
25
  createMiddleware,
26
26
  type IntersectAllValidatorInputs,
27
27
  type IntersectAllValidatorOutputs,
28
- type MiddlewareServerFn,
29
- type AnyMiddleware,
30
- type MiddlewareOptions,
31
- type MiddlewareWithTypes,
32
- type MiddlewareValidator,
33
- type MiddlewareServer,
34
- type MiddlewareAfterClient,
35
- type MiddlewareAfterMiddleware,
36
- type MiddlewareAfterServer,
37
- type Middleware,
38
- type MiddlewareClientFnOptions,
39
- type MiddlewareClientFnResult,
40
- type MiddlewareClientNextFn,
41
- type ClientResultWithContext,
28
+ type FunctionMiddlewareServerFn,
29
+ type AnyFunctionMiddleware,
30
+ type FunctionMiddlewareOptions,
31
+ type FunctionMiddlewareWithTypes,
32
+ type FunctionMiddlewareValidator,
33
+ type FunctionMiddlewareServer,
34
+ type FunctionMiddlewareAfterClient,
35
+ type FunctionMiddlewareAfterServer,
36
+ type FunctionMiddleware,
37
+ type FunctionMiddlewareClientFnOptions,
38
+ type FunctionMiddlewareClientFnResult,
39
+ type FunctionMiddlewareClientNextFn,
40
+ type FunctionClientResultWithContext,
42
41
  type AssignAllClientContextBeforeNext,
43
42
  type AssignAllMiddleware,
44
43
  type AssignAllServerContext,
45
- type MiddlewareAfterValidator,
46
- type MiddlewareClientFn,
47
- type MiddlewareServerFnResult,
48
- type MiddlewareClient,
49
- type MiddlewareServerFnOptions,
50
- type MiddlewareServerNextFn,
51
- type ServerResultWithContext,
44
+ type FunctionMiddlewareAfterValidator,
45
+ type FunctionMiddlewareClientFn,
46
+ type FunctionMiddlewareServerFnResult,
47
+ type FunctionMiddlewareClient,
48
+ type FunctionMiddlewareServerFnOptions,
49
+ type FunctionMiddlewareServerNextFn,
50
+ type FunctionServerResultWithContext,
51
+ type AnyRequestMiddleware,
52
52
  } from './createMiddleware'
53
53
  export {
54
54
  registerGlobalMiddleware,
@@ -84,4 +84,5 @@ export {
84
84
  extractFormDataContext,
85
85
  flattenMiddlewares,
86
86
  serverFnStaticCache,
87
+ executeMiddleware,
87
88
  } from './createServerFn'
@@ -1,9 +1,9 @@
1
- import type { AnyMiddleware } from './createMiddleware'
1
+ import type { AnyFunctionMiddleware } from './createMiddleware'
2
2
 
3
- export const globalMiddleware: Array<AnyMiddleware> = []
3
+ export const globalMiddleware: Array<AnyFunctionMiddleware> = []
4
4
 
5
5
  export function registerGlobalMiddleware(options: {
6
- middleware: Array<AnyMiddleware>
6
+ middleware: Array<AnyFunctionMiddleware>
7
7
  }) {
8
8
  globalMiddleware.push(...options.middleware)
9
9
  }
@@ -77,15 +77,16 @@ export interface ResolvePromiseState {
77
77
  export interface DehydratedRouter {
78
78
  manifest: Manifest | undefined
79
79
  dehydratedData: any
80
+ lastMatchId: string
80
81
  }
81
82
 
82
- export function hydrate(router: AnyRouter) {
83
+ export async function hydrate(router: AnyRouter): Promise<any> {
83
84
  invariant(
84
85
  window.__TSR_SSR__?.dehydrated,
85
86
  'Expected to find a dehydrated data on window.__TSR_SSR__.dehydrated... but we did not. Please file an issue!',
86
87
  )
87
88
 
88
- const { manifest, dehydratedData } = startSerializer.parse(
89
+ const { manifest, dehydratedData, lastMatchId } = startSerializer.parse(
89
90
  window.__TSR_SSR__.dehydrated,
90
91
  ) as DehydratedRouter
91
92
 
@@ -116,6 +117,7 @@ export function hydrate(router: AnyRouter) {
116
117
 
117
118
  // Hydrate the router state
118
119
  const matches = router.matchRoutes(router.state.location)
120
+
119
121
  // kick off loading the route chunks
120
122
  const routeChunkPromise = Promise.all(
121
123
  matches.map((match) => {
@@ -123,6 +125,7 @@ export function hydrate(router: AnyRouter) {
123
125
  return router.loadRouteChunk(route)
124
126
  }),
125
127
  )
128
+
126
129
  // Right after hydration and before the first render, we need to rehydrate each match
127
130
  // First step is to reyhdrate loaderData and __beforeLoadContext
128
131
  matches.forEach((match) => {
@@ -130,39 +133,36 @@ export function hydrate(router: AnyRouter) {
130
133
  (d) => d.id === match.id,
131
134
  )
132
135
 
133
- if (dehydratedMatch) {
134
- Object.assign(match, dehydratedMatch)
136
+ if (!dehydratedMatch) {
137
+ return
138
+ }
135
139
 
136
- // Handle beforeLoadContext
137
- if (dehydratedMatch.__beforeLoadContext) {
138
- match.__beforeLoadContext = router.ssr!.serializer.parse(
139
- dehydratedMatch.__beforeLoadContext,
140
- ) as any
141
- }
140
+ Object.assign(match, dehydratedMatch)
142
141
 
143
- // Handle loaderData
144
- if (dehydratedMatch.loaderData) {
145
- match.loaderData = router.ssr!.serializer.parse(
146
- dehydratedMatch.loaderData,
147
- )
148
- }
142
+ // Handle beforeLoadContext
143
+ if (dehydratedMatch.__beforeLoadContext) {
144
+ match.__beforeLoadContext = router.ssr!.serializer.parse(
145
+ dehydratedMatch.__beforeLoadContext,
146
+ ) as any
147
+ }
149
148
 
150
- // Handle error
151
- if (dehydratedMatch.error) {
152
- match.error = router.ssr!.serializer.parse(dehydratedMatch.error)
153
- }
149
+ // Handle loaderData
150
+ if (dehydratedMatch.loaderData) {
151
+ match.loaderData = router.ssr!.serializer.parse(
152
+ dehydratedMatch.loaderData,
153
+ )
154
+ }
154
155
 
155
- // Handle extracted
156
- ;(match as unknown as SsrMatch).extracted?.forEach((ex) => {
157
- deepMutableSetByPath(match, ['loaderData', ...ex.path], ex.value)
158
- })
159
- } else {
160
- Object.assign(match, {
161
- status: 'success',
162
- updatedAt: Date.now(),
163
- })
156
+ // Handle error
157
+ if (dehydratedMatch.error) {
158
+ match.error = router.ssr!.serializer.parse(dehydratedMatch.error)
164
159
  }
165
160
 
161
+ // Handle extracted
162
+ ;(match as unknown as SsrMatch).extracted?.forEach((ex) => {
163
+ deepMutableSetByPath(match, ['loaderData', ...ex.path], ex.value)
164
+ })
165
+
166
166
  return match
167
167
  })
168
168
 
@@ -179,50 +179,56 @@ export function hydrate(router: AnyRouter) {
179
179
  // now that all necessary data is hydrated:
180
180
  // 1) fully reconstruct the route context
181
181
  // 2) execute `head()` and `scripts()` for each match
182
- router.state.matches.forEach((match) => {
183
- const route = router.looseRoutesById[match.routeId]!
184
-
185
- const parentMatch = router.state.matches[match.index - 1]
186
- const parentContext = parentMatch?.context ?? router.options.context ?? {}
187
-
188
- // `context()` was already executed by `matchRoutes`, however route context was not yet fully reconstructed
189
- // so run it again and merge route context
190
- const contextFnContext: RouteContextOptions<any, any, any, any> = {
191
- deps: match.loaderDeps,
192
- params: match.params,
193
- context: parentContext,
194
- location: router.state.location,
195
- navigate: (opts: any) =>
196
- router.navigate({ ...opts, _fromLocation: router.state.location }),
197
- buildLocation: router.buildLocation,
198
- cause: match.cause,
199
- abortController: match.abortController,
200
- preload: false,
201
- matches,
202
- }
203
- match.__routeContext = route.options.context?.(contextFnContext) ?? {}
182
+ await Promise.all(
183
+ router.state.matches.map(async (match) => {
184
+ const route = router.looseRoutesById[match.routeId]!
204
185
 
205
- match.context = {
206
- ...parentContext,
207
- ...match.__routeContext,
208
- ...match.__beforeLoadContext,
209
- }
186
+ const parentMatch = router.state.matches[match.index - 1]
187
+ const parentContext = parentMatch?.context ?? router.options.context ?? {}
188
+
189
+ // `context()` was already executed by `matchRoutes`, however route context was not yet fully reconstructed
190
+ // so run it again and merge route context
191
+ const contextFnContext: RouteContextOptions<any, any, any, any> = {
192
+ deps: match.loaderDeps,
193
+ params: match.params,
194
+ context: parentContext,
195
+ location: router.state.location,
196
+ navigate: (opts: any) =>
197
+ router.navigate({ ...opts, _fromLocation: router.state.location }),
198
+ buildLocation: router.buildLocation,
199
+ cause: match.cause,
200
+ abortController: match.abortController,
201
+ preload: false,
202
+ matches,
203
+ }
204
+ match.__routeContext = route.options.context?.(contextFnContext) ?? {}
210
205
 
211
- const assetContext = {
212
- matches: router.state.matches,
213
- match,
214
- params: match.params,
215
- loaderData: match.loaderData,
216
- }
217
- const headFnContent = route.options.head?.(assetContext)
206
+ match.context = {
207
+ ...parentContext,
208
+ ...match.__routeContext,
209
+ ...match.__beforeLoadContext,
210
+ }
218
211
 
219
- const scripts = route.options.scripts?.(assetContext)
212
+ const assetContext = {
213
+ matches: router.state.matches,
214
+ match,
215
+ params: match.params,
216
+ loaderData: match.loaderData,
217
+ }
218
+ const headFnContent = await route.options.head?.(assetContext)
220
219
 
221
- match.meta = headFnContent?.meta
222
- match.links = headFnContent?.links
223
- match.headScripts = headFnContent?.scripts
224
- match.scripts = scripts
225
- })
220
+ const scripts = await route.options.scripts?.(assetContext)
221
+
222
+ match.meta = headFnContent?.meta
223
+ match.links = headFnContent?.links
224
+ match.headScripts = headFnContent?.scripts
225
+ match.scripts = scripts
226
+ }),
227
+ )
228
+
229
+ if (matches[matches.length - 1]!.id !== lastMatchId) {
230
+ return await Promise.all([routeChunkPromise, router.load()])
231
+ }
226
232
 
227
233
  return routeChunkPromise
228
234
  }
@@ -60,21 +60,25 @@ test('createServerFn with validator', () => {
60
60
  })
61
61
 
62
62
  test('createServerFn with middleware and context', () => {
63
- const middleware1 = createMiddleware().server(({ next }) => {
64
- return next({ context: { a: 'a' } as const })
65
- })
63
+ const middleware1 = createMiddleware({ type: 'function' }).server(
64
+ ({ next }) => {
65
+ return next({ context: { a: 'a' } as const })
66
+ },
67
+ )
66
68
 
67
- const middleware2 = createMiddleware().server(({ next }) => {
68
- return next({ context: { b: 'b' } as const })
69
- })
69
+ const middleware2 = createMiddleware({ type: 'function' }).server(
70
+ ({ next }) => {
71
+ return next({ context: { b: 'b' } as const })
72
+ },
73
+ )
70
74
 
71
- const middleware3 = createMiddleware()
75
+ const middleware3 = createMiddleware({ type: 'function' })
72
76
  .middleware([middleware1, middleware2])
73
77
  .client(({ next }) => {
74
78
  return next({ context: { c: 'c' } as const })
75
79
  })
76
80
 
77
- const middleware4 = createMiddleware()
81
+ const middleware4 = createMiddleware({ type: 'function' })
78
82
  .middleware([middleware3])
79
83
  .client(({ context, next }) => {
80
84
  return next({ sendContext: context })
@@ -113,21 +117,24 @@ test('createServerFn with middleware and context', () => {
113
117
  })
114
118
 
115
119
  describe('createServerFn with middleware and validator', () => {
116
- const middleware1 = createMiddleware().validator(
120
+ const middleware1 = createMiddleware({ type: 'function' }).validator(
117
121
  (input: { readonly inputA: 'inputA' }) =>
118
122
  ({
119
123
  outputA: 'outputA',
120
124
  }) as const,
121
125
  )
122
126
 
123
- const middleware2 = createMiddleware().validator(
127
+ const middleware2 = createMiddleware({ type: 'function' }).validator(
124
128
  (input: { readonly inputB: 'inputB' }) =>
125
129
  ({
126
130
  outputB: 'outputB',
127
131
  }) as const,
128
132
  )
129
133
 
130
- const middleware3 = createMiddleware().middleware([middleware1, middleware2])
134
+ const middleware3 = createMiddleware({ type: 'function' }).middleware([
135
+ middleware1,
136
+ middleware2,
137
+ ])
131
138
 
132
139
  test(`response: 'data'`, () => {
133
140
  const fn = createServerFn({ method: 'GET', response: 'data' })
@@ -222,7 +229,7 @@ describe('createServerFn with middleware and validator', () => {
222
229
  })
223
230
 
224
231
  test('createServerFn overrides properties', () => {
225
- const middleware1 = createMiddleware()
232
+ const middleware1 = createMiddleware({ type: 'function' })
226
233
  .validator(
227
234
  () =>
228
235
  ({
@@ -246,7 +253,7 @@ test('createServerFn overrides properties', () => {
246
253
  return next({ sendContext: newContext, context: newContext })
247
254
  })
248
255
 
249
- const middleware2 = createMiddleware()
256
+ const middleware2 = createMiddleware({ type: 'function' })
250
257
  .middleware([middleware1])
251
258
  .validator(
252
259
  () =>