The package @mionkit/drizzle auto generates Drizzle ORM table schemas directly from TypeScript types.
Unlike drizzle-zod or other drizzle plugins that generates Zod schemas FROM drizzle tables, this package works in the opposite direction: it auto-generates drizzle table configurations FROM TypeScript types while allowing optional overrides.
@mionkit/type-formats - when you use format types like StrUUIDv7, StrEmail, NumInteger, etc., the package automatically selects the most appropriate database column type for each database.| Database | Function | Import |
|---|---|---|
| PostgreSQL | mapPGTable<T>() | @mionkit/drizzle |
| MySQL | mapMySqlTable<T>() | @mionkit/drizzle |
| SQLite | mapSqliteTable<T>() | @mionkit/drizzle |
import {mapPGTable} from '@mionkit/drizzle';
import {uuid, timestamp} from 'drizzle-orm/pg-core';
// Note: Must use regular import (not `import type`) for reflection to work
import {StrUUIDv7, StrEmail} from '@mionkit/type-formats/FormatsString';
import {NumInteger} from '@mionkit/type-formats/FormatsNumber';
/** User entity with format types for intelligent column mapping */
interface User {
id: StrUUIDv7;
email: StrEmail;
name: string;
bio?: string; // Optional = nullable column
age: NumInteger;
tags: string[]; // Array = jsonb column
settings: {theme: string; notifications: boolean}; // Nested object = jsonb
createdAt: Date;
}
// we should always configure primary and foreign keys
export const usersAutoGenerated = mapPGTable<User>().build('users', {
id: uuid('id').primaryKey(),
createdAt: timestamp('created_at').defaultNow().notNull(),
});
import {mapMySqlTable} from '@mionkit/drizzle';
import {varchar, timestamp} from 'drizzle-orm/mysql-core';
// Note: Must use regular import (not `import type`) for reflection to work
import {StrUUIDv7, StrEmail} from '@mionkit/type-formats/FormatsString';
import {NumInteger} from '@mionkit/type-formats/FormatsNumber';
/** User entity with format types for intelligent column mapping */
interface User {
id: StrUUIDv7;
email: StrEmail;
name: string;
bio?: string; // Optional = nullable column
age: NumInteger;
tags: string[]; // Array = json column
settings: {theme: string; notifications: boolean}; // Nested object = json
createdAt: Date;
}
// we should always configure primary and foreign keys
export const usersAutoGenerated = mapMySqlTable<User>().build('users', {
id: varchar('id', {length: 36}).primaryKey(),
createdAt: timestamp('created_at').defaultNow().notNull(),
});
import {mapSqliteTable} from '@mionkit/drizzle';
import {text, integer} from 'drizzle-orm/sqlite-core';
// Note: Must use regular import (not `import type`) for reflection to work
import {StrUUIDv7, StrEmail} from '@mionkit/type-formats/FormatsString';
import {NumInteger} from '@mionkit/type-formats/FormatsNumber';
/** User entity with format types for intelligent column mapping */
interface User {
id: StrUUIDv7;
email: StrEmail;
name: string;
bio?: string; // Optional = nullable column
age: NumInteger;
tags: string[]; // Array = text({mode: 'json'}) column
settings: {theme: string; notifications: boolean}; // Nested object = text({mode: 'json'})
createdAt: Date;
}
// we should always configure primary and foreign keys
export const usersAutoGenerated = mapSqliteTable<User>().build('users', {
id: text('id').primaryKey(),
createdAt: integer('created_at', {mode: 'timestamp'}).notNull(),
});
Primary keys and foreign keys should always be defined in the tableConfig. This gives you full control over key constraints and relationships:
import {mapPGTable} from '@mionkit/drizzle';
import {uuid, timestamp} from 'drizzle-orm/pg-core';
// Note: Must use regular import (not `import type`) for reflection to work
import {StrUUIDv7} from '@mionkit/type-formats/FormatsString';
/** User entity */
interface User {
id: StrUUIDv7;
name: string;
createdAt: Date;
}
/** Post entity with foreign key reference */
interface Post {
id: StrUUIDv7;
title: string;
content: string;
authorId: StrUUIDv7; // Foreign key - just a string type
createdAt: Date;
}
// Primary keys should be defined in the tableConfig override
export const users = mapPGTable<User>().build('users', {
id: uuid('id').primaryKey(), // Primary key defined here
createdAt: timestamp('created_at').defaultNow().notNull(),
});
// Foreign keys should also be defined in the tableConfig override
export const posts = mapPGTable<Post>().build('posts', {
id: uuid('id').primaryKey(), // Primary key
authorId: uuid('author_id') // Foreign key with reference
.references(() => users.id, {onDelete: 'cascade'})
.notNull(),
createdAt: timestamp('created_at').defaultNow().notNull(),
});
The lengthBuffer option multiplies the maxLength defined in custom string formats (StrFormat) to provide extra capacity for the database column. This is useful when you want to allow for some flexibility in the data length prevent resizing in the future, but still want to validate the correct length at runtime.
lengthBuffer only applies to custom StrFormat types with a maxLength constraint. Well-known formats like StrEmail, StrURL, and StrDomain use their standard maximum lengths without any buffer.// Custom string format with maxLength
type Username = StrFormat<{maxLength: 50}>;
interface User {
id: StrUUIDv7;
username: Username;
}
// Default lengthBuffer is 1.5
const users = mapPGTable<User>().build('users');
// username with maxLength 50 → varchar(75)
// Custom lengthBuffer of 2.0
const users = mapPGTable<User>({lengthBuffer: 2.0}).build('users');
// username with maxLength 50 → varchar(100)
The the build function validates that your tableConfig matches the TypeScript type
interface User {
id: string;
name: string;
}
// ❌ Error: Column "email" exists in tableConfig but not in type "User"
const users = mapPGTable<User>().build('users', {
id: uuid('id').primaryKey(),
email: text('email'), // This property doesn't exist in User!
});
@mionkit/type-formats, you must use regular imports (not import type) for the runtime type metadata to be preserved.// ✅ Correct - regular import preserves metadata
import {StrUUIDv7, StrEmail} from '@mionkit/type-formats/FormatsString';
// ❌ Wrong - type import strips metadata
import type {StrUUIDv7, StrEmail} from '@mionkit/type-formats';
Nested objects are stored as JSON. Use foreign key IDs for entity references:
// ✅ Good: Profile is a value object - stored as JSON
interface User {
id: string;
profile: {bio: string; avatar: string};
}
// ❌ Bad: Don't embed entire entities
interface Book {
id: string;
owner: User; // This stores the entire User as JSON!
}
// ✅ Good: Use foreign key ID instead
interface Book {
id: string;
ownerId: string; // Reference by ID
}