cogsbox-shape
Version:
A TypeScript library for creating type-safe database schemas with Zod validation, SQL type definitions, and automatic client/server transformations. Unifies client, server, and database types through a single schema definition, with built-in support for r
187 lines (156 loc) • 4.98 kB
Markdown
# Unified Schema
> ⚠️ **Warning**: This package is currently a work in progress and not ready for production use. The API is unstable and subject to breaking changes. Please do not use in production environments.
---
A TypeScript library for creating type-safe database schemas with Zod validation, SQL type definitions, and automatic client/server transformations. Unifies client, server, and database types through a single schema definition, with built-in support for relationships and serialization.
## Features
- Single source of truth for database, server, and client types
- Type-safe schema definitions with TypeScript
- Built-in Zod validation
- Automatic type transformations between client and database
- Relationship handling (hasMany, hasOne, belongsTo)
- Schema serialization
- Default value generation
## Installation
```bash
npm install cogsbox-shape
```
## Basic Usage
```typescript
import { shape, hasMany, createSchema } from "cogsbox-shape";
const productSchema = {
_tableName: "products",
id: shape.sql({ type: "int", pk: true }),
sku: shape
.sql({ type: "varchar", length: 50 })
.initialState(
z.string(),
() => "PRD-" + Math.random().toString(36).slice(2)
)
.validation(({ sql }) => sql.min(5).max(50)),
price: shape
.sql({ type: "int" })
.client(({ sql }) => z.number().multipleOf(0.01))
.transform({
toClient: (dbValue) => dbValue / 100,
toDb: (clientValue) => Math.round(clientValue * 100),
}),
inStock: shape
.sql({ type: "boolean" })
.client(({ sql }) => z.boolean())
.initialState(z.boolean(), () => true),
categories: hasMany({
fromKey: "id",
toKey: () => categorySchema.productId,
schema: () => categorySchema,
}),
};
const { sqlSchema, clientSchema, validationSchema, defaultValues } =
createSchema(productSchema);
```
## Advanced Features
### Type Transformations
Transform data between client and database representations:
```typescript
const orderSchema = {
_tableName: "orders",
id: shape
.sql({ type: "int", pk: true })
.initialState(z.string().uuid(), () => crypto.randomUUID())
.client(({ sql, initialState }) => z.union([sql, initialState])),
status: shape
.sql({ type: "varchar", length: 20 })
.client(({ sql }) =>
z.enum(["pending", "processing", "shipped", "delivered"])
)
.validation(({ sql }) =>
sql.refine((val) =>
["pending", "processing", "shipped", "delivered"].includes(val)
)
),
metadata: shape
.sql({ type: "text" })
.client(({ sql }) => z.record(z.unknown()))
.transform({
toClient: (value) => JSON.parse(value),
toDb: (value) => JSON.stringify(value),
}),
createdAt: shape
.sql({ type: "datetime" })
.client(({ sql }) => z.string().datetime())
.transform({
toClient: (date) => date.toISOString(),
toDb: (isoString) => new Date(isoString),
}),
};
```
### Relationships
Define relationships between schemas:
```typescript
const customerSchema = {
_tableName: "customers",
id: shape.sql({ type: "int", pk: true }),
name: shape.sql({ type: "varchar", length: 100 }),
orders: hasMany({
fromKey: "id",
toKey: () => orderSchema.customerId,
schema: () => orderSchema,
}),
primaryAddress: hasOne({
fromKey: "id",
toKey: () => addressSchema.customerId,
schema: () => addressSchema,
}),
company: belongsTo({
fromKey: "companyId",
toKey: () => companySchema.id,
schema: () => companySchema,
}),
};
```
### SQL Types
Built-in SQL type definitions:
```typescript
shape.int({ nullable: true });
shape.varchar({ length: 255 });
shape.boolean();
shape.date();
shape.datetime();
shape.text();
shape.longtext();
```
### Validation
Add Zod validation to your schemas:
```typescript
const userSchema = {
_tableName: "users",
email: shape
.sql({ type: "varchar", length: 255 })
.validation(({ sql }) => sql.email().toLowerCase()),
password: shape
.sql({ type: "varchar", length: 255 })
.validation(({ sql }) =>
sql
.min(8)
.regex(/[A-Z]/, "Must contain uppercase letter")
.regex(/[0-9]/, "Must contain number")
),
birthDate: shape
.sql({ type: "date" })
.validation(({ sql }) => sql.min(new Date("1900-01-01")).max(new Date())),
};
```
## Type Safety
The library provides full type inference:
```typescript
const { sqlSchema, clientSchema, validationSchema, defaultValues } =
createSchema(userSchema);
// These are fully typed:
type DBUser = z.infer<typeof sqlSchema>;
type ClientUser = z.infer<typeof clientSchema>;
type ValidationUser = z.infer<typeof validationSchema>;
const defaults: typeof defaultValues = {
// TypeScript will ensure this matches your schema
};
```
## License
MIT