Audit fields and change history
Two related mechanisms serve different purposes:
WithAudit(BaseEntity)adds current audit columns to a domain row.- the action-history feature stores a timeline of before/after snapshots.
Audit columns
WithTimestamps(BaseEntity) adds createdAt, updatedAt, and deletedAt. WithAudit(BaseEntity) includes those columns plus createdBy, modifiedBy, creator, and modifier.
import { Column, Entity } from 'typeorm';
import { BaseEntity, WithAudit } from '@sdcorejs/nestjs/core';
@Entity('customer')
export class Customer extends WithAudit(BaseEntity) {
@Column()
name!: string;
}The default audit strategy fills createdBy and modifiedBy from ContextService.userId. It does not invent domain-shaped user snapshots for jsonb creator and modifier columns.
Custom strategy
import { Injectable } from '@nestjs/common';
import type { DeepPartial } from 'typeorm';
import type {
IAuditStrategy,
RequestContext,
UserSnapshot,
} from '@sdcorejs/nestjs/core';
@Injectable()
export class AppAuditStrategy implements IAuditStrategy {
onCreate(entity: DeepPartial<Record<string, unknown>>, context: RequestContext): void {
if (!context.userId) return;
entity.createdBy = context.userId;
entity.modifiedBy = context.userId;
const principal = context.user as { email?: string; displayName?: string } | undefined;
const snapshot: UserSnapshot = {
id: context.userId,
username: principal?.email ?? context.userId,
fullName: principal?.displayName ?? '',
};
entity.creator = snapshot;
entity.modifier = snapshot;
}
onUpdate(entity: DeepPartial<Record<string, unknown>>, context: RequestContext): void {
if (context.userId) entity.modifiedBy = context.userId;
}
onSoftDelete(): void {}
}Register it with audit: { strategy: AppAuditStrategy } and pass the injected strategy into each BaseRepository that should fill fields.
Direct TypeORM writes
BaseRepository.create/update/import invoke the configured strategy directly. Writes performed through an ordinary TypeORM repository need AuditSubscriber added to the active DataSource:
import { DataSource } from 'typeorm';
import { AuditSubscriber } from '@sdcorejs/nestjs/core';
const subscriber = app.get(AuditSubscriber);
const dataSource = app.get(DataSource);
if (!dataSource.subscribers.includes(subscriber)) {
dataSource.subscribers.push(subscriber);
}The snippet belongs in bootstrap after creating app and before writes begin. The subscriber acts only on entities created with WithAudit.
Snapshot history
Set logHistory: true in a repository and enable actionHistory to persist CREATE, UPDATE, and DELETE snapshots inside the mutation transaction. Read policy, redaction, retention, and controller mounting are covered in Action history.
