A
arktype3w ago
PIat

Converting Zod to Arktype

Hello! I'm really interested in using Conform instead of React Hook Form for better DX and server-side capabilities. However, it doesn't have Ark support yet, so I'll have to create my own simple patch. Are there by any chance some examples or comparisons between Zod and Arktype architectures to go off of when translating these files, namely to extract the name of the constraints like Array in runtime? expression might be "key"? pun intended https://github.com/edmundhung/conform/blob/main/packages/conform-zod/constraint.ts https://github.com/edmundhung/conform/blob/main/packages/conform-zod/parse.ts Also, is there an way to do something like superRefine for custom run-time conditions? https://zod.dev/?id=superrefine
87 Replies
ssalbdivad
ssalbdivad2w ago
Sorry I missed this. It looks like you've figured out some of this. You can iterate over the errors and check .code to check the kind of error it was. It has lots of additional introspectable information about the type of error. superRefine is just a long-winded narrow 😅
PIat
PIat2w ago
Ah, thank you! I'm slowly starting to understand morphs and constraints I've been looking through the tests, and got a bunch of questions answered The possiibilities are endless 🤯
ssalbdivad
ssalbdivad2w ago
I appreciate your diligence! Unit tests definitely the best place to see comprehensive docs at the moment haha
PIat
PIat2w ago
Yes, I in turn appreciate your thoroughness in writing the tests, it's really helpful
ssalbdivad
ssalbdivad2w ago
It's also helpful for making sure everything works 😅
PIat
PIat2w ago
But I couldn't find an example on how to create/edit types on runtime 🥹
const keysWithNumberValue = <T extends Type<object>>(t: T): T => {
let res = {}

t.keyof().internal.distribute((key) => {
let tt = t.get(key as never)
if (tt.extends(type.number))
return (res[key] = type('number|string').pipe((v) => Number(v)))

return (res[key] = tt.in)
}) as never

return type(res)
}

const userType = keysWithNumberValue(
type({
userName: 'string',
age: 'number',
}),
)

const out = userType({
userName: 'my-name',
age: '123',
})

if (out instanceof ArkErrors) console.log(out.summary)

/**
value at ["\"age\""] must be a number or a string (was missing)
value at ["\"userName\""] must be a string (was missing)
*/
const keysWithNumberValue = <T extends Type<object>>(t: T): T => {
let res = {}

t.keyof().internal.distribute((key) => {
let tt = t.get(key as never)
if (tt.extends(type.number))
return (res[key] = type('number|string').pipe((v) => Number(v)))

return (res[key] = tt.in)
}) as never

return type(res)
}

const userType = keysWithNumberValue(
type({
userName: 'string',
age: 'number',
}),
)

const out = userType({
userName: 'my-name',
age: '123',
})

if (out instanceof ArkErrors) console.log(out.summary)

/**
value at ["\"age\""] must be a number or a string (was missing)
value at ["\"userName\""] must be a string (was missing)
*/
This seemed to get me far enough, but then when I acually use it, it thinks all the values are missing If there is a specific test or discussion about it, I'd gladly read it, I just couldn't find the proper keywords to find it
ssalbdivad
ssalbdivad2w ago
This kind of internal type mapping stuff isn't really a big focus of the documentation yet. It mirrors a lot of operations in TS, but there's a ton of nuance to the way unions, structures etc. are handled (like in TS) Here's a cleaner implementation though:
const keysWithNumberValue = <T extends Type<object>>(t: T): T =>
t.keyof().internal.distribute(
key => {
if (!key.hasKind("unit")) {
throw new Error("Index signatures cannot be mapped")
}

const k: Key = key.unit as never

let value: Type = t.get(key as never)
return [
k,
(value.extends(type.number) ?
value.or("string.numeric.parse")
: value) as Type
]
},
entries => type(Object.fromEntries(entries))
)
const keysWithNumberValue = <T extends Type<object>>(t: T): T =>
t.keyof().internal.distribute(
key => {
if (!key.hasKind("unit")) {
throw new Error("Index signatures cannot be mapped")
}

const k: Key = key.unit as never

let value: Type = t.get(key as never)
return [
k,
(value.extends(type.number) ?
value.or("string.numeric.parse")
: value) as Type
]
},
entries => type(Object.fromEntries(entries))
)
It will be nice when there is a mapped type abstraction built around this Maybe I will add it now
PIat
PIat2w ago
So the first callback if for iterating the keys, and the second callback is for building the result object, which distribute returns?
ssalbdivad
ssalbdivad2w ago
Yeah
PIat
PIat2w ago
Got ittttt....... It works ;-;-;-;-;-; That makes me so happy I bashed my head on the wall for about 4 hours with this yesterday 😆
ssalbdivad
ssalbdivad2w ago
Haha yeah it helps to know how the type system works
PIat
PIat2w ago
Do you know why this acted like it did?
ssalbdivad
ssalbdivad2w ago
Your biggest problem is that you were treating key like a literal key but it was a node So I guess it gets converted to a string using .expression
PIat
PIat2w ago
And the actual literal is key.unit
ssalbdivad
ssalbdivad2w ago
Yeah
Want results from more Discord servers?
Add your server