@replayio-app-building/netlify-recorder 0.4.0 → 0.6.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
@@ -28,7 +28,7 @@ The Netlify Recorder app (`https://netlify-recorder-bm4wmw.netlify.app`) provide
28
28
 
29
29
  | Variable | Description | How to set |
30
30
  |---|---|---|
31
- | `REPOSITORY_URL` | Your app's git repository URL (e.g. `https://github.com/org/repo.git`) | Set in your deploy script or Netlify site settings |
31
+ | `REPLAY_REPOSITORY_URL` | Your app's git repository URL (e.g. `https://github.com/org/repo.git`) | Set in your deploy script or Netlify site settings |
32
32
  | `COMMIT_SHA` | The git commit hash of the deployed code | Set in your deploy script via `git rev-parse HEAD` |
33
33
  | `BRANCH_NAME` | The git branch of the deployed code | Set in your deploy script via `git rev-parse --abbrev-ref HEAD` |
34
34
 
@@ -59,12 +59,7 @@ import type { Handler } from "@netlify/functions";
59
59
  const RECORDER_URL = "https://netlify-recorder-bm4wmw.netlify.app";
60
60
 
61
61
  const handler: Handler = async (event) => {
62
- const reqContext = startRequest({
63
- method: event.httpMethod,
64
- url: event.path,
65
- headers: event.headers as Record<string, string>,
66
- body: event.body ?? undefined,
67
- });
62
+ const reqContext = startRequest(event);
68
63
 
69
64
  try {
70
65
  const result = await myBusinessLogic();
@@ -143,7 +138,7 @@ If you need full control, you can manage your own blob storage, database, and re
143
138
 
144
139
  ### 1. Set required environment variables
145
140
 
146
- Same as Option A — you must set `REPOSITORY_URL`, `COMMIT_SHA`, and `BRANCH_NAME` on your Netlify site. See the table in Option A, Step 1 above.
141
+ 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.
147
142
 
148
143
  ### 2. Wrap your Netlify function
149
144
 
@@ -154,12 +149,7 @@ import { startRequest, finishRequest } from "@replayio-app-building/netlify-reco
154
149
  import type { Handler } from "@netlify/functions";
155
150
 
156
151
  const handler: Handler = async (event) => {
157
- const reqContext = startRequest({
158
- method: event.httpMethod,
159
- url: event.path,
160
- headers: event.headers as Record<string, string>,
161
- body: event.body ?? undefined,
162
- });
152
+ const reqContext = startRequest(event);
163
153
 
164
154
  try {
165
155
  const result = await myBusinessLogic();
@@ -300,15 +290,12 @@ Self-hosted recording requires these environment variables:
300
290
 
301
291
  ## API Reference
302
292
 
303
- ### `startRequest(requestInfo): RequestContext`
293
+ ### `startRequest(event): RequestContext`
304
294
 
305
- Begins capturing a Netlify handler execution. Patches `globalThis.fetch` and `process.env` to record all outbound network calls and environment variable reads.
295
+ Begins capturing a Netlify handler execution. Accepts the raw Netlify event object and extracts all request metadata internally (method, path, headers, body, query string parameters, etc.). Patches `globalThis.fetch` and `process.env` to record all outbound network calls and environment variable reads.
306
296
 
307
297
  **Parameters:**
308
- - `requestInfo.method` — HTTP method of the incoming request
309
- - `requestInfo.url` — Request URL/path
310
- - `requestInfo.headers` — Request headers
311
- - `requestInfo.body` — Optional request body
298
+ - `event` — The Netlify handler event object (passed directly)
312
299
 
313
300
  **Returns:** A `RequestContext` object to pass to `finishRequest`.
314
301
 
@@ -316,7 +303,7 @@ Begins capturing a Netlify handler execution. Patches `globalThis.fetch` and `pr
316
303
 
317
304
  Finalizes the request capture. Restores original `fetch` and `process.env`, serializes the captured data, uploads it via the callbacks, and returns the response with `X-Replay-Request-Id` header set.
318
305
 
319
- **Requires** the following environment variables (or equivalent options overrides): `COMMIT_SHA`, `BRANCH_NAME`, `REPOSITORY_URL`. Throws if any are missing.
306
+ **Requires** the following environment variables (or equivalent options overrides): `COMMIT_SHA`, `BRANCH_NAME`, `REPLAY_REPOSITORY_URL`. Throws if any are missing.
320
307
 
321
308
  **Parameters:**
322
309
  - `requestContext` — The context returned by `startRequest`
@@ -325,7 +312,7 @@ Finalizes the request capture. Restores original `fetch` and `process.env`, seri
325
312
  - `options.handlerPath` — Path to the handler file (used for recording metadata)
326
313
  - `options.commitSha` — Override `COMMIT_SHA` env var
327
314
  - `options.branchName` — Override `BRANCH_NAME` env var
328
- - `options.repositoryUrl` — Override `REPOSITORY_URL` env var
315
+ - `options.repositoryUrl` — Override `REPLAY_REPOSITORY_URL` env var
329
316
 
330
317
  ### `remoteCallbacks(serviceUrl): FinishRequestCallbacks`
331
318
 
@@ -364,7 +351,7 @@ These must be set on your Netlify site. Your deploy script should resolve them f
364
351
  |---|---|---|
365
352
  | `COMMIT_SHA` | Git commit hash of the deployed code | `git rev-parse HEAD` |
366
353
  | `BRANCH_NAME` | Git branch of the deployed code | `git rev-parse --abbrev-ref HEAD` |
367
- | `REPOSITORY_URL` | Git repository URL (no embedded credentials) | `git remote get-url origin` (strip tokens) |
354
+ | `REPLAY_REPOSITORY_URL` | Git repository URL (no embedded credentials) | `git remote get-url origin` (strip tokens) |
368
355
 
369
356
  ### Required for self-hosted recording (Option B)
370
357
 
package/dist/index.d.ts CHANGED
@@ -1,8 +1,32 @@
1
+ /**
2
+ * Normalized request metadata stored in the blob.
3
+ * Populated internally by startRequest from the Netlify event.
4
+ */
1
5
  interface RequestInfo {
2
6
  method: string;
3
7
  url: string;
4
8
  headers: Record<string, string>;
5
9
  body?: string;
10
+ queryStringParameters?: Record<string, string | undefined> | null;
11
+ multiValueQueryStringParameters?: Record<string, string[] | undefined> | null;
12
+ rawUrl?: string;
13
+ rawQuery?: string;
14
+ isBase64Encoded?: boolean;
15
+ }
16
+ /**
17
+ * Input type for startRequest. Mirrors the Netlify HandlerEvent shape so
18
+ * handlers can simply pass the event object directly.
19
+ */
20
+ interface NetlifyEvent {
21
+ httpMethod: string;
22
+ path: string;
23
+ headers: Record<string, string | undefined>;
24
+ body?: string | null;
25
+ queryStringParameters?: Record<string, string | undefined> | null;
26
+ multiValueQueryStringParameters?: Record<string, string[] | undefined> | null;
27
+ rawUrl?: string;
28
+ rawQuery?: string;
29
+ isBase64Encoded?: boolean;
6
30
  }
7
31
  interface RequestContext {
8
32
  requestInfo: RequestInfo;
@@ -92,6 +116,7 @@ interface EnsureRecordingOptions {
92
116
 
93
117
  /**
94
118
  * Called at the beginning of a Netlify handler execution.
119
+ * Accepts the raw Netlify event and extracts all request metadata internally.
95
120
  * Installs interceptors on globalThis.fetch and process.env to capture
96
121
  * outbound network calls and environment variable reads made by the handler.
97
122
  * Returns a request context used by finishRequest.
@@ -101,7 +126,7 @@ interface EnsureRecordingOptions {
101
126
  * capture interceptors to avoid overwriting the replay layer (which would
102
127
  * also crash on Node v16 where Headers/Response don't exist).
103
128
  */
104
- declare function startRequest(requestInfo: RequestInfo): RequestContext;
129
+ declare function startRequest(event: NetlifyEvent): RequestContext;
105
130
 
106
131
  interface HandlerResponse {
107
132
  statusCode: number;
@@ -114,7 +139,7 @@ interface FinishRequestOptions {
114
139
  commitSha?: string;
115
140
  /** Override BRANCH_NAME env var. */
116
141
  branchName?: string;
117
- /** Override REPOSITORY_URL env var. */
142
+ /** Override REPLAY_REPOSITORY_URL env var. */
118
143
  repositoryUrl?: string;
119
144
  }
120
145
  /**
@@ -227,4 +252,4 @@ interface SpawnRecordingContainerOptions {
227
252
  */
228
253
  declare function spawnRecordingContainer(options: SpawnRecordingContainerOptions): Promise<string>;
229
254
 
230
- export { type BlobData, type CapturedData, type ContainerInfraConfig, type EnsureRecordingOptions, type EnvRead, type FinishRequestCallbacks, type FinishRequestOptions, type NetworkCall, type RequestContext, type RequestInfo, type SpawnRecordingContainerOptions, createRequestRecording, ensureRequestRecording, finishRequest, readInfraConfigFromEnv, redactBlobData, remoteCallbacks, spawnRecordingContainer, startRequest };
255
+ export { type BlobData, type CapturedData, type ContainerInfraConfig, type EnsureRecordingOptions, type EnvRead, type FinishRequestCallbacks, type FinishRequestOptions, type NetlifyEvent, type NetworkCall, type RequestContext, type RequestInfo, type SpawnRecordingContainerOptions, createRequestRecording, ensureRequestRecording, finishRequest, readInfraConfigFromEnv, redactBlobData, remoteCallbacks, spawnRecordingContainer, startRequest };
package/dist/index.js CHANGED
@@ -8,6 +8,7 @@ var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require
8
8
  // src/interceptors/network.ts
9
9
  function installNetworkInterceptor(mode, calls) {
10
10
  const originalFetch = globalThis.fetch;
11
+ let replayCallIndex = 0;
11
12
  if (mode === "capture") {
12
13
  const captureFetch = async (input, init) => {
13
14
  const url = typeof input === "string" ? input : input instanceof URL ? input.href : input.url;
@@ -39,14 +40,17 @@ function installNetworkInterceptor(mode, calls) {
39
40
  };
40
41
  globalThis.fetch = captureFetch;
41
42
  } else {
42
- let callIndex = 0;
43
43
  const replayFetch = async () => {
44
- const call = calls[callIndex++];
44
+ const idx = replayCallIndex++;
45
+ const call = calls[idx];
45
46
  if (!call) {
46
47
  throw new Error(
47
48
  `No more recorded network calls to replay (exhausted ${calls.length} calls)`
48
49
  );
49
50
  }
51
+ console.log(
52
+ ` [network-replay] Consumed call ${idx + 1}/${calls.length}: ${call.method} ${call.url} => ${call.responseStatus}`
53
+ );
50
54
  const body = call.responseBody ?? "";
51
55
  const status = call.responseStatus;
52
56
  return {
@@ -82,6 +86,12 @@ function installNetworkInterceptor(mode, calls) {
82
86
  return {
83
87
  restore() {
84
88
  globalThis.fetch = originalFetch;
89
+ },
90
+ consumedCount() {
91
+ return replayCallIndex;
92
+ },
93
+ totalCount() {
94
+ return calls.length;
85
95
  }
86
96
  };
87
97
  }
@@ -121,7 +131,24 @@ function installEnvironmentInterceptor(mode, reads) {
121
131
  }
122
132
 
123
133
  // src/startRequest.ts
124
- function startRequest(requestInfo) {
134
+ function startRequest(event) {
135
+ const headers = {};
136
+ for (const [key, value] of Object.entries(event.headers)) {
137
+ if (value !== void 0) {
138
+ headers[key] = value;
139
+ }
140
+ }
141
+ const requestInfo = {
142
+ method: event.httpMethod,
143
+ url: event.path,
144
+ headers,
145
+ body: event.body ?? void 0,
146
+ queryStringParameters: event.queryStringParameters ?? null,
147
+ multiValueQueryStringParameters: event.multiValueQueryStringParameters ?? null,
148
+ rawUrl: event.rawUrl,
149
+ rawQuery: event.rawQuery,
150
+ isBase64Encoded: event.isBase64Encoded
151
+ };
125
152
  const capturedData = { networkCalls: [], envReads: [] };
126
153
  const isReplay = globalThis.__REPLAY_RECORDING_MODE__ === true;
127
154
  let cleanup;
@@ -180,6 +207,7 @@ var ENV_ALLOW_LIST = /* @__PURE__ */ new Set([
180
207
  "DEPLOY_ID",
181
208
  "DEPLOY_URL",
182
209
  "REPOSITORY_URL",
210
+ "REPLAY_REPOSITORY_URL",
183
211
  "APP_REPOSITORY_URL",
184
212
  "BRANCH",
185
213
  "HEAD",
@@ -277,11 +305,11 @@ async function finishRequest(requestContext, callbacks, response, options) {
277
305
  }
278
306
  const rawCommitSha = options?.commitSha ?? process.env.COMMIT_SHA;
279
307
  const rawBranchName = options?.branchName ?? process.env.BRANCH_NAME;
280
- const rawRepositoryUrl = options?.repositoryUrl ?? process.env.REPOSITORY_URL;
308
+ const rawRepositoryUrl = options?.repositoryUrl ?? process.env.REPLAY_REPOSITORY_URL;
281
309
  const missing = [];
282
310
  if (!rawCommitSha) missing.push("COMMIT_SHA");
283
311
  if (!rawBranchName) missing.push("BRANCH_NAME");
284
- if (!rawRepositoryUrl) missing.push("REPOSITORY_URL");
312
+ if (!rawRepositoryUrl) missing.push("REPLAY_REPOSITORY_URL");
285
313
  if (missing.length > 0) {
286
314
  throw new Error(
287
315
  `netlify-recorder: missing required configuration: ${missing.join(", ")}. Set these as environment variables on your Netlify site, or pass them via the options parameter. See the package README for setup instructions.`
@@ -653,9 +681,31 @@ async function createRequestRecording(blobUrlOrData, handlerPath, requestInfo) {
653
681
  httpMethod: requestInfo.method,
654
682
  path: requestInfo.url,
655
683
  headers: requestInfo.headers,
656
- body: requestInfo.body ?? null
684
+ body: requestInfo.body ?? null,
685
+ queryStringParameters: requestInfo.queryStringParameters ?? null,
686
+ multiValueQueryStringParameters: requestInfo.multiValueQueryStringParameters ?? null,
687
+ rawUrl: requestInfo.rawUrl ?? requestInfo.url,
688
+ rawQuery: requestInfo.rawQuery ?? "",
689
+ isBase64Encoded: requestInfo.isBase64Encoded ?? false
657
690
  });
658
691
  } finally {
692
+ const consumed = networkHandle.consumedCount();
693
+ const total = networkHandle.totalCount();
694
+ if (consumed < total) {
695
+ console.error(
696
+ `ERROR: Not all recorded network calls were consumed during replay. Consumed ${consumed} of ${total} calls. ${total - consumed} call(s) were never replayed.`
697
+ );
698
+ for (let i = consumed; i < total; i++) {
699
+ const call = blobData.capturedData.networkCalls[i];
700
+ if (call) {
701
+ console.error(
702
+ ` Unconsumed call ${i + 1}/${total}: ${call.method} ${call.url} => ${call.responseStatus}`
703
+ );
704
+ }
705
+ }
706
+ } else {
707
+ console.log(` [network-replay] All ${total} recorded network call(s) were consumed.`);
708
+ }
659
709
  globalThis.__REPLAY_RECORDING_MODE__ = false;
660
710
  networkHandle.restore();
661
711
  envHandle.restore();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@replayio-app-building/netlify-recorder",
3
- "version": "0.4.0",
3
+ "version": "0.6.0",
4
4
  "description": "Capture and replay Netlify function executions as Replay recordings",
5
5
  "type": "module",
6
6
  "exports": {