@replayio-app-building/netlify-recorder 0.12.0 → 0.14.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 +8 -7
- package/dist/index.js +29 -16
- package/package.json +1 -1
package/dist/index.d.ts
CHANGED
|
@@ -442,13 +442,14 @@ 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
|
|
446
|
-
*
|
|
447
|
-
*
|
|
445
|
+
* Wraps a Neon SQL tagged-template function so that each query runs
|
|
446
|
+
* inside a transaction that first calls `set_config()` to inject the
|
|
447
|
+
* current request ID and call index. The `audit_trigger_function`
|
|
448
|
+
* reads these via `current_setting()` and stamps audit rows atomically
|
|
449
|
+
* — no separate UPDATE required.
|
|
448
450
|
*
|
|
449
|
-
*
|
|
450
|
-
* `
|
|
451
|
-
* wrapper passes calls through without tagging.
|
|
451
|
+
* Requires the Neon HTTP driver's `.transaction()` method so that
|
|
452
|
+
* `set_config` and the user's query share one transaction.
|
|
452
453
|
*
|
|
453
454
|
* @example
|
|
454
455
|
* ```ts
|
|
@@ -456,7 +457,7 @@ declare function databaseAuditDumpLogTable(sql: SqlFunction): Promise<Record<str
|
|
|
456
457
|
*
|
|
457
458
|
* const sql = createAuditedSql(getSql());
|
|
458
459
|
* await sql`INSERT INTO haikus (text) VALUES (${haiku})`;
|
|
459
|
-
* // audit_log
|
|
460
|
+
* // audit_log row created by the trigger already has the request ID
|
|
460
461
|
* ```
|
|
461
462
|
*/
|
|
462
463
|
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,23 @@ async function databaseAuditDumpLogTable(sql) {
|
|
|
1177
1189
|
}
|
|
1178
1190
|
function createAuditedSql(sql) {
|
|
1179
1191
|
let callIndex = 0;
|
|
1180
|
-
const
|
|
1192
|
+
const sqlWithTx = sql;
|
|
1181
1193
|
const auditedSql = async (strings, ...values) => {
|
|
1182
|
-
const
|
|
1194
|
+
const requestId = getCurrentRequestId();
|
|
1183
1195
|
callIndex++;
|
|
1184
|
-
if (requestId) {
|
|
1196
|
+
if (requestId && typeof sqlWithTx.transaction === "function") {
|
|
1185
1197
|
try {
|
|
1186
|
-
await
|
|
1187
|
-
|
|
1188
|
-
|
|
1189
|
-
|
|
1190
|
-
|
|
1198
|
+
const results = await sqlWithTx.transaction([
|
|
1199
|
+
sql`SELECT set_config('app.replay_request_id', ${requestId}, true)`,
|
|
1200
|
+
sql`SELECT set_config('app.replay_call_index', ${String(callIndex)}, true)`,
|
|
1201
|
+
sql(strings, ...values)
|
|
1202
|
+
]);
|
|
1203
|
+
return results[2];
|
|
1191
1204
|
} catch (err) {
|
|
1192
|
-
console.warn("netlify-recorder:
|
|
1205
|
+
console.warn("netlify-recorder: audited transaction failed, falling back:", err);
|
|
1193
1206
|
}
|
|
1194
1207
|
}
|
|
1195
|
-
return
|
|
1208
|
+
return await sql(strings, ...values);
|
|
1196
1209
|
};
|
|
1197
1210
|
return auditedSql;
|
|
1198
1211
|
}
|