Skip to content

Commit 53d3da5

Browse files
authored
Merge pull request #506 from traversable/valibot-default-value-type-fix
fix(valibot): fixes return type for `vx.defaultValue`
2 parents b5498bf + 1aa2a24 commit 53d3da5

File tree

3 files changed

+296
-9
lines changed

3 files changed

+296
-9
lines changed

.changeset/purple-walls-swim.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@traversable/valibot": patch
3+
---
4+
5+
fix(valibot): fixes return type for `vx.defaultValue`

packages/valibot/src/default-value.ts

Lines changed: 67 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import * as v from 'valibot'
2-
import type { Primitive } from '@traversable/registry'
2+
import type { Primitive, Showable } from '@traversable/registry'
33
import { fn, has, isPrimitive } from '@traversable/registry'
44
import type { AnyTag, AnyValibotSchema } from '@traversable/valibot-types'
55
import { isNullary, fold, tagged } from '@traversable/valibot-types'
@@ -43,14 +43,70 @@ const pathIncludes = (longer: (keyof any)[], shorter: (keyof any)[]) => pathsAre
4343
shorter
4444
)
4545

46-
export type defaultValue<T, Fallback = undefined>
47-
= T extends Primitive | Atom ? T | Fallback
48-
: T extends Set<any> ? Set<defaultValue<ReturnType<(ReturnType<T['values']>['return'] & {})>['value'] & {}, Fallback>>
46+
export type Match<T, Matchers>
47+
= T extends Primitive ?
48+
| Matchers[T & keyof Matchers]
49+
| Matchers[`${T & Showable}` & keyof Matchers]
50+
: { [K in keyof T]: Match<T[K], Matchers> }
51+
52+
export type MatchPrimitive<
53+
T,
54+
TypeMap,
55+
M extends MergeTypeMap<TypeMap> = MergeTypeMap<TypeMap>
56+
> = Match<T,
57+
& { [x: string]: M['string'] }
58+
& { [x: number]: M['number'] }
59+
& { [x: symbol]: M['boolean'] }
60+
& { [x: `${bigint}`]: M['bigint'] }
61+
& {
62+
true: M['boolean']
63+
false: M['boolean']
64+
null: M['null']
65+
undefined: M['undefined']
66+
}
67+
>
68+
69+
export type DefaultTypeMap = {
70+
never: undefined
71+
any: undefined
72+
unknown: undefined
73+
undefined: undefined
74+
null: undefined
75+
boolean: undefined
76+
bigint: undefined
77+
number: undefined
78+
string: undefined
79+
}
80+
81+
export type AnyTypeMap = {
82+
never: unknown
83+
any: unknown
84+
unknown: unknown
85+
undefined: unknown
86+
null: unknown
87+
boolean: unknown
88+
bigint: unknown
89+
number: unknown
90+
string: unknown
91+
}
92+
93+
export type MergeTypeMap<T> = {
94+
[K in keyof DefaultTypeMap]: K extends keyof T ? T[K] : DefaultTypeMap[K]
95+
}
96+
97+
export type defaultValue<
98+
T,
99+
TypeMap = DefaultTypeMap,
100+
M extends MergeTypeMap<TypeMap> = MergeTypeMap<TypeMap>
101+
> = unknown extends T ? (0 extends T & 1 ? M['any'] : M['unknown'])
102+
: [T] extends [never] ? M['never']
103+
: T extends Primitive ? MatchPrimitive<T, M>
104+
: T extends Set<any> ? Set<defaultValue<ReturnType<(ReturnType<T['values']>['return'] & {})>['value'] & {}, TypeMap, M>>
49105
: T extends Map<any, any> ? Map<
50-
defaultValue<({} & ReturnType<{} & ReturnType<T['entries']>['return']>['value'])[0], Fallback>,
51-
defaultValue<({} & ReturnType<{} & ReturnType<T['entries']>['return']>['value'])[1], Fallback>
106+
defaultValue<({} & ReturnType<{} & ReturnType<T['entries']>['return']>['value'])[0], TypeMap, M>,
107+
defaultValue<({} & ReturnType<{} & ReturnType<T['entries']>['return']>['value'])[1], TypeMap, M>
52108
>
53-
: { [K in keyof T]-?: defaultValue<T[K], Fallback> }
109+
: { [K in keyof T]: defaultValue<T[K], TypeMap, M> }
54110

55111
/**
56112
* ## {@link defaultValue `vx.defaultValue`}
@@ -131,8 +187,11 @@ export type defaultValue<T, Fallback = undefined>
131187
* )
132188
*/
133189

190+
// export function defaultValue<T extends AnyValibotSchema>(type: T): defaultValue<v.InferOutput<T>>
191+
// export function defaultValue<T extends AnyValibotSchema, Leaves extends Fallbacks>(type: T, options: Options<Leaves>): defaultValue<v.InferOutput<T>, Leaves[keyof Leaves]>
192+
// export function defaultValue<T extends AnyValibotSchema>(
134193
export function defaultValue<T extends AnyValibotSchema>(type: T): defaultValue<v.InferOutput<T>>
135-
export function defaultValue<T extends AnyValibotSchema, Leaves extends Fallbacks>(type: T, options: Options<Leaves>): defaultValue<v.InferOutput<T>, Leaves[keyof Leaves]>
194+
export function defaultValue<T extends AnyValibotSchema, Leaves extends Fallbacks>(type: T, options: Options<Leaves>): defaultValue<v.InferOutput<T>, Leaves>
136195
export function defaultValue<T extends AnyValibotSchema>(
137196
schema: T, {
138197
fallbacks = defaultValue.defaults.fallbacks,

packages/valibot/test/default-value.test.ts

Lines changed: 224 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@ import { vx } from '@traversable/valibot'
55

66
vi.describe('〖⛳️〗‹‹‹ ❲@traversable/valibot❳', () => {
77
vi.test('〖⛳️〗› ❲vx.defaultValue❳', () => {
8-
98
const schema_01 = v.object({
109
A: v.optional(
1110
v.union([
@@ -228,4 +227,228 @@ vi.describe('〖⛳️〗‹‹‹ ❲@traversable/valibot❳', () => {
228227
}
229228
`)
230229
})
230+
231+
vi.test('〖⛳️〗› ❲vx.defaultValue❳: type-level tests', () => {
232+
// https://github.com/traversable/schema/issues/505
233+
vi.expectTypeOf(
234+
vx.defaultValue(
235+
v.object({
236+
firstName: v.string(),
237+
lastName: v.string(),
238+
age: v.number(),
239+
}), {
240+
fallbacks: {
241+
number: 0,
242+
string: '',
243+
},
244+
})
245+
).toEqualTypeOf<{
246+
firstName: string
247+
lastName: string
248+
age: number
249+
}>()
250+
251+
vi.expectTypeOf(
252+
vx.defaultValue(
253+
v.object({
254+
firstName: v.string(),
255+
lastName: v.string(),
256+
age: v.number(),
257+
})
258+
)
259+
).toEqualTypeOf<{
260+
firstName: undefined
261+
lastName: undefined
262+
age: undefined
263+
}>()
264+
265+
vi.expectTypeOf(
266+
vx.defaultValue(
267+
v.object({
268+
firstName: v.string(),
269+
lastName: v.string(),
270+
age: v.number(),
271+
}), {
272+
fallbacks: {
273+
number: 0,
274+
},
275+
})
276+
).toEqualTypeOf<{
277+
firstName: undefined
278+
lastName: undefined
279+
age: number
280+
}>()
281+
282+
vi.expectTypeOf(
283+
vx.defaultValue(
284+
v.object({
285+
firstName: v.string(),
286+
lastName: v.string(),
287+
age: v.number(),
288+
}), {
289+
fallbacks: {
290+
string: {},
291+
},
292+
})
293+
).toEqualTypeOf<{
294+
firstName: {}
295+
lastName: {}
296+
age: undefined
297+
}>()
298+
299+
vi.expectTypeOf(
300+
vx.defaultValue(
301+
v.object({
302+
firstName: v.boolean(),
303+
lastName: v.string(),
304+
age: v.number(),
305+
}), {
306+
fallbacks: {
307+
boolean: {},
308+
},
309+
})
310+
).toEqualTypeOf<{
311+
firstName: {}
312+
lastName: undefined
313+
age: undefined
314+
}>()
315+
316+
vi.expectTypeOf(
317+
vx.defaultValue(
318+
v.set(
319+
v.object({
320+
firstName: v.boolean(),
321+
lastName: v.string(),
322+
age: v.number(),
323+
})
324+
), {
325+
fallbacks: {
326+
boolean: {},
327+
},
328+
})
329+
).toEqualTypeOf<
330+
Set<{
331+
firstName: {}
332+
lastName: undefined
333+
age: undefined
334+
}>
335+
>()
336+
337+
vi.expectTypeOf(
338+
vx.defaultValue(
339+
v.map(
340+
v.object({
341+
firstName: v.string(),
342+
lastName: v.string(),
343+
age: v.number(),
344+
}),
345+
v.object({
346+
street1: v.string(),
347+
street2: v.optional(v.string()),
348+
city: v.string(),
349+
postalCode: v.optional(v.string()),
350+
})
351+
), {
352+
fallbacks: {
353+
string: 0,
354+
},
355+
})
356+
).toEqualTypeOf<
357+
Map<
358+
{
359+
firstName: number
360+
lastName: number
361+
age: undefined
362+
},
363+
{
364+
street1: number
365+
street2?: number
366+
city: number
367+
postalCode?: number
368+
}
369+
>
370+
>()
371+
372+
373+
vi.expectTypeOf(
374+
vx.defaultValue(
375+
v.tuple([
376+
v.map(
377+
v.object({
378+
firstName: v.string(),
379+
lastName: v.string(),
380+
age: v.number(),
381+
}),
382+
v.object({
383+
street1: v.string(),
384+
street2: v.optional(v.string()),
385+
city: v.string(),
386+
postalCode: v.optional(v.string()),
387+
})
388+
)
389+
]), {
390+
fallbacks: {
391+
string: 0,
392+
},
393+
})
394+
).toEqualTypeOf<
395+
[
396+
Map<
397+
{
398+
firstName: number
399+
lastName: number
400+
age: undefined
401+
},
402+
{
403+
street1: number
404+
street2?: number
405+
city: number
406+
postalCode?: number
407+
}
408+
>
409+
]
410+
>()
411+
412+
vi.expectTypeOf(
413+
vx.defaultValue(
414+
v.array(
415+
v.tuple([
416+
v.map(
417+
v.object({
418+
firstName: v.string(),
419+
lastName: v.string(),
420+
age: v.number(),
421+
}),
422+
v.object({
423+
street1: v.string(),
424+
street2: v.optional(v.string()),
425+
city: v.string(),
426+
postalCode: v.optional(v.string()),
427+
})
428+
)
429+
])
430+
), {
431+
fallbacks: {
432+
string: 0,
433+
},
434+
})
435+
).toEqualTypeOf<
436+
Array<[
437+
Map<
438+
{
439+
firstName: number
440+
lastName: number
441+
age: undefined
442+
},
443+
{
444+
street1: number
445+
street2?: number
446+
city: number
447+
postalCode?: number
448+
}
449+
>
450+
]>
451+
>()
452+
453+
})
231454
})

0 commit comments

Comments
 (0)