// Api app config — the SaaS plugin bundle. Read by `voltro dev`. // // Four cross-cutting SaaS plugins, wired turnkey. Each contributes its own // routes + tables; the `projects.create` mutation pulls three of them together // (billing entitlement gate → analytics event → notification). // // `store: 'memory'` keeps first-run zero-infra. NOTE the analytics caveat below. import { billingPlugin } from '@voltro/plugin-billing' import { presencePlugin } from '@voltro/plugin-presence' import { notificationsPlugin, consoleChannel } from '@voltro/plugin-notifications' export default { type: 'api' as const, name: '{{capProjectName}}{{capAppName}}', store: 'memory' as const, plugins: [ // ── Billing ────────────────────────────────────────────────────────── // Subscriptions + entitlements over a pluggable provider. 'mock' is the // in-memory provider (no Stripe key) — great for dev. `plans` is the single // source of tier→quota truth; the `free` default caps `projects` at 3, so // the 4th `projects.create` fails typed `EntitlementExceeded`. Swap // provider:'stripe' (+ STRIPE_SECRET_KEY) + real priceIds for production. billingPlugin({ provider: 'mock', plans: { free: { entitlements: { projects: 3 } }, pro: { priceId: 'price_demo_pro', entitlements: { projects: 'unlimited' } }, }, }), // ── Notifications ──────────────────────────────────────────────────── // One `send` across channels + a durable in-app inbox (added automatically). // consoleChannel() prints to stdout for dev; add email/webhook/sms/push for // real delivery. Reach the inbox from the browser with useInbox()/useUnreadCount(). notificationsPlugin({ channels: [consoleChannel()] }), // ── Presence ───────────────────────────────────────────────────────── // Ephemeral "who's online" — presence.heartbeat / list / leave routes + // the usePresence(channel) hook. Swept `_voltro_presence` table; cross-instance. presencePlugin(), ], // ── Analytics ────────────────────────────────────────────────────────── // `useAnalytics().track(...)` is called in projects.create and is NOOP-SAFE // with no sink configured (it never crashes) — so the event API is reachable // on the zero-infra memory store. To actually STORE + query events, add a // sink AND a SQL store (the postgres-lite sink needs a SqlClient): // // pnpm add @voltro/plugin-analytics-postgres // import { postgresAnalytics } from '@voltro/plugin-analytics-postgres' // store: 'postgres' as const, // or 'sqlite' (file:./.voltro/db.sqlite) // analytics: postgresAnalytics(), // events → _voltro_events; aggregate/timeseries/topN }