junjo.audit
Read the audit log for a group. The audit log is the durable record of every mutation Junjo has performed; the live event stream (junjo.groups.subscribe) is for transient UX, and audit.list is for “what has happened to this group, ever.”
import { Junjo } from "@junjo.io/sdk";
const junjo = new Junjo({ apiKey: process.env.JUNJO_API_KEY! });
const page = await junjo.audit.list(groupId, {
limit: 50,
actions: ["member.invited", "member.joined", "member.left", "member.kicked"],
});
for (const entry of page.items) {
console.log(entry.action, entry.targetId, entry.createdAt);
}list(groupId, opts?)
Returns Promise<Page<AuditEntry>>. Entries are sorted newest first.
list(groupId: GroupId, opts?: ListAuditOptions): Promise<Page<AuditEntry>>;
interface ListAuditOptions {
limit?: number;
// Page boundary: a Date (sent as ISO 8601) or an opaque cursor string
// from a previous page's `nextCursor`, passed through verbatim.
before?: Date | string;
actions?: AuditAction[];
}Options
| Field | Type | Default | Notes |
|---|---|---|---|
limit | number (1-100) | 50 | Max entries to return on this page. |
before | Date | string | (no filter) | Page boundary. Pass the previous page’s nextCursor string (an opaque cursor, exact keyset pagination) to walk pages; a Date is sent as ISO 8601 with strictly-older-than createdAt semantics for date-bounded reads. |
actions | AuditAction[] | (no filter) | Restrict the page to entries whose action is in the supplied list. Empty array is treated as “no filter” by the SDK and the server. |
Pagination
Page<AuditEntry>.nextCursor is an opaque cursor when more entries exist, or null on the last page. Feed it straight back in as before:
let cursor: string | null = null;
do {
const opts: ListAuditOptions = { limit: 100 };
if (cursor !== null) opts.before = cursor;
const page = await junjo.audit.list(groupId, opts);
for (const entry of page.items) handle(entry);
cursor = page.nextCursor;
} while (cursor !== null);The opaque cursor resolves server-side to exact keyset pagination on the (createdAt, id) sort, so page boundaries are immune to entries sharing a millisecond. Only the Date form of before has the older exclusive-timestamp semantics, where entries sharing the exact boundary timestamp can be skipped.
listAll(groupId, opts?)
Async-iterator wrapper over list that walks the group’s whole audit timeline, newest first, feeding each page’s nextCursor back in as before. actions and limit filter exactly as on list.
for await (const entry of junjo.audit.listAll(groupId, {
actions: ["member.joined", "member.left"],
})) {
handle(entry);
}Entry shape
interface AuditEntry {
id: AuditEntryId;
groupId: GroupId;
actorUserId: UserId | null;
action: AuditAction;
targetId: string | null;
payload: Record<string, unknown>;
createdAt: Date;
}actorUserId is null for system-driven actions and for the V1 mutations that have no auth-adapter actor wired (most of them). targetId is a free-form pointer to whatever the action targeted: a user id, role id, permission key, or group id; type depends on action.
Errors
| Code | Status | When |
|---|---|---|
bad_request | 400 | Out-of-range limit, malformed before, or unknown action value. |
not_found | 404 | Group missing, soft-deleted, or owned by a different game. |
invalid_api_key | 401 | API key missing, malformed, or revoked. |