@replayio-app-building/netlify-recorder 0.12.0 → 0.13.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/dist/index.d.ts CHANGED
@@ -442,13 +442,11 @@ declare function databaseAuditMonitorTable(sql: SqlFunction, tableName: string):
442
442
  */
443
443
  declare function databaseAuditDumpLogTable(sql: SqlFunction): Promise<Record<string, unknown>[]>;
444
444
  /**
445
- * Wraps a Neon SQL tagged-template function so that after each query,
446
- * any new `audit_log` rows (with NULL `replay_request_id`) are tagged
447
- * with the current request ID and an incrementing call index.
448
- *
449
- * The request ID is read from module-level state set by
450
- * `createRecordingRequestHandler`. If no request is active, the
451
- * wrapper passes calls through without tagging.
445
+ * Wraps a Neon SQL tagged-template function so that before each query,
446
+ * `SET LOCAL` is used to inject the current request ID and call index
447
+ * into the PostgreSQL session. The `audit_trigger_function` reads these
448
+ * via `current_setting()` and stamps audit rows atomically at INSERT
449
+ * time inside the trigger no separate UPDATE required.
452
450
  *
453
451
  * @example
454
452
  * ```ts
@@ -456,7 +454,7 @@ declare function databaseAuditDumpLogTable(sql: SqlFunction): Promise<Record<str
456
454
  *
457
455
  * const sql = createAuditedSql(getSql());
458
456
  * await sql`INSERT INTO haikus (text) VALUES (${haiku})`;
459
- * // audit_log entries from this INSERT are now tagged with the request ID
457
+ * // audit_log row created by the trigger already has the request ID
460
458
  * ```
461
459
  */
462
460
  declare function createAuditedSql(sql: SqlFunction): SqlFunction;
package/dist/index.js CHANGED
@@ -1130,10 +1130,22 @@ async function databaseAuditEnsureLogTable(sql) {
1130
1130
  RETURNS TRIGGER AS $$
1131
1131
  DECLARE
1132
1132
  changed_cols TEXT[];
1133
+ req_id TEXT;
1134
+ call_idx INTEGER;
1133
1135
  BEGIN
1136
+ -- Read application context set via SET LOCAL by createAuditedSql
1137
+ req_id := COALESCE(current_setting('app.replay_request_id', true), '');
1138
+ IF req_id = '' THEN req_id := NULL; END IF;
1139
+
1140
+ BEGIN
1141
+ call_idx := current_setting('app.replay_call_index', true)::INTEGER;
1142
+ EXCEPTION WHEN OTHERS THEN
1143
+ call_idx := NULL;
1144
+ END;
1145
+
1134
1146
  IF TG_OP = 'INSERT' THEN
1135
- INSERT INTO audit_log (table_name, record_id, action, new_data)
1136
- VALUES (TG_TABLE_NAME, NEW.id::TEXT, 'INSERT', to_jsonb(NEW));
1147
+ INSERT INTO audit_log (table_name, record_id, action, new_data, replay_request_id, replay_request_call_index)
1148
+ VALUES (TG_TABLE_NAME, NEW.id::TEXT, 'INSERT', to_jsonb(NEW), req_id, call_idx);
1137
1149
  RETURN NEW;
1138
1150
  ELSIF TG_OP = 'UPDATE' THEN
1139
1151
  SELECT ARRAY_AGG(n.key) INTO changed_cols
@@ -1141,12 +1153,12 @@ async function databaseAuditEnsureLogTable(sql) {
1141
1153
  LEFT JOIN jsonb_each_text(to_jsonb(OLD)) o ON n.key = o.key
1142
1154
  WHERE o.value IS DISTINCT FROM n.value;
1143
1155
 
1144
- INSERT INTO audit_log (table_name, record_id, action, old_data, new_data, changed_fields)
1145
- VALUES (TG_TABLE_NAME, NEW.id::TEXT, 'UPDATE', to_jsonb(OLD), to_jsonb(NEW), COALESCE(changed_cols, ARRAY[]::TEXT[]));
1156
+ INSERT INTO audit_log (table_name, record_id, action, old_data, new_data, changed_fields, replay_request_id, replay_request_call_index)
1157
+ VALUES (TG_TABLE_NAME, NEW.id::TEXT, 'UPDATE', to_jsonb(OLD), to_jsonb(NEW), COALESCE(changed_cols, ARRAY[]::TEXT[]), req_id, call_idx);
1146
1158
  RETURN NEW;
1147
1159
  ELSIF TG_OP = 'DELETE' THEN
1148
- INSERT INTO audit_log (table_name, record_id, action, old_data)
1149
- VALUES (TG_TABLE_NAME, OLD.id::TEXT, 'DELETE', to_jsonb(OLD));
1160
+ INSERT INTO audit_log (table_name, record_id, action, old_data, replay_request_id, replay_request_call_index)
1161
+ VALUES (TG_TABLE_NAME, OLD.id::TEXT, 'DELETE', to_jsonb(OLD), req_id, call_idx);
1150
1162
  RETURN OLD;
1151
1163
  END IF;
1152
1164
  RETURN NULL;
@@ -1177,22 +1189,19 @@ async function databaseAuditDumpLogTable(sql) {
1177
1189
  }
1178
1190
  function createAuditedSql(sql) {
1179
1191
  let callIndex = 0;
1180
- const requestId = getCurrentRequestId();
1192
+ const rawSql = sql;
1181
1193
  const auditedSql = async (strings, ...values) => {
1182
- const result = await sql(strings, ...values);
1194
+ const requestId = getCurrentRequestId();
1183
1195
  callIndex++;
1184
1196
  if (requestId) {
1185
1197
  try {
1186
- await sql`
1187
- UPDATE audit_log
1188
- SET replay_request_id = ${requestId}, replay_request_call_index = ${callIndex}
1189
- WHERE replay_request_id IS NULL
1190
- `;
1198
+ await rawSql(`SET LOCAL app.replay_request_id = '${requestId.replace(/'/g, "''")}'`);
1199
+ await rawSql(`SET LOCAL app.replay_call_index = '${callIndex}'`);
1191
1200
  } catch (err) {
1192
- console.warn("netlify-recorder: failed to update audit log:", err);
1201
+ console.warn("netlify-recorder: failed to SET LOCAL audit context:", err);
1193
1202
  }
1194
1203
  }
1195
- return result;
1204
+ return await sql(strings, ...values);
1196
1205
  };
1197
1206
  return auditedSql;
1198
1207
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@replayio-app-building/netlify-recorder",
3
- "version": "0.12.0",
3
+ "version": "0.13.0",
4
4
  "description": "Capture and replay Netlify function executions as Replay recordings",
5
5
  "type": "module",
6
6
  "exports": {