{"id":23826312,"url":"https://github.com/t1mmen/srtd","last_synced_at":"2025-10-24T11:52:20.478Z","repository":{"id":270386968,"uuid":"904400493","full_name":"t1mmen/srtd","owner":"t1mmen","description":"🪄 Supabase migrations made magical: Live-reloading SQL + Sane, reviewable diffs + Maintainable templates = Delightful DX","archived":false,"fork":false,"pushed_at":"2025-03-31T18:50:19.000Z","size":1925,"stargazers_count":66,"open_issues_count":3,"forks_count":4,"subscribers_count":1,"default_branch":"main","last_synced_at":"2025-04-09T19:16:49.183Z","etag":null,"topics":["cli","hot-reload","live-reload","migrations","postgres","repeatable-migrations","sql-templates","supabase","supabase-cli","supabase-db","templates"],"latest_commit_sha":null,"homepage":"https://timm.stokke.me/blog/srtd-live-reloading-and-sql-templates-for-supabase","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/t1mmen.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":"CONTRIBUTING.md","funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2024-12-16T20:18:08.000Z","updated_at":"2025-04-09T16:16:20.000Z","dependencies_parsed_at":null,"dependency_job_id":"ef11233b-b435-4365-ab81-4ea7910ccab0","html_url":"https://github.com/t1mmen/srtd","commit_stats":null,"previous_names":["t1mmen/srtd"],"tags_count":11,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/t1mmen%2Fsrtd","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/t1mmen%2Fsrtd/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/t1mmen%2Fsrtd/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/t1mmen%2Fsrtd/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/t1mmen","download_url":"https://codeload.github.com/t1mmen/srtd/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":248094988,"owners_count":21046770,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["cli","hot-reload","live-reload","migrations","postgres","repeatable-migrations","sql-templates","supabase","supabase-cli","supabase-db","templates"],"created_at":"2025-01-02T12:19:01.891Z","updated_at":"2025-10-24T11:52:20.426Z","avatar_url":"https://github.com/t1mmen.png","language":"TypeScript","funding_links":["https://www.buymeacoffee.com/t1mmen"],"categories":["TypeScript"],"sub_categories":[],"readme":"# `srtd` 🪄 Supabase Repeatable Template Definitions\n\n\n\n\u003e Live-reloading SQL templates for [Supabase](https://supabase.com) projects. DX supercharged! 🚀\n\n[![NPM Version](https://img.shields.io/npm/v/%40t1mmen%2Fsrtd)](https://www.npmjs.com/package/@t1mmen/srtd)\n[![Downloads](https://img.shields.io/npm/dt/%40t1mmen%2Fsrtd)](https://www.npmjs.com/package/@t1mmen/srtd)\n[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)\n[![CI/CD](https://github.com/t1mmen/srtd/actions/workflows/ci.yml/badge.svg)](https://github.com/t1mmen/srtd/actions/workflows/ci.yml)\n[![codecov](https://codecov.io/gh/t1mmen/srtd/graph/badge.svg?token=CIMAZ55KCJ)](https://codecov.io/gh/t1mmen/srtd)\n\n\n[![video demo](./readme-demo.gif)](./readme-demo.gif)\n\n\n`srtd` enhances the [Supabase](https://supabase.com) DX by adding **live-reloading SQL** from templates into local db.\n\n**Templates act as single-source-of-truth of your database objects**, that `build` to regular SQL migrations; This makes for sane code reviews and functional change history, (e.g `git blame` works as expected).\n\n\n📖 Blog: [Introducing `srtd`: Live-Reloading SQL Templates for Supabase](https://timm.stokke.me/blog/srtd-live-reloading-and-sql-templates-for-supabase)\n\n### Why This Exists 🤔\n\nWhile building [Timely](https://www.timely.com)'s next-generation [Memory Engine](https://www.timely.com/memory-app) on [Supabase](https://supabase.com), we found ourselves facing two major annoyances:\n\n1. Code reviews were painful - function changes showed up as complete rewrites, `git blame` was useless\n2. Designing and iterating on database changes locally was full of friction, no matter which workflow we tried\n\nI spent [nearly two](https://news.ycombinator.com/item?id=37755076) [years looking](https://news.ycombinator.com/item?id=36007640) for something pre-existing, to no avail. Sufficiently fed up, I paired with [Claude](https://claude.ai) to eliminate these annoyances.\n\nSay hello to `srtd`.\n\n[![screenshot of srtd](./readme-screenshot.png)](./readme-screenshot.png)\n\n## Key Features ✨\n\n- **Live Reload**: Changes to your SQL templates instantly update your local database\n- **Templates as source of truth**: Templates are the source of (non-mutable) database objects\n- **Just SQL**: Templates as just SQL, and `build` to standard [Supabase](https://supabase.com) migrations when you're ready to ship\n- **Sane code reviews**: Templates evolve like regular code, with diffs in PR's working `git blame`.\n- **Developer Friendly**: Interactive CLI with visual feedback for all operations.\n\nBuilt specifically for projects using the standard [Supabase](https://supabase.com) stack (but probably works alright for other Postgres-based projects, too).\n\n## Quick Start 🚀\n\n### Requirements\n\n- Node.js v20.18.1 or higher\n- [Supabase](https://supabase.com) project initialized (in `/supabase`).\n\n### Installation\n\n\n\u003e [!WARNING]\n\u003e Projects using React 19 may not work with per-project installation of `srtd` (ref [Ink CLI issue](https://github.com/vadimdemedes/ink/issues/688)). Until resolved, **global installation is recommended.**\n\n\n\n```bash\n# Global installation\nnpm install -g @t1mmen/srtd\n\n# Project installation\nnpm install --save-dev @t1mmen/srtd\n\n# Or run directly\nnpx @t1mmen/srtd\n```\n\n### Setup\n\n```bash\ncd your-supabase-project\nnpx @t1mmen/srtd init # Creates srtd.config.json, not required\n```\n\n### Create Your First Template\n\nCreate `supabase/migrations-templates/my_function.sql`:\n\n```sql\nDROP FUNCTION IF EXISTS public.my_function; -- Makes it easier to change args later\nCREATE FUNCTION my_function()\nRETURNS void AS $$\nBEGIN\n  -- Your function logic here\nEND;\n$$ LANGUAGE plpgsql;\n```\n\n### Development Workflow\n\n1. Start watch mode:\n```bash\nnpx @t1mmen/srtd watch  # Changes auto-apply to local database\n```\n\n2. When ready to deploy:\n```bash\nnpx @t1mmen/srtd build     # Creates timestamped migration file\nsupabase migration up      # Apply using Supabase CLI\n```\n\n\u003e [!TIP]\n\u003e To reduce noise in PR's, consider adding `supabase/migrations/*srtd*.sql linguist-generated=true` to your [`.gitattributes` file.](https://docs.github.com/en/repositories/working-with-files/managing-files/customizing-how-changed-files-appear-on-github) (unless you manually edit the generated files)\n\n\n## The Power of Templates 💪\n\nWithout 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.\n\n\n### Perfect For 🎯\n\n✅ Database functions:\n```diff\n  -- Event notifications\n  DROP FUNCTION IF EXISTS notify_changes;\n  CREATE FUNCTION notify_changes()\n  RETURNS trigger AS $$\n  BEGIN\n    PERFORM pg_notify(\n      'changes',\n      json_build_object('table', TG_TABLE_NAME, 'id', NEW.id)::text\n    );\n+   RAISE NOTICE 'Notified changes for %', TG_TABLE_NAME; -- Debug logging\n    RETURN NEW;\n  END;\n  $$ LANGUAGE plpgsql;\n```\n\n✅ Row-Level Security (RLS):\n```diff\n  -- Replace/update policies safely\n  DROP POLICY IF EXISTS \"workspace_access\" ON resources;\n  CREATE POLICY \"workspace_access\" ON resources\n    USING (workspace_id IN (\n      SELECT id FROM workspaces\n      WHERE organization_id = auth.organization_id()\n+       AND auth.user_role() NOT IN ('pending')\n    ));\n```\n\n✅ Views for data abstraction:\n```diff\n  CREATE OR REPLACE VIEW active_subscriptions AS\n  SELECT\n    s.*,\n    p.name as plan_name,\n    p.features\n  FROM subscriptions s\n  JOIN plans p ON p.id = s.plan_id\n-  WHERE s.status = 'active';\n+  WHERE s.status = 'active'\n+    AND s.expires_at \u003e CURRENT_TIMESTAMP;\n```\n\n✅ Roles and Permissions:\n```diff\n  -- Revoke all first for clean state\n  REVOKE ALL ON ALL TABLES IN SCHEMA public FROM public;\n\n  -- Grant specific access\n  GRANT USAGE ON SCHEMA public TO authenticated;\n  GRANT SELECT ON ALL TABLES IN SCHEMA public TO authenticated;\n+ GRANT SELECT, INSERT, UPDATE, DELETE ON ALL TABLES IN SCHEMA public TO admin;\n```\n\n✅ Safe Type Extensions:\n```diff\n DO $$\n BEGIN\n   -- Add new enum values idempotently\n   IF NOT EXISTS (SELECT 1 FROM pg_type WHERE typname = 'notification_type') THEN\n     CREATE TYPE notification_type AS ENUM ('email', 'sms');\n   END IF;\n\n   -- Extend existing enum safely\n   ALTER TYPE notification_type ADD VALUE IF NOT EXISTS 'push';\n   ALTER TYPE notification_type ADD VALUE IF NOT EXISTS 'pusher';\n   ALTER TYPE notification_type ADD VALUE IF NOT EXISTS 'webhook';\n+  ALTER TYPE notification_type ADD VALUE IF NOT EXISTS 'whatsapp';\n END $$;\n```\n\n✅ Triggers\n```diff\n DROP TRIGGER IF EXISTS on_new_user ON auth.users;\n DROP FUNCTION IF EXISTS public.setup_new_user;\n\n CREATE FUNCTION public.setup_new_user() RETURNS TRIGGER LANGUAGE plpgsql SECURITY DEFINER\n SET search_path = public AS $$\n BEGIN\n   -- Existing logic for new users\n\n+  -- Your new changes go here..\n END;\n $$;\n\n CREATE TRIGGER on_new_user AFTER INSERT ON auth.users FOR EACH ROW EXECUTE PROCEDURE public.setup_new_user ();\n```\n\n\u003e [!TIP]\n\u003e 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.\n\n### Not Recommended For:\n* ❌ Table structures\n* ❌ Indexes\n* ❌ Data modifications\n* ❌ Non-idempotent operations\n\nUse regular [Supabase](https://supabase.com) migrations for these cases.\n\n\n## Commands 🎮\n\n### Interactive Mode\n\nRunning `npx @t1mmen/srtd` without arguments opens an interactive menu. All commands can also be run directly:\n\n- 👀 `srtd watch` - Watch and auto-apply changes\n- 🏗️  `srtd build [--force] [--bundle]` - Generate migrations from templates\n- ▶️  `srtd apply [--force]` - Apply templates directly to local database\n- ✍️  `srtd register [file.sql...]` - Mark templates as already built\n- 🚀  `srtd promote - [file.sql ...]` - Promote WIP template to buildable templates\n- 🧹 `srtd clean` - Remove all logs and reset config\n\n\u003e [!IMPORTANT]\n\u003e `watch` and `apply` commands modify your local database directly and don't clean up after themselves. Use with caution!\n\n## Configuration 📝\n\n`srtd.config.json` can be created with `init` command. It is not necessary, if the defaults suit your needs.\n\n```jsonc\n{\n  // Prevents building templates with this extension\n  \"wipIndicator\": \".wip\",\n\n  // Migration file naming: 20211001000000_srtd-my_function.sql\n  \"migrationPrefix\": \"srtd\",\n\n  // Template discovery\n  \"filter\": \"**/*.sql\",\n\n  // Migration file comments\n  \"banner\": \"You very likely **DO NOT** want to manually edit this generated file.\",\n  \"footer\": \"\",\n\n  // Wrap migrations in transaction\n  \"wrapInTransaction\": true,\n\n  // File paths\n  \"templateDir\": \"supabase/migrations-templates\",\n  \"migrationDir\": \"supabase/migrations\",\n  \"buildLog\": \"supabase/migrations-templates/.buildlog.json\",\n  \"localBuildLog\": \"supabase/migrations-templates/.buildlog.local.json\",\n\n  // Database connection\n  \"pgConnection\": \"postgresql://postgres:postgres@localhost:54322/postgres\"\n}\n```\n\n## Other Features 🔧\n\n### Work in Progress Templates\n\nAdd `.wip.sql` extension to prevent migration generation:\n```bash\nmy_function.wip.sql  # Only applied locally, never built\n```\n\nMake a WIP template buildable as migration by renaming it, or using the `promote` command:\n```bash\nnpx @t1mmen/srtd promote my_function.wip.sql\n```\n\n### Register Existing Objects\n\nRegistering 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)\n\n```bash\n# Register specific template\nnpx @t1mmen/srtd register my_function.sql another_fn.sql\n\n# Interactive multi-select UI\nnpx @t1mmen/srtd register\n```\n\nThis can be useful when setting up `srtd` for an existing project, where you may have hundreds of existing functions, views, etc that you want as templates, but don't want to generate migrations until changed later.\n\n### Template State Management\n\nThe state of templates are stored to..\n\n- [`.buildlog.json`](https://github.com/t1mmen/srtd/blob/main/supabase/migrations-templates/.srtd.buildlog.json) - Migration build state (commit this)\n- `.buildlog.local.json` - Local database state (add to `.gitignore`)\n\nThis helps `srtd` identify when templates are changed, to only `build` (as migrations) or `apply`  the necessary changes (directly to local db).\n\n## Development 🛠️\n\n### Local Setup\n\n```bash\n# Clone and install\ngit clone https://github.com/stokke/srtd.git\ncd srtd\nnpm install\n\n# Development\nnpm run dev     # Watch mode\nnpm test        # Run tests\nnpm start       # Run CLI\nnpm start:link  # Build, npm link, and run CLI\n\n# Quality Checks\nnpm run typecheck       # Type checking\nnpm run lint            # Lint and fix\nnpm run format          # Format code\nnpm run test:coverage   # Test coverage\n```\n\n## Contributing 🤝\n\nWhile feature-complete for our needs, we welcome:\n\n- 🐛 Bug fixes and reliability improvements\n- 📚 Documentation improvements\n- ✅ Test coverage enhancements\n- ⚡️ Performance optimizations\n\n### Contribution Process\n\n1. Create a [changeset](https://github.com/changesets/changesets) (`npm run changeset`)\n2. Ensure tests pass (`npm test`)\n3. Follow existing code style\n4. Update documentation\n\nNote: New features are evaluated based on alignment with project scope.\n\n## Built With 🛠️\n\n### Terminal UI\n- [Ink](https://github.com/vadimdemedes/ink) - React for CLI interfaces\n- [Pastel](https://github.com/vadimdemedes/pastel) - Next-like framework for Ink\n- [Figures](https://github.com/sindresorhus/figures) - Unicode symbols\n- [Chokidar](https://github.com/paulmillr/chokidar) - File watcher\n- [update-notifier](https://github.com/sindresorhus/update-notifier) - Version checks\n- [Zod](https://zod.dev/) - Schema validation\n- [Conf](https://github.com/sindresorhus/conf) - Config management\n- [vhs](https://github.com/charmbracelet/vhs) - Video recording\n\n\n## License\n\nMIT License - see [LICENSE](LICENSE) file.\n\n---\n\nMade with 🪄 by [Timm Stokke](https://timm.stokke.me) \u0026 [Claude Sonnet](https://claude.ai)\n\n[![\"Buy Me A Coffee\"](https://www.buymeacoffee.com/assets/img/custom_images/orange_img.png)](https://www.buymeacoffee.com/t1mmen)\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ft1mmen%2Fsrtd","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Ft1mmen%2Fsrtd","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ft1mmen%2Fsrtd/lists"}