@usebetterdev/audit-prisma 0.4.0-beta.4 → 0.5.0-beta.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 usebetter
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/dist/index.cjs CHANGED
@@ -21,6 +21,7 @@ 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,
24
25
  withAuditExtension: () => withAuditExtension
25
26
  });
26
27
  module.exports = __toCommonJS(index_exports);
@@ -93,12 +94,44 @@ function getAuditOperation(action) {
93
94
  return void 0;
94
95
  }
95
96
 
97
+ // src/model-map.ts
98
+ function hasPrismaRuntimeDataModel(value) {
99
+ if (!("_runtimeDataModel" in value)) {
100
+ return false;
101
+ }
102
+ const { _runtimeDataModel: rdm } = value;
103
+ if (rdm === null || typeof rdm !== "object") {
104
+ return false;
105
+ }
106
+ return "models" in rdm;
107
+ }
108
+ function prismaModelMap(prisma) {
109
+ if (!hasPrismaRuntimeDataModel(prisma)) {
110
+ return void 0;
111
+ }
112
+ const result = {};
113
+ for (const [modelName, meta] of Object.entries(prisma._runtimeDataModel.models)) {
114
+ result[modelName] = meta.dbName ?? modelName;
115
+ }
116
+ return result;
117
+ }
118
+ function buildTableNameResolver(prisma, transform) {
119
+ if (transform !== void 0) {
120
+ return transform;
121
+ }
122
+ const map = prismaModelMap(prisma);
123
+ if (map !== void 0) {
124
+ return (modelName) => map[modelName] ?? modelName;
125
+ }
126
+ return (modelName) => modelName;
127
+ }
128
+
96
129
  // src/extension.ts
97
130
  function withAuditExtension(prisma, captureLog, options = {}) {
98
131
  const bulkMode = options.bulkMode ?? "per-row";
99
132
  const handleError = buildErrorHandler(options.onError);
100
133
  const metadata = options.metadata;
101
- const tableNameTransform = options.tableNameTransform;
134
+ const resolveTableName = buildTableNameResolver(prisma, options.tableNameTransform);
102
135
  const extended = prisma.$extends({
103
136
  query: {
104
137
  $allModels: {
@@ -113,17 +146,17 @@ function withAuditExtension(prisma, captureLog, options = {}) {
113
146
  if (auditOp === void 0) {
114
147
  return result;
115
148
  }
149
+ const tableName = resolveTableName(model);
116
150
  try {
117
151
  await fireCaptureLog({
118
- model,
152
+ tableName,
119
153
  operation,
120
154
  auditOp,
121
155
  args,
122
156
  result,
123
157
  captureLog,
124
158
  bulkMode,
125
- metadata,
126
- tableNameTransform
159
+ metadata
127
160
  });
128
161
  } catch (err) {
129
162
  handleError(err);
@@ -136,17 +169,15 @@ function withAuditExtension(prisma, captureLog, options = {}) {
136
169
  return extended;
137
170
  }
138
171
  async function fireCaptureLog({
139
- model,
172
+ tableName,
140
173
  operation,
141
174
  auditOp,
142
175
  args,
143
176
  result,
144
177
  captureLog,
145
178
  bulkMode,
146
- metadata,
147
- tableNameTransform
179
+ metadata
148
180
  }) {
149
- const tableName = tableNameTransform !== void 0 ? tableNameTransform(model) : model;
150
181
  const actorId = (0, import_audit_core.getAuditContext)()?.actorId;
151
182
  if (operation === "createMany") {
152
183
  await handleCreateMany({ tableName, auditOp, args, captureLog, bulkMode, metadata, actorId });
@@ -327,6 +358,7 @@ function getArgsData(args) {
327
358
  // Annotate the CommonJS export names for ESM import in node:
328
359
  0 && (module.exports = {
329
360
  prismaAuditAdapter,
361
+ prismaModelMap,
330
362
  withAuditExtension
331
363
  });
332
364
  //# sourceMappingURL=index.cjs.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts","../src/adapter.ts","../src/extension.ts","../src/action-map.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\";\n","import type { AuditDatabaseAdapter, AuditLog } from \"@usebetterdev/audit-core\";\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}\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 * Only `writeLog` is implemented — querying audit logs via Prisma is out of scope.\n * Use `drizzleAuditAdapter` if you also need `queryLogs`.\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}\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\";\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\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 * individual record IDs are unavailable so one entry with `recordId: \"unknown\"`\n * is fired regardless.\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 * Defaults to the model name as-is (PascalCase, e.g. `\"Project\"`).\n *\n * Use `(name) => name.toLowerCase()` to normalise to SQL table naming so\n * Prisma audit entries align with Drizzle and other SQL-based adapters.\n *\n * @example\n * ```ts\n * withAuditExtension(prisma, captureLog, {\n * tableNameTransform: (name) => name.toLowerCase(), // \"Project\" → \"projects\"\n * })\n * ```\n */\n tableNameTransform?: (modelName: string) => string;\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 * Filtering by audited tables is NOT done here — `captureLog` (from\n * `betterAudit`) already skips tables not in `auditTables`.\n *\n * @example\n * ```ts\n * const audit = betterAudit({ database: prismaAuditAdapter(prisma), auditTables: [\"User\"] });\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 tableNameTransform = 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 result = await query(args);\n\n const auditOp = getAuditOperation(operation);\n if (auditOp === undefined) {\n return result;\n }\n\n try {\n await fireCaptureLog({\n model,\n operation,\n auditOp,\n args,\n result,\n captureLog,\n bulkMode,\n metadata,\n tableNameTransform,\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\ninterface FireCaptureLogParams {\n model: string;\n operation: string;\n auditOp: AuditOperation;\n args: unknown;\n result: unknown;\n captureLog: (input: CaptureLogInput) => Promise<void>;\n bulkMode: BulkMode;\n metadata: Record<string, unknown> | undefined;\n tableNameTransform: ((modelName: string) => string) | undefined;\n}\n\nasync function fireCaptureLog({\n model,\n operation,\n auditOp,\n args,\n result,\n captureLog,\n bulkMode,\n metadata,\n tableNameTransform,\n}: FireCaptureLogParams): Promise<void> {\n const tableName = tableNameTransform !== undefined ? tableNameTransform(model) : model;\n const actorId = getAuditContext()?.actorId;\n\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\" || operation === \"deleteMany\") {\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 // Single-record operations: create, update, upsert, delete\n const row = toRecord(result);\n const recordId = (row !== undefined ? extractId(row) : undefined) ?? \"unknown\";\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 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 // DELETE: result is the deleted record\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\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\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\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 = (args as Record<string, unknown>)[\"data\"];\n if (Array.isArray(data)) {\n return data;\n }\n }\n return [];\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"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../src/index.ts","../src/adapter.ts","../src/extension.ts","../src/action-map.ts","../src/model-map.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\";\n","import type { AuditDatabaseAdapter, AuditLog } from \"@usebetterdev/audit-core\";\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}\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 * Only `writeLog` is implemented — querying audit logs via Prisma is out of scope.\n * Use `drizzleAuditAdapter` if you also need `queryLogs`.\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}\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\";\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\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 * individual record IDs are unavailable so one entry with `recordId: \"unknown\"`\n * is fired regardless.\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\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 * 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 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 result = await query(args);\n\n const auditOp = getAuditOperation(operation);\n if (auditOp === undefined) {\n return result;\n }\n\n const tableName = resolveTableName(model);\n\n try {\n await fireCaptureLog({\n tableName,\n operation,\n auditOp,\n args,\n result,\n captureLog,\n bulkMode,\n metadata,\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\ninterface FireCaptureLogParams {\n tableName: string;\n operation: string;\n auditOp: AuditOperation;\n args: unknown;\n result: unknown;\n captureLog: (input: CaptureLogInput) => Promise<void>;\n bulkMode: BulkMode;\n metadata: Record<string, unknown> | undefined;\n}\n\nasync function fireCaptureLog({\n tableName,\n operation,\n auditOp,\n args,\n result,\n captureLog,\n bulkMode,\n metadata,\n}: FireCaptureLogParams): Promise<void> {\n const actorId = getAuditContext()?.actorId;\n\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\" || operation === \"deleteMany\") {\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 // Single-record operations: create, update, upsert, delete\n const row = toRecord(result);\n const recordId = (row !== undefined ? extractId(row) : undefined) ?? \"unknown\";\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 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 // DELETE: result is the deleted record\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\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\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\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 = (args as Record<string, unknown>)[\"data\"];\n if (Array.isArray(data)) {\n return data;\n }\n }\n return [];\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"],"mappings":"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","names":[]}
package/dist/index.d.cts CHANGED
@@ -70,15 +70,17 @@ interface WithAuditExtensionOptions {
70
70
  metadata?: Record<string, unknown>;
71
71
  /**
72
72
  * Maps a Prisma model name to the `tableName` stored in audit logs.
73
- * Defaults to the model name as-is (PascalCase, e.g. `"Project"`).
74
73
  *
75
- * Use `(name) => name.toLowerCase()` to normalise to SQL table naming so
76
- * Prisma audit entries align with Drizzle and other SQL-based adapters.
74
+ * By default the extension auto-detects the SQL table name from Prisma's
75
+ * `_runtimeDataModel` (populated by `@@map` directives). If the runtime
76
+ * metadata is unavailable, the model name is used as-is.
77
+ *
78
+ * When provided, this function takes full precedence over auto-detection.
77
79
  *
78
80
  * @example
79
81
  * ```ts
80
82
  * withAuditExtension(prisma, captureLog, {
81
- * tableNameTransform: (name) => name.toLowerCase(), // "Project" → "projects"
83
+ * tableNameTransform: (name) => name.toLowerCase(),
82
84
  * })
83
85
  * ```
84
86
  */
@@ -99,11 +101,25 @@ interface WithAuditExtensionOptions {
99
101
  *
100
102
  * @example
101
103
  * ```ts
102
- * const audit = betterAudit({ database: prismaAuditAdapter(prisma), auditTables: ["User"] });
104
+ * // auditTables must use SQL table names (auto-detected from @@map directives)
105
+ * const audit = betterAudit({ database: prismaAuditAdapter(prisma), auditTables: ["users"] });
103
106
  * const auditedPrisma = withAuditExtension(prisma, audit.captureLog);
104
107
  * // Use auditedPrisma everywhere — mutations are transparently logged
105
108
  * ```
106
109
  */
107
110
  declare function withAuditExtension<T extends PrismaClientLike>(prisma: T, captureLog: (input: CaptureLogInput) => Promise<void>, options?: WithAuditExtensionOptions): T;
108
111
 
109
- export { type BulkMode, type PrismaClientLike, type PrismaClientWithRaw, type WithAuditExtensionOptions, prismaAuditAdapter, withAuditExtension };
112
+ /**
113
+ * Runtime model-to-table mapping for Prisma clients that expose
114
+ * `_runtimeDataModel.models`. This lets the audit extension auto-detect
115
+ * the SQL table name (from `@@map`) without user configuration.
116
+ */
117
+ /**
118
+ * Returns a `Record<string, string>` mapping Prisma model names to their
119
+ * SQL table names (from `@@map`). Models without `@@map` map to themselves.
120
+ *
121
+ * Returns `undefined` if the client does not expose `_runtimeDataModel`.
122
+ */
123
+ declare function prismaModelMap(prisma: object): Record<string, string> | undefined;
124
+
125
+ export { type BulkMode, type PrismaClientLike, type PrismaClientWithRaw, type WithAuditExtensionOptions, prismaAuditAdapter, prismaModelMap, withAuditExtension };
package/dist/index.d.ts CHANGED
@@ -70,15 +70,17 @@ interface WithAuditExtensionOptions {
70
70
  metadata?: Record<string, unknown>;
71
71
  /**
72
72
  * Maps a Prisma model name to the `tableName` stored in audit logs.
73
- * Defaults to the model name as-is (PascalCase, e.g. `"Project"`).
74
73
  *
75
- * Use `(name) => name.toLowerCase()` to normalise to SQL table naming so
76
- * Prisma audit entries align with Drizzle and other SQL-based adapters.
74
+ * By default the extension auto-detects the SQL table name from Prisma's
75
+ * `_runtimeDataModel` (populated by `@@map` directives). If the runtime
76
+ * metadata is unavailable, the model name is used as-is.
77
+ *
78
+ * When provided, this function takes full precedence over auto-detection.
77
79
  *
78
80
  * @example
79
81
  * ```ts
80
82
  * withAuditExtension(prisma, captureLog, {
81
- * tableNameTransform: (name) => name.toLowerCase(), // "Project" → "projects"
83
+ * tableNameTransform: (name) => name.toLowerCase(),
82
84
  * })
83
85
  * ```
84
86
  */
@@ -99,11 +101,25 @@ interface WithAuditExtensionOptions {
99
101
  *
100
102
  * @example
101
103
  * ```ts
102
- * const audit = betterAudit({ database: prismaAuditAdapter(prisma), auditTables: ["User"] });
104
+ * // auditTables must use SQL table names (auto-detected from @@map directives)
105
+ * const audit = betterAudit({ database: prismaAuditAdapter(prisma), auditTables: ["users"] });
103
106
  * const auditedPrisma = withAuditExtension(prisma, audit.captureLog);
104
107
  * // Use auditedPrisma everywhere — mutations are transparently logged
105
108
  * ```
106
109
  */
107
110
  declare function withAuditExtension<T extends PrismaClientLike>(prisma: T, captureLog: (input: CaptureLogInput) => Promise<void>, options?: WithAuditExtensionOptions): T;
108
111
 
109
- export { type BulkMode, type PrismaClientLike, type PrismaClientWithRaw, type WithAuditExtensionOptions, prismaAuditAdapter, withAuditExtension };
112
+ /**
113
+ * Runtime model-to-table mapping for Prisma clients that expose
114
+ * `_runtimeDataModel.models`. This lets the audit extension auto-detect
115
+ * the SQL table name (from `@@map`) without user configuration.
116
+ */
117
+ /**
118
+ * Returns a `Record<string, string>` mapping Prisma model names to their
119
+ * SQL table names (from `@@map`). Models without `@@map` map to themselves.
120
+ *
121
+ * Returns `undefined` if the client does not expose `_runtimeDataModel`.
122
+ */
123
+ declare function prismaModelMap(prisma: object): Record<string, string> | undefined;
124
+
125
+ export { type BulkMode, type PrismaClientLike, type PrismaClientWithRaw, type WithAuditExtensionOptions, prismaAuditAdapter, prismaModelMap, withAuditExtension };
package/dist/index.js CHANGED
@@ -66,12 +66,44 @@ function getAuditOperation(action) {
66
66
  return void 0;
67
67
  }
68
68
 
69
+ // src/model-map.ts
70
+ function hasPrismaRuntimeDataModel(value) {
71
+ if (!("_runtimeDataModel" in value)) {
72
+ return false;
73
+ }
74
+ const { _runtimeDataModel: rdm } = value;
75
+ if (rdm === null || typeof rdm !== "object") {
76
+ return false;
77
+ }
78
+ return "models" in rdm;
79
+ }
80
+ function prismaModelMap(prisma) {
81
+ if (!hasPrismaRuntimeDataModel(prisma)) {
82
+ return void 0;
83
+ }
84
+ const result = {};
85
+ for (const [modelName, meta] of Object.entries(prisma._runtimeDataModel.models)) {
86
+ result[modelName] = meta.dbName ?? modelName;
87
+ }
88
+ return result;
89
+ }
90
+ function buildTableNameResolver(prisma, transform) {
91
+ if (transform !== void 0) {
92
+ return transform;
93
+ }
94
+ const map = prismaModelMap(prisma);
95
+ if (map !== void 0) {
96
+ return (modelName) => map[modelName] ?? modelName;
97
+ }
98
+ return (modelName) => modelName;
99
+ }
100
+
69
101
  // src/extension.ts
70
102
  function withAuditExtension(prisma, captureLog, options = {}) {
71
103
  const bulkMode = options.bulkMode ?? "per-row";
72
104
  const handleError = buildErrorHandler(options.onError);
73
105
  const metadata = options.metadata;
74
- const tableNameTransform = options.tableNameTransform;
106
+ const resolveTableName = buildTableNameResolver(prisma, options.tableNameTransform);
75
107
  const extended = prisma.$extends({
76
108
  query: {
77
109
  $allModels: {
@@ -86,17 +118,17 @@ function withAuditExtension(prisma, captureLog, options = {}) {
86
118
  if (auditOp === void 0) {
87
119
  return result;
88
120
  }
121
+ const tableName = resolveTableName(model);
89
122
  try {
90
123
  await fireCaptureLog({
91
- model,
124
+ tableName,
92
125
  operation,
93
126
  auditOp,
94
127
  args,
95
128
  result,
96
129
  captureLog,
97
130
  bulkMode,
98
- metadata,
99
- tableNameTransform
131
+ metadata
100
132
  });
101
133
  } catch (err) {
102
134
  handleError(err);
@@ -109,17 +141,15 @@ function withAuditExtension(prisma, captureLog, options = {}) {
109
141
  return extended;
110
142
  }
111
143
  async function fireCaptureLog({
112
- model,
144
+ tableName,
113
145
  operation,
114
146
  auditOp,
115
147
  args,
116
148
  result,
117
149
  captureLog,
118
150
  bulkMode,
119
- metadata,
120
- tableNameTransform
151
+ metadata
121
152
  }) {
122
- const tableName = tableNameTransform !== void 0 ? tableNameTransform(model) : model;
123
153
  const actorId = getAuditContext()?.actorId;
124
154
  if (operation === "createMany") {
125
155
  await handleCreateMany({ tableName, auditOp, args, captureLog, bulkMode, metadata, actorId });
@@ -299,6 +329,7 @@ function getArgsData(args) {
299
329
  }
300
330
  export {
301
331
  prismaAuditAdapter,
332
+ prismaModelMap,
302
333
  withAuditExtension
303
334
  };
304
335
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/adapter.ts","../src/extension.ts","../src/action-map.ts"],"sourcesContent":["import type { AuditDatabaseAdapter, AuditLog } from \"@usebetterdev/audit-core\";\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}\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 * Only `writeLog` is implemented — querying audit logs via Prisma is out of scope.\n * Use `drizzleAuditAdapter` if you also need `queryLogs`.\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}\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\";\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\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 * individual record IDs are unavailable so one entry with `recordId: \"unknown\"`\n * is fired regardless.\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 * Defaults to the model name as-is (PascalCase, e.g. `\"Project\"`).\n *\n * Use `(name) => name.toLowerCase()` to normalise to SQL table naming so\n * Prisma audit entries align with Drizzle and other SQL-based adapters.\n *\n * @example\n * ```ts\n * withAuditExtension(prisma, captureLog, {\n * tableNameTransform: (name) => name.toLowerCase(), // \"Project\" → \"projects\"\n * })\n * ```\n */\n tableNameTransform?: (modelName: string) => string;\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 * Filtering by audited tables is NOT done here — `captureLog` (from\n * `betterAudit`) already skips tables not in `auditTables`.\n *\n * @example\n * ```ts\n * const audit = betterAudit({ database: prismaAuditAdapter(prisma), auditTables: [\"User\"] });\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 tableNameTransform = 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 result = await query(args);\n\n const auditOp = getAuditOperation(operation);\n if (auditOp === undefined) {\n return result;\n }\n\n try {\n await fireCaptureLog({\n model,\n operation,\n auditOp,\n args,\n result,\n captureLog,\n bulkMode,\n metadata,\n tableNameTransform,\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\ninterface FireCaptureLogParams {\n model: string;\n operation: string;\n auditOp: AuditOperation;\n args: unknown;\n result: unknown;\n captureLog: (input: CaptureLogInput) => Promise<void>;\n bulkMode: BulkMode;\n metadata: Record<string, unknown> | undefined;\n tableNameTransform: ((modelName: string) => string) | undefined;\n}\n\nasync function fireCaptureLog({\n model,\n operation,\n auditOp,\n args,\n result,\n captureLog,\n bulkMode,\n metadata,\n tableNameTransform,\n}: FireCaptureLogParams): Promise<void> {\n const tableName = tableNameTransform !== undefined ? tableNameTransform(model) : model;\n const actorId = getAuditContext()?.actorId;\n\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\" || operation === \"deleteMany\") {\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 // Single-record operations: create, update, upsert, delete\n const row = toRecord(result);\n const recordId = (row !== undefined ? extractId(row) : undefined) ?? \"unknown\";\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 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 // DELETE: result is the deleted record\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\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\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\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 = (args as Record<string, unknown>)[\"data\"];\n if (Array.isArray(data)) {\n return data;\n }\n }\n return [];\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"],"mappings":";AAmBO,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,EACF;AACF;AAEA,SAAS,WAAW,OAA+B;AACjD,MAAI,UAAU,UAAa,UAAU,MAAM;AACzC,WAAO;AAAA,EACT;AACA,SAAO,KAAK,UAAU,KAAK;AAC7B;;;AC5DA,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;;;AD8DO,SAAS,mBACd,QACA,YACA,UAAqC,CAAC,GACnC;AACH,QAAM,WAAW,QAAQ,YAAY;AACrC,QAAM,cAAc,kBAAkB,QAAQ,OAAO;AACrD,QAAM,WAAW,QAAQ;AACzB,QAAM,qBAAqB,QAAQ;AAEnC,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,SAAS,MAAM,MAAM,IAAI;AAE/B,gBAAM,UAAU,kBAAkB,SAAS;AAC3C,cAAI,YAAY,QAAW;AACzB,mBAAO;AAAA,UACT;AAEA,cAAI;AACF,kBAAM,eAAe;AAAA,cACnB;AAAA,cACA;AAAA,cACA;AAAA,cACA;AAAA,cACA;AAAA,cACA;AAAA,cACA;AAAA,cACA;AAAA,cACA;AAAA,YACF,CAAC;AAAA,UACH,SAAS,KAAK;AACZ,wBAAY,GAAG;AAAA,UACjB;AAEA,iBAAO;AAAA,QACT;AAAA,MACF;AAAA,IACF;AAAA,EACF,CAAC;AAID,SAAO;AACT;AAcA,eAAe,eAAe;AAAA,EAC5B;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,GAAwC;AACtC,QAAM,YAAY,uBAAuB,SAAY,mBAAmB,KAAK,IAAI;AACjF,QAAM,UAAU,gBAAgB,GAAG;AAEnC,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,gBAAgB,cAAc,cAAc;AAC5D,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,MAAM,SAAS,MAAM;AAC3B,QAAM,YAAY,QAAQ,SAAY,UAAU,GAAG,IAAI,WAAc;AAErE,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,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;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;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,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;AAEA,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;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,OAAQ,KAAiC,MAAM;AACrD,QAAI,MAAM,QAAQ,IAAI,GAAG;AACvB,aAAO;AAAA,IACT;AAAA,EACF;AACA,SAAO,CAAC;AACV;","names":[]}
1
+ {"version":3,"sources":["../src/adapter.ts","../src/extension.ts","../src/action-map.ts","../src/model-map.ts"],"sourcesContent":["import type { AuditDatabaseAdapter, AuditLog } from \"@usebetterdev/audit-core\";\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}\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 * Only `writeLog` is implemented — querying audit logs via Prisma is out of scope.\n * Use `drizzleAuditAdapter` if you also need `queryLogs`.\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}\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\";\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\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 * individual record IDs are unavailable so one entry with `recordId: \"unknown\"`\n * is fired regardless.\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\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 * 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 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 result = await query(args);\n\n const auditOp = getAuditOperation(operation);\n if (auditOp === undefined) {\n return result;\n }\n\n const tableName = resolveTableName(model);\n\n try {\n await fireCaptureLog({\n tableName,\n operation,\n auditOp,\n args,\n result,\n captureLog,\n bulkMode,\n metadata,\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\ninterface FireCaptureLogParams {\n tableName: string;\n operation: string;\n auditOp: AuditOperation;\n args: unknown;\n result: unknown;\n captureLog: (input: CaptureLogInput) => Promise<void>;\n bulkMode: BulkMode;\n metadata: Record<string, unknown> | undefined;\n}\n\nasync function fireCaptureLog({\n tableName,\n operation,\n auditOp,\n args,\n result,\n captureLog,\n bulkMode,\n metadata,\n}: FireCaptureLogParams): Promise<void> {\n const actorId = getAuditContext()?.actorId;\n\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\" || operation === \"deleteMany\") {\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 // Single-record operations: create, update, upsert, delete\n const row = toRecord(result);\n const recordId = (row !== undefined ? extractId(row) : undefined) ?? \"unknown\";\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 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 // DELETE: result is the deleted record\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\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\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\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 = (args as Record<string, unknown>)[\"data\"];\n if (Array.isArray(data)) {\n return data;\n }\n }\n return [];\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"],"mappings":"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","names":[]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@usebetterdev/audit-prisma",
3
- "version": "0.4.0-beta.4",
3
+ "version": "0.5.0-beta.1",
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",
@@ -24,16 +24,8 @@
24
24
  "dist",
25
25
  "README.md"
26
26
  ],
27
- "scripts": {
28
- "build": "tsup",
29
- "lint": "oxlint",
30
- "test": "vitest run",
31
- "test:integration": "vitest run -c vitest.integration.config.ts",
32
- "typecheck": "tsc --noEmit",
33
- "prisma:generate": "prisma generate --config prisma.config.ts"
34
- },
35
27
  "dependencies": {
36
- "@usebetterdev/audit-core": "workspace:*"
28
+ "@usebetterdev/audit-core": "0.5.0-beta.1"
37
29
  },
38
30
  "peerDependencies": {
39
31
  "@prisma/client": ">=5.0.0"
@@ -49,14 +41,22 @@
49
41
  "@testcontainers/postgresql": "^11.11.0",
50
42
  "@types/node": "^22.10.0",
51
43
  "@types/pg": "^8.11.0",
52
- "@usebetterdev/test-utils": "workspace:*",
53
44
  "pg": "^8.13.0",
54
45
  "prisma": "^7.0.0",
55
46
  "tsup": "^8.3.5",
56
47
  "typescript": "~5.7.2",
57
- "vitest": "^2.1.6"
48
+ "vitest": "^2.1.6",
49
+ "@usebetterdev/test-utils": "0.5.0-beta.1"
58
50
  },
59
51
  "engines": {
60
52
  "node": ">=22"
53
+ },
54
+ "scripts": {
55
+ "build": "tsup",
56
+ "lint": "oxlint",
57
+ "test": "vitest run",
58
+ "test:integration": "vitest run -c vitest.integration.config.ts",
59
+ "typecheck": "tsc --noEmit",
60
+ "prisma:generate": "prisma generate --config prisma.config.ts"
61
61
  }
62
- }
62
+ }