nestjs-audit-trail-workspace 1.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (34) hide show
  1. package/.prettierrc +4 -0
  2. package/README.md +293 -0
  3. package/eslint.config.mjs +35 -0
  4. package/libs/nestjs-audit-trail/README.md +293 -0
  5. package/libs/nestjs-audit-trail/package.json +57 -0
  6. package/libs/nestjs-audit-trail/src/audit-trail-options.ts +10 -0
  7. package/libs/nestjs-audit-trail/src/audit-trail.module.spec.ts +41 -0
  8. package/libs/nestjs-audit-trail/src/audit-trail.module.ts +45 -0
  9. package/libs/nestjs-audit-trail/src/decorators/audit.decorator.ts +18 -0
  10. package/libs/nestjs-audit-trail/src/decorators/index.ts +1 -0
  11. package/libs/nestjs-audit-trail/src/hashing/hash-audit.spec.ts +41 -0
  12. package/libs/nestjs-audit-trail/src/hashing/hash-audit.ts +42 -0
  13. package/libs/nestjs-audit-trail/src/hashing/index.ts +2 -0
  14. package/libs/nestjs-audit-trail/src/hashing/serialize-deterministic.ts +31 -0
  15. package/libs/nestjs-audit-trail/src/index.ts +16 -0
  16. package/libs/nestjs-audit-trail/src/interceptor/audit.interceptor.spec.ts +87 -0
  17. package/libs/nestjs-audit-trail/src/interceptor/audit.interceptor.ts +74 -0
  18. package/libs/nestjs-audit-trail/src/interceptor/index.ts +1 -0
  19. package/libs/nestjs-audit-trail/src/interfaces/audit-storage.interface.ts +18 -0
  20. package/libs/nestjs-audit-trail/src/interfaces/index.ts +1 -0
  21. package/libs/nestjs-audit-trail/src/nestjs-audit-trail.module.ts +8 -0
  22. package/libs/nestjs-audit-trail/src/nestjs-audit-trail.service.spec.ts +18 -0
  23. package/libs/nestjs-audit-trail/src/nestjs-audit-trail.service.ts +4 -0
  24. package/libs/nestjs-audit-trail/src/services/audit.service.spec.ts +90 -0
  25. package/libs/nestjs-audit-trail/src/services/audit.service.ts +39 -0
  26. package/libs/nestjs-audit-trail/src/services/index.ts +1 -0
  27. package/libs/nestjs-audit-trail/src/types/audit-record.ts +29 -0
  28. package/libs/nestjs-audit-trail/src/types/index.ts +1 -0
  29. package/libs/nestjs-audit-trail/tsconfig.lib.json +13 -0
  30. package/nest-cli.json +16 -0
  31. package/package.json +83 -0
  32. package/test/jest-e2e.json +16 -0
  33. package/tsconfig.build.json +4 -0
  34. package/tsconfig.json +33 -0
package/.prettierrc ADDED
@@ -0,0 +1,4 @@
1
+ {
2
+ "singleQuote": true,
3
+ "trailingComma": "all"
4
+ }
package/README.md ADDED
@@ -0,0 +1,293 @@
1
+ # nestjs-audit-trail
2
+
3
+ A small, storage-agnostic audit trail module for NestJS.
4
+
5
+ If you need a reliable record of who did what, when, and what changed - without coupling your app to a specific database or ORM - this is for you.
6
+
7
+ Records are SHA256-hashed and can be chained for tamper-evidence. You bring your own persistence layer.
8
+
9
+ ## Install
10
+
11
+ ```bash
12
+ npm install nestjs-audit-trail
13
+ ```
14
+
15
+ ## Quick start
16
+
17
+ 1. Implement `IAuditStorage` (or use an adapter from a separate package).
18
+ 2. Register the module and the interceptor.
19
+ 3. Mark handlers with `@Audit({ action, entity })`.
20
+
21
+ ```typescript
22
+ // app.module.ts
23
+ import { Module } from '@nestjs/common';
24
+ import { APP_INTERCEPTOR } from '@nestjs/core';
25
+ import {
26
+ AuditTrailModule,
27
+ AuditInterceptor,
28
+ type IAuditStorage,
29
+ } from 'nestjs-audit-trail';
30
+
31
+ const myStorage: IAuditStorage = {
32
+ save: async (record) => {
33
+ // persist to your DB, queue, or object store
34
+ },
35
+ getLastHash: async () => null, // optional, for chain linking
36
+ };
37
+
38
+ @Module({
39
+ imports: [
40
+ AuditTrailModule.forRoot({
41
+ storage: myStorage,
42
+ correlationIdHeader: 'x-correlation-id', // optional
43
+ }),
44
+ ],
45
+ providers: [{ provide: APP_INTERCEPTOR, useClass: AuditInterceptor }],
46
+ })
47
+ export class AppModule {}
48
+ ```
49
+
50
+ ```typescript
51
+ // user.controller.ts
52
+ import { Controller, Post, Body } from '@nestjs/common';
53
+ import { Audit } from 'nestjs-audit-trail';
54
+
55
+ @Controller('users')
56
+ export class UserController {
57
+ @Post()
58
+ @Audit({ action: 'CREATE', entity: 'User' })
59
+ create(@Body() body: CreateUserDto) {
60
+ return this.userService.create(body);
61
+ }
62
+ }
63
+ ```
64
+
65
+ The interceptor reads `action` and `entity` from `@Audit`, and fills `actorId` (from `x-actor-id` or `request.user.id`), `correlationId` (from the configured header), `payloadBefore` (request body), and `payloadAfter` (handler result). Records are hashed and saved via your storage.
66
+
67
+ ---
68
+
69
+ ## Storage interface
70
+
71
+ All persistence is behind this contract. The core does not implement storage.
72
+
73
+ ```typescript
74
+ import type { AuditRecord, IAuditStorage } from 'nestjs-audit-trail';
75
+
76
+ const adapter: IAuditStorage = {
77
+ async save(record: AuditRecord): Promise<void> {
78
+ // Persist record (e.g. insert into table, send to queue, write to S3).
79
+ },
80
+
81
+ // Optional: return the hash of the last saved record for chain linking.
82
+ async getLastHash(): Promise<string | null> {
83
+ return null;
84
+ },
85
+ };
86
+ ```
87
+
88
+ `AuditRecord` is a plain type: `action`, `entity`, `actorId`, `correlationId`, `payloadBefore`, `payloadAfter`, `metadata`, `hash`, `createdAt`, `previousHash?`. No ORM decorators; you map it to your schema.
89
+
90
+ ---
91
+
92
+ ## Building custom storage adapters
93
+
94
+ Adapters live in **separate packages** (e.g. `@nestjs-audit-trail/typeorm`, `@nestjs-audit-trail/prisma`). Your adapter only needs to implement `IAuditStorage`.
95
+
96
+ 1. Create a new package that depends on `nestjs-audit-trail` and your persistence layer.
97
+ 2. Implement `IAuditStorage`: in `save()`, map `AuditRecord` to your model and persist; optionally implement `getLastHash()` by reading the latest row and returning its `hash`.
98
+ 3. Export a ready-to-use storage instance or factory (e.g. a function that takes a data source and returns `IAuditStorage`).
99
+ 4. In the app, install the adapter and pass its storage to `AuditTrailModule.forRoot({ storage })`.
100
+
101
+ The core stays free of ORM/database imports; only NestJS and Node `crypto` are used.
102
+
103
+ ---
104
+
105
+ ## Domain examples
106
+
107
+ Same library, different domains: use `@Audit` with actions and entities that match your domain. Pass a storage implementation that fits your stack (SQL, NoSQL, queue, object store).
108
+
109
+ ### Healthcare (e.g. PHI / HIPAA-style audit)
110
+
111
+ Audit access and changes to patient and clinical data. Use a storage backend that meets your retention and access rules.
112
+
113
+ ```typescript
114
+ @Controller('patients')
115
+ export class PatientController {
116
+ @Get(':id/records')
117
+ @Audit({ action: 'READ', entity: 'PatientRecord' })
118
+ getRecords(@Param('id') id: string) {
119
+ return this.patientService.getRecords(id);
120
+ }
121
+
122
+ @Patch(':id')
123
+ @Audit({ action: 'UPDATE', entity: 'Patient' })
124
+ updatePatient(@Param('id') id: string, @Body() dto: UpdatePatientDto) {
125
+ return this.patientService.update(id, dto);
126
+ }
127
+
128
+ @Post(':id/prescriptions')
129
+ @Audit({ action: 'CREATE', entity: 'Prescription' })
130
+ createPrescription(
131
+ @Param('id') id: string,
132
+ @Body() dto: CreatePrescriptionDto,
133
+ ) {
134
+ return this.patientService.addPrescription(id, dto);
135
+ }
136
+ }
137
+ ```
138
+
139
+ ### Ecommerce (orders, inventory, payments)
140
+
141
+ Audit order lifecycle, inventory changes, and payment events for support and disputes.
142
+
143
+ ```typescript
144
+ @Controller('orders')
145
+ export class OrderController {
146
+ @Post()
147
+ @Audit({ action: 'CREATE', entity: 'Order' })
148
+ create(@Body() dto: CreateOrderDto) {
149
+ return this.orderService.create(dto);
150
+ }
151
+
152
+ @Patch(':id/status')
153
+ @Audit({ action: 'UPDATE', entity: 'Order' })
154
+ updateStatus(@Param('id') id: string, @Body() body: { status: string }) {
155
+ return this.orderService.setStatus(id, body.status);
156
+ }
157
+ }
158
+
159
+ @Controller('inventory')
160
+ export class InventoryController {
161
+ @Patch(':sku')
162
+ @Audit({ action: 'UPDATE', entity: 'Inventory' })
163
+ adjust(@Param('sku') sku: string, @Body() dto: AdjustInventoryDto) {
164
+ return this.inventoryService.adjust(sku, dto);
165
+ }
166
+ }
167
+ ```
168
+
169
+ ### Fintech (transactions, KYC, accounts)
170
+
171
+ Audit transactions, KYC updates, and account changes for regulatory and operational traceability.
172
+
173
+ ```typescript
174
+ @Controller('transactions')
175
+ export class TransactionController {
176
+ @Post('transfer')
177
+ @Audit({ action: 'CREATE', entity: 'Transfer' })
178
+ transfer(@Body() dto: TransferDto) {
179
+ return this.transferService.execute(dto);
180
+ }
181
+ }
182
+
183
+ @Controller('accounts')
184
+ export class AccountController {
185
+ @Patch(':id')
186
+ @Audit({ action: 'UPDATE', entity: 'Account' })
187
+ updateAccount(@Param('id') id: string, @Body() dto: UpdateAccountDto) {
188
+ return this.accountService.update(id, dto);
189
+ }
190
+ }
191
+
192
+ @Controller('kyc')
193
+ export class KycController {
194
+ @Post('submit')
195
+ @Audit({ action: 'SUBMIT', entity: 'KycApplication' })
196
+ submit(@Body() dto: KycDto) {
197
+ return this.kycService.submit(dto);
198
+ }
199
+
200
+ @Patch(':id/approve')
201
+ @Audit({ action: 'APPROVE', entity: 'KycApplication' })
202
+ approve(@Param('id') id: string) {
203
+ return this.kycService.approve(id);
204
+ }
205
+ }
206
+ ```
207
+
208
+ ### Education (enrollments, grades, courses)
209
+
210
+ Audit enrollment and grade changes for academic integrity and admin audits.
211
+
212
+ ```typescript
213
+ @Controller('enrollments')
214
+ export class EnrollmentController {
215
+ @Post()
216
+ @Audit({ action: 'CREATE', entity: 'Enrollment' })
217
+ enroll(@Body() dto: EnrollDto) {
218
+ return this.enrollmentService.enroll(dto);
219
+ }
220
+
221
+ @Delete(':id')
222
+ @Audit({ action: 'DELETE', entity: 'Enrollment' })
223
+ withdraw(@Param('id') id: string) {
224
+ return this.enrollmentService.withdraw(id);
225
+ }
226
+ }
227
+
228
+ @Controller('grades')
229
+ export class GradeController {
230
+ @Put(':enrollmentId')
231
+ @Audit({ action: 'UPDATE', entity: 'Grade' })
232
+ setGrade(@Param('enrollmentId') id: string, @Body() dto: GradeDto) {
233
+ return this.gradeService.setGrade(id, dto);
234
+ }
235
+ }
236
+
237
+ @Controller('courses')
238
+ export class CourseController {
239
+ @Post()
240
+ @Audit({ action: 'CREATE', entity: 'Course' })
241
+ create(@Body() dto: CreateCourseDto) {
242
+ return this.courseService.create(dto);
243
+ }
244
+ }
245
+ ```
246
+
247
+ In every case you use the same `@Audit({ action, entity })` and one `IAuditStorage` implementation; only the semantics (and your storage backend) change per domain.
248
+
249
+ ---
250
+
251
+ ## Manual recording
252
+
253
+ You can skip the interceptor and record from services:
254
+
255
+ ```typescript
256
+ import { AuditService } from 'nestjs-audit-trail';
257
+
258
+ @Injectable()
259
+ export class MyService {
260
+ constructor(private readonly audit: AuditService) {}
261
+
262
+ async doSomething(actorId: string, correlationId: string) {
263
+ const before = { count: 0 };
264
+ // ... work ...
265
+ const after = { count: 1 };
266
+ await this.audit.record({
267
+ action: 'UPDATE',
268
+ entity: 'Counter',
269
+ actorId,
270
+ correlationId,
271
+ payloadBefore: before,
272
+ payloadAfter: after,
273
+ });
274
+ }
275
+ }
276
+ ```
277
+
278
+ ---
279
+
280
+ ## Configuration
281
+
282
+ | Option | Required | Default | Description |
283
+ | --------------------- | -------- | -------------------- | ------------------------------------ |
284
+ | `storage` | Yes | — | Your `IAuditStorage` implementation. |
285
+ | `correlationIdHeader` | No | `'x-correlation-id'` | Request header used for correlation. |
286
+
287
+ Set `x-actor-id` on the request (or ensure `request.user.id` is set) so each record has an actor. The core uses deterministic SHA256 hashing and optional chaining via `getLastHash()`; no database or ORM is required.
288
+
289
+ ---
290
+
291
+ ## License
292
+
293
+ MIT
@@ -0,0 +1,35 @@
1
+ // @ts-check
2
+ import eslint from '@eslint/js';
3
+ import eslintPluginPrettierRecommended from 'eslint-plugin-prettier/recommended';
4
+ import globals from 'globals';
5
+ import tseslint from 'typescript-eslint';
6
+
7
+ export default tseslint.config(
8
+ {
9
+ ignores: ['eslint.config.mjs'],
10
+ },
11
+ eslint.configs.recommended,
12
+ ...tseslint.configs.recommendedTypeChecked,
13
+ eslintPluginPrettierRecommended,
14
+ {
15
+ languageOptions: {
16
+ globals: {
17
+ ...globals.node,
18
+ ...globals.jest,
19
+ },
20
+ sourceType: 'commonjs',
21
+ parserOptions: {
22
+ projectService: true,
23
+ tsconfigRootDir: import.meta.dirname,
24
+ },
25
+ },
26
+ },
27
+ {
28
+ rules: {
29
+ '@typescript-eslint/no-explicit-any': 'off',
30
+ '@typescript-eslint/no-floating-promises': 'warn',
31
+ '@typescript-eslint/no-unsafe-argument': 'warn',
32
+ "prettier/prettier": ["error", { endOfLine: "auto" }],
33
+ },
34
+ },
35
+ );
@@ -0,0 +1,293 @@
1
+ # nestjs-audit-trail
2
+
3
+ A small, storage-agnostic audit trail module for NestJS.
4
+
5
+ If you need a reliable record of who did what, when, and what changed - without coupling your app to a specific database or ORM - this is for you.
6
+
7
+ Records are SHA256-hashed and can be chained for tamper-evidence. You bring your own persistence layer.
8
+
9
+ ## Install
10
+
11
+ ```bash
12
+ npm install nestjs-audit-trail
13
+ ```
14
+
15
+ ## Quick start
16
+
17
+ 1. Implement `IAuditStorage` (or use an adapter from a separate package).
18
+ 2. Register the module and the interceptor.
19
+ 3. Mark handlers with `@Audit({ action, entity })`.
20
+
21
+ ```typescript
22
+ // app.module.ts
23
+ import { Module } from '@nestjs/common';
24
+ import { APP_INTERCEPTOR } from '@nestjs/core';
25
+ import {
26
+ AuditTrailModule,
27
+ AuditInterceptor,
28
+ type IAuditStorage,
29
+ } from 'nestjs-audit-trail';
30
+
31
+ const myStorage: IAuditStorage = {
32
+ save: async (record) => {
33
+ // persist to your DB, queue, or object store
34
+ },
35
+ getLastHash: async () => null, // optional, for chain linking
36
+ };
37
+
38
+ @Module({
39
+ imports: [
40
+ AuditTrailModule.forRoot({
41
+ storage: myStorage,
42
+ correlationIdHeader: 'x-correlation-id', // optional
43
+ }),
44
+ ],
45
+ providers: [{ provide: APP_INTERCEPTOR, useClass: AuditInterceptor }],
46
+ })
47
+ export class AppModule {}
48
+ ```
49
+
50
+ ```typescript
51
+ // user.controller.ts
52
+ import { Controller, Post, Body } from '@nestjs/common';
53
+ import { Audit } from 'nestjs-audit-trail';
54
+
55
+ @Controller('users')
56
+ export class UserController {
57
+ @Post()
58
+ @Audit({ action: 'CREATE', entity: 'User' })
59
+ create(@Body() body: CreateUserDto) {
60
+ return this.userService.create(body);
61
+ }
62
+ }
63
+ ```
64
+
65
+ The interceptor reads `action` and `entity` from `@Audit`, and fills `actorId` (from `x-actor-id` or `request.user.id`), `correlationId` (from the configured header), `payloadBefore` (request body), and `payloadAfter` (handler result). Records are hashed and saved via your storage.
66
+
67
+ ---
68
+
69
+ ## Storage interface
70
+
71
+ All persistence is behind this contract. The core does not implement storage.
72
+
73
+ ```typescript
74
+ import type { AuditRecord, IAuditStorage } from 'nestjs-audit-trail';
75
+
76
+ const adapter: IAuditStorage = {
77
+ async save(record: AuditRecord): Promise<void> {
78
+ // Persist record (e.g. insert into table, send to queue, write to S3).
79
+ },
80
+
81
+ // Optional: return the hash of the last saved record for chain linking.
82
+ async getLastHash(): Promise<string | null> {
83
+ return null;
84
+ },
85
+ };
86
+ ```
87
+
88
+ `AuditRecord` is a plain type: `action`, `entity`, `actorId`, `correlationId`, `payloadBefore`, `payloadAfter`, `metadata`, `hash`, `createdAt`, `previousHash?`. No ORM decorators; you map it to your schema.
89
+
90
+ ---
91
+
92
+ ## Building custom storage adapters
93
+
94
+ Adapters live in **separate packages** (e.g. `@nestjs-audit-trail/typeorm`, `@nestjs-audit-trail/prisma`). Your adapter only needs to implement `IAuditStorage`.
95
+
96
+ 1. Create a new package that depends on `nestjs-audit-trail` and your persistence layer.
97
+ 2. Implement `IAuditStorage`: in `save()`, map `AuditRecord` to your model and persist; optionally implement `getLastHash()` by reading the latest row and returning its `hash`.
98
+ 3. Export a ready-to-use storage instance or factory (e.g. a function that takes a data source and returns `IAuditStorage`).
99
+ 4. In the app, install the adapter and pass its storage to `AuditTrailModule.forRoot({ storage })`.
100
+
101
+ The core stays free of ORM/database imports; only NestJS and Node `crypto` are used.
102
+
103
+ ---
104
+
105
+ ## Domain examples
106
+
107
+ Same library, different domains: use `@Audit` with actions and entities that match your domain. Pass a storage implementation that fits your stack (SQL, NoSQL, queue, object store).
108
+
109
+ ### Healthcare (e.g. PHI / HIPAA-style audit)
110
+
111
+ Audit access and changes to patient and clinical data. Use a storage backend that meets your retention and access rules.
112
+
113
+ ```typescript
114
+ @Controller('patients')
115
+ export class PatientController {
116
+ @Get(':id/records')
117
+ @Audit({ action: 'READ', entity: 'PatientRecord' })
118
+ getRecords(@Param('id') id: string) {
119
+ return this.patientService.getRecords(id);
120
+ }
121
+
122
+ @Patch(':id')
123
+ @Audit({ action: 'UPDATE', entity: 'Patient' })
124
+ updatePatient(@Param('id') id: string, @Body() dto: UpdatePatientDto) {
125
+ return this.patientService.update(id, dto);
126
+ }
127
+
128
+ @Post(':id/prescriptions')
129
+ @Audit({ action: 'CREATE', entity: 'Prescription' })
130
+ createPrescription(
131
+ @Param('id') id: string,
132
+ @Body() dto: CreatePrescriptionDto,
133
+ ) {
134
+ return this.patientService.addPrescription(id, dto);
135
+ }
136
+ }
137
+ ```
138
+
139
+ ### Ecommerce (orders, inventory, payments)
140
+
141
+ Audit order lifecycle, inventory changes, and payment events for support and disputes.
142
+
143
+ ```typescript
144
+ @Controller('orders')
145
+ export class OrderController {
146
+ @Post()
147
+ @Audit({ action: 'CREATE', entity: 'Order' })
148
+ create(@Body() dto: CreateOrderDto) {
149
+ return this.orderService.create(dto);
150
+ }
151
+
152
+ @Patch(':id/status')
153
+ @Audit({ action: 'UPDATE', entity: 'Order' })
154
+ updateStatus(@Param('id') id: string, @Body() body: { status: string }) {
155
+ return this.orderService.setStatus(id, body.status);
156
+ }
157
+ }
158
+
159
+ @Controller('inventory')
160
+ export class InventoryController {
161
+ @Patch(':sku')
162
+ @Audit({ action: 'UPDATE', entity: 'Inventory' })
163
+ adjust(@Param('sku') sku: string, @Body() dto: AdjustInventoryDto) {
164
+ return this.inventoryService.adjust(sku, dto);
165
+ }
166
+ }
167
+ ```
168
+
169
+ ### Fintech (transactions, KYC, accounts)
170
+
171
+ Audit transactions, KYC updates, and account changes for regulatory and operational traceability.
172
+
173
+ ```typescript
174
+ @Controller('transactions')
175
+ export class TransactionController {
176
+ @Post('transfer')
177
+ @Audit({ action: 'CREATE', entity: 'Transfer' })
178
+ transfer(@Body() dto: TransferDto) {
179
+ return this.transferService.execute(dto);
180
+ }
181
+ }
182
+
183
+ @Controller('accounts')
184
+ export class AccountController {
185
+ @Patch(':id')
186
+ @Audit({ action: 'UPDATE', entity: 'Account' })
187
+ updateAccount(@Param('id') id: string, @Body() dto: UpdateAccountDto) {
188
+ return this.accountService.update(id, dto);
189
+ }
190
+ }
191
+
192
+ @Controller('kyc')
193
+ export class KycController {
194
+ @Post('submit')
195
+ @Audit({ action: 'SUBMIT', entity: 'KycApplication' })
196
+ submit(@Body() dto: KycDto) {
197
+ return this.kycService.submit(dto);
198
+ }
199
+
200
+ @Patch(':id/approve')
201
+ @Audit({ action: 'APPROVE', entity: 'KycApplication' })
202
+ approve(@Param('id') id: string) {
203
+ return this.kycService.approve(id);
204
+ }
205
+ }
206
+ ```
207
+
208
+ ### Education (enrollments, grades, courses)
209
+
210
+ Audit enrollment and grade changes for academic integrity and admin audits.
211
+
212
+ ```typescript
213
+ @Controller('enrollments')
214
+ export class EnrollmentController {
215
+ @Post()
216
+ @Audit({ action: 'CREATE', entity: 'Enrollment' })
217
+ enroll(@Body() dto: EnrollDto) {
218
+ return this.enrollmentService.enroll(dto);
219
+ }
220
+
221
+ @Delete(':id')
222
+ @Audit({ action: 'DELETE', entity: 'Enrollment' })
223
+ withdraw(@Param('id') id: string) {
224
+ return this.enrollmentService.withdraw(id);
225
+ }
226
+ }
227
+
228
+ @Controller('grades')
229
+ export class GradeController {
230
+ @Put(':enrollmentId')
231
+ @Audit({ action: 'UPDATE', entity: 'Grade' })
232
+ setGrade(@Param('enrollmentId') id: string, @Body() dto: GradeDto) {
233
+ return this.gradeService.setGrade(id, dto);
234
+ }
235
+ }
236
+
237
+ @Controller('courses')
238
+ export class CourseController {
239
+ @Post()
240
+ @Audit({ action: 'CREATE', entity: 'Course' })
241
+ create(@Body() dto: CreateCourseDto) {
242
+ return this.courseService.create(dto);
243
+ }
244
+ }
245
+ ```
246
+
247
+ In every case you use the same `@Audit({ action, entity })` and one `IAuditStorage` implementation; only the semantics (and your storage backend) change per domain.
248
+
249
+ ---
250
+
251
+ ## Manual recording
252
+
253
+ You can skip the interceptor and record from services:
254
+
255
+ ```typescript
256
+ import { AuditService } from 'nestjs-audit-trail';
257
+
258
+ @Injectable()
259
+ export class MyService {
260
+ constructor(private readonly audit: AuditService) {}
261
+
262
+ async doSomething(actorId: string, correlationId: string) {
263
+ const before = { count: 0 };
264
+ // ... work ...
265
+ const after = { count: 1 };
266
+ await this.audit.record({
267
+ action: 'UPDATE',
268
+ entity: 'Counter',
269
+ actorId,
270
+ correlationId,
271
+ payloadBefore: before,
272
+ payloadAfter: after,
273
+ });
274
+ }
275
+ }
276
+ ```
277
+
278
+ ---
279
+
280
+ ## Configuration
281
+
282
+ | Option | Required | Default | Description |
283
+ | --------------------- | -------- | -------------------- | ------------------------------------ |
284
+ | `storage` | Yes | — | Your `IAuditStorage` implementation. |
285
+ | `correlationIdHeader` | No | `'x-correlation-id'` | Request header used for correlation. |
286
+
287
+ Set `x-actor-id` on the request (or ensure `request.user.id` is set) so each record has an actor. The core uses deterministic SHA256 hashing and optional chaining via `getLastHash()`; no database or ORM is required.
288
+
289
+ ---
290
+
291
+ ## License
292
+
293
+ MIT
@@ -0,0 +1,57 @@
1
+ {
2
+ "name": "nestjs-audit-trail",
3
+ "version": "1.0.0",
4
+ "description": "Lightweight audit trail module for NestJS",
5
+ "author": "Luiz Gonçalves <dev.luizh@gmail.com>",
6
+ "license": "MIT",
7
+ "main": "dist/index.js",
8
+ "types": "dist/index.d.ts",
9
+ "exports": {
10
+ ".": {
11
+ "types": "./dist/index.d.ts",
12
+ "require": "./dist/index.js",
13
+ "import": "./dist/index.js",
14
+ "default": "./dist/index.js"
15
+ }
16
+ },
17
+ "files": [
18
+ "dist",
19
+ "README.md"
20
+ ],
21
+ "scripts": {
22
+ "clean": "node -e \"require('fs').rmSync('dist', { recursive: true, force: true })\"",
23
+ "build": "tsc -p tsconfig.lib.json --outDir ./dist",
24
+ "clean:meta": "node -e \"const fs=require('fs'); ['dist/tsconfig.lib.tsbuildinfo'].forEach((file)=>{ if(fs.existsSync(file)){ fs.rmSync(file, { force: true }); } });\"",
25
+ "verify:dist": "node -e \"const fs=require('fs'); ['dist/index.js','dist/index.d.ts'].forEach((file)=>{ if(!fs.existsSync(file)){ console.error('Missing build artifact: '+file); process.exit(1); } });\"",
26
+ "prepack": "npm run clean && npm run build && npm run clean:meta && npm run verify:dist",
27
+ "pack:dry-run": "npm pack --dry-run"
28
+ },
29
+ "peerDependencies": {
30
+ "@nestjs/common": "^10.0.0 || ^11.0.0",
31
+ "@nestjs/core": "^10.0.0 || ^11.0.0",
32
+ "reflect-metadata": "^0.2.0",
33
+ "rxjs": "^7.0.0"
34
+ },
35
+ "peerDependenciesMeta": {
36
+ "reflect-metadata": {
37
+ "optional": false
38
+ },
39
+ "rxjs": {
40
+ "optional": false
41
+ }
42
+ },
43
+ "engines": {
44
+ "node": ">=18"
45
+ },
46
+ "keywords": [
47
+ "nestjs",
48
+ "audit",
49
+ "audit-trail",
50
+ "compliance",
51
+ "storage-agnostic"
52
+ ],
53
+ "repository": {
54
+ "type": "git",
55
+ "url": "https://github.com/operfildoluiz/nestjs-audit-trail.git"
56
+ }
57
+ }
@@ -0,0 +1,10 @@
1
+ /**
2
+ * Options for AuditTrailModule.forRoot.
3
+ * Used by AuditInterceptor for correlationId header and future config.
4
+ */
5
+ export interface AuditTrailOptions {
6
+ /** Request header name for correlation ID. Default: 'x-correlation-id'. */
7
+ correlationIdHeader?: string;
8
+ }
9
+
10
+ export const AUDIT_TRAIL_OPTIONS = 'AUDIT_TRAIL_OPTIONS';