Skip to content

Find a component

Loading the directory…

↑ ↓ to select · Enter to open · Esc to closeBrowse all →
Contributor login

Events

Append-only per-subject activity feed and event ledger, as a Bijection component

Bijection source
git clone https://github.com/bijectionhq/events.git
cd events
git checkout ba5d07de213b41de96b67f0d3e3d562f265b7f30

Bijection 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.

Available to evaluate

Review the source and its dependencies before adopting this package. Source availability does not establish npm publication or runtime qualification.

Getting started

  1. Get the source at the revision above and read its package documentation.
  2. Check the required Bijection SDK version, dependencies, and external service credentials.
  3. Follow the package setup instructions and the guide to using components.
  4. 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.

agent-prompt.md
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 — record stamps createdAt and inserts; events are never mutated.
  • Per-subject feeds — list / count keyed by an opaque subjectRef, newest-first.
  • Cursor pagination — paginate returns 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 + 1 rows 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 / actorRef are 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

MethodKindResult
record(ctx, subjectRef, type, opts?)mutationnew event id (string)
list(ctx, subjectRef, opts?)queryEventDoc<TMeta>[] (newest-first)
paginate(ctx, subjectRef, paginationOpts, opts?)queryEventPage<TMeta>
count(ctx, subjectRef, opts?)query{ count, isExact }
purge(ctx, subjectRef, opts?)mutationnumber (deleted in the first batch)
configure(ctx, retentionMs?)mutationnull (sets/clears the retention window)
pruneExpired(ctx, opts?)mutationnumber (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 });
HookWrapsReturns
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, and metadata are opaque and never inspected.
  • Optional client-boundary guards (allowedTypes, maxTypeLength, maxMetadataBytes, metadataValidator) reject malformed input with a code-tagged EventValidationError.

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.

Keywords