@willyim/drizzle-audit 0.3.0 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (47) hide show
  1. package/README.md +102 -21
  2. package/dist/src/d1/audit-log-schema.d.ts +3 -2
  3. package/dist/src/d1/audit-log-schema.d.ts.map +1 -1
  4. package/dist/src/d1/audit-log-schema.js +14 -4
  5. package/dist/src/d1/index.d.ts +2 -1
  6. package/dist/src/d1/index.d.ts.map +1 -1
  7. package/dist/src/d1/runtime.d.ts +11 -11
  8. package/dist/src/d1/runtime.d.ts.map +1 -1
  9. package/dist/src/d1/runtime.js +26 -9
  10. package/dist/src/d1/sql.d.ts +2 -2
  11. package/dist/src/d1/sql.d.ts.map +1 -1
  12. package/dist/src/d1/sql.js +61 -29
  13. package/dist/src/d1/types.d.ts +10 -2
  14. package/dist/src/d1/types.d.ts.map +1 -1
  15. package/dist/src/d1-runtime/with-audit.d.ts +15 -11
  16. package/dist/src/d1-runtime/with-audit.d.ts.map +1 -1
  17. package/dist/src/d1-runtime/with-audit.js +53 -60
  18. package/dist/src/index.d.ts +2 -1
  19. package/dist/src/index.d.ts.map +1 -1
  20. package/dist/src/index.js +1 -1
  21. package/dist/src/postgres/audit-log-schema.d.ts +7 -14
  22. package/dist/src/postgres/audit-log-schema.d.ts.map +1 -1
  23. package/dist/src/postgres/audit-log-schema.js +14 -4
  24. package/dist/src/postgres/index.d.ts +3 -2
  25. package/dist/src/postgres/index.d.ts.map +1 -1
  26. package/dist/src/postgres/index.js +1 -1
  27. package/dist/src/postgres/runtime.d.ts +6 -8
  28. package/dist/src/postgres/runtime.d.ts.map +1 -1
  29. package/dist/src/postgres/runtime.js +15 -3
  30. package/dist/src/postgres/sql.d.ts +10 -7
  31. package/dist/src/postgres/sql.d.ts.map +1 -1
  32. package/dist/src/postgres/sql.js +72 -50
  33. package/dist/src/postgres/types.d.ts +10 -2
  34. package/dist/src/postgres/types.d.ts.map +1 -1
  35. package/dist/test/d1-async.integration.test.d.ts +13 -0
  36. package/dist/test/d1-async.integration.test.d.ts.map +1 -0
  37. package/dist/test/d1-async.integration.test.js +159 -0
  38. package/dist/test/d1.integration.test.js +71 -4
  39. package/dist/test/sqlite.integration.test.d.ts +2 -0
  40. package/dist/test/sqlite.integration.test.d.ts.map +1 -0
  41. package/dist/test/{d1-runtime.integration.test.js → sqlite.integration.test.js} +82 -25
  42. package/package.json +2 -1
  43. package/dist/test/d1-runtime.integration.test.d.ts +0 -2
  44. package/dist/test/d1-runtime.integration.test.d.ts.map +0 -1
  45. package/dist/test/postgres.integration.test.d.ts +0 -2
  46. package/dist/test/postgres.integration.test.d.ts.map +0 -1
  47. package/dist/test/postgres.integration.test.js +0 -286
@@ -3,7 +3,7 @@ import test from "node:test";
3
3
  import Database from "better-sqlite3";
4
4
  import { asc, eq, isNull } from "drizzle-orm";
5
5
  import { drizzle } from "drizzle-orm/better-sqlite3";
6
- import { sqliteTable, text, integer } from "drizzle-orm/sqlite-core";
6
+ import { integer, sqliteTable, text } from "drizzle-orm/sqlite-core";
7
7
  import { d1AuditLogTable } from "../src/d1/index.js";
8
8
  import { withAudit } from "../src/d1-runtime/index.js";
9
9
  const users = sqliteTable("users", {
@@ -44,11 +44,11 @@ function setupDb() {
44
44
  `);
45
45
  return { db, sqlite };
46
46
  }
47
- test("withAudit insert logs audit row with new_data", () => {
47
+ test("withAudit insert logs audit row with new_data", async () => {
48
48
  const { db, sqlite } = setupDb();
49
49
  try {
50
50
  const audited = withAudit(db, auditLogs, { userId: "user_1" });
51
- const row = audited.insert(users, { id: "u1", name: "Ada", email: "ada@example.com" });
51
+ const row = await audited.insert(users, { id: "u1", name: "Ada", email: "ada@example.com" });
52
52
  assert.equal(row.id, "u1");
53
53
  assert.equal(row.name, "Ada");
54
54
  const logs = db.select().from(auditLogs).all();
@@ -68,13 +68,12 @@ test("withAudit insert logs audit row with new_data", () => {
68
68
  sqlite.close();
69
69
  }
70
70
  });
71
- test("withAudit update captures old and new data", () => {
71
+ test("withAudit update captures old and new data", async () => {
72
72
  const { db, sqlite } = setupDb();
73
73
  try {
74
- // Seed a row
75
74
  db.insert(users).values({ id: "u1", name: "Ada", email: "ada@example.com" }).run();
76
75
  const audited = withAudit(db, auditLogs, { userId: "user_2" });
77
- const rows = audited.update(users, eq(users.id, "u1"), { name: "Ada Lovelace" });
76
+ const rows = await audited.update(users, eq(users.id, "u1"), { name: "Ada Lovelace" });
78
77
  assert.equal(rows.length, 1);
79
78
  assert.equal(rows[0]?.name, "Ada Lovelace");
80
79
  const logs = db.select().from(auditLogs).all();
@@ -93,18 +92,16 @@ test("withAudit update captures old and new data", () => {
93
92
  sqlite.close();
94
93
  }
95
94
  });
96
- test("withAudit delete captures old data", () => {
95
+ test("withAudit delete captures old data", async () => {
97
96
  const { db, sqlite } = setupDb();
98
97
  try {
99
98
  db.insert(users).values({ id: "u1", name: "Ada" }).run();
100
99
  const audited = withAudit(db, auditLogs, { userId: "user_3" });
101
- const deleted = audited.delete(users, eq(users.id, "u1"));
100
+ const deleted = await audited.delete(users, eq(users.id, "u1"));
102
101
  assert.equal(deleted.length, 1);
103
102
  assert.equal(deleted[0]?.id, "u1");
104
- // Verify row is gone
105
103
  const remaining = db.select().from(users).all();
106
104
  assert.equal(remaining.length, 0);
107
- // Verify audit log
108
105
  const logs = db.select().from(auditLogs).all();
109
106
  assert.equal(logs.length, 1);
110
107
  assert.equal(logs[0]?.operation, "DELETE");
@@ -121,13 +118,13 @@ test("withAudit delete captures old data", () => {
121
118
  sqlite.close();
122
119
  }
123
120
  });
124
- test("withAudit works with custom primary key column", () => {
121
+ test("withAudit works with custom primary key column", async () => {
125
122
  const { db, sqlite } = setupDb();
126
123
  try {
127
124
  const audited = withAudit(db, auditLogs, { userId: "user_1" });
128
- audited.insert(invoices, { invoice_id: "inv_1", amount: 100 });
129
- audited.update(invoices, eq(invoices.invoice_id, "inv_1"), { amount: 200 });
130
- audited.delete(invoices, eq(invoices.invoice_id, "inv_1"));
125
+ await audited.insert(invoices, { invoice_id: "inv_1", amount: 100 });
126
+ await audited.update(invoices, eq(invoices.invoice_id, "inv_1"), { amount: 200 });
127
+ await audited.delete(invoices, eq(invoices.invoice_id, "inv_1"));
131
128
  const logs = db.select().from(auditLogs).orderBy(asc(auditLogs.id)).all();
132
129
  assert.equal(logs.length, 3);
133
130
  assert.equal(logs[0]?.table_name, "invoices");
@@ -143,7 +140,7 @@ test("withAudit works with custom primary key column", () => {
143
140
  sqlite.close();
144
141
  }
145
142
  });
146
- test("withAudit handles multi-row update", () => {
143
+ test("withAudit handles multi-row update", async () => {
147
144
  const { db, sqlite } = setupDb();
148
145
  try {
149
146
  db.insert(users).values([
@@ -152,25 +149,25 @@ test("withAudit handles multi-row update", () => {
152
149
  { id: "u3", name: "Carol" },
153
150
  ]).run();
154
151
  const audited = withAudit(db, auditLogs, { userId: "admin" });
155
- // Update all users (no where = all rows, but let's use a broader condition)
156
- const rows = audited.update(users, isNull(users.email), { email: "bulk@example.com" });
152
+ const rows = await audited.update(users, isNull(users.email), { email: "bulk@example.com" });
157
153
  assert.equal(rows.length, 3);
158
154
  const logs = db.select().from(auditLogs).orderBy(asc(auditLogs.id)).all();
159
155
  assert.equal(logs.length, 3);
160
156
  for (const log of logs) {
161
157
  assert.equal(log.operation, "UPDATE");
162
158
  assert.equal(log.user_id, "admin");
163
- const newData = JSON.parse(log.new_data);
164
- assert.equal(newData.email, "bulk@example.com");
159
+ assert.equal(JSON.parse(log.new_data).email, "bulk@example.com");
165
160
  }
166
161
  }
167
162
  finally {
168
163
  sqlite.close();
169
164
  }
170
165
  });
171
- test("withAudit with workspace_id", () => {
166
+ test("withAudit with workspace_id", async () => {
172
167
  const sqlite = new Database(":memory:");
173
- const auditLogsWithWs = d1AuditLogTable({ workspaceIdColumn: "workspace_id" });
168
+ const auditLogsWithWs = d1AuditLogTable({
169
+ contextColumns: [{ column: "workspace_id" }],
170
+ });
174
171
  const db = drizzle({ client: sqlite, schema: { auditLogs: auditLogsWithWs, users } });
175
172
  try {
176
173
  sqlite.exec(`
@@ -193,9 +190,9 @@ test("withAudit with workspace_id", () => {
193
190
  `);
194
191
  const audited = withAudit(db, auditLogsWithWs, {
195
192
  userId: "user_1",
196
- workspaceId: "ws_1",
193
+ context: { workspace_id: "ws_1" },
197
194
  });
198
- audited.insert(users, { id: "u1", name: "Ada" });
195
+ await audited.insert(users, { id: "u1", name: "Ada" });
199
196
  const logs = db.select().from(auditLogsWithWs).all();
200
197
  assert.equal(logs.length, 1);
201
198
  assert.equal(logs[0]?.user_id, "user_1");
@@ -205,11 +202,71 @@ test("withAudit with workspace_id", () => {
205
202
  sqlite.close();
206
203
  }
207
204
  });
208
- test("withAudit.db gives access to raw db for non-audited ops", () => {
205
+ test("withAudit with generic context columns", async () => {
206
+ const sqlite = new Database(":memory:");
207
+ const auditLogsWithCtx = d1AuditLogTable({
208
+ contextColumns: [
209
+ { column: "workspace_id" },
210
+ { column: "tenant_id" },
211
+ { column: "request_id" },
212
+ ],
213
+ });
214
+ const db = drizzle({ client: sqlite, schema: { auditLogs: auditLogsWithCtx, users } });
215
+ try {
216
+ sqlite.exec(`
217
+ CREATE TABLE audit_logs (
218
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
219
+ table_name TEXT NOT NULL,
220
+ operation TEXT NOT NULL,
221
+ row_id TEXT,
222
+ user_id TEXT,
223
+ workspace_id TEXT,
224
+ tenant_id TEXT,
225
+ request_id TEXT,
226
+ old_data TEXT,
227
+ new_data TEXT,
228
+ created_at TEXT NOT NULL DEFAULT (datetime('now'))
229
+ );
230
+ CREATE TABLE users (
231
+ id TEXT PRIMARY KEY,
232
+ name TEXT NOT NULL,
233
+ email TEXT
234
+ );
235
+ `);
236
+ const audited = withAudit(db, auditLogsWithCtx, {
237
+ userId: "user_1",
238
+ context: {
239
+ workspace_id: "ws_1",
240
+ tenant_id: "tenant_1",
241
+ request_id: "req_1",
242
+ },
243
+ });
244
+ await audited.insert(users, { id: "u1", name: "Ada" });
245
+ const logs = db.select().from(auditLogsWithCtx).all();
246
+ assert.equal(logs.length, 1);
247
+ const row = logs[0];
248
+ assert.equal(row.user_id, "user_1");
249
+ assert.equal(row.workspace_id, "ws_1");
250
+ assert.equal(row.tenant_id, "tenant_1");
251
+ assert.equal(row.request_id, "req_1");
252
+ // No context: extra columns stay NULL.
253
+ const auditedNoCtx = withAudit(db, auditLogsWithCtx, { userId: "user_2" });
254
+ await auditedNoCtx.insert(users, { id: "u2", name: "Bob" });
255
+ const all = db.select().from(auditLogsWithCtx).orderBy(asc(auditLogsWithCtx.id)).all();
256
+ const last = all[all.length - 1];
257
+ assert.equal(last.user_id, "user_2");
258
+ assert.equal(last.workspace_id, null);
259
+ assert.equal(last.tenant_id, null);
260
+ assert.equal(last.request_id, null);
261
+ }
262
+ finally {
263
+ sqlite.close();
264
+ }
265
+ });
266
+ test("withAudit.db gives access to raw db for non-audited ops", async () => {
209
267
  const { db, sqlite } = setupDb();
210
268
  try {
211
269
  const audited = withAudit(db, auditLogs, { userId: "user_1" });
212
- // Direct insert — no audit
213
270
  audited.db.insert(users).values({ id: "u1", name: "Ada" }).run();
214
271
  const logs = db.select().from(auditLogs).all();
215
272
  assert.equal(logs.length, 0);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@willyim/drizzle-audit",
3
- "version": "0.3.0",
3
+ "version": "0.5.0",
4
4
  "description": "Lightweight audit logging for Drizzle ORM using database triggers (Postgres + D1/SQLite)",
5
5
  "type": "module",
6
6
  "main": "./dist/src/index.js",
@@ -53,6 +53,7 @@
53
53
  "devDependencies": {
54
54
  "@types/node": "^22.19.7",
55
55
  "drizzle-orm": "^1.0.0-beta.15-859cf75",
56
+ "miniflare": "^4.20260410.0",
56
57
  "typescript": "^5.9.3"
57
58
  },
58
59
  "publishConfig": {
@@ -1,2 +0,0 @@
1
- export {};
2
- //# sourceMappingURL=d1-runtime.integration.test.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"d1-runtime.integration.test.d.ts","sourceRoot":"","sources":["../../test/d1-runtime.integration.test.ts"],"names":[],"mappings":""}
@@ -1,2 +0,0 @@
1
- export {};
2
- //# sourceMappingURL=postgres.integration.test.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"postgres.integration.test.d.ts","sourceRoot":"","sources":["../../test/postgres.integration.test.ts"],"names":[],"mappings":""}
@@ -1,286 +0,0 @@
1
- import assert from "node:assert/strict";
2
- import test from "node:test";
3
- import { PGlite } from "@electric-sql/pglite";
4
- import { asc, eq } from "drizzle-orm";
5
- import { drizzle } from "drizzle-orm/pglite";
6
- import { integer, pgTable, text } from "drizzle-orm/pg-core";
7
- import { createAttachAuditTriggersSql, createAuditInstallSql, pgAuditLogTable, withAuditedTransaction, } from "../src/postgres/index.js";
8
- const users = pgTable("users", {
9
- id: text("id").primaryKey(),
10
- name: text("name").notNull(),
11
- });
12
- const invoices = pgTable("invoices", {
13
- invoice_id: text("invoice_id").primaryKey(),
14
- amount: integer("amount").notNull(),
15
- });
16
- const auditLogs = pgAuditLogTable();
17
- test("postgres auditing works end to end", async () => {
18
- const client = new PGlite();
19
- const db = drizzle({
20
- client,
21
- schema: {
22
- auditLogs,
23
- users,
24
- invoices,
25
- },
26
- });
27
- try {
28
- await client.exec(createAuditInstallSql());
29
- await client.exec(`
30
- CREATE TABLE users (
31
- id TEXT PRIMARY KEY,
32
- name TEXT NOT NULL
33
- );
34
-
35
- CREATE TABLE invoices (
36
- invoice_id TEXT PRIMARY KEY,
37
- amount INTEGER NOT NULL
38
- );
39
- `);
40
- await client.exec(createAttachAuditTriggersSql([
41
- { table: "users" },
42
- { table: "invoices", rowIdColumn: "invoice_id" },
43
- ]));
44
- // Insert without audit context should succeed with user_id = NULL
45
- await db.insert(users).values({ id: "user_0", name: "No Context" });
46
- const existingUsers = await db.select().from(users);
47
- assert.equal(existingUsers.length, 1);
48
- const noContextLogs = await db.select().from(auditLogs).orderBy(asc(auditLogs.id));
49
- assert.equal(noContextLogs.length, 1);
50
- assert.equal(noContextLogs[0]?.user_id, null);
51
- assert.equal(noContextLogs[0]?.table_name, "users");
52
- assert.equal(noContextLogs[0]?.operation, "INSERT");
53
- await withAuditedTransaction(db, "user_123", async (tx) => {
54
- await tx.insert(users).values({ id: "user_1", name: "Ada" });
55
- await tx
56
- .update(users)
57
- .set({ name: "Ada Lovelace" })
58
- .where(eq(users.id, "user_1"));
59
- await tx.insert(invoices).values({ invoice_id: "inv_1", amount: 42 });
60
- await tx.delete(users).where(eq(users.id, "user_1"));
61
- });
62
- const logs = await db
63
- .select()
64
- .from(auditLogs)
65
- .orderBy(asc(auditLogs.id));
66
- assert.equal(logs.length, 5);
67
- assert.equal(logs[1]?.table_name, "users");
68
- assert.equal(logs[1]?.operation, "INSERT");
69
- assert.equal(logs[1]?.row_id, "user_1");
70
- assert.equal(logs[1]?.user_id, "user_123");
71
- assert.deepEqual(logs[1]?.new_data, { id: "user_1", name: "Ada" });
72
- assert.equal(logs[2]?.table_name, "users");
73
- assert.equal(logs[2]?.operation, "UPDATE");
74
- assert.equal(logs[2]?.row_id, "user_1");
75
- assert.deepEqual(logs[2]?.old_data, { id: "user_1", name: "Ada" });
76
- assert.deepEqual(logs[2]?.new_data, {
77
- id: "user_1",
78
- name: "Ada Lovelace",
79
- });
80
- assert.equal(logs[3]?.table_name, "invoices");
81
- assert.equal(logs[3]?.operation, "INSERT");
82
- assert.equal(logs[3]?.row_id, "inv_1");
83
- assert.deepEqual(logs[3]?.new_data, {
84
- invoice_id: "inv_1",
85
- amount: 42,
86
- });
87
- assert.equal(logs[4]?.table_name, "users");
88
- assert.equal(logs[4]?.operation, "DELETE");
89
- assert.equal(logs[4]?.row_id, "user_1");
90
- assert.deepEqual(logs[4]?.old_data, {
91
- id: "user_1",
92
- name: "Ada Lovelace",
93
- });
94
- }
95
- finally {
96
- await client.close();
97
- }
98
- });
99
- test("migration SQL bundle installs and enforces audit context", async () => {
100
- const client = new PGlite();
101
- const db = drizzle({
102
- client,
103
- schema: {
104
- auditLogs,
105
- users,
106
- invoices,
107
- },
108
- });
109
- try {
110
- await client.exec(`
111
- CREATE TABLE users (
112
- id TEXT PRIMARY KEY,
113
- name TEXT NOT NULL
114
- );
115
-
116
- CREATE TABLE invoices (
117
- invoice_id TEXT PRIMARY KEY,
118
- amount INTEGER NOT NULL
119
- );
120
- `);
121
- const migrationBundle = [
122
- createAuditInstallSql(),
123
- createAttachAuditTriggersSql([
124
- { table: "users" },
125
- { table: "invoices", rowIdColumn: "invoice_id" },
126
- ]),
127
- ].join("\n\n");
128
- await client.exec(migrationBundle);
129
- // Insert without audit context should succeed with user_id = NULL
130
- await db.insert(users).values({ id: "u_no_ctx", name: "No Context" });
131
- const noCtxLogs = await db.select().from(auditLogs);
132
- assert.equal(noCtxLogs.length, 1);
133
- assert.equal(noCtxLogs[0]?.user_id, null);
134
- assert.equal(noCtxLogs[0]?.operation, "INSERT");
135
- await withAuditedTransaction(db, "system:test", async (tx) => {
136
- await tx.insert(users).values({ id: "u", name: "With Context" });
137
- });
138
- const logs = await db.select().from(auditLogs).orderBy(asc(auditLogs.id));
139
- assert.equal(logs.length, 2);
140
- assert.equal(logs[1]?.table_name, "users");
141
- assert.equal(logs[1]?.operation, "INSERT");
142
- assert.equal(logs[1]?.user_id, "system:test");
143
- }
144
- finally {
145
- await client.close();
146
- }
147
- });
148
- test("writes without audit context produce audit rows with user_id = NULL", async () => {
149
- const client = new PGlite();
150
- const db = drizzle({
151
- client,
152
- schema: {
153
- auditLogs,
154
- users,
155
- },
156
- });
157
- try {
158
- await client.exec(createAuditInstallSql());
159
- await client.exec(`
160
- CREATE TABLE users (
161
- id TEXT PRIMARY KEY,
162
- name TEXT NOT NULL
163
- );
164
- `);
165
- await client.exec(createAttachAuditTriggersSql([{ table: "users" }]));
166
- // INSERT without audit context
167
- await db.insert(users).values({ id: "u1", name: "Alice" });
168
- // UPDATE without audit context
169
- await db.update(users).set({ name: "Alice Updated" }).where(eq(users.id, "u1"));
170
- // DELETE without audit context
171
- await db.delete(users).where(eq(users.id, "u1"));
172
- const logs = await db.select().from(auditLogs).orderBy(asc(auditLogs.id));
173
- assert.equal(logs.length, 3);
174
- assert.equal(logs[0]?.operation, "INSERT");
175
- assert.equal(logs[0]?.user_id, null);
176
- assert.equal(logs[0]?.row_id, "u1");
177
- assert.deepEqual(logs[0]?.new_data, { id: "u1", name: "Alice" });
178
- assert.equal(logs[1]?.operation, "UPDATE");
179
- assert.equal(logs[1]?.user_id, null);
180
- assert.deepEqual(logs[1]?.old_data, { id: "u1", name: "Alice" });
181
- assert.deepEqual(logs[1]?.new_data, { id: "u1", name: "Alice Updated" });
182
- assert.equal(logs[2]?.operation, "DELETE");
183
- assert.equal(logs[2]?.user_id, null);
184
- assert.deepEqual(logs[2]?.old_data, { id: "u1", name: "Alice Updated" });
185
- }
186
- finally {
187
- await client.close();
188
- }
189
- });
190
- test("workspace_id column and context are stored when enabled", async () => {
191
- const client = new PGlite();
192
- const auditLogsWithWorkspace = pgAuditLogTable({ workspaceIdColumn: "workspace_id" });
193
- const db = drizzle({
194
- client,
195
- schema: {
196
- auditLogs: auditLogsWithWorkspace,
197
- users,
198
- invoices,
199
- },
200
- });
201
- try {
202
- await client.exec(createAuditInstallSql({ workspaceIdColumn: "workspace_id" }));
203
- await client.exec(`
204
- CREATE TABLE users (
205
- id TEXT PRIMARY KEY,
206
- name TEXT NOT NULL
207
- );
208
- `);
209
- await client.exec(createAttachAuditTriggersSql([{ table: "users" }]));
210
- await withAuditedTransaction(db, "user_1", async (tx) => {
211
- await tx.insert(users).values({ id: "u1", name: "Alice" });
212
- }, "app.user_id", { workspaceId: "ws_1" });
213
- const logs = await db.select().from(auditLogsWithWorkspace);
214
- assert.equal(logs.length, 1);
215
- assert.equal(logs[0]?.user_id, "user_1");
216
- assert.equal(logs[0].workspace_id, "ws_1");
217
- await withAuditedTransaction(db, "user_2", async (tx) => {
218
- await tx.insert(users).values({ id: "u2", name: "Bob" });
219
- });
220
- const logs2 = await db.select().from(auditLogsWithWorkspace).orderBy(asc(auditLogsWithWorkspace.id));
221
- assert.equal(logs2.length, 2);
222
- assert.equal(logs2[1]?.user_id, "user_2");
223
- assert.equal(logs2[1].workspace_id, null);
224
- }
225
- finally {
226
- await client.close();
227
- }
228
- });
229
- test("custom workspace column name uses matching context key", async () => {
230
- const client = new PGlite();
231
- const auditLogsWithTenant = pgAuditLogTable({ workspaceIdColumn: "tenant_id" });
232
- const db = drizzle({
233
- client,
234
- schema: {
235
- auditLogs: auditLogsWithTenant,
236
- users,
237
- },
238
- });
239
- try {
240
- await client.exec(createAuditInstallSql({ workspaceIdColumn: "tenant_id" }));
241
- await client.exec(`
242
- CREATE TABLE users (
243
- id TEXT PRIMARY KEY,
244
- name TEXT NOT NULL
245
- );
246
- `);
247
- await client.exec(createAttachAuditTriggersSql([{ table: "users" }]));
248
- // Use workspaceContextKey matching the trigger's "app.tenant_id"
249
- await withAuditedTransaction(db, "user_1", async (tx) => {
250
- await tx.insert(users).values({ id: "u1", name: "Alice" });
251
- }, "app.user_id", { workspaceId: "tenant_abc", workspaceContextKey: "app.tenant_id" });
252
- const logs = await db.select().from(auditLogsWithTenant);
253
- assert.equal(logs.length, 1);
254
- assert.equal(logs[0]?.user_id, "user_1");
255
- assert.equal(logs[0].tenant_id, "tenant_abc");
256
- }
257
- finally {
258
- await client.close();
259
- }
260
- });
261
- test("custom context key for user_id works", async () => {
262
- const client = new PGlite();
263
- const db = drizzle({
264
- client,
265
- schema: { auditLogs, users },
266
- });
267
- try {
268
- await client.exec(createAuditInstallSql({ contextKey: "myapp.actor" }));
269
- await client.exec(`
270
- CREATE TABLE users (
271
- id TEXT PRIMARY KEY,
272
- name TEXT NOT NULL
273
- );
274
- `);
275
- await client.exec(createAttachAuditTriggersSql([{ table: "users" }], { contextKey: "myapp.actor" }));
276
- await withAuditedTransaction(db, "custom_user", async (tx) => {
277
- await tx.insert(users).values({ id: "u1", name: "Alice" });
278
- }, "myapp.actor");
279
- const logs = await db.select().from(auditLogs).orderBy(asc(auditLogs.id));
280
- assert.equal(logs.length, 1);
281
- assert.equal(logs[0]?.user_id, "custom_user");
282
- }
283
- finally {
284
- await client.close();
285
- }
286
- });