@usebetterdev/audit-drizzle 0.4.0-beta.1 → 0.4.0-beta.3
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/LICENSE +21 -0
- package/dist/index.cjs +115 -2
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +3 -2
- package/dist/index.d.ts +3 -2
- package/dist/index.js +114 -1
- package/dist/index.js.map +1 -1
- package/package.json +13 -13
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 usebetter
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/dist/index.cjs
CHANGED
|
@@ -7,11 +7,11 @@ var __export = (target, all) => {
|
|
|
7
7
|
for (var name in all)
|
|
8
8
|
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
9
|
};
|
|
10
|
-
var __copyProps = (to, from, except,
|
|
10
|
+
var __copyProps = (to, from, except, desc3) => {
|
|
11
11
|
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
12
|
for (let key of __getOwnPropNames(from))
|
|
13
13
|
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
-
__defProp(to, key, { get: () => from[key], enumerable: !(
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc3 = __getOwnPropDesc(from, key)) || desc3.enumerable });
|
|
15
15
|
}
|
|
16
16
|
return to;
|
|
17
17
|
};
|
|
@@ -316,6 +316,15 @@ function decodeCursor(cursor) {
|
|
|
316
316
|
// src/adapter.ts
|
|
317
317
|
var DEFAULT_LIMIT = 50;
|
|
318
318
|
var MAX_LIMIT = 250;
|
|
319
|
+
function toCount(value) {
|
|
320
|
+
if (typeof value === "number") {
|
|
321
|
+
return value;
|
|
322
|
+
}
|
|
323
|
+
if (typeof value === "string") {
|
|
324
|
+
return Number(value);
|
|
325
|
+
}
|
|
326
|
+
return 0;
|
|
327
|
+
}
|
|
319
328
|
function drizzleAuditAdapter(db) {
|
|
320
329
|
return {
|
|
321
330
|
async writeLog(log) {
|
|
@@ -349,9 +358,113 @@ function drizzleAuditAdapter(db) {
|
|
|
349
358
|
return null;
|
|
350
359
|
}
|
|
351
360
|
return rowToAuditLog(row);
|
|
361
|
+
},
|
|
362
|
+
/**
|
|
363
|
+
* Delete audit log entries older than `before`.
|
|
364
|
+
*
|
|
365
|
+
* **Warning:** Large deletes may hold a row-level lock on the `audit_logs`
|
|
366
|
+
* table for an extended period. Run during low-traffic windows when purging
|
|
367
|
+
* millions of rows.
|
|
368
|
+
*/
|
|
369
|
+
async purgeLogs(options) {
|
|
370
|
+
if (options.tableName !== void 0 && options.tableName.trim().length === 0) {
|
|
371
|
+
throw new Error("purgeLogs: tableName must be a non-empty string when provided");
|
|
372
|
+
}
|
|
373
|
+
const conditions = [(0, import_drizzle_orm2.lt)(auditLogs.timestamp, options.before)];
|
|
374
|
+
if (options.tableName !== void 0) {
|
|
375
|
+
conditions.push((0, import_drizzle_orm2.eq)(auditLogs.tableName, options.tableName));
|
|
376
|
+
}
|
|
377
|
+
const result = await db.delete(auditLogs).where((0, import_drizzle_orm2.and)(...conditions));
|
|
378
|
+
const rowCount = result.rowCount;
|
|
379
|
+
return { deletedCount: rowCount ?? 0 };
|
|
380
|
+
},
|
|
381
|
+
async getStats(options) {
|
|
382
|
+
const sinceCondition = options?.since !== void 0 ? (0, import_drizzle_orm2.gte)(auditLogs.timestamp, options.since) : void 0;
|
|
383
|
+
const summaryQuery = db.select({
|
|
384
|
+
totalLogs: import_drizzle_orm2.sql`count(*)`,
|
|
385
|
+
tablesAudited: import_drizzle_orm2.sql`count(DISTINCT ${auditLogs.tableName})`
|
|
386
|
+
}).from(auditLogs).where(sinceCondition);
|
|
387
|
+
const eventsPerDayQuery = db.select({
|
|
388
|
+
date: import_drizzle_orm2.sql`date_trunc('day', ${auditLogs.timestamp})::date`,
|
|
389
|
+
count: import_drizzle_orm2.sql`count(*)`
|
|
390
|
+
}).from(auditLogs).where(sinceCondition).groupBy(import_drizzle_orm2.sql`date_trunc('day', ${auditLogs.timestamp})`).orderBy(import_drizzle_orm2.sql`date_trunc('day', ${auditLogs.timestamp})`).limit(365);
|
|
391
|
+
const topActorsQuery = db.select({
|
|
392
|
+
actorId: auditLogs.actorId,
|
|
393
|
+
count: import_drizzle_orm2.sql`count(*)`
|
|
394
|
+
}).from(auditLogs).where((0, import_drizzle_orm2.and)(sinceCondition, (0, import_drizzle_orm2.isNotNull)(auditLogs.actorId))).groupBy(auditLogs.actorId).orderBy((0, import_drizzle_orm2.desc)(import_drizzle_orm2.sql`count(*)`)).limit(10);
|
|
395
|
+
const topTablesQuery = db.select({
|
|
396
|
+
tableName: auditLogs.tableName,
|
|
397
|
+
count: import_drizzle_orm2.sql`count(*)`
|
|
398
|
+
}).from(auditLogs).where(sinceCondition).groupBy(auditLogs.tableName).orderBy((0, import_drizzle_orm2.desc)(import_drizzle_orm2.sql`count(*)`)).limit(10);
|
|
399
|
+
const operationQuery = db.select({
|
|
400
|
+
operation: auditLogs.operation,
|
|
401
|
+
count: import_drizzle_orm2.sql`count(*)`
|
|
402
|
+
}).from(auditLogs).where(sinceCondition).groupBy(auditLogs.operation);
|
|
403
|
+
const severityQuery = db.select({
|
|
404
|
+
severity: auditLogs.severity,
|
|
405
|
+
count: import_drizzle_orm2.sql`count(*)`
|
|
406
|
+
}).from(auditLogs).where((0, import_drizzle_orm2.and)(sinceCondition, (0, import_drizzle_orm2.isNotNull)(auditLogs.severity))).groupBy(auditLogs.severity);
|
|
407
|
+
const results = await Promise.all([
|
|
408
|
+
summaryQuery,
|
|
409
|
+
eventsPerDayQuery,
|
|
410
|
+
topActorsQuery,
|
|
411
|
+
topTablesQuery,
|
|
412
|
+
operationQuery,
|
|
413
|
+
severityQuery
|
|
414
|
+
]);
|
|
415
|
+
const [
|
|
416
|
+
summaryRows,
|
|
417
|
+
eventsPerDayRows,
|
|
418
|
+
topActorsRows,
|
|
419
|
+
topTablesRows,
|
|
420
|
+
operationRows,
|
|
421
|
+
severityRows
|
|
422
|
+
] = results;
|
|
423
|
+
return assembleStats(
|
|
424
|
+
summaryRows,
|
|
425
|
+
eventsPerDayRows,
|
|
426
|
+
topActorsRows,
|
|
427
|
+
topTablesRows,
|
|
428
|
+
operationRows,
|
|
429
|
+
severityRows
|
|
430
|
+
);
|
|
352
431
|
}
|
|
353
432
|
};
|
|
354
433
|
}
|
|
434
|
+
function assembleStats(summaryRows, eventsPerDayRows, topActorsRows, topTablesRows, operationRows, severityRows) {
|
|
435
|
+
const summary = summaryRows[0];
|
|
436
|
+
const totalLogs = summary !== void 0 ? toCount(summary.totalLogs) : 0;
|
|
437
|
+
const tablesAudited = summary !== void 0 ? toCount(summary.tablesAudited) : 0;
|
|
438
|
+
const eventsPerDay = eventsPerDayRows.map((row) => ({
|
|
439
|
+
date: row.date instanceof Date ? row.date.toISOString().split("T")[0] : String(row.date),
|
|
440
|
+
count: toCount(row.count)
|
|
441
|
+
}));
|
|
442
|
+
const topActors = topActorsRows.map((row) => ({
|
|
443
|
+
actorId: String(row.actorId),
|
|
444
|
+
count: toCount(row.count)
|
|
445
|
+
}));
|
|
446
|
+
const topTables = topTablesRows.map((row) => ({
|
|
447
|
+
tableName: String(row.tableName),
|
|
448
|
+
count: toCount(row.count)
|
|
449
|
+
}));
|
|
450
|
+
const operationBreakdown = {};
|
|
451
|
+
for (const row of operationRows) {
|
|
452
|
+
operationBreakdown[String(row.operation)] = toCount(row.count);
|
|
453
|
+
}
|
|
454
|
+
const severityBreakdown = {};
|
|
455
|
+
for (const row of severityRows) {
|
|
456
|
+
severityBreakdown[String(row.severity)] = toCount(row.count);
|
|
457
|
+
}
|
|
458
|
+
return {
|
|
459
|
+
totalLogs,
|
|
460
|
+
tablesAudited,
|
|
461
|
+
eventsPerDay,
|
|
462
|
+
topActors,
|
|
463
|
+
topTables,
|
|
464
|
+
operationBreakdown,
|
|
465
|
+
severityBreakdown
|
|
466
|
+
};
|
|
467
|
+
}
|
|
355
468
|
|
|
356
469
|
// src/proxy.ts
|
|
357
470
|
var import_drizzle_orm3 = require("drizzle-orm");
|
package/dist/index.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.ts","../src/adapter.ts","../src/schema.ts","../src/column-map.ts","../src/query.ts","../src/proxy.ts","../src/operation-map.ts"],"sourcesContent":["export { drizzleAuditAdapter } from \"./adapter.js\";\nexport type { DrizzlePgDatabase } from \"./adapter.js\";\nexport { withAuditProxy } from \"./proxy.js\";\nexport type { AuditProxyOptions, MissingRecordIdBehavior } from \"./proxy.js\";\nexport { auditLogs } from \"./schema.js\";\nexport type { AuditLogRow, NewAuditLogRow } from \"./schema.js\";\nexport {\n buildWhereConditions,\n buildCursorCondition,\n buildOrderBy,\n encodeCursor,\n decodeCursor,\n} from \"./query.js\";\n","import type {\n AuditDatabaseAdapter,\n AuditLog,\n AuditQuerySpec,\n AuditQueryResult,\n} from \"@usebetterdev/audit-core\";\nimport { and, eq } from \"drizzle-orm\";\nimport { auditLogs } from \"./schema.js\";\nimport { auditLogToRow, rowToAuditLog } from \"./column-map.js\";\nimport {\n buildWhereConditions,\n buildCursorCondition,\n buildOrderBy,\n encodeCursor,\n} from \"./query.js\";\n\n/**\n * Minimal shape for a Drizzle pg database that supports insert and select operations.\n * Duck-typed so callers don't need the full Drizzle generic types.\n */\nexport interface DrizzlePgDatabase {\n insert(table: unknown): {\n values(data: unknown): { execute(): Promise<unknown> };\n };\n select(): unknown;\n}\n\nconst DEFAULT_LIMIT = 50;\nconst MAX_LIMIT = 250;\n\n/**\n * Creates an `AuditDatabaseAdapter` backed by a Drizzle pg database.\n *\n * Implements `writeLog` for inserting audit entries and `queryLogs` for\n * filtered, cursor-paginated queries.\n */\nexport function drizzleAuditAdapter(\n db: DrizzlePgDatabase,\n): AuditDatabaseAdapter {\n return {\n async writeLog(log: AuditLog): Promise<void> {\n const row = auditLogToRow(log);\n await db.insert(auditLogs).values(row).execute();\n },\n\n async queryLogs(spec: AuditQuerySpec): Promise<AuditQueryResult> {\n const sortOrder = spec.sortOrder ?? \"desc\";\n const limit = Math.min(spec.limit ?? DEFAULT_LIMIT, MAX_LIMIT);\n\n const whereCondition = buildWhereConditions(spec.filters);\n const cursorCondition =\n spec.cursor !== undefined\n ? buildCursorCondition(spec.cursor, sortOrder)\n : undefined;\n const combined = and(whereCondition, cursorCondition);\n\n const fetchLimit = limit + 1;\n const orderColumns = buildOrderBy(sortOrder);\n\n // The duck-typed interface returns `unknown` from select().\n // We build the full Drizzle query chain and cast at the boundary.\n const query = (db.select() as ReturnType<typeof buildSelectChain>)\n .from(auditLogs)\n .where(combined)\n .orderBy(...orderColumns)\n .limit(fetchLimit);\n\n const rows = (await query) as (typeof auditLogs.$inferSelect)[];\n\n const hasNextPage = rows.length > limit;\n const resultRows = hasNextPage ? rows.slice(0, -1) : rows;\n const entries = resultRows.map(rowToAuditLog);\n const lastRow = resultRows[resultRows.length - 1];\n\n if (hasNextPage && lastRow !== undefined) {\n return { entries, nextCursor: encodeCursor(lastRow.timestamp, lastRow.id) };\n }\n\n return { entries };\n },\n\n async getLogById(id: string): Promise<AuditLog | null> {\n const query = (db.select() as ReturnType<typeof buildSelectChain>)\n .from(auditLogs)\n .where(eq(auditLogs.id, id))\n .limit(1);\n\n const rows = (await query) as (typeof auditLogs.$inferSelect)[];\n const row = rows[0];\n if (row === undefined) {\n return null;\n }\n return rowToAuditLog(row);\n },\n };\n}\n\n/**\n * Type helper for the Drizzle select chain — used only for casting.\n * Not exported; exists to avoid `any`.\n */\nfunction buildSelectChain() {\n // This function is never called — it exists only for its return type.\n return undefined as unknown as {\n from(table: unknown): {\n where(condition: unknown): {\n orderBy(...columns: unknown[]): {\n limit(n: number): Promise<unknown[]>;\n };\n limit(n: number): Promise<unknown[]>;\n };\n orderBy(...columns: unknown[]): {\n limit(n: number): Promise<unknown[]>;\n };\n limit(n: number): Promise<unknown[]>;\n };\n };\n}\n","import {\n pgTable,\n uuid,\n timestamp,\n text,\n jsonb,\n boolean,\n index,\n} from \"drizzle-orm/pg-core\";\nimport type { InferSelectModel, InferInsertModel } from \"drizzle-orm\";\n\nexport const auditLogs = pgTable(\n \"audit_logs\",\n {\n id: uuid().primaryKey().defaultRandom(),\n timestamp: timestamp({ withTimezone: true }).notNull().defaultNow(),\n tableName: text(\"table_name\").notNull(),\n operation: text().notNull(),\n recordId: text(\"record_id\").notNull(),\n actorId: text(\"actor_id\"),\n beforeData: jsonb(\"before_data\"),\n afterData: jsonb(\"after_data\"),\n diff: jsonb(),\n label: text(),\n description: text(),\n severity: text(),\n compliance: jsonb(),\n notify: boolean(),\n reason: text(),\n metadata: jsonb(),\n redactedFields: jsonb(\"redacted_fields\"),\n },\n (table) => [\n index(\"audit_logs_table_name_timestamp_idx\").on(\n table.tableName,\n table.timestamp,\n ),\n index(\"audit_logs_actor_id_idx\").on(table.actorId),\n index(\"audit_logs_record_id_idx\").on(table.recordId),\n index(\"audit_logs_table_name_record_id_idx\").on(\n table.tableName,\n table.recordId,\n ),\n index(\"audit_logs_operation_idx\").on(table.operation),\n index(\"audit_logs_timestamp_idx\").on(table.timestamp),\n index(\"audit_logs_timestamp_id_idx\").on(table.timestamp, table.id),\n ],\n);\n\nexport type AuditLogRow = InferSelectModel<typeof auditLogs>;\nexport type NewAuditLogRow = InferInsertModel<typeof auditLogs>;\n","import type { AuditLog, AuditOperation, AuditSeverity } from \"@usebetterdev/audit-core\";\nimport type { AuditLogRow, NewAuditLogRow } from \"./schema.js\";\n\nconst VALID_OPERATIONS: ReadonlySet<string> = new Set([\n \"INSERT\",\n \"UPDATE\",\n \"DELETE\",\n]);\n\nconst VALID_SEVERITIES: ReadonlySet<string> = new Set([\n \"low\",\n \"medium\",\n \"high\",\n \"critical\",\n]);\n\nfunction isAuditOperation(value: string): value is AuditOperation {\n return VALID_OPERATIONS.has(value);\n}\n\nfunction isAuditSeverity(value: string): value is AuditSeverity {\n return VALID_SEVERITIES.has(value);\n}\n\n/**\n * Converts a camelCase `AuditLog` to a snake_case DB row for insertion.\n * Only includes optional fields when defined (satisfies `exactOptionalPropertyTypes`).\n */\nexport function auditLogToRow(log: AuditLog): NewAuditLogRow {\n const row: NewAuditLogRow = {\n id: log.id,\n timestamp: log.timestamp,\n tableName: log.tableName,\n operation: log.operation,\n recordId: log.recordId,\n };\n\n if (log.actorId !== undefined) {\n row.actorId = log.actorId;\n }\n if (log.beforeData !== undefined) {\n row.beforeData = log.beforeData;\n }\n if (log.afterData !== undefined) {\n row.afterData = log.afterData;\n }\n if (log.diff !== undefined) {\n row.diff = log.diff;\n }\n if (log.label !== undefined) {\n row.label = log.label;\n }\n if (log.description !== undefined) {\n row.description = log.description;\n }\n if (log.severity !== undefined) {\n row.severity = log.severity;\n }\n if (log.compliance !== undefined) {\n row.compliance = log.compliance;\n }\n if (log.notify !== undefined) {\n row.notify = log.notify;\n }\n if (log.reason !== undefined) {\n row.reason = log.reason;\n }\n if (log.metadata !== undefined) {\n row.metadata = log.metadata;\n }\n if (log.redactedFields !== undefined) {\n row.redactedFields = log.redactedFields;\n }\n\n return row;\n}\n\n/**\n * Converts a snake_case DB row to a camelCase `AuditLog`.\n * Only includes optional fields when non-null.\n */\nexport function rowToAuditLog(row: AuditLogRow): AuditLog {\n if (!isAuditOperation(row.operation)) {\n throw new Error(\n `Invalid audit operation: \"${row.operation}\". Expected one of: INSERT, UPDATE, DELETE`,\n );\n }\n\n const log: AuditLog = {\n id: row.id,\n timestamp: row.timestamp,\n tableName: row.tableName,\n operation: row.operation,\n recordId: row.recordId,\n };\n\n if (row.actorId !== null) {\n log.actorId = row.actorId;\n }\n if (row.beforeData !== null) {\n log.beforeData = row.beforeData as Record<string, unknown>;\n }\n if (row.afterData !== null) {\n log.afterData = row.afterData as Record<string, unknown>;\n }\n if (row.diff !== null) {\n log.diff = row.diff as { changedFields: string[] };\n }\n if (row.label !== null) {\n log.label = row.label;\n }\n if (row.description !== null) {\n log.description = row.description;\n }\n if (row.severity !== null && row.severity !== undefined) {\n if (!isAuditSeverity(row.severity)) {\n throw new Error(\n `Invalid audit severity: \"${row.severity}\". Expected one of: low, medium, high, critical`,\n );\n }\n log.severity = row.severity;\n }\n if (row.compliance !== null) {\n log.compliance = row.compliance as string[];\n }\n if (row.notify !== null) {\n log.notify = row.notify;\n }\n if (row.reason !== null) {\n log.reason = row.reason;\n }\n if (row.metadata !== null) {\n log.metadata = row.metadata as Record<string, unknown>;\n }\n if (row.redactedFields !== null) {\n log.redactedFields = row.redactedFields as string[];\n }\n\n return log;\n}\n","import type { AuditQueryFilters, TimeFilter } from \"@usebetterdev/audit-core\";\nimport { parseDuration } from \"@usebetterdev/audit-core\";\nimport {\n and,\n or,\n eq,\n gt,\n lt,\n gte,\n lte,\n inArray,\n ilike,\n asc,\n desc,\n sql,\n} from \"drizzle-orm\";\nimport type { SQL } from \"drizzle-orm\";\nimport { auditLogs } from \"./schema.js\";\n\n/**\n * Escapes LIKE/ILIKE wildcard characters so they match literally.\n * Backslash is the default escape character in PostgreSQL LIKE patterns.\n */\nfunction escapeLikePattern(input: string): string {\n return input.replace(/[%_\\\\]/g, \"\\\\$&\");\n}\n\n/**\n * Resolves a `TimeFilter` to an absolute `Date`.\n * Absolute dates are returned as-is; duration strings are parsed relative to now.\n */\nfunction resolveTimeFilter(filter: TimeFilter): Date {\n if (\"date\" in filter && filter.date !== undefined) {\n return filter.date;\n }\n if (\"duration\" in filter && filter.duration !== undefined) {\n return parseDuration(filter.duration);\n }\n throw new Error(\"TimeFilter must have either date or duration\");\n}\n\n/**\n * Translates `AuditQueryFilters` into a Drizzle `SQL` condition (combined with AND).\n * Returns `undefined` when no filters are active — callers should omit `.where()`.\n */\nexport function buildWhereConditions(\n filters: AuditQueryFilters,\n): SQL | undefined {\n const conditions: SQL[] = [];\n\n // resource.tableName + optional recordId\n if (filters.resource !== undefined) {\n conditions.push(eq(auditLogs.tableName, filters.resource.tableName));\n if (filters.resource.recordId !== undefined) {\n conditions.push(eq(auditLogs.recordId, filters.resource.recordId));\n }\n }\n\n // actorIds — single eq or inArray\n if (filters.actorIds !== undefined && filters.actorIds.length > 0) {\n if (filters.actorIds.length === 1) {\n conditions.push(eq(auditLogs.actorId, filters.actorIds[0]!));\n } else {\n conditions.push(inArray(auditLogs.actorId, filters.actorIds));\n }\n }\n\n // severities — single eq or inArray\n if (filters.severities !== undefined && filters.severities.length > 0) {\n if (filters.severities.length === 1) {\n conditions.push(eq(auditLogs.severity, filters.severities[0]!));\n } else {\n conditions.push(inArray(auditLogs.severity, filters.severities));\n }\n }\n\n // operations — single eq or inArray\n if (filters.operations !== undefined && filters.operations.length > 0) {\n if (filters.operations.length === 1) {\n conditions.push(eq(auditLogs.operation, filters.operations[0]!));\n } else {\n conditions.push(inArray(auditLogs.operation, filters.operations));\n }\n }\n\n // since — gte(timestamp, resolved date)\n if (filters.since !== undefined) {\n conditions.push(gte(auditLogs.timestamp, resolveTimeFilter(filters.since)));\n }\n\n // until — lte(timestamp, resolved date)\n if (filters.until !== undefined) {\n conditions.push(lte(auditLogs.timestamp, resolveTimeFilter(filters.until)));\n }\n\n // searchText — ilike on label OR description\n if (filters.searchText !== undefined && filters.searchText.length > 0) {\n const escaped = escapeLikePattern(filters.searchText);\n const pattern = `%${escaped}%`;\n const searchCondition = or(\n ilike(auditLogs.label, pattern),\n ilike(auditLogs.description, pattern),\n );\n if (searchCondition !== undefined) {\n conditions.push(searchCondition);\n }\n }\n\n // compliance — jsonb @> (contains all tags)\n if (filters.compliance !== undefined && filters.compliance.length > 0) {\n conditions.push(\n sql`${auditLogs.compliance} @> ${JSON.stringify(filters.compliance)}::jsonb`,\n );\n }\n\n if (conditions.length === 0) {\n return undefined;\n }\n\n return and(...conditions);\n}\n\n/**\n * Builds a cursor-based pagination condition.\n *\n * Uses `(timestamp, id)` as the cursor key pair for stable ordering.\n * - DESC: `(ts < cursor.ts) OR (ts = cursor.ts AND id < cursor.id)`\n * - ASC: `(ts > cursor.ts) OR (ts = cursor.ts AND id > cursor.id)`\n */\nexport function buildCursorCondition(\n cursor: string,\n sortOrder: \"asc\" | \"desc\",\n): SQL | undefined {\n const decoded = decodeCursor(cursor);\n const tsCompare = sortOrder === \"asc\" ? gt : lt;\n const idCompare = sortOrder === \"asc\" ? gt : lt;\n\n return or(\n tsCompare(auditLogs.timestamp, decoded.timestamp),\n and(\n eq(auditLogs.timestamp, decoded.timestamp),\n idCompare(auditLogs.id, decoded.id),\n ),\n );\n}\n\n/**\n * Returns the `orderBy` columns for the given sort direction.\n * Default is descending (newest first).\n */\nexport function buildOrderBy(sortOrder: \"asc\" | \"desc\") {\n if (sortOrder === \"asc\") {\n return [asc(auditLogs.timestamp), asc(auditLogs.id)];\n }\n return [desc(auditLogs.timestamp), desc(auditLogs.id)];\n}\n\n/**\n * Encodes a cursor from a timestamp and id.\n * Format: base64(JSON.stringify({ t: iso_string, i: id }))\n */\nexport function encodeCursor(timestamp: Date, id: string): string {\n const payload = JSON.stringify({ t: timestamp.toISOString(), i: id });\n return btoa(payload);\n}\n\nconst UUID_PATTERN =\n /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;\n\n/**\n * Decodes an opaque cursor string back to timestamp + id.\n * Throws on invalid input.\n */\nexport function decodeCursor(cursor: string): { timestamp: Date; id: string } {\n let parsed: unknown;\n try {\n parsed = JSON.parse(atob(cursor));\n } catch {\n throw new Error(\"Invalid cursor: failed to decode\");\n }\n\n if (\n typeof parsed !== \"object\" ||\n parsed === null ||\n !(\"t\" in parsed) ||\n !(\"i\" in parsed)\n ) {\n throw new Error(\"Invalid cursor: missing required fields\");\n }\n\n const { t, i } = parsed as { t: unknown; i: unknown };\n\n if (typeof t !== \"string\" || typeof i !== \"string\") {\n throw new Error(\"Invalid cursor: fields must be strings\");\n }\n\n const timestamp = new Date(t);\n if (isNaN(timestamp.getTime())) {\n throw new Error(\"Invalid cursor: invalid timestamp\");\n }\n\n if (!UUID_PATTERN.test(i)) {\n throw new Error(\"Invalid cursor: id must be a valid UUID\");\n }\n\n return { timestamp, id: i };\n}\n","import type { CaptureLogInput, AuditOperation } from \"@usebetterdev/audit-core\";\nimport { getTableName } from \"drizzle-orm\";\nimport type { PgTable } from \"drizzle-orm/pg-core\";\nimport { OPERATION_MAP, type DrizzleMutationMethod } from \"./operation-map.js\";\n\nexport type MissingRecordIdBehavior = \"warn\" | \"skip\" | \"throw\";\n\nexport interface AuditProxyOptions {\n /**\n * Fallback column name for extracting `recordId` when the primary key\n * cannot be auto-detected from the Drizzle table schema. Defaults to `\"id\"`.\n *\n * In most cases this is not needed — the proxy reads `.primaryKey()` metadata\n * from Drizzle column objects at runtime.\n */\n primaryKey?: string;\n /**\n * Called when audit capture fails. Falls back to `console.error` if not set.\n */\n onError?: (error: unknown) => void;\n /**\n * What to do when the `recordId` cannot be determined.\n *\n * - `\"warn\"` (default) — log via `onError`, proceed with `recordId: \"unknown\"`\n * - `\"skip\"` — silently drop the audit entry\n * - `\"throw\"` — throw an error (caught by the proxy's outer error handler)\n */\n onMissingRecordId?: MissingRecordIdBehavior;\n /**\n * Table names for which the pre-mutation SELECT should be skipped.\n * Use this for high-throughput tables where the extra query is too expensive.\n */\n skipBeforeState?: string[];\n /**\n * Safety limit: if the pre-mutation SELECT returns more rows than this,\n * skip the before-state capture and warn. Defaults to 1000.\n */\n maxBeforeStateRows?: number;\n}\n\ninterface BuilderContext {\n tableName: string;\n operation: AuditOperation;\n captureLog: (input: CaptureLogInput) => Promise<void>;\n primaryKey: string;\n handleError: (error: unknown, table: string, op: string) => void;\n onMissingRecordId: MissingRecordIdBehavior;\n auditedSet: WeakSet<object>;\n dbTarget: Record<string, unknown>;\n table: PgTable;\n skipBeforeState: Set<string>;\n maxBeforeStateRows: number;\n}\n\n/**\n * Wraps a Drizzle database (or transaction) with a transparent proxy that\n * intercepts `db.insert()`, `db.update()`, `db.delete()` and calls\n * `captureLog()` after each successful mutation.\n *\n * The proxy also intercepts `db.transaction()` so that the `tx` handle\n * passed to the callback is itself proxied — this is what makes it work\n * with `better-tenant`, where all user code runs inside a transaction.\n *\n * Filtering by audited tables is NOT done here — `captureLog` (from\n * `betterAudit`) already skips tables not in `auditTables`, so there is\n * a single source of truth for which tables are audited.\n */\n// eslint-disable-next-line @typescript-eslint/no-empty-object-type\nexport function withAuditProxy<TDb extends {}>(\n db: TDb,\n captureLog: (input: CaptureLogInput) => Promise<void>,\n options?: AuditProxyOptions,\n): TDb {\n const primaryKey = options?.primaryKey ?? \"id\";\n const missingRecordIdPolicy = options?.onMissingRecordId ?? \"warn\";\n const skipBeforeState = new Set(options?.skipBeforeState ?? []);\n const maxBeforeStateRows = options?.maxBeforeStateRows ?? 1000;\n\n const handleError = (error: unknown, table: string, op: string): void => {\n try {\n if (options?.onError !== undefined) {\n options.onError(error);\n } else {\n const msg = error instanceof Error ? error.message : String(error);\n console.error(\n `audit-drizzle: capture failed for ${op} on ${table} — ${msg}`,\n );\n }\n } catch {\n // onError callback itself threw — swallow to prevent audit from breaking mutations.\n }\n };\n\n return new Proxy(db, {\n get(target, prop, receiver) {\n // Intercept insert / update / delete\n if (\n typeof prop === \"string\" &&\n (prop === \"insert\" || prop === \"update\" || prop === \"delete\")\n ) {\n const method = prop as DrizzleMutationMethod;\n const originalMethod = Reflect.get(target, prop, receiver) as (\n table: PgTable,\n ) => Record<string, unknown>;\n\n return (table: PgTable) => {\n const tableName = getTableName(table);\n const detectedPk = getPrimaryKeyColumnName(table);\n const effectivePk = detectedPk ?? primaryKey;\n const originalBuilder = originalMethod.call(target, table);\n\n const ctx: BuilderContext = {\n tableName,\n operation: OPERATION_MAP[method],\n captureLog,\n primaryKey: effectivePk,\n handleError,\n onMissingRecordId: missingRecordIdPolicy,\n auditedSet: new WeakSet<object>(),\n dbTarget: target as Record<string, unknown>,\n table,\n skipBeforeState,\n maxBeforeStateRows,\n };\n\n return wrapBuilder(originalBuilder, ctx);\n };\n }\n\n // Intercept transaction() so the tx handle is also proxied\n if (prop === \"transaction\") {\n const originalTransaction = Reflect.get(\n target,\n prop,\n receiver,\n ) as Function;\n\n return (...args: unknown[]) => {\n const callback = args[0] as (tx: unknown) => Promise<unknown>;\n const rest = args.slice(1);\n const wrappedCallback = (tx: unknown) => {\n const proxiedTx = withAuditProxy(\n tx as TDb,\n captureLog,\n options,\n );\n return callback(proxiedTx);\n };\n return originalTransaction.call(target, wrappedCallback, ...rest);\n };\n }\n\n return Reflect.get(target, prop, receiver);\n },\n });\n}\n\ninterface TrackedState {\n trackedValues?: Record<string, unknown>[];\n trackedSet?: Record<string, unknown>;\n trackedWhere?: unknown;\n hasReturning?: boolean;\n}\n\nfunction wrapBuilder(\n builder: Record<string, unknown>,\n ctx: BuilderContext,\n state: TrackedState = {},\n): Record<string, unknown> {\n return new Proxy(builder, {\n get(target, prop, receiver) {\n // Track .values() data for INSERT\n if (prop === \"values\") {\n return (...args: unknown[]) => {\n const data = args[0] as\n | Record<string, unknown>\n | Record<string, unknown>[];\n const newTrackedValues = Array.isArray(data) ? data : [data];\n const result = (\n target.values as (...a: unknown[]) => Record<string, unknown>\n )(...args);\n return wrapBuilder(result, ctx, {\n ...state,\n trackedValues: newTrackedValues,\n });\n };\n }\n\n // Track .set() data for UPDATE\n if (prop === \"set\") {\n return (...args: unknown[]) => {\n const newTrackedSet = args[0] as Record<string, unknown>;\n const result = (\n target.set as (...a: unknown[]) => Record<string, unknown>\n )(...args);\n return wrapBuilder(result, ctx, {\n ...state,\n trackedSet: newTrackedSet,\n });\n };\n }\n\n // Track .where() condition for UPDATE/DELETE pre-SELECT\n if (prop === \"where\") {\n return (...args: unknown[]) => {\n const condition = args[0];\n const result = (\n target.where as (...a: unknown[]) => Record<string, unknown>\n )(...args);\n return wrapBuilder(result, ctx, {\n ...state,\n trackedWhere: condition,\n });\n };\n }\n\n // Track .returning() so we know the result contains full rows\n if (prop === \"returning\") {\n return (...args: unknown[]) => {\n const result = (\n target.returning as (...a: unknown[]) => Record<string, unknown>\n )(...args);\n return wrapBuilder(result, ctx, {\n ...state,\n hasReturning: true,\n });\n };\n }\n\n // Intercept .then() — the terminal await point\n if (prop === \"then\") {\n return (\n onFulfilled?: (value: unknown) => unknown,\n onRejected?: (reason: unknown) => unknown,\n ) => {\n const thenFn = Reflect.get(target, \"then\", receiver) as (\n onFulfilled?: (value: unknown) => unknown,\n onRejected?: (reason: unknown) => unknown,\n ) => unknown;\n\n const needsBeforeState =\n (ctx.operation === \"UPDATE\" || ctx.operation === \"DELETE\") &&\n state.trackedWhere !== undefined &&\n !ctx.skipBeforeState.has(ctx.tableName) &&\n // For DELETE with .returning(), returned rows ARE the before-state\n !(ctx.operation === \"DELETE\" && state.hasReturning === true);\n\n if (needsBeforeState) {\n return executeWithBeforeState(\n target,\n thenFn,\n ctx,\n state,\n onFulfilled,\n onRejected,\n );\n }\n\n return thenFn.call(\n target,\n async (result: unknown) => {\n if (ctx.auditedSet.has(target)) {\n return onFulfilled?.(result);\n }\n ctx.auditedSet.add(target);\n\n try {\n await fireCaptureLog(result, ctx, state);\n } catch (error) {\n ctx.handleError(error, ctx.tableName, ctx.operation);\n }\n return onFulfilled?.(result);\n },\n onRejected,\n );\n };\n }\n\n // All other methods: forward and re-wrap\n const value = Reflect.get(target, prop, receiver);\n if (typeof value === \"function\") {\n return (...args: unknown[]) => {\n const result = (value as (...a: unknown[]) => unknown).apply(\n target,\n args,\n );\n if (result !== null && typeof result === \"object\") {\n return wrapBuilder(\n result as Record<string, unknown>,\n ctx,\n state,\n );\n }\n return result;\n };\n }\n\n return value;\n },\n });\n}\n\n/**\n * Executes a pre-mutation SELECT, then the original mutation, then fires\n * captureLog with matched before/after pairs.\n */\nfunction executeWithBeforeState(\n target: Record<string, unknown>,\n thenFn: (\n onFulfilled?: (value: unknown) => unknown,\n onRejected?: (reason: unknown) => unknown,\n ) => unknown,\n ctx: BuilderContext,\n state: TrackedState,\n onFulfilled?: (value: unknown) => unknown,\n onRejected?: (reason: unknown) => unknown,\n): unknown {\n // Issue the pre-mutation SELECT, then chain the original mutation\n const beforePromise = fetchBeforeState(ctx, state);\n\n return beforePromise.then(\n (beforeRows) => {\n // Now execute the original mutation\n return thenFn.call(\n target,\n async (result: unknown) => {\n if (ctx.auditedSet.has(target)) {\n return onFulfilled?.(result);\n }\n ctx.auditedSet.add(target);\n\n try {\n if (beforeRows !== undefined) {\n await fireCaptureLogWithBeforeState(\n result,\n beforeRows,\n ctx,\n state,\n );\n } else {\n // Fallback: before-state unavailable, use original behavior\n await fireCaptureLog(result, ctx, state);\n }\n } catch (error) {\n ctx.handleError(error, ctx.tableName, ctx.operation);\n }\n return onFulfilled?.(result);\n },\n onRejected,\n );\n },\n (error) => {\n // Pre-SELECT failed — log error, fall back to original behavior.\n ctx.handleError(error, ctx.tableName, ctx.operation);\n return thenFn.call(\n target,\n async (result: unknown) => {\n if (ctx.auditedSet.has(target)) {\n return onFulfilled?.(result);\n }\n ctx.auditedSet.add(target);\n\n try {\n await fireCaptureLog(result, ctx, state);\n } catch (captureError) {\n ctx.handleError(captureError, ctx.tableName, ctx.operation);\n }\n return onFulfilled?.(result);\n },\n onRejected,\n );\n },\n );\n}\n\n/**\n * Issues a SELECT to fetch the rows that will be affected by the mutation.\n * Returns `undefined` if the fetch should be skipped (too many rows).\n */\nasync function fetchBeforeState(\n ctx: BuilderContext,\n state: TrackedState,\n): Promise<Record<string, unknown>[] | undefined> {\n const selectFn = ctx.dbTarget.select as\n | ((...args: unknown[]) => Record<string, unknown>)\n | undefined;\n if (selectFn === undefined) {\n return undefined;\n }\n\n const selectBuilder = selectFn.call(ctx.dbTarget);\n const fromFn = selectBuilder.from as\n | ((table: PgTable) => Record<string, unknown>)\n | undefined;\n if (fromFn === undefined) {\n return undefined;\n }\n\n const fromBuilder = fromFn.call(selectBuilder, ctx.table);\n const whereFn = fromBuilder.where as\n | ((...args: unknown[]) => Record<string, unknown>)\n | undefined;\n if (whereFn === undefined) {\n return undefined;\n }\n\n const whereBuilder = whereFn.call(fromBuilder, state.trackedWhere);\n\n // Apply LIMIT to avoid fetching unbounded rows into memory.\n // Fetch limit + 1 so we can detect when the limit is exceeded.\n const limitFn = whereBuilder.limit as\n | ((n: number) => Record<string, unknown>)\n | undefined;\n const fetchLimit = ctx.maxBeforeStateRows + 1;\n const queryBuilder =\n limitFn !== undefined\n ? limitFn.call(whereBuilder, fetchLimit)\n : whereBuilder;\n\n const rows = (await queryBuilder) as unknown as Record<string, unknown>[];\n\n if (rows.length > ctx.maxBeforeStateRows) {\n ctx.handleError(\n new Error(\n `audit-drizzle: before-state SELECT returned more than ${ctx.maxBeforeStateRows} rows, skipping before-state capture`,\n ),\n ctx.tableName,\n ctx.operation,\n );\n return undefined;\n }\n\n return rows;\n}\n\nfunction getPrimaryKeyColumnName(table: PgTable): string | undefined {\n for (const [key, value] of Object.entries(table)) {\n if (\n value !== null &&\n typeof value === \"object\" &&\n \"primary\" in value &&\n value.primary === true\n ) {\n return key;\n }\n }\n return undefined;\n}\n\nfunction extractRecordId(\n row: Record<string, unknown>,\n primaryKey: string,\n): string {\n const value = row[primaryKey];\n if (value !== undefined && value !== null) {\n return String(value);\n }\n return \"\";\n}\n\n/**\n * Applies the missing-record-id policy.\n * Returns `true` if the audit entry should still be written (with \"unknown\"),\n * or `false` if it should be skipped.\n */\nfunction applyMissingRecordIdPolicy(\n ctx: BuilderContext,\n detail: string,\n): boolean {\n const policy = ctx.onMissingRecordId;\n if (policy === \"skip\") {\n return false;\n }\n if (policy === \"throw\") {\n throw new Error(\n `audit-drizzle: missing recordId for ${ctx.operation} on ${ctx.tableName} — ${detail}`,\n );\n }\n // \"warn\" — report via handleError, then proceed\n ctx.handleError(\n new Error(\n `audit-drizzle: missing recordId for ${ctx.operation} on ${ctx.tableName} — ${detail}`,\n ),\n ctx.tableName,\n ctx.operation,\n );\n return true;\n}\n\n/**\n * Fires captureLog with matched before/after pairs using pre-mutation state.\n */\nasync function fireCaptureLogWithBeforeState(\n result: unknown,\n beforeRows: Record<string, unknown>[],\n ctx: BuilderContext,\n state: TrackedState,\n): Promise<void> {\n const returnedRows = Array.isArray(result) ? result : [];\n\n if (ctx.operation === \"UPDATE\") {\n if (beforeRows.length === 0) {\n // Race condition: rows disappeared between SELECT and UPDATE\n ctx.handleError(\n new Error(\n \"audit-drizzle: before-state SELECT returned 0 rows but UPDATE succeeded — possible race condition\",\n ),\n ctx.tableName,\n ctx.operation,\n );\n // Fall back to original UPDATE behavior\n await fireCaptureLog(result, ctx, state);\n return;\n }\n\n // Build before map keyed by primary key\n const beforeMap = new Map<string, Record<string, unknown>>();\n for (const row of beforeRows) {\n const pk = extractRecordId(row, ctx.primaryKey);\n if (pk !== \"\") {\n beforeMap.set(pk, row);\n }\n }\n\n if (beforeMap.size === 0) {\n // All before-rows lacked the primary key column — likely misconfiguration\n ctx.handleError(\n new Error(\n `audit-drizzle: before-state rows exist but none have primary key \"${ctx.primaryKey}\" — check primaryKey option`,\n ),\n ctx.tableName,\n ctx.operation,\n );\n await fireCaptureLog(result, ctx, state);\n return;\n }\n\n if (state.hasReturning === true && returnedRows.length > 0) {\n // After-state from .returning() result\n for (const returnedRow of returnedRows) {\n const row = returnedRow as Record<string, unknown>;\n const recordId = extractRecordId(row, ctx.primaryKey);\n if (recordId === \"\") {\n continue;\n }\n const beforeRow = beforeMap.get(recordId);\n await ctx.captureLog({\n tableName: ctx.tableName,\n operation: ctx.operation,\n recordId,\n ...(beforeRow !== undefined && { before: beforeRow }),\n after: row,\n });\n }\n } else {\n // After-state: merge .set() data onto each before-row\n for (const [pk, beforeRow] of beforeMap) {\n const afterRow =\n state.trackedSet !== undefined\n ? { ...beforeRow, ...state.trackedSet }\n : beforeRow;\n await ctx.captureLog({\n tableName: ctx.tableName,\n operation: ctx.operation,\n recordId: pk,\n before: beforeRow,\n after: afterRow,\n });\n }\n }\n } else if (ctx.operation === \"DELETE\") {\n if (beforeRows.length === 0) {\n // Race condition: rows disappeared before DELETE\n ctx.handleError(\n new Error(\n \"audit-drizzle: before-state SELECT returned 0 rows but DELETE succeeded — possible race condition\",\n ),\n ctx.tableName,\n ctx.operation,\n );\n // Fall back to original behavior\n await fireCaptureLog(result, ctx, state);\n return;\n }\n\n for (const beforeRow of beforeRows) {\n const recordId = extractRecordId(beforeRow, ctx.primaryKey);\n if (recordId === \"\") {\n const shouldProceed = applyMissingRecordIdPolicy(\n ctx,\n \"before-state row missing primary key\",\n );\n if (!shouldProceed) {\n continue;\n }\n await ctx.captureLog({\n tableName: ctx.tableName,\n operation: ctx.operation,\n recordId: \"unknown\",\n before: beforeRow,\n });\n continue;\n }\n await ctx.captureLog({\n tableName: ctx.tableName,\n operation: ctx.operation,\n recordId,\n before: beforeRow,\n });\n }\n }\n}\n\nasync function fireCaptureLog(\n result: unknown,\n ctx: BuilderContext,\n state: TrackedState,\n): Promise<void> {\n const { trackedValues, trackedSet } = state;\n const returnedRows = Array.isArray(result) ? result : [];\n\n if (ctx.operation === \"INSERT\") {\n const values = trackedValues ?? [];\n for (let i = 0; i < values.length; i++) {\n const row = values[i];\n if (row === undefined) {\n continue;\n }\n const returnedRow =\n returnedRows.length > i\n ? (returnedRows[i] as Record<string, unknown>)\n : undefined;\n const recordId =\n returnedRow !== undefined\n ? extractRecordId(returnedRow, ctx.primaryKey)\n : extractRecordId(row, ctx.primaryKey);\n\n if (recordId === \"\") {\n const shouldProceed = applyMissingRecordIdPolicy(\n ctx,\n \"use .returning() or include the primary key in .values()\",\n );\n if (!shouldProceed) {\n continue;\n }\n\n await ctx.captureLog({\n tableName: ctx.tableName,\n operation: ctx.operation,\n recordId: \"unknown\",\n after: row,\n });\n continue;\n }\n\n await ctx.captureLog({\n tableName: ctx.tableName,\n operation: ctx.operation,\n recordId,\n after: row,\n });\n }\n } else if (ctx.operation === \"UPDATE\") {\n if (returnedRows.length > 0) {\n for (const returnedRow of returnedRows) {\n const row = returnedRow as Record<string, unknown>;\n const recordId = extractRecordId(row, ctx.primaryKey);\n if (recordId === \"\") {\n continue;\n }\n await ctx.captureLog({\n tableName: ctx.tableName,\n operation: ctx.operation,\n recordId,\n ...(trackedSet !== undefined && { after: trackedSet }),\n });\n }\n } else if (trackedSet !== undefined) {\n const recordId = extractRecordId(trackedSet, ctx.primaryKey);\n if (recordId === \"\") {\n const shouldProceed = applyMissingRecordIdPolicy(\n ctx,\n \"use .returning() to get the record id\",\n );\n if (!shouldProceed) {\n return;\n }\n }\n await ctx.captureLog({\n tableName: ctx.tableName,\n operation: ctx.operation,\n recordId: recordId || \"unknown\",\n after: trackedSet,\n });\n }\n } else if (ctx.operation === \"DELETE\") {\n if (returnedRows.length > 0) {\n for (const returnedRow of returnedRows) {\n const row = returnedRow as Record<string, unknown>;\n const recordId = extractRecordId(row, ctx.primaryKey);\n if (recordId === \"\") {\n continue;\n }\n await ctx.captureLog({\n tableName: ctx.tableName,\n operation: ctx.operation,\n recordId,\n before: row,\n });\n }\n } else {\n const shouldProceed = applyMissingRecordIdPolicy(\n ctx,\n \"use .returning() to get the record id\",\n );\n if (!shouldProceed) {\n return;\n }\n await ctx.captureLog({\n tableName: ctx.tableName,\n operation: ctx.operation,\n recordId: \"unknown\",\n });\n }\n }\n}\n","import type { AuditOperation } from \"@usebetterdev/audit-core\";\n\nexport const OPERATION_MAP = {\n insert: \"INSERT\",\n update: \"UPDATE\",\n delete: \"DELETE\",\n} as const satisfies Record<string, AuditOperation>;\n\nexport type DrizzleMutationMethod = keyof typeof OPERATION_MAP;\n"],"mappings":"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","names":["import_drizzle_orm","timestamp","import_drizzle_orm"]}
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/adapter.ts","../src/schema.ts","../src/column-map.ts","../src/query.ts","../src/proxy.ts","../src/operation-map.ts"],"sourcesContent":["export { drizzleAuditAdapter } from \"./adapter.js\";\nexport type { DrizzlePgDatabase } from \"./adapter.js\";\nexport { withAuditProxy } from \"./proxy.js\";\nexport type { AuditProxyOptions, MissingRecordIdBehavior } from \"./proxy.js\";\nexport { auditLogs } from \"./schema.js\";\nexport type { AuditLogRow, NewAuditLogRow } from \"./schema.js\";\nexport {\n buildWhereConditions,\n buildCursorCondition,\n buildOrderBy,\n encodeCursor,\n decodeCursor,\n} from \"./query.js\";\n","import type {\n AuditDatabaseAdapter,\n AuditLog,\n AuditQuerySpec,\n AuditQueryResult,\n AuditStats,\n} from \"@usebetterdev/audit-core\";\nimport { and, eq, gte, lt, isNotNull, sql, desc } from \"drizzle-orm\";\nimport { auditLogs } from \"./schema.js\";\nimport { auditLogToRow, rowToAuditLog } from \"./column-map.js\";\nimport {\n buildWhereConditions,\n buildCursorCondition,\n buildOrderBy,\n encodeCursor,\n} from \"./query.js\";\n\n/**\n * Minimal shape for a Drizzle pg database that supports insert, select, and delete operations.\n * Duck-typed so callers don't need the full Drizzle generic types.\n */\nexport interface DrizzlePgDatabase {\n insert(table: unknown): {\n values(data: unknown): { execute(): Promise<unknown> };\n };\n select(fields?: unknown): unknown;\n delete(table: unknown): unknown;\n}\n\nconst DEFAULT_LIMIT = 50;\nconst MAX_LIMIT = 250;\n\n/**\n * Safely converts a PostgreSQL bigint count (returned as string) to a number.\n */\nfunction toCount(value: unknown): number {\n if (typeof value === \"number\") {\n return value;\n }\n if (typeof value === \"string\") {\n return Number(value);\n }\n return 0;\n}\n\n/**\n * Creates an `AuditDatabaseAdapter` backed by a Drizzle pg database.\n *\n * Implements `writeLog` for inserting audit entries and `queryLogs` for\n * filtered, cursor-paginated queries.\n */\nexport function drizzleAuditAdapter(\n db: DrizzlePgDatabase,\n): AuditDatabaseAdapter {\n return {\n async writeLog(log: AuditLog): Promise<void> {\n const row = auditLogToRow(log);\n await db.insert(auditLogs).values(row).execute();\n },\n\n async queryLogs(spec: AuditQuerySpec): Promise<AuditQueryResult> {\n const sortOrder = spec.sortOrder ?? \"desc\";\n const limit = Math.min(spec.limit ?? DEFAULT_LIMIT, MAX_LIMIT);\n\n const whereCondition = buildWhereConditions(spec.filters);\n const cursorCondition =\n spec.cursor !== undefined\n ? buildCursorCondition(spec.cursor, sortOrder)\n : undefined;\n const combined = and(whereCondition, cursorCondition);\n\n const fetchLimit = limit + 1;\n const orderColumns = buildOrderBy(sortOrder);\n\n // The duck-typed interface returns `unknown` from select().\n // We build the full Drizzle query chain and cast at the boundary.\n const query = (db.select() as ReturnType<typeof buildSelectChain>)\n .from(auditLogs)\n .where(combined)\n .orderBy(...orderColumns)\n .limit(fetchLimit);\n\n const rows = (await query) as (typeof auditLogs.$inferSelect)[];\n\n const hasNextPage = rows.length > limit;\n const resultRows = hasNextPage ? rows.slice(0, -1) : rows;\n const entries = resultRows.map(rowToAuditLog);\n const lastRow = resultRows[resultRows.length - 1];\n\n if (hasNextPage && lastRow !== undefined) {\n return { entries, nextCursor: encodeCursor(lastRow.timestamp, lastRow.id) };\n }\n\n return { entries };\n },\n\n async getLogById(id: string): Promise<AuditLog | null> {\n const query = (db.select() as ReturnType<typeof buildSelectChain>)\n .from(auditLogs)\n .where(eq(auditLogs.id, id))\n .limit(1);\n\n const rows = (await query) as (typeof auditLogs.$inferSelect)[];\n const row = rows[0];\n if (row === undefined) {\n return null;\n }\n return rowToAuditLog(row);\n },\n\n /**\n * Delete audit log entries older than `before`.\n *\n * **Warning:** Large deletes may hold a row-level lock on the `audit_logs`\n * table for an extended period. Run during low-traffic windows when purging\n * millions of rows.\n */\n async purgeLogs(options: { before: Date; tableName?: string }): Promise<{ deletedCount: number }> {\n if (options.tableName !== undefined && options.tableName.trim().length === 0) {\n throw new Error(\"purgeLogs: tableName must be a non-empty string when provided\");\n }\n\n const conditions = [lt(auditLogs.timestamp, options.before)];\n if (options.tableName !== undefined) {\n conditions.push(eq(auditLogs.tableName, options.tableName));\n }\n\n const result = await (\n db.delete(auditLogs) as ReturnType<typeof buildDeleteChain>\n ).where(and(...conditions));\n\n const rowCount = (result as { rowCount?: number | null }).rowCount;\n return { deletedCount: rowCount ?? 0 };\n },\n\n async getStats(options?: { since?: Date }): Promise<AuditStats> {\n const sinceCondition =\n options?.since !== undefined\n ? gte(auditLogs.timestamp, options.since)\n : undefined;\n\n // Query 1: totalLogs + tablesAudited\n const summaryQuery = (\n db.select({\n totalLogs: sql`count(*)`,\n tablesAudited: sql`count(DISTINCT ${auditLogs.tableName})`,\n }) as ReturnType<typeof buildAggregateSelectChain>\n )\n .from(auditLogs)\n .where(sinceCondition);\n\n // Query 2: eventsPerDay\n const eventsPerDayQuery = (\n db.select({\n date: sql`date_trunc('day', ${auditLogs.timestamp})::date`,\n count: sql`count(*)`,\n }) as ReturnType<typeof buildAggregateSelectChain>\n )\n .from(auditLogs)\n .where(sinceCondition)\n .groupBy(sql`date_trunc('day', ${auditLogs.timestamp})`)\n .orderBy(sql`date_trunc('day', ${auditLogs.timestamp})`)\n .limit(365);\n\n // Query 3: topActors (filter NULL actors)\n const topActorsQuery = (\n db.select({\n actorId: auditLogs.actorId,\n count: sql`count(*)`,\n }) as ReturnType<typeof buildAggregateSelectChain>\n )\n .from(auditLogs)\n .where(and(sinceCondition, isNotNull(auditLogs.actorId)))\n .groupBy(auditLogs.actorId)\n .orderBy(desc(sql`count(*)`))\n .limit(10);\n\n // Query 4: topTables\n const topTablesQuery = (\n db.select({\n tableName: auditLogs.tableName,\n count: sql`count(*)`,\n }) as ReturnType<typeof buildAggregateSelectChain>\n )\n .from(auditLogs)\n .where(sinceCondition)\n .groupBy(auditLogs.tableName)\n .orderBy(desc(sql`count(*)`))\n .limit(10);\n\n // Query 5: operationBreakdown\n const operationQuery = (\n db.select({\n operation: auditLogs.operation,\n count: sql`count(*)`,\n }) as ReturnType<typeof buildAggregateSelectChain>\n )\n .from(auditLogs)\n .where(sinceCondition)\n .groupBy(auditLogs.operation);\n\n // Query 6: severityBreakdown (filter NULL severity)\n const severityQuery = (\n db.select({\n severity: auditLogs.severity,\n count: sql`count(*)`,\n }) as ReturnType<typeof buildAggregateSelectChain>\n )\n .from(auditLogs)\n .where(and(sinceCondition, isNotNull(auditLogs.severity)))\n .groupBy(auditLogs.severity);\n\n const results = await Promise.all([\n summaryQuery,\n eventsPerDayQuery,\n topActorsQuery,\n topTablesQuery,\n operationQuery,\n severityQuery,\n ]);\n\n const [\n summaryRows,\n eventsPerDayRows,\n topActorsRows,\n topTablesRows,\n operationRows,\n severityRows,\n ] = results as unknown as [\n Array<{ totalLogs: unknown; tablesAudited: unknown }>,\n Array<{ date: unknown; count: unknown }>,\n Array<{ actorId: unknown; count: unknown }>,\n Array<{ tableName: unknown; count: unknown }>,\n Array<{ operation: unknown; count: unknown }>,\n Array<{ severity: unknown; count: unknown }>,\n ];\n\n return assembleStats(\n summaryRows,\n eventsPerDayRows,\n topActorsRows,\n topTablesRows,\n operationRows,\n severityRows,\n );\n },\n };\n}\n\n/**\n * Assembles query results into an `AuditStats` object.\n */\nfunction assembleStats(\n summaryRows: Array<{ totalLogs: unknown; tablesAudited: unknown }>,\n eventsPerDayRows: Array<{ date: unknown; count: unknown }>,\n topActorsRows: Array<{ actorId: unknown; count: unknown }>,\n topTablesRows: Array<{ tableName: unknown; count: unknown }>,\n operationRows: Array<{ operation: unknown; count: unknown }>,\n severityRows: Array<{ severity: unknown; count: unknown }>,\n): AuditStats {\n const summary = summaryRows[0];\n const totalLogs = summary !== undefined ? toCount(summary.totalLogs) : 0;\n const tablesAudited = summary !== undefined ? toCount(summary.tablesAudited) : 0;\n\n const eventsPerDay = eventsPerDayRows.map((row) => ({\n date: row.date instanceof Date ? row.date.toISOString().split(\"T\")[0]! : String(row.date),\n count: toCount(row.count),\n }));\n\n const topActors = topActorsRows.map((row) => ({\n actorId: String(row.actorId),\n count: toCount(row.count),\n }));\n\n const topTables = topTablesRows.map((row) => ({\n tableName: String(row.tableName),\n count: toCount(row.count),\n }));\n\n const operationBreakdown: Record<string, number> = {};\n for (const row of operationRows) {\n operationBreakdown[String(row.operation)] = toCount(row.count);\n }\n\n const severityBreakdown: Record<string, number> = {};\n for (const row of severityRows) {\n severityBreakdown[String(row.severity)] = toCount(row.count);\n }\n\n return {\n totalLogs,\n tablesAudited,\n eventsPerDay,\n topActors,\n topTables,\n operationBreakdown,\n severityBreakdown,\n };\n}\n\n/**\n * Type helper for the Drizzle select chain — used only for casting.\n * Not exported; exists to avoid `any`.\n */\nfunction buildSelectChain() {\n // This function is never called — it exists only for its return type.\n return undefined as unknown as {\n from(table: unknown): {\n where(condition: unknown): {\n orderBy(...columns: unknown[]): {\n limit(n: number): Promise<unknown[]>;\n };\n limit(n: number): Promise<unknown[]>;\n };\n orderBy(...columns: unknown[]): {\n limit(n: number): Promise<unknown[]>;\n };\n limit(n: number): Promise<unknown[]>;\n };\n };\n}\n\n/** Awaitable node: can be awaited directly or chained further. */\ninterface AggregateTerminal extends Promise<unknown[]> {\n limit(n: number): Promise<unknown[]>;\n}\n\n/** After groupBy: can orderBy, limit, or await. */\ninterface AggregateGrouped extends Promise<unknown[]> {\n orderBy(...columns: unknown[]): AggregateTerminal;\n limit(n: number): Promise<unknown[]>;\n}\n\n/** After where: can groupBy, orderBy, limit, or await. */\ninterface AggregateFiltered extends Promise<unknown[]> {\n groupBy(...columns: unknown[]): AggregateGrouped;\n orderBy(...columns: unknown[]): AggregateTerminal;\n limit(n: number): Promise<unknown[]>;\n}\n\n/**\n * Type helper for aggregate select chains with groupBy support — used only for casting.\n */\nfunction buildAggregateSelectChain() {\n // This function is never called — it exists only for its return type.\n return undefined as unknown as {\n from(table: unknown): {\n where(condition: unknown): AggregateFiltered;\n groupBy(...columns: unknown[]): AggregateGrouped;\n };\n };\n}\n\n/**\n * Type helper for the Drizzle delete chain — used only for casting.\n */\nfunction buildDeleteChain() {\n // This function is never called — it exists only for its return type.\n return undefined as unknown as {\n where(condition: unknown): Promise<{ rowCount: number }>;\n };\n}\n","import {\n pgTable,\n uuid,\n timestamp,\n text,\n jsonb,\n boolean,\n index,\n} from \"drizzle-orm/pg-core\";\nimport type { InferSelectModel, InferInsertModel } from \"drizzle-orm\";\n\nexport const auditLogs = pgTable(\n \"audit_logs\",\n {\n id: uuid().primaryKey().defaultRandom(),\n timestamp: timestamp({ withTimezone: true }).notNull().defaultNow(),\n tableName: text(\"table_name\").notNull(),\n operation: text().notNull(),\n recordId: text(\"record_id\").notNull(),\n actorId: text(\"actor_id\"),\n beforeData: jsonb(\"before_data\"),\n afterData: jsonb(\"after_data\"),\n diff: jsonb(),\n label: text(),\n description: text(),\n severity: text(),\n compliance: jsonb(),\n notify: boolean(),\n reason: text(),\n metadata: jsonb(),\n redactedFields: jsonb(\"redacted_fields\"),\n },\n (table) => [\n index(\"audit_logs_table_name_timestamp_idx\").on(\n table.tableName,\n table.timestamp,\n ),\n index(\"audit_logs_actor_id_idx\").on(table.actorId),\n index(\"audit_logs_record_id_idx\").on(table.recordId),\n index(\"audit_logs_table_name_record_id_idx\").on(\n table.tableName,\n table.recordId,\n ),\n index(\"audit_logs_operation_idx\").on(table.operation),\n index(\"audit_logs_timestamp_idx\").on(table.timestamp),\n index(\"audit_logs_timestamp_id_idx\").on(table.timestamp, table.id),\n ],\n);\n\nexport type AuditLogRow = InferSelectModel<typeof auditLogs>;\nexport type NewAuditLogRow = InferInsertModel<typeof auditLogs>;\n","import type { AuditLog, AuditOperation, AuditSeverity } from \"@usebetterdev/audit-core\";\nimport type { AuditLogRow, NewAuditLogRow } from \"./schema.js\";\n\nconst VALID_OPERATIONS: ReadonlySet<string> = new Set([\n \"INSERT\",\n \"UPDATE\",\n \"DELETE\",\n]);\n\nconst VALID_SEVERITIES: ReadonlySet<string> = new Set([\n \"low\",\n \"medium\",\n \"high\",\n \"critical\",\n]);\n\nfunction isAuditOperation(value: string): value is AuditOperation {\n return VALID_OPERATIONS.has(value);\n}\n\nfunction isAuditSeverity(value: string): value is AuditSeverity {\n return VALID_SEVERITIES.has(value);\n}\n\n/**\n * Converts a camelCase `AuditLog` to a snake_case DB row for insertion.\n * Only includes optional fields when defined (satisfies `exactOptionalPropertyTypes`).\n */\nexport function auditLogToRow(log: AuditLog): NewAuditLogRow {\n const row: NewAuditLogRow = {\n id: log.id,\n timestamp: log.timestamp,\n tableName: log.tableName,\n operation: log.operation,\n recordId: log.recordId,\n };\n\n if (log.actorId !== undefined) {\n row.actorId = log.actorId;\n }\n if (log.beforeData !== undefined) {\n row.beforeData = log.beforeData;\n }\n if (log.afterData !== undefined) {\n row.afterData = log.afterData;\n }\n if (log.diff !== undefined) {\n row.diff = log.diff;\n }\n if (log.label !== undefined) {\n row.label = log.label;\n }\n if (log.description !== undefined) {\n row.description = log.description;\n }\n if (log.severity !== undefined) {\n row.severity = log.severity;\n }\n if (log.compliance !== undefined) {\n row.compliance = log.compliance;\n }\n if (log.notify !== undefined) {\n row.notify = log.notify;\n }\n if (log.reason !== undefined) {\n row.reason = log.reason;\n }\n if (log.metadata !== undefined) {\n row.metadata = log.metadata;\n }\n if (log.redactedFields !== undefined) {\n row.redactedFields = log.redactedFields;\n }\n\n return row;\n}\n\n/**\n * Converts a snake_case DB row to a camelCase `AuditLog`.\n * Only includes optional fields when non-null.\n */\nexport function rowToAuditLog(row: AuditLogRow): AuditLog {\n if (!isAuditOperation(row.operation)) {\n throw new Error(\n `Invalid audit operation: \"${row.operation}\". Expected one of: INSERT, UPDATE, DELETE`,\n );\n }\n\n const log: AuditLog = {\n id: row.id,\n timestamp: row.timestamp,\n tableName: row.tableName,\n operation: row.operation,\n recordId: row.recordId,\n };\n\n if (row.actorId !== null) {\n log.actorId = row.actorId;\n }\n if (row.beforeData !== null) {\n log.beforeData = row.beforeData as Record<string, unknown>;\n }\n if (row.afterData !== null) {\n log.afterData = row.afterData as Record<string, unknown>;\n }\n if (row.diff !== null) {\n log.diff = row.diff as { changedFields: string[] };\n }\n if (row.label !== null) {\n log.label = row.label;\n }\n if (row.description !== null) {\n log.description = row.description;\n }\n if (row.severity !== null && row.severity !== undefined) {\n if (!isAuditSeverity(row.severity)) {\n throw new Error(\n `Invalid audit severity: \"${row.severity}\". Expected one of: low, medium, high, critical`,\n );\n }\n log.severity = row.severity;\n }\n if (row.compliance !== null) {\n log.compliance = row.compliance as string[];\n }\n if (row.notify !== null) {\n log.notify = row.notify;\n }\n if (row.reason !== null) {\n log.reason = row.reason;\n }\n if (row.metadata !== null) {\n log.metadata = row.metadata as Record<string, unknown>;\n }\n if (row.redactedFields !== null) {\n log.redactedFields = row.redactedFields as string[];\n }\n\n return log;\n}\n","import type { AuditQueryFilters, TimeFilter } from \"@usebetterdev/audit-core\";\nimport { parseDuration } from \"@usebetterdev/audit-core\";\nimport {\n and,\n or,\n eq,\n gt,\n lt,\n gte,\n lte,\n inArray,\n ilike,\n asc,\n desc,\n sql,\n} from \"drizzle-orm\";\nimport type { SQL } from \"drizzle-orm\";\nimport { auditLogs } from \"./schema.js\";\n\n/**\n * Escapes LIKE/ILIKE wildcard characters so they match literally.\n * Backslash is the default escape character in PostgreSQL LIKE patterns.\n */\nfunction escapeLikePattern(input: string): string {\n return input.replace(/[%_\\\\]/g, \"\\\\$&\");\n}\n\n/**\n * Resolves a `TimeFilter` to an absolute `Date`.\n * Absolute dates are returned as-is; duration strings are parsed relative to now.\n */\nfunction resolveTimeFilter(filter: TimeFilter): Date {\n if (\"date\" in filter && filter.date !== undefined) {\n return filter.date;\n }\n if (\"duration\" in filter && filter.duration !== undefined) {\n return parseDuration(filter.duration);\n }\n throw new Error(\"TimeFilter must have either date or duration\");\n}\n\n/**\n * Translates `AuditQueryFilters` into a Drizzle `SQL` condition (combined with AND).\n * Returns `undefined` when no filters are active — callers should omit `.where()`.\n */\nexport function buildWhereConditions(\n filters: AuditQueryFilters,\n): SQL | undefined {\n const conditions: SQL[] = [];\n\n // resource.tableName + optional recordId\n if (filters.resource !== undefined) {\n conditions.push(eq(auditLogs.tableName, filters.resource.tableName));\n if (filters.resource.recordId !== undefined) {\n conditions.push(eq(auditLogs.recordId, filters.resource.recordId));\n }\n }\n\n // actorIds — single eq or inArray\n if (filters.actorIds !== undefined && filters.actorIds.length > 0) {\n if (filters.actorIds.length === 1) {\n conditions.push(eq(auditLogs.actorId, filters.actorIds[0]!));\n } else {\n conditions.push(inArray(auditLogs.actorId, filters.actorIds));\n }\n }\n\n // severities — single eq or inArray\n if (filters.severities !== undefined && filters.severities.length > 0) {\n if (filters.severities.length === 1) {\n conditions.push(eq(auditLogs.severity, filters.severities[0]!));\n } else {\n conditions.push(inArray(auditLogs.severity, filters.severities));\n }\n }\n\n // operations — single eq or inArray\n if (filters.operations !== undefined && filters.operations.length > 0) {\n if (filters.operations.length === 1) {\n conditions.push(eq(auditLogs.operation, filters.operations[0]!));\n } else {\n conditions.push(inArray(auditLogs.operation, filters.operations));\n }\n }\n\n // since — gte(timestamp, resolved date)\n if (filters.since !== undefined) {\n conditions.push(gte(auditLogs.timestamp, resolveTimeFilter(filters.since)));\n }\n\n // until — lte(timestamp, resolved date)\n if (filters.until !== undefined) {\n conditions.push(lte(auditLogs.timestamp, resolveTimeFilter(filters.until)));\n }\n\n // searchText — ilike on label OR description\n if (filters.searchText !== undefined && filters.searchText.length > 0) {\n const escaped = escapeLikePattern(filters.searchText);\n const pattern = `%${escaped}%`;\n const searchCondition = or(\n ilike(auditLogs.label, pattern),\n ilike(auditLogs.description, pattern),\n );\n if (searchCondition !== undefined) {\n conditions.push(searchCondition);\n }\n }\n\n // compliance — jsonb @> (contains all tags)\n if (filters.compliance !== undefined && filters.compliance.length > 0) {\n conditions.push(\n sql`${auditLogs.compliance} @> ${JSON.stringify(filters.compliance)}::jsonb`,\n );\n }\n\n if (conditions.length === 0) {\n return undefined;\n }\n\n return and(...conditions);\n}\n\n/**\n * Builds a cursor-based pagination condition.\n *\n * Uses `(timestamp, id)` as the cursor key pair for stable ordering.\n * - DESC: `(ts < cursor.ts) OR (ts = cursor.ts AND id < cursor.id)`\n * - ASC: `(ts > cursor.ts) OR (ts = cursor.ts AND id > cursor.id)`\n */\nexport function buildCursorCondition(\n cursor: string,\n sortOrder: \"asc\" | \"desc\",\n): SQL | undefined {\n const decoded = decodeCursor(cursor);\n const tsCompare = sortOrder === \"asc\" ? gt : lt;\n const idCompare = sortOrder === \"asc\" ? gt : lt;\n\n return or(\n tsCompare(auditLogs.timestamp, decoded.timestamp),\n and(\n eq(auditLogs.timestamp, decoded.timestamp),\n idCompare(auditLogs.id, decoded.id),\n ),\n );\n}\n\n/**\n * Returns the `orderBy` columns for the given sort direction.\n * Default is descending (newest first).\n */\nexport function buildOrderBy(sortOrder: \"asc\" | \"desc\") {\n if (sortOrder === \"asc\") {\n return [asc(auditLogs.timestamp), asc(auditLogs.id)];\n }\n return [desc(auditLogs.timestamp), desc(auditLogs.id)];\n}\n\n/**\n * Encodes a cursor from a timestamp and id.\n * Format: base64(JSON.stringify({ t: iso_string, i: id }))\n */\nexport function encodeCursor(timestamp: Date, id: string): string {\n const payload = JSON.stringify({ t: timestamp.toISOString(), i: id });\n return btoa(payload);\n}\n\nconst UUID_PATTERN =\n /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;\n\n/**\n * Decodes an opaque cursor string back to timestamp + id.\n * Throws on invalid input.\n */\nexport function decodeCursor(cursor: string): { timestamp: Date; id: string } {\n let parsed: unknown;\n try {\n parsed = JSON.parse(atob(cursor));\n } catch {\n throw new Error(\"Invalid cursor: failed to decode\");\n }\n\n if (\n typeof parsed !== \"object\" ||\n parsed === null ||\n !(\"t\" in parsed) ||\n !(\"i\" in parsed)\n ) {\n throw new Error(\"Invalid cursor: missing required fields\");\n }\n\n const { t, i } = parsed as { t: unknown; i: unknown };\n\n if (typeof t !== \"string\" || typeof i !== \"string\") {\n throw new Error(\"Invalid cursor: fields must be strings\");\n }\n\n const timestamp = new Date(t);\n if (isNaN(timestamp.getTime())) {\n throw new Error(\"Invalid cursor: invalid timestamp\");\n }\n\n if (!UUID_PATTERN.test(i)) {\n throw new Error(\"Invalid cursor: id must be a valid UUID\");\n }\n\n return { timestamp, id: i };\n}\n","import type { CaptureLogInput, AuditOperation } from \"@usebetterdev/audit-core\";\nimport { getTableName } from \"drizzle-orm\";\nimport type { PgTable } from \"drizzle-orm/pg-core\";\nimport { OPERATION_MAP, type DrizzleMutationMethod } from \"./operation-map.js\";\n\nexport type MissingRecordIdBehavior = \"warn\" | \"skip\" | \"throw\";\n\nexport interface AuditProxyOptions {\n /**\n * Fallback column name for extracting `recordId` when the primary key\n * cannot be auto-detected from the Drizzle table schema. Defaults to `\"id\"`.\n *\n * In most cases this is not needed — the proxy reads `.primaryKey()` metadata\n * from Drizzle column objects at runtime.\n */\n primaryKey?: string;\n /**\n * Called when audit capture fails. Falls back to `console.error` if not set.\n */\n onError?: (error: unknown) => void;\n /**\n * What to do when the `recordId` cannot be determined.\n *\n * - `\"warn\"` (default) — log via `onError`, proceed with `recordId: \"unknown\"`\n * - `\"skip\"` — silently drop the audit entry\n * - `\"throw\"` — throw an error (caught by the proxy's outer error handler)\n */\n onMissingRecordId?: MissingRecordIdBehavior;\n /**\n * Table names for which the pre-mutation SELECT should be skipped.\n * Use this for high-throughput tables where the extra query is too expensive.\n */\n skipBeforeState?: string[];\n /**\n * Safety limit: if the pre-mutation SELECT returns more rows than this,\n * skip the before-state capture and warn. Defaults to 1000.\n */\n maxBeforeStateRows?: number;\n}\n\ninterface BuilderContext {\n tableName: string;\n operation: AuditOperation;\n captureLog: (input: CaptureLogInput) => Promise<void>;\n primaryKey: string;\n handleError: (error: unknown, table: string, op: string) => void;\n onMissingRecordId: MissingRecordIdBehavior;\n auditedSet: WeakSet<object>;\n dbTarget: Record<string, unknown>;\n table: PgTable;\n skipBeforeState: Set<string>;\n maxBeforeStateRows: number;\n}\n\n/**\n * Wraps a Drizzle database (or transaction) with a transparent proxy that\n * intercepts `db.insert()`, `db.update()`, `db.delete()` and calls\n * `captureLog()` after each successful mutation.\n *\n * The proxy also intercepts `db.transaction()` so that the `tx` handle\n * passed to the callback is itself proxied — this is what makes it work\n * with `better-tenant`, where all user code runs inside a transaction.\n *\n * Filtering by audited tables is NOT done here — `captureLog` (from\n * `betterAudit`) already skips tables not in `auditTables`, so there is\n * a single source of truth for which tables are audited.\n */\n// eslint-disable-next-line @typescript-eslint/no-empty-object-type\nexport function withAuditProxy<TDb extends {}>(\n db: TDb,\n captureLog: (input: CaptureLogInput) => Promise<void>,\n options?: AuditProxyOptions,\n): TDb {\n const primaryKey = options?.primaryKey ?? \"id\";\n const missingRecordIdPolicy = options?.onMissingRecordId ?? \"warn\";\n const skipBeforeState = new Set(options?.skipBeforeState ?? []);\n const maxBeforeStateRows = options?.maxBeforeStateRows ?? 1000;\n\n const handleError = (error: unknown, table: string, op: string): void => {\n try {\n if (options?.onError !== undefined) {\n options.onError(error);\n } else {\n const msg = error instanceof Error ? error.message : String(error);\n console.error(\n `audit-drizzle: capture failed for ${op} on ${table} — ${msg}`,\n );\n }\n } catch {\n // onError callback itself threw — swallow to prevent audit from breaking mutations.\n }\n };\n\n return new Proxy(db, {\n get(target, prop, receiver) {\n // Intercept insert / update / delete\n if (\n typeof prop === \"string\" &&\n (prop === \"insert\" || prop === \"update\" || prop === \"delete\")\n ) {\n const method = prop as DrizzleMutationMethod;\n const originalMethod = Reflect.get(target, prop, receiver) as (\n table: PgTable,\n ) => Record<string, unknown>;\n\n return (table: PgTable) => {\n const tableName = getTableName(table);\n const detectedPk = getPrimaryKeyColumnName(table);\n const effectivePk = detectedPk ?? primaryKey;\n const originalBuilder = originalMethod.call(target, table);\n\n const ctx: BuilderContext = {\n tableName,\n operation: OPERATION_MAP[method],\n captureLog,\n primaryKey: effectivePk,\n handleError,\n onMissingRecordId: missingRecordIdPolicy,\n auditedSet: new WeakSet<object>(),\n dbTarget: target as Record<string, unknown>,\n table,\n skipBeforeState,\n maxBeforeStateRows,\n };\n\n return wrapBuilder(originalBuilder, ctx);\n };\n }\n\n // Intercept transaction() so the tx handle is also proxied\n if (prop === \"transaction\") {\n const originalTransaction = Reflect.get(\n target,\n prop,\n receiver,\n ) as Function;\n\n return (...args: unknown[]) => {\n const callback = args[0] as (tx: unknown) => Promise<unknown>;\n const rest = args.slice(1);\n const wrappedCallback = (tx: unknown) => {\n const proxiedTx = withAuditProxy(\n tx as TDb,\n captureLog,\n options,\n );\n return callback(proxiedTx);\n };\n return originalTransaction.call(target, wrappedCallback, ...rest);\n };\n }\n\n return Reflect.get(target, prop, receiver);\n },\n });\n}\n\ninterface TrackedState {\n trackedValues?: Record<string, unknown>[];\n trackedSet?: Record<string, unknown>;\n trackedWhere?: unknown;\n hasReturning?: boolean;\n}\n\nfunction wrapBuilder(\n builder: Record<string, unknown>,\n ctx: BuilderContext,\n state: TrackedState = {},\n): Record<string, unknown> {\n return new Proxy(builder, {\n get(target, prop, receiver) {\n // Track .values() data for INSERT\n if (prop === \"values\") {\n return (...args: unknown[]) => {\n const data = args[0] as\n | Record<string, unknown>\n | Record<string, unknown>[];\n const newTrackedValues = Array.isArray(data) ? data : [data];\n const result = (\n target.values as (...a: unknown[]) => Record<string, unknown>\n )(...args);\n return wrapBuilder(result, ctx, {\n ...state,\n trackedValues: newTrackedValues,\n });\n };\n }\n\n // Track .set() data for UPDATE\n if (prop === \"set\") {\n return (...args: unknown[]) => {\n const newTrackedSet = args[0] as Record<string, unknown>;\n const result = (\n target.set as (...a: unknown[]) => Record<string, unknown>\n )(...args);\n return wrapBuilder(result, ctx, {\n ...state,\n trackedSet: newTrackedSet,\n });\n };\n }\n\n // Track .where() condition for UPDATE/DELETE pre-SELECT\n if (prop === \"where\") {\n return (...args: unknown[]) => {\n const condition = args[0];\n const result = (\n target.where as (...a: unknown[]) => Record<string, unknown>\n )(...args);\n return wrapBuilder(result, ctx, {\n ...state,\n trackedWhere: condition,\n });\n };\n }\n\n // Track .returning() so we know the result contains full rows\n if (prop === \"returning\") {\n return (...args: unknown[]) => {\n const result = (\n target.returning as (...a: unknown[]) => Record<string, unknown>\n )(...args);\n return wrapBuilder(result, ctx, {\n ...state,\n hasReturning: true,\n });\n };\n }\n\n // Intercept .then() — the terminal await point\n if (prop === \"then\") {\n return (\n onFulfilled?: (value: unknown) => unknown,\n onRejected?: (reason: unknown) => unknown,\n ) => {\n const thenFn = Reflect.get(target, \"then\", receiver) as (\n onFulfilled?: (value: unknown) => unknown,\n onRejected?: (reason: unknown) => unknown,\n ) => unknown;\n\n const needsBeforeState =\n (ctx.operation === \"UPDATE\" || ctx.operation === \"DELETE\") &&\n state.trackedWhere !== undefined &&\n !ctx.skipBeforeState.has(ctx.tableName) &&\n // For DELETE with .returning(), returned rows ARE the before-state\n !(ctx.operation === \"DELETE\" && state.hasReturning === true);\n\n if (needsBeforeState) {\n return executeWithBeforeState(\n target,\n thenFn,\n ctx,\n state,\n onFulfilled,\n onRejected,\n );\n }\n\n return thenFn.call(\n target,\n async (result: unknown) => {\n if (ctx.auditedSet.has(target)) {\n return onFulfilled?.(result);\n }\n ctx.auditedSet.add(target);\n\n try {\n await fireCaptureLog(result, ctx, state);\n } catch (error) {\n ctx.handleError(error, ctx.tableName, ctx.operation);\n }\n return onFulfilled?.(result);\n },\n onRejected,\n );\n };\n }\n\n // All other methods: forward and re-wrap\n const value = Reflect.get(target, prop, receiver);\n if (typeof value === \"function\") {\n return (...args: unknown[]) => {\n const result = (value as (...a: unknown[]) => unknown).apply(\n target,\n args,\n );\n if (result !== null && typeof result === \"object\") {\n return wrapBuilder(\n result as Record<string, unknown>,\n ctx,\n state,\n );\n }\n return result;\n };\n }\n\n return value;\n },\n });\n}\n\n/**\n * Executes a pre-mutation SELECT, then the original mutation, then fires\n * captureLog with matched before/after pairs.\n */\nfunction executeWithBeforeState(\n target: Record<string, unknown>,\n thenFn: (\n onFulfilled?: (value: unknown) => unknown,\n onRejected?: (reason: unknown) => unknown,\n ) => unknown,\n ctx: BuilderContext,\n state: TrackedState,\n onFulfilled?: (value: unknown) => unknown,\n onRejected?: (reason: unknown) => unknown,\n): unknown {\n // Issue the pre-mutation SELECT, then chain the original mutation\n const beforePromise = fetchBeforeState(ctx, state);\n\n return beforePromise.then(\n (beforeRows) => {\n // Now execute the original mutation\n return thenFn.call(\n target,\n async (result: unknown) => {\n if (ctx.auditedSet.has(target)) {\n return onFulfilled?.(result);\n }\n ctx.auditedSet.add(target);\n\n try {\n if (beforeRows !== undefined) {\n await fireCaptureLogWithBeforeState(\n result,\n beforeRows,\n ctx,\n state,\n );\n } else {\n // Fallback: before-state unavailable, use original behavior\n await fireCaptureLog(result, ctx, state);\n }\n } catch (error) {\n ctx.handleError(error, ctx.tableName, ctx.operation);\n }\n return onFulfilled?.(result);\n },\n onRejected,\n );\n },\n (error) => {\n // Pre-SELECT failed — log error, fall back to original behavior.\n ctx.handleError(error, ctx.tableName, ctx.operation);\n return thenFn.call(\n target,\n async (result: unknown) => {\n if (ctx.auditedSet.has(target)) {\n return onFulfilled?.(result);\n }\n ctx.auditedSet.add(target);\n\n try {\n await fireCaptureLog(result, ctx, state);\n } catch (captureError) {\n ctx.handleError(captureError, ctx.tableName, ctx.operation);\n }\n return onFulfilled?.(result);\n },\n onRejected,\n );\n },\n );\n}\n\n/**\n * Issues a SELECT to fetch the rows that will be affected by the mutation.\n * Returns `undefined` if the fetch should be skipped (too many rows).\n */\nasync function fetchBeforeState(\n ctx: BuilderContext,\n state: TrackedState,\n): Promise<Record<string, unknown>[] | undefined> {\n const selectFn = ctx.dbTarget.select as\n | ((...args: unknown[]) => Record<string, unknown>)\n | undefined;\n if (selectFn === undefined) {\n return undefined;\n }\n\n const selectBuilder = selectFn.call(ctx.dbTarget);\n const fromFn = selectBuilder.from as\n | ((table: PgTable) => Record<string, unknown>)\n | undefined;\n if (fromFn === undefined) {\n return undefined;\n }\n\n const fromBuilder = fromFn.call(selectBuilder, ctx.table);\n const whereFn = fromBuilder.where as\n | ((...args: unknown[]) => Record<string, unknown>)\n | undefined;\n if (whereFn === undefined) {\n return undefined;\n }\n\n const whereBuilder = whereFn.call(fromBuilder, state.trackedWhere);\n\n // Apply LIMIT to avoid fetching unbounded rows into memory.\n // Fetch limit + 1 so we can detect when the limit is exceeded.\n const limitFn = whereBuilder.limit as\n | ((n: number) => Record<string, unknown>)\n | undefined;\n const fetchLimit = ctx.maxBeforeStateRows + 1;\n const queryBuilder =\n limitFn !== undefined\n ? limitFn.call(whereBuilder, fetchLimit)\n : whereBuilder;\n\n const rows = (await queryBuilder) as unknown as Record<string, unknown>[];\n\n if (rows.length > ctx.maxBeforeStateRows) {\n ctx.handleError(\n new Error(\n `audit-drizzle: before-state SELECT returned more than ${ctx.maxBeforeStateRows} rows, skipping before-state capture`,\n ),\n ctx.tableName,\n ctx.operation,\n );\n return undefined;\n }\n\n return rows;\n}\n\nfunction getPrimaryKeyColumnName(table: PgTable): string | undefined {\n for (const [key, value] of Object.entries(table)) {\n if (\n value !== null &&\n typeof value === \"object\" &&\n \"primary\" in value &&\n value.primary === true\n ) {\n return key;\n }\n }\n return undefined;\n}\n\nfunction extractRecordId(\n row: Record<string, unknown>,\n primaryKey: string,\n): string {\n const value = row[primaryKey];\n if (value !== undefined && value !== null) {\n return String(value);\n }\n return \"\";\n}\n\n/**\n * Applies the missing-record-id policy.\n * Returns `true` if the audit entry should still be written (with \"unknown\"),\n * or `false` if it should be skipped.\n */\nfunction applyMissingRecordIdPolicy(\n ctx: BuilderContext,\n detail: string,\n): boolean {\n const policy = ctx.onMissingRecordId;\n if (policy === \"skip\") {\n return false;\n }\n if (policy === \"throw\") {\n throw new Error(\n `audit-drizzle: missing recordId for ${ctx.operation} on ${ctx.tableName} — ${detail}`,\n );\n }\n // \"warn\" — report via handleError, then proceed\n ctx.handleError(\n new Error(\n `audit-drizzle: missing recordId for ${ctx.operation} on ${ctx.tableName} — ${detail}`,\n ),\n ctx.tableName,\n ctx.operation,\n );\n return true;\n}\n\n/**\n * Fires captureLog with matched before/after pairs using pre-mutation state.\n */\nasync function fireCaptureLogWithBeforeState(\n result: unknown,\n beforeRows: Record<string, unknown>[],\n ctx: BuilderContext,\n state: TrackedState,\n): Promise<void> {\n const returnedRows = Array.isArray(result) ? result : [];\n\n if (ctx.operation === \"UPDATE\") {\n if (beforeRows.length === 0) {\n // Race condition: rows disappeared between SELECT and UPDATE\n ctx.handleError(\n new Error(\n \"audit-drizzle: before-state SELECT returned 0 rows but UPDATE succeeded — possible race condition\",\n ),\n ctx.tableName,\n ctx.operation,\n );\n // Fall back to original UPDATE behavior\n await fireCaptureLog(result, ctx, state);\n return;\n }\n\n // Build before map keyed by primary key\n const beforeMap = new Map<string, Record<string, unknown>>();\n for (const row of beforeRows) {\n const pk = extractRecordId(row, ctx.primaryKey);\n if (pk !== \"\") {\n beforeMap.set(pk, row);\n }\n }\n\n if (beforeMap.size === 0) {\n // All before-rows lacked the primary key column — likely misconfiguration\n ctx.handleError(\n new Error(\n `audit-drizzle: before-state rows exist but none have primary key \"${ctx.primaryKey}\" — check primaryKey option`,\n ),\n ctx.tableName,\n ctx.operation,\n );\n await fireCaptureLog(result, ctx, state);\n return;\n }\n\n if (state.hasReturning === true && returnedRows.length > 0) {\n // After-state from .returning() result\n for (const returnedRow of returnedRows) {\n const row = returnedRow as Record<string, unknown>;\n const recordId = extractRecordId(row, ctx.primaryKey);\n if (recordId === \"\") {\n continue;\n }\n const beforeRow = beforeMap.get(recordId);\n await ctx.captureLog({\n tableName: ctx.tableName,\n operation: ctx.operation,\n recordId,\n ...(beforeRow !== undefined && { before: beforeRow }),\n after: row,\n });\n }\n } else {\n // After-state: merge .set() data onto each before-row\n for (const [pk, beforeRow] of beforeMap) {\n const afterRow =\n state.trackedSet !== undefined\n ? { ...beforeRow, ...state.trackedSet }\n : beforeRow;\n await ctx.captureLog({\n tableName: ctx.tableName,\n operation: ctx.operation,\n recordId: pk,\n before: beforeRow,\n after: afterRow,\n });\n }\n }\n } else if (ctx.operation === \"DELETE\") {\n if (beforeRows.length === 0) {\n // Race condition: rows disappeared before DELETE\n ctx.handleError(\n new Error(\n \"audit-drizzle: before-state SELECT returned 0 rows but DELETE succeeded — possible race condition\",\n ),\n ctx.tableName,\n ctx.operation,\n );\n // Fall back to original behavior\n await fireCaptureLog(result, ctx, state);\n return;\n }\n\n for (const beforeRow of beforeRows) {\n const recordId = extractRecordId(beforeRow, ctx.primaryKey);\n if (recordId === \"\") {\n const shouldProceed = applyMissingRecordIdPolicy(\n ctx,\n \"before-state row missing primary key\",\n );\n if (!shouldProceed) {\n continue;\n }\n await ctx.captureLog({\n tableName: ctx.tableName,\n operation: ctx.operation,\n recordId: \"unknown\",\n before: beforeRow,\n });\n continue;\n }\n await ctx.captureLog({\n tableName: ctx.tableName,\n operation: ctx.operation,\n recordId,\n before: beforeRow,\n });\n }\n }\n}\n\nasync function fireCaptureLog(\n result: unknown,\n ctx: BuilderContext,\n state: TrackedState,\n): Promise<void> {\n const { trackedValues, trackedSet } = state;\n const returnedRows = Array.isArray(result) ? result : [];\n\n if (ctx.operation === \"INSERT\") {\n const values = trackedValues ?? [];\n for (let i = 0; i < values.length; i++) {\n const row = values[i];\n if (row === undefined) {\n continue;\n }\n const returnedRow =\n returnedRows.length > i\n ? (returnedRows[i] as Record<string, unknown>)\n : undefined;\n const recordId =\n returnedRow !== undefined\n ? extractRecordId(returnedRow, ctx.primaryKey)\n : extractRecordId(row, ctx.primaryKey);\n\n if (recordId === \"\") {\n const shouldProceed = applyMissingRecordIdPolicy(\n ctx,\n \"use .returning() or include the primary key in .values()\",\n );\n if (!shouldProceed) {\n continue;\n }\n\n await ctx.captureLog({\n tableName: ctx.tableName,\n operation: ctx.operation,\n recordId: \"unknown\",\n after: row,\n });\n continue;\n }\n\n await ctx.captureLog({\n tableName: ctx.tableName,\n operation: ctx.operation,\n recordId,\n after: row,\n });\n }\n } else if (ctx.operation === \"UPDATE\") {\n if (returnedRows.length > 0) {\n for (const returnedRow of returnedRows) {\n const row = returnedRow as Record<string, unknown>;\n const recordId = extractRecordId(row, ctx.primaryKey);\n if (recordId === \"\") {\n continue;\n }\n await ctx.captureLog({\n tableName: ctx.tableName,\n operation: ctx.operation,\n recordId,\n ...(trackedSet !== undefined && { after: trackedSet }),\n });\n }\n } else if (trackedSet !== undefined) {\n const recordId = extractRecordId(trackedSet, ctx.primaryKey);\n if (recordId === \"\") {\n const shouldProceed = applyMissingRecordIdPolicy(\n ctx,\n \"use .returning() to get the record id\",\n );\n if (!shouldProceed) {\n return;\n }\n }\n await ctx.captureLog({\n tableName: ctx.tableName,\n operation: ctx.operation,\n recordId: recordId || \"unknown\",\n after: trackedSet,\n });\n }\n } else if (ctx.operation === \"DELETE\") {\n if (returnedRows.length > 0) {\n for (const returnedRow of returnedRows) {\n const row = returnedRow as Record<string, unknown>;\n const recordId = extractRecordId(row, ctx.primaryKey);\n if (recordId === \"\") {\n continue;\n }\n await ctx.captureLog({\n tableName: ctx.tableName,\n operation: ctx.operation,\n recordId,\n before: row,\n });\n }\n } else {\n const shouldProceed = applyMissingRecordIdPolicy(\n ctx,\n \"use .returning() to get the record id\",\n );\n if (!shouldProceed) {\n return;\n }\n await ctx.captureLog({\n tableName: ctx.tableName,\n operation: ctx.operation,\n recordId: \"unknown\",\n });\n }\n }\n}\n","import type { AuditOperation } from \"@usebetterdev/audit-core\";\n\nexport const OPERATION_MAP = {\n insert: \"INSERT\",\n update: \"UPDATE\",\n delete: \"DELETE\",\n} as const satisfies Record<string, AuditOperation>;\n\nexport type DrizzleMutationMethod = keyof typeof OPERATION_MAP;\n"],"mappings":"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","names":["import_drizzle_orm","timestamp","import_drizzle_orm"]}
|
package/dist/index.d.cts
CHANGED
|
@@ -3,7 +3,7 @@ import * as drizzle_orm_pg_core from 'drizzle-orm/pg-core';
|
|
|
3
3
|
import { InferSelectModel, InferInsertModel, SQL } from 'drizzle-orm';
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
|
-
* Minimal shape for a Drizzle pg database that supports insert and
|
|
6
|
+
* Minimal shape for a Drizzle pg database that supports insert, select, and delete operations.
|
|
7
7
|
* Duck-typed so callers don't need the full Drizzle generic types.
|
|
8
8
|
*/
|
|
9
9
|
interface DrizzlePgDatabase {
|
|
@@ -12,7 +12,8 @@ interface DrizzlePgDatabase {
|
|
|
12
12
|
execute(): Promise<unknown>;
|
|
13
13
|
};
|
|
14
14
|
};
|
|
15
|
-
select(): unknown;
|
|
15
|
+
select(fields?: unknown): unknown;
|
|
16
|
+
delete(table: unknown): unknown;
|
|
16
17
|
}
|
|
17
18
|
/**
|
|
18
19
|
* Creates an `AuditDatabaseAdapter` backed by a Drizzle pg database.
|
package/dist/index.d.ts
CHANGED
|
@@ -3,7 +3,7 @@ import * as drizzle_orm_pg_core from 'drizzle-orm/pg-core';
|
|
|
3
3
|
import { InferSelectModel, InferInsertModel, SQL } from 'drizzle-orm';
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
|
-
* Minimal shape for a Drizzle pg database that supports insert and
|
|
6
|
+
* Minimal shape for a Drizzle pg database that supports insert, select, and delete operations.
|
|
7
7
|
* Duck-typed so callers don't need the full Drizzle generic types.
|
|
8
8
|
*/
|
|
9
9
|
interface DrizzlePgDatabase {
|
|
@@ -12,7 +12,8 @@ interface DrizzlePgDatabase {
|
|
|
12
12
|
execute(): Promise<unknown>;
|
|
13
13
|
};
|
|
14
14
|
};
|
|
15
|
-
select(): unknown;
|
|
15
|
+
select(fields?: unknown): unknown;
|
|
16
|
+
delete(table: unknown): unknown;
|
|
16
17
|
}
|
|
17
18
|
/**
|
|
18
19
|
* Creates an `AuditDatabaseAdapter` backed by a Drizzle pg database.
|
package/dist/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
// src/adapter.ts
|
|
2
|
-
import { and as and2, eq as eq2 } from "drizzle-orm";
|
|
2
|
+
import { and as and2, eq as eq2, gte as gte2, lt as lt2, isNotNull, sql as sql2, desc as desc2 } from "drizzle-orm";
|
|
3
3
|
|
|
4
4
|
// src/schema.ts
|
|
5
5
|
import {
|
|
@@ -304,6 +304,15 @@ function decodeCursor(cursor) {
|
|
|
304
304
|
// src/adapter.ts
|
|
305
305
|
var DEFAULT_LIMIT = 50;
|
|
306
306
|
var MAX_LIMIT = 250;
|
|
307
|
+
function toCount(value) {
|
|
308
|
+
if (typeof value === "number") {
|
|
309
|
+
return value;
|
|
310
|
+
}
|
|
311
|
+
if (typeof value === "string") {
|
|
312
|
+
return Number(value);
|
|
313
|
+
}
|
|
314
|
+
return 0;
|
|
315
|
+
}
|
|
307
316
|
function drizzleAuditAdapter(db) {
|
|
308
317
|
return {
|
|
309
318
|
async writeLog(log) {
|
|
@@ -337,9 +346,113 @@ function drizzleAuditAdapter(db) {
|
|
|
337
346
|
return null;
|
|
338
347
|
}
|
|
339
348
|
return rowToAuditLog(row);
|
|
349
|
+
},
|
|
350
|
+
/**
|
|
351
|
+
* Delete audit log entries older than `before`.
|
|
352
|
+
*
|
|
353
|
+
* **Warning:** Large deletes may hold a row-level lock on the `audit_logs`
|
|
354
|
+
* table for an extended period. Run during low-traffic windows when purging
|
|
355
|
+
* millions of rows.
|
|
356
|
+
*/
|
|
357
|
+
async purgeLogs(options) {
|
|
358
|
+
if (options.tableName !== void 0 && options.tableName.trim().length === 0) {
|
|
359
|
+
throw new Error("purgeLogs: tableName must be a non-empty string when provided");
|
|
360
|
+
}
|
|
361
|
+
const conditions = [lt2(auditLogs.timestamp, options.before)];
|
|
362
|
+
if (options.tableName !== void 0) {
|
|
363
|
+
conditions.push(eq2(auditLogs.tableName, options.tableName));
|
|
364
|
+
}
|
|
365
|
+
const result = await db.delete(auditLogs).where(and2(...conditions));
|
|
366
|
+
const rowCount = result.rowCount;
|
|
367
|
+
return { deletedCount: rowCount ?? 0 };
|
|
368
|
+
},
|
|
369
|
+
async getStats(options) {
|
|
370
|
+
const sinceCondition = options?.since !== void 0 ? gte2(auditLogs.timestamp, options.since) : void 0;
|
|
371
|
+
const summaryQuery = db.select({
|
|
372
|
+
totalLogs: sql2`count(*)`,
|
|
373
|
+
tablesAudited: sql2`count(DISTINCT ${auditLogs.tableName})`
|
|
374
|
+
}).from(auditLogs).where(sinceCondition);
|
|
375
|
+
const eventsPerDayQuery = db.select({
|
|
376
|
+
date: sql2`date_trunc('day', ${auditLogs.timestamp})::date`,
|
|
377
|
+
count: sql2`count(*)`
|
|
378
|
+
}).from(auditLogs).where(sinceCondition).groupBy(sql2`date_trunc('day', ${auditLogs.timestamp})`).orderBy(sql2`date_trunc('day', ${auditLogs.timestamp})`).limit(365);
|
|
379
|
+
const topActorsQuery = db.select({
|
|
380
|
+
actorId: auditLogs.actorId,
|
|
381
|
+
count: sql2`count(*)`
|
|
382
|
+
}).from(auditLogs).where(and2(sinceCondition, isNotNull(auditLogs.actorId))).groupBy(auditLogs.actorId).orderBy(desc2(sql2`count(*)`)).limit(10);
|
|
383
|
+
const topTablesQuery = db.select({
|
|
384
|
+
tableName: auditLogs.tableName,
|
|
385
|
+
count: sql2`count(*)`
|
|
386
|
+
}).from(auditLogs).where(sinceCondition).groupBy(auditLogs.tableName).orderBy(desc2(sql2`count(*)`)).limit(10);
|
|
387
|
+
const operationQuery = db.select({
|
|
388
|
+
operation: auditLogs.operation,
|
|
389
|
+
count: sql2`count(*)`
|
|
390
|
+
}).from(auditLogs).where(sinceCondition).groupBy(auditLogs.operation);
|
|
391
|
+
const severityQuery = db.select({
|
|
392
|
+
severity: auditLogs.severity,
|
|
393
|
+
count: sql2`count(*)`
|
|
394
|
+
}).from(auditLogs).where(and2(sinceCondition, isNotNull(auditLogs.severity))).groupBy(auditLogs.severity);
|
|
395
|
+
const results = await Promise.all([
|
|
396
|
+
summaryQuery,
|
|
397
|
+
eventsPerDayQuery,
|
|
398
|
+
topActorsQuery,
|
|
399
|
+
topTablesQuery,
|
|
400
|
+
operationQuery,
|
|
401
|
+
severityQuery
|
|
402
|
+
]);
|
|
403
|
+
const [
|
|
404
|
+
summaryRows,
|
|
405
|
+
eventsPerDayRows,
|
|
406
|
+
topActorsRows,
|
|
407
|
+
topTablesRows,
|
|
408
|
+
operationRows,
|
|
409
|
+
severityRows
|
|
410
|
+
] = results;
|
|
411
|
+
return assembleStats(
|
|
412
|
+
summaryRows,
|
|
413
|
+
eventsPerDayRows,
|
|
414
|
+
topActorsRows,
|
|
415
|
+
topTablesRows,
|
|
416
|
+
operationRows,
|
|
417
|
+
severityRows
|
|
418
|
+
);
|
|
340
419
|
}
|
|
341
420
|
};
|
|
342
421
|
}
|
|
422
|
+
function assembleStats(summaryRows, eventsPerDayRows, topActorsRows, topTablesRows, operationRows, severityRows) {
|
|
423
|
+
const summary = summaryRows[0];
|
|
424
|
+
const totalLogs = summary !== void 0 ? toCount(summary.totalLogs) : 0;
|
|
425
|
+
const tablesAudited = summary !== void 0 ? toCount(summary.tablesAudited) : 0;
|
|
426
|
+
const eventsPerDay = eventsPerDayRows.map((row) => ({
|
|
427
|
+
date: row.date instanceof Date ? row.date.toISOString().split("T")[0] : String(row.date),
|
|
428
|
+
count: toCount(row.count)
|
|
429
|
+
}));
|
|
430
|
+
const topActors = topActorsRows.map((row) => ({
|
|
431
|
+
actorId: String(row.actorId),
|
|
432
|
+
count: toCount(row.count)
|
|
433
|
+
}));
|
|
434
|
+
const topTables = topTablesRows.map((row) => ({
|
|
435
|
+
tableName: String(row.tableName),
|
|
436
|
+
count: toCount(row.count)
|
|
437
|
+
}));
|
|
438
|
+
const operationBreakdown = {};
|
|
439
|
+
for (const row of operationRows) {
|
|
440
|
+
operationBreakdown[String(row.operation)] = toCount(row.count);
|
|
441
|
+
}
|
|
442
|
+
const severityBreakdown = {};
|
|
443
|
+
for (const row of severityRows) {
|
|
444
|
+
severityBreakdown[String(row.severity)] = toCount(row.count);
|
|
445
|
+
}
|
|
446
|
+
return {
|
|
447
|
+
totalLogs,
|
|
448
|
+
tablesAudited,
|
|
449
|
+
eventsPerDay,
|
|
450
|
+
topActors,
|
|
451
|
+
topTables,
|
|
452
|
+
operationBreakdown,
|
|
453
|
+
severityBreakdown
|
|
454
|
+
};
|
|
455
|
+
}
|
|
343
456
|
|
|
344
457
|
// src/proxy.ts
|
|
345
458
|
import { getTableName } from "drizzle-orm";
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/adapter.ts","../src/schema.ts","../src/column-map.ts","../src/query.ts","../src/proxy.ts","../src/operation-map.ts"],"sourcesContent":["import type {\n AuditDatabaseAdapter,\n AuditLog,\n AuditQuerySpec,\n AuditQueryResult,\n} from \"@usebetterdev/audit-core\";\nimport { and, eq } from \"drizzle-orm\";\nimport { auditLogs } from \"./schema.js\";\nimport { auditLogToRow, rowToAuditLog } from \"./column-map.js\";\nimport {\n buildWhereConditions,\n buildCursorCondition,\n buildOrderBy,\n encodeCursor,\n} from \"./query.js\";\n\n/**\n * Minimal shape for a Drizzle pg database that supports insert and select operations.\n * Duck-typed so callers don't need the full Drizzle generic types.\n */\nexport interface DrizzlePgDatabase {\n insert(table: unknown): {\n values(data: unknown): { execute(): Promise<unknown> };\n };\n select(): unknown;\n}\n\nconst DEFAULT_LIMIT = 50;\nconst MAX_LIMIT = 250;\n\n/**\n * Creates an `AuditDatabaseAdapter` backed by a Drizzle pg database.\n *\n * Implements `writeLog` for inserting audit entries and `queryLogs` for\n * filtered, cursor-paginated queries.\n */\nexport function drizzleAuditAdapter(\n db: DrizzlePgDatabase,\n): AuditDatabaseAdapter {\n return {\n async writeLog(log: AuditLog): Promise<void> {\n const row = auditLogToRow(log);\n await db.insert(auditLogs).values(row).execute();\n },\n\n async queryLogs(spec: AuditQuerySpec): Promise<AuditQueryResult> {\n const sortOrder = spec.sortOrder ?? \"desc\";\n const limit = Math.min(spec.limit ?? DEFAULT_LIMIT, MAX_LIMIT);\n\n const whereCondition = buildWhereConditions(spec.filters);\n const cursorCondition =\n spec.cursor !== undefined\n ? buildCursorCondition(spec.cursor, sortOrder)\n : undefined;\n const combined = and(whereCondition, cursorCondition);\n\n const fetchLimit = limit + 1;\n const orderColumns = buildOrderBy(sortOrder);\n\n // The duck-typed interface returns `unknown` from select().\n // We build the full Drizzle query chain and cast at the boundary.\n const query = (db.select() as ReturnType<typeof buildSelectChain>)\n .from(auditLogs)\n .where(combined)\n .orderBy(...orderColumns)\n .limit(fetchLimit);\n\n const rows = (await query) as (typeof auditLogs.$inferSelect)[];\n\n const hasNextPage = rows.length > limit;\n const resultRows = hasNextPage ? rows.slice(0, -1) : rows;\n const entries = resultRows.map(rowToAuditLog);\n const lastRow = resultRows[resultRows.length - 1];\n\n if (hasNextPage && lastRow !== undefined) {\n return { entries, nextCursor: encodeCursor(lastRow.timestamp, lastRow.id) };\n }\n\n return { entries };\n },\n\n async getLogById(id: string): Promise<AuditLog | null> {\n const query = (db.select() as ReturnType<typeof buildSelectChain>)\n .from(auditLogs)\n .where(eq(auditLogs.id, id))\n .limit(1);\n\n const rows = (await query) as (typeof auditLogs.$inferSelect)[];\n const row = rows[0];\n if (row === undefined) {\n return null;\n }\n return rowToAuditLog(row);\n },\n };\n}\n\n/**\n * Type helper for the Drizzle select chain — used only for casting.\n * Not exported; exists to avoid `any`.\n */\nfunction buildSelectChain() {\n // This function is never called — it exists only for its return type.\n return undefined as unknown as {\n from(table: unknown): {\n where(condition: unknown): {\n orderBy(...columns: unknown[]): {\n limit(n: number): Promise<unknown[]>;\n };\n limit(n: number): Promise<unknown[]>;\n };\n orderBy(...columns: unknown[]): {\n limit(n: number): Promise<unknown[]>;\n };\n limit(n: number): Promise<unknown[]>;\n };\n };\n}\n","import {\n pgTable,\n uuid,\n timestamp,\n text,\n jsonb,\n boolean,\n index,\n} from \"drizzle-orm/pg-core\";\nimport type { InferSelectModel, InferInsertModel } from \"drizzle-orm\";\n\nexport const auditLogs = pgTable(\n \"audit_logs\",\n {\n id: uuid().primaryKey().defaultRandom(),\n timestamp: timestamp({ withTimezone: true }).notNull().defaultNow(),\n tableName: text(\"table_name\").notNull(),\n operation: text().notNull(),\n recordId: text(\"record_id\").notNull(),\n actorId: text(\"actor_id\"),\n beforeData: jsonb(\"before_data\"),\n afterData: jsonb(\"after_data\"),\n diff: jsonb(),\n label: text(),\n description: text(),\n severity: text(),\n compliance: jsonb(),\n notify: boolean(),\n reason: text(),\n metadata: jsonb(),\n redactedFields: jsonb(\"redacted_fields\"),\n },\n (table) => [\n index(\"audit_logs_table_name_timestamp_idx\").on(\n table.tableName,\n table.timestamp,\n ),\n index(\"audit_logs_actor_id_idx\").on(table.actorId),\n index(\"audit_logs_record_id_idx\").on(table.recordId),\n index(\"audit_logs_table_name_record_id_idx\").on(\n table.tableName,\n table.recordId,\n ),\n index(\"audit_logs_operation_idx\").on(table.operation),\n index(\"audit_logs_timestamp_idx\").on(table.timestamp),\n index(\"audit_logs_timestamp_id_idx\").on(table.timestamp, table.id),\n ],\n);\n\nexport type AuditLogRow = InferSelectModel<typeof auditLogs>;\nexport type NewAuditLogRow = InferInsertModel<typeof auditLogs>;\n","import type { AuditLog, AuditOperation, AuditSeverity } from \"@usebetterdev/audit-core\";\nimport type { AuditLogRow, NewAuditLogRow } from \"./schema.js\";\n\nconst VALID_OPERATIONS: ReadonlySet<string> = new Set([\n \"INSERT\",\n \"UPDATE\",\n \"DELETE\",\n]);\n\nconst VALID_SEVERITIES: ReadonlySet<string> = new Set([\n \"low\",\n \"medium\",\n \"high\",\n \"critical\",\n]);\n\nfunction isAuditOperation(value: string): value is AuditOperation {\n return VALID_OPERATIONS.has(value);\n}\n\nfunction isAuditSeverity(value: string): value is AuditSeverity {\n return VALID_SEVERITIES.has(value);\n}\n\n/**\n * Converts a camelCase `AuditLog` to a snake_case DB row for insertion.\n * Only includes optional fields when defined (satisfies `exactOptionalPropertyTypes`).\n */\nexport function auditLogToRow(log: AuditLog): NewAuditLogRow {\n const row: NewAuditLogRow = {\n id: log.id,\n timestamp: log.timestamp,\n tableName: log.tableName,\n operation: log.operation,\n recordId: log.recordId,\n };\n\n if (log.actorId !== undefined) {\n row.actorId = log.actorId;\n }\n if (log.beforeData !== undefined) {\n row.beforeData = log.beforeData;\n }\n if (log.afterData !== undefined) {\n row.afterData = log.afterData;\n }\n if (log.diff !== undefined) {\n row.diff = log.diff;\n }\n if (log.label !== undefined) {\n row.label = log.label;\n }\n if (log.description !== undefined) {\n row.description = log.description;\n }\n if (log.severity !== undefined) {\n row.severity = log.severity;\n }\n if (log.compliance !== undefined) {\n row.compliance = log.compliance;\n }\n if (log.notify !== undefined) {\n row.notify = log.notify;\n }\n if (log.reason !== undefined) {\n row.reason = log.reason;\n }\n if (log.metadata !== undefined) {\n row.metadata = log.metadata;\n }\n if (log.redactedFields !== undefined) {\n row.redactedFields = log.redactedFields;\n }\n\n return row;\n}\n\n/**\n * Converts a snake_case DB row to a camelCase `AuditLog`.\n * Only includes optional fields when non-null.\n */\nexport function rowToAuditLog(row: AuditLogRow): AuditLog {\n if (!isAuditOperation(row.operation)) {\n throw new Error(\n `Invalid audit operation: \"${row.operation}\". Expected one of: INSERT, UPDATE, DELETE`,\n );\n }\n\n const log: AuditLog = {\n id: row.id,\n timestamp: row.timestamp,\n tableName: row.tableName,\n operation: row.operation,\n recordId: row.recordId,\n };\n\n if (row.actorId !== null) {\n log.actorId = row.actorId;\n }\n if (row.beforeData !== null) {\n log.beforeData = row.beforeData as Record<string, unknown>;\n }\n if (row.afterData !== null) {\n log.afterData = row.afterData as Record<string, unknown>;\n }\n if (row.diff !== null) {\n log.diff = row.diff as { changedFields: string[] };\n }\n if (row.label !== null) {\n log.label = row.label;\n }\n if (row.description !== null) {\n log.description = row.description;\n }\n if (row.severity !== null && row.severity !== undefined) {\n if (!isAuditSeverity(row.severity)) {\n throw new Error(\n `Invalid audit severity: \"${row.severity}\". Expected one of: low, medium, high, critical`,\n );\n }\n log.severity = row.severity;\n }\n if (row.compliance !== null) {\n log.compliance = row.compliance as string[];\n }\n if (row.notify !== null) {\n log.notify = row.notify;\n }\n if (row.reason !== null) {\n log.reason = row.reason;\n }\n if (row.metadata !== null) {\n log.metadata = row.metadata as Record<string, unknown>;\n }\n if (row.redactedFields !== null) {\n log.redactedFields = row.redactedFields as string[];\n }\n\n return log;\n}\n","import type { AuditQueryFilters, TimeFilter } from \"@usebetterdev/audit-core\";\nimport { parseDuration } from \"@usebetterdev/audit-core\";\nimport {\n and,\n or,\n eq,\n gt,\n lt,\n gte,\n lte,\n inArray,\n ilike,\n asc,\n desc,\n sql,\n} from \"drizzle-orm\";\nimport type { SQL } from \"drizzle-orm\";\nimport { auditLogs } from \"./schema.js\";\n\n/**\n * Escapes LIKE/ILIKE wildcard characters so they match literally.\n * Backslash is the default escape character in PostgreSQL LIKE patterns.\n */\nfunction escapeLikePattern(input: string): string {\n return input.replace(/[%_\\\\]/g, \"\\\\$&\");\n}\n\n/**\n * Resolves a `TimeFilter` to an absolute `Date`.\n * Absolute dates are returned as-is; duration strings are parsed relative to now.\n */\nfunction resolveTimeFilter(filter: TimeFilter): Date {\n if (\"date\" in filter && filter.date !== undefined) {\n return filter.date;\n }\n if (\"duration\" in filter && filter.duration !== undefined) {\n return parseDuration(filter.duration);\n }\n throw new Error(\"TimeFilter must have either date or duration\");\n}\n\n/**\n * Translates `AuditQueryFilters` into a Drizzle `SQL` condition (combined with AND).\n * Returns `undefined` when no filters are active — callers should omit `.where()`.\n */\nexport function buildWhereConditions(\n filters: AuditQueryFilters,\n): SQL | undefined {\n const conditions: SQL[] = [];\n\n // resource.tableName + optional recordId\n if (filters.resource !== undefined) {\n conditions.push(eq(auditLogs.tableName, filters.resource.tableName));\n if (filters.resource.recordId !== undefined) {\n conditions.push(eq(auditLogs.recordId, filters.resource.recordId));\n }\n }\n\n // actorIds — single eq or inArray\n if (filters.actorIds !== undefined && filters.actorIds.length > 0) {\n if (filters.actorIds.length === 1) {\n conditions.push(eq(auditLogs.actorId, filters.actorIds[0]!));\n } else {\n conditions.push(inArray(auditLogs.actorId, filters.actorIds));\n }\n }\n\n // severities — single eq or inArray\n if (filters.severities !== undefined && filters.severities.length > 0) {\n if (filters.severities.length === 1) {\n conditions.push(eq(auditLogs.severity, filters.severities[0]!));\n } else {\n conditions.push(inArray(auditLogs.severity, filters.severities));\n }\n }\n\n // operations — single eq or inArray\n if (filters.operations !== undefined && filters.operations.length > 0) {\n if (filters.operations.length === 1) {\n conditions.push(eq(auditLogs.operation, filters.operations[0]!));\n } else {\n conditions.push(inArray(auditLogs.operation, filters.operations));\n }\n }\n\n // since — gte(timestamp, resolved date)\n if (filters.since !== undefined) {\n conditions.push(gte(auditLogs.timestamp, resolveTimeFilter(filters.since)));\n }\n\n // until — lte(timestamp, resolved date)\n if (filters.until !== undefined) {\n conditions.push(lte(auditLogs.timestamp, resolveTimeFilter(filters.until)));\n }\n\n // searchText — ilike on label OR description\n if (filters.searchText !== undefined && filters.searchText.length > 0) {\n const escaped = escapeLikePattern(filters.searchText);\n const pattern = `%${escaped}%`;\n const searchCondition = or(\n ilike(auditLogs.label, pattern),\n ilike(auditLogs.description, pattern),\n );\n if (searchCondition !== undefined) {\n conditions.push(searchCondition);\n }\n }\n\n // compliance — jsonb @> (contains all tags)\n if (filters.compliance !== undefined && filters.compliance.length > 0) {\n conditions.push(\n sql`${auditLogs.compliance} @> ${JSON.stringify(filters.compliance)}::jsonb`,\n );\n }\n\n if (conditions.length === 0) {\n return undefined;\n }\n\n return and(...conditions);\n}\n\n/**\n * Builds a cursor-based pagination condition.\n *\n * Uses `(timestamp, id)` as the cursor key pair for stable ordering.\n * - DESC: `(ts < cursor.ts) OR (ts = cursor.ts AND id < cursor.id)`\n * - ASC: `(ts > cursor.ts) OR (ts = cursor.ts AND id > cursor.id)`\n */\nexport function buildCursorCondition(\n cursor: string,\n sortOrder: \"asc\" | \"desc\",\n): SQL | undefined {\n const decoded = decodeCursor(cursor);\n const tsCompare = sortOrder === \"asc\" ? gt : lt;\n const idCompare = sortOrder === \"asc\" ? gt : lt;\n\n return or(\n tsCompare(auditLogs.timestamp, decoded.timestamp),\n and(\n eq(auditLogs.timestamp, decoded.timestamp),\n idCompare(auditLogs.id, decoded.id),\n ),\n );\n}\n\n/**\n * Returns the `orderBy` columns for the given sort direction.\n * Default is descending (newest first).\n */\nexport function buildOrderBy(sortOrder: \"asc\" | \"desc\") {\n if (sortOrder === \"asc\") {\n return [asc(auditLogs.timestamp), asc(auditLogs.id)];\n }\n return [desc(auditLogs.timestamp), desc(auditLogs.id)];\n}\n\n/**\n * Encodes a cursor from a timestamp and id.\n * Format: base64(JSON.stringify({ t: iso_string, i: id }))\n */\nexport function encodeCursor(timestamp: Date, id: string): string {\n const payload = JSON.stringify({ t: timestamp.toISOString(), i: id });\n return btoa(payload);\n}\n\nconst UUID_PATTERN =\n /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;\n\n/**\n * Decodes an opaque cursor string back to timestamp + id.\n * Throws on invalid input.\n */\nexport function decodeCursor(cursor: string): { timestamp: Date; id: string } {\n let parsed: unknown;\n try {\n parsed = JSON.parse(atob(cursor));\n } catch {\n throw new Error(\"Invalid cursor: failed to decode\");\n }\n\n if (\n typeof parsed !== \"object\" ||\n parsed === null ||\n !(\"t\" in parsed) ||\n !(\"i\" in parsed)\n ) {\n throw new Error(\"Invalid cursor: missing required fields\");\n }\n\n const { t, i } = parsed as { t: unknown; i: unknown };\n\n if (typeof t !== \"string\" || typeof i !== \"string\") {\n throw new Error(\"Invalid cursor: fields must be strings\");\n }\n\n const timestamp = new Date(t);\n if (isNaN(timestamp.getTime())) {\n throw new Error(\"Invalid cursor: invalid timestamp\");\n }\n\n if (!UUID_PATTERN.test(i)) {\n throw new Error(\"Invalid cursor: id must be a valid UUID\");\n }\n\n return { timestamp, id: i };\n}\n","import type { CaptureLogInput, AuditOperation } from \"@usebetterdev/audit-core\";\nimport { getTableName } from \"drizzle-orm\";\nimport type { PgTable } from \"drizzle-orm/pg-core\";\nimport { OPERATION_MAP, type DrizzleMutationMethod } from \"./operation-map.js\";\n\nexport type MissingRecordIdBehavior = \"warn\" | \"skip\" | \"throw\";\n\nexport interface AuditProxyOptions {\n /**\n * Fallback column name for extracting `recordId` when the primary key\n * cannot be auto-detected from the Drizzle table schema. Defaults to `\"id\"`.\n *\n * In most cases this is not needed — the proxy reads `.primaryKey()` metadata\n * from Drizzle column objects at runtime.\n */\n primaryKey?: string;\n /**\n * Called when audit capture fails. Falls back to `console.error` if not set.\n */\n onError?: (error: unknown) => void;\n /**\n * What to do when the `recordId` cannot be determined.\n *\n * - `\"warn\"` (default) — log via `onError`, proceed with `recordId: \"unknown\"`\n * - `\"skip\"` — silently drop the audit entry\n * - `\"throw\"` — throw an error (caught by the proxy's outer error handler)\n */\n onMissingRecordId?: MissingRecordIdBehavior;\n /**\n * Table names for which the pre-mutation SELECT should be skipped.\n * Use this for high-throughput tables where the extra query is too expensive.\n */\n skipBeforeState?: string[];\n /**\n * Safety limit: if the pre-mutation SELECT returns more rows than this,\n * skip the before-state capture and warn. Defaults to 1000.\n */\n maxBeforeStateRows?: number;\n}\n\ninterface BuilderContext {\n tableName: string;\n operation: AuditOperation;\n captureLog: (input: CaptureLogInput) => Promise<void>;\n primaryKey: string;\n handleError: (error: unknown, table: string, op: string) => void;\n onMissingRecordId: MissingRecordIdBehavior;\n auditedSet: WeakSet<object>;\n dbTarget: Record<string, unknown>;\n table: PgTable;\n skipBeforeState: Set<string>;\n maxBeforeStateRows: number;\n}\n\n/**\n * Wraps a Drizzle database (or transaction) with a transparent proxy that\n * intercepts `db.insert()`, `db.update()`, `db.delete()` and calls\n * `captureLog()` after each successful mutation.\n *\n * The proxy also intercepts `db.transaction()` so that the `tx` handle\n * passed to the callback is itself proxied — this is what makes it work\n * with `better-tenant`, where all user code runs inside a transaction.\n *\n * Filtering by audited tables is NOT done here — `captureLog` (from\n * `betterAudit`) already skips tables not in `auditTables`, so there is\n * a single source of truth for which tables are audited.\n */\n// eslint-disable-next-line @typescript-eslint/no-empty-object-type\nexport function withAuditProxy<TDb extends {}>(\n db: TDb,\n captureLog: (input: CaptureLogInput) => Promise<void>,\n options?: AuditProxyOptions,\n): TDb {\n const primaryKey = options?.primaryKey ?? \"id\";\n const missingRecordIdPolicy = options?.onMissingRecordId ?? \"warn\";\n const skipBeforeState = new Set(options?.skipBeforeState ?? []);\n const maxBeforeStateRows = options?.maxBeforeStateRows ?? 1000;\n\n const handleError = (error: unknown, table: string, op: string): void => {\n try {\n if (options?.onError !== undefined) {\n options.onError(error);\n } else {\n const msg = error instanceof Error ? error.message : String(error);\n console.error(\n `audit-drizzle: capture failed for ${op} on ${table} — ${msg}`,\n );\n }\n } catch {\n // onError callback itself threw — swallow to prevent audit from breaking mutations.\n }\n };\n\n return new Proxy(db, {\n get(target, prop, receiver) {\n // Intercept insert / update / delete\n if (\n typeof prop === \"string\" &&\n (prop === \"insert\" || prop === \"update\" || prop === \"delete\")\n ) {\n const method = prop as DrizzleMutationMethod;\n const originalMethod = Reflect.get(target, prop, receiver) as (\n table: PgTable,\n ) => Record<string, unknown>;\n\n return (table: PgTable) => {\n const tableName = getTableName(table);\n const detectedPk = getPrimaryKeyColumnName(table);\n const effectivePk = detectedPk ?? primaryKey;\n const originalBuilder = originalMethod.call(target, table);\n\n const ctx: BuilderContext = {\n tableName,\n operation: OPERATION_MAP[method],\n captureLog,\n primaryKey: effectivePk,\n handleError,\n onMissingRecordId: missingRecordIdPolicy,\n auditedSet: new WeakSet<object>(),\n dbTarget: target as Record<string, unknown>,\n table,\n skipBeforeState,\n maxBeforeStateRows,\n };\n\n return wrapBuilder(originalBuilder, ctx);\n };\n }\n\n // Intercept transaction() so the tx handle is also proxied\n if (prop === \"transaction\") {\n const originalTransaction = Reflect.get(\n target,\n prop,\n receiver,\n ) as Function;\n\n return (...args: unknown[]) => {\n const callback = args[0] as (tx: unknown) => Promise<unknown>;\n const rest = args.slice(1);\n const wrappedCallback = (tx: unknown) => {\n const proxiedTx = withAuditProxy(\n tx as TDb,\n captureLog,\n options,\n );\n return callback(proxiedTx);\n };\n return originalTransaction.call(target, wrappedCallback, ...rest);\n };\n }\n\n return Reflect.get(target, prop, receiver);\n },\n });\n}\n\ninterface TrackedState {\n trackedValues?: Record<string, unknown>[];\n trackedSet?: Record<string, unknown>;\n trackedWhere?: unknown;\n hasReturning?: boolean;\n}\n\nfunction wrapBuilder(\n builder: Record<string, unknown>,\n ctx: BuilderContext,\n state: TrackedState = {},\n): Record<string, unknown> {\n return new Proxy(builder, {\n get(target, prop, receiver) {\n // Track .values() data for INSERT\n if (prop === \"values\") {\n return (...args: unknown[]) => {\n const data = args[0] as\n | Record<string, unknown>\n | Record<string, unknown>[];\n const newTrackedValues = Array.isArray(data) ? data : [data];\n const result = (\n target.values as (...a: unknown[]) => Record<string, unknown>\n )(...args);\n return wrapBuilder(result, ctx, {\n ...state,\n trackedValues: newTrackedValues,\n });\n };\n }\n\n // Track .set() data for UPDATE\n if (prop === \"set\") {\n return (...args: unknown[]) => {\n const newTrackedSet = args[0] as Record<string, unknown>;\n const result = (\n target.set as (...a: unknown[]) => Record<string, unknown>\n )(...args);\n return wrapBuilder(result, ctx, {\n ...state,\n trackedSet: newTrackedSet,\n });\n };\n }\n\n // Track .where() condition for UPDATE/DELETE pre-SELECT\n if (prop === \"where\") {\n return (...args: unknown[]) => {\n const condition = args[0];\n const result = (\n target.where as (...a: unknown[]) => Record<string, unknown>\n )(...args);\n return wrapBuilder(result, ctx, {\n ...state,\n trackedWhere: condition,\n });\n };\n }\n\n // Track .returning() so we know the result contains full rows\n if (prop === \"returning\") {\n return (...args: unknown[]) => {\n const result = (\n target.returning as (...a: unknown[]) => Record<string, unknown>\n )(...args);\n return wrapBuilder(result, ctx, {\n ...state,\n hasReturning: true,\n });\n };\n }\n\n // Intercept .then() — the terminal await point\n if (prop === \"then\") {\n return (\n onFulfilled?: (value: unknown) => unknown,\n onRejected?: (reason: unknown) => unknown,\n ) => {\n const thenFn = Reflect.get(target, \"then\", receiver) as (\n onFulfilled?: (value: unknown) => unknown,\n onRejected?: (reason: unknown) => unknown,\n ) => unknown;\n\n const needsBeforeState =\n (ctx.operation === \"UPDATE\" || ctx.operation === \"DELETE\") &&\n state.trackedWhere !== undefined &&\n !ctx.skipBeforeState.has(ctx.tableName) &&\n // For DELETE with .returning(), returned rows ARE the before-state\n !(ctx.operation === \"DELETE\" && state.hasReturning === true);\n\n if (needsBeforeState) {\n return executeWithBeforeState(\n target,\n thenFn,\n ctx,\n state,\n onFulfilled,\n onRejected,\n );\n }\n\n return thenFn.call(\n target,\n async (result: unknown) => {\n if (ctx.auditedSet.has(target)) {\n return onFulfilled?.(result);\n }\n ctx.auditedSet.add(target);\n\n try {\n await fireCaptureLog(result, ctx, state);\n } catch (error) {\n ctx.handleError(error, ctx.tableName, ctx.operation);\n }\n return onFulfilled?.(result);\n },\n onRejected,\n );\n };\n }\n\n // All other methods: forward and re-wrap\n const value = Reflect.get(target, prop, receiver);\n if (typeof value === \"function\") {\n return (...args: unknown[]) => {\n const result = (value as (...a: unknown[]) => unknown).apply(\n target,\n args,\n );\n if (result !== null && typeof result === \"object\") {\n return wrapBuilder(\n result as Record<string, unknown>,\n ctx,\n state,\n );\n }\n return result;\n };\n }\n\n return value;\n },\n });\n}\n\n/**\n * Executes a pre-mutation SELECT, then the original mutation, then fires\n * captureLog with matched before/after pairs.\n */\nfunction executeWithBeforeState(\n target: Record<string, unknown>,\n thenFn: (\n onFulfilled?: (value: unknown) => unknown,\n onRejected?: (reason: unknown) => unknown,\n ) => unknown,\n ctx: BuilderContext,\n state: TrackedState,\n onFulfilled?: (value: unknown) => unknown,\n onRejected?: (reason: unknown) => unknown,\n): unknown {\n // Issue the pre-mutation SELECT, then chain the original mutation\n const beforePromise = fetchBeforeState(ctx, state);\n\n return beforePromise.then(\n (beforeRows) => {\n // Now execute the original mutation\n return thenFn.call(\n target,\n async (result: unknown) => {\n if (ctx.auditedSet.has(target)) {\n return onFulfilled?.(result);\n }\n ctx.auditedSet.add(target);\n\n try {\n if (beforeRows !== undefined) {\n await fireCaptureLogWithBeforeState(\n result,\n beforeRows,\n ctx,\n state,\n );\n } else {\n // Fallback: before-state unavailable, use original behavior\n await fireCaptureLog(result, ctx, state);\n }\n } catch (error) {\n ctx.handleError(error, ctx.tableName, ctx.operation);\n }\n return onFulfilled?.(result);\n },\n onRejected,\n );\n },\n (error) => {\n // Pre-SELECT failed — log error, fall back to original behavior.\n ctx.handleError(error, ctx.tableName, ctx.operation);\n return thenFn.call(\n target,\n async (result: unknown) => {\n if (ctx.auditedSet.has(target)) {\n return onFulfilled?.(result);\n }\n ctx.auditedSet.add(target);\n\n try {\n await fireCaptureLog(result, ctx, state);\n } catch (captureError) {\n ctx.handleError(captureError, ctx.tableName, ctx.operation);\n }\n return onFulfilled?.(result);\n },\n onRejected,\n );\n },\n );\n}\n\n/**\n * Issues a SELECT to fetch the rows that will be affected by the mutation.\n * Returns `undefined` if the fetch should be skipped (too many rows).\n */\nasync function fetchBeforeState(\n ctx: BuilderContext,\n state: TrackedState,\n): Promise<Record<string, unknown>[] | undefined> {\n const selectFn = ctx.dbTarget.select as\n | ((...args: unknown[]) => Record<string, unknown>)\n | undefined;\n if (selectFn === undefined) {\n return undefined;\n }\n\n const selectBuilder = selectFn.call(ctx.dbTarget);\n const fromFn = selectBuilder.from as\n | ((table: PgTable) => Record<string, unknown>)\n | undefined;\n if (fromFn === undefined) {\n return undefined;\n }\n\n const fromBuilder = fromFn.call(selectBuilder, ctx.table);\n const whereFn = fromBuilder.where as\n | ((...args: unknown[]) => Record<string, unknown>)\n | undefined;\n if (whereFn === undefined) {\n return undefined;\n }\n\n const whereBuilder = whereFn.call(fromBuilder, state.trackedWhere);\n\n // Apply LIMIT to avoid fetching unbounded rows into memory.\n // Fetch limit + 1 so we can detect when the limit is exceeded.\n const limitFn = whereBuilder.limit as\n | ((n: number) => Record<string, unknown>)\n | undefined;\n const fetchLimit = ctx.maxBeforeStateRows + 1;\n const queryBuilder =\n limitFn !== undefined\n ? limitFn.call(whereBuilder, fetchLimit)\n : whereBuilder;\n\n const rows = (await queryBuilder) as unknown as Record<string, unknown>[];\n\n if (rows.length > ctx.maxBeforeStateRows) {\n ctx.handleError(\n new Error(\n `audit-drizzle: before-state SELECT returned more than ${ctx.maxBeforeStateRows} rows, skipping before-state capture`,\n ),\n ctx.tableName,\n ctx.operation,\n );\n return undefined;\n }\n\n return rows;\n}\n\nfunction getPrimaryKeyColumnName(table: PgTable): string | undefined {\n for (const [key, value] of Object.entries(table)) {\n if (\n value !== null &&\n typeof value === \"object\" &&\n \"primary\" in value &&\n value.primary === true\n ) {\n return key;\n }\n }\n return undefined;\n}\n\nfunction extractRecordId(\n row: Record<string, unknown>,\n primaryKey: string,\n): string {\n const value = row[primaryKey];\n if (value !== undefined && value !== null) {\n return String(value);\n }\n return \"\";\n}\n\n/**\n * Applies the missing-record-id policy.\n * Returns `true` if the audit entry should still be written (with \"unknown\"),\n * or `false` if it should be skipped.\n */\nfunction applyMissingRecordIdPolicy(\n ctx: BuilderContext,\n detail: string,\n): boolean {\n const policy = ctx.onMissingRecordId;\n if (policy === \"skip\") {\n return false;\n }\n if (policy === \"throw\") {\n throw new Error(\n `audit-drizzle: missing recordId for ${ctx.operation} on ${ctx.tableName} — ${detail}`,\n );\n }\n // \"warn\" — report via handleError, then proceed\n ctx.handleError(\n new Error(\n `audit-drizzle: missing recordId for ${ctx.operation} on ${ctx.tableName} — ${detail}`,\n ),\n ctx.tableName,\n ctx.operation,\n );\n return true;\n}\n\n/**\n * Fires captureLog with matched before/after pairs using pre-mutation state.\n */\nasync function fireCaptureLogWithBeforeState(\n result: unknown,\n beforeRows: Record<string, unknown>[],\n ctx: BuilderContext,\n state: TrackedState,\n): Promise<void> {\n const returnedRows = Array.isArray(result) ? result : [];\n\n if (ctx.operation === \"UPDATE\") {\n if (beforeRows.length === 0) {\n // Race condition: rows disappeared between SELECT and UPDATE\n ctx.handleError(\n new Error(\n \"audit-drizzle: before-state SELECT returned 0 rows but UPDATE succeeded — possible race condition\",\n ),\n ctx.tableName,\n ctx.operation,\n );\n // Fall back to original UPDATE behavior\n await fireCaptureLog(result, ctx, state);\n return;\n }\n\n // Build before map keyed by primary key\n const beforeMap = new Map<string, Record<string, unknown>>();\n for (const row of beforeRows) {\n const pk = extractRecordId(row, ctx.primaryKey);\n if (pk !== \"\") {\n beforeMap.set(pk, row);\n }\n }\n\n if (beforeMap.size === 0) {\n // All before-rows lacked the primary key column — likely misconfiguration\n ctx.handleError(\n new Error(\n `audit-drizzle: before-state rows exist but none have primary key \"${ctx.primaryKey}\" — check primaryKey option`,\n ),\n ctx.tableName,\n ctx.operation,\n );\n await fireCaptureLog(result, ctx, state);\n return;\n }\n\n if (state.hasReturning === true && returnedRows.length > 0) {\n // After-state from .returning() result\n for (const returnedRow of returnedRows) {\n const row = returnedRow as Record<string, unknown>;\n const recordId = extractRecordId(row, ctx.primaryKey);\n if (recordId === \"\") {\n continue;\n }\n const beforeRow = beforeMap.get(recordId);\n await ctx.captureLog({\n tableName: ctx.tableName,\n operation: ctx.operation,\n recordId,\n ...(beforeRow !== undefined && { before: beforeRow }),\n after: row,\n });\n }\n } else {\n // After-state: merge .set() data onto each before-row\n for (const [pk, beforeRow] of beforeMap) {\n const afterRow =\n state.trackedSet !== undefined\n ? { ...beforeRow, ...state.trackedSet }\n : beforeRow;\n await ctx.captureLog({\n tableName: ctx.tableName,\n operation: ctx.operation,\n recordId: pk,\n before: beforeRow,\n after: afterRow,\n });\n }\n }\n } else if (ctx.operation === \"DELETE\") {\n if (beforeRows.length === 0) {\n // Race condition: rows disappeared before DELETE\n ctx.handleError(\n new Error(\n \"audit-drizzle: before-state SELECT returned 0 rows but DELETE succeeded — possible race condition\",\n ),\n ctx.tableName,\n ctx.operation,\n );\n // Fall back to original behavior\n await fireCaptureLog(result, ctx, state);\n return;\n }\n\n for (const beforeRow of beforeRows) {\n const recordId = extractRecordId(beforeRow, ctx.primaryKey);\n if (recordId === \"\") {\n const shouldProceed = applyMissingRecordIdPolicy(\n ctx,\n \"before-state row missing primary key\",\n );\n if (!shouldProceed) {\n continue;\n }\n await ctx.captureLog({\n tableName: ctx.tableName,\n operation: ctx.operation,\n recordId: \"unknown\",\n before: beforeRow,\n });\n continue;\n }\n await ctx.captureLog({\n tableName: ctx.tableName,\n operation: ctx.operation,\n recordId,\n before: beforeRow,\n });\n }\n }\n}\n\nasync function fireCaptureLog(\n result: unknown,\n ctx: BuilderContext,\n state: TrackedState,\n): Promise<void> {\n const { trackedValues, trackedSet } = state;\n const returnedRows = Array.isArray(result) ? result : [];\n\n if (ctx.operation === \"INSERT\") {\n const values = trackedValues ?? [];\n for (let i = 0; i < values.length; i++) {\n const row = values[i];\n if (row === undefined) {\n continue;\n }\n const returnedRow =\n returnedRows.length > i\n ? (returnedRows[i] as Record<string, unknown>)\n : undefined;\n const recordId =\n returnedRow !== undefined\n ? extractRecordId(returnedRow, ctx.primaryKey)\n : extractRecordId(row, ctx.primaryKey);\n\n if (recordId === \"\") {\n const shouldProceed = applyMissingRecordIdPolicy(\n ctx,\n \"use .returning() or include the primary key in .values()\",\n );\n if (!shouldProceed) {\n continue;\n }\n\n await ctx.captureLog({\n tableName: ctx.tableName,\n operation: ctx.operation,\n recordId: \"unknown\",\n after: row,\n });\n continue;\n }\n\n await ctx.captureLog({\n tableName: ctx.tableName,\n operation: ctx.operation,\n recordId,\n after: row,\n });\n }\n } else if (ctx.operation === \"UPDATE\") {\n if (returnedRows.length > 0) {\n for (const returnedRow of returnedRows) {\n const row = returnedRow as Record<string, unknown>;\n const recordId = extractRecordId(row, ctx.primaryKey);\n if (recordId === \"\") {\n continue;\n }\n await ctx.captureLog({\n tableName: ctx.tableName,\n operation: ctx.operation,\n recordId,\n ...(trackedSet !== undefined && { after: trackedSet }),\n });\n }\n } else if (trackedSet !== undefined) {\n const recordId = extractRecordId(trackedSet, ctx.primaryKey);\n if (recordId === \"\") {\n const shouldProceed = applyMissingRecordIdPolicy(\n ctx,\n \"use .returning() to get the record id\",\n );\n if (!shouldProceed) {\n return;\n }\n }\n await ctx.captureLog({\n tableName: ctx.tableName,\n operation: ctx.operation,\n recordId: recordId || \"unknown\",\n after: trackedSet,\n });\n }\n } else if (ctx.operation === \"DELETE\") {\n if (returnedRows.length > 0) {\n for (const returnedRow of returnedRows) {\n const row = returnedRow as Record<string, unknown>;\n const recordId = extractRecordId(row, ctx.primaryKey);\n if (recordId === \"\") {\n continue;\n }\n await ctx.captureLog({\n tableName: ctx.tableName,\n operation: ctx.operation,\n recordId,\n before: row,\n });\n }\n } else {\n const shouldProceed = applyMissingRecordIdPolicy(\n ctx,\n \"use .returning() to get the record id\",\n );\n if (!shouldProceed) {\n return;\n }\n await ctx.captureLog({\n tableName: ctx.tableName,\n operation: ctx.operation,\n recordId: \"unknown\",\n });\n }\n }\n}\n","import type { AuditOperation } from \"@usebetterdev/audit-core\";\n\nexport const OPERATION_MAP = {\n insert: \"INSERT\",\n update: \"UPDATE\",\n delete: \"DELETE\",\n} as const satisfies Record<string, AuditOperation>;\n\nexport type DrizzleMutationMethod = keyof typeof OPERATION_MAP;\n"],"mappings":"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","names":["and","eq","timestamp","and","eq"]}
|
|
1
|
+
{"version":3,"sources":["../src/adapter.ts","../src/schema.ts","../src/column-map.ts","../src/query.ts","../src/proxy.ts","../src/operation-map.ts"],"sourcesContent":["import type {\n AuditDatabaseAdapter,\n AuditLog,\n AuditQuerySpec,\n AuditQueryResult,\n AuditStats,\n} from \"@usebetterdev/audit-core\";\nimport { and, eq, gte, lt, isNotNull, sql, desc } from \"drizzle-orm\";\nimport { auditLogs } from \"./schema.js\";\nimport { auditLogToRow, rowToAuditLog } from \"./column-map.js\";\nimport {\n buildWhereConditions,\n buildCursorCondition,\n buildOrderBy,\n encodeCursor,\n} from \"./query.js\";\n\n/**\n * Minimal shape for a Drizzle pg database that supports insert, select, and delete operations.\n * Duck-typed so callers don't need the full Drizzle generic types.\n */\nexport interface DrizzlePgDatabase {\n insert(table: unknown): {\n values(data: unknown): { execute(): Promise<unknown> };\n };\n select(fields?: unknown): unknown;\n delete(table: unknown): unknown;\n}\n\nconst DEFAULT_LIMIT = 50;\nconst MAX_LIMIT = 250;\n\n/**\n * Safely converts a PostgreSQL bigint count (returned as string) to a number.\n */\nfunction toCount(value: unknown): number {\n if (typeof value === \"number\") {\n return value;\n }\n if (typeof value === \"string\") {\n return Number(value);\n }\n return 0;\n}\n\n/**\n * Creates an `AuditDatabaseAdapter` backed by a Drizzle pg database.\n *\n * Implements `writeLog` for inserting audit entries and `queryLogs` for\n * filtered, cursor-paginated queries.\n */\nexport function drizzleAuditAdapter(\n db: DrizzlePgDatabase,\n): AuditDatabaseAdapter {\n return {\n async writeLog(log: AuditLog): Promise<void> {\n const row = auditLogToRow(log);\n await db.insert(auditLogs).values(row).execute();\n },\n\n async queryLogs(spec: AuditQuerySpec): Promise<AuditQueryResult> {\n const sortOrder = spec.sortOrder ?? \"desc\";\n const limit = Math.min(spec.limit ?? DEFAULT_LIMIT, MAX_LIMIT);\n\n const whereCondition = buildWhereConditions(spec.filters);\n const cursorCondition =\n spec.cursor !== undefined\n ? buildCursorCondition(spec.cursor, sortOrder)\n : undefined;\n const combined = and(whereCondition, cursorCondition);\n\n const fetchLimit = limit + 1;\n const orderColumns = buildOrderBy(sortOrder);\n\n // The duck-typed interface returns `unknown` from select().\n // We build the full Drizzle query chain and cast at the boundary.\n const query = (db.select() as ReturnType<typeof buildSelectChain>)\n .from(auditLogs)\n .where(combined)\n .orderBy(...orderColumns)\n .limit(fetchLimit);\n\n const rows = (await query) as (typeof auditLogs.$inferSelect)[];\n\n const hasNextPage = rows.length > limit;\n const resultRows = hasNextPage ? rows.slice(0, -1) : rows;\n const entries = resultRows.map(rowToAuditLog);\n const lastRow = resultRows[resultRows.length - 1];\n\n if (hasNextPage && lastRow !== undefined) {\n return { entries, nextCursor: encodeCursor(lastRow.timestamp, lastRow.id) };\n }\n\n return { entries };\n },\n\n async getLogById(id: string): Promise<AuditLog | null> {\n const query = (db.select() as ReturnType<typeof buildSelectChain>)\n .from(auditLogs)\n .where(eq(auditLogs.id, id))\n .limit(1);\n\n const rows = (await query) as (typeof auditLogs.$inferSelect)[];\n const row = rows[0];\n if (row === undefined) {\n return null;\n }\n return rowToAuditLog(row);\n },\n\n /**\n * Delete audit log entries older than `before`.\n *\n * **Warning:** Large deletes may hold a row-level lock on the `audit_logs`\n * table for an extended period. Run during low-traffic windows when purging\n * millions of rows.\n */\n async purgeLogs(options: { before: Date; tableName?: string }): Promise<{ deletedCount: number }> {\n if (options.tableName !== undefined && options.tableName.trim().length === 0) {\n throw new Error(\"purgeLogs: tableName must be a non-empty string when provided\");\n }\n\n const conditions = [lt(auditLogs.timestamp, options.before)];\n if (options.tableName !== undefined) {\n conditions.push(eq(auditLogs.tableName, options.tableName));\n }\n\n const result = await (\n db.delete(auditLogs) as ReturnType<typeof buildDeleteChain>\n ).where(and(...conditions));\n\n const rowCount = (result as { rowCount?: number | null }).rowCount;\n return { deletedCount: rowCount ?? 0 };\n },\n\n async getStats(options?: { since?: Date }): Promise<AuditStats> {\n const sinceCondition =\n options?.since !== undefined\n ? gte(auditLogs.timestamp, options.since)\n : undefined;\n\n // Query 1: totalLogs + tablesAudited\n const summaryQuery = (\n db.select({\n totalLogs: sql`count(*)`,\n tablesAudited: sql`count(DISTINCT ${auditLogs.tableName})`,\n }) as ReturnType<typeof buildAggregateSelectChain>\n )\n .from(auditLogs)\n .where(sinceCondition);\n\n // Query 2: eventsPerDay\n const eventsPerDayQuery = (\n db.select({\n date: sql`date_trunc('day', ${auditLogs.timestamp})::date`,\n count: sql`count(*)`,\n }) as ReturnType<typeof buildAggregateSelectChain>\n )\n .from(auditLogs)\n .where(sinceCondition)\n .groupBy(sql`date_trunc('day', ${auditLogs.timestamp})`)\n .orderBy(sql`date_trunc('day', ${auditLogs.timestamp})`)\n .limit(365);\n\n // Query 3: topActors (filter NULL actors)\n const topActorsQuery = (\n db.select({\n actorId: auditLogs.actorId,\n count: sql`count(*)`,\n }) as ReturnType<typeof buildAggregateSelectChain>\n )\n .from(auditLogs)\n .where(and(sinceCondition, isNotNull(auditLogs.actorId)))\n .groupBy(auditLogs.actorId)\n .orderBy(desc(sql`count(*)`))\n .limit(10);\n\n // Query 4: topTables\n const topTablesQuery = (\n db.select({\n tableName: auditLogs.tableName,\n count: sql`count(*)`,\n }) as ReturnType<typeof buildAggregateSelectChain>\n )\n .from(auditLogs)\n .where(sinceCondition)\n .groupBy(auditLogs.tableName)\n .orderBy(desc(sql`count(*)`))\n .limit(10);\n\n // Query 5: operationBreakdown\n const operationQuery = (\n db.select({\n operation: auditLogs.operation,\n count: sql`count(*)`,\n }) as ReturnType<typeof buildAggregateSelectChain>\n )\n .from(auditLogs)\n .where(sinceCondition)\n .groupBy(auditLogs.operation);\n\n // Query 6: severityBreakdown (filter NULL severity)\n const severityQuery = (\n db.select({\n severity: auditLogs.severity,\n count: sql`count(*)`,\n }) as ReturnType<typeof buildAggregateSelectChain>\n )\n .from(auditLogs)\n .where(and(sinceCondition, isNotNull(auditLogs.severity)))\n .groupBy(auditLogs.severity);\n\n const results = await Promise.all([\n summaryQuery,\n eventsPerDayQuery,\n topActorsQuery,\n topTablesQuery,\n operationQuery,\n severityQuery,\n ]);\n\n const [\n summaryRows,\n eventsPerDayRows,\n topActorsRows,\n topTablesRows,\n operationRows,\n severityRows,\n ] = results as unknown as [\n Array<{ totalLogs: unknown; tablesAudited: unknown }>,\n Array<{ date: unknown; count: unknown }>,\n Array<{ actorId: unknown; count: unknown }>,\n Array<{ tableName: unknown; count: unknown }>,\n Array<{ operation: unknown; count: unknown }>,\n Array<{ severity: unknown; count: unknown }>,\n ];\n\n return assembleStats(\n summaryRows,\n eventsPerDayRows,\n topActorsRows,\n topTablesRows,\n operationRows,\n severityRows,\n );\n },\n };\n}\n\n/**\n * Assembles query results into an `AuditStats` object.\n */\nfunction assembleStats(\n summaryRows: Array<{ totalLogs: unknown; tablesAudited: unknown }>,\n eventsPerDayRows: Array<{ date: unknown; count: unknown }>,\n topActorsRows: Array<{ actorId: unknown; count: unknown }>,\n topTablesRows: Array<{ tableName: unknown; count: unknown }>,\n operationRows: Array<{ operation: unknown; count: unknown }>,\n severityRows: Array<{ severity: unknown; count: unknown }>,\n): AuditStats {\n const summary = summaryRows[0];\n const totalLogs = summary !== undefined ? toCount(summary.totalLogs) : 0;\n const tablesAudited = summary !== undefined ? toCount(summary.tablesAudited) : 0;\n\n const eventsPerDay = eventsPerDayRows.map((row) => ({\n date: row.date instanceof Date ? row.date.toISOString().split(\"T\")[0]! : String(row.date),\n count: toCount(row.count),\n }));\n\n const topActors = topActorsRows.map((row) => ({\n actorId: String(row.actorId),\n count: toCount(row.count),\n }));\n\n const topTables = topTablesRows.map((row) => ({\n tableName: String(row.tableName),\n count: toCount(row.count),\n }));\n\n const operationBreakdown: Record<string, number> = {};\n for (const row of operationRows) {\n operationBreakdown[String(row.operation)] = toCount(row.count);\n }\n\n const severityBreakdown: Record<string, number> = {};\n for (const row of severityRows) {\n severityBreakdown[String(row.severity)] = toCount(row.count);\n }\n\n return {\n totalLogs,\n tablesAudited,\n eventsPerDay,\n topActors,\n topTables,\n operationBreakdown,\n severityBreakdown,\n };\n}\n\n/**\n * Type helper for the Drizzle select chain — used only for casting.\n * Not exported; exists to avoid `any`.\n */\nfunction buildSelectChain() {\n // This function is never called — it exists only for its return type.\n return undefined as unknown as {\n from(table: unknown): {\n where(condition: unknown): {\n orderBy(...columns: unknown[]): {\n limit(n: number): Promise<unknown[]>;\n };\n limit(n: number): Promise<unknown[]>;\n };\n orderBy(...columns: unknown[]): {\n limit(n: number): Promise<unknown[]>;\n };\n limit(n: number): Promise<unknown[]>;\n };\n };\n}\n\n/** Awaitable node: can be awaited directly or chained further. */\ninterface AggregateTerminal extends Promise<unknown[]> {\n limit(n: number): Promise<unknown[]>;\n}\n\n/** After groupBy: can orderBy, limit, or await. */\ninterface AggregateGrouped extends Promise<unknown[]> {\n orderBy(...columns: unknown[]): AggregateTerminal;\n limit(n: number): Promise<unknown[]>;\n}\n\n/** After where: can groupBy, orderBy, limit, or await. */\ninterface AggregateFiltered extends Promise<unknown[]> {\n groupBy(...columns: unknown[]): AggregateGrouped;\n orderBy(...columns: unknown[]): AggregateTerminal;\n limit(n: number): Promise<unknown[]>;\n}\n\n/**\n * Type helper for aggregate select chains with groupBy support — used only for casting.\n */\nfunction buildAggregateSelectChain() {\n // This function is never called — it exists only for its return type.\n return undefined as unknown as {\n from(table: unknown): {\n where(condition: unknown): AggregateFiltered;\n groupBy(...columns: unknown[]): AggregateGrouped;\n };\n };\n}\n\n/**\n * Type helper for the Drizzle delete chain — used only for casting.\n */\nfunction buildDeleteChain() {\n // This function is never called — it exists only for its return type.\n return undefined as unknown as {\n where(condition: unknown): Promise<{ rowCount: number }>;\n };\n}\n","import {\n pgTable,\n uuid,\n timestamp,\n text,\n jsonb,\n boolean,\n index,\n} from \"drizzle-orm/pg-core\";\nimport type { InferSelectModel, InferInsertModel } from \"drizzle-orm\";\n\nexport const auditLogs = pgTable(\n \"audit_logs\",\n {\n id: uuid().primaryKey().defaultRandom(),\n timestamp: timestamp({ withTimezone: true }).notNull().defaultNow(),\n tableName: text(\"table_name\").notNull(),\n operation: text().notNull(),\n recordId: text(\"record_id\").notNull(),\n actorId: text(\"actor_id\"),\n beforeData: jsonb(\"before_data\"),\n afterData: jsonb(\"after_data\"),\n diff: jsonb(),\n label: text(),\n description: text(),\n severity: text(),\n compliance: jsonb(),\n notify: boolean(),\n reason: text(),\n metadata: jsonb(),\n redactedFields: jsonb(\"redacted_fields\"),\n },\n (table) => [\n index(\"audit_logs_table_name_timestamp_idx\").on(\n table.tableName,\n table.timestamp,\n ),\n index(\"audit_logs_actor_id_idx\").on(table.actorId),\n index(\"audit_logs_record_id_idx\").on(table.recordId),\n index(\"audit_logs_table_name_record_id_idx\").on(\n table.tableName,\n table.recordId,\n ),\n index(\"audit_logs_operation_idx\").on(table.operation),\n index(\"audit_logs_timestamp_idx\").on(table.timestamp),\n index(\"audit_logs_timestamp_id_idx\").on(table.timestamp, table.id),\n ],\n);\n\nexport type AuditLogRow = InferSelectModel<typeof auditLogs>;\nexport type NewAuditLogRow = InferInsertModel<typeof auditLogs>;\n","import type { AuditLog, AuditOperation, AuditSeverity } from \"@usebetterdev/audit-core\";\nimport type { AuditLogRow, NewAuditLogRow } from \"./schema.js\";\n\nconst VALID_OPERATIONS: ReadonlySet<string> = new Set([\n \"INSERT\",\n \"UPDATE\",\n \"DELETE\",\n]);\n\nconst VALID_SEVERITIES: ReadonlySet<string> = new Set([\n \"low\",\n \"medium\",\n \"high\",\n \"critical\",\n]);\n\nfunction isAuditOperation(value: string): value is AuditOperation {\n return VALID_OPERATIONS.has(value);\n}\n\nfunction isAuditSeverity(value: string): value is AuditSeverity {\n return VALID_SEVERITIES.has(value);\n}\n\n/**\n * Converts a camelCase `AuditLog` to a snake_case DB row for insertion.\n * Only includes optional fields when defined (satisfies `exactOptionalPropertyTypes`).\n */\nexport function auditLogToRow(log: AuditLog): NewAuditLogRow {\n const row: NewAuditLogRow = {\n id: log.id,\n timestamp: log.timestamp,\n tableName: log.tableName,\n operation: log.operation,\n recordId: log.recordId,\n };\n\n if (log.actorId !== undefined) {\n row.actorId = log.actorId;\n }\n if (log.beforeData !== undefined) {\n row.beforeData = log.beforeData;\n }\n if (log.afterData !== undefined) {\n row.afterData = log.afterData;\n }\n if (log.diff !== undefined) {\n row.diff = log.diff;\n }\n if (log.label !== undefined) {\n row.label = log.label;\n }\n if (log.description !== undefined) {\n row.description = log.description;\n }\n if (log.severity !== undefined) {\n row.severity = log.severity;\n }\n if (log.compliance !== undefined) {\n row.compliance = log.compliance;\n }\n if (log.notify !== undefined) {\n row.notify = log.notify;\n }\n if (log.reason !== undefined) {\n row.reason = log.reason;\n }\n if (log.metadata !== undefined) {\n row.metadata = log.metadata;\n }\n if (log.redactedFields !== undefined) {\n row.redactedFields = log.redactedFields;\n }\n\n return row;\n}\n\n/**\n * Converts a snake_case DB row to a camelCase `AuditLog`.\n * Only includes optional fields when non-null.\n */\nexport function rowToAuditLog(row: AuditLogRow): AuditLog {\n if (!isAuditOperation(row.operation)) {\n throw new Error(\n `Invalid audit operation: \"${row.operation}\". Expected one of: INSERT, UPDATE, DELETE`,\n );\n }\n\n const log: AuditLog = {\n id: row.id,\n timestamp: row.timestamp,\n tableName: row.tableName,\n operation: row.operation,\n recordId: row.recordId,\n };\n\n if (row.actorId !== null) {\n log.actorId = row.actorId;\n }\n if (row.beforeData !== null) {\n log.beforeData = row.beforeData as Record<string, unknown>;\n }\n if (row.afterData !== null) {\n log.afterData = row.afterData as Record<string, unknown>;\n }\n if (row.diff !== null) {\n log.diff = row.diff as { changedFields: string[] };\n }\n if (row.label !== null) {\n log.label = row.label;\n }\n if (row.description !== null) {\n log.description = row.description;\n }\n if (row.severity !== null && row.severity !== undefined) {\n if (!isAuditSeverity(row.severity)) {\n throw new Error(\n `Invalid audit severity: \"${row.severity}\". Expected one of: low, medium, high, critical`,\n );\n }\n log.severity = row.severity;\n }\n if (row.compliance !== null) {\n log.compliance = row.compliance as string[];\n }\n if (row.notify !== null) {\n log.notify = row.notify;\n }\n if (row.reason !== null) {\n log.reason = row.reason;\n }\n if (row.metadata !== null) {\n log.metadata = row.metadata as Record<string, unknown>;\n }\n if (row.redactedFields !== null) {\n log.redactedFields = row.redactedFields as string[];\n }\n\n return log;\n}\n","import type { AuditQueryFilters, TimeFilter } from \"@usebetterdev/audit-core\";\nimport { parseDuration } from \"@usebetterdev/audit-core\";\nimport {\n and,\n or,\n eq,\n gt,\n lt,\n gte,\n lte,\n inArray,\n ilike,\n asc,\n desc,\n sql,\n} from \"drizzle-orm\";\nimport type { SQL } from \"drizzle-orm\";\nimport { auditLogs } from \"./schema.js\";\n\n/**\n * Escapes LIKE/ILIKE wildcard characters so they match literally.\n * Backslash is the default escape character in PostgreSQL LIKE patterns.\n */\nfunction escapeLikePattern(input: string): string {\n return input.replace(/[%_\\\\]/g, \"\\\\$&\");\n}\n\n/**\n * Resolves a `TimeFilter` to an absolute `Date`.\n * Absolute dates are returned as-is; duration strings are parsed relative to now.\n */\nfunction resolveTimeFilter(filter: TimeFilter): Date {\n if (\"date\" in filter && filter.date !== undefined) {\n return filter.date;\n }\n if (\"duration\" in filter && filter.duration !== undefined) {\n return parseDuration(filter.duration);\n }\n throw new Error(\"TimeFilter must have either date or duration\");\n}\n\n/**\n * Translates `AuditQueryFilters` into a Drizzle `SQL` condition (combined with AND).\n * Returns `undefined` when no filters are active — callers should omit `.where()`.\n */\nexport function buildWhereConditions(\n filters: AuditQueryFilters,\n): SQL | undefined {\n const conditions: SQL[] = [];\n\n // resource.tableName + optional recordId\n if (filters.resource !== undefined) {\n conditions.push(eq(auditLogs.tableName, filters.resource.tableName));\n if (filters.resource.recordId !== undefined) {\n conditions.push(eq(auditLogs.recordId, filters.resource.recordId));\n }\n }\n\n // actorIds — single eq or inArray\n if (filters.actorIds !== undefined && filters.actorIds.length > 0) {\n if (filters.actorIds.length === 1) {\n conditions.push(eq(auditLogs.actorId, filters.actorIds[0]!));\n } else {\n conditions.push(inArray(auditLogs.actorId, filters.actorIds));\n }\n }\n\n // severities — single eq or inArray\n if (filters.severities !== undefined && filters.severities.length > 0) {\n if (filters.severities.length === 1) {\n conditions.push(eq(auditLogs.severity, filters.severities[0]!));\n } else {\n conditions.push(inArray(auditLogs.severity, filters.severities));\n }\n }\n\n // operations — single eq or inArray\n if (filters.operations !== undefined && filters.operations.length > 0) {\n if (filters.operations.length === 1) {\n conditions.push(eq(auditLogs.operation, filters.operations[0]!));\n } else {\n conditions.push(inArray(auditLogs.operation, filters.operations));\n }\n }\n\n // since — gte(timestamp, resolved date)\n if (filters.since !== undefined) {\n conditions.push(gte(auditLogs.timestamp, resolveTimeFilter(filters.since)));\n }\n\n // until — lte(timestamp, resolved date)\n if (filters.until !== undefined) {\n conditions.push(lte(auditLogs.timestamp, resolveTimeFilter(filters.until)));\n }\n\n // searchText — ilike on label OR description\n if (filters.searchText !== undefined && filters.searchText.length > 0) {\n const escaped = escapeLikePattern(filters.searchText);\n const pattern = `%${escaped}%`;\n const searchCondition = or(\n ilike(auditLogs.label, pattern),\n ilike(auditLogs.description, pattern),\n );\n if (searchCondition !== undefined) {\n conditions.push(searchCondition);\n }\n }\n\n // compliance — jsonb @> (contains all tags)\n if (filters.compliance !== undefined && filters.compliance.length > 0) {\n conditions.push(\n sql`${auditLogs.compliance} @> ${JSON.stringify(filters.compliance)}::jsonb`,\n );\n }\n\n if (conditions.length === 0) {\n return undefined;\n }\n\n return and(...conditions);\n}\n\n/**\n * Builds a cursor-based pagination condition.\n *\n * Uses `(timestamp, id)` as the cursor key pair for stable ordering.\n * - DESC: `(ts < cursor.ts) OR (ts = cursor.ts AND id < cursor.id)`\n * - ASC: `(ts > cursor.ts) OR (ts = cursor.ts AND id > cursor.id)`\n */\nexport function buildCursorCondition(\n cursor: string,\n sortOrder: \"asc\" | \"desc\",\n): SQL | undefined {\n const decoded = decodeCursor(cursor);\n const tsCompare = sortOrder === \"asc\" ? gt : lt;\n const idCompare = sortOrder === \"asc\" ? gt : lt;\n\n return or(\n tsCompare(auditLogs.timestamp, decoded.timestamp),\n and(\n eq(auditLogs.timestamp, decoded.timestamp),\n idCompare(auditLogs.id, decoded.id),\n ),\n );\n}\n\n/**\n * Returns the `orderBy` columns for the given sort direction.\n * Default is descending (newest first).\n */\nexport function buildOrderBy(sortOrder: \"asc\" | \"desc\") {\n if (sortOrder === \"asc\") {\n return [asc(auditLogs.timestamp), asc(auditLogs.id)];\n }\n return [desc(auditLogs.timestamp), desc(auditLogs.id)];\n}\n\n/**\n * Encodes a cursor from a timestamp and id.\n * Format: base64(JSON.stringify({ t: iso_string, i: id }))\n */\nexport function encodeCursor(timestamp: Date, id: string): string {\n const payload = JSON.stringify({ t: timestamp.toISOString(), i: id });\n return btoa(payload);\n}\n\nconst UUID_PATTERN =\n /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;\n\n/**\n * Decodes an opaque cursor string back to timestamp + id.\n * Throws on invalid input.\n */\nexport function decodeCursor(cursor: string): { timestamp: Date; id: string } {\n let parsed: unknown;\n try {\n parsed = JSON.parse(atob(cursor));\n } catch {\n throw new Error(\"Invalid cursor: failed to decode\");\n }\n\n if (\n typeof parsed !== \"object\" ||\n parsed === null ||\n !(\"t\" in parsed) ||\n !(\"i\" in parsed)\n ) {\n throw new Error(\"Invalid cursor: missing required fields\");\n }\n\n const { t, i } = parsed as { t: unknown; i: unknown };\n\n if (typeof t !== \"string\" || typeof i !== \"string\") {\n throw new Error(\"Invalid cursor: fields must be strings\");\n }\n\n const timestamp = new Date(t);\n if (isNaN(timestamp.getTime())) {\n throw new Error(\"Invalid cursor: invalid timestamp\");\n }\n\n if (!UUID_PATTERN.test(i)) {\n throw new Error(\"Invalid cursor: id must be a valid UUID\");\n }\n\n return { timestamp, id: i };\n}\n","import type { CaptureLogInput, AuditOperation } from \"@usebetterdev/audit-core\";\nimport { getTableName } from \"drizzle-orm\";\nimport type { PgTable } from \"drizzle-orm/pg-core\";\nimport { OPERATION_MAP, type DrizzleMutationMethod } from \"./operation-map.js\";\n\nexport type MissingRecordIdBehavior = \"warn\" | \"skip\" | \"throw\";\n\nexport interface AuditProxyOptions {\n /**\n * Fallback column name for extracting `recordId` when the primary key\n * cannot be auto-detected from the Drizzle table schema. Defaults to `\"id\"`.\n *\n * In most cases this is not needed — the proxy reads `.primaryKey()` metadata\n * from Drizzle column objects at runtime.\n */\n primaryKey?: string;\n /**\n * Called when audit capture fails. Falls back to `console.error` if not set.\n */\n onError?: (error: unknown) => void;\n /**\n * What to do when the `recordId` cannot be determined.\n *\n * - `\"warn\"` (default) — log via `onError`, proceed with `recordId: \"unknown\"`\n * - `\"skip\"` — silently drop the audit entry\n * - `\"throw\"` — throw an error (caught by the proxy's outer error handler)\n */\n onMissingRecordId?: MissingRecordIdBehavior;\n /**\n * Table names for which the pre-mutation SELECT should be skipped.\n * Use this for high-throughput tables where the extra query is too expensive.\n */\n skipBeforeState?: string[];\n /**\n * Safety limit: if the pre-mutation SELECT returns more rows than this,\n * skip the before-state capture and warn. Defaults to 1000.\n */\n maxBeforeStateRows?: number;\n}\n\ninterface BuilderContext {\n tableName: string;\n operation: AuditOperation;\n captureLog: (input: CaptureLogInput) => Promise<void>;\n primaryKey: string;\n handleError: (error: unknown, table: string, op: string) => void;\n onMissingRecordId: MissingRecordIdBehavior;\n auditedSet: WeakSet<object>;\n dbTarget: Record<string, unknown>;\n table: PgTable;\n skipBeforeState: Set<string>;\n maxBeforeStateRows: number;\n}\n\n/**\n * Wraps a Drizzle database (or transaction) with a transparent proxy that\n * intercepts `db.insert()`, `db.update()`, `db.delete()` and calls\n * `captureLog()` after each successful mutation.\n *\n * The proxy also intercepts `db.transaction()` so that the `tx` handle\n * passed to the callback is itself proxied — this is what makes it work\n * with `better-tenant`, where all user code runs inside a transaction.\n *\n * Filtering by audited tables is NOT done here — `captureLog` (from\n * `betterAudit`) already skips tables not in `auditTables`, so there is\n * a single source of truth for which tables are audited.\n */\n// eslint-disable-next-line @typescript-eslint/no-empty-object-type\nexport function withAuditProxy<TDb extends {}>(\n db: TDb,\n captureLog: (input: CaptureLogInput) => Promise<void>,\n options?: AuditProxyOptions,\n): TDb {\n const primaryKey = options?.primaryKey ?? \"id\";\n const missingRecordIdPolicy = options?.onMissingRecordId ?? \"warn\";\n const skipBeforeState = new Set(options?.skipBeforeState ?? []);\n const maxBeforeStateRows = options?.maxBeforeStateRows ?? 1000;\n\n const handleError = (error: unknown, table: string, op: string): void => {\n try {\n if (options?.onError !== undefined) {\n options.onError(error);\n } else {\n const msg = error instanceof Error ? error.message : String(error);\n console.error(\n `audit-drizzle: capture failed for ${op} on ${table} — ${msg}`,\n );\n }\n } catch {\n // onError callback itself threw — swallow to prevent audit from breaking mutations.\n }\n };\n\n return new Proxy(db, {\n get(target, prop, receiver) {\n // Intercept insert / update / delete\n if (\n typeof prop === \"string\" &&\n (prop === \"insert\" || prop === \"update\" || prop === \"delete\")\n ) {\n const method = prop as DrizzleMutationMethod;\n const originalMethod = Reflect.get(target, prop, receiver) as (\n table: PgTable,\n ) => Record<string, unknown>;\n\n return (table: PgTable) => {\n const tableName = getTableName(table);\n const detectedPk = getPrimaryKeyColumnName(table);\n const effectivePk = detectedPk ?? primaryKey;\n const originalBuilder = originalMethod.call(target, table);\n\n const ctx: BuilderContext = {\n tableName,\n operation: OPERATION_MAP[method],\n captureLog,\n primaryKey: effectivePk,\n handleError,\n onMissingRecordId: missingRecordIdPolicy,\n auditedSet: new WeakSet<object>(),\n dbTarget: target as Record<string, unknown>,\n table,\n skipBeforeState,\n maxBeforeStateRows,\n };\n\n return wrapBuilder(originalBuilder, ctx);\n };\n }\n\n // Intercept transaction() so the tx handle is also proxied\n if (prop === \"transaction\") {\n const originalTransaction = Reflect.get(\n target,\n prop,\n receiver,\n ) as Function;\n\n return (...args: unknown[]) => {\n const callback = args[0] as (tx: unknown) => Promise<unknown>;\n const rest = args.slice(1);\n const wrappedCallback = (tx: unknown) => {\n const proxiedTx = withAuditProxy(\n tx as TDb,\n captureLog,\n options,\n );\n return callback(proxiedTx);\n };\n return originalTransaction.call(target, wrappedCallback, ...rest);\n };\n }\n\n return Reflect.get(target, prop, receiver);\n },\n });\n}\n\ninterface TrackedState {\n trackedValues?: Record<string, unknown>[];\n trackedSet?: Record<string, unknown>;\n trackedWhere?: unknown;\n hasReturning?: boolean;\n}\n\nfunction wrapBuilder(\n builder: Record<string, unknown>,\n ctx: BuilderContext,\n state: TrackedState = {},\n): Record<string, unknown> {\n return new Proxy(builder, {\n get(target, prop, receiver) {\n // Track .values() data for INSERT\n if (prop === \"values\") {\n return (...args: unknown[]) => {\n const data = args[0] as\n | Record<string, unknown>\n | Record<string, unknown>[];\n const newTrackedValues = Array.isArray(data) ? data : [data];\n const result = (\n target.values as (...a: unknown[]) => Record<string, unknown>\n )(...args);\n return wrapBuilder(result, ctx, {\n ...state,\n trackedValues: newTrackedValues,\n });\n };\n }\n\n // Track .set() data for UPDATE\n if (prop === \"set\") {\n return (...args: unknown[]) => {\n const newTrackedSet = args[0] as Record<string, unknown>;\n const result = (\n target.set as (...a: unknown[]) => Record<string, unknown>\n )(...args);\n return wrapBuilder(result, ctx, {\n ...state,\n trackedSet: newTrackedSet,\n });\n };\n }\n\n // Track .where() condition for UPDATE/DELETE pre-SELECT\n if (prop === \"where\") {\n return (...args: unknown[]) => {\n const condition = args[0];\n const result = (\n target.where as (...a: unknown[]) => Record<string, unknown>\n )(...args);\n return wrapBuilder(result, ctx, {\n ...state,\n trackedWhere: condition,\n });\n };\n }\n\n // Track .returning() so we know the result contains full rows\n if (prop === \"returning\") {\n return (...args: unknown[]) => {\n const result = (\n target.returning as (...a: unknown[]) => Record<string, unknown>\n )(...args);\n return wrapBuilder(result, ctx, {\n ...state,\n hasReturning: true,\n });\n };\n }\n\n // Intercept .then() — the terminal await point\n if (prop === \"then\") {\n return (\n onFulfilled?: (value: unknown) => unknown,\n onRejected?: (reason: unknown) => unknown,\n ) => {\n const thenFn = Reflect.get(target, \"then\", receiver) as (\n onFulfilled?: (value: unknown) => unknown,\n onRejected?: (reason: unknown) => unknown,\n ) => unknown;\n\n const needsBeforeState =\n (ctx.operation === \"UPDATE\" || ctx.operation === \"DELETE\") &&\n state.trackedWhere !== undefined &&\n !ctx.skipBeforeState.has(ctx.tableName) &&\n // For DELETE with .returning(), returned rows ARE the before-state\n !(ctx.operation === \"DELETE\" && state.hasReturning === true);\n\n if (needsBeforeState) {\n return executeWithBeforeState(\n target,\n thenFn,\n ctx,\n state,\n onFulfilled,\n onRejected,\n );\n }\n\n return thenFn.call(\n target,\n async (result: unknown) => {\n if (ctx.auditedSet.has(target)) {\n return onFulfilled?.(result);\n }\n ctx.auditedSet.add(target);\n\n try {\n await fireCaptureLog(result, ctx, state);\n } catch (error) {\n ctx.handleError(error, ctx.tableName, ctx.operation);\n }\n return onFulfilled?.(result);\n },\n onRejected,\n );\n };\n }\n\n // All other methods: forward and re-wrap\n const value = Reflect.get(target, prop, receiver);\n if (typeof value === \"function\") {\n return (...args: unknown[]) => {\n const result = (value as (...a: unknown[]) => unknown).apply(\n target,\n args,\n );\n if (result !== null && typeof result === \"object\") {\n return wrapBuilder(\n result as Record<string, unknown>,\n ctx,\n state,\n );\n }\n return result;\n };\n }\n\n return value;\n },\n });\n}\n\n/**\n * Executes a pre-mutation SELECT, then the original mutation, then fires\n * captureLog with matched before/after pairs.\n */\nfunction executeWithBeforeState(\n target: Record<string, unknown>,\n thenFn: (\n onFulfilled?: (value: unknown) => unknown,\n onRejected?: (reason: unknown) => unknown,\n ) => unknown,\n ctx: BuilderContext,\n state: TrackedState,\n onFulfilled?: (value: unknown) => unknown,\n onRejected?: (reason: unknown) => unknown,\n): unknown {\n // Issue the pre-mutation SELECT, then chain the original mutation\n const beforePromise = fetchBeforeState(ctx, state);\n\n return beforePromise.then(\n (beforeRows) => {\n // Now execute the original mutation\n return thenFn.call(\n target,\n async (result: unknown) => {\n if (ctx.auditedSet.has(target)) {\n return onFulfilled?.(result);\n }\n ctx.auditedSet.add(target);\n\n try {\n if (beforeRows !== undefined) {\n await fireCaptureLogWithBeforeState(\n result,\n beforeRows,\n ctx,\n state,\n );\n } else {\n // Fallback: before-state unavailable, use original behavior\n await fireCaptureLog(result, ctx, state);\n }\n } catch (error) {\n ctx.handleError(error, ctx.tableName, ctx.operation);\n }\n return onFulfilled?.(result);\n },\n onRejected,\n );\n },\n (error) => {\n // Pre-SELECT failed — log error, fall back to original behavior.\n ctx.handleError(error, ctx.tableName, ctx.operation);\n return thenFn.call(\n target,\n async (result: unknown) => {\n if (ctx.auditedSet.has(target)) {\n return onFulfilled?.(result);\n }\n ctx.auditedSet.add(target);\n\n try {\n await fireCaptureLog(result, ctx, state);\n } catch (captureError) {\n ctx.handleError(captureError, ctx.tableName, ctx.operation);\n }\n return onFulfilled?.(result);\n },\n onRejected,\n );\n },\n );\n}\n\n/**\n * Issues a SELECT to fetch the rows that will be affected by the mutation.\n * Returns `undefined` if the fetch should be skipped (too many rows).\n */\nasync function fetchBeforeState(\n ctx: BuilderContext,\n state: TrackedState,\n): Promise<Record<string, unknown>[] | undefined> {\n const selectFn = ctx.dbTarget.select as\n | ((...args: unknown[]) => Record<string, unknown>)\n | undefined;\n if (selectFn === undefined) {\n return undefined;\n }\n\n const selectBuilder = selectFn.call(ctx.dbTarget);\n const fromFn = selectBuilder.from as\n | ((table: PgTable) => Record<string, unknown>)\n | undefined;\n if (fromFn === undefined) {\n return undefined;\n }\n\n const fromBuilder = fromFn.call(selectBuilder, ctx.table);\n const whereFn = fromBuilder.where as\n | ((...args: unknown[]) => Record<string, unknown>)\n | undefined;\n if (whereFn === undefined) {\n return undefined;\n }\n\n const whereBuilder = whereFn.call(fromBuilder, state.trackedWhere);\n\n // Apply LIMIT to avoid fetching unbounded rows into memory.\n // Fetch limit + 1 so we can detect when the limit is exceeded.\n const limitFn = whereBuilder.limit as\n | ((n: number) => Record<string, unknown>)\n | undefined;\n const fetchLimit = ctx.maxBeforeStateRows + 1;\n const queryBuilder =\n limitFn !== undefined\n ? limitFn.call(whereBuilder, fetchLimit)\n : whereBuilder;\n\n const rows = (await queryBuilder) as unknown as Record<string, unknown>[];\n\n if (rows.length > ctx.maxBeforeStateRows) {\n ctx.handleError(\n new Error(\n `audit-drizzle: before-state SELECT returned more than ${ctx.maxBeforeStateRows} rows, skipping before-state capture`,\n ),\n ctx.tableName,\n ctx.operation,\n );\n return undefined;\n }\n\n return rows;\n}\n\nfunction getPrimaryKeyColumnName(table: PgTable): string | undefined {\n for (const [key, value] of Object.entries(table)) {\n if (\n value !== null &&\n typeof value === \"object\" &&\n \"primary\" in value &&\n value.primary === true\n ) {\n return key;\n }\n }\n return undefined;\n}\n\nfunction extractRecordId(\n row: Record<string, unknown>,\n primaryKey: string,\n): string {\n const value = row[primaryKey];\n if (value !== undefined && value !== null) {\n return String(value);\n }\n return \"\";\n}\n\n/**\n * Applies the missing-record-id policy.\n * Returns `true` if the audit entry should still be written (with \"unknown\"),\n * or `false` if it should be skipped.\n */\nfunction applyMissingRecordIdPolicy(\n ctx: BuilderContext,\n detail: string,\n): boolean {\n const policy = ctx.onMissingRecordId;\n if (policy === \"skip\") {\n return false;\n }\n if (policy === \"throw\") {\n throw new Error(\n `audit-drizzle: missing recordId for ${ctx.operation} on ${ctx.tableName} — ${detail}`,\n );\n }\n // \"warn\" — report via handleError, then proceed\n ctx.handleError(\n new Error(\n `audit-drizzle: missing recordId for ${ctx.operation} on ${ctx.tableName} — ${detail}`,\n ),\n ctx.tableName,\n ctx.operation,\n );\n return true;\n}\n\n/**\n * Fires captureLog with matched before/after pairs using pre-mutation state.\n */\nasync function fireCaptureLogWithBeforeState(\n result: unknown,\n beforeRows: Record<string, unknown>[],\n ctx: BuilderContext,\n state: TrackedState,\n): Promise<void> {\n const returnedRows = Array.isArray(result) ? result : [];\n\n if (ctx.operation === \"UPDATE\") {\n if (beforeRows.length === 0) {\n // Race condition: rows disappeared between SELECT and UPDATE\n ctx.handleError(\n new Error(\n \"audit-drizzle: before-state SELECT returned 0 rows but UPDATE succeeded — possible race condition\",\n ),\n ctx.tableName,\n ctx.operation,\n );\n // Fall back to original UPDATE behavior\n await fireCaptureLog(result, ctx, state);\n return;\n }\n\n // Build before map keyed by primary key\n const beforeMap = new Map<string, Record<string, unknown>>();\n for (const row of beforeRows) {\n const pk = extractRecordId(row, ctx.primaryKey);\n if (pk !== \"\") {\n beforeMap.set(pk, row);\n }\n }\n\n if (beforeMap.size === 0) {\n // All before-rows lacked the primary key column — likely misconfiguration\n ctx.handleError(\n new Error(\n `audit-drizzle: before-state rows exist but none have primary key \"${ctx.primaryKey}\" — check primaryKey option`,\n ),\n ctx.tableName,\n ctx.operation,\n );\n await fireCaptureLog(result, ctx, state);\n return;\n }\n\n if (state.hasReturning === true && returnedRows.length > 0) {\n // After-state from .returning() result\n for (const returnedRow of returnedRows) {\n const row = returnedRow as Record<string, unknown>;\n const recordId = extractRecordId(row, ctx.primaryKey);\n if (recordId === \"\") {\n continue;\n }\n const beforeRow = beforeMap.get(recordId);\n await ctx.captureLog({\n tableName: ctx.tableName,\n operation: ctx.operation,\n recordId,\n ...(beforeRow !== undefined && { before: beforeRow }),\n after: row,\n });\n }\n } else {\n // After-state: merge .set() data onto each before-row\n for (const [pk, beforeRow] of beforeMap) {\n const afterRow =\n state.trackedSet !== undefined\n ? { ...beforeRow, ...state.trackedSet }\n : beforeRow;\n await ctx.captureLog({\n tableName: ctx.tableName,\n operation: ctx.operation,\n recordId: pk,\n before: beforeRow,\n after: afterRow,\n });\n }\n }\n } else if (ctx.operation === \"DELETE\") {\n if (beforeRows.length === 0) {\n // Race condition: rows disappeared before DELETE\n ctx.handleError(\n new Error(\n \"audit-drizzle: before-state SELECT returned 0 rows but DELETE succeeded — possible race condition\",\n ),\n ctx.tableName,\n ctx.operation,\n );\n // Fall back to original behavior\n await fireCaptureLog(result, ctx, state);\n return;\n }\n\n for (const beforeRow of beforeRows) {\n const recordId = extractRecordId(beforeRow, ctx.primaryKey);\n if (recordId === \"\") {\n const shouldProceed = applyMissingRecordIdPolicy(\n ctx,\n \"before-state row missing primary key\",\n );\n if (!shouldProceed) {\n continue;\n }\n await ctx.captureLog({\n tableName: ctx.tableName,\n operation: ctx.operation,\n recordId: \"unknown\",\n before: beforeRow,\n });\n continue;\n }\n await ctx.captureLog({\n tableName: ctx.tableName,\n operation: ctx.operation,\n recordId,\n before: beforeRow,\n });\n }\n }\n}\n\nasync function fireCaptureLog(\n result: unknown,\n ctx: BuilderContext,\n state: TrackedState,\n): Promise<void> {\n const { trackedValues, trackedSet } = state;\n const returnedRows = Array.isArray(result) ? result : [];\n\n if (ctx.operation === \"INSERT\") {\n const values = trackedValues ?? [];\n for (let i = 0; i < values.length; i++) {\n const row = values[i];\n if (row === undefined) {\n continue;\n }\n const returnedRow =\n returnedRows.length > i\n ? (returnedRows[i] as Record<string, unknown>)\n : undefined;\n const recordId =\n returnedRow !== undefined\n ? extractRecordId(returnedRow, ctx.primaryKey)\n : extractRecordId(row, ctx.primaryKey);\n\n if (recordId === \"\") {\n const shouldProceed = applyMissingRecordIdPolicy(\n ctx,\n \"use .returning() or include the primary key in .values()\",\n );\n if (!shouldProceed) {\n continue;\n }\n\n await ctx.captureLog({\n tableName: ctx.tableName,\n operation: ctx.operation,\n recordId: \"unknown\",\n after: row,\n });\n continue;\n }\n\n await ctx.captureLog({\n tableName: ctx.tableName,\n operation: ctx.operation,\n recordId,\n after: row,\n });\n }\n } else if (ctx.operation === \"UPDATE\") {\n if (returnedRows.length > 0) {\n for (const returnedRow of returnedRows) {\n const row = returnedRow as Record<string, unknown>;\n const recordId = extractRecordId(row, ctx.primaryKey);\n if (recordId === \"\") {\n continue;\n }\n await ctx.captureLog({\n tableName: ctx.tableName,\n operation: ctx.operation,\n recordId,\n ...(trackedSet !== undefined && { after: trackedSet }),\n });\n }\n } else if (trackedSet !== undefined) {\n const recordId = extractRecordId(trackedSet, ctx.primaryKey);\n if (recordId === \"\") {\n const shouldProceed = applyMissingRecordIdPolicy(\n ctx,\n \"use .returning() to get the record id\",\n );\n if (!shouldProceed) {\n return;\n }\n }\n await ctx.captureLog({\n tableName: ctx.tableName,\n operation: ctx.operation,\n recordId: recordId || \"unknown\",\n after: trackedSet,\n });\n }\n } else if (ctx.operation === \"DELETE\") {\n if (returnedRows.length > 0) {\n for (const returnedRow of returnedRows) {\n const row = returnedRow as Record<string, unknown>;\n const recordId = extractRecordId(row, ctx.primaryKey);\n if (recordId === \"\") {\n continue;\n }\n await ctx.captureLog({\n tableName: ctx.tableName,\n operation: ctx.operation,\n recordId,\n before: row,\n });\n }\n } else {\n const shouldProceed = applyMissingRecordIdPolicy(\n ctx,\n \"use .returning() to get the record id\",\n );\n if (!shouldProceed) {\n return;\n }\n await ctx.captureLog({\n tableName: ctx.tableName,\n operation: ctx.operation,\n recordId: \"unknown\",\n });\n }\n }\n}\n","import type { AuditOperation } from \"@usebetterdev/audit-core\";\n\nexport const OPERATION_MAP = {\n insert: \"INSERT\",\n update: \"UPDATE\",\n delete: \"DELETE\",\n} as const satisfies Record<string, AuditOperation>;\n\nexport type DrizzleMutationMethod = keyof typeof OPERATION_MAP;\n"],"mappings":"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","names":["and","eq","gte","lt","sql","desc","timestamp","and","eq","lt","gte","sql","desc"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@usebetterdev/audit-drizzle",
|
|
3
|
-
"version": "0.4.0-beta.
|
|
3
|
+
"version": "0.4.0-beta.3",
|
|
4
4
|
"repository": "github:usebetter-dev/usebetter",
|
|
5
5
|
"bugs": "https://github.com/usebetter-dev/usebetter/issues",
|
|
6
6
|
"homepage": "https://github.com/usebetter-dev/usebetter#readme",
|
|
@@ -23,16 +23,9 @@
|
|
|
23
23
|
"dist",
|
|
24
24
|
"README.md"
|
|
25
25
|
],
|
|
26
|
-
"scripts": {
|
|
27
|
-
"build": "tsup",
|
|
28
|
-
"lint": "oxlint",
|
|
29
|
-
"test": "vitest run",
|
|
30
|
-
"test:integration": "vitest run -c vitest.integration.config.ts",
|
|
31
|
-
"typecheck": "tsc --noEmit"
|
|
32
|
-
},
|
|
33
26
|
"dependencies": {
|
|
34
|
-
"
|
|
35
|
-
"
|
|
27
|
+
"drizzle-orm": "^0.36.0",
|
|
28
|
+
"@usebetterdev/audit-core": "0.4.0-beta.3"
|
|
36
29
|
},
|
|
37
30
|
"peerDependencies": {
|
|
38
31
|
"pg": ">=8.0.0",
|
|
@@ -47,16 +40,23 @@
|
|
|
47
40
|
}
|
|
48
41
|
},
|
|
49
42
|
"devDependencies": {
|
|
50
|
-
"@usebetterdev/test-utils": "workspace:*",
|
|
51
43
|
"@testcontainers/postgresql": "^11.11.0",
|
|
52
44
|
"@types/node": "^22.10.0",
|
|
53
45
|
"@types/pg": "^8.11.0",
|
|
54
46
|
"pg": "^8.13.0",
|
|
55
47
|
"tsup": "^8.3.5",
|
|
56
48
|
"typescript": "~5.7.2",
|
|
57
|
-
"vitest": "^2.1.6"
|
|
49
|
+
"vitest": "^2.1.6",
|
|
50
|
+
"@usebetterdev/test-utils": "0.4.0-beta.3"
|
|
58
51
|
},
|
|
59
52
|
"engines": {
|
|
60
53
|
"node": ">=22"
|
|
54
|
+
},
|
|
55
|
+
"scripts": {
|
|
56
|
+
"build": "tsup",
|
|
57
|
+
"lint": "oxlint",
|
|
58
|
+
"test": "vitest run",
|
|
59
|
+
"test:integration": "vitest run -c vitest.integration.config.ts",
|
|
60
|
+
"typecheck": "tsc --noEmit"
|
|
61
61
|
}
|
|
62
|
-
}
|
|
62
|
+
}
|