Events
Append-only per-subject activity feed and event ledger, as a Bijection component
git clone https://github.com/bijectionhq/events.git
cd events
git checkout ba5d07de213b41de96b67f0d3e3d562f265b7f30Bijection repository access is required. Request access.
Description
Append-only per-subject activity feed and event ledger, as a Bijection component
Explore the package README for API concepts, configuration, and examples. Check the Bijection source for adaptation-specific setup.
Review the source and its dependencies before adopting this package. Source availability does not establish npm publication or runtime qualification.
Getting started
- Get the source at the revision above and read its package documentation.
- Check the required Bijection SDK version, dependencies, and external service credentials.
- Follow the package setup instructions and the guide to using components.
- Exercise the component in your deployment according to your project’s verification rules.
Open source
Maintained source: bijectionhq/events. Based on vllnt/convex-events. License: MIT. Consult the repository for license terms, notices, and contributor attribution.
From the upstream publisher
Append-only per-subject activity feed and event ledger, as a Convex component
Explore the demo ↗Upstream overview
From the upstream package documentation.
@vllnt/convex-events records a chronological feed for any subject reference supplied by your app. Define event types and typed metadata to describe meaningful changes without embedding your application’s tables in the component.
record appends an event with a server timestamp. list and paginate read newest-first feeds with type and time filters. count reports whether a bounded count is exact. Configurable retention and bounded purge operations manage stored history.
Your backend decides what happened, authenticates the actor, and controls who can read the feed. This is stored event history, not a workflow executor or a tamper-proof compliance archive. Retention can remove events; record only metadata appropriate to your access and retention policy.
Maintained by bntvllnt at VLLNT. Find the author on GitHub and X.
Use cases
From the upstream package documentation.
- Team products: show project changes or workspace activity.
- Account settings: present a history of selected account actions.
- Learning apps: record completed exercises or course activity.
- Games and agent tools: retain session milestones or host-reported operation events.
How it works
From the upstream package documentation.
record appends an event with a server timestamp. list and paginate read newest-first feeds with type and time filters. count reports whether a bounded count is exact. Configurable retention and bounded purge operations manage stored history.
Your backend decides what happened, authenticates the actor, and controls who can read the feed. This is stored event history, not a workflow executor or a tamper-proof compliance archive. Retention can remove events; record only metadata appropriate to your access and retention policy.
Upstream package FAQ
How do I install and configure the @vllnt/convex-events component?
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.
Does @vllnt/convex-events handle authentication?
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.
What is the difference between events.list and events.paginate?
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.
How does the bounded count work in @vllnt/convex-events?
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.
Can I use @vllnt/convex-events React hooks without coupling to the component's internal API?
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.
Use with agents and CLI
Give your coding agent the component’s source identity and setup guidance.
Help me integrate Events with Bijection.
Read: https://bijection.com/components/events/SKILL.md
Check SDK compatibility and dependency availability.
Follow this project's execution and verification rules.Upstream README.md
View repository ↗Documentation from vllnt/convex-events, recorded October 23, 2026. APIs and setup may differ in the Bijection adaptation.
@vllnt/convex-events
Append-only per-subject activity feed and event ledger, as a Convex component — record events against an opaque subjectRef, read them back newest-first as a feed or history.
const events = new Events(components.events);
await events.record(ctx, subjectRef, "created", { actorRef });
const feed = await events.list(ctx, subjectRef, { limit: 20 });
Features
- Append-only ledger —
recordstampscreatedAtand inserts; events are never mutated. - Per-subject feeds — list / count keyed by an opaque
subjectRef, newest-first. - Cursor pagination —
paginatereturns Convex's{ page, isDone, continueCursor }. - Type filter & since filter — narrow a feed (or count) to one
type, or page forward from a timestamp. - Bounded count — scans at most
maxCount + 1rows and reports{ count, isExact }. - Batched purge + retention cron — drains large feeds in self-rescheduling batches; an idempotent daily cron prunes past
retentionMs. - Typed metadata + host validator — generic over the host's metadata type, with optional client-boundary value guards.
- Opaque refs —
subjectRef/actorRefare arbitrary host strings the component never inspects.
Installation
pnpm add @vllnt/convex-events
Peer dependency: convex@^1.41.0.
Usage
// convex/convex.config.ts
import { defineApp } from "convex/server";
import events from "@vllnt/convex-events/convex.config";
const app = defineApp();
app.use(events);
export default app;
// convex/activity.ts — host owns auth; pass opaque refs in.
import { components } from "./_generated/api";
import { mutation, query } from "./_generated/server";
import { v } from "convex/values";
import { paginationOptsValidator } from "convex/server";
import { Events } from "@vllnt/convex-events";
interface Meta { note?: string }
const events = new Events<Meta>(components.events, {
allowedTypes: ["created", "updated", "deleted"], // optional value guard
});
export const log = mutation({
args: { userId: v.string(), type: v.string() },
handler: (ctx, { userId, type }) => events.record(ctx, userId, type, { actorRef: userId }),
});
export const feed = query({
args: { userId: v.string() },
handler: (ctx, { userId }) => events.list(ctx, userId, { limit: 20 }),
});
export const feedPage = query({
args: { userId: v.string(), paginationOpts: paginationOptsValidator },
handler: (ctx, { userId, paginationOpts }) => events.paginate(ctx, userId, paginationOpts),
});
API Reference
| Method | Kind | Result |
|---|---|---|
record(ctx, subjectRef, type, opts?) | mutation | new event id (string) |
list(ctx, subjectRef, opts?) | query | EventDoc<TMeta>[] (newest-first) |
paginate(ctx, subjectRef, paginationOpts, opts?) | query | EventPage<TMeta> |
count(ctx, subjectRef, opts?) | query | { count, isExact } |
purge(ctx, subjectRef, opts?) | mutation | number (deleted in the first batch) |
configure(ctx, retentionMs?) | mutation | null (sets/clears the retention window) |
pruneExpired(ctx, opts?) | mutation | number (manual retention sweep) |
Full reference: docs/API.md — including client options (defaultLimit, allowedTypes, metadataValidator, …) and EVENT_ERROR_CODES.
React
Optional, tree-shakeable hooks at @vllnt/convex-events/react; react and convex are optional peer deps. Pass the host's own re-exported query refs — the component never imports your api.
import { useActivityFeed, useEventCount } from "@vllnt/convex-events/react";
import { api } from "../convex/_generated/api";
const { results, status, loadMore } = useActivityFeed(api.events.paginate, { subjectRef }, { initialNumItems: 20 });
const count = useEventCount(api.events.count, { subjectRef });
| Hook | Wraps | Returns |
|---|---|---|
useActivityFeed(paginateRef, { subjectRef, type? }, { initialNumItems }) | usePaginatedQuery | { results, status, loadMore } |
useEventCount(countRef, { subjectRef, type? }) | useQuery | { count, isExact } | undefined |
Security
- Auth-agnostic — the host resolves identity, decides who may record or read a feed, and passes opaque refs.
- Tables are sandboxed;
subjectRef,actorRef, andmetadataare opaque and never inspected. - Optional client-boundary guards (
allowedTypes,maxTypeLength,maxMetadataBytes,metadataValidator) reject malformed input with a code-taggedEventValidationError.
See docs/API.md.
Testing
pnpm test # single run
pnpm test:coverage # enforced 100% on covered files
Tests run against the real component runtime via convex-test (@edge-runtime/vm), not mocks.
Contributing
See CONTRIBUTING.md.
Author
Built by bntvllnt · bntvllnt.com · X @bntvllnt
Part of the @vllnt Convex component fleet — vllnt.com
If this is useful, sponsor the work.
License
MIT — see LICENSE.