IceType schema compiler for db4 - concise, human-readable schema language with TypeScript inference
npm install @db4/schemaYour schema is lying to you.
Types in TypeScript. Migrations in SQL. Validation scattered everywhere. When they disagree, you get runtime errors.
@db4/schema ends the chaos. Define once with IceType. Get types, migrations, and validation that never drift.
Every database project follows the same path to pain:
1. Write TypeScript interfaces
2. Write SQL migrations
3. Add validation at API boundaries
4. Watch them slowly diverge
5. Debug 3am production errors because email was supposed to be required
6. Repeat forever
Teams make it worse. Someone updates the types, forgets the migration. Someone else edits the database directly. Your "single source of truth" becomes three sources of fiction.
One schema. Every output. Zero drift.
``typescript
import { parseSchema, defineSchema, InferType } from '@db4/schema'
// Full IceType syntax
const User = parseSchema({
$type: 'User',
id: 'uuid!',
email: 'string! #unique',
name: 'string!',
avatar: 'string?',
createdAt: 'timestamp! = now()',
posts: '[Post] -> author',
})
// Simple syntax with type inference
const userSchema = defineSchema('User', {
id: 'uuid',
email: 'string',
name: 'string',
avatar: 'string?',
age: 'int?',
})
type User = InferType
// { id: string; email: string; name: string; avatar?: string; age?: number }
`
`bash`
npm install @db4/schema
`typescript
import { parseSchema, defineSchema, SchemaRegistry } from '@db4/schema'
const PostSchema = parseSchema({
$type: 'Post',
id: 'uuid!',
title: 'string!',
content: 'text!',
published: 'boolean = false',
authorId: 'uuid! #index',
tags: '[string]?',
viewCount: 'int = 0',
embedding: 'vector[1536] ~> content', // AI auto-embed
createdAt: 'timestamp! = now()',
$fts: ['title', 'content'], // Full-text search
})
const TagSchema = defineSchema('Tag', {
id: 'uuid',
name: 'string',
color: 'string?',
})
`
`typescript
const registry = new SchemaRegistry()
registry.register(userSchema)
// Throws on invalid data
registry.validate('User', { id: '123', email: 'alice@example.com', name: 'Alice' })
// Or get detailed results
const result = registry.validateWithResult('User', someData)
if (!result.valid) {
console.log(result.errors)
}
`
Reads like documentation. Compiles to everything.
| Type | Description | TypeScript |
|------|-------------|------------|
| string | Text | string |text
| | Long text | string |int
| | Integer | number |float
| | Decimal | number |boolean
| | True/false | boolean |uuid
| | UUID string | string |timestamp
| | Date/time | Date |date
| | Date only | Date |time
| | Time only | Date |json
| | Any JSON | unknown |binary
| | Binary data | ArrayBuffer |
`typescript`
{
required: 'string!', // Required (!)
optional: 'string?', // Optional (?)
indexed: 'string #index', // Database index
unique: 'string! #unique', // Unique constraint
array: '[string]', // Array
withDefault: 'int = 0', // Default value
}
`typescript`
{
price: 'decimal(10,2)', // Precision
code: 'varchar(10)', // Fixed length
embedding: 'vector[1536]', // Dimensions
}
`typescript`
{
comments: '[Comment] -> post', // Has many
post: 'Post! <- comments', // Belongs to
similar: '~> content', // AI semantic match
}
`typescript`
{
firstName: 'string!',
lastName: 'string!',
fullName: 'string := firstName ++ " " ++ lastName',
displayName: 'string := nickname ?? firstName',
}
`typescript`
{
$type: 'User', // Schema name
$partitionBy: ['tenantId'], // Partition key
$fts: ['title', 'content'], // Full-text search
$vector: { embedding: 1536 }, // Vector index
$index: [['email', 'tenantId']], // Composite indexes
}
Manages schemas with validation caching and lazy loading:
`typescript
import { SchemaRegistry, defineSchema } from '@db4/schema'
const registry = new SchemaRegistry()
registry.register(userSchema)
registry.register(postSchema)
// Lazy loading for circular dependencies
registry.registerLazy('Comment', () => defineSchema('Comment', {
id: 'uuid',
content: 'string',
authorId: 'uuid',
postId: 'uuid',
}))
// Full validation details
const result = registry.validateWithResult('User', data)
// { valid: false, errors: [...], fieldsValidated: 5, durationMs: 2 }
// Cached validation (same object = instant)
registry.isValid('User', cachedUser)
`
Generate migrations from schema changes:
`typescript
import { diffSchemas, generateMigration, MigrationRunner } from '@db4/schema'
const diff = diffSchemas(oldSchema, newSchema)
if (diff.hasDestructiveChanges) {
console.warn('Warning:', diff.warnings)
}
const migration = generateMigration(diff, {
dialect: 'sqlite', // or 'postgresql', 'mysql', 'd1'
safeMode: true,
})
console.log(migration.up) // ALTER TABLE statements
console.log(migration.down) // Rollback statements
const runner = new MigrationRunner(executor, { dialect: 'sqlite' })
await runner.initialize()
await runner.runPending(migrations)
`
Full TypeScript types from schema definitions:
`typescript
import { defineSchema, InferType } from '@db4/schema'
const userSchema = defineSchema('User', {
id: 'uuid',
email: 'string',
name: 'string',
age: 'int?',
tags: 'string[]',
metadata: 'json?',
})
type User = InferType
// {
// id: string
// email: string
// name: string
// age?: number
// tags: string[]
// metadata?: unknown
// }
`
Infer schemas from existing data:
`typescript
import { inferType, inferSchema, discoverSchema } from '@db4/schema'
inferType('550e8400-e29b-41d4-a716-446655440000') // 'uuid'
inferType(42) // 'int'
inferType('2024-01-15T10:30:00Z') // 'timestamp'
const docs = [
{ id: 1, name: 'Alice', email: 'alice@example.com' },
{ id: 2, name: 'Bob' }, // email missing
]
const schema = inferSchema(docs)
// { id: 'int', name: 'string', email: 'string?' }
`
With a unified schema:
- TypeScript catches errors at compile time, not 3am
- Migrations generate automatically
- Validation stays in sync forever
- Teams ship faster with fewer bugs
- Sleep through the night
Without one:
- Runtime errors from type mismatches
- Manual migrations that drift from reality
- Validation duplicated and inconsistent
- "Worked yesterday" becomes your morning greeting
- Tech debt compounds with every feature
- parseField(fieldDef) - Parse a field definitionparseSchema(definition)
- - Parse a full schemaparseRelation(relDef)
- - Parse a relationtokenize(input)
- - Tokenize IceType syntax
- defineSchema(name, fields) - Create a type-safe schemaInferType
- - Infer TypeScript type from schema
- SchemaRegistry - Schema management with cachingLazySchema
- - Deferred schema loading
- diffSchemas(from, to) - Compare IceType schemasdiffSimpleSchemas(from, to)
- - Compare simple schemasgenerateMigration(diff, options)
- - Generate migration SQLMigrationRunner
- - Execute and track migrationsMigrationTracker
- - Track applied migrations
- parseComputedExpression(expr) - Parse computed syntaxextractDependencies(ast)
- - Get field dependenciescheckDeterministic(ast)
- - Check if expression is puredetectCircularDependencies(fields)
- - Find cyclesgetComputationOrder(fields)
- - Topological sortvalidateComputedDependencies(computed, fields)
- - Validate dependencies
- clearParserCaches() - Clear memoization cachesgenerateMigrationId()
- - Generate timestamp-based IDcomputeChecksum(migration)
- - Compute migration checksumgenerateMigrationTableSQL(dialect)` - Generate tracking table DDL
-
MIT