@geekmidas/audit 0.0.7 → 0.1.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/README.md +397 -0
- package/dist/{Auditor-D3me-qKX.d.mts → Auditor-DrR0ImvJ.d.cts} +43 -1
- package/dist/{Auditor-QYUMGJCH.d.cts → Auditor-Xf4Tp63p.d.mts} +43 -1
- package/dist/Auditor.d.cts +1 -1
- package/dist/Auditor.d.mts +1 -1
- package/dist/{DefaultAuditor-BTuMMiWh.d.cts → DefaultAuditor-3Q1zVd1b.d.mts} +2 -2
- package/dist/{DefaultAuditor-C1FWrJg6.d.mts → DefaultAuditor-BaZa4u_m.d.cts} +2 -2
- package/dist/DefaultAuditor.d.cts +2 -2
- package/dist/DefaultAuditor.d.mts +2 -2
- package/dist/cache-0AjJ0zis.d.cts +95 -0
- package/dist/cache-BbIl31RL.mjs +167 -0
- package/dist/cache-BbIl31RL.mjs.map +1 -0
- package/dist/cache-DBbGcCEq.d.mts +95 -0
- package/dist/cache-l7jAptBl.cjs +173 -0
- package/dist/cache-l7jAptBl.cjs.map +1 -0
- package/dist/cache.cjs +3 -0
- package/dist/cache.d.cts +3 -0
- package/dist/cache.d.mts +3 -0
- package/dist/cache.mjs +3 -0
- package/dist/index.d.cts +2 -2
- package/dist/index.d.mts +2 -2
- package/dist/kysely.cjs +25 -1
- package/dist/kysely.cjs.map +1 -1
- package/dist/kysely.d.cts +10 -1
- package/dist/kysely.d.mts +10 -1
- package/dist/kysely.mjs +25 -1
- package/dist/kysely.mjs.map +1 -1
- package/dist/memory.cjs +49 -0
- package/dist/memory.cjs.map +1 -0
- package/dist/memory.d.cts +43 -0
- package/dist/memory.d.mts +43 -0
- package/dist/memory.mjs +48 -0
- package/dist/memory.mjs.map +1 -0
- package/dist/storage.d.cts +1 -1
- package/dist/storage.d.mts +1 -1
- package/dist/types.d.cts +1 -1
- package/dist/types.d.mts +1 -1
- package/package.json +33 -5
- package/src/__tests__/CacheAuditStorage.spec.ts +382 -0
- package/src/__tests__/InMemoryAuditStorage.spec.ts +317 -0
- package/src/__tests__/KyselyAuditStorage.integration.spec.ts +24 -20
- package/src/cache.ts +263 -0
- package/src/kysely.ts +33 -0
- package/src/memory.ts +50 -0
- package/src/storage.ts +47 -0
package/README.md
ADDED
|
@@ -0,0 +1,397 @@
|
|
|
1
|
+
# @geekmidas/audit
|
|
2
|
+
|
|
3
|
+
> Type-safe audit logging with database integration for tracking application events and user actions
|
|
4
|
+
|
|
5
|
+
## Overview
|
|
6
|
+
|
|
7
|
+
`@geekmidas/audit` provides a comprehensive solution for recording and persisting audit trails in your application. It supports type-safe audit actions, transactional writes, and flexible storage backends.
|
|
8
|
+
|
|
9
|
+
## Features
|
|
10
|
+
|
|
11
|
+
- **Type-safe Audit Actions**: Define audit types with TypeScript for compile-time safety
|
|
12
|
+
- **Transactional Support**: Flush audits atomically within database transactions
|
|
13
|
+
- **Flexible Storage**: Pluggable storage interface (Kysely, in-memory, cache)
|
|
14
|
+
- **Actor Tracking**: Record who performed each action (users, services, systems)
|
|
15
|
+
- **Rich Metadata**: Attach request context, entity references, and custom data
|
|
16
|
+
- **Query Support**: Query audit logs with filters, pagination, and sorting
|
|
17
|
+
|
|
18
|
+
## Installation
|
|
19
|
+
|
|
20
|
+
```bash
|
|
21
|
+
npm install @geekmidas/audit
|
|
22
|
+
# or
|
|
23
|
+
pnpm add @geekmidas/audit
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
## Quick Start
|
|
27
|
+
|
|
28
|
+
### 1. Define Your Audit Actions
|
|
29
|
+
|
|
30
|
+
```typescript
|
|
31
|
+
import type { AuditableAction } from '@geekmidas/audit';
|
|
32
|
+
|
|
33
|
+
// Define type-safe audit actions
|
|
34
|
+
type AppAuditAction =
|
|
35
|
+
| AuditableAction<'user.created', { userId: string; email: string }>
|
|
36
|
+
| AuditableAction<'user.updated', { userId: string; changes: string[] }>
|
|
37
|
+
| AuditableAction<'order.placed', { orderId: string; total: number }>;
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
### 2. Set Up Storage
|
|
41
|
+
|
|
42
|
+
**For Development/Testing (InMemoryAuditStorage):**
|
|
43
|
+
|
|
44
|
+
```typescript
|
|
45
|
+
import { InMemoryAuditStorage } from '@geekmidas/audit/memory';
|
|
46
|
+
|
|
47
|
+
const storage = new InMemoryAuditStorage<AppAuditAction>();
|
|
48
|
+
|
|
49
|
+
// Query stored records
|
|
50
|
+
const records = await storage.query({ type: 'user.created' });
|
|
51
|
+
|
|
52
|
+
// Clear all records (useful in tests)
|
|
53
|
+
storage.clear();
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
**For Production (KyselyAuditStorage):**
|
|
57
|
+
|
|
58
|
+
```typescript
|
|
59
|
+
import { KyselyAuditStorage } from '@geekmidas/audit/kysely';
|
|
60
|
+
|
|
61
|
+
// Define your database schema
|
|
62
|
+
interface Database {
|
|
63
|
+
audit_logs: AuditLogTable;
|
|
64
|
+
// ... other tables
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
const storage = new KyselyAuditStorage<Database>({
|
|
68
|
+
db: kyselyDb,
|
|
69
|
+
tableName: 'audit_logs',
|
|
70
|
+
});
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
### 3. Create an Auditor
|
|
74
|
+
|
|
75
|
+
```typescript
|
|
76
|
+
import { DefaultAuditor } from '@geekmidas/audit';
|
|
77
|
+
|
|
78
|
+
const auditor = new DefaultAuditor<AppAuditAction>({
|
|
79
|
+
actor: { id: 'user-123', type: 'user' },
|
|
80
|
+
storage,
|
|
81
|
+
metadata: {
|
|
82
|
+
requestId: 'req-456',
|
|
83
|
+
endpoint: '/api/users',
|
|
84
|
+
},
|
|
85
|
+
});
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
### 4. Record Audits
|
|
89
|
+
|
|
90
|
+
```typescript
|
|
91
|
+
// Type-safe audit calls - TypeScript enforces correct payload shapes
|
|
92
|
+
auditor.audit('user.created', {
|
|
93
|
+
userId: '789',
|
|
94
|
+
email: 'test@example.com',
|
|
95
|
+
}); // OK
|
|
96
|
+
|
|
97
|
+
auditor.audit('user.created', {
|
|
98
|
+
orderId: '123', // Type error - wrong payload shape
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
// Flush to storage
|
|
102
|
+
await auditor.flush();
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
## Transactional Audits
|
|
106
|
+
|
|
107
|
+
Use `withAuditableTransaction` to ensure audits are atomic with your database operations:
|
|
108
|
+
|
|
109
|
+
```typescript
|
|
110
|
+
import { withAuditableTransaction } from '@geekmidas/audit/kysely';
|
|
111
|
+
|
|
112
|
+
const result = await withAuditableTransaction(
|
|
113
|
+
db,
|
|
114
|
+
auditor,
|
|
115
|
+
async (trx) => {
|
|
116
|
+
// Database operations
|
|
117
|
+
const user = await trx
|
|
118
|
+
.insertInto('users')
|
|
119
|
+
.values({ name: 'John', email: 'john@example.com' })
|
|
120
|
+
.returningAll()
|
|
121
|
+
.executeTakeFirstOrThrow();
|
|
122
|
+
|
|
123
|
+
// Audit is recorded
|
|
124
|
+
auditor.audit('user.created', {
|
|
125
|
+
userId: user.id,
|
|
126
|
+
email: user.email,
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
return user;
|
|
130
|
+
},
|
|
131
|
+
);
|
|
132
|
+
// Audits are automatically flushed before transaction commits
|
|
133
|
+
// If flush fails, the entire transaction rolls back
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
## API Reference
|
|
137
|
+
|
|
138
|
+
### Core Types
|
|
139
|
+
|
|
140
|
+
#### `AuditableAction<TType, TPayload>`
|
|
141
|
+
|
|
142
|
+
Defines an auditable action with a type and payload:
|
|
143
|
+
|
|
144
|
+
```typescript
|
|
145
|
+
type UserAction = AuditableAction<'user.created', { userId: string }>;
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
#### `AuditRecord`
|
|
149
|
+
|
|
150
|
+
Complete audit record with all metadata:
|
|
151
|
+
|
|
152
|
+
```typescript
|
|
153
|
+
interface AuditRecord<TPayload = unknown> {
|
|
154
|
+
id: string;
|
|
155
|
+
type: string;
|
|
156
|
+
operation: AuditOperation;
|
|
157
|
+
table?: string;
|
|
158
|
+
entityId?: string | Record<string, unknown>;
|
|
159
|
+
oldValues?: Record<string, unknown>;
|
|
160
|
+
newValues?: Record<string, unknown>;
|
|
161
|
+
payload?: TPayload;
|
|
162
|
+
timestamp: Date;
|
|
163
|
+
actor?: AuditActor;
|
|
164
|
+
metadata?: AuditMetadata;
|
|
165
|
+
}
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
#### `AuditActor`
|
|
169
|
+
|
|
170
|
+
Represents who performed the action:
|
|
171
|
+
|
|
172
|
+
```typescript
|
|
173
|
+
interface AuditActor {
|
|
174
|
+
id?: string;
|
|
175
|
+
type?: string;
|
|
176
|
+
[key: string]: unknown;
|
|
177
|
+
}
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
#### `AuditMetadata`
|
|
181
|
+
|
|
182
|
+
Request context and additional data:
|
|
183
|
+
|
|
184
|
+
```typescript
|
|
185
|
+
interface AuditMetadata {
|
|
186
|
+
requestId?: string;
|
|
187
|
+
endpoint?: string;
|
|
188
|
+
method?: string;
|
|
189
|
+
ip?: string;
|
|
190
|
+
userAgent?: string;
|
|
191
|
+
[key: string]: unknown;
|
|
192
|
+
}
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
### `Auditor` Interface
|
|
196
|
+
|
|
197
|
+
```typescript
|
|
198
|
+
interface Auditor<TAuditAction, TTransaction> {
|
|
199
|
+
readonly actor: AuditActor;
|
|
200
|
+
|
|
201
|
+
// Type-safe audit recording
|
|
202
|
+
audit<TType extends ExtractAuditType<TAuditAction>>(
|
|
203
|
+
type: TType,
|
|
204
|
+
payload: ExtractAuditPayload<TAuditAction, TType>,
|
|
205
|
+
options?: AuditOptions,
|
|
206
|
+
): void;
|
|
207
|
+
|
|
208
|
+
// Raw record insertion
|
|
209
|
+
record(record: Omit<AuditRecord, 'id' | 'timestamp' | 'actor'>): void;
|
|
210
|
+
|
|
211
|
+
// Get collected records
|
|
212
|
+
getRecords(): AuditRecord[];
|
|
213
|
+
|
|
214
|
+
// Flush to storage
|
|
215
|
+
flush(trx?: TTransaction): Promise<void>;
|
|
216
|
+
|
|
217
|
+
// Clear without flushing
|
|
218
|
+
clear(): void;
|
|
219
|
+
|
|
220
|
+
// Add metadata to future records
|
|
221
|
+
addMetadata(metadata: AuditMetadata): void;
|
|
222
|
+
|
|
223
|
+
// Transaction management
|
|
224
|
+
setTransaction(trx: TTransaction): void;
|
|
225
|
+
getTransaction(): TTransaction | undefined;
|
|
226
|
+
}
|
|
227
|
+
```
|
|
228
|
+
|
|
229
|
+
### `AuditStorage` Interface
|
|
230
|
+
|
|
231
|
+
Implement this interface for custom storage backends:
|
|
232
|
+
|
|
233
|
+
```typescript
|
|
234
|
+
interface AuditStorage<TAuditAction> {
|
|
235
|
+
// Required: Write records
|
|
236
|
+
write(records: AuditRecord[], trx?: unknown): Promise<void>;
|
|
237
|
+
|
|
238
|
+
// Optional: Query records
|
|
239
|
+
query?(options: AuditQueryOptions): Promise<AuditRecord[]>;
|
|
240
|
+
|
|
241
|
+
// Optional: Count records
|
|
242
|
+
count?(options: Omit<AuditQueryOptions, 'limit' | 'offset'>): Promise<number>;
|
|
243
|
+
|
|
244
|
+
// Optional: Get database for transactions
|
|
245
|
+
getDatabase?(): unknown;
|
|
246
|
+
}
|
|
247
|
+
```
|
|
248
|
+
|
|
249
|
+
### `KyselyAuditStorage`
|
|
250
|
+
|
|
251
|
+
Built-in Kysely storage implementation:
|
|
252
|
+
|
|
253
|
+
```typescript
|
|
254
|
+
const storage = new KyselyAuditStorage({
|
|
255
|
+
db: kyselyDb,
|
|
256
|
+
tableName: 'audit_logs',
|
|
257
|
+
databaseServiceName: 'database', // Optional: for automatic transaction injection
|
|
258
|
+
autoId: false, // Optional: let database generate IDs
|
|
259
|
+
});
|
|
260
|
+
|
|
261
|
+
// Query audits
|
|
262
|
+
const audits = await storage.query({
|
|
263
|
+
type: 'user.created',
|
|
264
|
+
actorId: 'user-123',
|
|
265
|
+
from: new Date('2024-01-01'),
|
|
266
|
+
limit: 100,
|
|
267
|
+
orderBy: 'timestamp',
|
|
268
|
+
orderDirection: 'desc',
|
|
269
|
+
});
|
|
270
|
+
|
|
271
|
+
// Count audits
|
|
272
|
+
const count = await storage.count({
|
|
273
|
+
type: ['user.created', 'user.updated'],
|
|
274
|
+
actorId: 'user-123',
|
|
275
|
+
});
|
|
276
|
+
```
|
|
277
|
+
|
|
278
|
+
### `InMemoryAuditStorage`
|
|
279
|
+
|
|
280
|
+
Convenience wrapper around `CacheAuditStorage` with `InMemoryCache`. Useful for testing and development:
|
|
281
|
+
|
|
282
|
+
```typescript
|
|
283
|
+
import { InMemoryAuditStorage } from '@geekmidas/audit/memory';
|
|
284
|
+
|
|
285
|
+
const storage = new InMemoryAuditStorage<AppAuditAction>();
|
|
286
|
+
|
|
287
|
+
// Query audits (same API as other storages)
|
|
288
|
+
const audits = await storage.query({
|
|
289
|
+
type: 'user.created',
|
|
290
|
+
limit: 10,
|
|
291
|
+
});
|
|
292
|
+
|
|
293
|
+
// Get all records (for assertions in tests)
|
|
294
|
+
const allRecords = await storage.getRecords();
|
|
295
|
+
|
|
296
|
+
// Clear all records (reset for next test)
|
|
297
|
+
await storage.clear();
|
|
298
|
+
```
|
|
299
|
+
|
|
300
|
+
### `CacheAuditStorage`
|
|
301
|
+
|
|
302
|
+
Cache-based storage using any `@geekmidas/cache` implementation:
|
|
303
|
+
|
|
304
|
+
```typescript
|
|
305
|
+
import { CacheAuditStorage } from '@geekmidas/audit/cache';
|
|
306
|
+
import { InMemoryCache } from '@geekmidas/cache/memory';
|
|
307
|
+
import { UpstashCache } from '@geekmidas/cache/upstash';
|
|
308
|
+
|
|
309
|
+
// With in-memory cache (development/testing)
|
|
310
|
+
const storage = new CacheAuditStorage({
|
|
311
|
+
cache: new InMemoryCache(),
|
|
312
|
+
ttl: 86400, // 24 hours
|
|
313
|
+
});
|
|
314
|
+
|
|
315
|
+
// With Upstash Redis (production - distributed systems)
|
|
316
|
+
const storage = new CacheAuditStorage({
|
|
317
|
+
cache: new UpstashCache({
|
|
318
|
+
url: process.env.UPSTASH_REDIS_URL,
|
|
319
|
+
token: process.env.UPSTASH_REDIS_TOKEN,
|
|
320
|
+
}),
|
|
321
|
+
prefix: 'audit', // Optional key prefix
|
|
322
|
+
ttl: 604800, // 7 days
|
|
323
|
+
});
|
|
324
|
+
|
|
325
|
+
// Query and count work the same as other storages
|
|
326
|
+
const audits = await storage.query({ type: 'user.created' });
|
|
327
|
+
const count = await storage.count({ actorId: 'user-123' });
|
|
328
|
+
|
|
329
|
+
// Clear all records
|
|
330
|
+
await storage.clear();
|
|
331
|
+
```
|
|
332
|
+
|
|
333
|
+
## Database Schema
|
|
334
|
+
|
|
335
|
+
Create an `audit_logs` table for `KyselyAuditStorage`:
|
|
336
|
+
|
|
337
|
+
```sql
|
|
338
|
+
CREATE TABLE audit_logs (
|
|
339
|
+
id VARCHAR(21) PRIMARY KEY, -- or use gen_random_uuid() with autoId: true
|
|
340
|
+
type VARCHAR(255) NOT NULL,
|
|
341
|
+
operation VARCHAR(20) NOT NULL,
|
|
342
|
+
"table" VARCHAR(255),
|
|
343
|
+
"entityId" VARCHAR(255),
|
|
344
|
+
"oldValues" JSONB,
|
|
345
|
+
"newValues" JSONB,
|
|
346
|
+
payload JSONB,
|
|
347
|
+
timestamp TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
|
348
|
+
"actorId" VARCHAR(255),
|
|
349
|
+
"actorType" VARCHAR(50),
|
|
350
|
+
"actorData" JSONB,
|
|
351
|
+
metadata JSONB
|
|
352
|
+
);
|
|
353
|
+
|
|
354
|
+
-- Recommended indexes
|
|
355
|
+
CREATE INDEX idx_audit_logs_type ON audit_logs(type);
|
|
356
|
+
CREATE INDEX idx_audit_logs_timestamp ON audit_logs(timestamp);
|
|
357
|
+
CREATE INDEX idx_audit_logs_actor_id ON audit_logs("actorId");
|
|
358
|
+
CREATE INDEX idx_audit_logs_entity_id ON audit_logs("entityId");
|
|
359
|
+
```
|
|
360
|
+
|
|
361
|
+
## Integration with @geekmidas/constructs
|
|
362
|
+
|
|
363
|
+
The audit package integrates seamlessly with `@geekmidas/constructs` endpoints:
|
|
364
|
+
|
|
365
|
+
```typescript
|
|
366
|
+
import { e } from '@geekmidas/constructs/endpoints';
|
|
367
|
+
|
|
368
|
+
const endpoint = e
|
|
369
|
+
.post('/users')
|
|
370
|
+
.body(UserSchema)
|
|
371
|
+
.output(UserResponseSchema)
|
|
372
|
+
.audit([
|
|
373
|
+
{
|
|
374
|
+
type: 'user.created',
|
|
375
|
+
payload: (response) => ({
|
|
376
|
+
userId: response.id,
|
|
377
|
+
email: response.email,
|
|
378
|
+
}),
|
|
379
|
+
},
|
|
380
|
+
])
|
|
381
|
+
.handle(async ({ body, auditor }) => {
|
|
382
|
+
// Audits are automatically recorded and flushed
|
|
383
|
+
return { id: '123', ...body };
|
|
384
|
+
});
|
|
385
|
+
```
|
|
386
|
+
|
|
387
|
+
## Best Practices
|
|
388
|
+
|
|
389
|
+
1. **Define all audit actions upfront**: Create a union type of all possible audit actions for your application
|
|
390
|
+
2. **Use transactions**: Wrap database operations and audits in transactions for consistency
|
|
391
|
+
3. **Include entity references**: Use `entityId` and `table` options for easier querying
|
|
392
|
+
4. **Add request context**: Include request IDs, endpoints, and IPs in metadata
|
|
393
|
+
5. **Use meaningful types**: Name audit types with domain context (e.g., `order.shipped` not `update`)
|
|
394
|
+
|
|
395
|
+
## License
|
|
396
|
+
|
|
397
|
+
MIT
|
|
@@ -117,6 +117,48 @@ interface AuditStorage<TAuditAction extends AuditableAction<string, unknown> = A
|
|
|
117
117
|
* ```
|
|
118
118
|
*/
|
|
119
119
|
databaseServiceName?: string;
|
|
120
|
+
/**
|
|
121
|
+
* Optional: Execute a callback within a database transaction.
|
|
122
|
+
* The auditor is registered with the transaction and audits are flushed
|
|
123
|
+
* before the transaction commits.
|
|
124
|
+
*
|
|
125
|
+
* This is database-agnostic - each storage implementation provides its own
|
|
126
|
+
* transaction handling based on the underlying database.
|
|
127
|
+
*
|
|
128
|
+
* If a database connection is provided, it should be used instead of the
|
|
129
|
+
* storage's internal connection. If the connection is already a transaction,
|
|
130
|
+
* it should be reused instead of creating a nested transaction.
|
|
131
|
+
*
|
|
132
|
+
* @param auditor - The auditor to register with the transaction
|
|
133
|
+
* @param callback - The callback to execute within the transaction
|
|
134
|
+
* @param db - Optional database connection (may already be a transaction)
|
|
135
|
+
* @returns The result of the callback
|
|
136
|
+
*
|
|
137
|
+
* @example
|
|
138
|
+
* ```typescript
|
|
139
|
+
* // KyselyAuditStorage implementation
|
|
140
|
+
* async withTransaction<T>(auditor, callback, db) {
|
|
141
|
+
* const connection = db ?? this.db;
|
|
142
|
+
* if (connection.isTransaction) {
|
|
143
|
+
* // Reuse existing transaction
|
|
144
|
+
* auditor.setTransaction(connection);
|
|
145
|
+
* const result = await callback();
|
|
146
|
+
* await auditor.flush(connection);
|
|
147
|
+
* return result;
|
|
148
|
+
* }
|
|
149
|
+
* return connection.transaction().execute(async (trx) => {
|
|
150
|
+
* auditor.setTransaction(trx);
|
|
151
|
+
* const result = await callback();
|
|
152
|
+
* await auditor.flush(trx);
|
|
153
|
+
* return result;
|
|
154
|
+
* });
|
|
155
|
+
* }
|
|
156
|
+
* ```
|
|
157
|
+
*/
|
|
158
|
+
withTransaction?<T>(auditor: {
|
|
159
|
+
setTransaction(trx: unknown): void;
|
|
160
|
+
flush(trx?: unknown): Promise<void>;
|
|
161
|
+
}, callback: () => Promise<T>, db?: unknown): Promise<T>;
|
|
120
162
|
}
|
|
121
163
|
//#endregion
|
|
122
164
|
//#region src/types.d.ts
|
|
@@ -378,4 +420,4 @@ interface Auditor<TAuditAction extends AuditableAction<string, unknown> = Audita
|
|
|
378
420
|
}
|
|
379
421
|
//#endregion
|
|
380
422
|
export { AuditActor, AuditMetadata, AuditOperation, AuditOptions, AuditQueryOptions, AuditRecord, AuditStorage, AuditableAction, Auditor, ExtractAuditPayload, ExtractAuditType, ExtractAuditorAction, ExtractStorageAuditAction, MappedAudit };
|
|
381
|
-
//# sourceMappingURL=Auditor-
|
|
423
|
+
//# sourceMappingURL=Auditor-DrR0ImvJ.d.cts.map
|
|
@@ -117,6 +117,48 @@ interface AuditStorage<TAuditAction extends AuditableAction<string, unknown> = A
|
|
|
117
117
|
* ```
|
|
118
118
|
*/
|
|
119
119
|
databaseServiceName?: string;
|
|
120
|
+
/**
|
|
121
|
+
* Optional: Execute a callback within a database transaction.
|
|
122
|
+
* The auditor is registered with the transaction and audits are flushed
|
|
123
|
+
* before the transaction commits.
|
|
124
|
+
*
|
|
125
|
+
* This is database-agnostic - each storage implementation provides its own
|
|
126
|
+
* transaction handling based on the underlying database.
|
|
127
|
+
*
|
|
128
|
+
* If a database connection is provided, it should be used instead of the
|
|
129
|
+
* storage's internal connection. If the connection is already a transaction,
|
|
130
|
+
* it should be reused instead of creating a nested transaction.
|
|
131
|
+
*
|
|
132
|
+
* @param auditor - The auditor to register with the transaction
|
|
133
|
+
* @param callback - The callback to execute within the transaction
|
|
134
|
+
* @param db - Optional database connection (may already be a transaction)
|
|
135
|
+
* @returns The result of the callback
|
|
136
|
+
*
|
|
137
|
+
* @example
|
|
138
|
+
* ```typescript
|
|
139
|
+
* // KyselyAuditStorage implementation
|
|
140
|
+
* async withTransaction<T>(auditor, callback, db) {
|
|
141
|
+
* const connection = db ?? this.db;
|
|
142
|
+
* if (connection.isTransaction) {
|
|
143
|
+
* // Reuse existing transaction
|
|
144
|
+
* auditor.setTransaction(connection);
|
|
145
|
+
* const result = await callback();
|
|
146
|
+
* await auditor.flush(connection);
|
|
147
|
+
* return result;
|
|
148
|
+
* }
|
|
149
|
+
* return connection.transaction().execute(async (trx) => {
|
|
150
|
+
* auditor.setTransaction(trx);
|
|
151
|
+
* const result = await callback();
|
|
152
|
+
* await auditor.flush(trx);
|
|
153
|
+
* return result;
|
|
154
|
+
* });
|
|
155
|
+
* }
|
|
156
|
+
* ```
|
|
157
|
+
*/
|
|
158
|
+
withTransaction?<T>(auditor: {
|
|
159
|
+
setTransaction(trx: unknown): void;
|
|
160
|
+
flush(trx?: unknown): Promise<void>;
|
|
161
|
+
}, callback: () => Promise<T>, db?: unknown): Promise<T>;
|
|
120
162
|
}
|
|
121
163
|
//#endregion
|
|
122
164
|
//#region src/types.d.ts
|
|
@@ -378,4 +420,4 @@ interface Auditor<TAuditAction extends AuditableAction<string, unknown> = Audita
|
|
|
378
420
|
}
|
|
379
421
|
//#endregion
|
|
380
422
|
export { AuditActor, AuditMetadata, AuditOperation, AuditOptions, AuditQueryOptions, AuditRecord, AuditStorage, AuditableAction, Auditor, ExtractAuditPayload, ExtractAuditType, ExtractAuditorAction, ExtractStorageAuditAction, MappedAudit };
|
|
381
|
-
//# sourceMappingURL=Auditor-
|
|
423
|
+
//# sourceMappingURL=Auditor-Xf4Tp63p.d.mts.map
|
package/dist/Auditor.d.cts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { Auditor } from "./Auditor-
|
|
1
|
+
import { Auditor } from "./Auditor-DrR0ImvJ.cjs";
|
|
2
2
|
export { Auditor };
|
package/dist/Auditor.d.mts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { Auditor } from "./Auditor-
|
|
1
|
+
import { Auditor } from "./Auditor-Xf4Tp63p.mjs";
|
|
2
2
|
export { Auditor };
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { AuditActor, AuditMetadata, AuditOptions, AuditRecord, AuditStorage, AuditableAction, Auditor, ExtractAuditPayload, ExtractAuditType } from "./Auditor-
|
|
1
|
+
import { AuditActor, AuditMetadata, AuditOptions, AuditRecord, AuditStorage, AuditableAction, Auditor, ExtractAuditPayload, ExtractAuditType } from "./Auditor-Xf4Tp63p.mjs";
|
|
2
2
|
|
|
3
3
|
//#region src/DefaultAuditor.d.ts
|
|
4
4
|
|
|
@@ -55,4 +55,4 @@ declare class DefaultAuditor<TAuditAction extends AuditableAction<string, unknow
|
|
|
55
55
|
}
|
|
56
56
|
//#endregion
|
|
57
57
|
export { DefaultAuditor, DefaultAuditorConfig };
|
|
58
|
-
//# sourceMappingURL=DefaultAuditor-
|
|
58
|
+
//# sourceMappingURL=DefaultAuditor-3Q1zVd1b.d.mts.map
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { AuditActor, AuditMetadata, AuditOptions, AuditRecord, AuditStorage, AuditableAction, Auditor, ExtractAuditPayload, ExtractAuditType } from "./Auditor-
|
|
1
|
+
import { AuditActor, AuditMetadata, AuditOptions, AuditRecord, AuditStorage, AuditableAction, Auditor, ExtractAuditPayload, ExtractAuditType } from "./Auditor-DrR0ImvJ.cjs";
|
|
2
2
|
|
|
3
3
|
//#region src/DefaultAuditor.d.ts
|
|
4
4
|
|
|
@@ -55,4 +55,4 @@ declare class DefaultAuditor<TAuditAction extends AuditableAction<string, unknow
|
|
|
55
55
|
}
|
|
56
56
|
//#endregion
|
|
57
57
|
export { DefaultAuditor, DefaultAuditorConfig };
|
|
58
|
-
//# sourceMappingURL=DefaultAuditor-
|
|
58
|
+
//# sourceMappingURL=DefaultAuditor-BaZa4u_m.d.cts.map
|
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
import "./Auditor-
|
|
2
|
-
import { DefaultAuditor, DefaultAuditorConfig } from "./DefaultAuditor-
|
|
1
|
+
import "./Auditor-DrR0ImvJ.cjs";
|
|
2
|
+
import { DefaultAuditor, DefaultAuditorConfig } from "./DefaultAuditor-BaZa4u_m.cjs";
|
|
3
3
|
export { DefaultAuditor, DefaultAuditorConfig };
|
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
import "./Auditor-
|
|
2
|
-
import { DefaultAuditor, DefaultAuditorConfig } from "./DefaultAuditor-
|
|
1
|
+
import "./Auditor-Xf4Tp63p.mjs";
|
|
2
|
+
import { DefaultAuditor, DefaultAuditorConfig } from "./DefaultAuditor-3Q1zVd1b.mjs";
|
|
3
3
|
export { DefaultAuditor, DefaultAuditorConfig };
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import { AuditQueryOptions, AuditRecord, AuditStorage, AuditableAction } from "./Auditor-DrR0ImvJ.cjs";
|
|
2
|
+
import { Cache } from "@geekmidas/cache";
|
|
3
|
+
|
|
4
|
+
//#region src/cache.d.ts
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Configuration for CacheAuditStorage.
|
|
8
|
+
*/
|
|
9
|
+
interface CacheAuditStorageConfig {
|
|
10
|
+
/** Cache instance to use for storage */
|
|
11
|
+
cache: Cache;
|
|
12
|
+
/**
|
|
13
|
+
* Key prefix for audit records.
|
|
14
|
+
* Records are stored as `${prefix}:${id}`.
|
|
15
|
+
* @default 'audit'
|
|
16
|
+
*/
|
|
17
|
+
prefix?: string;
|
|
18
|
+
/**
|
|
19
|
+
* TTL (time-to-live) in seconds for audit records.
|
|
20
|
+
* If not set, records use the cache's default TTL.
|
|
21
|
+
*/
|
|
22
|
+
ttl?: number;
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Cache-based audit storage implementation.
|
|
26
|
+
* Uses any @geekmidas/cache implementation for storage.
|
|
27
|
+
*
|
|
28
|
+
* Best suited for:
|
|
29
|
+
* - Development and testing
|
|
30
|
+
* - Temporary audit logs that don't need persistence
|
|
31
|
+
* - Applications with existing cache infrastructure
|
|
32
|
+
* - Distributed systems needing shared audit state (with Redis/Upstash)
|
|
33
|
+
*
|
|
34
|
+
* Note: Query performance may degrade with large numbers of records
|
|
35
|
+
* since filtering happens in memory after fetching all records.
|
|
36
|
+
*
|
|
37
|
+
* @template TAuditAction - Optional type parameter for type-safe audit actions.
|
|
38
|
+
*
|
|
39
|
+
* @example
|
|
40
|
+
* ```typescript
|
|
41
|
+
* import { CacheAuditStorage } from '@geekmidas/audit/cache';
|
|
42
|
+
* import { InMemoryCache } from '@geekmidas/cache/memory';
|
|
43
|
+
* import { UpstashCache } from '@geekmidas/cache/upstash';
|
|
44
|
+
*
|
|
45
|
+
* // With in-memory cache (development/testing)
|
|
46
|
+
* const storage = new CacheAuditStorage({
|
|
47
|
+
* cache: new InMemoryCache(),
|
|
48
|
+
* ttl: 86400, // 24 hours
|
|
49
|
+
* });
|
|
50
|
+
*
|
|
51
|
+
* // With Upstash Redis (production)
|
|
52
|
+
* const storage = new CacheAuditStorage({
|
|
53
|
+
* cache: new UpstashCache({
|
|
54
|
+
* url: process.env.UPSTASH_REDIS_URL,
|
|
55
|
+
* token: process.env.UPSTASH_REDIS_TOKEN,
|
|
56
|
+
* }),
|
|
57
|
+
* ttl: 604800, // 7 days
|
|
58
|
+
* });
|
|
59
|
+
* ```
|
|
60
|
+
*/
|
|
61
|
+
declare class CacheAuditStorage<TAuditAction extends AuditableAction<string, unknown> = AuditableAction<string, unknown>> implements AuditStorage<TAuditAction> {
|
|
62
|
+
private readonly cache;
|
|
63
|
+
private readonly prefix;
|
|
64
|
+
private readonly ttl?;
|
|
65
|
+
private readonly indexKey;
|
|
66
|
+
constructor(config: CacheAuditStorageConfig);
|
|
67
|
+
/**
|
|
68
|
+
* Write audit records to cache.
|
|
69
|
+
*/
|
|
70
|
+
write(records: AuditRecord[]): Promise<void>;
|
|
71
|
+
/**
|
|
72
|
+
* Query audit records from cache.
|
|
73
|
+
*/
|
|
74
|
+
query(options: AuditQueryOptions): Promise<AuditRecord[]>;
|
|
75
|
+
/**
|
|
76
|
+
* Count audit records matching filters.
|
|
77
|
+
*/
|
|
78
|
+
count(options: Omit<AuditQueryOptions, 'limit' | 'offset'>): Promise<number>;
|
|
79
|
+
/**
|
|
80
|
+
* Get all stored records (for testing/debugging).
|
|
81
|
+
*/
|
|
82
|
+
getRecords(): Promise<AuditRecord[]>;
|
|
83
|
+
/**
|
|
84
|
+
* Clear all stored records.
|
|
85
|
+
*/
|
|
86
|
+
clear(): Promise<void>;
|
|
87
|
+
private getRecordKey;
|
|
88
|
+
private getAllRecords;
|
|
89
|
+
private serializeRecord;
|
|
90
|
+
private deserializeRecord;
|
|
91
|
+
private applyFilters;
|
|
92
|
+
}
|
|
93
|
+
//#endregion
|
|
94
|
+
export { CacheAuditStorage, CacheAuditStorageConfig };
|
|
95
|
+
//# sourceMappingURL=cache-0AjJ0zis.d.cts.map
|