A
arktype•2mo ago
mshafir

Wrapping arktype with utilities

We have an implementation of an in-house ORM layer that is built upon arktype. It's been really amazing to work with and supports some of our huge model types that have hundreds of fields while remaining snappy. We have a set of prebuilt core types that these models have to use in order for the downstream system to know how to persist and generate APIs around them appropriately. Unfortunately, I haven't been able to figure out how these could work with the core AT constructs because they encompass typing + defaulting behavior which we want standardized + metadata that the downstream systems use we define our core model types like this
export const ModelTypes = {
string: (m?: SettableTypeMetadata) =>
type.string.configure({ ...m }).default(''),
decimal: (m?: SettableTypeMetadata) =>
type.number
.configure({ typeAlias: 'decimal', ...m })
.brand('decimal')
.default(0),
...
export const ModelTypes = {
string: (m?: SettableTypeMetadata) =>
type.string.configure({ ...m }).default(''),
decimal: (m?: SettableTypeMetadata) =>
type.number
.configure({ typeAlias: 'decimal', ...m })
.brand('decimal')
.default(0),
...
and then use them in model definitions like this
import { type } from 'arktype';

export const SharingRuleModel = defineModel({
name: 'SharingRule',
version: '1.0',
historyEnabled: false,
schema: type({
ruleId: ModelTypes.guid(),
name: ModelTypes.string(),
description: ModelTypes.string(),
recipientTenant: ModelTypes.string(),
import { type } from 'arktype';

export const SharingRuleModel = defineModel({
name: 'SharingRule',
version: '1.0',
historyEnabled: false,
schema: type({
ruleId: ModelTypes.guid(),
name: ModelTypes.string(),
description: ModelTypes.string(),
recipientTenant: ModelTypes.string(),
1. Is there a better way of setting this kind of thing up with scopes/modules or similar? 2. This works fine for these simple primitives, but I can't figure out the typing for enums, objects, or arrays. I'd like a way to take in the input to AT's type function and pass it through and still get all the nice type inference back so you could do things like - ModelTypes.enum(['yes','no']) and get back the equivalent of type('yes | 'no' | null).configure({ typeAlias: 'enum' }).brand('enum').default(null) - ModelTypes.object({ subfield: ModelTypes.string() }) to get back type({ subfield: type.string.default('') }).or(type.null).default(null) Is there a nice way to define a function like that?
5 Replies
ssalbdivad
ssalbdivad•2mo ago
Awesome to hear about the success you've had with arktype! The first snippet you mention does look suspiciously like a scope. The only difference I can see is the parameterized metadata, but since it's optional I wonder if it would be functionally equivalent to just call .configure directly as needed? There's nothing wrong with your current approach, though. Maybe not parameterizing the metadata could be better depending on the context so it's just a simple object with Type instances as values- that is essentially what a module is. The big advantage of a scope over something like this is the ability to string-embed the keywords you create directly into your expressions in addition to being able to use them independently as a module. That said, yes, the patterns (I think) you're describing are quite easy to wrap externally and it's great for integrating arktype validation+inference with your API: https://arktype.io/docs/generics#external the really cool thing is with the second approach, you don't even need to pass a type to your API- you can just pass a native definition and parse it in your function. worth noting also that .configure by default is additive so you could apply some base metadata internally and then external .configure calls chained off that type would have both parts of the metadata. the API itself is actually pretty flexible as well, e.g. you can pass a function that accepts the current metadata attached to the type and returns the updated metadata
mshafir
mshafirOP•2mo ago
Thanks! I'll give the external generics a shot
ssalbdivad
ssalbdivad•2mo ago
Good luck! There's a lot under the type namespace that would be useful here. Let me know if you get stuck looking for a particular utility I can probably help out if there's a clearly defined API you're aiming at
mshafir
mshafirOP•2mo ago
Ok I think I figured this out, sharing the code here in case it helps others in the future. @ArkDavid let me know if this is totally wrong or something. Idea again is these helper types which bake in defaulting and metadata assumptions.
ssalbdivad
ssalbdivad•2mo ago
Nice, this all looks reasonable to me! There may be some cleaner ways to handle casting in the implementations by using overloads and/or type.raw to accept arbitrary definitions but in the end it is mostly inconsequential since it won't affect external consumption. Great work 🙌

Did you find this page helpful?