@benjavicente/start-client-core 1.167.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (94) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +12 -0
  3. package/bin/intent.js +25 -0
  4. package/dist/esm/client/ServerFunctionSerializationAdapter.d.ts +7 -0
  5. package/dist/esm/client/ServerFunctionSerializationAdapter.js +18 -0
  6. package/dist/esm/client/ServerFunctionSerializationAdapter.js.map +1 -0
  7. package/dist/esm/client/hydrateStart.d.ts +2 -0
  8. package/dist/esm/client/hydrateStart.js +31 -0
  9. package/dist/esm/client/hydrateStart.js.map +1 -0
  10. package/dist/esm/client/index.d.ts +2 -0
  11. package/dist/esm/client/index.js +2 -0
  12. package/dist/esm/client-rpc/createClientRpc.d.ts +6 -0
  13. package/dist/esm/client-rpc/createClientRpc.js +21 -0
  14. package/dist/esm/client-rpc/createClientRpc.js.map +1 -0
  15. package/dist/esm/client-rpc/frame-decoder.d.ts +23 -0
  16. package/dist/esm/client-rpc/frame-decoder.js +231 -0
  17. package/dist/esm/client-rpc/frame-decoder.js.map +1 -0
  18. package/dist/esm/client-rpc/index.d.ts +1 -0
  19. package/dist/esm/client-rpc/index.js +2 -0
  20. package/dist/esm/client-rpc/serverFnFetcher.d.ts +1 -0
  21. package/dist/esm/client-rpc/serverFnFetcher.js +231 -0
  22. package/dist/esm/client-rpc/serverFnFetcher.js.map +1 -0
  23. package/dist/esm/constants.d.ts +53 -0
  24. package/dist/esm/constants.js +46 -0
  25. package/dist/esm/constants.js.map +1 -0
  26. package/dist/esm/createMiddleware.d.ts +195 -0
  27. package/dist/esm/createMiddleware.js +26 -0
  28. package/dist/esm/createMiddleware.js.map +1 -0
  29. package/dist/esm/createServerFn.d.ts +131 -0
  30. package/dist/esm/createServerFn.js +200 -0
  31. package/dist/esm/createServerFn.js.map +1 -0
  32. package/dist/esm/createStart.d.ts +50 -0
  33. package/dist/esm/createStart.js +29 -0
  34. package/dist/esm/createStart.js.map +1 -0
  35. package/dist/esm/fake-start-entry.d.ts +2 -0
  36. package/dist/esm/fake-start-entry.js +7 -0
  37. package/dist/esm/fake-start-entry.js.map +1 -0
  38. package/dist/esm/getDefaultSerovalPlugins.d.ts +1 -0
  39. package/dist/esm/getDefaultSerovalPlugins.js +10 -0
  40. package/dist/esm/getDefaultSerovalPlugins.js.map +1 -0
  41. package/dist/esm/getGlobalStartContext.d.ts +3 -0
  42. package/dist/esm/getGlobalStartContext.js +12 -0
  43. package/dist/esm/getGlobalStartContext.js.map +1 -0
  44. package/dist/esm/getRouterInstance.d.ts +2 -0
  45. package/dist/esm/getRouterInstance.js +8 -0
  46. package/dist/esm/getRouterInstance.js.map +1 -0
  47. package/dist/esm/getStartContextServerOnly.d.ts +2 -0
  48. package/dist/esm/getStartContextServerOnly.js +8 -0
  49. package/dist/esm/getStartContextServerOnly.js.map +1 -0
  50. package/dist/esm/getStartOptions.d.ts +2 -0
  51. package/dist/esm/getStartOptions.js +8 -0
  52. package/dist/esm/getStartOptions.js.map +1 -0
  53. package/dist/esm/global.d.ts +7 -0
  54. package/dist/esm/index.d.ts +20 -0
  55. package/dist/esm/index.js +12 -0
  56. package/dist/esm/safeObjectMerge.d.ts +10 -0
  57. package/dist/esm/safeObjectMerge.js +30 -0
  58. package/dist/esm/safeObjectMerge.js.map +1 -0
  59. package/dist/esm/serverRoute.d.ts +65 -0
  60. package/dist/esm/startEntry.d.ts +8 -0
  61. package/dist/esm/tests/createServerFn.test-d.d.ts +1 -0
  62. package/dist/esm/tests/createServerMiddleware.test-d.d.ts +1 -0
  63. package/package.json +98 -0
  64. package/skills/start-core/SKILL.md +210 -0
  65. package/skills/start-core/deployment/SKILL.md +306 -0
  66. package/skills/start-core/execution-model/SKILL.md +302 -0
  67. package/skills/start-core/middleware/SKILL.md +365 -0
  68. package/skills/start-core/server-functions/SKILL.md +335 -0
  69. package/skills/start-core/server-routes/SKILL.md +280 -0
  70. package/src/client/ServerFunctionSerializationAdapter.ts +16 -0
  71. package/src/client/hydrateStart.ts +43 -0
  72. package/src/client/index.ts +2 -0
  73. package/src/client-rpc/createClientRpc.ts +20 -0
  74. package/src/client-rpc/frame-decoder.ts +389 -0
  75. package/src/client-rpc/index.ts +1 -0
  76. package/src/client-rpc/serverFnFetcher.ts +416 -0
  77. package/src/constants.ts +90 -0
  78. package/src/createMiddleware.ts +824 -0
  79. package/src/createServerFn.ts +813 -0
  80. package/src/createStart.ts +166 -0
  81. package/src/fake-start-entry.ts +2 -0
  82. package/src/getDefaultSerovalPlugins.ts +17 -0
  83. package/src/getGlobalStartContext.ts +18 -0
  84. package/src/getRouterInstance.ts +8 -0
  85. package/src/getStartContextServerOnly.ts +4 -0
  86. package/src/getStartOptions.ts +8 -0
  87. package/src/global.ts +9 -0
  88. package/src/index.tsx +119 -0
  89. package/src/safeObjectMerge.ts +38 -0
  90. package/src/serverRoute.ts +509 -0
  91. package/src/start-entry.d.ts +11 -0
  92. package/src/startEntry.ts +10 -0
  93. package/src/tests/createServerFn.test-d.ts +866 -0
  94. package/src/tests/createServerMiddleware.test-d.ts +810 -0
@@ -0,0 +1,813 @@
1
+ import { mergeHeaders } from '@benjavicente/router-core/ssr/client'
2
+
3
+ import { isRedirect, parseRedirect } from '@benjavicente/router-core'
4
+ import { TSS_SERVER_FUNCTION_FACTORY } from './constants'
5
+ import { getStartOptions } from './getStartOptions'
6
+ import { getStartContextServerOnly } from './getStartContextServerOnly'
7
+ import { createNullProtoObject, safeObjectMerge } from './safeObjectMerge'
8
+ import type {
9
+ ClientFnMeta,
10
+ ServerFnMeta,
11
+ TSS_SERVER_FUNCTION,
12
+ } from './constants'
13
+ import type {
14
+ AnyValidator,
15
+ Constrain,
16
+ Expand,
17
+ Register,
18
+ RegisteredSerializableInput,
19
+ ResolveValidatorInput,
20
+ ValidateSerializable,
21
+ ValidateSerializableInput,
22
+ Validator,
23
+ } from '@benjavicente/router-core'
24
+ import type {
25
+ AnyFunctionMiddleware,
26
+ AnyRequestMiddleware,
27
+ AssignAllServerFnContext,
28
+ FunctionMiddlewareServerFnResult,
29
+ IntersectAllValidatorInputs,
30
+ IntersectAllValidatorOutputs,
31
+ } from './createMiddleware'
32
+
33
+ type TODO = any
34
+
35
+ export type CreateServerFn<TRegister> = <
36
+ TMethod extends Method,
37
+ TResponse = unknown,
38
+ TMiddlewares = undefined,
39
+ TInputValidator = undefined,
40
+ >(
41
+ options?: {
42
+ method?: TMethod
43
+ },
44
+ __opts?: ServerFnBaseOptions<
45
+ TRegister,
46
+ TMethod,
47
+ TResponse,
48
+ TMiddlewares,
49
+ TInputValidator
50
+ >,
51
+ ) => ServerFnBuilder<TRegister, TMethod>
52
+
53
+ export const createServerFn: CreateServerFn<Register> = (options, __opts) => {
54
+ const resolvedOptions = (__opts || options || {}) as ServerFnBaseOptions<
55
+ any,
56
+ any,
57
+ any,
58
+ any,
59
+ any
60
+ >
61
+
62
+ if (typeof resolvedOptions.method === 'undefined') {
63
+ resolvedOptions.method = 'GET' as Method
64
+ }
65
+
66
+ const res: ServerFnBuilder<Register, Method> = {
67
+ options: resolvedOptions,
68
+ middleware: (middleware) => {
69
+ // multiple calls to `middleware()` merge the middlewares with the previously supplied ones
70
+ // this is primarily useful for letting users create their own abstractions on top of `createServerFn`
71
+
72
+ const newMiddleware = [...(resolvedOptions.middleware || [])]
73
+ middleware.map((m) => {
74
+ if (TSS_SERVER_FUNCTION_FACTORY in m) {
75
+ if (m.options.middleware) {
76
+ newMiddleware.push(...m.options.middleware)
77
+ }
78
+ } else {
79
+ newMiddleware.push(m)
80
+ }
81
+ })
82
+
83
+ const newOptions = {
84
+ ...resolvedOptions,
85
+ middleware: newMiddleware,
86
+ }
87
+ const res = createServerFn(undefined, newOptions) as any
88
+ res[TSS_SERVER_FUNCTION_FACTORY] = true
89
+ return res
90
+ },
91
+ inputValidator: (inputValidator) => {
92
+ const newOptions = { ...resolvedOptions, inputValidator }
93
+ return createServerFn(undefined, newOptions) as any
94
+ },
95
+ handler: (...args) => {
96
+ // This function signature changes due to AST transformations
97
+ // in the babel plugin. We need to cast it to the correct
98
+ // function signature post-transformation
99
+ const [extractedFn, serverFn] = args as unknown as [
100
+ CompiledFetcherFn<Register, any>,
101
+ ServerFn<Register, Method, any, any, any>,
102
+ ]
103
+
104
+ // Keep the original function around so we can use it
105
+ // in the server environment
106
+ const newOptions = { ...resolvedOptions, extractedFn, serverFn }
107
+
108
+ const resolvedMiddleware = [
109
+ ...(newOptions.middleware || []),
110
+ serverFnBaseToMiddleware(newOptions),
111
+ ]
112
+
113
+ // We want to make sure the new function has the same
114
+ // properties as the original function
115
+
116
+ // Propagate the declared HTTP method onto the extracted handler
117
+ // so the manifest-exported symbol (resolved by getServerFnById)
118
+ // carries `method`, enabling the server handler to reject
119
+ // mismatched HTTP methods before parsing request payloads.
120
+ ;(extractedFn as any).method = resolvedOptions.method
121
+
122
+ return Object.assign(
123
+ async (opts?: CompiledFetcherFnOptions) => {
124
+ // Start by executing the client-side middleware chain
125
+ const result = await executeMiddleware(resolvedMiddleware, 'client', {
126
+ ...extractedFn,
127
+ ...newOptions,
128
+ data: opts?.data as any,
129
+ headers: opts?.headers,
130
+ signal: opts?.signal,
131
+ fetch: opts?.fetch,
132
+ context: createNullProtoObject(),
133
+ })
134
+
135
+ const redirect = parseRedirect(result.error)
136
+ if (redirect) {
137
+ throw redirect
138
+ }
139
+
140
+ if (result.error) throw result.error
141
+ return result.result
142
+ },
143
+ {
144
+ // This copies over the URL, function ID
145
+ ...extractedFn,
146
+ // Expose the declared HTTP method so the server handler
147
+ // can reject mismatched methods before parsing payloads
148
+ method: resolvedOptions.method,
149
+ // The extracted function on the server-side calls
150
+ // this function
151
+ __executeServer: async (opts: any) => {
152
+ const startContext = getStartContextServerOnly()
153
+ const serverContextAfterGlobalMiddlewares =
154
+ startContext.contextAfterGlobalMiddlewares
155
+ // Use safeObjectMerge for opts.context which comes from client
156
+ const ctx = {
157
+ ...extractedFn,
158
+ ...opts,
159
+ // Ensure we use the full serverFnMeta from the provider file's extractedFn
160
+ // (which has id, name, filename) rather than the partial one from SSR/client
161
+ // callers (which only has id)
162
+ serverFnMeta: extractedFn.serverFnMeta,
163
+ // Use safeObjectMerge for opts.context which comes from client
164
+ context: safeObjectMerge(
165
+ serverContextAfterGlobalMiddlewares,
166
+ opts.context,
167
+ ),
168
+ request: startContext.request,
169
+ }
170
+
171
+ const result = await executeMiddleware(
172
+ resolvedMiddleware,
173
+ 'server',
174
+ ctx,
175
+ ).then((d) => ({
176
+ // Only send the result and sendContext back to the client
177
+ result: d.result,
178
+ error: d.error,
179
+ context: d.sendContext,
180
+ }))
181
+
182
+ return result
183
+ },
184
+ },
185
+ ) as any
186
+ },
187
+ } as ServerFnBuilder<Register, Method>
188
+ const fun = (options?: { method?: Method }) => {
189
+ const newOptions = {
190
+ ...resolvedOptions,
191
+ ...options,
192
+ }
193
+ return createServerFn(undefined, newOptions) as any
194
+ }
195
+ return Object.assign(fun, res) as any
196
+ }
197
+
198
+ export async function executeMiddleware(
199
+ middlewares: Array<AnyFunctionMiddleware | AnyRequestMiddleware>,
200
+ env: 'client' | 'server',
201
+ opts: ServerFnMiddlewareOptions,
202
+ ): Promise<ServerFnMiddlewareResult> {
203
+ const globalMiddlewares = getStartOptions()?.functionMiddleware || []
204
+ let flattenedMiddlewares = flattenMiddlewares([
205
+ ...globalMiddlewares,
206
+ ...middlewares,
207
+ ])
208
+
209
+ // On server, filter out middlewares that already executed in the request phase
210
+ // to prevent duplicate execution (issue #5239)
211
+ if (env === 'server') {
212
+ const startContext = getStartContextServerOnly({ throwIfNotFound: false })
213
+ if (startContext?.executedRequestMiddlewares) {
214
+ flattenedMiddlewares = flattenedMiddlewares.filter(
215
+ (m) => !startContext.executedRequestMiddlewares.has(m),
216
+ )
217
+ }
218
+ }
219
+
220
+ const callNextMiddleware: NextFn = async (ctx) => {
221
+ // Get the next middleware
222
+ const nextMiddleware = flattenedMiddlewares.shift()
223
+
224
+ // If there are no more middlewares, return the context
225
+ if (!nextMiddleware) {
226
+ return ctx
227
+ }
228
+
229
+ // Execute the middleware
230
+ try {
231
+ if (
232
+ 'inputValidator' in nextMiddleware.options &&
233
+ nextMiddleware.options.inputValidator &&
234
+ env === 'server'
235
+ ) {
236
+ // Execute the middleware's input function
237
+ ctx.data = await execValidator(
238
+ nextMiddleware.options.inputValidator,
239
+ ctx.data,
240
+ )
241
+ }
242
+
243
+ let middlewareFn: MiddlewareFn | undefined = undefined
244
+ if (env === 'client') {
245
+ if ('client' in nextMiddleware.options) {
246
+ middlewareFn = nextMiddleware.options.client as
247
+ | MiddlewareFn
248
+ | undefined
249
+ }
250
+ }
251
+ // env === 'server'
252
+ else if ('server' in nextMiddleware.options) {
253
+ middlewareFn = nextMiddleware.options.server as MiddlewareFn | undefined
254
+ }
255
+
256
+ if (middlewareFn) {
257
+ const userNext = async (
258
+ userCtx: ServerFnMiddlewareResult | undefined = {} as any,
259
+ ) => {
260
+ // Return the next middleware
261
+ // Use safeObjectMerge for context objects to prevent prototype pollution
262
+ const nextCtx = {
263
+ ...ctx,
264
+ ...userCtx,
265
+ context: safeObjectMerge(ctx.context, userCtx.context),
266
+ sendContext: safeObjectMerge(ctx.sendContext, userCtx.sendContext),
267
+ headers: mergeHeaders(ctx.headers, userCtx.headers),
268
+ _callSiteFetch: ctx._callSiteFetch,
269
+ fetch: ctx._callSiteFetch ?? userCtx.fetch ?? ctx.fetch,
270
+ result:
271
+ userCtx.result !== undefined
272
+ ? userCtx.result
273
+ : userCtx instanceof Response
274
+ ? userCtx
275
+ : (ctx as any).result,
276
+ error: userCtx.error ?? (ctx as any).error,
277
+ }
278
+
279
+ const result = await callNextMiddleware(nextCtx)
280
+
281
+ if (result.error) {
282
+ throw result.error
283
+ }
284
+
285
+ return result
286
+ }
287
+
288
+ // Execute the middleware
289
+ const result = await middlewareFn({
290
+ ...ctx,
291
+ next: userNext,
292
+ })
293
+
294
+ // If result is NOT a ctx object, we need to return it as
295
+ // the { result }
296
+ if (isRedirect(result)) {
297
+ return {
298
+ ...ctx,
299
+ error: result,
300
+ }
301
+ }
302
+
303
+ if (result instanceof Response) {
304
+ return {
305
+ ...ctx,
306
+ result,
307
+ }
308
+ }
309
+
310
+ if (!(result as any)) {
311
+ throw new Error(
312
+ 'User middleware returned undefined. You must call next() or return a result in your middlewares.',
313
+ )
314
+ }
315
+
316
+ return result
317
+ }
318
+
319
+ return callNextMiddleware(ctx)
320
+ } catch (error: any) {
321
+ return {
322
+ ...ctx,
323
+ error,
324
+ }
325
+ }
326
+ }
327
+
328
+ // Start the middleware chain
329
+ return callNextMiddleware({
330
+ ...opts,
331
+ headers: opts.headers || {},
332
+ sendContext: opts.sendContext || {},
333
+ context: opts.context || createNullProtoObject(),
334
+ _callSiteFetch: opts.fetch,
335
+ })
336
+ }
337
+
338
+ export type CompiledFetcherFnOptions = {
339
+ method: Method
340
+ data: unknown
341
+ headers?: HeadersInit
342
+ signal?: AbortSignal
343
+ fetch?: CustomFetch
344
+ context?: any
345
+ }
346
+
347
+ export type Fetcher<TMiddlewares, TInputValidator, TResponse> =
348
+ undefined extends IntersectAllValidatorInputs<TMiddlewares, TInputValidator>
349
+ ? OptionalFetcher<TMiddlewares, TInputValidator, TResponse>
350
+ : RequiredFetcher<TMiddlewares, TInputValidator, TResponse>
351
+
352
+ export interface FetcherBase {
353
+ [TSS_SERVER_FUNCTION]: true
354
+ url: string
355
+ method: Method
356
+ __executeServer: (opts: {
357
+ method: Method
358
+ data: unknown
359
+ headers?: HeadersInit
360
+ context?: any
361
+ }) => Promise<unknown>
362
+ }
363
+
364
+ export interface OptionalFetcher<
365
+ TMiddlewares,
366
+ TInputValidator,
367
+ TResponse,
368
+ > extends FetcherBase {
369
+ (
370
+ options?: OptionalFetcherDataOptions<TMiddlewares, TInputValidator>,
371
+ ): Promise<Awaited<TResponse>>
372
+ }
373
+
374
+ export interface RequiredFetcher<
375
+ TMiddlewares,
376
+ TInputValidator,
377
+ TResponse,
378
+ > extends FetcherBase {
379
+ (
380
+ opts: RequiredFetcherDataOptions<TMiddlewares, TInputValidator>,
381
+ ): Promise<Awaited<TResponse>>
382
+ }
383
+
384
+ export type CustomFetch = typeof globalThis.fetch
385
+
386
+ export type FetcherBaseOptions = {
387
+ headers?: HeadersInit
388
+ signal?: AbortSignal
389
+ fetch?: CustomFetch
390
+ }
391
+
392
+ export interface OptionalFetcherDataOptions<
393
+ TMiddlewares,
394
+ TInputValidator,
395
+ > extends FetcherBaseOptions {
396
+ data?: Expand<IntersectAllValidatorInputs<TMiddlewares, TInputValidator>>
397
+ }
398
+
399
+ export interface RequiredFetcherDataOptions<
400
+ TMiddlewares,
401
+ TInputValidator,
402
+ > extends FetcherBaseOptions {
403
+ data: Expand<IntersectAllValidatorInputs<TMiddlewares, TInputValidator>>
404
+ }
405
+
406
+ export type RscStream<T> = {
407
+ __cacheState: T
408
+ }
409
+
410
+ export type Method = 'GET' | 'POST'
411
+
412
+ export type ServerFnReturnType<TRegister, TResponse> =
413
+ TResponse extends PromiseLike<infer U>
414
+ ? Promise<ServerFnReturnType<TRegister, U>>
415
+ : TResponse extends Response
416
+ ? TResponse
417
+ : ValidateSerializableInput<TRegister, TResponse>
418
+
419
+ export type ServerFn<
420
+ TRegister,
421
+ TMethod,
422
+ TMiddlewares,
423
+ TInputValidator,
424
+ TResponse,
425
+ > = (
426
+ ctx: ServerFnCtx<TRegister, TMethod, TMiddlewares, TInputValidator>,
427
+ ) => ServerFnReturnType<TRegister, TResponse>
428
+
429
+ export interface ServerFnCtx<
430
+ TRegister,
431
+ TMethod,
432
+ TMiddlewares,
433
+ TInputValidator,
434
+ > {
435
+ data: Expand<IntersectAllValidatorOutputs<TMiddlewares, TInputValidator>>
436
+ serverFnMeta: ServerFnMeta
437
+ context: Expand<AssignAllServerFnContext<TRegister, TMiddlewares, {}>>
438
+ method: TMethod
439
+ }
440
+
441
+ export type CompiledFetcherFn<TRegister, TResponse> = {
442
+ (
443
+ opts: CompiledFetcherFnOptions & ServerFnBaseOptions<TRegister, Method>,
444
+ ): Promise<TResponse>
445
+ url: string
446
+ serverFnMeta: ServerFnMeta
447
+ }
448
+
449
+ export type ServerFnBaseOptions<
450
+ TRegister,
451
+ TMethod extends Method = 'GET',
452
+ TResponse = unknown,
453
+ TMiddlewares = unknown,
454
+ TInputValidator = unknown,
455
+ > = {
456
+ method: TMethod
457
+ middleware?: Constrain<
458
+ TMiddlewares,
459
+ ReadonlyArray<AnyFunctionMiddleware | AnyRequestMiddleware>
460
+ >
461
+ inputValidator?: ConstrainValidator<TRegister, TMethod, TInputValidator>
462
+ extractedFn?: CompiledFetcherFn<TRegister, TResponse>
463
+ serverFn?: ServerFn<
464
+ TRegister,
465
+ TMethod,
466
+ TMiddlewares,
467
+ TInputValidator,
468
+ TResponse
469
+ >
470
+ }
471
+
472
+ export type ValidateValidatorInput<
473
+ TRegister,
474
+ TMethod extends Method,
475
+ TInputValidator,
476
+ > = TMethod extends 'POST'
477
+ ? ResolveValidatorInput<TInputValidator> extends FormData
478
+ ? ResolveValidatorInput<TInputValidator>
479
+ : ValidateSerializable<
480
+ ResolveValidatorInput<TInputValidator>,
481
+ RegisteredSerializableInput<TRegister>
482
+ >
483
+ : ValidateSerializable<
484
+ ResolveValidatorInput<TInputValidator>,
485
+ RegisteredSerializableInput<TRegister>
486
+ >
487
+
488
+ export type ValidateValidator<
489
+ TRegister,
490
+ TMethod extends Method,
491
+ TInputValidator,
492
+ > =
493
+ ValidateValidatorInput<
494
+ TRegister,
495
+ TMethod,
496
+ TInputValidator
497
+ > extends infer TInput
498
+ ? Validator<TInput, any>
499
+ : never
500
+
501
+ export type ConstrainValidator<
502
+ TRegister,
503
+ TMethod extends Method,
504
+ TInputValidator,
505
+ > =
506
+ | (unknown extends TInputValidator
507
+ ? TInputValidator
508
+ : ResolveValidatorInput<TInputValidator> extends ValidateValidator<
509
+ TRegister,
510
+ TMethod,
511
+ TInputValidator
512
+ >
513
+ ? TInputValidator
514
+ : never)
515
+ | ValidateValidator<TRegister, TMethod, TInputValidator>
516
+
517
+ export type AppendMiddlewares<TMiddlewares, TNewMiddlewares> =
518
+ TMiddlewares extends ReadonlyArray<any>
519
+ ? TNewMiddlewares extends ReadonlyArray<any>
520
+ ? readonly [...TMiddlewares, ...TNewMiddlewares]
521
+ : TMiddlewares
522
+ : TNewMiddlewares
523
+
524
+ export interface ServerFnMiddleware<
525
+ TRegister,
526
+ TMethod extends Method,
527
+ TMiddlewares,
528
+ TInputValidator,
529
+ > {
530
+ middleware: <const TNewMiddlewares>(
531
+ middlewares: Constrain<
532
+ TNewMiddlewares,
533
+ ReadonlyArray<AnyFunctionMiddleware | AnyRequestMiddleware | AnyServerFn>
534
+ >,
535
+ ) => ServerFnAfterMiddleware<
536
+ TRegister,
537
+ TMethod,
538
+ AppendMiddlewares<TMiddlewares, TNewMiddlewares>,
539
+ TInputValidator
540
+ >
541
+ }
542
+
543
+ export interface ServerFnAfterMiddleware<
544
+ TRegister,
545
+ TMethod extends Method,
546
+ TMiddlewares,
547
+ TInputValidator,
548
+ >
549
+ extends
550
+ ServerFnWithTypes<
551
+ TRegister,
552
+ TMethod,
553
+ TMiddlewares,
554
+ TInputValidator,
555
+ undefined
556
+ >,
557
+ ServerFnMiddleware<TRegister, TMethod, TMiddlewares, undefined>,
558
+ ServerFnValidator<TRegister, TMethod, TMiddlewares>,
559
+ ServerFnHandler<TRegister, TMethod, TMiddlewares, TInputValidator> {
560
+ <TNewMethod extends Method = TMethod>(options?: {
561
+ method?: TNewMethod
562
+ }): ServerFnAfterMiddleware<
563
+ TRegister,
564
+ TNewMethod,
565
+ TMiddlewares,
566
+ TInputValidator
567
+ >
568
+ }
569
+
570
+ export type ValidatorFn<TRegister, TMethod extends Method, TMiddlewares> = <
571
+ TInputValidator,
572
+ >(
573
+ inputValidator: ConstrainValidator<TRegister, TMethod, TInputValidator>,
574
+ ) => ServerFnAfterValidator<TRegister, TMethod, TMiddlewares, TInputValidator>
575
+
576
+ export interface ServerFnValidator<
577
+ TRegister,
578
+ TMethod extends Method,
579
+ TMiddlewares,
580
+ > {
581
+ inputValidator: ValidatorFn<TRegister, TMethod, TMiddlewares>
582
+ }
583
+
584
+ export interface ServerFnAfterValidator<
585
+ TRegister,
586
+ TMethod extends Method,
587
+ TMiddlewares,
588
+ TInputValidator,
589
+ >
590
+ extends
591
+ ServerFnWithTypes<
592
+ TRegister,
593
+ TMethod,
594
+ TMiddlewares,
595
+ TInputValidator,
596
+ undefined
597
+ >,
598
+ ServerFnMiddleware<TRegister, TMethod, TMiddlewares, TInputValidator>,
599
+ ServerFnHandler<TRegister, TMethod, TMiddlewares, TInputValidator> {}
600
+
601
+ export interface ServerFnAfterTyper<
602
+ TRegister,
603
+ TMethod extends Method,
604
+ TMiddlewares,
605
+ TInputValidator,
606
+ >
607
+ extends
608
+ ServerFnWithTypes<
609
+ TRegister,
610
+ TMethod,
611
+ TMiddlewares,
612
+ TInputValidator,
613
+ undefined
614
+ >,
615
+ ServerFnHandler<TRegister, TMethod, TMiddlewares, TInputValidator> {}
616
+
617
+ // Handler
618
+ export interface ServerFnHandler<
619
+ TRegister,
620
+ TMethod extends Method,
621
+ TMiddlewares,
622
+ TInputValidator,
623
+ > {
624
+ handler: <TNewResponse>(
625
+ fn?: ServerFn<
626
+ TRegister,
627
+ TMethod,
628
+ TMiddlewares,
629
+ TInputValidator,
630
+ TNewResponse
631
+ >,
632
+ ) => Fetcher<TMiddlewares, TInputValidator, TNewResponse>
633
+ }
634
+
635
+ export interface ServerFnBuilder<TRegister, TMethod extends Method = 'GET'>
636
+ extends
637
+ ServerFnWithTypes<TRegister, TMethod, undefined, undefined, undefined>,
638
+ ServerFnMiddleware<TRegister, TMethod, undefined, undefined>,
639
+ ServerFnValidator<TRegister, TMethod, undefined>,
640
+ ServerFnHandler<TRegister, TMethod, undefined, undefined> {
641
+ options: ServerFnBaseOptions<
642
+ TRegister,
643
+ TMethod,
644
+ unknown,
645
+ undefined,
646
+ undefined
647
+ >
648
+ }
649
+
650
+ export interface ServerFnWithTypes<
651
+ in out TRegister,
652
+ in out TMethod extends Method,
653
+ in out TMiddlewares,
654
+ in out TInputValidator,
655
+ in out TResponse,
656
+ > {
657
+ '~types': ServerFnTypes<
658
+ TRegister,
659
+ TMethod,
660
+ TMiddlewares,
661
+ TInputValidator,
662
+ TResponse
663
+ >
664
+ options: ServerFnBaseOptions<
665
+ TRegister,
666
+ TMethod,
667
+ unknown,
668
+ undefined,
669
+ undefined
670
+ >
671
+ [TSS_SERVER_FUNCTION_FACTORY]: true
672
+ }
673
+
674
+ export type AnyServerFn = ServerFnWithTypes<any, any, any, any, any>
675
+
676
+ export interface ServerFnTypes<
677
+ in out TRegister,
678
+ in out TMethod extends Method,
679
+ in out TMiddlewares,
680
+ in out TInputValidator,
681
+ in out TResponse,
682
+ > {
683
+ method: TMethod
684
+ middlewares: TMiddlewares
685
+ inputValidator: TInputValidator
686
+ response: TResponse
687
+ allServerContext: AssignAllServerFnContext<TRegister, TMiddlewares>
688
+ allInput: IntersectAllValidatorInputs<TMiddlewares, TInputValidator>
689
+ allOutput: IntersectAllValidatorOutputs<TMiddlewares, TInputValidator>
690
+ }
691
+
692
+ export function flattenMiddlewares<
693
+ T extends AnyFunctionMiddleware | AnyRequestMiddleware,
694
+ >(middlewares: Array<T>, maxDepth: number = 100): Array<T> {
695
+ const seen = new Set<T>()
696
+ const flattened: Array<T> = []
697
+
698
+ const recurse = (middleware: Array<T>, depth: number) => {
699
+ if (depth > maxDepth) {
700
+ throw new Error(
701
+ `Middleware nesting depth exceeded maximum of ${maxDepth}. Check for circular references.`,
702
+ )
703
+ }
704
+ middleware.forEach((m) => {
705
+ if (m.options.middleware) {
706
+ recurse(m.options.middleware as Array<T>, depth + 1)
707
+ }
708
+
709
+ if (!seen.has(m)) {
710
+ seen.add(m)
711
+ flattened.push(m)
712
+ }
713
+ })
714
+ }
715
+
716
+ recurse(middlewares, 0)
717
+
718
+ return flattened
719
+ }
720
+
721
+ export type ServerFnMiddlewareOptions = {
722
+ method: Method
723
+ data: any
724
+ headers?: HeadersInit
725
+ signal?: AbortSignal
726
+ sendContext?: any
727
+ context?: any
728
+ serverFnMeta: ClientFnMeta
729
+ fetch?: CustomFetch
730
+ /** @internal - Preserves the call-site fetch to ensure it has highest priority over middleware */
731
+ _callSiteFetch?: CustomFetch
732
+ }
733
+
734
+ export type ServerFnMiddlewareResult = ServerFnMiddlewareOptions & {
735
+ result?: unknown
736
+ error?: unknown
737
+ }
738
+
739
+ export type NextFn = (
740
+ ctx: ServerFnMiddlewareResult,
741
+ ) => Promise<ServerFnMiddlewareResult>
742
+
743
+ export type MiddlewareFn = (
744
+ ctx: ServerFnMiddlewareOptions & {
745
+ next: NextFn
746
+ },
747
+ ) => Promise<ServerFnMiddlewareResult>
748
+
749
+ export async function execValidator(
750
+ validator: AnyValidator,
751
+ input: unknown,
752
+ ): Promise<unknown> {
753
+ if (validator == null) return {}
754
+
755
+ if ('~standard' in validator) {
756
+ const result = await validator['~standard'].validate(input)
757
+
758
+ if (result.issues)
759
+ throw new Error(JSON.stringify(result.issues, undefined, 2))
760
+
761
+ return result.value
762
+ }
763
+
764
+ if ('parse' in validator) {
765
+ return validator.parse(input)
766
+ }
767
+
768
+ if (typeof validator === 'function') {
769
+ return validator(input)
770
+ }
771
+
772
+ throw new Error('Invalid validator type!')
773
+ }
774
+
775
+ function serverFnBaseToMiddleware(
776
+ options: ServerFnBaseOptions<any, any, any, any, any>,
777
+ ): AnyFunctionMiddleware {
778
+ return {
779
+ '~types': undefined!,
780
+ options: {
781
+ inputValidator: options.inputValidator,
782
+ client: async ({ next, sendContext, fetch, ...ctx }) => {
783
+ const payload = {
784
+ ...ctx,
785
+ // switch the sendContext over to context
786
+ context: sendContext,
787
+ fetch,
788
+ } as any
789
+
790
+ // Execute the extracted function
791
+ // but not before serializing the context
792
+ const res = await options.extractedFn?.(payload)
793
+
794
+ return next(res)
795
+ },
796
+ server: async ({ next, ...ctx }) => {
797
+ // Execute the server function
798
+ const result = await options.serverFn?.(ctx as TODO)
799
+
800
+ return next({
801
+ ...ctx,
802
+ result,
803
+ } as any) as unknown as FunctionMiddlewareServerFnResult<
804
+ any,
805
+ any,
806
+ any,
807
+ any,
808
+ any
809
+ >
810
+ },
811
+ },
812
+ }
813
+ }