@geekmidas/audit 0.1.0 → 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/dist/{Auditor-DrR0ImvJ.d.cts → Auditor-_Dn2dp8d.d.mts} +4 -1
- package/dist/Auditor-_Dn2dp8d.d.mts.map +1 -0
- package/dist/{Auditor-Xf4Tp63p.d.mts → Auditor-sW7YAJIA.d.cts} +4 -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-3Q1zVd1b.d.mts → DefaultAuditor-BSYMwojG.d.cts} +3 -2
- package/dist/DefaultAuditor-BSYMwojG.d.cts.map +1 -0
- package/dist/{DefaultAuditor-BaZa4u_m.d.cts → 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-0AjJ0zis.d.cts → cache-2VI80Vao.d.mts} +3 -2
- package/dist/cache-2VI80Vao.d.mts.map +1 -0
- package/dist/cache-BbIl31RL.mjs.map +1 -1
- package/dist/{cache-DBbGcCEq.d.mts → cache-CDHqXqcl.d.cts} +3 -2
- package/dist/cache-CDHqXqcl.d.cts.map +1 -0
- package/dist/cache-l7jAptBl.cjs.map +1 -1
- package/dist/cache.d.cts +2 -2
- package/dist/cache.d.mts +2 -2
- package/dist/index.d.cts +2 -2
- package/dist/index.d.mts +2 -2
- package/dist/kysely.cjs.map +1 -1
- package/dist/kysely.d.cts +2 -1
- package/dist/kysely.d.cts.map +1 -0
- package/dist/kysely.d.mts +2 -1
- package/dist/kysely.d.mts.map +1 -0
- package/dist/kysely.mjs.map +1 -1
- package/dist/memory.cjs.map +1 -1
- package/dist/memory.d.cts +3 -2
- package/dist/memory.d.cts.map +1 -0
- package/dist/memory.d.mts +3 -2
- package/dist/memory.d.mts.map +1 -0
- package/dist/memory.mjs.map +1 -1
- 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 +3 -3
- package/src/Auditor.ts +121 -121
- package/src/DefaultAuditor.ts +91 -91
- package/src/__tests__/CacheAuditStorage.spec.ts +375 -375
- package/src/__tests__/DefaultAuditor.spec.ts +520 -462
- package/src/__tests__/InMemoryAuditStorage.spec.ts +311 -311
- package/src/__tests__/KyselyAuditStorage.integration.spec.ts +496 -496
- package/src/__tests__/KyselyAuditStorage.spec.ts +433 -433
- package/src/cache.ts +179 -179
- package/src/index.ts +14 -15
- package/src/kysely.ts +292 -292
- package/src/memory.ts +11 -11
- package/src/storage.ts +131 -131
- package/src/types.ts +73 -74
- package/tsconfig.json +9 -0
package/src/cache.ts
CHANGED
|
@@ -1,24 +1,24 @@
|
|
|
1
1
|
import type { Cache } from '@geekmidas/cache';
|
|
2
2
|
import type { AuditQueryOptions, AuditStorage } from './storage';
|
|
3
|
-
import type {
|
|
3
|
+
import type { AuditableAction, AuditRecord } from './types';
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
6
|
* Configuration for CacheAuditStorage.
|
|
7
7
|
*/
|
|
8
8
|
export interface CacheAuditStorageConfig {
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
9
|
+
/** Cache instance to use for storage */
|
|
10
|
+
cache: Cache;
|
|
11
|
+
/**
|
|
12
|
+
* Key prefix for audit records.
|
|
13
|
+
* Records are stored as `${prefix}:${id}`.
|
|
14
|
+
* @default 'audit'
|
|
15
|
+
*/
|
|
16
|
+
prefix?: string;
|
|
17
|
+
/**
|
|
18
|
+
* TTL (time-to-live) in seconds for audit records.
|
|
19
|
+
* If not set, records use the cache's default TTL.
|
|
20
|
+
*/
|
|
21
|
+
ttl?: number;
|
|
22
22
|
}
|
|
23
23
|
|
|
24
24
|
/**
|
|
@@ -59,199 +59,199 @@ export interface CacheAuditStorageConfig {
|
|
|
59
59
|
* ```
|
|
60
60
|
*/
|
|
61
61
|
export class CacheAuditStorage<
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
62
|
+
TAuditAction extends AuditableAction<string, unknown> = AuditableAction<
|
|
63
|
+
string,
|
|
64
|
+
unknown
|
|
65
|
+
>,
|
|
66
66
|
> implements AuditStorage<TAuditAction>
|
|
67
67
|
{
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
68
|
+
private readonly cache: Cache;
|
|
69
|
+
private readonly prefix: string;
|
|
70
|
+
private readonly ttl?: number;
|
|
71
|
+
private readonly indexKey: string;
|
|
72
72
|
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
73
|
+
constructor(config: CacheAuditStorageConfig) {
|
|
74
|
+
this.cache = config.cache;
|
|
75
|
+
this.prefix = config.prefix ?? 'audit';
|
|
76
|
+
this.ttl = config.ttl;
|
|
77
|
+
this.indexKey = `${this.prefix}:__index__`;
|
|
78
|
+
}
|
|
79
79
|
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
80
|
+
/**
|
|
81
|
+
* Write audit records to cache.
|
|
82
|
+
*/
|
|
83
|
+
async write(records: AuditRecord[]): Promise<void> {
|
|
84
|
+
if (records.length === 0) {
|
|
85
|
+
return;
|
|
86
|
+
}
|
|
87
87
|
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
88
|
+
// Get existing index
|
|
89
|
+
const existingIds = (await this.cache.get<string[]>(this.indexKey)) ?? [];
|
|
90
|
+
const newIds: string[] = [];
|
|
91
91
|
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
92
|
+
// Write each record
|
|
93
|
+
for (const record of records) {
|
|
94
|
+
const key = this.getRecordKey(record.id);
|
|
95
|
+
// Serialize Date to ISO string for cache storage
|
|
96
|
+
const serialized = this.serializeRecord(record);
|
|
97
|
+
await this.cache.set(key, serialized, this.ttl);
|
|
98
|
+
newIds.push(record.id);
|
|
99
|
+
}
|
|
100
100
|
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
101
|
+
// Update index with new IDs
|
|
102
|
+
const updatedIds = [...existingIds, ...newIds];
|
|
103
|
+
await this.cache.set(this.indexKey, updatedIds, this.ttl);
|
|
104
|
+
}
|
|
105
105
|
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
106
|
+
/**
|
|
107
|
+
* Query audit records from cache.
|
|
108
|
+
*/
|
|
109
|
+
async query(options: AuditQueryOptions): Promise<AuditRecord[]> {
|
|
110
|
+
const allRecords = await this.getAllRecords();
|
|
111
|
+
let results = this.applyFilters(allRecords, options);
|
|
112
112
|
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
113
|
+
// Ordering
|
|
114
|
+
const orderBy = options.orderBy ?? 'timestamp';
|
|
115
|
+
const orderDirection = options.orderDirection ?? 'desc';
|
|
116
|
+
results.sort((a, b) => {
|
|
117
|
+
const aValue = orderBy === 'timestamp' ? a.timestamp.getTime() : a.type;
|
|
118
|
+
const bValue = orderBy === 'timestamp' ? b.timestamp.getTime() : b.type;
|
|
119
|
+
if (aValue < bValue) return orderDirection === 'asc' ? -1 : 1;
|
|
120
|
+
if (aValue > bValue) return orderDirection === 'asc' ? 1 : -1;
|
|
121
|
+
return 0;
|
|
122
|
+
});
|
|
123
123
|
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
124
|
+
// Pagination
|
|
125
|
+
if (options.offset !== undefined) {
|
|
126
|
+
results = results.slice(options.offset);
|
|
127
|
+
}
|
|
128
|
+
if (options.limit !== undefined) {
|
|
129
|
+
results = results.slice(0, options.limit);
|
|
130
|
+
}
|
|
131
131
|
|
|
132
|
-
|
|
133
|
-
|
|
132
|
+
return results;
|
|
133
|
+
}
|
|
134
134
|
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
135
|
+
/**
|
|
136
|
+
* Count audit records matching filters.
|
|
137
|
+
*/
|
|
138
|
+
async count(
|
|
139
|
+
options: Omit<AuditQueryOptions, 'limit' | 'offset'>,
|
|
140
|
+
): Promise<number> {
|
|
141
|
+
const allRecords = await this.getAllRecords();
|
|
142
|
+
return this.applyFilters(allRecords, options).length;
|
|
143
|
+
}
|
|
144
144
|
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
145
|
+
/**
|
|
146
|
+
* Get all stored records (for testing/debugging).
|
|
147
|
+
*/
|
|
148
|
+
async getRecords(): Promise<AuditRecord[]> {
|
|
149
|
+
return this.getAllRecords();
|
|
150
|
+
}
|
|
151
151
|
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
152
|
+
/**
|
|
153
|
+
* Clear all stored records.
|
|
154
|
+
*/
|
|
155
|
+
async clear(): Promise<void> {
|
|
156
|
+
const ids = (await this.cache.get<string[]>(this.indexKey)) ?? [];
|
|
157
157
|
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
158
|
+
// Delete all records
|
|
159
|
+
for (const id of ids) {
|
|
160
|
+
await this.cache.delete(this.getRecordKey(id));
|
|
161
|
+
}
|
|
162
162
|
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
163
|
+
// Delete index
|
|
164
|
+
await this.cache.delete(this.indexKey);
|
|
165
|
+
}
|
|
166
166
|
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
167
|
+
private getRecordKey(id: string): string {
|
|
168
|
+
return `${this.prefix}:${id}`;
|
|
169
|
+
}
|
|
170
170
|
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
171
|
+
private async getAllRecords(): Promise<AuditRecord[]> {
|
|
172
|
+
const ids = (await this.cache.get<string[]>(this.indexKey)) ?? [];
|
|
173
|
+
const records: AuditRecord[] = [];
|
|
174
|
+
const validIds: string[] = [];
|
|
175
175
|
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
176
|
+
for (const id of ids) {
|
|
177
|
+
const key = this.getRecordKey(id);
|
|
178
|
+
const serialized = await this.cache.get<SerializedAuditRecord>(key);
|
|
179
|
+
if (serialized) {
|
|
180
|
+
records.push(this.deserializeRecord(serialized));
|
|
181
|
+
validIds.push(id);
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
184
|
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
185
|
+
// Clean up index if some records expired
|
|
186
|
+
if (validIds.length !== ids.length) {
|
|
187
|
+
await this.cache.set(this.indexKey, validIds, this.ttl);
|
|
188
|
+
}
|
|
189
189
|
|
|
190
|
-
|
|
191
|
-
|
|
190
|
+
return records;
|
|
191
|
+
}
|
|
192
192
|
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
193
|
+
private serializeRecord(record: AuditRecord): SerializedAuditRecord {
|
|
194
|
+
return {
|
|
195
|
+
...record,
|
|
196
|
+
timestamp: record.timestamp.toISOString(),
|
|
197
|
+
};
|
|
198
|
+
}
|
|
199
199
|
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
200
|
+
private deserializeRecord(serialized: SerializedAuditRecord): AuditRecord {
|
|
201
|
+
return {
|
|
202
|
+
...serialized,
|
|
203
|
+
timestamp: new Date(serialized.timestamp),
|
|
204
|
+
};
|
|
205
|
+
}
|
|
206
206
|
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
207
|
+
private applyFilters(
|
|
208
|
+
records: AuditRecord[],
|
|
209
|
+
options: AuditQueryOptions,
|
|
210
|
+
): AuditRecord[] {
|
|
211
|
+
return records.filter((record) => {
|
|
212
|
+
// Type filter
|
|
213
|
+
if (options.type !== undefined) {
|
|
214
|
+
if (Array.isArray(options.type)) {
|
|
215
|
+
if (!options.type.includes(record.type)) return false;
|
|
216
|
+
} else {
|
|
217
|
+
if (record.type !== options.type) return false;
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
220
|
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
221
|
+
// Entity ID filter
|
|
222
|
+
if (options.entityId !== undefined) {
|
|
223
|
+
const entityId =
|
|
224
|
+
typeof options.entityId === 'string'
|
|
225
|
+
? options.entityId
|
|
226
|
+
: JSON.stringify(options.entityId);
|
|
227
|
+
const recordEntityId =
|
|
228
|
+
typeof record.entityId === 'string'
|
|
229
|
+
? record.entityId
|
|
230
|
+
: JSON.stringify(record.entityId);
|
|
231
|
+
if (recordEntityId !== entityId) return false;
|
|
232
|
+
}
|
|
233
233
|
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
234
|
+
// Table filter
|
|
235
|
+
if (options.table !== undefined) {
|
|
236
|
+
if (record.table !== options.table) return false;
|
|
237
|
+
}
|
|
238
238
|
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
239
|
+
// Actor ID filter
|
|
240
|
+
if (options.actorId !== undefined) {
|
|
241
|
+
if (record.actor?.id !== options.actorId) return false;
|
|
242
|
+
}
|
|
243
243
|
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
244
|
+
// Date range filters
|
|
245
|
+
if (options.from !== undefined) {
|
|
246
|
+
if (record.timestamp < options.from) return false;
|
|
247
|
+
}
|
|
248
|
+
if (options.to !== undefined) {
|
|
249
|
+
if (record.timestamp > options.to) return false;
|
|
250
|
+
}
|
|
251
251
|
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
252
|
+
return true;
|
|
253
|
+
});
|
|
254
|
+
}
|
|
255
255
|
}
|
|
256
256
|
|
|
257
257
|
/**
|
|
@@ -259,5 +259,5 @@ export class CacheAuditStorage<
|
|
|
259
259
|
* Dates are stored as ISO strings.
|
|
260
260
|
*/
|
|
261
261
|
type SerializedAuditRecord = Omit<AuditRecord, 'timestamp'> & {
|
|
262
|
-
|
|
262
|
+
timestamp: string;
|
|
263
263
|
};
|
package/src/index.ts
CHANGED
|
@@ -1,24 +1,23 @@
|
|
|
1
1
|
// Core types
|
|
2
|
-
export type {
|
|
3
|
-
AuditableAction,
|
|
4
|
-
AuditActor,
|
|
5
|
-
AuditMetadata,
|
|
6
|
-
AuditOperation,
|
|
7
|
-
AuditOptions,
|
|
8
|
-
AuditRecord,
|
|
9
|
-
ExtractAuditPayload,
|
|
10
|
-
ExtractAuditType,
|
|
11
|
-
ExtractAuditorAction,
|
|
12
|
-
ExtractStorageAuditAction,
|
|
13
|
-
MappedAudit,
|
|
14
|
-
} from './types';
|
|
15
2
|
|
|
16
3
|
// Auditor interface
|
|
17
4
|
export type { Auditor } from './Auditor';
|
|
5
|
+
export type { DefaultAuditorConfig } from './DefaultAuditor';
|
|
18
6
|
|
|
19
7
|
// Default implementation
|
|
20
8
|
export { DefaultAuditor } from './DefaultAuditor';
|
|
21
|
-
export type { DefaultAuditorConfig } from './DefaultAuditor';
|
|
22
|
-
|
|
23
9
|
// Storage interface
|
|
24
10
|
export type { AuditQueryOptions, AuditStorage } from './storage';
|
|
11
|
+
export type {
|
|
12
|
+
AuditActor,
|
|
13
|
+
AuditableAction,
|
|
14
|
+
AuditMetadata,
|
|
15
|
+
AuditOperation,
|
|
16
|
+
AuditOptions,
|
|
17
|
+
AuditRecord,
|
|
18
|
+
ExtractAuditorAction,
|
|
19
|
+
ExtractAuditPayload,
|
|
20
|
+
ExtractAuditType,
|
|
21
|
+
ExtractStorageAuditAction,
|
|
22
|
+
MappedAudit,
|
|
23
|
+
} from './types';
|