@usebetterdev/audit-prisma 0.6.1 → 0.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -28,17 +28,10 @@ __export(index_exports, {
28
28
  module.exports = __toCommonJS(index_exports);
29
29
 
30
30
  // src/query.ts
31
- var import_audit_core = require("@usebetterdev/audit-core");
31
+ var import_audit_core2 = require("@usebetterdev/audit-core");
32
32
 
33
33
  // src/column-map.ts
34
- var VALID_OPERATIONS = /* @__PURE__ */ new Set(["INSERT", "UPDATE", "DELETE"]);
35
- var VALID_SEVERITIES = /* @__PURE__ */ new Set(["low", "medium", "high", "critical"]);
36
- function isAuditOperation(value) {
37
- return VALID_OPERATIONS.has(value);
38
- }
39
- function isAuditSeverity(value) {
40
- return VALID_SEVERITIES.has(value);
41
- }
34
+ var import_audit_core = require("@usebetterdev/audit-core");
42
35
  function rowToAuditLog(row) {
43
36
  const { id, timestamp, table_name, operation, record_id } = row;
44
37
  if (typeof id !== "string") {
@@ -56,7 +49,7 @@ function rowToAuditLog(row) {
56
49
  if (typeof record_id !== "string") {
57
50
  throw new Error("rowToAuditLog: record_id must be a string");
58
51
  }
59
- if (!isAuditOperation(operation)) {
52
+ if (!(0, import_audit_core.isAuditOperation)(operation)) {
60
53
  throw new Error(
61
54
  `Invalid audit operation: "${operation}". Expected one of: INSERT, UPDATE, DELETE`
62
55
  );
@@ -102,7 +95,7 @@ function rowToAuditLog(row) {
102
95
  }
103
96
  if (severity !== null && severity !== void 0) {
104
97
  const s = String(severity);
105
- if (!isAuditSeverity(s)) {
98
+ if (!(0, import_audit_core.isAuditSeverity)(s)) {
106
99
  throw new Error(
107
100
  `Invalid audit severity: "${s}". Expected one of: low, medium, high, critical`
108
101
  );
@@ -131,140 +124,72 @@ function rowToAuditLog(row) {
131
124
  var DEFAULT_LIMIT = 50;
132
125
  var MAX_LIMIT = 250;
133
126
  var SELECT_COLUMNS = "id, timestamp, table_name, operation, record_id, actor_id, before_data, after_data, diff, label, description, severity, compliance, notify, reason, metadata, redacted_fields";
134
- var UUID_PATTERN = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
135
- function encodeCursor(timestamp, id) {
136
- const payload = JSON.stringify({ t: timestamp.toISOString(), i: id });
137
- return btoa(payload);
138
- }
139
- function decodeCursor(cursor) {
140
- let parsed;
141
- try {
142
- parsed = JSON.parse(atob(cursor));
143
- } catch {
144
- throw new Error("Invalid cursor: failed to decode");
145
- }
146
- if (typeof parsed !== "object" || parsed === null || !("t" in parsed) || !("i" in parsed)) {
147
- throw new Error("Invalid cursor: missing required fields");
148
- }
149
- const { t, i } = parsed;
150
- if (typeof t !== "string" || typeof i !== "string") {
151
- throw new Error("Invalid cursor: fields must be strings");
152
- }
153
- const timestamp = new Date(t);
154
- if (isNaN(timestamp.getTime())) {
155
- throw new Error("Invalid cursor: invalid timestamp");
156
- }
157
- if (!UUID_PATTERN.test(i)) {
158
- throw new Error("Invalid cursor: id must be a valid UUID");
159
- }
160
- return { timestamp, id: i };
161
- }
162
- function escapeLikePattern(input) {
163
- return input.replace(/[%_\\]/g, "\\$&");
164
- }
165
- function resolveTimeFilter(filter) {
166
- if ("date" in filter && filter.date !== void 0) {
167
- return filter.date;
168
- }
169
- if ("duration" in filter && filter.duration !== void 0) {
170
- return (0, import_audit_core.parseDuration)(filter.duration);
171
- }
172
- throw new Error("TimeFilter must have either date or duration");
173
- }
174
- function toCount(value) {
175
- if (typeof value === "number") {
176
- return value;
177
- }
178
- if (typeof value === "string") {
179
- return Number(value);
180
- }
181
- if (typeof value === "bigint") {
182
- return Number(value);
183
- }
184
- return 0;
185
- }
186
127
  function addParam(state, value) {
187
128
  state.params.push(value);
188
129
  return `$${state.params.length}`;
189
130
  }
190
- function buildWhereClause(filters, cursor, sortOrder) {
191
- const state = { fragments: [], params: [] };
192
- if (filters.resource !== void 0) {
193
- const p = addParam(state, filters.resource.tableName);
194
- state.fragments.push(`table_name = ${p}`);
195
- if (filters.resource.recordId !== void 0) {
196
- const r = addParam(state, filters.resource.recordId);
197
- state.fragments.push(`record_id = ${r}`);
131
+ var FIELD_COLUMN_NAMES = {
132
+ tableName: "table_name",
133
+ recordId: "record_id",
134
+ actorId: "actor_id",
135
+ severity: "severity",
136
+ operation: "operation"
137
+ };
138
+ function mapCondition(state, condition) {
139
+ switch (condition.kind) {
140
+ case "eq": {
141
+ const col = FIELD_COLUMN_NAMES[condition.field];
142
+ const p = addParam(state, condition.value);
143
+ state.fragments.push(`${col} = ${p}`);
144
+ break;
198
145
  }
199
- }
200
- if (filters.actorIds !== void 0 && filters.actorIds.length > 0) {
201
- if (filters.actorIds.length === 1) {
202
- const first = filters.actorIds[0];
203
- if (first !== void 0) {
204
- const p = addParam(state, first);
205
- state.fragments.push(`actor_id = ${p}`);
206
- }
207
- } else {
208
- const p = addParam(state, filters.actorIds);
209
- state.fragments.push(`actor_id = ANY(${p}::text[])`);
146
+ case "in": {
147
+ const col = FIELD_COLUMN_NAMES[condition.field];
148
+ const p = addParam(state, condition.values);
149
+ state.fragments.push(`${col} = ANY(${p}::text[])`);
150
+ break;
210
151
  }
211
- }
212
- if (filters.severities !== void 0 && filters.severities.length > 0) {
213
- if (filters.severities.length === 1) {
214
- const first = filters.severities[0];
215
- if (first !== void 0) {
216
- const p = addParam(state, first);
217
- state.fragments.push(`severity = ${p}`);
218
- }
219
- } else {
220
- const p = addParam(state, filters.severities);
221
- state.fragments.push(`severity = ANY(${p}::text[])`);
152
+ case "timestampGte": {
153
+ const p = addParam(state, condition.value.toISOString());
154
+ state.fragments.push(`timestamp >= ${p}::timestamptz`);
155
+ break;
222
156
  }
223
- }
224
- if (filters.operations !== void 0 && filters.operations.length > 0) {
225
- if (filters.operations.length === 1) {
226
- const first = filters.operations[0];
227
- if (first !== void 0) {
228
- const p = addParam(state, first);
229
- state.fragments.push(`operation = ${p}`);
157
+ case "timestampLte": {
158
+ const p = addParam(state, condition.value.toISOString());
159
+ state.fragments.push(`timestamp <= ${p}::timestamptz`);
160
+ break;
161
+ }
162
+ case "search": {
163
+ const p = addParam(state, condition.pattern);
164
+ state.fragments.push(`(label ILIKE ${p} OR description ILIKE ${p})`);
165
+ break;
166
+ }
167
+ case "compliance": {
168
+ const p = addParam(state, JSON.stringify(condition.tags));
169
+ state.fragments.push(`compliance @> ${p}::jsonb`);
170
+ break;
171
+ }
172
+ case "cursor": {
173
+ const tRef = addParam(state, condition.timestamp.toISOString());
174
+ const iRef = addParam(state, condition.id);
175
+ if (condition.sortOrder === "asc") {
176
+ state.fragments.push(
177
+ `(timestamp > ${tRef}::timestamptz OR (timestamp = ${tRef}::timestamptz AND id::text > ${iRef}))`
178
+ );
179
+ } else {
180
+ state.fragments.push(
181
+ `(timestamp < ${tRef}::timestamptz OR (timestamp = ${tRef}::timestamptz AND id::text < ${iRef}))`
182
+ );
230
183
  }
231
- } else {
232
- const p = addParam(state, filters.operations);
233
- state.fragments.push(`operation = ANY(${p}::text[])`);
184
+ break;
234
185
  }
235
186
  }
236
- if (filters.since !== void 0) {
237
- const date = resolveTimeFilter(filters.since);
238
- const p = addParam(state, date.toISOString());
239
- state.fragments.push(`timestamp >= ${p}::timestamptz`);
240
- }
241
- if (filters.until !== void 0) {
242
- const date = resolveTimeFilter(filters.until);
243
- const p = addParam(state, date.toISOString());
244
- state.fragments.push(`timestamp <= ${p}::timestamptz`);
245
- }
246
- if (filters.searchText !== void 0 && filters.searchText.length > 0) {
247
- const escaped = escapeLikePattern(filters.searchText);
248
- const pattern = `%${escaped}%`;
249
- const p = addParam(state, pattern);
250
- state.fragments.push(`(label ILIKE ${p} OR description ILIKE ${p})`);
251
- }
252
- if (filters.compliance !== void 0 && filters.compliance.length > 0) {
253
- const p = addParam(state, JSON.stringify(filters.compliance));
254
- state.fragments.push(`compliance @> ${p}::jsonb`);
255
- }
256
- if (cursor !== void 0) {
257
- const tRef = addParam(state, cursor.timestamp.toISOString());
258
- const iRef = addParam(state, cursor.id);
259
- if (sortOrder === "asc") {
260
- state.fragments.push(
261
- `(timestamp > ${tRef}::timestamptz OR (timestamp = ${tRef}::timestamptz AND id::text > ${iRef}))`
262
- );
263
- } else {
264
- state.fragments.push(
265
- `(timestamp < ${tRef}::timestamptz OR (timestamp = ${tRef}::timestamptz AND id::text < ${iRef}))`
266
- );
267
- }
187
+ }
188
+ function buildWhereClause(filters, cursor, sortOrder) {
189
+ const state = { fragments: [], params: [] };
190
+ const conditions = (0, import_audit_core2.interpretFilters)(filters, { cursor, sortOrder });
191
+ for (const condition of conditions) {
192
+ mapCondition(state, condition);
268
193
  }
269
194
  return state;
270
195
  }
@@ -272,7 +197,7 @@ async function queryLogs(spec, db) {
272
197
  const sortOrder = spec.sortOrder ?? "desc";
273
198
  const limit = Math.min(spec.limit ?? DEFAULT_LIMIT, MAX_LIMIT);
274
199
  const fetchLimit = limit + 1;
275
- const cursor = spec.cursor !== void 0 ? decodeCursor(spec.cursor) : void 0;
200
+ const cursor = spec.cursor !== void 0 ? (0, import_audit_core2.decodeCursor)(spec.cursor) : void 0;
276
201
  const where = buildWhereClause(spec.filters, cursor, sortOrder);
277
202
  const queryParams = [...where.params, fetchLimit];
278
203
  const limitRef = `$${queryParams.length}`;
@@ -288,7 +213,7 @@ async function queryLogs(spec, db) {
288
213
  if (lastEntry !== void 0) {
289
214
  return {
290
215
  entries,
291
- nextCursor: encodeCursor(lastEntry.timestamp, lastEntry.id)
216
+ nextCursor: (0, import_audit_core2.encodeCursor)(lastEntry.timestamp, lastEntry.id)
292
217
  };
293
218
  }
294
219
  }
@@ -356,38 +281,14 @@ async function getStats(options, db) {
356
281
  ...params
357
282
  );
358
283
  const [summaryRows, eventsPerDayRows, topActorsRows, topTablesRows, operationRows, severityRows] = await Promise.all([summaryQuery, eventsPerDayQuery, topActorsQuery, topTablesQuery, operationQuery, severityQuery]);
359
- const summary = summaryRows[0];
360
- const totalLogs = summary !== void 0 ? toCount(summary.total_logs) : 0;
361
- const tablesAudited = summary !== void 0 ? toCount(summary.tables_audited) : 0;
362
- const eventsPerDay = eventsPerDayRows.map((row) => ({
363
- date: String(row.date),
364
- count: toCount(row.count)
365
- }));
366
- const topActors = topActorsRows.map((row) => ({
367
- actorId: String(row.actor_id),
368
- count: toCount(row.count)
369
- }));
370
- const topTables = topTablesRows.map((row) => ({
371
- tableName: String(row.table_name),
372
- count: toCount(row.count)
373
- }));
374
- const operationBreakdown = {};
375
- for (const row of operationRows) {
376
- operationBreakdown[String(row.operation)] = toCount(row.count);
377
- }
378
- const severityBreakdown = {};
379
- for (const row of severityRows) {
380
- severityBreakdown[String(row.severity)] = toCount(row.count);
381
- }
382
- return {
383
- totalLogs,
384
- tablesAudited,
385
- eventsPerDay,
386
- topActors,
387
- topTables,
388
- operationBreakdown,
389
- severityBreakdown
390
- };
284
+ return (0, import_audit_core2.assembleStats)(
285
+ summaryRows.map((r) => ({ totalLogs: r.total_logs, tablesAudited: r.tables_audited })),
286
+ eventsPerDayRows,
287
+ topActorsRows.map((r) => ({ actorId: r.actor_id, count: r.count })),
288
+ topTablesRows.map((r) => ({ tableName: r.table_name, count: r.count })),
289
+ operationRows,
290
+ severityRows
291
+ );
391
292
  }
392
293
 
393
294
  // src/adapter.ts
@@ -447,7 +348,7 @@ function jsonOrNull(value) {
447
348
  }
448
349
 
449
350
  // src/extension.ts
450
- var import_audit_core2 = require("@usebetterdev/audit-core");
351
+ var import_audit_core3 = require("@usebetterdev/audit-core");
451
352
 
452
353
  // src/action-map.ts
453
354
  var ACTION_MAP = {
@@ -548,7 +449,7 @@ function withAuditExtension(prisma, captureLog, options = {}) {
548
449
  captureLog,
549
450
  bulkMode,
550
451
  metadata,
551
- actorId: (0, import_audit_core2.getAuditContext)()?.actorId
452
+ actorId: (0, import_audit_core3.getAuditContext)()?.actorId
552
453
  });
553
454
  } catch (err) {
554
455
  handleError(err);
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts","../src/query.ts","../src/column-map.ts","../src/adapter.ts","../src/extension.ts","../src/action-map.ts","../src/model-map.ts","../src/tx-store.ts"],"sourcesContent":["export { prismaAuditAdapter } from \"./adapter.js\";\nexport type { PrismaClientWithRaw } from \"./adapter.js\";\nexport { withAuditExtension } from \"./extension.js\";\nexport type {\n PrismaClientLike,\n WithAuditExtensionOptions,\n BulkMode,\n} from \"./extension.js\";\nexport { prismaModelMap } from \"./model-map.js\";\nexport { runWithTxClient } from \"./tx-store.js\";\n","import type {\n AuditLog,\n AuditQueryFilters,\n AuditQuerySpec,\n AuditQueryResult,\n AuditStats,\n TimeFilter,\n} from \"@usebetterdev/audit-core\";\nimport { parseDuration } from \"@usebetterdev/audit-core\";\nimport { rowToAuditLog } from \"./column-map.js\";\n\n/**\n * Minimal duck-typed interface for the database operations used by query functions.\n * Structurally compatible with PrismaClientWithRaw in adapter.ts.\n */\ninterface QueryDb {\n $queryRawUnsafe(query: string, ...params: unknown[]): Promise<unknown>;\n $executeRawUnsafe(query: string, ...params: unknown[]): Promise<number>;\n}\n\nconst DEFAULT_LIMIT = 50;\nconst MAX_LIMIT = 250;\n\nconst SELECT_COLUMNS =\n \"id, timestamp, table_name, operation, record_id, actor_id, \" +\n \"before_data, after_data, diff, label, description, severity, \" +\n \"compliance, notify, reason, metadata, redacted_fields\";\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 * 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\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\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 * Safely converts a PostgreSQL count to a JavaScript number.\n * pg returns COUNT(*) as a string; @prisma/adapter-pg may return BigInt.\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 if (typeof value === \"bigint\") {\n return Number(value);\n }\n return 0;\n}\n\ninterface QueryState {\n fragments: string[];\n params: unknown[];\n}\n\nfunction addParam(state: QueryState, value: unknown): string {\n state.params.push(value);\n return `$${state.params.length}`;\n}\n\n/**\n * Builds a parameterized WHERE clause body from AuditQueryFilters and optional cursor.\n * Returns the SQL fragment (without the WHERE keyword) and bound parameters.\n *\n * The same positional parameter reference (e.g. $3) may appear multiple times\n * in the output SQL — PostgreSQL supports this for shared values like search patterns\n * and cursor timestamps.\n */\nfunction buildWhereClause(\n filters: AuditQueryFilters,\n cursor: { timestamp: Date; id: string } | undefined,\n sortOrder: \"asc\" | \"desc\",\n): QueryState {\n const state: QueryState = { fragments: [], params: [] };\n\n // resource filter\n if (filters.resource !== undefined) {\n const p = addParam(state, filters.resource.tableName);\n state.fragments.push(`table_name = ${p}`);\n if (filters.resource.recordId !== undefined) {\n const r = addParam(state, filters.resource.recordId);\n state.fragments.push(`record_id = ${r}`);\n }\n }\n\n // actorIds — single eq or ANY\n if (filters.actorIds !== undefined && filters.actorIds.length > 0) {\n if (filters.actorIds.length === 1) {\n const first = filters.actorIds[0];\n if (first !== undefined) {\n const p = addParam(state, first);\n state.fragments.push(`actor_id = ${p}`);\n }\n } else {\n const p = addParam(state, filters.actorIds);\n state.fragments.push(`actor_id = ANY(${p}::text[])`);\n }\n }\n\n // severities — single eq or ANY\n if (filters.severities !== undefined && filters.severities.length > 0) {\n if (filters.severities.length === 1) {\n const first = filters.severities[0];\n if (first !== undefined) {\n const p = addParam(state, first);\n state.fragments.push(`severity = ${p}`);\n }\n } else {\n const p = addParam(state, filters.severities);\n state.fragments.push(`severity = ANY(${p}::text[])`);\n }\n }\n\n // operations — single eq or ANY\n if (filters.operations !== undefined && filters.operations.length > 0) {\n if (filters.operations.length === 1) {\n const first = filters.operations[0];\n if (first !== undefined) {\n const p = addParam(state, first);\n state.fragments.push(`operation = ${p}`);\n }\n } else {\n const p = addParam(state, filters.operations);\n state.fragments.push(`operation = ANY(${p}::text[])`);\n }\n }\n\n // since — gte(timestamp, resolved date)\n if (filters.since !== undefined) {\n const date = resolveTimeFilter(filters.since);\n const p = addParam(state, date.toISOString());\n state.fragments.push(`timestamp >= ${p}::timestamptz`);\n }\n\n // until — lte(timestamp, resolved date)\n if (filters.until !== undefined) {\n const date = resolveTimeFilter(filters.until);\n const p = addParam(state, date.toISOString());\n state.fragments.push(`timestamp <= ${p}::timestamptz`);\n }\n\n // searchText — ILIKE on label OR description ($N used twice for same param)\n if (filters.searchText !== undefined && filters.searchText.length > 0) {\n const escaped = escapeLikePattern(filters.searchText);\n const pattern = `%${escaped}%`;\n const p = addParam(state, pattern);\n state.fragments.push(`(label ILIKE ${p} OR description ILIKE ${p})`);\n }\n\n // compliance — jsonb @> (entry must contain ALL listed tags)\n if (filters.compliance !== undefined && filters.compliance.length > 0) {\n const p = addParam(state, JSON.stringify(filters.compliance));\n state.fragments.push(`compliance @> ${p}::jsonb`);\n }\n\n // cursor — (timestamp, id) tuple pagination\n if (cursor !== undefined) {\n // Timestamp referenced twice in the fragment but added once as a param\n const tRef = addParam(state, cursor.timestamp.toISOString());\n const iRef = addParam(state, cursor.id);\n if (sortOrder === \"asc\") {\n state.fragments.push(\n `(timestamp > ${tRef}::timestamptz OR (timestamp = ${tRef}::timestamptz AND id::text > ${iRef}))`,\n );\n } else {\n state.fragments.push(\n `(timestamp < ${tRef}::timestamptz OR (timestamp = ${tRef}::timestamptz AND id::text < ${iRef}))`,\n );\n }\n }\n\n return state;\n}\n\n/**\n * Executes a filtered, cursor-paginated query against the audit_logs table.\n */\nexport async function queryLogs(\n spec: AuditQuerySpec,\n db: QueryDb,\n): Promise<AuditQueryResult> {\n const sortOrder = spec.sortOrder ?? \"desc\";\n const limit = Math.min(spec.limit ?? DEFAULT_LIMIT, MAX_LIMIT);\n const fetchLimit = limit + 1;\n\n const cursor =\n spec.cursor !== undefined ? decodeCursor(spec.cursor) : undefined;\n const where = buildWhereClause(spec.filters, cursor, sortOrder);\n\n // LIMIT param is kept separate so buildWhereClause's state is not mutated.\n const queryParams = [...where.params, fetchLimit];\n const limitRef = `$${queryParams.length}`;\n\n const orderDir = sortOrder === \"asc\" ? \"ASC\" : \"DESC\";\n const whereClause =\n where.fragments.length > 0 ? `WHERE ${where.fragments.join(\" AND \")}` : \"\";\n\n const sql =\n `SELECT ${SELECT_COLUMNS} FROM audit_logs ` +\n `${whereClause} ` +\n `ORDER BY timestamp ${orderDir}, id ${orderDir} ` +\n `LIMIT ${limitRef}`;\n\n const raw = (await db.$queryRawUnsafe(sql, ...queryParams)) as Record<\n string,\n unknown\n >[];\n\n const hasNextPage = raw.length > limit;\n const resultRows = hasNextPage ? raw.slice(0, -1) : raw;\n const entries = resultRows.map(rowToAuditLog);\n\n if (hasNextPage) {\n const lastEntry = entries[entries.length - 1];\n if (lastEntry !== undefined) {\n return {\n entries,\n nextCursor: encodeCursor(lastEntry.timestamp, lastEntry.id),\n };\n }\n }\n\n return { entries };\n}\n\n/**\n * Fetches a single audit log entry by its UUID. Returns null if not found.\n */\nexport async function getLogById(\n id: string,\n db: QueryDb,\n): Promise<AuditLog | null> {\n const sql = `SELECT ${SELECT_COLUMNS} FROM audit_logs WHERE id = $1::uuid LIMIT 1`;\n const raw = (await db.$queryRawUnsafe(sql, id)) as Record<string, unknown>[];\n const row = raw[0];\n if (row === undefined) {\n return null;\n }\n return rowToAuditLog(row);\n}\n\n/**\n * Deletes audit log entries older than `before`, optionally scoped to one table.\n * Returns the number of deleted rows.\n */\nexport async function purgeLogs(\n options: { before: Date; tableName?: string },\n db: QueryDb,\n): Promise<{ deletedCount: number }> {\n if (\n options.tableName !== undefined &&\n options.tableName.trim().length === 0\n ) {\n throw new Error(\n \"purgeLogs: tableName must be a non-empty string when provided\",\n );\n }\n\n let sql = \"DELETE FROM audit_logs WHERE timestamp < $1::timestamptz\";\n const params: unknown[] = [options.before.toISOString()];\n\n if (options.tableName !== undefined) {\n params.push(options.tableName);\n sql += ` AND table_name = $${params.length}`;\n }\n\n const deletedCount = await db.$executeRawUnsafe(sql, ...params);\n return { deletedCount };\n}\n\n/**\n * Builds WHERE / AND fragments for an optional `since` filter.\n * The positional `$N` reference is derived from `paramCount` so it is always\n * correct regardless of how many params come before it in the query.\n */\nfunction buildSinceFragment(paramCount: number): { where: string; and: string } {\n const ref = `$${paramCount}::timestamptz`;\n return {\n where: ` WHERE timestamp >= ${ref}`,\n and: ` AND timestamp >= ${ref}`,\n };\n}\n\n/**\n * Returns aggregated statistics for the audit_logs table.\n * Runs six queries in parallel; each optionally filtered by `since`.\n */\nexport async function getStats(\n options: { since?: Date },\n db: QueryDb,\n): Promise<AuditStats> {\n const params: unknown[] = [];\n if (options.since !== undefined) {\n params.push(options.since.toISOString());\n }\n const since =\n params.length > 0\n ? buildSinceFragment(params.length)\n : { where: \"\", and: \"\" };\n\n // Query 1: totalLogs + tablesAudited\n const summaryQuery = db.$queryRawUnsafe(\n `SELECT COUNT(*) AS total_logs, COUNT(DISTINCT table_name) AS tables_audited FROM audit_logs${since.where}`,\n ...params,\n ) as Promise<Array<{ total_logs: unknown; tables_audited: unknown }>>;\n\n // Query 2: eventsPerDay\n const eventsPerDayQuery = db.$queryRawUnsafe(\n `SELECT to_char(date_trunc('day', timestamp), 'YYYY-MM-DD') AS date, COUNT(*) AS count ` +\n `FROM audit_logs${since.where} ` +\n `GROUP BY date_trunc('day', timestamp) ORDER BY date_trunc('day', timestamp) LIMIT 365`,\n ...params,\n ) as Promise<Array<{ date: unknown; count: unknown }>>;\n\n // Query 3: topActors (NULL actors excluded)\n const topActorsQuery = db.$queryRawUnsafe(\n `SELECT actor_id, COUNT(*) AS count FROM audit_logs ` +\n `WHERE actor_id IS NOT NULL${since.and} ` +\n `GROUP BY actor_id ORDER BY COUNT(*) DESC LIMIT 10`,\n ...params,\n ) as Promise<Array<{ actor_id: unknown; count: unknown }>>;\n\n // Query 4: topTables\n const topTablesQuery = db.$queryRawUnsafe(\n `SELECT table_name, COUNT(*) AS count FROM audit_logs${since.where} ` +\n `GROUP BY table_name ORDER BY COUNT(*) DESC LIMIT 10`,\n ...params,\n ) as Promise<Array<{ table_name: unknown; count: unknown }>>;\n\n // Query 5: operationBreakdown\n const operationQuery = db.$queryRawUnsafe(\n `SELECT operation, COUNT(*) AS count FROM audit_logs${since.where} ` +\n `GROUP BY operation`,\n ...params,\n ) as Promise<Array<{ operation: unknown; count: unknown }>>;\n\n // Query 6: severityBreakdown (NULL severities excluded)\n const severityQuery = db.$queryRawUnsafe(\n `SELECT severity, COUNT(*) AS count FROM audit_logs ` +\n `WHERE severity IS NOT NULL${since.and} ` +\n `GROUP BY severity`,\n ...params,\n ) as Promise<Array<{ severity: unknown; count: unknown }>>;\n\n const [summaryRows, eventsPerDayRows, topActorsRows, topTablesRows, operationRows, severityRows] =\n await Promise.all([summaryQuery, eventsPerDayQuery, topActorsQuery, topTablesQuery, operationQuery, severityQuery]);\n\n const summary = summaryRows[0];\n const totalLogs = summary !== undefined ? toCount(summary.total_logs) : 0;\n const tablesAudited =\n summary !== undefined ? toCount(summary.tables_audited) : 0;\n\n const eventsPerDay = eventsPerDayRows.map((row) => ({\n date: String(row.date),\n count: toCount(row.count),\n }));\n\n const topActors = topActorsRows.map((row) => ({\n actorId: String(row.actor_id),\n count: toCount(row.count),\n }));\n\n const topTables = topTablesRows.map((row) => ({\n tableName: String(row.table_name),\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","import type { AuditLog, AuditOperation, AuditSeverity } from \"@usebetterdev/audit-core\";\n\nconst VALID_OPERATIONS: ReadonlySet<string> = new Set([\"INSERT\", \"UPDATE\", \"DELETE\"]);\nconst VALID_SEVERITIES: ReadonlySet<string> = new Set([\"low\", \"medium\", \"high\", \"critical\"]);\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 raw database row (from $queryRawUnsafe) to a camelCase AuditLog.\n * Handles null → undefined mapping and validates enum fields at the boundary.\n *\n * Note: `auditLogToRow` is intentionally absent. The write path lives in\n * `adapter.ts` and uses `$executeRawUnsafe` with explicit `::uuid` / `::jsonb`\n * casts that cannot be expressed through a generic row-mapper function.\n */\nexport function rowToAuditLog(row: Record<string, unknown>): AuditLog {\n const { id, timestamp, table_name, operation, record_id } = row;\n\n if (typeof id !== \"string\") {\n throw new Error(\"rowToAuditLog: id must be a string\");\n }\n if (!(timestamp instanceof Date)) {\n throw new Error(\"rowToAuditLog: timestamp must be a Date\");\n }\n if (typeof table_name !== \"string\") {\n throw new Error(\"rowToAuditLog: table_name must be a string\");\n }\n if (typeof operation !== \"string\") {\n throw new Error(\"rowToAuditLog: operation must be a string\");\n }\n if (typeof record_id !== \"string\") {\n throw new Error(\"rowToAuditLog: record_id must be a string\");\n }\n\n if (!isAuditOperation(operation)) {\n throw new Error(\n `Invalid audit operation: \"${operation}\". Expected one of: INSERT, UPDATE, DELETE`,\n );\n }\n\n const log: AuditLog = {\n id,\n timestamp,\n tableName: table_name,\n operation,\n recordId: record_id,\n };\n\n const {\n actor_id,\n before_data,\n after_data,\n diff,\n label,\n description,\n severity,\n compliance,\n notify,\n reason,\n metadata,\n redacted_fields,\n } = row;\n\n if (actor_id !== null && actor_id !== undefined) {\n log.actorId = String(actor_id);\n }\n if (before_data !== null && before_data !== undefined) {\n log.beforeData = before_data as Record<string, unknown>;\n }\n if (after_data !== null && after_data !== undefined) {\n log.afterData = after_data as Record<string, unknown>;\n }\n if (diff !== null && diff !== undefined) {\n log.diff = diff as { changedFields: string[] };\n }\n if (label !== null && label !== undefined) {\n log.label = String(label);\n }\n if (description !== null && description !== undefined) {\n log.description = String(description);\n }\n if (severity !== null && severity !== undefined) {\n const s = String(severity);\n if (!isAuditSeverity(s)) {\n throw new Error(\n `Invalid audit severity: \"${s}\". Expected one of: low, medium, high, critical`,\n );\n }\n log.severity = s;\n }\n if (compliance !== null && compliance !== undefined) {\n log.compliance = compliance as string[];\n }\n if (notify !== null && notify !== undefined) {\n log.notify = Boolean(notify);\n }\n if (reason !== null && reason !== undefined) {\n log.reason = String(reason);\n }\n if (metadata !== null && metadata !== undefined) {\n log.metadata = metadata as Record<string, unknown>;\n }\n if (redacted_fields !== null && redacted_fields !== undefined) {\n log.redactedFields = redacted_fields as string[];\n }\n\n return log;\n}\n","import type {\n AuditDatabaseAdapter,\n AuditLog,\n AuditQuerySpec,\n AuditQueryResult,\n AuditStats,\n} from \"@usebetterdev/audit-core\";\nimport { queryLogs, getLogById, getStats, purgeLogs } from \"./query.js\";\n\n/**\n * Minimal duck-typed interface for a Prisma client that supports raw query execution.\n * Avoids a hard dependency on the generated `@prisma/client`.\n */\nexport interface PrismaClientWithRaw {\n $executeRawUnsafe(query: string, ...params: unknown[]): Promise<number>;\n $queryRawUnsafe(query: string, ...params: unknown[]): Promise<unknown>;\n}\n\n/**\n * Creates an `AuditDatabaseAdapter` backed by a Prisma client.\n *\n * Implements `writeLog` via `$executeRawUnsafe` for precise control over\n * PostgreSQL type casts (::uuid, ::timestamptz, ::jsonb) and null handling.\n *\n * Implements `queryLogs`, `getLogById`, `getStats`, and `purgeLogs` via\n * parameterized raw SQL — all user inputs are bound parameters, never interpolated.\n */\nexport function prismaAuditAdapter(db: PrismaClientWithRaw): AuditDatabaseAdapter {\n return {\n async writeLog(log: AuditLog): Promise<void> {\n await db.$executeRawUnsafe(\n `INSERT INTO audit_logs (\n id, timestamp, table_name, operation, record_id,\n actor_id, before_data, after_data, diff,\n label, description, severity, compliance,\n notify, reason, metadata, redacted_fields\n ) VALUES (\n $1::uuid, $2::timestamptz, $3, $4, $5,\n $6, $7::jsonb, $8::jsonb, $9::jsonb,\n $10, $11, $12, $13::jsonb,\n $14, $15, $16::jsonb, $17::jsonb\n )`,\n log.id,\n log.timestamp.toISOString(),\n log.tableName,\n log.operation,\n log.recordId,\n log.actorId ?? null,\n jsonOrNull(log.beforeData),\n jsonOrNull(log.afterData),\n jsonOrNull(log.diff),\n log.label ?? null,\n log.description ?? null,\n log.severity ?? null,\n jsonOrNull(log.compliance),\n log.notify ?? null,\n log.reason ?? null,\n jsonOrNull(log.metadata),\n jsonOrNull(log.redactedFields),\n );\n },\n\n async queryLogs(spec: AuditQuerySpec): Promise<AuditQueryResult> {\n return queryLogs(spec, db);\n },\n\n async getLogById(id: string): Promise<AuditLog | null> {\n return getLogById(id, db);\n },\n\n async getStats(options?: { since?: Date }): Promise<AuditStats> {\n return getStats(options ?? {}, db);\n },\n\n async purgeLogs(options: {\n before: Date;\n tableName?: string;\n }): Promise<{ deletedCount: number }> {\n return purgeLogs(options, db);\n },\n };\n}\n\nfunction jsonOrNull(value: unknown): string | null {\n if (value === undefined || value === null) {\n return null;\n }\n return JSON.stringify(value);\n}\n","import type { CaptureLogInput, AuditOperation } from \"@usebetterdev/audit-core\";\nimport { getAuditContext } from \"@usebetterdev/audit-core\";\nimport { getAuditOperation } from \"./action-map.js\";\nimport { buildTableNameResolver } from \"./model-map.js\";\nimport { getTxClient } from \"./tx-store.js\";\n\n/**\n * Minimal duck-typed interface for a Prisma client that supports `$extends`.\n * Avoids a hard dependency on the generated `@prisma/client`.\n */\nexport interface PrismaClientLike {\n $extends(extension: PrismaExtensionDefinition): unknown;\n}\n\n/**\n * Shape of the Prisma `$extends` query extension definition.\n * We only type what we need — the full shape lives in `@prisma/client/extension`.\n */\ninterface PrismaExtensionDefinition {\n query: {\n $allModels: {\n $allOperations: (params: {\n model: string;\n operation: string;\n args: unknown;\n query: (args: unknown) => Promise<unknown>;\n }) => Promise<unknown>;\n };\n };\n}\n\n/**\n * Minimal duck-typed interface for a Prisma model delegate.\n * Used internally to issue before-state lookups prior to mutations.\n * Any real Prisma client's model delegate satisfies this.\n */\ninterface PrismaModelDelegate {\n findUnique(args: { where: unknown }): Promise<unknown>;\n findMany(args: { where?: unknown; take?: number }): Promise<unknown[]>;\n}\n\n/**\n * Represents the before-state captured prior to a mutation.\n *\n * - `\"none\"`: no before-state needed (create operations, single delete whose\n * result already contains the deleted record)\n * - `\"single\"`: one record looked up via `findUnique` (update, upsert)\n * - `\"many\"`: multiple records looked up via `findMany` (updateMany, deleteMany)\n */\ntype BeforeState =\n | { type: \"none\" }\n | { type: \"single\"; row: Record<string, unknown> | undefined }\n | { type: \"many\"; rows: Record<string, unknown>[] };\n\nexport type BulkMode = \"per-row\" | \"bulk\";\n\nexport interface WithAuditExtensionOptions {\n /**\n * How to handle bulk write operations (`createMany`, `updateMany`, `deleteMany`).\n *\n * - `\"per-row\"` (default): for `createMany` and `createManyAndReturn`, fires\n * one audit entry per item in `args.data`. For `updateMany` and `deleteMany`,\n * fires one entry per row captured in the before-state lookup, or a single\n * bulk entry if no rows are found.\n * - `\"bulk\"`: fires a single audit entry for the entire operation.\n */\n bulkMode?: BulkMode;\n /**\n * Called when audit capture fails. Falls back to `console.error` if not set.\n * Errors are always swallowed — audit failures never propagate to callers.\n */\n onError?: (error: unknown) => void;\n /**\n * Extra structured data merged into every audit log entry produced by this\n * extension. Useful for tagging logs with the adapter name or environment.\n *\n * @example\n * ```ts\n * withAuditExtension(prisma, audit.captureLog, { metadata: { adapter: \"prisma\" } })\n * ```\n */\n metadata?: Record<string, unknown>;\n /**\n * Maps a Prisma model name to the `tableName` stored in audit logs.\n *\n * By default the extension auto-detects the SQL table name from Prisma's\n * `_runtimeDataModel` (populated by `@@map` directives). If the runtime\n * metadata is unavailable, the model name is used as-is.\n *\n * When provided, this function takes full precedence over auto-detection.\n *\n * @example\n * ```ts\n * withAuditExtension(prisma, captureLog, {\n * tableNameTransform: (name) => name.toLowerCase(),\n * })\n * ```\n */\n tableNameTransform?: (modelName: string) => string;\n /**\n * Table names (SQL names, after `tableNameTransform`) to skip before-state\n * capture for. No extra `findUnique` or `findMany` is issued for these tables.\n *\n * Useful for high-volume tables where the additional SELECT is too expensive.\n *\n * @example\n * ```ts\n * withAuditExtension(prisma, captureLog, {\n * skipBeforeCapture: [\"events\", \"metrics\"],\n * })\n * ```\n */\n skipBeforeCapture?: string[];\n /**\n * Maximum number of rows fetched by the before-state `findMany` for\n * `updateMany` and `deleteMany` operations (default: `100`).\n *\n * When the matched row count exceeds this limit the before-state lookup is\n * skipped and a single bulk audit entry is written instead — preventing\n * unbounded memory consumption on large batch operations.\n *\n * Set to `Infinity` to disable the limit (not recommended in production).\n *\n * @example\n * ```ts\n * withAuditExtension(prisma, captureLog, { maxBeforeStateRows: 50 })\n * ```\n */\n maxBeforeStateRows?: number;\n}\n\n/**\n * Wraps a Prisma client with a `$extends` query extension that calls\n * `captureLog` after each successful mutation.\n *\n * Returns a new extended client of the same type `T`. Use this extended\n * client in place of the original — all queries behave identically.\n *\n * Actor identity is read automatically from the current `AuditContext`\n * (set via `audit.withContext` or `runWithAuditContext` from audit-core).\n *\n * For `update` and `upsert`, a `findUnique` is issued before the mutation\n * to capture `before_data`. For `updateMany` and `deleteMany` in `\"per-row\"`\n * mode, a `findMany` is issued before the mutation to capture per-row state.\n * Pass `skipBeforeCapture` to opt out for specific high-volume tables.\n *\n * Filtering by audited tables is NOT done here — `captureLog` (from\n * `betterAudit`) already skips tables not in `auditTables`.\n *\n * @example\n * ```ts\n * // auditTables must use SQL table names (auto-detected from @@map directives)\n * const audit = betterAudit({ database: prismaAuditAdapter(prisma), auditTables: [\"users\"] });\n * const auditedPrisma = withAuditExtension(prisma, audit.captureLog);\n * // Use auditedPrisma everywhere — mutations are transparently logged\n * ```\n */\nexport function withAuditExtension<T extends PrismaClientLike>(\n prisma: T,\n captureLog: (input: CaptureLogInput) => Promise<void>,\n options: WithAuditExtensionOptions = {},\n): T {\n const bulkMode = options.bulkMode ?? \"per-row\";\n const handleError = buildErrorHandler(options.onError);\n const metadata = options.metadata;\n const skipBeforeCapture = options.skipBeforeCapture;\n const maxBeforeStateRows = options.maxBeforeStateRows ?? 100;\n const resolveTableName = buildTableNameResolver(prisma, options.tableNameTransform);\n\n const extended = prisma.$extends({\n query: {\n $allModels: {\n async $allOperations({\n model,\n operation,\n args,\n query,\n }: {\n model: string;\n operation: string;\n args: unknown;\n query: (args: unknown) => Promise<unknown>;\n }) {\n const auditOp = getAuditOperation(operation);\n if (auditOp === undefined) {\n return query(args);\n }\n\n const tableName = resolveTableName(model);\n const shouldSkipBefore =\n skipBeforeCapture !== undefined && skipBeforeCapture.includes(tableName);\n\n const beforeState: BeforeState = shouldSkipBefore\n ? { type: \"none\" }\n : await fetchBeforeState({ prisma, model, operation, args, handleError, maxBeforeStateRows });\n\n const result = await query(args);\n\n try {\n await fireCaptureLog({\n tableName,\n operation,\n auditOp,\n args,\n result,\n beforeState,\n captureLog,\n bulkMode,\n metadata,\n actorId: getAuditContext()?.actorId,\n });\n } catch (err) {\n handleError(err);\n }\n\n return result;\n },\n },\n },\n });\n\n // The $extends return type is `unknown` — casting back to T is safe because\n // the extension only adds transparent interception; it does not change the query API.\n return extended as unknown as T;\n}\n\n// ---------------------------------------------------------------------------\n// Before-state fetching\n// ---------------------------------------------------------------------------\n\ninterface FetchBeforeStateParams {\n prisma: PrismaClientLike;\n model: string;\n operation: string;\n args: unknown;\n handleError: (err: unknown) => void;\n maxBeforeStateRows: number;\n}\n\n/**\n * Issues a `findUnique` or `findMany` before the mutation to capture the\n * record state that will be stored as `before_data` in the audit log.\n *\n * Returns `{ type: \"none\" }` for operations that don't need a pre-query:\n * - `create` / `createMany` / `createManyAndReturn`: no before-state exists\n * - `delete`: Prisma returns the deleted record as the mutation result\n */\nasync function fetchBeforeState({\n prisma,\n model,\n operation,\n args,\n handleError,\n maxBeforeStateRows,\n}: FetchBeforeStateParams): Promise<BeforeState> {\n if (\n operation === \"create\" ||\n operation === \"createMany\" ||\n operation === \"createManyAndReturn\" ||\n operation === \"delete\"\n ) {\n return { type: \"none\" };\n }\n\n const delegate = getModelDelegate(getTxClient() ?? prisma, model);\n if (delegate === undefined) {\n return { type: \"none\" };\n }\n\n if (operation === \"update\" || operation === \"upsert\") {\n const where = getArgsWhere(args);\n if (where === undefined) {\n return { type: \"single\", row: undefined };\n }\n try {\n const found = await delegate.findUnique({ where });\n return { type: \"single\", row: toRecord(found) };\n } catch (err) {\n handleError(err);\n return { type: \"single\", row: undefined };\n }\n }\n\n if (operation === \"updateMany\" || operation === \"deleteMany\") {\n const where = getArgsWhere(args);\n try {\n const rows = await delegate.findMany({ where: where ?? {}, take: maxBeforeStateRows + 1 });\n if (rows.length > maxBeforeStateRows) {\n // Too many rows to capture individually — fall back to a single bulk entry.\n return { type: \"none\" };\n }\n const records = rows\n .map((r) => toRecord(r))\n .filter((r): r is Record<string, unknown> => r !== undefined);\n return { type: \"many\", rows: records };\n } catch (err) {\n handleError(err);\n return { type: \"many\", rows: [] };\n }\n }\n\n return { type: \"none\" };\n}\n\n// ---------------------------------------------------------------------------\n// captureLog dispatch\n// ---------------------------------------------------------------------------\n\ninterface FireCaptureLogParams {\n tableName: string;\n operation: string;\n auditOp: AuditOperation;\n args: unknown;\n result: unknown;\n beforeState: BeforeState;\n captureLog: (input: CaptureLogInput) => Promise<void>;\n bulkMode: BulkMode;\n metadata: Record<string, unknown> | undefined;\n actorId: string | undefined;\n}\n\nasync function fireCaptureLog({\n tableName,\n operation,\n auditOp,\n args,\n result,\n beforeState,\n captureLog,\n bulkMode,\n metadata,\n actorId,\n}: FireCaptureLogParams): Promise<void> {\n if (operation === \"createMany\") {\n await handleCreateMany({ tableName, auditOp, args, captureLog, bulkMode, metadata, actorId });\n return;\n }\n\n if (operation === \"createManyAndReturn\") {\n await handleCreateManyAndReturn({ tableName, auditOp, result, captureLog, bulkMode, metadata, actorId });\n return;\n }\n\n if (operation === \"updateMany\") {\n await handleUpdateMany({ tableName, auditOp, beforeState, captureLog, bulkMode, metadata, actorId });\n return;\n }\n\n if (operation === \"deleteMany\") {\n await handleDeleteMany({ tableName, auditOp, beforeState, captureLog, bulkMode, metadata, actorId });\n return;\n }\n\n // Single-record operations: create, update, upsert, delete\n const row = toRecord(result);\n const recordId = (row !== undefined ? extractId(row) : undefined) ?? \"unknown\";\n\n if (operation === \"upsert\") {\n // A before-state row means the record existed → UPDATE path.\n // No before-state row means the record was created → INSERT path.\n const before = beforeState.type === \"single\" ? beforeState.row : undefined;\n const effectiveOp: AuditOperation = before !== undefined ? \"UPDATE\" : \"INSERT\";\n await captureLog({\n tableName,\n operation: effectiveOp,\n recordId,\n ...(before !== undefined && { before }),\n ...(row !== undefined && { after: row }),\n ...(metadata !== undefined && { metadata }),\n ...(actorId !== undefined && { actorId }),\n });\n return;\n }\n\n if (auditOp === \"INSERT\") {\n await captureLog({\n tableName,\n operation: auditOp,\n recordId,\n ...(row !== undefined && { after: row }),\n ...(metadata !== undefined && { metadata }),\n ...(actorId !== undefined && { actorId }),\n });\n return;\n }\n\n if (auditOp === \"UPDATE\") {\n const before = beforeState.type === \"single\" ? beforeState.row : undefined;\n await captureLog({\n tableName,\n operation: auditOp,\n recordId,\n ...(before !== undefined && { before }),\n ...(row !== undefined && { after: row }),\n ...(metadata !== undefined && { metadata }),\n ...(actorId !== undefined && { actorId }),\n });\n return;\n }\n\n // DELETE: Prisma returns the deleted record as the result — use it as before_data.\n await captureLog({\n tableName,\n operation: auditOp,\n recordId,\n ...(row !== undefined && { before: row }),\n ...(metadata !== undefined && { metadata }),\n ...(actorId !== undefined && { actorId }),\n });\n}\n\n// ---------------------------------------------------------------------------\n// Per-operation handlers\n// ---------------------------------------------------------------------------\n\ninterface HandleCreateManyParams {\n tableName: string;\n auditOp: AuditOperation;\n args: unknown;\n captureLog: (input: CaptureLogInput) => Promise<void>;\n bulkMode: BulkMode;\n metadata: Record<string, unknown> | undefined;\n actorId: string | undefined;\n}\n\nasync function handleCreateMany({\n tableName,\n auditOp,\n args,\n captureLog,\n bulkMode,\n metadata,\n actorId,\n}: HandleCreateManyParams): Promise<void> {\n if (bulkMode === \"bulk\") {\n await captureLog({\n tableName,\n operation: auditOp,\n recordId: \"unknown\",\n ...(metadata !== undefined && { metadata }),\n ...(actorId !== undefined && { actorId }),\n });\n return;\n }\n\n // per-row: fire one entry per item in args.data\n const rows = getArgsData(args);\n if (rows.length === 0) {\n await captureLog({\n tableName,\n operation: auditOp,\n recordId: \"unknown\",\n ...(metadata !== undefined && { metadata }),\n ...(actorId !== undefined && { actorId }),\n });\n return;\n }\n\n await Promise.all(\n rows.map((row) => {\n const record = toRecord(row);\n const recordId = (record !== undefined ? extractId(record) : undefined) ?? \"unknown\";\n return captureLog({\n tableName,\n operation: auditOp,\n recordId,\n ...(record !== undefined && { after: record }),\n ...(metadata !== undefined && { metadata }),\n ...(actorId !== undefined && { actorId }),\n });\n }),\n );\n}\n\ninterface HandleCreateManyAndReturnParams {\n tableName: string;\n auditOp: AuditOperation;\n result: unknown;\n captureLog: (input: CaptureLogInput) => Promise<void>;\n bulkMode: BulkMode;\n metadata: Record<string, unknown> | undefined;\n actorId: string | undefined;\n}\n\nasync function handleCreateManyAndReturn({\n tableName,\n auditOp,\n result,\n captureLog,\n bulkMode,\n metadata,\n actorId,\n}: HandleCreateManyAndReturnParams): Promise<void> {\n if (bulkMode === \"bulk\") {\n await captureLog({\n tableName,\n operation: auditOp,\n recordId: \"unknown\",\n ...(metadata !== undefined && { metadata }),\n ...(actorId !== undefined && { actorId }),\n });\n return;\n }\n\n const rows = Array.isArray(result) ? result : [];\n if (rows.length === 0) {\n await captureLog({\n tableName,\n operation: auditOp,\n recordId: \"unknown\",\n ...(metadata !== undefined && { metadata }),\n ...(actorId !== undefined && { actorId }),\n });\n return;\n }\n\n await Promise.all(\n rows.map((row) => {\n const record = toRecord(row);\n const recordId = (record !== undefined ? extractId(record) : undefined) ?? \"unknown\";\n return captureLog({\n tableName,\n operation: auditOp,\n recordId,\n ...(record !== undefined && { after: record }),\n ...(metadata !== undefined && { metadata }),\n ...(actorId !== undefined && { actorId }),\n });\n }),\n );\n}\n\ninterface HandleUpdateManyParams {\n tableName: string;\n auditOp: AuditOperation;\n beforeState: BeforeState;\n captureLog: (input: CaptureLogInput) => Promise<void>;\n bulkMode: BulkMode;\n metadata: Record<string, unknown> | undefined;\n actorId: string | undefined;\n}\n\nasync function handleUpdateMany({\n tableName,\n auditOp,\n beforeState,\n captureLog,\n bulkMode,\n metadata,\n actorId,\n}: HandleUpdateManyParams): Promise<void> {\n if (bulkMode === \"bulk\") {\n await captureLog({\n tableName,\n operation: auditOp,\n recordId: \"unknown\",\n ...(metadata !== undefined && { metadata }),\n ...(actorId !== undefined && { actorId }),\n });\n return;\n }\n\n // per-row: fire one entry per row captured before the mutation\n const rows = beforeState.type === \"many\" ? beforeState.rows : [];\n if (rows.length === 0) {\n await captureLog({\n tableName,\n operation: auditOp,\n recordId: \"unknown\",\n ...(metadata !== undefined && { metadata }),\n ...(actorId !== undefined && { actorId }),\n });\n return;\n }\n\n await Promise.all(\n rows.map((row) => {\n const recordId = extractId(row) ?? \"unknown\";\n return captureLog({\n tableName,\n operation: auditOp,\n recordId,\n before: row,\n ...(metadata !== undefined && { metadata }),\n ...(actorId !== undefined && { actorId }),\n });\n }),\n );\n}\n\ninterface HandleDeleteManyParams {\n tableName: string;\n auditOp: AuditOperation;\n beforeState: BeforeState;\n captureLog: (input: CaptureLogInput) => Promise<void>;\n bulkMode: BulkMode;\n metadata: Record<string, unknown> | undefined;\n actorId: string | undefined;\n}\n\nasync function handleDeleteMany({\n tableName,\n auditOp,\n beforeState,\n captureLog,\n bulkMode,\n metadata,\n actorId,\n}: HandleDeleteManyParams): Promise<void> {\n if (bulkMode === \"bulk\") {\n await captureLog({\n tableName,\n operation: auditOp,\n recordId: \"unknown\",\n ...(metadata !== undefined && { metadata }),\n ...(actorId !== undefined && { actorId }),\n });\n return;\n }\n\n // per-row: fire one entry per row captured before deletion\n const rows = beforeState.type === \"many\" ? beforeState.rows : [];\n if (rows.length === 0) {\n await captureLog({\n tableName,\n operation: auditOp,\n recordId: \"unknown\",\n ...(metadata !== undefined && { metadata }),\n ...(actorId !== undefined && { actorId }),\n });\n return;\n }\n\n await Promise.all(\n rows.map((row) => {\n const recordId = extractId(row) ?? \"unknown\";\n return captureLog({\n tableName,\n operation: auditOp,\n recordId,\n before: row,\n ...(metadata !== undefined && { metadata }),\n ...(actorId !== undefined && { actorId }),\n });\n }),\n );\n}\n\n// ---------------------------------------------------------------------------\n// Helpers\n// ---------------------------------------------------------------------------\n\nfunction buildErrorHandler(\n onError: ((error: unknown) => void) | undefined,\n): (error: unknown) => void {\n return (error: unknown) => {\n try {\n if (onError !== undefined) {\n onError(error);\n } else {\n const msg = error instanceof Error ? error.message : String(error);\n console.error(`audit-prisma: capture failed — ${msg}`);\n }\n } catch {\n // onError itself threw — swallow to never break mutations.\n }\n };\n}\n\n/**\n * Returns true when `value` quacks like a Prisma model delegate with\n * `findUnique` and `findMany` methods.\n */\nfunction isModelDelegate(value: unknown): value is PrismaModelDelegate {\n if (value === null || value === undefined || typeof value !== \"object\") {\n return false;\n }\n return (\n \"findUnique\" in value &&\n \"findMany\" in value &&\n typeof Reflect.get(value, \"findUnique\") === \"function\" &&\n typeof Reflect.get(value, \"findMany\") === \"function\"\n );\n}\n\n/**\n * Returns the model delegate for `model` (e.g. `prisma.user` for model `\"User\"`)\n * by lowercasing the first character and doing a dynamic property lookup.\n * Returns `undefined` when the delegate is not found or doesn't have the\n * expected shape — the caller falls back to `{ type: \"none\" }` before-state.\n */\nfunction getModelDelegate(\n client: unknown,\n model: string,\n): PrismaModelDelegate | undefined {\n const lowerModel = model.charAt(0).toLowerCase() + model.slice(1);\n const value: unknown = Reflect.get(Object(client), lowerModel);\n if (isModelDelegate(value)) {\n return value;\n }\n return undefined;\n}\n\nfunction extractId(record: Record<string, unknown>): string | undefined {\n const id = record[\"id\"];\n if (id !== undefined && id !== null) {\n return String(id);\n }\n return undefined;\n}\n\nfunction toRecord(value: unknown): Record<string, unknown> | undefined {\n if (value !== null && value !== undefined && typeof value === \"object\" && !Array.isArray(value)) {\n return value as Record<string, unknown>;\n }\n return undefined;\n}\n\nfunction getArgsData(args: unknown): unknown[] {\n if (args !== null && typeof args === \"object\" && \"data\" in args) {\n const data = Reflect.get(args, \"data\");\n if (Array.isArray(data)) {\n return data;\n }\n }\n return [];\n}\n\n/**\n * Extracts `args.where` for before-state lookups. Returns `undefined` when\n * the argument object has no `where` property.\n */\nfunction getArgsWhere(args: unknown): unknown {\n if (args !== null && typeof args === \"object\" && \"where\" in args) {\n return Reflect.get(args, \"where\");\n }\n return undefined;\n}\n","import type { AuditOperation } from \"@usebetterdev/audit-core\";\n\n/**\n * Maps Prisma query action names to AuditOperation values.\n * Actions not in this map are read-only queries and are not audited.\n */\nexport const ACTION_MAP = {\n create: \"INSERT\",\n createMany: \"INSERT\",\n createManyAndReturn: \"INSERT\",\n update: \"UPDATE\",\n updateMany: \"UPDATE\",\n upsert: \"UPDATE\",\n delete: \"DELETE\",\n deleteMany: \"DELETE\",\n} as const satisfies Record<string, AuditOperation>;\n\nexport type MutableAction = keyof typeof ACTION_MAP;\n\nfunction isMutableAction(action: string): action is MutableAction {\n return action in ACTION_MAP;\n}\n\n/**\n * Returns the AuditOperation for a Prisma action name, or `undefined` for\n * read-only operations (findMany, findUnique, count, aggregate, etc.).\n */\nexport function getAuditOperation(action: string): AuditOperation | undefined {\n if (isMutableAction(action)) {\n return ACTION_MAP[action];\n }\n return undefined;\n}\n","/**\n * Runtime model-to-table mapping for Prisma clients that expose\n * `_runtimeDataModel.models`. This lets the audit extension auto-detect\n * the SQL table name (from `@@map`) without user configuration.\n */\n\ninterface RuntimeModel {\n dbName: string | null;\n}\n\ninterface RuntimeDataModel {\n models: Record<string, RuntimeModel>;\n}\n\ninterface PrismaWithRuntimeDataModel {\n _runtimeDataModel: RuntimeDataModel;\n}\n\n/**\n * Type guard: checks whether `value` exposes Prisma's `_runtimeDataModel.models`.\n */\nfunction hasPrismaRuntimeDataModel(value: object): value is PrismaWithRuntimeDataModel {\n if (!(\"_runtimeDataModel\" in value)) {\n return false;\n }\n\n const { _runtimeDataModel: rdm } = value;\n if (rdm === null || typeof rdm !== \"object\") {\n return false;\n }\n\n return \"models\" in rdm;\n}\n\n/**\n * Returns a `Record<string, string>` mapping Prisma model names to their\n * SQL table names (from `@@map`). Models without `@@map` map to themselves.\n *\n * Returns `undefined` if the client does not expose `_runtimeDataModel`.\n */\nexport function prismaModelMap(prisma: object): Record<string, string> | undefined {\n if (!hasPrismaRuntimeDataModel(prisma)) {\n return undefined;\n }\n\n const result: Record<string, string> = {};\n for (const [modelName, meta] of Object.entries(prisma._runtimeDataModel.models)) {\n result[modelName] = meta.dbName ?? modelName;\n }\n return result;\n}\n\n/**\n * Builds a function that resolves a Prisma model name to a table name.\n *\n * Resolution order:\n * 1. If `transform` is provided, use it (full user control).\n * 2. Else if the client exposes `_runtimeDataModel`, use `dbName ?? modelName`.\n * 3. Else return the model name as-is (safe fallback).\n */\nexport function buildTableNameResolver(\n prisma: object,\n transform: ((modelName: string) => string) | undefined,\n): (modelName: string) => string {\n if (transform !== undefined) {\n return transform;\n }\n\n const map = prismaModelMap(prisma);\n if (map !== undefined) {\n return (modelName: string) => map[modelName] ?? modelName;\n }\n\n return (modelName: string) => modelName;\n}\n","import { AsyncLocalStorage } from \"node:async_hooks\";\n\n/**\n * Stores the current Prisma transaction client for the duration of a\n * tenant-scoped request.\n *\n * When the audit extension issues a `findUnique` to capture before-state,\n * it queries the outer (non-transaction) Prisma client by default. That\n * connection has no `SET LOCAL app.current_tenant` active, so RLS blocks\n * all rows and before-state is lost.\n *\n * Call `runWithTxClient(tx, fn)` inside `prismaAdapter.runWithTenant` (after\n * the `SET LOCAL` statement) to make `tx` available to the extension. The\n * extension reads it via `getTxClient()` and uses it for the `findUnique`\n * instead of the outer client.\n */\nconst txStorage = new AsyncLocalStorage<unknown>();\n\n/**\n * Stores `tx` in the async context for the duration of `fn`.\n *\n * Call this wrapping the inner callback of a Prisma `$transaction` after the\n * tenant `SET LOCAL` has been issued so the audit extension can reuse the\n * same connection for before-state lookups.\n *\n * @example\n * ```ts\n * // Inside your betterTenant adapter wrapper:\n * const adapter = {\n * ...db.adapter,\n * runWithTenant(tenantId, fn) {\n * return db.adapter.runWithTenant(tenantId, (tx) =>\n * runWithTxClient(tx, () => fn(tx))\n * );\n * },\n * };\n * ```\n */\nexport function runWithTxClient<T>(tx: unknown, fn: () => Promise<T>): Promise<T> {\n return txStorage.run(tx, fn);\n}\n\n/**\n * Returns the current transaction client stored by `runWithTxClient`, or\n * `undefined` when called outside a tenant transaction context.\n */\nexport function getTxClient(): unknown {\n return txStorage.getStore();\n}\n"],"mappings":"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","names":["import_audit_core"]}
1
+ {"version":3,"sources":["../src/index.ts","../src/query.ts","../src/column-map.ts","../src/adapter.ts","../src/extension.ts","../src/action-map.ts","../src/model-map.ts","../src/tx-store.ts"],"sourcesContent":["export { prismaAuditAdapter } from \"./adapter.js\";\nexport type { PrismaClientWithRaw } from \"./adapter.js\";\nexport { withAuditExtension } from \"./extension.js\";\nexport type {\n PrismaClientLike,\n WithAuditExtensionOptions,\n BulkMode,\n} from \"./extension.js\";\nexport { prismaModelMap } from \"./model-map.js\";\nexport { runWithTxClient } from \"./tx-store.js\";\n","import type {\n AuditLog,\n AuditFilterField,\n AuditQueryFilters,\n AuditQuerySpec,\n AuditQueryResult,\n AuditStats,\n FilterCondition,\n} from \"@usebetterdev/audit-core\";\nimport {\n encodeCursor,\n decodeCursor,\n interpretFilters,\n assembleStats,\n} from \"@usebetterdev/audit-core\";\nimport { rowToAuditLog } from \"./column-map.js\";\n\n/**\n * Minimal duck-typed interface for the database operations used by query functions.\n * Structurally compatible with PrismaClientWithRaw in adapter.ts.\n */\ninterface QueryDb {\n $queryRawUnsafe(query: string, ...params: unknown[]): Promise<unknown>;\n $executeRawUnsafe(query: string, ...params: unknown[]): Promise<number>;\n}\n\nconst DEFAULT_LIMIT = 50;\nconst MAX_LIMIT = 250;\n\nconst SELECT_COLUMNS =\n \"id, timestamp, table_name, operation, record_id, actor_id, \" +\n \"before_data, after_data, diff, label, description, severity, \" +\n \"compliance, notify, reason, metadata, redacted_fields\";\n\ninterface QueryState {\n fragments: string[];\n params: unknown[];\n}\n\nfunction addParam(state: QueryState, value: unknown): string {\n state.params.push(value);\n return `$${state.params.length}`;\n}\n\n/** Maps an AuditFilterField to its database column name. */\nconst FIELD_COLUMN_NAMES: Record<AuditFilterField, string> = {\n tableName: \"table_name\",\n recordId: \"record_id\",\n actorId: \"actor_id\",\n severity: \"severity\",\n operation: \"operation\",\n};\n\n/** Maps a single FilterCondition to a SQL fragment, pushing params into state. */\nfunction mapCondition(state: QueryState, condition: FilterCondition): void {\n switch (condition.kind) {\n case \"eq\": {\n const col = FIELD_COLUMN_NAMES[condition.field];\n const p = addParam(state, condition.value);\n state.fragments.push(`${col} = ${p}`);\n break;\n }\n case \"in\": {\n const col = FIELD_COLUMN_NAMES[condition.field];\n const p = addParam(state, condition.values);\n state.fragments.push(`${col} = ANY(${p}::text[])`);\n break;\n }\n case \"timestampGte\": {\n const p = addParam(state, condition.value.toISOString());\n state.fragments.push(`timestamp >= ${p}::timestamptz`);\n break;\n }\n case \"timestampLte\": {\n const p = addParam(state, condition.value.toISOString());\n state.fragments.push(`timestamp <= ${p}::timestamptz`);\n break;\n }\n case \"search\": {\n const p = addParam(state, condition.pattern);\n state.fragments.push(`(label ILIKE ${p} OR description ILIKE ${p})`);\n break;\n }\n case \"compliance\": {\n const p = addParam(state, JSON.stringify(condition.tags));\n state.fragments.push(`compliance @> ${p}::jsonb`);\n break;\n }\n case \"cursor\": {\n const tRef = addParam(state, condition.timestamp.toISOString());\n const iRef = addParam(state, condition.id);\n if (condition.sortOrder === \"asc\") {\n state.fragments.push(\n `(timestamp > ${tRef}::timestamptz OR (timestamp = ${tRef}::timestamptz AND id::text > ${iRef}))`,\n );\n } else {\n state.fragments.push(\n `(timestamp < ${tRef}::timestamptz OR (timestamp = ${tRef}::timestamptz AND id::text < ${iRef}))`,\n );\n }\n break;\n }\n }\n}\n\n/**\n * Builds a parameterized WHERE clause body from AuditQueryFilters and optional cursor.\n * Returns the SQL fragment (without the WHERE keyword) and bound parameters.\n *\n * Uses the core `interpretFilters()` IR to produce filter conditions, then maps\n * each condition to a PostgreSQL SQL fragment with positional parameters.\n */\nfunction buildWhereClause(\n filters: AuditQueryFilters,\n cursor: { timestamp: Date; id: string } | undefined,\n sortOrder: \"asc\" | \"desc\",\n): QueryState {\n const state: QueryState = { fragments: [], params: [] };\n const conditions = interpretFilters(filters, { cursor, sortOrder });\n\n for (const condition of conditions) {\n mapCondition(state, condition);\n }\n\n return state;\n}\n\n/**\n * Executes a filtered, cursor-paginated query against the audit_logs table.\n */\nexport async function queryLogs(\n spec: AuditQuerySpec,\n db: QueryDb,\n): Promise<AuditQueryResult> {\n const sortOrder = spec.sortOrder ?? \"desc\";\n const limit = Math.min(spec.limit ?? DEFAULT_LIMIT, MAX_LIMIT);\n const fetchLimit = limit + 1;\n\n const cursor =\n spec.cursor !== undefined ? decodeCursor(spec.cursor) : undefined;\n const where = buildWhereClause(spec.filters, cursor, sortOrder);\n\n // LIMIT param is kept separate so buildWhereClause's state is not mutated.\n const queryParams = [...where.params, fetchLimit];\n const limitRef = `$${queryParams.length}`;\n\n const orderDir = sortOrder === \"asc\" ? \"ASC\" : \"DESC\";\n const whereClause =\n where.fragments.length > 0 ? `WHERE ${where.fragments.join(\" AND \")}` : \"\";\n\n const sql =\n `SELECT ${SELECT_COLUMNS} FROM audit_logs ` +\n `${whereClause} ` +\n `ORDER BY timestamp ${orderDir}, id ${orderDir} ` +\n `LIMIT ${limitRef}`;\n\n const raw = (await db.$queryRawUnsafe(sql, ...queryParams)) as Record<\n string,\n unknown\n >[];\n\n const hasNextPage = raw.length > limit;\n const resultRows = hasNextPage ? raw.slice(0, -1) : raw;\n const entries = resultRows.map(rowToAuditLog);\n\n if (hasNextPage) {\n const lastEntry = entries[entries.length - 1];\n if (lastEntry !== undefined) {\n return {\n entries,\n nextCursor: encodeCursor(lastEntry.timestamp, lastEntry.id),\n };\n }\n }\n\n return { entries };\n}\n\n/**\n * Fetches a single audit log entry by its UUID. Returns null if not found.\n */\nexport async function getLogById(\n id: string,\n db: QueryDb,\n): Promise<AuditLog | null> {\n const sql = `SELECT ${SELECT_COLUMNS} FROM audit_logs WHERE id = $1::uuid LIMIT 1`;\n const raw = (await db.$queryRawUnsafe(sql, id)) as Record<string, unknown>[];\n const row = raw[0];\n if (row === undefined) {\n return null;\n }\n return rowToAuditLog(row);\n}\n\n/**\n * Deletes audit log entries older than `before`, optionally scoped to one table.\n * Returns the number of deleted rows.\n */\nexport async function purgeLogs(\n options: { before: Date; tableName?: string },\n db: QueryDb,\n): Promise<{ deletedCount: number }> {\n if (\n options.tableName !== undefined &&\n options.tableName.trim().length === 0\n ) {\n throw new Error(\n \"purgeLogs: tableName must be a non-empty string when provided\",\n );\n }\n\n let sql = \"DELETE FROM audit_logs WHERE timestamp < $1::timestamptz\";\n const params: unknown[] = [options.before.toISOString()];\n\n if (options.tableName !== undefined) {\n params.push(options.tableName);\n sql += ` AND table_name = $${params.length}`;\n }\n\n const deletedCount = await db.$executeRawUnsafe(sql, ...params);\n return { deletedCount };\n}\n\n/**\n * Builds WHERE / AND fragments for an optional `since` filter.\n * The positional `$N` reference is derived from `paramCount` so it is always\n * correct regardless of how many params come before it in the query.\n */\nfunction buildSinceFragment(paramCount: number): { where: string; and: string } {\n const ref = `$${paramCount}::timestamptz`;\n return {\n where: ` WHERE timestamp >= ${ref}`,\n and: ` AND timestamp >= ${ref}`,\n };\n}\n\n/**\n * Returns aggregated statistics for the audit_logs table.\n * Runs six queries in parallel; each optionally filtered by `since`.\n */\nexport async function getStats(\n options: { since?: Date },\n db: QueryDb,\n): Promise<AuditStats> {\n const params: unknown[] = [];\n if (options.since !== undefined) {\n params.push(options.since.toISOString());\n }\n const since =\n params.length > 0\n ? buildSinceFragment(params.length)\n : { where: \"\", and: \"\" };\n\n // Query 1: totalLogs + tablesAudited\n const summaryQuery = db.$queryRawUnsafe(\n `SELECT COUNT(*) AS total_logs, COUNT(DISTINCT table_name) AS tables_audited FROM audit_logs${since.where}`,\n ...params,\n ) as Promise<Array<{ total_logs: unknown; tables_audited: unknown }>>;\n\n // Query 2: eventsPerDay\n const eventsPerDayQuery = db.$queryRawUnsafe(\n `SELECT to_char(date_trunc('day', timestamp), 'YYYY-MM-DD') AS date, COUNT(*) AS count ` +\n `FROM audit_logs${since.where} ` +\n `GROUP BY date_trunc('day', timestamp) ORDER BY date_trunc('day', timestamp) LIMIT 365`,\n ...params,\n ) as Promise<Array<{ date: unknown; count: unknown }>>;\n\n // Query 3: topActors (NULL actors excluded)\n const topActorsQuery = db.$queryRawUnsafe(\n `SELECT actor_id, COUNT(*) AS count FROM audit_logs ` +\n `WHERE actor_id IS NOT NULL${since.and} ` +\n `GROUP BY actor_id ORDER BY COUNT(*) DESC LIMIT 10`,\n ...params,\n ) as Promise<Array<{ actor_id: unknown; count: unknown }>>;\n\n // Query 4: topTables\n const topTablesQuery = db.$queryRawUnsafe(\n `SELECT table_name, COUNT(*) AS count FROM audit_logs${since.where} ` +\n `GROUP BY table_name ORDER BY COUNT(*) DESC LIMIT 10`,\n ...params,\n ) as Promise<Array<{ table_name: unknown; count: unknown }>>;\n\n // Query 5: operationBreakdown\n const operationQuery = db.$queryRawUnsafe(\n `SELECT operation, COUNT(*) AS count FROM audit_logs${since.where} ` +\n `GROUP BY operation`,\n ...params,\n ) as Promise<Array<{ operation: unknown; count: unknown }>>;\n\n // Query 6: severityBreakdown (NULL severities excluded)\n const severityQuery = db.$queryRawUnsafe(\n `SELECT severity, COUNT(*) AS count FROM audit_logs ` +\n `WHERE severity IS NOT NULL${since.and} ` +\n `GROUP BY severity`,\n ...params,\n ) as Promise<Array<{ severity: unknown; count: unknown }>>;\n\n const [summaryRows, eventsPerDayRows, topActorsRows, topTablesRows, operationRows, severityRows] =\n await Promise.all([summaryQuery, eventsPerDayQuery, topActorsQuery, topTablesQuery, operationQuery, severityQuery]);\n\n return assembleStats(\n summaryRows.map((r) => ({ totalLogs: r.total_logs, tablesAudited: r.tables_audited })),\n eventsPerDayRows,\n topActorsRows.map((r) => ({ actorId: r.actor_id, count: r.count })),\n topTablesRows.map((r) => ({ tableName: r.table_name, count: r.count })),\n operationRows,\n severityRows,\n );\n}\n","import type { AuditLog } from \"@usebetterdev/audit-core\";\nimport { isAuditOperation, isAuditSeverity } from \"@usebetterdev/audit-core\";\n\n/**\n * Converts a raw database row (from $queryRawUnsafe) to a camelCase AuditLog.\n * Handles null → undefined mapping and validates enum fields at the boundary.\n *\n * Note: `auditLogToRow` is intentionally absent. The write path lives in\n * `adapter.ts` and uses `$executeRawUnsafe` with explicit `::uuid` / `::jsonb`\n * casts that cannot be expressed through a generic row-mapper function.\n */\nexport function rowToAuditLog(row: Record<string, unknown>): AuditLog {\n const { id, timestamp, table_name, operation, record_id } = row;\n\n if (typeof id !== \"string\") {\n throw new Error(\"rowToAuditLog: id must be a string\");\n }\n if (!(timestamp instanceof Date)) {\n throw new Error(\"rowToAuditLog: timestamp must be a Date\");\n }\n if (typeof table_name !== \"string\") {\n throw new Error(\"rowToAuditLog: table_name must be a string\");\n }\n if (typeof operation !== \"string\") {\n throw new Error(\"rowToAuditLog: operation must be a string\");\n }\n if (typeof record_id !== \"string\") {\n throw new Error(\"rowToAuditLog: record_id must be a string\");\n }\n\n if (!isAuditOperation(operation)) {\n throw new Error(\n `Invalid audit operation: \"${operation}\". Expected one of: INSERT, UPDATE, DELETE`,\n );\n }\n\n const log: AuditLog = {\n id,\n timestamp,\n tableName: table_name,\n operation,\n recordId: record_id,\n };\n\n const {\n actor_id,\n before_data,\n after_data,\n diff,\n label,\n description,\n severity,\n compliance,\n notify,\n reason,\n metadata,\n redacted_fields,\n } = row;\n\n if (actor_id !== null && actor_id !== undefined) {\n log.actorId = String(actor_id);\n }\n if (before_data !== null && before_data !== undefined) {\n log.beforeData = before_data as Record<string, unknown>;\n }\n if (after_data !== null && after_data !== undefined) {\n log.afterData = after_data as Record<string, unknown>;\n }\n if (diff !== null && diff !== undefined) {\n log.diff = diff as { changedFields: string[] };\n }\n if (label !== null && label !== undefined) {\n log.label = String(label);\n }\n if (description !== null && description !== undefined) {\n log.description = String(description);\n }\n if (severity !== null && severity !== undefined) {\n const s = String(severity);\n if (!isAuditSeverity(s)) {\n throw new Error(\n `Invalid audit severity: \"${s}\". Expected one of: low, medium, high, critical`,\n );\n }\n log.severity = s;\n }\n if (compliance !== null && compliance !== undefined) {\n log.compliance = compliance as string[];\n }\n if (notify !== null && notify !== undefined) {\n log.notify = Boolean(notify);\n }\n if (reason !== null && reason !== undefined) {\n log.reason = String(reason);\n }\n if (metadata !== null && metadata !== undefined) {\n log.metadata = metadata as Record<string, unknown>;\n }\n if (redacted_fields !== null && redacted_fields !== undefined) {\n log.redactedFields = redacted_fields as string[];\n }\n\n return log;\n}\n","import type {\n AuditDatabaseAdapter,\n AuditLog,\n AuditQuerySpec,\n AuditQueryResult,\n AuditStats,\n} from \"@usebetterdev/audit-core\";\nimport { queryLogs, getLogById, getStats, purgeLogs } from \"./query.js\";\n\n/**\n * Minimal duck-typed interface for a Prisma client that supports raw query execution.\n * Avoids a hard dependency on the generated `@prisma/client`.\n */\nexport interface PrismaClientWithRaw {\n $executeRawUnsafe(query: string, ...params: unknown[]): Promise<number>;\n $queryRawUnsafe(query: string, ...params: unknown[]): Promise<unknown>;\n}\n\n/**\n * Creates an `AuditDatabaseAdapter` backed by a Prisma client.\n *\n * Implements `writeLog` via `$executeRawUnsafe` for precise control over\n * PostgreSQL type casts (::uuid, ::timestamptz, ::jsonb) and null handling.\n *\n * Implements `queryLogs`, `getLogById`, `getStats`, and `purgeLogs` via\n * parameterized raw SQL — all user inputs are bound parameters, never interpolated.\n */\nexport function prismaAuditAdapter(db: PrismaClientWithRaw): AuditDatabaseAdapter {\n return {\n async writeLog(log: AuditLog): Promise<void> {\n await db.$executeRawUnsafe(\n `INSERT INTO audit_logs (\n id, timestamp, table_name, operation, record_id,\n actor_id, before_data, after_data, diff,\n label, description, severity, compliance,\n notify, reason, metadata, redacted_fields\n ) VALUES (\n $1::uuid, $2::timestamptz, $3, $4, $5,\n $6, $7::jsonb, $8::jsonb, $9::jsonb,\n $10, $11, $12, $13::jsonb,\n $14, $15, $16::jsonb, $17::jsonb\n )`,\n log.id,\n log.timestamp.toISOString(),\n log.tableName,\n log.operation,\n log.recordId,\n log.actorId ?? null,\n jsonOrNull(log.beforeData),\n jsonOrNull(log.afterData),\n jsonOrNull(log.diff),\n log.label ?? null,\n log.description ?? null,\n log.severity ?? null,\n jsonOrNull(log.compliance),\n log.notify ?? null,\n log.reason ?? null,\n jsonOrNull(log.metadata),\n jsonOrNull(log.redactedFields),\n );\n },\n\n async queryLogs(spec: AuditQuerySpec): Promise<AuditQueryResult> {\n return queryLogs(spec, db);\n },\n\n async getLogById(id: string): Promise<AuditLog | null> {\n return getLogById(id, db);\n },\n\n async getStats(options?: { since?: Date }): Promise<AuditStats> {\n return getStats(options ?? {}, db);\n },\n\n async purgeLogs(options: {\n before: Date;\n tableName?: string;\n }): Promise<{ deletedCount: number }> {\n return purgeLogs(options, db);\n },\n };\n}\n\nfunction jsonOrNull(value: unknown): string | null {\n if (value === undefined || value === null) {\n return null;\n }\n return JSON.stringify(value);\n}\n","import type { CaptureLogInput, AuditOperation } from \"@usebetterdev/audit-core\";\nimport { getAuditContext } from \"@usebetterdev/audit-core\";\nimport { getAuditOperation } from \"./action-map.js\";\nimport { buildTableNameResolver } from \"./model-map.js\";\nimport { getTxClient } from \"./tx-store.js\";\n\n/**\n * Minimal duck-typed interface for a Prisma client that supports `$extends`.\n * Avoids a hard dependency on the generated `@prisma/client`.\n */\nexport interface PrismaClientLike {\n $extends(extension: PrismaExtensionDefinition): unknown;\n}\n\n/**\n * Shape of the Prisma `$extends` query extension definition.\n * We only type what we need — the full shape lives in `@prisma/client/extension`.\n */\ninterface PrismaExtensionDefinition {\n query: {\n $allModels: {\n $allOperations: (params: {\n model: string;\n operation: string;\n args: unknown;\n query: (args: unknown) => Promise<unknown>;\n }) => Promise<unknown>;\n };\n };\n}\n\n/**\n * Minimal duck-typed interface for a Prisma model delegate.\n * Used internally to issue before-state lookups prior to mutations.\n * Any real Prisma client's model delegate satisfies this.\n */\ninterface PrismaModelDelegate {\n findUnique(args: { where: unknown }): Promise<unknown>;\n findMany(args: { where?: unknown; take?: number }): Promise<unknown[]>;\n}\n\n/**\n * Represents the before-state captured prior to a mutation.\n *\n * - `\"none\"`: no before-state needed (create operations, single delete whose\n * result already contains the deleted record)\n * - `\"single\"`: one record looked up via `findUnique` (update, upsert)\n * - `\"many\"`: multiple records looked up via `findMany` (updateMany, deleteMany)\n */\ntype BeforeState =\n | { type: \"none\" }\n | { type: \"single\"; row: Record<string, unknown> | undefined }\n | { type: \"many\"; rows: Record<string, unknown>[] };\n\nexport type BulkMode = \"per-row\" | \"bulk\";\n\nexport interface WithAuditExtensionOptions {\n /**\n * How to handle bulk write operations (`createMany`, `updateMany`, `deleteMany`).\n *\n * - `\"per-row\"` (default): for `createMany` and `createManyAndReturn`, fires\n * one audit entry per item in `args.data`. For `updateMany` and `deleteMany`,\n * fires one entry per row captured in the before-state lookup, or a single\n * bulk entry if no rows are found.\n * - `\"bulk\"`: fires a single audit entry for the entire operation.\n */\n bulkMode?: BulkMode;\n /**\n * Called when audit capture fails. Falls back to `console.error` if not set.\n * Errors are always swallowed — audit failures never propagate to callers.\n */\n onError?: (error: unknown) => void;\n /**\n * Extra structured data merged into every audit log entry produced by this\n * extension. Useful for tagging logs with the adapter name or environment.\n *\n * @example\n * ```ts\n * withAuditExtension(prisma, audit.captureLog, { metadata: { adapter: \"prisma\" } })\n * ```\n */\n metadata?: Record<string, unknown>;\n /**\n * Maps a Prisma model name to the `tableName` stored in audit logs.\n *\n * By default the extension auto-detects the SQL table name from Prisma's\n * `_runtimeDataModel` (populated by `@@map` directives). If the runtime\n * metadata is unavailable, the model name is used as-is.\n *\n * When provided, this function takes full precedence over auto-detection.\n *\n * @example\n * ```ts\n * withAuditExtension(prisma, captureLog, {\n * tableNameTransform: (name) => name.toLowerCase(),\n * })\n * ```\n */\n tableNameTransform?: (modelName: string) => string;\n /**\n * Table names (SQL names, after `tableNameTransform`) to skip before-state\n * capture for. No extra `findUnique` or `findMany` is issued for these tables.\n *\n * Useful for high-volume tables where the additional SELECT is too expensive.\n *\n * @example\n * ```ts\n * withAuditExtension(prisma, captureLog, {\n * skipBeforeCapture: [\"events\", \"metrics\"],\n * })\n * ```\n */\n skipBeforeCapture?: string[];\n /**\n * Maximum number of rows fetched by the before-state `findMany` for\n * `updateMany` and `deleteMany` operations (default: `100`).\n *\n * When the matched row count exceeds this limit the before-state lookup is\n * skipped and a single bulk audit entry is written instead — preventing\n * unbounded memory consumption on large batch operations.\n *\n * Set to `Infinity` to disable the limit (not recommended in production).\n *\n * @example\n * ```ts\n * withAuditExtension(prisma, captureLog, { maxBeforeStateRows: 50 })\n * ```\n */\n maxBeforeStateRows?: number;\n}\n\n/**\n * Wraps a Prisma client with a `$extends` query extension that calls\n * `captureLog` after each successful mutation.\n *\n * Returns a new extended client of the same type `T`. Use this extended\n * client in place of the original — all queries behave identically.\n *\n * Actor identity is read automatically from the current `AuditContext`\n * (set via `audit.withContext` or `runWithAuditContext` from audit-core).\n *\n * For `update` and `upsert`, a `findUnique` is issued before the mutation\n * to capture `before_data`. For `updateMany` and `deleteMany` in `\"per-row\"`\n * mode, a `findMany` is issued before the mutation to capture per-row state.\n * Pass `skipBeforeCapture` to opt out for specific high-volume tables.\n *\n * Filtering by audited tables is NOT done here — `captureLog` (from\n * `betterAudit`) already skips tables not in `auditTables`.\n *\n * @example\n * ```ts\n * // auditTables must use SQL table names (auto-detected from @@map directives)\n * const audit = betterAudit({ database: prismaAuditAdapter(prisma), auditTables: [\"users\"] });\n * const auditedPrisma = withAuditExtension(prisma, audit.captureLog);\n * // Use auditedPrisma everywhere — mutations are transparently logged\n * ```\n */\nexport function withAuditExtension<T extends PrismaClientLike>(\n prisma: T,\n captureLog: (input: CaptureLogInput) => Promise<void>,\n options: WithAuditExtensionOptions = {},\n): T {\n const bulkMode = options.bulkMode ?? \"per-row\";\n const handleError = buildErrorHandler(options.onError);\n const metadata = options.metadata;\n const skipBeforeCapture = options.skipBeforeCapture;\n const maxBeforeStateRows = options.maxBeforeStateRows ?? 100;\n const resolveTableName = buildTableNameResolver(prisma, options.tableNameTransform);\n\n const extended = prisma.$extends({\n query: {\n $allModels: {\n async $allOperations({\n model,\n operation,\n args,\n query,\n }: {\n model: string;\n operation: string;\n args: unknown;\n query: (args: unknown) => Promise<unknown>;\n }) {\n const auditOp = getAuditOperation(operation);\n if (auditOp === undefined) {\n return query(args);\n }\n\n const tableName = resolveTableName(model);\n const shouldSkipBefore =\n skipBeforeCapture !== undefined && skipBeforeCapture.includes(tableName);\n\n const beforeState: BeforeState = shouldSkipBefore\n ? { type: \"none\" }\n : await fetchBeforeState({ prisma, model, operation, args, handleError, maxBeforeStateRows });\n\n const result = await query(args);\n\n try {\n await fireCaptureLog({\n tableName,\n operation,\n auditOp,\n args,\n result,\n beforeState,\n captureLog,\n bulkMode,\n metadata,\n actorId: getAuditContext()?.actorId,\n });\n } catch (err) {\n handleError(err);\n }\n\n return result;\n },\n },\n },\n });\n\n // The $extends return type is `unknown` — casting back to T is safe because\n // the extension only adds transparent interception; it does not change the query API.\n return extended as unknown as T;\n}\n\n// ---------------------------------------------------------------------------\n// Before-state fetching\n// ---------------------------------------------------------------------------\n\ninterface FetchBeforeStateParams {\n prisma: PrismaClientLike;\n model: string;\n operation: string;\n args: unknown;\n handleError: (err: unknown) => void;\n maxBeforeStateRows: number;\n}\n\n/**\n * Issues a `findUnique` or `findMany` before the mutation to capture the\n * record state that will be stored as `before_data` in the audit log.\n *\n * Returns `{ type: \"none\" }` for operations that don't need a pre-query:\n * - `create` / `createMany` / `createManyAndReturn`: no before-state exists\n * - `delete`: Prisma returns the deleted record as the mutation result\n */\nasync function fetchBeforeState({\n prisma,\n model,\n operation,\n args,\n handleError,\n maxBeforeStateRows,\n}: FetchBeforeStateParams): Promise<BeforeState> {\n if (\n operation === \"create\" ||\n operation === \"createMany\" ||\n operation === \"createManyAndReturn\" ||\n operation === \"delete\"\n ) {\n return { type: \"none\" };\n }\n\n const delegate = getModelDelegate(getTxClient() ?? prisma, model);\n if (delegate === undefined) {\n return { type: \"none\" };\n }\n\n if (operation === \"update\" || operation === \"upsert\") {\n const where = getArgsWhere(args);\n if (where === undefined) {\n return { type: \"single\", row: undefined };\n }\n try {\n const found = await delegate.findUnique({ where });\n return { type: \"single\", row: toRecord(found) };\n } catch (err) {\n handleError(err);\n return { type: \"single\", row: undefined };\n }\n }\n\n if (operation === \"updateMany\" || operation === \"deleteMany\") {\n const where = getArgsWhere(args);\n try {\n const rows = await delegate.findMany({ where: where ?? {}, take: maxBeforeStateRows + 1 });\n if (rows.length > maxBeforeStateRows) {\n // Too many rows to capture individually — fall back to a single bulk entry.\n return { type: \"none\" };\n }\n const records = rows\n .map((r) => toRecord(r))\n .filter((r): r is Record<string, unknown> => r !== undefined);\n return { type: \"many\", rows: records };\n } catch (err) {\n handleError(err);\n return { type: \"many\", rows: [] };\n }\n }\n\n return { type: \"none\" };\n}\n\n// ---------------------------------------------------------------------------\n// captureLog dispatch\n// ---------------------------------------------------------------------------\n\ninterface FireCaptureLogParams {\n tableName: string;\n operation: string;\n auditOp: AuditOperation;\n args: unknown;\n result: unknown;\n beforeState: BeforeState;\n captureLog: (input: CaptureLogInput) => Promise<void>;\n bulkMode: BulkMode;\n metadata: Record<string, unknown> | undefined;\n actorId: string | undefined;\n}\n\nasync function fireCaptureLog({\n tableName,\n operation,\n auditOp,\n args,\n result,\n beforeState,\n captureLog,\n bulkMode,\n metadata,\n actorId,\n}: FireCaptureLogParams): Promise<void> {\n if (operation === \"createMany\") {\n await handleCreateMany({ tableName, auditOp, args, captureLog, bulkMode, metadata, actorId });\n return;\n }\n\n if (operation === \"createManyAndReturn\") {\n await handleCreateManyAndReturn({ tableName, auditOp, result, captureLog, bulkMode, metadata, actorId });\n return;\n }\n\n if (operation === \"updateMany\") {\n await handleUpdateMany({ tableName, auditOp, beforeState, captureLog, bulkMode, metadata, actorId });\n return;\n }\n\n if (operation === \"deleteMany\") {\n await handleDeleteMany({ tableName, auditOp, beforeState, captureLog, bulkMode, metadata, actorId });\n return;\n }\n\n // Single-record operations: create, update, upsert, delete\n const row = toRecord(result);\n const recordId = (row !== undefined ? extractId(row) : undefined) ?? \"unknown\";\n\n if (operation === \"upsert\") {\n // A before-state row means the record existed → UPDATE path.\n // No before-state row means the record was created → INSERT path.\n const before = beforeState.type === \"single\" ? beforeState.row : undefined;\n const effectiveOp: AuditOperation = before !== undefined ? \"UPDATE\" : \"INSERT\";\n await captureLog({\n tableName,\n operation: effectiveOp,\n recordId,\n ...(before !== undefined && { before }),\n ...(row !== undefined && { after: row }),\n ...(metadata !== undefined && { metadata }),\n ...(actorId !== undefined && { actorId }),\n });\n return;\n }\n\n if (auditOp === \"INSERT\") {\n await captureLog({\n tableName,\n operation: auditOp,\n recordId,\n ...(row !== undefined && { after: row }),\n ...(metadata !== undefined && { metadata }),\n ...(actorId !== undefined && { actorId }),\n });\n return;\n }\n\n if (auditOp === \"UPDATE\") {\n const before = beforeState.type === \"single\" ? beforeState.row : undefined;\n await captureLog({\n tableName,\n operation: auditOp,\n recordId,\n ...(before !== undefined && { before }),\n ...(row !== undefined && { after: row }),\n ...(metadata !== undefined && { metadata }),\n ...(actorId !== undefined && { actorId }),\n });\n return;\n }\n\n // DELETE: Prisma returns the deleted record as the result — use it as before_data.\n await captureLog({\n tableName,\n operation: auditOp,\n recordId,\n ...(row !== undefined && { before: row }),\n ...(metadata !== undefined && { metadata }),\n ...(actorId !== undefined && { actorId }),\n });\n}\n\n// ---------------------------------------------------------------------------\n// Per-operation handlers\n// ---------------------------------------------------------------------------\n\ninterface HandleCreateManyParams {\n tableName: string;\n auditOp: AuditOperation;\n args: unknown;\n captureLog: (input: CaptureLogInput) => Promise<void>;\n bulkMode: BulkMode;\n metadata: Record<string, unknown> | undefined;\n actorId: string | undefined;\n}\n\nasync function handleCreateMany({\n tableName,\n auditOp,\n args,\n captureLog,\n bulkMode,\n metadata,\n actorId,\n}: HandleCreateManyParams): Promise<void> {\n if (bulkMode === \"bulk\") {\n await captureLog({\n tableName,\n operation: auditOp,\n recordId: \"unknown\",\n ...(metadata !== undefined && { metadata }),\n ...(actorId !== undefined && { actorId }),\n });\n return;\n }\n\n // per-row: fire one entry per item in args.data\n const rows = getArgsData(args);\n if (rows.length === 0) {\n await captureLog({\n tableName,\n operation: auditOp,\n recordId: \"unknown\",\n ...(metadata !== undefined && { metadata }),\n ...(actorId !== undefined && { actorId }),\n });\n return;\n }\n\n await Promise.all(\n rows.map((row) => {\n const record = toRecord(row);\n const recordId = (record !== undefined ? extractId(record) : undefined) ?? \"unknown\";\n return captureLog({\n tableName,\n operation: auditOp,\n recordId,\n ...(record !== undefined && { after: record }),\n ...(metadata !== undefined && { metadata }),\n ...(actorId !== undefined && { actorId }),\n });\n }),\n );\n}\n\ninterface HandleCreateManyAndReturnParams {\n tableName: string;\n auditOp: AuditOperation;\n result: unknown;\n captureLog: (input: CaptureLogInput) => Promise<void>;\n bulkMode: BulkMode;\n metadata: Record<string, unknown> | undefined;\n actorId: string | undefined;\n}\n\nasync function handleCreateManyAndReturn({\n tableName,\n auditOp,\n result,\n captureLog,\n bulkMode,\n metadata,\n actorId,\n}: HandleCreateManyAndReturnParams): Promise<void> {\n if (bulkMode === \"bulk\") {\n await captureLog({\n tableName,\n operation: auditOp,\n recordId: \"unknown\",\n ...(metadata !== undefined && { metadata }),\n ...(actorId !== undefined && { actorId }),\n });\n return;\n }\n\n const rows = Array.isArray(result) ? result : [];\n if (rows.length === 0) {\n await captureLog({\n tableName,\n operation: auditOp,\n recordId: \"unknown\",\n ...(metadata !== undefined && { metadata }),\n ...(actorId !== undefined && { actorId }),\n });\n return;\n }\n\n await Promise.all(\n rows.map((row) => {\n const record = toRecord(row);\n const recordId = (record !== undefined ? extractId(record) : undefined) ?? \"unknown\";\n return captureLog({\n tableName,\n operation: auditOp,\n recordId,\n ...(record !== undefined && { after: record }),\n ...(metadata !== undefined && { metadata }),\n ...(actorId !== undefined && { actorId }),\n });\n }),\n );\n}\n\ninterface HandleUpdateManyParams {\n tableName: string;\n auditOp: AuditOperation;\n beforeState: BeforeState;\n captureLog: (input: CaptureLogInput) => Promise<void>;\n bulkMode: BulkMode;\n metadata: Record<string, unknown> | undefined;\n actorId: string | undefined;\n}\n\nasync function handleUpdateMany({\n tableName,\n auditOp,\n beforeState,\n captureLog,\n bulkMode,\n metadata,\n actorId,\n}: HandleUpdateManyParams): Promise<void> {\n if (bulkMode === \"bulk\") {\n await captureLog({\n tableName,\n operation: auditOp,\n recordId: \"unknown\",\n ...(metadata !== undefined && { metadata }),\n ...(actorId !== undefined && { actorId }),\n });\n return;\n }\n\n // per-row: fire one entry per row captured before the mutation\n const rows = beforeState.type === \"many\" ? beforeState.rows : [];\n if (rows.length === 0) {\n await captureLog({\n tableName,\n operation: auditOp,\n recordId: \"unknown\",\n ...(metadata !== undefined && { metadata }),\n ...(actorId !== undefined && { actorId }),\n });\n return;\n }\n\n await Promise.all(\n rows.map((row) => {\n const recordId = extractId(row) ?? \"unknown\";\n return captureLog({\n tableName,\n operation: auditOp,\n recordId,\n before: row,\n ...(metadata !== undefined && { metadata }),\n ...(actorId !== undefined && { actorId }),\n });\n }),\n );\n}\n\ninterface HandleDeleteManyParams {\n tableName: string;\n auditOp: AuditOperation;\n beforeState: BeforeState;\n captureLog: (input: CaptureLogInput) => Promise<void>;\n bulkMode: BulkMode;\n metadata: Record<string, unknown> | undefined;\n actorId: string | undefined;\n}\n\nasync function handleDeleteMany({\n tableName,\n auditOp,\n beforeState,\n captureLog,\n bulkMode,\n metadata,\n actorId,\n}: HandleDeleteManyParams): Promise<void> {\n if (bulkMode === \"bulk\") {\n await captureLog({\n tableName,\n operation: auditOp,\n recordId: \"unknown\",\n ...(metadata !== undefined && { metadata }),\n ...(actorId !== undefined && { actorId }),\n });\n return;\n }\n\n // per-row: fire one entry per row captured before deletion\n const rows = beforeState.type === \"many\" ? beforeState.rows : [];\n if (rows.length === 0) {\n await captureLog({\n tableName,\n operation: auditOp,\n recordId: \"unknown\",\n ...(metadata !== undefined && { metadata }),\n ...(actorId !== undefined && { actorId }),\n });\n return;\n }\n\n await Promise.all(\n rows.map((row) => {\n const recordId = extractId(row) ?? \"unknown\";\n return captureLog({\n tableName,\n operation: auditOp,\n recordId,\n before: row,\n ...(metadata !== undefined && { metadata }),\n ...(actorId !== undefined && { actorId }),\n });\n }),\n );\n}\n\n// ---------------------------------------------------------------------------\n// Helpers\n// ---------------------------------------------------------------------------\n\nfunction buildErrorHandler(\n onError: ((error: unknown) => void) | undefined,\n): (error: unknown) => void {\n return (error: unknown) => {\n try {\n if (onError !== undefined) {\n onError(error);\n } else {\n const msg = error instanceof Error ? error.message : String(error);\n console.error(`audit-prisma: capture failed — ${msg}`);\n }\n } catch {\n // onError itself threw — swallow to never break mutations.\n }\n };\n}\n\n/**\n * Returns true when `value` quacks like a Prisma model delegate with\n * `findUnique` and `findMany` methods.\n */\nfunction isModelDelegate(value: unknown): value is PrismaModelDelegate {\n if (value === null || value === undefined || typeof value !== \"object\") {\n return false;\n }\n return (\n \"findUnique\" in value &&\n \"findMany\" in value &&\n typeof Reflect.get(value, \"findUnique\") === \"function\" &&\n typeof Reflect.get(value, \"findMany\") === \"function\"\n );\n}\n\n/**\n * Returns the model delegate for `model` (e.g. `prisma.user` for model `\"User\"`)\n * by lowercasing the first character and doing a dynamic property lookup.\n * Returns `undefined` when the delegate is not found or doesn't have the\n * expected shape — the caller falls back to `{ type: \"none\" }` before-state.\n */\nfunction getModelDelegate(\n client: unknown,\n model: string,\n): PrismaModelDelegate | undefined {\n const lowerModel = model.charAt(0).toLowerCase() + model.slice(1);\n const value: unknown = Reflect.get(Object(client), lowerModel);\n if (isModelDelegate(value)) {\n return value;\n }\n return undefined;\n}\n\nfunction extractId(record: Record<string, unknown>): string | undefined {\n const id = record[\"id\"];\n if (id !== undefined && id !== null) {\n return String(id);\n }\n return undefined;\n}\n\nfunction toRecord(value: unknown): Record<string, unknown> | undefined {\n if (value !== null && value !== undefined && typeof value === \"object\" && !Array.isArray(value)) {\n return value as Record<string, unknown>;\n }\n return undefined;\n}\n\nfunction getArgsData(args: unknown): unknown[] {\n if (args !== null && typeof args === \"object\" && \"data\" in args) {\n const data = Reflect.get(args, \"data\");\n if (Array.isArray(data)) {\n return data;\n }\n }\n return [];\n}\n\n/**\n * Extracts `args.where` for before-state lookups. Returns `undefined` when\n * the argument object has no `where` property.\n */\nfunction getArgsWhere(args: unknown): unknown {\n if (args !== null && typeof args === \"object\" && \"where\" in args) {\n return Reflect.get(args, \"where\");\n }\n return undefined;\n}\n","import type { AuditOperation } from \"@usebetterdev/audit-core\";\n\n/**\n * Maps Prisma query action names to AuditOperation values.\n * Actions not in this map are read-only queries and are not audited.\n */\nexport const ACTION_MAP = {\n create: \"INSERT\",\n createMany: \"INSERT\",\n createManyAndReturn: \"INSERT\",\n update: \"UPDATE\",\n updateMany: \"UPDATE\",\n upsert: \"UPDATE\",\n delete: \"DELETE\",\n deleteMany: \"DELETE\",\n} as const satisfies Record<string, AuditOperation>;\n\nexport type MutableAction = keyof typeof ACTION_MAP;\n\nfunction isMutableAction(action: string): action is MutableAction {\n return action in ACTION_MAP;\n}\n\n/**\n * Returns the AuditOperation for a Prisma action name, or `undefined` for\n * read-only operations (findMany, findUnique, count, aggregate, etc.).\n */\nexport function getAuditOperation(action: string): AuditOperation | undefined {\n if (isMutableAction(action)) {\n return ACTION_MAP[action];\n }\n return undefined;\n}\n","/**\n * Runtime model-to-table mapping for Prisma clients that expose\n * `_runtimeDataModel.models`. This lets the audit extension auto-detect\n * the SQL table name (from `@@map`) without user configuration.\n */\n\ninterface RuntimeModel {\n dbName: string | null;\n}\n\ninterface RuntimeDataModel {\n models: Record<string, RuntimeModel>;\n}\n\ninterface PrismaWithRuntimeDataModel {\n _runtimeDataModel: RuntimeDataModel;\n}\n\n/**\n * Type guard: checks whether `value` exposes Prisma's `_runtimeDataModel.models`.\n */\nfunction hasPrismaRuntimeDataModel(value: object): value is PrismaWithRuntimeDataModel {\n if (!(\"_runtimeDataModel\" in value)) {\n return false;\n }\n\n const { _runtimeDataModel: rdm } = value;\n if (rdm === null || typeof rdm !== \"object\") {\n return false;\n }\n\n return \"models\" in rdm;\n}\n\n/**\n * Returns a `Record<string, string>` mapping Prisma model names to their\n * SQL table names (from `@@map`). Models without `@@map` map to themselves.\n *\n * Returns `undefined` if the client does not expose `_runtimeDataModel`.\n */\nexport function prismaModelMap(prisma: object): Record<string, string> | undefined {\n if (!hasPrismaRuntimeDataModel(prisma)) {\n return undefined;\n }\n\n const result: Record<string, string> = {};\n for (const [modelName, meta] of Object.entries(prisma._runtimeDataModel.models)) {\n result[modelName] = meta.dbName ?? modelName;\n }\n return result;\n}\n\n/**\n * Builds a function that resolves a Prisma model name to a table name.\n *\n * Resolution order:\n * 1. If `transform` is provided, use it (full user control).\n * 2. Else if the client exposes `_runtimeDataModel`, use `dbName ?? modelName`.\n * 3. Else return the model name as-is (safe fallback).\n */\nexport function buildTableNameResolver(\n prisma: object,\n transform: ((modelName: string) => string) | undefined,\n): (modelName: string) => string {\n if (transform !== undefined) {\n return transform;\n }\n\n const map = prismaModelMap(prisma);\n if (map !== undefined) {\n return (modelName: string) => map[modelName] ?? modelName;\n }\n\n return (modelName: string) => modelName;\n}\n","import { AsyncLocalStorage } from \"node:async_hooks\";\n\n/**\n * Stores the current Prisma transaction client for the duration of a\n * tenant-scoped request.\n *\n * When the audit extension issues a `findUnique` to capture before-state,\n * it queries the outer (non-transaction) Prisma client by default. That\n * connection has no `SET LOCAL app.current_tenant` active, so RLS blocks\n * all rows and before-state is lost.\n *\n * Call `runWithTxClient(tx, fn)` inside `prismaAdapter.runWithTenant` (after\n * the `SET LOCAL` statement) to make `tx` available to the extension. The\n * extension reads it via `getTxClient()` and uses it for the `findUnique`\n * instead of the outer client.\n */\nconst txStorage = new AsyncLocalStorage<unknown>();\n\n/**\n * Stores `tx` in the async context for the duration of `fn`.\n *\n * Call this wrapping the inner callback of a Prisma `$transaction` after the\n * tenant `SET LOCAL` has been issued so the audit extension can reuse the\n * same connection for before-state lookups.\n *\n * @example\n * ```ts\n * // Inside your betterTenant adapter wrapper:\n * const adapter = {\n * ...db.adapter,\n * runWithTenant(tenantId, fn) {\n * return db.adapter.runWithTenant(tenantId, (tx) =>\n * runWithTxClient(tx, () => fn(tx))\n * );\n * },\n * };\n * ```\n */\nexport function runWithTxClient<T>(tx: unknown, fn: () => Promise<T>): Promise<T> {\n return txStorage.run(tx, fn);\n}\n\n/**\n * Returns the current transaction client stored by `runWithTxClient`, or\n * `undefined` when called outside a tenant transaction context.\n */\nexport function getTxClient(): unknown {\n return txStorage.getStore();\n}\n"],"mappings":"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","names":["import_audit_core","import_audit_core"]}
package/dist/index.js CHANGED
@@ -1,15 +1,13 @@
1
1
  // src/query.ts
2
- import { parseDuration } from "@usebetterdev/audit-core";
2
+ import {
3
+ encodeCursor,
4
+ decodeCursor,
5
+ interpretFilters,
6
+ assembleStats
7
+ } from "@usebetterdev/audit-core";
3
8
 
4
9
  // src/column-map.ts
5
- var VALID_OPERATIONS = /* @__PURE__ */ new Set(["INSERT", "UPDATE", "DELETE"]);
6
- var VALID_SEVERITIES = /* @__PURE__ */ new Set(["low", "medium", "high", "critical"]);
7
- function isAuditOperation(value) {
8
- return VALID_OPERATIONS.has(value);
9
- }
10
- function isAuditSeverity(value) {
11
- return VALID_SEVERITIES.has(value);
12
- }
10
+ import { isAuditOperation, isAuditSeverity } from "@usebetterdev/audit-core";
13
11
  function rowToAuditLog(row) {
14
12
  const { id, timestamp, table_name, operation, record_id } = row;
15
13
  if (typeof id !== "string") {
@@ -102,140 +100,72 @@ function rowToAuditLog(row) {
102
100
  var DEFAULT_LIMIT = 50;
103
101
  var MAX_LIMIT = 250;
104
102
  var SELECT_COLUMNS = "id, timestamp, table_name, operation, record_id, actor_id, before_data, after_data, diff, label, description, severity, compliance, notify, reason, metadata, redacted_fields";
105
- var UUID_PATTERN = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
106
- function encodeCursor(timestamp, id) {
107
- const payload = JSON.stringify({ t: timestamp.toISOString(), i: id });
108
- return btoa(payload);
109
- }
110
- function decodeCursor(cursor) {
111
- let parsed;
112
- try {
113
- parsed = JSON.parse(atob(cursor));
114
- } catch {
115
- throw new Error("Invalid cursor: failed to decode");
116
- }
117
- if (typeof parsed !== "object" || parsed === null || !("t" in parsed) || !("i" in parsed)) {
118
- throw new Error("Invalid cursor: missing required fields");
119
- }
120
- const { t, i } = parsed;
121
- if (typeof t !== "string" || typeof i !== "string") {
122
- throw new Error("Invalid cursor: fields must be strings");
123
- }
124
- const timestamp = new Date(t);
125
- if (isNaN(timestamp.getTime())) {
126
- throw new Error("Invalid cursor: invalid timestamp");
127
- }
128
- if (!UUID_PATTERN.test(i)) {
129
- throw new Error("Invalid cursor: id must be a valid UUID");
130
- }
131
- return { timestamp, id: i };
132
- }
133
- function escapeLikePattern(input) {
134
- return input.replace(/[%_\\]/g, "\\$&");
135
- }
136
- function resolveTimeFilter(filter) {
137
- if ("date" in filter && filter.date !== void 0) {
138
- return filter.date;
139
- }
140
- if ("duration" in filter && filter.duration !== void 0) {
141
- return parseDuration(filter.duration);
142
- }
143
- throw new Error("TimeFilter must have either date or duration");
144
- }
145
- function toCount(value) {
146
- if (typeof value === "number") {
147
- return value;
148
- }
149
- if (typeof value === "string") {
150
- return Number(value);
151
- }
152
- if (typeof value === "bigint") {
153
- return Number(value);
154
- }
155
- return 0;
156
- }
157
103
  function addParam(state, value) {
158
104
  state.params.push(value);
159
105
  return `$${state.params.length}`;
160
106
  }
161
- function buildWhereClause(filters, cursor, sortOrder) {
162
- const state = { fragments: [], params: [] };
163
- if (filters.resource !== void 0) {
164
- const p = addParam(state, filters.resource.tableName);
165
- state.fragments.push(`table_name = ${p}`);
166
- if (filters.resource.recordId !== void 0) {
167
- const r = addParam(state, filters.resource.recordId);
168
- state.fragments.push(`record_id = ${r}`);
107
+ var FIELD_COLUMN_NAMES = {
108
+ tableName: "table_name",
109
+ recordId: "record_id",
110
+ actorId: "actor_id",
111
+ severity: "severity",
112
+ operation: "operation"
113
+ };
114
+ function mapCondition(state, condition) {
115
+ switch (condition.kind) {
116
+ case "eq": {
117
+ const col = FIELD_COLUMN_NAMES[condition.field];
118
+ const p = addParam(state, condition.value);
119
+ state.fragments.push(`${col} = ${p}`);
120
+ break;
169
121
  }
170
- }
171
- if (filters.actorIds !== void 0 && filters.actorIds.length > 0) {
172
- if (filters.actorIds.length === 1) {
173
- const first = filters.actorIds[0];
174
- if (first !== void 0) {
175
- const p = addParam(state, first);
176
- state.fragments.push(`actor_id = ${p}`);
177
- }
178
- } else {
179
- const p = addParam(state, filters.actorIds);
180
- state.fragments.push(`actor_id = ANY(${p}::text[])`);
122
+ case "in": {
123
+ const col = FIELD_COLUMN_NAMES[condition.field];
124
+ const p = addParam(state, condition.values);
125
+ state.fragments.push(`${col} = ANY(${p}::text[])`);
126
+ break;
181
127
  }
182
- }
183
- if (filters.severities !== void 0 && filters.severities.length > 0) {
184
- if (filters.severities.length === 1) {
185
- const first = filters.severities[0];
186
- if (first !== void 0) {
187
- const p = addParam(state, first);
188
- state.fragments.push(`severity = ${p}`);
189
- }
190
- } else {
191
- const p = addParam(state, filters.severities);
192
- state.fragments.push(`severity = ANY(${p}::text[])`);
128
+ case "timestampGte": {
129
+ const p = addParam(state, condition.value.toISOString());
130
+ state.fragments.push(`timestamp >= ${p}::timestamptz`);
131
+ break;
193
132
  }
194
- }
195
- if (filters.operations !== void 0 && filters.operations.length > 0) {
196
- if (filters.operations.length === 1) {
197
- const first = filters.operations[0];
198
- if (first !== void 0) {
199
- const p = addParam(state, first);
200
- state.fragments.push(`operation = ${p}`);
133
+ case "timestampLte": {
134
+ const p = addParam(state, condition.value.toISOString());
135
+ state.fragments.push(`timestamp <= ${p}::timestamptz`);
136
+ break;
137
+ }
138
+ case "search": {
139
+ const p = addParam(state, condition.pattern);
140
+ state.fragments.push(`(label ILIKE ${p} OR description ILIKE ${p})`);
141
+ break;
142
+ }
143
+ case "compliance": {
144
+ const p = addParam(state, JSON.stringify(condition.tags));
145
+ state.fragments.push(`compliance @> ${p}::jsonb`);
146
+ break;
147
+ }
148
+ case "cursor": {
149
+ const tRef = addParam(state, condition.timestamp.toISOString());
150
+ const iRef = addParam(state, condition.id);
151
+ if (condition.sortOrder === "asc") {
152
+ state.fragments.push(
153
+ `(timestamp > ${tRef}::timestamptz OR (timestamp = ${tRef}::timestamptz AND id::text > ${iRef}))`
154
+ );
155
+ } else {
156
+ state.fragments.push(
157
+ `(timestamp < ${tRef}::timestamptz OR (timestamp = ${tRef}::timestamptz AND id::text < ${iRef}))`
158
+ );
201
159
  }
202
- } else {
203
- const p = addParam(state, filters.operations);
204
- state.fragments.push(`operation = ANY(${p}::text[])`);
160
+ break;
205
161
  }
206
162
  }
207
- if (filters.since !== void 0) {
208
- const date = resolveTimeFilter(filters.since);
209
- const p = addParam(state, date.toISOString());
210
- state.fragments.push(`timestamp >= ${p}::timestamptz`);
211
- }
212
- if (filters.until !== void 0) {
213
- const date = resolveTimeFilter(filters.until);
214
- const p = addParam(state, date.toISOString());
215
- state.fragments.push(`timestamp <= ${p}::timestamptz`);
216
- }
217
- if (filters.searchText !== void 0 && filters.searchText.length > 0) {
218
- const escaped = escapeLikePattern(filters.searchText);
219
- const pattern = `%${escaped}%`;
220
- const p = addParam(state, pattern);
221
- state.fragments.push(`(label ILIKE ${p} OR description ILIKE ${p})`);
222
- }
223
- if (filters.compliance !== void 0 && filters.compliance.length > 0) {
224
- const p = addParam(state, JSON.stringify(filters.compliance));
225
- state.fragments.push(`compliance @> ${p}::jsonb`);
226
- }
227
- if (cursor !== void 0) {
228
- const tRef = addParam(state, cursor.timestamp.toISOString());
229
- const iRef = addParam(state, cursor.id);
230
- if (sortOrder === "asc") {
231
- state.fragments.push(
232
- `(timestamp > ${tRef}::timestamptz OR (timestamp = ${tRef}::timestamptz AND id::text > ${iRef}))`
233
- );
234
- } else {
235
- state.fragments.push(
236
- `(timestamp < ${tRef}::timestamptz OR (timestamp = ${tRef}::timestamptz AND id::text < ${iRef}))`
237
- );
238
- }
163
+ }
164
+ function buildWhereClause(filters, cursor, sortOrder) {
165
+ const state = { fragments: [], params: [] };
166
+ const conditions = interpretFilters(filters, { cursor, sortOrder });
167
+ for (const condition of conditions) {
168
+ mapCondition(state, condition);
239
169
  }
240
170
  return state;
241
171
  }
@@ -327,38 +257,14 @@ async function getStats(options, db) {
327
257
  ...params
328
258
  );
329
259
  const [summaryRows, eventsPerDayRows, topActorsRows, topTablesRows, operationRows, severityRows] = await Promise.all([summaryQuery, eventsPerDayQuery, topActorsQuery, topTablesQuery, operationQuery, severityQuery]);
330
- const summary = summaryRows[0];
331
- const totalLogs = summary !== void 0 ? toCount(summary.total_logs) : 0;
332
- const tablesAudited = summary !== void 0 ? toCount(summary.tables_audited) : 0;
333
- const eventsPerDay = eventsPerDayRows.map((row) => ({
334
- date: String(row.date),
335
- count: toCount(row.count)
336
- }));
337
- const topActors = topActorsRows.map((row) => ({
338
- actorId: String(row.actor_id),
339
- count: toCount(row.count)
340
- }));
341
- const topTables = topTablesRows.map((row) => ({
342
- tableName: String(row.table_name),
343
- count: toCount(row.count)
344
- }));
345
- const operationBreakdown = {};
346
- for (const row of operationRows) {
347
- operationBreakdown[String(row.operation)] = toCount(row.count);
348
- }
349
- const severityBreakdown = {};
350
- for (const row of severityRows) {
351
- severityBreakdown[String(row.severity)] = toCount(row.count);
352
- }
353
- return {
354
- totalLogs,
355
- tablesAudited,
356
- eventsPerDay,
357
- topActors,
358
- topTables,
359
- operationBreakdown,
360
- severityBreakdown
361
- };
260
+ return assembleStats(
261
+ summaryRows.map((r) => ({ totalLogs: r.total_logs, tablesAudited: r.tables_audited })),
262
+ eventsPerDayRows,
263
+ topActorsRows.map((r) => ({ actorId: r.actor_id, count: r.count })),
264
+ topTablesRows.map((r) => ({ tableName: r.table_name, count: r.count })),
265
+ operationRows,
266
+ severityRows
267
+ );
362
268
  }
363
269
 
364
270
  // src/adapter.ts
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/query.ts","../src/column-map.ts","../src/adapter.ts","../src/extension.ts","../src/action-map.ts","../src/model-map.ts","../src/tx-store.ts"],"sourcesContent":["import type {\n AuditLog,\n AuditQueryFilters,\n AuditQuerySpec,\n AuditQueryResult,\n AuditStats,\n TimeFilter,\n} from \"@usebetterdev/audit-core\";\nimport { parseDuration } from \"@usebetterdev/audit-core\";\nimport { rowToAuditLog } from \"./column-map.js\";\n\n/**\n * Minimal duck-typed interface for the database operations used by query functions.\n * Structurally compatible with PrismaClientWithRaw in adapter.ts.\n */\ninterface QueryDb {\n $queryRawUnsafe(query: string, ...params: unknown[]): Promise<unknown>;\n $executeRawUnsafe(query: string, ...params: unknown[]): Promise<number>;\n}\n\nconst DEFAULT_LIMIT = 50;\nconst MAX_LIMIT = 250;\n\nconst SELECT_COLUMNS =\n \"id, timestamp, table_name, operation, record_id, actor_id, \" +\n \"before_data, after_data, diff, label, description, severity, \" +\n \"compliance, notify, reason, metadata, redacted_fields\";\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 * 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\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\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 * Safely converts a PostgreSQL count to a JavaScript number.\n * pg returns COUNT(*) as a string; @prisma/adapter-pg may return BigInt.\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 if (typeof value === \"bigint\") {\n return Number(value);\n }\n return 0;\n}\n\ninterface QueryState {\n fragments: string[];\n params: unknown[];\n}\n\nfunction addParam(state: QueryState, value: unknown): string {\n state.params.push(value);\n return `$${state.params.length}`;\n}\n\n/**\n * Builds a parameterized WHERE clause body from AuditQueryFilters and optional cursor.\n * Returns the SQL fragment (without the WHERE keyword) and bound parameters.\n *\n * The same positional parameter reference (e.g. $3) may appear multiple times\n * in the output SQL — PostgreSQL supports this for shared values like search patterns\n * and cursor timestamps.\n */\nfunction buildWhereClause(\n filters: AuditQueryFilters,\n cursor: { timestamp: Date; id: string } | undefined,\n sortOrder: \"asc\" | \"desc\",\n): QueryState {\n const state: QueryState = { fragments: [], params: [] };\n\n // resource filter\n if (filters.resource !== undefined) {\n const p = addParam(state, filters.resource.tableName);\n state.fragments.push(`table_name = ${p}`);\n if (filters.resource.recordId !== undefined) {\n const r = addParam(state, filters.resource.recordId);\n state.fragments.push(`record_id = ${r}`);\n }\n }\n\n // actorIds — single eq or ANY\n if (filters.actorIds !== undefined && filters.actorIds.length > 0) {\n if (filters.actorIds.length === 1) {\n const first = filters.actorIds[0];\n if (first !== undefined) {\n const p = addParam(state, first);\n state.fragments.push(`actor_id = ${p}`);\n }\n } else {\n const p = addParam(state, filters.actorIds);\n state.fragments.push(`actor_id = ANY(${p}::text[])`);\n }\n }\n\n // severities — single eq or ANY\n if (filters.severities !== undefined && filters.severities.length > 0) {\n if (filters.severities.length === 1) {\n const first = filters.severities[0];\n if (first !== undefined) {\n const p = addParam(state, first);\n state.fragments.push(`severity = ${p}`);\n }\n } else {\n const p = addParam(state, filters.severities);\n state.fragments.push(`severity = ANY(${p}::text[])`);\n }\n }\n\n // operations — single eq or ANY\n if (filters.operations !== undefined && filters.operations.length > 0) {\n if (filters.operations.length === 1) {\n const first = filters.operations[0];\n if (first !== undefined) {\n const p = addParam(state, first);\n state.fragments.push(`operation = ${p}`);\n }\n } else {\n const p = addParam(state, filters.operations);\n state.fragments.push(`operation = ANY(${p}::text[])`);\n }\n }\n\n // since — gte(timestamp, resolved date)\n if (filters.since !== undefined) {\n const date = resolveTimeFilter(filters.since);\n const p = addParam(state, date.toISOString());\n state.fragments.push(`timestamp >= ${p}::timestamptz`);\n }\n\n // until — lte(timestamp, resolved date)\n if (filters.until !== undefined) {\n const date = resolveTimeFilter(filters.until);\n const p = addParam(state, date.toISOString());\n state.fragments.push(`timestamp <= ${p}::timestamptz`);\n }\n\n // searchText — ILIKE on label OR description ($N used twice for same param)\n if (filters.searchText !== undefined && filters.searchText.length > 0) {\n const escaped = escapeLikePattern(filters.searchText);\n const pattern = `%${escaped}%`;\n const p = addParam(state, pattern);\n state.fragments.push(`(label ILIKE ${p} OR description ILIKE ${p})`);\n }\n\n // compliance — jsonb @> (entry must contain ALL listed tags)\n if (filters.compliance !== undefined && filters.compliance.length > 0) {\n const p = addParam(state, JSON.stringify(filters.compliance));\n state.fragments.push(`compliance @> ${p}::jsonb`);\n }\n\n // cursor — (timestamp, id) tuple pagination\n if (cursor !== undefined) {\n // Timestamp referenced twice in the fragment but added once as a param\n const tRef = addParam(state, cursor.timestamp.toISOString());\n const iRef = addParam(state, cursor.id);\n if (sortOrder === \"asc\") {\n state.fragments.push(\n `(timestamp > ${tRef}::timestamptz OR (timestamp = ${tRef}::timestamptz AND id::text > ${iRef}))`,\n );\n } else {\n state.fragments.push(\n `(timestamp < ${tRef}::timestamptz OR (timestamp = ${tRef}::timestamptz AND id::text < ${iRef}))`,\n );\n }\n }\n\n return state;\n}\n\n/**\n * Executes a filtered, cursor-paginated query against the audit_logs table.\n */\nexport async function queryLogs(\n spec: AuditQuerySpec,\n db: QueryDb,\n): Promise<AuditQueryResult> {\n const sortOrder = spec.sortOrder ?? \"desc\";\n const limit = Math.min(spec.limit ?? DEFAULT_LIMIT, MAX_LIMIT);\n const fetchLimit = limit + 1;\n\n const cursor =\n spec.cursor !== undefined ? decodeCursor(spec.cursor) : undefined;\n const where = buildWhereClause(spec.filters, cursor, sortOrder);\n\n // LIMIT param is kept separate so buildWhereClause's state is not mutated.\n const queryParams = [...where.params, fetchLimit];\n const limitRef = `$${queryParams.length}`;\n\n const orderDir = sortOrder === \"asc\" ? \"ASC\" : \"DESC\";\n const whereClause =\n where.fragments.length > 0 ? `WHERE ${where.fragments.join(\" AND \")}` : \"\";\n\n const sql =\n `SELECT ${SELECT_COLUMNS} FROM audit_logs ` +\n `${whereClause} ` +\n `ORDER BY timestamp ${orderDir}, id ${orderDir} ` +\n `LIMIT ${limitRef}`;\n\n const raw = (await db.$queryRawUnsafe(sql, ...queryParams)) as Record<\n string,\n unknown\n >[];\n\n const hasNextPage = raw.length > limit;\n const resultRows = hasNextPage ? raw.slice(0, -1) : raw;\n const entries = resultRows.map(rowToAuditLog);\n\n if (hasNextPage) {\n const lastEntry = entries[entries.length - 1];\n if (lastEntry !== undefined) {\n return {\n entries,\n nextCursor: encodeCursor(lastEntry.timestamp, lastEntry.id),\n };\n }\n }\n\n return { entries };\n}\n\n/**\n * Fetches a single audit log entry by its UUID. Returns null if not found.\n */\nexport async function getLogById(\n id: string,\n db: QueryDb,\n): Promise<AuditLog | null> {\n const sql = `SELECT ${SELECT_COLUMNS} FROM audit_logs WHERE id = $1::uuid LIMIT 1`;\n const raw = (await db.$queryRawUnsafe(sql, id)) as Record<string, unknown>[];\n const row = raw[0];\n if (row === undefined) {\n return null;\n }\n return rowToAuditLog(row);\n}\n\n/**\n * Deletes audit log entries older than `before`, optionally scoped to one table.\n * Returns the number of deleted rows.\n */\nexport async function purgeLogs(\n options: { before: Date; tableName?: string },\n db: QueryDb,\n): Promise<{ deletedCount: number }> {\n if (\n options.tableName !== undefined &&\n options.tableName.trim().length === 0\n ) {\n throw new Error(\n \"purgeLogs: tableName must be a non-empty string when provided\",\n );\n }\n\n let sql = \"DELETE FROM audit_logs WHERE timestamp < $1::timestamptz\";\n const params: unknown[] = [options.before.toISOString()];\n\n if (options.tableName !== undefined) {\n params.push(options.tableName);\n sql += ` AND table_name = $${params.length}`;\n }\n\n const deletedCount = await db.$executeRawUnsafe(sql, ...params);\n return { deletedCount };\n}\n\n/**\n * Builds WHERE / AND fragments for an optional `since` filter.\n * The positional `$N` reference is derived from `paramCount` so it is always\n * correct regardless of how many params come before it in the query.\n */\nfunction buildSinceFragment(paramCount: number): { where: string; and: string } {\n const ref = `$${paramCount}::timestamptz`;\n return {\n where: ` WHERE timestamp >= ${ref}`,\n and: ` AND timestamp >= ${ref}`,\n };\n}\n\n/**\n * Returns aggregated statistics for the audit_logs table.\n * Runs six queries in parallel; each optionally filtered by `since`.\n */\nexport async function getStats(\n options: { since?: Date },\n db: QueryDb,\n): Promise<AuditStats> {\n const params: unknown[] = [];\n if (options.since !== undefined) {\n params.push(options.since.toISOString());\n }\n const since =\n params.length > 0\n ? buildSinceFragment(params.length)\n : { where: \"\", and: \"\" };\n\n // Query 1: totalLogs + tablesAudited\n const summaryQuery = db.$queryRawUnsafe(\n `SELECT COUNT(*) AS total_logs, COUNT(DISTINCT table_name) AS tables_audited FROM audit_logs${since.where}`,\n ...params,\n ) as Promise<Array<{ total_logs: unknown; tables_audited: unknown }>>;\n\n // Query 2: eventsPerDay\n const eventsPerDayQuery = db.$queryRawUnsafe(\n `SELECT to_char(date_trunc('day', timestamp), 'YYYY-MM-DD') AS date, COUNT(*) AS count ` +\n `FROM audit_logs${since.where} ` +\n `GROUP BY date_trunc('day', timestamp) ORDER BY date_trunc('day', timestamp) LIMIT 365`,\n ...params,\n ) as Promise<Array<{ date: unknown; count: unknown }>>;\n\n // Query 3: topActors (NULL actors excluded)\n const topActorsQuery = db.$queryRawUnsafe(\n `SELECT actor_id, COUNT(*) AS count FROM audit_logs ` +\n `WHERE actor_id IS NOT NULL${since.and} ` +\n `GROUP BY actor_id ORDER BY COUNT(*) DESC LIMIT 10`,\n ...params,\n ) as Promise<Array<{ actor_id: unknown; count: unknown }>>;\n\n // Query 4: topTables\n const topTablesQuery = db.$queryRawUnsafe(\n `SELECT table_name, COUNT(*) AS count FROM audit_logs${since.where} ` +\n `GROUP BY table_name ORDER BY COUNT(*) DESC LIMIT 10`,\n ...params,\n ) as Promise<Array<{ table_name: unknown; count: unknown }>>;\n\n // Query 5: operationBreakdown\n const operationQuery = db.$queryRawUnsafe(\n `SELECT operation, COUNT(*) AS count FROM audit_logs${since.where} ` +\n `GROUP BY operation`,\n ...params,\n ) as Promise<Array<{ operation: unknown; count: unknown }>>;\n\n // Query 6: severityBreakdown (NULL severities excluded)\n const severityQuery = db.$queryRawUnsafe(\n `SELECT severity, COUNT(*) AS count FROM audit_logs ` +\n `WHERE severity IS NOT NULL${since.and} ` +\n `GROUP BY severity`,\n ...params,\n ) as Promise<Array<{ severity: unknown; count: unknown }>>;\n\n const [summaryRows, eventsPerDayRows, topActorsRows, topTablesRows, operationRows, severityRows] =\n await Promise.all([summaryQuery, eventsPerDayQuery, topActorsQuery, topTablesQuery, operationQuery, severityQuery]);\n\n const summary = summaryRows[0];\n const totalLogs = summary !== undefined ? toCount(summary.total_logs) : 0;\n const tablesAudited =\n summary !== undefined ? toCount(summary.tables_audited) : 0;\n\n const eventsPerDay = eventsPerDayRows.map((row) => ({\n date: String(row.date),\n count: toCount(row.count),\n }));\n\n const topActors = topActorsRows.map((row) => ({\n actorId: String(row.actor_id),\n count: toCount(row.count),\n }));\n\n const topTables = topTablesRows.map((row) => ({\n tableName: String(row.table_name),\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","import type { AuditLog, AuditOperation, AuditSeverity } from \"@usebetterdev/audit-core\";\n\nconst VALID_OPERATIONS: ReadonlySet<string> = new Set([\"INSERT\", \"UPDATE\", \"DELETE\"]);\nconst VALID_SEVERITIES: ReadonlySet<string> = new Set([\"low\", \"medium\", \"high\", \"critical\"]);\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 raw database row (from $queryRawUnsafe) to a camelCase AuditLog.\n * Handles null → undefined mapping and validates enum fields at the boundary.\n *\n * Note: `auditLogToRow` is intentionally absent. The write path lives in\n * `adapter.ts` and uses `$executeRawUnsafe` with explicit `::uuid` / `::jsonb`\n * casts that cannot be expressed through a generic row-mapper function.\n */\nexport function rowToAuditLog(row: Record<string, unknown>): AuditLog {\n const { id, timestamp, table_name, operation, record_id } = row;\n\n if (typeof id !== \"string\") {\n throw new Error(\"rowToAuditLog: id must be a string\");\n }\n if (!(timestamp instanceof Date)) {\n throw new Error(\"rowToAuditLog: timestamp must be a Date\");\n }\n if (typeof table_name !== \"string\") {\n throw new Error(\"rowToAuditLog: table_name must be a string\");\n }\n if (typeof operation !== \"string\") {\n throw new Error(\"rowToAuditLog: operation must be a string\");\n }\n if (typeof record_id !== \"string\") {\n throw new Error(\"rowToAuditLog: record_id must be a string\");\n }\n\n if (!isAuditOperation(operation)) {\n throw new Error(\n `Invalid audit operation: \"${operation}\". Expected one of: INSERT, UPDATE, DELETE`,\n );\n }\n\n const log: AuditLog = {\n id,\n timestamp,\n tableName: table_name,\n operation,\n recordId: record_id,\n };\n\n const {\n actor_id,\n before_data,\n after_data,\n diff,\n label,\n description,\n severity,\n compliance,\n notify,\n reason,\n metadata,\n redacted_fields,\n } = row;\n\n if (actor_id !== null && actor_id !== undefined) {\n log.actorId = String(actor_id);\n }\n if (before_data !== null && before_data !== undefined) {\n log.beforeData = before_data as Record<string, unknown>;\n }\n if (after_data !== null && after_data !== undefined) {\n log.afterData = after_data as Record<string, unknown>;\n }\n if (diff !== null && diff !== undefined) {\n log.diff = diff as { changedFields: string[] };\n }\n if (label !== null && label !== undefined) {\n log.label = String(label);\n }\n if (description !== null && description !== undefined) {\n log.description = String(description);\n }\n if (severity !== null && severity !== undefined) {\n const s = String(severity);\n if (!isAuditSeverity(s)) {\n throw new Error(\n `Invalid audit severity: \"${s}\". Expected one of: low, medium, high, critical`,\n );\n }\n log.severity = s;\n }\n if (compliance !== null && compliance !== undefined) {\n log.compliance = compliance as string[];\n }\n if (notify !== null && notify !== undefined) {\n log.notify = Boolean(notify);\n }\n if (reason !== null && reason !== undefined) {\n log.reason = String(reason);\n }\n if (metadata !== null && metadata !== undefined) {\n log.metadata = metadata as Record<string, unknown>;\n }\n if (redacted_fields !== null && redacted_fields !== undefined) {\n log.redactedFields = redacted_fields as string[];\n }\n\n return log;\n}\n","import type {\n AuditDatabaseAdapter,\n AuditLog,\n AuditQuerySpec,\n AuditQueryResult,\n AuditStats,\n} from \"@usebetterdev/audit-core\";\nimport { queryLogs, getLogById, getStats, purgeLogs } from \"./query.js\";\n\n/**\n * Minimal duck-typed interface for a Prisma client that supports raw query execution.\n * Avoids a hard dependency on the generated `@prisma/client`.\n */\nexport interface PrismaClientWithRaw {\n $executeRawUnsafe(query: string, ...params: unknown[]): Promise<number>;\n $queryRawUnsafe(query: string, ...params: unknown[]): Promise<unknown>;\n}\n\n/**\n * Creates an `AuditDatabaseAdapter` backed by a Prisma client.\n *\n * Implements `writeLog` via `$executeRawUnsafe` for precise control over\n * PostgreSQL type casts (::uuid, ::timestamptz, ::jsonb) and null handling.\n *\n * Implements `queryLogs`, `getLogById`, `getStats`, and `purgeLogs` via\n * parameterized raw SQL — all user inputs are bound parameters, never interpolated.\n */\nexport function prismaAuditAdapter(db: PrismaClientWithRaw): AuditDatabaseAdapter {\n return {\n async writeLog(log: AuditLog): Promise<void> {\n await db.$executeRawUnsafe(\n `INSERT INTO audit_logs (\n id, timestamp, table_name, operation, record_id,\n actor_id, before_data, after_data, diff,\n label, description, severity, compliance,\n notify, reason, metadata, redacted_fields\n ) VALUES (\n $1::uuid, $2::timestamptz, $3, $4, $5,\n $6, $7::jsonb, $8::jsonb, $9::jsonb,\n $10, $11, $12, $13::jsonb,\n $14, $15, $16::jsonb, $17::jsonb\n )`,\n log.id,\n log.timestamp.toISOString(),\n log.tableName,\n log.operation,\n log.recordId,\n log.actorId ?? null,\n jsonOrNull(log.beforeData),\n jsonOrNull(log.afterData),\n jsonOrNull(log.diff),\n log.label ?? null,\n log.description ?? null,\n log.severity ?? null,\n jsonOrNull(log.compliance),\n log.notify ?? null,\n log.reason ?? null,\n jsonOrNull(log.metadata),\n jsonOrNull(log.redactedFields),\n );\n },\n\n async queryLogs(spec: AuditQuerySpec): Promise<AuditQueryResult> {\n return queryLogs(spec, db);\n },\n\n async getLogById(id: string): Promise<AuditLog | null> {\n return getLogById(id, db);\n },\n\n async getStats(options?: { since?: Date }): Promise<AuditStats> {\n return getStats(options ?? {}, db);\n },\n\n async purgeLogs(options: {\n before: Date;\n tableName?: string;\n }): Promise<{ deletedCount: number }> {\n return purgeLogs(options, db);\n },\n };\n}\n\nfunction jsonOrNull(value: unknown): string | null {\n if (value === undefined || value === null) {\n return null;\n }\n return JSON.stringify(value);\n}\n","import type { CaptureLogInput, AuditOperation } from \"@usebetterdev/audit-core\";\nimport { getAuditContext } from \"@usebetterdev/audit-core\";\nimport { getAuditOperation } from \"./action-map.js\";\nimport { buildTableNameResolver } from \"./model-map.js\";\nimport { getTxClient } from \"./tx-store.js\";\n\n/**\n * Minimal duck-typed interface for a Prisma client that supports `$extends`.\n * Avoids a hard dependency on the generated `@prisma/client`.\n */\nexport interface PrismaClientLike {\n $extends(extension: PrismaExtensionDefinition): unknown;\n}\n\n/**\n * Shape of the Prisma `$extends` query extension definition.\n * We only type what we need — the full shape lives in `@prisma/client/extension`.\n */\ninterface PrismaExtensionDefinition {\n query: {\n $allModels: {\n $allOperations: (params: {\n model: string;\n operation: string;\n args: unknown;\n query: (args: unknown) => Promise<unknown>;\n }) => Promise<unknown>;\n };\n };\n}\n\n/**\n * Minimal duck-typed interface for a Prisma model delegate.\n * Used internally to issue before-state lookups prior to mutations.\n * Any real Prisma client's model delegate satisfies this.\n */\ninterface PrismaModelDelegate {\n findUnique(args: { where: unknown }): Promise<unknown>;\n findMany(args: { where?: unknown; take?: number }): Promise<unknown[]>;\n}\n\n/**\n * Represents the before-state captured prior to a mutation.\n *\n * - `\"none\"`: no before-state needed (create operations, single delete whose\n * result already contains the deleted record)\n * - `\"single\"`: one record looked up via `findUnique` (update, upsert)\n * - `\"many\"`: multiple records looked up via `findMany` (updateMany, deleteMany)\n */\ntype BeforeState =\n | { type: \"none\" }\n | { type: \"single\"; row: Record<string, unknown> | undefined }\n | { type: \"many\"; rows: Record<string, unknown>[] };\n\nexport type BulkMode = \"per-row\" | \"bulk\";\n\nexport interface WithAuditExtensionOptions {\n /**\n * How to handle bulk write operations (`createMany`, `updateMany`, `deleteMany`).\n *\n * - `\"per-row\"` (default): for `createMany` and `createManyAndReturn`, fires\n * one audit entry per item in `args.data`. For `updateMany` and `deleteMany`,\n * fires one entry per row captured in the before-state lookup, or a single\n * bulk entry if no rows are found.\n * - `\"bulk\"`: fires a single audit entry for the entire operation.\n */\n bulkMode?: BulkMode;\n /**\n * Called when audit capture fails. Falls back to `console.error` if not set.\n * Errors are always swallowed — audit failures never propagate to callers.\n */\n onError?: (error: unknown) => void;\n /**\n * Extra structured data merged into every audit log entry produced by this\n * extension. Useful for tagging logs with the adapter name or environment.\n *\n * @example\n * ```ts\n * withAuditExtension(prisma, audit.captureLog, { metadata: { adapter: \"prisma\" } })\n * ```\n */\n metadata?: Record<string, unknown>;\n /**\n * Maps a Prisma model name to the `tableName` stored in audit logs.\n *\n * By default the extension auto-detects the SQL table name from Prisma's\n * `_runtimeDataModel` (populated by `@@map` directives). If the runtime\n * metadata is unavailable, the model name is used as-is.\n *\n * When provided, this function takes full precedence over auto-detection.\n *\n * @example\n * ```ts\n * withAuditExtension(prisma, captureLog, {\n * tableNameTransform: (name) => name.toLowerCase(),\n * })\n * ```\n */\n tableNameTransform?: (modelName: string) => string;\n /**\n * Table names (SQL names, after `tableNameTransform`) to skip before-state\n * capture for. No extra `findUnique` or `findMany` is issued for these tables.\n *\n * Useful for high-volume tables where the additional SELECT is too expensive.\n *\n * @example\n * ```ts\n * withAuditExtension(prisma, captureLog, {\n * skipBeforeCapture: [\"events\", \"metrics\"],\n * })\n * ```\n */\n skipBeforeCapture?: string[];\n /**\n * Maximum number of rows fetched by the before-state `findMany` for\n * `updateMany` and `deleteMany` operations (default: `100`).\n *\n * When the matched row count exceeds this limit the before-state lookup is\n * skipped and a single bulk audit entry is written instead — preventing\n * unbounded memory consumption on large batch operations.\n *\n * Set to `Infinity` to disable the limit (not recommended in production).\n *\n * @example\n * ```ts\n * withAuditExtension(prisma, captureLog, { maxBeforeStateRows: 50 })\n * ```\n */\n maxBeforeStateRows?: number;\n}\n\n/**\n * Wraps a Prisma client with a `$extends` query extension that calls\n * `captureLog` after each successful mutation.\n *\n * Returns a new extended client of the same type `T`. Use this extended\n * client in place of the original — all queries behave identically.\n *\n * Actor identity is read automatically from the current `AuditContext`\n * (set via `audit.withContext` or `runWithAuditContext` from audit-core).\n *\n * For `update` and `upsert`, a `findUnique` is issued before the mutation\n * to capture `before_data`. For `updateMany` and `deleteMany` in `\"per-row\"`\n * mode, a `findMany` is issued before the mutation to capture per-row state.\n * Pass `skipBeforeCapture` to opt out for specific high-volume tables.\n *\n * Filtering by audited tables is NOT done here — `captureLog` (from\n * `betterAudit`) already skips tables not in `auditTables`.\n *\n * @example\n * ```ts\n * // auditTables must use SQL table names (auto-detected from @@map directives)\n * const audit = betterAudit({ database: prismaAuditAdapter(prisma), auditTables: [\"users\"] });\n * const auditedPrisma = withAuditExtension(prisma, audit.captureLog);\n * // Use auditedPrisma everywhere — mutations are transparently logged\n * ```\n */\nexport function withAuditExtension<T extends PrismaClientLike>(\n prisma: T,\n captureLog: (input: CaptureLogInput) => Promise<void>,\n options: WithAuditExtensionOptions = {},\n): T {\n const bulkMode = options.bulkMode ?? \"per-row\";\n const handleError = buildErrorHandler(options.onError);\n const metadata = options.metadata;\n const skipBeforeCapture = options.skipBeforeCapture;\n const maxBeforeStateRows = options.maxBeforeStateRows ?? 100;\n const resolveTableName = buildTableNameResolver(prisma, options.tableNameTransform);\n\n const extended = prisma.$extends({\n query: {\n $allModels: {\n async $allOperations({\n model,\n operation,\n args,\n query,\n }: {\n model: string;\n operation: string;\n args: unknown;\n query: (args: unknown) => Promise<unknown>;\n }) {\n const auditOp = getAuditOperation(operation);\n if (auditOp === undefined) {\n return query(args);\n }\n\n const tableName = resolveTableName(model);\n const shouldSkipBefore =\n skipBeforeCapture !== undefined && skipBeforeCapture.includes(tableName);\n\n const beforeState: BeforeState = shouldSkipBefore\n ? { type: \"none\" }\n : await fetchBeforeState({ prisma, model, operation, args, handleError, maxBeforeStateRows });\n\n const result = await query(args);\n\n try {\n await fireCaptureLog({\n tableName,\n operation,\n auditOp,\n args,\n result,\n beforeState,\n captureLog,\n bulkMode,\n metadata,\n actorId: getAuditContext()?.actorId,\n });\n } catch (err) {\n handleError(err);\n }\n\n return result;\n },\n },\n },\n });\n\n // The $extends return type is `unknown` — casting back to T is safe because\n // the extension only adds transparent interception; it does not change the query API.\n return extended as unknown as T;\n}\n\n// ---------------------------------------------------------------------------\n// Before-state fetching\n// ---------------------------------------------------------------------------\n\ninterface FetchBeforeStateParams {\n prisma: PrismaClientLike;\n model: string;\n operation: string;\n args: unknown;\n handleError: (err: unknown) => void;\n maxBeforeStateRows: number;\n}\n\n/**\n * Issues a `findUnique` or `findMany` before the mutation to capture the\n * record state that will be stored as `before_data` in the audit log.\n *\n * Returns `{ type: \"none\" }` for operations that don't need a pre-query:\n * - `create` / `createMany` / `createManyAndReturn`: no before-state exists\n * - `delete`: Prisma returns the deleted record as the mutation result\n */\nasync function fetchBeforeState({\n prisma,\n model,\n operation,\n args,\n handleError,\n maxBeforeStateRows,\n}: FetchBeforeStateParams): Promise<BeforeState> {\n if (\n operation === \"create\" ||\n operation === \"createMany\" ||\n operation === \"createManyAndReturn\" ||\n operation === \"delete\"\n ) {\n return { type: \"none\" };\n }\n\n const delegate = getModelDelegate(getTxClient() ?? prisma, model);\n if (delegate === undefined) {\n return { type: \"none\" };\n }\n\n if (operation === \"update\" || operation === \"upsert\") {\n const where = getArgsWhere(args);\n if (where === undefined) {\n return { type: \"single\", row: undefined };\n }\n try {\n const found = await delegate.findUnique({ where });\n return { type: \"single\", row: toRecord(found) };\n } catch (err) {\n handleError(err);\n return { type: \"single\", row: undefined };\n }\n }\n\n if (operation === \"updateMany\" || operation === \"deleteMany\") {\n const where = getArgsWhere(args);\n try {\n const rows = await delegate.findMany({ where: where ?? {}, take: maxBeforeStateRows + 1 });\n if (rows.length > maxBeforeStateRows) {\n // Too many rows to capture individually — fall back to a single bulk entry.\n return { type: \"none\" };\n }\n const records = rows\n .map((r) => toRecord(r))\n .filter((r): r is Record<string, unknown> => r !== undefined);\n return { type: \"many\", rows: records };\n } catch (err) {\n handleError(err);\n return { type: \"many\", rows: [] };\n }\n }\n\n return { type: \"none\" };\n}\n\n// ---------------------------------------------------------------------------\n// captureLog dispatch\n// ---------------------------------------------------------------------------\n\ninterface FireCaptureLogParams {\n tableName: string;\n operation: string;\n auditOp: AuditOperation;\n args: unknown;\n result: unknown;\n beforeState: BeforeState;\n captureLog: (input: CaptureLogInput) => Promise<void>;\n bulkMode: BulkMode;\n metadata: Record<string, unknown> | undefined;\n actorId: string | undefined;\n}\n\nasync function fireCaptureLog({\n tableName,\n operation,\n auditOp,\n args,\n result,\n beforeState,\n captureLog,\n bulkMode,\n metadata,\n actorId,\n}: FireCaptureLogParams): Promise<void> {\n if (operation === \"createMany\") {\n await handleCreateMany({ tableName, auditOp, args, captureLog, bulkMode, metadata, actorId });\n return;\n }\n\n if (operation === \"createManyAndReturn\") {\n await handleCreateManyAndReturn({ tableName, auditOp, result, captureLog, bulkMode, metadata, actorId });\n return;\n }\n\n if (operation === \"updateMany\") {\n await handleUpdateMany({ tableName, auditOp, beforeState, captureLog, bulkMode, metadata, actorId });\n return;\n }\n\n if (operation === \"deleteMany\") {\n await handleDeleteMany({ tableName, auditOp, beforeState, captureLog, bulkMode, metadata, actorId });\n return;\n }\n\n // Single-record operations: create, update, upsert, delete\n const row = toRecord(result);\n const recordId = (row !== undefined ? extractId(row) : undefined) ?? \"unknown\";\n\n if (operation === \"upsert\") {\n // A before-state row means the record existed → UPDATE path.\n // No before-state row means the record was created → INSERT path.\n const before = beforeState.type === \"single\" ? beforeState.row : undefined;\n const effectiveOp: AuditOperation = before !== undefined ? \"UPDATE\" : \"INSERT\";\n await captureLog({\n tableName,\n operation: effectiveOp,\n recordId,\n ...(before !== undefined && { before }),\n ...(row !== undefined && { after: row }),\n ...(metadata !== undefined && { metadata }),\n ...(actorId !== undefined && { actorId }),\n });\n return;\n }\n\n if (auditOp === \"INSERT\") {\n await captureLog({\n tableName,\n operation: auditOp,\n recordId,\n ...(row !== undefined && { after: row }),\n ...(metadata !== undefined && { metadata }),\n ...(actorId !== undefined && { actorId }),\n });\n return;\n }\n\n if (auditOp === \"UPDATE\") {\n const before = beforeState.type === \"single\" ? beforeState.row : undefined;\n await captureLog({\n tableName,\n operation: auditOp,\n recordId,\n ...(before !== undefined && { before }),\n ...(row !== undefined && { after: row }),\n ...(metadata !== undefined && { metadata }),\n ...(actorId !== undefined && { actorId }),\n });\n return;\n }\n\n // DELETE: Prisma returns the deleted record as the result — use it as before_data.\n await captureLog({\n tableName,\n operation: auditOp,\n recordId,\n ...(row !== undefined && { before: row }),\n ...(metadata !== undefined && { metadata }),\n ...(actorId !== undefined && { actorId }),\n });\n}\n\n// ---------------------------------------------------------------------------\n// Per-operation handlers\n// ---------------------------------------------------------------------------\n\ninterface HandleCreateManyParams {\n tableName: string;\n auditOp: AuditOperation;\n args: unknown;\n captureLog: (input: CaptureLogInput) => Promise<void>;\n bulkMode: BulkMode;\n metadata: Record<string, unknown> | undefined;\n actorId: string | undefined;\n}\n\nasync function handleCreateMany({\n tableName,\n auditOp,\n args,\n captureLog,\n bulkMode,\n metadata,\n actorId,\n}: HandleCreateManyParams): Promise<void> {\n if (bulkMode === \"bulk\") {\n await captureLog({\n tableName,\n operation: auditOp,\n recordId: \"unknown\",\n ...(metadata !== undefined && { metadata }),\n ...(actorId !== undefined && { actorId }),\n });\n return;\n }\n\n // per-row: fire one entry per item in args.data\n const rows = getArgsData(args);\n if (rows.length === 0) {\n await captureLog({\n tableName,\n operation: auditOp,\n recordId: \"unknown\",\n ...(metadata !== undefined && { metadata }),\n ...(actorId !== undefined && { actorId }),\n });\n return;\n }\n\n await Promise.all(\n rows.map((row) => {\n const record = toRecord(row);\n const recordId = (record !== undefined ? extractId(record) : undefined) ?? \"unknown\";\n return captureLog({\n tableName,\n operation: auditOp,\n recordId,\n ...(record !== undefined && { after: record }),\n ...(metadata !== undefined && { metadata }),\n ...(actorId !== undefined && { actorId }),\n });\n }),\n );\n}\n\ninterface HandleCreateManyAndReturnParams {\n tableName: string;\n auditOp: AuditOperation;\n result: unknown;\n captureLog: (input: CaptureLogInput) => Promise<void>;\n bulkMode: BulkMode;\n metadata: Record<string, unknown> | undefined;\n actorId: string | undefined;\n}\n\nasync function handleCreateManyAndReturn({\n tableName,\n auditOp,\n result,\n captureLog,\n bulkMode,\n metadata,\n actorId,\n}: HandleCreateManyAndReturnParams): Promise<void> {\n if (bulkMode === \"bulk\") {\n await captureLog({\n tableName,\n operation: auditOp,\n recordId: \"unknown\",\n ...(metadata !== undefined && { metadata }),\n ...(actorId !== undefined && { actorId }),\n });\n return;\n }\n\n const rows = Array.isArray(result) ? result : [];\n if (rows.length === 0) {\n await captureLog({\n tableName,\n operation: auditOp,\n recordId: \"unknown\",\n ...(metadata !== undefined && { metadata }),\n ...(actorId !== undefined && { actorId }),\n });\n return;\n }\n\n await Promise.all(\n rows.map((row) => {\n const record = toRecord(row);\n const recordId = (record !== undefined ? extractId(record) : undefined) ?? \"unknown\";\n return captureLog({\n tableName,\n operation: auditOp,\n recordId,\n ...(record !== undefined && { after: record }),\n ...(metadata !== undefined && { metadata }),\n ...(actorId !== undefined && { actorId }),\n });\n }),\n );\n}\n\ninterface HandleUpdateManyParams {\n tableName: string;\n auditOp: AuditOperation;\n beforeState: BeforeState;\n captureLog: (input: CaptureLogInput) => Promise<void>;\n bulkMode: BulkMode;\n metadata: Record<string, unknown> | undefined;\n actorId: string | undefined;\n}\n\nasync function handleUpdateMany({\n tableName,\n auditOp,\n beforeState,\n captureLog,\n bulkMode,\n metadata,\n actorId,\n}: HandleUpdateManyParams): Promise<void> {\n if (bulkMode === \"bulk\") {\n await captureLog({\n tableName,\n operation: auditOp,\n recordId: \"unknown\",\n ...(metadata !== undefined && { metadata }),\n ...(actorId !== undefined && { actorId }),\n });\n return;\n }\n\n // per-row: fire one entry per row captured before the mutation\n const rows = beforeState.type === \"many\" ? beforeState.rows : [];\n if (rows.length === 0) {\n await captureLog({\n tableName,\n operation: auditOp,\n recordId: \"unknown\",\n ...(metadata !== undefined && { metadata }),\n ...(actorId !== undefined && { actorId }),\n });\n return;\n }\n\n await Promise.all(\n rows.map((row) => {\n const recordId = extractId(row) ?? \"unknown\";\n return captureLog({\n tableName,\n operation: auditOp,\n recordId,\n before: row,\n ...(metadata !== undefined && { metadata }),\n ...(actorId !== undefined && { actorId }),\n });\n }),\n );\n}\n\ninterface HandleDeleteManyParams {\n tableName: string;\n auditOp: AuditOperation;\n beforeState: BeforeState;\n captureLog: (input: CaptureLogInput) => Promise<void>;\n bulkMode: BulkMode;\n metadata: Record<string, unknown> | undefined;\n actorId: string | undefined;\n}\n\nasync function handleDeleteMany({\n tableName,\n auditOp,\n beforeState,\n captureLog,\n bulkMode,\n metadata,\n actorId,\n}: HandleDeleteManyParams): Promise<void> {\n if (bulkMode === \"bulk\") {\n await captureLog({\n tableName,\n operation: auditOp,\n recordId: \"unknown\",\n ...(metadata !== undefined && { metadata }),\n ...(actorId !== undefined && { actorId }),\n });\n return;\n }\n\n // per-row: fire one entry per row captured before deletion\n const rows = beforeState.type === \"many\" ? beforeState.rows : [];\n if (rows.length === 0) {\n await captureLog({\n tableName,\n operation: auditOp,\n recordId: \"unknown\",\n ...(metadata !== undefined && { metadata }),\n ...(actorId !== undefined && { actorId }),\n });\n return;\n }\n\n await Promise.all(\n rows.map((row) => {\n const recordId = extractId(row) ?? \"unknown\";\n return captureLog({\n tableName,\n operation: auditOp,\n recordId,\n before: row,\n ...(metadata !== undefined && { metadata }),\n ...(actorId !== undefined && { actorId }),\n });\n }),\n );\n}\n\n// ---------------------------------------------------------------------------\n// Helpers\n// ---------------------------------------------------------------------------\n\nfunction buildErrorHandler(\n onError: ((error: unknown) => void) | undefined,\n): (error: unknown) => void {\n return (error: unknown) => {\n try {\n if (onError !== undefined) {\n onError(error);\n } else {\n const msg = error instanceof Error ? error.message : String(error);\n console.error(`audit-prisma: capture failed — ${msg}`);\n }\n } catch {\n // onError itself threw — swallow to never break mutations.\n }\n };\n}\n\n/**\n * Returns true when `value` quacks like a Prisma model delegate with\n * `findUnique` and `findMany` methods.\n */\nfunction isModelDelegate(value: unknown): value is PrismaModelDelegate {\n if (value === null || value === undefined || typeof value !== \"object\") {\n return false;\n }\n return (\n \"findUnique\" in value &&\n \"findMany\" in value &&\n typeof Reflect.get(value, \"findUnique\") === \"function\" &&\n typeof Reflect.get(value, \"findMany\") === \"function\"\n );\n}\n\n/**\n * Returns the model delegate for `model` (e.g. `prisma.user` for model `\"User\"`)\n * by lowercasing the first character and doing a dynamic property lookup.\n * Returns `undefined` when the delegate is not found or doesn't have the\n * expected shape — the caller falls back to `{ type: \"none\" }` before-state.\n */\nfunction getModelDelegate(\n client: unknown,\n model: string,\n): PrismaModelDelegate | undefined {\n const lowerModel = model.charAt(0).toLowerCase() + model.slice(1);\n const value: unknown = Reflect.get(Object(client), lowerModel);\n if (isModelDelegate(value)) {\n return value;\n }\n return undefined;\n}\n\nfunction extractId(record: Record<string, unknown>): string | undefined {\n const id = record[\"id\"];\n if (id !== undefined && id !== null) {\n return String(id);\n }\n return undefined;\n}\n\nfunction toRecord(value: unknown): Record<string, unknown> | undefined {\n if (value !== null && value !== undefined && typeof value === \"object\" && !Array.isArray(value)) {\n return value as Record<string, unknown>;\n }\n return undefined;\n}\n\nfunction getArgsData(args: unknown): unknown[] {\n if (args !== null && typeof args === \"object\" && \"data\" in args) {\n const data = Reflect.get(args, \"data\");\n if (Array.isArray(data)) {\n return data;\n }\n }\n return [];\n}\n\n/**\n * Extracts `args.where` for before-state lookups. Returns `undefined` when\n * the argument object has no `where` property.\n */\nfunction getArgsWhere(args: unknown): unknown {\n if (args !== null && typeof args === \"object\" && \"where\" in args) {\n return Reflect.get(args, \"where\");\n }\n return undefined;\n}\n","import type { AuditOperation } from \"@usebetterdev/audit-core\";\n\n/**\n * Maps Prisma query action names to AuditOperation values.\n * Actions not in this map are read-only queries and are not audited.\n */\nexport const ACTION_MAP = {\n create: \"INSERT\",\n createMany: \"INSERT\",\n createManyAndReturn: \"INSERT\",\n update: \"UPDATE\",\n updateMany: \"UPDATE\",\n upsert: \"UPDATE\",\n delete: \"DELETE\",\n deleteMany: \"DELETE\",\n} as const satisfies Record<string, AuditOperation>;\n\nexport type MutableAction = keyof typeof ACTION_MAP;\n\nfunction isMutableAction(action: string): action is MutableAction {\n return action in ACTION_MAP;\n}\n\n/**\n * Returns the AuditOperation for a Prisma action name, or `undefined` for\n * read-only operations (findMany, findUnique, count, aggregate, etc.).\n */\nexport function getAuditOperation(action: string): AuditOperation | undefined {\n if (isMutableAction(action)) {\n return ACTION_MAP[action];\n }\n return undefined;\n}\n","/**\n * Runtime model-to-table mapping for Prisma clients that expose\n * `_runtimeDataModel.models`. This lets the audit extension auto-detect\n * the SQL table name (from `@@map`) without user configuration.\n */\n\ninterface RuntimeModel {\n dbName: string | null;\n}\n\ninterface RuntimeDataModel {\n models: Record<string, RuntimeModel>;\n}\n\ninterface PrismaWithRuntimeDataModel {\n _runtimeDataModel: RuntimeDataModel;\n}\n\n/**\n * Type guard: checks whether `value` exposes Prisma's `_runtimeDataModel.models`.\n */\nfunction hasPrismaRuntimeDataModel(value: object): value is PrismaWithRuntimeDataModel {\n if (!(\"_runtimeDataModel\" in value)) {\n return false;\n }\n\n const { _runtimeDataModel: rdm } = value;\n if (rdm === null || typeof rdm !== \"object\") {\n return false;\n }\n\n return \"models\" in rdm;\n}\n\n/**\n * Returns a `Record<string, string>` mapping Prisma model names to their\n * SQL table names (from `@@map`). Models without `@@map` map to themselves.\n *\n * Returns `undefined` if the client does not expose `_runtimeDataModel`.\n */\nexport function prismaModelMap(prisma: object): Record<string, string> | undefined {\n if (!hasPrismaRuntimeDataModel(prisma)) {\n return undefined;\n }\n\n const result: Record<string, string> = {};\n for (const [modelName, meta] of Object.entries(prisma._runtimeDataModel.models)) {\n result[modelName] = meta.dbName ?? modelName;\n }\n return result;\n}\n\n/**\n * Builds a function that resolves a Prisma model name to a table name.\n *\n * Resolution order:\n * 1. If `transform` is provided, use it (full user control).\n * 2. Else if the client exposes `_runtimeDataModel`, use `dbName ?? modelName`.\n * 3. Else return the model name as-is (safe fallback).\n */\nexport function buildTableNameResolver(\n prisma: object,\n transform: ((modelName: string) => string) | undefined,\n): (modelName: string) => string {\n if (transform !== undefined) {\n return transform;\n }\n\n const map = prismaModelMap(prisma);\n if (map !== undefined) {\n return (modelName: string) => map[modelName] ?? modelName;\n }\n\n return (modelName: string) => modelName;\n}\n","import { AsyncLocalStorage } from \"node:async_hooks\";\n\n/**\n * Stores the current Prisma transaction client for the duration of a\n * tenant-scoped request.\n *\n * When the audit extension issues a `findUnique` to capture before-state,\n * it queries the outer (non-transaction) Prisma client by default. That\n * connection has no `SET LOCAL app.current_tenant` active, so RLS blocks\n * all rows and before-state is lost.\n *\n * Call `runWithTxClient(tx, fn)` inside `prismaAdapter.runWithTenant` (after\n * the `SET LOCAL` statement) to make `tx` available to the extension. The\n * extension reads it via `getTxClient()` and uses it for the `findUnique`\n * instead of the outer client.\n */\nconst txStorage = new AsyncLocalStorage<unknown>();\n\n/**\n * Stores `tx` in the async context for the duration of `fn`.\n *\n * Call this wrapping the inner callback of a Prisma `$transaction` after the\n * tenant `SET LOCAL` has been issued so the audit extension can reuse the\n * same connection for before-state lookups.\n *\n * @example\n * ```ts\n * // Inside your betterTenant adapter wrapper:\n * const adapter = {\n * ...db.adapter,\n * runWithTenant(tenantId, fn) {\n * return db.adapter.runWithTenant(tenantId, (tx) =>\n * runWithTxClient(tx, () => fn(tx))\n * );\n * },\n * };\n * ```\n */\nexport function runWithTxClient<T>(tx: unknown, fn: () => Promise<T>): Promise<T> {\n return txStorage.run(tx, fn);\n}\n\n/**\n * Returns the current transaction client stored by `runWithTxClient`, or\n * `undefined` when called outside a tenant transaction context.\n */\nexport function getTxClient(): unknown {\n return txStorage.getStore();\n}\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../src/query.ts","../src/column-map.ts","../src/adapter.ts","../src/extension.ts","../src/action-map.ts","../src/model-map.ts","../src/tx-store.ts"],"sourcesContent":["import type {\n AuditLog,\n AuditFilterField,\n AuditQueryFilters,\n AuditQuerySpec,\n AuditQueryResult,\n AuditStats,\n FilterCondition,\n} from \"@usebetterdev/audit-core\";\nimport {\n encodeCursor,\n decodeCursor,\n interpretFilters,\n assembleStats,\n} from \"@usebetterdev/audit-core\";\nimport { rowToAuditLog } from \"./column-map.js\";\n\n/**\n * Minimal duck-typed interface for the database operations used by query functions.\n * Structurally compatible with PrismaClientWithRaw in adapter.ts.\n */\ninterface QueryDb {\n $queryRawUnsafe(query: string, ...params: unknown[]): Promise<unknown>;\n $executeRawUnsafe(query: string, ...params: unknown[]): Promise<number>;\n}\n\nconst DEFAULT_LIMIT = 50;\nconst MAX_LIMIT = 250;\n\nconst SELECT_COLUMNS =\n \"id, timestamp, table_name, operation, record_id, actor_id, \" +\n \"before_data, after_data, diff, label, description, severity, \" +\n \"compliance, notify, reason, metadata, redacted_fields\";\n\ninterface QueryState {\n fragments: string[];\n params: unknown[];\n}\n\nfunction addParam(state: QueryState, value: unknown): string {\n state.params.push(value);\n return `$${state.params.length}`;\n}\n\n/** Maps an AuditFilterField to its database column name. */\nconst FIELD_COLUMN_NAMES: Record<AuditFilterField, string> = {\n tableName: \"table_name\",\n recordId: \"record_id\",\n actorId: \"actor_id\",\n severity: \"severity\",\n operation: \"operation\",\n};\n\n/** Maps a single FilterCondition to a SQL fragment, pushing params into state. */\nfunction mapCondition(state: QueryState, condition: FilterCondition): void {\n switch (condition.kind) {\n case \"eq\": {\n const col = FIELD_COLUMN_NAMES[condition.field];\n const p = addParam(state, condition.value);\n state.fragments.push(`${col} = ${p}`);\n break;\n }\n case \"in\": {\n const col = FIELD_COLUMN_NAMES[condition.field];\n const p = addParam(state, condition.values);\n state.fragments.push(`${col} = ANY(${p}::text[])`);\n break;\n }\n case \"timestampGte\": {\n const p = addParam(state, condition.value.toISOString());\n state.fragments.push(`timestamp >= ${p}::timestamptz`);\n break;\n }\n case \"timestampLte\": {\n const p = addParam(state, condition.value.toISOString());\n state.fragments.push(`timestamp <= ${p}::timestamptz`);\n break;\n }\n case \"search\": {\n const p = addParam(state, condition.pattern);\n state.fragments.push(`(label ILIKE ${p} OR description ILIKE ${p})`);\n break;\n }\n case \"compliance\": {\n const p = addParam(state, JSON.stringify(condition.tags));\n state.fragments.push(`compliance @> ${p}::jsonb`);\n break;\n }\n case \"cursor\": {\n const tRef = addParam(state, condition.timestamp.toISOString());\n const iRef = addParam(state, condition.id);\n if (condition.sortOrder === \"asc\") {\n state.fragments.push(\n `(timestamp > ${tRef}::timestamptz OR (timestamp = ${tRef}::timestamptz AND id::text > ${iRef}))`,\n );\n } else {\n state.fragments.push(\n `(timestamp < ${tRef}::timestamptz OR (timestamp = ${tRef}::timestamptz AND id::text < ${iRef}))`,\n );\n }\n break;\n }\n }\n}\n\n/**\n * Builds a parameterized WHERE clause body from AuditQueryFilters and optional cursor.\n * Returns the SQL fragment (without the WHERE keyword) and bound parameters.\n *\n * Uses the core `interpretFilters()` IR to produce filter conditions, then maps\n * each condition to a PostgreSQL SQL fragment with positional parameters.\n */\nfunction buildWhereClause(\n filters: AuditQueryFilters,\n cursor: { timestamp: Date; id: string } | undefined,\n sortOrder: \"asc\" | \"desc\",\n): QueryState {\n const state: QueryState = { fragments: [], params: [] };\n const conditions = interpretFilters(filters, { cursor, sortOrder });\n\n for (const condition of conditions) {\n mapCondition(state, condition);\n }\n\n return state;\n}\n\n/**\n * Executes a filtered, cursor-paginated query against the audit_logs table.\n */\nexport async function queryLogs(\n spec: AuditQuerySpec,\n db: QueryDb,\n): Promise<AuditQueryResult> {\n const sortOrder = spec.sortOrder ?? \"desc\";\n const limit = Math.min(spec.limit ?? DEFAULT_LIMIT, MAX_LIMIT);\n const fetchLimit = limit + 1;\n\n const cursor =\n spec.cursor !== undefined ? decodeCursor(spec.cursor) : undefined;\n const where = buildWhereClause(spec.filters, cursor, sortOrder);\n\n // LIMIT param is kept separate so buildWhereClause's state is not mutated.\n const queryParams = [...where.params, fetchLimit];\n const limitRef = `$${queryParams.length}`;\n\n const orderDir = sortOrder === \"asc\" ? \"ASC\" : \"DESC\";\n const whereClause =\n where.fragments.length > 0 ? `WHERE ${where.fragments.join(\" AND \")}` : \"\";\n\n const sql =\n `SELECT ${SELECT_COLUMNS} FROM audit_logs ` +\n `${whereClause} ` +\n `ORDER BY timestamp ${orderDir}, id ${orderDir} ` +\n `LIMIT ${limitRef}`;\n\n const raw = (await db.$queryRawUnsafe(sql, ...queryParams)) as Record<\n string,\n unknown\n >[];\n\n const hasNextPage = raw.length > limit;\n const resultRows = hasNextPage ? raw.slice(0, -1) : raw;\n const entries = resultRows.map(rowToAuditLog);\n\n if (hasNextPage) {\n const lastEntry = entries[entries.length - 1];\n if (lastEntry !== undefined) {\n return {\n entries,\n nextCursor: encodeCursor(lastEntry.timestamp, lastEntry.id),\n };\n }\n }\n\n return { entries };\n}\n\n/**\n * Fetches a single audit log entry by its UUID. Returns null if not found.\n */\nexport async function getLogById(\n id: string,\n db: QueryDb,\n): Promise<AuditLog | null> {\n const sql = `SELECT ${SELECT_COLUMNS} FROM audit_logs WHERE id = $1::uuid LIMIT 1`;\n const raw = (await db.$queryRawUnsafe(sql, id)) as Record<string, unknown>[];\n const row = raw[0];\n if (row === undefined) {\n return null;\n }\n return rowToAuditLog(row);\n}\n\n/**\n * Deletes audit log entries older than `before`, optionally scoped to one table.\n * Returns the number of deleted rows.\n */\nexport async function purgeLogs(\n options: { before: Date; tableName?: string },\n db: QueryDb,\n): Promise<{ deletedCount: number }> {\n if (\n options.tableName !== undefined &&\n options.tableName.trim().length === 0\n ) {\n throw new Error(\n \"purgeLogs: tableName must be a non-empty string when provided\",\n );\n }\n\n let sql = \"DELETE FROM audit_logs WHERE timestamp < $1::timestamptz\";\n const params: unknown[] = [options.before.toISOString()];\n\n if (options.tableName !== undefined) {\n params.push(options.tableName);\n sql += ` AND table_name = $${params.length}`;\n }\n\n const deletedCount = await db.$executeRawUnsafe(sql, ...params);\n return { deletedCount };\n}\n\n/**\n * Builds WHERE / AND fragments for an optional `since` filter.\n * The positional `$N` reference is derived from `paramCount` so it is always\n * correct regardless of how many params come before it in the query.\n */\nfunction buildSinceFragment(paramCount: number): { where: string; and: string } {\n const ref = `$${paramCount}::timestamptz`;\n return {\n where: ` WHERE timestamp >= ${ref}`,\n and: ` AND timestamp >= ${ref}`,\n };\n}\n\n/**\n * Returns aggregated statistics for the audit_logs table.\n * Runs six queries in parallel; each optionally filtered by `since`.\n */\nexport async function getStats(\n options: { since?: Date },\n db: QueryDb,\n): Promise<AuditStats> {\n const params: unknown[] = [];\n if (options.since !== undefined) {\n params.push(options.since.toISOString());\n }\n const since =\n params.length > 0\n ? buildSinceFragment(params.length)\n : { where: \"\", and: \"\" };\n\n // Query 1: totalLogs + tablesAudited\n const summaryQuery = db.$queryRawUnsafe(\n `SELECT COUNT(*) AS total_logs, COUNT(DISTINCT table_name) AS tables_audited FROM audit_logs${since.where}`,\n ...params,\n ) as Promise<Array<{ total_logs: unknown; tables_audited: unknown }>>;\n\n // Query 2: eventsPerDay\n const eventsPerDayQuery = db.$queryRawUnsafe(\n `SELECT to_char(date_trunc('day', timestamp), 'YYYY-MM-DD') AS date, COUNT(*) AS count ` +\n `FROM audit_logs${since.where} ` +\n `GROUP BY date_trunc('day', timestamp) ORDER BY date_trunc('day', timestamp) LIMIT 365`,\n ...params,\n ) as Promise<Array<{ date: unknown; count: unknown }>>;\n\n // Query 3: topActors (NULL actors excluded)\n const topActorsQuery = db.$queryRawUnsafe(\n `SELECT actor_id, COUNT(*) AS count FROM audit_logs ` +\n `WHERE actor_id IS NOT NULL${since.and} ` +\n `GROUP BY actor_id ORDER BY COUNT(*) DESC LIMIT 10`,\n ...params,\n ) as Promise<Array<{ actor_id: unknown; count: unknown }>>;\n\n // Query 4: topTables\n const topTablesQuery = db.$queryRawUnsafe(\n `SELECT table_name, COUNT(*) AS count FROM audit_logs${since.where} ` +\n `GROUP BY table_name ORDER BY COUNT(*) DESC LIMIT 10`,\n ...params,\n ) as Promise<Array<{ table_name: unknown; count: unknown }>>;\n\n // Query 5: operationBreakdown\n const operationQuery = db.$queryRawUnsafe(\n `SELECT operation, COUNT(*) AS count FROM audit_logs${since.where} ` +\n `GROUP BY operation`,\n ...params,\n ) as Promise<Array<{ operation: unknown; count: unknown }>>;\n\n // Query 6: severityBreakdown (NULL severities excluded)\n const severityQuery = db.$queryRawUnsafe(\n `SELECT severity, COUNT(*) AS count FROM audit_logs ` +\n `WHERE severity IS NOT NULL${since.and} ` +\n `GROUP BY severity`,\n ...params,\n ) as Promise<Array<{ severity: unknown; count: unknown }>>;\n\n const [summaryRows, eventsPerDayRows, topActorsRows, topTablesRows, operationRows, severityRows] =\n await Promise.all([summaryQuery, eventsPerDayQuery, topActorsQuery, topTablesQuery, operationQuery, severityQuery]);\n\n return assembleStats(\n summaryRows.map((r) => ({ totalLogs: r.total_logs, tablesAudited: r.tables_audited })),\n eventsPerDayRows,\n topActorsRows.map((r) => ({ actorId: r.actor_id, count: r.count })),\n topTablesRows.map((r) => ({ tableName: r.table_name, count: r.count })),\n operationRows,\n severityRows,\n );\n}\n","import type { AuditLog } from \"@usebetterdev/audit-core\";\nimport { isAuditOperation, isAuditSeverity } from \"@usebetterdev/audit-core\";\n\n/**\n * Converts a raw database row (from $queryRawUnsafe) to a camelCase AuditLog.\n * Handles null → undefined mapping and validates enum fields at the boundary.\n *\n * Note: `auditLogToRow` is intentionally absent. The write path lives in\n * `adapter.ts` and uses `$executeRawUnsafe` with explicit `::uuid` / `::jsonb`\n * casts that cannot be expressed through a generic row-mapper function.\n */\nexport function rowToAuditLog(row: Record<string, unknown>): AuditLog {\n const { id, timestamp, table_name, operation, record_id } = row;\n\n if (typeof id !== \"string\") {\n throw new Error(\"rowToAuditLog: id must be a string\");\n }\n if (!(timestamp instanceof Date)) {\n throw new Error(\"rowToAuditLog: timestamp must be a Date\");\n }\n if (typeof table_name !== \"string\") {\n throw new Error(\"rowToAuditLog: table_name must be a string\");\n }\n if (typeof operation !== \"string\") {\n throw new Error(\"rowToAuditLog: operation must be a string\");\n }\n if (typeof record_id !== \"string\") {\n throw new Error(\"rowToAuditLog: record_id must be a string\");\n }\n\n if (!isAuditOperation(operation)) {\n throw new Error(\n `Invalid audit operation: \"${operation}\". Expected one of: INSERT, UPDATE, DELETE`,\n );\n }\n\n const log: AuditLog = {\n id,\n timestamp,\n tableName: table_name,\n operation,\n recordId: record_id,\n };\n\n const {\n actor_id,\n before_data,\n after_data,\n diff,\n label,\n description,\n severity,\n compliance,\n notify,\n reason,\n metadata,\n redacted_fields,\n } = row;\n\n if (actor_id !== null && actor_id !== undefined) {\n log.actorId = String(actor_id);\n }\n if (before_data !== null && before_data !== undefined) {\n log.beforeData = before_data as Record<string, unknown>;\n }\n if (after_data !== null && after_data !== undefined) {\n log.afterData = after_data as Record<string, unknown>;\n }\n if (diff !== null && diff !== undefined) {\n log.diff = diff as { changedFields: string[] };\n }\n if (label !== null && label !== undefined) {\n log.label = String(label);\n }\n if (description !== null && description !== undefined) {\n log.description = String(description);\n }\n if (severity !== null && severity !== undefined) {\n const s = String(severity);\n if (!isAuditSeverity(s)) {\n throw new Error(\n `Invalid audit severity: \"${s}\". Expected one of: low, medium, high, critical`,\n );\n }\n log.severity = s;\n }\n if (compliance !== null && compliance !== undefined) {\n log.compliance = compliance as string[];\n }\n if (notify !== null && notify !== undefined) {\n log.notify = Boolean(notify);\n }\n if (reason !== null && reason !== undefined) {\n log.reason = String(reason);\n }\n if (metadata !== null && metadata !== undefined) {\n log.metadata = metadata as Record<string, unknown>;\n }\n if (redacted_fields !== null && redacted_fields !== undefined) {\n log.redactedFields = redacted_fields as string[];\n }\n\n return log;\n}\n","import type {\n AuditDatabaseAdapter,\n AuditLog,\n AuditQuerySpec,\n AuditQueryResult,\n AuditStats,\n} from \"@usebetterdev/audit-core\";\nimport { queryLogs, getLogById, getStats, purgeLogs } from \"./query.js\";\n\n/**\n * Minimal duck-typed interface for a Prisma client that supports raw query execution.\n * Avoids a hard dependency on the generated `@prisma/client`.\n */\nexport interface PrismaClientWithRaw {\n $executeRawUnsafe(query: string, ...params: unknown[]): Promise<number>;\n $queryRawUnsafe(query: string, ...params: unknown[]): Promise<unknown>;\n}\n\n/**\n * Creates an `AuditDatabaseAdapter` backed by a Prisma client.\n *\n * Implements `writeLog` via `$executeRawUnsafe` for precise control over\n * PostgreSQL type casts (::uuid, ::timestamptz, ::jsonb) and null handling.\n *\n * Implements `queryLogs`, `getLogById`, `getStats`, and `purgeLogs` via\n * parameterized raw SQL — all user inputs are bound parameters, never interpolated.\n */\nexport function prismaAuditAdapter(db: PrismaClientWithRaw): AuditDatabaseAdapter {\n return {\n async writeLog(log: AuditLog): Promise<void> {\n await db.$executeRawUnsafe(\n `INSERT INTO audit_logs (\n id, timestamp, table_name, operation, record_id,\n actor_id, before_data, after_data, diff,\n label, description, severity, compliance,\n notify, reason, metadata, redacted_fields\n ) VALUES (\n $1::uuid, $2::timestamptz, $3, $4, $5,\n $6, $7::jsonb, $8::jsonb, $9::jsonb,\n $10, $11, $12, $13::jsonb,\n $14, $15, $16::jsonb, $17::jsonb\n )`,\n log.id,\n log.timestamp.toISOString(),\n log.tableName,\n log.operation,\n log.recordId,\n log.actorId ?? null,\n jsonOrNull(log.beforeData),\n jsonOrNull(log.afterData),\n jsonOrNull(log.diff),\n log.label ?? null,\n log.description ?? null,\n log.severity ?? null,\n jsonOrNull(log.compliance),\n log.notify ?? null,\n log.reason ?? null,\n jsonOrNull(log.metadata),\n jsonOrNull(log.redactedFields),\n );\n },\n\n async queryLogs(spec: AuditQuerySpec): Promise<AuditQueryResult> {\n return queryLogs(spec, db);\n },\n\n async getLogById(id: string): Promise<AuditLog | null> {\n return getLogById(id, db);\n },\n\n async getStats(options?: { since?: Date }): Promise<AuditStats> {\n return getStats(options ?? {}, db);\n },\n\n async purgeLogs(options: {\n before: Date;\n tableName?: string;\n }): Promise<{ deletedCount: number }> {\n return purgeLogs(options, db);\n },\n };\n}\n\nfunction jsonOrNull(value: unknown): string | null {\n if (value === undefined || value === null) {\n return null;\n }\n return JSON.stringify(value);\n}\n","import type { CaptureLogInput, AuditOperation } from \"@usebetterdev/audit-core\";\nimport { getAuditContext } from \"@usebetterdev/audit-core\";\nimport { getAuditOperation } from \"./action-map.js\";\nimport { buildTableNameResolver } from \"./model-map.js\";\nimport { getTxClient } from \"./tx-store.js\";\n\n/**\n * Minimal duck-typed interface for a Prisma client that supports `$extends`.\n * Avoids a hard dependency on the generated `@prisma/client`.\n */\nexport interface PrismaClientLike {\n $extends(extension: PrismaExtensionDefinition): unknown;\n}\n\n/**\n * Shape of the Prisma `$extends` query extension definition.\n * We only type what we need — the full shape lives in `@prisma/client/extension`.\n */\ninterface PrismaExtensionDefinition {\n query: {\n $allModels: {\n $allOperations: (params: {\n model: string;\n operation: string;\n args: unknown;\n query: (args: unknown) => Promise<unknown>;\n }) => Promise<unknown>;\n };\n };\n}\n\n/**\n * Minimal duck-typed interface for a Prisma model delegate.\n * Used internally to issue before-state lookups prior to mutations.\n * Any real Prisma client's model delegate satisfies this.\n */\ninterface PrismaModelDelegate {\n findUnique(args: { where: unknown }): Promise<unknown>;\n findMany(args: { where?: unknown; take?: number }): Promise<unknown[]>;\n}\n\n/**\n * Represents the before-state captured prior to a mutation.\n *\n * - `\"none\"`: no before-state needed (create operations, single delete whose\n * result already contains the deleted record)\n * - `\"single\"`: one record looked up via `findUnique` (update, upsert)\n * - `\"many\"`: multiple records looked up via `findMany` (updateMany, deleteMany)\n */\ntype BeforeState =\n | { type: \"none\" }\n | { type: \"single\"; row: Record<string, unknown> | undefined }\n | { type: \"many\"; rows: Record<string, unknown>[] };\n\nexport type BulkMode = \"per-row\" | \"bulk\";\n\nexport interface WithAuditExtensionOptions {\n /**\n * How to handle bulk write operations (`createMany`, `updateMany`, `deleteMany`).\n *\n * - `\"per-row\"` (default): for `createMany` and `createManyAndReturn`, fires\n * one audit entry per item in `args.data`. For `updateMany` and `deleteMany`,\n * fires one entry per row captured in the before-state lookup, or a single\n * bulk entry if no rows are found.\n * - `\"bulk\"`: fires a single audit entry for the entire operation.\n */\n bulkMode?: BulkMode;\n /**\n * Called when audit capture fails. Falls back to `console.error` if not set.\n * Errors are always swallowed — audit failures never propagate to callers.\n */\n onError?: (error: unknown) => void;\n /**\n * Extra structured data merged into every audit log entry produced by this\n * extension. Useful for tagging logs with the adapter name or environment.\n *\n * @example\n * ```ts\n * withAuditExtension(prisma, audit.captureLog, { metadata: { adapter: \"prisma\" } })\n * ```\n */\n metadata?: Record<string, unknown>;\n /**\n * Maps a Prisma model name to the `tableName` stored in audit logs.\n *\n * By default the extension auto-detects the SQL table name from Prisma's\n * `_runtimeDataModel` (populated by `@@map` directives). If the runtime\n * metadata is unavailable, the model name is used as-is.\n *\n * When provided, this function takes full precedence over auto-detection.\n *\n * @example\n * ```ts\n * withAuditExtension(prisma, captureLog, {\n * tableNameTransform: (name) => name.toLowerCase(),\n * })\n * ```\n */\n tableNameTransform?: (modelName: string) => string;\n /**\n * Table names (SQL names, after `tableNameTransform`) to skip before-state\n * capture for. No extra `findUnique` or `findMany` is issued for these tables.\n *\n * Useful for high-volume tables where the additional SELECT is too expensive.\n *\n * @example\n * ```ts\n * withAuditExtension(prisma, captureLog, {\n * skipBeforeCapture: [\"events\", \"metrics\"],\n * })\n * ```\n */\n skipBeforeCapture?: string[];\n /**\n * Maximum number of rows fetched by the before-state `findMany` for\n * `updateMany` and `deleteMany` operations (default: `100`).\n *\n * When the matched row count exceeds this limit the before-state lookup is\n * skipped and a single bulk audit entry is written instead — preventing\n * unbounded memory consumption on large batch operations.\n *\n * Set to `Infinity` to disable the limit (not recommended in production).\n *\n * @example\n * ```ts\n * withAuditExtension(prisma, captureLog, { maxBeforeStateRows: 50 })\n * ```\n */\n maxBeforeStateRows?: number;\n}\n\n/**\n * Wraps a Prisma client with a `$extends` query extension that calls\n * `captureLog` after each successful mutation.\n *\n * Returns a new extended client of the same type `T`. Use this extended\n * client in place of the original — all queries behave identically.\n *\n * Actor identity is read automatically from the current `AuditContext`\n * (set via `audit.withContext` or `runWithAuditContext` from audit-core).\n *\n * For `update` and `upsert`, a `findUnique` is issued before the mutation\n * to capture `before_data`. For `updateMany` and `deleteMany` in `\"per-row\"`\n * mode, a `findMany` is issued before the mutation to capture per-row state.\n * Pass `skipBeforeCapture` to opt out for specific high-volume tables.\n *\n * Filtering by audited tables is NOT done here — `captureLog` (from\n * `betterAudit`) already skips tables not in `auditTables`.\n *\n * @example\n * ```ts\n * // auditTables must use SQL table names (auto-detected from @@map directives)\n * const audit = betterAudit({ database: prismaAuditAdapter(prisma), auditTables: [\"users\"] });\n * const auditedPrisma = withAuditExtension(prisma, audit.captureLog);\n * // Use auditedPrisma everywhere — mutations are transparently logged\n * ```\n */\nexport function withAuditExtension<T extends PrismaClientLike>(\n prisma: T,\n captureLog: (input: CaptureLogInput) => Promise<void>,\n options: WithAuditExtensionOptions = {},\n): T {\n const bulkMode = options.bulkMode ?? \"per-row\";\n const handleError = buildErrorHandler(options.onError);\n const metadata = options.metadata;\n const skipBeforeCapture = options.skipBeforeCapture;\n const maxBeforeStateRows = options.maxBeforeStateRows ?? 100;\n const resolveTableName = buildTableNameResolver(prisma, options.tableNameTransform);\n\n const extended = prisma.$extends({\n query: {\n $allModels: {\n async $allOperations({\n model,\n operation,\n args,\n query,\n }: {\n model: string;\n operation: string;\n args: unknown;\n query: (args: unknown) => Promise<unknown>;\n }) {\n const auditOp = getAuditOperation(operation);\n if (auditOp === undefined) {\n return query(args);\n }\n\n const tableName = resolveTableName(model);\n const shouldSkipBefore =\n skipBeforeCapture !== undefined && skipBeforeCapture.includes(tableName);\n\n const beforeState: BeforeState = shouldSkipBefore\n ? { type: \"none\" }\n : await fetchBeforeState({ prisma, model, operation, args, handleError, maxBeforeStateRows });\n\n const result = await query(args);\n\n try {\n await fireCaptureLog({\n tableName,\n operation,\n auditOp,\n args,\n result,\n beforeState,\n captureLog,\n bulkMode,\n metadata,\n actorId: getAuditContext()?.actorId,\n });\n } catch (err) {\n handleError(err);\n }\n\n return result;\n },\n },\n },\n });\n\n // The $extends return type is `unknown` — casting back to T is safe because\n // the extension only adds transparent interception; it does not change the query API.\n return extended as unknown as T;\n}\n\n// ---------------------------------------------------------------------------\n// Before-state fetching\n// ---------------------------------------------------------------------------\n\ninterface FetchBeforeStateParams {\n prisma: PrismaClientLike;\n model: string;\n operation: string;\n args: unknown;\n handleError: (err: unknown) => void;\n maxBeforeStateRows: number;\n}\n\n/**\n * Issues a `findUnique` or `findMany` before the mutation to capture the\n * record state that will be stored as `before_data` in the audit log.\n *\n * Returns `{ type: \"none\" }` for operations that don't need a pre-query:\n * - `create` / `createMany` / `createManyAndReturn`: no before-state exists\n * - `delete`: Prisma returns the deleted record as the mutation result\n */\nasync function fetchBeforeState({\n prisma,\n model,\n operation,\n args,\n handleError,\n maxBeforeStateRows,\n}: FetchBeforeStateParams): Promise<BeforeState> {\n if (\n operation === \"create\" ||\n operation === \"createMany\" ||\n operation === \"createManyAndReturn\" ||\n operation === \"delete\"\n ) {\n return { type: \"none\" };\n }\n\n const delegate = getModelDelegate(getTxClient() ?? prisma, model);\n if (delegate === undefined) {\n return { type: \"none\" };\n }\n\n if (operation === \"update\" || operation === \"upsert\") {\n const where = getArgsWhere(args);\n if (where === undefined) {\n return { type: \"single\", row: undefined };\n }\n try {\n const found = await delegate.findUnique({ where });\n return { type: \"single\", row: toRecord(found) };\n } catch (err) {\n handleError(err);\n return { type: \"single\", row: undefined };\n }\n }\n\n if (operation === \"updateMany\" || operation === \"deleteMany\") {\n const where = getArgsWhere(args);\n try {\n const rows = await delegate.findMany({ where: where ?? {}, take: maxBeforeStateRows + 1 });\n if (rows.length > maxBeforeStateRows) {\n // Too many rows to capture individually — fall back to a single bulk entry.\n return { type: \"none\" };\n }\n const records = rows\n .map((r) => toRecord(r))\n .filter((r): r is Record<string, unknown> => r !== undefined);\n return { type: \"many\", rows: records };\n } catch (err) {\n handleError(err);\n return { type: \"many\", rows: [] };\n }\n }\n\n return { type: \"none\" };\n}\n\n// ---------------------------------------------------------------------------\n// captureLog dispatch\n// ---------------------------------------------------------------------------\n\ninterface FireCaptureLogParams {\n tableName: string;\n operation: string;\n auditOp: AuditOperation;\n args: unknown;\n result: unknown;\n beforeState: BeforeState;\n captureLog: (input: CaptureLogInput) => Promise<void>;\n bulkMode: BulkMode;\n metadata: Record<string, unknown> | undefined;\n actorId: string | undefined;\n}\n\nasync function fireCaptureLog({\n tableName,\n operation,\n auditOp,\n args,\n result,\n beforeState,\n captureLog,\n bulkMode,\n metadata,\n actorId,\n}: FireCaptureLogParams): Promise<void> {\n if (operation === \"createMany\") {\n await handleCreateMany({ tableName, auditOp, args, captureLog, bulkMode, metadata, actorId });\n return;\n }\n\n if (operation === \"createManyAndReturn\") {\n await handleCreateManyAndReturn({ tableName, auditOp, result, captureLog, bulkMode, metadata, actorId });\n return;\n }\n\n if (operation === \"updateMany\") {\n await handleUpdateMany({ tableName, auditOp, beforeState, captureLog, bulkMode, metadata, actorId });\n return;\n }\n\n if (operation === \"deleteMany\") {\n await handleDeleteMany({ tableName, auditOp, beforeState, captureLog, bulkMode, metadata, actorId });\n return;\n }\n\n // Single-record operations: create, update, upsert, delete\n const row = toRecord(result);\n const recordId = (row !== undefined ? extractId(row) : undefined) ?? \"unknown\";\n\n if (operation === \"upsert\") {\n // A before-state row means the record existed → UPDATE path.\n // No before-state row means the record was created → INSERT path.\n const before = beforeState.type === \"single\" ? beforeState.row : undefined;\n const effectiveOp: AuditOperation = before !== undefined ? \"UPDATE\" : \"INSERT\";\n await captureLog({\n tableName,\n operation: effectiveOp,\n recordId,\n ...(before !== undefined && { before }),\n ...(row !== undefined && { after: row }),\n ...(metadata !== undefined && { metadata }),\n ...(actorId !== undefined && { actorId }),\n });\n return;\n }\n\n if (auditOp === \"INSERT\") {\n await captureLog({\n tableName,\n operation: auditOp,\n recordId,\n ...(row !== undefined && { after: row }),\n ...(metadata !== undefined && { metadata }),\n ...(actorId !== undefined && { actorId }),\n });\n return;\n }\n\n if (auditOp === \"UPDATE\") {\n const before = beforeState.type === \"single\" ? beforeState.row : undefined;\n await captureLog({\n tableName,\n operation: auditOp,\n recordId,\n ...(before !== undefined && { before }),\n ...(row !== undefined && { after: row }),\n ...(metadata !== undefined && { metadata }),\n ...(actorId !== undefined && { actorId }),\n });\n return;\n }\n\n // DELETE: Prisma returns the deleted record as the result — use it as before_data.\n await captureLog({\n tableName,\n operation: auditOp,\n recordId,\n ...(row !== undefined && { before: row }),\n ...(metadata !== undefined && { metadata }),\n ...(actorId !== undefined && { actorId }),\n });\n}\n\n// ---------------------------------------------------------------------------\n// Per-operation handlers\n// ---------------------------------------------------------------------------\n\ninterface HandleCreateManyParams {\n tableName: string;\n auditOp: AuditOperation;\n args: unknown;\n captureLog: (input: CaptureLogInput) => Promise<void>;\n bulkMode: BulkMode;\n metadata: Record<string, unknown> | undefined;\n actorId: string | undefined;\n}\n\nasync function handleCreateMany({\n tableName,\n auditOp,\n args,\n captureLog,\n bulkMode,\n metadata,\n actorId,\n}: HandleCreateManyParams): Promise<void> {\n if (bulkMode === \"bulk\") {\n await captureLog({\n tableName,\n operation: auditOp,\n recordId: \"unknown\",\n ...(metadata !== undefined && { metadata }),\n ...(actorId !== undefined && { actorId }),\n });\n return;\n }\n\n // per-row: fire one entry per item in args.data\n const rows = getArgsData(args);\n if (rows.length === 0) {\n await captureLog({\n tableName,\n operation: auditOp,\n recordId: \"unknown\",\n ...(metadata !== undefined && { metadata }),\n ...(actorId !== undefined && { actorId }),\n });\n return;\n }\n\n await Promise.all(\n rows.map((row) => {\n const record = toRecord(row);\n const recordId = (record !== undefined ? extractId(record) : undefined) ?? \"unknown\";\n return captureLog({\n tableName,\n operation: auditOp,\n recordId,\n ...(record !== undefined && { after: record }),\n ...(metadata !== undefined && { metadata }),\n ...(actorId !== undefined && { actorId }),\n });\n }),\n );\n}\n\ninterface HandleCreateManyAndReturnParams {\n tableName: string;\n auditOp: AuditOperation;\n result: unknown;\n captureLog: (input: CaptureLogInput) => Promise<void>;\n bulkMode: BulkMode;\n metadata: Record<string, unknown> | undefined;\n actorId: string | undefined;\n}\n\nasync function handleCreateManyAndReturn({\n tableName,\n auditOp,\n result,\n captureLog,\n bulkMode,\n metadata,\n actorId,\n}: HandleCreateManyAndReturnParams): Promise<void> {\n if (bulkMode === \"bulk\") {\n await captureLog({\n tableName,\n operation: auditOp,\n recordId: \"unknown\",\n ...(metadata !== undefined && { metadata }),\n ...(actorId !== undefined && { actorId }),\n });\n return;\n }\n\n const rows = Array.isArray(result) ? result : [];\n if (rows.length === 0) {\n await captureLog({\n tableName,\n operation: auditOp,\n recordId: \"unknown\",\n ...(metadata !== undefined && { metadata }),\n ...(actorId !== undefined && { actorId }),\n });\n return;\n }\n\n await Promise.all(\n rows.map((row) => {\n const record = toRecord(row);\n const recordId = (record !== undefined ? extractId(record) : undefined) ?? \"unknown\";\n return captureLog({\n tableName,\n operation: auditOp,\n recordId,\n ...(record !== undefined && { after: record }),\n ...(metadata !== undefined && { metadata }),\n ...(actorId !== undefined && { actorId }),\n });\n }),\n );\n}\n\ninterface HandleUpdateManyParams {\n tableName: string;\n auditOp: AuditOperation;\n beforeState: BeforeState;\n captureLog: (input: CaptureLogInput) => Promise<void>;\n bulkMode: BulkMode;\n metadata: Record<string, unknown> | undefined;\n actorId: string | undefined;\n}\n\nasync function handleUpdateMany({\n tableName,\n auditOp,\n beforeState,\n captureLog,\n bulkMode,\n metadata,\n actorId,\n}: HandleUpdateManyParams): Promise<void> {\n if (bulkMode === \"bulk\") {\n await captureLog({\n tableName,\n operation: auditOp,\n recordId: \"unknown\",\n ...(metadata !== undefined && { metadata }),\n ...(actorId !== undefined && { actorId }),\n });\n return;\n }\n\n // per-row: fire one entry per row captured before the mutation\n const rows = beforeState.type === \"many\" ? beforeState.rows : [];\n if (rows.length === 0) {\n await captureLog({\n tableName,\n operation: auditOp,\n recordId: \"unknown\",\n ...(metadata !== undefined && { metadata }),\n ...(actorId !== undefined && { actorId }),\n });\n return;\n }\n\n await Promise.all(\n rows.map((row) => {\n const recordId = extractId(row) ?? \"unknown\";\n return captureLog({\n tableName,\n operation: auditOp,\n recordId,\n before: row,\n ...(metadata !== undefined && { metadata }),\n ...(actorId !== undefined && { actorId }),\n });\n }),\n );\n}\n\ninterface HandleDeleteManyParams {\n tableName: string;\n auditOp: AuditOperation;\n beforeState: BeforeState;\n captureLog: (input: CaptureLogInput) => Promise<void>;\n bulkMode: BulkMode;\n metadata: Record<string, unknown> | undefined;\n actorId: string | undefined;\n}\n\nasync function handleDeleteMany({\n tableName,\n auditOp,\n beforeState,\n captureLog,\n bulkMode,\n metadata,\n actorId,\n}: HandleDeleteManyParams): Promise<void> {\n if (bulkMode === \"bulk\") {\n await captureLog({\n tableName,\n operation: auditOp,\n recordId: \"unknown\",\n ...(metadata !== undefined && { metadata }),\n ...(actorId !== undefined && { actorId }),\n });\n return;\n }\n\n // per-row: fire one entry per row captured before deletion\n const rows = beforeState.type === \"many\" ? beforeState.rows : [];\n if (rows.length === 0) {\n await captureLog({\n tableName,\n operation: auditOp,\n recordId: \"unknown\",\n ...(metadata !== undefined && { metadata }),\n ...(actorId !== undefined && { actorId }),\n });\n return;\n }\n\n await Promise.all(\n rows.map((row) => {\n const recordId = extractId(row) ?? \"unknown\";\n return captureLog({\n tableName,\n operation: auditOp,\n recordId,\n before: row,\n ...(metadata !== undefined && { metadata }),\n ...(actorId !== undefined && { actorId }),\n });\n }),\n );\n}\n\n// ---------------------------------------------------------------------------\n// Helpers\n// ---------------------------------------------------------------------------\n\nfunction buildErrorHandler(\n onError: ((error: unknown) => void) | undefined,\n): (error: unknown) => void {\n return (error: unknown) => {\n try {\n if (onError !== undefined) {\n onError(error);\n } else {\n const msg = error instanceof Error ? error.message : String(error);\n console.error(`audit-prisma: capture failed — ${msg}`);\n }\n } catch {\n // onError itself threw — swallow to never break mutations.\n }\n };\n}\n\n/**\n * Returns true when `value` quacks like a Prisma model delegate with\n * `findUnique` and `findMany` methods.\n */\nfunction isModelDelegate(value: unknown): value is PrismaModelDelegate {\n if (value === null || value === undefined || typeof value !== \"object\") {\n return false;\n }\n return (\n \"findUnique\" in value &&\n \"findMany\" in value &&\n typeof Reflect.get(value, \"findUnique\") === \"function\" &&\n typeof Reflect.get(value, \"findMany\") === \"function\"\n );\n}\n\n/**\n * Returns the model delegate for `model` (e.g. `prisma.user` for model `\"User\"`)\n * by lowercasing the first character and doing a dynamic property lookup.\n * Returns `undefined` when the delegate is not found or doesn't have the\n * expected shape — the caller falls back to `{ type: \"none\" }` before-state.\n */\nfunction getModelDelegate(\n client: unknown,\n model: string,\n): PrismaModelDelegate | undefined {\n const lowerModel = model.charAt(0).toLowerCase() + model.slice(1);\n const value: unknown = Reflect.get(Object(client), lowerModel);\n if (isModelDelegate(value)) {\n return value;\n }\n return undefined;\n}\n\nfunction extractId(record: Record<string, unknown>): string | undefined {\n const id = record[\"id\"];\n if (id !== undefined && id !== null) {\n return String(id);\n }\n return undefined;\n}\n\nfunction toRecord(value: unknown): Record<string, unknown> | undefined {\n if (value !== null && value !== undefined && typeof value === \"object\" && !Array.isArray(value)) {\n return value as Record<string, unknown>;\n }\n return undefined;\n}\n\nfunction getArgsData(args: unknown): unknown[] {\n if (args !== null && typeof args === \"object\" && \"data\" in args) {\n const data = Reflect.get(args, \"data\");\n if (Array.isArray(data)) {\n return data;\n }\n }\n return [];\n}\n\n/**\n * Extracts `args.where` for before-state lookups. Returns `undefined` when\n * the argument object has no `where` property.\n */\nfunction getArgsWhere(args: unknown): unknown {\n if (args !== null && typeof args === \"object\" && \"where\" in args) {\n return Reflect.get(args, \"where\");\n }\n return undefined;\n}\n","import type { AuditOperation } from \"@usebetterdev/audit-core\";\n\n/**\n * Maps Prisma query action names to AuditOperation values.\n * Actions not in this map are read-only queries and are not audited.\n */\nexport const ACTION_MAP = {\n create: \"INSERT\",\n createMany: \"INSERT\",\n createManyAndReturn: \"INSERT\",\n update: \"UPDATE\",\n updateMany: \"UPDATE\",\n upsert: \"UPDATE\",\n delete: \"DELETE\",\n deleteMany: \"DELETE\",\n} as const satisfies Record<string, AuditOperation>;\n\nexport type MutableAction = keyof typeof ACTION_MAP;\n\nfunction isMutableAction(action: string): action is MutableAction {\n return action in ACTION_MAP;\n}\n\n/**\n * Returns the AuditOperation for a Prisma action name, or `undefined` for\n * read-only operations (findMany, findUnique, count, aggregate, etc.).\n */\nexport function getAuditOperation(action: string): AuditOperation | undefined {\n if (isMutableAction(action)) {\n return ACTION_MAP[action];\n }\n return undefined;\n}\n","/**\n * Runtime model-to-table mapping for Prisma clients that expose\n * `_runtimeDataModel.models`. This lets the audit extension auto-detect\n * the SQL table name (from `@@map`) without user configuration.\n */\n\ninterface RuntimeModel {\n dbName: string | null;\n}\n\ninterface RuntimeDataModel {\n models: Record<string, RuntimeModel>;\n}\n\ninterface PrismaWithRuntimeDataModel {\n _runtimeDataModel: RuntimeDataModel;\n}\n\n/**\n * Type guard: checks whether `value` exposes Prisma's `_runtimeDataModel.models`.\n */\nfunction hasPrismaRuntimeDataModel(value: object): value is PrismaWithRuntimeDataModel {\n if (!(\"_runtimeDataModel\" in value)) {\n return false;\n }\n\n const { _runtimeDataModel: rdm } = value;\n if (rdm === null || typeof rdm !== \"object\") {\n return false;\n }\n\n return \"models\" in rdm;\n}\n\n/**\n * Returns a `Record<string, string>` mapping Prisma model names to their\n * SQL table names (from `@@map`). Models without `@@map` map to themselves.\n *\n * Returns `undefined` if the client does not expose `_runtimeDataModel`.\n */\nexport function prismaModelMap(prisma: object): Record<string, string> | undefined {\n if (!hasPrismaRuntimeDataModel(prisma)) {\n return undefined;\n }\n\n const result: Record<string, string> = {};\n for (const [modelName, meta] of Object.entries(prisma._runtimeDataModel.models)) {\n result[modelName] = meta.dbName ?? modelName;\n }\n return result;\n}\n\n/**\n * Builds a function that resolves a Prisma model name to a table name.\n *\n * Resolution order:\n * 1. If `transform` is provided, use it (full user control).\n * 2. Else if the client exposes `_runtimeDataModel`, use `dbName ?? modelName`.\n * 3. Else return the model name as-is (safe fallback).\n */\nexport function buildTableNameResolver(\n prisma: object,\n transform: ((modelName: string) => string) | undefined,\n): (modelName: string) => string {\n if (transform !== undefined) {\n return transform;\n }\n\n const map = prismaModelMap(prisma);\n if (map !== undefined) {\n return (modelName: string) => map[modelName] ?? modelName;\n }\n\n return (modelName: string) => modelName;\n}\n","import { AsyncLocalStorage } from \"node:async_hooks\";\n\n/**\n * Stores the current Prisma transaction client for the duration of a\n * tenant-scoped request.\n *\n * When the audit extension issues a `findUnique` to capture before-state,\n * it queries the outer (non-transaction) Prisma client by default. That\n * connection has no `SET LOCAL app.current_tenant` active, so RLS blocks\n * all rows and before-state is lost.\n *\n * Call `runWithTxClient(tx, fn)` inside `prismaAdapter.runWithTenant` (after\n * the `SET LOCAL` statement) to make `tx` available to the extension. The\n * extension reads it via `getTxClient()` and uses it for the `findUnique`\n * instead of the outer client.\n */\nconst txStorage = new AsyncLocalStorage<unknown>();\n\n/**\n * Stores `tx` in the async context for the duration of `fn`.\n *\n * Call this wrapping the inner callback of a Prisma `$transaction` after the\n * tenant `SET LOCAL` has been issued so the audit extension can reuse the\n * same connection for before-state lookups.\n *\n * @example\n * ```ts\n * // Inside your betterTenant adapter wrapper:\n * const adapter = {\n * ...db.adapter,\n * runWithTenant(tenantId, fn) {\n * return db.adapter.runWithTenant(tenantId, (tx) =>\n * runWithTxClient(tx, () => fn(tx))\n * );\n * },\n * };\n * ```\n */\nexport function runWithTxClient<T>(tx: unknown, fn: () => Promise<T>): Promise<T> {\n return txStorage.run(tx, fn);\n}\n\n/**\n * Returns the current transaction client stored by `runWithTxClient`, or\n * `undefined` when called outside a tenant transaction context.\n */\nexport function getTxClient(): unknown {\n return txStorage.getStore();\n}\n"],"mappings":";AASA;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OACK;;;ACbP,SAAS,kBAAkB,uBAAuB;AAU3C,SAAS,cAAc,KAAwC;AACpE,QAAM,EAAE,IAAI,WAAW,YAAY,WAAW,UAAU,IAAI;AAE5D,MAAI,OAAO,OAAO,UAAU;AAC1B,UAAM,IAAI,MAAM,oCAAoC;AAAA,EACtD;AACA,MAAI,EAAE,qBAAqB,OAAO;AAChC,UAAM,IAAI,MAAM,yCAAyC;AAAA,EAC3D;AACA,MAAI,OAAO,eAAe,UAAU;AAClC,UAAM,IAAI,MAAM,4CAA4C;AAAA,EAC9D;AACA,MAAI,OAAO,cAAc,UAAU;AACjC,UAAM,IAAI,MAAM,2CAA2C;AAAA,EAC7D;AACA,MAAI,OAAO,cAAc,UAAU;AACjC,UAAM,IAAI,MAAM,2CAA2C;AAAA,EAC7D;AAEA,MAAI,CAAC,iBAAiB,SAAS,GAAG;AAChC,UAAM,IAAI;AAAA,MACR,6BAA6B,SAAS;AAAA,IACxC;AAAA,EACF;AAEA,QAAM,MAAgB;AAAA,IACpB;AAAA,IACA;AAAA,IACA,WAAW;AAAA,IACX;AAAA,IACA,UAAU;AAAA,EACZ;AAEA,QAAM;AAAA,IACJ;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACF,IAAI;AAEJ,MAAI,aAAa,QAAQ,aAAa,QAAW;AAC/C,QAAI,UAAU,OAAO,QAAQ;AAAA,EAC/B;AACA,MAAI,gBAAgB,QAAQ,gBAAgB,QAAW;AACrD,QAAI,aAAa;AAAA,EACnB;AACA,MAAI,eAAe,QAAQ,eAAe,QAAW;AACnD,QAAI,YAAY;AAAA,EAClB;AACA,MAAI,SAAS,QAAQ,SAAS,QAAW;AACvC,QAAI,OAAO;AAAA,EACb;AACA,MAAI,UAAU,QAAQ,UAAU,QAAW;AACzC,QAAI,QAAQ,OAAO,KAAK;AAAA,EAC1B;AACA,MAAI,gBAAgB,QAAQ,gBAAgB,QAAW;AACrD,QAAI,cAAc,OAAO,WAAW;AAAA,EACtC;AACA,MAAI,aAAa,QAAQ,aAAa,QAAW;AAC/C,UAAM,IAAI,OAAO,QAAQ;AACzB,QAAI,CAAC,gBAAgB,CAAC,GAAG;AACvB,YAAM,IAAI;AAAA,QACR,4BAA4B,CAAC;AAAA,MAC/B;AAAA,IACF;AACA,QAAI,WAAW;AAAA,EACjB;AACA,MAAI,eAAe,QAAQ,eAAe,QAAW;AACnD,QAAI,aAAa;AAAA,EACnB;AACA,MAAI,WAAW,QAAQ,WAAW,QAAW;AAC3C,QAAI,SAAS,QAAQ,MAAM;AAAA,EAC7B;AACA,MAAI,WAAW,QAAQ,WAAW,QAAW;AAC3C,QAAI,SAAS,OAAO,MAAM;AAAA,EAC5B;AACA,MAAI,aAAa,QAAQ,aAAa,QAAW;AAC/C,QAAI,WAAW;AAAA,EACjB;AACA,MAAI,oBAAoB,QAAQ,oBAAoB,QAAW;AAC7D,QAAI,iBAAiB;AAAA,EACvB;AAEA,SAAO;AACT;;;AD7EA,IAAM,gBAAgB;AACtB,IAAM,YAAY;AAElB,IAAM,iBACJ;AASF,SAAS,SAAS,OAAmB,OAAwB;AAC3D,QAAM,OAAO,KAAK,KAAK;AACvB,SAAO,IAAI,MAAM,OAAO,MAAM;AAChC;AAGA,IAAM,qBAAuD;AAAA,EAC3D,WAAW;AAAA,EACX,UAAU;AAAA,EACV,SAAS;AAAA,EACT,UAAU;AAAA,EACV,WAAW;AACb;AAGA,SAAS,aAAa,OAAmB,WAAkC;AACzE,UAAQ,UAAU,MAAM;AAAA,IACtB,KAAK,MAAM;AACT,YAAM,MAAM,mBAAmB,UAAU,KAAK;AAC9C,YAAM,IAAI,SAAS,OAAO,UAAU,KAAK;AACzC,YAAM,UAAU,KAAK,GAAG,GAAG,MAAM,CAAC,EAAE;AACpC;AAAA,IACF;AAAA,IACA,KAAK,MAAM;AACT,YAAM,MAAM,mBAAmB,UAAU,KAAK;AAC9C,YAAM,IAAI,SAAS,OAAO,UAAU,MAAM;AAC1C,YAAM,UAAU,KAAK,GAAG,GAAG,UAAU,CAAC,WAAW;AACjD;AAAA,IACF;AAAA,IACA,KAAK,gBAAgB;AACnB,YAAM,IAAI,SAAS,OAAO,UAAU,MAAM,YAAY,CAAC;AACvD,YAAM,UAAU,KAAK,gBAAgB,CAAC,eAAe;AACrD;AAAA,IACF;AAAA,IACA,KAAK,gBAAgB;AACnB,YAAM,IAAI,SAAS,OAAO,UAAU,MAAM,YAAY,CAAC;AACvD,YAAM,UAAU,KAAK,gBAAgB,CAAC,eAAe;AACrD;AAAA,IACF;AAAA,IACA,KAAK,UAAU;AACb,YAAM,IAAI,SAAS,OAAO,UAAU,OAAO;AAC3C,YAAM,UAAU,KAAK,gBAAgB,CAAC,yBAAyB,CAAC,GAAG;AACnE;AAAA,IACF;AAAA,IACA,KAAK,cAAc;AACjB,YAAM,IAAI,SAAS,OAAO,KAAK,UAAU,UAAU,IAAI,CAAC;AACxD,YAAM,UAAU,KAAK,iBAAiB,CAAC,SAAS;AAChD;AAAA,IACF;AAAA,IACA,KAAK,UAAU;AACb,YAAM,OAAO,SAAS,OAAO,UAAU,UAAU,YAAY,CAAC;AAC9D,YAAM,OAAO,SAAS,OAAO,UAAU,EAAE;AACzC,UAAI,UAAU,cAAc,OAAO;AACjC,cAAM,UAAU;AAAA,UACd,gBAAgB,IAAI,iCAAiC,IAAI,gCAAgC,IAAI;AAAA,QAC/F;AAAA,MACF,OAAO;AACL,cAAM,UAAU;AAAA,UACd,gBAAgB,IAAI,iCAAiC,IAAI,gCAAgC,IAAI;AAAA,QAC/F;AAAA,MACF;AACA;AAAA,IACF;AAAA,EACF;AACF;AASA,SAAS,iBACP,SACA,QACA,WACY;AACZ,QAAM,QAAoB,EAAE,WAAW,CAAC,GAAG,QAAQ,CAAC,EAAE;AACtD,QAAM,aAAa,iBAAiB,SAAS,EAAE,QAAQ,UAAU,CAAC;AAElE,aAAW,aAAa,YAAY;AAClC,iBAAa,OAAO,SAAS;AAAA,EAC/B;AAEA,SAAO;AACT;AAKA,eAAsB,UACpB,MACA,IAC2B;AAC3B,QAAM,YAAY,KAAK,aAAa;AACpC,QAAM,QAAQ,KAAK,IAAI,KAAK,SAAS,eAAe,SAAS;AAC7D,QAAM,aAAa,QAAQ;AAE3B,QAAM,SACJ,KAAK,WAAW,SAAY,aAAa,KAAK,MAAM,IAAI;AAC1D,QAAM,QAAQ,iBAAiB,KAAK,SAAS,QAAQ,SAAS;AAG9D,QAAM,cAAc,CAAC,GAAG,MAAM,QAAQ,UAAU;AAChD,QAAM,WAAW,IAAI,YAAY,MAAM;AAEvC,QAAM,WAAW,cAAc,QAAQ,QAAQ;AAC/C,QAAM,cACJ,MAAM,UAAU,SAAS,IAAI,SAAS,MAAM,UAAU,KAAK,OAAO,CAAC,KAAK;AAE1E,QAAM,MACJ,UAAU,cAAc,oBACrB,WAAW,uBACQ,QAAQ,QAAQ,QAAQ,UACrC,QAAQ;AAEnB,QAAM,MAAO,MAAM,GAAG,gBAAgB,KAAK,GAAG,WAAW;AAKzD,QAAM,cAAc,IAAI,SAAS;AACjC,QAAM,aAAa,cAAc,IAAI,MAAM,GAAG,EAAE,IAAI;AACpD,QAAM,UAAU,WAAW,IAAI,aAAa;AAE5C,MAAI,aAAa;AACf,UAAM,YAAY,QAAQ,QAAQ,SAAS,CAAC;AAC5C,QAAI,cAAc,QAAW;AAC3B,aAAO;AAAA,QACL;AAAA,QACA,YAAY,aAAa,UAAU,WAAW,UAAU,EAAE;AAAA,MAC5D;AAAA,IACF;AAAA,EACF;AAEA,SAAO,EAAE,QAAQ;AACnB;AAKA,eAAsB,WACpB,IACA,IAC0B;AAC1B,QAAM,MAAM,UAAU,cAAc;AACpC,QAAM,MAAO,MAAM,GAAG,gBAAgB,KAAK,EAAE;AAC7C,QAAM,MAAM,IAAI,CAAC;AACjB,MAAI,QAAQ,QAAW;AACrB,WAAO;AAAA,EACT;AACA,SAAO,cAAc,GAAG;AAC1B;AAMA,eAAsB,UACpB,SACA,IACmC;AACnC,MACE,QAAQ,cAAc,UACtB,QAAQ,UAAU,KAAK,EAAE,WAAW,GACpC;AACA,UAAM,IAAI;AAAA,MACR;AAAA,IACF;AAAA,EACF;AAEA,MAAI,MAAM;AACV,QAAM,SAAoB,CAAC,QAAQ,OAAO,YAAY,CAAC;AAEvD,MAAI,QAAQ,cAAc,QAAW;AACnC,WAAO,KAAK,QAAQ,SAAS;AAC7B,WAAO,sBAAsB,OAAO,MAAM;AAAA,EAC5C;AAEA,QAAM,eAAe,MAAM,GAAG,kBAAkB,KAAK,GAAG,MAAM;AAC9D,SAAO,EAAE,aAAa;AACxB;AAOA,SAAS,mBAAmB,YAAoD;AAC9E,QAAM,MAAM,IAAI,UAAU;AAC1B,SAAO;AAAA,IACL,OAAO,uBAAuB,GAAG;AAAA,IACjC,KAAK,qBAAqB,GAAG;AAAA,EAC/B;AACF;AAMA,eAAsB,SACpB,SACA,IACqB;AACrB,QAAM,SAAoB,CAAC;AAC3B,MAAI,QAAQ,UAAU,QAAW;AAC/B,WAAO,KAAK,QAAQ,MAAM,YAAY,CAAC;AAAA,EACzC;AACA,QAAM,QACJ,OAAO,SAAS,IACZ,mBAAmB,OAAO,MAAM,IAChC,EAAE,OAAO,IAAI,KAAK,GAAG;AAG3B,QAAM,eAAe,GAAG;AAAA,IACtB,8FAA8F,MAAM,KAAK;AAAA,IACzG,GAAG;AAAA,EACL;AAGA,QAAM,oBAAoB,GAAG;AAAA,IAC3B,wGACoB,MAAM,KAAK;AAAA,IAE/B,GAAG;AAAA,EACL;AAGA,QAAM,iBAAiB,GAAG;AAAA,IACxB,gFAC+B,MAAM,GAAG;AAAA,IAExC,GAAG;AAAA,EACL;AAGA,QAAM,iBAAiB,GAAG;AAAA,IACxB,uDAAuD,MAAM,KAAK;AAAA,IAElE,GAAG;AAAA,EACL;AAGA,QAAM,iBAAiB,GAAG;AAAA,IACxB,sDAAsD,MAAM,KAAK;AAAA,IAEjE,GAAG;AAAA,EACL;AAGA,QAAM,gBAAgB,GAAG;AAAA,IACvB,gFAC+B,MAAM,GAAG;AAAA,IAExC,GAAG;AAAA,EACL;AAEA,QAAM,CAAC,aAAa,kBAAkB,eAAe,eAAe,eAAe,YAAY,IAC7F,MAAM,QAAQ,IAAI,CAAC,cAAc,mBAAmB,gBAAgB,gBAAgB,gBAAgB,aAAa,CAAC;AAEpH,SAAO;AAAA,IACL,YAAY,IAAI,CAAC,OAAO,EAAE,WAAW,EAAE,YAAY,eAAe,EAAE,eAAe,EAAE;AAAA,IACrF;AAAA,IACA,cAAc,IAAI,CAAC,OAAO,EAAE,SAAS,EAAE,UAAU,OAAO,EAAE,MAAM,EAAE;AAAA,IAClE,cAAc,IAAI,CAAC,OAAO,EAAE,WAAW,EAAE,YAAY,OAAO,EAAE,MAAM,EAAE;AAAA,IACtE;AAAA,IACA;AAAA,EACF;AACF;;;AEzRO,SAAS,mBAAmB,IAA+C;AAChF,SAAO;AAAA,IACL,MAAM,SAAS,KAA8B;AAC3C,YAAM,GAAG;AAAA,QACP;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,QAWA,IAAI;AAAA,QACJ,IAAI,UAAU,YAAY;AAAA,QAC1B,IAAI;AAAA,QACJ,IAAI;AAAA,QACJ,IAAI;AAAA,QACJ,IAAI,WAAW;AAAA,QACf,WAAW,IAAI,UAAU;AAAA,QACzB,WAAW,IAAI,SAAS;AAAA,QACxB,WAAW,IAAI,IAAI;AAAA,QACnB,IAAI,SAAS;AAAA,QACb,IAAI,eAAe;AAAA,QACnB,IAAI,YAAY;AAAA,QAChB,WAAW,IAAI,UAAU;AAAA,QACzB,IAAI,UAAU;AAAA,QACd,IAAI,UAAU;AAAA,QACd,WAAW,IAAI,QAAQ;AAAA,QACvB,WAAW,IAAI,cAAc;AAAA,MAC/B;AAAA,IACF;AAAA,IAEA,MAAM,UAAU,MAAiD;AAC/D,aAAO,UAAU,MAAM,EAAE;AAAA,IAC3B;AAAA,IAEA,MAAM,WAAW,IAAsC;AACrD,aAAO,WAAW,IAAI,EAAE;AAAA,IAC1B;AAAA,IAEA,MAAM,SAAS,SAAiD;AAC9D,aAAO,SAAS,WAAW,CAAC,GAAG,EAAE;AAAA,IACnC;AAAA,IAEA,MAAM,UAAU,SAGsB;AACpC,aAAO,UAAU,SAAS,EAAE;AAAA,IAC9B;AAAA,EACF;AACF;AAEA,SAAS,WAAW,OAA+B;AACjD,MAAI,UAAU,UAAa,UAAU,MAAM;AACzC,WAAO;AAAA,EACT;AACA,SAAO,KAAK,UAAU,KAAK;AAC7B;;;ACvFA,SAAS,uBAAuB;;;ACKzB,IAAM,aAAa;AAAA,EACxB,QAAQ;AAAA,EACR,YAAY;AAAA,EACZ,qBAAqB;AAAA,EACrB,QAAQ;AAAA,EACR,YAAY;AAAA,EACZ,QAAQ;AAAA,EACR,QAAQ;AAAA,EACR,YAAY;AACd;AAIA,SAAS,gBAAgB,QAAyC;AAChE,SAAO,UAAU;AACnB;AAMO,SAAS,kBAAkB,QAA4C;AAC5E,MAAI,gBAAgB,MAAM,GAAG;AAC3B,WAAO,WAAW,MAAM;AAAA,EAC1B;AACA,SAAO;AACT;;;ACXA,SAAS,0BAA0B,OAAoD;AACrF,MAAI,EAAE,uBAAuB,QAAQ;AACnC,WAAO;AAAA,EACT;AAEA,QAAM,EAAE,mBAAmB,IAAI,IAAI;AACnC,MAAI,QAAQ,QAAQ,OAAO,QAAQ,UAAU;AAC3C,WAAO;AAAA,EACT;AAEA,SAAO,YAAY;AACrB;AAQO,SAAS,eAAe,QAAoD;AACjF,MAAI,CAAC,0BAA0B,MAAM,GAAG;AACtC,WAAO;AAAA,EACT;AAEA,QAAM,SAAiC,CAAC;AACxC,aAAW,CAAC,WAAW,IAAI,KAAK,OAAO,QAAQ,OAAO,kBAAkB,MAAM,GAAG;AAC/E,WAAO,SAAS,IAAI,KAAK,UAAU;AAAA,EACrC;AACA,SAAO;AACT;AAUO,SAAS,uBACd,QACA,WAC+B;AAC/B,MAAI,cAAc,QAAW;AAC3B,WAAO;AAAA,EACT;AAEA,QAAM,MAAM,eAAe,MAAM;AACjC,MAAI,QAAQ,QAAW;AACrB,WAAO,CAAC,cAAsB,IAAI,SAAS,KAAK;AAAA,EAClD;AAEA,SAAO,CAAC,cAAsB;AAChC;;;AC1EA,SAAS,yBAAyB;AAgBlC,IAAM,YAAY,IAAI,kBAA2B;AAsB1C,SAAS,gBAAmB,IAAa,IAAkC;AAChF,SAAO,UAAU,IAAI,IAAI,EAAE;AAC7B;AAMO,SAAS,cAAuB;AACrC,SAAO,UAAU,SAAS;AAC5B;;;AH6GO,SAAS,mBACd,QACA,YACA,UAAqC,CAAC,GACnC;AACH,QAAM,WAAW,QAAQ,YAAY;AACrC,QAAM,cAAc,kBAAkB,QAAQ,OAAO;AACrD,QAAM,WAAW,QAAQ;AACzB,QAAM,oBAAoB,QAAQ;AAClC,QAAM,qBAAqB,QAAQ,sBAAsB;AACzD,QAAM,mBAAmB,uBAAuB,QAAQ,QAAQ,kBAAkB;AAElF,QAAM,WAAW,OAAO,SAAS;AAAA,IAC/B,OAAO;AAAA,MACL,YAAY;AAAA,QACV,MAAM,eAAe;AAAA,UACnB;AAAA,UACA;AAAA,UACA;AAAA,UACA;AAAA,QACF,GAKG;AACD,gBAAM,UAAU,kBAAkB,SAAS;AAC3C,cAAI,YAAY,QAAW;AACzB,mBAAO,MAAM,IAAI;AAAA,UACnB;AAEA,gBAAM,YAAY,iBAAiB,KAAK;AACxC,gBAAM,mBACJ,sBAAsB,UAAa,kBAAkB,SAAS,SAAS;AAEzE,gBAAM,cAA2B,mBAC7B,EAAE,MAAM,OAAO,IACf,MAAM,iBAAiB,EAAE,QAAQ,OAAO,WAAW,MAAM,aAAa,mBAAmB,CAAC;AAE9F,gBAAM,SAAS,MAAM,MAAM,IAAI;AAE/B,cAAI;AACF,kBAAM,eAAe;AAAA,cACnB;AAAA,cACA;AAAA,cACA;AAAA,cACA;AAAA,cACA;AAAA,cACA;AAAA,cACA;AAAA,cACA;AAAA,cACA;AAAA,cACA,SAAS,gBAAgB,GAAG;AAAA,YAC9B,CAAC;AAAA,UACH,SAAS,KAAK;AACZ,wBAAY,GAAG;AAAA,UACjB;AAEA,iBAAO;AAAA,QACT;AAAA,MACF;AAAA,IACF;AAAA,EACF,CAAC;AAID,SAAO;AACT;AAuBA,eAAe,iBAAiB;AAAA,EAC9B;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,GAAiD;AAC/C,MACE,cAAc,YACd,cAAc,gBACd,cAAc,yBACd,cAAc,UACd;AACA,WAAO,EAAE,MAAM,OAAO;AAAA,EACxB;AAEA,QAAM,WAAW,iBAAiB,YAAY,KAAK,QAAQ,KAAK;AAChE,MAAI,aAAa,QAAW;AAC1B,WAAO,EAAE,MAAM,OAAO;AAAA,EACxB;AAEA,MAAI,cAAc,YAAY,cAAc,UAAU;AACpD,UAAM,QAAQ,aAAa,IAAI;AAC/B,QAAI,UAAU,QAAW;AACvB,aAAO,EAAE,MAAM,UAAU,KAAK,OAAU;AAAA,IAC1C;AACA,QAAI;AACF,YAAM,QAAQ,MAAM,SAAS,WAAW,EAAE,MAAM,CAAC;AACjD,aAAO,EAAE,MAAM,UAAU,KAAK,SAAS,KAAK,EAAE;AAAA,IAChD,SAAS,KAAK;AACZ,kBAAY,GAAG;AACf,aAAO,EAAE,MAAM,UAAU,KAAK,OAAU;AAAA,IAC1C;AAAA,EACF;AAEA,MAAI,cAAc,gBAAgB,cAAc,cAAc;AAC5D,UAAM,QAAQ,aAAa,IAAI;AAC/B,QAAI;AACF,YAAM,OAAO,MAAM,SAAS,SAAS,EAAE,OAAO,SAAS,CAAC,GAAG,MAAM,qBAAqB,EAAE,CAAC;AACzF,UAAI,KAAK,SAAS,oBAAoB;AAEpC,eAAO,EAAE,MAAM,OAAO;AAAA,MACxB;AACA,YAAM,UAAU,KACb,IAAI,CAAC,MAAM,SAAS,CAAC,CAAC,EACtB,OAAO,CAAC,MAAoC,MAAM,MAAS;AAC9D,aAAO,EAAE,MAAM,QAAQ,MAAM,QAAQ;AAAA,IACvC,SAAS,KAAK;AACZ,kBAAY,GAAG;AACf,aAAO,EAAE,MAAM,QAAQ,MAAM,CAAC,EAAE;AAAA,IAClC;AAAA,EACF;AAEA,SAAO,EAAE,MAAM,OAAO;AACxB;AAmBA,eAAe,eAAe;AAAA,EAC5B;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,GAAwC;AACtC,MAAI,cAAc,cAAc;AAC9B,UAAM,iBAAiB,EAAE,WAAW,SAAS,MAAM,YAAY,UAAU,UAAU,QAAQ,CAAC;AAC5F;AAAA,EACF;AAEA,MAAI,cAAc,uBAAuB;AACvC,UAAM,0BAA0B,EAAE,WAAW,SAAS,QAAQ,YAAY,UAAU,UAAU,QAAQ,CAAC;AACvG;AAAA,EACF;AAEA,MAAI,cAAc,cAAc;AAC9B,UAAM,iBAAiB,EAAE,WAAW,SAAS,aAAa,YAAY,UAAU,UAAU,QAAQ,CAAC;AACnG;AAAA,EACF;AAEA,MAAI,cAAc,cAAc;AAC9B,UAAM,iBAAiB,EAAE,WAAW,SAAS,aAAa,YAAY,UAAU,UAAU,QAAQ,CAAC;AACnG;AAAA,EACF;AAGA,QAAM,MAAM,SAAS,MAAM;AAC3B,QAAM,YAAY,QAAQ,SAAY,UAAU,GAAG,IAAI,WAAc;AAErE,MAAI,cAAc,UAAU;AAG1B,UAAM,SAAS,YAAY,SAAS,WAAW,YAAY,MAAM;AACjE,UAAM,cAA8B,WAAW,SAAY,WAAW;AACtE,UAAM,WAAW;AAAA,MACf;AAAA,MACA,WAAW;AAAA,MACX;AAAA,MACA,GAAI,WAAW,UAAa,EAAE,OAAO;AAAA,MACrC,GAAI,QAAQ,UAAa,EAAE,OAAO,IAAI;AAAA,MACtC,GAAI,aAAa,UAAa,EAAE,SAAS;AAAA,MACzC,GAAI,YAAY,UAAa,EAAE,QAAQ;AAAA,IACzC,CAAC;AACD;AAAA,EACF;AAEA,MAAI,YAAY,UAAU;AACxB,UAAM,WAAW;AAAA,MACf;AAAA,MACA,WAAW;AAAA,MACX;AAAA,MACA,GAAI,QAAQ,UAAa,EAAE,OAAO,IAAI;AAAA,MACtC,GAAI,aAAa,UAAa,EAAE,SAAS;AAAA,MACzC,GAAI,YAAY,UAAa,EAAE,QAAQ;AAAA,IACzC,CAAC;AACD;AAAA,EACF;AAEA,MAAI,YAAY,UAAU;AACxB,UAAM,SAAS,YAAY,SAAS,WAAW,YAAY,MAAM;AACjE,UAAM,WAAW;AAAA,MACf;AAAA,MACA,WAAW;AAAA,MACX;AAAA,MACA,GAAI,WAAW,UAAa,EAAE,OAAO;AAAA,MACrC,GAAI,QAAQ,UAAa,EAAE,OAAO,IAAI;AAAA,MACtC,GAAI,aAAa,UAAa,EAAE,SAAS;AAAA,MACzC,GAAI,YAAY,UAAa,EAAE,QAAQ;AAAA,IACzC,CAAC;AACD;AAAA,EACF;AAGA,QAAM,WAAW;AAAA,IACf;AAAA,IACA,WAAW;AAAA,IACX;AAAA,IACA,GAAI,QAAQ,UAAa,EAAE,QAAQ,IAAI;AAAA,IACvC,GAAI,aAAa,UAAa,EAAE,SAAS;AAAA,IACzC,GAAI,YAAY,UAAa,EAAE,QAAQ;AAAA,EACzC,CAAC;AACH;AAgBA,eAAe,iBAAiB;AAAA,EAC9B;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,GAA0C;AACxC,MAAI,aAAa,QAAQ;AACvB,UAAM,WAAW;AAAA,MACf;AAAA,MACA,WAAW;AAAA,MACX,UAAU;AAAA,MACV,GAAI,aAAa,UAAa,EAAE,SAAS;AAAA,MACzC,GAAI,YAAY,UAAa,EAAE,QAAQ;AAAA,IACzC,CAAC;AACD;AAAA,EACF;AAGA,QAAM,OAAO,YAAY,IAAI;AAC7B,MAAI,KAAK,WAAW,GAAG;AACrB,UAAM,WAAW;AAAA,MACf;AAAA,MACA,WAAW;AAAA,MACX,UAAU;AAAA,MACV,GAAI,aAAa,UAAa,EAAE,SAAS;AAAA,MACzC,GAAI,YAAY,UAAa,EAAE,QAAQ;AAAA,IACzC,CAAC;AACD;AAAA,EACF;AAEA,QAAM,QAAQ;AAAA,IACZ,KAAK,IAAI,CAAC,QAAQ;AAChB,YAAM,SAAS,SAAS,GAAG;AAC3B,YAAM,YAAY,WAAW,SAAY,UAAU,MAAM,IAAI,WAAc;AAC3E,aAAO,WAAW;AAAA,QAChB;AAAA,QACA,WAAW;AAAA,QACX;AAAA,QACA,GAAI,WAAW,UAAa,EAAE,OAAO,OAAO;AAAA,QAC5C,GAAI,aAAa,UAAa,EAAE,SAAS;AAAA,QACzC,GAAI,YAAY,UAAa,EAAE,QAAQ;AAAA,MACzC,CAAC;AAAA,IACH,CAAC;AAAA,EACH;AACF;AAYA,eAAe,0BAA0B;AAAA,EACvC;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,GAAmD;AACjD,MAAI,aAAa,QAAQ;AACvB,UAAM,WAAW;AAAA,MACf;AAAA,MACA,WAAW;AAAA,MACX,UAAU;AAAA,MACV,GAAI,aAAa,UAAa,EAAE,SAAS;AAAA,MACzC,GAAI,YAAY,UAAa,EAAE,QAAQ;AAAA,IACzC,CAAC;AACD;AAAA,EACF;AAEA,QAAM,OAAO,MAAM,QAAQ,MAAM,IAAI,SAAS,CAAC;AAC/C,MAAI,KAAK,WAAW,GAAG;AACrB,UAAM,WAAW;AAAA,MACf;AAAA,MACA,WAAW;AAAA,MACX,UAAU;AAAA,MACV,GAAI,aAAa,UAAa,EAAE,SAAS;AAAA,MACzC,GAAI,YAAY,UAAa,EAAE,QAAQ;AAAA,IACzC,CAAC;AACD;AAAA,EACF;AAEA,QAAM,QAAQ;AAAA,IACZ,KAAK,IAAI,CAAC,QAAQ;AAChB,YAAM,SAAS,SAAS,GAAG;AAC3B,YAAM,YAAY,WAAW,SAAY,UAAU,MAAM,IAAI,WAAc;AAC3E,aAAO,WAAW;AAAA,QAChB;AAAA,QACA,WAAW;AAAA,QACX;AAAA,QACA,GAAI,WAAW,UAAa,EAAE,OAAO,OAAO;AAAA,QAC5C,GAAI,aAAa,UAAa,EAAE,SAAS;AAAA,QACzC,GAAI,YAAY,UAAa,EAAE,QAAQ;AAAA,MACzC,CAAC;AAAA,IACH,CAAC;AAAA,EACH;AACF;AAYA,eAAe,iBAAiB;AAAA,EAC9B;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,GAA0C;AACxC,MAAI,aAAa,QAAQ;AACvB,UAAM,WAAW;AAAA,MACf;AAAA,MACA,WAAW;AAAA,MACX,UAAU;AAAA,MACV,GAAI,aAAa,UAAa,EAAE,SAAS;AAAA,MACzC,GAAI,YAAY,UAAa,EAAE,QAAQ;AAAA,IACzC,CAAC;AACD;AAAA,EACF;AAGA,QAAM,OAAO,YAAY,SAAS,SAAS,YAAY,OAAO,CAAC;AAC/D,MAAI,KAAK,WAAW,GAAG;AACrB,UAAM,WAAW;AAAA,MACf;AAAA,MACA,WAAW;AAAA,MACX,UAAU;AAAA,MACV,GAAI,aAAa,UAAa,EAAE,SAAS;AAAA,MACzC,GAAI,YAAY,UAAa,EAAE,QAAQ;AAAA,IACzC,CAAC;AACD;AAAA,EACF;AAEA,QAAM,QAAQ;AAAA,IACZ,KAAK,IAAI,CAAC,QAAQ;AAChB,YAAM,WAAW,UAAU,GAAG,KAAK;AACnC,aAAO,WAAW;AAAA,QAChB;AAAA,QACA,WAAW;AAAA,QACX;AAAA,QACA,QAAQ;AAAA,QACR,GAAI,aAAa,UAAa,EAAE,SAAS;AAAA,QACzC,GAAI,YAAY,UAAa,EAAE,QAAQ;AAAA,MACzC,CAAC;AAAA,IACH,CAAC;AAAA,EACH;AACF;AAYA,eAAe,iBAAiB;AAAA,EAC9B;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,GAA0C;AACxC,MAAI,aAAa,QAAQ;AACvB,UAAM,WAAW;AAAA,MACf;AAAA,MACA,WAAW;AAAA,MACX,UAAU;AAAA,MACV,GAAI,aAAa,UAAa,EAAE,SAAS;AAAA,MACzC,GAAI,YAAY,UAAa,EAAE,QAAQ;AAAA,IACzC,CAAC;AACD;AAAA,EACF;AAGA,QAAM,OAAO,YAAY,SAAS,SAAS,YAAY,OAAO,CAAC;AAC/D,MAAI,KAAK,WAAW,GAAG;AACrB,UAAM,WAAW;AAAA,MACf;AAAA,MACA,WAAW;AAAA,MACX,UAAU;AAAA,MACV,GAAI,aAAa,UAAa,EAAE,SAAS;AAAA,MACzC,GAAI,YAAY,UAAa,EAAE,QAAQ;AAAA,IACzC,CAAC;AACD;AAAA,EACF;AAEA,QAAM,QAAQ;AAAA,IACZ,KAAK,IAAI,CAAC,QAAQ;AAChB,YAAM,WAAW,UAAU,GAAG,KAAK;AACnC,aAAO,WAAW;AAAA,QAChB;AAAA,QACA,WAAW;AAAA,QACX;AAAA,QACA,QAAQ;AAAA,QACR,GAAI,aAAa,UAAa,EAAE,SAAS;AAAA,QACzC,GAAI,YAAY,UAAa,EAAE,QAAQ;AAAA,MACzC,CAAC;AAAA,IACH,CAAC;AAAA,EACH;AACF;AAMA,SAAS,kBACP,SAC0B;AAC1B,SAAO,CAAC,UAAmB;AACzB,QAAI;AACF,UAAI,YAAY,QAAW;AACzB,gBAAQ,KAAK;AAAA,MACf,OAAO;AACL,cAAM,MAAM,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK;AACjE,gBAAQ,MAAM,uCAAkC,GAAG,EAAE;AAAA,MACvD;AAAA,IACF,QAAQ;AAAA,IAER;AAAA,EACF;AACF;AAMA,SAAS,gBAAgB,OAA8C;AACrE,MAAI,UAAU,QAAQ,UAAU,UAAa,OAAO,UAAU,UAAU;AACtE,WAAO;AAAA,EACT;AACA,SACE,gBAAgB,SAChB,cAAc,SACd,OAAO,QAAQ,IAAI,OAAO,YAAY,MAAM,cAC5C,OAAO,QAAQ,IAAI,OAAO,UAAU,MAAM;AAE9C;AAQA,SAAS,iBACP,QACA,OACiC;AACjC,QAAM,aAAa,MAAM,OAAO,CAAC,EAAE,YAAY,IAAI,MAAM,MAAM,CAAC;AAChE,QAAM,QAAiB,QAAQ,IAAI,OAAO,MAAM,GAAG,UAAU;AAC7D,MAAI,gBAAgB,KAAK,GAAG;AAC1B,WAAO;AAAA,EACT;AACA,SAAO;AACT;AAEA,SAAS,UAAU,QAAqD;AACtE,QAAM,KAAK,OAAO,IAAI;AACtB,MAAI,OAAO,UAAa,OAAO,MAAM;AACnC,WAAO,OAAO,EAAE;AAAA,EAClB;AACA,SAAO;AACT;AAEA,SAAS,SAAS,OAAqD;AACrE,MAAI,UAAU,QAAQ,UAAU,UAAa,OAAO,UAAU,YAAY,CAAC,MAAM,QAAQ,KAAK,GAAG;AAC/F,WAAO;AAAA,EACT;AACA,SAAO;AACT;AAEA,SAAS,YAAY,MAA0B;AAC7C,MAAI,SAAS,QAAQ,OAAO,SAAS,YAAY,UAAU,MAAM;AAC/D,UAAM,OAAO,QAAQ,IAAI,MAAM,MAAM;AACrC,QAAI,MAAM,QAAQ,IAAI,GAAG;AACvB,aAAO;AAAA,IACT;AAAA,EACF;AACA,SAAO,CAAC;AACV;AAMA,SAAS,aAAa,MAAwB;AAC5C,MAAI,SAAS,QAAQ,OAAO,SAAS,YAAY,WAAW,MAAM;AAChE,WAAO,QAAQ,IAAI,MAAM,OAAO;AAAA,EAClC;AACA,SAAO;AACT;","names":[]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@usebetterdev/audit-prisma",
3
- "version": "0.6.1",
3
+ "version": "0.7.0",
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",
@@ -25,7 +25,7 @@
25
25
  "README.md"
26
26
  ],
27
27
  "dependencies": {
28
- "@usebetterdev/audit-core": "0.6.1"
28
+ "@usebetterdev/audit-core": "0.7.0"
29
29
  },
30
30
  "peerDependencies": {
31
31
  "@prisma/client": ">=5.0.0"
@@ -46,7 +46,7 @@
46
46
  "tsup": "^8.3.5",
47
47
  "typescript": "~5.7.2",
48
48
  "vitest": "^2.1.6",
49
- "@usebetterdev/test-utils": "0.5.1"
49
+ "@usebetterdev/test-utils": "0.5.2"
50
50
  },
51
51
  "engines": {
52
52
  "node": ">=22"