@willyim/drizzle-audit 0.4.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 (37) hide show
  1. package/README.md +52 -16
  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 +3 -2
  16. package/dist/src/d1-runtime/with-audit.d.ts.map +1 -1
  17. package/dist/src/d1-runtime/with-audit.js +12 -7
  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 +3 -2
  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.integration.test.js +71 -4
  36. package/dist/test/sqlite.integration.test.js +65 -2
  37. package/package.json +1 -1
package/README.md CHANGED
@@ -120,47 +120,62 @@ sqlite.exec(createAttachD1AuditTriggersSqlWithColumns([
120
120
  ]))
121
121
  ```
122
122
 
123
- ## Workspace / Tenant Scoping
123
+ ## Context Columns
124
124
 
125
- All three approaches support an optional workspace column for multi-tenant apps.
125
+ Beyond `user_id`, you can declare arbitrary extra context columns (e.g.
126
+ `workspace_id`, `tenant_id`, `request_id`). Each is an extra `TEXT` column on
127
+ `audit_logs`, populated at write-time from a named runtime context value (a
128
+ Postgres session GUC, or a `_audit_context` KV row in D1).
129
+
130
+ ```ts
131
+ type AuditContextColumn = {
132
+ column: string // audit table column (TEXT, nullable)
133
+ sessionKey?: string // Postgres GUC the trigger reads — default `app.${column}`
134
+ // D1: the _audit_context key — default `${column}`
135
+ index?: boolean // create an index on the column — default true
136
+ }
137
+ ```
126
138
 
127
139
  ### Postgres
128
140
 
129
141
  ```ts
130
- // Install with workspace column
131
- createAuditInstallSql({ workspaceIdColumn: "workspace_id" })
132
- export const auditLogs = pgAuditLogTable({ workspaceIdColumn: "workspace_id" })
142
+ const contextColumns = [{ column: "workspace_id" }, { column: "tenant_id" }]
143
+
144
+ createAuditInstallSql({ contextColumns })
145
+ export const auditLogs = pgAuditLogTable({ contextColumns })
133
146
 
134
- // Pass workspace at runtime
147
+ // Pass context at runtime (GUC name → value)
135
148
  await withAuditedTransaction(
136
149
  db, userId, async (tx) => { /* ... */ },
137
150
  "app.user_id",
138
- { workspaceId: "ws_1" },
151
+ { context: { "app.workspace_id": "ws_1", "app.tenant_id": "t_1" } },
139
152
  )
140
153
  ```
141
154
 
142
- To add workspace to an existing install, use `createAuditAddWorkspaceColumnSql()`.
155
+ To add context columns to an existing install, use
156
+ `createAuditAddContextColumnsSql({ contextColumns })` — it adds each column and
157
+ regenerates the trigger over the full set. Pass the complete list of columns the
158
+ table should have.
143
159
 
144
160
  ### D1 Runtime
145
161
 
146
162
  ```ts
147
163
  const audit = withAudit(db, auditLogs, {
148
164
  userId: "user_1",
149
- workspaceId: "ws_1",
165
+ context: { workspace_id: "ws_1", tenant_id: "t_1" },
150
166
  })
151
167
  ```
152
168
 
153
169
  ### D1 Triggers
154
170
 
155
171
  ```ts
156
- createD1AuditInstallSql({ workspaceIdColumn: "workspace_id" })
157
- createAttachD1AuditTriggersSql(
158
- [{ table: "users" }],
159
- { workspaceIdColumn: "workspace_id" },
160
- )
172
+ const contextColumns = [{ column: "workspace_id" }, { column: "tenant_id" }]
173
+
174
+ createD1AuditInstallSql({ contextColumns })
175
+ createAttachD1AuditTriggersSql([{ table: "users" }], { contextColumns })
161
176
 
162
177
  withD1AuditedTransaction(db, "user_1", (tx) => { /* ... */ }, {
163
- workspaceId: "ws_1",
178
+ context: { workspace_id: "ws_1", tenant_id: "t_1" },
164
179
  })
165
180
  ```
166
181
 
@@ -202,7 +217,7 @@ export function createAuditSql() {
202
217
  | `createAuditInstallSql(options?)` | SQL to create the audit table, indexes, and trigger function |
203
218
  | `createAttachAuditTriggerSql(target, options?)` | SQL to attach audit trigger to one table |
204
219
  | `createAttachAuditTriggersSql(targets, options?)` | Same, for multiple tables |
205
- | `createAuditAddWorkspaceColumnSql(options)` | SQL to add workspace column to existing install |
220
+ | `createAuditAddContextColumnsSql(options)` | SQL to add context columns + regenerate trigger on existing install |
206
221
  | `setAuditContext(db, actorId, contextKey?, options?)` | Set actor context in current transaction |
207
222
  | `withAuditedTransaction(db, actorId, callback, contextKey?, options?)` | Transaction wrapper with actor context |
208
223
 
@@ -318,6 +333,27 @@ CREATE TABLE audit_logs (
318
333
  | **Bypass risk** | Low (DB-level) | Medium (must use wrapper) | Low (DB-level) |
319
334
  | **Best for** | Postgres apps | D1/Cloudflare Workers | SQLite apps needing DB-level guarantees |
320
335
 
336
+ ## Migrating from 0.2.x → 0.3.0
337
+
338
+ The `workspace_id`-specific options were removed in favor of the generic
339
+ [Context Columns](#context-columns) API. Mechanical replacements:
340
+
341
+ | Removed | Replacement |
342
+ |---|---|
343
+ | `pgAuditLogTable({ workspaceIdColumn: "workspace_id" })` | `pgAuditLogTable({ contextColumns: [{ column: "workspace_id" }] })` |
344
+ | `d1AuditLogTable({ workspaceIdColumn: "workspace_id" })` | `d1AuditLogTable({ contextColumns: [{ column: "workspace_id" }] })` |
345
+ | `createAuditInstallSql({ workspaceIdColumn: "workspace_id" })` | `createAuditInstallSql({ contextColumns: [{ column: "workspace_id" }] })` |
346
+ | `createD1AuditInstallSql({ workspaceIdColumn })` | `createD1AuditInstallSql({ contextColumns: [{ column }] })` |
347
+ | `createAttachD1AuditTriggersSql(targets, { workspaceIdColumn })` | `createAttachD1AuditTriggersSql(targets, { contextColumns: [{ column }] })` |
348
+ | `createAuditAddWorkspaceColumnSql(options)` | `createAuditAddContextColumnsSql({ contextColumns: [...] })` |
349
+ | Postgres runtime `{ workspaceId: v }` | `{ context: { "app.workspace_id": v } }` |
350
+ | Postgres runtime `{ workspaceId: v, workspaceContextKey: "app.tenant_id" }` | `{ context: { "app.tenant_id": v } }` |
351
+ | D1 runtime `{ workspaceId: v }` | `{ context: { workspace_id: v } }` |
352
+ | `withAudit(db, table, { userId, workspaceId: v })` | `withAudit(db, table, { userId, context: { workspace_id: v } })` |
353
+
354
+ The on-disk column name (`workspace_id`) is unchanged, so no data migration is
355
+ needed — only call sites change.
356
+
321
357
  ## License
322
358
 
323
359
  MIT
@@ -1,6 +1,7 @@
1
+ import type { AuditContextColumn } from "./types.js";
1
2
  export type D1AuditLogTableOptions = {
2
- /** When set (e.g. "workspace_id"), the table definition includes this optional column. */
3
- workspaceIdColumn?: string;
3
+ /** Extra context columns to include in the table definition, matching the install. */
4
+ contextColumns?: AuditContextColumn[];
4
5
  };
5
6
  export declare function d1AuditLogTable(options?: D1AuditLogTableOptions): import("drizzle-orm/sqlite-core").SQLiteTableWithColumns<{
6
7
  name: "audit_logs";
@@ -1 +1 @@
1
- {"version":3,"file":"audit-log-schema.d.ts","sourceRoot":"","sources":["../../../src/d1/audit-log-schema.ts"],"names":[],"mappings":"AAOA,MAAM,MAAM,sBAAsB,GAAG;IACnC,0FAA0F;IAC1F,iBAAiB,CAAC,EAAE,MAAM,CAAA;CAC3B,CAAA;AAED,wBAAgB,eAAe,CAAC,OAAO,CAAC,EAAE,sBAAsB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAyB/D;AAED,wBAAgB,mBAAmB,CAAC,OAAO,CAAC,EAAE;IAAE,YAAY,CAAC,EAAE,MAAM,CAAA;CAAE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAMtE"}
1
+ {"version":3,"file":"audit-log-schema.d.ts","sourceRoot":"","sources":["../../../src/d1/audit-log-schema.ts"],"names":[],"mappings":"AAOA,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;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAuB/D;AAED,wBAAgB,mBAAmB,CAAC,OAAO,CAAC,EAAE;IAAE,YAAY,CAAC,EAAE,MAAM,CAAA;CAAE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAMtE"}
@@ -1,15 +1,25 @@
1
1
  import { index, integer, sqliteTable, text, } from "drizzle-orm/sqlite-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 d1AuditLogTable(options) {
3
- const workspaceIdColumn = options?.workspaceIdColumn?.trim();
15
+ const contextColumns = resolveColumns(options);
4
16
  const columns = {
5
17
  id: integer("id").primaryKey({ autoIncrement: true }),
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
- ...(workspaceIdColumn
11
- ? { [workspaceIdColumn]: text(workspaceIdColumn) }
12
- : {}),
22
+ ...Object.fromEntries(contextColumns.map((c) => [c, text(c)])),
13
23
  old_data: text("old_data"),
14
24
  new_data: text("new_data"),
15
25
  created_at: text("created_at").notNull().default("(datetime('now'))"),
@@ -3,5 +3,6 @@ export type { D1AuditLogTableOptions } from "./audit-log-schema.js";
3
3
  export { createAttachD1AuditTriggerSql, createAttachD1AuditTriggersSql, createAttachD1AuditTriggerSqlWithColumns, createAttachD1AuditTriggersSqlWithColumns, createD1AuditInstallSql, } from "./sql.js";
4
4
  export type { D1AuditTriggerTargetWithColumns } from "./sql.js";
5
5
  export { clearD1AuditContext, setD1AuditContext, withD1AuditedTransaction, } from "./runtime.js";
6
- export type { D1AuditInstallOptions, D1AuditSqlExecutor, D1AuditTriggerTarget, } from "./types.js";
6
+ export type { D1AuditContextOptions } from "./runtime.js";
7
+ export type { AuditContextColumn, D1AuditInstallOptions, D1AuditSqlExecutor, D1AuditTriggerTarget, } from "./types.js";
7
8
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/d1/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,mBAAmB,EAAE,MAAM,uBAAuB,CAAA;AAC5E,YAAY,EAAE,sBAAsB,EAAE,MAAM,uBAAuB,CAAA;AACnE,OAAO,EACL,6BAA6B,EAC7B,8BAA8B,EAC9B,wCAAwC,EACxC,yCAAyC,EACzC,uBAAuB,GACxB,MAAM,UAAU,CAAA;AACjB,YAAY,EAAE,+BAA+B,EAAE,MAAM,UAAU,CAAA;AAC/D,OAAO,EACL,mBAAmB,EACnB,iBAAiB,EACjB,wBAAwB,GACzB,MAAM,cAAc,CAAA;AAErB,YAAY,EACV,qBAAqB,EACrB,kBAAkB,EAClB,oBAAoB,GACrB,MAAM,YAAY,CAAA"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/d1/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,mBAAmB,EAAE,MAAM,uBAAuB,CAAA;AAC5E,YAAY,EAAE,sBAAsB,EAAE,MAAM,uBAAuB,CAAA;AACnE,OAAO,EACL,6BAA6B,EAC7B,8BAA8B,EAC9B,wCAAwC,EACxC,yCAAyC,EACzC,uBAAuB,GACxB,MAAM,UAAU,CAAA;AACjB,YAAY,EAAE,+BAA+B,EAAE,MAAM,UAAU,CAAA;AAC/D,OAAO,EACL,mBAAmB,EACnB,iBAAiB,EACjB,wBAAwB,GACzB,MAAM,cAAc,CAAA;AACrB,YAAY,EAAE,qBAAqB,EAAE,MAAM,cAAc,CAAA;AAEzD,YAAY,EACV,kBAAkB,EAClB,qBAAqB,EACrB,kBAAkB,EAClB,oBAAoB,GACrB,MAAM,YAAY,CAAA"}
@@ -1,4 +1,9 @@
1
1
  import type { D1AuditSqlExecutor } from "./types.js";
2
+ export type D1AuditContextOptions = {
3
+ /** Map of context key → value written as KV rows the triggers read. */
4
+ context?: Record<string, string>;
5
+ contextTable?: string;
6
+ };
2
7
  /**
3
8
  * Sets the audit context for the current transaction by writing to the
4
9
  * _audit_context table. Must be called inside a transaction before any
@@ -6,17 +11,15 @@ import type { D1AuditSqlExecutor } from "./types.js";
6
11
  *
7
12
  * D1/SQLite has no session variables, so triggers read context from this table.
8
13
  */
9
- export declare function setD1AuditContext(db: D1AuditSqlExecutor, actorId: string, options?: {
10
- workspaceId?: string;
11
- contextTable?: string;
12
- }): Promise<unknown> | undefined;
14
+ export declare function setD1AuditContext(db: D1AuditSqlExecutor, actorId: string, options?: D1AuditContextOptions): Promise<unknown> | undefined;
13
15
  /**
14
16
  * Clears the audit context after a transaction completes.
15
17
  * Called automatically by withD1AuditedTransaction.
18
+ *
19
+ * Clears `user_id` plus any keys set via `context` so the next transaction
20
+ * starts clean.
16
21
  */
17
- export declare function clearD1AuditContext(db: D1AuditSqlExecutor, options?: {
18
- contextTable?: string;
19
- }): unknown;
22
+ export declare function clearD1AuditContext(db: D1AuditSqlExecutor, options?: D1AuditContextOptions): unknown;
20
23
  /**
21
24
  * Wraps a Drizzle SQLite transaction with audit context. Sets the actor
22
25
  * before the callback and clears the context after (success or failure).
@@ -37,8 +40,5 @@ export declare function clearD1AuditContext(db: D1AuditSqlExecutor, options?: {
37
40
  */
38
41
  export declare function withD1AuditedTransaction<TDb extends D1AuditSqlExecutor, TResult>(db: TDb & {
39
42
  transaction: (cb: (tx: any) => TResult) => TResult;
40
- }, actorId: string, callback: (tx: TDb) => TResult, options?: {
41
- workspaceId?: string;
42
- contextTable?: string;
43
- }): TResult;
43
+ }, actorId: string, callback: (tx: TDb) => TResult, options?: D1AuditContextOptions): TResult;
44
44
  //# sourceMappingURL=runtime.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"runtime.d.ts","sourceRoot":"","sources":["../../../src/d1/runtime.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,YAAY,CAAA;AAUpD;;;;;;GAMG;AACH,wBAAgB,iBAAiB,CAC/B,EAAE,EAAE,kBAAkB,EACtB,OAAO,EAAE,MAAM,EACf,OAAO,CAAC,EAAE;IAAE,WAAW,CAAC,EAAE,MAAM,CAAC;IAAC,YAAY,CAAC,EAAE,MAAM,CAAA;CAAE,gCAyB1D;AAED;;;GAGG;AACH,wBAAgB,mBAAmB,CACjC,EAAE,EAAE,kBAAkB,EACtB,OAAO,CAAC,EAAE;IAAE,YAAY,CAAC,EAAE,MAAM,CAAA;CAAE,WAMpC;AAED;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,wBAAwB,CAAC,GAAG,SAAS,kBAAkB,EAAE,OAAO,EAC9E,EAAE,EAAE,GAAG,GAAG;IAAE,WAAW,EAAE,CAAC,EAAE,EAAE,CAAC,EAAE,EAAE,GAAG,KAAK,OAAO,KAAK,OAAO,CAAA;CAAE,EAChE,OAAO,EAAE,MAAM,EACf,QAAQ,EAAE,CAAC,EAAE,EAAE,GAAG,KAAK,OAAO,EAC9B,OAAO,CAAC,EAAE;IAAE,WAAW,CAAC,EAAE,MAAM,CAAC;IAAC,YAAY,CAAC,EAAE,MAAM,CAAA;CAAE,GACxD,OAAO,CAWT"}
1
+ {"version":3,"file":"runtime.d.ts","sourceRoot":"","sources":["../../../src/d1/runtime.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,YAAY,CAAA;AAUpD,MAAM,MAAM,qBAAqB,GAAG;IAClC,uEAAuE;IACvE,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;IAChC,YAAY,CAAC,EAAE,MAAM,CAAA;CACtB,CAAA;AAkBD;;;;;;GAMG;AACH,wBAAgB,iBAAiB,CAC/B,EAAE,EAAE,kBAAkB,EACtB,OAAO,EAAE,MAAM,EACf,OAAO,CAAC,EAAE,qBAAqB,gCAwBhC;AAED;;;;;;GAMG;AACH,wBAAgB,mBAAmB,CACjC,EAAE,EAAE,kBAAkB,EACtB,OAAO,CAAC,EAAE,qBAAqB,WAYhC;AAED;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,wBAAwB,CAAC,GAAG,SAAS,kBAAkB,EAAE,OAAO,EAC9E,EAAE,EAAE,GAAG,GAAG;IAAE,WAAW,EAAE,CAAC,EAAE,EAAE,CAAC,EAAE,EAAE,GAAG,KAAK,OAAO,KAAK,OAAO,CAAA;CAAE,EAChE,OAAO,EAAE,MAAM,EACf,QAAQ,EAAE,CAAC,EAAE,EAAE,GAAG,KAAK,OAAO,EAC9B,OAAO,CAAC,EAAE,qBAAqB,GAC9B,OAAO,CAWT"}
@@ -5,6 +5,19 @@ function assertActorId(actorId) {
5
5
  throw new Error("actorId must not be empty");
6
6
  }
7
7
  }
8
+ /**
9
+ * Builds the KV map (key → value) to write. Empty/undefined values are dropped
10
+ * so the corresponding column stays NULL.
11
+ */
12
+ function resolveContext(options) {
13
+ const context = {};
14
+ for (const [key, value] of Object.entries(options?.context ?? {})) {
15
+ if (value !== undefined && value !== "") {
16
+ context[key] = value;
17
+ }
18
+ }
19
+ return context;
20
+ }
8
21
  /**
9
22
  * Sets the audit context for the current transaction by writing to the
10
23
  * _audit_context table. Must be called inside a transaction before any
@@ -15,26 +28,30 @@ function assertActorId(actorId) {
15
28
  export function setD1AuditContext(db, actorId, options) {
16
29
  assertActorId(actorId);
17
30
  const table = options?.contextTable ?? DEFAULT_CONTEXT_TABLE;
18
- const result = db.run(sql `INSERT OR REPLACE INTO ${sql.identifier(table)} (key, value) VALUES ('user_id', ${actorId})`);
31
+ const context = resolveContext(options);
32
+ const writeKv = (key, value) => db.run(sql `INSERT OR REPLACE INTO ${sql.identifier(table)} (key, value) VALUES (${key}, ${value})`);
33
+ const result = writeKv("user_id", actorId);
19
34
  // Handle async drivers (D1) by chaining if result is a Promise
20
35
  if (result && typeof result.then === "function") {
21
- return result.then(() => {
22
- if (options?.workspaceId !== undefined && options.workspaceId !== "") {
23
- return db.run(sql `INSERT OR REPLACE INTO ${sql.identifier(table)} (key, value) VALUES ('workspace_id', ${options.workspaceId})`);
24
- }
25
- });
36
+ return Object.entries(context).reduce((prev, [key, value]) => prev.then(() => writeKv(key, value)), result);
26
37
  }
27
- if (options?.workspaceId !== undefined && options.workspaceId !== "") {
28
- db.run(sql `INSERT OR REPLACE INTO ${sql.identifier(table)} (key, value) VALUES ('workspace_id', ${options.workspaceId})`);
38
+ for (const [key, value] of Object.entries(context)) {
39
+ writeKv(key, value);
29
40
  }
30
41
  }
31
42
  /**
32
43
  * Clears the audit context after a transaction completes.
33
44
  * Called automatically by withD1AuditedTransaction.
45
+ *
46
+ * Clears `user_id` plus any keys set via `context` so the next transaction
47
+ * starts clean.
34
48
  */
35
49
  export function clearD1AuditContext(db, options) {
36
50
  const table = options?.contextTable ?? DEFAULT_CONTEXT_TABLE;
37
- return db.run(sql `DELETE FROM ${sql.identifier(table)} WHERE key IN ('user_id', 'workspace_id')`);
51
+ const keys = ["user_id", ...Object.keys(options?.context ?? {})];
52
+ const uniqueKeys = [...new Set(keys)];
53
+ const keyList = sql.join(uniqueKeys.map((k) => sql `${k}`), sql `, `);
54
+ return db.run(sql `DELETE FROM ${sql.identifier(table)} WHERE key IN (${keyList})`);
38
55
  }
39
56
  /**
40
57
  * Wraps a Drizzle SQLite transaction with audit context. Sets the actor
@@ -2,8 +2,8 @@ import type { D1AuditInstallOptions, D1AuditTriggerTarget } from "./types.js";
2
2
  /**
3
3
  * Generates SQL to install the audit_logs table and _audit_context table.
4
4
  *
5
- * The _audit_context table stores user_id (and optionally workspace_id) for
6
- * the current transaction. Since D1/SQLite has no session variables, triggers
5
+ * The _audit_context table stores user_id (and any configured context columns)
6
+ * for the current transaction. Since D1/SQLite has no session variables, triggers
7
7
  * read context from this table instead.
8
8
  */
9
9
  export declare function createD1AuditInstallSql(options?: D1AuditInstallOptions): string;
@@ -1 +1 @@
1
- {"version":3,"file":"sql.d.ts","sourceRoot":"","sources":["../../../src/d1/sql.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,qBAAqB,EAAE,oBAAoB,EAAE,MAAM,YAAY,CAAA;AAqB7E;;;;;;GAMG;AACH,wBAAgB,uBAAuB,CAAC,OAAO,GAAE,qBAA0B,UA6C1E;AAoHD;;;;;;GAMG;AACH,wBAAgB,6BAA6B,CAC3C,MAAM,EAAE,oBAAoB,EAC5B,OAAO,GAAE,qBAA0B,UAOpC;AAED,wBAAgB,8BAA8B,CAC5C,OAAO,EAAE,oBAAoB,EAAE,EAC/B,OAAO,GAAE,qBAA0B,UAQpC;AAID,MAAM,MAAM,+BAA+B,GAAG,oBAAoB,GAAG;IACnE,kEAAkE;IAClE,OAAO,EAAE,MAAM,EAAE,CAAA;CAClB,CAAA;AA4HD;;;;;;;;;;;GAWG;AACH,wBAAgB,wCAAwC,CACtD,MAAM,EAAE,+BAA+B,EACvC,OAAO,GAAE,qBAA0B,UAUpC;AAED,wBAAgB,yCAAyC,CACvD,OAAO,EAAE,+BAA+B,EAAE,EAC1C,OAAO,GAAE,qBAA0B,UAQpC"}
1
+ {"version":3,"file":"sql.d.ts","sourceRoot":"","sources":["../../../src/d1/sql.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAEV,qBAAqB,EACrB,oBAAoB,EACrB,MAAM,YAAY,CAAA;AAyEnB;;;;;;GAMG;AACH,wBAAgB,uBAAuB,CAAC,OAAO,GAAE,qBAA0B,UA8C1E;AAoHD;;;;;;GAMG;AACH,wBAAgB,6BAA6B,CAC3C,MAAM,EAAE,oBAAoB,EAC5B,OAAO,GAAE,qBAA0B,UAOpC;AAED,wBAAgB,8BAA8B,CAC5C,OAAO,EAAE,oBAAoB,EAAE,EAC/B,OAAO,GAAE,qBAA0B,UAQpC;AAID,MAAM,MAAM,+BAA+B,GAAG,oBAAoB,GAAG;IACnE,kEAAkE;IAClE,OAAO,EAAE,MAAM,EAAE,CAAA;CAClB,CAAA;AA4HD;;;;;;;;;;;GAWG;AACH,wBAAgB,wCAAwC,CACtD,MAAM,EAAE,+BAA+B,EACvC,OAAO,GAAE,qBAA0B,UAUpC;AAED,wBAAgB,yCAAyC,CACvD,OAAO,EAAE,+BAA+B,EAAE,EAC1C,OAAO,GAAE,qBAA0B,UAQpC"}
@@ -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 optionally workspace_id) for
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 workspaceIdColumn = options.workspaceIdColumn?.trim();
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
- ...(workspaceIdColumn ? [`${quoteIdent(workspaceIdColumn)} TEXT`] : []),
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 contextColumns = [
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
- ...(workspaceIdColumn
47
- ? [
48
- `CREATE INDEX IF NOT EXISTS ${quoteIdent(`${auditTable}_${workspaceIdColumn}_idx`)} ON ${quoteIdent(auditTable)} (${quoteIdent(workspaceIdColumn)});`,
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 ${contextColumns.join(",\n ")}\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 workspaceIdColumn = options.workspaceIdColumn?.trim();
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${workspaceIdColumn ? `, ${quoteIdent(workspaceIdColumn)}` : ""})
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')${workspaceIdColumn ? `,\n (SELECT value FROM ${quoteIdent(contextTable)} WHERE key = 'workspace_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 workspaceIdColumn = options.workspaceIdColumn?.trim();
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${workspaceIdColumn ? `, ${quoteIdent(workspaceIdColumn)}` : ""})
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')${workspaceIdColumn ? `,\n (SELECT value FROM ${quoteIdent(contextTable)} WHERE key = 'workspace_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 workspaceIdColumn = options.workspaceIdColumn?.trim();
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${workspaceIdColumn ? `, ${quoteIdent(workspaceIdColumn)}` : ""})
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')${workspaceIdColumn ? `,\n (SELECT value FROM ${quoteIdent(contextTable)} WHERE key = 'workspace_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 workspaceIdColumn = options.workspaceIdColumn?.trim();
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${workspaceIdColumn ? `, ${quoteIdent(workspaceIdColumn)}` : ""}, new_data)
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')${workspaceIdColumn ? `,\n (SELECT value FROM ${quoteIdent(contextTable)} WHERE key = 'workspace_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 workspaceIdColumn = options.workspaceIdColumn?.trim();
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${workspaceIdColumn ? `, ${quoteIdent(workspaceIdColumn)}` : ""}, old_data, new_data)
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')${workspaceIdColumn ? `,\n (SELECT value FROM ${quoteIdent(contextTable)} WHERE key = 'workspace_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 workspaceIdColumn = options.workspaceIdColumn?.trim();
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${workspaceIdColumn ? `, ${quoteIdent(workspaceIdColumn)}` : ""}, old_data)
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')${workspaceIdColumn ? `,\n (SELECT value FROM ${quoteIdent(contextTable)} WHERE key = 'workspace_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();
@@ -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
- /** Optional workspace column name (e.g. "workspace_id") */
15
- workspaceIdColumn?: string;
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,2DAA2D;IAC3D,iBAAiB,CAAC,EAAE,MAAM,CAAA;CAC3B,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"}
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
- workspaceId?: string;
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 workspaceId)
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,WAAW,CAAC,EAAE,MAAM,CAAA;CACrB,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,CA8FhB"}
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"}