Runtime coercion
I'm building a system to load declarative runtime configuration from a couple different sources, all of which provide values as strings (env vars, AWS SSM Parameters).
Currently I'm walking through the
typeJson
at runtime to build accessors for each field, such as joining field names with __
to build environment variable keys, and that is working great. I can cleanly detect when I reach a "leaf" type to load, and I either have a string or an object with a "domain"; my challenge is whether there is an elegant way of coercing the string I load into the type expected at this point (without making some huge imperative logic block).
I'm hopeful there might be some clever thing under the hood that I can use in place of rolling my own.
Also, are there types defined somewhere that more concretely describe the typeJson
structure?29 Replies
Hey, cool to hear you've been delving into some of the type system in depth!
The easiest thing would be to traverse the type nodes directly instead of the JSON.
If you create a type like:
I'm not totally sure what you mean about coercing the string into an expected type, maybe an example would help?
Sure! (also, thanks for the quick reply!)
So, I want to define a "config" type like
and then introspect that type to load the value from
process.env.SERVER__BASE_URL
.
This works fine currently if the type (url
in this case) is a string-based type, but if I want to load a number, I need a way to coerce into the base type (before any validators)Maybe something like this would be helpful:
It's not like Zod where there's a "before validation" phase or similar, types can be chained through transformations to other types
There are also some builtin keywords that could be helpful here like
parse.number
Oh that sounds interesting
where can I get
parse.number
?It should be available by default
As a global?
Like I mean
You can also import
ark
And use ark.parse.number
Oh I see
still wrapping my head around using
type
like thatThe completions should help a lot
yeah, those are super nifty
And very specific error messages if you get anything wrong
kudos on building such an intricate type machine
The best thing about the syntax IMO is that once you call type once you don't have to keep wrapping things
You can define an object as deep as you want within a single type call
how would I go about parsing a comma separated string into an array of things
So it looks much more like pure TS syntax
Then whatever additional transforms/validation you want on each item
I'm guessing thats what I would use a morph for?
Yeah
Pipe accepts N morphs. Where another
Type
is basically just a morph that will stop chaining if there are errors if you need intermediate validation🤯
I hope once people get the hang of the first few concepts everything is very composable and intuitive from there.
Working on new docs now 🙏
Should this work as an inline morph:
Which version are you using?
2.0.0-dev.18
I believeAhh okay that was the alpha morph operator for inlining, you want
=>
in 2.0ahh, that makes more sense too
You shouldn't need to explicitly type
string
although there are limits to what TS will do with some nested tuples
You can also always define the morph type on its own, then reference itI think it was just lacking inferrence because of the wrong operator in the middle
yep, works without the explicit
string
Or you can inline the type call, whatever is easiest (although I've found defining them as separate variables is nice for reusability, readability, and TS has an easier time with it)
Or tuple expressions, it's really just the nested
type` calls that TS doesn't likeOne last question for you @ssalbdivad (though I see you're offline for now, so no rush)
If I wanted to do
listOfInts
how should I go about adding validation? Just doing
allows for NaN
to slip into the array
I tried type('string').pipe((s) => s.split(',').map((x) => type('integer')(x.trim())));
but that felt clunkyMaybe this?