Creating Events
The SDK provides server actions that make it easy to create audit events from your Next.js server components, API routes, and server actions. These functions handle authentication, request formatting, and error handling for you.
Why Server Actions?
Server actions are perfect for creating audit events because:
- Security: Your API key stays on the server, never exposed to the client
- Simplicity: No need to manually format requests or handle authentication
- Type safety: Full TypeScript support with proper types
- Error handling: Built-in error handling and retry logic
Basic Usage
Creating a Single Event
import { createEvent } from '@archiva/archiva-nextjs';
// In a server component, API route, or server action
export async function updateInvoice(invoiceId: string, userId: string) {
// Your business logic here...
// Create an audit event
const { eventId, replayed } = await createEvent({
actionKey: 'invoice.update',
actionDescription: `Invoice ${invoiceId} was updated`,
entityType: 'invoice',
entityId: invoiceId,
actorType: 'user',
actorId: userId,
actorDisplay: 'John Doe',
context: {
changes: { status: 'draft' },
requestId: crypto.randomUUID(),
},
});
return { success: true, eventId };
}
Creating Multiple Events (Bulk)
import { createEvents } from '@archiva/archiva-nextjs';
// Create multiple events at once
const { eventIds } = await createEvents([
{
actionKey: 'invoice.create',
entityType: 'invoice',
entityId: 'inv_001',
actorType: 'user',
actorId: 'usr_123',
},
{
actionKey: 'invoice.create',
entityType: 'invoice',
entityId: 'inv_002',
actorType: 'user',
actorId: 'usr_123',
},
]);
Function Reference
createEvent
Creates a single audit event.
Signature:
createEvent(
event: CreateEventInput,
options?: CreateEventOptions,
apiKey?: string
): Promise<{ eventId: string; replayed: boolean }>
Parameters:
| Parameter | Type | Required | Description |
|---|---|---|---|
event | CreateEventInput | Yes | The event data to create |
options | CreateEventOptions | No | Optional idempotency key and request ID |
apiKey | string | No | Optional API key (defaults to ARCHIVA_SECRET_KEY env var) |
Returns:
eventId: The unique identifier of the created eventreplayed:trueif this was an idempotent replay (same idempotency key and body),falseif it's a new event
createEvents
Creates multiple audit events in a single request (bulk operation).
Signature:
createEvents(
events: CreateEventInput[],
options?: CreateEventOptions,
apiKey?: string
): Promise<{ eventIds: string[] }>
Parameters:
| Parameter | Type | Required | Description |
|---|---|---|---|
events | CreateEventInput[] | Yes | Array of events to create |
options | CreateEventOptions | No | Optional idempotency key and request ID (applied to all events) |
apiKey | string | No | Optional API key (defaults to ARCHIVA_SECRET_KEY env var) |
Returns:
eventIds: Array of unique identifiers for the created events
Event Input Type
The CreateEventInput type defines what data you can include in an event:
type CreateEventInput = {
// Action information
actionKey?: string; // e.g., 'invoice.update'
actionDescription?: string; // Human-readable description
action?: string; // Legacy field (use actionKey instead)
// Entity information
entityType: string; // Required: e.g., 'invoice', 'user', 'order'
entityId: string; // Required: unique identifier
// Actor information
actorType?: 'user' | 'service' | 'system'; // Default: 'user'
actorId: string; // Required: unique identifier
actorDisplay?: string; // Human-readable name
// Additional context
tenantId?: string; // For multi-tenant applications
occurredAt?: string; // ISO 8601 timestamp (defaults to now)
source?: string; // e.g., 'web-app', 'api', 'background-job'
context?: Record<string, unknown>; // Additional metadata (max 10KB)
changes?: EventChange[]; // Field-level changes
};
Options Type
The CreateEventOptions type allows you to specify idempotency and request tracking:
type CreateEventOptions = {
idempotencyKey?: string; // For safe retries (see Idempotency docs)
requestId?: string; // For tracking and logging
};
Examples
In a Server Action
'use server';
import { createEvent } from '@archiva/archiva-nextjs';
export async function updateInvoiceStatus(
invoiceId: string,
newStatus: string,
userId: string
) {
// Update invoice in database
await db.invoice.update({
where: { id: invoiceId },
data: { status: newStatus },
});
// Log the audit event
await createEvent({
actionKey: 'invoice.status.update',
actionDescription: `Invoice status changed to ${newStatus}`,
entityType: 'invoice',
entityId: invoiceId,
actorType: 'user',
actorId: userId,
changes: [
{
op: 'set',
path: 'status',
before: 'draft',
after: newStatus,
},
],
});
return { success: true };
}
In an API Route
import { createEvent } from '@archiva/archiva-nextjs';
import { NextRequest, NextResponse } from 'next/server';
export async function POST(request: NextRequest) {
const body = await request.json();
// Your business logic...
// Create audit event
try {
const { eventId } = await createEvent({
actionKey: 'payment.processed',
entityType: 'payment',
entityId: body.paymentId,
actorType: 'system',
actorId: 'payment-service',
context: {
amount: body.amount,
currency: body.currency,
},
}, {
requestId: request.headers.get('x-request-id') || crypto.randomUUID(),
});
return NextResponse.json({ success: true, eventId });
} catch (error) {
return NextResponse.json(
{ error: 'Failed to create audit event' },
{ status: 500 }
);
}
}
With Idempotency
import { createEvent } from '@archiva/archiva-nextjs';
export async function processWebhook(webhookId: string, eventData: any) {
// Use webhook ID as idempotency key to prevent duplicates
const { eventId, replayed } = await createEvent({
actionKey: 'webhook.processed',
entityType: 'webhook',
entityId: webhookId,
actorType: 'service',
actorId: 'webhook-handler',
context: eventData,
}, {
idempotencyKey: `webhook_${webhookId}`,
});
if (replayed) {
console.log('Event was already processed (idempotent replay)');
}
return { eventId };
}
Bulk Creation
import { createEvents } from '@archiva/archiva-nextjs';
export async function importHistoricalEvents(events: CreateEventInput[]) {
// Create events in batches of 100
const batchSize = 100;
const batches = [];
for (let i = 0; i < events.length; i += batchSize) {
const batch = events.slice(i, i + batchSize);
batches.push(createEvents(batch));
}
// Process all batches
const results = await Promise.all(batches);
const allEventIds = results.flatMap(r => r.eventIds);
return { eventIds: allEventIds, count: allEventIds.length };
}
Error Handling
The functions throw ArchivaError for API errors:
import { createEvent, ArchivaError } from '@archiva/archiva-nextjs';
try {
await createEvent({ /* ... */ });
} catch (error) {
if (error instanceof ArchivaError) {
switch (error.code) {
case 'UNAUTHORIZED':
// API key is invalid
break;
case 'RATE_LIMITED':
// Rate limit exceeded, wait error.retryAfterSeconds
break;
case 'IDEMPOTENCY_CONFLICT':
// Same idempotency key used with different body
break;
default:
// Other error
}
}
throw error;
}
Requirements
- Must have
ARCHIVA_SECRET_KEYenvironment variable set (or provideapiKeyparameter) - Must be used in server-side code (server components, API routes, server actions)
- Requires network access to
https://api.archiva.app(or your configured API URL)
Best Practices
- Create events after successful operations - Only log events for actions that actually completed
- Use descriptive action keys - Follow a consistent format like
entity.action(e.g.,invoice.update) - Include context - Add relevant metadata in the
contextfield for debugging - Use idempotency keys - For retryable operations (webhooks, scheduled jobs), use idempotency keys
- Handle errors gracefully - Don't let audit logging failures break your main functionality
Next Steps
- Learn about displaying events with the Timeline component
- See a complete example showing create and display together
- Read about idempotency for safe retries