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