Skip to content

t1mmen/srtd

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

srtd — Live-Reloading, declarative, SQL migration templates for Supabase and Postgres

NPM Version Downloads License: MIT CI/CD

The workflow of Vite or Nodemon—but for SQL. Hot-reload SQL while you iterate. Build as migrations when it's time to ship.

demo


Why srtd exists

Database development usually has a slow, frustrating feedback loop:

  • Edit SQL in your IDE
  • Copy to SQL editor or create migration
  • Run it
    • Hit an error? Fix in IDE, copy again, run again
    • Works? Back to your IDE, until the next change
  • Repeat

Code reviews aren't much better. Changing a single line in a function often shows up as a full rewrite in Git, making it hard to see what actually changed.

srtd exists to fix both problems:

  • Fast local iteration with live reload
  • Clean, reviewable diffs for database logic
  • Real migrations for production safety

After searching for two years while building Timely's Memory Engine on Supabase, the itch needed to be scratched.


The core idea

srtd intentionally separates iteration, review, and execution.

Templates are for humans

  • Plain SQL files
  • Concise and readable
  • The primary thing you review in Git

Migrations are for databases

  • Generated from templates
  • Explicit and deterministic
  • Safe to deploy in CI and production

Templates evolve over time. Migrations are the execution record.


The diff problem—solved

Without templates, changing one line in a function means your PR shows a complete rewrite—the old DROP + CREATE replaced by a new one. Reviewers have to read the whole thing, and manually compare it to the last migration to touch the function to spot changes. Talk about friction!

With srtd, your PR shows what you actually changed;

Without srtd

+ DROP FUNCTION IF EXISTS calculate_total;
+ CREATE FUNCTION calculate_total(order_id uuid)
+ RETURNS numeric AS $fn$
+ BEGIN
+   RETURN (SELECT SUM(price * quantity) FROM order_items);
+ END;
+ $fn$ LANGUAGE plpgsql;

With srtd

  CREATE FUNCTION calculate_total(order_id uuid)
  RETURNS numeric AS $fn$
  BEGIN
-   RETURN (SELECT SUM(price) FROM order_items);
+   RETURN (SELECT SUM(price * quantity) FROM order_items);
  END;
  $fn$ LANGUAGE plpgsql;

git blame works. Code reviews are useful. Your database logic is treated like real code.

How To Use It

Quick start

npm install -g @t1mmen/srtd
cd your-supabase-project

# Create a template
mkdir -p supabase/migrations-templates
cat > supabase/migrations-templates/hello.sql << 'EOF'
DROP FUNCTION IF EXISTS hello;
CREATE FUNCTION hello() RETURNS text AS $$
BEGIN RETURN 'Hello from srtd!'; END;
$$ LANGUAGE plpgsql;
EOF

# Start watch mode
srtd watch

Edit hello.sql, save—it's live on your local database. No migration file, no restart, no waiting.

When ready to deploy:

srtd build            # Creates supabase/migrations/20241226_srtd-hello.sql
supabase migration up # Deploy with Supabase CLI

Already have functions deployed? Create templates for them, then register so srtd knows they're not new:

srtd register existing_function.sql  # Won't rebuild until the template actually changes

Development loop (watch mode)

srtd watch   # Edit template → auto-applied to local DB

Hot reload is a DX feature. It exists to make iteration smooth and fast.

Watch mode demo


Shipping to production (generate migrations)

srtd build            # → migrations/20250109123456_srtd-template-name.sql
supabase migration up # Deploy via Supabase CLI

Generated migrations fully redefine objects—databases prefer explicit redefinition, humans prefer small diffs. srtd optimizes for both in different layers.


WIP templates

Use .wip.sql files for experiments:

my_experiment.wip.sql  → Applies locally, never builds to migration
  • Applied during watch and apply
  • Excluded from build
  • Safe to experiment without affecting production

When ready: srtd promote my_experiment.wip.sql

WIP workflow demo


Template dependencies

Declare dependencies between templates with @depends-on comments:

-- @depends-on: helper_functions.sql
CREATE FUNCTION complex_calc() ...

During apply and build, templates are sorted so dependencies run first. Circular dependencies are detected and reported. Use --no-deps to disable.


What works as templates

Templates need to be idempotent—safe to run multiple times. This works great for:

Object Pattern
Functions DROP FUNCTION IF EXISTS + CREATE FUNCTION
Views CREATE OR REPLACE VIEW
RLS Policies DROP POLICY IF EXISTS + CREATE POLICY
Triggers Drop + recreate trigger and function
Roles REVOKE ALL + GRANT
Enums ADD VALUE IF NOT EXISTS

Not for templates: Table structures, indexes, data modifications—use regular migrations for those.


Commands

srtd                     # Interactive menu
srtd watch               # Live reload—applies templates on save
srtd build               # Generate migration files
srtd apply               # Apply all templates once (no watch)
srtd register            # Mark templates as already deployed
srtd promote             # Convert .wip template to buildable
srtd clear               # Reset build state
srtd init                # Initialize config file
srtd doctor              # Validate setup and diagnose issues

# Build options
srtd build --force       # Rebuild all templates
srtd build --apply       # Apply immediately after building
srtd build --bundle      # Combine all into single migration
srtd build --no-deps     # Disable dependency ordering

# Apply options
srtd apply --force       # Force apply all templates
srtd apply --no-deps     # Disable dependency ordering

# Clear options
srtd clear --local       # Clear local build log only
srtd clear --shared      # Clear shared build log only
srtd clear --reset       # Reset everything to defaults

# Global options (all commands)
--json                   # Machine-readable output
--non-interactive        # Disable prompts (for scripts)

JSON output

All commands support --json for machine-readable output (CI/CD, LLM integrations):

srtd build --json   # Single JSON object with results array and summary
srtd watch --json   # NDJSON stream (one event per line)

Output includes success, command, timestamp, and command-specific fields. Errors use a top-level error field.

Works great with Claude Code background tasks—pair with the srtd-cli skill and srtd rule for SQL template guidance.


Configuration

Defaults work for standard Supabase projects. Optional srtd.config.json:

{
  "templateDir": "supabase/migrations-templates",
  "migrationDir": "supabase/migrations",
  "pgConnection": "postgresql://postgres:postgres@localhost:54322/postgres",
  "migrationPrefix": "srtd",
  "migrationFilename": "$timestamp_$prefix$migrationName.sql",
  "wipIndicator": ".wip",
  "wrapInTransaction": true,
  "filter": "**/*.sql",
  "banner": "/* Auto-generated by srtd. Edit the template, not this file. */",
  "footer": ""
}

Run srtd doctor to validate your configuration and diagnose setup issues.


Custom migration paths

The migrationFilename option lets you match your project's existing migration structure:

Variable Description Example
$timestamp Build timestamp (YYYYMMDDHHmmss) 20240315143022
$migrationName Template name (without .sql) create_users
$prefix Migration prefix with trailing dash srtd-

Examples

Default (Supabase-style):

{ "migrationFilename": "$timestamp_$prefix$migrationName.sql" }
// → migrations/20240315143022_srtd-create_users.sql

Directory per migration (Prisma-style):

{ "migrationFilename": "$timestamp_$migrationName/migration.sql" }
// → migrations/20240315143022_create_users/migration.sql

Flyway-style (V prefix):

{
  "migrationFilename": "V$timestamp__$migrationName.sql",
  "migrationPrefix": ""
}
// → migrations/V20240315143022__create_users.sql

Nested directories are created automatically.


State tracking

File Purpose Git
.buildlog.json What's been built to migrations Commit
.buildlog.local.json What's applied to your local DB Gitignore

FAQ

Why do generated migrations redefine entire objects?

Full redefinitions ensure deterministic, idempotent execution across environments. Templates encode intent; migrations encode execution.

Can I use hot reload in production?

No. Hot reload is strictly a local development feature. Production deploys always use generated migrations.

Why plain SQL instead of a DSL?

SQL is already the right language. srtd adds workflow improvements, not syntax.

What does "srtd" stand for?

Supabase Repeatable Template Definitions—but then general Postgres support happened, and... naming things is hard.


Contributing

Bug fixes, docs, and test coverage welcome. See CONTRIBUTING.md.

For development: CLAUDE.md.


More


Built by Timm Stokke with Claude, after two years of being annoyed.

About

🪄 Supabase migrations made magical: Live-reloading SQL + Sane, reviewable diffs + Maintainable templates = Delightful DX

Topics

Resources

License

Contributing

Stars

Watchers

Forks

Packages

No packages published

Contributors 8

Languages