@usebetterdev/audit-prisma 0.4.0-beta.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.cjs +332 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +109 -0
- package/dist/index.d.ts +109 -0
- package/dist/index.js +304 -0
- package/dist/index.js.map +1 -0
- package/package.json +62 -0
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,332 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
|
|
20
|
+
// src/index.ts
|
|
21
|
+
var index_exports = {};
|
|
22
|
+
__export(index_exports, {
|
|
23
|
+
prismaAuditAdapter: () => prismaAuditAdapter,
|
|
24
|
+
withAuditExtension: () => withAuditExtension
|
|
25
|
+
});
|
|
26
|
+
module.exports = __toCommonJS(index_exports);
|
|
27
|
+
|
|
28
|
+
// src/adapter.ts
|
|
29
|
+
function prismaAuditAdapter(db) {
|
|
30
|
+
return {
|
|
31
|
+
async writeLog(log) {
|
|
32
|
+
await db.$executeRawUnsafe(
|
|
33
|
+
`INSERT INTO audit_logs (
|
|
34
|
+
id, timestamp, table_name, operation, record_id,
|
|
35
|
+
actor_id, before_data, after_data, diff,
|
|
36
|
+
label, description, severity, compliance,
|
|
37
|
+
notify, reason, metadata, redacted_fields
|
|
38
|
+
) VALUES (
|
|
39
|
+
$1::uuid, $2::timestamptz, $3, $4, $5,
|
|
40
|
+
$6, $7::jsonb, $8::jsonb, $9::jsonb,
|
|
41
|
+
$10, $11, $12, $13::jsonb,
|
|
42
|
+
$14, $15, $16::jsonb, $17::jsonb
|
|
43
|
+
)`,
|
|
44
|
+
log.id,
|
|
45
|
+
log.timestamp.toISOString(),
|
|
46
|
+
log.tableName,
|
|
47
|
+
log.operation,
|
|
48
|
+
log.recordId,
|
|
49
|
+
log.actorId ?? null,
|
|
50
|
+
jsonOrNull(log.beforeData),
|
|
51
|
+
jsonOrNull(log.afterData),
|
|
52
|
+
jsonOrNull(log.diff),
|
|
53
|
+
log.label ?? null,
|
|
54
|
+
log.description ?? null,
|
|
55
|
+
log.severity ?? null,
|
|
56
|
+
jsonOrNull(log.compliance),
|
|
57
|
+
log.notify ?? null,
|
|
58
|
+
log.reason ?? null,
|
|
59
|
+
jsonOrNull(log.metadata),
|
|
60
|
+
jsonOrNull(log.redactedFields)
|
|
61
|
+
);
|
|
62
|
+
}
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
function jsonOrNull(value) {
|
|
66
|
+
if (value === void 0 || value === null) {
|
|
67
|
+
return null;
|
|
68
|
+
}
|
|
69
|
+
return JSON.stringify(value);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// src/extension.ts
|
|
73
|
+
var import_audit_core = require("@usebetterdev/audit-core");
|
|
74
|
+
|
|
75
|
+
// src/action-map.ts
|
|
76
|
+
var ACTION_MAP = {
|
|
77
|
+
create: "INSERT",
|
|
78
|
+
createMany: "INSERT",
|
|
79
|
+
createManyAndReturn: "INSERT",
|
|
80
|
+
update: "UPDATE",
|
|
81
|
+
updateMany: "UPDATE",
|
|
82
|
+
upsert: "UPDATE",
|
|
83
|
+
delete: "DELETE",
|
|
84
|
+
deleteMany: "DELETE"
|
|
85
|
+
};
|
|
86
|
+
function isMutableAction(action) {
|
|
87
|
+
return action in ACTION_MAP;
|
|
88
|
+
}
|
|
89
|
+
function getAuditOperation(action) {
|
|
90
|
+
if (isMutableAction(action)) {
|
|
91
|
+
return ACTION_MAP[action];
|
|
92
|
+
}
|
|
93
|
+
return void 0;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// src/extension.ts
|
|
97
|
+
function withAuditExtension(prisma, captureLog, options = {}) {
|
|
98
|
+
const bulkMode = options.bulkMode ?? "per-row";
|
|
99
|
+
const handleError = buildErrorHandler(options.onError);
|
|
100
|
+
const metadata = options.metadata;
|
|
101
|
+
const tableNameTransform = options.tableNameTransform;
|
|
102
|
+
const extended = prisma.$extends({
|
|
103
|
+
query: {
|
|
104
|
+
$allModels: {
|
|
105
|
+
async $allOperations({
|
|
106
|
+
model,
|
|
107
|
+
operation,
|
|
108
|
+
args,
|
|
109
|
+
query
|
|
110
|
+
}) {
|
|
111
|
+
const result = await query(args);
|
|
112
|
+
const auditOp = getAuditOperation(operation);
|
|
113
|
+
if (auditOp === void 0) {
|
|
114
|
+
return result;
|
|
115
|
+
}
|
|
116
|
+
try {
|
|
117
|
+
await fireCaptureLog({
|
|
118
|
+
model,
|
|
119
|
+
operation,
|
|
120
|
+
auditOp,
|
|
121
|
+
args,
|
|
122
|
+
result,
|
|
123
|
+
captureLog,
|
|
124
|
+
bulkMode,
|
|
125
|
+
metadata,
|
|
126
|
+
tableNameTransform
|
|
127
|
+
});
|
|
128
|
+
} catch (err) {
|
|
129
|
+
handleError(err);
|
|
130
|
+
}
|
|
131
|
+
return result;
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
});
|
|
136
|
+
return extended;
|
|
137
|
+
}
|
|
138
|
+
async function fireCaptureLog({
|
|
139
|
+
model,
|
|
140
|
+
operation,
|
|
141
|
+
auditOp,
|
|
142
|
+
args,
|
|
143
|
+
result,
|
|
144
|
+
captureLog,
|
|
145
|
+
bulkMode,
|
|
146
|
+
metadata,
|
|
147
|
+
tableNameTransform
|
|
148
|
+
}) {
|
|
149
|
+
const tableName = tableNameTransform !== void 0 ? tableNameTransform(model) : model;
|
|
150
|
+
const actorId = (0, import_audit_core.getAuditContext)()?.actorId;
|
|
151
|
+
if (operation === "createMany") {
|
|
152
|
+
await handleCreateMany({ tableName, auditOp, args, captureLog, bulkMode, metadata, actorId });
|
|
153
|
+
return;
|
|
154
|
+
}
|
|
155
|
+
if (operation === "createManyAndReturn") {
|
|
156
|
+
await handleCreateManyAndReturn({ tableName, auditOp, result, captureLog, bulkMode, metadata, actorId });
|
|
157
|
+
return;
|
|
158
|
+
}
|
|
159
|
+
if (operation === "updateMany" || operation === "deleteMany") {
|
|
160
|
+
await captureLog({
|
|
161
|
+
tableName,
|
|
162
|
+
operation: auditOp,
|
|
163
|
+
recordId: "unknown",
|
|
164
|
+
...metadata !== void 0 && { metadata },
|
|
165
|
+
...actorId !== void 0 && { actorId }
|
|
166
|
+
});
|
|
167
|
+
return;
|
|
168
|
+
}
|
|
169
|
+
const row = toRecord(result);
|
|
170
|
+
const recordId = (row !== void 0 ? extractId(row) : void 0) ?? "unknown";
|
|
171
|
+
if (auditOp === "INSERT") {
|
|
172
|
+
await captureLog({
|
|
173
|
+
tableName,
|
|
174
|
+
operation: auditOp,
|
|
175
|
+
recordId,
|
|
176
|
+
...row !== void 0 && { after: row },
|
|
177
|
+
...metadata !== void 0 && { metadata },
|
|
178
|
+
...actorId !== void 0 && { actorId }
|
|
179
|
+
});
|
|
180
|
+
return;
|
|
181
|
+
}
|
|
182
|
+
if (auditOp === "UPDATE") {
|
|
183
|
+
await captureLog({
|
|
184
|
+
tableName,
|
|
185
|
+
operation: auditOp,
|
|
186
|
+
recordId,
|
|
187
|
+
...row !== void 0 && { after: row },
|
|
188
|
+
...metadata !== void 0 && { metadata },
|
|
189
|
+
...actorId !== void 0 && { actorId }
|
|
190
|
+
});
|
|
191
|
+
return;
|
|
192
|
+
}
|
|
193
|
+
await captureLog({
|
|
194
|
+
tableName,
|
|
195
|
+
operation: auditOp,
|
|
196
|
+
recordId,
|
|
197
|
+
...row !== void 0 && { before: row },
|
|
198
|
+
...metadata !== void 0 && { metadata },
|
|
199
|
+
...actorId !== void 0 && { actorId }
|
|
200
|
+
});
|
|
201
|
+
}
|
|
202
|
+
async function handleCreateMany({
|
|
203
|
+
tableName,
|
|
204
|
+
auditOp,
|
|
205
|
+
args,
|
|
206
|
+
captureLog,
|
|
207
|
+
bulkMode,
|
|
208
|
+
metadata,
|
|
209
|
+
actorId
|
|
210
|
+
}) {
|
|
211
|
+
if (bulkMode === "bulk") {
|
|
212
|
+
await captureLog({
|
|
213
|
+
tableName,
|
|
214
|
+
operation: auditOp,
|
|
215
|
+
recordId: "unknown",
|
|
216
|
+
...metadata !== void 0 && { metadata },
|
|
217
|
+
...actorId !== void 0 && { actorId }
|
|
218
|
+
});
|
|
219
|
+
return;
|
|
220
|
+
}
|
|
221
|
+
const rows = getArgsData(args);
|
|
222
|
+
if (rows.length === 0) {
|
|
223
|
+
await captureLog({
|
|
224
|
+
tableName,
|
|
225
|
+
operation: auditOp,
|
|
226
|
+
recordId: "unknown",
|
|
227
|
+
...metadata !== void 0 && { metadata },
|
|
228
|
+
...actorId !== void 0 && { actorId }
|
|
229
|
+
});
|
|
230
|
+
return;
|
|
231
|
+
}
|
|
232
|
+
await Promise.all(
|
|
233
|
+
rows.map((row) => {
|
|
234
|
+
const record = toRecord(row);
|
|
235
|
+
const recordId = (record !== void 0 ? extractId(record) : void 0) ?? "unknown";
|
|
236
|
+
return captureLog({
|
|
237
|
+
tableName,
|
|
238
|
+
operation: auditOp,
|
|
239
|
+
recordId,
|
|
240
|
+
...record !== void 0 && { after: record },
|
|
241
|
+
...metadata !== void 0 && { metadata },
|
|
242
|
+
...actorId !== void 0 && { actorId }
|
|
243
|
+
});
|
|
244
|
+
})
|
|
245
|
+
);
|
|
246
|
+
}
|
|
247
|
+
async function handleCreateManyAndReturn({
|
|
248
|
+
tableName,
|
|
249
|
+
auditOp,
|
|
250
|
+
result,
|
|
251
|
+
captureLog,
|
|
252
|
+
bulkMode,
|
|
253
|
+
metadata,
|
|
254
|
+
actorId
|
|
255
|
+
}) {
|
|
256
|
+
if (bulkMode === "bulk") {
|
|
257
|
+
await captureLog({
|
|
258
|
+
tableName,
|
|
259
|
+
operation: auditOp,
|
|
260
|
+
recordId: "unknown",
|
|
261
|
+
...metadata !== void 0 && { metadata },
|
|
262
|
+
...actorId !== void 0 && { actorId }
|
|
263
|
+
});
|
|
264
|
+
return;
|
|
265
|
+
}
|
|
266
|
+
const rows = Array.isArray(result) ? result : [];
|
|
267
|
+
if (rows.length === 0) {
|
|
268
|
+
await captureLog({
|
|
269
|
+
tableName,
|
|
270
|
+
operation: auditOp,
|
|
271
|
+
recordId: "unknown",
|
|
272
|
+
...metadata !== void 0 && { metadata },
|
|
273
|
+
...actorId !== void 0 && { actorId }
|
|
274
|
+
});
|
|
275
|
+
return;
|
|
276
|
+
}
|
|
277
|
+
await Promise.all(
|
|
278
|
+
rows.map((row) => {
|
|
279
|
+
const record = toRecord(row);
|
|
280
|
+
const recordId = (record !== void 0 ? extractId(record) : void 0) ?? "unknown";
|
|
281
|
+
return captureLog({
|
|
282
|
+
tableName,
|
|
283
|
+
operation: auditOp,
|
|
284
|
+
recordId,
|
|
285
|
+
...record !== void 0 && { after: record },
|
|
286
|
+
...metadata !== void 0 && { metadata },
|
|
287
|
+
...actorId !== void 0 && { actorId }
|
|
288
|
+
});
|
|
289
|
+
})
|
|
290
|
+
);
|
|
291
|
+
}
|
|
292
|
+
function buildErrorHandler(onError) {
|
|
293
|
+
return (error) => {
|
|
294
|
+
try {
|
|
295
|
+
if (onError !== void 0) {
|
|
296
|
+
onError(error);
|
|
297
|
+
} else {
|
|
298
|
+
const msg = error instanceof Error ? error.message : String(error);
|
|
299
|
+
console.error(`audit-prisma: capture failed \u2014 ${msg}`);
|
|
300
|
+
}
|
|
301
|
+
} catch {
|
|
302
|
+
}
|
|
303
|
+
};
|
|
304
|
+
}
|
|
305
|
+
function extractId(record) {
|
|
306
|
+
const id = record["id"];
|
|
307
|
+
if (id !== void 0 && id !== null) {
|
|
308
|
+
return String(id);
|
|
309
|
+
}
|
|
310
|
+
return void 0;
|
|
311
|
+
}
|
|
312
|
+
function toRecord(value) {
|
|
313
|
+
if (value !== null && value !== void 0 && typeof value === "object" && !Array.isArray(value)) {
|
|
314
|
+
return value;
|
|
315
|
+
}
|
|
316
|
+
return void 0;
|
|
317
|
+
}
|
|
318
|
+
function getArgsData(args) {
|
|
319
|
+
if (args !== null && typeof args === "object" && "data" in args) {
|
|
320
|
+
const data = args["data"];
|
|
321
|
+
if (Array.isArray(data)) {
|
|
322
|
+
return data;
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
return [];
|
|
326
|
+
}
|
|
327
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
328
|
+
0 && (module.exports = {
|
|
329
|
+
prismaAuditAdapter,
|
|
330
|
+
withAuditExtension
|
|
331
|
+
});
|
|
332
|
+
//# sourceMappingURL=index.cjs.map
|
|
@@ -0,0 +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":[]}
|
package/dist/index.d.cts
ADDED
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
import { AuditDatabaseAdapter, CaptureLogInput } from '@usebetterdev/audit-core';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Minimal duck-typed interface for a Prisma client that supports raw query execution.
|
|
5
|
+
* Avoids a hard dependency on the generated `@prisma/client`.
|
|
6
|
+
*/
|
|
7
|
+
interface PrismaClientWithRaw {
|
|
8
|
+
$executeRawUnsafe(query: string, ...params: unknown[]): Promise<number>;
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* Creates an `AuditDatabaseAdapter` backed by a Prisma client.
|
|
12
|
+
*
|
|
13
|
+
* Implements `writeLog` via `$executeRawUnsafe` for precise control over
|
|
14
|
+
* PostgreSQL type casts (::uuid, ::timestamptz, ::jsonb) and null handling.
|
|
15
|
+
*
|
|
16
|
+
* Only `writeLog` is implemented — querying audit logs via Prisma is out of scope.
|
|
17
|
+
* Use `drizzleAuditAdapter` if you also need `queryLogs`.
|
|
18
|
+
*/
|
|
19
|
+
declare function prismaAuditAdapter(db: PrismaClientWithRaw): AuditDatabaseAdapter;
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Minimal duck-typed interface for a Prisma client that supports `$extends`.
|
|
23
|
+
* Avoids a hard dependency on the generated `@prisma/client`.
|
|
24
|
+
*/
|
|
25
|
+
interface PrismaClientLike {
|
|
26
|
+
$extends(extension: PrismaExtensionDefinition): unknown;
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Shape of the Prisma `$extends` query extension definition.
|
|
30
|
+
* We only type what we need — the full shape lives in `@prisma/client/extension`.
|
|
31
|
+
*/
|
|
32
|
+
interface PrismaExtensionDefinition {
|
|
33
|
+
query: {
|
|
34
|
+
$allModels: {
|
|
35
|
+
$allOperations: (params: {
|
|
36
|
+
model: string;
|
|
37
|
+
operation: string;
|
|
38
|
+
args: unknown;
|
|
39
|
+
query: (args: unknown) => Promise<unknown>;
|
|
40
|
+
}) => Promise<unknown>;
|
|
41
|
+
};
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
type BulkMode = "per-row" | "bulk";
|
|
45
|
+
interface WithAuditExtensionOptions {
|
|
46
|
+
/**
|
|
47
|
+
* How to handle bulk write operations (`createMany`, `updateMany`, `deleteMany`).
|
|
48
|
+
*
|
|
49
|
+
* - `"per-row"` (default): for `createMany` and `createManyAndReturn`, fires
|
|
50
|
+
* one audit entry per item in `args.data`. For `updateMany` and `deleteMany`,
|
|
51
|
+
* individual record IDs are unavailable so one entry with `recordId: "unknown"`
|
|
52
|
+
* is fired regardless.
|
|
53
|
+
* - `"bulk"`: fires a single audit entry for the entire operation.
|
|
54
|
+
*/
|
|
55
|
+
bulkMode?: BulkMode;
|
|
56
|
+
/**
|
|
57
|
+
* Called when audit capture fails. Falls back to `console.error` if not set.
|
|
58
|
+
* Errors are always swallowed — audit failures never propagate to callers.
|
|
59
|
+
*/
|
|
60
|
+
onError?: (error: unknown) => void;
|
|
61
|
+
/**
|
|
62
|
+
* Extra structured data merged into every audit log entry produced by this
|
|
63
|
+
* extension. Useful for tagging logs with the adapter name or environment.
|
|
64
|
+
*
|
|
65
|
+
* @example
|
|
66
|
+
* ```ts
|
|
67
|
+
* withAuditExtension(prisma, audit.captureLog, { metadata: { adapter: "prisma" } })
|
|
68
|
+
* ```
|
|
69
|
+
*/
|
|
70
|
+
metadata?: Record<string, unknown>;
|
|
71
|
+
/**
|
|
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
|
+
*
|
|
75
|
+
* Use `(name) => name.toLowerCase()` to normalise to SQL table naming so
|
|
76
|
+
* Prisma audit entries align with Drizzle and other SQL-based adapters.
|
|
77
|
+
*
|
|
78
|
+
* @example
|
|
79
|
+
* ```ts
|
|
80
|
+
* withAuditExtension(prisma, captureLog, {
|
|
81
|
+
* tableNameTransform: (name) => name.toLowerCase(), // "Project" → "projects"
|
|
82
|
+
* })
|
|
83
|
+
* ```
|
|
84
|
+
*/
|
|
85
|
+
tableNameTransform?: (modelName: string) => string;
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* Wraps a Prisma client with a `$extends` query extension that calls
|
|
89
|
+
* `captureLog` after each successful mutation.
|
|
90
|
+
*
|
|
91
|
+
* Returns a new extended client of the same type `T`. Use this extended
|
|
92
|
+
* client in place of the original — all queries behave identically.
|
|
93
|
+
*
|
|
94
|
+
* Actor identity is read automatically from the current `AuditContext`
|
|
95
|
+
* (set via `audit.withContext` or `runWithAuditContext` from audit-core).
|
|
96
|
+
*
|
|
97
|
+
* Filtering by audited tables is NOT done here — `captureLog` (from
|
|
98
|
+
* `betterAudit`) already skips tables not in `auditTables`.
|
|
99
|
+
*
|
|
100
|
+
* @example
|
|
101
|
+
* ```ts
|
|
102
|
+
* const audit = betterAudit({ database: prismaAuditAdapter(prisma), auditTables: ["User"] });
|
|
103
|
+
* const auditedPrisma = withAuditExtension(prisma, audit.captureLog);
|
|
104
|
+
* // Use auditedPrisma everywhere — mutations are transparently logged
|
|
105
|
+
* ```
|
|
106
|
+
*/
|
|
107
|
+
declare function withAuditExtension<T extends PrismaClientLike>(prisma: T, captureLog: (input: CaptureLogInput) => Promise<void>, options?: WithAuditExtensionOptions): T;
|
|
108
|
+
|
|
109
|
+
export { type BulkMode, type PrismaClientLike, type PrismaClientWithRaw, type WithAuditExtensionOptions, prismaAuditAdapter, withAuditExtension };
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
import { AuditDatabaseAdapter, CaptureLogInput } from '@usebetterdev/audit-core';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Minimal duck-typed interface for a Prisma client that supports raw query execution.
|
|
5
|
+
* Avoids a hard dependency on the generated `@prisma/client`.
|
|
6
|
+
*/
|
|
7
|
+
interface PrismaClientWithRaw {
|
|
8
|
+
$executeRawUnsafe(query: string, ...params: unknown[]): Promise<number>;
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* Creates an `AuditDatabaseAdapter` backed by a Prisma client.
|
|
12
|
+
*
|
|
13
|
+
* Implements `writeLog` via `$executeRawUnsafe` for precise control over
|
|
14
|
+
* PostgreSQL type casts (::uuid, ::timestamptz, ::jsonb) and null handling.
|
|
15
|
+
*
|
|
16
|
+
* Only `writeLog` is implemented — querying audit logs via Prisma is out of scope.
|
|
17
|
+
* Use `drizzleAuditAdapter` if you also need `queryLogs`.
|
|
18
|
+
*/
|
|
19
|
+
declare function prismaAuditAdapter(db: PrismaClientWithRaw): AuditDatabaseAdapter;
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Minimal duck-typed interface for a Prisma client that supports `$extends`.
|
|
23
|
+
* Avoids a hard dependency on the generated `@prisma/client`.
|
|
24
|
+
*/
|
|
25
|
+
interface PrismaClientLike {
|
|
26
|
+
$extends(extension: PrismaExtensionDefinition): unknown;
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Shape of the Prisma `$extends` query extension definition.
|
|
30
|
+
* We only type what we need — the full shape lives in `@prisma/client/extension`.
|
|
31
|
+
*/
|
|
32
|
+
interface PrismaExtensionDefinition {
|
|
33
|
+
query: {
|
|
34
|
+
$allModels: {
|
|
35
|
+
$allOperations: (params: {
|
|
36
|
+
model: string;
|
|
37
|
+
operation: string;
|
|
38
|
+
args: unknown;
|
|
39
|
+
query: (args: unknown) => Promise<unknown>;
|
|
40
|
+
}) => Promise<unknown>;
|
|
41
|
+
};
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
type BulkMode = "per-row" | "bulk";
|
|
45
|
+
interface WithAuditExtensionOptions {
|
|
46
|
+
/**
|
|
47
|
+
* How to handle bulk write operations (`createMany`, `updateMany`, `deleteMany`).
|
|
48
|
+
*
|
|
49
|
+
* - `"per-row"` (default): for `createMany` and `createManyAndReturn`, fires
|
|
50
|
+
* one audit entry per item in `args.data`. For `updateMany` and `deleteMany`,
|
|
51
|
+
* individual record IDs are unavailable so one entry with `recordId: "unknown"`
|
|
52
|
+
* is fired regardless.
|
|
53
|
+
* - `"bulk"`: fires a single audit entry for the entire operation.
|
|
54
|
+
*/
|
|
55
|
+
bulkMode?: BulkMode;
|
|
56
|
+
/**
|
|
57
|
+
* Called when audit capture fails. Falls back to `console.error` if not set.
|
|
58
|
+
* Errors are always swallowed — audit failures never propagate to callers.
|
|
59
|
+
*/
|
|
60
|
+
onError?: (error: unknown) => void;
|
|
61
|
+
/**
|
|
62
|
+
* Extra structured data merged into every audit log entry produced by this
|
|
63
|
+
* extension. Useful for tagging logs with the adapter name or environment.
|
|
64
|
+
*
|
|
65
|
+
* @example
|
|
66
|
+
* ```ts
|
|
67
|
+
* withAuditExtension(prisma, audit.captureLog, { metadata: { adapter: "prisma" } })
|
|
68
|
+
* ```
|
|
69
|
+
*/
|
|
70
|
+
metadata?: Record<string, unknown>;
|
|
71
|
+
/**
|
|
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
|
+
*
|
|
75
|
+
* Use `(name) => name.toLowerCase()` to normalise to SQL table naming so
|
|
76
|
+
* Prisma audit entries align with Drizzle and other SQL-based adapters.
|
|
77
|
+
*
|
|
78
|
+
* @example
|
|
79
|
+
* ```ts
|
|
80
|
+
* withAuditExtension(prisma, captureLog, {
|
|
81
|
+
* tableNameTransform: (name) => name.toLowerCase(), // "Project" → "projects"
|
|
82
|
+
* })
|
|
83
|
+
* ```
|
|
84
|
+
*/
|
|
85
|
+
tableNameTransform?: (modelName: string) => string;
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* Wraps a Prisma client with a `$extends` query extension that calls
|
|
89
|
+
* `captureLog` after each successful mutation.
|
|
90
|
+
*
|
|
91
|
+
* Returns a new extended client of the same type `T`. Use this extended
|
|
92
|
+
* client in place of the original — all queries behave identically.
|
|
93
|
+
*
|
|
94
|
+
* Actor identity is read automatically from the current `AuditContext`
|
|
95
|
+
* (set via `audit.withContext` or `runWithAuditContext` from audit-core).
|
|
96
|
+
*
|
|
97
|
+
* Filtering by audited tables is NOT done here — `captureLog` (from
|
|
98
|
+
* `betterAudit`) already skips tables not in `auditTables`.
|
|
99
|
+
*
|
|
100
|
+
* @example
|
|
101
|
+
* ```ts
|
|
102
|
+
* const audit = betterAudit({ database: prismaAuditAdapter(prisma), auditTables: ["User"] });
|
|
103
|
+
* const auditedPrisma = withAuditExtension(prisma, audit.captureLog);
|
|
104
|
+
* // Use auditedPrisma everywhere — mutations are transparently logged
|
|
105
|
+
* ```
|
|
106
|
+
*/
|
|
107
|
+
declare function withAuditExtension<T extends PrismaClientLike>(prisma: T, captureLog: (input: CaptureLogInput) => Promise<void>, options?: WithAuditExtensionOptions): T;
|
|
108
|
+
|
|
109
|
+
export { type BulkMode, type PrismaClientLike, type PrismaClientWithRaw, type WithAuditExtensionOptions, prismaAuditAdapter, withAuditExtension };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,304 @@
|
|
|
1
|
+
// src/adapter.ts
|
|
2
|
+
function prismaAuditAdapter(db) {
|
|
3
|
+
return {
|
|
4
|
+
async writeLog(log) {
|
|
5
|
+
await db.$executeRawUnsafe(
|
|
6
|
+
`INSERT INTO audit_logs (
|
|
7
|
+
id, timestamp, table_name, operation, record_id,
|
|
8
|
+
actor_id, before_data, after_data, diff,
|
|
9
|
+
label, description, severity, compliance,
|
|
10
|
+
notify, reason, metadata, redacted_fields
|
|
11
|
+
) VALUES (
|
|
12
|
+
$1::uuid, $2::timestamptz, $3, $4, $5,
|
|
13
|
+
$6, $7::jsonb, $8::jsonb, $9::jsonb,
|
|
14
|
+
$10, $11, $12, $13::jsonb,
|
|
15
|
+
$14, $15, $16::jsonb, $17::jsonb
|
|
16
|
+
)`,
|
|
17
|
+
log.id,
|
|
18
|
+
log.timestamp.toISOString(),
|
|
19
|
+
log.tableName,
|
|
20
|
+
log.operation,
|
|
21
|
+
log.recordId,
|
|
22
|
+
log.actorId ?? null,
|
|
23
|
+
jsonOrNull(log.beforeData),
|
|
24
|
+
jsonOrNull(log.afterData),
|
|
25
|
+
jsonOrNull(log.diff),
|
|
26
|
+
log.label ?? null,
|
|
27
|
+
log.description ?? null,
|
|
28
|
+
log.severity ?? null,
|
|
29
|
+
jsonOrNull(log.compliance),
|
|
30
|
+
log.notify ?? null,
|
|
31
|
+
log.reason ?? null,
|
|
32
|
+
jsonOrNull(log.metadata),
|
|
33
|
+
jsonOrNull(log.redactedFields)
|
|
34
|
+
);
|
|
35
|
+
}
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
function jsonOrNull(value) {
|
|
39
|
+
if (value === void 0 || value === null) {
|
|
40
|
+
return null;
|
|
41
|
+
}
|
|
42
|
+
return JSON.stringify(value);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// src/extension.ts
|
|
46
|
+
import { getAuditContext } from "@usebetterdev/audit-core";
|
|
47
|
+
|
|
48
|
+
// src/action-map.ts
|
|
49
|
+
var ACTION_MAP = {
|
|
50
|
+
create: "INSERT",
|
|
51
|
+
createMany: "INSERT",
|
|
52
|
+
createManyAndReturn: "INSERT",
|
|
53
|
+
update: "UPDATE",
|
|
54
|
+
updateMany: "UPDATE",
|
|
55
|
+
upsert: "UPDATE",
|
|
56
|
+
delete: "DELETE",
|
|
57
|
+
deleteMany: "DELETE"
|
|
58
|
+
};
|
|
59
|
+
function isMutableAction(action) {
|
|
60
|
+
return action in ACTION_MAP;
|
|
61
|
+
}
|
|
62
|
+
function getAuditOperation(action) {
|
|
63
|
+
if (isMutableAction(action)) {
|
|
64
|
+
return ACTION_MAP[action];
|
|
65
|
+
}
|
|
66
|
+
return void 0;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// src/extension.ts
|
|
70
|
+
function withAuditExtension(prisma, captureLog, options = {}) {
|
|
71
|
+
const bulkMode = options.bulkMode ?? "per-row";
|
|
72
|
+
const handleError = buildErrorHandler(options.onError);
|
|
73
|
+
const metadata = options.metadata;
|
|
74
|
+
const tableNameTransform = options.tableNameTransform;
|
|
75
|
+
const extended = prisma.$extends({
|
|
76
|
+
query: {
|
|
77
|
+
$allModels: {
|
|
78
|
+
async $allOperations({
|
|
79
|
+
model,
|
|
80
|
+
operation,
|
|
81
|
+
args,
|
|
82
|
+
query
|
|
83
|
+
}) {
|
|
84
|
+
const result = await query(args);
|
|
85
|
+
const auditOp = getAuditOperation(operation);
|
|
86
|
+
if (auditOp === void 0) {
|
|
87
|
+
return result;
|
|
88
|
+
}
|
|
89
|
+
try {
|
|
90
|
+
await fireCaptureLog({
|
|
91
|
+
model,
|
|
92
|
+
operation,
|
|
93
|
+
auditOp,
|
|
94
|
+
args,
|
|
95
|
+
result,
|
|
96
|
+
captureLog,
|
|
97
|
+
bulkMode,
|
|
98
|
+
metadata,
|
|
99
|
+
tableNameTransform
|
|
100
|
+
});
|
|
101
|
+
} catch (err) {
|
|
102
|
+
handleError(err);
|
|
103
|
+
}
|
|
104
|
+
return result;
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
});
|
|
109
|
+
return extended;
|
|
110
|
+
}
|
|
111
|
+
async function fireCaptureLog({
|
|
112
|
+
model,
|
|
113
|
+
operation,
|
|
114
|
+
auditOp,
|
|
115
|
+
args,
|
|
116
|
+
result,
|
|
117
|
+
captureLog,
|
|
118
|
+
bulkMode,
|
|
119
|
+
metadata,
|
|
120
|
+
tableNameTransform
|
|
121
|
+
}) {
|
|
122
|
+
const tableName = tableNameTransform !== void 0 ? tableNameTransform(model) : model;
|
|
123
|
+
const actorId = getAuditContext()?.actorId;
|
|
124
|
+
if (operation === "createMany") {
|
|
125
|
+
await handleCreateMany({ tableName, auditOp, args, captureLog, bulkMode, metadata, actorId });
|
|
126
|
+
return;
|
|
127
|
+
}
|
|
128
|
+
if (operation === "createManyAndReturn") {
|
|
129
|
+
await handleCreateManyAndReturn({ tableName, auditOp, result, captureLog, bulkMode, metadata, actorId });
|
|
130
|
+
return;
|
|
131
|
+
}
|
|
132
|
+
if (operation === "updateMany" || operation === "deleteMany") {
|
|
133
|
+
await captureLog({
|
|
134
|
+
tableName,
|
|
135
|
+
operation: auditOp,
|
|
136
|
+
recordId: "unknown",
|
|
137
|
+
...metadata !== void 0 && { metadata },
|
|
138
|
+
...actorId !== void 0 && { actorId }
|
|
139
|
+
});
|
|
140
|
+
return;
|
|
141
|
+
}
|
|
142
|
+
const row = toRecord(result);
|
|
143
|
+
const recordId = (row !== void 0 ? extractId(row) : void 0) ?? "unknown";
|
|
144
|
+
if (auditOp === "INSERT") {
|
|
145
|
+
await captureLog({
|
|
146
|
+
tableName,
|
|
147
|
+
operation: auditOp,
|
|
148
|
+
recordId,
|
|
149
|
+
...row !== void 0 && { after: row },
|
|
150
|
+
...metadata !== void 0 && { metadata },
|
|
151
|
+
...actorId !== void 0 && { actorId }
|
|
152
|
+
});
|
|
153
|
+
return;
|
|
154
|
+
}
|
|
155
|
+
if (auditOp === "UPDATE") {
|
|
156
|
+
await captureLog({
|
|
157
|
+
tableName,
|
|
158
|
+
operation: auditOp,
|
|
159
|
+
recordId,
|
|
160
|
+
...row !== void 0 && { after: row },
|
|
161
|
+
...metadata !== void 0 && { metadata },
|
|
162
|
+
...actorId !== void 0 && { actorId }
|
|
163
|
+
});
|
|
164
|
+
return;
|
|
165
|
+
}
|
|
166
|
+
await captureLog({
|
|
167
|
+
tableName,
|
|
168
|
+
operation: auditOp,
|
|
169
|
+
recordId,
|
|
170
|
+
...row !== void 0 && { before: row },
|
|
171
|
+
...metadata !== void 0 && { metadata },
|
|
172
|
+
...actorId !== void 0 && { actorId }
|
|
173
|
+
});
|
|
174
|
+
}
|
|
175
|
+
async function handleCreateMany({
|
|
176
|
+
tableName,
|
|
177
|
+
auditOp,
|
|
178
|
+
args,
|
|
179
|
+
captureLog,
|
|
180
|
+
bulkMode,
|
|
181
|
+
metadata,
|
|
182
|
+
actorId
|
|
183
|
+
}) {
|
|
184
|
+
if (bulkMode === "bulk") {
|
|
185
|
+
await captureLog({
|
|
186
|
+
tableName,
|
|
187
|
+
operation: auditOp,
|
|
188
|
+
recordId: "unknown",
|
|
189
|
+
...metadata !== void 0 && { metadata },
|
|
190
|
+
...actorId !== void 0 && { actorId }
|
|
191
|
+
});
|
|
192
|
+
return;
|
|
193
|
+
}
|
|
194
|
+
const rows = getArgsData(args);
|
|
195
|
+
if (rows.length === 0) {
|
|
196
|
+
await captureLog({
|
|
197
|
+
tableName,
|
|
198
|
+
operation: auditOp,
|
|
199
|
+
recordId: "unknown",
|
|
200
|
+
...metadata !== void 0 && { metadata },
|
|
201
|
+
...actorId !== void 0 && { actorId }
|
|
202
|
+
});
|
|
203
|
+
return;
|
|
204
|
+
}
|
|
205
|
+
await Promise.all(
|
|
206
|
+
rows.map((row) => {
|
|
207
|
+
const record = toRecord(row);
|
|
208
|
+
const recordId = (record !== void 0 ? extractId(record) : void 0) ?? "unknown";
|
|
209
|
+
return captureLog({
|
|
210
|
+
tableName,
|
|
211
|
+
operation: auditOp,
|
|
212
|
+
recordId,
|
|
213
|
+
...record !== void 0 && { after: record },
|
|
214
|
+
...metadata !== void 0 && { metadata },
|
|
215
|
+
...actorId !== void 0 && { actorId }
|
|
216
|
+
});
|
|
217
|
+
})
|
|
218
|
+
);
|
|
219
|
+
}
|
|
220
|
+
async function handleCreateManyAndReturn({
|
|
221
|
+
tableName,
|
|
222
|
+
auditOp,
|
|
223
|
+
result,
|
|
224
|
+
captureLog,
|
|
225
|
+
bulkMode,
|
|
226
|
+
metadata,
|
|
227
|
+
actorId
|
|
228
|
+
}) {
|
|
229
|
+
if (bulkMode === "bulk") {
|
|
230
|
+
await captureLog({
|
|
231
|
+
tableName,
|
|
232
|
+
operation: auditOp,
|
|
233
|
+
recordId: "unknown",
|
|
234
|
+
...metadata !== void 0 && { metadata },
|
|
235
|
+
...actorId !== void 0 && { actorId }
|
|
236
|
+
});
|
|
237
|
+
return;
|
|
238
|
+
}
|
|
239
|
+
const rows = Array.isArray(result) ? result : [];
|
|
240
|
+
if (rows.length === 0) {
|
|
241
|
+
await captureLog({
|
|
242
|
+
tableName,
|
|
243
|
+
operation: auditOp,
|
|
244
|
+
recordId: "unknown",
|
|
245
|
+
...metadata !== void 0 && { metadata },
|
|
246
|
+
...actorId !== void 0 && { actorId }
|
|
247
|
+
});
|
|
248
|
+
return;
|
|
249
|
+
}
|
|
250
|
+
await Promise.all(
|
|
251
|
+
rows.map((row) => {
|
|
252
|
+
const record = toRecord(row);
|
|
253
|
+
const recordId = (record !== void 0 ? extractId(record) : void 0) ?? "unknown";
|
|
254
|
+
return captureLog({
|
|
255
|
+
tableName,
|
|
256
|
+
operation: auditOp,
|
|
257
|
+
recordId,
|
|
258
|
+
...record !== void 0 && { after: record },
|
|
259
|
+
...metadata !== void 0 && { metadata },
|
|
260
|
+
...actorId !== void 0 && { actorId }
|
|
261
|
+
});
|
|
262
|
+
})
|
|
263
|
+
);
|
|
264
|
+
}
|
|
265
|
+
function buildErrorHandler(onError) {
|
|
266
|
+
return (error) => {
|
|
267
|
+
try {
|
|
268
|
+
if (onError !== void 0) {
|
|
269
|
+
onError(error);
|
|
270
|
+
} else {
|
|
271
|
+
const msg = error instanceof Error ? error.message : String(error);
|
|
272
|
+
console.error(`audit-prisma: capture failed \u2014 ${msg}`);
|
|
273
|
+
}
|
|
274
|
+
} catch {
|
|
275
|
+
}
|
|
276
|
+
};
|
|
277
|
+
}
|
|
278
|
+
function extractId(record) {
|
|
279
|
+
const id = record["id"];
|
|
280
|
+
if (id !== void 0 && id !== null) {
|
|
281
|
+
return String(id);
|
|
282
|
+
}
|
|
283
|
+
return void 0;
|
|
284
|
+
}
|
|
285
|
+
function toRecord(value) {
|
|
286
|
+
if (value !== null && value !== void 0 && typeof value === "object" && !Array.isArray(value)) {
|
|
287
|
+
return value;
|
|
288
|
+
}
|
|
289
|
+
return void 0;
|
|
290
|
+
}
|
|
291
|
+
function getArgsData(args) {
|
|
292
|
+
if (args !== null && typeof args === "object" && "data" in args) {
|
|
293
|
+
const data = args["data"];
|
|
294
|
+
if (Array.isArray(data)) {
|
|
295
|
+
return data;
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
return [];
|
|
299
|
+
}
|
|
300
|
+
export {
|
|
301
|
+
prismaAuditAdapter,
|
|
302
|
+
withAuditExtension
|
|
303
|
+
};
|
|
304
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +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":"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","names":[]}
|
package/package.json
ADDED
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@usebetterdev/audit-prisma",
|
|
3
|
+
"version": "0.4.0-beta.4",
|
|
4
|
+
"repository": "github:usebetter-dev/usebetter",
|
|
5
|
+
"bugs": "https://github.com/usebetter-dev/usebetter/issues",
|
|
6
|
+
"homepage": "https://github.com/usebetter-dev/usebetter#readme",
|
|
7
|
+
"publishConfig": {
|
|
8
|
+
"access": "public",
|
|
9
|
+
"registry": "https://registry.npmjs.org/"
|
|
10
|
+
},
|
|
11
|
+
"license": "MIT",
|
|
12
|
+
"type": "module",
|
|
13
|
+
"main": "./dist/index.cjs",
|
|
14
|
+
"module": "./dist/index.js",
|
|
15
|
+
"types": "./dist/index.d.ts",
|
|
16
|
+
"exports": {
|
|
17
|
+
".": {
|
|
18
|
+
"types": "./dist/index.d.ts",
|
|
19
|
+
"import": "./dist/index.js",
|
|
20
|
+
"require": "./dist/index.cjs"
|
|
21
|
+
}
|
|
22
|
+
},
|
|
23
|
+
"files": [
|
|
24
|
+
"dist",
|
|
25
|
+
"README.md"
|
|
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
|
+
"dependencies": {
|
|
36
|
+
"@usebetterdev/audit-core": "workspace:*"
|
|
37
|
+
},
|
|
38
|
+
"peerDependencies": {
|
|
39
|
+
"@prisma/client": ">=5.0.0"
|
|
40
|
+
},
|
|
41
|
+
"peerDependenciesMeta": {
|
|
42
|
+
"@prisma/client": {
|
|
43
|
+
"optional": true
|
|
44
|
+
}
|
|
45
|
+
},
|
|
46
|
+
"devDependencies": {
|
|
47
|
+
"@prisma/adapter-pg": "^7.0.0",
|
|
48
|
+
"@prisma/client": "^7.0.0",
|
|
49
|
+
"@testcontainers/postgresql": "^11.11.0",
|
|
50
|
+
"@types/node": "^22.10.0",
|
|
51
|
+
"@types/pg": "^8.11.0",
|
|
52
|
+
"@usebetterdev/test-utils": "workspace:*",
|
|
53
|
+
"pg": "^8.13.0",
|
|
54
|
+
"prisma": "^7.0.0",
|
|
55
|
+
"tsup": "^8.3.5",
|
|
56
|
+
"typescript": "~5.7.2",
|
|
57
|
+
"vitest": "^2.1.6"
|
|
58
|
+
},
|
|
59
|
+
"engines": {
|
|
60
|
+
"node": ">=22"
|
|
61
|
+
}
|
|
62
|
+
}
|