@geekmidas/audit 10.0.0-alpha.2 → 10.0.0-alpha.21

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.
Files changed (77) hide show
  1. package/dist/{Auditor-CW2NrfHS.d.mts → Auditor-BBXXCD0v.d.cts} +2 -7
  2. package/dist/Auditor-BBXXCD0v.d.cts.map +1 -0
  3. package/dist/{Auditor-DpBHXIJM.d.cts → Auditor-BBXXCD0v.d.mts} +2 -7
  4. package/dist/Auditor-BBXXCD0v.d.mts.map +1 -0
  5. package/dist/Auditor.d.cts +1 -1
  6. package/dist/Auditor.d.mts +1 -1
  7. package/dist/Auditor.mjs +1 -0
  8. package/dist/DefaultAuditor.cjs +95 -2
  9. package/dist/DefaultAuditor.cjs.map +1 -0
  10. package/dist/DefaultAuditor.d.cts +55 -3
  11. package/dist/DefaultAuditor.d.cts.map +1 -0
  12. package/dist/DefaultAuditor.d.mts +55 -3
  13. package/dist/DefaultAuditor.d.mts.map +1 -0
  14. package/dist/DefaultAuditor.mjs +94 -2
  15. package/dist/DefaultAuditor.mjs.map +1 -0
  16. package/dist/cache.cjs +166 -2
  17. package/dist/cache.cjs.map +1 -0
  18. package/dist/cache.d.cts +92 -3
  19. package/dist/cache.d.cts.map +1 -0
  20. package/dist/cache.d.mts +92 -3
  21. package/dist/cache.d.mts.map +1 -0
  22. package/dist/cache.mjs +165 -2
  23. package/dist/cache.mjs.map +1 -0
  24. package/dist/index.cjs +3 -3
  25. package/dist/index.d.cts +3 -3
  26. package/dist/index.d.mts +3 -3
  27. package/dist/index.mjs +2 -3
  28. package/dist/knex.cjs +9 -9
  29. package/dist/knex.cjs.map +1 -1
  30. package/dist/knex.d.cts +9 -11
  31. package/dist/knex.d.cts.map +1 -1
  32. package/dist/knex.d.mts +9 -11
  33. package/dist/knex.d.mts.map +1 -1
  34. package/dist/knex.mjs +7 -7
  35. package/dist/knex.mjs.map +1 -1
  36. package/dist/kysely.cjs +9 -9
  37. package/dist/kysely.cjs.map +1 -1
  38. package/dist/kysely.d.cts +9 -11
  39. package/dist/kysely.d.cts.map +1 -1
  40. package/dist/kysely.d.mts +9 -11
  41. package/dist/kysely.d.mts.map +1 -1
  42. package/dist/kysely.mjs +7 -7
  43. package/dist/kysely.mjs.map +1 -1
  44. package/dist/memory.cjs +6 -7
  45. package/dist/memory.cjs.map +1 -1
  46. package/dist/memory.d.cts +3 -7
  47. package/dist/memory.d.cts.map +1 -1
  48. package/dist/memory.d.mts +3 -7
  49. package/dist/memory.d.mts.map +1 -1
  50. package/dist/memory.mjs +3 -4
  51. package/dist/memory.mjs.map +1 -1
  52. package/dist/storage.d.cts +1 -1
  53. package/dist/storage.d.mts +1 -1
  54. package/dist/storage.mjs +1 -0
  55. package/dist/types.d.cts +1 -1
  56. package/dist/types.d.mts +1 -1
  57. package/dist/types.mjs +1 -0
  58. package/package.json +13 -13
  59. package/dist/Auditor-CW2NrfHS.d.mts.map +0 -1
  60. package/dist/Auditor-DpBHXIJM.d.cts.map +0 -1
  61. package/dist/DefaultAuditor-BAVnNmRh.mjs +0 -96
  62. package/dist/DefaultAuditor-BAVnNmRh.mjs.map +0 -1
  63. package/dist/DefaultAuditor-DlJ1ENfx.d.mts +0 -59
  64. package/dist/DefaultAuditor-DlJ1ENfx.d.mts.map +0 -1
  65. package/dist/DefaultAuditor-GRFI86N_.d.cts +0 -59
  66. package/dist/DefaultAuditor-GRFI86N_.d.cts.map +0 -1
  67. package/dist/DefaultAuditor-JbZ_BQfh.cjs +0 -102
  68. package/dist/DefaultAuditor-JbZ_BQfh.cjs.map +0 -1
  69. package/dist/cache-BbIl31RL.mjs +0 -167
  70. package/dist/cache-BbIl31RL.mjs.map +0 -1
  71. package/dist/cache-Bih6HxY6.d.cts +0 -96
  72. package/dist/cache-Bih6HxY6.d.cts.map +0 -1
  73. package/dist/cache-l7jAptBl.cjs +0 -173
  74. package/dist/cache-l7jAptBl.cjs.map +0 -1
  75. package/dist/cache-wET57t6d.d.mts +0 -96
  76. package/dist/cache-wET57t6d.d.mts.map +0 -1
  77. package/dist/chunk-CUT6urMc.cjs +0 -30
package/dist/cache.d.cts CHANGED
@@ -1,3 +1,92 @@
1
- import "./Auditor-DpBHXIJM.cjs";
2
- import { CacheAuditStorage, CacheAuditStorageConfig } from "./cache-Bih6HxY6.cjs";
3
- export { CacheAuditStorage, CacheAuditStorageConfig };
1
+ import { m as AuditStorage, o as AuditRecord, p as AuditQueryOptions, s as AuditableAction } from "./Auditor-BBXXCD0v.cjs";
2
+ import { Cache } from "@geekmidas/cache";
3
+ //#region src/cache.d.ts
4
+ /**
5
+ * Configuration for CacheAuditStorage.
6
+ */
7
+ export interface CacheAuditStorageConfig {
8
+ /** Cache instance to use for storage */
9
+ cache: Cache;
10
+ /**
11
+ * Key prefix for audit records.
12
+ * Records are stored as `${prefix}:${id}`.
13
+ * @default 'audit'
14
+ */
15
+ prefix?: string;
16
+ /**
17
+ * TTL (time-to-live) in seconds for audit records.
18
+ * If not set, records use the cache's default TTL.
19
+ */
20
+ ttl?: number;
21
+ }
22
+ /**
23
+ * Cache-based audit storage implementation.
24
+ * Uses any @geekmidas/cache implementation for storage.
25
+ *
26
+ * Best suited for:
27
+ * - Development and testing
28
+ * - Temporary audit logs that don't need persistence
29
+ * - Applications with existing cache infrastructure
30
+ * - Distributed systems needing shared audit state (with Redis/Upstash)
31
+ *
32
+ * Note: Query performance may degrade with large numbers of records
33
+ * since filtering happens in memory after fetching all records.
34
+ *
35
+ * @template TAuditAction - Optional type parameter for type-safe audit actions.
36
+ *
37
+ * @example
38
+ * ```typescript
39
+ * import { CacheAuditStorage } from '@geekmidas/audit/cache';
40
+ * import { InMemoryCache } from '@geekmidas/cache/memory';
41
+ * import { UpstashCache } from '@geekmidas/cache/upstash';
42
+ *
43
+ * // With in-memory cache (development/testing)
44
+ * const storage = new CacheAuditStorage({
45
+ * cache: new InMemoryCache(),
46
+ * ttl: 86400, // 24 hours
47
+ * });
48
+ *
49
+ * // With Upstash Redis (production)
50
+ * const storage = new CacheAuditStorage({
51
+ * cache: new UpstashCache({
52
+ * url: process.env.UPSTASH_REDIS_URL,
53
+ * token: process.env.UPSTASH_REDIS_TOKEN,
54
+ * }),
55
+ * ttl: 604800, // 7 days
56
+ * });
57
+ * ```
58
+ */
59
+ export declare class CacheAuditStorage<TAuditAction extends AuditableAction<string, unknown> = AuditableAction<string, unknown>> implements AuditStorage<TAuditAction> {
60
+ private readonly cache;
61
+ private readonly prefix;
62
+ private readonly ttl?;
63
+ private readonly indexKey;
64
+ constructor(config: CacheAuditStorageConfig);
65
+ /**
66
+ * Write audit records to cache.
67
+ */
68
+ write(records: AuditRecord[]): Promise<void>;
69
+ /**
70
+ * Query audit records from cache.
71
+ */
72
+ query(options: AuditQueryOptions): Promise<AuditRecord[]>;
73
+ /**
74
+ * Count audit records matching filters.
75
+ */
76
+ count(options: Omit<AuditQueryOptions, 'limit' | 'offset'>): Promise<number>;
77
+ /**
78
+ * Get all stored records (for testing/debugging).
79
+ */
80
+ getRecords(): Promise<AuditRecord[]>;
81
+ /**
82
+ * Clear all stored records.
83
+ */
84
+ clear(): Promise<void>;
85
+ private getRecordKey;
86
+ private getAllRecords;
87
+ private serializeRecord;
88
+ private deserializeRecord;
89
+ private applyFilters;
90
+ }
91
+ //#endregion
92
+ //# sourceMappingURL=cache.d.cts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cache.d.cts","names":[],"sources":["../src/cache.ts"],"mappings":";;;;;;iBAOiB;;EAEhB,OAAO;;;;;;EAMP;;;;;EAKA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;qBAwCY,kBACZ,qBAAqB,mCAAmC,6CAI5C,aAAa;mBAER;mBACA;mBACA;mBACA;EAEjB,YAAY,QAAQ;;;;EAUd,MAAM,SAAS,gBAAgB;;;;EA0B/B,MAAM,SAAS,oBAAoB,QAAQ;;;;EA6B3C,MACL,SAAS,KAAK,yCACZ;;;;EAQG,cAAc,QAAQ;;;;EAOtB,SAAS;UAYP;UAIM;UAsBN;UAOA;UAOA"}
package/dist/cache.d.mts CHANGED
@@ -1,3 +1,92 @@
1
- import "./Auditor-CW2NrfHS.mjs";
2
- import { CacheAuditStorage, CacheAuditStorageConfig } from "./cache-wET57t6d.mjs";
3
- export { CacheAuditStorage, CacheAuditStorageConfig };
1
+ import { m as AuditStorage, o as AuditRecord, p as AuditQueryOptions, s as AuditableAction } from "./Auditor-BBXXCD0v.mjs";
2
+ import { Cache } from "@geekmidas/cache";
3
+ //#region src/cache.d.ts
4
+ /**
5
+ * Configuration for CacheAuditStorage.
6
+ */
7
+ export interface CacheAuditStorageConfig {
8
+ /** Cache instance to use for storage */
9
+ cache: Cache;
10
+ /**
11
+ * Key prefix for audit records.
12
+ * Records are stored as `${prefix}:${id}`.
13
+ * @default 'audit'
14
+ */
15
+ prefix?: string;
16
+ /**
17
+ * TTL (time-to-live) in seconds for audit records.
18
+ * If not set, records use the cache's default TTL.
19
+ */
20
+ ttl?: number;
21
+ }
22
+ /**
23
+ * Cache-based audit storage implementation.
24
+ * Uses any @geekmidas/cache implementation for storage.
25
+ *
26
+ * Best suited for:
27
+ * - Development and testing
28
+ * - Temporary audit logs that don't need persistence
29
+ * - Applications with existing cache infrastructure
30
+ * - Distributed systems needing shared audit state (with Redis/Upstash)
31
+ *
32
+ * Note: Query performance may degrade with large numbers of records
33
+ * since filtering happens in memory after fetching all records.
34
+ *
35
+ * @template TAuditAction - Optional type parameter for type-safe audit actions.
36
+ *
37
+ * @example
38
+ * ```typescript
39
+ * import { CacheAuditStorage } from '@geekmidas/audit/cache';
40
+ * import { InMemoryCache } from '@geekmidas/cache/memory';
41
+ * import { UpstashCache } from '@geekmidas/cache/upstash';
42
+ *
43
+ * // With in-memory cache (development/testing)
44
+ * const storage = new CacheAuditStorage({
45
+ * cache: new InMemoryCache(),
46
+ * ttl: 86400, // 24 hours
47
+ * });
48
+ *
49
+ * // With Upstash Redis (production)
50
+ * const storage = new CacheAuditStorage({
51
+ * cache: new UpstashCache({
52
+ * url: process.env.UPSTASH_REDIS_URL,
53
+ * token: process.env.UPSTASH_REDIS_TOKEN,
54
+ * }),
55
+ * ttl: 604800, // 7 days
56
+ * });
57
+ * ```
58
+ */
59
+ export declare class CacheAuditStorage<TAuditAction extends AuditableAction<string, unknown> = AuditableAction<string, unknown>> implements AuditStorage<TAuditAction> {
60
+ private readonly cache;
61
+ private readonly prefix;
62
+ private readonly ttl?;
63
+ private readonly indexKey;
64
+ constructor(config: CacheAuditStorageConfig);
65
+ /**
66
+ * Write audit records to cache.
67
+ */
68
+ write(records: AuditRecord[]): Promise<void>;
69
+ /**
70
+ * Query audit records from cache.
71
+ */
72
+ query(options: AuditQueryOptions): Promise<AuditRecord[]>;
73
+ /**
74
+ * Count audit records matching filters.
75
+ */
76
+ count(options: Omit<AuditQueryOptions, 'limit' | 'offset'>): Promise<number>;
77
+ /**
78
+ * Get all stored records (for testing/debugging).
79
+ */
80
+ getRecords(): Promise<AuditRecord[]>;
81
+ /**
82
+ * Clear all stored records.
83
+ */
84
+ clear(): Promise<void>;
85
+ private getRecordKey;
86
+ private getAllRecords;
87
+ private serializeRecord;
88
+ private deserializeRecord;
89
+ private applyFilters;
90
+ }
91
+ //#endregion
92
+ //# sourceMappingURL=cache.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cache.d.mts","names":[],"sources":["../src/cache.ts"],"mappings":";;;;;;iBAOiB;;EAEhB,OAAO;;;;;;EAMP;;;;;EAKA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;qBAwCY,kBACZ,qBAAqB,mCAAmC,6CAI5C,aAAa;mBAER;mBACA;mBACA;mBACA;EAEjB,YAAY,QAAQ;;;;EAUd,MAAM,SAAS,gBAAgB;;;;EA0B/B,MAAM,SAAS,oBAAoB,QAAQ;;;;EA6B3C,MACL,SAAS,KAAK,yCACZ;;;;EAQG,cAAc,QAAQ;;;;EAOtB,SAAS;UAYP;UAIM;UAsBN;UAOA;UAOA"}
package/dist/cache.mjs CHANGED
@@ -1,3 +1,166 @@
1
- import { CacheAuditStorage } from "./cache-BbIl31RL.mjs";
1
+ //#region src/cache.ts
2
+ /**
3
+ * Cache-based audit storage implementation.
4
+ * Uses any @geekmidas/cache implementation for storage.
5
+ *
6
+ * Best suited for:
7
+ * - Development and testing
8
+ * - Temporary audit logs that don't need persistence
9
+ * - Applications with existing cache infrastructure
10
+ * - Distributed systems needing shared audit state (with Redis/Upstash)
11
+ *
12
+ * Note: Query performance may degrade with large numbers of records
13
+ * since filtering happens in memory after fetching all records.
14
+ *
15
+ * @template TAuditAction - Optional type parameter for type-safe audit actions.
16
+ *
17
+ * @example
18
+ * ```typescript
19
+ * import { CacheAuditStorage } from '@geekmidas/audit/cache';
20
+ * import { InMemoryCache } from '@geekmidas/cache/memory';
21
+ * import { UpstashCache } from '@geekmidas/cache/upstash';
22
+ *
23
+ * // With in-memory cache (development/testing)
24
+ * const storage = new CacheAuditStorage({
25
+ * cache: new InMemoryCache(),
26
+ * ttl: 86400, // 24 hours
27
+ * });
28
+ *
29
+ * // With Upstash Redis (production)
30
+ * const storage = new CacheAuditStorage({
31
+ * cache: new UpstashCache({
32
+ * url: process.env.UPSTASH_REDIS_URL,
33
+ * token: process.env.UPSTASH_REDIS_TOKEN,
34
+ * }),
35
+ * ttl: 604800, // 7 days
36
+ * });
37
+ * ```
38
+ */
39
+ var CacheAuditStorage = class {
40
+ cache;
41
+ prefix;
42
+ ttl;
43
+ indexKey;
44
+ constructor(config) {
45
+ this.cache = config.cache;
46
+ this.prefix = config.prefix ?? "audit";
47
+ this.ttl = config.ttl;
48
+ this.indexKey = `${this.prefix}:__index__`;
49
+ }
50
+ /**
51
+ * Write audit records to cache.
52
+ */
53
+ async write(records) {
54
+ if (records.length === 0) return;
55
+ const existingIds = await this.cache.get(this.indexKey) ?? [];
56
+ const newIds = [];
57
+ for (const record of records) {
58
+ const key = this.getRecordKey(record.id);
59
+ const serialized = this.serializeRecord(record);
60
+ await this.cache.set(key, serialized, this.ttl);
61
+ newIds.push(record.id);
62
+ }
63
+ const updatedIds = [...existingIds, ...newIds];
64
+ await this.cache.set(this.indexKey, updatedIds, this.ttl);
65
+ }
66
+ /**
67
+ * Query audit records from cache.
68
+ */
69
+ async query(options) {
70
+ const allRecords = await this.getAllRecords();
71
+ let results = this.applyFilters(allRecords, options);
72
+ const orderBy = options.orderBy ?? "timestamp";
73
+ const orderDirection = options.orderDirection ?? "desc";
74
+ results.sort((a, b) => {
75
+ const aValue = orderBy === "timestamp" ? a.timestamp.getTime() : a.type;
76
+ const bValue = orderBy === "timestamp" ? b.timestamp.getTime() : b.type;
77
+ if (aValue < bValue) return orderDirection === "asc" ? -1 : 1;
78
+ if (aValue > bValue) return orderDirection === "asc" ? 1 : -1;
79
+ return 0;
80
+ });
81
+ if (options.offset !== void 0) results = results.slice(options.offset);
82
+ if (options.limit !== void 0) results = results.slice(0, options.limit);
83
+ return results;
84
+ }
85
+ /**
86
+ * Count audit records matching filters.
87
+ */
88
+ async count(options) {
89
+ const allRecords = await this.getAllRecords();
90
+ return this.applyFilters(allRecords, options).length;
91
+ }
92
+ /**
93
+ * Get all stored records (for testing/debugging).
94
+ */
95
+ async getRecords() {
96
+ return this.getAllRecords();
97
+ }
98
+ /**
99
+ * Clear all stored records.
100
+ */
101
+ async clear() {
102
+ const ids = await this.cache.get(this.indexKey) ?? [];
103
+ for (const id of ids) await this.cache.delete(this.getRecordKey(id));
104
+ await this.cache.delete(this.indexKey);
105
+ }
106
+ getRecordKey(id) {
107
+ return `${this.prefix}:${id}`;
108
+ }
109
+ async getAllRecords() {
110
+ const ids = await this.cache.get(this.indexKey) ?? [];
111
+ const records = [];
112
+ const validIds = [];
113
+ for (const id of ids) {
114
+ const key = this.getRecordKey(id);
115
+ const serialized = await this.cache.get(key);
116
+ if (serialized) {
117
+ records.push(this.deserializeRecord(serialized));
118
+ validIds.push(id);
119
+ }
120
+ }
121
+ if (validIds.length !== ids.length) await this.cache.set(this.indexKey, validIds, this.ttl);
122
+ return records;
123
+ }
124
+ serializeRecord(record) {
125
+ return {
126
+ ...record,
127
+ timestamp: record.timestamp.toISOString()
128
+ };
129
+ }
130
+ deserializeRecord(serialized) {
131
+ return {
132
+ ...serialized,
133
+ timestamp: new Date(serialized.timestamp)
134
+ };
135
+ }
136
+ applyFilters(records, options) {
137
+ return records.filter((record) => {
138
+ if (options.type !== void 0) {
139
+ if (Array.isArray(options.type)) {
140
+ if (!options.type.includes(record.type)) return false;
141
+ } else if (record.type !== options.type) return false;
142
+ }
143
+ if (options.entityId !== void 0) {
144
+ const entityId = typeof options.entityId === "string" ? options.entityId : JSON.stringify(options.entityId);
145
+ if ((typeof record.entityId === "string" ? record.entityId : JSON.stringify(record.entityId)) !== entityId) return false;
146
+ }
147
+ if (options.table !== void 0) {
148
+ if (record.table !== options.table) return false;
149
+ }
150
+ if (options.actorId !== void 0) {
151
+ if (record.actor?.id !== options.actorId) return false;
152
+ }
153
+ if (options.from !== void 0) {
154
+ if (record.timestamp < options.from) return false;
155
+ }
156
+ if (options.to !== void 0) {
157
+ if (record.timestamp > options.to) return false;
158
+ }
159
+ return true;
160
+ });
161
+ }
162
+ };
163
+ //#endregion
164
+ export { CacheAuditStorage };
2
165
 
3
- export { CacheAuditStorage };
166
+ //# sourceMappingURL=cache.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cache.mjs","names":[],"sources":["../src/cache.ts"],"sourcesContent":["import type { Cache } from '@geekmidas/cache';\nimport type { AuditQueryOptions, AuditStorage } from './storage';\nimport type { AuditableAction, AuditRecord } from './types';\n\n/**\n * Configuration for CacheAuditStorage.\n */\nexport interface CacheAuditStorageConfig {\n\t/** Cache instance to use for storage */\n\tcache: Cache;\n\t/**\n\t * Key prefix for audit records.\n\t * Records are stored as `${prefix}:${id}`.\n\t * @default 'audit'\n\t */\n\tprefix?: string;\n\t/**\n\t * TTL (time-to-live) in seconds for audit records.\n\t * If not set, records use the cache's default TTL.\n\t */\n\tttl?: number;\n}\n\n/**\n * Cache-based audit storage implementation.\n * Uses any @geekmidas/cache implementation for storage.\n *\n * Best suited for:\n * - Development and testing\n * - Temporary audit logs that don't need persistence\n * - Applications with existing cache infrastructure\n * - Distributed systems needing shared audit state (with Redis/Upstash)\n *\n * Note: Query performance may degrade with large numbers of records\n * since filtering happens in memory after fetching all records.\n *\n * @template TAuditAction - Optional type parameter for type-safe audit actions.\n *\n * @example\n * ```typescript\n * import { CacheAuditStorage } from '@geekmidas/audit/cache';\n * import { InMemoryCache } from '@geekmidas/cache/memory';\n * import { UpstashCache } from '@geekmidas/cache/upstash';\n *\n * // With in-memory cache (development/testing)\n * const storage = new CacheAuditStorage({\n * cache: new InMemoryCache(),\n * ttl: 86400, // 24 hours\n * });\n *\n * // With Upstash Redis (production)\n * const storage = new CacheAuditStorage({\n * cache: new UpstashCache({\n * url: process.env.UPSTASH_REDIS_URL,\n * token: process.env.UPSTASH_REDIS_TOKEN,\n * }),\n * ttl: 604800, // 7 days\n * });\n * ```\n */\nexport class CacheAuditStorage<\n\tTAuditAction extends AuditableAction<string, unknown> = AuditableAction<\n\t\tstring,\n\t\tunknown\n\t>,\n> implements AuditStorage<TAuditAction>\n{\n\tprivate readonly cache: Cache;\n\tprivate readonly prefix: string;\n\tprivate readonly ttl?: number;\n\tprivate readonly indexKey: string;\n\n\tconstructor(config: CacheAuditStorageConfig) {\n\t\tthis.cache = config.cache;\n\t\tthis.prefix = config.prefix ?? 'audit';\n\t\tthis.ttl = config.ttl;\n\t\tthis.indexKey = `${this.prefix}:__index__`;\n\t}\n\n\t/**\n\t * Write audit records to cache.\n\t */\n\tasync write(records: AuditRecord[]): Promise<void> {\n\t\tif (records.length === 0) {\n\t\t\treturn;\n\t\t}\n\n\t\t// Get existing index\n\t\tconst existingIds = (await this.cache.get<string[]>(this.indexKey)) ?? [];\n\t\tconst newIds: string[] = [];\n\n\t\t// Write each record\n\t\tfor (const record of records) {\n\t\t\tconst key = this.getRecordKey(record.id);\n\t\t\t// Serialize Date to ISO string for cache storage\n\t\t\tconst serialized = this.serializeRecord(record);\n\t\t\tawait this.cache.set(key, serialized, this.ttl);\n\t\t\tnewIds.push(record.id);\n\t\t}\n\n\t\t// Update index with new IDs\n\t\tconst updatedIds = [...existingIds, ...newIds];\n\t\tawait this.cache.set(this.indexKey, updatedIds, this.ttl);\n\t}\n\n\t/**\n\t * Query audit records from cache.\n\t */\n\tasync query(options: AuditQueryOptions): Promise<AuditRecord[]> {\n\t\tconst allRecords = await this.getAllRecords();\n\t\tlet results = this.applyFilters(allRecords, options);\n\n\t\t// Ordering\n\t\tconst orderBy = options.orderBy ?? 'timestamp';\n\t\tconst orderDirection = options.orderDirection ?? 'desc';\n\t\tresults.sort((a, b) => {\n\t\t\tconst aValue = orderBy === 'timestamp' ? a.timestamp.getTime() : a.type;\n\t\t\tconst bValue = orderBy === 'timestamp' ? b.timestamp.getTime() : b.type;\n\t\t\tif (aValue < bValue) return orderDirection === 'asc' ? -1 : 1;\n\t\t\tif (aValue > bValue) return orderDirection === 'asc' ? 1 : -1;\n\t\t\treturn 0;\n\t\t});\n\n\t\t// Pagination\n\t\tif (options.offset !== undefined) {\n\t\t\tresults = results.slice(options.offset);\n\t\t}\n\t\tif (options.limit !== undefined) {\n\t\t\tresults = results.slice(0, options.limit);\n\t\t}\n\n\t\treturn results;\n\t}\n\n\t/**\n\t * Count audit records matching filters.\n\t */\n\tasync count(\n\t\toptions: Omit<AuditQueryOptions, 'limit' | 'offset'>,\n\t): Promise<number> {\n\t\tconst allRecords = await this.getAllRecords();\n\t\treturn this.applyFilters(allRecords, options).length;\n\t}\n\n\t/**\n\t * Get all stored records (for testing/debugging).\n\t */\n\tasync getRecords(): Promise<AuditRecord[]> {\n\t\treturn this.getAllRecords();\n\t}\n\n\t/**\n\t * Clear all stored records.\n\t */\n\tasync clear(): Promise<void> {\n\t\tconst ids = (await this.cache.get<string[]>(this.indexKey)) ?? [];\n\n\t\t// Delete all records\n\t\tfor (const id of ids) {\n\t\t\tawait this.cache.delete(this.getRecordKey(id));\n\t\t}\n\n\t\t// Delete index\n\t\tawait this.cache.delete(this.indexKey);\n\t}\n\n\tprivate getRecordKey(id: string): string {\n\t\treturn `${this.prefix}:${id}`;\n\t}\n\n\tprivate async getAllRecords(): Promise<AuditRecord[]> {\n\t\tconst ids = (await this.cache.get<string[]>(this.indexKey)) ?? [];\n\t\tconst records: AuditRecord[] = [];\n\t\tconst validIds: string[] = [];\n\n\t\tfor (const id of ids) {\n\t\t\tconst key = this.getRecordKey(id);\n\t\t\tconst serialized = await this.cache.get<SerializedAuditRecord>(key);\n\t\t\tif (serialized) {\n\t\t\t\trecords.push(this.deserializeRecord(serialized));\n\t\t\t\tvalidIds.push(id);\n\t\t\t}\n\t\t}\n\n\t\t// Clean up index if some records expired\n\t\tif (validIds.length !== ids.length) {\n\t\t\tawait this.cache.set(this.indexKey, validIds, this.ttl);\n\t\t}\n\n\t\treturn records;\n\t}\n\n\tprivate serializeRecord(record: AuditRecord): SerializedAuditRecord {\n\t\treturn {\n\t\t\t...record,\n\t\t\ttimestamp: record.timestamp.toISOString(),\n\t\t};\n\t}\n\n\tprivate deserializeRecord(serialized: SerializedAuditRecord): AuditRecord {\n\t\treturn {\n\t\t\t...serialized,\n\t\t\ttimestamp: new Date(serialized.timestamp),\n\t\t};\n\t}\n\n\tprivate applyFilters(\n\t\trecords: AuditRecord[],\n\t\toptions: AuditQueryOptions,\n\t): AuditRecord[] {\n\t\treturn records.filter((record) => {\n\t\t\t// Type filter\n\t\t\tif (options.type !== undefined) {\n\t\t\t\tif (Array.isArray(options.type)) {\n\t\t\t\t\tif (!options.type.includes(record.type)) return false;\n\t\t\t\t} else {\n\t\t\t\t\tif (record.type !== options.type) return false;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// Entity ID filter\n\t\t\tif (options.entityId !== undefined) {\n\t\t\t\tconst entityId =\n\t\t\t\t\ttypeof options.entityId === 'string'\n\t\t\t\t\t\t? options.entityId\n\t\t\t\t\t\t: JSON.stringify(options.entityId);\n\t\t\t\tconst recordEntityId =\n\t\t\t\t\ttypeof record.entityId === 'string'\n\t\t\t\t\t\t? record.entityId\n\t\t\t\t\t\t: JSON.stringify(record.entityId);\n\t\t\t\tif (recordEntityId !== entityId) return false;\n\t\t\t}\n\n\t\t\t// Table filter\n\t\t\tif (options.table !== undefined) {\n\t\t\t\tif (record.table !== options.table) return false;\n\t\t\t}\n\n\t\t\t// Actor ID filter\n\t\t\tif (options.actorId !== undefined) {\n\t\t\t\tif (record.actor?.id !== options.actorId) return false;\n\t\t\t}\n\n\t\t\t// Date range filters\n\t\t\tif (options.from !== undefined) {\n\t\t\t\tif (record.timestamp < options.from) return false;\n\t\t\t}\n\t\t\tif (options.to !== undefined) {\n\t\t\t\tif (record.timestamp > options.to) return false;\n\t\t\t}\n\n\t\t\treturn true;\n\t\t});\n\t}\n}\n\n/**\n * Serialized version of AuditRecord for cache storage.\n * Dates are stored as ISO strings.\n */\ntype SerializedAuditRecord = Omit<AuditRecord, 'timestamp'> & {\n\ttimestamp: string;\n};\n"],"mappings":"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"}
package/dist/index.cjs CHANGED
@@ -1,3 +1,3 @@
1
- const require_DefaultAuditor = require('./DefaultAuditor-JbZ_BQfh.cjs');
2
-
3
- exports.DefaultAuditor = require_DefaultAuditor.DefaultAuditor;
1
+ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
+ const require_DefaultAuditor = require("./DefaultAuditor.cjs");
3
+ exports.DefaultAuditor = require_DefaultAuditor.DefaultAuditor;
package/dist/index.d.cts CHANGED
@@ -1,3 +1,3 @@
1
- import { AuditActor, AuditMetadata, AuditOperation, AuditOptions, AuditQueryOptions, AuditRecord, AuditStorage, AuditableAction, Auditor, ExtractAuditPayload, ExtractAuditType, ExtractAuditorAction, ExtractStorageAuditAction, MappedAudit } from "./Auditor-DpBHXIJM.cjs";
2
- import { DefaultAuditor, DefaultAuditorConfig } from "./DefaultAuditor-GRFI86N_.cjs";
3
- export { AuditActor, AuditMetadata, AuditOperation, AuditOptions, AuditQueryOptions, AuditRecord, AuditStorage, AuditableAction, Auditor, DefaultAuditor, DefaultAuditorConfig, ExtractAuditPayload, ExtractAuditType, ExtractAuditorAction, ExtractStorageAuditAction, MappedAudit };
1
+ import { a as AuditOptions, c as ExtractAuditPayload, d as ExtractStorageAuditAction, f as MappedAudit, i as AuditOperation, l as ExtractAuditType, m as AuditStorage, n as AuditActor, o as AuditRecord, p as AuditQueryOptions, r as AuditMetadata, s as AuditableAction, t as Auditor, u as ExtractAuditorAction } from "./Auditor-BBXXCD0v.cjs";
2
+ import { DefaultAuditor, DefaultAuditorConfig } from "./DefaultAuditor.cjs";
3
+ export { type AuditActor, type AuditMetadata, type AuditOperation, type AuditOptions, type AuditQueryOptions, type AuditRecord, type AuditStorage, type AuditableAction, type Auditor, DefaultAuditor, type DefaultAuditorConfig, type ExtractAuditPayload, type ExtractAuditType, type ExtractAuditorAction, type ExtractStorageAuditAction, type MappedAudit };
package/dist/index.d.mts CHANGED
@@ -1,3 +1,3 @@
1
- import { AuditActor, AuditMetadata, AuditOperation, AuditOptions, AuditQueryOptions, AuditRecord, AuditStorage, AuditableAction, Auditor, ExtractAuditPayload, ExtractAuditType, ExtractAuditorAction, ExtractStorageAuditAction, MappedAudit } from "./Auditor-CW2NrfHS.mjs";
2
- import { DefaultAuditor, DefaultAuditorConfig } from "./DefaultAuditor-DlJ1ENfx.mjs";
3
- export { AuditActor, AuditMetadata, AuditOperation, AuditOptions, AuditQueryOptions, AuditRecord, AuditStorage, AuditableAction, Auditor, DefaultAuditor, DefaultAuditorConfig, ExtractAuditPayload, ExtractAuditType, ExtractAuditorAction, ExtractStorageAuditAction, MappedAudit };
1
+ import { a as AuditOptions, c as ExtractAuditPayload, d as ExtractStorageAuditAction, f as MappedAudit, i as AuditOperation, l as ExtractAuditType, m as AuditStorage, n as AuditActor, o as AuditRecord, p as AuditQueryOptions, r as AuditMetadata, s as AuditableAction, t as Auditor, u as ExtractAuditorAction } from "./Auditor-BBXXCD0v.mjs";
2
+ import { DefaultAuditor, DefaultAuditorConfig } from "./DefaultAuditor.mjs";
3
+ export { type AuditActor, type AuditMetadata, type AuditOperation, type AuditOptions, type AuditQueryOptions, type AuditRecord, type AuditStorage, type AuditableAction, type Auditor, DefaultAuditor, type DefaultAuditorConfig, type ExtractAuditPayload, type ExtractAuditType, type ExtractAuditorAction, type ExtractStorageAuditAction, type MappedAudit };
package/dist/index.mjs CHANGED
@@ -1,3 +1,2 @@
1
- import { DefaultAuditor } from "./DefaultAuditor-BAVnNmRh.mjs";
2
-
3
- export { DefaultAuditor };
1
+ import { DefaultAuditor } from "./DefaultAuditor.mjs";
2
+ export { DefaultAuditor };
package/dist/knex.cjs CHANGED
@@ -1,6 +1,5 @@
1
- const require_chunk = require('./chunk-CUT6urMc.cjs');
2
- const nanoid = require_chunk.__toESM(require("nanoid"));
3
-
1
+ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
+ let nanoid = require("nanoid");
4
3
  //#region src/knex.ts
5
4
  /**
6
5
  * Type guard for an active Knex transaction.
@@ -103,8 +102,7 @@ var KnexAuditStorage = class {
103
102
  query = query.orderBy(orderBy === "timestamp" ? "timestamp" : "type", orderDirection);
104
103
  if (options.limit !== void 0) query = query.limit(options.limit);
105
104
  if (options.offset !== void 0) query = query.offset(options.offset);
106
- const rows = await query;
107
- return rows.map((row) => this.fromRow(row));
105
+ return (await query).map((row) => this.fromRow(row));
108
106
  }
109
107
  async count(options) {
110
108
  let query = this.db(this.tableName).count({ count: "id" });
@@ -143,8 +141,10 @@ var KnexAuditStorage = class {
143
141
  });
144
142
  }
145
143
  applyFilters(query, options) {
146
- if (options.type !== void 0) if (Array.isArray(options.type)) query = query.whereIn("type", options.type);
147
- else query = query.where("type", options.type);
144
+ if (options.type !== void 0) {
145
+ if (Array.isArray(options.type)) query = query.whereIn("type", options.type);
146
+ else query = query.where("type", options.type);
147
+ }
148
148
  if (options.entityId !== void 0) {
149
149
  const entityId = typeof options.entityId === "string" ? options.entityId : JSON.stringify(options.entityId);
150
150
  query = query.where("entityId", entityId);
@@ -213,7 +213,7 @@ var KnexAuditStorage = class {
213
213
  return {};
214
214
  }
215
215
  getActorData(actor) {
216
- const { id, type,...rest } = actor;
216
+ const { id, type, ...rest } = actor;
217
217
  return rest;
218
218
  }
219
219
  parseEntityId(entityId) {
@@ -226,8 +226,8 @@ var KnexAuditStorage = class {
226
226
  }
227
227
  }
228
228
  };
229
-
230
229
  //#endregion
231
230
  exports.KnexAuditStorage = KnexAuditStorage;
232
231
  exports.withAuditableTransaction = withAuditableTransaction;
232
+
233
233
  //# sourceMappingURL=knex.cjs.map
package/dist/knex.cjs.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"knex.cjs","names":["connection: DatabaseConnection","db: DatabaseConnection","auditor: TransactionAwareAuditor<Knex.Transaction>","cb: (trx: Knex.Transaction) => Promise<T>","settings?: TransactionSettings","trx: Knex.Transaction","config: KnexAuditStorageConfig","records: AuditRecord[]","trx?: unknown","options: AuditQueryOptions","row: AuditLogTable","options: Omit<AuditQueryOptions, 'limit' | 'offset'>","callback: () => Promise<T>","db?: DatabaseConnection","query: any","record: AuditRecord","value: unknown","actor: NonNullable<AuditRecord['actor']>","entityId: string"],"sources":["../src/knex.ts"],"sourcesContent":["import type { Knex } from 'knex';\nimport { nanoid } from 'nanoid';\nimport type {\n\tAuditQueryOptions,\n\tAuditStorage,\n\tTransactionAwareAuditor,\n} from './storage';\nimport type { AuditRecord } from './types';\n\nexport type { TransactionAwareAuditor };\n\nexport interface TransactionSettings {\n\tisolationLevel?: Knex.IsolationLevels;\n}\n\nexport type DatabaseConnection = Knex | Knex.Transaction;\n\n/**\n * Type guard for an active Knex transaction.\n *\n * Knex marks transaction instances with `isTransaction`, and a completed\n * transaction can no longer be used, so a settled transaction is treated as a\n * plain connection.\n */\nfunction isActiveTransaction(\n\tconnection: DatabaseConnection,\n): connection is Knex.Transaction {\n\treturn (\n\t\tconnection.isTransaction === true &&\n\t\t!(connection as Knex.Transaction).isCompleted()\n\t);\n}\n\n/**\n * Execute a callback within a database transaction with automatic audit handling.\n *\n * This wrapper ensures that:\n * 1. The transaction is automatically registered with the auditor\n * 2. Manual audits (via `auditor.audit()`) are flushed BEFORE the transaction commits\n * 3. If audit flush fails, the entire transaction rolls back\n * 4. If the callback fails, audits are NOT written (atomic consistency)\n *\n * **Note:** Declarative audits (defined via `.audit([...])` on the endpoint builder)\n * are processed AFTER the handler returns, so they run outside this transaction.\n * If you need all audits to be atomic with your database operations, use manual\n * audits via `auditor.audit()` inside this wrapper.\n *\n * @param db - Database connection (Knex instance or Transaction)\n * @param auditor - Auditor instance that will receive the transaction\n * @param cb - Callback to execute within the transaction\n * @param settings - Optional transaction settings (isolation level)\n * @returns The result of the callback\n *\n * @example\n * ```typescript\n * import { withAuditableTransaction } from '@geekmidas/audit/knex';\n *\n * const result = await withAuditableTransaction(\n * services.database,\n * auditor,\n * async (trx) => {\n * const [user] = await trx('users').insert(data).returning('*');\n *\n * // Manual audits are atomic with the transaction\n * auditor.audit('user.created', { userId: user.id, email: user.email });\n *\n * return user;\n * },\n * );\n * // Audits are automatically flushed inside the transaction before commit\n * ```\n */\nexport async function withAuditableTransaction<T>(\n\tdb: DatabaseConnection,\n\tauditor: TransactionAwareAuditor<Knex.Transaction>,\n\tcb: (trx: Knex.Transaction) => Promise<T>,\n\tsettings?: TransactionSettings,\n): Promise<T> {\n\tconst execute = async (trx: Knex.Transaction): Promise<T> => {\n\t\t// Register transaction with auditor\n\t\tauditor.setTransaction(trx);\n\n\t\t// Execute the callback\n\t\tconst result = await cb(trx);\n\n\t\t// Flush audits BEFORE transaction commits\n\t\t// If this fails, the transaction will roll back\n\t\tawait auditor.flush(trx);\n\n\t\treturn result;\n\t};\n\n\t// If already in a transaction, just run with it.\n\t// Knex would otherwise open a savepoint, which commits independently.\n\tif (isActiveTransaction(db)) {\n\t\treturn execute(db);\n\t}\n\n\treturn db.transaction(execute, settings);\n}\n\n/**\n * Shape of a row in the audit log table.\n * Use this to describe your audit table when declaring Knex table types.\n *\n * Property names are camelCase to match the Kysely storage. Pair this with\n * `knexSnakeCaseMappers()` if your database columns are snake_case.\n *\n * @example\n * ```typescript\n * import { knexSnakeCaseMappers } from 'objection';\n *\n * const db = knex({\n * client: 'pg',\n * connection: process.env.DATABASE_URL,\n * ...knexSnakeCaseMappers(),\n * });\n *\n * declare module 'knex/types/tables' {\n * interface Tables {\n * audit_logs: AuditLogTable;\n * }\n * }\n * ```\n */\nexport interface AuditLogTable {\n\tid: string;\n\ttype: string;\n\toperation: string;\n\ttable: string | null;\n\tentityId: string | null;\n\toldValues: unknown | null;\n\tnewValues: unknown | null;\n\tpayload: unknown | null;\n\ttimestamp: Date;\n\tactorId: string | null;\n\tactorType: string | null;\n\tactorData: unknown | null;\n\tmetadata: unknown | null;\n}\n\n/**\n * Insertable version of AuditLogTable where id is optional.\n * Use this when your database auto-generates IDs or when using the autoId option.\n */\nexport type InsertableAuditLogTable = Omit<AuditLogTable, 'id'> & {\n\tid?: string;\n};\n\n/**\n * Configuration for KnexAuditStorage.\n */\nexport interface KnexAuditStorageConfig {\n\t/** Knex database instance */\n\tdb: Knex;\n\t/** Table name for audit logs */\n\ttableName: string;\n\t/**\n\t * Service name of the database service.\n\t * When set, endpoint adaptors will automatically use the audit transaction as `db`\n\t * in the handler context if the endpoint's database service has the same name.\n\t */\n\tdatabaseServiceName?: string;\n\t/**\n\t * Let the database auto-generate IDs (e.g., via DEFAULT gen_random_uuid()).\n\t * When true, the ID field is omitted from inserts if not provided.\n\t * When false (default), IDs are generated using nanoid if not provided.\n\t * @default false\n\t */\n\tautoId?: boolean;\n}\n\n/**\n * Knex-based audit storage implementation.\n * Stores audit records in a database table using Knex.\n *\n * @example\n * ```typescript\n * const storage = new KnexAuditStorage({\n * db: knexDb,\n * tableName: 'audit_logs',\n * });\n *\n * const auditor = new DefaultAuditor({\n * actor: { id: 'user-123', type: 'user' },\n * storage,\n * });\n * ```\n */\nexport class KnexAuditStorage implements AuditStorage {\n\tprivate readonly db: Knex;\n\tprivate readonly tableName: string;\n\tprivate readonly autoId: boolean;\n\treadonly databaseServiceName?: string;\n\n\tconstructor(config: KnexAuditStorageConfig) {\n\t\tthis.db = config.db;\n\t\tthis.tableName = config.tableName;\n\t\tthis.databaseServiceName = config.databaseServiceName;\n\t\tthis.autoId = config.autoId ?? false;\n\t}\n\n\tasync write(records: AuditRecord[], trx?: unknown): Promise<void> {\n\t\tif (records.length === 0) {\n\t\t\treturn;\n\t\t}\n\n\t\tconst db = (trx as Knex.Transaction) ?? this.db;\n\t\tconst rows = records.map((record) => this.toRow(record));\n\n\t\tawait db(this.tableName).insert(rows);\n\t}\n\n\tasync query(options: AuditQueryOptions): Promise<AuditRecord[]> {\n\t\tlet query = this.db(this.tableName).select('*');\n\n\t\tquery = this.applyFilters(query, options);\n\n\t\t// Ordering\n\t\tconst orderBy = options.orderBy ?? 'timestamp';\n\t\tconst orderDirection = options.orderDirection ?? 'desc';\n\t\tquery = query.orderBy(\n\t\t\torderBy === 'timestamp' ? 'timestamp' : 'type',\n\t\t\torderDirection,\n\t\t);\n\n\t\t// Pagination\n\t\tif (options.limit !== undefined) {\n\t\t\tquery = query.limit(options.limit);\n\t\t}\n\t\tif (options.offset !== undefined) {\n\t\t\tquery = query.offset(options.offset);\n\t\t}\n\n\t\tconst rows = await query;\n\t\treturn rows.map((row: AuditLogTable) => this.fromRow(row));\n\t}\n\n\tasync count(\n\t\toptions: Omit<AuditQueryOptions, 'limit' | 'offset'>,\n\t): Promise<number> {\n\t\tlet query = this.db(this.tableName).count({ count: 'id' });\n\n\t\tquery = this.applyFilters(query, options);\n\n\t\tconst result = await query.first();\n\t\treturn Number(result?.count ?? 0);\n\t}\n\n\t/**\n\t * Get the Knex database instance for transactional operations.\n\t * Used by endpoint adaptors to automatically wrap handlers in transactions.\n\t */\n\tgetDatabase(): Knex {\n\t\treturn this.db;\n\t}\n\n\t/**\n\t * Execute a callback within a Knex transaction with automatic audit handling.\n\t * The auditor is registered with the transaction and audits are flushed\n\t * before the transaction commits.\n\t *\n\t * If the provided db connection is already a transaction, it will be reused\n\t * instead of opening a savepoint.\n\t */\n\tasync withTransaction<T>(\n\t\tauditor: TransactionAwareAuditor<Knex.Transaction>,\n\t\tcallback: () => Promise<T>,\n\t\tdb?: DatabaseConnection,\n\t): Promise<T> {\n\t\tconst connection = db ?? this.db;\n\n\t\t// If already in a transaction, reuse it\n\t\tif (isActiveTransaction(connection)) {\n\t\t\tauditor.setTransaction(connection);\n\t\t\tconst result = await callback();\n\t\t\tawait auditor.flush(connection);\n\t\t\treturn result;\n\t\t}\n\n\t\t// Create new transaction\n\t\treturn connection.transaction(async (trx) => {\n\t\t\tauditor.setTransaction(trx);\n\t\t\tconst result = await callback();\n\t\t\tawait auditor.flush(trx);\n\t\t\treturn result;\n\t\t});\n\t}\n\n\tprivate applyFilters(query: any, options: AuditQueryOptions): any {\n\t\t// Type filter\n\t\tif (options.type !== undefined) {\n\t\t\tif (Array.isArray(options.type)) {\n\t\t\t\tquery = query.whereIn('type', options.type);\n\t\t\t} else {\n\t\t\t\tquery = query.where('type', options.type);\n\t\t\t}\n\t\t}\n\n\t\t// Entity ID filter\n\t\tif (options.entityId !== undefined) {\n\t\t\tconst entityId =\n\t\t\t\ttypeof options.entityId === 'string'\n\t\t\t\t\t? options.entityId\n\t\t\t\t\t: JSON.stringify(options.entityId);\n\t\t\tquery = query.where('entityId', entityId);\n\t\t}\n\n\t\t// Table filter\n\t\tif (options.table !== undefined) {\n\t\t\tquery = query.where('table', options.table);\n\t\t}\n\n\t\t// Actor ID filter\n\t\tif (options.actorId !== undefined) {\n\t\t\tquery = query.where('actorId', options.actorId);\n\t\t}\n\n\t\t// Date range filters\n\t\tif (options.from !== undefined) {\n\t\t\tquery = query.where('timestamp', '>=', options.from);\n\t\t}\n\t\tif (options.to !== undefined) {\n\t\t\tquery = query.where('timestamp', '<=', options.to);\n\t\t}\n\n\t\treturn query;\n\t}\n\n\tprivate toRow(record: AuditRecord): AuditLogTable {\n\t\t// If autoId is true, let database generate ID (ignore record.id)\n\t\t// If autoId is false (default), use record.id or generate with nanoid\n\t\tconst id = this.autoId ? undefined : record.id || nanoid();\n\n\t\treturn {\n\t\t\t...(id && { id }),\n\t\t\ttype: record.type,\n\t\t\toperation: record.operation,\n\t\t\ttable: record.table ?? null,\n\t\t\tentityId:\n\t\t\t\trecord.entityId === undefined\n\t\t\t\t\t? null\n\t\t\t\t\t: typeof record.entityId === 'string'\n\t\t\t\t\t\t? record.entityId\n\t\t\t\t\t\t: JSON.stringify(record.entityId),\n\t\t\toldValues: this.toJsonColumn(record.oldValues),\n\t\t\tnewValues: this.toJsonColumn(record.newValues),\n\t\t\tpayload: this.toJsonColumn(record.payload),\n\t\t\ttimestamp: record.timestamp,\n\t\t\tactorId: record.actor?.id ?? null,\n\t\t\tactorType: record.actor?.type ?? null,\n\t\t\tactorData:\n\t\t\t\trecord.actor !== undefined\n\t\t\t\t\t? this.toJsonColumn(this.getActorData(record.actor))\n\t\t\t\t\t: null,\n\t\t\tmetadata: this.toJsonColumn(record.metadata),\n\t\t} as AuditLogTable;\n\t}\n\n\tprivate fromRow(row: AuditLogTable): AuditRecord {\n\t\tconst actor =\n\t\t\trow.actorId !== null || row.actorType !== null\n\t\t\t\t? {\n\t\t\t\t\t\tid: row.actorId ?? undefined,\n\t\t\t\t\t\ttype: row.actorType ?? undefined,\n\t\t\t\t\t\t...(row.actorData ? this.parseJson(row.actorData) : {}),\n\t\t\t\t\t}\n\t\t\t\t: undefined;\n\n\t\treturn {\n\t\t\tid: row.id,\n\t\t\ttype: row.type,\n\t\t\toperation: row.operation as AuditRecord['operation'],\n\t\t\ttable: row.table ?? undefined,\n\t\t\tentityId: row.entityId ? this.parseEntityId(row.entityId) : undefined,\n\t\t\toldValues: row.oldValues ? this.parseJson(row.oldValues) : undefined,\n\t\t\tnewValues: row.newValues ? this.parseJson(row.newValues) : undefined,\n\t\t\tpayload: row.payload ? this.parseJson(row.payload) : undefined,\n\t\t\ttimestamp: row.timestamp,\n\t\t\tactor,\n\t\t\tmetadata: row.metadata ? this.parseJson(row.metadata) : undefined,\n\t\t};\n\t}\n\n\t/**\n\t * Serialize a JSON value for insertion.\n\t *\n\t * Unlike Kysely, Knex has no per-column type information to tell a json/jsonb\n\t * column from an object it should bind as a composite value, so plain objects\n\t * are stringified before they reach the driver.\n\t */\n\tprivate toJsonColumn(value: unknown): string | null {\n\t\tif (value === undefined || value === null) {\n\t\t\treturn null;\n\t\t}\n\t\treturn JSON.stringify(value);\n\t}\n\n\t/**\n\t * Parse a JSON value that may already be parsed (e.g., from jsonb columns).\n\t */\n\tprivate parseJson(value: unknown): Record<string, unknown> {\n\t\tif (typeof value === 'object' && value !== null) {\n\t\t\treturn value as Record<string, unknown>;\n\t\t}\n\t\tif (typeof value === 'string') {\n\t\t\treturn JSON.parse(value);\n\t\t}\n\t\treturn {};\n\t}\n\n\tprivate getActorData(\n\t\tactor: NonNullable<AuditRecord['actor']>,\n\t): Record<string, unknown> {\n\t\tconst { id, type, ...rest } = actor;\n\t\treturn rest;\n\t}\n\n\tprivate parseEntityId(entityId: string): string | Record<string, unknown> {\n\t\ttry {\n\t\t\tconst parsed = JSON.parse(entityId);\n\t\t\tif (typeof parsed === 'object' && parsed !== null) {\n\t\t\t\treturn parsed;\n\t\t\t}\n\t\t\treturn entityId;\n\t\t} catch {\n\t\t\treturn entityId;\n\t\t}\n\t}\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"knex.cjs","names":[],"sources":["../src/knex.ts"],"sourcesContent":["import type { Knex } from 'knex';\nimport { nanoid } from 'nanoid';\nimport type {\n\tAuditQueryOptions,\n\tAuditStorage,\n\tTransactionAwareAuditor,\n} from './storage';\nimport type { AuditRecord } from './types';\n\nexport type { TransactionAwareAuditor };\n\nexport interface TransactionSettings {\n\tisolationLevel?: Knex.IsolationLevels;\n}\n\nexport type DatabaseConnection = Knex | Knex.Transaction;\n\n/**\n * Type guard for an active Knex transaction.\n *\n * Knex marks transaction instances with `isTransaction`, and a completed\n * transaction can no longer be used, so a settled transaction is treated as a\n * plain connection.\n */\nfunction isActiveTransaction(\n\tconnection: DatabaseConnection,\n): connection is Knex.Transaction {\n\treturn (\n\t\tconnection.isTransaction === true &&\n\t\t!(connection as Knex.Transaction).isCompleted()\n\t);\n}\n\n/**\n * Execute a callback within a database transaction with automatic audit handling.\n *\n * This wrapper ensures that:\n * 1. The transaction is automatically registered with the auditor\n * 2. Manual audits (via `auditor.audit()`) are flushed BEFORE the transaction commits\n * 3. If audit flush fails, the entire transaction rolls back\n * 4. If the callback fails, audits are NOT written (atomic consistency)\n *\n * **Note:** Declarative audits (defined via `.audit([...])` on the endpoint builder)\n * are processed AFTER the handler returns, so they run outside this transaction.\n * If you need all audits to be atomic with your database operations, use manual\n * audits via `auditor.audit()` inside this wrapper.\n *\n * @param db - Database connection (Knex instance or Transaction)\n * @param auditor - Auditor instance that will receive the transaction\n * @param cb - Callback to execute within the transaction\n * @param settings - Optional transaction settings (isolation level)\n * @returns The result of the callback\n *\n * @example\n * ```typescript\n * import { withAuditableTransaction } from '@geekmidas/audit/knex';\n *\n * const result = await withAuditableTransaction(\n * services.database,\n * auditor,\n * async (trx) => {\n * const [user] = await trx('users').insert(data).returning('*');\n *\n * // Manual audits are atomic with the transaction\n * auditor.audit('user.created', { userId: user.id, email: user.email });\n *\n * return user;\n * },\n * );\n * // Audits are automatically flushed inside the transaction before commit\n * ```\n */\nexport async function withAuditableTransaction<T>(\n\tdb: DatabaseConnection,\n\tauditor: TransactionAwareAuditor<Knex.Transaction>,\n\tcb: (trx: Knex.Transaction) => Promise<T>,\n\tsettings?: TransactionSettings,\n): Promise<T> {\n\tconst execute = async (trx: Knex.Transaction): Promise<T> => {\n\t\t// Register transaction with auditor\n\t\tauditor.setTransaction(trx);\n\n\t\t// Execute the callback\n\t\tconst result = await cb(trx);\n\n\t\t// Flush audits BEFORE transaction commits\n\t\t// If this fails, the transaction will roll back\n\t\tawait auditor.flush(trx);\n\n\t\treturn result;\n\t};\n\n\t// If already in a transaction, just run with it.\n\t// Knex would otherwise open a savepoint, which commits independently.\n\tif (isActiveTransaction(db)) {\n\t\treturn execute(db);\n\t}\n\n\treturn db.transaction(execute, settings);\n}\n\n/**\n * Shape of a row in the audit log table.\n * Use this to describe your audit table when declaring Knex table types.\n *\n * Property names are camelCase to match the Kysely storage. Pair this with\n * `knexSnakeCaseMappers()` if your database columns are snake_case.\n *\n * @example\n * ```typescript\n * import { knexSnakeCaseMappers } from 'objection';\n *\n * const db = knex({\n * client: 'pg',\n * connection: process.env.DATABASE_URL,\n * ...knexSnakeCaseMappers(),\n * });\n *\n * declare module 'knex/types/tables' {\n * interface Tables {\n * audit_logs: AuditLogTable;\n * }\n * }\n * ```\n */\nexport interface AuditLogTable {\n\tid: string;\n\ttype: string;\n\toperation: string;\n\ttable: string | null;\n\tentityId: string | null;\n\toldValues: unknown | null;\n\tnewValues: unknown | null;\n\tpayload: unknown | null;\n\ttimestamp: Date;\n\tactorId: string | null;\n\tactorType: string | null;\n\tactorData: unknown | null;\n\tmetadata: unknown | null;\n}\n\n/**\n * Insertable version of AuditLogTable where id is optional.\n * Use this when your database auto-generates IDs or when using the autoId option.\n */\nexport type InsertableAuditLogTable = Omit<AuditLogTable, 'id'> & {\n\tid?: string;\n};\n\n/**\n * Configuration for KnexAuditStorage.\n */\nexport interface KnexAuditStorageConfig {\n\t/** Knex database instance */\n\tdb: Knex;\n\t/** Table name for audit logs */\n\ttableName: string;\n\t/**\n\t * Service name of the database service.\n\t * When set, endpoint adaptors will automatically use the audit transaction as `db`\n\t * in the handler context if the endpoint's database service has the same name.\n\t */\n\tdatabaseServiceName?: string;\n\t/**\n\t * Let the database auto-generate IDs (e.g., via DEFAULT gen_random_uuid()).\n\t * When true, the ID field is omitted from inserts if not provided.\n\t * When false (default), IDs are generated using nanoid if not provided.\n\t * @default false\n\t */\n\tautoId?: boolean;\n}\n\n/**\n * Knex-based audit storage implementation.\n * Stores audit records in a database table using Knex.\n *\n * @example\n * ```typescript\n * const storage = new KnexAuditStorage({\n * db: knexDb,\n * tableName: 'audit_logs',\n * });\n *\n * const auditor = new DefaultAuditor({\n * actor: { id: 'user-123', type: 'user' },\n * storage,\n * });\n * ```\n */\nexport class KnexAuditStorage implements AuditStorage {\n\tprivate readonly db: Knex;\n\tprivate readonly tableName: string;\n\tprivate readonly autoId: boolean;\n\treadonly databaseServiceName?: string;\n\n\tconstructor(config: KnexAuditStorageConfig) {\n\t\tthis.db = config.db;\n\t\tthis.tableName = config.tableName;\n\t\tthis.databaseServiceName = config.databaseServiceName;\n\t\tthis.autoId = config.autoId ?? false;\n\t}\n\n\tasync write(records: AuditRecord[], trx?: unknown): Promise<void> {\n\t\tif (records.length === 0) {\n\t\t\treturn;\n\t\t}\n\n\t\tconst db = (trx as Knex.Transaction) ?? this.db;\n\t\tconst rows = records.map((record) => this.toRow(record));\n\n\t\tawait db(this.tableName).insert(rows);\n\t}\n\n\tasync query(options: AuditQueryOptions): Promise<AuditRecord[]> {\n\t\tlet query = this.db(this.tableName).select('*');\n\n\t\tquery = this.applyFilters(query, options);\n\n\t\t// Ordering\n\t\tconst orderBy = options.orderBy ?? 'timestamp';\n\t\tconst orderDirection = options.orderDirection ?? 'desc';\n\t\tquery = query.orderBy(\n\t\t\torderBy === 'timestamp' ? 'timestamp' : 'type',\n\t\t\torderDirection,\n\t\t);\n\n\t\t// Pagination\n\t\tif (options.limit !== undefined) {\n\t\t\tquery = query.limit(options.limit);\n\t\t}\n\t\tif (options.offset !== undefined) {\n\t\t\tquery = query.offset(options.offset);\n\t\t}\n\n\t\tconst rows = await query;\n\t\treturn rows.map((row: AuditLogTable) => this.fromRow(row));\n\t}\n\n\tasync count(\n\t\toptions: Omit<AuditQueryOptions, 'limit' | 'offset'>,\n\t): Promise<number> {\n\t\tlet query = this.db(this.tableName).count({ count: 'id' });\n\n\t\tquery = this.applyFilters(query, options);\n\n\t\tconst result = await query.first();\n\t\treturn Number(result?.count ?? 0);\n\t}\n\n\t/**\n\t * Get the Knex database instance for transactional operations.\n\t * Used by endpoint adaptors to automatically wrap handlers in transactions.\n\t */\n\tgetDatabase(): Knex {\n\t\treturn this.db;\n\t}\n\n\t/**\n\t * Execute a callback within a Knex transaction with automatic audit handling.\n\t * The auditor is registered with the transaction and audits are flushed\n\t * before the transaction commits.\n\t *\n\t * If the provided db connection is already a transaction, it will be reused\n\t * instead of opening a savepoint.\n\t */\n\tasync withTransaction<T>(\n\t\tauditor: TransactionAwareAuditor<Knex.Transaction>,\n\t\tcallback: () => Promise<T>,\n\t\tdb?: DatabaseConnection,\n\t): Promise<T> {\n\t\tconst connection = db ?? this.db;\n\n\t\t// If already in a transaction, reuse it\n\t\tif (isActiveTransaction(connection)) {\n\t\t\tauditor.setTransaction(connection);\n\t\t\tconst result = await callback();\n\t\t\tawait auditor.flush(connection);\n\t\t\treturn result;\n\t\t}\n\n\t\t// Create new transaction\n\t\treturn connection.transaction(async (trx) => {\n\t\t\tauditor.setTransaction(trx);\n\t\t\tconst result = await callback();\n\t\t\tawait auditor.flush(trx);\n\t\t\treturn result;\n\t\t});\n\t}\n\n\tprivate applyFilters(query: any, options: AuditQueryOptions): any {\n\t\t// Type filter\n\t\tif (options.type !== undefined) {\n\t\t\tif (Array.isArray(options.type)) {\n\t\t\t\tquery = query.whereIn('type', options.type);\n\t\t\t} else {\n\t\t\t\tquery = query.where('type', options.type);\n\t\t\t}\n\t\t}\n\n\t\t// Entity ID filter\n\t\tif (options.entityId !== undefined) {\n\t\t\tconst entityId =\n\t\t\t\ttypeof options.entityId === 'string'\n\t\t\t\t\t? options.entityId\n\t\t\t\t\t: JSON.stringify(options.entityId);\n\t\t\tquery = query.where('entityId', entityId);\n\t\t}\n\n\t\t// Table filter\n\t\tif (options.table !== undefined) {\n\t\t\tquery = query.where('table', options.table);\n\t\t}\n\n\t\t// Actor ID filter\n\t\tif (options.actorId !== undefined) {\n\t\t\tquery = query.where('actorId', options.actorId);\n\t\t}\n\n\t\t// Date range filters\n\t\tif (options.from !== undefined) {\n\t\t\tquery = query.where('timestamp', '>=', options.from);\n\t\t}\n\t\tif (options.to !== undefined) {\n\t\t\tquery = query.where('timestamp', '<=', options.to);\n\t\t}\n\n\t\treturn query;\n\t}\n\n\tprivate toRow(record: AuditRecord): AuditLogTable {\n\t\t// If autoId is true, let database generate ID (ignore record.id)\n\t\t// If autoId is false (default), use record.id or generate with nanoid\n\t\tconst id = this.autoId ? undefined : record.id || nanoid();\n\n\t\treturn {\n\t\t\t...(id && { id }),\n\t\t\ttype: record.type,\n\t\t\toperation: record.operation,\n\t\t\ttable: record.table ?? null,\n\t\t\tentityId:\n\t\t\t\trecord.entityId === undefined\n\t\t\t\t\t? null\n\t\t\t\t\t: typeof record.entityId === 'string'\n\t\t\t\t\t\t? record.entityId\n\t\t\t\t\t\t: JSON.stringify(record.entityId),\n\t\t\toldValues: this.toJsonColumn(record.oldValues),\n\t\t\tnewValues: this.toJsonColumn(record.newValues),\n\t\t\tpayload: this.toJsonColumn(record.payload),\n\t\t\ttimestamp: record.timestamp,\n\t\t\tactorId: record.actor?.id ?? null,\n\t\t\tactorType: record.actor?.type ?? null,\n\t\t\tactorData:\n\t\t\t\trecord.actor !== undefined\n\t\t\t\t\t? this.toJsonColumn(this.getActorData(record.actor))\n\t\t\t\t\t: null,\n\t\t\tmetadata: this.toJsonColumn(record.metadata),\n\t\t} as AuditLogTable;\n\t}\n\n\tprivate fromRow(row: AuditLogTable): AuditRecord {\n\t\tconst actor =\n\t\t\trow.actorId !== null || row.actorType !== null\n\t\t\t\t? {\n\t\t\t\t\t\tid: row.actorId ?? undefined,\n\t\t\t\t\t\ttype: row.actorType ?? undefined,\n\t\t\t\t\t\t...(row.actorData ? this.parseJson(row.actorData) : {}),\n\t\t\t\t\t}\n\t\t\t\t: undefined;\n\n\t\treturn {\n\t\t\tid: row.id,\n\t\t\ttype: row.type,\n\t\t\toperation: row.operation as AuditRecord['operation'],\n\t\t\ttable: row.table ?? undefined,\n\t\t\tentityId: row.entityId ? this.parseEntityId(row.entityId) : undefined,\n\t\t\toldValues: row.oldValues ? this.parseJson(row.oldValues) : undefined,\n\t\t\tnewValues: row.newValues ? this.parseJson(row.newValues) : undefined,\n\t\t\tpayload: row.payload ? this.parseJson(row.payload) : undefined,\n\t\t\ttimestamp: row.timestamp,\n\t\t\tactor,\n\t\t\tmetadata: row.metadata ? this.parseJson(row.metadata) : undefined,\n\t\t};\n\t}\n\n\t/**\n\t * Serialize a JSON value for insertion.\n\t *\n\t * Unlike Kysely, Knex has no per-column type information to tell a json/jsonb\n\t * column from an object it should bind as a composite value, so plain objects\n\t * are stringified before they reach the driver.\n\t */\n\tprivate toJsonColumn(value: unknown): string | null {\n\t\tif (value === undefined || value === null) {\n\t\t\treturn null;\n\t\t}\n\t\treturn JSON.stringify(value);\n\t}\n\n\t/**\n\t * Parse a JSON value that may already be parsed (e.g., from jsonb columns).\n\t */\n\tprivate parseJson(value: unknown): Record<string, unknown> {\n\t\tif (typeof value === 'object' && value !== null) {\n\t\t\treturn value as Record<string, unknown>;\n\t\t}\n\t\tif (typeof value === 'string') {\n\t\t\treturn JSON.parse(value);\n\t\t}\n\t\treturn {};\n\t}\n\n\tprivate getActorData(\n\t\tactor: NonNullable<AuditRecord['actor']>,\n\t): Record<string, unknown> {\n\t\tconst { id, type, ...rest } = actor;\n\t\treturn rest;\n\t}\n\n\tprivate parseEntityId(entityId: string): string | Record<string, unknown> {\n\t\ttry {\n\t\t\tconst parsed = JSON.parse(entityId);\n\t\t\tif (typeof parsed === 'object' && parsed !== null) {\n\t\t\t\treturn parsed;\n\t\t\t}\n\t\t\treturn entityId;\n\t\t} catch {\n\t\t\treturn entityId;\n\t\t}\n\t}\n}\n"],"mappings":"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"}
package/dist/knex.d.cts CHANGED
@@ -1,11 +1,10 @@
1
- import { AuditQueryOptions, AuditRecord, AuditStorage, TransactionAwareAuditor } from "./Auditor-DpBHXIJM.cjs";
1
+ import { h as TransactionAwareAuditor, m as AuditStorage, o as AuditRecord, p as AuditQueryOptions } from "./Auditor-BBXXCD0v.cjs";
2
2
  import { Knex } from "knex";
3
-
4
3
  //#region src/knex.d.ts
5
- interface TransactionSettings {
4
+ export interface TransactionSettings {
6
5
  isolationLevel?: Knex.IsolationLevels;
7
6
  }
8
- type DatabaseConnection = Knex | Knex.Transaction;
7
+ export type DatabaseConnection = Knex | Knex.Transaction;
9
8
  /**
10
9
  * Execute a callback within a database transaction with automatic audit handling.
11
10
  *
@@ -45,7 +44,7 @@ type DatabaseConnection = Knex | Knex.Transaction;
45
44
  * // Audits are automatically flushed inside the transaction before commit
46
45
  * ```
47
46
  */
48
- declare function withAuditableTransaction<T>(db: DatabaseConnection, auditor: TransactionAwareAuditor<Knex.Transaction>, cb: (trx: Knex.Transaction) => Promise<T>, settings?: TransactionSettings): Promise<T>;
47
+ export declare function withAuditableTransaction<T>(db: DatabaseConnection, auditor: TransactionAwareAuditor<Knex.Transaction>, cb: (trx: Knex.Transaction) => Promise<T>, settings?: TransactionSettings): Promise<T>;
49
48
  /**
50
49
  * Shape of a row in the audit log table.
51
50
  * Use this to describe your audit table when declaring Knex table types.
@@ -70,7 +69,7 @@ declare function withAuditableTransaction<T>(db: DatabaseConnection, auditor: Tr
70
69
  * }
71
70
  * ```
72
71
  */
73
- interface AuditLogTable {
72
+ export interface AuditLogTable {
74
73
  id: string;
75
74
  type: string;
76
75
  operation: string;
@@ -89,13 +88,13 @@ interface AuditLogTable {
89
88
  * Insertable version of AuditLogTable where id is optional.
90
89
  * Use this when your database auto-generates IDs or when using the autoId option.
91
90
  */
92
- type InsertableAuditLogTable = Omit<AuditLogTable, 'id'> & {
91
+ export type InsertableAuditLogTable = Omit<AuditLogTable, 'id'> & {
93
92
  id?: string;
94
93
  };
95
94
  /**
96
95
  * Configuration for KnexAuditStorage.
97
96
  */
98
- interface KnexAuditStorageConfig {
97
+ export interface KnexAuditStorageConfig {
99
98
  /** Knex database instance */
100
99
  db: Knex;
101
100
  /** Table name for audit logs */
@@ -131,7 +130,7 @@ interface KnexAuditStorageConfig {
131
130
  * });
132
131
  * ```
133
132
  */
134
- declare class KnexAuditStorage implements AuditStorage {
133
+ export declare class KnexAuditStorage implements AuditStorage {
135
134
  private readonly db;
136
135
  private readonly tableName;
137
136
  private readonly autoId;
@@ -172,7 +171,6 @@ declare class KnexAuditStorage implements AuditStorage {
172
171
  private getActorData;
173
172
  private parseEntityId;
174
173
  }
175
- //# sourceMappingURL=knex.d.ts.map
176
174
  //#endregion
177
- export { AuditLogTable, DatabaseConnection, InsertableAuditLogTable, KnexAuditStorage, KnexAuditStorageConfig, TransactionAwareAuditor, TransactionSettings, withAuditableTransaction };
175
+ export type { TransactionAwareAuditor };
178
176
  //# sourceMappingURL=knex.d.cts.map
@@ -1 +1 @@
1
- {"version":3,"file":"knex.d.cts","names":[],"sources":["../src/knex.ts"],"sourcesContent":[],"mappings":";;;;AAWiB,UAAA,mBAAA,CACC;EAGN,cAAA,CAAA,EAHM,IAAA,CAAK,eAGO;;AAAG,KAArB,kBAAA,GAAqB,IAAA,GAAO,IAAA,CAAK,WAAZ;;AAAuB;AAyDxD;;;;;;;;;;;AAKU;AAgDV;AAoBA;;;;AAA0C;AAO1C;AAqCA;;;;;;;;;;;;;;;;;;AAgFI,iBArMkB,wBAqMlB,CAAA,CAAA,CAAA,CAAA,EAAA,EApMC,kBAoMD,EAAA,OAAA,EAnMM,uBAmMN,CAnM8B,IAAA,CAAK,WAmMnC,CAAA,EAAA,EAAA,EAAA,CAAA,GAAA,EAlMO,IAAA,CAAK,WAkMZ,EAAA,GAlM4B,OAkM5B,CAlMoC,CAkMpC,CAAA,EAAA,QAAA,CAAA,EAjMQ,mBAiMR,CAAA,EAhMD,OAgMC,CAhMO,CAgMP,CAAA;;AAhFiD;;;;;;;;;;;;;;;;;;;;;;;UAhEpC,aAAA;;;;;;;;;aASL;;;;;;;;;;KAWA,uBAAA,GAA0B,KAAK;;;;;;UAO1B,sBAAA;;MAEZ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;cAmCQ,gBAAA,YAA4B;;;;;sBAMpB;iBAOC,+BAA+B;iBAW/B,oBAAoB,QAAQ;iBA0BvC,KAAK,yCACZ;;;;;iBAaY;;;;;;;;;8BAaL,wBAAwB,IAAA,CAAK,8BACtB,QAAQ,SACnB,qBACH,QAAQ"}
1
+ {"version":3,"file":"knex.d.cts","names":[],"sources":["../src/knex.ts"],"mappings":";;;iBAWiB;EAChB,iBAAiB,KAAK;;YAGX,qBAAqB,OAAO,KAAK;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;wBAyDvB,yBAAyB,GAC9C,IAAI,oBACJ,SAAS,wBAAwB,KAAK,cACtC,KAAK,KAAK,KAAK,gBAAgB,QAAQ,IACvC,WAAW,sBACT,QAAQ;;;;;;;;;;;;;;;;;;;;;;;;;iBAgDM;EAChB;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA,WAAW;EACX;EACA;EACA;EACA;;;;;;YAOW,0BAA0B,KAAK;EAC1C;;;;;iBAMgB;;EAEhB,IAAI;;EAEJ;;;;;;EAMA;;;;;;;EAOA;;;;;;;;;;;;;;;;;;;qBAoBY,4BAA4B;mBACvB;mBACA;mBACA;WACR;EAET,YAAY,QAAQ;EAOd,MAAM,SAAS,eAAe,gBAAgB;EAW9C,MAAM,SAAS,oBAAoB,QAAQ;EAyB3C,MACL,SAAS,KAAK,yCACZ;;;;;EAaH,eAAe;;;;;;;;;EAYT,gBAAgB,GACrB,SAAS,wBAAwB,KAAK,cACtC,gBAAgB,QAAQ,IACxB,KAAK,qBACH,QAAQ;UAoBH;UAwCA;UA8BA;;;;;;;;UAgCA;;;;UAUA;UAUA;UAOA"}