@tej_gokani/sqlsmith
v0.1.0
Published
SQLSmith converts JavaScript object schemas into clean, safe, Supabase-ready Postgres SQL migration files with schema diffing, CLI tools, and type overrides.
Maintainers
Readme
SQLSmith
JavaScript Schema to Postgres (Supabase) Migration Generator
SQLSmith converts JavaScript object schemas into clean, safe, Supabase-ready Postgres SQL migration files with schema diffing, CLI tools, and type overrides.
🚀 Features
- ✨ JS → SQL Schema Generation - Write schemas in JavaScript, get clean SQL
- 🔄 Schema Diffing - Generate ALTER statements from schema changes
- 📦 Supabase-Ready - Timestamped migrations in the Supabase format
- 🎯 Shorthand Notation - Concise column definitions (
"uuid:pk","text:unique:notnull") - 🔐 RLS Policy Templates - Optional Supabase Row Level Security policy generation
- ⚙️ Highly Configurable - Type overrides, naming strategies, timestamps, and more
- 🧪 Well-Tested - Comprehensive test suite included
- 🛠️ CLI + Programmatic API - Use from command line or Node.js code
📦 Installation
npm install -D sqlsmith
# or
yarn add -D sqlsmith🏁 Quick Start
1. Initialize SQLSmith
npx sqlsmith initThis creates:
sqlsmith.config.js- Configuration fileschema.example.js- Example schema file
2. Define Your Schema
Edit schema.example.js:
export default {
users: {
id: "uuid:pk",
name: "text:notnull",
email: "text:unique:notnull",
age: "integer:default=18",
profile: {
bio: "text",
avatar_url: "text"
},
created_at: "timestamptz:default=now()"
},
profiles: {
id: "uuid:pk",
user_id: "uuid:fk=users.id",
visibility: "text:default='public'"
}
};3. Generate SQL
# Generate SQL file
npx sqlsmith generate --schema=schema.example.js --out=schema.sql
# Or create a timestamped migration
npx sqlsmith migrate --schema=schema.example.js --message="init"4. Output
Generated SQL:
CREATE TABLE IF NOT EXISTS public.users (
id uuid PRIMARY KEY,
name text NOT NULL,
email text UNIQUE NOT NULL,
age integer DEFAULT 18,
profile jsonb,
created_at timestamptz DEFAULT now()
);
CREATE TABLE IF NOT EXISTS public.profiles (
id uuid PRIMARY KEY,
user_id uuid REFERENCES public.users(id),
visibility text DEFAULT 'public'
);📚 CLI Commands
init
Create configuration and example schema files
npx sqlsmith initgenerate
Generate SQL from schema
npx sqlsmith generate --schema=schema.js --out=schema.sqlmigrate
Create timestamped migration file
npx sqlsmith migrate --schema=schema.js --message="create users table"diff
Generate ALTER statements from schema differences
npx sqlsmith diff --from=old-schema.js --to=new-schema.js --out=diff.sqlpolicies
Generate Supabase RLS policy templates
npx sqlsmith policies --table=users --out=policies.sql💻 Programmatic API
import { generateSQL, diffSchemas, migrate } from "sqlsmith";
// Generate SQL from schema
const sql = generateSQL(schemaObject);
// Generate ALTER statements
const diff = diffSchemas(oldSchema, newSchema);
// Create migration file
const filePath = migrate(schemaObject, "./supabase/migrations", "init");📝 Schema Notation
Shorthand Format
Column definitions use the format: "<type>[:modifier1][:modifier2]..."
{
id: "uuid:pk", // Primary key
email: "text:unique:notnull", // Unique and NOT NULL
age: "integer:default=18", // Default value
created_at: "timestamptz:default=now()", // Function default
user_id: "uuid:fk=users.id", // Foreign key
settings: "jsonb:default='{}'" // JSON default
}Supported Modifiers
pk- Primary key (implies NOT NULL)notnull- NOT NULL constraintunique- UNIQUE constraintdefault=<value>- Default valuefk=<table>.<column>- Foreign key reference
Type Mapping
JavaScript types to PostgreSQL:
string → text
number → integer
boolean → boolean
uuid → uuid
object → jsonb
array → jsonbExplicit PostgreSQL types supported:
text,varchar,integer,bigint,smallintnumeric,decimal,real,double precisionuuid,boolean,date,timestamp,timestamptzjson,jsonb,text[],integer[], etc.
Nested Objects
Nested objects are automatically converted to jsonb:
{
users: {
profile: {
bio: "text",
avatar_url: "text"
}
}
}Becomes:
profile jsonb⚙️ Configuration
sqlsmith.config.js:
export default {
schema: 'public', // PostgreSQL schema
namingStrategy: 'snake_case', // snake_case or camelCase
timestamps: true, // Add created_at/updated_at
jsonForObjects: true, // Convert nested objects to jsonb
foreignKeyStrategy: 'inline', // inline or separateTable
policyGeneration: false, // Generate RLS policies
supabaseDir: './supabase/migrations',
typeOverrides: {
// Custom type mappings
id: 'uuid',
email: 'varchar(255)'
}
};Options
| Option | Type | Default | Description |
|--------|------|---------|-------------|
| schema | string | 'public' | PostgreSQL schema name |
| namingStrategy | string | 'snake_case' | Naming convention |
| timestamps | boolean/object | false | Auto-add timestamps |
| jsonForObjects | boolean | true | Convert objects to jsonb |
| foreignKeyStrategy | string | 'inline' | FK strategy |
| policyGeneration | boolean | false | Generate RLS policies |
| supabaseDir | string | './supabase/migrations' | Migration directory |
| typeOverrides | object | {} | Custom type mappings |
🔄 Schema Diffing
SQLSmith can generate ALTER statements by comparing two schemas:
// old-schema.js
export default {
users: {
id: "uuid:pk",
name: "text"
}
};
// new-schema.js
export default {
users: {
id: "uuid:pk",
name: "text",
email: "text:unique:notnull" // Added column
}
};npx sqlsmith diff --from=old-schema.js --to=new-schema.jsOutput:
ALTER TABLE public.users ADD COLUMN email text UNIQUE NOT NULL;Destructive Changes
SQLSmith warns about destructive changes:
-- WARNING: DROP_COLUMN on users.name is DESTRUCTIVE!
-- ALTER TABLE public.users DROP COLUMN name;🔐 Supabase Integration
Migrations
SQLSmith generates timestamped migration files compatible with Supabase:
supabase/migrations/
20251121143022_create_users.sql
20251121143045_add_profiles.sqlRLS Policies
Generate Row Level Security policy templates:
npx sqlsmith policies --table=usersOutput:
-- Enable Row Level Security
ALTER TABLE public.users ENABLE ROW LEVEL SECURITY;
-- Policy: Authenticated users can view all records
CREATE POLICY "authenticated_select_policy" ON public.users
FOR SELECT
TO authenticated
USING (true);
-- Policy: Users can update their own records
CREATE POLICY "user_update_own_policy" ON public.users
FOR UPDATE
TO authenticated
USING (auth.uid() = user_id)
WITH CHECK (auth.uid() = user_id);🧪 Testing
Run the test suite:
npm testTests include:
- Parser validation
- SQL generation
- Schema diffing
- Type mapping
- Configuration handling
📁 Project Structure
sqlsmith/
├── bin/
│ └── sqlsmith.js # CLI entry point
├── src/
│ ├── index.js # Main API
│ ├── cli.js # CLI commands
│ ├── parser/
│ │ ├── parseSchema.js # Schema parser
│ │ └── validateSchema.js # Schema validator
│ ├── generator/
│ │ ├── sqlCreate.js # CREATE TABLE generator
│ │ ├── sqlAlter.js # ALTER TABLE generator
│ │ └── typeMapper.js # Type mapping
│ ├── integrator/
│ │ ├── supabaseWriter.js # Migration file writer
│ │ └── policyGenerator.js# RLS policy generator
│ └── utils/
│ ├── fileWriter.js # File utilities
│ ├── logger.js # Logger
│ └── timestamp.js # Timestamp generator
├── templates/
│ ├── migration.sql.tpl # Migration template
│ └── policy.template.sql # Policy template
├── examples/
│ └── schema.example.js # Example schema
├── tests/
│ ├── parser.test.js # Parser tests
│ └── generator.test.js # Generator tests
└── package.json🤝 Contributing
Contributions are welcome! Please feel free to submit a Pull Request.
📄 License
MIT © Your Name
🔗 Links
✨ Examples
Basic Schema
export default {
posts: {
id: "uuid:pk",
title: "text:notnull",
content: "text",
author_id: "uuid:fk=users.id",
published: "boolean:default=false",
created_at: "timestamptz:default=now()"
}
};With Timestamps Config
// sqlsmith.config.js
export default {
timestamps: {
created_at: true,
updated_at: true
}
};
// schema.js
export default {
articles: {
id: "uuid:pk",
title: "text:notnull"
// created_at and updated_at added automatically
}
};Complex Schema
export default {
organizations: {
id: "uuid:pk",
name: "text:notnull",
settings: "jsonb:default='{}'",
created_at: "timestamptz:default=now()"
},
users: {
id: "uuid:pk",
email: "text:unique:notnull",
org_id: "uuid:fk=organizations.id",
role: "text:default='member'",
metadata: {
bio: "text",
avatar: "text",
preferences: {}
}
},
posts: {
id: "uuid:pk",
author_id: "uuid:fk=users.id",
org_id: "uuid:fk=organizations.id",
title: "text:notnull",
content: "text",
tags: "text[]",
published_at: "timestamptz"
}
};Built with ❤️ for the Supabase and PostgreSQL community
