@geekmidas/audit 0.0.8 → 0.2.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-_Dn2dp8d.d.mts} +46 -1
- package/dist/Auditor-_Dn2dp8d.d.mts.map +1 -0
- package/dist/{Auditor-QYUMGJCH.d.cts → Auditor-sW7YAJIA.d.cts} +46 -1
- package/dist/Auditor-sW7YAJIA.d.cts.map +1 -0
- package/dist/Auditor.d.cts +1 -1
- package/dist/Auditor.d.mts +1 -1
- package/dist/DefaultAuditor-BAVnNmRh.mjs.map +1 -1
- package/dist/{DefaultAuditor-BTuMMiWh.d.cts → DefaultAuditor-BSYMwojG.d.cts} +3 -2
- package/dist/DefaultAuditor-BSYMwojG.d.cts.map +1 -0
- package/dist/{DefaultAuditor-C1FWrJg6.d.mts → DefaultAuditor-CYE-uAry.d.mts} +3 -2
- package/dist/DefaultAuditor-CYE-uAry.d.mts.map +1 -0
- package/dist/DefaultAuditor-JbZ_BQfh.cjs.map +1 -1
- package/dist/DefaultAuditor.d.cts +2 -2
- package/dist/DefaultAuditor.d.mts +2 -2
- package/dist/cache-2VI80Vao.d.mts +96 -0
- package/dist/cache-2VI80Vao.d.mts.map +1 -0
- package/dist/cache-BbIl31RL.mjs +167 -0
- package/dist/cache-BbIl31RL.mjs.map +1 -0
- package/dist/cache-CDHqXqcl.d.cts +96 -0
- package/dist/cache-CDHqXqcl.d.cts.map +1 -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 +24 -0
- package/dist/kysely.cjs.map +1 -1
- package/dist/kysely.d.cts +11 -1
- package/dist/kysely.d.cts.map +1 -0
- package/dist/kysely.d.mts +11 -1
- package/dist/kysely.d.mts.map +1 -0
- package/dist/kysely.mjs +24 -0
- 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 +44 -0
- package/dist/memory.d.cts.map +1 -0
- package/dist/memory.d.mts +44 -0
- package/dist/memory.d.mts.map +1 -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/Auditor.ts +121 -121
- package/src/DefaultAuditor.ts +91 -91
- package/src/__tests__/CacheAuditStorage.spec.ts +382 -0
- package/src/__tests__/DefaultAuditor.spec.ts +520 -462
- package/src/__tests__/InMemoryAuditStorage.spec.ts +317 -0
- package/src/__tests__/KyselyAuditStorage.integration.spec.ts +500 -496
- package/src/__tests__/KyselyAuditStorage.spec.ts +433 -433
- package/src/cache.ts +263 -0
- package/src/index.ts +14 -15
- package/src/kysely.ts +292 -259
- package/src/memory.ts +50 -0
- package/src/storage.ts +132 -85
- package/src/types.ts +73 -74
- package/tsconfig.json +9 -0
package/src/storage.ts
CHANGED
|
@@ -1,29 +1,29 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { AuditableAction, AuditRecord } from './types';
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
4
|
* Options for querying audit records.
|
|
5
5
|
*/
|
|
6
6
|
export interface AuditQueryOptions {
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
7
|
+
/** Filter by audit type */
|
|
8
|
+
type?: string | string[];
|
|
9
|
+
/** Filter by entity ID */
|
|
10
|
+
entityId?: string | Record<string, unknown>;
|
|
11
|
+
/** Filter by table name */
|
|
12
|
+
table?: string;
|
|
13
|
+
/** Filter by actor ID */
|
|
14
|
+
actorId?: string;
|
|
15
|
+
/** Filter by date range (start) */
|
|
16
|
+
from?: Date;
|
|
17
|
+
/** Filter by date range (end) */
|
|
18
|
+
to?: Date;
|
|
19
|
+
/** Limit number of results */
|
|
20
|
+
limit?: number;
|
|
21
|
+
/** Offset for pagination */
|
|
22
|
+
offset?: number;
|
|
23
|
+
/** Sort order */
|
|
24
|
+
orderBy?: 'timestamp' | 'type';
|
|
25
|
+
/** Sort direction */
|
|
26
|
+
orderDirection?: 'asc' | 'desc';
|
|
27
27
|
}
|
|
28
28
|
|
|
29
29
|
/**
|
|
@@ -56,73 +56,120 @@ export interface AuditQueryOptions {
|
|
|
56
56
|
* extracted by EndpointBuilder to provide type inference for `.audit([...])`.
|
|
57
57
|
*/
|
|
58
58
|
export interface AuditStorage<
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
59
|
+
TAuditAction extends AuditableAction<string, unknown> = AuditableAction<
|
|
60
|
+
string,
|
|
61
|
+
unknown
|
|
62
|
+
>,
|
|
63
63
|
> {
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
64
|
+
/** @internal Type marker for extracting audit action type */
|
|
65
|
+
readonly __auditActionType?: TAuditAction;
|
|
66
|
+
/**
|
|
67
|
+
* Write audit records to storage.
|
|
68
|
+
* Called by Auditor.flush() to persist collected audits.
|
|
69
|
+
*
|
|
70
|
+
* @param records - The audit records to write
|
|
71
|
+
* @param trx - Optional transaction context (for same-DB storage)
|
|
72
|
+
*/
|
|
73
|
+
write(records: AuditRecord[], trx?: unknown): Promise<void>;
|
|
74
74
|
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
75
|
+
/**
|
|
76
|
+
* Optional: Query audit records for retrieval.
|
|
77
|
+
* Implement this for audit log viewing/searching.
|
|
78
|
+
*
|
|
79
|
+
* @param options - Query filters and pagination
|
|
80
|
+
* @returns Matching audit records
|
|
81
|
+
*/
|
|
82
|
+
query?(options: AuditQueryOptions): Promise<AuditRecord[]>;
|
|
83
83
|
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
84
|
+
/**
|
|
85
|
+
* Optional: Count audit records matching filters.
|
|
86
|
+
* Useful for pagination.
|
|
87
|
+
*
|
|
88
|
+
* @param options - Query filters (limit/offset ignored)
|
|
89
|
+
* @returns Count of matching records
|
|
90
|
+
*/
|
|
91
|
+
count?(options: Omit<AuditQueryOptions, 'limit' | 'offset'>): Promise<number>;
|
|
92
92
|
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
93
|
+
/**
|
|
94
|
+
* Optional: Get the database connection for transactional audit writes.
|
|
95
|
+
* When implemented, the endpoint adaptor can automatically wrap handlers
|
|
96
|
+
* in a transaction, ensuring audits are atomic with other database operations.
|
|
97
|
+
*
|
|
98
|
+
* @returns Database connection (e.g., Kysely instance)
|
|
99
|
+
*
|
|
100
|
+
* @example
|
|
101
|
+
* ```typescript
|
|
102
|
+
* class KyselyAuditStorage implements AuditStorage {
|
|
103
|
+
* constructor(private db: Kysely<DB>) {}
|
|
104
|
+
*
|
|
105
|
+
* getDatabase() {
|
|
106
|
+
* return this.db;
|
|
107
|
+
* }
|
|
108
|
+
* }
|
|
109
|
+
* ```
|
|
110
|
+
*/
|
|
111
|
+
getDatabase?(): unknown;
|
|
112
112
|
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
113
|
+
/**
|
|
114
|
+
* Optional: The service name of the database service used by this storage.
|
|
115
|
+
* When set, endpoint adaptors will automatically use the audit transaction as `db`
|
|
116
|
+
* in the handler context if the endpoint's database service has the same name.
|
|
117
|
+
*
|
|
118
|
+
* @example
|
|
119
|
+
* ```typescript
|
|
120
|
+
* const storage = new KyselyAuditStorage({
|
|
121
|
+
* db,
|
|
122
|
+
* tableName: 'audit_logs',
|
|
123
|
+
* databaseServiceName: 'database', // Matches databaseService.serviceName
|
|
124
|
+
* });
|
|
125
|
+
* ```
|
|
126
|
+
*/
|
|
127
|
+
databaseServiceName?: string;
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* Optional: Execute a callback within a database transaction.
|
|
131
|
+
* The auditor is registered with the transaction and audits are flushed
|
|
132
|
+
* before the transaction commits.
|
|
133
|
+
*
|
|
134
|
+
* This is database-agnostic - each storage implementation provides its own
|
|
135
|
+
* transaction handling based on the underlying database.
|
|
136
|
+
*
|
|
137
|
+
* If a database connection is provided, it should be used instead of the
|
|
138
|
+
* storage's internal connection. If the connection is already a transaction,
|
|
139
|
+
* it should be reused instead of creating a nested transaction.
|
|
140
|
+
*
|
|
141
|
+
* @param auditor - The auditor to register with the transaction
|
|
142
|
+
* @param callback - The callback to execute within the transaction
|
|
143
|
+
* @param db - Optional database connection (may already be a transaction)
|
|
144
|
+
* @returns The result of the callback
|
|
145
|
+
*
|
|
146
|
+
* @example
|
|
147
|
+
* ```typescript
|
|
148
|
+
* // KyselyAuditStorage implementation
|
|
149
|
+
* async withTransaction<T>(auditor, callback, db) {
|
|
150
|
+
* const connection = db ?? this.db;
|
|
151
|
+
* if (connection.isTransaction) {
|
|
152
|
+
* // Reuse existing transaction
|
|
153
|
+
* auditor.setTransaction(connection);
|
|
154
|
+
* const result = await callback();
|
|
155
|
+
* await auditor.flush(connection);
|
|
156
|
+
* return result;
|
|
157
|
+
* }
|
|
158
|
+
* return connection.transaction().execute(async (trx) => {
|
|
159
|
+
* auditor.setTransaction(trx);
|
|
160
|
+
* const result = await callback();
|
|
161
|
+
* await auditor.flush(trx);
|
|
162
|
+
* return result;
|
|
163
|
+
* });
|
|
164
|
+
* }
|
|
165
|
+
* ```
|
|
166
|
+
*/
|
|
167
|
+
withTransaction?<T>(
|
|
168
|
+
auditor: {
|
|
169
|
+
setTransaction(trx: unknown): void;
|
|
170
|
+
flush(trx?: unknown): Promise<void>;
|
|
171
|
+
},
|
|
172
|
+
callback: () => Promise<T>,
|
|
173
|
+
db?: unknown,
|
|
174
|
+
): Promise<T>;
|
|
128
175
|
}
|
package/src/types.ts
CHANGED
|
@@ -17,22 +17,22 @@ import type { StandardSchemaV1 } from '@standard-schema/spec';
|
|
|
17
17
|
* ```
|
|
18
18
|
*/
|
|
19
19
|
export type AuditableAction<TType extends string, TPayload = unknown> = {
|
|
20
|
-
|
|
21
|
-
|
|
20
|
+
type: TType;
|
|
21
|
+
payload: TPayload;
|
|
22
22
|
};
|
|
23
23
|
|
|
24
24
|
/**
|
|
25
25
|
* Extract the type string from an AuditableAction union.
|
|
26
26
|
*/
|
|
27
27
|
export type ExtractAuditType<T extends AuditableAction<string, unknown>> =
|
|
28
|
-
|
|
28
|
+
T extends AuditableAction<infer TType, unknown> ? TType : never;
|
|
29
29
|
|
|
30
30
|
/**
|
|
31
31
|
* Extract the payload for a specific audit type from an AuditableAction union.
|
|
32
32
|
*/
|
|
33
33
|
export type ExtractAuditPayload<
|
|
34
|
-
|
|
35
|
-
|
|
34
|
+
T extends AuditableAction<string, unknown>,
|
|
35
|
+
TType extends ExtractAuditType<T>,
|
|
36
36
|
> = T extends AuditableAction<TType, infer TPayload> ? TPayload : never;
|
|
37
37
|
|
|
38
38
|
/**
|
|
@@ -44,74 +44,74 @@ export type AuditOperation = 'INSERT' | 'UPDATE' | 'DELETE' | 'CUSTOM';
|
|
|
44
44
|
* Represents the actor who performed an audited action.
|
|
45
45
|
*/
|
|
46
46
|
export interface AuditActor {
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
47
|
+
/** Unique identifier for the actor (user ID, service ID, etc.) */
|
|
48
|
+
id?: string;
|
|
49
|
+
/** Type of actor ('user', 'system', 'service', etc.) */
|
|
50
|
+
type?: string;
|
|
51
|
+
/** Additional actor properties */
|
|
52
|
+
[key: string]: unknown;
|
|
53
53
|
}
|
|
54
54
|
|
|
55
55
|
/**
|
|
56
56
|
* Metadata associated with an audit record.
|
|
57
57
|
*/
|
|
58
58
|
export interface AuditMetadata {
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
59
|
+
/** Request correlation ID */
|
|
60
|
+
requestId?: string;
|
|
61
|
+
/** Which endpoint was called */
|
|
62
|
+
endpoint?: string;
|
|
63
|
+
/** HTTP method */
|
|
64
|
+
method?: string;
|
|
65
|
+
/** Client IP address */
|
|
66
|
+
ip?: string;
|
|
67
|
+
/** Client user agent */
|
|
68
|
+
userAgent?: string;
|
|
69
|
+
/** Additional metadata */
|
|
70
|
+
[key: string]: unknown;
|
|
71
71
|
}
|
|
72
72
|
|
|
73
73
|
/**
|
|
74
74
|
* A complete audit record representing a tracked action.
|
|
75
75
|
*/
|
|
76
76
|
export interface AuditRecord<TPayload = unknown> {
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
77
|
+
/** Unique identifier for this audit record */
|
|
78
|
+
id: string;
|
|
79
|
+
/** Audit type (e.g., 'user.created', 'order.placed') */
|
|
80
|
+
type: string;
|
|
81
|
+
/** Operation type for database audits */
|
|
82
|
+
operation: AuditOperation;
|
|
83
|
+
/** Database table name (for database operations) */
|
|
84
|
+
table?: string;
|
|
85
|
+
/** Entity primary key(s) */
|
|
86
|
+
entityId?: string | Record<string, unknown>;
|
|
87
|
+
/** Previous state (for UPDATE/DELETE) */
|
|
88
|
+
oldValues?: Record<string, unknown>;
|
|
89
|
+
/** New state (for INSERT/UPDATE) */
|
|
90
|
+
newValues?: Record<string, unknown>;
|
|
91
|
+
/** Custom payload (for CUSTOM operations) */
|
|
92
|
+
payload?: TPayload;
|
|
93
|
+
/** When the audit was recorded */
|
|
94
|
+
timestamp: Date;
|
|
95
|
+
/** Who performed the action */
|
|
96
|
+
actor?: AuditActor;
|
|
97
|
+
/** Request context */
|
|
98
|
+
metadata?: AuditMetadata;
|
|
99
99
|
}
|
|
100
100
|
|
|
101
101
|
/**
|
|
102
102
|
* Options for manual audit calls.
|
|
103
103
|
*/
|
|
104
104
|
export interface AuditOptions {
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
105
|
+
/** Entity primary key(s) for easier querying */
|
|
106
|
+
entityId?: string | Record<string, unknown>;
|
|
107
|
+
/** Database table name */
|
|
108
|
+
table?: string;
|
|
109
|
+
/** Operation type (defaults to 'CUSTOM') */
|
|
110
|
+
operation?: AuditOperation;
|
|
111
|
+
/** Previous state */
|
|
112
|
+
oldValues?: Record<string, unknown>;
|
|
113
|
+
/** New state */
|
|
114
|
+
newValues?: Record<string, unknown>;
|
|
115
115
|
}
|
|
116
116
|
|
|
117
117
|
/**
|
|
@@ -119,23 +119,23 @@ export interface AuditOptions {
|
|
|
119
119
|
* Similar to MappedEvent in @geekmidas/events.
|
|
120
120
|
*/
|
|
121
121
|
export interface MappedAudit<
|
|
122
|
-
|
|
123
|
-
|
|
122
|
+
TAuditAction extends AuditableAction<string, unknown>,
|
|
123
|
+
TOutput extends StandardSchemaV1 | undefined = undefined,
|
|
124
124
|
> {
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
125
|
+
/** The audit type - must be a valid type from the AuditableAction union */
|
|
126
|
+
type: ExtractAuditType<TAuditAction>;
|
|
127
|
+
/** Function to extract payload from the response */
|
|
128
|
+
payload: (
|
|
129
|
+
response: InferStandardSchema<TOutput>,
|
|
130
|
+
) => ExtractAuditPayload<TAuditAction, ExtractAuditType<TAuditAction>>;
|
|
131
|
+
/** Optional condition - only audit if this returns true */
|
|
132
|
+
when?: (response: InferStandardSchema<TOutput>) => boolean;
|
|
133
|
+
/** Optional entity ID extractor for easier querying */
|
|
134
|
+
entityId?: (
|
|
135
|
+
response: InferStandardSchema<TOutput>,
|
|
136
|
+
) => string | Record<string, unknown>;
|
|
137
|
+
/** Optional table name for database association */
|
|
138
|
+
table?: string;
|
|
139
139
|
}
|
|
140
140
|
|
|
141
141
|
/**
|
|
@@ -146,9 +146,8 @@ export type ExtractAuditorAction<T> = T extends Auditor<infer A> ? A : never;
|
|
|
146
146
|
/**
|
|
147
147
|
* Extract the AuditableAction type from an AuditStorage.
|
|
148
148
|
*/
|
|
149
|
-
export type ExtractStorageAuditAction<T> =
|
|
150
|
-
|
|
151
|
-
: AuditableAction<string, unknown>;
|
|
149
|
+
export type ExtractStorageAuditAction<T> =
|
|
150
|
+
T extends AuditStorage<infer A> ? A : AuditableAction<string, unknown>;
|
|
152
151
|
|
|
153
152
|
// Forward declaration for Auditor type extraction
|
|
154
153
|
import type { Auditor } from './Auditor';
|