@t1mmen/srtd v0.4.7
srtd
๐ช Supabase Repeatable Template Definitions
Live-reloading SQL templates for Supabase projects. DX supercharged! ๐
srtd
enhances the Supabase DX by adding live-reloading SQL templates into local db. The single-source-of-truth template โก๏ธ migrations system brings sanity to code reviews, making git blame
useful.
๐ Blog: Introducing srtd
: Live-Reloading SQL Templates for Supabase
Why This Exists ๐ค
While building Timely's next-generation Memory Engine on Supabase, we found ourselves facing two major annoyances:
- Code reviews were painful - function changes showed up as complete rewrites,
git blame
was useless - Designing and iterating on database changes locally was full of friction, no matter which workflow we tried
I spent nearly two years looking for something pre-existing, to no avail. Sufficiently fed up, I paired with Claude to eliminate these annoyances.
Say hello to srtd
.
Key Features โจ
- Live Reload: Changes to your SQL templates instantly update your local database
- Templates as source of truth: Templates are the source of (non-mutable) database objects
- Just SQL: Templates as just SQL, and
build
to standard Supabase migrations when you're ready to ship - Sane code reviews: Templates evolve like regular code, with diffs in PR's working
git blame
. - Developer Friendly: Interactive CLI with visual feedback for all operations.
Built specifically for projects using the standard Supabase stack (but probably works alright for other Postgres-based projects, too).
Quick Start ๐
Requirements
- Node.js v20.x or higher
- Supabase project initialized (in
/supabase
).
Installation
# Global installation
npm install -g @t1mmen/srtd
# Project installation
npm install --save-dev @t1mmen/srtd
# Or run directly
npx @t1mmen/srtd
Setup
cd your-supabase-project
npx @t1mmen/srtd init # Creates srtd.config.json, not required
Create Your First Template
Create supabase/migrations-templates/my_function.sql
:
DROP FUNCTION IF EXISTS public.my_function; -- Makes it easier to change args later
CREATE FUNCTION my_function()
RETURNS void AS $$
BEGIN
-- Your function logic here
END;
$$ LANGUAGE plpgsql;
Development Workflow
- Start watch mode:
npx @t1mmen/srtd watch # Changes auto-apply to local database
- When ready to deploy:
npx @t1mmen/srtd build # Creates timestamped migration file
supabase migration up # Apply using Supabase CLI
!TIP To reduce noise in PR's, consider adding
migration-templates/*srtd*.sql linguist-generated=true
to your.gitattributes
file. (unless you manually edit the generated files)
The Power of Templates ๐ช
Without templates, the smallest change to a function would show up as a complete rewrite in your version control system. With templates, the diff is clear and concise.
Perfect For ๐ฏ
โ Database functions:
-- Event notifications
DROP FUNCTION IF EXISTS notify_changes;
CREATE FUNCTION notify_changes()
RETURNS trigger AS $$
BEGIN
PERFORM pg_notify(
'changes',
json_build_object('table', TG_TABLE_NAME, 'id', NEW.id)::text
);
+ RAISE NOTICE 'Notified changes for %', TG_TABLE_NAME; -- Debug logging
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
โ Row-Level Security (RLS):
-- Replace/update policies safely
DROP POLICY IF EXISTS "workspace_access" ON resources;
CREATE POLICY "workspace_access" ON resources
USING (workspace_id IN (
SELECT id FROM workspaces
WHERE organization_id = auth.organization_id()
+ AND auth.user_role() NOT IN ('pending')
));
โ Views for data abstraction:
CREATE OR REPLACE VIEW active_subscriptions AS
SELECT
s.*,
p.name as plan_name,
p.features
FROM subscriptions s
JOIN plans p ON p.id = s.plan_id
- WHERE s.status = 'active';
+ WHERE s.status = 'active'
+ AND s.expires_at > CURRENT_TIMESTAMP;
โ Roles and Permissions:
-- Revoke all first for clean state
REVOKE ALL ON ALL TABLES IN SCHEMA public FROM public;
-- Grant specific access
GRANT USAGE ON SCHEMA public TO authenticated;
GRANT SELECT ON ALL TABLES IN SCHEMA public TO authenticated;
+ GRANT SELECT, INSERT, UPDATE, DELETE ON ALL TABLES IN SCHEMA public TO admin;
โ Safe Type Extensions:
DO $$
BEGIN
-- Add new enum values idempotently
IF NOT EXISTS (SELECT 1 FROM pg_type WHERE typname = 'notification_type') THEN
CREATE TYPE notification_type AS ENUM ('email', 'sms');
END IF;
-- Extend existing enum safely
ALTER TYPE notification_type ADD VALUE IF NOT EXISTS 'push';
ALTER TYPE notification_type ADD VALUE IF NOT EXISTS 'pusher';
ALTER TYPE notification_type ADD VALUE IF NOT EXISTS 'webhook';
+ ALTER TYPE notification_type ADD VALUE IF NOT EXISTS 'whatsapp';
END $$;
โ Triggers
DROP TRIGGER IF EXISTS on_new_user ON auth.users;
DROP FUNCTION IF EXISTS public.setup_new_user;
CREATE FUNCTION public.setup_new_user() RETURNS TRIGGER LANGUAGE plpgsql SECURITY DEFINER
SET search_path = public AS $$
BEGIN
-- Existing logic for new users
+ -- Your new changes go here..
END;
$$;
CREATE TRIGGER on_new_user AFTER INSERT ON auth.users FOR EACHW EXECUTE PROCEDURE public.setup_new_user ();
!TIP You don't need to specifying parameters in drop functions. E.g
DROP FUNCTION IF EXISTS public.my_function;
. This ensures you don't end up with multiple functions with the same name, but different parameters.
Not Recommended Fo
- โ Table structures
- โ Indexes
- โ Data modifications
- โ Non-idempotent operations
Use regular Supabase migrations for these cases.
Commands ๐ฎ
Interactive Mode
Running npx @t1mmen/srtd
without arguments opens an interactive menu. All commands can also be run directly:
- ๐
srtd watch
- Watch and auto-apply changes - ๐๏ธ
srtd build [--force]
- Generate migrations from templates - โถ๏ธ
srtd apply [--force]
- Apply templates directly to local database - โ๏ธ
srtd register [file.sql...]
- Mark templates as already built - ๐
srtd promote - [file.sql ...]
- Promote WIP template to buildable templates - ๐งน
srtd clean
- Remove all logs and reset config
!IMPORTANT
watch
andapply
commands modify your local database directly and don't clean up after themselves. Use with caution!
Configuration ๐
srtd.config.json
can be created with init
command. It is not necessary, if the defaults suit your needs.
{
// Prevents building templates with this extension
"wipIndicator": ".wip",
// Migration file naming: 20211001000000_srtd-my_function.sql
"migrationPrefix": "srtd",
// Template discovery
"filter": "**/*.sql",
// Migration file comments
"banner": "You very likely **DO NOT** want to manually edit this generated file.",
"footer": "",
// Wrap migrations in transaction
"wrapInTransaction": true,
// File paths
"templateDir": "supabase/migrations-templates",
"migrationDir": "supabase/migrations",
"buildLog": "supabase/migrations-templates/.buildlog.json",
"localBuildLog": "supabase/migrations-templates/.buildlog.local.json",
// Database connection
"pgConnection": "postgresql://postgres:postgres@localhost:54322/postgres"
}
Other Features ๐ง
Work in Progress Templates
Add .wip.sql
extension to prevent migration generation:
my_function.wip.sql # Only applied locally, never built
Make a WIP template buildable as migration by renaming it, or using the promote
command:
npx @t1mmen/srtd promote my_function.wip.sql
Template State Management
Two state tracking files:
.buildlog.json
- Migration build state (commit this).buildlog.local.json
- Local database state (add to.gitignore
)
Register Existing Objects
Registering a template is useful when you're creating templates for what is already in your database. This avoids generating migrations on build
(until they're changed)
# Register specific template
npx @t1mmen/srtd register my_function.sql another_fn.sql
# Interactive multi-select UI
npx @t1mmen/srtd register
Development ๐ ๏ธ
Local Setup
# Clone and install
git clone https://github.com/stokke/srtd.git
cd srtd
npm install
# Development
npm run dev # Watch mode
npm test # Run tests
npm start # Run CLI
npm start:link # Build, npm link, and run CLI
# Quality Checks
npm run typecheck # Type checking
npm run lint # Lint and fix
npm run format # Format code
npm run test:coverage # Test coverage
Contributing ๐ค
While feature-complete for our needs, we welcome:
- ๐ Bug fixes and reliability improvements
- ๐ Documentation improvements
- โ Test coverage enhancements
- โก๏ธ Performance optimizations
Contribution Process
- Create a changeset (
npm run changeset
) - Ensure tests pass (
npm test
) - Follow existing code style
- Update documentation
Note: New features are evaluated based on alignment with project scope.
Built With ๐ ๏ธ
Terminal UI
- Ink - React for CLI interfaces
- Pastel - Next-like framework for Ink
- Figures - Unicode symbols
- Chokidar - File watcher
- update-notifier - Version checks
- Zod - Schema validation
- Conf - Config management
- vhs - Video recording
License
MIT License - see LICENSE file.
Made with ๐ช by Timm Stokke & Claude Sonnet