{"id":28324412,"url":"https://github.com/bewinxed/pg-typesafe-triggers","last_synced_at":"2025-06-24T02:30:38.505Z","repository":{"id":294147132,"uuid":"986068876","full_name":"Bewinxed/pg-typesafe-triggers","owner":"Bewinxed","description":"Declarative, Typesafe triggers for Prisma Postgres","archived":false,"fork":false,"pushed_at":"2025-05-26T13:41:55.000Z","size":8819,"stargazers_count":1,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"main","last_synced_at":"2025-06-02T03:22:59.259Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/Bewinxed.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"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,"zenodo":null}},"created_at":"2025-05-19T04:12:07.000Z","updated_at":"2025-05-27T21:44:09.000Z","dependencies_parsed_at":"2025-05-19T23:24:07.232Z","dependency_job_id":null,"html_url":"https://github.com/Bewinxed/pg-typesafe-triggers","commit_stats":null,"previous_names":["bewinxed/prisma-pg-typesafe-triggers","bewinxed/pg-typesafe-triggers"],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/Bewinxed/pg-typesafe-triggers","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Bewinxed%2Fpg-typesafe-triggers","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Bewinxed%2Fpg-typesafe-triggers/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Bewinxed%2Fpg-typesafe-triggers/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Bewinxed%2Fpg-typesafe-triggers/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/Bewinxed","download_url":"https://codeload.github.com/Bewinxed/pg-typesafe-triggers/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Bewinxed%2Fpg-typesafe-triggers/sbom","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":261591573,"owners_count":23181750,"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":[],"created_at":"2025-05-25T18:10:46.850Z","updated_at":"2025-06-24T02:30:38.498Z","avatar_url":"https://github.com/Bewinxed.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# 🎯 pg-typesafe-triggers\n\n[![CI](https://github.com/bewinxed/pg-typesafe-triggers/actions/workflows/ci.yml/badge.svg)](https://github.com/bewinxed/pg-typesafe-triggers/actions/workflows/ci.yml)\n[![npm version](https://badge.fury.io/js/pg-typesafe-triggers.svg)](https://badge.fury.io/js/pg-typesafe-triggers)\n[![npm downloads](https://img.shields.io/npm/dm/pg-typesafe-triggers.svg)](https://www.npmjs.com/package/pg-typesafe-triggers)\n\n![ComfyUI_00006_](https://github.com/user-attachments/assets/8aef8bac-282c-4316-8a59-bc6f17dc5544)\n\n![TypeScript](https://img.shields.io/badge/typescript-%23007ACC.svg?style=for-the-badge\u0026logo=typescript\u0026logoColor=white)\n![Postgres](https://img.shields.io/badge/postgres-%23316192.svg?style=for-the-badge\u0026logo=postgresql\u0026logoColor=white)\n![Prisma](https://img.shields.io/badge/Prisma-3982CE?style=for-the-badge\u0026logo=Prisma\u0026logoColor=white)\n\nA friendly TypeScript library that brings the power of PostgreSQL triggers to your Prisma workflow! 🚀\n\nNever write SQL trigger syntax again - define your database triggers using a beautiful, type-safe API that integrates seamlessly with your existing Prisma schema.\n\n## ✨ Features\n\n- 🌍 **Universal Compatibility**: Works with any Prisma client, including custom generated clients\n- 🛡️ **Fully Typesafe**: Leverages your specific Prisma schema for complete type safety\n- 🎨 **Generic Design**: No hardcoding of model names or fields - it just works with YOUR schema!\n- 🔗 **Fluent Builder API**: Create triggers using a chainable, intuitive API that feels natural\n- 🎯 **Typesafe Condition Building**: Write trigger conditions with full TypeScript intellisense\n- 📬 **Real-time Notifications**: Subscribe to database changes with type-safe payload handling\n- 📚 **Multiple Approaches**: Use individual triggers or a centralized registry - your choice!\n- 🚀 **Zero SQL Required**: No need to remember PostgreSQL trigger syntax ever again\n\n## 📦 Installation\n\n```bash\nnpm install pg-typesafe-triggers\n# or\nyarn add pg-typesafe-triggers\n# or\npnpm add pg-typesafe-triggers\n# or\nbun add pg-typesafe-triggers\n```\n\n**Prerequisites:**\n- Prisma v4.0 or later\n- PostgreSQL database\n- `postgres` package (for database connections)\n\n## 🚀 Quick Start\n\nChoose your adventure! We offer two delightful ways to work with triggers:\n\n### 🎯 Approach 1: Fluent Builder API\n\nPerfect for when you want fine-grained control over individual triggers:\n\n```typescript\nimport { createTriggers } from 'pg-typesafe-triggers';\nimport { PrismaClient } from '@prisma/client';\n\nconst prisma = new PrismaClient();\nconst triggers = createTriggers\u003ctypeof prisma\u003e(process.env.DATABASE_URL!);\n\n// Create a beautiful, type-safe trigger ✨\nconst itemTrigger = triggers\n  .for('item')  // 👈 Your model names are auto-completed!\n  .withName('notify_item_changes')\n  .after()\n  .on('INSERT', 'UPDATE')\n  .when((c) =\u003e c.NEW('status').eq('completed'))  // 👈 Type-safe conditions!\n  .notify('item_updates')\n  .build();\n\n// Set it up and start listening\nawait itemTrigger.setup();\nawait itemTrigger.listen();\n\n// React to changes in real-time! 🎉\nitemTrigger.subscribe((event) =\u003e {\n  console.log(`Item ${event.data.name} is now ${event.data.status}!`);\n});\n\n// Your trigger fires automatically when conditions are met\nawait prisma.item.create({\n  data: { \n    name: 'Important Task',\n    status: 'completed'  // This will trigger our notification!\n  }\n});\n```\n\n### 📚 Approach 2: Registry Pattern\n\nIdeal for managing multiple triggers across your application:\n\n```typescript\nimport { createTriggers } from 'pg-typesafe-triggers';\nimport { PrismaClient } from '@prisma/client';\n\nconst prisma = new PrismaClient();\nconst triggers = createTriggers\u003ctypeof prisma\u003e(process.env.DATABASE_URL!);\n\n// Create a registry to organize all your triggers 📁\nconst registry = triggers.registry();\n\n// Add multiple models with their triggers in one go!\nregistry\n  .add('item', {\n    events: ['INSERT', 'UPDATE', 'DELETE'],\n    timing: 'AFTER',\n    forEach: 'ROW',\n    notify: 'item_events'\n  })\n  .add('user', {\n    events: ['INSERT', 'UPDATE'],\n    timing: 'AFTER',\n    forEach: 'ROW',\n    notify: 'user_events'\n  })\n  .add('order', {\n    events: ['INSERT'],\n    timing: 'AFTER',\n    forEach: 'ROW',\n    when: (c) =\u003e c.NEW('status').eq('confirmed'),\n    notify: 'confirmed_orders'\n  });\n\n// Set up everything with one command! 🎪\nawait registry.setup();\nawait registry.listen();\n\n// Subscribe to all your channels elegantly\nregistry.on('item', (event) =\u003e {\n  console.log(`Item event: ${event.operation} on ${event.data.name}`);\n});\n\nregistry.on('user', (event) =\u003e {\n  console.log(`New user registered: ${event.data.email}`);\n});\n\nregistry.on('confirmed_orders', (event) =\u003e {\n  console.log(`Order confirmed! Amount: $${event.data.total}`);\n  // Send confirmation email, update inventory, etc.\n});\n```\n\n## 🤔 Why pg-typesafe-triggers?\n\n**Before** (writing raw SQL triggers):\n```sql\nCREATE OR REPLACE FUNCTION notify_item_change() RETURNS TRIGGER AS $$\nBEGIN\n  IF NEW.status = 'completed' THEN\n    PERFORM pg_notify('item_updates', json_build_object(\n      'operation', TG_OP,\n      'data', row_to_json(NEW)\n    )::text);\n  END IF;\n  RETURN NEW;\nEND;\n$$ LANGUAGE plpgsql;\n\nCREATE TRIGGER item_status_trigger\nAFTER INSERT OR UPDATE ON \"Item\"\nFOR EACH ROW\nWHEN (NEW.status = 'completed')\nEXECUTE FUNCTION notify_item_change();\n```\n\n**After** (with pg-typesafe-triggers):\n```typescript\ntriggers\n  .for('item')\n  .after()\n  .on('INSERT', 'UPDATE')\n  .when((c) =\u003e c.NEW('status').eq('completed'))\n  .notify('item_updates')\n  .build();\n```\n\n✨ **Same result, 10x less code, 100% type-safe!**\n\n## 📖 API Reference\n\n### 🔨 Building Triggers\n\n```typescript\n// Fluent Builder API\ntriggers\n  .for('modelName')           // Your Prisma model (auto-completed!)\n  .withName('my_trigger')      // Optional: custom name\n  .before() / .after()         // Timing\n  .on('INSERT', 'UPDATE')      // Events to watch\n  .watchColumns('col1', 'col2') // Optional: specific columns\n  .when(condition)             // Optional: conditions\n  .notify('channel')           // Send notifications\n  .build();\n```\n\n### 🎯 Condition Builder\n\n```typescript\n// Type-safe field comparisons\n.when((c) =\u003e c.NEW('status').eq('active'))\n.when((c) =\u003e c.NEW('price').gt(100))\n.when((c) =\u003e c.OLD('email').ne(c.NEW('email')))\n\n// Boolean logic\n.when((c) =\u003e c.and(\n  c.NEW('status').eq('published'),\n  c.NEW('visibility').eq('public')\n))\n\n// Check if field changed\n.when((c) =\u003e c.changed('status'))\n\n// Raw SQL (escape hatch)\n.when('NEW.price \u003e 1000 AND NEW.currency = \\'USD\\'')\n```\n\n### 📡 Notification Handling\n\n```typescript\n// Single trigger\ntrigger.subscribe((event) =\u003e {\n  console.log(event.operation);  // 'INSERT' | 'UPDATE' | 'DELETE'\n  console.log(event.timestamp);   // When it happened\n  console.log(event.data);        // Your typed model data\n});\n\n// Registry pattern\nregistry.on('channel', (event) =\u003e {\n  // Same event structure, fully typed!\n});\n```\n\n### 🎮 Lifecycle Management\n\n```typescript\n// Individual triggers\nawait trigger.setup();    // Create in database\nawait trigger.listen();   // Start listening\nawait trigger.stop();     // Stop listening\nawait trigger.drop();     // Remove from database\n\n// Registry\nawait registry.setup();   // Set up all triggers\nawait registry.listen();  // Start all listeners\nawait registry.stop();    // Stop all listeners\nawait registry.drop();    // Clean up everything\n```\n\n## 💡 Common Patterns\n\n### Audit Logging\n```typescript\ntriggers\n  .for('user')\n  .after()\n  .on('UPDATE')\n  .when((c) =\u003e c.changed('email'))\n  .notify('audit_log')\n  .build();\n```\n\n### Status Workflows\n```typescript\ntriggers\n  .for('order')\n  .after()\n  .on('UPDATE')\n  .when((c) =\u003e c.and(\n    c.OLD('status').eq('pending'),\n    c.NEW('status').eq('confirmed')\n  ))\n  .notify('order_confirmed')\n  .build();\n```\n\n### Real-time Updates\n```typescript\ntriggers\n  .for('message')\n  .after()\n  .on('INSERT')\n  .notify('new_messages')\n  .build();\n```\n\n## 🚨 Troubleshooting\n\n### \"Could not access Prisma DMMF\"\nThis warning appears when using Prisma with adapters. Your triggers will still work correctly! The library falls back to smart defaults.\n\n### Notifications not received?\n1. Check your PostgreSQL logs for errors\n2. Ensure your database user has TRIGGER privileges\n3. Verify the channel names match between trigger and listener\n4. Try running `SELECT * FROM pg_trigger` to see if triggers were created\n\n### Type errors?\nMake sure you're passing your Prisma client type correctly:\n```typescript\nconst triggers = createTriggers\u003ctypeof prisma\u003e(DATABASE_URL);\n//                              ^^^^^^^^^^^^^^ This is important!\n```\n\n## 🤝 Contributing\n\nWe'd love your help making this library even better! \n\n1. Fork the repository\n2. Create your feature branch (`git checkout -b feature/amazing-feature`)\n3. Run tests (`bun test`)\n4. Commit your changes (`git commit -m 'Add some amazing feature'`)\n5. Push to the branch (`git push origin feature/amazing-feature`)\n6. Open a Pull Request\n\n### Development Setup\n\n```bash\n# Clone and install\ngit clone https://github.com/bewinxed/pg-typesafe-triggers.git\ncd pg-typesafe-triggers\nbun install\n\n# Run tests\nbun test\n\n# Build\nbun run build\n```\n\n## 📄 License\n\nMIT © [bewinxed](https://github.com/bewinxed)\n\n---\n\n\u003cp align=\"center\"\u003e\n  Made with 💜 by developers who were tired of writing PostgreSQL trigger syntax\n  \u003cbr\u003e\n  \u003cbr\u003e\n  If this library helped you, consider giving it a ⭐️\n  \u003cbr\u003e\n  \u003cbr\u003e\n  \u003ca href=\"https://github.com/bewinxed/pg-typesafe-triggers/issues\"\u003eReport Bug\u003c/a\u003e\n  ·\n  \u003ca href=\"https://github.com/bewinxed/pg-typesafe-triggers/issues\"\u003eRequest Feature\u003c/a\u003e\n  ·\n  \u003ca href=\"https://github.com/bewinxed/pg-typesafe-triggers/discussions\"\u003eJoin Discussion\u003c/a\u003e\n\u003c/p\u003e","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fbewinxed%2Fpg-typesafe-triggers","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fbewinxed%2Fpg-typesafe-triggers","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fbewinxed%2Fpg-typesafe-triggers/lists"}