A
arktype•2w ago
alle00

question about arktype => TS

I'm building OSS tooling for event-driven architecture and I have a question about ArkType, I'm currently using zod but I find it a bit cumbersome as I have to add 2 external packages to achieve what I'm doing. For some context I'm building over the wire typesafety and typegen, how I achieve this is I take in a schema, I turn that schema into a string representation of the type, then I send that over an event bus to all the other apps which listen to that event and then there I write it to the filesystem if it's local development. The current approach is this:
// you have the class initialized:
ourClass.register("user.create", z.object({ id: z.string(), name: z.string() }))
// internally i convert this to a string:
const eventType = `${ZodToTypescript.convert(schema, { name: convertSubjectNameToEventType(subject) })}`
// then I send it to all other apps via the event bus, then all the other apps get this eventType and write it to the filesystem.
// you have the class initialized:
ourClass.register("user.create", z.object({ id: z.string(), name: z.string() }))
// internally i convert this to a string:
const eventType = `${ZodToTypescript.convert(schema, { name: convertSubjectNameToEventType(subject) })}`
// then I send it to all other apps via the event bus, then all the other apps get this eventType and write it to the filesystem.
I would like to make it more "typescript-y and json-y" where you don't have to install zod into every app that consumes this class and pass in the schema, but rather for it to feel very intuitive and easy to write and I can then move this into the class itself. I think ArkType is the closest and most intuitive solution for this so I'd like to see how you guys would go about achieving this?
30 Replies
alle00
alle00OP•2w ago
@TizzySaurus here you go
alle00
alle00OP•2w ago
i've looked into this: https://arktype.io/docs/blog/2.1
ArkType
ArkType Docs
TypeScript's 1:1 validator, optimized from editor to runtime
alle00
alle00OP•2w ago
and toJsonTsPattern("foo") feels like a very close thing to what i'm trying to do
TizzySaurus
TizzySaurus•2w ago
Is there a specific reason it has be a TypeScript string? E.g. could you just send MyType.toJSONSchema() (assuming there's never morphs/narrows/etc.)?
alle00
alle00OP•2w ago
I need to write it to an actual file to overwrite the types here is the end result of it events.ts file:
export type SubscribeEvents = {
"user.delete": UserDeleteEvent;
"user.create": UserCreateEvent;
"user.>": UserDeleteEvent | UserCreateEvent;
"user.*": UserDeleteEvent | UserCreateEvent;
}

export type PublishEvents = {
"user.delete": UserDeleteEvent;
"user.create": UserCreateEvent;
}

export type UserDeleteEvent = {
id: string;
};
export type UserCreateEvent = {
id: string;
name: string;
};
export type SubscribeEvents = {
"user.delete": UserDeleteEvent;
"user.create": UserCreateEvent;
"user.>": UserDeleteEvent | UserCreateEvent;
"user.*": UserDeleteEvent | UserCreateEvent;
}

export type PublishEvents = {
"user.delete": UserDeleteEvent;
"user.create": UserCreateEvent;
}

export type UserDeleteEvent = {
id: string;
};
export type UserCreateEvent = {
id: string;
name: string;
};
TizzySaurus
TizzySaurus•2w ago
So? Just write an array of json schemas that you parse
alle00
alle00OP•2w ago
then this gets registered like:
import type { SubscribeEvents, PublishEvents } from "./events.js"
declare module "our-lib" {
interface Register {
subscribe: SubscribeEvents
publish: PublishEvents
}
}
import type { SubscribeEvents, PublishEvents } from "./events.js"
declare module "our-lib" {
interface Register {
subscribe: SubscribeEvents
publish: PublishEvents
}
}
TizzySaurus
TizzySaurus•2w ago
If you have
[
{
"type": "string",
"minLength": 5
},
{
"type": "number",
"minimum": 10
}
]
[
{
"type": "string",
"minLength": 5
},
{
"type": "number",
"minimum": 10
}
]
then you can parse those in to arktype types via @ark/json-schema and do t["infer"] to get the TypeScript type And I think it's possible to add annotations (e.g. a name for the event or w/e)
alle00
alle00OP•2w ago
just so I'm clear, you mean I write the JSON schemas into the files, then I use the infer from ark to convert them into types?
TizzySaurus
TizzySaurus•2w ago
I think so, yeah Write JSON to file -> read JSON file -> parse each schema with @ark/json-schema npm package -> use t["infer"] on each parsed schema
alle00
alle00OP•2w ago
ahh okay, so what I'm trying to do is create a 0 dep flow where I would hopefully use arktype to get a TS only types and write them there, so the user himself doesn't have to install arktype/anything else in the consumer apps
TizzySaurus
TizzySaurus•2w ago
I mean what you're trying to do seems very much like an anti-pattern in my head I still don't really understand the use case tbh
alle00
alle00OP•2w ago
event-driven arch. is usually used to link multiple org applications together through an event-bus with events, I'm trying to make them typesafe so instead of being able to subscribe to "event.anything" it narrows down the type to actual events, and in order to make the payloads typesafe I use schemas to describe what the payload can look like. so if you have app A, then apps B & C can send the payload to the event as described in app A without having access to that apps types (in certain cases where users don't use monorepos etc) Ideally I'd like the user to not have to install additional deps in apps A/B/C to make it work
TizzySaurus
TizzySaurus•2w ago
Really you should be validating shapes at every boundary So when B sends the payload, whoever receives it should be validating Is there actually a code-enforced guarantee that the payload B sends has to match a certain shape? Because if there's not then what you're trying just doesn't make sense (you're trying to add type-safety in a scenario where there is inherently none) ( @alle00 )
alle00
alle00OP•2w ago
there is, the tool we created handles the encoding/decoding of data so you're guaranteed you'll get the data you expect it's not "just types" I send the json schema as well and then I parse the data on the other side with the matching schema to make sure it works
TizzySaurus
TizzySaurus•2w ago
So infer the type from the json schema then? Writing TS types to a file is just a massive anti-pattern in my head, and I highly doubt that's actually the best approach
alle00
alle00OP•2w ago
I mean, it's almost identical in terms of approach to react-router v7 type-safety features, I don't think inference would work in these scenarios but i'll have to check 🤔
TizzySaurus
TizzySaurus•2w ago
Yeah inference might not work tbf, because you don't know the type of the json schema
alle00
alle00OP•2w ago
it just requires a typegen step that writes it and overrides it, it's not unsafe because the other things i do under the hood guarantee the types plus there are wildcard combinations you could do user.* which subscribes you to 5 different events
TizzySaurus
TizzySaurus•2w ago
The general approach for this afaik would be to create a shared package between A & B & C so that all of them have all the types. Which I guess is sorta what you're trying to do by writing TS types to a file...
alle00
alle00OP•2w ago
it's kind of similar to prisma generate, if that's the easier way to think abouti t
TizzySaurus
TizzySaurus•2w ago
I'm not familiar with that 🙈
ssalbdivad
ssalbdivad•2w ago
First-class support for optimized .d.ts would be something I'd be interested in eventually, though it would be complex to do well and isn't the top priority at the moment. .expression is the closest you'd get as an interim solution. You could strip out extra constraints first if you wanted to align with TS as closely as possible (though it would be tricky to do without knowledge of some internal APIs)
alle00
alle00OP•2w ago
I'll check it out, I'm also looking into standard schema and the .types property, looks really interesting as well
ssalbdivad
ssalbdivad•2w ago
Standard Schema is a thin validation wrapper with some static-only props attached for inference so that definitely won't help You can just publish .d.ts from inferred arktype directly, but it will incur extra overhead of inference and can't represent certain types e.g. that are cyclic
alle00
alle00OP•2w ago
My preferred solution is allowing the user to bring his own validation lib 😅 hmm I'll look into the .expression first
ssalbdivad
ssalbdivad•2w ago
There shouldn't be any reason you have to do any of this if all you want is for users to be able to provide a schema and you infer it and use it to provide type-safe APIs
alle00
alle00OP•2w ago
unfortunately the type-safe APIs span across multiple apps and are usually separated by a network layer
ssalbdivad
ssalbdivad•2w ago
There are advantages to generating the computed types directly, but it adds a lot of complexity and will be essentially impossible to do in a schema-agnostic way
alle00
alle00OP•2w ago
it's a very rare edge case yeah it seems that way unfortunately, the dream would be BYOSVL (schema validation lib), but that looks like an impossible solution atm thank you boys for the time and talking to me! really appreciated!

Did you find this page helpful?