@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.js CHANGED
@@ -1,3 +1,366 @@
1
+ // src/query.ts
2
+ import { parseDuration } from "@usebetterdev/audit-core";
3
+
4
+ // 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
+ }
13
+ function rowToAuditLog(row) {
14
+ const { id, timestamp, table_name, operation, record_id } = row;
15
+ if (typeof id !== "string") {
16
+ throw new Error("rowToAuditLog: id must be a string");
17
+ }
18
+ if (!(timestamp instanceof Date)) {
19
+ throw new Error("rowToAuditLog: timestamp must be a Date");
20
+ }
21
+ if (typeof table_name !== "string") {
22
+ throw new Error("rowToAuditLog: table_name must be a string");
23
+ }
24
+ if (typeof operation !== "string") {
25
+ throw new Error("rowToAuditLog: operation must be a string");
26
+ }
27
+ if (typeof record_id !== "string") {
28
+ throw new Error("rowToAuditLog: record_id must be a string");
29
+ }
30
+ if (!isAuditOperation(operation)) {
31
+ throw new Error(
32
+ `Invalid audit operation: "${operation}". Expected one of: INSERT, UPDATE, DELETE`
33
+ );
34
+ }
35
+ const log = {
36
+ id,
37
+ timestamp,
38
+ tableName: table_name,
39
+ operation,
40
+ recordId: record_id
41
+ };
42
+ const {
43
+ actor_id,
44
+ before_data,
45
+ after_data,
46
+ diff,
47
+ label,
48
+ description,
49
+ severity,
50
+ compliance,
51
+ notify,
52
+ reason,
53
+ metadata,
54
+ redacted_fields
55
+ } = row;
56
+ if (actor_id !== null && actor_id !== void 0) {
57
+ log.actorId = String(actor_id);
58
+ }
59
+ if (before_data !== null && before_data !== void 0) {
60
+ log.beforeData = before_data;
61
+ }
62
+ if (after_data !== null && after_data !== void 0) {
63
+ log.afterData = after_data;
64
+ }
65
+ if (diff !== null && diff !== void 0) {
66
+ log.diff = diff;
67
+ }
68
+ if (label !== null && label !== void 0) {
69
+ log.label = String(label);
70
+ }
71
+ if (description !== null && description !== void 0) {
72
+ log.description = String(description);
73
+ }
74
+ if (severity !== null && severity !== void 0) {
75
+ const s = String(severity);
76
+ if (!isAuditSeverity(s)) {
77
+ throw new Error(
78
+ `Invalid audit severity: "${s}". Expected one of: low, medium, high, critical`
79
+ );
80
+ }
81
+ log.severity = s;
82
+ }
83
+ if (compliance !== null && compliance !== void 0) {
84
+ log.compliance = compliance;
85
+ }
86
+ if (notify !== null && notify !== void 0) {
87
+ log.notify = Boolean(notify);
88
+ }
89
+ if (reason !== null && reason !== void 0) {
90
+ log.reason = String(reason);
91
+ }
92
+ if (metadata !== null && metadata !== void 0) {
93
+ log.metadata = metadata;
94
+ }
95
+ if (redacted_fields !== null && redacted_fields !== void 0) {
96
+ log.redactedFields = redacted_fields;
97
+ }
98
+ return log;
99
+ }
100
+
101
+ // src/query.ts
102
+ var DEFAULT_LIMIT = 50;
103
+ var MAX_LIMIT = 250;
104
+ 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
+ function addParam(state, value) {
158
+ state.params.push(value);
159
+ return `$${state.params.length}`;
160
+ }
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}`);
169
+ }
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[])`);
181
+ }
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[])`);
193
+ }
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}`);
201
+ }
202
+ } else {
203
+ const p = addParam(state, filters.operations);
204
+ state.fragments.push(`operation = ANY(${p}::text[])`);
205
+ }
206
+ }
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
+ }
239
+ }
240
+ return state;
241
+ }
242
+ async function queryLogs(spec, db) {
243
+ const sortOrder = spec.sortOrder ?? "desc";
244
+ const limit = Math.min(spec.limit ?? DEFAULT_LIMIT, MAX_LIMIT);
245
+ const fetchLimit = limit + 1;
246
+ const cursor = spec.cursor !== void 0 ? decodeCursor(spec.cursor) : void 0;
247
+ const where = buildWhereClause(spec.filters, cursor, sortOrder);
248
+ const queryParams = [...where.params, fetchLimit];
249
+ const limitRef = `$${queryParams.length}`;
250
+ const orderDir = sortOrder === "asc" ? "ASC" : "DESC";
251
+ const whereClause = where.fragments.length > 0 ? `WHERE ${where.fragments.join(" AND ")}` : "";
252
+ const sql = `SELECT ${SELECT_COLUMNS} FROM audit_logs ${whereClause} ORDER BY timestamp ${orderDir}, id ${orderDir} LIMIT ${limitRef}`;
253
+ const raw = await db.$queryRawUnsafe(sql, ...queryParams);
254
+ const hasNextPage = raw.length > limit;
255
+ const resultRows = hasNextPage ? raw.slice(0, -1) : raw;
256
+ const entries = resultRows.map(rowToAuditLog);
257
+ if (hasNextPage) {
258
+ const lastEntry = entries[entries.length - 1];
259
+ if (lastEntry !== void 0) {
260
+ return {
261
+ entries,
262
+ nextCursor: encodeCursor(lastEntry.timestamp, lastEntry.id)
263
+ };
264
+ }
265
+ }
266
+ return { entries };
267
+ }
268
+ async function getLogById(id, db) {
269
+ const sql = `SELECT ${SELECT_COLUMNS} FROM audit_logs WHERE id = $1::uuid LIMIT 1`;
270
+ const raw = await db.$queryRawUnsafe(sql, id);
271
+ const row = raw[0];
272
+ if (row === void 0) {
273
+ return null;
274
+ }
275
+ return rowToAuditLog(row);
276
+ }
277
+ async function purgeLogs(options, db) {
278
+ if (options.tableName !== void 0 && options.tableName.trim().length === 0) {
279
+ throw new Error(
280
+ "purgeLogs: tableName must be a non-empty string when provided"
281
+ );
282
+ }
283
+ let sql = "DELETE FROM audit_logs WHERE timestamp < $1::timestamptz";
284
+ const params = [options.before.toISOString()];
285
+ if (options.tableName !== void 0) {
286
+ params.push(options.tableName);
287
+ sql += ` AND table_name = $${params.length}`;
288
+ }
289
+ const deletedCount = await db.$executeRawUnsafe(sql, ...params);
290
+ return { deletedCount };
291
+ }
292
+ function buildSinceFragment(paramCount) {
293
+ const ref = `$${paramCount}::timestamptz`;
294
+ return {
295
+ where: ` WHERE timestamp >= ${ref}`,
296
+ and: ` AND timestamp >= ${ref}`
297
+ };
298
+ }
299
+ async function getStats(options, db) {
300
+ const params = [];
301
+ if (options.since !== void 0) {
302
+ params.push(options.since.toISOString());
303
+ }
304
+ const since = params.length > 0 ? buildSinceFragment(params.length) : { where: "", and: "" };
305
+ const summaryQuery = db.$queryRawUnsafe(
306
+ `SELECT COUNT(*) AS total_logs, COUNT(DISTINCT table_name) AS tables_audited FROM audit_logs${since.where}`,
307
+ ...params
308
+ );
309
+ const eventsPerDayQuery = db.$queryRawUnsafe(
310
+ `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`,
311
+ ...params
312
+ );
313
+ const topActorsQuery = db.$queryRawUnsafe(
314
+ `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`,
315
+ ...params
316
+ );
317
+ const topTablesQuery = db.$queryRawUnsafe(
318
+ `SELECT table_name, COUNT(*) AS count FROM audit_logs${since.where} GROUP BY table_name ORDER BY COUNT(*) DESC LIMIT 10`,
319
+ ...params
320
+ );
321
+ const operationQuery = db.$queryRawUnsafe(
322
+ `SELECT operation, COUNT(*) AS count FROM audit_logs${since.where} GROUP BY operation`,
323
+ ...params
324
+ );
325
+ const severityQuery = db.$queryRawUnsafe(
326
+ `SELECT severity, COUNT(*) AS count FROM audit_logs WHERE severity IS NOT NULL${since.and} GROUP BY severity`,
327
+ ...params
328
+ );
329
+ 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
+ };
362
+ }
363
+
1
364
  // src/adapter.ts
2
365
  function prismaAuditAdapter(db) {
3
366
  return {
@@ -32,6 +395,18 @@ function prismaAuditAdapter(db) {
32
395
  jsonOrNull(log.metadata),
33
396
  jsonOrNull(log.redactedFields)
34
397
  );
398
+ },
399
+ async queryLogs(spec) {
400
+ return queryLogs(spec, db);
401
+ },
402
+ async getLogById(id) {
403
+ return getLogById(id, db);
404
+ },
405
+ async getStats(options) {
406
+ return getStats(options ?? {}, db);
407
+ },
408
+ async purgeLogs(options) {
409
+ return purgeLogs(options, db);
35
410
  }
36
411
  };
37
412
  }
@@ -66,12 +441,56 @@ function getAuditOperation(action) {
66
441
  return void 0;
67
442
  }
68
443
 
444
+ // src/model-map.ts
445
+ function hasPrismaRuntimeDataModel(value) {
446
+ if (!("_runtimeDataModel" in value)) {
447
+ return false;
448
+ }
449
+ const { _runtimeDataModel: rdm } = value;
450
+ if (rdm === null || typeof rdm !== "object") {
451
+ return false;
452
+ }
453
+ return "models" in rdm;
454
+ }
455
+ function prismaModelMap(prisma) {
456
+ if (!hasPrismaRuntimeDataModel(prisma)) {
457
+ return void 0;
458
+ }
459
+ const result = {};
460
+ for (const [modelName, meta] of Object.entries(prisma._runtimeDataModel.models)) {
461
+ result[modelName] = meta.dbName ?? modelName;
462
+ }
463
+ return result;
464
+ }
465
+ function buildTableNameResolver(prisma, transform) {
466
+ if (transform !== void 0) {
467
+ return transform;
468
+ }
469
+ const map = prismaModelMap(prisma);
470
+ if (map !== void 0) {
471
+ return (modelName) => map[modelName] ?? modelName;
472
+ }
473
+ return (modelName) => modelName;
474
+ }
475
+
476
+ // src/tx-store.ts
477
+ import { AsyncLocalStorage } from "async_hooks";
478
+ var txStorage = new AsyncLocalStorage();
479
+ function runWithTxClient(tx, fn) {
480
+ return txStorage.run(tx, fn);
481
+ }
482
+ function getTxClient() {
483
+ return txStorage.getStore();
484
+ }
485
+
69
486
  // src/extension.ts
70
487
  function withAuditExtension(prisma, captureLog, options = {}) {
71
488
  const bulkMode = options.bulkMode ?? "per-row";
72
489
  const handleError = buildErrorHandler(options.onError);
73
490
  const metadata = options.metadata;
74
- const tableNameTransform = options.tableNameTransform;
491
+ const skipBeforeCapture = options.skipBeforeCapture;
492
+ const maxBeforeStateRows = options.maxBeforeStateRows ?? 100;
493
+ const resolveTableName = buildTableNameResolver(prisma, options.tableNameTransform);
75
494
  const extended = prisma.$extends({
76
495
  query: {
77
496
  $allModels: {
@@ -81,22 +500,26 @@ function withAuditExtension(prisma, captureLog, options = {}) {
81
500
  args,
82
501
  query
83
502
  }) {
84
- const result = await query(args);
85
503
  const auditOp = getAuditOperation(operation);
86
504
  if (auditOp === void 0) {
87
- return result;
505
+ return query(args);
88
506
  }
507
+ const tableName = resolveTableName(model);
508
+ const shouldSkipBefore = skipBeforeCapture !== void 0 && skipBeforeCapture.includes(tableName);
509
+ const beforeState = shouldSkipBefore ? { type: "none" } : await fetchBeforeState({ prisma, model, operation, args, handleError, maxBeforeStateRows });
510
+ const result = await query(args);
89
511
  try {
90
512
  await fireCaptureLog({
91
- model,
513
+ tableName,
92
514
  operation,
93
515
  auditOp,
94
516
  args,
95
517
  result,
518
+ beforeState,
96
519
  captureLog,
97
520
  bulkMode,
98
521
  metadata,
99
- tableNameTransform
522
+ actorId: getAuditContext()?.actorId
100
523
  });
101
524
  } catch (err) {
102
525
  handleError(err);
@@ -108,19 +531,62 @@ function withAuditExtension(prisma, captureLog, options = {}) {
108
531
  });
109
532
  return extended;
110
533
  }
111
- async function fireCaptureLog({
534
+ async function fetchBeforeState({
535
+ prisma,
112
536
  model,
113
537
  operation,
538
+ args,
539
+ handleError,
540
+ maxBeforeStateRows
541
+ }) {
542
+ if (operation === "create" || operation === "createMany" || operation === "createManyAndReturn" || operation === "delete") {
543
+ return { type: "none" };
544
+ }
545
+ const delegate = getModelDelegate(getTxClient() ?? prisma, model);
546
+ if (delegate === void 0) {
547
+ return { type: "none" };
548
+ }
549
+ if (operation === "update" || operation === "upsert") {
550
+ const where = getArgsWhere(args);
551
+ if (where === void 0) {
552
+ return { type: "single", row: void 0 };
553
+ }
554
+ try {
555
+ const found = await delegate.findUnique({ where });
556
+ return { type: "single", row: toRecord(found) };
557
+ } catch (err) {
558
+ handleError(err);
559
+ return { type: "single", row: void 0 };
560
+ }
561
+ }
562
+ if (operation === "updateMany" || operation === "deleteMany") {
563
+ const where = getArgsWhere(args);
564
+ try {
565
+ const rows = await delegate.findMany({ where: where ?? {}, take: maxBeforeStateRows + 1 });
566
+ if (rows.length > maxBeforeStateRows) {
567
+ return { type: "none" };
568
+ }
569
+ const records = rows.map((r) => toRecord(r)).filter((r) => r !== void 0);
570
+ return { type: "many", rows: records };
571
+ } catch (err) {
572
+ handleError(err);
573
+ return { type: "many", rows: [] };
574
+ }
575
+ }
576
+ return { type: "none" };
577
+ }
578
+ async function fireCaptureLog({
579
+ tableName,
580
+ operation,
114
581
  auditOp,
115
582
  args,
116
583
  result,
584
+ beforeState,
117
585
  captureLog,
118
586
  bulkMode,
119
587
  metadata,
120
- tableNameTransform
588
+ actorId
121
589
  }) {
122
- const tableName = tableNameTransform !== void 0 ? tableNameTransform(model) : model;
123
- const actorId = getAuditContext()?.actorId;
124
590
  if (operation === "createMany") {
125
591
  await handleCreateMany({ tableName, auditOp, args, captureLog, bulkMode, metadata, actorId });
126
592
  return;
@@ -129,18 +595,30 @@ async function fireCaptureLog({
129
595
  await handleCreateManyAndReturn({ tableName, auditOp, result, captureLog, bulkMode, metadata, actorId });
130
596
  return;
131
597
  }
132
- if (operation === "updateMany" || operation === "deleteMany") {
598
+ if (operation === "updateMany") {
599
+ await handleUpdateMany({ tableName, auditOp, beforeState, captureLog, bulkMode, metadata, actorId });
600
+ return;
601
+ }
602
+ if (operation === "deleteMany") {
603
+ await handleDeleteMany({ tableName, auditOp, beforeState, captureLog, bulkMode, metadata, actorId });
604
+ return;
605
+ }
606
+ const row = toRecord(result);
607
+ const recordId = (row !== void 0 ? extractId(row) : void 0) ?? "unknown";
608
+ if (operation === "upsert") {
609
+ const before = beforeState.type === "single" ? beforeState.row : void 0;
610
+ const effectiveOp = before !== void 0 ? "UPDATE" : "INSERT";
133
611
  await captureLog({
134
612
  tableName,
135
- operation: auditOp,
136
- recordId: "unknown",
613
+ operation: effectiveOp,
614
+ recordId,
615
+ ...before !== void 0 && { before },
616
+ ...row !== void 0 && { after: row },
137
617
  ...metadata !== void 0 && { metadata },
138
618
  ...actorId !== void 0 && { actorId }
139
619
  });
140
620
  return;
141
621
  }
142
- const row = toRecord(result);
143
- const recordId = (row !== void 0 ? extractId(row) : void 0) ?? "unknown";
144
622
  if (auditOp === "INSERT") {
145
623
  await captureLog({
146
624
  tableName,
@@ -153,10 +631,12 @@ async function fireCaptureLog({
153
631
  return;
154
632
  }
155
633
  if (auditOp === "UPDATE") {
634
+ const before = beforeState.type === "single" ? beforeState.row : void 0;
156
635
  await captureLog({
157
636
  tableName,
158
637
  operation: auditOp,
159
638
  recordId,
639
+ ...before !== void 0 && { before },
160
640
  ...row !== void 0 && { after: row },
161
641
  ...metadata !== void 0 && { metadata },
162
642
  ...actorId !== void 0 && { actorId }
@@ -262,6 +742,94 @@ async function handleCreateManyAndReturn({
262
742
  })
263
743
  );
264
744
  }
745
+ async function handleUpdateMany({
746
+ tableName,
747
+ auditOp,
748
+ beforeState,
749
+ captureLog,
750
+ bulkMode,
751
+ metadata,
752
+ actorId
753
+ }) {
754
+ if (bulkMode === "bulk") {
755
+ await captureLog({
756
+ tableName,
757
+ operation: auditOp,
758
+ recordId: "unknown",
759
+ ...metadata !== void 0 && { metadata },
760
+ ...actorId !== void 0 && { actorId }
761
+ });
762
+ return;
763
+ }
764
+ const rows = beforeState.type === "many" ? beforeState.rows : [];
765
+ if (rows.length === 0) {
766
+ await captureLog({
767
+ tableName,
768
+ operation: auditOp,
769
+ recordId: "unknown",
770
+ ...metadata !== void 0 && { metadata },
771
+ ...actorId !== void 0 && { actorId }
772
+ });
773
+ return;
774
+ }
775
+ await Promise.all(
776
+ rows.map((row) => {
777
+ const recordId = extractId(row) ?? "unknown";
778
+ return captureLog({
779
+ tableName,
780
+ operation: auditOp,
781
+ recordId,
782
+ before: row,
783
+ ...metadata !== void 0 && { metadata },
784
+ ...actorId !== void 0 && { actorId }
785
+ });
786
+ })
787
+ );
788
+ }
789
+ async function handleDeleteMany({
790
+ tableName,
791
+ auditOp,
792
+ beforeState,
793
+ captureLog,
794
+ bulkMode,
795
+ metadata,
796
+ actorId
797
+ }) {
798
+ if (bulkMode === "bulk") {
799
+ await captureLog({
800
+ tableName,
801
+ operation: auditOp,
802
+ recordId: "unknown",
803
+ ...metadata !== void 0 && { metadata },
804
+ ...actorId !== void 0 && { actorId }
805
+ });
806
+ return;
807
+ }
808
+ const rows = beforeState.type === "many" ? beforeState.rows : [];
809
+ if (rows.length === 0) {
810
+ await captureLog({
811
+ tableName,
812
+ operation: auditOp,
813
+ recordId: "unknown",
814
+ ...metadata !== void 0 && { metadata },
815
+ ...actorId !== void 0 && { actorId }
816
+ });
817
+ return;
818
+ }
819
+ await Promise.all(
820
+ rows.map((row) => {
821
+ const recordId = extractId(row) ?? "unknown";
822
+ return captureLog({
823
+ tableName,
824
+ operation: auditOp,
825
+ recordId,
826
+ before: row,
827
+ ...metadata !== void 0 && { metadata },
828
+ ...actorId !== void 0 && { actorId }
829
+ });
830
+ })
831
+ );
832
+ }
265
833
  function buildErrorHandler(onError) {
266
834
  return (error) => {
267
835
  try {
@@ -275,6 +843,20 @@ function buildErrorHandler(onError) {
275
843
  }
276
844
  };
277
845
  }
846
+ function isModelDelegate(value) {
847
+ if (value === null || value === void 0 || typeof value !== "object") {
848
+ return false;
849
+ }
850
+ return "findUnique" in value && "findMany" in value && typeof Reflect.get(value, "findUnique") === "function" && typeof Reflect.get(value, "findMany") === "function";
851
+ }
852
+ function getModelDelegate(client, model) {
853
+ const lowerModel = model.charAt(0).toLowerCase() + model.slice(1);
854
+ const value = Reflect.get(Object(client), lowerModel);
855
+ if (isModelDelegate(value)) {
856
+ return value;
857
+ }
858
+ return void 0;
859
+ }
278
860
  function extractId(record) {
279
861
  const id = record["id"];
280
862
  if (id !== void 0 && id !== null) {
@@ -290,15 +872,23 @@ function toRecord(value) {
290
872
  }
291
873
  function getArgsData(args) {
292
874
  if (args !== null && typeof args === "object" && "data" in args) {
293
- const data = args["data"];
875
+ const data = Reflect.get(args, "data");
294
876
  if (Array.isArray(data)) {
295
877
  return data;
296
878
  }
297
879
  }
298
880
  return [];
299
881
  }
882
+ function getArgsWhere(args) {
883
+ if (args !== null && typeof args === "object" && "where" in args) {
884
+ return Reflect.get(args, "where");
885
+ }
886
+ return void 0;
887
+ }
300
888
  export {
301
889
  prismaAuditAdapter,
890
+ prismaModelMap,
891
+ runWithTxClient,
302
892
  withAuditExtension
303
893
  };
304
894
  //# sourceMappingURL=index.js.map