@usebetterdev/audit-prisma 0.4.0-beta.4 → 0.5.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
@@ -21,10 +21,375 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
21
21
  var index_exports = {};
22
22
  __export(index_exports, {
23
23
  prismaAuditAdapter: () => prismaAuditAdapter,
24
+ prismaModelMap: () => prismaModelMap,
25
+ runWithTxClient: () => runWithTxClient,
24
26
  withAuditExtension: () => withAuditExtension
25
27
  });
26
28
  module.exports = __toCommonJS(index_exports);
27
29
 
30
+ // src/query.ts
31
+ var import_audit_core = require("@usebetterdev/audit-core");
32
+
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
+ }
42
+ function rowToAuditLog(row) {
43
+ const { id, timestamp, table_name, operation, record_id } = row;
44
+ if (typeof id !== "string") {
45
+ throw new Error("rowToAuditLog: id must be a string");
46
+ }
47
+ if (!(timestamp instanceof Date)) {
48
+ throw new Error("rowToAuditLog: timestamp must be a Date");
49
+ }
50
+ if (typeof table_name !== "string") {
51
+ throw new Error("rowToAuditLog: table_name must be a string");
52
+ }
53
+ if (typeof operation !== "string") {
54
+ throw new Error("rowToAuditLog: operation must be a string");
55
+ }
56
+ if (typeof record_id !== "string") {
57
+ throw new Error("rowToAuditLog: record_id must be a string");
58
+ }
59
+ if (!isAuditOperation(operation)) {
60
+ throw new Error(
61
+ `Invalid audit operation: "${operation}". Expected one of: INSERT, UPDATE, DELETE`
62
+ );
63
+ }
64
+ const log = {
65
+ id,
66
+ timestamp,
67
+ tableName: table_name,
68
+ operation,
69
+ recordId: record_id
70
+ };
71
+ const {
72
+ actor_id,
73
+ before_data,
74
+ after_data,
75
+ diff,
76
+ label,
77
+ description,
78
+ severity,
79
+ compliance,
80
+ notify,
81
+ reason,
82
+ metadata,
83
+ redacted_fields
84
+ } = row;
85
+ if (actor_id !== null && actor_id !== void 0) {
86
+ log.actorId = String(actor_id);
87
+ }
88
+ if (before_data !== null && before_data !== void 0) {
89
+ log.beforeData = before_data;
90
+ }
91
+ if (after_data !== null && after_data !== void 0) {
92
+ log.afterData = after_data;
93
+ }
94
+ if (diff !== null && diff !== void 0) {
95
+ log.diff = diff;
96
+ }
97
+ if (label !== null && label !== void 0) {
98
+ log.label = String(label);
99
+ }
100
+ if (description !== null && description !== void 0) {
101
+ log.description = String(description);
102
+ }
103
+ if (severity !== null && severity !== void 0) {
104
+ const s = String(severity);
105
+ if (!isAuditSeverity(s)) {
106
+ throw new Error(
107
+ `Invalid audit severity: "${s}". Expected one of: low, medium, high, critical`
108
+ );
109
+ }
110
+ log.severity = s;
111
+ }
112
+ if (compliance !== null && compliance !== void 0) {
113
+ log.compliance = compliance;
114
+ }
115
+ if (notify !== null && notify !== void 0) {
116
+ log.notify = Boolean(notify);
117
+ }
118
+ if (reason !== null && reason !== void 0) {
119
+ log.reason = String(reason);
120
+ }
121
+ if (metadata !== null && metadata !== void 0) {
122
+ log.metadata = metadata;
123
+ }
124
+ if (redacted_fields !== null && redacted_fields !== void 0) {
125
+ log.redactedFields = redacted_fields;
126
+ }
127
+ return log;
128
+ }
129
+
130
+ // src/query.ts
131
+ var DEFAULT_LIMIT = 50;
132
+ var MAX_LIMIT = 250;
133
+ 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
+ function addParam(state, value) {
187
+ state.params.push(value);
188
+ return `$${state.params.length}`;
189
+ }
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}`);
198
+ }
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[])`);
210
+ }
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[])`);
222
+ }
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}`);
230
+ }
231
+ } else {
232
+ const p = addParam(state, filters.operations);
233
+ state.fragments.push(`operation = ANY(${p}::text[])`);
234
+ }
235
+ }
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
+ }
268
+ }
269
+ return state;
270
+ }
271
+ async function queryLogs(spec, db) {
272
+ const sortOrder = spec.sortOrder ?? "desc";
273
+ const limit = Math.min(spec.limit ?? DEFAULT_LIMIT, MAX_LIMIT);
274
+ const fetchLimit = limit + 1;
275
+ const cursor = spec.cursor !== void 0 ? decodeCursor(spec.cursor) : void 0;
276
+ const where = buildWhereClause(spec.filters, cursor, sortOrder);
277
+ const queryParams = [...where.params, fetchLimit];
278
+ const limitRef = `$${queryParams.length}`;
279
+ const orderDir = sortOrder === "asc" ? "ASC" : "DESC";
280
+ const whereClause = where.fragments.length > 0 ? `WHERE ${where.fragments.join(" AND ")}` : "";
281
+ const sql = `SELECT ${SELECT_COLUMNS} FROM audit_logs ${whereClause} ORDER BY timestamp ${orderDir}, id ${orderDir} LIMIT ${limitRef}`;
282
+ const raw = await db.$queryRawUnsafe(sql, ...queryParams);
283
+ const hasNextPage = raw.length > limit;
284
+ const resultRows = hasNextPage ? raw.slice(0, -1) : raw;
285
+ const entries = resultRows.map(rowToAuditLog);
286
+ if (hasNextPage) {
287
+ const lastEntry = entries[entries.length - 1];
288
+ if (lastEntry !== void 0) {
289
+ return {
290
+ entries,
291
+ nextCursor: encodeCursor(lastEntry.timestamp, lastEntry.id)
292
+ };
293
+ }
294
+ }
295
+ return { entries };
296
+ }
297
+ async function getLogById(id, db) {
298
+ const sql = `SELECT ${SELECT_COLUMNS} FROM audit_logs WHERE id = $1::uuid LIMIT 1`;
299
+ const raw = await db.$queryRawUnsafe(sql, id);
300
+ const row = raw[0];
301
+ if (row === void 0) {
302
+ return null;
303
+ }
304
+ return rowToAuditLog(row);
305
+ }
306
+ async function purgeLogs(options, db) {
307
+ if (options.tableName !== void 0 && options.tableName.trim().length === 0) {
308
+ throw new Error(
309
+ "purgeLogs: tableName must be a non-empty string when provided"
310
+ );
311
+ }
312
+ let sql = "DELETE FROM audit_logs WHERE timestamp < $1::timestamptz";
313
+ const params = [options.before.toISOString()];
314
+ if (options.tableName !== void 0) {
315
+ params.push(options.tableName);
316
+ sql += ` AND table_name = $${params.length}`;
317
+ }
318
+ const deletedCount = await db.$executeRawUnsafe(sql, ...params);
319
+ return { deletedCount };
320
+ }
321
+ function buildSinceFragment(paramCount) {
322
+ const ref = `$${paramCount}::timestamptz`;
323
+ return {
324
+ where: ` WHERE timestamp >= ${ref}`,
325
+ and: ` AND timestamp >= ${ref}`
326
+ };
327
+ }
328
+ async function getStats(options, db) {
329
+ const params = [];
330
+ if (options.since !== void 0) {
331
+ params.push(options.since.toISOString());
332
+ }
333
+ const since = params.length > 0 ? buildSinceFragment(params.length) : { where: "", and: "" };
334
+ const summaryQuery = db.$queryRawUnsafe(
335
+ `SELECT COUNT(*) AS total_logs, COUNT(DISTINCT table_name) AS tables_audited FROM audit_logs${since.where}`,
336
+ ...params
337
+ );
338
+ const eventsPerDayQuery = db.$queryRawUnsafe(
339
+ `SELECT to_char(date_trunc('day', timestamp), 'YYYY-MM-DD') AS date, COUNT(*) AS count FROM audit_logs${since.where} GROUP BY date_trunc('day', timestamp) ORDER BY date_trunc('day', timestamp) LIMIT 365`,
340
+ ...params
341
+ );
342
+ const topActorsQuery = db.$queryRawUnsafe(
343
+ `SELECT actor_id, COUNT(*) AS count FROM audit_logs WHERE actor_id IS NOT NULL${since.and} GROUP BY actor_id ORDER BY COUNT(*) DESC LIMIT 10`,
344
+ ...params
345
+ );
346
+ const topTablesQuery = db.$queryRawUnsafe(
347
+ `SELECT table_name, COUNT(*) AS count FROM audit_logs${since.where} GROUP BY table_name ORDER BY COUNT(*) DESC LIMIT 10`,
348
+ ...params
349
+ );
350
+ const operationQuery = db.$queryRawUnsafe(
351
+ `SELECT operation, COUNT(*) AS count FROM audit_logs${since.where} GROUP BY operation`,
352
+ ...params
353
+ );
354
+ const severityQuery = db.$queryRawUnsafe(
355
+ `SELECT severity, COUNT(*) AS count FROM audit_logs WHERE severity IS NOT NULL${since.and} GROUP BY severity`,
356
+ ...params
357
+ );
358
+ 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
+ };
391
+ }
392
+
28
393
  // src/adapter.ts
29
394
  function prismaAuditAdapter(db) {
30
395
  return {
@@ -59,6 +424,18 @@ function prismaAuditAdapter(db) {
59
424
  jsonOrNull(log.metadata),
60
425
  jsonOrNull(log.redactedFields)
61
426
  );
427
+ },
428
+ async queryLogs(spec) {
429
+ return queryLogs(spec, db);
430
+ },
431
+ async getLogById(id) {
432
+ return getLogById(id, db);
433
+ },
434
+ async getStats(options) {
435
+ return getStats(options ?? {}, db);
436
+ },
437
+ async purgeLogs(options) {
438
+ return purgeLogs(options, db);
62
439
  }
63
440
  };
64
441
  }
@@ -70,7 +447,7 @@ function jsonOrNull(value) {
70
447
  }
71
448
 
72
449
  // src/extension.ts
73
- var import_audit_core = require("@usebetterdev/audit-core");
450
+ var import_audit_core2 = require("@usebetterdev/audit-core");
74
451
 
75
452
  // src/action-map.ts
76
453
  var ACTION_MAP = {
@@ -93,12 +470,56 @@ function getAuditOperation(action) {
93
470
  return void 0;
94
471
  }
95
472
 
473
+ // src/model-map.ts
474
+ function hasPrismaRuntimeDataModel(value) {
475
+ if (!("_runtimeDataModel" in value)) {
476
+ return false;
477
+ }
478
+ const { _runtimeDataModel: rdm } = value;
479
+ if (rdm === null || typeof rdm !== "object") {
480
+ return false;
481
+ }
482
+ return "models" in rdm;
483
+ }
484
+ function prismaModelMap(prisma) {
485
+ if (!hasPrismaRuntimeDataModel(prisma)) {
486
+ return void 0;
487
+ }
488
+ const result = {};
489
+ for (const [modelName, meta] of Object.entries(prisma._runtimeDataModel.models)) {
490
+ result[modelName] = meta.dbName ?? modelName;
491
+ }
492
+ return result;
493
+ }
494
+ function buildTableNameResolver(prisma, transform) {
495
+ if (transform !== void 0) {
496
+ return transform;
497
+ }
498
+ const map = prismaModelMap(prisma);
499
+ if (map !== void 0) {
500
+ return (modelName) => map[modelName] ?? modelName;
501
+ }
502
+ return (modelName) => modelName;
503
+ }
504
+
505
+ // src/tx-store.ts
506
+ var import_node_async_hooks = require("async_hooks");
507
+ var txStorage = new import_node_async_hooks.AsyncLocalStorage();
508
+ function runWithTxClient(tx, fn) {
509
+ return txStorage.run(tx, fn);
510
+ }
511
+ function getTxClient() {
512
+ return txStorage.getStore();
513
+ }
514
+
96
515
  // src/extension.ts
97
516
  function withAuditExtension(prisma, captureLog, options = {}) {
98
517
  const bulkMode = options.bulkMode ?? "per-row";
99
518
  const handleError = buildErrorHandler(options.onError);
100
519
  const metadata = options.metadata;
101
- const tableNameTransform = options.tableNameTransform;
520
+ const skipBeforeCapture = options.skipBeforeCapture;
521
+ const maxBeforeStateRows = options.maxBeforeStateRows ?? 100;
522
+ const resolveTableName = buildTableNameResolver(prisma, options.tableNameTransform);
102
523
  const extended = prisma.$extends({
103
524
  query: {
104
525
  $allModels: {
@@ -108,22 +529,26 @@ function withAuditExtension(prisma, captureLog, options = {}) {
108
529
  args,
109
530
  query
110
531
  }) {
111
- const result = await query(args);
112
532
  const auditOp = getAuditOperation(operation);
113
533
  if (auditOp === void 0) {
114
- return result;
534
+ return query(args);
115
535
  }
536
+ const tableName = resolveTableName(model);
537
+ const shouldSkipBefore = skipBeforeCapture !== void 0 && skipBeforeCapture.includes(tableName);
538
+ const beforeState = shouldSkipBefore ? { type: "none" } : await fetchBeforeState({ prisma, model, operation, args, handleError, maxBeforeStateRows });
539
+ const result = await query(args);
116
540
  try {
117
541
  await fireCaptureLog({
118
- model,
542
+ tableName,
119
543
  operation,
120
544
  auditOp,
121
545
  args,
122
546
  result,
547
+ beforeState,
123
548
  captureLog,
124
549
  bulkMode,
125
550
  metadata,
126
- tableNameTransform
551
+ actorId: (0, import_audit_core2.getAuditContext)()?.actorId
127
552
  });
128
553
  } catch (err) {
129
554
  handleError(err);
@@ -135,19 +560,62 @@ function withAuditExtension(prisma, captureLog, options = {}) {
135
560
  });
136
561
  return extended;
137
562
  }
138
- async function fireCaptureLog({
563
+ async function fetchBeforeState({
564
+ prisma,
139
565
  model,
140
566
  operation,
567
+ args,
568
+ handleError,
569
+ maxBeforeStateRows
570
+ }) {
571
+ if (operation === "create" || operation === "createMany" || operation === "createManyAndReturn" || operation === "delete") {
572
+ return { type: "none" };
573
+ }
574
+ const delegate = getModelDelegate(getTxClient() ?? prisma, model);
575
+ if (delegate === void 0) {
576
+ return { type: "none" };
577
+ }
578
+ if (operation === "update" || operation === "upsert") {
579
+ const where = getArgsWhere(args);
580
+ if (where === void 0) {
581
+ return { type: "single", row: void 0 };
582
+ }
583
+ try {
584
+ const found = await delegate.findUnique({ where });
585
+ return { type: "single", row: toRecord(found) };
586
+ } catch (err) {
587
+ handleError(err);
588
+ return { type: "single", row: void 0 };
589
+ }
590
+ }
591
+ if (operation === "updateMany" || operation === "deleteMany") {
592
+ const where = getArgsWhere(args);
593
+ try {
594
+ const rows = await delegate.findMany({ where: where ?? {}, take: maxBeforeStateRows + 1 });
595
+ if (rows.length > maxBeforeStateRows) {
596
+ return { type: "none" };
597
+ }
598
+ const records = rows.map((r) => toRecord(r)).filter((r) => r !== void 0);
599
+ return { type: "many", rows: records };
600
+ } catch (err) {
601
+ handleError(err);
602
+ return { type: "many", rows: [] };
603
+ }
604
+ }
605
+ return { type: "none" };
606
+ }
607
+ async function fireCaptureLog({
608
+ tableName,
609
+ operation,
141
610
  auditOp,
142
611
  args,
143
612
  result,
613
+ beforeState,
144
614
  captureLog,
145
615
  bulkMode,
146
616
  metadata,
147
- tableNameTransform
617
+ actorId
148
618
  }) {
149
- const tableName = tableNameTransform !== void 0 ? tableNameTransform(model) : model;
150
- const actorId = (0, import_audit_core.getAuditContext)()?.actorId;
151
619
  if (operation === "createMany") {
152
620
  await handleCreateMany({ tableName, auditOp, args, captureLog, bulkMode, metadata, actorId });
153
621
  return;
@@ -156,18 +624,30 @@ async function fireCaptureLog({
156
624
  await handleCreateManyAndReturn({ tableName, auditOp, result, captureLog, bulkMode, metadata, actorId });
157
625
  return;
158
626
  }
159
- if (operation === "updateMany" || operation === "deleteMany") {
627
+ if (operation === "updateMany") {
628
+ await handleUpdateMany({ tableName, auditOp, beforeState, captureLog, bulkMode, metadata, actorId });
629
+ return;
630
+ }
631
+ if (operation === "deleteMany") {
632
+ await handleDeleteMany({ tableName, auditOp, beforeState, captureLog, bulkMode, metadata, actorId });
633
+ return;
634
+ }
635
+ const row = toRecord(result);
636
+ const recordId = (row !== void 0 ? extractId(row) : void 0) ?? "unknown";
637
+ if (operation === "upsert") {
638
+ const before = beforeState.type === "single" ? beforeState.row : void 0;
639
+ const effectiveOp = before !== void 0 ? "UPDATE" : "INSERT";
160
640
  await captureLog({
161
641
  tableName,
162
- operation: auditOp,
163
- recordId: "unknown",
642
+ operation: effectiveOp,
643
+ recordId,
644
+ ...before !== void 0 && { before },
645
+ ...row !== void 0 && { after: row },
164
646
  ...metadata !== void 0 && { metadata },
165
647
  ...actorId !== void 0 && { actorId }
166
648
  });
167
649
  return;
168
650
  }
169
- const row = toRecord(result);
170
- const recordId = (row !== void 0 ? extractId(row) : void 0) ?? "unknown";
171
651
  if (auditOp === "INSERT") {
172
652
  await captureLog({
173
653
  tableName,
@@ -180,10 +660,12 @@ async function fireCaptureLog({
180
660
  return;
181
661
  }
182
662
  if (auditOp === "UPDATE") {
663
+ const before = beforeState.type === "single" ? beforeState.row : void 0;
183
664
  await captureLog({
184
665
  tableName,
185
666
  operation: auditOp,
186
667
  recordId,
668
+ ...before !== void 0 && { before },
187
669
  ...row !== void 0 && { after: row },
188
670
  ...metadata !== void 0 && { metadata },
189
671
  ...actorId !== void 0 && { actorId }
@@ -289,6 +771,94 @@ async function handleCreateManyAndReturn({
289
771
  })
290
772
  );
291
773
  }
774
+ async function handleUpdateMany({
775
+ tableName,
776
+ auditOp,
777
+ beforeState,
778
+ captureLog,
779
+ bulkMode,
780
+ metadata,
781
+ actorId
782
+ }) {
783
+ if (bulkMode === "bulk") {
784
+ await captureLog({
785
+ tableName,
786
+ operation: auditOp,
787
+ recordId: "unknown",
788
+ ...metadata !== void 0 && { metadata },
789
+ ...actorId !== void 0 && { actorId }
790
+ });
791
+ return;
792
+ }
793
+ const rows = beforeState.type === "many" ? beforeState.rows : [];
794
+ if (rows.length === 0) {
795
+ await captureLog({
796
+ tableName,
797
+ operation: auditOp,
798
+ recordId: "unknown",
799
+ ...metadata !== void 0 && { metadata },
800
+ ...actorId !== void 0 && { actorId }
801
+ });
802
+ return;
803
+ }
804
+ await Promise.all(
805
+ rows.map((row) => {
806
+ const recordId = extractId(row) ?? "unknown";
807
+ return captureLog({
808
+ tableName,
809
+ operation: auditOp,
810
+ recordId,
811
+ before: row,
812
+ ...metadata !== void 0 && { metadata },
813
+ ...actorId !== void 0 && { actorId }
814
+ });
815
+ })
816
+ );
817
+ }
818
+ async function handleDeleteMany({
819
+ tableName,
820
+ auditOp,
821
+ beforeState,
822
+ captureLog,
823
+ bulkMode,
824
+ metadata,
825
+ actorId
826
+ }) {
827
+ if (bulkMode === "bulk") {
828
+ await captureLog({
829
+ tableName,
830
+ operation: auditOp,
831
+ recordId: "unknown",
832
+ ...metadata !== void 0 && { metadata },
833
+ ...actorId !== void 0 && { actorId }
834
+ });
835
+ return;
836
+ }
837
+ const rows = beforeState.type === "many" ? beforeState.rows : [];
838
+ if (rows.length === 0) {
839
+ await captureLog({
840
+ tableName,
841
+ operation: auditOp,
842
+ recordId: "unknown",
843
+ ...metadata !== void 0 && { metadata },
844
+ ...actorId !== void 0 && { actorId }
845
+ });
846
+ return;
847
+ }
848
+ await Promise.all(
849
+ rows.map((row) => {
850
+ const recordId = extractId(row) ?? "unknown";
851
+ return captureLog({
852
+ tableName,
853
+ operation: auditOp,
854
+ recordId,
855
+ before: row,
856
+ ...metadata !== void 0 && { metadata },
857
+ ...actorId !== void 0 && { actorId }
858
+ });
859
+ })
860
+ );
861
+ }
292
862
  function buildErrorHandler(onError) {
293
863
  return (error) => {
294
864
  try {
@@ -302,6 +872,20 @@ function buildErrorHandler(onError) {
302
872
  }
303
873
  };
304
874
  }
875
+ function isModelDelegate(value) {
876
+ if (value === null || value === void 0 || typeof value !== "object") {
877
+ return false;
878
+ }
879
+ return "findUnique" in value && "findMany" in value && typeof Reflect.get(value, "findUnique") === "function" && typeof Reflect.get(value, "findMany") === "function";
880
+ }
881
+ function getModelDelegate(client, model) {
882
+ const lowerModel = model.charAt(0).toLowerCase() + model.slice(1);
883
+ const value = Reflect.get(Object(client), lowerModel);
884
+ if (isModelDelegate(value)) {
885
+ return value;
886
+ }
887
+ return void 0;
888
+ }
305
889
  function extractId(record) {
306
890
  const id = record["id"];
307
891
  if (id !== void 0 && id !== null) {
@@ -317,16 +901,24 @@ function toRecord(value) {
317
901
  }
318
902
  function getArgsData(args) {
319
903
  if (args !== null && typeof args === "object" && "data" in args) {
320
- const data = args["data"];
904
+ const data = Reflect.get(args, "data");
321
905
  if (Array.isArray(data)) {
322
906
  return data;
323
907
  }
324
908
  }
325
909
  return [];
326
910
  }
911
+ function getArgsWhere(args) {
912
+ if (args !== null && typeof args === "object" && "where" in args) {
913
+ return Reflect.get(args, "where");
914
+ }
915
+ return void 0;
916
+ }
327
917
  // Annotate the CommonJS export names for ESM import in node:
328
918
  0 && (module.exports = {
329
919
  prismaAuditAdapter,
920
+ prismaModelMap,
921
+ runWithTxClient,
330
922
  withAuditExtension
331
923
  });
332
924
  //# sourceMappingURL=index.cjs.map