@replayio-app-building/netlify-recorder 0.44.0 → 0.45.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 +10 -1
- package/dist/index.js +100 -47
- package/package.json +1 -1
package/dist/index.d.ts
CHANGED
|
@@ -121,6 +121,8 @@ interface FinishRequestCallbacks {
|
|
|
121
121
|
handlerPath: string;
|
|
122
122
|
/** Pre-generated request ID. Use as the row ID when provided. */
|
|
123
123
|
requestId?: string;
|
|
124
|
+
/** ID of the first request handled by this module instance (warm-start linkage). */
|
|
125
|
+
originalRequestId?: string;
|
|
124
126
|
}) => Promise<string>;
|
|
125
127
|
}
|
|
126
128
|
|
|
@@ -250,8 +252,13 @@ interface RecordingResult {
|
|
|
250
252
|
*
|
|
251
253
|
* Accepts either a blob URL (fetched at runtime) or pre-parsed BlobData (avoids needing
|
|
252
254
|
* globalThis.fetch, which is missing in replay-node's Node v16 environment).
|
|
255
|
+
*
|
|
256
|
+
* When `precedingBlobs` is provided, the handler is executed once for each preceding
|
|
257
|
+
* blob (in order) before the target request. This replays prior requests on the same
|
|
258
|
+
* module instance to populate module-level state (e.g. in-memory caches) that would
|
|
259
|
+
* have existed on a warm Lambda container in production.
|
|
253
260
|
*/
|
|
254
|
-
declare function createRequestRecording(blobUrlOrData: string | BlobData, handlerPath: string, requestInfo: RequestInfo): Promise<RecordingResult>;
|
|
261
|
+
declare function createRequestRecording(blobUrlOrData: string | BlobData, handlerPath: string, requestInfo: RequestInfo, precedingBlobs?: BlobData[]): Promise<RecordingResult>;
|
|
255
262
|
|
|
256
263
|
type SqlFunction$2 = (...args: any[]) => Promise<any[]>;
|
|
257
264
|
interface BackendRequest {
|
|
@@ -261,6 +268,7 @@ interface BackendRequest {
|
|
|
261
268
|
commit_sha: string;
|
|
262
269
|
branch_name: string;
|
|
263
270
|
repository_url: string | null;
|
|
271
|
+
original_request_id: string | null;
|
|
264
272
|
status: string;
|
|
265
273
|
recording_id: string | null;
|
|
266
274
|
error_message: string | null;
|
|
@@ -282,6 +290,7 @@ declare function backendRequestsInsert(sql: SqlFunction$2, data: {
|
|
|
282
290
|
commitSha: string;
|
|
283
291
|
branchName: string;
|
|
284
292
|
repositoryUrl?: string | null;
|
|
293
|
+
originalRequestId?: string | null;
|
|
285
294
|
}): Promise<string>;
|
|
286
295
|
declare function backendRequestsGet(sql: SqlFunction$2, id: string): Promise<BackendRequest | null>;
|
|
287
296
|
declare function backendRequestsGetBlobUrl(sql: SqlFunction$2, id: string): Promise<string | null>;
|
package/dist/index.js
CHANGED
|
@@ -742,7 +742,8 @@ async function finishRequest(requestContext, callbacks, response, options) {
|
|
|
742
742
|
branchName,
|
|
743
743
|
repositoryUrl,
|
|
744
744
|
handlerPath,
|
|
745
|
-
requestId: options?.requestId
|
|
745
|
+
requestId: options?.requestId,
|
|
746
|
+
originalRequestId: options?.originalRequestId
|
|
746
747
|
});
|
|
747
748
|
const storeDuration = Date.now() - storeStart;
|
|
748
749
|
const totalDuration = Date.now() - finishStart;
|
|
@@ -859,7 +860,7 @@ function createRecordingRequestHandler(handler, options) {
|
|
|
859
860
|
}
|
|
860
861
|
|
|
861
862
|
// src/createRequestRecording.ts
|
|
862
|
-
async function createRequestRecording(blobUrlOrData, handlerPath, requestInfo) {
|
|
863
|
+
async function createRequestRecording(blobUrlOrData, handlerPath, requestInfo, precedingBlobs) {
|
|
863
864
|
let blobData;
|
|
864
865
|
if (typeof blobUrlOrData === "string") {
|
|
865
866
|
const response = await fetch(blobUrlOrData);
|
|
@@ -1032,56 +1033,98 @@ async function createRequestRecording(blobUrlOrData, handlerPath, requestInfo) {
|
|
|
1032
1033
|
g.Response = ResponseShim;
|
|
1033
1034
|
}
|
|
1034
1035
|
const result = { responseMismatch: false, unconsumedNetworkCalls: false };
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1036
|
+
const handlerModule = await import(handlerPath);
|
|
1037
|
+
let handler;
|
|
1038
|
+
let isV2 = false;
|
|
1039
|
+
let current = handlerModule;
|
|
1040
|
+
for (let depth = 0; depth < 6 && current && typeof current === "object"; depth++) {
|
|
1041
|
+
const obj = current;
|
|
1042
|
+
if (typeof obj.handler === "function") {
|
|
1043
|
+
handler = obj.handler;
|
|
1044
|
+
isV2 = false;
|
|
1045
|
+
break;
|
|
1046
|
+
}
|
|
1047
|
+
if (typeof obj.default === "function") {
|
|
1048
|
+
handler = obj.default;
|
|
1049
|
+
isV2 = true;
|
|
1050
|
+
break;
|
|
1051
|
+
}
|
|
1052
|
+
if (obj.default && typeof obj.default === "object") {
|
|
1053
|
+
current = obj.default;
|
|
1054
|
+
continue;
|
|
1055
|
+
}
|
|
1056
|
+
break;
|
|
1057
|
+
}
|
|
1058
|
+
if (!handler && typeof handlerModule === "function") {
|
|
1059
|
+
handler = handlerModule;
|
|
1060
|
+
isV2 = true;
|
|
1061
|
+
}
|
|
1062
|
+
if (!handler && requestInfo.method) {
|
|
1063
|
+
const httpMethodNames = ["GET", "POST", "PUT", "DELETE", "PATCH", "HEAD", "OPTIONS"];
|
|
1064
|
+
let target = handlerModule;
|
|
1065
|
+
for (let depth = 0; depth < 6 && target && typeof target === "object"; depth++) {
|
|
1066
|
+
const obj = target;
|
|
1067
|
+
const method = requestInfo.method.toUpperCase();
|
|
1068
|
+
if (httpMethodNames.includes(method) && typeof obj[method] === "function") {
|
|
1069
|
+
handler = obj[method];
|
|
1049
1070
|
isV2 = true;
|
|
1050
1071
|
break;
|
|
1051
1072
|
}
|
|
1052
1073
|
if (obj.default && typeof obj.default === "object") {
|
|
1053
|
-
|
|
1074
|
+
target = obj.default;
|
|
1054
1075
|
continue;
|
|
1055
1076
|
}
|
|
1056
1077
|
break;
|
|
1057
1078
|
}
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1079
|
+
}
|
|
1080
|
+
if (!handler) {
|
|
1081
|
+
throw new Error(
|
|
1082
|
+
`Could not resolve handler from ${handlerPath}. Module exports: [${Object.keys(handlerModule).join(", ")}]`
|
|
1083
|
+
);
|
|
1084
|
+
}
|
|
1085
|
+
if (precedingBlobs && precedingBlobs.length > 0) {
|
|
1086
|
+
console.log(` [warm-up] Replaying ${precedingBlobs.length} preceding request(s) to populate module-level state\u2026`);
|
|
1087
|
+
for (let i = 0; i < precedingBlobs.length; i++) {
|
|
1088
|
+
const pb = precedingBlobs[i];
|
|
1089
|
+
console.log(` [warm-up ${i + 1}/${precedingBlobs.length}] ${pb.requestInfo.method} ${pb.requestInfo.url}`);
|
|
1090
|
+
const netH = installNetworkInterceptor("replay", pb.capturedData.networkCalls);
|
|
1091
|
+
const envH = installEnvironmentInterceptor("replay", pb.capturedData.envReads);
|
|
1092
|
+
try {
|
|
1093
|
+
if (isV2) {
|
|
1094
|
+
const url = pb.requestInfo.rawUrl ?? `https://localhost${pb.requestInfo.url}`;
|
|
1095
|
+
const reqInit = { method: pb.requestInfo.method, headers: pb.requestInfo.headers };
|
|
1096
|
+
if (pb.requestInfo.body && pb.requestInfo.method !== "GET" && pb.requestInfo.method !== "HEAD") {
|
|
1097
|
+
reqInit.body = pb.requestInfo.body;
|
|
1098
|
+
}
|
|
1099
|
+
const RequestCtor = g.Request;
|
|
1100
|
+
if (RequestCtor) {
|
|
1101
|
+
const req = new RequestCtor(url, reqInit);
|
|
1102
|
+
const ctx = { geo: {}, ip: "127.0.0.1", requestId: "replay-warmup", server: { region: "local" } };
|
|
1103
|
+
g.__REPLAY_REQUEST_COOKIES__ = Object.keys(pb.requestInfo.headers).find((k) => k.toLowerCase() === "cookie") ? pb.requestInfo.headers[Object.keys(pb.requestInfo.headers).find((k) => k.toLowerCase() === "cookie")] : "";
|
|
1104
|
+
g.__REPLAY_REQUEST_HEADERS__ = pb.requestInfo.headers;
|
|
1105
|
+
await handler(req, ctx);
|
|
1106
|
+
}
|
|
1107
|
+
} else {
|
|
1108
|
+
await handler({
|
|
1109
|
+
httpMethod: pb.requestInfo.method,
|
|
1110
|
+
path: pb.requestInfo.url,
|
|
1111
|
+
headers: pb.requestInfo.headers,
|
|
1112
|
+
body: pb.requestInfo.body ?? null,
|
|
1113
|
+
queryStringParameters: pb.requestInfo.queryStringParameters ?? null,
|
|
1114
|
+
multiValueQueryStringParameters: pb.requestInfo.multiValueQueryStringParameters ?? null,
|
|
1115
|
+
rawUrl: pb.requestInfo.rawUrl ?? pb.requestInfo.url,
|
|
1116
|
+
rawQuery: pb.requestInfo.rawQuery ?? "",
|
|
1117
|
+
isBase64Encoded: pb.requestInfo.isBase64Encoded ?? false
|
|
1118
|
+
});
|
|
1076
1119
|
}
|
|
1077
|
-
|
|
1120
|
+
} catch {
|
|
1078
1121
|
}
|
|
1122
|
+
netH.restore();
|
|
1123
|
+
envH.restore();
|
|
1079
1124
|
}
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
);
|
|
1084
|
-
}
|
|
1125
|
+
console.log(` [warm-up] Done.`);
|
|
1126
|
+
}
|
|
1127
|
+
try {
|
|
1085
1128
|
let rawResult;
|
|
1086
1129
|
try {
|
|
1087
1130
|
if (isV2) {
|
|
@@ -1225,30 +1268,38 @@ async function backendRequestsEnsureTable(sql) {
|
|
|
1225
1268
|
await sql`
|
|
1226
1269
|
CREATE INDEX IF NOT EXISTS idx_backend_requests_created_at ON backend_requests (created_at DESC)
|
|
1227
1270
|
`;
|
|
1271
|
+
await sql`
|
|
1272
|
+
ALTER TABLE backend_requests ADD COLUMN IF NOT EXISTS original_request_id TEXT
|
|
1273
|
+
`;
|
|
1274
|
+
await sql`
|
|
1275
|
+
CREATE INDEX IF NOT EXISTS idx_backend_requests_original_request_id ON backend_requests (original_request_id)
|
|
1276
|
+
`;
|
|
1228
1277
|
}
|
|
1229
1278
|
async function backendRequestsInsert(sql, data) {
|
|
1230
1279
|
if (data.id) {
|
|
1231
1280
|
await sql`
|
|
1232
|
-
INSERT INTO backend_requests (id, blob_data_url, handler_path, commit_sha, branch_name, repository_url)
|
|
1281
|
+
INSERT INTO backend_requests (id, blob_data_url, handler_path, commit_sha, branch_name, repository_url, original_request_id)
|
|
1233
1282
|
VALUES (
|
|
1234
1283
|
${data.id}::uuid,
|
|
1235
1284
|
${data.blobDataUrl},
|
|
1236
1285
|
${data.handlerPath},
|
|
1237
1286
|
${data.commitSha},
|
|
1238
1287
|
${data.branchName},
|
|
1239
|
-
${data.repositoryUrl ?? null}
|
|
1288
|
+
${data.repositoryUrl ?? null},
|
|
1289
|
+
${data.originalRequestId ?? null}
|
|
1240
1290
|
)
|
|
1241
1291
|
`;
|
|
1242
1292
|
return data.id;
|
|
1243
1293
|
}
|
|
1244
1294
|
const rows = await sql`
|
|
1245
|
-
INSERT INTO backend_requests (blob_data_url, handler_path, commit_sha, branch_name, repository_url)
|
|
1295
|
+
INSERT INTO backend_requests (blob_data_url, handler_path, commit_sha, branch_name, repository_url, original_request_id)
|
|
1246
1296
|
VALUES (
|
|
1247
1297
|
${data.blobDataUrl},
|
|
1248
1298
|
${data.handlerPath},
|
|
1249
1299
|
${data.commitSha},
|
|
1250
1300
|
${data.branchName},
|
|
1251
|
-
${data.repositoryUrl ?? null}
|
|
1301
|
+
${data.repositoryUrl ?? null},
|
|
1302
|
+
${data.originalRequestId ?? null}
|
|
1252
1303
|
)
|
|
1253
1304
|
RETURNING id
|
|
1254
1305
|
`;
|
|
@@ -1339,7 +1390,8 @@ function databaseCallbacks(sql) {
|
|
|
1339
1390
|
handlerPath: data.handlerPath,
|
|
1340
1391
|
commitSha: data.commitSha,
|
|
1341
1392
|
branchName: data.branchName,
|
|
1342
|
-
repositoryUrl: data.repositoryUrl
|
|
1393
|
+
repositoryUrl: data.repositoryUrl,
|
|
1394
|
+
originalRequestId: data.originalRequestId
|
|
1343
1395
|
});
|
|
1344
1396
|
}
|
|
1345
1397
|
};
|
|
@@ -1357,7 +1409,8 @@ function remoteCallbacks(recorderUrl) {
|
|
|
1357
1409
|
branchName: data.branchName,
|
|
1358
1410
|
repositoryUrl: data.repositoryUrl,
|
|
1359
1411
|
handlerPath: data.handlerPath,
|
|
1360
|
-
requestId: data.requestId
|
|
1412
|
+
requestId: data.requestId,
|
|
1413
|
+
originalRequestId: data.originalRequestId
|
|
1361
1414
|
})
|
|
1362
1415
|
});
|
|
1363
1416
|
if (!res.ok) {
|