Append-only event ledger for Convex with per-subject activity feeds, cursor pagination, type/since filters, and retention cron — no external SaaS required.
npm install @vllnt/convex-eventsEvent Ledger is an append-only per-subject activity feed and event ledger as a Convex component: `record` stamps `createdAt` and inserts; events are never mutated. Feeds are queried newest-first by opaque `subjectRef`, with type/since filters, cursor pagination via `paginate`, and bounded counts that scan at most `maxCount + 1` rows and return `{ count, isExact }`. Retention runs automatically via an idempotent daily cron pruning past `retentionMs` and self-rescheduling batched purge; tables are sandboxed, `subjectRef`/`actorRef` are opaque strings the component never inspects, and auth stays with the host.
The @vllnt/convex-events component provides a per-subject activity feed backed by an append-only ledger. Call events.record(ctx, subjectRef, type, opts) in a mutation to append an event, then call events.list or events.paginate in a query to read events back newest-first. The subjectRef is an opaque string the component never inspects, so it can be a user ID, document ID, or any host-defined identifier.
The @vllnt/convex-events component implements an append-only ledger where events are stamped with createdAt at insert time and never mutated. The events.paginate method accepts Convex's paginationOptsValidator and returns the standard { page, isDone, continueCursor } shape, making it compatible with usePaginatedQuery. You can also narrow pages to a single event type or start from a given timestamp using the type and since options.
The @vllnt/convex-events component includes a retention system configurable via events.configure(ctx, retentionMs). Once set, an idempotent daily cron prunes events older than the retention window. Large feeds are drained in self-rescheduling batches to avoid hitting Convex mutation time limits. You can also call events.pruneExpired manually for on-demand sweeps.
The Events class in @vllnt/convex-events is generic over a host-defined metadata type: new Events<Meta>(components.events, opts). You can pass an allowedTypes array, a metadataValidator, maxTypeLength, and maxMetadataBytes as client-boundary guards. Invalid input is rejected with a code-tagged EventValidationError before it reaches the database.
Install with npm install @vllnt/convex-events (or pnpm add @vllnt/convex-events). The peer dependency is convex@^1.41.0. Register the component in convex/convex.config.ts by calling app.use(events) where events is imported from @vllnt/convex-events/convex.config. Then instantiate the Events class in your host functions file with new Events(components.events) and call its methods inside your own mutations and queries.
No. The @vllnt/convex-events component is auth-agnostic by design. The host application resolves identity, enforces authorization, and passes opaque subjectRef and actorRef strings into the component's methods. The component never inspects these values, so it works with any auth system the host uses.
events.list returns a plain EventDoc array up to a configurable limit, suitable for simple feeds where you want the most recent N events. events.paginate accepts Convex's paginationOptsValidator and returns { page, isDone, continueCursor }, which is the correct shape for use with usePaginatedQuery and the useActivityFeed React hook from @vllnt/convex-events/react. Both methods support type and since filters.
The events.count method scans at most maxCount + 1 rows and returns { count, isExact }. If the actual number of events exceeds maxCount, isExact is false and count reflects the scan limit rather than the true total. This avoids full-table scans on large feeds while still giving useful cardinality information for UI badges and indicators.
Yes. The React hooks at @vllnt/convex-events/react accept the host's own re-exported query references rather than importing the component's internal API directly. You pass your api.events.paginate or api.events.count refs to useActivityFeed and useEventCount respectively. Both react and convex are optional peer dependencies, and the hooks are tree-shakeable.