echo-audit-log 1.0.0
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.
- package/LICENSE +21 -0
- package/README.md +568 -0
- package/dist/adapters/base-adapter.d.ts +13 -0
- package/dist/adapters/base-adapter.d.ts.map +1 -0
- package/dist/adapters/base-adapter.js +17 -0
- package/dist/adapters/base-adapter.js.map +1 -0
- package/dist/adapters/sequelize-adapter.d.ts +22 -0
- package/dist/adapters/sequelize-adapter.d.ts.map +1 -0
- package/dist/adapters/sequelize-adapter.js +181 -0
- package/dist/adapters/sequelize-adapter.js.map +1 -0
- package/dist/core/audit-logger.d.ts +15 -0
- package/dist/core/audit-logger.d.ts.map +1 -0
- package/dist/core/audit-logger.js +36 -0
- package/dist/core/audit-logger.js.map +1 -0
- package/dist/index.d.ts +12 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +9 -0
- package/dist/index.js.map +1 -0
- package/dist/middleware/express-middleware.d.ts +10 -0
- package/dist/middleware/express-middleware.d.ts.map +1 -0
- package/dist/middleware/express-middleware.js +37 -0
- package/dist/middleware/express-middleware.js.map +1 -0
- package/dist/migrations/create-audit-logs-table.d.ts +4 -0
- package/dist/migrations/create-audit-logs-table.d.ts.map +1 -0
- package/dist/migrations/create-audit-logs-table.js +75 -0
- package/dist/migrations/create-audit-logs-table.js.map +1 -0
- package/dist/models/audit-log.model.d.ts +29 -0
- package/dist/models/audit-log.model.d.ts.map +1 -0
- package/dist/models/audit-log.model.js +80 -0
- package/dist/models/audit-log.model.js.map +1 -0
- package/dist/types/index.d.ts +72 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types/index.js +2 -0
- package/dist/types/index.js.map +1 -0
- package/dist/utils/config-resolver.d.ts +12 -0
- package/dist/utils/config-resolver.d.ts.map +1 -0
- package/dist/utils/config-resolver.js +90 -0
- package/dist/utils/config-resolver.js.map +1 -0
- package/dist/utils/field-filter.d.ts +10 -0
- package/dist/utils/field-filter.d.ts.map +1 -0
- package/dist/utils/field-filter.js +43 -0
- package/dist/utils/field-filter.js.map +1 -0
- package/dist/utils/validation.d.ts +8 -0
- package/dist/utils/validation.d.ts.map +1 -0
- package/dist/utils/validation.js +31 -0
- package/dist/utils/validation.js.map +1 -0
- package/package.json +54 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Echo Audit Log Contributors
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,568 @@
|
|
|
1
|
+
# Echo Audit Log
|
|
2
|
+
|
|
3
|
+
**Production-grade, schema-driven audit logging utility for Node.js backend applications**
|
|
4
|
+
|
|
5
|
+
Echo Audit Log is an enterprise-ready npm package that provides centralized, configurable, low-coupling audit logging for backend applications. Built with TypeScript for Node.js v22+, it generates JavaScript modules with zero manual boilerplate while maintaining ORM-agnostic architecture through an adapter pattern.
|
|
6
|
+
|
|
7
|
+
## Features
|
|
8
|
+
|
|
9
|
+
- ✅ **Enterprise-Ready**: Production-grade, deterministic, and opinionated
|
|
10
|
+
- ✅ **Schema-Driven**: Configuration-based with strong typing
|
|
11
|
+
- ✅ **ORM-Agnostic**: Adapter pattern for multiple ORMs (Sequelize included)
|
|
12
|
+
- ✅ **Express-First**: Built-in middleware for Express.js
|
|
13
|
+
- ✅ **Fine-Grained Control**: Model, column, and operation-level configuration
|
|
14
|
+
- ✅ **Zero Coupling**: No business logic dependencies
|
|
15
|
+
- ✅ **TypeScript**: Written in TypeScript, generates JavaScript ES6 modules
|
|
16
|
+
- ✅ **Database Support**: MySQL and PostgreSQL
|
|
17
|
+
- ✅ **Comprehensive Tracking**: INSERT, UPDATE, DELETE operations
|
|
18
|
+
|
|
19
|
+
## Installation
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
npm install echo-audit-log
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
### Peer Dependencies
|
|
26
|
+
|
|
27
|
+
```bash
|
|
28
|
+
npm install sequelize
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
## Quick Start
|
|
32
|
+
|
|
33
|
+
### 1. Run Migration
|
|
34
|
+
|
|
35
|
+
Create the audit logs table in your database:
|
|
36
|
+
|
|
37
|
+
```javascript
|
|
38
|
+
import { createAuditLogsTable } from 'echo-audit-log';
|
|
39
|
+
import { sequelize } from './database.js';
|
|
40
|
+
|
|
41
|
+
await createAuditLogsTable(sequelize.getQueryInterface(), 'audit_logs');
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
Or use as a Sequelize migration:
|
|
45
|
+
|
|
46
|
+
```javascript
|
|
47
|
+
// migrations/XXXXXX-create-audit-logs.js
|
|
48
|
+
import { createAuditLogsTable, dropAuditLogsTable } from 'echo-audit-log';
|
|
49
|
+
|
|
50
|
+
export async function up(queryInterface) {
|
|
51
|
+
await createAuditLogsTable(queryInterface, 'audit_logs');
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export async function down(queryInterface) {
|
|
55
|
+
await dropAuditLogsTable(queryInterface, 'audit_logs');
|
|
56
|
+
}
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
### 2. Initialize Audit Logger
|
|
60
|
+
|
|
61
|
+
```javascript
|
|
62
|
+
import { Sequelize } from 'sequelize';
|
|
63
|
+
import { AuditLogger, SequelizeAdapter } from 'echo-audit-log';
|
|
64
|
+
|
|
65
|
+
const sequelize = new Sequelize('mysql://user:pass@localhost:3306/mydb');
|
|
66
|
+
|
|
67
|
+
const config = {
|
|
68
|
+
global: {
|
|
69
|
+
enabled: true,
|
|
70
|
+
columns: {
|
|
71
|
+
exclude: ['password', 'token', 'secret'],
|
|
72
|
+
},
|
|
73
|
+
},
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
const adapter = new SequelizeAdapter(sequelize, config);
|
|
77
|
+
const auditLogger = new AuditLogger(adapter);
|
|
78
|
+
|
|
79
|
+
await auditLogger.initialize();
|
|
80
|
+
// Note: This only creates the audit_logs table if it doesn't exist
|
|
81
|
+
// It will never drop or modify existing tables
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
### 3. Attach Hooks to Models
|
|
85
|
+
|
|
86
|
+
```javascript
|
|
87
|
+
import models from './models/index.js';
|
|
88
|
+
|
|
89
|
+
auditLogger.attachHooks(models);
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
### 4. Set Audit Context (Optional)
|
|
93
|
+
|
|
94
|
+
```javascript
|
|
95
|
+
auditLogger.setContext({
|
|
96
|
+
userId: 123,
|
|
97
|
+
requestId: 'req-abc-123',
|
|
98
|
+
ipAddress: '192.168.1.1',
|
|
99
|
+
userAgent: 'Mozilla/5.0...',
|
|
100
|
+
});
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
### 5. Use with Express Middleware
|
|
104
|
+
|
|
105
|
+
```javascript
|
|
106
|
+
import express from 'express';
|
|
107
|
+
import { createAuditMiddleware } from 'echo-audit-log';
|
|
108
|
+
|
|
109
|
+
const app = express();
|
|
110
|
+
|
|
111
|
+
const auditMiddleware = createAuditMiddleware(auditLogger, {
|
|
112
|
+
getUserId: (req) => req.user?.id,
|
|
113
|
+
getRequestId: (req) => req.headers['x-request-id'],
|
|
114
|
+
captureIp: true,
|
|
115
|
+
captureUserAgent: true,
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
app.use(auditMiddleware);
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
## Configuration
|
|
122
|
+
|
|
123
|
+
### Configuration Schema
|
|
124
|
+
|
|
125
|
+
```typescript
|
|
126
|
+
interface AuditLogConfig {
|
|
127
|
+
global?: GlobalConfig;
|
|
128
|
+
models?: {
|
|
129
|
+
[modelName: string]: ModelConfig;
|
|
130
|
+
};
|
|
131
|
+
auditTableName?: string;
|
|
132
|
+
captureMetadata?: boolean;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
interface GlobalConfig {
|
|
136
|
+
enabled?: boolean;
|
|
137
|
+
columns?: ColumnConfig;
|
|
138
|
+
operations?: OperationConfig;
|
|
139
|
+
valueStorageMode?: 'all' | 'changed';
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
interface ModelConfig {
|
|
143
|
+
enabled?: boolean;
|
|
144
|
+
columns?: ColumnConfig;
|
|
145
|
+
operations?: OperationConfig;
|
|
146
|
+
valueStorageMode?: 'all' | 'changed';
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
interface ColumnConfig {
|
|
150
|
+
include?: string[];
|
|
151
|
+
exclude?: string[];
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
interface OperationConfig {
|
|
155
|
+
insert?: boolean;
|
|
156
|
+
update?: boolean;
|
|
157
|
+
delete?: boolean;
|
|
158
|
+
}
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
### Configuration Precedence
|
|
162
|
+
|
|
163
|
+
Configuration follows a clear precedence hierarchy:
|
|
164
|
+
|
|
165
|
+
1. **Model-specific configuration** (highest priority)
|
|
166
|
+
2. **Global configuration**
|
|
167
|
+
3. **Default values** (lowest priority)
|
|
168
|
+
|
|
169
|
+
### Default Values
|
|
170
|
+
|
|
171
|
+
```javascript
|
|
172
|
+
{
|
|
173
|
+
global: {
|
|
174
|
+
enabled: true,
|
|
175
|
+
columns: {
|
|
176
|
+
include: null, // All columns
|
|
177
|
+
exclude: [],
|
|
178
|
+
},
|
|
179
|
+
operations: {
|
|
180
|
+
insert: true,
|
|
181
|
+
update: true,
|
|
182
|
+
delete: true,
|
|
183
|
+
},
|
|
184
|
+
valueStorageMode: 'all',
|
|
185
|
+
},
|
|
186
|
+
auditTableName: 'audit_logs',
|
|
187
|
+
captureMetadata: true,
|
|
188
|
+
}
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
## Configuration Examples
|
|
192
|
+
|
|
193
|
+
### Example 1: Basic Configuration
|
|
194
|
+
|
|
195
|
+
```javascript
|
|
196
|
+
const config = {
|
|
197
|
+
global: {
|
|
198
|
+
enabled: true,
|
|
199
|
+
columns: {
|
|
200
|
+
exclude: ['password', 'passwordHash', 'apiKey'],
|
|
201
|
+
},
|
|
202
|
+
},
|
|
203
|
+
};
|
|
204
|
+
```
|
|
205
|
+
|
|
206
|
+
### Example 2: Model-Specific Overrides
|
|
207
|
+
|
|
208
|
+
```javascript
|
|
209
|
+
const config = {
|
|
210
|
+
global: {
|
|
211
|
+
enabled: true,
|
|
212
|
+
columns: {
|
|
213
|
+
exclude: ['password', 'token'],
|
|
214
|
+
},
|
|
215
|
+
valueStorageMode: 'changed',
|
|
216
|
+
},
|
|
217
|
+
models: {
|
|
218
|
+
User: {
|
|
219
|
+
columns: {
|
|
220
|
+
exclude: ['password', 'passwordHash', 'resetToken'],
|
|
221
|
+
},
|
|
222
|
+
valueStorageMode: 'all',
|
|
223
|
+
},
|
|
224
|
+
Session: {
|
|
225
|
+
enabled: false,
|
|
226
|
+
},
|
|
227
|
+
Product: {
|
|
228
|
+
columns: {
|
|
229
|
+
include: ['name', 'price', 'quantity'],
|
|
230
|
+
},
|
|
231
|
+
},
|
|
232
|
+
},
|
|
233
|
+
};
|
|
234
|
+
```
|
|
235
|
+
|
|
236
|
+
### Example 3: Operation Control
|
|
237
|
+
|
|
238
|
+
```javascript
|
|
239
|
+
const config = {
|
|
240
|
+
global: {
|
|
241
|
+
operations: {
|
|
242
|
+
insert: true,
|
|
243
|
+
update: true,
|
|
244
|
+
delete: true,
|
|
245
|
+
},
|
|
246
|
+
},
|
|
247
|
+
models: {
|
|
248
|
+
Order: {
|
|
249
|
+
operations: {
|
|
250
|
+
delete: false,
|
|
251
|
+
},
|
|
252
|
+
},
|
|
253
|
+
},
|
|
254
|
+
};
|
|
255
|
+
```
|
|
256
|
+
|
|
257
|
+
### Example 4: Value Storage Modes
|
|
258
|
+
|
|
259
|
+
```javascript
|
|
260
|
+
const config = {
|
|
261
|
+
global: {
|
|
262
|
+
valueStorageMode: 'changed',
|
|
263
|
+
},
|
|
264
|
+
models: {
|
|
265
|
+
User: {
|
|
266
|
+
valueStorageMode: 'all',
|
|
267
|
+
},
|
|
268
|
+
},
|
|
269
|
+
};
|
|
270
|
+
```
|
|
271
|
+
|
|
272
|
+
**Value Storage Modes:**
|
|
273
|
+
|
|
274
|
+
- `'all'` (default): Store all field values in old_values and new_values
|
|
275
|
+
- `'changed'`: Store only changed fields in old_values and new_values
|
|
276
|
+
|
|
277
|
+
## Audit Log Table Schema
|
|
278
|
+
|
|
279
|
+
The package creates an `audit_logs` table with the following structure:
|
|
280
|
+
|
|
281
|
+
| Column | Type | Description |
|
|
282
|
+
|--------|------|-------------|
|
|
283
|
+
| `id` | BIGINT | Primary key (auto-increment) |
|
|
284
|
+
| `entity_id` | BIGINT | Primary key value of the audited model |
|
|
285
|
+
| `entity_name` | VARCHAR(255) | Name of the model |
|
|
286
|
+
| `action_type` | ENUM | Operation type (INSERT, UPDATE, DELETE) |
|
|
287
|
+
| `old_values` | JSON | Column values before operation (NULL for INSERT) |
|
|
288
|
+
| `new_values` | JSON | Column values after operation (NULL for DELETE) |
|
|
289
|
+
| `request_id` | VARCHAR(64) | Request ID for tracing |
|
|
290
|
+
| `ip_address` | VARCHAR(45) | Client IP address (IPv4/IPv6 support) |
|
|
291
|
+
| `user_agent` | VARCHAR(255) | User agent string |
|
|
292
|
+
| `action_by` | BIGINT | User ID who performed the action (NULL for background jobs) |
|
|
293
|
+
| `action_timestamp` | TIMESTAMP | When the operation was performed |
|
|
294
|
+
|
|
295
|
+
**Indexes:**
|
|
296
|
+
|
|
297
|
+
- `idx_audit_logs_entity` on (`entity_name`, `entity_id`)
|
|
298
|
+
- `idx_audit_logs_timestamp` on (`action_timestamp`)
|
|
299
|
+
- `idx_audit_logs_action_by` on (`action_by`)
|
|
300
|
+
|
|
301
|
+
## API Reference
|
|
302
|
+
|
|
303
|
+
### AuditLogger
|
|
304
|
+
|
|
305
|
+
Main class for managing audit logging.
|
|
306
|
+
|
|
307
|
+
#### Methods
|
|
308
|
+
|
|
309
|
+
- `constructor(adapter: BaseAdapter)`: Create a new audit logger
|
|
310
|
+
- `async initialize()`: Initialize the audit logger and create audit_logs table (if not exists)
|
|
311
|
+
- `attachHooks(models)`: Attach audit hooks to models
|
|
312
|
+
- `detachHooks(models)`: Detach audit hooks from models
|
|
313
|
+
- `setContext(context: AuditContext)`: Set audit context for current operation
|
|
314
|
+
- `clearContext()`: Clear audit context
|
|
315
|
+
- `getContext()`: Get current audit context
|
|
316
|
+
- `isInitialized()`: Check if logger is initialized
|
|
317
|
+
|
|
318
|
+
### SequelizeAdapter
|
|
319
|
+
|
|
320
|
+
Sequelize ORM adapter for audit logging.
|
|
321
|
+
|
|
322
|
+
#### Constructor
|
|
323
|
+
|
|
324
|
+
```javascript
|
|
325
|
+
new SequelizeAdapter(sequelize: Sequelize, config: AuditLogConfig)
|
|
326
|
+
```
|
|
327
|
+
|
|
328
|
+
### createAuditMiddleware
|
|
329
|
+
|
|
330
|
+
Express middleware factory for automatic context management.
|
|
331
|
+
|
|
332
|
+
#### Options
|
|
333
|
+
|
|
334
|
+
```typescript
|
|
335
|
+
interface AuditMiddlewareOptions {
|
|
336
|
+
getUserId?: (req: Request) => number | null | undefined;
|
|
337
|
+
getRequestId?: (req: Request) => string | undefined;
|
|
338
|
+
captureIp?: boolean;
|
|
339
|
+
captureUserAgent?: boolean;
|
|
340
|
+
}
|
|
341
|
+
```
|
|
342
|
+
|
|
343
|
+
## Advanced Usage
|
|
344
|
+
|
|
345
|
+
### Custom Adapter Implementation
|
|
346
|
+
|
|
347
|
+
Create your own adapter for other ORMs:
|
|
348
|
+
|
|
349
|
+
```javascript
|
|
350
|
+
import { BaseAdapter } from 'echo-audit-log';
|
|
351
|
+
|
|
352
|
+
class MyORMAdapter extends BaseAdapter {
|
|
353
|
+
async initialize() {
|
|
354
|
+
// Initialize audit log table
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
attachHooks(models) {
|
|
358
|
+
// Attach hooks to models
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
detachHooks(models) {
|
|
362
|
+
// Detach hooks from models
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
```
|
|
366
|
+
|
|
367
|
+
### Manual Context Management
|
|
368
|
+
|
|
369
|
+
```javascript
|
|
370
|
+
auditLogger.setContext({
|
|
371
|
+
userId: 123,
|
|
372
|
+
requestId: 'background-job-456',
|
|
373
|
+
});
|
|
374
|
+
|
|
375
|
+
await performDatabaseOperations();
|
|
376
|
+
|
|
377
|
+
auditLogger.clearContext();
|
|
378
|
+
```
|
|
379
|
+
|
|
380
|
+
### Background Jobs
|
|
381
|
+
|
|
382
|
+
For background jobs without user context:
|
|
383
|
+
|
|
384
|
+
```javascript
|
|
385
|
+
auditLogger.setContext({
|
|
386
|
+
requestId: 'cron-job-data-import',
|
|
387
|
+
});
|
|
388
|
+
|
|
389
|
+
// Your business logic here (e.g., data import, processing, etc.)
|
|
390
|
+
await importDataFromExternalSource();
|
|
391
|
+
|
|
392
|
+
auditLogger.clearContext();
|
|
393
|
+
```
|
|
394
|
+
|
|
395
|
+
> **Note**: The package does NOT provide functionality to delete or cleanup audit logs. Audit logs are immutable records and should be retained according to your compliance requirements. If you need to archive old logs, implement your own archival strategy outside of this package.
|
|
396
|
+
|
|
397
|
+
## Best Practices
|
|
398
|
+
|
|
399
|
+
### 1. Security
|
|
400
|
+
|
|
401
|
+
Always exclude sensitive fields:
|
|
402
|
+
|
|
403
|
+
```javascript
|
|
404
|
+
const config = {
|
|
405
|
+
global: {
|
|
406
|
+
columns: {
|
|
407
|
+
exclude: [
|
|
408
|
+
'password',
|
|
409
|
+
'passwordHash',
|
|
410
|
+
'apiKey',
|
|
411
|
+
'secret',
|
|
412
|
+
'token',
|
|
413
|
+
'refreshToken',
|
|
414
|
+
'resetToken',
|
|
415
|
+
'verificationToken',
|
|
416
|
+
],
|
|
417
|
+
},
|
|
418
|
+
},
|
|
419
|
+
};
|
|
420
|
+
```
|
|
421
|
+
|
|
422
|
+
### 2. Performance
|
|
423
|
+
|
|
424
|
+
For high-volume tables, consider:
|
|
425
|
+
|
|
426
|
+
- Using `valueStorageMode: 'changed'` to reduce storage
|
|
427
|
+
- Excluding non-critical models
|
|
428
|
+
|
|
429
|
+
```javascript
|
|
430
|
+
const config = {
|
|
431
|
+
global: {
|
|
432
|
+
valueStorageMode: 'changed',
|
|
433
|
+
},
|
|
434
|
+
models: {
|
|
435
|
+
HighVolumeTable: {
|
|
436
|
+
valueStorageMode: 'changed',
|
|
437
|
+
},
|
|
438
|
+
SessionTable: {
|
|
439
|
+
enabled: false,
|
|
440
|
+
},
|
|
441
|
+
},
|
|
442
|
+
};
|
|
443
|
+
```
|
|
444
|
+
|
|
445
|
+
### 3. Disable Self-Auditing
|
|
446
|
+
|
|
447
|
+
The `AuditLog` model is **automatically disabled** from auditing to prevent infinite loops. You don't need to configure this - it's built-in protection that cannot be overridden.
|
|
448
|
+
|
|
449
|
+
### 4. Request Tracing
|
|
450
|
+
|
|
451
|
+
Use request IDs for distributed tracing:
|
|
452
|
+
|
|
453
|
+
```javascript
|
|
454
|
+
const auditMiddleware = createAuditMiddleware(auditLogger, {
|
|
455
|
+
getRequestId: (req) => req.headers['x-request-id'] || req.id,
|
|
456
|
+
});
|
|
457
|
+
```
|
|
458
|
+
|
|
459
|
+
## Error Handling
|
|
460
|
+
|
|
461
|
+
The package handles errors gracefully:
|
|
462
|
+
|
|
463
|
+
- Audit failures are logged to console but don't interrupt application flow
|
|
464
|
+
- Invalid configurations throw errors during initialization
|
|
465
|
+
- Missing dependencies are caught at runtime
|
|
466
|
+
|
|
467
|
+
## Database Support
|
|
468
|
+
|
|
469
|
+
### MySQL
|
|
470
|
+
|
|
471
|
+
```javascript
|
|
472
|
+
const sequelize = new Sequelize({
|
|
473
|
+
dialect: 'mysql',
|
|
474
|
+
host: 'localhost',
|
|
475
|
+
username: 'root',
|
|
476
|
+
password: 'password',
|
|
477
|
+
database: 'myapp',
|
|
478
|
+
});
|
|
479
|
+
```
|
|
480
|
+
|
|
481
|
+
### PostgreSQL
|
|
482
|
+
|
|
483
|
+
```javascript
|
|
484
|
+
const sequelize = new Sequelize({
|
|
485
|
+
dialect: 'postgres',
|
|
486
|
+
host: 'localhost',
|
|
487
|
+
username: 'postgres',
|
|
488
|
+
password: 'password',
|
|
489
|
+
database: 'myapp',
|
|
490
|
+
});
|
|
491
|
+
```
|
|
492
|
+
|
|
493
|
+
## Querying Audit Logs
|
|
494
|
+
|
|
495
|
+
```javascript
|
|
496
|
+
import { AuditLog } from 'echo-audit-log';
|
|
497
|
+
import { Op } from 'sequelize';
|
|
498
|
+
|
|
499
|
+
const userAudits = await AuditLog.findAll({
|
|
500
|
+
where: {
|
|
501
|
+
entityName: 'User',
|
|
502
|
+
entityId: 123,
|
|
503
|
+
},
|
|
504
|
+
order: [['actionTimestamp', 'DESC']],
|
|
505
|
+
});
|
|
506
|
+
|
|
507
|
+
const recentChanges = await AuditLog.findAll({
|
|
508
|
+
where: {
|
|
509
|
+
actionType: 'UPDATE',
|
|
510
|
+
actionTimestamp: {
|
|
511
|
+
[Op.gte]: new Date(Date.now() - 24 * 60 * 60 * 1000),
|
|
512
|
+
},
|
|
513
|
+
},
|
|
514
|
+
});
|
|
515
|
+
```
|
|
516
|
+
|
|
517
|
+
## Troubleshooting
|
|
518
|
+
|
|
519
|
+
### Hooks Not Firing
|
|
520
|
+
|
|
521
|
+
Ensure hooks are attached after models are defined:
|
|
522
|
+
|
|
523
|
+
```javascript
|
|
524
|
+
await auditLogger.initialize();
|
|
525
|
+
auditLogger.attachHooks(models);
|
|
526
|
+
```
|
|
527
|
+
|
|
528
|
+
### Missing Audit Logs
|
|
529
|
+
|
|
530
|
+
Check if the model is enabled:
|
|
531
|
+
|
|
532
|
+
```javascript
|
|
533
|
+
const config = {
|
|
534
|
+
models: {
|
|
535
|
+
YourModel: {
|
|
536
|
+
enabled: true,
|
|
537
|
+
},
|
|
538
|
+
},
|
|
539
|
+
};
|
|
540
|
+
```
|
|
541
|
+
|
|
542
|
+
### Performance Issues
|
|
543
|
+
|
|
544
|
+
- Use `valueStorageMode: 'changed'`
|
|
545
|
+
- Exclude high-volume, low-value tables
|
|
546
|
+
- Add database indexes on frequently queried columns
|
|
547
|
+
|
|
548
|
+
## Requirements
|
|
549
|
+
|
|
550
|
+
- Node.js >= 22.0.0
|
|
551
|
+
- Sequelize >= 6.0.0
|
|
552
|
+
- MySQL or PostgreSQL database
|
|
553
|
+
|
|
554
|
+
## License
|
|
555
|
+
|
|
556
|
+
MIT
|
|
557
|
+
|
|
558
|
+
## Contributing
|
|
559
|
+
|
|
560
|
+
Contributions are welcome! Please open an issue or submit a pull request.
|
|
561
|
+
|
|
562
|
+
## Support
|
|
563
|
+
|
|
564
|
+
For issues, questions, or feature requests, please open an issue on GitHub.
|
|
565
|
+
|
|
566
|
+
---
|
|
567
|
+
|
|
568
|
+
**Built with ❤️ for enterprise Node.js applications**
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { AuditLogConfig, AuditContext } from '../types/index.js';
|
|
2
|
+
export declare abstract class BaseAdapter {
|
|
3
|
+
protected config: AuditLogConfig;
|
|
4
|
+
protected auditContext: AuditContext;
|
|
5
|
+
constructor(config: AuditLogConfig);
|
|
6
|
+
abstract initialize(): Promise<void>;
|
|
7
|
+
abstract attachHooks(models: any): void;
|
|
8
|
+
abstract detachHooks(models: any): void;
|
|
9
|
+
setAuditContext(context: AuditContext): void;
|
|
10
|
+
clearAuditContext(): void;
|
|
11
|
+
getAuditContext(): AuditContext;
|
|
12
|
+
}
|
|
13
|
+
//# sourceMappingURL=base-adapter.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"base-adapter.d.ts","sourceRoot":"","sources":["../../src/adapters/base-adapter.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AAEjE,8BAAsB,WAAW;IAC/B,SAAS,CAAC,MAAM,EAAE,cAAc,CAAC;IACjC,SAAS,CAAC,YAAY,EAAE,YAAY,CAAM;gBAE9B,MAAM,EAAE,cAAc;IAIlC,QAAQ,CAAC,UAAU,IAAI,OAAO,CAAC,IAAI,CAAC;IAEpC,QAAQ,CAAC,WAAW,CAAC,MAAM,EAAE,GAAG,GAAG,IAAI;IAEvC,QAAQ,CAAC,WAAW,CAAC,MAAM,EAAE,GAAG,GAAG,IAAI;IAEvC,eAAe,CAAC,OAAO,EAAE,YAAY,GAAG,IAAI;IAI5C,iBAAiB,IAAI,IAAI;IAIzB,eAAe,IAAI,YAAY;CAGhC"}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
export class BaseAdapter {
|
|
2
|
+
config;
|
|
3
|
+
auditContext = {};
|
|
4
|
+
constructor(config) {
|
|
5
|
+
this.config = config;
|
|
6
|
+
}
|
|
7
|
+
setAuditContext(context) {
|
|
8
|
+
this.auditContext = { ...this.auditContext, ...context };
|
|
9
|
+
}
|
|
10
|
+
clearAuditContext() {
|
|
11
|
+
this.auditContext = {};
|
|
12
|
+
}
|
|
13
|
+
getAuditContext() {
|
|
14
|
+
return { ...this.auditContext };
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
//# sourceMappingURL=base-adapter.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"base-adapter.js","sourceRoot":"","sources":["../../src/adapters/base-adapter.ts"],"names":[],"mappings":"AAEA,MAAM,OAAgB,WAAW;IACrB,MAAM,CAAiB;IACvB,YAAY,GAAiB,EAAE,CAAC;IAE1C,YAAY,MAAsB;QAChC,IAAI,CAAC,MAAM,GAAG,MAAM,CAAC;IACvB,CAAC;IAQD,eAAe,CAAC,OAAqB;QACnC,IAAI,CAAC,YAAY,GAAG,EAAE,GAAG,IAAI,CAAC,YAAY,EAAE,GAAG,OAAO,EAAE,CAAC;IAC3D,CAAC;IAED,iBAAiB;QACf,IAAI,CAAC,YAAY,GAAG,EAAE,CAAC;IACzB,CAAC;IAED,eAAe;QACb,OAAO,EAAE,GAAG,IAAI,CAAC,YAAY,EAAE,CAAC;IAClC,CAAC;CACF"}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { Sequelize, Model, ModelStatic } from 'sequelize';
|
|
2
|
+
import { BaseAdapter } from './base-adapter.js';
|
|
3
|
+
import { AuditLogConfig } from '../types/index.js';
|
|
4
|
+
export declare class SequelizeAdapter extends BaseAdapter {
|
|
5
|
+
private sequelize;
|
|
6
|
+
private configResolver;
|
|
7
|
+
private auditLogModel;
|
|
8
|
+
private hookRegistry;
|
|
9
|
+
constructor(sequelize: Sequelize, config: AuditLogConfig);
|
|
10
|
+
initialize(): Promise<void>;
|
|
11
|
+
attachHooks(models: Record<string, ModelStatic<Model>> | ModelStatic<Model>[]): void;
|
|
12
|
+
detachHooks(models: Record<string, ModelStatic<Model>> | ModelStatic<Model>[]): void;
|
|
13
|
+
private createAfterCreateHook;
|
|
14
|
+
private createAfterUpdateHook;
|
|
15
|
+
private createAfterDestroyHook;
|
|
16
|
+
private createAuditLog;
|
|
17
|
+
private getPrimaryKeyValue;
|
|
18
|
+
private getMetadataFields;
|
|
19
|
+
private normalizeIp;
|
|
20
|
+
private handleError;
|
|
21
|
+
}
|
|
22
|
+
//# sourceMappingURL=sequelize-adapter.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sequelize-adapter.d.ts","sourceRoot":"","sources":["../../src/adapters/sequelize-adapter.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,KAAK,EAAE,WAAW,EAAE,MAAM,WAAW,CAAC;AAC1D,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAChD,OAAO,EAAE,cAAc,EAA4B,MAAM,mBAAmB,CAAC;AAK7E,qBAAa,gBAAiB,SAAQ,WAAW;IAC/C,OAAO,CAAC,SAAS,CAAY;IAC7B,OAAO,CAAC,cAAc,CAAiB;IACvC,OAAO,CAAC,aAAa,CAAkB;IACvC,OAAO,CAAC,YAAY,CAAiC;gBAEzC,SAAS,EAAE,SAAS,EAAE,MAAM,EAAE,cAAc;IASlD,UAAU,IAAI,OAAO,CAAC,IAAI,CAAC;IAYjC,WAAW,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,WAAW,CAAC,KAAK,CAAC,CAAC,GAAG,WAAW,CAAC,KAAK,CAAC,EAAE,GAAG,IAAI;IAmCpF,WAAW,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,WAAW,CAAC,KAAK,CAAC,CAAC,GAAG,WAAW,CAAC,KAAK,CAAC,EAAE,GAAG,IAAI;IAgBpF,OAAO,CAAC,qBAAqB;IAyB7B,OAAO,CAAC,qBAAqB;IAiC7B,OAAO,CAAC,sBAAsB;YAyBhB,cAAc;IAoB5B,OAAO,CAAC,kBAAkB;IAM1B,OAAO,CAAC,iBAAiB;IAuBzB,OAAO,CAAC,WAAW;IAcnB,OAAO,CAAC,WAAW;CAGpB"}
|