@t1mmen/srtd
Version:
Supabase Repeatable Template Definitions (srtd): 🪄 Live-reloading SQL templates for Supabase DX. Make your database changes reviewable and migrations maintainable! 🚀
380 lines (256 loc) • 11.1 kB
Markdown
# `srtd` — Live-Reloading, declarative, SQL migration templates for Supabase and Postgres
[](https://www.npmjs.com/package/@t1mmen/srtd)
[](https://www.npmjs.com/package/@t1mmen/srtd)
[](https://opensource.org/licenses/MIT)
[](https://github.com/t1mmen/srtd/actions/workflows/ci.yml)
> **The workflow of Vite or Nodemon—but for SQL**.
> Hot-reload SQL while you iterate. Build as migrations when it's time to ship.

### 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](https://news.ycombinator.com/item?id=37755076) for [two years](https://news.ycombinator.com/item?id=36007640) while building [Timely](https://www.timely.com)'s [Memory Engine](https://www.timely.com/memory-app) 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;
<table>
<tr>
<td valign="top">
**Without srtd**
```diff
+ 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;
```
</td>
<td valign="top">
**With srtd**
```diff
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;
```
</td>
</tr>
</table>
`git blame` works. Code reviews are useful. Your database logic is treated like real code.
## How To Use It
### Quick start
```bash
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:
```bash
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:
```bash
srtd register existing_function.sql # Won't rebuild until the template actually changes
```
### Development loop (watch mode)
```bash
srtd watch # Edit template → auto-applied to local DB
```
Hot reload is a **DX feature**. It exists to make iteration smooth and fast.

### Shipping to production (generate migrations)
```bash
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`

### Template dependencies
Declare dependencies between templates with `@depends-on` comments:
```sql
-- @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
```bash
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):
```bash
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](https://claude.ai/code) background tasks—pair with the [srtd-cli skill](./.claude/skills/srtd-cli.md) and [srtd rule](./.claude/rules/srtd.md) for SQL template guidance.
### Configuration
Defaults work for standard Supabase projects. Optional `srtd.config.json`:
```jsonc
{
"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):**
```jsonc
{ "migrationFilename": "$timestamp_$prefix$migrationName.sql" }
// → migrations/20240315143022_srtd-create_users.sql
```
**Directory per migration (Prisma-style):**
```jsonc
{ "migrationFilename": "$timestamp_$migrationName/migration.sql" }
// → migrations/20240315143022_create_users/migration.sql
```
**Flyway-style (V prefix):**
```jsonc
{
"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
<details>
<summary><strong>Why do generated migrations redefine entire objects?</strong></summary>
Full redefinitions ensure deterministic, idempotent execution across environments. Templates encode intent; migrations encode execution.
</details>
<details>
<summary><strong>Can I use hot reload in production?</strong></summary>
No. Hot reload is strictly a local development feature. Production deploys always use generated migrations.
</details>
<details>
<summary><strong>Why plain SQL instead of a DSL?</strong></summary>
SQL is already the right language. srtd adds workflow improvements, not syntax.
</details>
<details>
<summary><strong>What does "srtd" stand for?</strong></summary>
Supabase Repeatable Template Definitions—but then general Postgres support happened, and... naming things is hard.
</details>
### Contributing
Bug fixes, docs, and test coverage welcome. See [CONTRIBUTING.md](./CONTRIBUTING.md).
For development: [CLAUDE.md](./CLAUDE.md).
### More
- [Blog post](https://timm.stokke.me/blog/srtd-live-reloading-and-sql-templates-for-supabase)
- [MIT License](./LICENSE)
Built by [Timm Stokke](https://timm.stokke.me) with [Claude](https://claude.ai), after two years of being annoyed.