[](https://crates.io/crates/vld-ts)
[](https://docs.rs/vld-ts)
[](https://github.com/s00d/vld/blob/main/LICENSE)
[](https://github.com/s00d/vld)
[](https://github.com/s00d/vld/issues)
[](https://github.com/s00d/vld/stargazers)
# vld-ts
Generate TypeScript [Zod](https://zod.dev/) and [Valibot](https://valibot.dev/) schemas directly from [vld](https://crates.io/crates/vld) schemas and types.
## Overview
`vld-ts` is **vld-first**: use your `vld` schemas/types as the source of truth and generate frontend Zod/Valibot from them.
## Installation
```toml
[dependencies]
vld-ts = "0.4"
```
## Quick start
```rust
use vld_ts::{
impl_to_openapi, impl_to_valibot, impl_to_zod, openapi_refs, to_openapi, to_valibot, to_zod,
ToOpenApi, ToRefs, ToValibot, ToZod,
};
vld::schema! {
pub struct Address {
pub city: String => vld::string().min(1),
}
}
vld::schema! {
pub struct Order {
pub shipping: Address => vld::nested!(Address),
}
}
impl_to_zod!(Order);
impl_to_valibot!(Order);
impl_to_openapi!(Order);
impl_to_openapi!(Address);
// From schema instance
let zod = to_zod(&vld::string().min(2).email());
assert!(zod.contains(".email()"));
// From vld type (single schema expression, no imports/exports)
let order_zod = Order::to_zod();
assert!(order_zod.starts_with("z.object("));
assert!(order_zod.contains("shipping: z.lazy(() => AddressSchema)"));
assert!(!order_zod.contains("import { z } from \"zod\""));
// Valibot from schema instance
let single_valibot = to_valibot(&vld::string().min(2).email());
assert!(single_valibot.contains("v.string()"));
assert!(single_valibot.contains("v.email()"));
// Valibot from vld type (single schema expression, no imports/exports)
let order_valibot = Order::to_valibot();
assert!(order_valibot.starts_with("v.object"));
assert!(order_valibot.contains("shipping: v.lazy(() => AddressSchema)"));
assert!(!order_valibot.contains("import * as v from \"valibot\""));
// OpenAPI schema object from schema instance
let email_schema = to_openapi(&vld::string().email());
assert_eq!(email_schema["type"], "string");
// OpenAPI schema object from vld type
let openapi_schema = Address::to_openapi();
assert_eq!(openapi_schema["type"], "object");
let refs = openapi_refs(&Order::to_openapi());
assert!(refs.contains(&"#/components/schemas/Address".to_string()));
let refs2 = Order::to_refs();
assert!(refs2.contains(&"#/components/schemas/Address".to_string()));
```
### Generated output
```typescript
// Auto-generated by vld-ts — do not edit manually
import { z } from "zod";
export const UserSchema = z.object({
name: z.string().min(2).max(50),
email: z.string().email(),
age: z.number().int().min(0).optional()
});
export type User = z.infer<typeof UserSchema>;
export const EmailSchema = z.string().email();
export type Email = z.infer<typeof EmailSchema>;
```
```typescript
// Auto-generated by vld-ts — do not edit manually
import * as v from "valibot";
export const UserSchema = v.objectWithRest({
name: v.pipe(v.string(), v.minLength(2), v.maxLength(50)),
email: v.pipe(v.string(), v.email())
}, v.unknown());
export type User = v.InferOutput<typeof UserSchema>;
```
## Supported output features
| `type: "string"` | `z.string()` |
| `type: "number"` | `z.number()` |
| `type: "integer"` | `z.number().int()` |
| `type: "boolean"` | `z.boolean()` |
| `type: "null"` | `z.null()` |
| `type: "array"` | `z.array(...)` |
| `type: "object"` | `z.object({...})` |
| `oneOf` | `z.union([...])` |
| `allOf` | `z.intersection(...)` |
| `anyOf` | `z.union([...])` |
| `enum` | `z.literal(...)` / `z.union()` |
| `format: "email"` | `.email()` |
| `format: "uuid"` | `.uuid()` |
| `format: "date"/"date-time"` | `.date()` / `.datetime()` |
| `minLength/maxLength` | `.min()/.max()` |
| `minimum/maximum` | `.min()/.max()` |
| `pattern` | `.regex()` |
| `description` | `.describe()` |
| nullable (`oneOf` + null) | `.nullable()` |
| `$ref` to named schemas | `z.lazy(() => NameSchema)` |
## Examples
```bash
cargo run -p vld-ts --example generate_zod
```
## License
MIT