-
-
Notifications
You must be signed in to change notification settings - Fork 304
Description
Creating recursive schemas is limited by the need to manually define the type and lose the reliable type-to-value correspondence usually provided by valibot. Our best option at the moment is defining a non-recursive schema and then adding the recursion using lazy and intersection/union/tuple.
We can do better than this, but it would require adding a few bytes to existing operators.
Here's a type that creates recursive types:
const Recur = Symbol('Recur')
type Recur = typeof Recur
type Recursive<RootDesc, SubDesc = RootDesc> = {
[K in keyof SubDesc]:
SubDesc[K] extends undefined
? undefined :
SubDesc[K] extends Recur | undefined
? Recursive<RootDesc>
: Recursive<RootDesc, SubDesc[K]>
}Here are tests that show TreeA = TreeB: Typescript Playground
type TreeA = {
value: string
children?: TreeA[]
}
type TreeB = Recursive<{
value: string
children?: Recur[]
}>New operator
Requirements:
There would be an extension of the usual Schema type to also allow the Recur symbol anywhere. Many operators would need to accept this type as input and output. This should be a minor runtime change. Possibly:
if (dataset.value == Recur) return Recurparse() would not accept this type. recursive() would replace all instances of the symbol with recursion.
parse should be otherwise unchanged, since it's already capable of parsing recursive schemas.
This would allow us the usual composition API.
Usage
const author = pipe(
object({ 'dc:creator': string() }),
transform(x => ({ author: x['dc:creator'] }))
)
const children = pipe(
object({
spine: object({
itemref: pipe(
object({ '@idref': optional(array(Recur)) }), // <--- Recur
transform(x => x['@idref']),
),
}),
}),
transform(x => ({ children: x.spine.itemref }))
)
const recursiveSchema = recursive(intersect([author, children]))
// inferred types
type Input = {
'dc:creator': string
spine: {
itemref: {
'@idref'?: Input[]
}
}
}
type Output = {
author: string
children?: Output[]
}Limitation
The Recur symbol must be inside an object, not only a tuple.
Details
This is fine:
type List<T> = [T] | [T, List<T>]But this doesn't work:
type List<T> = Recursive<[T] | [T, Recur]>But this does work:
type List<T> = Recursive<{ head: T } | { head: T, tail: Recur }>Solution
type List<T> = Recursive<[T] | { 0: T, 1: Recur }>This could probably built in to Recursive<T>.