SDKaudit

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

FieldTypeDefaultNotes
limitnumber (1-100)50Max entries to return on this page.
beforeDate | 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.
actionsAuditAction[](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

CodeStatusWhen
bad_request400Out-of-range limit, malformed before, or unknown action value.
not_found404Group missing, soft-deleted, or owned by a different game.
invalid_api_key401API key missing, malformed, or revoked.