Skip to content

Commit c7be5b3

Browse files
authored
Merge pull request #512 from traversable/zod-default-value-fix
fix(zod): fixes return type for `zx.defaultValue`
2 parents d60d45c + 9d85073 commit c7be5b3

File tree

5 files changed

+320
-14
lines changed

5 files changed

+320
-14
lines changed

.changeset/tired-cups-yell.md

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

packages/valibot/src/default-value.ts

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -187,9 +187,6 @@ export type defaultValue<
187187
* )
188188
*/
189189

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>(
193190
export function defaultValue<T extends AnyValibotSchema>(type: T): defaultValue<v.InferOutput<T>>
194191
export function defaultValue<T extends AnyValibotSchema, Leaves extends Fallbacks>(type: T, options: Options<Leaves>): defaultValue<v.InferOutput<T>, Leaves>
195192
export function defaultValue<T extends AnyValibotSchema>(

packages/zod/src/default-value.ts

Lines changed: 77 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { z } from 'zod'
2-
import type { Primitive } from '@traversable/registry'
2+
import type { Primitive, Showable } from '@traversable/registry'
33
import { Array_isArray, fn, has, isPrimitive, Object_assign, Object_fromEntries, Object_keys } from '@traversable/registry'
44
import { F, tagged, TypeName } from '@traversable/zod-types'
55

@@ -50,14 +50,82 @@ const ARE = (x: unknown, y: unknown) => x != null && y != null
5050
/** @internal */
5151
const CATCH_ALL = undefined
5252

53-
export type defaultValue<T, Fallback = undefined>
54-
= T extends Primitive | Atom ? T | Fallback
55-
: T extends Set<any> ? Set<defaultValue<ReturnType<(ReturnType<T['values']>['return'] & {})>['value'] & {}, Fallback>>
53+
export type Match<T, Matchers>
54+
= T extends Primitive ?
55+
| Matchers[T & keyof Matchers]
56+
| Matchers[`${T & Showable}` & keyof Matchers]
57+
: { [K in keyof T]: Match<T[K], Matchers> }
58+
59+
export type MatchPrimitive<
60+
T,
61+
TypeMap,
62+
M extends MergeTypeMap<TypeMap> = MergeTypeMap<TypeMap>
63+
> = Match<T,
64+
& { [x: string]: M['string'] }
65+
& { [x: number]: M['number'] }
66+
& { [x: symbol]: M['boolean'] }
67+
& { [x: `${bigint}`]: M['bigint'] }
68+
& {
69+
true: M['boolean']
70+
false: M['boolean']
71+
null: M['null']
72+
undefined: M['undefined']
73+
}
74+
>
75+
76+
export type DefaultTypeMap = {
77+
never: undefined
78+
any: undefined
79+
unknown: undefined
80+
undefined: undefined
81+
null: undefined
82+
boolean: undefined
83+
bigint: undefined
84+
number: undefined
85+
string: undefined
86+
}
87+
88+
export type PreserveTypeMap = {
89+
never: never
90+
any: any
91+
unknown: unknown
92+
undefined: undefined
93+
null: null | undefined
94+
boolean: boolean | undefined
95+
bigint: bigint | undefined
96+
number: number | undefined
97+
string: string | undefined
98+
}
99+
100+
export type AnyTypeMap = {
101+
never: unknown
102+
any: unknown
103+
unknown: unknown
104+
undefined: unknown
105+
null: unknown
106+
boolean: unknown
107+
bigint: unknown
108+
number: unknown
109+
string: unknown
110+
}
111+
112+
export type MergeTypeMap<T> = {
113+
[K in keyof DefaultTypeMap]: K extends keyof T ? T[K] : DefaultTypeMap[K]
114+
}
115+
116+
export type defaultValue<
117+
T,
118+
TypeMap = DefaultTypeMap,
119+
M extends MergeTypeMap<TypeMap> = MergeTypeMap<TypeMap>
120+
> = unknown extends T ? (0 extends T & 1 ? M['any'] : M['unknown'])
121+
: [T] extends [never] ? M['never']
122+
: T extends Primitive ? MatchPrimitive<T, M>
123+
: T extends Set<any> ? Set<defaultValue<ReturnType<(ReturnType<T['values']>['return'] & {})>['value'] & {}, TypeMap, M>>
56124
: T extends Map<any, any> ? Map<
57-
defaultValue<({} & ReturnType<{} & ReturnType<T['entries']>['return']>['value'])[0], Fallback>,
58-
defaultValue<({} & ReturnType<{} & ReturnType<T['entries']>['return']>['value'])[1], Fallback>
125+
defaultValue<({} & ReturnType<{} & ReturnType<T['entries']>['return']>['value'])[0], TypeMap, M>,
126+
defaultValue<({} & ReturnType<{} & ReturnType<T['entries']>['return']>['value'])[1], TypeMap, M>
59127
>
60-
: { [K in keyof T]-?: defaultValue<T[K], Fallback> }
128+
: { [K in keyof T]: defaultValue<T[K], TypeMap, M> }
61129

62130
/**
63131
* ## {@link defaultValue `zx.defaultValue`}
@@ -125,8 +193,8 @@ export type defaultValue<T, Fallback = undefined>
125193
* )
126194
*/
127195

128-
export function defaultValue<T extends z.ZodType | z.core.$ZodType>(type: T): defaultValue<z.infer<T>>
129-
export function defaultValue<T extends z.ZodType | z.core.$ZodType, Leaves extends Fallbacks>(type: T, options: Options<Leaves>): defaultValue<z.infer<T>, Leaves[keyof Leaves]>
196+
export function defaultValue<T extends z.ZodType>(type: T): defaultValue<z.infer<T>>
197+
export function defaultValue<T extends z.ZodType, Leaves extends Fallbacks>(type: T, options: Options<Leaves>): defaultValue<z.infer<T>, Leaves>
130198
export function defaultValue<T extends F.Z.Hole<Fixpoint>>(
131199
type: T, {
132200
fallbacks = defaultValue.defaults.fallbacks,

packages/zod/src/makeLens.ts

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { z } from 'zod'
2-
import type { Key, newtype, Showable } from '@traversable/registry'
2+
import type { Key, newtype, Primitive, Showable } from '@traversable/registry'
33
import {
44
Array_from,
55
Array_isArray,
@@ -532,9 +532,23 @@ interface Proxy_tuple<T, S, KS extends (keyof any)[]> extends newtype<
532532
[symbol.path]: KS
533533
}
534534

535+
export type Atom =
536+
| globalThis.Date
537+
| globalThis.RegExp
538+
539+
export type CoalesceOptional<T, Fallback = undefined>
540+
= T extends Primitive | Atom ? T | Fallback
541+
: T extends Set<any> ? Set<CoalesceOptional<ReturnType<(ReturnType<T['values']>['return'] & {})>['value'] & {}, Fallback>>
542+
: T extends Map<any, any> ? Map<
543+
CoalesceOptional<({} & ReturnType<{} & ReturnType<T['entries']>['return']>['value'])[0], Fallback>,
544+
CoalesceOptional<({} & ReturnType<{} & ReturnType<T['entries']>['return']>['value'])[1], Fallback>
545+
>
546+
: { [K in keyof T]-?: CoalesceOptional<T[K], Fallback> }
547+
535548
interface Proxy_optional<T, S, KS extends (keyof any)[]> {
536549
[DSL.chainOptional]: Proxy<S>
537-
[DSL.coalesceOptional]: Proxy<S, [defaultValue<S>], KS>
550+
// [DSL.coalesceOptional]: Proxy<S, [S], KS>
551+
[DSL.coalesceOptional]: Proxy<S, [CoalesceOptional<S>], KS>
538552
[symbol.type]: T
539553
[symbol.path]: KS
540554
}

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

Lines changed: 222 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -212,4 +212,226 @@ vi.describe('〖⛳️〗‹‹‹ ❲@traversable/zod❳', () => {
212212
}
213213
`)
214214
})
215+
216+
vi.test('〖⛳️〗› ❲zx.defaultValue❳: type-level tests', () => {
217+
vi.expectTypeOf(
218+
zx.defaultValue(
219+
z.object({
220+
firstName: z.string(),
221+
lastName: z.string(),
222+
age: z.number(),
223+
}), {
224+
fallbacks: {
225+
number: 0,
226+
string: '',
227+
},
228+
})
229+
).toEqualTypeOf<{
230+
firstName: string
231+
lastName: string
232+
age: number
233+
}>()
234+
235+
vi.expectTypeOf(
236+
zx.defaultValue(
237+
z.object({
238+
firstName: z.string(),
239+
lastName: z.string(),
240+
age: z.number(),
241+
})
242+
)
243+
).toEqualTypeOf<{
244+
firstName: undefined
245+
lastName: undefined
246+
age: undefined
247+
}>()
248+
249+
vi.expectTypeOf(
250+
zx.defaultValue(
251+
z.object({
252+
firstName: z.string(),
253+
lastName: z.string(),
254+
age: z.number(),
255+
}), {
256+
fallbacks: {
257+
number: 0,
258+
},
259+
})
260+
).toEqualTypeOf<{
261+
firstName: undefined
262+
lastName: undefined
263+
age: number
264+
}>()
265+
266+
vi.expectTypeOf(
267+
zx.defaultValue(
268+
z.object({
269+
firstName: z.string(),
270+
lastName: z.string(),
271+
age: z.number(),
272+
}), {
273+
fallbacks: {
274+
string: {},
275+
},
276+
})
277+
).toEqualTypeOf<{
278+
firstName: {}
279+
lastName: {}
280+
age: undefined
281+
}>()
282+
283+
vi.expectTypeOf(
284+
zx.defaultValue(
285+
z.object({
286+
firstName: z.boolean(),
287+
lastName: z.string(),
288+
age: z.number(),
289+
}), {
290+
fallbacks: {
291+
boolean: {},
292+
},
293+
})
294+
).toEqualTypeOf<{
295+
firstName: {}
296+
lastName: undefined
297+
age: undefined
298+
}>()
299+
300+
vi.expectTypeOf(
301+
zx.defaultValue(
302+
z.set(
303+
z.object({
304+
firstName: z.boolean(),
305+
lastName: z.string(),
306+
age: z.number(),
307+
})
308+
), {
309+
fallbacks: {
310+
boolean: {},
311+
},
312+
})
313+
).toEqualTypeOf<
314+
Set<{
315+
firstName: {}
316+
lastName: undefined
317+
age: undefined
318+
}>
319+
>()
320+
321+
vi.expectTypeOf(
322+
zx.defaultValue(
323+
z.map(
324+
z.object({
325+
firstName: z.string(),
326+
lastName: z.string(),
327+
age: z.number(),
328+
}),
329+
z.object({
330+
street1: z.string(),
331+
street2: z.optional(z.string()),
332+
city: z.string(),
333+
postalCode: z.optional(z.string()),
334+
})
335+
), {
336+
fallbacks: {
337+
string: 0,
338+
},
339+
})
340+
).toEqualTypeOf<
341+
Map<
342+
{
343+
firstName: number
344+
lastName: number
345+
age: undefined
346+
},
347+
{
348+
street1: number
349+
street2?: number
350+
city: number
351+
postalCode?: number
352+
}
353+
>
354+
>()
355+
356+
357+
vi.expectTypeOf(
358+
zx.defaultValue(
359+
z.tuple([
360+
z.map(
361+
z.object({
362+
firstName: z.string(),
363+
lastName: z.string(),
364+
age: z.number(),
365+
}),
366+
z.object({
367+
street1: z.string(),
368+
street2: z.optional(z.string()),
369+
city: z.string(),
370+
postalCode: z.optional(z.string()),
371+
})
372+
)
373+
]), {
374+
fallbacks: {
375+
string: 0,
376+
},
377+
})
378+
).toEqualTypeOf<
379+
[
380+
Map<
381+
{
382+
firstName: number
383+
lastName: number
384+
age: undefined
385+
},
386+
{
387+
street1: number
388+
street2?: number
389+
city: number
390+
postalCode?: number
391+
}
392+
>
393+
]
394+
>()
395+
396+
vi.expectTypeOf(
397+
zx.defaultValue(
398+
z.array(
399+
z.tuple([
400+
z.map(
401+
z.object({
402+
firstName: z.string(),
403+
lastName: z.string(),
404+
age: z.number(),
405+
}),
406+
z.object({
407+
street1: z.string(),
408+
street2: z.optional(z.string()),
409+
city: z.string(),
410+
postalCode: z.optional(z.string()),
411+
})
412+
)
413+
])
414+
), {
415+
fallbacks: {
416+
string: 0,
417+
},
418+
})
419+
).toEqualTypeOf<
420+
Array<[
421+
Map<
422+
{
423+
firstName: number
424+
lastName: number
425+
age: undefined
426+
},
427+
{
428+
street1: number
429+
street2?: number
430+
city: number
431+
postalCode?: number
432+
}
433+
>
434+
]>
435+
>()
436+
})
215437
})

0 commit comments

Comments
 (0)