@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
@@ -0,0 +1,790 @@
1
+ import { default as invariant } from 'tiny-invariant'
2
+ import { default as warning } from 'tiny-warning'
3
+ import { isNotFound, isRedirect } from '@tanstack/react-router'
4
+ import { mergeHeaders } from './headers'
5
+ import { globalMiddleware } from './registerGlobalMiddleware'
6
+ import { startSerializer } from './serializer'
7
+ import type {
8
+ AnyValidator,
9
+ Constrain,
10
+ Expand,
11
+ ResolveValidatorInput,
12
+ SerializerParse,
13
+ SerializerStringify,
14
+ SerializerStringifyBy,
15
+ Validator,
16
+ } from '@tanstack/router-core'
17
+ import type {
18
+ AnyMiddleware,
19
+ AssignAllClientSendContext,
20
+ AssignAllServerContext,
21
+ IntersectAllValidatorInputs,
22
+ IntersectAllValidatorOutputs,
23
+ MiddlewareClientFnResult,
24
+ MiddlewareServerFnResult,
25
+ } from './createMiddleware'
26
+
27
+ export interface JsonResponse<TData> extends Response {
28
+ json: () => Promise<TData>
29
+ }
30
+
31
+ export type CompiledFetcherFnOptions = {
32
+ method: Method
33
+ data: unknown
34
+ headers?: HeadersInit
35
+ signal?: AbortSignal
36
+ context?: any
37
+ }
38
+
39
+ export type Fetcher<TMiddlewares, TValidator, TResponse> =
40
+ undefined extends IntersectAllValidatorInputs<TMiddlewares, TValidator>
41
+ ? OptionalFetcher<TMiddlewares, TValidator, TResponse>
42
+ : RequiredFetcher<TMiddlewares, TValidator, TResponse>
43
+
44
+ export interface FetcherBase {
45
+ url: string
46
+ __executeServer: (opts: {
47
+ method: Method
48
+ data: unknown
49
+ headers?: HeadersInit
50
+ context?: any
51
+ signal: AbortSignal
52
+ }) => Promise<unknown>
53
+ }
54
+
55
+ export type FetchResult<
56
+ TMiddlewares,
57
+ TResponse,
58
+ TFullResponse extends boolean,
59
+ > = false extends TFullResponse
60
+ ? Promise<FetcherData<TResponse>>
61
+ : Promise<FullFetcherData<TMiddlewares, TResponse>>
62
+
63
+ export interface OptionalFetcher<TMiddlewares, TValidator, TResponse>
64
+ extends FetcherBase {
65
+ <TFullResponse extends boolean>(
66
+ options?: OptionalFetcherDataOptions<
67
+ TMiddlewares,
68
+ TValidator,
69
+ TFullResponse
70
+ >,
71
+ ): FetchResult<TMiddlewares, TResponse, TFullResponse>
72
+ }
73
+
74
+ export interface RequiredFetcher<TMiddlewares, TValidator, TResponse>
75
+ extends FetcherBase {
76
+ <TFullResponse extends boolean>(
77
+ opts: RequiredFetcherDataOptions<TMiddlewares, TValidator, TFullResponse>,
78
+ ): FetchResult<TMiddlewares, TResponse, TFullResponse>
79
+ }
80
+
81
+ export type FetcherBaseOptions<TFullResponse extends boolean = false> = {
82
+ headers?: HeadersInit
83
+ type?: ServerFnType
84
+ signal?: AbortSignal
85
+ fullResponse?: TFullResponse
86
+ }
87
+
88
+ export type ServerFnType = 'static' | 'dynamic'
89
+
90
+ export interface OptionalFetcherDataOptions<
91
+ TMiddlewares,
92
+ TValidator,
93
+ TFullResponse extends boolean,
94
+ > extends FetcherBaseOptions<TFullResponse> {
95
+ data?: Expand<IntersectAllValidatorInputs<TMiddlewares, TValidator>>
96
+ }
97
+
98
+ export interface RequiredFetcherDataOptions<
99
+ TMiddlewares,
100
+ TValidator,
101
+ TFullResponse extends boolean,
102
+ > extends FetcherBaseOptions<TFullResponse> {
103
+ data: Expand<IntersectAllValidatorInputs<TMiddlewares, TValidator>>
104
+ }
105
+
106
+ export interface FullFetcherData<TMiddlewares, TResponse> {
107
+ error: unknown
108
+ result: FetcherData<TResponse>
109
+ context: AssignAllClientSendContext<TMiddlewares>
110
+ }
111
+
112
+ export type FetcherData<TResponse> =
113
+ TResponse extends JsonResponse<any>
114
+ ? SerializerParse<ReturnType<TResponse['json']>>
115
+ : SerializerParse<TResponse>
116
+
117
+ export type RscStream<T> = {
118
+ __cacheState: T
119
+ }
120
+
121
+ export type Method = 'GET' | 'POST'
122
+
123
+ export type ServerFn<TMethod, TMiddlewares, TValidator, TResponse> = (
124
+ ctx: ServerFnCtx<TMethod, TMiddlewares, TValidator>,
125
+ ) => Promise<SerializerStringify<TResponse>> | SerializerStringify<TResponse>
126
+
127
+ export interface ServerFnCtx<TMethod, TMiddlewares, TValidator> {
128
+ method: TMethod
129
+ data: Expand<IntersectAllValidatorOutputs<TMiddlewares, TValidator>>
130
+ context: Expand<AssignAllServerContext<TMiddlewares>>
131
+ signal: AbortSignal
132
+ }
133
+
134
+ export type CompiledFetcherFn<TResponse> = {
135
+ (
136
+ opts: CompiledFetcherFnOptions & ServerFnBaseOptions<Method>,
137
+ ): Promise<TResponse>
138
+ url: string
139
+ }
140
+
141
+ type ServerFnBaseOptions<
142
+ TMethod extends Method = 'GET',
143
+ TResponse = unknown,
144
+ TMiddlewares = unknown,
145
+ TInput = unknown,
146
+ > = {
147
+ method: TMethod
148
+ validateClient?: boolean
149
+ middleware?: Constrain<TMiddlewares, ReadonlyArray<AnyMiddleware>>
150
+ validator?: ConstrainValidator<TInput>
151
+ extractedFn?: CompiledFetcherFn<TResponse>
152
+ serverFn?: ServerFn<TMethod, TMiddlewares, TInput, TResponse>
153
+ functionId: string
154
+ type: ServerFnTypeOrTypeFn<TMethod, TMiddlewares, AnyValidator>
155
+ }
156
+
157
+ export type ValidatorSerializerStringify<TValidator> = Validator<
158
+ SerializerStringifyBy<
159
+ ResolveValidatorInput<TValidator>,
160
+ Date | undefined | FormData
161
+ >,
162
+ any
163
+ >
164
+
165
+ export type ConstrainValidator<TValidator> = unknown extends TValidator
166
+ ? TValidator
167
+ : Constrain<TValidator, ValidatorSerializerStringify<TValidator>>
168
+
169
+ export interface ServerFnMiddleware<TMethod extends Method, TValidator> {
170
+ middleware: <const TNewMiddlewares = undefined>(
171
+ middlewares: Constrain<TNewMiddlewares, ReadonlyArray<AnyMiddleware>>,
172
+ ) => ServerFnAfterMiddleware<TMethod, TNewMiddlewares, TValidator>
173
+ }
174
+
175
+ export interface ServerFnAfterMiddleware<
176
+ TMethod extends Method,
177
+ TMiddlewares,
178
+ TValidator,
179
+ > extends ServerFnValidator<TMethod, TMiddlewares>,
180
+ ServerFnTyper<TMethod, TMiddlewares, TValidator>,
181
+ ServerFnHandler<TMethod, TMiddlewares, TValidator> {}
182
+
183
+ export type ValidatorFn<TMethod extends Method, TMiddlewares> = <TValidator>(
184
+ validator: ConstrainValidator<TValidator>,
185
+ ) => ServerFnAfterValidator<TMethod, TMiddlewares, TValidator>
186
+
187
+ export interface ServerFnValidator<TMethod extends Method, TMiddlewares> {
188
+ validator: ValidatorFn<TMethod, TMiddlewares>
189
+ }
190
+
191
+ export interface ServerFnAfterValidator<
192
+ TMethod extends Method,
193
+ TMiddlewares,
194
+ TValidator,
195
+ > extends ServerFnMiddleware<TMethod, TValidator>,
196
+ ServerFnTyper<TMethod, TMiddlewares, TValidator>,
197
+ ServerFnHandler<TMethod, TMiddlewares, TValidator> {}
198
+
199
+ // Typer
200
+ export interface ServerFnTyper<
201
+ TMethod extends Method,
202
+ TMiddlewares,
203
+ TValidator,
204
+ > {
205
+ type: (
206
+ typer: ServerFnTypeOrTypeFn<TMethod, TMiddlewares, TValidator>,
207
+ ) => ServerFnAfterTyper<TMethod, TMiddlewares, TValidator>
208
+ }
209
+
210
+ export type ServerFnTypeOrTypeFn<
211
+ TMethod extends Method,
212
+ TMiddlewares,
213
+ TValidator,
214
+ > =
215
+ | ServerFnType
216
+ | ((ctx: ServerFnCtx<TMethod, TMiddlewares, TValidator>) => ServerFnType)
217
+
218
+ export interface ServerFnAfterTyper<
219
+ TMethod extends Method,
220
+ TMiddlewares,
221
+ TValidator,
222
+ > extends ServerFnHandler<TMethod, TMiddlewares, TValidator> {}
223
+
224
+ // Handler
225
+ export interface ServerFnHandler<
226
+ TMethod extends Method,
227
+ TMiddlewares,
228
+ TValidator,
229
+ > {
230
+ handler: <TNewResponse>(
231
+ fn?: ServerFn<TMethod, TMiddlewares, TValidator, TNewResponse>,
232
+ ) => Fetcher<TMiddlewares, TValidator, TNewResponse>
233
+ }
234
+
235
+ export interface ServerFnBuilder<TMethod extends Method = 'GET'>
236
+ extends ServerFnMiddleware<TMethod, undefined>,
237
+ ServerFnValidator<TMethod, undefined>,
238
+ ServerFnTyper<TMethod, undefined, undefined>,
239
+ ServerFnHandler<TMethod, undefined, undefined> {
240
+ options: ServerFnBaseOptions<TMethod, unknown, undefined, undefined>
241
+ }
242
+
243
+ type StaticCachedResult = {
244
+ ctx?: {
245
+ result: any
246
+ context: any
247
+ }
248
+ error?: any
249
+ }
250
+
251
+ export type ServerFnStaticCache = {
252
+ getItem: (
253
+ ctx: MiddlewareResult,
254
+ ) => StaticCachedResult | Promise<StaticCachedResult | undefined>
255
+ setItem: (
256
+ ctx: MiddlewareResult,
257
+ response: StaticCachedResult,
258
+ ) => Promise<void>
259
+ fetchItem: (
260
+ ctx: MiddlewareResult,
261
+ ) => StaticCachedResult | Promise<StaticCachedResult | undefined>
262
+ }
263
+
264
+ let serverFnStaticCache: ServerFnStaticCache | undefined
265
+
266
+ export function setServerFnStaticCache(
267
+ cache?: ServerFnStaticCache | (() => ServerFnStaticCache | undefined),
268
+ ) {
269
+ const previousCache = serverFnStaticCache
270
+ serverFnStaticCache = typeof cache === 'function' ? cache() : cache
271
+
272
+ return () => {
273
+ serverFnStaticCache = previousCache
274
+ }
275
+ }
276
+
277
+ export function createServerFnStaticCache(
278
+ serverFnStaticCache: ServerFnStaticCache,
279
+ ) {
280
+ return serverFnStaticCache
281
+ }
282
+
283
+ setServerFnStaticCache(() => {
284
+ const getStaticCacheUrl = (options: MiddlewareResult, hash: string) => {
285
+ return `/__tsr/staticServerFnCache/${options.functionId}__${hash}.json`
286
+ }
287
+
288
+ const jsonToFilenameSafeString = (json: any) => {
289
+ // Custom replacer to sort keys
290
+ const sortedKeysReplacer = (key: string, value: any) =>
291
+ value && typeof value === 'object' && !Array.isArray(value)
292
+ ? Object.keys(value)
293
+ .sort()
294
+ .reduce((acc: any, curr: string) => {
295
+ acc[curr] = value[curr]
296
+ return acc
297
+ }, {})
298
+ : value
299
+
300
+ // Convert JSON to string with sorted keys
301
+ const jsonString = JSON.stringify(json ?? '', sortedKeysReplacer)
302
+
303
+ // Replace characters invalid in filenames
304
+ return jsonString
305
+ .replace(/[/\\?%*:|"<>]/g, '-') // Replace invalid characters with a dash
306
+ .replace(/\s+/g, '_') // Optionally replace whitespace with underscores
307
+ }
308
+
309
+ const staticClientCache =
310
+ typeof document !== 'undefined' ? new Map<string, any>() : null
311
+
312
+ return createServerFnStaticCache({
313
+ getItem: async (ctx) => {
314
+ if (typeof document === 'undefined') {
315
+ const hash = jsonToFilenameSafeString(ctx.data)
316
+ const url = getStaticCacheUrl(ctx, hash)
317
+ const publicUrl = process.env.TSS_OUTPUT_PUBLIC_DIR!
318
+
319
+ // Use fs instead of fetch to read from filesystem
320
+ const { promises: fs } = await import('node:fs')
321
+ const path = await import('node:path')
322
+ const filePath = path.join(publicUrl, url)
323
+
324
+ const [cachedResult, readError] = await fs
325
+ .readFile(filePath, 'utf-8')
326
+ .then((c) => [
327
+ startSerializer.parse(c) as {
328
+ ctx: unknown
329
+ error: any
330
+ },
331
+ null,
332
+ ])
333
+ .catch((e) => [null, e])
334
+
335
+ if (readError && readError.code !== 'ENOENT') {
336
+ throw readError
337
+ }
338
+
339
+ return cachedResult as StaticCachedResult
340
+ }
341
+
342
+ return undefined
343
+ },
344
+ setItem: async (ctx, response) => {
345
+ const { promises: fs } = await import('node:fs')
346
+ const path = await import('node:path')
347
+
348
+ const hash = jsonToFilenameSafeString(ctx.data)
349
+ const url = getStaticCacheUrl(ctx, hash)
350
+ const publicUrl = process.env.TSS_OUTPUT_PUBLIC_DIR!
351
+ const filePath = path.join(publicUrl, url)
352
+
353
+ // Ensure the directory exists
354
+ await fs.mkdir(path.dirname(filePath), { recursive: true })
355
+
356
+ // Store the result with fs
357
+ await fs.writeFile(filePath, startSerializer.stringify(response))
358
+ },
359
+ fetchItem: async (ctx) => {
360
+ const hash = jsonToFilenameSafeString(ctx.data)
361
+ const url = getStaticCacheUrl(ctx, hash)
362
+
363
+ let result: any = staticClientCache?.get(url)
364
+
365
+ if (!result) {
366
+ result = await fetch(url, {
367
+ method: 'GET',
368
+ })
369
+ .then((r) => r.text())
370
+ .then((d) => startSerializer.parse(d))
371
+
372
+ staticClientCache?.set(url, result)
373
+ }
374
+
375
+ return result
376
+ },
377
+ })
378
+ })
379
+
380
+ export function createServerFn<
381
+ TMethod extends Method,
382
+ TResponse = unknown,
383
+ TMiddlewares = undefined,
384
+ TValidator = undefined,
385
+ >(
386
+ options?: {
387
+ method?: TMethod
388
+ type?: ServerFnType
389
+ },
390
+ __opts?: ServerFnBaseOptions<TMethod, TResponse, TMiddlewares, TValidator>,
391
+ ): ServerFnBuilder<TMethod> {
392
+ const resolvedOptions = (__opts || options || {}) as ServerFnBaseOptions<
393
+ TMethod,
394
+ TResponse,
395
+ TMiddlewares,
396
+ TValidator
397
+ >
398
+
399
+ if (typeof resolvedOptions.method === 'undefined') {
400
+ resolvedOptions.method = 'GET' as TMethod
401
+ }
402
+
403
+ return {
404
+ options: resolvedOptions as any,
405
+ middleware: (middleware) => {
406
+ return createServerFn<TMethod, TResponse, TMiddlewares, TValidator>(
407
+ undefined,
408
+ Object.assign(resolvedOptions, { middleware }),
409
+ ) as any
410
+ },
411
+ validator: (validator) => {
412
+ return createServerFn<TMethod, TResponse, TMiddlewares, TValidator>(
413
+ undefined,
414
+ Object.assign(resolvedOptions, { validator }),
415
+ ) as any
416
+ },
417
+ type: (type) => {
418
+ return createServerFn<TMethod, TResponse, TMiddlewares, TValidator>(
419
+ undefined,
420
+ Object.assign(resolvedOptions, { type }),
421
+ ) as any
422
+ },
423
+ handler: (...args) => {
424
+ // This function signature changes due to AST transformations
425
+ // in the babel plugin. We need to cast it to the correct
426
+ // function signature post-transformation
427
+ const [extractedFn, serverFn] = args as unknown as [
428
+ CompiledFetcherFn<TResponse>,
429
+ ServerFn<TMethod, TMiddlewares, TValidator, TResponse>,
430
+ ]
431
+
432
+ // Keep the original function around so we can use it
433
+ // in the server environment
434
+ Object.assign(resolvedOptions, {
435
+ ...extractedFn,
436
+ extractedFn,
437
+ serverFn,
438
+ })
439
+
440
+ const resolvedMiddleware = [
441
+ ...(resolvedOptions.middleware || []),
442
+ serverFnBaseToMiddleware(resolvedOptions),
443
+ ]
444
+
445
+ // We want to make sure the new function has the same
446
+ // properties as the original function
447
+ return Object.assign(
448
+ async (opts?: CompiledFetcherFnOptions) => {
449
+ // Start by executing the client-side middleware chain
450
+ return executeMiddleware(resolvedMiddleware, 'client', {
451
+ ...extractedFn,
452
+ ...resolvedOptions,
453
+ data: opts?.data as any,
454
+ headers: opts?.headers,
455
+ signal: opts?.signal,
456
+ context: {},
457
+ }).then((d) => {
458
+ if (d.error) throw d.error
459
+ return d.result
460
+ })
461
+ },
462
+ {
463
+ // This copies over the URL, function ID
464
+ ...extractedFn,
465
+ // The extracted function on the server-side calls
466
+ // this function
467
+ __executeServer: async (opts_: any, signal: AbortSignal) => {
468
+ const opts =
469
+ opts_ instanceof FormData ? extractFormDataContext(opts_) : opts_
470
+
471
+ opts.type =
472
+ typeof resolvedOptions.type === 'function'
473
+ ? resolvedOptions.type(opts)
474
+ : resolvedOptions.type
475
+
476
+ const ctx = {
477
+ ...extractedFn,
478
+ ...opts,
479
+ signal,
480
+ }
481
+
482
+ const run = () =>
483
+ executeMiddleware(resolvedMiddleware, 'server', ctx).then(
484
+ (d) => ({
485
+ // Only send the result and sendContext back to the client
486
+ result: d.result,
487
+ error: d.error,
488
+ context: d.sendContext,
489
+ }),
490
+ )
491
+
492
+ if (ctx.type === 'static') {
493
+ let response: StaticCachedResult | undefined
494
+
495
+ // If we can get the cached item, try to get it
496
+ if (serverFnStaticCache?.getItem) {
497
+ // If this throws, it's okay to let it bubble up
498
+ response = await serverFnStaticCache.getItem(ctx)
499
+ }
500
+
501
+ if (!response) {
502
+ // If there's no cached item, execute the server function
503
+ response = await run()
504
+ .then((d) => {
505
+ return {
506
+ ctx: d,
507
+ error: null,
508
+ }
509
+ })
510
+ .catch((e) => {
511
+ return {
512
+ ctx: undefined,
513
+ error: e,
514
+ }
515
+ })
516
+
517
+ if (serverFnStaticCache?.setItem) {
518
+ await serverFnStaticCache.setItem(ctx, response)
519
+ }
520
+ }
521
+
522
+ invariant(
523
+ response,
524
+ 'No response from both server and static cache!',
525
+ )
526
+
527
+ if (response.error) {
528
+ throw response.error
529
+ }
530
+
531
+ return response.ctx
532
+ }
533
+
534
+ return run()
535
+ },
536
+ },
537
+ ) as any
538
+ },
539
+ }
540
+ }
541
+
542
+ function extractFormDataContext(formData: FormData) {
543
+ const serializedContext = formData.get('__TSR_CONTEXT')
544
+ formData.delete('__TSR_CONTEXT')
545
+
546
+ if (typeof serializedContext !== 'string') {
547
+ return {
548
+ context: {},
549
+ data: formData,
550
+ }
551
+ }
552
+
553
+ try {
554
+ const context = startSerializer.parse(serializedContext)
555
+ return {
556
+ context,
557
+ data: formData,
558
+ }
559
+ } catch {
560
+ return {
561
+ data: formData,
562
+ }
563
+ }
564
+ }
565
+
566
+ function flattenMiddlewares(
567
+ middlewares: Array<AnyMiddleware>,
568
+ ): Array<AnyMiddleware> {
569
+ const seen = new Set<AnyMiddleware>()
570
+ const flattened: Array<AnyMiddleware> = []
571
+
572
+ const recurse = (middleware: Array<AnyMiddleware>) => {
573
+ middleware.forEach((m) => {
574
+ if (m.options.middleware) {
575
+ recurse(m.options.middleware)
576
+ }
577
+
578
+ if (!seen.has(m)) {
579
+ seen.add(m)
580
+ flattened.push(m)
581
+ }
582
+ })
583
+ }
584
+
585
+ recurse(middlewares)
586
+
587
+ return flattened
588
+ }
589
+
590
+ export type MiddlewareOptions = {
591
+ method: Method
592
+ data: any
593
+ headers?: HeadersInit
594
+ signal?: AbortSignal
595
+ sendContext?: any
596
+ context?: any
597
+ type: ServerFnTypeOrTypeFn<any, any, any>
598
+ functionId: string
599
+ }
600
+
601
+ export type MiddlewareResult = MiddlewareOptions & {
602
+ result?: unknown
603
+ error?: unknown
604
+ type: ServerFnTypeOrTypeFn<any, any, any>
605
+ }
606
+
607
+ export type NextFn = (ctx: MiddlewareResult) => Promise<MiddlewareResult>
608
+
609
+ export type MiddlewareFn = (
610
+ ctx: MiddlewareOptions & {
611
+ next: NextFn
612
+ },
613
+ ) => Promise<MiddlewareResult>
614
+
615
+ const applyMiddleware = async (
616
+ middlewareFn: MiddlewareFn,
617
+ ctx: MiddlewareOptions,
618
+ nextFn: NextFn,
619
+ ) => {
620
+ return middlewareFn({
621
+ ...ctx,
622
+ next: (async (userCtx: MiddlewareResult | undefined = {} as any) => {
623
+ // Return the next middleware
624
+ return nextFn({
625
+ ...ctx,
626
+ ...userCtx,
627
+ context: {
628
+ ...ctx.context,
629
+ ...userCtx.context,
630
+ },
631
+ sendContext: {
632
+ ...ctx.sendContext,
633
+ ...(userCtx.sendContext ?? {}),
634
+ },
635
+ headers: mergeHeaders(ctx.headers, userCtx.headers),
636
+ result:
637
+ userCtx.result !== undefined ? userCtx.result : (ctx as any).result,
638
+ error: userCtx.error ?? (ctx as any).error,
639
+ })
640
+ }) as any,
641
+ } as any)
642
+ }
643
+
644
+ function execValidator(validator: AnyValidator, input: unknown): unknown {
645
+ if (validator == null) return {}
646
+
647
+ if ('~standard' in validator) {
648
+ const result = validator['~standard'].validate(input)
649
+
650
+ if (result instanceof Promise)
651
+ throw new Error('Async validation not supported')
652
+
653
+ if (result.issues)
654
+ throw new Error(JSON.stringify(result.issues, undefined, 2))
655
+
656
+ return result.value
657
+ }
658
+
659
+ if ('parse' in validator) {
660
+ return validator.parse(input)
661
+ }
662
+
663
+ if (typeof validator === 'function') {
664
+ return validator(input)
665
+ }
666
+
667
+ throw new Error('Invalid validator type!')
668
+ }
669
+
670
+ async function executeMiddleware(
671
+ middlewares: Array<AnyMiddleware>,
672
+ env: 'client' | 'server',
673
+ opts: MiddlewareOptions,
674
+ ): Promise<MiddlewareResult> {
675
+ const flattenedMiddlewares = flattenMiddlewares([
676
+ ...globalMiddleware,
677
+ ...middlewares,
678
+ ])
679
+
680
+ const next: NextFn = async (ctx) => {
681
+ // Get the next middleware
682
+ const nextMiddleware = flattenedMiddlewares.shift()
683
+
684
+ // If there are no more middlewares, return the context
685
+ if (!nextMiddleware) {
686
+ return ctx
687
+ }
688
+
689
+ if (
690
+ nextMiddleware.options.validator &&
691
+ (env === 'client' ? nextMiddleware.options.validateClient : true)
692
+ ) {
693
+ // Execute the middleware's input function
694
+ ctx.data = await execValidator(nextMiddleware.options.validator, ctx.data)
695
+ }
696
+
697
+ const middlewareFn = (
698
+ env === 'client'
699
+ ? nextMiddleware.options.client
700
+ : nextMiddleware.options.server
701
+ ) as MiddlewareFn | undefined
702
+
703
+ if (middlewareFn) {
704
+ // Execute the middleware
705
+ return applyMiddleware(middlewareFn, ctx, async (newCtx) => {
706
+ return next(newCtx).catch((error) => {
707
+ if (isRedirect(error) || isNotFound(error)) {
708
+ return {
709
+ ...newCtx,
710
+ error,
711
+ }
712
+ }
713
+
714
+ throw error
715
+ })
716
+ })
717
+ }
718
+
719
+ return next(ctx)
720
+ }
721
+
722
+ // Start the middleware chain
723
+ return next({
724
+ ...opts,
725
+ headers: opts.headers || {},
726
+ sendContext: opts.sendContext || {},
727
+ context: opts.context || {},
728
+ })
729
+ }
730
+
731
+ function serverFnBaseToMiddleware(
732
+ options: ServerFnBaseOptions<any, any, any, any>,
733
+ ): AnyMiddleware {
734
+ return {
735
+ _types: undefined!,
736
+ options: {
737
+ validator: options.validator,
738
+ validateClient: options.validateClient,
739
+ client: async ({ next, sendContext, ...ctx }) => {
740
+ const payload = {
741
+ ...ctx,
742
+ // switch the sendContext over to context
743
+ context: sendContext,
744
+ type: typeof ctx.type === 'function' ? ctx.type(ctx) : ctx.type,
745
+ } as any
746
+
747
+ if (
748
+ ctx.type === 'static' &&
749
+ process.env.NODE_ENV === 'production' &&
750
+ typeof document !== 'undefined'
751
+ ) {
752
+ invariant(
753
+ serverFnStaticCache,
754
+ 'serverFnStaticCache.fetchItem is not available!',
755
+ )
756
+
757
+ const result = await serverFnStaticCache.fetchItem(payload)
758
+
759
+ if (result) {
760
+ if (result.error) {
761
+ throw result.error
762
+ }
763
+
764
+ return next(result.ctx)
765
+ }
766
+
767
+ warning(
768
+ result,
769
+ `No static cache item found for ${payload.functionId}__${JSON.stringify(payload.data)}, falling back to server function...`,
770
+ )
771
+ }
772
+
773
+ // Execute the extracted function
774
+ // but not before serializing the context
775
+ const res = await options.extractedFn?.(payload)
776
+
777
+ return next(res) as unknown as MiddlewareClientFnResult<any, any, any>
778
+ },
779
+ server: async ({ next, ...ctx }) => {
780
+ // Execute the server function
781
+ const result = await options.serverFn?.(ctx)
782
+
783
+ return next({
784
+ ...ctx,
785
+ result,
786
+ } as any) as unknown as MiddlewareServerFnResult<any, any, any, any>
787
+ },
788
+ },
789
+ }
790
+ }