@tanstack/start-client-core 1.114.6 → 1.114.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.
@@ -0,0 +1,488 @@
1
+ import { describe, expectTypeOf, test } from 'vitest'
2
+ import { createMiddleware } from '../createMiddleware'
3
+ import { createServerFn } from '../createServerFn'
4
+ import type { Constrain, Validator } from '@tanstack/router-core'
5
+
6
+ test('createServerFn method with autocomplete', () => {
7
+ createServerFn().handler((options) => {
8
+ expectTypeOf(options.method).toEqualTypeOf<'GET' | 'POST'>()
9
+ })
10
+ })
11
+
12
+ test('createServerFn without middleware', () => {
13
+ expectTypeOf(createServerFn()).toHaveProperty('handler')
14
+ expectTypeOf(createServerFn()).toHaveProperty('middleware')
15
+ expectTypeOf(createServerFn()).toHaveProperty('validator')
16
+
17
+ createServerFn({ method: 'GET' }).handler((options) => {
18
+ expectTypeOf(options).toEqualTypeOf<{
19
+ method: 'GET'
20
+ context: undefined
21
+ data: undefined
22
+ signal: AbortSignal
23
+ response: 'data'
24
+ }>()
25
+ })
26
+ })
27
+
28
+ test('createServerFn with validator', () => {
29
+ const fnAfterValidator = createServerFn({
30
+ method: 'GET',
31
+ }).validator((input: { input: string }) => ({
32
+ a: input.input,
33
+ }))
34
+
35
+ expectTypeOf(fnAfterValidator).toHaveProperty('handler')
36
+ expectTypeOf(fnAfterValidator).toHaveProperty('middleware')
37
+ expectTypeOf(fnAfterValidator).not.toHaveProperty('validator')
38
+
39
+ const fn = fnAfterValidator.handler((options) => {
40
+ expectTypeOf(options).toEqualTypeOf<{
41
+ method: 'GET'
42
+ context: undefined
43
+ data: {
44
+ a: string
45
+ }
46
+ signal: AbortSignal
47
+ response: 'data'
48
+ }>()
49
+ })
50
+
51
+ expectTypeOf(fn).parameter(0).toEqualTypeOf<{
52
+ data: { input: string }
53
+ headers?: HeadersInit
54
+ type?: 'static' | 'dynamic'
55
+ signal?: AbortSignal
56
+ }>()
57
+
58
+ expectTypeOf<ReturnType<typeof fn>>().resolves.toEqualTypeOf<void>()
59
+ })
60
+
61
+ test('createServerFn with middleware and context', () => {
62
+ const middleware1 = createMiddleware().server(({ next }) => {
63
+ return next({ context: { a: 'a' } as const })
64
+ })
65
+
66
+ const middleware2 = createMiddleware().server(({ next }) => {
67
+ return next({ context: { b: 'b' } as const })
68
+ })
69
+
70
+ const middleware3 = createMiddleware()
71
+ .middleware([middleware1, middleware2])
72
+ .client(({ next }) => {
73
+ return next({ context: { c: 'c' } as const })
74
+ })
75
+
76
+ const middleware4 = createMiddleware()
77
+ .middleware([middleware3])
78
+ .client(({ context, next }) => {
79
+ return next({ sendContext: context })
80
+ })
81
+ .server(({ context, next }) => {
82
+ expectTypeOf(context).toEqualTypeOf<{
83
+ readonly a: 'a'
84
+ readonly b: 'b'
85
+ readonly c: 'c'
86
+ }>()
87
+ return next({ context: { d: 'd' } as const })
88
+ })
89
+
90
+ const fnWithMiddleware = createServerFn({ method: 'GET' }).middleware([
91
+ middleware4,
92
+ ])
93
+
94
+ expectTypeOf(fnWithMiddleware).toHaveProperty('handler')
95
+ expectTypeOf(fnWithMiddleware).toHaveProperty('validator')
96
+ expectTypeOf(fnWithMiddleware).not.toHaveProperty('middleware')
97
+
98
+ fnWithMiddleware.handler((options) => {
99
+ expectTypeOf(options).toEqualTypeOf<{
100
+ method: 'GET'
101
+ context: {
102
+ readonly a: 'a'
103
+ readonly b: 'b'
104
+ readonly c: 'c'
105
+ readonly d: 'd'
106
+ }
107
+ data: undefined
108
+ signal: AbortSignal
109
+ response: 'data'
110
+ }>()
111
+ })
112
+ })
113
+
114
+ describe('createServerFn with middleware and validator', () => {
115
+ const middleware1 = createMiddleware().validator(
116
+ (input: { readonly inputA: 'inputA' }) =>
117
+ ({
118
+ outputA: 'outputA',
119
+ }) as const,
120
+ )
121
+
122
+ const middleware2 = createMiddleware().validator(
123
+ (input: { readonly inputB: 'inputB' }) =>
124
+ ({
125
+ outputB: 'outputB',
126
+ }) as const,
127
+ )
128
+
129
+ const middleware3 = createMiddleware().middleware([middleware1, middleware2])
130
+
131
+ test(`response: 'data'`, () => {
132
+ const fn = createServerFn({ method: 'GET', response: 'data' })
133
+ .middleware([middleware3])
134
+ .validator(
135
+ (input: { readonly inputC: 'inputC' }) =>
136
+ ({
137
+ outputC: 'outputC',
138
+ }) as const,
139
+ )
140
+ .handler((options) => {
141
+ expectTypeOf(options).toEqualTypeOf<{
142
+ method: 'GET'
143
+ context: undefined
144
+ data: {
145
+ readonly outputA: 'outputA'
146
+ readonly outputB: 'outputB'
147
+ readonly outputC: 'outputC'
148
+ }
149
+ signal: AbortSignal
150
+ response: 'data'
151
+ }>()
152
+
153
+ return 'some-data' as const
154
+ })
155
+
156
+ expectTypeOf(fn).parameter(0).toEqualTypeOf<{
157
+ data: {
158
+ readonly inputA: 'inputA'
159
+ readonly inputB: 'inputB'
160
+ readonly inputC: 'inputC'
161
+ }
162
+ headers?: HeadersInit
163
+ type?: 'static' | 'dynamic'
164
+ signal?: AbortSignal
165
+ }>()
166
+
167
+ expectTypeOf(fn).returns.resolves.toEqualTypeOf<'some-data'>()
168
+ expectTypeOf(() =>
169
+ fn({
170
+ data: { inputA: 'inputA', inputB: 'inputB', inputC: 'inputC' },
171
+ }),
172
+ ).returns.resolves.toEqualTypeOf<'some-data'>()
173
+ })
174
+
175
+ test(`response: 'full'`, () => {
176
+ const fn = createServerFn({ method: 'GET', response: 'full' })
177
+ .middleware([middleware3])
178
+ .validator(
179
+ (input: { readonly inputC: 'inputC' }) =>
180
+ ({
181
+ outputC: 'outputC',
182
+ }) as const,
183
+ )
184
+ .handler((options) => {
185
+ expectTypeOf(options).toEqualTypeOf<{
186
+ method: 'GET'
187
+ context: undefined
188
+ data: {
189
+ readonly outputA: 'outputA'
190
+ readonly outputB: 'outputB'
191
+ readonly outputC: 'outputC'
192
+ }
193
+ signal: AbortSignal
194
+ response: 'full'
195
+ }>()
196
+
197
+ return 'some-data' as const
198
+ })
199
+
200
+ expectTypeOf(fn).parameter(0).toEqualTypeOf<{
201
+ data: {
202
+ readonly inputA: 'inputA'
203
+ readonly inputB: 'inputB'
204
+ readonly inputC: 'inputC'
205
+ }
206
+ headers?: HeadersInit
207
+ type?: 'static' | 'dynamic'
208
+ signal?: AbortSignal
209
+ }>()
210
+
211
+ expectTypeOf(() =>
212
+ fn({
213
+ data: { inputA: 'inputA', inputB: 'inputB', inputC: 'inputC' },
214
+ }),
215
+ ).returns.resolves.toEqualTypeOf<{
216
+ result: 'some-data'
217
+ context: undefined
218
+ error: unknown
219
+ }>()
220
+ })
221
+ })
222
+
223
+ test('createServerFn overrides properties', () => {
224
+ const middleware1 = createMiddleware()
225
+ .validator(
226
+ () =>
227
+ ({
228
+ input: 'a' as 'a' | 'b' | 'c',
229
+ }) as const,
230
+ )
231
+ .client(({ context, next }) => {
232
+ expectTypeOf(context).toEqualTypeOf<undefined>()
233
+
234
+ const newContext = { context: 'a' } as const
235
+ return next({ sendContext: newContext, context: newContext })
236
+ })
237
+ .server(({ data, context, next }) => {
238
+ expectTypeOf(data).toEqualTypeOf<{ readonly input: 'a' | 'b' | 'c' }>()
239
+
240
+ expectTypeOf(context).toEqualTypeOf<{
241
+ readonly context: 'a'
242
+ }>()
243
+
244
+ const newContext = { context: 'b' } as const
245
+ return next({ sendContext: newContext, context: newContext })
246
+ })
247
+
248
+ const middleware2 = createMiddleware()
249
+ .middleware([middleware1])
250
+ .validator(
251
+ () =>
252
+ ({
253
+ input: 'b' as 'b' | 'c',
254
+ }) as const,
255
+ )
256
+ .client(({ context, next }) => {
257
+ expectTypeOf(context).toEqualTypeOf<{ readonly context: 'a' }>()
258
+
259
+ const newContext = { context: 'aa' } as const
260
+
261
+ return next({ sendContext: newContext, context: newContext })
262
+ })
263
+ .server(({ context, next }) => {
264
+ expectTypeOf(context).toEqualTypeOf<{ readonly context: 'aa' }>()
265
+
266
+ const newContext = { context: 'bb' } as const
267
+
268
+ return next({ sendContext: newContext, context: newContext })
269
+ })
270
+
271
+ createServerFn()
272
+ .middleware([middleware2])
273
+ .validator(
274
+ () =>
275
+ ({
276
+ input: 'c',
277
+ }) as const,
278
+ )
279
+ .handler(({ data, context }) => {
280
+ expectTypeOf(data).toEqualTypeOf<{
281
+ readonly input: 'c'
282
+ }>()
283
+ expectTypeOf(context).toEqualTypeOf<{ readonly context: 'bb' }>()
284
+ })
285
+ })
286
+
287
+ test('createServerFn where validator is a primitive', () => {
288
+ createServerFn({ method: 'GET' })
289
+ .validator(() => 'c' as const)
290
+ .handler((options) => {
291
+ expectTypeOf(options).toEqualTypeOf<{
292
+ method: 'GET'
293
+ context: undefined
294
+ data: 'c'
295
+ signal: AbortSignal
296
+ response: 'data'
297
+ }>()
298
+ })
299
+ })
300
+
301
+ test('createServerFn where validator is optional if object is optional', () => {
302
+ const fn = createServerFn({ method: 'GET' })
303
+ .validator((input: 'c' | undefined) => input)
304
+ .handler((options) => {
305
+ expectTypeOf(options).toEqualTypeOf<{
306
+ method: 'GET'
307
+ context: undefined
308
+ data: 'c' | undefined
309
+ signal: AbortSignal
310
+ response: 'data'
311
+ }>()
312
+ })
313
+
314
+ expectTypeOf(fn).parameter(0).toEqualTypeOf<
315
+ | {
316
+ data?: 'c' | undefined
317
+ headers?: HeadersInit
318
+ type?: 'static' | 'dynamic'
319
+ signal?: AbortSignal
320
+ }
321
+ | undefined
322
+ >()
323
+
324
+ expectTypeOf<ReturnType<typeof fn>>().resolves.toEqualTypeOf<void>()
325
+ })
326
+
327
+ test('createServerFn where data is optional if there is no validator', () => {
328
+ const fn = createServerFn({ method: 'GET' }).handler((options) => {
329
+ expectTypeOf(options).toEqualTypeOf<{
330
+ method: 'GET'
331
+ context: undefined
332
+ data: undefined
333
+ signal: AbortSignal
334
+ response: 'data'
335
+ }>()
336
+ })
337
+
338
+ expectTypeOf(fn).parameter(0).toEqualTypeOf<
339
+ | {
340
+ data?: undefined
341
+ headers?: HeadersInit
342
+ type?: 'static' | 'dynamic'
343
+ signal?: AbortSignal
344
+ }
345
+ | undefined
346
+ >()
347
+
348
+ expectTypeOf<ReturnType<typeof fn>>().resolves.toEqualTypeOf<void>()
349
+ })
350
+
351
+ test('createServerFn returns Date', () => {
352
+ const fn = createServerFn().handler(() => ({
353
+ dates: [new Date(), new Date()] as const,
354
+ }))
355
+
356
+ expectTypeOf(fn()).toEqualTypeOf<Promise<{ dates: readonly [Date, Date] }>>()
357
+ })
358
+
359
+ test('createServerFn returns undefined', () => {
360
+ const fn = createServerFn().handler(() => ({
361
+ nothing: undefined,
362
+ }))
363
+
364
+ expectTypeOf(fn()).toEqualTypeOf<Promise<{ nothing: undefined }>>()
365
+ })
366
+
367
+ test('createServerFn cannot return function', () => {
368
+ expectTypeOf(createServerFn().handler<{ func: () => 'func' }>)
369
+ .parameter(0)
370
+ .returns.toEqualTypeOf<
371
+ | { func: 'Function is not serializable' }
372
+ | Promise<{ func: 'Function is not serializable' }>
373
+ >()
374
+ })
375
+
376
+ test('createServerFn cannot validate function', () => {
377
+ const validator = createServerFn().validator<
378
+ (input: { func: () => 'string' }) => { output: 'string' }
379
+ >
380
+
381
+ expectTypeOf(validator)
382
+ .parameter(0)
383
+ .toEqualTypeOf<
384
+ Constrain<
385
+ (input: { func: () => 'string' }) => { output: 'string' },
386
+ Validator<{ func: 'Function is not serializable' }, any>
387
+ >
388
+ >()
389
+ })
390
+
391
+ test('createServerFn can validate Date', () => {
392
+ const validator = createServerFn().validator<
393
+ (input: Date) => { output: 'string' }
394
+ >
395
+
396
+ expectTypeOf(validator)
397
+ .parameter(0)
398
+ .toEqualTypeOf<
399
+ Constrain<(input: Date) => { output: 'string' }, Validator<Date, any>>
400
+ >()
401
+ })
402
+
403
+ test('createServerFn can validate FormData', () => {
404
+ const validator = createServerFn().validator<
405
+ (input: FormData) => { output: 'string' }
406
+ >
407
+
408
+ expectTypeOf(validator)
409
+ .parameter(0)
410
+ .toEqualTypeOf<
411
+ Constrain<
412
+ (input: FormData) => { output: 'string' },
413
+ Validator<FormData, any>
414
+ >
415
+ >()
416
+ })
417
+
418
+ describe('response', () => {
419
+ describe('data', () => {
420
+ test(`response: 'data' is passed into handler without response being set`, () => {
421
+ createServerFn().handler((options) => {
422
+ expectTypeOf(options.response).toEqualTypeOf<'data'>()
423
+ })
424
+ })
425
+
426
+ test(`response: 'data' is passed into handler with explicit response: 'data'`, () => {
427
+ createServerFn({ response: 'data' }).handler((options) => {
428
+ expectTypeOf(options.response).toEqualTypeOf<'data'>()
429
+ })
430
+ })
431
+ })
432
+ describe('full', () => {
433
+ test(`response: 'full' is passed into handler`, () => {
434
+ createServerFn({ response: 'full' }).handler((options) => {
435
+ expectTypeOf(options.response).toEqualTypeOf<'full'>()
436
+ })
437
+ })
438
+ })
439
+
440
+ describe('raw', () => {
441
+ test(`response: 'raw' is passed into handler`, () => {
442
+ createServerFn({ response: 'raw' }).handler((options) => {
443
+ expectTypeOf(options.response).toEqualTypeOf<'raw'>()
444
+ return null
445
+ })
446
+ })
447
+ })
448
+ test(`client receives Response when Response is returned`, () => {
449
+ const fn = createServerFn({ response: 'raw' }).handler(() => {
450
+ return new Response('Hello World')
451
+ })
452
+
453
+ expectTypeOf(fn()).toEqualTypeOf<Promise<Response>>()
454
+ })
455
+
456
+ test(`client receives Response when ReadableStream is returned`, () => {
457
+ const fn = createServerFn({ response: 'raw' }).handler(() => {
458
+ return new ReadableStream()
459
+ })
460
+
461
+ expectTypeOf(fn()).toEqualTypeOf<Promise<Response>>()
462
+ })
463
+
464
+ test(`client receives Response when string is returned`, () => {
465
+ const fn = createServerFn({ response: 'raw' }).handler(() => {
466
+ return 'hello'
467
+ })
468
+
469
+ expectTypeOf(fn()).toEqualTypeOf<Promise<Response>>()
470
+ })
471
+ })
472
+
473
+ test('createServerFn can be used as a mutation function', () => {
474
+ const serverFn = createServerFn()
475
+ .validator((data: number) => data)
476
+ .handler(() => 'foo')
477
+
478
+ type MutationFunction<TData = unknown, TVariables = unknown> = (
479
+ variables: TVariables,
480
+ ) => Promise<TData>
481
+
482
+ // simplifeid "clone" of @tansctack/react-query's useMutation
483
+ const useMutation = <TData, TVariables>(
484
+ fn: MutationFunction<TData, TVariables>,
485
+ ) => {}
486
+
487
+ useMutation(serverFn)
488
+ })