@willyim/drizzle-audit 0.4.0 → 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +118 -16
- package/dist/src/context/index.d.ts +42 -0
- package/dist/src/context/index.d.ts.map +1 -0
- package/dist/src/context/index.js +94 -0
- package/dist/src/d1/audit-log-schema.d.ts +3 -2
- package/dist/src/d1/audit-log-schema.d.ts.map +1 -1
- package/dist/src/d1/audit-log-schema.js +14 -4
- package/dist/src/d1/index.d.ts +2 -1
- package/dist/src/d1/index.d.ts.map +1 -1
- package/dist/src/d1/runtime.d.ts +11 -11
- package/dist/src/d1/runtime.d.ts.map +1 -1
- package/dist/src/d1/runtime.js +26 -9
- package/dist/src/d1/sql.d.ts +2 -2
- package/dist/src/d1/sql.d.ts.map +1 -1
- package/dist/src/d1/sql.js +61 -29
- package/dist/src/d1/types.d.ts +10 -2
- package/dist/src/d1/types.d.ts.map +1 -1
- package/dist/src/d1-runtime/with-audit.d.ts +3 -2
- package/dist/src/d1-runtime/with-audit.d.ts.map +1 -1
- package/dist/src/d1-runtime/with-audit.js +12 -7
- package/dist/src/index.d.ts +2 -1
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js +1 -1
- package/dist/src/postgres/audit-log-schema.d.ts +3 -2
- package/dist/src/postgres/audit-log-schema.d.ts.map +1 -1
- package/dist/src/postgres/audit-log-schema.js +14 -4
- package/dist/src/postgres/index.d.ts +3 -2
- package/dist/src/postgres/index.d.ts.map +1 -1
- package/dist/src/postgres/index.js +1 -1
- package/dist/src/postgres/runtime.d.ts +6 -8
- package/dist/src/postgres/runtime.d.ts.map +1 -1
- package/dist/src/postgres/runtime.js +15 -3
- package/dist/src/postgres/sql.d.ts +10 -7
- package/dist/src/postgres/sql.d.ts.map +1 -1
- package/dist/src/postgres/sql.js +72 -50
- package/dist/src/postgres/types.d.ts +10 -2
- package/dist/src/postgres/types.d.ts.map +1 -1
- package/dist/test/context.test.d.ts +2 -0
- package/dist/test/context.test.d.ts.map +1 -0
- package/dist/test/context.test.js +98 -0
- package/dist/test/d1.integration.test.js +71 -4
- package/dist/test/sqlite.integration.test.js +65 -2
- package/package.json +2 -1
package/dist/src/d1/sql.js
CHANGED
|
@@ -13,29 +13,63 @@ function assertNonEmpty(value, label) {
|
|
|
13
13
|
}
|
|
14
14
|
return value;
|
|
15
15
|
}
|
|
16
|
+
/**
|
|
17
|
+
* Normalizes the context columns from the install options, de-duplicating by
|
|
18
|
+
* column name. For D1 the KV key defaults to the column name itself.
|
|
19
|
+
*/
|
|
20
|
+
function normalizeContextColumns(options) {
|
|
21
|
+
const resolved = [];
|
|
22
|
+
const seen = new Set();
|
|
23
|
+
for (const entry of options.contextColumns ?? []) {
|
|
24
|
+
const column = entry.column?.trim();
|
|
25
|
+
if (!column) {
|
|
26
|
+
throw new Error("contextColumns[].column must not be empty");
|
|
27
|
+
}
|
|
28
|
+
if (seen.has(column)) {
|
|
29
|
+
continue;
|
|
30
|
+
}
|
|
31
|
+
seen.add(column);
|
|
32
|
+
resolved.push({
|
|
33
|
+
column,
|
|
34
|
+
sessionKey: entry.sessionKey?.trim() || column,
|
|
35
|
+
index: entry.index ?? true,
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
return resolved;
|
|
39
|
+
}
|
|
40
|
+
/** Builds the column-name suffix for an INSERT column list. */
|
|
41
|
+
function contextColsClause(contextColumns) {
|
|
42
|
+
return contextColumns.map((c) => `, ${quoteIdent(c.column)}`).join("");
|
|
43
|
+
}
|
|
44
|
+
/** Builds the matching VALUES suffix that reads each KV row from the context table. */
|
|
45
|
+
function contextValuesClause(contextColumns, contextTable) {
|
|
46
|
+
return contextColumns
|
|
47
|
+
.map((c) => `,\n (SELECT value FROM ${quoteIdent(contextTable)} WHERE key = ${quoteLiteral(c.sessionKey)})`)
|
|
48
|
+
.join("");
|
|
49
|
+
}
|
|
16
50
|
/**
|
|
17
51
|
* Generates SQL to install the audit_logs table and _audit_context table.
|
|
18
52
|
*
|
|
19
|
-
* The _audit_context table stores user_id (and
|
|
20
|
-
* the current transaction. Since D1/SQLite has no session variables, triggers
|
|
53
|
+
* The _audit_context table stores user_id (and any configured context columns)
|
|
54
|
+
* for the current transaction. Since D1/SQLite has no session variables, triggers
|
|
21
55
|
* read context from this table instead.
|
|
22
56
|
*/
|
|
23
57
|
export function createD1AuditInstallSql(options = {}) {
|
|
24
58
|
const auditTable = assertNonEmpty(options.auditTable ?? DEFAULT_AUDIT_TABLE, "auditTable");
|
|
25
59
|
const contextTable = assertNonEmpty(options.contextTable ?? DEFAULT_CONTEXT_TABLE, "contextTable");
|
|
26
|
-
const
|
|
60
|
+
const contextColumns = normalizeContextColumns(options);
|
|
27
61
|
const auditColumns = [
|
|
28
62
|
"id INTEGER PRIMARY KEY AUTOINCREMENT",
|
|
29
63
|
"table_name TEXT NOT NULL",
|
|
30
64
|
"operation TEXT NOT NULL CHECK (operation IN ('INSERT', 'UPDATE', 'DELETE'))",
|
|
31
65
|
"row_id TEXT",
|
|
32
66
|
"user_id TEXT",
|
|
33
|
-
...(
|
|
67
|
+
...contextColumns.map((c) => `${quoteIdent(c.column)} TEXT`),
|
|
34
68
|
"old_data TEXT",
|
|
35
69
|
"new_data TEXT",
|
|
36
70
|
"created_at TEXT NOT NULL DEFAULT (datetime('now'))",
|
|
37
71
|
];
|
|
38
|
-
const
|
|
72
|
+
const contextTableColumns = [
|
|
39
73
|
"key TEXT PRIMARY KEY",
|
|
40
74
|
"value TEXT",
|
|
41
75
|
];
|
|
@@ -43,16 +77,14 @@ export function createD1AuditInstallSql(options = {}) {
|
|
|
43
77
|
`CREATE INDEX IF NOT EXISTS ${quoteIdent(`${auditTable}_table_name_idx`)} ON ${quoteIdent(auditTable)} (table_name);`,
|
|
44
78
|
`CREATE INDEX IF NOT EXISTS ${quoteIdent(`${auditTable}_row_id_idx`)} ON ${quoteIdent(auditTable)} (row_id);`,
|
|
45
79
|
`CREATE INDEX IF NOT EXISTS ${quoteIdent(`${auditTable}_user_id_idx`)} ON ${quoteIdent(auditTable)} (user_id);`,
|
|
46
|
-
...
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
]
|
|
50
|
-
: []),
|
|
80
|
+
...contextColumns
|
|
81
|
+
.filter((c) => c.index)
|
|
82
|
+
.map((c) => `CREATE INDEX IF NOT EXISTS ${quoteIdent(`${auditTable}_${c.column}_idx`)} ON ${quoteIdent(auditTable)} (${quoteIdent(c.column)});`),
|
|
51
83
|
`CREATE INDEX IF NOT EXISTS ${quoteIdent(`${auditTable}_created_at_idx`)} ON ${quoteIdent(auditTable)} (created_at);`,
|
|
52
84
|
];
|
|
53
85
|
return [
|
|
54
86
|
`CREATE TABLE IF NOT EXISTS ${quoteIdent(auditTable)} (\n ${auditColumns.join(",\n ")}\n);`,
|
|
55
|
-
`CREATE TABLE IF NOT EXISTS ${quoteIdent(contextTable)} (\n ${
|
|
87
|
+
`CREATE TABLE IF NOT EXISTS ${quoteIdent(contextTable)} (\n ${contextTableColumns.join(",\n ")}\n);`,
|
|
56
88
|
...indexStatements,
|
|
57
89
|
].join("\n\n");
|
|
58
90
|
}
|
|
@@ -63,7 +95,7 @@ function buildInsertTriggerSql(target, options) {
|
|
|
63
95
|
const contextTable = assertNonEmpty(options.contextTable ?? DEFAULT_CONTEXT_TABLE, "contextTable");
|
|
64
96
|
const triggerPrefix = target.triggerPrefix ?? table;
|
|
65
97
|
const triggerName = `${triggerPrefix}_audit_insert`;
|
|
66
|
-
const
|
|
98
|
+
const contextColumns = normalizeContextColumns(options);
|
|
67
99
|
return `
|
|
68
100
|
DROP TRIGGER IF EXISTS ${quoteIdent(triggerName)};
|
|
69
101
|
|
|
@@ -71,12 +103,12 @@ CREATE TRIGGER ${quoteIdent(triggerName)}
|
|
|
71
103
|
AFTER INSERT ON ${quoteIdent(table)}
|
|
72
104
|
FOR EACH ROW
|
|
73
105
|
BEGIN
|
|
74
|
-
INSERT INTO ${quoteIdent(auditTable)} (table_name, operation, row_id, user_id${
|
|
106
|
+
INSERT INTO ${quoteIdent(auditTable)} (table_name, operation, row_id, user_id${contextColsClause(contextColumns)})
|
|
75
107
|
VALUES (
|
|
76
108
|
${quoteLiteral(table)},
|
|
77
109
|
'INSERT',
|
|
78
110
|
NEW.${quoteIdent(rowIdColumn)},
|
|
79
|
-
(SELECT value FROM ${quoteIdent(contextTable)} WHERE key = 'user_id')${
|
|
111
|
+
(SELECT value FROM ${quoteIdent(contextTable)} WHERE key = 'user_id')${contextValuesClause(contextColumns, contextTable)}
|
|
80
112
|
);
|
|
81
113
|
END;`.trim();
|
|
82
114
|
}
|
|
@@ -87,7 +119,7 @@ function buildUpdateTriggerSql(target, options) {
|
|
|
87
119
|
const contextTable = assertNonEmpty(options.contextTable ?? DEFAULT_CONTEXT_TABLE, "contextTable");
|
|
88
120
|
const triggerPrefix = target.triggerPrefix ?? table;
|
|
89
121
|
const triggerName = `${triggerPrefix}_audit_update`;
|
|
90
|
-
const
|
|
122
|
+
const contextColumns = normalizeContextColumns(options);
|
|
91
123
|
return `
|
|
92
124
|
DROP TRIGGER IF EXISTS ${quoteIdent(triggerName)};
|
|
93
125
|
|
|
@@ -95,12 +127,12 @@ CREATE TRIGGER ${quoteIdent(triggerName)}
|
|
|
95
127
|
AFTER UPDATE ON ${quoteIdent(table)}
|
|
96
128
|
FOR EACH ROW
|
|
97
129
|
BEGIN
|
|
98
|
-
INSERT INTO ${quoteIdent(auditTable)} (table_name, operation, row_id, user_id${
|
|
130
|
+
INSERT INTO ${quoteIdent(auditTable)} (table_name, operation, row_id, user_id${contextColsClause(contextColumns)})
|
|
99
131
|
VALUES (
|
|
100
132
|
${quoteLiteral(table)},
|
|
101
133
|
'UPDATE',
|
|
102
134
|
NEW.${quoteIdent(rowIdColumn)},
|
|
103
|
-
(SELECT value FROM ${quoteIdent(contextTable)} WHERE key = 'user_id')${
|
|
135
|
+
(SELECT value FROM ${quoteIdent(contextTable)} WHERE key = 'user_id')${contextValuesClause(contextColumns, contextTable)}
|
|
104
136
|
);
|
|
105
137
|
END;`.trim();
|
|
106
138
|
}
|
|
@@ -111,7 +143,7 @@ function buildDeleteTriggerSql(target, options) {
|
|
|
111
143
|
const contextTable = assertNonEmpty(options.contextTable ?? DEFAULT_CONTEXT_TABLE, "contextTable");
|
|
112
144
|
const triggerPrefix = target.triggerPrefix ?? table;
|
|
113
145
|
const triggerName = `${triggerPrefix}_audit_delete`;
|
|
114
|
-
const
|
|
146
|
+
const contextColumns = normalizeContextColumns(options);
|
|
115
147
|
return `
|
|
116
148
|
DROP TRIGGER IF EXISTS ${quoteIdent(triggerName)};
|
|
117
149
|
|
|
@@ -119,12 +151,12 @@ CREATE TRIGGER ${quoteIdent(triggerName)}
|
|
|
119
151
|
AFTER DELETE ON ${quoteIdent(table)}
|
|
120
152
|
FOR EACH ROW
|
|
121
153
|
BEGIN
|
|
122
|
-
INSERT INTO ${quoteIdent(auditTable)} (table_name, operation, row_id, user_id${
|
|
154
|
+
INSERT INTO ${quoteIdent(auditTable)} (table_name, operation, row_id, user_id${contextColsClause(contextColumns)})
|
|
123
155
|
VALUES (
|
|
124
156
|
${quoteLiteral(table)},
|
|
125
157
|
'DELETE',
|
|
126
158
|
OLD.${quoteIdent(rowIdColumn)},
|
|
127
|
-
(SELECT value FROM ${quoteIdent(contextTable)} WHERE key = 'user_id')${
|
|
159
|
+
(SELECT value FROM ${quoteIdent(contextTable)} WHERE key = 'user_id')${contextValuesClause(contextColumns, contextTable)}
|
|
128
160
|
);
|
|
129
161
|
END;`.trim();
|
|
130
162
|
}
|
|
@@ -160,7 +192,7 @@ function buildInsertTriggerWithColumnsSql(target, options) {
|
|
|
160
192
|
const contextTable = assertNonEmpty(options.contextTable ?? DEFAULT_CONTEXT_TABLE, "contextTable");
|
|
161
193
|
const triggerPrefix = target.triggerPrefix ?? table;
|
|
162
194
|
const triggerName = `${triggerPrefix}_audit_insert`;
|
|
163
|
-
const
|
|
195
|
+
const contextColumns = normalizeContextColumns(options);
|
|
164
196
|
return `
|
|
165
197
|
DROP TRIGGER IF EXISTS ${quoteIdent(triggerName)};
|
|
166
198
|
|
|
@@ -168,12 +200,12 @@ CREATE TRIGGER ${quoteIdent(triggerName)}
|
|
|
168
200
|
AFTER INSERT ON ${quoteIdent(table)}
|
|
169
201
|
FOR EACH ROW
|
|
170
202
|
BEGIN
|
|
171
|
-
INSERT INTO ${quoteIdent(auditTable)} (table_name, operation, row_id, user_id${
|
|
203
|
+
INSERT INTO ${quoteIdent(auditTable)} (table_name, operation, row_id, user_id${contextColsClause(contextColumns)}, new_data)
|
|
172
204
|
VALUES (
|
|
173
205
|
${quoteLiteral(table)},
|
|
174
206
|
'INSERT',
|
|
175
207
|
NEW.${quoteIdent(rowIdColumn)},
|
|
176
|
-
(SELECT value FROM ${quoteIdent(contextTable)} WHERE key = 'user_id')${
|
|
208
|
+
(SELECT value FROM ${quoteIdent(contextTable)} WHERE key = 'user_id')${contextValuesClause(contextColumns, contextTable)},
|
|
177
209
|
${buildJsonObjectExpr(target.columns, "NEW")}
|
|
178
210
|
);
|
|
179
211
|
END;`.trim();
|
|
@@ -185,7 +217,7 @@ function buildUpdateTriggerWithColumnsSql(target, options) {
|
|
|
185
217
|
const contextTable = assertNonEmpty(options.contextTable ?? DEFAULT_CONTEXT_TABLE, "contextTable");
|
|
186
218
|
const triggerPrefix = target.triggerPrefix ?? table;
|
|
187
219
|
const triggerName = `${triggerPrefix}_audit_update`;
|
|
188
|
-
const
|
|
220
|
+
const contextColumns = normalizeContextColumns(options);
|
|
189
221
|
return `
|
|
190
222
|
DROP TRIGGER IF EXISTS ${quoteIdent(triggerName)};
|
|
191
223
|
|
|
@@ -193,12 +225,12 @@ CREATE TRIGGER ${quoteIdent(triggerName)}
|
|
|
193
225
|
AFTER UPDATE ON ${quoteIdent(table)}
|
|
194
226
|
FOR EACH ROW
|
|
195
227
|
BEGIN
|
|
196
|
-
INSERT INTO ${quoteIdent(auditTable)} (table_name, operation, row_id, user_id${
|
|
228
|
+
INSERT INTO ${quoteIdent(auditTable)} (table_name, operation, row_id, user_id${contextColsClause(contextColumns)}, old_data, new_data)
|
|
197
229
|
VALUES (
|
|
198
230
|
${quoteLiteral(table)},
|
|
199
231
|
'UPDATE',
|
|
200
232
|
NEW.${quoteIdent(rowIdColumn)},
|
|
201
|
-
(SELECT value FROM ${quoteIdent(contextTable)} WHERE key = 'user_id')${
|
|
233
|
+
(SELECT value FROM ${quoteIdent(contextTable)} WHERE key = 'user_id')${contextValuesClause(contextColumns, contextTable)},
|
|
202
234
|
${buildJsonObjectExpr(target.columns, "OLD")},
|
|
203
235
|
${buildJsonObjectExpr(target.columns, "NEW")}
|
|
204
236
|
);
|
|
@@ -211,7 +243,7 @@ function buildDeleteTriggerWithColumnsSql(target, options) {
|
|
|
211
243
|
const contextTable = assertNonEmpty(options.contextTable ?? DEFAULT_CONTEXT_TABLE, "contextTable");
|
|
212
244
|
const triggerPrefix = target.triggerPrefix ?? table;
|
|
213
245
|
const triggerName = `${triggerPrefix}_audit_delete`;
|
|
214
|
-
const
|
|
246
|
+
const contextColumns = normalizeContextColumns(options);
|
|
215
247
|
return `
|
|
216
248
|
DROP TRIGGER IF EXISTS ${quoteIdent(triggerName)};
|
|
217
249
|
|
|
@@ -219,12 +251,12 @@ CREATE TRIGGER ${quoteIdent(triggerName)}
|
|
|
219
251
|
AFTER DELETE ON ${quoteIdent(table)}
|
|
220
252
|
FOR EACH ROW
|
|
221
253
|
BEGIN
|
|
222
|
-
INSERT INTO ${quoteIdent(auditTable)} (table_name, operation, row_id, user_id${
|
|
254
|
+
INSERT INTO ${quoteIdent(auditTable)} (table_name, operation, row_id, user_id${contextColsClause(contextColumns)}, old_data)
|
|
223
255
|
VALUES (
|
|
224
256
|
${quoteLiteral(table)},
|
|
225
257
|
'DELETE',
|
|
226
258
|
OLD.${quoteIdent(rowIdColumn)},
|
|
227
|
-
(SELECT value FROM ${quoteIdent(contextTable)} WHERE key = 'user_id')${
|
|
259
|
+
(SELECT value FROM ${quoteIdent(contextTable)} WHERE key = 'user_id')${contextValuesClause(contextColumns, contextTable)},
|
|
228
260
|
${buildJsonObjectExpr(target.columns, "OLD")}
|
|
229
261
|
);
|
|
230
262
|
END;`.trim();
|
package/dist/src/d1/types.d.ts
CHANGED
|
@@ -6,13 +6,21 @@ import type { SQL } from "drizzle-orm";
|
|
|
6
6
|
export type D1AuditSqlExecutor = {
|
|
7
7
|
run: (query: SQL) => unknown;
|
|
8
8
|
};
|
|
9
|
+
export type AuditContextColumn = {
|
|
10
|
+
/** Column added to the audit table (TEXT, nullable). */
|
|
11
|
+
column: string;
|
|
12
|
+
/** The `_audit_context` key the trigger reads. Default `${column}`. */
|
|
13
|
+
sessionKey?: string;
|
|
14
|
+
/** Create an index on the column. Default true. */
|
|
15
|
+
index?: boolean;
|
|
16
|
+
};
|
|
9
17
|
export type D1AuditInstallOptions = {
|
|
10
18
|
/** Name of the audit log table (default: "audit_logs") */
|
|
11
19
|
auditTable?: string;
|
|
12
20
|
/** Name of the context table used to pass user_id to triggers (default: "_audit_context") */
|
|
13
21
|
contextTable?: string;
|
|
14
|
-
/**
|
|
15
|
-
|
|
22
|
+
/** Extra context columns added to the audit table and populated by triggers from the _audit_context KV table. */
|
|
23
|
+
contextColumns?: AuditContextColumn[];
|
|
16
24
|
};
|
|
17
25
|
export type D1AuditTriggerTarget = {
|
|
18
26
|
table: string;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../src/d1/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,aAAa,CAAA;AAEtC;;;GAGG;AACH,MAAM,MAAM,kBAAkB,GAAG;IAC/B,GAAG,EAAE,CAAC,KAAK,EAAE,GAAG,KAAK,OAAO,CAAA;CAC7B,CAAA;AAED,MAAM,MAAM,qBAAqB,GAAG;IAClC,0DAA0D;IAC1D,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,6FAA6F;IAC7F,YAAY,CAAC,EAAE,MAAM,CAAA;IACrB,
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../src/d1/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,aAAa,CAAA;AAEtC;;;GAGG;AACH,MAAM,MAAM,kBAAkB,GAAG;IAC/B,GAAG,EAAE,CAAC,KAAK,EAAE,GAAG,KAAK,OAAO,CAAA;CAC7B,CAAA;AAED,MAAM,MAAM,kBAAkB,GAAG;IAC/B,wDAAwD;IACxD,MAAM,EAAE,MAAM,CAAA;IACd,uEAAuE;IACvE,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,mDAAmD;IACnD,KAAK,CAAC,EAAE,OAAO,CAAA;CAChB,CAAA;AAED,MAAM,MAAM,qBAAqB,GAAG;IAClC,0DAA0D;IAC1D,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,6FAA6F;IAC7F,YAAY,CAAC,EAAE,MAAM,CAAA;IACrB,iHAAiH;IACjH,cAAc,CAAC,EAAE,kBAAkB,EAAE,CAAA;CACtC,CAAA;AAED,MAAM,MAAM,oBAAoB,GAAG;IACjC,KAAK,EAAE,MAAM,CAAA;IACb,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,aAAa,CAAC,EAAE,MAAM,CAAA;CACvB,CAAA"}
|
|
@@ -2,7 +2,8 @@ import { type InferInsertModel, type InferSelectModel, type SQL } from "drizzle-
|
|
|
2
2
|
import type { SQLiteTable } from "drizzle-orm/sqlite-core";
|
|
3
3
|
export type AuditContext = {
|
|
4
4
|
userId: string;
|
|
5
|
-
|
|
5
|
+
/** Map of extra audit context column name → value (matching contextColumns). */
|
|
6
|
+
context?: Record<string, string>;
|
|
6
7
|
};
|
|
7
8
|
export type AuditLogInsertShape = {
|
|
8
9
|
table_name: string;
|
|
@@ -55,7 +56,7 @@ export type AuditedDb<TDb extends DrizzleSQLiteDb> = {
|
|
|
55
56
|
*
|
|
56
57
|
* @param db - A Drizzle SQLite database instance (D1, better-sqlite3, libsql)
|
|
57
58
|
* @param auditTable - The Drizzle table definition for audit_logs
|
|
58
|
-
* @param context - The audit context (userId, optional
|
|
59
|
+
* @param context - The audit context (userId, optional context columns)
|
|
59
60
|
*
|
|
60
61
|
* @example
|
|
61
62
|
* import { withAudit } from "drizzle-audit/d1-runtime"
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"with-audit.d.ts","sourceRoot":"","sources":["../../../src/d1-runtime/with-audit.ts"],"names":[],"mappings":"AAAA,OAAO,EAGL,KAAK,gBAAgB,EACrB,KAAK,gBAAgB,EACrB,KAAK,GAAG,EACT,MAAM,aAAa,CAAA;AACpB,OAAO,KAAK,EAAgB,WAAW,EAAE,MAAM,yBAAyB,CAAA;AAIxE,MAAM,MAAM,YAAY,GAAG;IACzB,MAAM,EAAE,MAAM,CAAA;IACd,
|
|
1
|
+
{"version":3,"file":"with-audit.d.ts","sourceRoot":"","sources":["../../../src/d1-runtime/with-audit.ts"],"names":[],"mappings":"AAAA,OAAO,EAGL,KAAK,gBAAgB,EACrB,KAAK,gBAAgB,EACrB,KAAK,GAAG,EACT,MAAM,aAAa,CAAA;AACpB,OAAO,KAAK,EAAgB,WAAW,EAAE,MAAM,yBAAyB,CAAA;AAIxE,MAAM,MAAM,YAAY,GAAG;IACzB,MAAM,EAAE,MAAM,CAAA;IACd,gFAAgF;IAChF,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;CACjC,CAAA;AAED,MAAM,MAAM,mBAAmB,GAAG;IAChC,UAAU,EAAE,MAAM,CAAA;IAClB,SAAS,EAAE,MAAM,CAAA;IACjB,MAAM,EAAE,MAAM,GAAG,IAAI,CAAA;IACrB,OAAO,EAAE,MAAM,CAAA;IACf,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAA;IACvB,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAA;IACvB,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAA;CACvB,CAAA;AAED;;;GAGG;AACH,MAAM,MAAM,eAAe,GAAG;IAC5B,MAAM,EAAE,CAAC,KAAK,EAAE,GAAG,KAAK,GAAG,CAAA;IAC3B,MAAM,EAAE,CAAC,KAAK,EAAE,GAAG,KAAK,GAAG,CAAA;IAC3B,MAAM,EAAE,CAAC,KAAK,EAAE,GAAG,KAAK,GAAG,CAAA;IAC3B,MAAM,EAAE,CAAC,MAAM,CAAC,EAAE,GAAG,KAAK,GAAG,CAAA;CAC9B,CAAA;AAgBD,MAAM,MAAM,SAAS,CAAC,GAAG,SAAS,eAAe,IAAI;IACnD;;;OAGG;IACH,MAAM,EAAE,CAAC,CAAC,SAAS,WAAW,EAC5B,KAAK,EAAE,CAAC,EACR,IAAI,EAAE,gBAAgB,CAAC,CAAC,CAAC,KACtB,OAAO,CAAC,gBAAgB,CAAC,CAAC,CAAC,CAAC,CAAA;IAEjC;;;;OAIG;IACH,MAAM,EAAE,CAAC,CAAC,SAAS,WAAW,EAC5B,KAAK,EAAE,CAAC,EACR,KAAK,EAAE,GAAG,EACV,IAAI,EAAE,OAAO,CAAC,gBAAgB,CAAC,CAAC,CAAC,CAAC,KAC/B,OAAO,CAAC,gBAAgB,CAAC,CAAC,CAAC,EAAE,CAAC,CAAA;IAEnC;;;;OAIG;IACH,MAAM,EAAE,CAAC,CAAC,SAAS,WAAW,EAC5B,KAAK,EAAE,CAAC,EACR,KAAK,EAAE,GAAG,KACP,OAAO,CAAC,gBAAgB,CAAC,CAAC,CAAC,EAAE,CAAC,CAAA;IAEnC,2DAA2D;IAC3D,EAAE,EAAE,GAAG,CAAA;CACR,CAAA;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+BG;AACH,wBAAgB,SAAS,CAAC,GAAG,SAAS,eAAe,EACnD,EAAE,EAAE,GAAG,EACP,UAAU,EAAE,WAAW,EACvB,OAAO,EAAE,YAAY,GACpB,SAAS,CAAC,GAAG,CAAC,CAqGhB"}
|
|
@@ -21,7 +21,7 @@ function getRowId(row, pk) {
|
|
|
21
21
|
*
|
|
22
22
|
* @param db - A Drizzle SQLite database instance (D1, better-sqlite3, libsql)
|
|
23
23
|
* @param auditTable - The Drizzle table definition for audit_logs
|
|
24
|
-
* @param context - The audit context (userId, optional
|
|
24
|
+
* @param context - The audit context (userId, optional context columns)
|
|
25
25
|
*
|
|
26
26
|
* @example
|
|
27
27
|
* import { withAudit } from "drizzle-audit/d1-runtime"
|
|
@@ -43,7 +43,7 @@ function getRowId(row, pk) {
|
|
|
43
43
|
* audited.db.select().from(users).all()
|
|
44
44
|
*/
|
|
45
45
|
export function withAudit(db, auditTable, context) {
|
|
46
|
-
const
|
|
46
|
+
const extraColumns = (() => {
|
|
47
47
|
const cols = getTableColumns(auditTable);
|
|
48
48
|
const known = new Set([
|
|
49
49
|
"id",
|
|
@@ -55,10 +55,17 @@ export function withAudit(db, auditTable, context) {
|
|
|
55
55
|
"new_data",
|
|
56
56
|
"created_at",
|
|
57
57
|
]);
|
|
58
|
-
|
|
59
|
-
return extra ?? null;
|
|
58
|
+
return Object.keys(cols).filter((k) => !known.has(k));
|
|
60
59
|
})();
|
|
60
|
+
const contextValues = { ...(context.context ?? {}) };
|
|
61
61
|
function buildAuditRow(tableName, operation, rowId, oldData, newData) {
|
|
62
|
+
const extra = {};
|
|
63
|
+
for (const column of extraColumns) {
|
|
64
|
+
const value = contextValues[column];
|
|
65
|
+
if (value !== undefined && value !== "") {
|
|
66
|
+
extra[column] = value;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
62
69
|
return {
|
|
63
70
|
table_name: tableName,
|
|
64
71
|
operation,
|
|
@@ -66,9 +73,7 @@ export function withAudit(db, auditTable, context) {
|
|
|
66
73
|
user_id: context.userId,
|
|
67
74
|
old_data: oldData ? JSON.stringify(oldData) : null,
|
|
68
75
|
new_data: newData ? JSON.stringify(newData) : null,
|
|
69
|
-
...
|
|
70
|
-
? { [workspaceIdColumn]: context.workspaceId }
|
|
71
|
-
: {}),
|
|
76
|
+
...extra,
|
|
72
77
|
};
|
|
73
78
|
}
|
|
74
79
|
return {
|
package/dist/src/index.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
export * from "./postgres/index.js";
|
|
2
|
-
export
|
|
2
|
+
export { clearD1AuditContext, createAttachD1AuditTriggerSql, createAttachD1AuditTriggersSql, createAttachD1AuditTriggerSqlWithColumns, createAttachD1AuditTriggersSqlWithColumns, createD1AuditInstallSql, d1AuditContextTable, d1AuditLogTable, setD1AuditContext, withD1AuditedTransaction, } from "./d1/index.js";
|
|
3
|
+
export type { D1AuditContextOptions, D1AuditInstallOptions, D1AuditLogTableOptions, D1AuditSqlExecutor, D1AuditTriggerTarget, D1AuditTriggerTargetWithColumns, } from "./d1/index.js";
|
|
3
4
|
export * from "./d1-runtime/index.js";
|
|
4
5
|
export * from "./compute-diff.js";
|
|
5
6
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/src/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,qBAAqB,CAAA;AACnC,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,qBAAqB,CAAA;AACnC,OAAO,EACL,mBAAmB,EACnB,6BAA6B,EAC7B,8BAA8B,EAC9B,wCAAwC,EACxC,yCAAyC,EACzC,uBAAuB,EACvB,mBAAmB,EACnB,eAAe,EACf,iBAAiB,EACjB,wBAAwB,GACzB,MAAM,eAAe,CAAA;AACtB,YAAY,EACV,qBAAqB,EACrB,qBAAqB,EACrB,sBAAsB,EACtB,kBAAkB,EAClB,oBAAoB,EACpB,+BAA+B,GAChC,MAAM,eAAe,CAAA;AACtB,cAAc,uBAAuB,CAAA;AACrC,cAAc,mBAAmB,CAAA"}
|
package/dist/src/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
export * from "./postgres/index.js";
|
|
2
|
-
export
|
|
2
|
+
export { clearD1AuditContext, createAttachD1AuditTriggerSql, createAttachD1AuditTriggersSql, createAttachD1AuditTriggerSqlWithColumns, createAttachD1AuditTriggersSqlWithColumns, createD1AuditInstallSql, d1AuditContextTable, d1AuditLogTable, setD1AuditContext, withD1AuditedTransaction, } from "./d1/index.js";
|
|
3
3
|
export * from "./d1-runtime/index.js";
|
|
4
4
|
export * from "./compute-diff.js";
|
|
@@ -1,6 +1,7 @@
|
|
|
1
|
+
import type { AuditContextColumn } from "./types.js";
|
|
1
2
|
export type PgAuditLogTableOptions = {
|
|
2
|
-
/**
|
|
3
|
-
|
|
3
|
+
/** Extra context columns to include in the table definition, matching the install. */
|
|
4
|
+
contextColumns?: AuditContextColumn[];
|
|
4
5
|
};
|
|
5
6
|
export declare function pgAuditLogTable(options?: PgAuditLogTableOptions): import("drizzle-orm/pg-core").PgTableWithColumns<{
|
|
6
7
|
name: "audit_logs";
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"audit-log-schema.d.ts","sourceRoot":"","sources":["../../../src/postgres/audit-log-schema.ts"],"names":[],"mappings":"AASA,MAAM,MAAM,sBAAsB,GAAG;IACnC
|
|
1
|
+
{"version":3,"file":"audit-log-schema.d.ts","sourceRoot":"","sources":["../../../src/postgres/audit-log-schema.ts"],"names":[],"mappings":"AASA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,YAAY,CAAA;AAEpD,MAAM,MAAM,sBAAsB,GAAG;IACnC,sFAAsF;IACtF,cAAc,CAAC,EAAE,kBAAkB,EAAE,CAAA;CACtC,CAAA;AAiBD,wBAAgB,eAAe,CAAC,OAAO,CAAC,EAAE,sBAAsB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAyB/D"}
|
|
@@ -1,15 +1,25 @@
|
|
|
1
1
|
import { bigserial, index, jsonb, pgTable, text, timestamp, } from "drizzle-orm/pg-core";
|
|
2
|
+
function resolveColumns(options) {
|
|
3
|
+
const columns = [];
|
|
4
|
+
const seen = new Set();
|
|
5
|
+
for (const entry of options?.contextColumns ?? []) {
|
|
6
|
+
const column = entry.column?.trim();
|
|
7
|
+
if (column && !seen.has(column)) {
|
|
8
|
+
seen.add(column);
|
|
9
|
+
columns.push(column);
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
return columns;
|
|
13
|
+
}
|
|
2
14
|
export function pgAuditLogTable(options) {
|
|
3
|
-
const
|
|
15
|
+
const contextColumns = resolveColumns(options);
|
|
4
16
|
const columns = {
|
|
5
17
|
id: bigserial("id", { mode: "number" }).primaryKey(),
|
|
6
18
|
table_name: text("table_name").notNull(),
|
|
7
19
|
operation: text("operation").notNull(),
|
|
8
20
|
row_id: text("row_id"),
|
|
9
21
|
user_id: text("user_id"),
|
|
10
|
-
...(
|
|
11
|
-
? { [workspaceIdColumn]: text(workspaceIdColumn) }
|
|
12
|
-
: {}),
|
|
22
|
+
...Object.fromEntries(contextColumns.map((c) => [c, text(c)])),
|
|
13
23
|
old_data: jsonb("old_data"),
|
|
14
24
|
new_data: jsonb("new_data"),
|
|
15
25
|
created_at: timestamp("created_at", { withTimezone: true })
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
export { pgAuditLogTable } from "./audit-log-schema.js";
|
|
2
2
|
export type { PgAuditLogTableOptions } from "./audit-log-schema.js";
|
|
3
|
-
export { createAttachAuditTriggerSql, createAttachAuditTriggersSql,
|
|
3
|
+
export { createAttachAuditTriggerSql, createAttachAuditTriggersSql, createAuditAddContextColumnsSql, createAuditInstallSql, } from "./sql.js";
|
|
4
4
|
export { setAuditContext, withAuditedTransaction } from "./runtime.js";
|
|
5
|
-
export type {
|
|
5
|
+
export type { AuditContextOptions } from "./runtime.js";
|
|
6
|
+
export type { AuditContextColumn, AuditInstallOptions, AuditSqlExecutor, AuditTransactionCapable, AuditTriggerTarget, } from "./types.js";
|
|
6
7
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/postgres/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAA;AACvD,YAAY,EAAE,sBAAsB,EAAE,MAAM,uBAAuB,CAAA;AACnE,OAAO,EACL,2BAA2B,EAC3B,4BAA4B,EAC5B,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/postgres/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAA;AACvD,YAAY,EAAE,sBAAsB,EAAE,MAAM,uBAAuB,CAAA;AACnE,OAAO,EACL,2BAA2B,EAC3B,4BAA4B,EAC5B,+BAA+B,EAC/B,qBAAqB,GACtB,MAAM,UAAU,CAAA;AACjB,OAAO,EAAE,eAAe,EAAE,sBAAsB,EAAE,MAAM,cAAc,CAAA;AACtE,YAAY,EAAE,mBAAmB,EAAE,MAAM,cAAc,CAAA;AAEvD,YAAY,EACV,kBAAkB,EAClB,mBAAmB,EACnB,gBAAgB,EAChB,uBAAuB,EACvB,kBAAkB,GACnB,MAAM,YAAY,CAAA"}
|
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
export { pgAuditLogTable } from "./audit-log-schema.js";
|
|
2
|
-
export { createAttachAuditTriggerSql, createAttachAuditTriggersSql,
|
|
2
|
+
export { createAttachAuditTriggerSql, createAttachAuditTriggersSql, createAuditAddContextColumnsSql, createAuditInstallSql, } from "./sql.js";
|
|
3
3
|
export { setAuditContext, withAuditedTransaction } from "./runtime.js";
|
|
@@ -1,10 +1,8 @@
|
|
|
1
1
|
import type { AuditSqlExecutor, AuditTransactionCapable } from "./types.js";
|
|
2
|
-
export
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
}
|
|
6
|
-
export declare function
|
|
7
|
-
|
|
8
|
-
workspaceContextKey?: string;
|
|
9
|
-
}): Promise<TResult>;
|
|
2
|
+
export type AuditContextOptions = {
|
|
3
|
+
/** Map of session GUC name → value to set for the transaction. */
|
|
4
|
+
context?: Record<string, string>;
|
|
5
|
+
};
|
|
6
|
+
export declare function setAuditContext(db: AuditSqlExecutor, actorId: string, contextKey?: string, options?: AuditContextOptions): Promise<void>;
|
|
7
|
+
export declare function withAuditedTransaction<TTransaction extends AuditSqlExecutor, TResult>(db: AuditTransactionCapable<TTransaction>, actorId: string, callback: (tx: TTransaction) => Promise<TResult> | TResult, contextKey?: string, options?: AuditContextOptions): Promise<TResult>;
|
|
10
8
|
//# sourceMappingURL=runtime.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"runtime.d.ts","sourceRoot":"","sources":["../../../src/postgres/runtime.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EACV,gBAAgB,EAChB,uBAAuB,EACxB,MAAM,YAAY,CAAA;
|
|
1
|
+
{"version":3,"file":"runtime.d.ts","sourceRoot":"","sources":["../../../src/postgres/runtime.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EACV,gBAAgB,EAChB,uBAAuB,EACxB,MAAM,YAAY,CAAA;AAwBnB,MAAM,MAAM,mBAAmB,GAAG;IAChC,kEAAkE;IAClE,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;CACjC,CAAA;AAED,wBAAsB,eAAe,CACnC,EAAE,EAAE,gBAAgB,EACpB,OAAO,EAAE,MAAM,EACf,UAAU,SAAgB,EAC1B,OAAO,CAAC,EAAE,mBAAmB,iBAa9B;AAED,wBAAsB,sBAAsB,CAC1C,YAAY,SAAS,gBAAgB,EACrC,OAAO,EAEP,EAAE,EAAE,uBAAuB,CAAC,YAAY,CAAC,EACzC,OAAO,EAAE,MAAM,EACf,QAAQ,EAAE,CAAC,EAAE,EAAE,YAAY,KAAK,OAAO,CAAC,OAAO,CAAC,GAAG,OAAO,EAC1D,UAAU,SAAgB,EAC1B,OAAO,CAAC,EAAE,mBAAmB,oBAQ9B"}
|
|
@@ -4,12 +4,24 @@ function assertActorId(actorId) {
|
|
|
4
4
|
throw new Error("actorId must not be empty");
|
|
5
5
|
}
|
|
6
6
|
}
|
|
7
|
+
/**
|
|
8
|
+
* Builds the `context` GUC map from a set of options. Empty/undefined values
|
|
9
|
+
* are dropped so the trigger's NULLIF yields NULL.
|
|
10
|
+
*/
|
|
11
|
+
function resolveContext(options) {
|
|
12
|
+
const context = {};
|
|
13
|
+
for (const [key, value] of Object.entries(options?.context ?? {})) {
|
|
14
|
+
if (value !== undefined && value !== "") {
|
|
15
|
+
context[key] = value;
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
return context;
|
|
19
|
+
}
|
|
7
20
|
export async function setAuditContext(db, actorId, contextKey = "app.user_id", options) {
|
|
8
21
|
assertActorId(actorId);
|
|
9
22
|
await db.execute(sql `select set_config(${contextKey}, ${actorId}, true) as audit_context`);
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
await db.execute(sql `select set_config(${wsKey}, ${options.workspaceId}, true) as workspace_context`);
|
|
23
|
+
for (const [key, value] of Object.entries(resolveContext(options))) {
|
|
24
|
+
await db.execute(sql `select set_config(${key}, ${value}, true) as audit_context`);
|
|
13
25
|
}
|
|
14
26
|
}
|
|
15
27
|
export async function withAuditedTransaction(db, actorId, callback, contextKey = "app.user_id", options) {
|
|
@@ -3,12 +3,15 @@ export declare function createAuditInstallSql(options?: AuditInstallOptions): st
|
|
|
3
3
|
export declare function createAttachAuditTriggerSql(target: AuditTriggerTarget, options?: AuditInstallOptions): string;
|
|
4
4
|
export declare function createAttachAuditTriggersSql(targets: AuditTriggerTarget[], options?: AuditInstallOptions): string;
|
|
5
5
|
/**
|
|
6
|
-
* Generates SQL to add
|
|
7
|
-
* existing audit_logs table. Use in a new migration when adding
|
|
8
|
-
* the initial install.
|
|
9
|
-
*
|
|
6
|
+
* Generates SQL to add context columns and regenerate the trigger function on an
|
|
7
|
+
* existing audit_logs table. Use in a new migration when adding context columns
|
|
8
|
+
* after the initial install.
|
|
9
|
+
*
|
|
10
|
+
* Pass the FULL set of context columns the audit table should have (the trigger is
|
|
11
|
+
* a single CREATE OR REPLACE, so it must reference every column). Columns are added
|
|
12
|
+
* with `ADD COLUMN IF NOT EXISTS`, so already-present columns are left untouched.
|
|
13
|
+
* Options must match your install (auditSchema, auditTable, triggerFunctionName,
|
|
14
|
+
* contextKey).
|
|
10
15
|
*/
|
|
11
|
-
export declare function
|
|
12
|
-
workspaceIdColumn: string;
|
|
13
|
-
}): string;
|
|
16
|
+
export declare function createAuditAddContextColumnsSql(options?: AuditInstallOptions): string;
|
|
14
17
|
//# sourceMappingURL=sql.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"sql.d.ts","sourceRoot":"","sources":["../../../src/postgres/sql.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,
|
|
1
|
+
{"version":3,"file":"sql.d.ts","sourceRoot":"","sources":["../../../src/postgres/sql.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAEV,mBAAmB,EACnB,kBAAkB,EACnB,MAAM,YAAY,CAAA;AAkJnB,wBAAgB,qBAAqB,CAAC,OAAO,GAAE,mBAAwB,UA8DtE;AAED,wBAAgB,2BAA2B,CACzC,MAAM,EAAE,kBAAkB,EAC1B,OAAO,GAAE,mBAAwB,UAiClC;AAED,wBAAgB,4BAA4B,CAC1C,OAAO,EAAE,kBAAkB,EAAE,EAC7B,OAAO,GAAE,mBAAwB,UASlC;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,+BAA+B,CAAC,OAAO,GAAE,mBAAwB,UA2ChF"}
|