{"_id":"@agelum/backend","_rev":"6-b4e35179ad0dcec32fe330a578eeab2d","name":"@agelum/backend","dist-tags":{"latest":"0.1.5"},"versions":{"0.1.0":{"name":"@agelum/backend","version":"0.1.0","keywords":["drizzle","reactive","database","orm","real-time","cache","trpc","typescript"],"author":{"name":"Agelum"},"license":"MIT","_id":"@agelum/backend@0.1.0","maintainers":[{"name":"agelum","email":"facundomatiasfierro@gmail.com"}],"homepage":"https://github.com/agelum/drizzle-reactive#readme","bugs":{"url":"https://github.com/agelum/drizzle-reactive/issues"},"dist":{"shasum":"501d4c643c0949c6a20b7f24571e717afcace665","tarball":"https://registry.npmjs.org/@agelum/backend/-/backend-0.1.0.tgz","fileCount":106,"integrity":"sha512-y/ucZWcHUrchlFL7wgNJG/srJSZV26IWpfHCYHf7lASloAJIWgF0KNqm9AYVb3CW8Q6ooPoz2RKBQm7WArc8KA==","signatures":[{"sig":"MEYCIQCPgCpIM+3W4HEqg43e0N1ae02dYgcSMVyBNkL8rJi1vwIhAJ7BwdOplPb8QrUvaYKw39j74p9u27Gp4KZfkukikUp7","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":363502},"main":"dist/index.js","types":"dist/index.d.ts","exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js","require":"./dist/index.js"},"./trpc":{"types":"./dist/trpc/index.d.ts","import":"./dist/trpc/index.js","require":"./dist/trpc/index.js"},"./client":{"types":"./dist/client.d.ts","import":"./dist/client.js","require":"./dist/client.js"},"./server":{"types":"./dist/server.d.ts","import":"./dist/server.js","require":"./dist/server.js"}},"gitHead":"86a634318efe05d15cf0a7d5ba64cf6363c385d1","scripts":{"dev":"tsc --watch","lint":"eslint src/**/*.ts","test":"jest","build":"pnpm install && tsc","clean":"rm -rf dist","types":"tsc --declaration --emitDeclarationOnly"},"_npmUser":{"name":"agelum","email":"facundomatiasfierro@gmail.com"},"repository":{"url":"git+https://github.com/agelum/drizzle-reactive.git","type":"git"},"_npmVersion":"11.6.2","description":"Zero configuration, maximum intelligence. Reactive database library for Drizzle ORM with real-time synchronization.","directories":{},"_nodeVersion":"24.5.0","dependencies":{"zod":"^3.22.0","react":"^18.2.0","superjson":"^2.2.1","drizzle-orm":"0.44.4","@trpc/client":"^10.45.0","@trpc/server":"^10.45.0","@trpc/react-query":"^10.45.0","@tanstack/react-query":"^4.36.1"},"_hasShrinkwrap":false,"devDependencies":{"jest":"^29.0.0","eslint":"^8.0.0","typescript":"^5.4.5","@types/jest":"^29.0.0","@types/node":"^20.0.0","@types/react":"^18.2.55","@typescript-eslint/parser":"^6.0.0","@typescript-eslint/eslint-plugin":"^6.0.0"},"peerDependencies":{"zod":">=3.22.0","react":">=18.2.0","@trpc/client":">=10.0.0","@trpc/server":">=10.0.0"},"_npmOperationalInternal":{"tmp":"tmp/backend_0.1.0_1768743115275_0.45210265753728773","host":"s3://npm-registry-packages-npm-production"}},"0.1.1":{"name":"@agelum/backend","version":"0.1.1","keywords":["drizzle","reactive","database","orm","real-time","cache","trpc","typescript"],"author":{"name":"Agelum"},"license":"MIT","_id":"@agelum/backend@0.1.1","maintainers":[{"name":"agelum","email":"facundomatiasfierro@gmail.com"}],"homepage":"https://github.com/agelum/backend#readme","bugs":{"url":"https://github.com/agelum/backend/issues"},"dist":{"shasum":"8becaa6c0ba541d5bb4a7b07dba087fa3903de8c","tarball":"https://registry.npmjs.org/@agelum/backend/-/backend-0.1.1.tgz","fileCount":2,"integrity":"sha512-fUhWXQ7zsnZUWeysxy8sWYezJrM34GV8Ab/OBZNiMPQpAZIKRoCVIGAedoV+QB5kSpA/A5qo43ryZTwhHA+lMQ==","signatures":[{"sig":"MEUCIG6D88qBB5Y+RuTLkDd4uRnKGpa6YiXJPGb5yNG3FHAdAiEAiZyXI/WoH/yFueDVZwDRtzdfVHgTACCl4gaADVqWMOU=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":15656},"main":"dist/index.js","_from":"file:agelum-backend-0.1.1.tgz","types":"dist/index.d.ts","exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js","require":"./dist/index.js"},"./trpc":{"types":"./dist/trpc/index.d.ts","import":"./dist/trpc/index.js","require":"./dist/trpc/index.js"},"./client":{"types":"./dist/client.d.ts","import":"./dist/client.js","require":"./dist/client.js"},"./server":{"types":"./dist/server.d.ts","import":"./dist/server.js","require":"./dist/server.js"}},"scripts":{"dev":"tsc --watch","lint":"eslint src/**/*.ts","test":"jest","build":"pnpm install && tsc","clean":"rm -rf dist","types":"tsc --declaration --emitDeclarationOnly"},"_npmUser":{"name":"agelum","email":"facundomatiasfierro@gmail.com"},"_resolved":"/private/var/folders/h8/cq22hyf52mj35vvknzlj_qvm0000gn/T/0d2ede1ccabbdcf1142ac7c2f0a82384/agelum-backend-0.1.1.tgz","_integrity":"sha512-fUhWXQ7zsnZUWeysxy8sWYezJrM34GV8Ab/OBZNiMPQpAZIKRoCVIGAedoV+QB5kSpA/A5qo43ryZTwhHA+lMQ==","repository":{"url":"git+https://github.com/agelum/backend.git","type":"git"},"_npmVersion":"11.6.2","description":"Zero configuration, maximum intelligence. Reactive database library for Drizzle ORM with real-time synchronization.","directories":{},"_nodeVersion":"24.5.0","dependencies":{"zod":"^3.22.0","react":"^18.2.0","superjson":"^2.2.1","drizzle-orm":"0.44.4","@trpc/client":"^10.45.0","@trpc/server":"^10.45.0","@trpc/react-query":"^10.45.0","@tanstack/react-query":"^4.36.1"},"_hasShrinkwrap":false,"devDependencies":{"jest":"^29.0.0","eslint":"^8.0.0","typescript":"^5.4.5","@types/jest":"^29.0.0","@types/node":"^20.0.0","@types/react":"^18.2.55","@typescript-eslint/parser":"^6.0.0","@typescript-eslint/eslint-plugin":"^6.0.0"},"peerDependencies":{"zod":">=3.22.0","react":">=18.2.0","@trpc/client":">=10.0.0","@trpc/server":">=10.0.0"},"_npmOperationalInternal":{"tmp":"tmp/backend_0.1.1_1769211122144_0.9468819463718166","host":"s3://npm-registry-packages-npm-production"}},"0.1.2":{"name":"@agelum/backend","version":"0.1.2","keywords":["drizzle","reactive","database","orm","real-time","cache","trpc","typescript"],"author":{"name":"Agelum"},"license":"MIT","_id":"@agelum/backend@0.1.2","maintainers":[{"name":"agelum","email":"facundomatiasfierro@gmail.com"}],"homepage":"https://github.com/agelum/backend#readme","bugs":{"url":"https://github.com/agelum/backend/issues"},"dist":{"shasum":"f9b4211d967303fd091b45844dbbfe629fa673aa","tarball":"https://registry.npmjs.org/@agelum/backend/-/backend-0.1.2.tgz","fileCount":70,"integrity":"sha512-kXro86W48x3LXlUmBpQGlrQENYhBm15aWkxSWUWr/HO8av0p9CX5lgGHXnqYKhS94/vIPw8n4A+m168+A7eg6Q==","signatures":[{"sig":"MEUCIQC5/9OknmQ4Fq6W3CVHRmLLePs+eu5dHOQUkOMBREo2bAIgfCbitu0KGKJYqnioYE2h9KJruDphG6nH0IHsXA7Tx78=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":166952},"main":"dist/index.js","_from":"file:agelum-backend-0.1.2.tgz","types":"dist/index.d.ts","exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js","require":"./dist/index.js"},"./trpc":{"types":"./dist/trpc/index.d.ts","import":"./dist/trpc/index.js","require":"./dist/trpc/index.js"},"./client":{"types":"./dist/client.d.ts","import":"./dist/client.js","require":"./dist/client.js"},"./server":{"types":"./dist/server.d.ts","import":"./dist/server.js","require":"./dist/server.js"}},"scripts":{"dev":"tsc --watch","lint":"eslint src/**/*.ts","test":"jest","build":"pnpm install && tsc","clean":"rm -rf dist","types":"tsc --declaration --emitDeclarationOnly"},"_npmUser":{"name":"agelum","email":"facundomatiasfierro@gmail.com"},"_resolved":"/private/var/folders/h8/cq22hyf52mj35vvknzlj_qvm0000gn/T/5c5f9d4d72a716df2e9aabc4eeaa4e63/agelum-backend-0.1.2.tgz","_integrity":"sha512-kXro86W48x3LXlUmBpQGlrQENYhBm15aWkxSWUWr/HO8av0p9CX5lgGHXnqYKhS94/vIPw8n4A+m168+A7eg6Q==","repository":{"url":"git+https://github.com/agelum/backend.git","type":"git"},"_npmVersion":"11.6.2","description":"Zero configuration, maximum intelligence. Reactive database library for Drizzle ORM with real-time synchronization.","directories":{},"_nodeVersion":"24.5.0","dependencies":{"zod":"^3.22.0","react":"^18.2.0","superjson":"^2.2.1","drizzle-orm":"0.44.4","@trpc/client":"^10.45.0","@trpc/server":"^10.45.0","@trpc/react-query":"^10.45.0","@tanstack/react-query":"^4.36.1"},"_hasShrinkwrap":false,"devDependencies":{"jest":"^29.0.0","eslint":"^8.0.0","typescript":"^5.4.5","@types/jest":"^29.0.0","@types/node":"^20.0.0","@types/react":"^18.2.55","@typescript-eslint/parser":"^6.0.0","@typescript-eslint/eslint-plugin":"^6.0.0"},"peerDependencies":{"zod":">=3.22.0","react":">=18.2.0","@trpc/client":">=10.0.0","@trpc/server":">=10.0.0"},"_npmOperationalInternal":{"tmp":"tmp/backend_0.1.2_1769214303171_0.20181596938687485","host":"s3://npm-registry-packages-npm-production"}},"0.1.3":{"name":"@agelum/backend","version":"0.1.3","keywords":["drizzle","reactive","database","orm","real-time","cache","trpc","typescript"],"author":{"name":"Agelum"},"license":"MIT","_id":"@agelum/backend@0.1.3","maintainers":[{"name":"agelum","email":"facundomatiasfierro@gmail.com"}],"homepage":"https://github.com/agelum/backend#readme","bugs":{"url":"https://github.com/agelum/backend/issues"},"dist":{"shasum":"a44f0d387767f58da9b9287f075bae9dcd57a58d","tarball":"https://registry.npmjs.org/@agelum/backend/-/backend-0.1.3.tgz","fileCount":106,"integrity":"sha512-sMot9SJp8GX2KE/wYOzCZbhKPFJQSeRdAyZuyidtILq7OapJXFFf/wqz4NGNUddcscM75+zUpz027OncXNqtcA==","signatures":[{"sig":"MEUCIQCCaKCx7FWgWCwVEu2ASnpTknATvo5k43cV1nPqXRP5gQIgMgx1OsWCDR7Btfk6+MfvXWgwzNedMGvemI3ua3dwAV0=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":346339},"main":"dist/index.js","type":"module","types":"dist/index.d.ts","exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js","require":"./dist/index.js"},"./trpc":{"types":"./dist/trpc/index.d.ts","import":"./dist/trpc/index.js","require":"./dist/trpc/index.js"},"./client":{"types":"./dist/client.d.ts","import":"./dist/client.js","require":"./dist/client.js"},"./core/*":{"types":"./dist/core/*.d.ts","import":"./dist/core/*.js","require":"./dist/core/*.js"},"./server":{"types":"./dist/server.d.ts","import":"./dist/server.js","require":"./dist/server.js"},"./providers/*":{"types":"./dist/providers/*.d.ts","import":"./dist/providers/*.js","require":"./dist/providers/*.js"}},"gitHead":"266d2a0a0f491537f8c7e7769fc7427ae20ff890","scripts":{"dev":"tsc --watch","lint":"eslint src/**/*.ts","test":"jest","build":"pnpm install && tsc","clean":"rm -rf dist","types":"tsc --declaration --emitDeclarationOnly"},"_npmUser":{"name":"agelum","email":"facundomatiasfierro@gmail.com"},"repository":{"url":"git+https://github.com/agelum/backend.git","type":"git"},"_npmVersion":"11.6.2","description":"Zero configuration, maximum intelligence. Reactive database library for Drizzle ORM with real-time synchronization.","directories":{},"_nodeVersion":"24.5.0","dependencies":{"zod":"^3.22.0","react":"^18.2.0","superjson":"^2.2.1","drizzle-orm":"0.44.4","@trpc/client":"^10.45.0","@trpc/server":"^10.45.0","@trpc/react-query":"^10.45.0","@tanstack/react-query":"^4.36.1"},"_hasShrinkwrap":false,"devDependencies":{"jest":"^29.0.0","eslint":"^8.0.0","typescript":"^5.4.5","@types/jest":"^29.0.0","@types/node":"^20.0.0","@types/react":"^18.2.55","@typescript-eslint/parser":"^6.0.0","@typescript-eslint/eslint-plugin":"^6.0.0"},"peerDependencies":{"zod":">=3.22.0","react":">=18.2.0","@trpc/client":">=10.0.0","@trpc/server":">=10.0.0"},"_npmOperationalInternal":{"tmp":"tmp/backend_0.1.3_1769271635894_0.8572077945912551","host":"s3://npm-registry-packages-npm-production"}},"0.1.4":{"name":"@agelum/backend","version":"0.1.4","keywords":["drizzle","reactive","database","orm","real-time","cache","trpc","typescript"],"author":{"name":"Agelum"},"license":"MIT","_id":"@agelum/backend@0.1.4","maintainers":[{"name":"agelum","email":"facundomatiasfierro@gmail.com"}],"homepage":"https://github.com/agelum/backend#readme","bugs":{"url":"https://github.com/agelum/backend/issues"},"dist":{"shasum":"15a4edf4ba5dfa65163576c1ee0eb889468898bd","tarball":"https://registry.npmjs.org/@agelum/backend/-/backend-0.1.4.tgz","fileCount":106,"integrity":"sha512-fkwJh02Mz4xvIUf9eU8ohpz+CN8Pe8r8Av5zIbWG+1TrD1LXd+NrqskbI7AeLqTJr4YJiEHrqrtv2EVLIBoNkA==","signatures":[{"sig":"MEYCIQCnzKHv32hWms7AiSrsoK3/nkD2k9cvSEu2rtfxOpx6fAIhAJzMt9fBnVQN6w/mTlpAFCFM9t0IEb7A0tRuRlGkRdDh","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":365149},"main":"dist/index.js","type":"module","_from":"file:agelum-backend-0.1.4.tgz","types":"dist/index.d.ts","exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js","require":"./dist/index.js"},"./trpc":{"types":"./dist/trpc/index.d.ts","import":"./dist/trpc/index.js","require":"./dist/trpc/index.js"},"./client":{"types":"./dist/client.d.ts","import":"./dist/client.js","require":"./dist/client.js"},"./core/*":{"types":"./dist/core/*.d.ts","import":"./dist/core/*.js","require":"./dist/core/*.js"},"./server":{"types":"./dist/server.d.ts","import":"./dist/server.js","require":"./dist/server.js"},"./providers/*":{"types":"./dist/providers/*.d.ts","import":"./dist/providers/*.js","require":"./dist/providers/*.js"}},"scripts":{"dev":"tsc --watch","lint":"eslint src/**/*.ts","test":"jest","build":"pnpm install && tsc","clean":"rm -rf dist","types":"tsc --declaration --emitDeclarationOnly"},"_npmUser":{"name":"agelum","email":"facundomatiasfierro@gmail.com"},"_resolved":"/private/var/folders/h8/cq22hyf52mj35vvknzlj_qvm0000gn/T/18f7b907be014e69bb8e57cccfb7c251/agelum-backend-0.1.4.tgz","_integrity":"sha512-fkwJh02Mz4xvIUf9eU8ohpz+CN8Pe8r8Av5zIbWG+1TrD1LXd+NrqskbI7AeLqTJr4YJiEHrqrtv2EVLIBoNkA==","repository":{"url":"git+https://github.com/agelum/backend.git","type":"git"},"_npmVersion":"11.6.2","description":"Zero configuration, maximum intelligence. Reactive database library for Drizzle ORM with real-time synchronization.","directories":{},"_nodeVersion":"24.5.0","dependencies":{"zod":"^3.22.0","react":"^18.2.0","redis":"^4.7.0","superjson":"^2.2.1","drizzle-orm":"0.44.4","@trpc/client":"^10.45.0","@trpc/server":"^10.45.0","@trpc/react-query":"^10.45.0","@tanstack/react-query":"^4.36.1"},"_hasShrinkwrap":false,"devDependencies":{"jest":"^29.0.0","eslint":"^8.0.0","typescript":"^5.4.5","@types/jest":"^29.0.0","@types/node":"^20.0.0","@types/react":"^18.2.55","@typescript-eslint/parser":"^6.0.0","@typescript-eslint/eslint-plugin":"^6.0.0"},"peerDependencies":{"zod":">=3.22.0","react":">=18.2.0","@trpc/client":">=10.0.0","@trpc/server":">=10.0.0"},"_npmOperationalInternal":{"tmp":"tmp/backend_0.1.4_1769283966913_0.18370241612657168","host":"s3://npm-registry-packages-npm-production"}},"0.1.5":{"name":"@agelum/backend","version":"0.1.5","description":"Zero configuration, maximum intelligence. Reactive database library for Drizzle ORM with real-time synchronization.","type":"module","main":"dist/index.js","types":"dist/index.d.ts","exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js","require":"./dist/index.js"},"./server":{"types":"./dist/server.d.ts","import":"./dist/server.js","require":"./dist/server.js"},"./client":{"types":"./dist/client.d.ts","import":"./dist/client.js","require":"./dist/client.js"},"./trpc":{"types":"./dist/trpc/index.d.ts","import":"./dist/trpc/index.js","require":"./dist/trpc/index.js"},"./core/*":{"types":"./dist/core/*.d.ts","import":"./dist/core/*.js","require":"./dist/core/*.js"},"./providers/*":{"types":"./dist/providers/*.d.ts","import":"./dist/providers/*.js","require":"./dist/providers/*.js"}},"keywords":["drizzle","reactive","database","orm","real-time","cache","trpc","typescript"],"author":{"name":"Agelum"},"license":"MIT","dependencies":{"@trpc/server":"^10.45.0","@trpc/client":"^10.45.0","@trpc/react-query":"^10.45.0","@tanstack/react-query":"^4.36.1","drizzle-orm":"0.44.4","redis":"^4.7.0","zod":"^3.22.0","react":"^18.2.0","superjson":"^2.2.1"},"devDependencies":{"@types/react":"^18.2.55","@types/node":"^20.0.0","typescript":"^5.4.5","jest":"^29.0.0","@types/jest":"^29.0.0","eslint":"^8.0.0","@typescript-eslint/eslint-plugin":"^6.0.0","@typescript-eslint/parser":"^6.0.0"},"peerDependencies":{"@trpc/server":">=10.0.0","@trpc/client":">=10.0.0","react":">=18.2.0","zod":">=3.22.0"},"repository":{"type":"git","url":"git+https://github.com/agelum/backend.git"},"homepage":"https://github.com/agelum/backend#readme","bugs":{"url":"https://github.com/agelum/backend/issues"},"scripts":{"build":"pnpm install && tsc","dev":"tsc --watch","clean":"rm -rf dist","types":"tsc --declaration --emitDeclarationOnly","test":"jest","lint":"eslint src/**/*.ts"},"_id":"@agelum/backend@0.1.5","_integrity":"sha512-g9OJ7SXOnP1IqTs7m591aNqk6UuhDapLCjyofTGA/b/vv1azShT57t9+bJnjbwvRehahwxRvlHh7g0PKvg6+pw==","_resolved":"/private/var/folders/h8/cq22hyf52mj35vvknzlj_qvm0000gn/T/eec37695d7f8fbeb58518187a8a45394/agelum-backend-0.1.5.tgz","_from":"file:agelum-backend-0.1.5.tgz","_nodeVersion":"24.5.0","_npmVersion":"11.6.2","dist":{"integrity":"sha512-g9OJ7SXOnP1IqTs7m591aNqk6UuhDapLCjyofTGA/b/vv1azShT57t9+bJnjbwvRehahwxRvlHh7g0PKvg6+pw==","shasum":"44dfdf87045f366571ea73eb36f6c518ef08774e","tarball":"https://registry.npmjs.org/@agelum/backend/-/backend-0.1.5.tgz","fileCount":106,"unpackedSize":365117,"signatures":[{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEUCICKsDYLSy0jLeVkC0vsK+TgABclDniL3T+cfGkRuZzReAiEAu1mTBj2UAWSbyWnnAuMeXypQEMto4svJsiNKakGnflY="}]},"_npmUser":{"name":"agelum","email":"facundomatiasfierro@gmail.com"},"directories":{},"maintainers":[{"name":"agelum","email":"facundomatiasfierro@gmail.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/backend_0.1.5_1769296231600_0.41334177602490474"},"_hasShrinkwrap":false}},"time":{"created":"2026-01-18T13:31:55.211Z","modified":"2026-01-24T23:10:31.850Z","0.1.0":"2026-01-18T13:31:55.468Z","0.1.1":"2026-01-23T23:32:02.289Z","0.1.2":"2026-01-24T00:25:03.327Z","0.1.3":"2026-01-24T16:20:36.089Z","0.1.4":"2026-01-24T19:46:07.058Z","0.1.5":"2026-01-24T23:10:31.744Z"},"bugs":{"url":"https://github.com/agelum/backend/issues"},"author":{"name":"Agelum"},"license":"MIT","homepage":"https://github.com/agelum/backend#readme","keywords":["drizzle","reactive","database","orm","real-time","cache","trpc","typescript"],"repository":{"type":"git","url":"git+https://github.com/agelum/backend.git"},"description":"Zero configuration, maximum intelligence. Reactive database library for Drizzle ORM with real-time synchronization.","maintainers":[{"name":"agelum","email":"facundomatiasfierro@gmail.com"}],"readme":"# @agelum/backend\n\n**Zero configuration, maximum intelligence. Reactive everywhere with no boilerplate.**\n\nA reactive database library that transforms any Drizzle + tRPC setup into a reactive, real-time system with minimal configuration and zero boilerplate code changes.\n\n## ✨ Features\n\n- **🚀 Zero Configuration**: Single config file with just table relations\n- **⚡ Instant Cache**: Shows cached data immediately, revalidates smartly\n- **🔄 Real-time Sync**: Built-in Server-Sent Events for cache invalidation\n- **🎯 Smart Invalidation**: Only invalidates relevant queries based on relations\n- **📱 Offline Ready**: Handles page refresh and session gaps gracefully\n- **🔒 Type Safe**: 100% automatic type safety with tRPC integration\n- **☁️ Vercel Compatible**: Works perfectly with serverless deployment\n- **🧠 Intelligent**: Prioritizes active hooks for better UX\n\n## 🚀 Quick Start\n\n### Installation\n\n```bash\npnpm add @agelum/backend drizzle-orm @trpc/server @trpc/client zod\n```\n\n## 📖 Core Usage Patterns\n\n### 1. Define Reactive Functions\n\n**Key Concept**: Reactive functions work both standalone (server-side) AND via tRPC. The `name` property is crucial for cache keys and tRPC procedures. Handlers receive `{ input, db }` and you can use `db.db` to access the underlying Drizzle instance inside handlers.\n\n```typescript\n// server/functions/users.ts\nimport { defineReactiveFunction } from \"@agelum/backend/server\";\nimport { z } from \"zod\";\n\n// 1. Define a reactive function with explicit name\nexport const getUsers =\n  defineReactiveFunction({\n    name: \"users.getAll\", // 🔑 This becomes the cache key and tRPC procedure name\n\n    input: z.object({\n      companyId: z.string(), // Generic, not hardcoded organizationId\n      limit: z\n        .number()\n        .optional()\n        .default(50),\n    }),\n\n    dependencies: [\"user\"], // What tables this function reads from\n\n    handler: async ({ input, db }) => {\n      // Clean signature: ({ input, db })\n      return db.db.query.users.findMany(\n        {\n          where: (users, { eq }) =>\n            eq(\n              users.companyId,\n              input.companyId,\n            ),\n          limit: input.limit,\n        },\n      );\n    },\n  });\n\nexport const createUser =\n  defineReactiveFunction({\n    name: \"users.create\",\n\n    input: z.object({\n      name: z.string(),\n      email: z.string().email(),\n      companyId: z.string(),\n    }),\n\n    dependencies: [\"user\"],\n\n    handler: async ({ input, db }) => {\n      return db.db\n        .insert(users)\n        .values(input)\n        .returning();\n    },\n  });\n\nexport const getUserProfile =\n  defineReactiveFunction({\n    name: \"users.profile.getDetailed\", // 🏷️ Nested names work perfectly\n\n    input: z.object({\n      userId: z.string(),\n    }),\n\n    dependencies: [\n      \"user\",\n      \"profile\",\n      \"preferences\",\n    ],\n\n    handler: async ({ input, db }) => {\n      const user =\n        await db.db.query.users.findFirst(\n          {\n            where: (users, { eq }) =>\n              eq(\n                users.id,\n                input.userId,\n              ),\n            with: {\n              profile: true,\n              preferences: true,\n            },\n          },\n        );\n      return user;\n    },\n  });\n```\n\n**Optional Cache Settings (per function)**:\n\n```typescript\nexport const getUsers =\n  defineReactiveFunction({\n    name: \"users.getAll\",\n    input: z.object({\n      companyId: z.string(),\n    }),\n    dependencies: [\"user\"],\n    cacheEnabled: true,\n    cache: {\n      ttl: 300,\n      key: (input) =>\n        `users.getAll:${JSON.stringify(input)}`,\n    },\n    handler: async ({ input, db }) => {\n      return db.db.query.users.findMany(\n        {\n          where: (users, { eq }) =>\n            eq(\n              users.companyId,\n              input.companyId,\n            ),\n        },\n      );\n    },\n  });\n```\n\nCache is opt-in per function via `cacheEnabled: true` or `cache.enabled: true`.\n\n#### Typing Notes\n\nIf you want explicit handler typing, use `ReactiveFunctionContext<TInput>` with a Zod-inferred input type:\n\n```typescript\nimport type { ReactiveFunctionContext } from \"@agelum/backend/server\";\nimport { z } from \"zod\";\n\nconst getUsersInput = z.object({\n  companyId: z.string(),\n  limit: z.number().optional(),\n});\n\ntype GetUsersInput = z.infer<\n  typeof getUsersInput\n>;\n\nexport const getUsers =\n  defineReactiveFunction({\n    name: \"users.getAll\",\n    input: getUsersInput,\n    dependencies: [\"user\"],\n    handler: async ({\n      input,\n      db,\n    }: ReactiveFunctionContext<GetUsersInput>) => {\n      return db.db.query.users.findMany(\n        {\n          where: (users, { eq }) =>\n            eq(\n              users.companyId,\n              input.companyId,\n            ),\n          limit: input.limit ?? 50,\n        },\n      );\n    },\n  });\n```\n\n### 2. Server-Side Execution (Without tRPC)\n\n**Use Case**: Background jobs, API routes, server actions, webhooks, etc.\n\n```typescript\n// server/api/users/route.ts - Next.js API route\nimport {\n  getUsers,\n  createUser,\n} from \"../functions/users\";\nimport { db } from \"../db\";\n\nexport async function GET(\n  request: Request,\n) {\n  const { searchParams } = new URL(\n    request.url,\n  );\n  const companyId = searchParams.get(\n    \"companyId\",\n  )!;\n\n  // ✅ Execute reactive function directly on server\n  const users = await getUsers.execute(\n    { companyId, limit: 20 },\n    db, // Your reactive database instance\n  );\n\n  return Response.json({ users });\n}\n\nexport async function POST(\n  request: Request,\n) {\n  const body = await request.json();\n\n  // ✅ Execute reactive function directly on server\n  const newUser =\n    await createUser.execute(body, db);\n\n  return Response.json({\n    user: newUser,\n  });\n}\n```\n\n```typescript\n// server/jobs/daily-stats.ts - Background job\nimport { getUsers } from \"../functions/users\";\nimport { db } from \"../db\";\n\nexport async function generateDailyStats() {\n  const companies =\n    await db.db.query.companies.findMany();\n\n  for (const company of companies) {\n    // ✅ Execute reactive function in background job\n    const users =\n      await getUsers.execute(\n        { companyId: company.id },\n        db,\n      );\n\n    // Process stats...\n    console.log(\n      `Company ${company.name} has ${users.length} users`,\n    );\n  }\n}\n```\n\n### 3. tRPC Integration (Auto-Generated)\n\n**Key Feature**: The tRPC router automatically uses the function `name` as the procedure name. Call `.build()` to get the final tRPC router instance.\n\n```typescript\n// server/trpc/router.ts\nimport { createReactiveRouter } from \"@agelum/backend/server\";\nimport {\n  getUsers,\n  createUser,\n  getUserProfile,\n} from \"../functions/users\";\nimport { db } from \"../db\";\n\nexport const appRouter =\n  createReactiveRouter({ db })\n    .addQuery(getUsers) // 🔄 Creates procedure: users.getAll\n    .addMutation(createUser) // 🔄 Creates procedure: users.create\n    .addQuery(getUserProfile) // 🔄 Creates procedure: users.profile.getDetailed\n    .build();\n\n// ✅ Auto-generated procedures from function names:\n// - users.getAll (query)\n// - users.create (mutation)\n// - users.profile.getDetailed (query)\n\nexport type AppRouter =\n  typeof appRouter;\n```\n\n### 4. Client-Side Usage (React Hooks)\n\n**Zero Configuration**: Just use the tRPC procedure names (which match function names).\n\n```typescript\n// client/components/UserList.tsx\nimport { useReactive } from '@agelum/backend/client'\n\nfunction UserList({ companyId }: { companyId: string }) {\n  // ✅ Uses the function name automatically: 'users.getAll'\n  const {\n    data: users,\n    isStale,\n    isLoading,\n  } = useReactive('users.getAll', {\n    companyId,\n    limit: 20,\n  })\n\n  if (isLoading) return <div>Loading...</div>\n\n  return (\n    <div>\n      {isStale && <div className=\"text-orange-500\">Syncing...</div>}\n\n      {users?.map((user) => (\n        <UserCard key={user.id} user={user} />\n      ))}\n    </div>\n  )\n}\n\nfunction UserProfile({ userId }: { userId: string }) {\n  // ✅ Nested function names work perfectly\n  const { data: profile } = useReactive('users.profile.getDetailed', {\n    userId,\n  })\n\n  return (\n    <div>\n      <h1>{profile?.name}</h1>\n      <p>{profile?.email}</p>\n      {/* Profile details... */}\n    </div>\n  )\n}\n```\n\n### 5. Mutations with Real-time Updates\n\n```typescript\n// client/components/CreateUserForm.tsx\nimport { useMutation } from '@trpc/react-query'\nimport { trpc } from '../trpc'\n\nfunction CreateUserForm({ companyId }: { companyId: string }) {\n  const createUserMutation = trpc.users.create.useMutation({\n    onSuccess: () => {\n      // ✅ Automatic cache invalidation happens via SSE\n      // No manual invalidation needed!\n    },\n  })\n\n  const handleSubmit = (data: FormData) => {\n    createUserMutation.mutate({\n      name: data.get('name') as string,\n      email: data.get('email') as string,\n      companyId,\n    })\n  }\n\n  return <form onSubmit={handleSubmit}>{/* Form fields... */}</form>\n}\n```\n\n## 🏗️ Setup\n\n### 1. Database Configuration\n\n```typescript\n// server/db.ts\nimport { createReactiveDb } from \"@agelum/backend/server\";\nimport { drizzle } from \"drizzle-orm/postgres-js\";\n\nconst config = {\n  relations: {\n    // Relations are table names (not column paths)\n    // When user table changes, invalidate these queries\n    user: [\"profile\", \"preferences\"],\n\n    // When profile table changes, invalidate these queries\n    profile: [\"user\"],\n\n    // When preferences table changes, invalidate these queries\n    preferences: [\"user\"],\n  },\n};\n\nexport const db = createReactiveDb(\n  drizzle(pool),\n  config,\n);\n```\n\n### 2. Server Cache (Redis or Memory)\n\n```typescript\nconst config = {\n  relations: {\n    user: [\"profile\", \"preferences\"],\n    profile: [\"user\"],\n    preferences: [\"user\"],\n  },\n  cache: {\n    server: {\n      provider: \"redis\",\n      redis: {\n        url: process.env.REDIS_URL,\n      },\n    },\n  },\n};\n\nexport const db = createReactiveDb(\n  drizzle(pool),\n  config,\n);\n```\n\nYou can also pass an existing Redis client via `cache.server.redis.client`.\n\n### 3. SSE Endpoint (Next.js)\n\n```typescript\n// app/api/events/route.ts\nimport { createSSEStream } from \"@agelum/backend/server\";\n\nexport async function GET(\n  request: Request,\n) {\n  const { searchParams } = new URL(\n    request.url,\n  );\n  const organizationId =\n    searchParams.get(\"organizationId\")!;\n\n  return createSSEStream(\n    organizationId,\n  );\n}\n```\n\n```typescript\n// app/api/events/ack/route.ts\n// Required for reliable delivery: client acks invalidation events\nimport { acknowledgeEvent } from \"@agelum/backend/server\";\n\nexport async function POST(\n  request: Request,\n) {\n  const { eventId } =\n    await request.json();\n  acknowledgeEvent(eventId);\n  return Response.json({ ok: true });\n}\n```\n\n### 4. Client Setup\n\n```typescript\n// client/providers/ReactiveProvider.tsx\n// Recommended: use the built-in TrpcReactiveProvider to wire revalidation generically\n'use client'\nimport { TrpcReactiveProvider } from '@agelum/backend/client'\nimport { createTRPCProxyClient, httpBatchLink } from '@trpc/client'\nimport type { AppRouter } from '../server/trpc'\nimport { reactiveRelations } from '@your-db-package/reactive-config'\n\nconst trpcClient = createTRPCProxyClient<AppRouter>({\n  links: [httpBatchLink({ url: '/api/trpc' })],\n})\n\nexport function AppProviders({ children }: { children: React.ReactNode }) {\n  const organizationId = 'your-organization-id'\n  return (\n    <TrpcReactiveProvider\n      organizationId={organizationId}\n      relations={reactiveRelations}\n      trpcClient={trpcClient}\n    >\n      {children}\n    </TrpcReactiveProvider>\n  )\n}\n\n// Alternatively, you can create your own revalidateFn with createTrpcRevalidateFn\n// and pass it to ReactiveProvider if you need custom behavior.\n```\n\nIf you use `ReactiveProvider` directly, make sure to pass a `revalidateFn` for production; the default revalidator returns mock data.\n\n### 4.1 Client Storage & Revalidation Details\n\n- The hook composes cache keys as `name::JSON(input)`.\n- LocalStorage is sharded per query to avoid large single entries:\n  - Index per organization: `reactive_registry_<orgId>` stores metadata (last revalidated, last server change, connection status).\n  - Per-query entry key: `@agelum/backend:entry:<orgId>:<hash>` stores `{ name, input, queryKey, data }`.\n- On initial render, cached data (if present) is shown immediately; background revalidation respects a minimum time window to avoid thrashing on quick navigations/refreshes.\n- Errors during revalidation do not overwrite existing cache (no-write-on-error), keeping previously known-good data.\n- Real-time invalidation uses SSE with client acknowledgments and retry; no heartbeats are sent.\n\n## Cache Keys\n\nClient cache keys are automatically composed as `name::JSON(input)`:\n\n- `users.getAll::{\"companyId\":\"123\",\"limit\":50}`\n- `users.profile.getDetailed::{\"userId\":\"456\"}`\n\nServer cache keys for reactive functions default to `name:JSON(input)` and can be customized with `cache.key`.\n\nAccess cache key programmatically:\n\n```typescript\nconst cacheKey = getUsers.getCacheKey({\n  companyId: \"test\",\n  limit: 50,\n});\n```\n\nExample value: `users.getAll:{\"companyId\":\"test\",\"limit\":50}`\n\n### 4.2 Multi-tenant Tips (Optional)\n\n- Resolve tenant databases via a main database lookup (e.g., `organization.databaseName`), not by using IDs directly as database names.\n- Read paths should not create databases; handle missing DB (`3D000`) by propagating the error or returning empty based on product policy.\n- Provisioning (create DB/schemas) belongs to explicit setup flows.\n\n## 🎯 Key Benefits Over Manual Approach\n\n| Feature                 | Manual tRPC                        | @agelum/backend                 |\n| ----------------------- | ---------------------------------- | ------------------------------- |\n| **Function Definition** | Separate function + tRPC procedure | Single `defineReactiveFunction` |\n| **Cache Keys**          | Manual generation                  | Auto from function name         |\n| **Invalidation**        | Manual `invalidateQueries`         | Automatic via relations         |\n| **Real-time**           | Manual WebSocket setup             | Built-in SSE                    |\n| **Server Execution**    | Separate function needed           | Same function works everywhere  |\n| **Type Safety**         | Manual type wiring                 | 100% automatic                  |\n\n## 📈 Advanced Usage\n\n### Custom tRPC Procedure Names\n\n```typescript\n// If you need different tRPC names than function names\nconst router = createReactiveRouter({\n  db,\n})\n  .addQueryWithName(\n    getUsers,\n    \"getAllUsers\",\n  ) // Custom name\n  .addQuery(getUserProfile); // Uses function name: 'users.profile.getDetailed'\n```\n\n### Background Revalidation\n\n```typescript\n// client/hooks.ts\nfunction MyComponent() {\n  useReactivePriorities([\n    \"users.getAll\", // High priority (visible)\n    \"users.profile.getDetailed\", // Medium priority (likely next)\n  ]);\n\n  // Component content...\n}\n```\n\n## 🔧 How It Works\n\n1. **Function Definition**: `defineReactiveFunction` creates functions that work both server-side and via tRPC\n2. **Name-Based Mapping**: The `name` property becomes both the cache key and tRPC procedure name\n3. **Auto-Generated Router**: `createReactiveRouter` automatically creates tRPC procedures from functions\n4. **Smart Caching**: Server cache uses the configured provider (memory or Redis) and function dependencies to invalidate cached results. The React hook composes a key as `name::JSON(input)` internally to uniquely cache and revalidate by input.\n5. **Real-time Updates**: SSE automatically invalidates affected queries when data changes. No heartbeats are sent; reliability is ensured via client acknowledgments and retry.\n6. **Session Recovery**: Smart revalidation on page load handles offline scenarios and avoids thrashing with a minimum revalidation window.\n\n## 🤝 Contributing\n\nWe welcome contributions! Please see our [Contributing Guide](./CONTRIBUTING.md) for details.\n\n## 📄 License\n\nMIT License - see [LICENSE](./LICENSE) for details.\n\n---\n\n**Made with ❤️ by the TeamHub team**\n","readmeFilename":"README.md"}