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
IntINTEGERINTINTEGER
BigIntBIGINTBIGINTINTEGER
SmallIntSMALLINTSMALLINTINTEGER
SerialSERIALINT AUTO_INCREMENTINTEGER PRIMARY KEY AUTOINCREMENT
BigSerialBIGSERIALBIGINT AUTO_INCREMENTINTEGER PRIMARY KEY AUTOINCREMENT
FloatREALFLOATREAL
DoubleDOUBLE PRECISIONDOUBLEREAL
Decimal(p,s)DECIMAL(p,s)DECIMAL(p,s)NUMERIC
BooleanBOOLEANTINYINT(1)INTEGER
VarChar(n)VARCHAR(n)VARCHAR(n)TEXT
Char(n)CHAR(n)CHAR(n)TEXT
TextTEXTTEXTTEXT
DateDATEDATETEXT
TimeTIMETIMETEXT
TimestampTIMESTAMPTIMESTAMPTEXT
UUIDUUIDCHAR(36)TEXT
JSONJSONBJSONTEXT
BlobBYTEABLOBBLOB

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.