@geekmidas/audit 0.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.
- package/TECHNICAL.md +937 -0
- package/dist/Auditor-CZ8lkASv.d.cts +260 -0
- package/dist/Auditor-ii62d_pF.d.mts +260 -0
- package/dist/Auditor.cjs +0 -0
- package/dist/Auditor.d.cts +2 -0
- package/dist/Auditor.d.mts +2 -0
- package/dist/Auditor.mjs +0 -0
- package/dist/DefaultAuditor-1HDUGMub.cjs +124 -0
- package/dist/DefaultAuditor-1HDUGMub.cjs.map +1 -0
- package/dist/DefaultAuditor-B9Unin1g.d.mts +59 -0
- package/dist/DefaultAuditor-BAVnNmRh.mjs +96 -0
- package/dist/DefaultAuditor-BAVnNmRh.mjs.map +1 -0
- package/dist/DefaultAuditor-Dqc4UZA1.d.cts +59 -0
- package/dist/DefaultAuditor.cjs +3 -0
- package/dist/DefaultAuditor.d.cts +4 -0
- package/dist/DefaultAuditor.d.mts +4 -0
- package/dist/DefaultAuditor.mjs +3 -0
- package/dist/index.cjs +3 -0
- package/dist/index.d.cts +4 -0
- package/dist/index.d.mts +4 -0
- package/dist/index.mjs +3 -0
- package/dist/kysely.cjs +193 -0
- package/dist/kysely.cjs.map +1 -0
- package/dist/kysely.d.cts +165 -0
- package/dist/kysely.d.mts +165 -0
- package/dist/kysely.mjs +191 -0
- package/dist/kysely.mjs.map +1 -0
- package/dist/storage-BtFY7Rha.d.cts +102 -0
- package/dist/storage-DnAMfOA7.d.mts +102 -0
- package/dist/storage.cjs +0 -0
- package/dist/storage.d.cts +3 -0
- package/dist/storage.d.mts +3 -0
- package/dist/storage.mjs +0 -0
- package/dist/types.cjs +0 -0
- package/dist/types.d.cts +2 -0
- package/dist/types.d.mts +2 -0
- package/dist/types.mjs +0 -0
- package/package.json +34 -0
- package/src/Auditor.ts +157 -0
- package/src/DefaultAuditor.ts +141 -0
- package/src/__tests__/DefaultAuditor.spec.ts +420 -0
- package/src/__tests__/KyselyAuditStorage.integration.spec.ts +517 -0
- package/src/__tests__/KyselyAuditStorage.spec.ts +359 -0
- package/src/index.ts +23 -0
- package/src/kysely.ts +391 -0
- package/src/storage.ts +101 -0
- package/src/types.ts +147 -0
|
@@ -0,0 +1,260 @@
|
|
|
1
|
+
import { InferStandardSchema } from "@geekmidas/schema";
|
|
2
|
+
import { StandardSchemaV1 } from "@standard-schema/spec";
|
|
3
|
+
|
|
4
|
+
//#region src/types.d.ts
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Represents an auditable action with a type and payload.
|
|
8
|
+
* Similar to PublishableMessage in @geekmidas/events.
|
|
9
|
+
*
|
|
10
|
+
* @template TType - The audit type/name (e.g., 'user.created')
|
|
11
|
+
* @template TPayload - The audit payload data
|
|
12
|
+
*
|
|
13
|
+
* @example
|
|
14
|
+
* ```typescript
|
|
15
|
+
* type AppAuditAction =
|
|
16
|
+
* | AuditableAction<'user.created', { userId: string; email: string }>
|
|
17
|
+
* | AuditableAction<'user.updated', { userId: string; changes: string[] }>
|
|
18
|
+
* | AuditableAction<'order.placed', { orderId: string; total: number }>;
|
|
19
|
+
* ```
|
|
20
|
+
*/
|
|
21
|
+
type AuditableAction<TType extends string, TPayload = unknown> = {
|
|
22
|
+
type: TType;
|
|
23
|
+
payload: TPayload;
|
|
24
|
+
};
|
|
25
|
+
/**
|
|
26
|
+
* Extract the type string from an AuditableAction union.
|
|
27
|
+
*/
|
|
28
|
+
type ExtractAuditType<T extends AuditableAction<string, unknown>> = T extends AuditableAction<infer TType, unknown> ? TType : never;
|
|
29
|
+
/**
|
|
30
|
+
* Extract the payload for a specific audit type from an AuditableAction union.
|
|
31
|
+
*/
|
|
32
|
+
type ExtractAuditPayload<T extends AuditableAction<string, unknown>, TType extends ExtractAuditType<T>> = T extends AuditableAction<TType, infer TPayload> ? TPayload : never;
|
|
33
|
+
/**
|
|
34
|
+
* Audit operation types for database auditing.
|
|
35
|
+
*/
|
|
36
|
+
type AuditOperation = 'INSERT' | 'UPDATE' | 'DELETE' | 'CUSTOM';
|
|
37
|
+
/**
|
|
38
|
+
* Represents the actor who performed an audited action.
|
|
39
|
+
*/
|
|
40
|
+
interface AuditActor {
|
|
41
|
+
/** Unique identifier for the actor (user ID, service ID, etc.) */
|
|
42
|
+
id?: string;
|
|
43
|
+
/** Type of actor ('user', 'system', 'service', etc.) */
|
|
44
|
+
type?: string;
|
|
45
|
+
/** Additional actor properties */
|
|
46
|
+
[key: string]: unknown;
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Metadata associated with an audit record.
|
|
50
|
+
*/
|
|
51
|
+
interface AuditMetadata {
|
|
52
|
+
/** Request correlation ID */
|
|
53
|
+
requestId?: string;
|
|
54
|
+
/** Which endpoint was called */
|
|
55
|
+
endpoint?: string;
|
|
56
|
+
/** HTTP method */
|
|
57
|
+
method?: string;
|
|
58
|
+
/** Client IP address */
|
|
59
|
+
ip?: string;
|
|
60
|
+
/** Client user agent */
|
|
61
|
+
userAgent?: string;
|
|
62
|
+
/** Additional metadata */
|
|
63
|
+
[key: string]: unknown;
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* A complete audit record representing a tracked action.
|
|
67
|
+
*/
|
|
68
|
+
interface AuditRecord<TPayload = unknown> {
|
|
69
|
+
/** Unique identifier for this audit record */
|
|
70
|
+
id: string;
|
|
71
|
+
/** Audit type (e.g., 'user.created', 'order.placed') */
|
|
72
|
+
type: string;
|
|
73
|
+
/** Operation type for database audits */
|
|
74
|
+
operation: AuditOperation;
|
|
75
|
+
/** Database table name (for database operations) */
|
|
76
|
+
table?: string;
|
|
77
|
+
/** Entity primary key(s) */
|
|
78
|
+
entityId?: string | Record<string, unknown>;
|
|
79
|
+
/** Previous state (for UPDATE/DELETE) */
|
|
80
|
+
oldValues?: Record<string, unknown>;
|
|
81
|
+
/** New state (for INSERT/UPDATE) */
|
|
82
|
+
newValues?: Record<string, unknown>;
|
|
83
|
+
/** Custom payload (for CUSTOM operations) */
|
|
84
|
+
payload?: TPayload;
|
|
85
|
+
/** When the audit was recorded */
|
|
86
|
+
timestamp: Date;
|
|
87
|
+
/** Who performed the action */
|
|
88
|
+
actor?: AuditActor;
|
|
89
|
+
/** Request context */
|
|
90
|
+
metadata?: AuditMetadata;
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* Options for manual audit calls.
|
|
94
|
+
*/
|
|
95
|
+
interface AuditOptions {
|
|
96
|
+
/** Entity primary key(s) for easier querying */
|
|
97
|
+
entityId?: string | Record<string, unknown>;
|
|
98
|
+
/** Database table name */
|
|
99
|
+
table?: string;
|
|
100
|
+
/** Operation type (defaults to 'CUSTOM') */
|
|
101
|
+
operation?: AuditOperation;
|
|
102
|
+
/** Previous state */
|
|
103
|
+
oldValues?: Record<string, unknown>;
|
|
104
|
+
/** New state */
|
|
105
|
+
newValues?: Record<string, unknown>;
|
|
106
|
+
}
|
|
107
|
+
/**
|
|
108
|
+
* Mapped audit definition for declarative auditing.
|
|
109
|
+
* Similar to MappedEvent in @geekmidas/events.
|
|
110
|
+
*/
|
|
111
|
+
interface MappedAudit<TAuditAction extends AuditableAction<string, unknown>, TOutput extends StandardSchemaV1 | undefined = undefined> {
|
|
112
|
+
/** The audit type - must be a valid type from the AuditableAction union */
|
|
113
|
+
type: ExtractAuditType<TAuditAction>;
|
|
114
|
+
/** Function to extract payload from the response */
|
|
115
|
+
payload: (response: InferStandardSchema<TOutput>) => ExtractAuditPayload<TAuditAction, ExtractAuditType<TAuditAction>>;
|
|
116
|
+
/** Optional condition - only audit if this returns true */
|
|
117
|
+
when?: (response: InferStandardSchema<TOutput>) => boolean;
|
|
118
|
+
/** Optional entity ID extractor for easier querying */
|
|
119
|
+
entityId?: (response: InferStandardSchema<TOutput>) => string | Record<string, unknown>;
|
|
120
|
+
/** Optional table name for database association */
|
|
121
|
+
table?: string;
|
|
122
|
+
}
|
|
123
|
+
/**
|
|
124
|
+
* Extract the AuditableAction type from an Auditor.
|
|
125
|
+
*/
|
|
126
|
+
type ExtractAuditorAction<T> = T extends Auditor<infer A> ? A : never;
|
|
127
|
+
//#endregion
|
|
128
|
+
//#region src/Auditor.d.ts
|
|
129
|
+
/**
|
|
130
|
+
* Interface for audit collection and flushing.
|
|
131
|
+
* Generic over audit action types for full type safety.
|
|
132
|
+
*
|
|
133
|
+
* @template TAuditAction - Union of all allowed audit action types
|
|
134
|
+
* @template TTransaction - Transaction type (e.g., Kysely Transaction)
|
|
135
|
+
*
|
|
136
|
+
* @example
|
|
137
|
+
* ```typescript
|
|
138
|
+
* type AppAuditAction =
|
|
139
|
+
* | AuditableAction<'user.created', { userId: string; email: string }>
|
|
140
|
+
* | AuditableAction<'user.updated', { userId: string; changes: string[] }>;
|
|
141
|
+
*
|
|
142
|
+
* // In endpoint handler
|
|
143
|
+
* const auditor: Auditor<AppAuditAction> = ...;
|
|
144
|
+
*
|
|
145
|
+
* // Type-safe audit calls
|
|
146
|
+
* auditor.audit('user.created', { userId: '123', email: 'test@example.com' }); // ✅
|
|
147
|
+
* auditor.audit('user.created', { orderId: '123' }); // ❌ Type error
|
|
148
|
+
* auditor.audit('unknown.type', {}); // ❌ Type error
|
|
149
|
+
* ```
|
|
150
|
+
*/
|
|
151
|
+
interface Auditor<TAuditAction extends AuditableAction<string, unknown> = AuditableAction<string, unknown>, TTransaction = unknown> {
|
|
152
|
+
/**
|
|
153
|
+
* The actor for all audits in this context.
|
|
154
|
+
* Set at construction time, immutable throughout the request.
|
|
155
|
+
*/
|
|
156
|
+
readonly actor: AuditActor;
|
|
157
|
+
/**
|
|
158
|
+
* Record a type-safe audit entry.
|
|
159
|
+
* The payload type is inferred from the audit type.
|
|
160
|
+
*
|
|
161
|
+
* @param type - The audit type (must be a valid type from TAuditAction)
|
|
162
|
+
* @param payload - The audit payload (shape enforced by type)
|
|
163
|
+
* @param options - Optional audit metadata
|
|
164
|
+
*
|
|
165
|
+
* @example
|
|
166
|
+
* ```typescript
|
|
167
|
+
* auditor.audit('user.created', {
|
|
168
|
+
* userId: '123',
|
|
169
|
+
* email: 'test@example.com',
|
|
170
|
+
* });
|
|
171
|
+
*
|
|
172
|
+
* auditor.audit('order.placed', {
|
|
173
|
+
* orderId: 'order-456',
|
|
174
|
+
* total: 99.99,
|
|
175
|
+
* }, {
|
|
176
|
+
* entityId: 'order-456',
|
|
177
|
+
* table: 'orders',
|
|
178
|
+
* });
|
|
179
|
+
* ```
|
|
180
|
+
*/
|
|
181
|
+
audit<TType extends ExtractAuditType<TAuditAction>>(type: TType, payload: ExtractAuditPayload<TAuditAction, TType>, options?: AuditOptions): void;
|
|
182
|
+
/**
|
|
183
|
+
* Record a raw audit record.
|
|
184
|
+
* Use this when you need full control over the audit structure,
|
|
185
|
+
* bypassing type safety.
|
|
186
|
+
*
|
|
187
|
+
* @param record - The audit record (id, timestamp, actor added automatically)
|
|
188
|
+
*/
|
|
189
|
+
record(record: Omit<AuditRecord, 'id' | 'timestamp' | 'actor'>): void;
|
|
190
|
+
/**
|
|
191
|
+
* Get all collected audit records.
|
|
192
|
+
* Useful for inspection or custom processing.
|
|
193
|
+
*/
|
|
194
|
+
getRecords(): AuditRecord[];
|
|
195
|
+
/**
|
|
196
|
+
* Flush all collected audits to storage.
|
|
197
|
+
* Called automatically by the endpoint adaptor inside the transaction.
|
|
198
|
+
*
|
|
199
|
+
* @param trx - Optional transaction context for atomic writes
|
|
200
|
+
*/
|
|
201
|
+
flush(trx?: TTransaction): Promise<void>;
|
|
202
|
+
/**
|
|
203
|
+
* Clear all collected audit records without flushing.
|
|
204
|
+
* Use with caution - collected audits will be lost.
|
|
205
|
+
*/
|
|
206
|
+
clear(): void;
|
|
207
|
+
/**
|
|
208
|
+
* Add metadata to all future audit records.
|
|
209
|
+
* Merges with existing metadata (new values override existing).
|
|
210
|
+
* Typically called by adaptors to add request context.
|
|
211
|
+
*
|
|
212
|
+
* @param metadata - Metadata to add (requestId, endpoint, method, ip, etc.)
|
|
213
|
+
*
|
|
214
|
+
* @example
|
|
215
|
+
* ```typescript
|
|
216
|
+
* // In endpoint adaptor
|
|
217
|
+
* auditor.addMetadata({
|
|
218
|
+
* requestId: 'req-123',
|
|
219
|
+
* endpoint: '/users',
|
|
220
|
+
* method: 'POST',
|
|
221
|
+
* ip: '192.168.1.1',
|
|
222
|
+
* });
|
|
223
|
+
* ```
|
|
224
|
+
*/
|
|
225
|
+
addMetadata(metadata: AuditMetadata): void;
|
|
226
|
+
/**
|
|
227
|
+
* Set the transaction context for audit flushing.
|
|
228
|
+
* When set, flush() will use this transaction instead of requiring
|
|
229
|
+
* it to be passed explicitly. This enables declarative audits to
|
|
230
|
+
* participate in the same transaction as the handler's database operations.
|
|
231
|
+
*
|
|
232
|
+
* @param trx - The transaction context (e.g., Kysely Transaction)
|
|
233
|
+
*
|
|
234
|
+
* @example
|
|
235
|
+
* ```typescript
|
|
236
|
+
* // In handler with explicit transaction management
|
|
237
|
+
* const result = await withTransaction(services.database.raw, async (trx) => {
|
|
238
|
+
* // Register transaction with auditor so declarative audits use it
|
|
239
|
+
* auditor.setTransaction(trx);
|
|
240
|
+
*
|
|
241
|
+
* const user = await trx.insertInto('users').values(data).returningAll().executeTakeFirstOrThrow();
|
|
242
|
+
*
|
|
243
|
+
* // Manual audits will also use this transaction when flush() is called
|
|
244
|
+
* auditor.audit('user.created', { userId: user.id });
|
|
245
|
+
*
|
|
246
|
+
* return user;
|
|
247
|
+
* });
|
|
248
|
+
* // After handler, adaptor calls auditor.flush() which uses the stored transaction
|
|
249
|
+
* ```
|
|
250
|
+
*/
|
|
251
|
+
setTransaction(trx: TTransaction): void;
|
|
252
|
+
/**
|
|
253
|
+
* Get the currently set transaction context.
|
|
254
|
+
* Returns undefined if no transaction has been set.
|
|
255
|
+
*/
|
|
256
|
+
getTransaction(): TTransaction | undefined;
|
|
257
|
+
}
|
|
258
|
+
//#endregion
|
|
259
|
+
export { AuditActor, AuditMetadata, AuditOperation, AuditOptions, AuditRecord, AuditableAction, Auditor, ExtractAuditPayload, ExtractAuditType, ExtractAuditorAction, MappedAudit };
|
|
260
|
+
//# sourceMappingURL=Auditor-CZ8lkASv.d.cts.map
|
|
@@ -0,0 +1,260 @@
|
|
|
1
|
+
import { InferStandardSchema } from "@geekmidas/schema";
|
|
2
|
+
import { StandardSchemaV1 } from "@standard-schema/spec";
|
|
3
|
+
|
|
4
|
+
//#region src/types.d.ts
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Represents an auditable action with a type and payload.
|
|
8
|
+
* Similar to PublishableMessage in @geekmidas/events.
|
|
9
|
+
*
|
|
10
|
+
* @template TType - The audit type/name (e.g., 'user.created')
|
|
11
|
+
* @template TPayload - The audit payload data
|
|
12
|
+
*
|
|
13
|
+
* @example
|
|
14
|
+
* ```typescript
|
|
15
|
+
* type AppAuditAction =
|
|
16
|
+
* | AuditableAction<'user.created', { userId: string; email: string }>
|
|
17
|
+
* | AuditableAction<'user.updated', { userId: string; changes: string[] }>
|
|
18
|
+
* | AuditableAction<'order.placed', { orderId: string; total: number }>;
|
|
19
|
+
* ```
|
|
20
|
+
*/
|
|
21
|
+
type AuditableAction<TType extends string, TPayload = unknown> = {
|
|
22
|
+
type: TType;
|
|
23
|
+
payload: TPayload;
|
|
24
|
+
};
|
|
25
|
+
/**
|
|
26
|
+
* Extract the type string from an AuditableAction union.
|
|
27
|
+
*/
|
|
28
|
+
type ExtractAuditType<T extends AuditableAction<string, unknown>> = T extends AuditableAction<infer TType, unknown> ? TType : never;
|
|
29
|
+
/**
|
|
30
|
+
* Extract the payload for a specific audit type from an AuditableAction union.
|
|
31
|
+
*/
|
|
32
|
+
type ExtractAuditPayload<T extends AuditableAction<string, unknown>, TType extends ExtractAuditType<T>> = T extends AuditableAction<TType, infer TPayload> ? TPayload : never;
|
|
33
|
+
/**
|
|
34
|
+
* Audit operation types for database auditing.
|
|
35
|
+
*/
|
|
36
|
+
type AuditOperation = 'INSERT' | 'UPDATE' | 'DELETE' | 'CUSTOM';
|
|
37
|
+
/**
|
|
38
|
+
* Represents the actor who performed an audited action.
|
|
39
|
+
*/
|
|
40
|
+
interface AuditActor {
|
|
41
|
+
/** Unique identifier for the actor (user ID, service ID, etc.) */
|
|
42
|
+
id?: string;
|
|
43
|
+
/** Type of actor ('user', 'system', 'service', etc.) */
|
|
44
|
+
type?: string;
|
|
45
|
+
/** Additional actor properties */
|
|
46
|
+
[key: string]: unknown;
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Metadata associated with an audit record.
|
|
50
|
+
*/
|
|
51
|
+
interface AuditMetadata {
|
|
52
|
+
/** Request correlation ID */
|
|
53
|
+
requestId?: string;
|
|
54
|
+
/** Which endpoint was called */
|
|
55
|
+
endpoint?: string;
|
|
56
|
+
/** HTTP method */
|
|
57
|
+
method?: string;
|
|
58
|
+
/** Client IP address */
|
|
59
|
+
ip?: string;
|
|
60
|
+
/** Client user agent */
|
|
61
|
+
userAgent?: string;
|
|
62
|
+
/** Additional metadata */
|
|
63
|
+
[key: string]: unknown;
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* A complete audit record representing a tracked action.
|
|
67
|
+
*/
|
|
68
|
+
interface AuditRecord<TPayload = unknown> {
|
|
69
|
+
/** Unique identifier for this audit record */
|
|
70
|
+
id: string;
|
|
71
|
+
/** Audit type (e.g., 'user.created', 'order.placed') */
|
|
72
|
+
type: string;
|
|
73
|
+
/** Operation type for database audits */
|
|
74
|
+
operation: AuditOperation;
|
|
75
|
+
/** Database table name (for database operations) */
|
|
76
|
+
table?: string;
|
|
77
|
+
/** Entity primary key(s) */
|
|
78
|
+
entityId?: string | Record<string, unknown>;
|
|
79
|
+
/** Previous state (for UPDATE/DELETE) */
|
|
80
|
+
oldValues?: Record<string, unknown>;
|
|
81
|
+
/** New state (for INSERT/UPDATE) */
|
|
82
|
+
newValues?: Record<string, unknown>;
|
|
83
|
+
/** Custom payload (for CUSTOM operations) */
|
|
84
|
+
payload?: TPayload;
|
|
85
|
+
/** When the audit was recorded */
|
|
86
|
+
timestamp: Date;
|
|
87
|
+
/** Who performed the action */
|
|
88
|
+
actor?: AuditActor;
|
|
89
|
+
/** Request context */
|
|
90
|
+
metadata?: AuditMetadata;
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* Options for manual audit calls.
|
|
94
|
+
*/
|
|
95
|
+
interface AuditOptions {
|
|
96
|
+
/** Entity primary key(s) for easier querying */
|
|
97
|
+
entityId?: string | Record<string, unknown>;
|
|
98
|
+
/** Database table name */
|
|
99
|
+
table?: string;
|
|
100
|
+
/** Operation type (defaults to 'CUSTOM') */
|
|
101
|
+
operation?: AuditOperation;
|
|
102
|
+
/** Previous state */
|
|
103
|
+
oldValues?: Record<string, unknown>;
|
|
104
|
+
/** New state */
|
|
105
|
+
newValues?: Record<string, unknown>;
|
|
106
|
+
}
|
|
107
|
+
/**
|
|
108
|
+
* Mapped audit definition for declarative auditing.
|
|
109
|
+
* Similar to MappedEvent in @geekmidas/events.
|
|
110
|
+
*/
|
|
111
|
+
interface MappedAudit<TAuditAction extends AuditableAction<string, unknown>, TOutput extends StandardSchemaV1 | undefined = undefined> {
|
|
112
|
+
/** The audit type - must be a valid type from the AuditableAction union */
|
|
113
|
+
type: ExtractAuditType<TAuditAction>;
|
|
114
|
+
/** Function to extract payload from the response */
|
|
115
|
+
payload: (response: InferStandardSchema<TOutput>) => ExtractAuditPayload<TAuditAction, ExtractAuditType<TAuditAction>>;
|
|
116
|
+
/** Optional condition - only audit if this returns true */
|
|
117
|
+
when?: (response: InferStandardSchema<TOutput>) => boolean;
|
|
118
|
+
/** Optional entity ID extractor for easier querying */
|
|
119
|
+
entityId?: (response: InferStandardSchema<TOutput>) => string | Record<string, unknown>;
|
|
120
|
+
/** Optional table name for database association */
|
|
121
|
+
table?: string;
|
|
122
|
+
}
|
|
123
|
+
/**
|
|
124
|
+
* Extract the AuditableAction type from an Auditor.
|
|
125
|
+
*/
|
|
126
|
+
type ExtractAuditorAction<T> = T extends Auditor<infer A> ? A : never;
|
|
127
|
+
//#endregion
|
|
128
|
+
//#region src/Auditor.d.ts
|
|
129
|
+
/**
|
|
130
|
+
* Interface for audit collection and flushing.
|
|
131
|
+
* Generic over audit action types for full type safety.
|
|
132
|
+
*
|
|
133
|
+
* @template TAuditAction - Union of all allowed audit action types
|
|
134
|
+
* @template TTransaction - Transaction type (e.g., Kysely Transaction)
|
|
135
|
+
*
|
|
136
|
+
* @example
|
|
137
|
+
* ```typescript
|
|
138
|
+
* type AppAuditAction =
|
|
139
|
+
* | AuditableAction<'user.created', { userId: string; email: string }>
|
|
140
|
+
* | AuditableAction<'user.updated', { userId: string; changes: string[] }>;
|
|
141
|
+
*
|
|
142
|
+
* // In endpoint handler
|
|
143
|
+
* const auditor: Auditor<AppAuditAction> = ...;
|
|
144
|
+
*
|
|
145
|
+
* // Type-safe audit calls
|
|
146
|
+
* auditor.audit('user.created', { userId: '123', email: 'test@example.com' }); // ✅
|
|
147
|
+
* auditor.audit('user.created', { orderId: '123' }); // ❌ Type error
|
|
148
|
+
* auditor.audit('unknown.type', {}); // ❌ Type error
|
|
149
|
+
* ```
|
|
150
|
+
*/
|
|
151
|
+
interface Auditor<TAuditAction extends AuditableAction<string, unknown> = AuditableAction<string, unknown>, TTransaction = unknown> {
|
|
152
|
+
/**
|
|
153
|
+
* The actor for all audits in this context.
|
|
154
|
+
* Set at construction time, immutable throughout the request.
|
|
155
|
+
*/
|
|
156
|
+
readonly actor: AuditActor;
|
|
157
|
+
/**
|
|
158
|
+
* Record a type-safe audit entry.
|
|
159
|
+
* The payload type is inferred from the audit type.
|
|
160
|
+
*
|
|
161
|
+
* @param type - The audit type (must be a valid type from TAuditAction)
|
|
162
|
+
* @param payload - The audit payload (shape enforced by type)
|
|
163
|
+
* @param options - Optional audit metadata
|
|
164
|
+
*
|
|
165
|
+
* @example
|
|
166
|
+
* ```typescript
|
|
167
|
+
* auditor.audit('user.created', {
|
|
168
|
+
* userId: '123',
|
|
169
|
+
* email: 'test@example.com',
|
|
170
|
+
* });
|
|
171
|
+
*
|
|
172
|
+
* auditor.audit('order.placed', {
|
|
173
|
+
* orderId: 'order-456',
|
|
174
|
+
* total: 99.99,
|
|
175
|
+
* }, {
|
|
176
|
+
* entityId: 'order-456',
|
|
177
|
+
* table: 'orders',
|
|
178
|
+
* });
|
|
179
|
+
* ```
|
|
180
|
+
*/
|
|
181
|
+
audit<TType extends ExtractAuditType<TAuditAction>>(type: TType, payload: ExtractAuditPayload<TAuditAction, TType>, options?: AuditOptions): void;
|
|
182
|
+
/**
|
|
183
|
+
* Record a raw audit record.
|
|
184
|
+
* Use this when you need full control over the audit structure,
|
|
185
|
+
* bypassing type safety.
|
|
186
|
+
*
|
|
187
|
+
* @param record - The audit record (id, timestamp, actor added automatically)
|
|
188
|
+
*/
|
|
189
|
+
record(record: Omit<AuditRecord, 'id' | 'timestamp' | 'actor'>): void;
|
|
190
|
+
/**
|
|
191
|
+
* Get all collected audit records.
|
|
192
|
+
* Useful for inspection or custom processing.
|
|
193
|
+
*/
|
|
194
|
+
getRecords(): AuditRecord[];
|
|
195
|
+
/**
|
|
196
|
+
* Flush all collected audits to storage.
|
|
197
|
+
* Called automatically by the endpoint adaptor inside the transaction.
|
|
198
|
+
*
|
|
199
|
+
* @param trx - Optional transaction context for atomic writes
|
|
200
|
+
*/
|
|
201
|
+
flush(trx?: TTransaction): Promise<void>;
|
|
202
|
+
/**
|
|
203
|
+
* Clear all collected audit records without flushing.
|
|
204
|
+
* Use with caution - collected audits will be lost.
|
|
205
|
+
*/
|
|
206
|
+
clear(): void;
|
|
207
|
+
/**
|
|
208
|
+
* Add metadata to all future audit records.
|
|
209
|
+
* Merges with existing metadata (new values override existing).
|
|
210
|
+
* Typically called by adaptors to add request context.
|
|
211
|
+
*
|
|
212
|
+
* @param metadata - Metadata to add (requestId, endpoint, method, ip, etc.)
|
|
213
|
+
*
|
|
214
|
+
* @example
|
|
215
|
+
* ```typescript
|
|
216
|
+
* // In endpoint adaptor
|
|
217
|
+
* auditor.addMetadata({
|
|
218
|
+
* requestId: 'req-123',
|
|
219
|
+
* endpoint: '/users',
|
|
220
|
+
* method: 'POST',
|
|
221
|
+
* ip: '192.168.1.1',
|
|
222
|
+
* });
|
|
223
|
+
* ```
|
|
224
|
+
*/
|
|
225
|
+
addMetadata(metadata: AuditMetadata): void;
|
|
226
|
+
/**
|
|
227
|
+
* Set the transaction context for audit flushing.
|
|
228
|
+
* When set, flush() will use this transaction instead of requiring
|
|
229
|
+
* it to be passed explicitly. This enables declarative audits to
|
|
230
|
+
* participate in the same transaction as the handler's database operations.
|
|
231
|
+
*
|
|
232
|
+
* @param trx - The transaction context (e.g., Kysely Transaction)
|
|
233
|
+
*
|
|
234
|
+
* @example
|
|
235
|
+
* ```typescript
|
|
236
|
+
* // In handler with explicit transaction management
|
|
237
|
+
* const result = await withTransaction(services.database.raw, async (trx) => {
|
|
238
|
+
* // Register transaction with auditor so declarative audits use it
|
|
239
|
+
* auditor.setTransaction(trx);
|
|
240
|
+
*
|
|
241
|
+
* const user = await trx.insertInto('users').values(data).returningAll().executeTakeFirstOrThrow();
|
|
242
|
+
*
|
|
243
|
+
* // Manual audits will also use this transaction when flush() is called
|
|
244
|
+
* auditor.audit('user.created', { userId: user.id });
|
|
245
|
+
*
|
|
246
|
+
* return user;
|
|
247
|
+
* });
|
|
248
|
+
* // After handler, adaptor calls auditor.flush() which uses the stored transaction
|
|
249
|
+
* ```
|
|
250
|
+
*/
|
|
251
|
+
setTransaction(trx: TTransaction): void;
|
|
252
|
+
/**
|
|
253
|
+
* Get the currently set transaction context.
|
|
254
|
+
* Returns undefined if no transaction has been set.
|
|
255
|
+
*/
|
|
256
|
+
getTransaction(): TTransaction | undefined;
|
|
257
|
+
}
|
|
258
|
+
//#endregion
|
|
259
|
+
export { AuditActor, AuditMetadata, AuditOperation, AuditOptions, AuditRecord, AuditableAction, Auditor, ExtractAuditPayload, ExtractAuditType, ExtractAuditorAction, MappedAudit };
|
|
260
|
+
//# sourceMappingURL=Auditor-ii62d_pF.d.mts.map
|
package/dist/Auditor.cjs
ADDED
|
File without changes
|
package/dist/Auditor.mjs
ADDED
|
File without changes
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
//#region rolldown:runtime
|
|
2
|
+
var __create = Object.create;
|
|
3
|
+
var __defProp = Object.defineProperty;
|
|
4
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
5
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
7
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
8
|
+
var __copyProps = (to, from, except, desc) => {
|
|
9
|
+
if (from && typeof from === "object" || typeof from === "function") for (var keys = __getOwnPropNames(from), i = 0, n = keys.length, key; i < n; i++) {
|
|
10
|
+
key = keys[i];
|
|
11
|
+
if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, {
|
|
12
|
+
get: ((k) => from[k]).bind(null, key),
|
|
13
|
+
enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable
|
|
14
|
+
});
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", {
|
|
19
|
+
value: mod,
|
|
20
|
+
enumerable: true
|
|
21
|
+
}) : target, mod));
|
|
22
|
+
|
|
23
|
+
//#endregion
|
|
24
|
+
const nanoid = __toESM(require("nanoid"));
|
|
25
|
+
|
|
26
|
+
//#region src/DefaultAuditor.ts
|
|
27
|
+
/**
|
|
28
|
+
* Default implementation of the Auditor interface.
|
|
29
|
+
* Collects audit records in memory and flushes to storage.
|
|
30
|
+
*
|
|
31
|
+
* @template TAuditAction - Union of all allowed audit action types
|
|
32
|
+
* @template TTransaction - Transaction type (e.g., Kysely Transaction)
|
|
33
|
+
*
|
|
34
|
+
* @example
|
|
35
|
+
* ```typescript
|
|
36
|
+
* const auditor = new DefaultAuditor<AppAuditAction>({
|
|
37
|
+
* actor: { id: 'user-123', type: 'user' },
|
|
38
|
+
* storage: auditStorage,
|
|
39
|
+
* metadata: { requestId: 'req-456', endpoint: '/users' },
|
|
40
|
+
* });
|
|
41
|
+
*
|
|
42
|
+
* auditor.audit('user.created', { userId: '789', email: 'test@example.com' });
|
|
43
|
+
*
|
|
44
|
+
* // Flush inside transaction
|
|
45
|
+
* await auditor.flush(trx);
|
|
46
|
+
* ```
|
|
47
|
+
*/
|
|
48
|
+
var DefaultAuditor = class {
|
|
49
|
+
actor;
|
|
50
|
+
storage;
|
|
51
|
+
metadata;
|
|
52
|
+
generateId;
|
|
53
|
+
records = [];
|
|
54
|
+
transaction;
|
|
55
|
+
constructor(config) {
|
|
56
|
+
this.actor = config.actor;
|
|
57
|
+
this.storage = config.storage;
|
|
58
|
+
this.metadata = config.metadata;
|
|
59
|
+
this.generateId = config.generateId ?? (() => (0, nanoid.nanoid)());
|
|
60
|
+
}
|
|
61
|
+
audit(type, payload, options) {
|
|
62
|
+
const record = {
|
|
63
|
+
id: this.generateId(),
|
|
64
|
+
type,
|
|
65
|
+
operation: options?.operation ?? "CUSTOM",
|
|
66
|
+
table: options?.table,
|
|
67
|
+
entityId: options?.entityId,
|
|
68
|
+
oldValues: options?.oldValues,
|
|
69
|
+
newValues: options?.newValues,
|
|
70
|
+
payload,
|
|
71
|
+
timestamp: /* @__PURE__ */ new Date(),
|
|
72
|
+
actor: this.actor,
|
|
73
|
+
metadata: this.metadata
|
|
74
|
+
};
|
|
75
|
+
this.records.push(record);
|
|
76
|
+
}
|
|
77
|
+
record(record) {
|
|
78
|
+
const fullRecord = {
|
|
79
|
+
...record,
|
|
80
|
+
id: this.generateId(),
|
|
81
|
+
timestamp: /* @__PURE__ */ new Date(),
|
|
82
|
+
actor: this.actor,
|
|
83
|
+
metadata: this.metadata ? {
|
|
84
|
+
...this.metadata,
|
|
85
|
+
...record.metadata
|
|
86
|
+
} : record.metadata
|
|
87
|
+
};
|
|
88
|
+
this.records.push(fullRecord);
|
|
89
|
+
}
|
|
90
|
+
getRecords() {
|
|
91
|
+
return [...this.records];
|
|
92
|
+
}
|
|
93
|
+
async flush(trx) {
|
|
94
|
+
if (this.records.length === 0) return;
|
|
95
|
+
const recordsToFlush = [...this.records];
|
|
96
|
+
this.records = [];
|
|
97
|
+
const transactionToUse = trx ?? this.transaction;
|
|
98
|
+
await this.storage.write(recordsToFlush, transactionToUse);
|
|
99
|
+
}
|
|
100
|
+
setTransaction(trx) {
|
|
101
|
+
this.transaction = trx;
|
|
102
|
+
}
|
|
103
|
+
getTransaction() {
|
|
104
|
+
return this.transaction;
|
|
105
|
+
}
|
|
106
|
+
clear() {
|
|
107
|
+
this.records = [];
|
|
108
|
+
}
|
|
109
|
+
addMetadata(metadata) {
|
|
110
|
+
this.metadata = this.metadata ? {
|
|
111
|
+
...this.metadata,
|
|
112
|
+
...metadata
|
|
113
|
+
} : metadata;
|
|
114
|
+
}
|
|
115
|
+
};
|
|
116
|
+
|
|
117
|
+
//#endregion
|
|
118
|
+
Object.defineProperty(exports, 'DefaultAuditor', {
|
|
119
|
+
enumerable: true,
|
|
120
|
+
get: function () {
|
|
121
|
+
return DefaultAuditor;
|
|
122
|
+
}
|
|
123
|
+
});
|
|
124
|
+
//# sourceMappingURL=DefaultAuditor-1HDUGMub.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"DefaultAuditor-1HDUGMub.cjs","names":["config: DefaultAuditorConfig","type: TType","payload: ExtractAuditPayload<TAuditAction, TType>","options?: AuditOptions","record: AuditRecord","record: Omit<AuditRecord, 'id' | 'timestamp' | 'actor'>","fullRecord: AuditRecord","trx?: TTransaction","trx: TTransaction","metadata: AuditMetadata"],"sources":["../src/DefaultAuditor.ts"],"sourcesContent":["import { nanoid } from 'nanoid';\nimport type { Auditor } from './Auditor';\nimport type { AuditStorage } from './storage';\nimport type {\n AuditableAction,\n AuditActor,\n AuditMetadata,\n AuditOptions,\n AuditRecord,\n ExtractAuditPayload,\n ExtractAuditType,\n} from './types';\n\n/**\n * Configuration for DefaultAuditor.\n */\nexport interface DefaultAuditorConfig {\n /** The actor performing audits (set at construction, immutable) */\n actor: AuditActor;\n /** Storage backend for persisting audits */\n storage: AuditStorage;\n /** Optional metadata to attach to all audits */\n metadata?: AuditMetadata;\n /** Optional custom ID generator (defaults to nanoid) */\n generateId?: () => string;\n}\n\n/**\n * Default implementation of the Auditor interface.\n * Collects audit records in memory and flushes to storage.\n *\n * @template TAuditAction - Union of all allowed audit action types\n * @template TTransaction - Transaction type (e.g., Kysely Transaction)\n *\n * @example\n * ```typescript\n * const auditor = new DefaultAuditor<AppAuditAction>({\n * actor: { id: 'user-123', type: 'user' },\n * storage: auditStorage,\n * metadata: { requestId: 'req-456', endpoint: '/users' },\n * });\n *\n * auditor.audit('user.created', { userId: '789', email: 'test@example.com' });\n *\n * // Flush inside transaction\n * await auditor.flush(trx);\n * ```\n */\nexport class DefaultAuditor<\n TAuditAction extends AuditableAction<string, unknown> = AuditableAction<\n string,\n unknown\n >,\n TTransaction = unknown,\n> implements Auditor<TAuditAction, TTransaction>\n{\n readonly actor: AuditActor;\n private readonly storage: AuditStorage;\n private metadata?: AuditMetadata;\n private readonly generateId: () => string;\n private records: AuditRecord[] = [];\n private transaction?: TTransaction;\n\n constructor(config: DefaultAuditorConfig) {\n this.actor = config.actor;\n this.storage = config.storage;\n this.metadata = config.metadata;\n this.generateId = config.generateId ?? (() => nanoid());\n }\n\n audit<TType extends ExtractAuditType<TAuditAction>>(\n type: TType,\n payload: ExtractAuditPayload<TAuditAction, TType>,\n options?: AuditOptions,\n ): void {\n const record: AuditRecord = {\n id: this.generateId(),\n type,\n operation: options?.operation ?? 'CUSTOM',\n table: options?.table,\n entityId: options?.entityId,\n oldValues: options?.oldValues,\n newValues: options?.newValues,\n payload,\n timestamp: new Date(),\n actor: this.actor,\n metadata: this.metadata,\n };\n\n this.records.push(record);\n }\n\n record(record: Omit<AuditRecord, 'id' | 'timestamp' | 'actor'>): void {\n const fullRecord: AuditRecord = {\n ...record,\n id: this.generateId(),\n timestamp: new Date(),\n actor: this.actor,\n metadata: this.metadata\n ? { ...this.metadata, ...record.metadata }\n : record.metadata,\n };\n\n this.records.push(fullRecord);\n }\n\n getRecords(): AuditRecord[] {\n return [...this.records];\n }\n\n async flush(trx?: TTransaction): Promise<void> {\n if (this.records.length === 0) {\n return;\n }\n\n const recordsToFlush = [...this.records];\n this.records = [];\n\n // Use explicitly passed transaction, or fall back to stored transaction\n const transactionToUse = trx ?? this.transaction;\n await this.storage.write(recordsToFlush, transactionToUse);\n }\n\n setTransaction(trx: TTransaction): void {\n this.transaction = trx;\n }\n\n getTransaction(): TTransaction | undefined {\n return this.transaction;\n }\n\n clear(): void {\n this.records = [];\n }\n\n addMetadata(metadata: AuditMetadata): void {\n this.metadata = this.metadata\n ? { ...this.metadata, ...metadata }\n : metadata;\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAgDA,IAAa,iBAAb,MAOA;CACE,AAAS;CACT,AAAiB;CACjB,AAAQ;CACR,AAAiB;CACjB,AAAQ,UAAyB,CAAE;CACnC,AAAQ;CAER,YAAYA,QAA8B;AACxC,OAAK,QAAQ,OAAO;AACpB,OAAK,UAAU,OAAO;AACtB,OAAK,WAAW,OAAO;AACvB,OAAK,aAAa,OAAO,eAAe,MAAM,oBAAQ;CACvD;CAED,MACEC,MACAC,SACAC,SACM;EACN,MAAMC,SAAsB;GAC1B,IAAI,KAAK,YAAY;GACrB;GACA,WAAW,SAAS,aAAa;GACjC,OAAO,SAAS;GAChB,UAAU,SAAS;GACnB,WAAW,SAAS;GACpB,WAAW,SAAS;GACpB;GACA,2BAAW,IAAI;GACf,OAAO,KAAK;GACZ,UAAU,KAAK;EAChB;AAED,OAAK,QAAQ,KAAK,OAAO;CAC1B;CAED,OAAOC,QAA+D;EACpE,MAAMC,aAA0B;GAC9B,GAAG;GACH,IAAI,KAAK,YAAY;GACrB,2BAAW,IAAI;GACf,OAAO,KAAK;GACZ,UAAU,KAAK,WACX;IAAE,GAAG,KAAK;IAAU,GAAG,OAAO;GAAU,IACxC,OAAO;EACZ;AAED,OAAK,QAAQ,KAAK,WAAW;CAC9B;CAED,aAA4B;AAC1B,SAAO,CAAC,GAAG,KAAK,OAAQ;CACzB;CAED,MAAM,MAAMC,KAAmC;AAC7C,MAAI,KAAK,QAAQ,WAAW,EAC1B;EAGF,MAAM,iBAAiB,CAAC,GAAG,KAAK,OAAQ;AACxC,OAAK,UAAU,CAAE;EAGjB,MAAM,mBAAmB,OAAO,KAAK;AACrC,QAAM,KAAK,QAAQ,MAAM,gBAAgB,iBAAiB;CAC3D;CAED,eAAeC,KAAyB;AACtC,OAAK,cAAc;CACpB;CAED,iBAA2C;AACzC,SAAO,KAAK;CACb;CAED,QAAc;AACZ,OAAK,UAAU,CAAE;CAClB;CAED,YAAYC,UAA+B;AACzC,OAAK,WAAW,KAAK,WACjB;GAAE,GAAG,KAAK;GAAU,GAAG;EAAU,IACjC;CACL;AACF"}
|