Track per-subject usage allowances with calendar, rolling, or epoch reset windows in Convex, including timezone-aware daily/weekly/monthly caps.
npm install @vllnt/convex-quota@vllnt/convex-quota tracks how much allowance a subject has used, how much remains, and when its window resets. Your backend defines the subject, unit, limit, and calendar policy rather than adopting a fixed billing or game model.
Use cases
- AI tools: show a remaining allowance and reset time, a product pattern familiar from Codex-style usage screens. Your app defines the actual charging rules.
- SaaS workspaces: cap exports, generations, or processing operations per period.
- Mobile and learning apps: limit daily practice attempts or weekly feature access.
- Games: enforce play allowances that reset in the player’s configured timezone.
- Support adjustments: refund usage after a failed operation; your host must authorize and deduplicate the adjustment.
How it works
Use Quota.consume to check and consume allowance together, and remaining to read used, remaining, and resetsAt. Choose calendar day/week/month windows with an IANA timezone, rolling windows from first allowed use, or aligned epoch windows. Mounts isolate tables and scopes separate namespaces.
Integration
The host owns auth, trusted amounts, retry deduplication, and any purchased or banked reset-credit system. This is not a Codex integration. Keep window policy stable per key; time passing alone does not refresh subscriptions. Prefer the official rate-limiter for general request throttling.
Maintained by [bntvllnt](https://bntvllnt.com) at [VLLNT](https://vllnt.com). Find the author on [GitHub](https://github.com/bntvllnt) and [X](https://x.com/bntvllnt).
Use @vllnt/convex-quota and call quota.consume(ctx, subjectRef, key, limit, { kind: 'calendar', period: 'day', timeZone: 'America/New_York' }) inside a Convex mutation. The component tracks used and remaining amounts and returns resetsAt so you can display the next reset time. Window math runs server-side, so client clock skew cannot affect the period.
Call quota.remaining(ctx, subjectRef, key, limit, window) from a Convex query to get { used, remaining, limit, periodKey, resetsAt }. Expose this through your own host query to the frontend. Note that time passing alone does not invalidate a Convex subscription, so treat resetsAt as an expiry hint and recheck server time on each consume.
Pass a workspace identifier as the subjectRef and a feature name as the key to quota.consume, with { kind: 'calendar', period: 'month', timeZone } as the window policy. Use the scope parameter to namespace multiple products within the same mount. Keep the window policy fixed for each (scope, subjectRef, key) triple; changing policy for an existing key throws POLICY_MISMATCH.
Call quota.refund(ctx, subjectRef, key, amount, periodKey, scope) inside a privileged Convex mutation after confirming the failure. The refund mutation is not idempotent, so the host must deduplicate refund events in its own transaction using periodKey as a window label. Auth and deduplication are the host's responsibility, not the component's.
@convex-dev/rate-limiter covers general request throttling with fixed windows, token buckets, readable status, and React hooks. @vllnt/convex-quota focuses on the narrower use case of calendar-aligned, timezone-aware reset windows and a privileged refund surface for adjusting allowances after failed operations. The README explicitly recommends using the official rate-limiter for general throttling.
@vllnt/convex-quota supports three window kinds: calendar windows (day, week, or month aligned to an IANA timezone), rolling windows that start from the first allowed consume, and epoch windows computed as floor(now / durationMs). The window policy for a given (scope, subjectRef, key) combination must remain stable; changing it throws a POLICY_MISMATCH error.
No. quota.consume is not idempotent. If your mutation retries due to Convex OCC conflicts, each successful execution will consume allowance. The README recommends wrapping retried operations with @vllnt/convex-idempotency to avoid double-counting. Never pass limit or amount values from the end-user; the host must supply them.
@vllnt/convex-quota supports two isolation mechanisms. Multiple mounts (app.use(quota, { name: 'webQuota' })) give each mount its own sandboxed database tables. Within a single mount, the scope parameter namespaces records at runtime, which is useful for separating tenants or product lines without separate deployments.
@vllnt/convex-quota is auth-agnostic. The host resolves identity and passes an opaque subjectRef string to the component. The component does not implement billing metering, persistent balances, or purchased reset credits. Any auth checks, purchased allowance systems, or audit logging must be implemented in the host application.