+3
-2
src/feedline/schema/actions-entry.ts
+3
-2
src/feedline/schema/actions-entry.ts
···
1
-
import {zx} from '@traversable/zod'
2
1
import {z} from 'zod/mini'
2
+
3
+
import {deepPartial} from '#lib/schema'
3
4
4
5
import {makeActionSchema} from '#realm/schema'
5
6
···
11
12
z.object({
12
13
guid: z.string(), // entry GUID from feed
13
14
feedurl: z.url(), // which feed this entry belongs to
14
-
payload: zx.deepPartial(entrySchema, 'applyToOutputType'),
15
+
payload: deepPartial(entrySchema),
15
16
}),
16
17
)
17
18
+3
-2
src/feedline/schema/actions-feed.ts
+3
-2
src/feedline/schema/actions-feed.ts
···
1
-
import {zx} from '@traversable/zod'
2
1
import {z} from 'zod/mini'
2
+
3
+
import {deepPartial} from '#lib/schema'
3
4
4
5
import {makeActionSchema} from '#realm/schema'
5
6
···
28
29
'feed:patch',
29
30
z.object({
30
31
url: z.url(),
31
-
payload: zx.deepPartial(feedSchema, 'applyToOutputType'),
32
+
payload: deepPartial(feedSchema),
32
33
}),
33
34
)
34
35
+1
src/lib/schema.ts
+1
src/lib/schema.ts
+84
src/lib/schema/deep-partial.ts
+84
src/lib/schema/deep-partial.ts
···
1
+
import {z} from 'zod/mini'
2
+
3
+
// Type-level deep partial for zod schemas
4
+
type DeepPartialShape<T> = {
5
+
[K in keyof T]: T[K] extends z.ZodMiniType<infer O>
6
+
? z.ZodMiniOptional<DeepPartialSchema<T[K] & z.ZodMiniType<O>>>
7
+
: T[K]
8
+
}
9
+
10
+
type DeepPartialSchema<T extends z.ZodMiniType> =
11
+
T extends z.ZodMiniObject<infer Shape>
12
+
? z.ZodMiniObject<DeepPartialShape<Shape>>
13
+
: T extends z.ZodMiniArray<infer E extends z.ZodMiniType>
14
+
? z.ZodMiniArray<DeepPartialSchema<E>>
15
+
: T extends z.ZodMiniOptional<infer U extends z.ZodMiniType>
16
+
? z.ZodMiniOptional<DeepPartialSchema<U>>
17
+
: T extends z.ZodMiniNullable<infer U extends z.ZodMiniType>
18
+
? z.ZodMiniNullable<DeepPartialSchema<U>>
19
+
: T
20
+
21
+
export type DeepPartial<T extends z.ZodMiniType> = DeepPartialSchema<T>
22
+
23
+
// Internal type for accessing schema internals
24
+
type SchemaDef = {_zod: {def: {type: string}}}
25
+
26
+
/**
27
+
* Creates a deep partial version of a zod schema.
28
+
* All object properties become optional recursively.
29
+
*/
30
+
export function deepPartial<T extends z.ZodMiniType>(schema: T): DeepPartial<T> {
31
+
return deepPartialImpl(schema) as DeepPartial<T>
32
+
}
33
+
34
+
function deepPartialImpl(schema: z.ZodMiniType): z.ZodMiniType {
35
+
const def = (schema as unknown as SchemaDef)._zod.def
36
+
37
+
switch (def.type) {
38
+
case 'object': {
39
+
const objDef = def as {type: 'object'; shape: Record<string, z.ZodMiniType>}
40
+
const newShape: Record<string, z.ZodMiniType> = {}
41
+
42
+
for (const [key, value] of Object.entries(objDef.shape)) {
43
+
newShape[key] = z.optional(deepPartialImpl(value))
44
+
}
45
+
46
+
return z.object(newShape)
47
+
}
48
+
49
+
case 'array': {
50
+
const arrDef = def as {type: 'array'; element: z.ZodMiniType}
51
+
return z.array(deepPartialImpl(arrDef.element))
52
+
}
53
+
54
+
case 'optional': {
55
+
const optDef = def as {type: 'optional'; innerType: z.ZodMiniType}
56
+
return z.optional(deepPartialImpl(optDef.innerType))
57
+
}
58
+
59
+
case 'nullable': {
60
+
const nullDef = def as {type: 'nullable'; innerType: z.ZodMiniType}
61
+
return z.nullable(deepPartialImpl(nullDef.innerType))
62
+
}
63
+
64
+
case 'record': {
65
+
type RecordDef = {type: 'record'; keyType: z.core.$ZodRecordKey; valueType: z.ZodMiniType}
66
+
const recDef = def as RecordDef
67
+
return z.record(recDef.keyType, deepPartialImpl(recDef.valueType))
68
+
}
69
+
70
+
case 'union': {
71
+
const unionDef = def as {type: 'union'; options: z.ZodMiniType[]}
72
+
return z.union(unionDef.options.map(deepPartialImpl) as [z.ZodMiniType, ...z.ZodMiniType[]])
73
+
}
74
+
75
+
case 'intersection': {
76
+
const intDef = def as {type: 'intersection'; left: z.ZodMiniType; right: z.ZodMiniType}
77
+
return z.intersection(deepPartialImpl(intDef.left), deepPartialImpl(intDef.right))
78
+
}
79
+
80
+
// Primitives and other types pass through unchanged
81
+
default:
82
+
return schema
83
+
}
84
+
}
+5
-3
src/lib/types.ts
+5
-3
src/lib/types.ts
···
1
-
import type {deepPartial} from '@traversable/zod'
2
-
3
1
/**
4
2
* Makes all properties in T optional recursively, including nested objects.
5
3
* @typeParam T - The type to make deeply partial
6
4
*/
7
-
export type DeepPartial<T> = deepPartial<T>
5
+
export type DeepPartial<T> = T extends object
6
+
? T extends (infer U)[]
7
+
? DeepPartial<U>[]
8
+
: {[K in keyof T]?: DeepPartial<T[K]>}
9
+
: T
8
10
9
11
/**
10
12
* Excludes array types from T. Useful for constraining generics to non-array values.