Documentation
Complete guide to Schemact - a zero-dependency, AST-based database schema management tool.
Installation
# npm
npm install schemact
# yarn
yarn add schemact
# pnpm
pnpm add schemact
Schemact has zero dependencies - it only uses Node.js built-in modules.
Quick Start
1. Initialize Project
schemact init
Creates schemact.config.js and migrations/ directory.
2. Create a Migration
schemact new create_users
Creates migrations/001_create_users.sigl
3. Define Your Schema
model User {
id Serial @pk
email VarChar(255) @unique @notnull
name Text
role VarChar(50) @default('user')
createdAt Timestamp @default(CURRENT_TIMESTAMP)
}
4. Generate SQL
schemact generate --dialect postgres
5. Apply Migrations
schemact up
Configuration
Create schemact.config.js in your project root:
export default {
// Database dialect: 'postgres', 'mysql', or 'sqlite'
dialect: 'postgres',
// Directory for migration files
migrationsDir: './migrations',
// Output directory for generated SQL
outputDir: './sql',
// Ledger file for tracking applied migrations
ledgerFile: '.schemact.ledger.json',
// Security limits
maxMigrationFileSize: 5 * 1024 * 1024, // 5MB
maxTotalMigrationsSize: 50 * 1024 * 1024, // 50MB
// Logging options
logging: {
console: true,
file: '.schemact.log',
level: 'INFO',
auditTrail: true
}
};
DSL Reference
Models
Models define database tables. Each model has a name and a list of fields.
model TableName {
fieldName DataType @decorators
}
Data Types
| Type | PostgreSQL | MySQL | SQLite |
|---|---|---|---|
| Int | INTEGER | INT | INTEGER |
| BigInt | BIGINT | BIGINT | INTEGER |
| SmallInt | SMALLINT | SMALLINT | INTEGER |
| Serial | SERIAL | INT AUTO_INCREMENT | INTEGER PRIMARY KEY AUTOINCREMENT |
| BigSerial | BIGSERIAL | BIGINT AUTO_INCREMENT | INTEGER PRIMARY KEY AUTOINCREMENT |
| Float | REAL | FLOAT | REAL |
| Double | DOUBLE PRECISION | DOUBLE | REAL |
| Decimal(p,s) | DECIMAL(p,s) | DECIMAL(p,s) | NUMERIC |
| Boolean | BOOLEAN | TINYINT(1) | INTEGER |
| VarChar(n) | VARCHAR(n) | VARCHAR(n) | TEXT |
| Char(n) | CHAR(n) | CHAR(n) | TEXT |
| Text | TEXT | TEXT | TEXT |
| Date | DATE | DATE | TEXT |
| Time | TIME | TIME | TEXT |
| Timestamp | TIMESTAMP | TIMESTAMP | TEXT |
| UUID | UUID | CHAR(36) | TEXT |
| JSON | JSONB | JSON | TEXT |
| Blob | BYTEA | BLOB | BLOB |
Decorators
Decorators add constraints and behaviors to fields.
@pk
Primary key constraint
id Int @pk@unique
Unique constraint
email VarChar(255) @unique@notnull
NOT NULL constraint
name Text @notnull@default(value)
Default value. Use quotes for strings.
role VarChar(50) @default('user')
count Int @default(0)
createdAt Timestamp @default(CURRENT_TIMESTAMP)@check(expression)
CHECK constraint
age Int @check(age >= 0)
price Decimal(10,2) @check(price > 0)@references(Table.field)
Foreign key reference
userId Int @references(User.id)@onDelete(action) / @onUpdate(action)
Referential actions. Multi-word actions must be quoted.
userId Int @references(User.id) @onDelete(CASCADE)
parentId Int @references(Category.id) @onDelete('SET NULL')Actions: CASCADE, RESTRICT, 'NO ACTION', 'SET NULL', 'SET DEFAULT'
Relationships
Define foreign key relationships between models.
One-to-Many
model User {
id Serial @pk
name Text
}
model Post {
id Serial @pk
title Text
authorId Int @references(User.id) @onDelete(CASCADE)
}
Many-to-Many
model User {
id Serial @pk
}
model Role {
id Serial @pk
name VarChar(50) @unique
}
model UserRole {
userId Int @references(User.id) @onDelete(CASCADE)
roleId Int @references(Role.id) @onDelete(CASCADE)
@@pk(userId, roleId)
}
Indexes
Define indexes for better query performance.
model User {
id Serial @pk
email VarChar(255)
name Text
createdAt Timestamp
// Single column index
@@index(email)
// Composite index
@@index(name, createdAt)
// Unique index
@@unique(email)
}
Enums
Define enumerated types for constrained values.
enum Status {
PENDING
ACTIVE
ARCHIVED
}
model Task {
id Serial @pk
title Text
status Status @default(PENDING)
}
CLI Reference
schemact init
Initialize a new Schemact project.
schemact init [options]
Options:
--dialect <type> Database dialect (postgres|mysql|sqlite)
--dir <path> Migrations directory (default: ./migrations)
schemact new
Create a new migration file.
schemact new <name>
Examples:
schemact new create_users
schemact new add_email_to_posts
schemact up
Apply pending migrations.
schemact up [options]
Options:
--dry-run Show SQL without executing
--steps <n> Apply only n migrations
--to <name> Apply up to specific migration
schemact down
Rollback migrations.
schemact down [options]
Options:
--dry-run Show SQL without executing
--steps <n> Rollback n migrations (default: 1)
--to <name> Rollback to specific migration
schemact status
Show migration status.
schemact status
Output:
Applied 001_create_users 2025-01-15 10:30:00
Applied 002_add_posts 2025-01-15 10:31:00
Pending 003_add_comments
schemact generate
Generate SQL from migration files.
schemact generate [options]
Options:
--dialect <type> Target database (postgres|mysql|sqlite)
--output <path> Output directory for SQL files
--file <name> Generate for specific migration only
API Reference
Schemact can be used programmatically in your Node.js applications.
Lexer
Tokenizes Schemact DSL source code.
import { Lexer } from 'schemact';
const source = `
model User {
id Serial @pk
email VarChar(255) @unique
}
`;
const lexer = new Lexer(source);
const tokens = lexer.tokenize();
// tokens: Token[]
// [{ type: 'KEYWORD', value: 'model', line: 2, column: 1 }, ...]
Parser
Parses tokens into an Abstract Syntax Tree (AST).
import { Parser } from 'schemact';
const parser = new Parser(tokens);
const ast = parser.parse();
// ast: SchemaAST
// { models: [...], enums: [...] }
Generators
Generate SQL from AST for different databases.
import {
PostgresGenerator,
MySQLGenerator,
SQLiteGenerator
} from 'schemact';
// PostgreSQL
const pgGen = new PostgresGenerator();
const pgSQL = pgGen.generate(ast);
// MySQL
const mysqlGen = new MySQLGenerator();
const mysqlSQL = mysqlGen.generate(ast);
// SQLite
const sqliteGen = new SQLiteGenerator();
const sqliteSQL = sqliteGen.generate(ast);
Ledger
Tracks applied migrations with integrity verification.
import { Ledger } from 'schemact';
const ledger = new Ledger('.schemact.ledger.json');
// Load existing ledger
await ledger.load();
// Get applied migrations
const applied = ledger.getApplied();
// Record a migration
await ledger.record({
name: '001_create_users',
hash: 'sha256:...',
appliedAt: new Date()
});
// Verify integrity
const isValid = await ledger.verifyIntegrity();
Security
Schemact is built with security as a top priority.
SQL Injection Protection
All identifiers are properly escaped using database-specific methods. No user input is directly interpolated into SQL.
Path Traversal Prevention
Migration names are validated against path traversal attacks. Only alphanumeric characters, hyphens, and underscores are allowed.
Resource Exhaustion Protection
File size limits prevent DoS attacks via oversized migration files. Default limits: 5MB per file, 50MB total.
Race Condition Safe
File locking prevents concurrent modifications to the ledger file. Atomic operations ensure data integrity.
Integrity Verification
SHA-256 checksums verify migration file integrity. Tampering detection prevents unauthorized changes.
Architecture
Schemact uses a clean compiler pipeline architecture.
┌─────────┐ ┌────────┐ ┌─────┐ ┌───────────┐ ┌─────┐
│ .sact │ -> │ Lexer │ -> │ AST │ -> │ Generator │ -> │ SQL │
│ File │ │Tokenize│ │ │ │ (target) │ │ │
└─────────┘ └────────┘ └─────┘ └───────────┘ └─────┘
1. Lexer (Tokenization)
Converts source code into tokens with type, value, line, and column information.
2. Parser (AST Generation)
Builds an Abstract Syntax Tree from tokens, validating syntax and structure.
3. Generator (SQL Output)
Traverses AST and generates database-specific DDL statements.
4. Ledger (State Management)
Tracks applied migrations with checksums for integrity verification.