@beignet/core
Version:
Core framework primitives for Beignet
375 lines • 12.2 kB
TypeScript
import { type ProviderInstrumentationTarget } from "../providers/instrumentation.js";
/**
* The normalized category of actor that caused application activity.
*
* Actors describe who or what performed work for request context,
* authorization, audit logs, and diagnostics. They do not authenticate the
* request by themselves.
*/
export type ActivityActorType = "anonymous" | "service" | "system" | "user";
/**
* Whether an audited activity completed successfully or intentionally records
* a failed attempt.
*/
export type AuditOutcome = "success" | "failure";
/**
* JSON-like metadata values accepted by activity and audit descriptors.
*
* Metadata should stay intentionally small. Prefer stable IDs and short labels
* over full request bodies, secrets, PHI, or PII.
*/
export type ActivityMetadataValue = ActivityMetadataValue[] | boolean | null | number | string | {
[key: string]: ActivityMetadataValue | undefined;
};
/**
* Additional structured metadata attached to actors, tenants, resources, or
* audit entries.
*/
export type ActivityMetadata = Record<string, ActivityMetadataValue | undefined>;
/**
* A normalized descriptor for the person, service, or system process that
* caused application activity.
*
* Store this on application context as `ctx.actor` so routes, use cases, jobs,
* policies, audit logs, and devtools share one identity shape.
*/
export interface ActivityActor {
/**
* The actor category.
*/
type: ActivityActorType;
/**
* Stable application ID for this actor, when known.
*/
id?: string;
/**
* Human-readable label for diagnostics and audit views.
*/
displayName?: string;
/**
* Small, redaction-safe metadata about the actor.
*/
metadata?: ActivityMetadata;
}
/**
* A normalized tenant/account/workspace scope for activity.
*
* This is a context value used by audit logs, authorization, and diagnostics.
* It does not create, load, or persist a tenant record.
*/
export interface ActivityTenant {
/**
* Stable tenant/account/workspace ID.
*/
id: string;
/**
* Optional human-readable tenant slug.
*/
slug?: string;
/**
* Small, redaction-safe metadata about the tenant.
*/
metadata?: ActivityMetadata;
}
/**
* A normalized descriptor for the business object affected by an audit entry.
*/
export interface ActivityResource {
/**
* Resource type, usually a singular domain noun such as "post", "invoice",
* or "appointment".
*/
type: string;
/**
* Stable resource ID, when known.
*/
id?: string;
/**
* Human-readable resource label for audit views.
*/
name?: string;
/**
* Small, redaction-safe metadata about the resource.
*/
metadata?: ActivityMetadata;
}
/**
* A normalized audit/activity log entry.
*
* Application code usually records entries through an audit port wrapped with
* `createAmbientAuditLog(...)` from `@beignet/core/server`, which fills
* missing actor, tenant, request ID, and trace ID fields from the ambient
* request context at record time. Durability depends on the `AuditLogPort`
* implementation.
*/
export interface AuditLogEntry {
/**
* Stable action name, usually namespaced by feature and workflow.
*
* @example "posts.publish"
*/
action: string;
/**
* Actor that caused the activity.
*/
actor: ActivityActor;
/**
* Timestamp assigned when the activity occurred.
*/
occurredAt: Date;
/**
* Whether the activity succeeded or records a failed attempt.
*/
outcome: AuditOutcome;
/**
* Small, redaction-safe metadata about the activity.
*/
metadata?: ActivityMetadata;
/**
* Optional human-readable audit message.
*/
message?: string;
/**
* Request correlation ID, when the activity originated from a request or
* background context.
*/
requestId?: string;
/**
* Business resource affected by the activity.
*/
resource?: ActivityResource;
/**
* Tenant/account/workspace scope for the activity.
*/
tenant?: ActivityTenant;
/**
* Trace correlation ID, when tracing is enabled.
*/
traceId?: string;
}
/**
* Input accepted by `AuditLogPort.record(...)`.
*
* `actor`, `occurredAt`, and `outcome` are optional at call sites. Wrappers
* such as `createAmbientAuditLog(...)` fill a missing actor from the ambient
* request context. Adapters that store audit entries should call
* `normalizeAuditLogEntry(...)` before persistence or otherwise apply
* equivalent defaults; entries without an actor normalize to an anonymous
* actor.
*/
export type AuditLogEntryInput = Omit<AuditLogEntry, "actor" | "occurredAt" | "outcome"> & {
actor?: ActivityActor;
occurredAt?: Date;
outcome?: AuditOutcome;
};
/**
* App-facing port for audit/activity logging.
*
* Production implementations should usually write to a durable database table,
* append-only log, or external audit service. Tests can use an in-memory
* adapter. Application code should depend on this interface, not on a concrete
* audit provider.
*/
export interface AuditLogPort {
/**
* Persist or capture an audit entry.
*/
record(entry: AuditLogEntryInput): Promise<void> | void;
}
/**
* In-memory audit log port used by tests and local examples.
*/
export interface MemoryAuditLogPort extends AuditLogPort {
/**
* Captured, normalized, redacted audit entries.
*/
entries: AuditLogEntry[];
}
/**
* Options shared by audit log wrappers and in-memory audit adapters.
*/
export interface AuditLogOptions {
/**
* Optional final redaction/customization step applied after Beignet's default
* metadata redaction.
*/
redact?: (entry: AuditLogEntry) => AuditLogEntry;
}
/**
* Create an anonymous actor descriptor for unauthenticated activity.
*
* This helper only creates a normalized context value. It does not perform
* authentication.
*
* @example
* ```ts
* const actor = createAnonymousActor();
* ```
*
* @param options - Optional display name or metadata to include.
* @returns An activity actor with `type: "anonymous"`.
*/
export declare function createAnonymousActor(options?: Omit<ActivityActor, "type">): ActivityActor;
/**
* Create a service actor descriptor for work initiated by another service or
* integration.
*
* This is useful for webhooks, internal service calls, or integration-driven
* background jobs.
*
* @example
* ```ts
* const actor = createServiceActor("stripe-webhook");
* ```
*
* @param id - Stable service or integration ID.
* @param options - Optional display name or metadata to include.
* @returns An activity actor with `type: "service"`.
*/
export declare function createServiceActor(id: string, options?: Omit<ActivityActor, "type" | "id">): ActivityActor;
/**
* Create a system actor descriptor for framework or app-owned background work.
*
* Use this for schedules, scripts, maintenance jobs, and other work that
* is not directly caused by a user or external service.
*
* @example
* ```ts
* const actor = createSystemActor("nightly-maintenance");
* ```
*
* @param id - Stable system actor ID. Defaults to `"system"`.
* @param options - Optional display name or metadata to include.
* @returns An activity actor with `type: "system"`.
*/
export declare function createSystemActor(id?: string, options?: Omit<ActivityActor, "type" | "id">): ActivityActor;
/**
* Create a user actor descriptor for authenticated user activity.
*
* This helper only normalizes a known user ID for context, authorization,
* audit, and diagnostics. It does not verify a session or load a user record.
* Resolve authentication first, then call this helper with the authenticated
* user ID.
*
* @example
* ```ts
* const actor = createUserActor(session.user.id, {
* displayName: session.user.name,
* });
* ```
*
* @param id - Stable application user ID.
* @param options - Optional display name or metadata to include.
* @returns An activity actor with `type: "user"`.
*/
export declare function createUserActor(id: string, options?: Omit<ActivityActor, "type" | "id">): ActivityActor;
/**
* Create a tenant/account/workspace descriptor for request or background
* context.
*
* This helper only creates a normalized context value used by audit,
* authorization, logs, and diagnostics. It does not create, load, or persist a
* tenant record.
*
* @example
* ```ts
* const tenant = createTenant(session.organizationId, {
* slug: session.organizationSlug,
* });
* ```
*
* @param id - Stable tenant/account/workspace ID.
* @param options - Optional slug or metadata to include.
* @returns A normalized activity tenant descriptor.
*/
export declare function createTenant(id: string, options?: Omit<ActivityTenant, "id">): ActivityTenant;
/**
* Fill default audit fields for an input entry.
*
* @param entry - Partial audit entry accepted by `AuditLogPort.record(...)`.
* @returns A complete audit entry with `actor`, `occurredAt`, and `outcome`
* populated. Entries without an actor default to an anonymous actor.
*/
export declare function normalizeAuditLogEntry(entry: AuditLogEntryInput): AuditLogEntry;
/**
* Redact metadata on an already-normalized audit entry.
*
* This redacts metadata values on the entry, actor, tenant, and resource using
* the default redaction rules from `redactValue(...)`.
*
* @param entry - Audit entry to redact.
* @returns A shallow copy with redacted metadata fields.
*/
export declare function redactAuditLogEntry(entry: AuditLogEntry): AuditLogEntry;
/**
* Wrap an audit log port with default audit metadata redaction.
*
* Use this around durable adapters so application code can record entries
* without each call site remembering to redact metadata.
*
* @param audit - Underlying audit log port to write to after redaction.
* @param options - Optional final redaction/customization hook.
* @returns An audit log port that normalizes and redacts before writing.
*/
export declare function createRedactedAuditLog(audit: AuditLogPort, options?: AuditLogOptions): AuditLogPort;
/**
* Options for wrapping an audit log with instrumentation emission.
*/
export interface InstrumentedAuditLogOptions {
/**
* Durable audit log to write first.
*/
audit: AuditLogPort;
/**
* Instrumentation sink, port, or ports object. Pass the app ports object so
* the sink (`ports.instrumentation`, then `ports.devtools`) is resolved
* lazily on each write and observes provider startup order.
*/
instrumentation?: ProviderInstrumentationTarget;
/**
* Whether to emit instrumentation events. Defaults to true.
*/
emit?: boolean;
/**
* Optional app-owned redactor applied after Beignet's audit redaction.
*/
redact?: (entry: AuditLogEntry) => AuditLogEntry;
}
/**
* Wrap an audit log so durable audit writes also appear in instrumentation
* sinks such as devtools.
*
* Instrumentation failures are ignored so audit persistence remains the
* source of truth.
*
* @example
* ```ts
* const audit = createInstrumentedAuditLog({
* audit: createDrizzleSqliteAuditLogPort(db),
* instrumentation: ports,
* });
* ```
*/
export declare function createInstrumentedAuditLog(options: InstrumentedAuditLogOptions): AuditLogPort;
/**
* Create an in-memory audit log for tests and local examples.
*
* Entries are normalized and redacted before being pushed into the shared
* `entries` array.
*
* @example
* ```ts
* const audit = createMemoryAuditLog();
* await audit.record({
* action: "posts.publish",
* actor: createUserActor("user_1"),
* });
* expect(audit.entries).toHaveLength(1);
* ```
*
* @param entries - Optional backing array, useful when tests need shared state.
* @param options - Optional final redaction/customization hook.
* @returns An in-memory audit log port with captured `entries`.
*/
export declare function createMemoryAuditLog(entries?: AuditLogEntry[], options?: AuditLogOptions): MemoryAuditLogPort;
//# sourceMappingURL=audit.d.ts.map