{"_id":"@abshahin/subscriptions","_rev":"5-3f8932da40d6e0ad5ac23a1a392c1029","name":"@abshahin/subscriptions","dist-tags":{"latest":"0.2.0"},"versions":{"0.1.0":{"name":"@abshahin/subscriptions","version":"0.1.0","keywords":["subscriptions","billing","saas","multi-tenant","elysia","prisma","feature-flags","usage-limits","moyasar","typescript"],"license":"MIT","_id":"@abshahin/subscriptions@0.1.0","maintainers":[{"name":"abshahin","email":"abdelrahmanshaheeen8@gmail.com"}],"homepage":"https://github.com/aashahin/subscriptions-sdk#readme","bugs":{"url":"https://github.com/aashahin/subscriptions-sdk/issues"},"dist":{"shasum":"f4f3c1c55d6921cc79e120f1279d5346989ed136","tarball":"https://registry.npmjs.org/@abshahin/subscriptions/-/subscriptions-0.1.0.tgz","fileCount":24,"integrity":"sha512-n8UdFR9Q2fJb9USRfWGUUDIq1aTA4Fy+noNpi5RtaMwj42ZFaLB/vvK1iA17qlMrxfY4oB/Sm30pyGczYzTEyQ==","signatures":[{"sig":"MEUCIFuL0OOZc5cuIs5M/eBl8pYi0B5va/aKXowf6Z/cFsqIAiEA+yxh/KO3/TEtfWBzgUjsS90sx1rttHWZUxkZgdqJeCA=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":248320},"type":"module","exports":{".":"./src/index.ts","./elysia":"./src/integrations/elysia.ts","./adapters/cache":"./src/adapters/cache.adapter.ts","./adapters/prisma":"./src/adapters/prisma.adapter.ts","./adapters/moyasar":"./src/adapters/moyasar.adapter.ts"},"gitHead":"bc109df4c3f282b59479f500e123366357580088","scripts":{"typecheck":"tsc --noEmit -p tsconfig.json"},"_npmUser":{"name":"abshahin","email":"abdelrahmanshaheeen8@gmail.com"},"repository":{"url":"git+https://github.com/aashahin/subscriptions-sdk.git","type":"git"},"_npmVersion":"10.9.3","description":"Type-safe subscription plans, feature gates, usage limits, invoices, and Elysia integration for multi-tenant TypeScript applications.","directories":{},"sideEffects":false,"_nodeVersion":"22.20.0","dependencies":{"decimal.js":"^10.6.0","handlebars":"^4.7.8","@sinclair/typebox":"^0.34.48","puppeteer-html-pdf":"^4.0.8"},"_hasShrinkwrap":false,"devDependencies":{"bun-types":"^1.3.8","typescript":"^5.7.0"},"peerDependencies":{"elysia":"^1.4.22","@prisma/client":"^7.3.0"},"peerDependenciesMeta":{"elysia":{"optional":true},"@prisma/client":{"optional":true}},"_npmOperationalInternal":{"tmp":"tmp/subscriptions_0.1.0_1773899124832_0.12914922093125702","host":"s3://npm-registry-packages-npm-production"}},"0.1.1":{"name":"@abshahin/subscriptions","version":"0.1.1","keywords":["subscriptions","billing","saas","multi-tenant","elysia","prisma","feature-flags","usage-limits","moyasar","typescript"],"license":"MIT","_id":"@abshahin/subscriptions@0.1.1","maintainers":[{"name":"abshahin","email":"abdelrahmanshaheeen8@gmail.com"}],"homepage":"https://github.com/aashahin/subscriptions-sdk#readme","bugs":{"url":"https://github.com/aashahin/subscriptions-sdk/issues"},"dist":{"shasum":"b7b5ad3875555ef5cbdd95eaa621b453489f4bb1","tarball":"https://registry.npmjs.org/@abshahin/subscriptions/-/subscriptions-0.1.1.tgz","fileCount":24,"integrity":"sha512-BLQPvIep2zoOJAm28ClBbiL1vNFK7OqKMb55tZAGlCBpKqPwZ3vM1WP/epWnkgyYNVvale2ddhJpnXuyaib9Hw==","signatures":[{"sig":"MEUCIQDt6oHbK7ul+WnItNxlKn95u223sUlWp0qsOGTijk83HgIgKfF04zGXYAYUp+AVt0wnfQ4rKdXcH4kQDpMs57y4myk=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":250541},"type":"module","exports":{".":"./src/index.ts","./elysia":"./src/integrations/elysia.ts","./adapters/cache":"./src/adapters/cache.adapter.ts","./adapters/prisma":"./src/adapters/prisma.adapter.ts","./adapters/moyasar":"./src/adapters/moyasar.adapter.ts"},"gitHead":"bc109df4c3f282b59479f500e123366357580088","scripts":{"typecheck":"tsc --noEmit -p tsconfig.json"},"_npmUser":{"name":"abshahin","email":"abdelrahmanshaheeen8@gmail.com"},"repository":{"url":"git+https://github.com/aashahin/subscriptions-sdk.git","type":"git"},"_npmVersion":"10.9.3","description":"Type-safe subscription plans, feature gates, usage limits, invoices, and Elysia integration for multi-tenant TypeScript applications.","directories":{},"sideEffects":false,"_nodeVersion":"22.20.0","dependencies":{"decimal.js":"^10.6.0","handlebars":"^4.7.8","@sinclair/typebox":"^0.34.48","puppeteer-html-pdf":"^4.0.8"},"_hasShrinkwrap":false,"devDependencies":{"bun-types":"^1.3.8","typescript":"^5.7.0"},"peerDependencies":{"elysia":"^1.4.22","@prisma/client":"^7.3.0"},"peerDependenciesMeta":{"elysia":{"optional":true},"@prisma/client":{"optional":true}},"_npmOperationalInternal":{"tmp":"tmp/subscriptions_0.1.1_1774682025207_0.5796998792392529","host":"s3://npm-registry-packages-npm-production"}},"0.1.2":{"name":"@abshahin/subscriptions","version":"0.1.2","keywords":["subscriptions","billing","saas","multi-tenant","elysia","prisma","feature-flags","usage-limits","moyasar","typescript"],"license":"MIT","_id":"@abshahin/subscriptions@0.1.2","maintainers":[{"name":"abshahin","email":"abdelrahmanshaheeen8@gmail.com"}],"homepage":"https://github.com/aashahin/subscriptions-sdk#readme","bugs":{"url":"https://github.com/aashahin/subscriptions-sdk/issues"},"dist":{"shasum":"042d659bcb8aa87f64b165e8679193199ab88e4c","tarball":"https://registry.npmjs.org/@abshahin/subscriptions/-/subscriptions-0.1.2.tgz","fileCount":70,"integrity":"sha512-S5Ox8pS/WWh6ZLzkjsTXRKqkje4j85s9F7lUIePt4dRmGrgq44/IJykgg7WviZuLYWhWn/k7K8ZAjrVNl5Pprg==","signatures":[{"sig":"MEUCIFY51UzvbPVwDB0WREwFxUlimxIqV35tUF4TlEn5bP/bAiEAihIRnLh8fLmrtHeGg15m18U63oY+w0iKnDXA64OjRIQ=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":567985},"main":"./dist/index.js","type":"module","types":"./dist/index.d.ts","exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js"},"./elysia":{"types":"./dist/integrations/elysia.d.ts","import":"./dist/integrations/elysia.js"},"./adapters/cache":{"types":"./dist/adapters/cache.adapter.d.ts","import":"./dist/adapters/cache.adapter.js"},"./adapters/prisma":{"types":"./dist/adapters/prisma.adapter.d.ts","import":"./dist/adapters/prisma.adapter.js"},"./adapters/moyasar":{"types":"./dist/adapters/moyasar.adapter.d.ts","import":"./dist/adapters/moyasar.adapter.js"}},"gitHead":"be486cfaca3901b0bad372b7c47ed24ad6aba61a","scripts":{"build":"tsc -p tsconfig.build.json && mkdir -p dist/templates && cp src/templates/subscription-invoice.hbs dist/templates/subscription-invoice.hbs","prepare":"bun run build","typecheck":"tsc --noEmit -p tsconfig.json"},"_npmUser":{"name":"abshahin","email":"abdelrahmanshaheeen8@gmail.com"},"repository":{"url":"git+https://github.com/aashahin/subscriptions-sdk.git","type":"git"},"_npmVersion":"10.9.3","description":"Type-safe subscription plans, feature gates, usage limits, invoices, and Elysia integration for multi-tenant TypeScript applications.","directories":{},"sideEffects":false,"_nodeVersion":"22.20.0","dependencies":{"handlebars":"^4.7.8","@sinclair/typebox":"^0.34.48"},"_hasShrinkwrap":false,"devDependencies":{"elysia":"^1.4.22","typescript":"^5.7.0","@types/node":"^22.0.0"},"peerDependencies":{"elysia":"^1.4.22","@prisma/client":"^7.3.0","puppeteer-html-pdf":"^4.0.8"},"peerDependenciesMeta":{"elysia":{"optional":true},"@prisma/client":{"optional":true},"puppeteer-html-pdf":{"optional":true}},"_npmOperationalInternal":{"tmp":"tmp/subscriptions_0.1.2_1776757680085_0.8191618053132514","host":"s3://npm-registry-packages-npm-production"}},"0.1.3":{"name":"@abshahin/subscriptions","version":"0.1.3","keywords":["subscriptions","billing","saas","multi-tenant","elysia","prisma","feature-flags","usage-limits","moyasar","typescript"],"license":"MIT","_id":"@abshahin/subscriptions@0.1.3","maintainers":[{"name":"abshahin","email":"abdelrahmanshaheeen8@gmail.com"}],"homepage":"https://github.com/aashahin/subscriptions-sdk#readme","bugs":{"url":"https://github.com/aashahin/subscriptions-sdk/issues"},"dist":{"shasum":"2740c7c647ba468f2b5af79d1c21efee10df104e","tarball":"https://registry.npmjs.org/@abshahin/subscriptions/-/subscriptions-0.1.3.tgz","fileCount":70,"integrity":"sha512-N0E/uj0qhQgUM9dUaCVlvS/jAi9zuKB1dyXEO6/zmdZ4zbZWnYxiH983QezI2Ri591EM3Fn0uIHSJRMqrsND/g==","signatures":[{"sig":"MEUCIQCsQL2Z0JYMMTK9dlXQj79m7xCIGA+5GLLdBDrUA4Q8kQIgD/9NjMSeLHFdIMS3UrETnJEDP4k7Ocu8vjjkDPl9EAE=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":571470},"main":"./dist/index.js","type":"module","types":"./dist/index.d.ts","exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js"},"./elysia":{"types":"./dist/integrations/elysia.d.ts","import":"./dist/integrations/elysia.js"},"./adapters/cache":{"types":"./dist/adapters/cache.adapter.d.ts","import":"./dist/adapters/cache.adapter.js"},"./adapters/prisma":{"types":"./dist/adapters/prisma.adapter.d.ts","import":"./dist/adapters/prisma.adapter.js"},"./adapters/moyasar":{"types":"./dist/adapters/moyasar.adapter.d.ts","import":"./dist/adapters/moyasar.adapter.js"}},"gitHead":"87d5de317231acb4f93ffa3b4c632eddc4aba533","scripts":{"build":"tsc -p tsconfig.build.json && mkdir -p dist/templates && cp src/templates/subscription-invoice.hbs dist/templates/subscription-invoice.hbs","prepare":"bun run build","typecheck":"tsc --noEmit -p tsconfig.json","test:invoices":"bun tests/generate-test-invoices.ts"},"_npmUser":{"name":"abshahin","email":"abdelrahmanshaheeen8@gmail.com"},"repository":{"url":"git+https://github.com/aashahin/subscriptions-sdk.git","type":"git"},"_npmVersion":"11.12.1","description":"Type-safe subscription plans, feature gates, usage limits, invoices, and Elysia integration for multi-tenant TypeScript applications.","directories":{},"sideEffects":false,"_nodeVersion":"25.9.0","dependencies":{"handlebars":"^4.7.8","@sinclair/typebox":"^0.34.48"},"_hasShrinkwrap":false,"devDependencies":{"elysia":"^1.4.22","typescript":"^5.7.0","@types/node":"^22.0.0"},"peerDependencies":{"elysia":"^1.4.22","@prisma/client":"^7.3.0","puppeteer-html-pdf":"^4.0.8"},"peerDependenciesMeta":{"elysia":{"optional":true},"@prisma/client":{"optional":true},"puppeteer-html-pdf":{"optional":true}},"_npmOperationalInternal":{"tmp":"tmp/subscriptions_0.1.3_1778049607431_0.3626903788994402","host":"s3://npm-registry-packages-npm-production"}},"0.2.0":{"name":"@abshahin/subscriptions","version":"0.2.0","description":"Type-safe subscription plans, feature gates, usage limits, invoices, and Elysia integration for multi-tenant TypeScript applications.","license":"MIT","type":"module","main":"./dist/index.js","types":"./dist/index.d.ts","sideEffects":false,"keywords":["subscriptions","billing","saas","multi-tenant","elysia","prisma","feature-flags","usage-limits","moyasar","typescript"],"repository":{"type":"git","url":"git+https://github.com/aashahin/subscriptions-sdk.git"},"homepage":"https://github.com/aashahin/subscriptions-sdk#readme","exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js"},"./elysia":{"types":"./dist/integrations/elysia.d.ts","import":"./dist/integrations/elysia.js"},"./adapters/prisma":{"types":"./dist/adapters/prisma.adapter.d.ts","import":"./dist/adapters/prisma.adapter.js"},"./adapters/moyasar":{"types":"./dist/adapters/moyasar.adapter.d.ts","import":"./dist/adapters/moyasar.adapter.js"},"./adapters/stripe":{"types":"./dist/adapters/stripe.adapter.d.ts","import":"./dist/adapters/stripe.adapter.js"},"./adapters/paddle":{"types":"./dist/adapters/paddle.adapter.d.ts","import":"./dist/adapters/paddle.adapter.js"},"./adapters/lemonsqueezy":{"types":"./dist/adapters/lemonsqueezy.adapter.d.ts","import":"./dist/adapters/lemonsqueezy.adapter.js"},"./adapters/cache":{"types":"./dist/adapters/cache.adapter.d.ts","import":"./dist/adapters/cache.adapter.js"},"./adapters/redis":{"types":"./dist/adapters/redis.adapter.d.ts","import":"./dist/adapters/redis.adapter.js"},"./adapters/upstash":{"types":"./dist/adapters/upstash.adapter.d.ts","import":"./dist/adapters/upstash.adapter.js"},"./adapters/cloudflare-kv":{"types":"./dist/adapters/cloudflare-kv.adapter.d.ts","import":"./dist/adapters/cloudflare-kv.adapter.js"},"./adapters/drizzle":{"types":"./dist/adapters/drizzle.adapter.d.ts","import":"./dist/adapters/drizzle.adapter.js"},"./integrations/http":{"types":"./dist/integrations/http.d.ts","import":"./dist/integrations/http.js"},"./integrations/hono":{"types":"./dist/integrations/hono.d.ts","import":"./dist/integrations/hono.js"},"./integrations/next":{"types":"./dist/integrations/next.d.ts","import":"./dist/integrations/next.js"},"./pdf/puppeteer":{"types":"./dist/pdf/puppeteer.d.ts","import":"./dist/pdf/puppeteer.js"},"./pdf/cloudflare":{"types":"./dist/pdf/cloudflare.d.ts","import":"./dist/pdf/cloudflare.js"},"./templates/invoice-utils":{"types":"./dist/templates/invoice-utils.d.ts","import":"./dist/templates/invoice-utils.js"},"./testing":{"types":"./dist/testing/index.d.ts","import":"./dist/testing/index.js"},"./audit":{"types":"./dist/audit/index.d.ts","import":"./dist/audit/index.js"},"./core/events":{"types":"./dist/core/events.d.ts","import":"./dist/core/events.js"}},"peerDependencies":{"elysia":"^1.4.22","@prisma/client":"^7.8.0","puppeteer-html-pdf":"^4.0.8","ioredis":"^5","drizzle-orm":"^0.44.0","@cloudflare/puppeteer":"^1","hono":"^4"},"peerDependenciesMeta":{"elysia":{"optional":true},"@prisma/client":{"optional":true},"puppeteer-html-pdf":{"optional":true},"ioredis":{"optional":true},"drizzle-orm":{"optional":true},"@cloudflare/puppeteer":{"optional":true},"hono":{"optional":true}},"dependencies":{"@sinclair/typebox":"^0.34.52","handlebars":"^4.7.9"},"devDependencies":{"@arethetypeswrong/cli":"^0.18.5","@types/node":"^22.20.1","drizzle-orm":"^0.44.0","elysia":"^1.4.29","esbuild":"^0.28.1","publint":"^0.3.22","tsdown":"^0.22.14","typescript":"^5.9.3"},"scripts":{"build":"tsdown","prepare":"bun run build","test":"bun test","test:invoices":"bun tests/generate-test-invoices.ts","typecheck":"tsc --noEmit -p tsconfig.json","check:package":"publint && attw --pack . --ignore-rules no-resolution --ignore-rules cjs-resolves-to-esm"},"gitHead":"dc9f3f75fe704845306e5d725a938b55b9679224","_id":"@abshahin/subscriptions@0.2.0","bugs":{"url":"https://github.com/aashahin/subscriptions-sdk/issues"},"_nodeVersion":"25.9.0","_npmVersion":"11.12.1","dist":{"integrity":"sha512-L7WYxykYi6kJpx9svEI8gtVrxZrM/4tCDAmCvzRZUCbLj1G7U1T6VPtBckQ8ioRluVaCOzpW38mfgweG1YyP6w==","shasum":"a64232da27ac84cb1a0bc81170d3248c0fa2eaaf","tarball":"https://registry.npmjs.org/@abshahin/subscriptions/-/subscriptions-0.2.0.tgz","fileCount":113,"unpackedSize":1563683,"signatures":[{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEUCIQCgoQZ+l0Yxpb1jW3kQ52r9c9enhDwcwCjIjf5ijfZdRgIgcMr2St+jf3VoeYiVhItCLp6iGlNwY05lTBJjLkVbbZg="}]},"_npmUser":{"name":"abshahin","email":"abdelrahmanshaheeen8@gmail.com"},"directories":{},"maintainers":[{"name":"abshahin","email":"abdelrahmanshaheeen8@gmail.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/subscriptions_0.2.0_1784912481187_0.8392914461548091"},"_hasShrinkwrap":false}},"time":{"created":"2026-03-19T05:45:24.776Z","modified":"2026-07-24T17:01:21.485Z","0.1.0":"2026-03-19T05:45:24.976Z","0.1.1":"2026-03-28T07:13:45.349Z","0.1.2":"2026-04-21T07:48:00.213Z","0.1.3":"2026-05-06T06:40:07.580Z","0.2.0":"2026-07-24T17:01:21.333Z"},"bugs":{"url":"https://github.com/aashahin/subscriptions-sdk/issues"},"license":"MIT","homepage":"https://github.com/aashahin/subscriptions-sdk#readme","keywords":["subscriptions","billing","saas","multi-tenant","elysia","prisma","feature-flags","usage-limits","moyasar","typescript"],"repository":{"type":"git","url":"git+https://github.com/aashahin/subscriptions-sdk.git"},"description":"Type-safe subscription plans, feature gates, usage limits, invoices, and Elysia integration for multi-tenant TypeScript applications.","maintainers":[{"name":"abshahin","email":"abdelrahmanshaheeen8@gmail.com"}],"readme":"# @abshahin/subscriptions\n\nType-safe subscription plans, feature gates, usage limits, invoices, and Elysia integration for TypeScript applications.\n\nThe package ships with:\n\n- a Prisma database adapter\n- an optional cache adapter interface\n- optional Moyasar, Stripe, Paddle, and Lemon Squeezy payment adapters\n- an optional Elysia integration with routes and controller macros\n\nThe service layer is runtime-neutral and uses web-standard primitives for binary payloads and crypto-friendly flows. The current production integration uses tenant-scoped subscriptions, but the core package still models the subscribed entity as a generic subscriber.\n\n## What It Solves\n\n- Define typed subscription features once\n- Store plan overrides as JSON while keeping feature access type-safe\n- Enforce boolean feature access and numeric usage limits\n- Manage subscription lifecycle: create, change plan, cancel, pause, resume, reactivate, renew\n- Verify payment webhooks without coupling the core services to one provider\n- Generate invoice HTML and, in Node.js environments, invoice PDFs\n\n## Features\n\n- **Runtime-agnostic core**: runs on Node.js, Bun, Deno, and Cloudflare Workers using web-standard primitives\n- **Database adapters**: Prisma and Drizzle (including D1, Turso, and `bun:sqlite`)\n- **Cache adapters**: Redis, Upstash, Cloudflare KV, and in-memory — all optional, with a noop fallback\n- **Framework integrations**: Elysia, Hono, Next.js, and a framework-neutral fetch handler\n- **Billing**: sequential invoice numbering, per-line-item inclusive/exclusive tax, credit notes, multi-currency price points, metered usage, and per-seat/add-on data fields — see `docs/billing.md`\n- **Coupons**: percent/fixed discounts with once/repeating/forever durations, redemption caps, and expiry — see `docs/coupons.md`\n- **Dunning**: configurable retry schedules with pause/cancel final actions — see `docs/dunning.md`\n- **Events**: typed lifecycle events via `withEvents`, with an outbox adapter for guaranteed delivery — see `docs/events.md`\n- **Audit logging**: pluggable audit trail via `createAuditLogger` — see `docs/audit.md`\n- **Testing utilities**: in-memory adapters, a fake payment gateway, and adapter conformance suites from the `./testing` export — see `docs/testing.md`\n\n## Runtime Support\n\n- Core services and payment interfaces are runtime-neutral and accept webhook payloads as `string | Uint8Array`\n- Existing Node.js callers can still pass `Buffer`, because `Buffer` extends `Uint8Array`\n- Invoice HTML can be rendered anywhere by passing a template string (`templateSource`); filesystem template paths remain available on Node.js/Bun/Deno\n- Invoice PDF generation via `puppeteer-html-pdf` is Node.js-only; on Cloudflare Workers use `@cloudflare/puppeteer` with a Browser Rendering binding\n- See `docs/runtime-support.md` for the full support matrix and per-runtime setup recipes\n\n## Installation\n\n```bash\nbun add @abshahin/subscriptions\n```\n\nOptional peer dependencies used by common integrations:\n\n```bash\nbun add elysia @prisma/client\n```\n\nOptional peer dependency for Node.js invoice PDF generation:\n\n```bash\nbun add puppeteer-html-pdf\n```\n\nIf you only use the service layer, Prisma adapter, or webhook handling, you do not need the PDF dependency.\n\n## Quick Start\n\n### 1. Define Features\n\n```ts\nimport { defineFeatures } from \"@abshahin/subscriptions\";\n\nexport const features = defineFeatures({\n  analyticsEnabled: {\n    type: \"boolean\",\n    default: true,\n    description: \"Visitor analytics and reporting\",\n  },\n  customDomain: {\n    type: \"boolean\",\n    default: true,\n    description: \"Connect a custom domain\",\n  },\n  maxCourses: {\n    type: \"limit\",\n    default: -1,\n    description: \"Maximum number of courses\",\n  },\n  maxProducts: {\n    type: \"limit\",\n    default: -1,\n    description: \"Maximum number of products\",\n  },\n  transactionFee: {\n    type: \"rate\",\n    default: 5,\n    description: \"Platform transaction fee percentage\",\n  },\n});\n\nexport type AppFeatures = typeof features;\n```\n\n### 2. Create a Subscriptions Instance\n\n```ts\nimport { createSubscriptions } from \"@abshahin/subscriptions\";\nimport type { CacheAdapter } from \"@abshahin/subscriptions/adapters/cache\";\nimport { prismaAdapter } from \"@abshahin/subscriptions/adapters/prisma\";\nimport { db } from \"./db\";\nimport { features } from \"./features\";\n\nconst cacheAdapter: CacheAdapter = {\n  async get(key) {\n    return redis.get(key);\n  },\n  async set(key, value, ttlSeconds) {\n    await redis.set(key, value, { ttl: ttlSeconds });\n  },\n  async delete(key) {\n    await redis.del(key);\n  },\n  async deletePattern(pattern) {\n    await redis.deleteByPattern(pattern);\n  },\n};\n\nexport const subscriptions = createSubscriptions({\n  database: prismaAdapter(db),\n  features,\n  cache: cacheAdapter,\n  options: {\n    subscriberType: \"tenant\",\n    trialDays: 14,\n    gracePeriodDays: 3,\n    defaultCurrency: \"USD\",\n    cacheTtlSeconds: 300,\n  },\n});\n```\n\n### 3. Use the Service Layer\n\n```ts\nconst tenantId = \"tenant_123\";\n\nif (await subscriptions.can(tenantId, \"analyticsEnabled\")) {\n  console.log(\"analytics enabled\");\n}\n\nconst usage = await subscriptions.remaining(tenantId, \"maxProducts\");\nconsole.log(usage.remaining);\n\nawait subscriptions.use(tenantId, \"maxProducts\");\nawait subscriptions.release(tenantId, \"maxProducts\");\n\nconst fee = await subscriptions.permissions.getRate(\n  tenantId,\n  \"transactionFee\",\n);\n```\n\n## Core Model\n\n### Feature Types\n\n`defineFeatures` supports four feature kinds:\n\n- `boolean`: enable or disable a capability\n- `limit`: numeric usage caps, with `-1` meaning unlimited\n- `rate`: numeric values such as fees or delays\n- `metered`: tracked usage that never blocks — `remaining` may go negative so you can bill the overage\n\nPlan records only store overrides. Any omitted feature falls back to the default declared in `defineFeatures`.\n\n### Subscriber Model\n\nThe package refers to the subscribed entity as a subscriber. That can be either:\n\n- a tenant, when a whole workspace or organization shares a subscription\n- a user, when each user owns their own subscription\n\n`options.subscriberType` sets the default type for newly created subscriptions. The current Prisma adapter persists subscriber IDs through the `tenantId` column, so tenant-based usage is the most mature path and the one used in the backend project.\n\n## Service API\n\n### Plans\n\n```ts\nconst plan = await subscriptions.plans.create({\n  name: \"Pro\",\n  description: \"For growing teams\",\n  price: 49,\n  currency: \"USD\",\n  interval: \"monthly\",\n  trialDays: 14,\n  features: {\n    customDomain: true,\n    maxProducts: 1000,\n    transactionFee: 2.5,\n  },\n});\n\nconst plans = await subscriptions.plans.list({ activeOnly: true });\nconst current = await subscriptions.plans.get(plan.id);\nconst duplicated = await subscriptions.plans.duplicate(plan.id, {\n  name: \"Pro Annual\",\n  interval: \"yearly\",\n});\n```\n\n### Subscriptions\n\n```ts\nconst subscription = await subscriptions.subscriptions.create(\n  tenantId,\n  plan.id,\n  {\n    trialDays: 14,\n    gatewayCustomerId: \"token_or_customer_id\",\n  },\n);\n\nawait subscriptions.subscriptions.changePlan(tenantId, \"plan_enterprise\", {\n  prorate: true,\n  verifiedTokenId: \"verified_token_id\",\n});\n\nawait subscriptions.subscriptions.cancel(tenantId, { immediately: false });\nawait subscriptions.subscriptions.resume(tenantId);\nawait subscriptions.subscriptions.reactivate(tenantId);\nawait subscriptions.subscriptions.renew(tenantId);\n```\n\nUseful helpers:\n\n- `get(subscriberId)`\n- `previewChangePlan(subscriberId, newPlanId)`\n- `pause(subscriberId)`\n- `resume(subscriberId)`\n- `reactivate(subscriberId)`\n- `startTrial(subscriberId, planId, days)`\n- `extendTrial(subscriberId, days)`\n- `isActive(subscriberId)`\n- `isTrialing(subscriberId)`\n- `daysRemaining(subscriberId)`\n\n### Permissions and Usage\n\n```ts\nawait subscriptions.permissions.assertCan(tenantId, \"customDomain\");\nawait subscriptions.permissions.assertCanUse(tenantId, \"maxProducts\", 5);\n\nconst allFeatures = await subscriptions.permissions.getFeatures(tenantId);\nconst allUsage = await subscriptions.permissions.getAllUsage(tenantId);\n\nawait subscriptions.permissions.setUsage(tenantId, \"maxProducts\", 42);\nawait subscriptions.permissions.resetUsage(tenantId, \"maxProducts\");\n```\n\n### Webhooks\n\nWebhook handlers accept raw payloads as `string | Uint8Array`.\n\n```ts\nconst event = await subscriptions.handleWebhook(\n  \"moyasar\",\n  rawBody,\n  signature,\n);\n```\n\nThis works in Node.js, Bun, and edge-style runtimes as long as you preserve the raw request body.\n\n### Invoices\n\n```ts\nconst invoice = await subscriptions.invoices.create({\n  subscriptionId: subscription.id,\n  amount: 49,\n  currency: \"USD\",\n  status: \"paid\",\n  gatewayInvoiceId: \"pay_123\",\n  lineItems: [\n    {\n      description: \"Pro monthly subscription\",\n      quantity: 1,\n      unitPrice: 49,\n      amount: 49,\n    },\n  ],\n});\n\nconst detailed = await subscriptions.invoices.getWithDetails(invoice.id);\n```\n\nInvoice HTML rendering and PDF generation are exported from the package root. PDF generation is intended for Node.js environments.\n\n## Elysia Integration\n\nThe package exports `elysiaPlugin` from `@abshahin/subscriptions/elysia`.\n\n```ts\nimport { Elysia } from \"elysia\";\nimport { elysiaPlugin } from \"@abshahin/subscriptions/elysia\";\nimport { subscriptions } from \"./subscriptions\";\n\nconst app = new Elysia().use(\n  elysiaPlugin(subscriptions, {\n    prefix: \"/subscriptions\",\n    getSubscriberId: (ctx) => ctx.user.activeTenantId,\n    adminRoutes: true,\n    adminGuard: (ctx) => ctx.user.role === \"admin\",\n    invoice: {\n      platform: {\n        name: \"Manhali\",\n        website: \"https://example.com\",\n        supportEmail: \"support@example.com\",\n      },\n      locale: \"ar-EG\",\n      getSubscriberInfo: async (subscriberId) => ({\n        name: `Tenant ${subscriberId}`,\n      }),\n    },\n  }),\n);\n```\n\nBuilt-in routes include:\n\n- `GET /current`\n- `GET /plans`\n- `POST /subscribe`\n- `POST /create`\n- `POST /change-plan`\n- `GET /change-plan/preview/:planId`\n- `POST /cancel`\n- `POST /resume`\n- `POST /reactivate`\n- `GET /features`\n- `GET /usage`\n- `GET /usage/:feature`\n- `GET /can/:feature`\n- `GET /invoices`\n- `GET /invoices/:id/download`\n- `POST /webhooks/:provider`\n\nIt also adds route macros for controller-level enforcement:\n\n```ts\napp.get(\"/analytics\", handler, {\n  requireFeature: \"analyticsEnabled\",\n});\n\napp.post(\"/products\", handler, {\n  requireUsage: { feature: \"maxProducts\", count: 1 },\n});\n\napp.post(\"/products\", handler, {\n  useFeature: \"maxProducts\",\n});\n```\n\nIf you enable invoice downloads through the Elysia plugin, run that endpoint on Node.js and install `puppeteer-html-pdf`.\n\n## Payments\n\nPayments are optional. If no payment adapter is configured, the package still supports manual subscription management.\n\nFor Moyasar:\n\n```ts\nimport { moyasarAdapter } from \"@abshahin/subscriptions/adapters/moyasar\";\n\nconst payment = moyasarAdapter({\n  secretKey: process.env.MOYASAR_SECRET_KEY!,\n  publishableKey: process.env.MOYASAR_PUBLIC_KEY!,\n  webhookSecret: process.env.MOYASAR_WEBHOOK_SECRET,\n  callbackUrl: \"https://app.example.com/subscription\",\n});\n```\n\nStripe (`@abshahin/subscriptions/adapters/stripe`), Paddle (`@abshahin/subscriptions/adapters/paddle`), and Lemon Squeezy (`@abshahin/subscriptions/adapters/lemonsqueezy`) adapters ship as well — all dependency-free (`fetch` + Web Crypto). See `docs/adapters.md`.\n\nThe backend project currently uses direct payment charges plus saved token IDs for renewals and plan upgrades. That pattern is covered in the integration guide.\n\n## Prisma Schema Requirements\n\nThe package expects four core models:\n\n- `SubscriptionPlan`\n- `Subscription`\n- `Invoice`\n- `UsageRecord`\n\nSee `docs/prisma-schema.md` for a schema example based on the backend project.\n\n## What Stays Outside This Package\n\nThe backend project uses this package as the subscription source of truth, but keeps a few concerns in app code:\n\n- Redis-backed hot-path usage counters\n- cron-based renewal orchestration\n- tenant-aware cache invalidation across the broader app\n- payment verification callbacks specific to the frontend flow\n\nThat split is intentional. This package owns subscription state and policy. Your application can add faster counters, schedulers, and dashboards around it.\n\n## Type Safety\n\nThe package provides full type inference for features:\n\n```ts\nconst features = defineFeatures({\n  analytics: { type: \"boolean\", default: false },\n  maxProducts: { type: \"limit\", default: 100 },\n});\n\nawait subs.can(tenantId, \"analytics\");\nawait subs.permissions.getFeatures(tenantId);\n```\n\n## Documentation\n\n- `CHANGELOG.md`\n- `docs/README.md`\n- `docs/runtime-support.md`\n- `docs/adapters.md`\n- `docs/billing.md`\n- `docs/coupons.md`\n- `docs/dunning.md`\n- `docs/events.md`\n- `docs/audit.md`\n- `docs/testing.md`\n- `docs/error-handling.md`\n- `docs/integration-guide.md`\n- `docs/prisma-schema.md`\n\n## License\n\nMIT\n","readmeFilename":"README.md"}