@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,866 @@
1
+ import { describe, expectTypeOf, test } from 'vitest'
2
+ import { createMiddleware } from '../createMiddleware'
3
+ import { createServerFn } from '../createServerFn'
4
+ import { TSS_SERVER_FUNCTION } from '../constants'
5
+ import type { ServerFnMeta } from '../constants'
6
+ import type {
7
+ Constrain,
8
+ Register,
9
+ TsrSerializable,
10
+ ValidateSerializableInput,
11
+ Validator,
12
+ } from '@benjavicente/router-core'
13
+ import type {
14
+ ConstrainValidator,
15
+ CustomFetch,
16
+ ServerFnReturnType,
17
+ } from '../createServerFn'
18
+
19
+ test('createServerFn without middleware', () => {
20
+ expectTypeOf(createServerFn()).toHaveProperty('handler')
21
+ expectTypeOf(createServerFn()).toHaveProperty('middleware')
22
+ expectTypeOf(createServerFn()).toHaveProperty('inputValidator')
23
+
24
+ createServerFn({ method: 'GET' }).handler((options) => {
25
+ expectTypeOf(options).toEqualTypeOf<{
26
+ context: undefined
27
+ data: undefined
28
+ method: 'GET'
29
+ serverFnMeta: ServerFnMeta
30
+ }>()
31
+ })
32
+ })
33
+
34
+ test('createServerFn with validator function', () => {
35
+ const fnAfterValidator = createServerFn({
36
+ method: 'GET',
37
+ }).inputValidator((input: { input: string }) => ({
38
+ a: input.input,
39
+ }))
40
+
41
+ expectTypeOf(fnAfterValidator).toHaveProperty('handler')
42
+ expectTypeOf(fnAfterValidator).toHaveProperty('middleware')
43
+ expectTypeOf(fnAfterValidator).not.toHaveProperty('inputValidator')
44
+
45
+ const fn = fnAfterValidator.handler((options) => {
46
+ expectTypeOf(options).toEqualTypeOf<{
47
+ context: undefined
48
+ data: {
49
+ a: string
50
+ }
51
+ method: 'GET'
52
+ serverFnMeta: ServerFnMeta
53
+ }>()
54
+ })
55
+
56
+ expectTypeOf(fn).parameter(0).toEqualTypeOf<{
57
+ data: { input: string }
58
+ headers?: HeadersInit
59
+ signal?: AbortSignal
60
+ fetch?: CustomFetch
61
+ }>()
62
+
63
+ expectTypeOf<ReturnType<typeof fn>>().resolves.toEqualTypeOf<void>()
64
+ })
65
+
66
+ test('createServerFn with async validator function', () => {
67
+ const fnAfterValidator = createServerFn({
68
+ method: 'GET',
69
+ }).inputValidator((input: string) => Promise.resolve(input))
70
+
71
+ expectTypeOf(fnAfterValidator).toHaveProperty('handler')
72
+ expectTypeOf(fnAfterValidator).toHaveProperty('middleware')
73
+ expectTypeOf(fnAfterValidator).not.toHaveProperty('inputValidator')
74
+
75
+ const fn = fnAfterValidator.handler((options) => {
76
+ expectTypeOf(options).toEqualTypeOf<{
77
+ context: undefined
78
+ data: string
79
+ method: 'GET'
80
+ serverFnMeta: ServerFnMeta
81
+ }>()
82
+ })
83
+
84
+ expectTypeOf(fn).parameter(0).toEqualTypeOf<{
85
+ data: string
86
+ headers?: HeadersInit
87
+ signal?: AbortSignal
88
+ fetch?: CustomFetch
89
+ }>()
90
+
91
+ expectTypeOf<ReturnType<typeof fn>>().resolves.toEqualTypeOf<void>()
92
+ })
93
+
94
+ test('createServerFn with validator with parse method', () => {
95
+ const fnAfterValidator = createServerFn({
96
+ method: 'GET',
97
+ }).inputValidator({
98
+ parse: (input: string) => input,
99
+ })
100
+
101
+ expectTypeOf(fnAfterValidator).toHaveProperty('handler')
102
+ expectTypeOf(fnAfterValidator).toHaveProperty('middleware')
103
+ expectTypeOf(fnAfterValidator).not.toHaveProperty('inputValidator')
104
+
105
+ const fn = fnAfterValidator.handler((options) => {
106
+ expectTypeOf(options).toEqualTypeOf<{
107
+ context: undefined
108
+ data: string
109
+ method: 'GET'
110
+ serverFnMeta: ServerFnMeta
111
+ }>()
112
+ })
113
+
114
+ expectTypeOf(fn).parameter(0).toEqualTypeOf<{
115
+ data: string
116
+ headers?: HeadersInit
117
+ signal?: AbortSignal
118
+ fetch?: CustomFetch
119
+ }>()
120
+
121
+ expectTypeOf<ReturnType<typeof fn>>().resolves.toEqualTypeOf<void>()
122
+ })
123
+
124
+ test('createServerFn with async validator with parse method', () => {
125
+ const fnAfterValidator = createServerFn({
126
+ method: 'GET',
127
+ }).inputValidator({
128
+ parse: (input: string) => Promise.resolve(input),
129
+ })
130
+
131
+ expectTypeOf(fnAfterValidator).toHaveProperty('handler')
132
+ expectTypeOf(fnAfterValidator).toHaveProperty('middleware')
133
+ expectTypeOf(fnAfterValidator).not.toHaveProperty('inputValidator')
134
+
135
+ const fn = fnAfterValidator.handler((options) => {
136
+ expectTypeOf(options).toEqualTypeOf<{
137
+ context: undefined
138
+ data: string
139
+ method: 'GET'
140
+ serverFnMeta: ServerFnMeta
141
+ }>()
142
+ })
143
+
144
+ expectTypeOf(fn).parameter(0).toEqualTypeOf<{
145
+ data: string
146
+ headers?: HeadersInit
147
+ signal?: AbortSignal
148
+ fetch?: CustomFetch
149
+ }>()
150
+
151
+ expectTypeOf<ReturnType<typeof fn>>().resolves.toEqualTypeOf<void>()
152
+ })
153
+
154
+ test('createServerFn with standard validator', () => {
155
+ interface SyncValidator {
156
+ readonly '~standard': {
157
+ types?: {
158
+ input: string
159
+ output: string
160
+ }
161
+ validate: (input: unknown) => {
162
+ value: string
163
+ }
164
+ }
165
+ }
166
+ const validator: SyncValidator = {
167
+ ['~standard']: {
168
+ validate: (input: unknown) => ({
169
+ value: input as string,
170
+ }),
171
+ },
172
+ }
173
+
174
+ const fnAfterValidator = createServerFn({
175
+ method: 'GET',
176
+ }).inputValidator(validator)
177
+
178
+ expectTypeOf(fnAfterValidator).toHaveProperty('handler')
179
+ expectTypeOf(fnAfterValidator).toHaveProperty('middleware')
180
+ expectTypeOf(fnAfterValidator).not.toHaveProperty('inputValidator')
181
+
182
+ const fn = fnAfterValidator.handler((options) => {
183
+ expectTypeOf(options).toEqualTypeOf<{
184
+ context: undefined
185
+ data: string
186
+ method: 'GET'
187
+ serverFnMeta: ServerFnMeta
188
+ }>()
189
+ })
190
+
191
+ expectTypeOf(fn).parameter(0).toEqualTypeOf<{
192
+ data: string
193
+ headers?: HeadersInit
194
+ signal?: AbortSignal
195
+ fetch?: CustomFetch
196
+ }>()
197
+
198
+ expectTypeOf<ReturnType<typeof fn>>().resolves.toEqualTypeOf<void>()
199
+ })
200
+
201
+ test('createServerFn with async standard validator', () => {
202
+ interface AsyncValidator {
203
+ readonly '~standard': {
204
+ types?: {
205
+ input: string
206
+ output: string
207
+ }
208
+ validate: (input: unknown) => Promise<{
209
+ value: string
210
+ }>
211
+ }
212
+ }
213
+ const validator: AsyncValidator = {
214
+ ['~standard']: {
215
+ validate: (input: unknown) =>
216
+ Promise.resolve({
217
+ value: input as string,
218
+ }),
219
+ },
220
+ }
221
+
222
+ const fnAfterValidator = createServerFn({
223
+ method: 'GET',
224
+ }).inputValidator(validator)
225
+
226
+ expectTypeOf(fnAfterValidator).toHaveProperty('handler')
227
+ expectTypeOf(fnAfterValidator).toHaveProperty('middleware')
228
+ expectTypeOf(fnAfterValidator).not.toHaveProperty('inputValidator')
229
+
230
+ const fn = fnAfterValidator.handler((options) => {
231
+ expectTypeOf(options).toEqualTypeOf<{
232
+ context: undefined
233
+ data: string
234
+ method: 'GET'
235
+ serverFnMeta: ServerFnMeta
236
+ }>()
237
+ })
238
+
239
+ expectTypeOf(fn).parameter(0).toEqualTypeOf<{
240
+ data: string
241
+ headers?: HeadersInit
242
+ signal?: AbortSignal
243
+ fetch?: CustomFetch
244
+ }>()
245
+
246
+ expectTypeOf<ReturnType<typeof fn>>().resolves.toEqualTypeOf<void>()
247
+ })
248
+
249
+ test('createServerFn with middleware and context', () => {
250
+ const middleware1 = createMiddleware({ type: 'function' }).server(
251
+ ({ next }) => {
252
+ return next({ context: { a: 'a' } as const })
253
+ },
254
+ )
255
+
256
+ const middleware2 = createMiddleware({ type: 'function' }).server(
257
+ ({ next }) => {
258
+ return next({ context: { b: 'b' } as const })
259
+ },
260
+ )
261
+
262
+ const middleware3 = createMiddleware({ type: 'function' })
263
+ .middleware([middleware1, middleware2])
264
+ .client(({ next }) => {
265
+ return next({ context: { c: 'c' } as const })
266
+ })
267
+
268
+ const middleware4 = createMiddleware({ type: 'function' })
269
+ .middleware([middleware3])
270
+ .client(({ context, next }) => {
271
+ return next({ sendContext: context })
272
+ })
273
+ .server(({ context, next }) => {
274
+ expectTypeOf(context).toEqualTypeOf<{
275
+ readonly a: 'a'
276
+ readonly b: 'b'
277
+ readonly c: 'c'
278
+ }>()
279
+ return next({ context: { d: 'd' } as const })
280
+ })
281
+
282
+ const fnWithMiddleware = createServerFn({ method: 'GET' }).middleware([
283
+ middleware4,
284
+ ])
285
+
286
+ expectTypeOf(fnWithMiddleware).toHaveProperty('handler')
287
+ expectTypeOf(fnWithMiddleware).toHaveProperty('inputValidator')
288
+
289
+ fnWithMiddleware.handler((options) => {
290
+ expectTypeOf(options).toEqualTypeOf<{
291
+ context: {
292
+ readonly a: 'a'
293
+ readonly b: 'b'
294
+ readonly c: 'c'
295
+ readonly d: 'd'
296
+ }
297
+ data: undefined
298
+ method: 'GET'
299
+ serverFnMeta: ServerFnMeta
300
+ }>()
301
+ })
302
+ })
303
+
304
+ describe('createServerFn with middleware and validator', () => {
305
+ const middleware1 = createMiddleware({ type: 'function' }).inputValidator(
306
+ (input: { readonly inputA: 'inputA' }) =>
307
+ ({
308
+ outputA: 'outputA',
309
+ }) as const,
310
+ )
311
+
312
+ const middleware2 = createMiddleware({ type: 'function' }).inputValidator(
313
+ (input: { readonly inputB: 'inputB' }) =>
314
+ ({
315
+ outputB: 'outputB',
316
+ }) as const,
317
+ )
318
+
319
+ const middleware3 = createMiddleware({ type: 'function' }).middleware([
320
+ middleware1,
321
+ middleware2,
322
+ ])
323
+
324
+ test(`response`, () => {
325
+ const fn = createServerFn({ method: 'GET' })
326
+ .middleware([middleware3])
327
+ .inputValidator(
328
+ (input: { readonly inputC: 'inputC' }) =>
329
+ ({
330
+ outputC: 'outputC',
331
+ }) as const,
332
+ )
333
+ .handler((options) => {
334
+ expectTypeOf(options).toEqualTypeOf<{
335
+ context: undefined
336
+ data: {
337
+ readonly outputA: 'outputA'
338
+ readonly outputB: 'outputB'
339
+ readonly outputC: 'outputC'
340
+ }
341
+ method: 'GET'
342
+ serverFnMeta: ServerFnMeta
343
+ }>()
344
+
345
+ return 'some-data' as const
346
+ })
347
+
348
+ expectTypeOf(fn).parameter(0).toEqualTypeOf<{
349
+ data: {
350
+ readonly inputA: 'inputA'
351
+ readonly inputB: 'inputB'
352
+ readonly inputC: 'inputC'
353
+ }
354
+ headers?: HeadersInit
355
+ signal?: AbortSignal
356
+ fetch?: CustomFetch
357
+ }>()
358
+
359
+ expectTypeOf(fn).returns.resolves.toEqualTypeOf<'some-data'>()
360
+ expectTypeOf(() =>
361
+ fn({
362
+ data: { inputA: 'inputA', inputB: 'inputB', inputC: 'inputC' },
363
+ }),
364
+ ).returns.resolves.toEqualTypeOf<'some-data'>()
365
+ })
366
+ })
367
+
368
+ test('createServerFn overrides properties', () => {
369
+ const middleware1 = createMiddleware({ type: 'function' })
370
+ .inputValidator(
371
+ () =>
372
+ ({
373
+ input: 'a' as 'a' | 'b' | 'c',
374
+ }) as const,
375
+ )
376
+ .client(({ context, next }) => {
377
+ expectTypeOf(context).toEqualTypeOf<undefined>()
378
+
379
+ const newContext = { context: 'a' } as const
380
+ return next({ sendContext: newContext, context: newContext })
381
+ })
382
+ .server(({ data, context, next }) => {
383
+ expectTypeOf(data).toEqualTypeOf<{ readonly input: 'a' | 'b' | 'c' }>()
384
+
385
+ expectTypeOf(context).toEqualTypeOf<{
386
+ readonly context: 'a'
387
+ }>()
388
+
389
+ const newContext = { context: 'b' } as const
390
+ return next({ sendContext: newContext, context: newContext })
391
+ })
392
+
393
+ const middleware2 = createMiddleware({ type: 'function' })
394
+ .middleware([middleware1])
395
+ .inputValidator(
396
+ () =>
397
+ ({
398
+ input: 'b' as 'b' | 'c',
399
+ }) as const,
400
+ )
401
+ .client(({ context, next }) => {
402
+ expectTypeOf(context).toEqualTypeOf<{ readonly context: 'a' }>()
403
+
404
+ const newContext = { context: 'aa' } as const
405
+
406
+ return next({ sendContext: newContext, context: newContext })
407
+ })
408
+ .server(({ context, next }) => {
409
+ expectTypeOf(context).toEqualTypeOf<{ readonly context: 'aa' }>()
410
+
411
+ const newContext = { context: 'bb' } as const
412
+
413
+ return next({ sendContext: newContext, context: newContext })
414
+ })
415
+
416
+ createServerFn()
417
+ .middleware([middleware2])
418
+ .inputValidator(
419
+ () =>
420
+ ({
421
+ input: 'c',
422
+ }) as const,
423
+ )
424
+ .handler(({ data, context }) => {
425
+ expectTypeOf(data).toEqualTypeOf<{
426
+ readonly input: 'c'
427
+ }>()
428
+ expectTypeOf(context).toEqualTypeOf<{ readonly context: 'bb' }>()
429
+ })
430
+ })
431
+
432
+ test('createServerFn where validator is a primitive', () => {
433
+ createServerFn({ method: 'GET' })
434
+ .inputValidator(() => 'c' as const)
435
+ .handler((options) => {
436
+ expectTypeOf(options).toEqualTypeOf<{
437
+ context: undefined
438
+ data: 'c'
439
+ method: 'GET'
440
+ serverFnMeta: ServerFnMeta
441
+ }>()
442
+ })
443
+ })
444
+
445
+ test('createServerFn where validator is optional if object is optional', () => {
446
+ const fn = createServerFn({ method: 'GET' })
447
+ .inputValidator((input: 'c' | undefined) => input)
448
+ .handler((options) => {
449
+ expectTypeOf(options).toEqualTypeOf<{
450
+ context: undefined
451
+ data: 'c' | undefined
452
+ method: 'GET'
453
+ serverFnMeta: ServerFnMeta
454
+ }>()
455
+ })
456
+
457
+ expectTypeOf(fn).parameter(0).toEqualTypeOf<
458
+ | {
459
+ data?: 'c' | undefined
460
+ headers?: HeadersInit
461
+ signal?: AbortSignal
462
+ fetch?: CustomFetch
463
+ }
464
+ | undefined
465
+ >()
466
+
467
+ expectTypeOf<ReturnType<typeof fn>>().resolves.toEqualTypeOf<void>()
468
+ })
469
+
470
+ test('createServerFn where data is optional if there is no validator', () => {
471
+ const fn = createServerFn({ method: 'GET' }).handler((options) => {
472
+ expectTypeOf(options).toEqualTypeOf<{
473
+ context: undefined
474
+ data: undefined
475
+ method: 'GET'
476
+ serverFnMeta: ServerFnMeta
477
+ }>()
478
+ })
479
+
480
+ expectTypeOf(fn).parameter(0).toEqualTypeOf<
481
+ | {
482
+ data?: undefined
483
+ headers?: HeadersInit
484
+ signal?: AbortSignal
485
+ fetch?: CustomFetch
486
+ }
487
+ | undefined
488
+ >()
489
+
490
+ expectTypeOf<ReturnType<typeof fn>>().resolves.toEqualTypeOf<void>()
491
+ })
492
+
493
+ test('createServerFn returns Date', () => {
494
+ const fn = createServerFn().handler(() => ({
495
+ dates: [new Date(), new Date()] as const,
496
+ }))
497
+
498
+ expectTypeOf<ReturnType<typeof fn>>().toMatchTypeOf<Promise<unknown>>()
499
+ expectTypeOf<Awaited<ReturnType<typeof fn>>>().toMatchTypeOf<
500
+ ValidateSerializableInput<Register, { dates: readonly [Date, Date] }>
501
+ >()
502
+ })
503
+
504
+ test('createServerFn returns undefined', () => {
505
+ const fn = createServerFn().handler(() => ({
506
+ nothing: undefined,
507
+ }))
508
+
509
+ expectTypeOf(fn()).toEqualTypeOf<Promise<{ nothing: undefined }>>()
510
+ })
511
+
512
+ test('createServerFn cannot return function', () => {
513
+ expectTypeOf(createServerFn().handler<{ func: () => 'func' }>)
514
+ .parameter(0)
515
+ .returns.toEqualTypeOf<{ func: 'Function is not serializable' }>()
516
+ })
517
+
518
+ test('createServerFn cannot validate function', () => {
519
+ const validator = createServerFn().inputValidator<
520
+ (input: { func: () => 'string' }) => { output: 'string' }
521
+ >
522
+
523
+ expectTypeOf(validator)
524
+ .parameter(0)
525
+ .toEqualTypeOf<
526
+ Constrain<
527
+ (input: { func: () => 'string' }) => { output: 'string' },
528
+ Validator<{ func: 'Function is not serializable' }, any>
529
+ >
530
+ >()
531
+ })
532
+
533
+ test('createServerFn can validate Date', () => {
534
+ const validator = createServerFn().inputValidator<
535
+ (input: Date) => { output: 'string' }
536
+ >
537
+
538
+ expectTypeOf(validator)
539
+ .parameter(0)
540
+ .toEqualTypeOf<
541
+ ConstrainValidator<Register, 'GET', (input: Date) => { output: 'string' }>
542
+ >()
543
+ })
544
+
545
+ test('createServerFn can validate FormData', () => {
546
+ const validator = createServerFn({ method: 'POST' }).inputValidator<
547
+ (input: FormData) => { output: 'string' }
548
+ >
549
+
550
+ expectTypeOf(validator).parameter(0).parameter(0).toEqualTypeOf<FormData>()
551
+ })
552
+
553
+ test('createServerFn cannot validate FormData for GET', () => {
554
+ const validator = createServerFn({ method: 'GET' }).inputValidator<
555
+ (input: FormData) => { output: 'string' }
556
+ >
557
+
558
+ expectTypeOf(validator)
559
+ .parameter(0)
560
+ .parameter(0)
561
+ .not.toEqualTypeOf<FormData>()
562
+ })
563
+
564
+ describe('response', () => {
565
+ test(`client receives Response when Response is returned`, () => {
566
+ const fn = createServerFn().handler(() => {
567
+ return new Response('Hello World')
568
+ })
569
+
570
+ expectTypeOf(fn()).toEqualTypeOf<Promise<Response>>()
571
+ })
572
+
573
+ test(`client receives union when handler may return Response or string`, () => {
574
+ const fn = createServerFn().handler(() => {
575
+ const result: Response | 'Hello World' =
576
+ Math.random() > 0.5 ? new Response('Hello World') : 'Hello World'
577
+
578
+ return result
579
+ })
580
+
581
+ expectTypeOf(fn()).toEqualTypeOf<Promise<Response | 'Hello World'>>()
582
+ })
583
+ })
584
+
585
+ test('ServerFnReturnType distributes Response union', () => {
586
+ expectTypeOf<
587
+ ServerFnReturnType<Register, Response | 'Hello World'>
588
+ >().toEqualTypeOf<Response | 'Hello World'>()
589
+ })
590
+
591
+ test('createServerFn can be used as a mutation function', () => {
592
+ const serverFn = createServerFn()
593
+ .inputValidator((data: number) => data)
594
+ .handler(() => 'foo')
595
+
596
+ type MutationFunction<TData = unknown, TVariables = unknown> = (
597
+ variables: TVariables,
598
+ ) => Promise<TData>
599
+
600
+ // simplifeid "clone" of @tansctack/react-query's useMutation
601
+ const useMutation = <TData, TVariables>(
602
+ fn: MutationFunction<TData, TVariables>,
603
+ ) => {}
604
+
605
+ useMutation(serverFn)
606
+ })
607
+
608
+ test('createServerFn validator infers unknown for default input type', () => {
609
+ const fn = createServerFn()
610
+ .inputValidator((input) => {
611
+ expectTypeOf(input).toEqualTypeOf<unknown>()
612
+
613
+ if (typeof input === 'number') return 'success' as const
614
+
615
+ return 'failed' as const
616
+ })
617
+ .handler(({ data }) => {
618
+ expectTypeOf(data).toEqualTypeOf<'success' | 'failed'>()
619
+
620
+ return data
621
+ })
622
+
623
+ expectTypeOf(fn).parameter(0).toEqualTypeOf<
624
+ | {
625
+ data?: unknown | undefined
626
+ headers?: HeadersInit
627
+ signal?: AbortSignal
628
+ fetch?: CustomFetch
629
+ }
630
+ | undefined
631
+ >()
632
+
633
+ expectTypeOf(fn()).toEqualTypeOf<Promise<'failed' | 'success'>>()
634
+ })
635
+
636
+ test('incrementally building createServerFn with multiple middleware calls', () => {
637
+ const middleware1 = createMiddleware({ type: 'function' }).server(
638
+ ({ next }) => {
639
+ return next({ context: { a: 'a' } as const })
640
+ },
641
+ )
642
+
643
+ const middleware2 = createMiddleware({ type: 'function' }).server(
644
+ ({ next }) => {
645
+ return next({ context: { b: 'b' } as const })
646
+ },
647
+ )
648
+
649
+ const middleware3 = createMiddleware({ type: 'function' }).server(
650
+ ({ next }) => {
651
+ return next({ context: { c: 'c' } as const })
652
+ },
653
+ )
654
+
655
+ const builderWithMw1 = createServerFn({ method: 'GET' }).middleware([
656
+ middleware1,
657
+ ])
658
+
659
+ expectTypeOf(builderWithMw1).toHaveProperty('handler')
660
+ expectTypeOf(builderWithMw1).toHaveProperty('inputValidator')
661
+ expectTypeOf(builderWithMw1).toHaveProperty('middleware')
662
+
663
+ builderWithMw1.handler((options) => {
664
+ expectTypeOf(options).toEqualTypeOf<{
665
+ context: {
666
+ readonly a: 'a'
667
+ }
668
+ data: undefined
669
+ method: 'GET'
670
+ serverFnMeta: ServerFnMeta
671
+ }>()
672
+ })
673
+
674
+ // overrides method
675
+ const builderWithMw2 = builderWithMw1({ method: 'POST' }).middleware([
676
+ middleware2,
677
+ ])
678
+
679
+ expectTypeOf(builderWithMw2).toHaveProperty('handler')
680
+ expectTypeOf(builderWithMw2).toHaveProperty('inputValidator')
681
+ expectTypeOf(builderWithMw2).toHaveProperty('middleware')
682
+
683
+ builderWithMw2.handler((options) => {
684
+ expectTypeOf(options).toEqualTypeOf<{
685
+ context: {
686
+ readonly a: 'a'
687
+ readonly b: 'b'
688
+ }
689
+ data: undefined
690
+ method: 'POST'
691
+ serverFnMeta: ServerFnMeta
692
+ }>()
693
+ })
694
+
695
+ // overrides method again
696
+ const builderWithMw3 = builderWithMw2({ method: 'GET' }).middleware([
697
+ middleware3,
698
+ ])
699
+
700
+ expectTypeOf(builderWithMw3).toHaveProperty('handler')
701
+ expectTypeOf(builderWithMw3).toHaveProperty('inputValidator')
702
+ expectTypeOf(builderWithMw3).toHaveProperty('middleware')
703
+
704
+ builderWithMw3.handler((options) => {
705
+ expectTypeOf(options).toEqualTypeOf<{
706
+ context: {
707
+ readonly a: 'a'
708
+ readonly b: 'b'
709
+ readonly c: 'c'
710
+ }
711
+ data: undefined
712
+ method: 'GET'
713
+
714
+ serverFnMeta: ServerFnMeta
715
+ }>()
716
+ })
717
+ })
718
+
719
+ test('compose middlewares and server function factories', () => {
720
+ const middleware1 = createMiddleware({ type: 'function' }).server(
721
+ ({ next }) => {
722
+ return next({ context: { a: 'a' } as const })
723
+ },
724
+ )
725
+
726
+ const middleware2 = createMiddleware({ type: 'function' }).server(
727
+ ({ next }) => {
728
+ return next({ context: { b: 'b' } as const })
729
+ },
730
+ )
731
+
732
+ const builderWithMw1 = createServerFn().middleware([middleware1])
733
+
734
+ const composedBuilder = createServerFn({ method: 'GET' }).middleware([
735
+ middleware2,
736
+ builderWithMw1,
737
+ ])
738
+
739
+ composedBuilder.handler((options) => {
740
+ expectTypeOf(options).toEqualTypeOf<{
741
+ context: {
742
+ readonly a: 'a'
743
+ readonly b: 'b'
744
+ }
745
+ data: undefined
746
+ method: 'GET'
747
+ serverFnMeta: ServerFnMeta
748
+ }>()
749
+ })
750
+ })
751
+
752
+ test('createServerFn with request middleware', () => {
753
+ const reqMw = createMiddleware().server(({ next }) => {
754
+ return next()
755
+ })
756
+ const fn = createServerFn()
757
+ .middleware([reqMw])
758
+ .handler(() => ({}))
759
+
760
+ expectTypeOf(fn()).toEqualTypeOf<Promise<{}>>()
761
+ })
762
+
763
+ test('createServerFn with request middleware and function middleware', () => {
764
+ const reqMw = createMiddleware().server(({ next }) => {
765
+ return next()
766
+ })
767
+
768
+ const funMw = createMiddleware({ type: 'function' })
769
+ .inputValidator((x: string) => x)
770
+ .server(({ next }) => {
771
+ return next({ context: { a: 'a' } as const })
772
+ })
773
+ const fn = createServerFn()
774
+ .middleware([reqMw, funMw])
775
+ .handler(() => ({}))
776
+
777
+ expectTypeOf(fn({ data: 'a' })).toEqualTypeOf<Promise<{}>>()
778
+ })
779
+
780
+ test('createServerFn with inputValidator and request middleware', () => {
781
+ const loggingMiddleware = createMiddleware().server(async ({ next }) => {
782
+ console.log('Logging middleware executed on the server')
783
+ const result = await next()
784
+ return result
785
+ })
786
+
787
+ const fn = createServerFn()
788
+ .middleware([loggingMiddleware])
789
+ .inputValidator(({ userName }: { userName: string }) => {
790
+ return { userName }
791
+ })
792
+ .handler(async ({ data }) => {
793
+ return data.userName
794
+ })
795
+
796
+ expectTypeOf(fn({ data: { userName: 'test' } })).toEqualTypeOf<
797
+ Promise<string>
798
+ >()
799
+ })
800
+
801
+ test('createServerFn has TSS_SERVER_FUNCTION symbol set', () => {
802
+ const fn = createServerFn().handler(() => ({}))
803
+ expectTypeOf(fn).toHaveProperty(TSS_SERVER_FUNCTION)
804
+ expectTypeOf(fn[TSS_SERVER_FUNCTION]).toEqualTypeOf<true>()
805
+ })
806
+
807
+ test('createServerFn fetcher itself is serializable', () => {
808
+ const fn1 = createServerFn().handler(() => ({}))
809
+ const fn2 = createServerFn().handler(() => fn1)
810
+ })
811
+
812
+ test('createServerFn returns async Response', () => {
813
+ const serverFn = createServerFn().handler(async () => {
814
+ return new Response(new Blob([JSON.stringify({ a: 1 })]), {
815
+ status: 200,
816
+ headers: {
817
+ 'Content-Type': 'application/json',
818
+ },
819
+ })
820
+ })
821
+
822
+ expectTypeOf(serverFn()).toEqualTypeOf<Promise<Response>>()
823
+ })
824
+
825
+ test('createServerFn returns sync Response', () => {
826
+ const serverFn = createServerFn().handler(() => {
827
+ return new Response(new Blob([JSON.stringify({ a: 1 })]), {
828
+ status: 200,
829
+ headers: {
830
+ 'Content-Type': 'application/json',
831
+ },
832
+ })
833
+ })
834
+
835
+ expectTypeOf(serverFn()).toEqualTypeOf<Promise<Response>>()
836
+ })
837
+
838
+ test('createServerFn returns async array', () => {
839
+ const result: Array<{ a: number }> = [{ a: 1 }]
840
+ const serverFn = createServerFn({ method: 'GET' }).handler(async () => {
841
+ return result
842
+ })
843
+
844
+ expectTypeOf(serverFn()).toEqualTypeOf<Promise<Array<{ a: number }>>>()
845
+ })
846
+
847
+ test('createServerFn returns sync array', () => {
848
+ const result: Array<{ a: number }> = [{ a: 1 }]
849
+ const serverFn = createServerFn({ method: 'GET' }).handler(() => {
850
+ return result
851
+ })
852
+
853
+ expectTypeOf(serverFn()).toEqualTypeOf<Promise<Array<{ a: number }>>>()
854
+ })
855
+
856
+ test('createServerFn respects TsrSerializable', () => {
857
+ type MyCustomType = { f: () => void; value: string }
858
+ type MyCustomTypeSerializable = MyCustomType & TsrSerializable
859
+ const fn1 = createServerFn().handler(() => {
860
+ const custom: MyCustomType = { f: () => {}, value: 'test' }
861
+ return { nested: { custom: custom as MyCustomTypeSerializable } }
862
+ })
863
+ expectTypeOf(fn1()).toEqualTypeOf<
864
+ Promise<{ nested: { custom: MyCustomTypeSerializable } }>
865
+ >()
866
+ })