@replayio-app-building/netlify-recorder 0.15.10 → 0.17.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 +11 -203
- package/dist/index.d.ts +1 -1
- package/dist/index.js +5 -28
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# @replayio-app-building/netlify-recorder
|
|
2
2
|
|
|
3
|
-
Capture and replay Netlify function executions as [Replay](https://replay.io) recordings. This package intercepts outbound network calls and environment variable reads during handler execution, stores the captured data
|
|
3
|
+
Capture and replay Netlify function executions as [Replay](https://replay.io) recordings. This package intercepts outbound network calls and environment variable reads during handler execution, stores the captured data via the Netlify Recorder service, and can later reproduce the exact execution as a Replay recording for debugging and analysis.
|
|
4
4
|
|
|
5
5
|
## Installation
|
|
6
6
|
|
|
@@ -8,23 +8,11 @@ Capture and replay Netlify function executions as [Replay](https://replay.io) re
|
|
|
8
8
|
npm install @replayio-app-building/netlify-recorder
|
|
9
9
|
```
|
|
10
10
|
|
|
11
|
-
##
|
|
12
|
-
|
|
13
|
-
There are two ways to use this package:
|
|
14
|
-
|
|
15
|
-
- **Option A: Use the Netlify Recorder service (recommended)** — The service handles blob storage, request tracking, and recording creation. Your app just wraps its handlers and calls `remoteCallbacks()`. No database or container infrastructure needed.
|
|
16
|
-
|
|
17
|
-
- **Option B: Self-hosted** — You manage your own blob storage, database tables, and recording containers. Full control, but requires more setup.
|
|
18
|
-
|
|
19
|
-
---
|
|
20
|
-
|
|
21
|
-
## Option A: Using the Netlify Recorder Service
|
|
22
|
-
|
|
23
|
-
The Netlify Recorder app (`https://netlify-recorder-bm4wmw.netlify.app`) provides a hosted service that stores captured request data, manages a pool of recording containers, and creates Replay recordings on demand. Your app needs zero database or infrastructure setup.
|
|
11
|
+
## Setup
|
|
24
12
|
|
|
25
13
|
### 1. Set required environment variables
|
|
26
14
|
|
|
27
|
-
`finishRequest` needs to know which repository, branch, and commit your deployed code belongs to. Set these
|
|
15
|
+
`finishRequest` needs to know which repository, branch, and commit your deployed code belongs to. Set these environment variables on your Netlify site:
|
|
28
16
|
|
|
29
17
|
| Variable | Description | How to set |
|
|
30
18
|
|---|---|---|
|
|
@@ -115,7 +103,7 @@ export default createRecordingRequestHandler(
|
|
|
115
103
|
|
|
116
104
|
### 3. Create recordings
|
|
117
105
|
|
|
118
|
-
When you want to turn a captured request into a Replay recording, POST to the service's `create-recording` endpoint with the request ID. If the request was created with a secret, you must include it:
|
|
106
|
+
When you want to turn a captured request into a Replay recording, POST to the Netlify Recorder service's `create-recording` endpoint with the request ID. If the request was created with a secret, you must include it:
|
|
119
107
|
|
|
120
108
|
```typescript
|
|
121
109
|
const response = await fetch(
|
|
@@ -132,7 +120,7 @@ const response = await fetch(
|
|
|
132
120
|
);
|
|
133
121
|
```
|
|
134
122
|
|
|
135
|
-
The service looks up the stored blob data, dispatches the work to a
|
|
123
|
+
The service looks up the stored blob data, dispatches the work to a recording container, and creates the recording.
|
|
136
124
|
|
|
137
125
|
If `webhookUrl` is provided, the service will POST the result to that URL when the recording completes (or fails):
|
|
138
126
|
|
|
@@ -225,7 +213,7 @@ Requests created without a secret remain accessible without one (backward compat
|
|
|
225
213
|
|
|
226
214
|
Store your secret as a `NETLIFY_RECORDER_SECRET` environment variable. To keep it secure:
|
|
227
215
|
|
|
228
|
-
1. **Netlify site environment:** Add `NETLIFY_RECORDER_SECRET` in your Netlify site's environment variables (Site settings
|
|
216
|
+
1. **Netlify site environment:** Add `NETLIFY_RECORDER_SECRET` in your Netlify site's environment variables (Site settings > Environment variables). This makes it available to all deployed functions.
|
|
229
217
|
|
|
230
218
|
2. **Branch-level secret** (for CI/development): If you're using the Netlify Recorder agent infrastructure, store the secret as a branch secret so deploy scripts and background agents can access it:
|
|
231
219
|
|
|
@@ -239,158 +227,6 @@ Use a strong random string (e.g. `openssl rand -base64 32`) and rotate it if com
|
|
|
239
227
|
|
|
240
228
|
---
|
|
241
229
|
|
|
242
|
-
## Option B: Self-Hosted
|
|
243
|
-
|
|
244
|
-
If you need full control, you can manage your own blob storage, database, and recording containers. This requires more setup but gives you complete ownership of the data pipeline.
|
|
245
|
-
|
|
246
|
-
### 1. Set required environment variables
|
|
247
|
-
|
|
248
|
-
Same as Option A — you must set `REPLAY_REPOSITORY_URL`, `COMMIT_SHA`, and `BRANCH_NAME` on your Netlify site. See the table in Option A, Step 1 above.
|
|
249
|
-
|
|
250
|
-
### 2. Wrap your Netlify function
|
|
251
|
-
|
|
252
|
-
Use `createRecordingRequestHandler` with custom callbacks that write to your own storage and database:
|
|
253
|
-
|
|
254
|
-
```typescript
|
|
255
|
-
import { createRecordingRequestHandler } from "@replayio-app-building/netlify-recorder";
|
|
256
|
-
|
|
257
|
-
const handler = createRecordingRequestHandler(
|
|
258
|
-
async (event) => {
|
|
259
|
-
const result = await myBusinessLogic();
|
|
260
|
-
|
|
261
|
-
return {
|
|
262
|
-
statusCode: 200,
|
|
263
|
-
headers: { "Content-Type": "application/json" },
|
|
264
|
-
body: JSON.stringify(result),
|
|
265
|
-
};
|
|
266
|
-
},
|
|
267
|
-
{
|
|
268
|
-
secret: process.env.NETLIFY_RECORDER_SECRET,
|
|
269
|
-
callbacks: {
|
|
270
|
-
uploadBlob: async (data) => {
|
|
271
|
-
// Upload the JSON string to your blob storage (S3, R2, etc.)
|
|
272
|
-
const res = await fetch("https://storage.example.com/upload", {
|
|
273
|
-
method: "PUT",
|
|
274
|
-
body: data,
|
|
275
|
-
});
|
|
276
|
-
const { url } = await res.json();
|
|
277
|
-
return url;
|
|
278
|
-
},
|
|
279
|
-
storeRequestData: async ({ blobUrl, commitSha, branchName, repositoryUrl, handlerPath, secret }) => {
|
|
280
|
-
const [row] = await sql`
|
|
281
|
-
INSERT INTO requests (blob_url, commit_sha, branch_name, repository_url, handler_path, secret, status)
|
|
282
|
-
VALUES (${blobUrl}, ${commitSha}, ${branchName}, ${repositoryUrl}, ${handlerPath}, ${secret}, 'captured')
|
|
283
|
-
RETURNING id
|
|
284
|
-
`;
|
|
285
|
-
return row.id;
|
|
286
|
-
},
|
|
287
|
-
},
|
|
288
|
-
}
|
|
289
|
-
);
|
|
290
|
-
|
|
291
|
-
export { handler };
|
|
292
|
-
```
|
|
293
|
-
|
|
294
|
-
### 3. Create a requests database table
|
|
295
|
-
|
|
296
|
-
```sql
|
|
297
|
-
CREATE TABLE IF NOT EXISTS requests (
|
|
298
|
-
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
299
|
-
blob_url TEXT,
|
|
300
|
-
commit_sha TEXT,
|
|
301
|
-
branch_name TEXT,
|
|
302
|
-
repository_url TEXT,
|
|
303
|
-
handler_path TEXT,
|
|
304
|
-
secret TEXT,
|
|
305
|
-
recording_id TEXT,
|
|
306
|
-
status TEXT NOT NULL DEFAULT 'captured'
|
|
307
|
-
CHECK (status IN ('captured', 'processing', 'recorded', 'failed')),
|
|
308
|
-
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
|
309
|
-
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
|
310
|
-
);
|
|
311
|
-
|
|
312
|
-
CREATE INDEX IF NOT EXISTS idx_requests_secret ON requests (secret) WHERE secret IS NOT NULL;
|
|
313
|
-
```
|
|
314
|
-
|
|
315
|
-
### 4. Create a background function to produce recordings
|
|
316
|
-
|
|
317
|
-
```typescript
|
|
318
|
-
import { ensureRequestRecording } from "@replayio-app-building/netlify-recorder";
|
|
319
|
-
import type { Handler } from "@netlify/functions";
|
|
320
|
-
|
|
321
|
-
const handler: Handler = async (event) => {
|
|
322
|
-
const { requestId } = JSON.parse(event.body ?? "{}");
|
|
323
|
-
|
|
324
|
-
const recordingId = await ensureRequestRecording(requestId, {
|
|
325
|
-
repositoryUrl: process.env.APP_REPOSITORY_URL!,
|
|
326
|
-
lookupRequest: async (id) => {
|
|
327
|
-
const [row] = await sql`
|
|
328
|
-
SELECT blob_url, commit_sha, branch_name, handler_path
|
|
329
|
-
FROM requests WHERE id = ${id}
|
|
330
|
-
`;
|
|
331
|
-
return {
|
|
332
|
-
blobUrl: row.blob_url,
|
|
333
|
-
commitSha: row.commit_sha,
|
|
334
|
-
branchName: row.branch_name ?? "main",
|
|
335
|
-
handlerPath: row.handler_path,
|
|
336
|
-
};
|
|
337
|
-
},
|
|
338
|
-
updateStatus: async (id, status, recordingId) => {
|
|
339
|
-
await sql`
|
|
340
|
-
UPDATE requests
|
|
341
|
-
SET status = ${status},
|
|
342
|
-
recording_id = ${recordingId ?? null},
|
|
343
|
-
updated_at = NOW()
|
|
344
|
-
WHERE id = ${id}
|
|
345
|
-
`;
|
|
346
|
-
},
|
|
347
|
-
});
|
|
348
|
-
|
|
349
|
-
return {
|
|
350
|
-
statusCode: 200,
|
|
351
|
-
body: JSON.stringify({ recordingId }),
|
|
352
|
-
};
|
|
353
|
-
};
|
|
354
|
-
|
|
355
|
-
export { handler };
|
|
356
|
-
```
|
|
357
|
-
|
|
358
|
-
### 5. Create a container script
|
|
359
|
-
|
|
360
|
-
This script runs inside the recording container under `replay-node`:
|
|
361
|
-
|
|
362
|
-
```typescript
|
|
363
|
-
// scripts/create-request-recording.ts
|
|
364
|
-
import { createRequestRecording } from "@replayio-app-building/netlify-recorder";
|
|
365
|
-
|
|
366
|
-
const args = process.argv.slice(2);
|
|
367
|
-
const blobUrl = args[args.indexOf("--blob-url") + 1]!;
|
|
368
|
-
const handlerPath = args[args.indexOf("--handler-path") + 1]!;
|
|
369
|
-
|
|
370
|
-
await createRequestRecording(blobUrl, handlerPath, {
|
|
371
|
-
method: "POST",
|
|
372
|
-
url: handlerPath,
|
|
373
|
-
headers: {},
|
|
374
|
-
});
|
|
375
|
-
```
|
|
376
|
-
|
|
377
|
-
### Required infrastructure
|
|
378
|
-
|
|
379
|
-
Self-hosted recording requires these environment variables:
|
|
380
|
-
|
|
381
|
-
| Variable | Description |
|
|
382
|
-
|---|---|
|
|
383
|
-
| `INFISICAL_CLIENT_ID` | Infisical service account client ID |
|
|
384
|
-
| `INFISICAL_CLIENT_SECRET` | Infisical service account client secret |
|
|
385
|
-
| `INFISICAL_PROJECT_ID` | Infisical project ID |
|
|
386
|
-
| `INFISICAL_ENVIRONMENT` | Infisical environment (e.g. `production`) |
|
|
387
|
-
| `FLY_API_TOKEN` | Fly.io API token for container management |
|
|
388
|
-
| `FLY_APP_NAME` | Fly.io app name for container deployment |
|
|
389
|
-
| `APP_REPOSITORY_URL` | Git repository URL for container cloning |
|
|
390
|
-
| `RECORD_REPLAY_API_KEY` | Replay API key for recording upload |
|
|
391
|
-
|
|
392
|
-
---
|
|
393
|
-
|
|
394
230
|
## Audit Log Support
|
|
395
231
|
|
|
396
232
|
The package automatically tracks database mutations (INSERT, UPDATE, DELETE) in an `audit_log` table and links each change to the Replay request that caused it. When your handler is wrapped with `createRecordingRequestHandler`, all Neon SQL queries are automatically tagged with the request ID — no changes to your SQL code required.
|
|
@@ -484,7 +320,7 @@ When `context.waitUntil` is not available (v1 handlers or missing context), the
|
|
|
484
320
|
|
|
485
321
|
**Parameters:**
|
|
486
322
|
- `handler` — Your async handler function `(event, context?) => Promise<{ statusCode, headers?, body? }>`
|
|
487
|
-
- `options.callbacks` —
|
|
323
|
+
- `options.callbacks` — `remoteCallbacks(serviceUrl)` for sending captured data to the Netlify Recorder service
|
|
488
324
|
- `options.handlerPath` — Path to the handler file (used for recording metadata)
|
|
489
325
|
- `options.commitSha` — Override `COMMIT_SHA` env var
|
|
490
326
|
- `options.branchName` — Override `BRANCH_NAME` env var
|
|
@@ -520,7 +356,7 @@ Logs a `console.warn` when the total duration exceeds 2 seconds or when individu
|
|
|
520
356
|
|
|
521
357
|
**Parameters:**
|
|
522
358
|
- `requestContext` — The context returned by `startRequest`
|
|
523
|
-
- `callbacks` —
|
|
359
|
+
- `callbacks` — `remoteCallbacks(serviceUrl)` for sending captured data to the Netlify Recorder service
|
|
524
360
|
- `response` — The handler's response object (`{ statusCode, headers?, body? }`)
|
|
525
361
|
- `options.handlerPath` — Path to the handler file (used for recording metadata)
|
|
526
362
|
- `options.commitSha` — Override `COMMIT_SHA` env var
|
|
@@ -531,30 +367,11 @@ Logs a `console.warn` when the total duration exceeds 2 seconds or when individu
|
|
|
531
367
|
|
|
532
368
|
### `remoteCallbacks(serviceUrl): FinishRequestCallbacks`
|
|
533
369
|
|
|
534
|
-
Creates callbacks for `finishRequest` that send captured data to the Netlify Recorder service. The service handles blob storage and request tracking
|
|
370
|
+
Creates callbacks for `finishRequest` that send captured data to the Netlify Recorder service. The service handles blob storage and request tracking.
|
|
535
371
|
|
|
536
372
|
**Parameters:**
|
|
537
373
|
- `serviceUrl` — Base URL of the Netlify Recorder service (e.g. `"https://netlify-recorder-bm4wmw.netlify.app"`)
|
|
538
374
|
|
|
539
|
-
### `ensureRequestRecording(requestId, options): Promise<string>`
|
|
540
|
-
|
|
541
|
-
Spawns a container via `@replayio/app-building` to create a Replay recording from captured request data. Returns the recording ID. Only needed for self-hosted setups (Option B).
|
|
542
|
-
|
|
543
|
-
**Parameters:**
|
|
544
|
-
- `requestId` — The request to create a recording for
|
|
545
|
-
- `options.repositoryUrl` — Git repository URL for the container to clone
|
|
546
|
-
- `options.lookupRequest(id)` — Fetches `{ blobUrl, commitSha, branchName, handlerPath }` from the database
|
|
547
|
-
- `options.updateStatus(id, status, recordingId?)` — Updates the request status in the database
|
|
548
|
-
|
|
549
|
-
### `createRequestRecording(blobUrl, handlerPath, requestInfo): Promise<void>`
|
|
550
|
-
|
|
551
|
-
Called inside a container running under `replay-node`. Downloads the captured data blob, installs replay-mode interceptors (which return pre-recorded responses instead of making real calls), and executes the original handler so replay-node can record the execution. Only needed for self-hosted setups (Option B).
|
|
552
|
-
|
|
553
|
-
**Parameters:**
|
|
554
|
-
- `blobUrl` — URL to the captured data blob
|
|
555
|
-
- `handlerPath` — Path to the handler module to execute
|
|
556
|
-
- `requestInfo` — The original request info to replay
|
|
557
|
-
|
|
558
375
|
### `databaseAuditEnsureLogTable(sql): Promise<void>`
|
|
559
376
|
|
|
560
377
|
Creates the `audit_log` table, its indexes, and a reusable PL/pgSQL trigger function (`audit_trigger_function`). Call once during schema initialization.
|
|
@@ -579,8 +396,6 @@ Returns all rows from the `audit_log` table, ordered by `performed_at` DESC.
|
|
|
579
396
|
|
|
580
397
|
## Environment Variables
|
|
581
398
|
|
|
582
|
-
### Required for all setups (read by `finishRequest`)
|
|
583
|
-
|
|
584
399
|
These must be set on your Netlify site. Your deploy script should resolve them from git and push them to the Netlify environment before deploying. `finishRequest` will throw an error if any are missing.
|
|
585
400
|
|
|
586
401
|
| Variable | Description | How to resolve |
|
|
@@ -590,15 +405,8 @@ These must be set on your Netlify site. Your deploy script should resolve them f
|
|
|
590
405
|
| `REPLAY_REPOSITORY_URL` | Git repository URL (no embedded credentials) | `git remote get-url origin` (strip tokens) |
|
|
591
406
|
| `NETLIFY_RECORDER_SECRET` | Secret for access control (strongly recommended) | `openssl rand -base64 32` — store in Netlify site env vars |
|
|
592
407
|
|
|
593
|
-
### Required for self-hosted recording (Option B)
|
|
594
|
-
|
|
595
|
-
| Variable | Description |
|
|
596
|
-
|---|---|
|
|
597
|
-
| `RECORD_REPLAY_API_KEY` | Replay API key for uploading recordings |
|
|
598
|
-
| `APP_REPOSITORY_URL` | Git repository URL for container cloning |
|
|
599
|
-
|
|
600
408
|
## How It Works
|
|
601
409
|
|
|
602
|
-
1. **Capture phase** (`createRecordingRequestHandler` or `startRequest` / `finishRequest`): When a Netlify function handles a request, the recording layer patches `globalThis.fetch` and `process.env` with Proxies that record every outbound network call and environment variable read. When the handler completes, the originals are restored, the captured data is serialized to JSON, and sent to
|
|
410
|
+
1. **Capture phase** (`createRecordingRequestHandler` or `startRequest` / `finishRequest`): When a Netlify function handles a request, the recording layer patches `globalThis.fetch` and `process.env` with Proxies that record every outbound network call and environment variable read. When the handler completes, the originals are restored, the captured data is serialized to JSON, and sent to the Netlify Recorder service via `remoteCallbacks`.
|
|
603
411
|
|
|
604
|
-
2. **Recording phase**: The
|
|
412
|
+
2. **Recording phase**: The Netlify Recorder service dispatches the captured data to a recording container. Inside the container, the captured blob is downloaded, replay-mode interceptors return the pre-recorded responses instead of making real calls, and the original handler is re-executed under `replay-node`. Since replay-node records all execution, this produces a Replay recording of the exact same handler execution.
|
package/dist/index.d.ts
CHANGED
|
@@ -312,7 +312,7 @@ interface CreateRecordingRequestHandlerOptions extends FinishRequestOptions {
|
|
|
312
312
|
* );
|
|
313
313
|
* ```
|
|
314
314
|
*/
|
|
315
|
-
declare function createRecordingRequestHandler(handler: (event: NetlifyEvent | NetlifyV2Request, context?: unknown) => Promise<HandlerResponse>, options: CreateRecordingRequestHandlerOptions): (event: NetlifyEvent | NetlifyV2Request, context?: unknown) => Promise<HandlerResponse
|
|
315
|
+
declare function createRecordingRequestHandler(handler: (event: NetlifyEvent | NetlifyV2Request, context?: unknown) => Promise<HandlerResponse>, options: CreateRecordingRequestHandlerOptions): (event: NetlifyEvent | NetlifyV2Request, context?: unknown) => Promise<HandlerResponse>;
|
|
316
316
|
|
|
317
317
|
/**
|
|
318
318
|
* Creates `FinishRequestCallbacks` that send captured data to a remote
|
package/dist/index.js
CHANGED
|
@@ -578,7 +578,6 @@ async function finishRequest(requestContext, callbacks, response, options) {
|
|
|
578
578
|
import crypto from "crypto";
|
|
579
579
|
function createRecordingRequestHandler(handler, options) {
|
|
580
580
|
return async (event, context) => {
|
|
581
|
-
const isV2 = isWebApiRequest(event);
|
|
582
581
|
const requestId = crypto.randomUUID();
|
|
583
582
|
setCurrentRequestId(requestId);
|
|
584
583
|
const reqContext = startRequest(event);
|
|
@@ -612,18 +611,12 @@ function createRecordingRequestHandler(handler, options) {
|
|
|
612
611
|
}
|
|
613
612
|
)
|
|
614
613
|
);
|
|
615
|
-
return
|
|
614
|
+
return responseWithHeader;
|
|
616
615
|
}
|
|
617
616
|
await finishRequest(reqContext, options.callbacks, response, finishOpts);
|
|
618
|
-
return
|
|
617
|
+
return responseWithHeader;
|
|
619
618
|
};
|
|
620
619
|
}
|
|
621
|
-
function toWebResponse(result) {
|
|
622
|
-
return new Response(result.body, {
|
|
623
|
-
status: result.statusCode,
|
|
624
|
-
headers: result.headers
|
|
625
|
-
});
|
|
626
|
-
}
|
|
627
620
|
|
|
628
621
|
// src/remoteCallbacks.ts
|
|
629
622
|
function remoteCallbacks(serviceUrl) {
|
|
@@ -1236,23 +1229,7 @@ async function databaseAuditEnsureLogTable(sql) {
|
|
|
1236
1229
|
changed_cols TEXT[];
|
|
1237
1230
|
req_id TEXT;
|
|
1238
1231
|
call_idx INTEGER;
|
|
1239
|
-
pk_col TEXT;
|
|
1240
|
-
pk_val TEXT;
|
|
1241
1232
|
BEGIN
|
|
1242
|
-
-- Dynamically look up the primary key column for this table
|
|
1243
|
-
SELECT a.attname INTO pk_col
|
|
1244
|
-
FROM pg_index i
|
|
1245
|
-
JOIN pg_attribute a ON a.attrelid = i.indrelid AND a.attnum = ANY(i.indkey)
|
|
1246
|
-
WHERE i.indrelid = TG_RELID AND i.indisprimary
|
|
1247
|
-
LIMIT 1;
|
|
1248
|
-
|
|
1249
|
-
-- Extract PK value from the appropriate record
|
|
1250
|
-
IF TG_OP = 'DELETE' THEN
|
|
1251
|
-
EXECUTE format('SELECT ($1.%I)::TEXT', pk_col) INTO pk_val USING OLD;
|
|
1252
|
-
ELSE
|
|
1253
|
-
EXECUTE format('SELECT ($1.%I)::TEXT', pk_col) INTO pk_val USING NEW;
|
|
1254
|
-
END IF;
|
|
1255
|
-
|
|
1256
1233
|
-- Read application context injected by the network interceptor
|
|
1257
1234
|
req_id := COALESCE(current_setting('app.replay_request_id', true), '');
|
|
1258
1235
|
IF req_id = '' THEN req_id := NULL; END IF;
|
|
@@ -1265,7 +1242,7 @@ async function databaseAuditEnsureLogTable(sql) {
|
|
|
1265
1242
|
|
|
1266
1243
|
IF TG_OP = 'INSERT' THEN
|
|
1267
1244
|
INSERT INTO audit_log (table_name, record_id, action, new_data, replay_request_id, replay_request_call_index)
|
|
1268
|
-
VALUES (TG_TABLE_NAME,
|
|
1245
|
+
VALUES (TG_TABLE_NAME, NEW.id::TEXT, 'INSERT', to_jsonb(NEW), req_id, call_idx);
|
|
1269
1246
|
RETURN NEW;
|
|
1270
1247
|
ELSIF TG_OP = 'UPDATE' THEN
|
|
1271
1248
|
SELECT ARRAY_AGG(n.key) INTO changed_cols
|
|
@@ -1274,11 +1251,11 @@ async function databaseAuditEnsureLogTable(sql) {
|
|
|
1274
1251
|
WHERE o.value IS DISTINCT FROM n.value;
|
|
1275
1252
|
|
|
1276
1253
|
INSERT INTO audit_log (table_name, record_id, action, old_data, new_data, changed_fields, replay_request_id, replay_request_call_index)
|
|
1277
|
-
VALUES (TG_TABLE_NAME,
|
|
1254
|
+
VALUES (TG_TABLE_NAME, NEW.id::TEXT, 'UPDATE', to_jsonb(OLD), to_jsonb(NEW), COALESCE(changed_cols, ARRAY[]::TEXT[]), req_id, call_idx);
|
|
1278
1255
|
RETURN NEW;
|
|
1279
1256
|
ELSIF TG_OP = 'DELETE' THEN
|
|
1280
1257
|
INSERT INTO audit_log (table_name, record_id, action, old_data, replay_request_id, replay_request_call_index)
|
|
1281
|
-
VALUES (TG_TABLE_NAME,
|
|
1258
|
+
VALUES (TG_TABLE_NAME, OLD.id::TEXT, 'DELETE', to_jsonb(OLD), req_id, call_idx);
|
|
1282
1259
|
RETURN OLD;
|
|
1283
1260
|
END IF;
|
|
1284
1261
|
RETURN NULL;
|