@replayio-app-building/netlify-recorder 0.19.0 → 0.20.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 CHANGED
@@ -464,13 +464,14 @@ Creates the `audit_log` table, its indexes, and a reusable PL/pgSQL trigger func
464
464
  **Parameters:**
465
465
  - `sql` — A Neon SQL tagged-template function
466
466
 
467
- ### `databaseAuditMonitorTable(sql, tableName): Promise<void>`
467
+ ### `databaseAuditMonitorTable(sql, tableName, primaryKeyColumn?): Promise<void>`
468
468
 
469
469
  Attaches a trigger to the specified table that logs INSERT, UPDATE, and DELETE operations to the `audit_log` table. Throws if `tableName` is `'audit_log'`.
470
470
 
471
471
  **Parameters:**
472
472
  - `sql` — A Neon SQL tagged-template function
473
473
  - `tableName` — Name of the table to monitor (must match `^[a-zA-Z_][a-zA-Z0-9_]*$`)
474
+ - `primaryKeyColumn` — Name of the primary key column (default: `"id"`, must match `^[a-zA-Z_][a-zA-Z0-9_]*$`)
474
475
 
475
476
  ### `databaseAuditDumpLogTable(sql): Promise<Record<string, unknown>[]>`
476
477
 
package/dist/index.d.ts CHANGED
@@ -332,7 +332,7 @@ declare function databaseAuditEnsureLogTable(sql: SqlFunction): Promise<void>;
332
332
  *
333
333
  * Throws if `tableName` is `'audit_log'` (cannot monitor itself).
334
334
  */
335
- declare function databaseAuditMonitorTable(sql: SqlFunction, tableName: string): Promise<void>;
335
+ declare function databaseAuditMonitorTable(sql: SqlFunction, tableName: string, primaryKeyColumn?: string): Promise<void>;
336
336
  /**
337
337
  * Returns all rows from the `audit_log` table, ordered by `performed_at` DESC.
338
338
  */
package/dist/index.js CHANGED
@@ -1095,7 +1095,12 @@ async function databaseAuditEnsureLogTable(sql) {
1095
1095
  changed_cols TEXT[];
1096
1096
  req_id TEXT;
1097
1097
  call_idx INTEGER;
1098
+ pk_col TEXT;
1099
+ pk_val TEXT;
1098
1100
  BEGIN
1101
+ -- Primary key column name passed as trigger argument; default to 'id'
1102
+ pk_col := COALESCE(TG_ARGV[0], 'id');
1103
+
1099
1104
  -- Read application context injected by the network interceptor
1100
1105
  req_id := COALESCE(current_setting('app.replay_request_id', true), '');
1101
1106
  IF req_id = '' THEN req_id := NULL; END IF;
@@ -1107,21 +1112,24 @@ async function databaseAuditEnsureLogTable(sql) {
1107
1112
  END;
1108
1113
 
1109
1114
  IF TG_OP = 'INSERT' THEN
1115
+ EXECUTE format('SELECT ($1).%I::TEXT', pk_col) USING NEW INTO pk_val;
1110
1116
  INSERT INTO audit_log (table_name, record_id, action, new_data, replay_request_id, replay_request_call_index)
1111
- VALUES (TG_TABLE_NAME, NEW.id::TEXT, 'INSERT', to_jsonb(NEW), req_id, call_idx);
1117
+ VALUES (TG_TABLE_NAME, pk_val, 'INSERT', to_jsonb(NEW), req_id, call_idx);
1112
1118
  RETURN NEW;
1113
1119
  ELSIF TG_OP = 'UPDATE' THEN
1120
+ EXECUTE format('SELECT ($1).%I::TEXT', pk_col) USING NEW INTO pk_val;
1114
1121
  SELECT ARRAY_AGG(n.key) INTO changed_cols
1115
1122
  FROM jsonb_each_text(to_jsonb(NEW)) n
1116
1123
  LEFT JOIN jsonb_each_text(to_jsonb(OLD)) o ON n.key = o.key
1117
1124
  WHERE o.value IS DISTINCT FROM n.value;
1118
1125
 
1119
1126
  INSERT INTO audit_log (table_name, record_id, action, old_data, new_data, changed_fields, replay_request_id, replay_request_call_index)
1120
- VALUES (TG_TABLE_NAME, NEW.id::TEXT, 'UPDATE', to_jsonb(OLD), to_jsonb(NEW), COALESCE(changed_cols, ARRAY[]::TEXT[]), req_id, call_idx);
1127
+ VALUES (TG_TABLE_NAME, pk_val, 'UPDATE', to_jsonb(OLD), to_jsonb(NEW), COALESCE(changed_cols, ARRAY[]::TEXT[]), req_id, call_idx);
1121
1128
  RETURN NEW;
1122
1129
  ELSIF TG_OP = 'DELETE' THEN
1130
+ EXECUTE format('SELECT ($1).%I::TEXT', pk_col) USING OLD INTO pk_val;
1123
1131
  INSERT INTO audit_log (table_name, record_id, action, old_data, replay_request_id, replay_request_call_index)
1124
- VALUES (TG_TABLE_NAME, OLD.id::TEXT, 'DELETE', to_jsonb(OLD), req_id, call_idx);
1132
+ VALUES (TG_TABLE_NAME, pk_val, 'DELETE', to_jsonb(OLD), req_id, call_idx);
1125
1133
  RETURN OLD;
1126
1134
  END IF;
1127
1135
  RETURN NULL;
@@ -1129,19 +1137,22 @@ async function databaseAuditEnsureLogTable(sql) {
1129
1137
  $$ LANGUAGE plpgsql
1130
1138
  `;
1131
1139
  }
1132
- async function databaseAuditMonitorTable(sql, tableName) {
1140
+ async function databaseAuditMonitorTable(sql, tableName, primaryKeyColumn = "id") {
1133
1141
  if (tableName === "audit_log") {
1134
1142
  throw new Error("Cannot monitor the audit_log table itself");
1135
1143
  }
1136
1144
  if (!/^[a-zA-Z_][a-zA-Z0-9_]*$/.test(tableName)) {
1137
1145
  throw new Error(`Invalid table name: ${tableName}`);
1138
1146
  }
1147
+ if (!/^[a-zA-Z_][a-zA-Z0-9_]*$/.test(primaryKeyColumn)) {
1148
+ throw new Error(`Invalid primary key column name: ${primaryKeyColumn}`);
1149
+ }
1139
1150
  const triggerName = `audit_trigger_${tableName}`;
1140
1151
  await sql(
1141
1152
  `DROP TRIGGER IF EXISTS ${triggerName} ON "${tableName}"`
1142
1153
  );
1143
1154
  await sql(
1144
- `CREATE TRIGGER ${triggerName} AFTER INSERT OR UPDATE OR DELETE ON "${tableName}" FOR EACH ROW EXECUTE FUNCTION audit_trigger_function()`
1155
+ `CREATE TRIGGER ${triggerName} AFTER INSERT OR UPDATE OR DELETE ON "${tableName}" FOR EACH ROW EXECUTE FUNCTION audit_trigger_function('${primaryKeyColumn}')`
1145
1156
  );
1146
1157
  }
1147
1158
  async function databaseAuditDumpLogTable(sql) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@replayio-app-building/netlify-recorder",
3
- "version": "0.19.0",
3
+ "version": "0.20.0",
4
4
  "description": "Capture and replay Netlify function executions as Replay recordings",
5
5
  "type": "module",
6
6
  "exports": {