@tanstack/start-client-core 1.20.3-alpha.1

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 (91) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +12 -0
  3. package/dist/cjs/createIsomorphicFn.cjs +7 -0
  4. package/dist/cjs/createIsomorphicFn.cjs.map +1 -0
  5. package/dist/cjs/createIsomorphicFn.d.cts +12 -0
  6. package/dist/cjs/createMiddleware.cjs +37 -0
  7. package/dist/cjs/createMiddleware.cjs.map +1 -0
  8. package/dist/cjs/createMiddleware.d.cts +175 -0
  9. package/dist/cjs/createServerFn.cjs +378 -0
  10. package/dist/cjs/createServerFn.cjs.map +1 -0
  11. package/dist/cjs/createServerFn.d.cts +159 -0
  12. package/dist/cjs/envOnly.cjs +7 -0
  13. package/dist/cjs/envOnly.cjs.map +1 -0
  14. package/dist/cjs/envOnly.d.cts +4 -0
  15. package/dist/cjs/headers.cjs +30 -0
  16. package/dist/cjs/headers.cjs.map +1 -0
  17. package/dist/cjs/headers.d.cts +5 -0
  18. package/dist/cjs/index.cjs +33 -0
  19. package/dist/cjs/index.cjs.map +1 -0
  20. package/dist/cjs/index.d.cts +11 -0
  21. package/dist/cjs/json.cjs +14 -0
  22. package/dist/cjs/json.cjs.map +1 -0
  23. package/dist/cjs/json.d.cts +2 -0
  24. package/dist/cjs/registerGlobalMiddleware.cjs +9 -0
  25. package/dist/cjs/registerGlobalMiddleware.cjs.map +1 -0
  26. package/dist/cjs/registerGlobalMiddleware.d.cts +5 -0
  27. package/dist/cjs/serializer.cjs +152 -0
  28. package/dist/cjs/serializer.cjs.map +1 -0
  29. package/dist/cjs/serializer.d.cts +2 -0
  30. package/dist/cjs/ssr-client.cjs +130 -0
  31. package/dist/cjs/ssr-client.cjs.map +1 -0
  32. package/dist/cjs/ssr-client.d.cts +64 -0
  33. package/dist/cjs/tests/createIsomorphicFn.test-d.d.cts +1 -0
  34. package/dist/cjs/tests/createServerFn.test-d.d.cts +1 -0
  35. package/dist/cjs/tests/createServerMiddleware.test-d.d.cts +1 -0
  36. package/dist/cjs/tests/envOnly.test-d.d.cts +1 -0
  37. package/dist/cjs/tests/json.test.d.cts +1 -0
  38. package/dist/cjs/tests/transformer.test.d.cts +1 -0
  39. package/dist/esm/createIsomorphicFn.d.ts +12 -0
  40. package/dist/esm/createIsomorphicFn.js +7 -0
  41. package/dist/esm/createIsomorphicFn.js.map +1 -0
  42. package/dist/esm/createMiddleware.d.ts +175 -0
  43. package/dist/esm/createMiddleware.js +37 -0
  44. package/dist/esm/createMiddleware.js.map +1 -0
  45. package/dist/esm/createServerFn.d.ts +159 -0
  46. package/dist/esm/createServerFn.js +356 -0
  47. package/dist/esm/createServerFn.js.map +1 -0
  48. package/dist/esm/envOnly.d.ts +4 -0
  49. package/dist/esm/envOnly.js +7 -0
  50. package/dist/esm/envOnly.js.map +1 -0
  51. package/dist/esm/headers.d.ts +5 -0
  52. package/dist/esm/headers.js +30 -0
  53. package/dist/esm/headers.js.map +1 -0
  54. package/dist/esm/index.d.ts +11 -0
  55. package/dist/esm/index.js +30 -0
  56. package/dist/esm/index.js.map +1 -0
  57. package/dist/esm/json.d.ts +2 -0
  58. package/dist/esm/json.js +14 -0
  59. package/dist/esm/json.js.map +1 -0
  60. package/dist/esm/registerGlobalMiddleware.d.ts +5 -0
  61. package/dist/esm/registerGlobalMiddleware.js +9 -0
  62. package/dist/esm/registerGlobalMiddleware.js.map +1 -0
  63. package/dist/esm/serializer.d.ts +2 -0
  64. package/dist/esm/serializer.js +152 -0
  65. package/dist/esm/serializer.js.map +1 -0
  66. package/dist/esm/ssr-client.d.ts +64 -0
  67. package/dist/esm/ssr-client.js +130 -0
  68. package/dist/esm/ssr-client.js.map +1 -0
  69. package/dist/esm/tests/createIsomorphicFn.test-d.d.ts +1 -0
  70. package/dist/esm/tests/createServerFn.test-d.d.ts +1 -0
  71. package/dist/esm/tests/createServerMiddleware.test-d.d.ts +1 -0
  72. package/dist/esm/tests/envOnly.test-d.d.ts +1 -0
  73. package/dist/esm/tests/json.test.d.ts +1 -0
  74. package/dist/esm/tests/transformer.test.d.ts +1 -0
  75. package/package.json +56 -0
  76. package/src/createIsomorphicFn.ts +36 -0
  77. package/src/createMiddleware.ts +706 -0
  78. package/src/createServerFn.ts +1004 -0
  79. package/src/envOnly.ts +9 -0
  80. package/src/headers.ts +50 -0
  81. package/src/index.tsx +88 -0
  82. package/src/json.ts +15 -0
  83. package/src/registerGlobalMiddleware.ts +9 -0
  84. package/src/serializer.ts +177 -0
  85. package/src/ssr-client.tsx +243 -0
  86. package/src/tests/createIsomorphicFn.test-d.ts +72 -0
  87. package/src/tests/createServerFn.test-d.ts +519 -0
  88. package/src/tests/createServerMiddleware.test-d.ts +736 -0
  89. package/src/tests/envOnly.test-d.ts +34 -0
  90. package/src/tests/json.test.ts +37 -0
  91. package/src/tests/transformer.test.tsx +147 -0
@@ -0,0 +1,1004 @@
1
+ import { default as invariant } from 'tiny-invariant'
2
+ import { default as warning } from 'tiny-warning'
3
+ import { isNotFound, isRedirect } from '@tanstack/router-core'
4
+ import { startSerializer } from './serializer'
5
+ import { mergeHeaders } from './headers'
6
+ import { globalMiddleware } from './registerGlobalMiddleware'
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 { Readable } from 'node:stream'
18
+ import type {
19
+ AnyFunctionMiddleware,
20
+ AssignAllClientSendContext,
21
+ AssignAllServerContext,
22
+ FunctionMiddlewareClientFnResult,
23
+ FunctionMiddlewareServerFnResult,
24
+ IntersectAllValidatorInputs,
25
+ IntersectAllValidatorOutputs,
26
+ } from './createMiddleware'
27
+
28
+ type TODO = any
29
+
30
+ export function createServerFn<
31
+ TMethod extends Method,
32
+ TServerFnResponseType extends ServerFnResponseType = 'data',
33
+ TResponse = unknown,
34
+ TMiddlewares = undefined,
35
+ TValidator = undefined,
36
+ >(
37
+ options?: {
38
+ method?: TMethod
39
+ response?: TServerFnResponseType
40
+ type?: ServerFnType
41
+ },
42
+ __opts?: ServerFnBaseOptions<
43
+ TMethod,
44
+ TServerFnResponseType,
45
+ TResponse,
46
+ TMiddlewares,
47
+ TValidator
48
+ >,
49
+ ): ServerFnBuilder<TMethod, TServerFnResponseType> {
50
+ const resolvedOptions = (__opts || options || {}) as ServerFnBaseOptions<
51
+ TMethod,
52
+ ServerFnResponseType,
53
+ TResponse,
54
+ TMiddlewares,
55
+ TValidator
56
+ >
57
+
58
+ if (typeof resolvedOptions.method === 'undefined') {
59
+ resolvedOptions.method = 'GET' as TMethod
60
+ }
61
+
62
+ return {
63
+ options: resolvedOptions as any,
64
+ middleware: (middleware) => {
65
+ return createServerFn<
66
+ TMethod,
67
+ ServerFnResponseType,
68
+ TResponse,
69
+ TMiddlewares,
70
+ TValidator
71
+ >(undefined, Object.assign(resolvedOptions, { middleware })) as any
72
+ },
73
+ validator: (validator) => {
74
+ return createServerFn<
75
+ TMethod,
76
+ ServerFnResponseType,
77
+ TResponse,
78
+ TMiddlewares,
79
+ TValidator
80
+ >(undefined, Object.assign(resolvedOptions, { validator })) as any
81
+ },
82
+ type: (type) => {
83
+ return createServerFn<
84
+ TMethod,
85
+ ServerFnResponseType,
86
+ TResponse,
87
+ TMiddlewares,
88
+ TValidator
89
+ >(undefined, Object.assign(resolvedOptions, { type })) as any
90
+ },
91
+ handler: (...args) => {
92
+ // This function signature changes due to AST transformations
93
+ // in the babel plugin. We need to cast it to the correct
94
+ // function signature post-transformation
95
+ const [extractedFn, serverFn] = args as unknown as [
96
+ CompiledFetcherFn<TResponse, TServerFnResponseType>,
97
+ ServerFn<
98
+ TMethod,
99
+ TServerFnResponseType,
100
+ TMiddlewares,
101
+ TValidator,
102
+ TResponse
103
+ >,
104
+ ]
105
+
106
+ // Keep the original function around so we can use it
107
+ // in the server environment
108
+ Object.assign(resolvedOptions, {
109
+ ...extractedFn,
110
+ extractedFn,
111
+ serverFn,
112
+ })
113
+
114
+ const resolvedMiddleware = [
115
+ ...(resolvedOptions.middleware || []),
116
+ serverFnBaseToMiddleware(resolvedOptions),
117
+ ]
118
+
119
+ // We want to make sure the new function has the same
120
+ // properties as the original function
121
+
122
+ return Object.assign(
123
+ async (opts?: CompiledFetcherFnOptions) => {
124
+ // Start by executing the client-side middleware chain
125
+ return executeMiddleware(resolvedMiddleware, 'client', {
126
+ ...extractedFn,
127
+ ...resolvedOptions,
128
+ data: opts?.data as any,
129
+ headers: opts?.headers,
130
+ signal: opts?.signal,
131
+ context: {},
132
+ }).then((d) => {
133
+ if (resolvedOptions.response === 'full') {
134
+ return d
135
+ }
136
+ if (d.error) throw d.error
137
+ return d.result
138
+ })
139
+ },
140
+ {
141
+ // This copies over the URL, function ID
142
+ ...extractedFn,
143
+ // The extracted function on the server-side calls
144
+ // this function
145
+ __executeServer: async (opts_: any, signal: AbortSignal) => {
146
+ const opts =
147
+ opts_ instanceof FormData ? extractFormDataContext(opts_) : opts_
148
+
149
+ opts.type =
150
+ typeof resolvedOptions.type === 'function'
151
+ ? resolvedOptions.type(opts)
152
+ : resolvedOptions.type
153
+
154
+ const ctx = {
155
+ ...extractedFn,
156
+ ...opts,
157
+ signal,
158
+ }
159
+
160
+ const run = () =>
161
+ executeMiddleware(resolvedMiddleware, 'server', ctx).then(
162
+ (d) => ({
163
+ // Only send the result and sendContext back to the client
164
+ result: d.result,
165
+ error: d.error,
166
+ context: d.sendContext,
167
+ }),
168
+ )
169
+
170
+ if (ctx.type === 'static') {
171
+ let response: StaticCachedResult | undefined
172
+
173
+ // If we can get the cached item, try to get it
174
+ if (serverFnStaticCache?.getItem) {
175
+ // If this throws, it's okay to let it bubble up
176
+ response = await serverFnStaticCache.getItem(ctx)
177
+ }
178
+
179
+ if (!response) {
180
+ // If there's no cached item, execute the server function
181
+ response = await run()
182
+ .then((d) => {
183
+ return {
184
+ ctx: d,
185
+ error: null,
186
+ }
187
+ })
188
+ .catch((e) => {
189
+ return {
190
+ ctx: undefined,
191
+ error: e,
192
+ }
193
+ })
194
+
195
+ if (serverFnStaticCache?.setItem) {
196
+ await serverFnStaticCache.setItem(ctx, response)
197
+ }
198
+ }
199
+
200
+ invariant(
201
+ response,
202
+ 'No response from both server and static cache!',
203
+ )
204
+
205
+ if (response.error) {
206
+ throw response.error
207
+ }
208
+
209
+ return response.ctx
210
+ }
211
+
212
+ return run()
213
+ },
214
+ },
215
+ ) as any
216
+ },
217
+ }
218
+ }
219
+
220
+ export async function executeMiddleware(
221
+ middlewares: Array<AnyFunctionMiddleware>,
222
+ env: 'client' | 'server',
223
+ opts: ServerFnMiddlewareOptions,
224
+ ): Promise<ServerFnMiddlewareResult> {
225
+ const flattenedMiddlewares = flattenMiddlewares([
226
+ ...globalMiddleware,
227
+ ...middlewares,
228
+ ])
229
+
230
+ const next: NextFn = async (ctx) => {
231
+ // Get the next middleware
232
+ const nextMiddleware = flattenedMiddlewares.shift()
233
+
234
+ // If there are no more middlewares, return the context
235
+ if (!nextMiddleware) {
236
+ return ctx
237
+ }
238
+
239
+ if (
240
+ nextMiddleware.options.validator &&
241
+ (env === 'client' ? nextMiddleware.options.validateClient : true)
242
+ ) {
243
+ // Execute the middleware's input function
244
+ ctx.data = await execValidator(nextMiddleware.options.validator, ctx.data)
245
+ }
246
+
247
+ const middlewareFn = (
248
+ env === 'client'
249
+ ? nextMiddleware.options.client
250
+ : nextMiddleware.options.server
251
+ ) as MiddlewareFn | undefined
252
+
253
+ if (middlewareFn) {
254
+ // Execute the middleware
255
+ return applyMiddleware(middlewareFn, ctx, async (newCtx) => {
256
+ return next(newCtx).catch((error: any) => {
257
+ if (isRedirect(error) || isNotFound(error)) {
258
+ return {
259
+ ...newCtx,
260
+ error,
261
+ }
262
+ }
263
+
264
+ throw error
265
+ })
266
+ })
267
+ }
268
+
269
+ return next(ctx)
270
+ }
271
+
272
+ // Start the middleware chain
273
+ return next({
274
+ ...opts,
275
+ headers: opts.headers || {},
276
+ sendContext: opts.sendContext || {},
277
+ context: opts.context || {},
278
+ })
279
+ }
280
+
281
+ export interface JsonResponse<TData> extends Response {
282
+ json: () => Promise<TData>
283
+ }
284
+
285
+ export type CompiledFetcherFnOptions = {
286
+ method: Method
287
+ data: unknown
288
+ response?: ServerFnResponseType
289
+ headers?: HeadersInit
290
+ signal?: AbortSignal
291
+ context?: any
292
+ }
293
+
294
+ export type Fetcher<
295
+ TMiddlewares,
296
+ TValidator,
297
+ TResponse,
298
+ TServerFnResponseType extends ServerFnResponseType,
299
+ > =
300
+ undefined extends IntersectAllValidatorInputs<TMiddlewares, TValidator>
301
+ ? OptionalFetcher<
302
+ TMiddlewares,
303
+ TValidator,
304
+ TResponse,
305
+ TServerFnResponseType
306
+ >
307
+ : RequiredFetcher<
308
+ TMiddlewares,
309
+ TValidator,
310
+ TResponse,
311
+ TServerFnResponseType
312
+ >
313
+
314
+ export interface FetcherBase {
315
+ url: string
316
+ __executeServer: (opts: {
317
+ method: Method
318
+ response?: ServerFnResponseType
319
+ data: unknown
320
+ headers?: HeadersInit
321
+ context?: any
322
+ signal: AbortSignal
323
+ }) => Promise<unknown>
324
+ }
325
+
326
+ export type FetchResult<
327
+ TMiddlewares,
328
+ TResponse,
329
+ TServerFnResponseType extends ServerFnResponseType,
330
+ > = TServerFnResponseType extends 'raw'
331
+ ? Promise<Response>
332
+ : TServerFnResponseType extends 'full'
333
+ ? Promise<FullFetcherData<TMiddlewares, TResponse>>
334
+ : Promise<FetcherData<TResponse>>
335
+
336
+ export interface OptionalFetcher<
337
+ TMiddlewares,
338
+ TValidator,
339
+ TResponse,
340
+ TServerFnResponseType extends ServerFnResponseType,
341
+ > extends FetcherBase {
342
+ (
343
+ options?: OptionalFetcherDataOptions<TMiddlewares, TValidator>,
344
+ ): FetchResult<TMiddlewares, TResponse, TServerFnResponseType>
345
+ }
346
+
347
+ export interface RequiredFetcher<
348
+ TMiddlewares,
349
+ TValidator,
350
+ TResponse,
351
+ TServerFnResponseType extends ServerFnResponseType,
352
+ > extends FetcherBase {
353
+ (
354
+ opts: RequiredFetcherDataOptions<TMiddlewares, TValidator>,
355
+ ): FetchResult<TMiddlewares, TResponse, TServerFnResponseType>
356
+ }
357
+
358
+ export type FetcherBaseOptions = {
359
+ headers?: HeadersInit
360
+ type?: ServerFnType
361
+ signal?: AbortSignal
362
+ }
363
+
364
+ export type ServerFnType = 'static' | 'dynamic'
365
+
366
+ export interface OptionalFetcherDataOptions<TMiddlewares, TValidator>
367
+ extends FetcherBaseOptions {
368
+ data?: Expand<IntersectAllValidatorInputs<TMiddlewares, TValidator>>
369
+ }
370
+
371
+ export interface RequiredFetcherDataOptions<TMiddlewares, TValidator>
372
+ extends FetcherBaseOptions {
373
+ data: Expand<IntersectAllValidatorInputs<TMiddlewares, TValidator>>
374
+ }
375
+
376
+ export interface FullFetcherData<TMiddlewares, TResponse> {
377
+ error: unknown
378
+ result: FetcherData<TResponse>
379
+ context: AssignAllClientSendContext<TMiddlewares>
380
+ }
381
+
382
+ export type FetcherData<TResponse> =
383
+ TResponse extends JsonResponse<any>
384
+ ? SerializerParse<ReturnType<TResponse['json']>>
385
+ : SerializerParse<TResponse>
386
+
387
+ export type RscStream<T> = {
388
+ __cacheState: T
389
+ }
390
+
391
+ export type Method = 'GET' | 'POST'
392
+ export type ServerFnResponseType = 'data' | 'full' | 'raw'
393
+
394
+ // see https://h3.unjs.io/guide/event-handler#responses-types
395
+ export type RawResponse = Response | ReadableStream | Readable | null | string
396
+
397
+ export type ServerFnReturnType<
398
+ TServerFnResponseType extends ServerFnResponseType,
399
+ TResponse,
400
+ > = TServerFnResponseType extends 'raw'
401
+ ? RawResponse | Promise<RawResponse>
402
+ : Promise<SerializerStringify<TResponse>> | SerializerStringify<TResponse>
403
+
404
+ export type ServerFn<
405
+ TMethod,
406
+ TServerFnResponseType extends ServerFnResponseType,
407
+ TMiddlewares,
408
+ TValidator,
409
+ TResponse,
410
+ > = (
411
+ ctx: ServerFnCtx<TMethod, TServerFnResponseType, TMiddlewares, TValidator>,
412
+ ) => ServerFnReturnType<TServerFnResponseType, TResponse>
413
+
414
+ export interface ServerFnCtx<
415
+ TMethod,
416
+ TServerFnResponseType extends ServerFnResponseType,
417
+ TMiddlewares,
418
+ TValidator,
419
+ > {
420
+ method: TMethod
421
+ response: TServerFnResponseType
422
+ data: Expand<IntersectAllValidatorOutputs<TMiddlewares, TValidator>>
423
+ context: Expand<AssignAllServerContext<TMiddlewares>>
424
+ signal: AbortSignal
425
+ }
426
+
427
+ export type CompiledFetcherFn<
428
+ TResponse,
429
+ TServerFnResponseType extends ServerFnResponseType,
430
+ > = {
431
+ (
432
+ opts: CompiledFetcherFnOptions &
433
+ ServerFnBaseOptions<Method, TServerFnResponseType>,
434
+ ): Promise<TResponse>
435
+ url: string
436
+ }
437
+
438
+ export type ServerFnBaseOptions<
439
+ TMethod extends Method = 'GET',
440
+ TServerFnResponseType extends ServerFnResponseType = 'data',
441
+ TResponse = unknown,
442
+ TMiddlewares = unknown,
443
+ TInput = unknown,
444
+ > = {
445
+ method: TMethod
446
+ response?: TServerFnResponseType
447
+ validateClient?: boolean
448
+ middleware?: Constrain<TMiddlewares, ReadonlyArray<AnyFunctionMiddleware>>
449
+ validator?: ConstrainValidator<TInput>
450
+ extractedFn?: CompiledFetcherFn<TResponse, TServerFnResponseType>
451
+ serverFn?: ServerFn<
452
+ TMethod,
453
+ TServerFnResponseType,
454
+ TMiddlewares,
455
+ TInput,
456
+ TResponse
457
+ >
458
+ functionId: string
459
+ type: ServerFnTypeOrTypeFn<
460
+ TMethod,
461
+ TServerFnResponseType,
462
+ TMiddlewares,
463
+ AnyValidator
464
+ >
465
+ }
466
+
467
+ export type ValidatorInputStringify<TValidator> = SerializerStringifyBy<
468
+ ResolveValidatorInput<TValidator>,
469
+ Date | undefined | FormData
470
+ >
471
+
472
+ export type ValidatorSerializerStringify<TValidator> =
473
+ ValidatorInputStringify<TValidator> extends infer TInput
474
+ ? Validator<TInput, any>
475
+ : never
476
+
477
+ export type ConstrainValidator<TValidator> =
478
+ | (unknown extends TValidator
479
+ ? TValidator
480
+ : ResolveValidatorInput<TValidator> extends ValidatorInputStringify<TValidator>
481
+ ? TValidator
482
+ : never)
483
+ | ValidatorSerializerStringify<TValidator>
484
+
485
+ export interface ServerFnMiddleware<
486
+ TMethod extends Method,
487
+ TServerFnResponseType extends ServerFnResponseType,
488
+ TValidator,
489
+ > {
490
+ middleware: <const TNewMiddlewares = undefined>(
491
+ middlewares: Constrain<
492
+ TNewMiddlewares,
493
+ ReadonlyArray<AnyFunctionMiddleware>
494
+ >,
495
+ ) => ServerFnAfterMiddleware<
496
+ TMethod,
497
+ TServerFnResponseType,
498
+ TNewMiddlewares,
499
+ TValidator
500
+ >
501
+ }
502
+
503
+ export interface ServerFnAfterMiddleware<
504
+ TMethod extends Method,
505
+ TServerFnResponseType extends ServerFnResponseType,
506
+ TMiddlewares,
507
+ TValidator,
508
+ > extends ServerFnValidator<TMethod, TServerFnResponseType, TMiddlewares>,
509
+ ServerFnTyper<TMethod, TServerFnResponseType, TMiddlewares, TValidator>,
510
+ ServerFnHandler<TMethod, TServerFnResponseType, TMiddlewares, TValidator> {}
511
+
512
+ export type ValidatorFn<
513
+ TMethod extends Method,
514
+ TServerFnResponseType extends ServerFnResponseType,
515
+ TMiddlewares,
516
+ > = <TValidator>(
517
+ validator: ConstrainValidator<TValidator>,
518
+ ) => ServerFnAfterValidator<
519
+ TMethod,
520
+ TServerFnResponseType,
521
+ TMiddlewares,
522
+ TValidator
523
+ >
524
+
525
+ export interface ServerFnValidator<
526
+ TMethod extends Method,
527
+ TServerFnResponseType extends ServerFnResponseType,
528
+ TMiddlewares,
529
+ > {
530
+ validator: ValidatorFn<TMethod, TServerFnResponseType, TMiddlewares>
531
+ }
532
+
533
+ export interface ServerFnAfterValidator<
534
+ TMethod extends Method,
535
+ TServerFnResponseType extends ServerFnResponseType,
536
+ TMiddlewares,
537
+ TValidator,
538
+ > extends ServerFnMiddleware<TMethod, TServerFnResponseType, TValidator>,
539
+ ServerFnTyper<TMethod, TServerFnResponseType, TMiddlewares, TValidator>,
540
+ ServerFnHandler<TMethod, TServerFnResponseType, TMiddlewares, TValidator> {}
541
+
542
+ // Typer
543
+ export interface ServerFnTyper<
544
+ TMethod extends Method,
545
+ TServerFnResponseType extends ServerFnResponseType,
546
+ TMiddlewares,
547
+ TValidator,
548
+ > {
549
+ type: (
550
+ typer: ServerFnTypeOrTypeFn<
551
+ TMethod,
552
+ TServerFnResponseType,
553
+ TMiddlewares,
554
+ TValidator
555
+ >,
556
+ ) => ServerFnAfterTyper<
557
+ TMethod,
558
+ TServerFnResponseType,
559
+ TMiddlewares,
560
+ TValidator
561
+ >
562
+ }
563
+
564
+ export type ServerFnTypeOrTypeFn<
565
+ TMethod extends Method,
566
+ TServerFnResponseType extends ServerFnResponseType,
567
+ TMiddlewares,
568
+ TValidator,
569
+ > =
570
+ | ServerFnType
571
+ | ((
572
+ ctx: ServerFnCtx<
573
+ TMethod,
574
+ TServerFnResponseType,
575
+ TMiddlewares,
576
+ TValidator
577
+ >,
578
+ ) => ServerFnType)
579
+
580
+ export interface ServerFnAfterTyper<
581
+ TMethod extends Method,
582
+ TServerFnResponseType extends ServerFnResponseType,
583
+ TMiddlewares,
584
+ TValidator,
585
+ > extends ServerFnHandler<
586
+ TMethod,
587
+ TServerFnResponseType,
588
+ TMiddlewares,
589
+ TValidator
590
+ > {}
591
+
592
+ // Handler
593
+ export interface ServerFnHandler<
594
+ TMethod extends Method,
595
+ TServerFnResponseType extends ServerFnResponseType,
596
+ TMiddlewares,
597
+ TValidator,
598
+ > {
599
+ handler: <TNewResponse>(
600
+ fn?: ServerFn<
601
+ TMethod,
602
+ TServerFnResponseType,
603
+ TMiddlewares,
604
+ TValidator,
605
+ TNewResponse
606
+ >,
607
+ ) => Fetcher<TMiddlewares, TValidator, TNewResponse, TServerFnResponseType>
608
+ }
609
+
610
+ export interface ServerFnBuilder<
611
+ TMethod extends Method = 'GET',
612
+ TServerFnResponseType extends ServerFnResponseType = 'data',
613
+ > extends ServerFnMiddleware<TMethod, TServerFnResponseType, undefined>,
614
+ ServerFnValidator<TMethod, TServerFnResponseType, undefined>,
615
+ ServerFnTyper<TMethod, TServerFnResponseType, undefined, undefined>,
616
+ ServerFnHandler<TMethod, TServerFnResponseType, undefined, undefined> {
617
+ options: ServerFnBaseOptions<
618
+ TMethod,
619
+ TServerFnResponseType,
620
+ unknown,
621
+ undefined,
622
+ undefined
623
+ >
624
+ }
625
+
626
+ export type StaticCachedResult = {
627
+ ctx?: {
628
+ result: any
629
+ context: any
630
+ }
631
+ error?: any
632
+ }
633
+
634
+ export type ServerFnStaticCache = {
635
+ getItem: (
636
+ ctx: ServerFnMiddlewareResult,
637
+ ) => StaticCachedResult | Promise<StaticCachedResult | undefined>
638
+ setItem: (
639
+ ctx: ServerFnMiddlewareResult,
640
+ response: StaticCachedResult,
641
+ ) => Promise<void>
642
+ fetchItem: (
643
+ ctx: ServerFnMiddlewareResult,
644
+ ) => StaticCachedResult | Promise<StaticCachedResult | undefined>
645
+ }
646
+
647
+ export let serverFnStaticCache: ServerFnStaticCache | undefined
648
+
649
+ export function setServerFnStaticCache(
650
+ cache?: ServerFnStaticCache | (() => ServerFnStaticCache | undefined),
651
+ ) {
652
+ const previousCache = serverFnStaticCache
653
+ serverFnStaticCache = typeof cache === 'function' ? cache() : cache
654
+
655
+ return () => {
656
+ serverFnStaticCache = previousCache
657
+ }
658
+ }
659
+
660
+ export function createServerFnStaticCache(
661
+ serverFnStaticCache: ServerFnStaticCache,
662
+ ) {
663
+ return serverFnStaticCache
664
+ }
665
+
666
+ /**
667
+ * This is a simple hash function for generating a hash from a string to make the filenames shorter.
668
+ *
669
+ * It is not cryptographically secure (as its using SHA-1) and should not be used for any security purposes.
670
+ *
671
+ * It is only used to generate a hash for the static cache filenames.
672
+ *
673
+ * @param message - The input string to hash.
674
+ * @returns A promise that resolves to the SHA-1 hash of the input string in hexadecimal format.
675
+ *
676
+ * @example
677
+ * ```typescript
678
+ * const hash = await sha1Hash("hello");
679
+ * console.log(hash); // Outputs the SHA-1 hash of "hello" -> "aaf4c61ddcc5e8a2dabede0f3b482cd9aea9434d"
680
+ * ```
681
+ */
682
+ async function sha1Hash(message: string): Promise<string> {
683
+ // Encode the string as UTF-8
684
+ const msgBuffer = new TextEncoder().encode(message)
685
+
686
+ // Hash the message
687
+ const hashBuffer = await crypto.subtle.digest('SHA-1', msgBuffer)
688
+
689
+ // Convert the ArrayBuffer to a string
690
+ const hashArray = Array.from(new Uint8Array(hashBuffer))
691
+ const hashHex = hashArray.map((b) => b.toString(16).padStart(2, '0')).join('')
692
+ return hashHex
693
+ }
694
+
695
+ setServerFnStaticCache(() => {
696
+ const getStaticCacheUrl = async (
697
+ options: ServerFnMiddlewareResult,
698
+ hash: string,
699
+ ) => {
700
+ const filename = await sha1Hash(`${options.functionId}__${hash}`)
701
+ return `/__tsr/staticServerFnCache/${filename}.json`
702
+ }
703
+
704
+ const jsonToFilenameSafeString = (json: any) => {
705
+ // Custom replacer to sort keys
706
+ const sortedKeysReplacer = (key: string, value: any) =>
707
+ value && typeof value === 'object' && !Array.isArray(value)
708
+ ? Object.keys(value)
709
+ .sort()
710
+ .reduce((acc: any, curr: string) => {
711
+ acc[curr] = value[curr]
712
+ return acc
713
+ }, {})
714
+ : value
715
+
716
+ // Convert JSON to string with sorted keys
717
+ const jsonString = JSON.stringify(json ?? '', sortedKeysReplacer)
718
+
719
+ // Replace characters invalid in filenames
720
+ return jsonString
721
+ .replace(/[/\\?%*:|"<>]/g, '-') // Replace invalid characters with a dash
722
+ .replace(/\s+/g, '_') // Optionally replace whitespace with underscores
723
+ }
724
+
725
+ const staticClientCache =
726
+ typeof document !== 'undefined' ? new Map<string, any>() : null
727
+
728
+ return createServerFnStaticCache({
729
+ getItem: async (ctx) => {
730
+ if (typeof document === 'undefined') {
731
+ const hash = jsonToFilenameSafeString(ctx.data)
732
+ const url = await getStaticCacheUrl(ctx, hash)
733
+ const publicUrl = process.env.TSS_OUTPUT_PUBLIC_DIR!
734
+
735
+ // Use fs instead of fetch to read from filesystem
736
+ const { promises: fs } = await import('node:fs')
737
+ const path = await import('node:path')
738
+ const filePath = path.join(publicUrl, url)
739
+
740
+ const [cachedResult, readError] = await fs
741
+ .readFile(filePath, 'utf-8')
742
+ .then((c) => [
743
+ startSerializer.parse(c) as {
744
+ ctx: unknown
745
+ error: any
746
+ },
747
+ null,
748
+ ])
749
+ .catch((e) => [null, e])
750
+
751
+ if (readError && readError.code !== 'ENOENT') {
752
+ throw readError
753
+ }
754
+
755
+ return cachedResult as StaticCachedResult
756
+ }
757
+
758
+ return undefined
759
+ },
760
+ setItem: async (ctx, response) => {
761
+ const { promises: fs } = await import('node:fs')
762
+ const path = await import('node:path')
763
+
764
+ const hash = jsonToFilenameSafeString(ctx.data)
765
+ const url = await getStaticCacheUrl(ctx, hash)
766
+ const publicUrl = process.env.TSS_OUTPUT_PUBLIC_DIR!
767
+ const filePath = path.join(publicUrl, url)
768
+
769
+ // Ensure the directory exists
770
+ await fs.mkdir(path.dirname(filePath), { recursive: true })
771
+
772
+ // Store the result with fs
773
+ await fs.writeFile(filePath, startSerializer.stringify(response))
774
+ },
775
+ fetchItem: async (ctx) => {
776
+ const hash = jsonToFilenameSafeString(ctx.data)
777
+ const url = await getStaticCacheUrl(ctx, hash)
778
+
779
+ let result: any = staticClientCache?.get(url)
780
+
781
+ if (!result) {
782
+ result = await fetch(url, {
783
+ method: 'GET',
784
+ })
785
+ .then((r) => r.text())
786
+ .then((d) => startSerializer.parse(d))
787
+
788
+ staticClientCache?.set(url, result)
789
+ }
790
+
791
+ return result
792
+ },
793
+ })
794
+ })
795
+
796
+ export function extractFormDataContext(formData: FormData) {
797
+ const serializedContext = formData.get('__TSR_CONTEXT')
798
+ formData.delete('__TSR_CONTEXT')
799
+
800
+ if (typeof serializedContext !== 'string') {
801
+ return {
802
+ context: {},
803
+ data: formData,
804
+ }
805
+ }
806
+
807
+ try {
808
+ const context = startSerializer.parse(serializedContext)
809
+ return {
810
+ context,
811
+ data: formData,
812
+ }
813
+ } catch {
814
+ return {
815
+ data: formData,
816
+ }
817
+ }
818
+ }
819
+
820
+ export function flattenMiddlewares(
821
+ middlewares: Array<AnyFunctionMiddleware>,
822
+ ): Array<AnyFunctionMiddleware> {
823
+ const seen = new Set<AnyFunctionMiddleware>()
824
+ const flattened: Array<AnyFunctionMiddleware> = []
825
+
826
+ const recurse = (middleware: Array<AnyFunctionMiddleware>) => {
827
+ middleware.forEach((m) => {
828
+ if (m.options.middleware) {
829
+ recurse(m.options.middleware)
830
+ }
831
+
832
+ if (!seen.has(m)) {
833
+ seen.add(m)
834
+ flattened.push(m)
835
+ }
836
+ })
837
+ }
838
+
839
+ recurse(middlewares)
840
+
841
+ return flattened
842
+ }
843
+
844
+ export type ServerFnMiddlewareOptions = {
845
+ method: Method
846
+ response?: ServerFnResponseType
847
+ data: any
848
+ headers?: HeadersInit
849
+ signal?: AbortSignal
850
+ sendContext?: any
851
+ context?: any
852
+ type: ServerFnTypeOrTypeFn<any, any, any, any>
853
+ functionId: string
854
+ }
855
+
856
+ export type ServerFnMiddlewareResult = ServerFnMiddlewareOptions & {
857
+ result?: unknown
858
+ error?: unknown
859
+ type: ServerFnTypeOrTypeFn<any, any, any, any>
860
+ }
861
+
862
+ export type NextFn = (
863
+ ctx: ServerFnMiddlewareResult,
864
+ ) => Promise<ServerFnMiddlewareResult>
865
+
866
+ export type MiddlewareFn = (
867
+ ctx: ServerFnMiddlewareOptions & {
868
+ next: NextFn
869
+ },
870
+ ) => Promise<ServerFnMiddlewareResult>
871
+
872
+ export const applyMiddleware = async (
873
+ middlewareFn: MiddlewareFn,
874
+ ctx: ServerFnMiddlewareOptions,
875
+ nextFn: NextFn,
876
+ ) => {
877
+ return middlewareFn({
878
+ ...ctx,
879
+ next: (async (
880
+ userCtx: ServerFnMiddlewareResult | undefined = {} as any,
881
+ ) => {
882
+ // Return the next middleware
883
+ return nextFn({
884
+ ...ctx,
885
+ ...userCtx,
886
+ context: {
887
+ ...ctx.context,
888
+ ...userCtx.context,
889
+ },
890
+ sendContext: {
891
+ ...ctx.sendContext,
892
+ ...(userCtx.sendContext ?? {}),
893
+ },
894
+ headers: mergeHeaders(ctx.headers, userCtx.headers),
895
+ result:
896
+ userCtx.result !== undefined
897
+ ? userCtx.result
898
+ : ctx.response === 'raw'
899
+ ? userCtx
900
+ : (ctx as any).result,
901
+ error: userCtx.error ?? (ctx as any).error,
902
+ })
903
+ }) as any,
904
+ } as any)
905
+ }
906
+
907
+ export function execValidator(
908
+ validator: AnyValidator,
909
+ input: unknown,
910
+ ): unknown {
911
+ if (validator == null) return {}
912
+
913
+ if ('~standard' in validator) {
914
+ const result = validator['~standard'].validate(input)
915
+
916
+ if (result instanceof Promise)
917
+ throw new Error('Async validation not supported')
918
+
919
+ if (result.issues)
920
+ throw new Error(JSON.stringify(result.issues, undefined, 2))
921
+
922
+ return result.value
923
+ }
924
+
925
+ if ('parse' in validator) {
926
+ return validator.parse(input)
927
+ }
928
+
929
+ if (typeof validator === 'function') {
930
+ return validator(input)
931
+ }
932
+
933
+ throw new Error('Invalid validator type!')
934
+ }
935
+
936
+ export function serverFnBaseToMiddleware(
937
+ options: ServerFnBaseOptions<any, any, any, any, any>,
938
+ ): AnyFunctionMiddleware {
939
+ return {
940
+ _types: undefined!,
941
+ options: {
942
+ validator: options.validator,
943
+ validateClient: options.validateClient,
944
+ client: async ({ next, sendContext, ...ctx }) => {
945
+ const payload = {
946
+ ...ctx,
947
+ // switch the sendContext over to context
948
+ context: sendContext,
949
+ type: typeof ctx.type === 'function' ? ctx.type(ctx) : ctx.type,
950
+ } as any
951
+
952
+ if (
953
+ ctx.type === 'static' &&
954
+ process.env.NODE_ENV === 'production' &&
955
+ typeof document !== 'undefined'
956
+ ) {
957
+ invariant(
958
+ serverFnStaticCache,
959
+ 'serverFnStaticCache.fetchItem is not available!',
960
+ )
961
+
962
+ const result = await serverFnStaticCache.fetchItem(payload)
963
+
964
+ if (result) {
965
+ if (result.error) {
966
+ throw result.error
967
+ }
968
+
969
+ return next(result.ctx)
970
+ }
971
+
972
+ warning(
973
+ result,
974
+ `No static cache item found for ${payload.functionId}__${JSON.stringify(payload.data)}, falling back to server function...`,
975
+ )
976
+ }
977
+
978
+ // Execute the extracted function
979
+ // but not before serializing the context
980
+ const res = await options.extractedFn?.(payload)
981
+
982
+ return next(res) as unknown as FunctionMiddlewareClientFnResult<
983
+ any,
984
+ any,
985
+ any
986
+ >
987
+ },
988
+ server: async ({ next, ...ctx }) => {
989
+ // Execute the server function
990
+ const result = await options.serverFn?.(ctx as TODO)
991
+
992
+ return next({
993
+ ...ctx,
994
+ result,
995
+ } as any) as unknown as FunctionMiddlewareServerFnResult<
996
+ any,
997
+ any,
998
+ any,
999
+ any
1000
+ >
1001
+ },
1002
+ },
1003
+ }
1004
+ }