@alwaysmeticulous/backend-recorder-workerd 2.325.0 → 2.327.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 +94 -4
- package/dist/binding-patch.js +17 -8
- package/dist/binding-patch.js.map +1 -1
- package/dist/body-capture.js +2 -2
- package/dist/body-capture.js.map +1 -1
- package/dist/capture-buffer.js +114 -0
- package/dist/capture-buffer.js.map +1 -0
- package/dist/context.js +6 -3
- package/dist/context.js.map +1 -1
- package/dist/error-capture.d.ts +44 -0
- package/dist/error-capture.js +65 -0
- package/dist/error-capture.js.map +1 -0
- package/dist/fetch-patch.js +5 -5
- package/dist/fetch-patch.js.map +1 -1
- package/dist/index.d.ts +62 -39
- package/dist/index.js +65 -179
- package/dist/index.js.map +1 -1
- package/dist/kv-capture.js +2 -2
- package/dist/kv-capture.js.map +1 -1
- package/dist/kv-patch.js +9 -9
- package/dist/kv-patch.js.map +1 -1
- package/dist/log.js +2 -2
- package/dist/log.js.map +1 -1
- package/dist/original-fetch.js +2 -2
- package/dist/original-fetch.js.map +1 -1
- package/dist/outbound-capture.js +20 -28
- package/dist/outbound-capture.js.map +1 -1
- package/dist/pages-function.d.ts +33 -0
- package/dist/pages-function.js +28 -0
- package/dist/pages-function.js.map +1 -0
- package/dist/postgres/capture.d.ts +108 -0
- package/dist/postgres/capture.js +136 -0
- package/dist/postgres/capture.js.map +1 -0
- package/dist/postgres/with-meticulous-postgres.d.ts +32 -0
- package/dist/postgres/with-meticulous-postgres.js +152 -0
- package/dist/postgres/with-meticulous-postgres.js.map +1 -0
- package/dist/protocol.d.ts +88 -28
- package/dist/protocol.js +15 -10
- package/dist/protocol.js.map +1 -1
- package/dist/provisional-session-id.d.ts +58 -0
- package/dist/provisional-session-id.js +75 -0
- package/dist/provisional-session-id.js.map +1 -0
- package/dist/publish-session-id.js +41 -0
- package/dist/publish-session-id.js.map +1 -0
- package/dist/redact-body.js +2 -2
- package/dist/redact-body.js.map +1 -1
- package/dist/replay-fetch.js +17 -13
- package/dist/replay-fetch.js.map +1 -1
- package/dist/replay-sidecar-url.js +2 -2
- package/dist/replay-sidecar-url.js.map +1 -1
- package/dist/sidecar-client.js +31 -20
- package/dist/sidecar-client.js.map +1 -1
- package/dist/sidecar-transport.d.ts +38 -0
- package/dist/sidecar-transport.js +52 -0
- package/dist/sidecar-transport.js.map +1 -0
- package/dist/spans/attributes.d.ts +84 -0
- package/dist/spans/attributes.js +88 -0
- package/dist/spans/attributes.js.map +1 -0
- package/dist/spans/readable-span.d.ts +78 -0
- package/dist/spans/readable-span.js +35 -0
- package/dist/spans/readable-span.js.map +1 -0
- package/dist/spans/serialize-span.d.ts +14 -0
- package/dist/spans/serialize-span.js +38 -0
- package/dist/spans/serialize-span.js.map +1 -0
- package/dist/spans/span-builder.d.ts +61 -0
- package/dist/spans/span-builder.js +250 -0
- package/dist/spans/span-builder.js.map +1 -0
- package/dist/spans/url-redaction.d.ts +14 -0
- package/dist/spans/url-redaction.js +40 -0
- package/dist/spans/url-redaction.js.map +1 -0
- package/dist/virtual-clock.js +2 -2
- package/dist/virtual-clock.js.map +1 -1
- package/dist/virtual-random.js +47 -20
- package/dist/virtual-random.js.map +1 -1
- package/dist/with-meticulous.d.ts +32 -0
- package/dist/with-meticulous.js +0 -0
- package/dist/with-meticulous.js.map +1 -0
- package/package.json +2 -1
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
|
|
2
|
+
!function(){try{var e="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof globalThis?globalThis:"undefined"!=typeof self?self:{},n=(new e.Error).stack;n&&(e._sentryDebugIds=e._sentryDebugIds||{},e._sentryDebugIds[n]="b2626960-f48d-5500-a2b3-a8c9fbaf2002")}catch(e){}}();
|
|
3
|
+
import { serializeCapturedError } from "../error-capture.js";
|
|
4
|
+
//#region src/postgres/capture.ts
|
|
5
|
+
/**
|
|
6
|
+
* The record-side contract for postgres.js (the `postgres` npm package), shared by the Node
|
|
7
|
+
* recorder's `met-postgres-js-instrumentation/` and the in-worker `withMeticulousPostgres`.
|
|
8
|
+
*
|
|
9
|
+
* It lives in this package for the same reason `kv-capture.ts` does: a query recorded from a
|
|
10
|
+
* deployed Worker has to produce the byte-identical span a Node-recorded one does, because
|
|
11
|
+
* `PostgresJsMockStore` derives its match key from those attributes at load time. Get the
|
|
12
|
+
* serialization subtly wrong on one surface and every replay of a Worker recording misses.
|
|
13
|
+
* `packages/backend-recorder-js/src/postgres-js-capture-shared.ts` re-exports everything here
|
|
14
|
+
* and keeps the replay half (key building, result/error reconstruction) to itself — that half
|
|
15
|
+
* needs a synchronous SHA-256 and never runs inside a Worker.
|
|
16
|
+
*/
|
|
17
|
+
const POSTGRES_JS_QUERY_TEXT_ATTR = "meticulous.postgresjs.query.text";
|
|
18
|
+
const POSTGRES_JS_QUERY_PARAMS_ATTR = "meticulous.postgresjs.query.params";
|
|
19
|
+
const POSTGRES_JS_ROW_MODE_ATTR = "meticulous.postgresjs.row_mode";
|
|
20
|
+
const POSTGRES_JS_RESULT_ATTR = "meticulous.postgresjs.result";
|
|
21
|
+
const POSTGRES_JS_RESULT_TRUNCATED_ATTR = "meticulous.postgresjs.result.truncated";
|
|
22
|
+
const POSTGRES_JS_ERROR_ATTR = "meticulous.postgresjs.error";
|
|
23
|
+
const MAX_POSTGRES_JS_RESULT_SIZE = 256 * 1024;
|
|
24
|
+
const resolveRowMode = (isRaw) => {
|
|
25
|
+
if (isRaw === "values") return "values";
|
|
26
|
+
return isRaw ? "raw" : "";
|
|
27
|
+
};
|
|
28
|
+
/**
|
|
29
|
+
* Rebuilds the SQL text from a tagged template's literal chunks, with `$1`, `$2`… where the
|
|
30
|
+
* interpolated values went.
|
|
31
|
+
*
|
|
32
|
+
* The SQL text does not exist at interception time: postgres.js builds `query.string` only once
|
|
33
|
+
* a query reaches a connection, and replay never executes, so both sides must derive the text
|
|
34
|
+
* from `query.strings` instead.
|
|
35
|
+
*/
|
|
36
|
+
const reconstructQueryText = (strings) => {
|
|
37
|
+
if (!Array.isArray(strings) || strings.length === 0) return;
|
|
38
|
+
let text = "";
|
|
39
|
+
for (let i = 0; i < strings.length; i++) {
|
|
40
|
+
const chunk = strings[i];
|
|
41
|
+
if (typeof chunk !== "string") return;
|
|
42
|
+
text += i === 0 ? chunk : `$${i}${chunk}`;
|
|
43
|
+
}
|
|
44
|
+
return text;
|
|
45
|
+
};
|
|
46
|
+
/**
|
|
47
|
+
* Canonical serialization of the query's interpolated values.
|
|
48
|
+
*
|
|
49
|
+
* Read from `query.args` (the raw JS values), never `query.parameters`: the latter does not
|
|
50
|
+
* exist until postgres.js builds the statement, and its `Bind` step then mutates it in place
|
|
51
|
+
* into wire-format strings — so it differs between the two sides.
|
|
52
|
+
*
|
|
53
|
+
* `args` is heterogeneous: postgres.js' dynamic helpers put `Builder` / `Identifier` /
|
|
54
|
+
* `Parameter` instances in there alongside plain values. JSON-encoding them is stable across
|
|
55
|
+
* calls, which is all the match key needs.
|
|
56
|
+
*/
|
|
57
|
+
const serializePostgresJsArgs = (args) => {
|
|
58
|
+
try {
|
|
59
|
+
return JSON.stringify(Array.isArray(args) ? args : []);
|
|
60
|
+
} catch {
|
|
61
|
+
return "[]";
|
|
62
|
+
}
|
|
63
|
+
};
|
|
64
|
+
/** The leading SQL keyword, for the span name only. */
|
|
65
|
+
const postgresJsCommandOf = (text) => /^\s*([a-z]+)/i.exec(text)?.[1]?.toLowerCase() ?? "query";
|
|
66
|
+
const asRecord = (value) => value != null && typeof value === "object" ? value : {};
|
|
67
|
+
const numberOrNull = (value) => typeof value === "number" ? value : null;
|
|
68
|
+
/**
|
|
69
|
+
* postgres.js resolves a query with a `Result` — an `Array` subclass whose metadata lives in
|
|
70
|
+
* five non-enumerable own properties, so `JSON.stringify` would capture only the rows. Pull them
|
|
71
|
+
* out explicitly.
|
|
72
|
+
*
|
|
73
|
+
* Two fields are dropped on purpose: each column's `parser` (a function, so it can't survive
|
|
74
|
+
* JSON, and consumers never call it on an already-parsed row) and `state` (a live
|
|
75
|
+
* per-connection `{pid, secret}` handle used only for query cancellation, which is meaningless
|
|
76
|
+
* without a connection).
|
|
77
|
+
*/
|
|
78
|
+
const serializeOneResult = (res) => {
|
|
79
|
+
const meta = res;
|
|
80
|
+
const statement = asRecord(meta?.statement);
|
|
81
|
+
return {
|
|
82
|
+
rows: Array.isArray(res) ? [...res] : [],
|
|
83
|
+
count: numberOrNull(meta?.count),
|
|
84
|
+
command: typeof meta?.command === "string" ? meta.command : null,
|
|
85
|
+
columns: Array.isArray(meta?.columns) ? meta.columns.map((column) => {
|
|
86
|
+
const c = asRecord(column);
|
|
87
|
+
return {
|
|
88
|
+
name: String(c.name ?? ""),
|
|
89
|
+
type: numberOrNull(c.type) ?? 0,
|
|
90
|
+
table: numberOrNull(c.table) ?? 0,
|
|
91
|
+
number: numberOrNull(c.number) ?? 0
|
|
92
|
+
};
|
|
93
|
+
}) : [],
|
|
94
|
+
statement: meta?.statement != null ? {
|
|
95
|
+
string: String(statement.string ?? ""),
|
|
96
|
+
types: Array.isArray(statement.types) ? statement.types : [],
|
|
97
|
+
name: String(statement.name ?? "")
|
|
98
|
+
} : null
|
|
99
|
+
};
|
|
100
|
+
};
|
|
101
|
+
/**
|
|
102
|
+
* Serializes a postgres.js result (or the array form a multi-statement / `simple` query resolves
|
|
103
|
+
* to) to a JSON string for storage as a span attribute.
|
|
104
|
+
*/
|
|
105
|
+
const serializePostgresJsResult = (result) => {
|
|
106
|
+
const serialized = Array.isArray(result) && result.length > 0 && result.every((r) => isResultLike(r)) ? result.map(serializeOneResult) : serializeOneResult(result);
|
|
107
|
+
return JSON.stringify(serialized);
|
|
108
|
+
};
|
|
109
|
+
const isResultLike = (value) => Array.isArray(value) && Object.prototype.hasOwnProperty.call(value, "command") && Object.prototype.hasOwnProperty.call(value, "count");
|
|
110
|
+
const ERROR_DEBUG_PROPS = new Set([
|
|
111
|
+
"stack",
|
|
112
|
+
"query",
|
|
113
|
+
"parameters",
|
|
114
|
+
"args",
|
|
115
|
+
"types"
|
|
116
|
+
]);
|
|
117
|
+
/**
|
|
118
|
+
* Captures a rejected query's error as a JSON string. postgres.js surfaces server errors as
|
|
119
|
+
* `PostgresError` (`extends Error`, with the server's `code`/`severity`/`detail`/… fields
|
|
120
|
+
* `Object.assign`ed on) and transport/validation failures as plain `Error`s carrying `code`. All
|
|
121
|
+
* of those live in own enumerable properties, so copying those plus `message` (own but
|
|
122
|
+
* non-enumerable, from the `Error` constructor) captures everything a consumer inspects.
|
|
123
|
+
*/
|
|
124
|
+
const serializePostgresJsError = (error) => serializeCapturedError(error, ERROR_DEBUG_PROPS);
|
|
125
|
+
/**
|
|
126
|
+
* Whether a query resolves with a single `Result` we can capture and replay. Cursors,
|
|
127
|
+
* `.forEach()`, `.readable()`/`.writable()` (COPY streams) and `.describe()` resolve with
|
|
128
|
+
* something else, and `sql.file()` is constructed with no template chunks at all (its text is
|
|
129
|
+
* only read from disk later). All of those pass straight through, unrecorded and unmocked.
|
|
130
|
+
*/
|
|
131
|
+
const isSupportedPostgresJsQuery = (query) => reconstructQueryText(query.strings) != null && !query.onlyDescribe && !query.streaming && query.cursorFn == null && query.cursorRows == null && query.forEachFn == null;
|
|
132
|
+
//#endregion
|
|
133
|
+
export { MAX_POSTGRES_JS_RESULT_SIZE, POSTGRES_JS_ERROR_ATTR, POSTGRES_JS_QUERY_PARAMS_ATTR, POSTGRES_JS_QUERY_TEXT_ATTR, POSTGRES_JS_RESULT_ATTR, POSTGRES_JS_RESULT_TRUNCATED_ATTR, POSTGRES_JS_ROW_MODE_ATTR, isSupportedPostgresJsQuery, postgresJsCommandOf, reconstructQueryText, resolveRowMode, serializePostgresJsArgs, serializePostgresJsError, serializePostgresJsResult };
|
|
134
|
+
|
|
135
|
+
//# sourceMappingURL=capture.js.map
|
|
136
|
+
//# debugId=b2626960-f48d-5500-a2b3-a8c9fbaf2002
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"capture.js","sources":["../../src/postgres/capture.ts"],"sourcesContent":["import { serializeCapturedError } from \"../error-capture\";\n\n/**\n * The record-side contract for postgres.js (the `postgres` npm package), shared by the Node\n * recorder's `met-postgres-js-instrumentation/` and the in-worker `withMeticulousPostgres`.\n *\n * It lives in this package for the same reason `kv-capture.ts` does: a query recorded from a\n * deployed Worker has to produce the byte-identical span a Node-recorded one does, because\n * `PostgresJsMockStore` derives its match key from those attributes at load time. Get the\n * serialization subtly wrong on one surface and every replay of a Worker recording misses.\n * `packages/backend-recorder-js/src/postgres-js-capture-shared.ts` re-exports everything here\n * and keeps the replay half (key building, result/error reconstruction) to itself — that half\n * needs a synchronous SHA-256 and never runs inside a Worker.\n */\n\n// Custom Meticulous span attributes carrying the query input + result. Kept distinct from the\n// `pg` attributes (`meticulous.pg.*`) so the two stores can never match each other's spans,\n// even though both talk to Postgres.\nexport const POSTGRES_JS_QUERY_TEXT_ATTR = \"meticulous.postgresjs.query.text\";\nexport const POSTGRES_JS_QUERY_PARAMS_ATTR =\n \"meticulous.postgresjs.query.params\";\nexport const POSTGRES_JS_ROW_MODE_ATTR = \"meticulous.postgresjs.row_mode\";\nexport const POSTGRES_JS_RESULT_ATTR = \"meticulous.postgresjs.result\";\nexport const POSTGRES_JS_RESULT_TRUNCATED_ATTR =\n \"meticulous.postgresjs.result.truncated\";\n// Set instead of `result` when the query rejected, so a query that legitimately errors (a\n// constraint violation, a missing relation) replays as the same error rather than as a \"no\n// recorded result\" miss.\nexport const POSTGRES_JS_ERROR_ATTR = \"meticulous.postgresjs.error\";\n\n// Cap the captured result to bound span size, matching the HTTP/undici/pg 256 KB cap.\nexport const MAX_POSTGRES_JS_RESULT_SIZE = 256 * 1024;\n\n/**\n * How the rows of a result are shaped: objects keyed by column name by default, or arrays under\n * `.values()` / `.raw()`. postgres.js records this on the query as `isRaw` (`undefined` |\n * `true` | `\"values\"`).\n *\n * This belongs in the match key because the SAME sql text returns different row shapes\n * depending on it — Drizzle's postgres-js driver calls `.values()` for typed selects but awaits\n * the query directly for `db.execute`, so both shapes occur for identical text.\n */\nexport type PostgresJsRowMode = \"\" | \"raw\" | \"values\";\n\nexport const resolveRowMode = (isRaw: unknown): PostgresJsRowMode => {\n if (isRaw === \"values\") {\n return \"values\";\n }\n return isRaw ? \"raw\" : \"\";\n};\n\n/**\n * Rebuilds the SQL text from a tagged template's literal chunks, with `$1`, `$2`… where the\n * interpolated values went.\n *\n * The SQL text does not exist at interception time: postgres.js builds `query.string` only once\n * a query reaches a connection, and replay never executes, so both sides must derive the text\n * from `query.strings` instead.\n */\nexport const reconstructQueryText = (strings: unknown): string | undefined => {\n if (!Array.isArray(strings) || strings.length === 0) {\n return undefined;\n }\n let text = \"\";\n for (let i = 0; i < strings.length; i++) {\n const chunk = strings[i];\n if (typeof chunk !== \"string\") {\n return undefined;\n }\n text += i === 0 ? chunk : `$${i}${chunk}`;\n }\n return text;\n};\n\n/**\n * Canonical serialization of the query's interpolated values.\n *\n * Read from `query.args` (the raw JS values), never `query.parameters`: the latter does not\n * exist until postgres.js builds the statement, and its `Bind` step then mutates it in place\n * into wire-format strings — so it differs between the two sides.\n *\n * `args` is heterogeneous: postgres.js' dynamic helpers put `Builder` / `Identifier` /\n * `Parameter` instances in there alongside plain values. JSON-encoding them is stable across\n * calls, which is all the match key needs.\n */\nexport const serializePostgresJsArgs = (args: unknown): string => {\n try {\n return JSON.stringify(Array.isArray(args) ? args : []);\n } catch {\n return \"[]\";\n }\n};\n\n/** The leading SQL keyword, for the span name only. */\nexport const postgresJsCommandOf = (text: string): string =>\n /^\\s*([a-z]+)/i.exec(text)?.[1]?.toLowerCase() ?? \"query\";\n\ninterface SerializedPostgresJsResult {\n rows: unknown[];\n count: number | null;\n command: string | null;\n columns: { name: string; type: number; table: number; number: number }[];\n statement: { string: string; types: number[]; name: string } | null;\n}\n\n/**\n * The metadata postgres.js hangs off a `Result`. Declared with `unknown` values so the\n * serializer below narrows every field rather than trusting the shape.\n */\ninterface PostgresJsResultLike {\n count?: unknown;\n command?: unknown;\n columns?: unknown;\n statement?: unknown;\n}\n\nconst asRecord = (value: unknown): Record<string, unknown> =>\n value != null && typeof value === \"object\"\n ? (value as Record<string, unknown>)\n : {};\n\nconst numberOrNull = (value: unknown): number | null =>\n typeof value === \"number\" ? value : null;\n\n/**\n * postgres.js resolves a query with a `Result` — an `Array` subclass whose metadata lives in\n * five non-enumerable own properties, so `JSON.stringify` would capture only the rows. Pull them\n * out explicitly.\n *\n * Two fields are dropped on purpose: each column's `parser` (a function, so it can't survive\n * JSON, and consumers never call it on an already-parsed row) and `state` (a live\n * per-connection `{pid, secret}` handle used only for query cancellation, which is meaningless\n * without a connection).\n */\nconst serializeOneResult = (res: unknown): SerializedPostgresJsResult => {\n const meta = res as PostgresJsResultLike | null | undefined;\n const statement = asRecord(meta?.statement);\n return {\n rows: Array.isArray(res) ? [...(res as unknown[])] : [],\n count: numberOrNull(meta?.count),\n command: typeof meta?.command === \"string\" ? meta.command : null,\n columns: Array.isArray(meta?.columns)\n ? meta.columns.map((column) => {\n const c = asRecord(column);\n return {\n name: String(c.name ?? \"\"),\n type: numberOrNull(c.type) ?? 0,\n table: numberOrNull(c.table) ?? 0,\n number: numberOrNull(c.number) ?? 0,\n };\n })\n : [],\n statement:\n meta?.statement != null\n ? {\n string: String(statement.string ?? \"\"),\n types: Array.isArray(statement.types)\n ? (statement.types as number[])\n : [],\n name: String(statement.name ?? \"\"),\n }\n : null,\n };\n};\n\n/**\n * Serializes a postgres.js result (or the array form a multi-statement / `simple` query resolves\n * to) to a JSON string for storage as a span attribute.\n */\nexport const serializePostgresJsResult = (result: unknown): string => {\n // A Result IS an Array, so an array-of-Results is only distinguishable by its elements. The\n // `length > 0` guard matters: `[].every(...)` is true, so without it every zero-row result (a\n // SELECT that matched nothing, an INSERT without RETURNING, BEGIN/COMMIT) would be mistaken\n // for an empty list of results and lose its `count`/`command`.\n const isMultiple =\n Array.isArray(result) &&\n result.length > 0 &&\n result.every((r) => isResultLike(r));\n const serialized = isMultiple\n ? (result as unknown[]).map(serializeOneResult)\n : serializeOneResult(result);\n return JSON.stringify(serialized);\n};\n\n// A `Result` carries `command`/`count` own properties; a plain row object or scalar does not.\nconst isResultLike = (value: unknown): boolean =>\n Array.isArray(value) &&\n Object.prototype.hasOwnProperty.call(value, \"command\") &&\n Object.prototype.hasOwnProperty.call(value, \"count\");\n\n// Own properties postgres.js attaches to a query error for debugging. Excluded from the\n// captured error: they are large, redundant with the query attributes we already record, and\n// only enumerable when the app enabled postgres.js' `debug` option.\nconst ERROR_DEBUG_PROPS = new Set([\n \"stack\",\n \"query\",\n \"parameters\",\n \"args\",\n \"types\",\n]);\n\n/**\n * Captures a rejected query's error as a JSON string. postgres.js surfaces server errors as\n * `PostgresError` (`extends Error`, with the server's `code`/`severity`/`detail`/… fields\n * `Object.assign`ed on) and transport/validation failures as plain `Error`s carrying `code`. All\n * of those live in own enumerable properties, so copying those plus `message` (own but\n * non-enumerable, from the `Error` constructor) captures everything a consumer inspects.\n */\nexport const serializePostgresJsError = (error: unknown): string =>\n serializeCapturedError(error, ERROR_DEBUG_PROPS);\n\n/**\n * The shape of postgres.js' internal `Query` that both sides rely on. Declared structurally (no\n * `postgres` dependency).\n */\nexport interface PostgresJsQueryLike {\n /** Template chunks: the literal parts of a tagged template, or `[text]` for `unsafe`. */\n strings: unknown;\n /** The interpolated values, still raw JS values at interception time. */\n args: unknown;\n /** `undefined` by default, `true` after `.raw()`, `\"values\"` after `.values()`. */\n isRaw?: unknown;\n /** Set once `handle()` has run; guards against double-instrumenting one query. */\n executed?: boolean;\n /** Set by `.describe()` — resolves with a Statement, not a Result. */\n onlyDescribe?: boolean;\n /** Set by `.cursor()` / `.forEach()` / `.readable()` — streaming, not a single Result. */\n cursorFn?: unknown;\n cursorRows?: unknown;\n forEachFn?: unknown;\n streaming?: unknown;\n options?: {\n /**\n * Present only on the implicit `begin` query `sql.begin()` issues. postgres.js needs it\n * called with the serving connection before the transaction body runs.\n */\n onexecute?: (connection: unknown) => void;\n [key: string]: unknown;\n };\n resolve: (result: unknown) => void;\n reject: (error: unknown) => void;\n}\n\n/**\n * Whether a query resolves with a single `Result` we can capture and replay. Cursors,\n * `.forEach()`, `.readable()`/`.writable()` (COPY streams) and `.describe()` resolve with\n * something else, and `sql.file()` is constructed with no template chunks at all (its text is\n * only read from disk later). All of those pass straight through, unrecorded and unmocked.\n */\nexport const isSupportedPostgresJsQuery = (\n query: PostgresJsQueryLike,\n): boolean =>\n reconstructQueryText(query.strings) != null &&\n !query.onlyDescribe &&\n !query.streaming &&\n query.cursorFn == null &&\n query.cursorRows == null &&\n query.forEachFn == null;\n"],"names":[],"mappings":";;;;;;;;;;;;;;;;AAkBA,MAAa,8BAA8B;AAC3C,MAAa,gCACX;AACF,MAAa,4BAA4B;AACzC,MAAa,0BAA0B;AACvC,MAAa,oCACX;AAIF,MAAa,yBAAyB;AAGtC,MAAa,8BAA8B,MAAM;AAajD,MAAa,kBAAkB,UAAsC;AACnE,KAAI,UAAU,SACZ,QAAO;AAET,QAAO,QAAQ,QAAQ;;;;;;;;;;AAWzB,MAAa,wBAAwB,YAAyC;AAC5E,KAAI,CAAC,MAAM,QAAQ,QAAQ,IAAI,QAAQ,WAAW,EAChD;CAEF,IAAI,OAAO;AACX,MAAK,IAAI,IAAI,GAAG,IAAI,QAAQ,QAAQ,KAAK;EACvC,MAAM,QAAQ,QAAQ;AACtB,MAAI,OAAO,UAAU,SACnB;AAEF,UAAQ,MAAM,IAAI,QAAQ,IAAI,IAAI;;AAEpC,QAAO;;;;;;;;;;;;;AAcT,MAAa,2BAA2B,SAA0B;AAChE,KAAI;AACF,SAAO,KAAK,UAAU,MAAM,QAAQ,KAAK,GAAG,OAAO,EAAE,CAAC;SAChD;AACN,SAAO;;;;AAKX,MAAa,uBAAuB,SAClC,gBAAgB,KAAK,KAAK,GAAG,IAAI,aAAa,IAAI;AAqBpD,MAAM,YAAY,UAChB,SAAS,QAAQ,OAAO,UAAU,WAC7B,QACD,EAAE;AAER,MAAM,gBAAgB,UACpB,OAAO,UAAU,WAAW,QAAQ;;;;;;;;;;;AAYtC,MAAM,sBAAsB,QAA6C;CACvE,MAAM,OAAO;CACb,MAAM,YAAY,SAAS,MAAM,UAAU;AAC3C,QAAO;EACL,MAAM,MAAM,QAAQ,IAAI,GAAG,CAAC,GAAI,IAAkB,GAAG,EAAE;EACvD,OAAO,aAAa,MAAM,MAAM;EAChC,SAAS,OAAO,MAAM,YAAY,WAAW,KAAK,UAAU;EAC5D,SAAS,MAAM,QAAQ,MAAM,QAAQ,GACjC,KAAK,QAAQ,KAAK,WAAW;GAC3B,MAAM,IAAI,SAAS,OAAO;AAC1B,UAAO;IACL,MAAM,OAAO,EAAE,QAAQ,GAAG;IAC1B,MAAM,aAAa,EAAE,KAAK,IAAI;IAC9B,OAAO,aAAa,EAAE,MAAM,IAAI;IAChC,QAAQ,aAAa,EAAE,OAAO,IAAI;IACnC;IACD,GACF,EAAE;EACN,WACE,MAAM,aAAa,OACf;GACE,QAAQ,OAAO,UAAU,UAAU,GAAG;GACtC,OAAO,MAAM,QAAQ,UAAU,MAAM,GAChC,UAAU,QACX,EAAE;GACN,MAAM,OAAO,UAAU,QAAQ,GAAG;GACnC,GACD;EACP;;;;;;AAOH,MAAa,6BAA6B,WAA4B;CASpE,MAAM,aAHJ,MAAM,QAAQ,OAAO,IACrB,OAAO,SAAS,KAChB,OAAO,OAAO,MAAM,aAAa,EAAE,CAAC,GAEjC,OAAqB,IAAI,mBAAmB,GAC7C,mBAAmB,OAAO;AAC9B,QAAO,KAAK,UAAU,WAAW;;AAInC,MAAM,gBAAgB,UACpB,MAAM,QAAQ,MAAM,IACpB,OAAO,UAAU,eAAe,KAAK,OAAO,UAAU,IACtD,OAAO,UAAU,eAAe,KAAK,OAAO,QAAQ;AAKtD,MAAM,oBAAoB,IAAI,IAAI;CAChC;CACA;CACA;CACA;CACA;CACD,CAAC;;;;;;;;AASF,MAAa,4BAA4B,UACvC,uBAAuB,OAAO,kBAAkB;;;;;;;AAwClD,MAAa,8BACX,UAEA,qBAAqB,MAAM,QAAQ,IAAI,QACvC,CAAC,MAAM,gBACP,CAAC,MAAM,aACP,MAAM,YAAY,QAClB,MAAM,cAAc,QACpB,MAAM,aAAa","debugId":"b2626960-f48d-5500-a2b3-a8c9fbaf2002"}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
//#region src/postgres/with-meticulous-postgres.d.ts
|
|
2
|
+
/**
|
|
3
|
+
* Records postgres.js queries a deployed Worker makes — typically over Hyperdrive, which is the
|
|
4
|
+
* only way a Worker reaches Postgres.
|
|
5
|
+
*
|
|
6
|
+
* There is no seam to patch automatically here. The Node recorder can hook Node's module loader
|
|
7
|
+
* to patch `postgres/{src,cjs/src,cf/src}/query.js`, but a Worker is a single bundle: `postgres`
|
|
8
|
+
* was inlined at build time and never passes through a loader. So the app hands us its client:
|
|
9
|
+
*
|
|
10
|
+
* const sql = withMeticulousPostgres(postgres(env.HYPERDRIVE.connectionString));
|
|
11
|
+
*
|
|
12
|
+
* Every postgres.js query funnels through one internal method, `Query.prototype.handle`, and this
|
|
13
|
+
* reaches that shared prototype *through the instance* — via a throwaway query, which is free
|
|
14
|
+
* because postgres.js queries are lazy and nothing executes until awaited. One call therefore also
|
|
15
|
+
* covers read-replica clients and any other client in the isolate.
|
|
16
|
+
*
|
|
17
|
+
* Apply it **outermost** if something else also wraps the client (Sentry's
|
|
18
|
+
* `instrumentPostgresJsSql`, a logging proxy): those wrappers return a proxy whose `handle` is
|
|
19
|
+
* still the shared prototype's, so ordering does not change what is captured — but keeping ours
|
|
20
|
+
* outermost matches the Node recorder's contract and stays correct if that ever changes.
|
|
21
|
+
*
|
|
22
|
+
* Record-only, deliberately. A Worker recording is replayed against the app running under Node,
|
|
23
|
+
* where `@alwaysmeticulous/backend-recorder-js` serves these queries from its own mock store —
|
|
24
|
+
* which is why the captured attributes here are exactly the ones that store reads. Outside a
|
|
25
|
+
* recorded request this is a complete pass-through, so it is safe to leave in deployed code.
|
|
26
|
+
*
|
|
27
|
+
* Returns the client unchanged; the patch is on the prototype.
|
|
28
|
+
*/
|
|
29
|
+
declare const withMeticulousPostgres: <Sql>(sql: Sql) => Sql;
|
|
30
|
+
//#endregion
|
|
31
|
+
export { withMeticulousPostgres };
|
|
32
|
+
//# sourceMappingURL=with-meticulous-postgres.d.ts.map
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
|
|
2
|
+
!function(){try{var e="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof globalThis?globalThis:"undefined"!=typeof self?self:{},n=(new e.Error).stack;n&&(e._sentryDebugIds=e._sentryDebugIds||{},e._sentryDebugIds[n]="bcb30e85-33e1-52b2-9e08-5dd84f66d61c")}catch(e){}}();
|
|
3
|
+
import { requestCaptureContext } from "../context.js";
|
|
4
|
+
import { warnOnce } from "../log.js";
|
|
5
|
+
import { MAX_POSTGRES_JS_RESULT_SIZE, isSupportedPostgresJsQuery, reconstructQueryText, resolveRowMode, serializePostgresJsArgs, serializePostgresJsError, serializePostgresJsResult } from "./capture.js";
|
|
6
|
+
//#region src/postgres/with-meticulous-postgres.ts
|
|
7
|
+
/**
|
|
8
|
+
* Records postgres.js queries a deployed Worker makes — typically over Hyperdrive, which is the
|
|
9
|
+
* only way a Worker reaches Postgres.
|
|
10
|
+
*
|
|
11
|
+
* There is no seam to patch automatically here. The Node recorder can hook Node's module loader
|
|
12
|
+
* to patch `postgres/{src,cjs/src,cf/src}/query.js`, but a Worker is a single bundle: `postgres`
|
|
13
|
+
* was inlined at build time and never passes through a loader. So the app hands us its client:
|
|
14
|
+
*
|
|
15
|
+
* const sql = withMeticulousPostgres(postgres(env.HYPERDRIVE.connectionString));
|
|
16
|
+
*
|
|
17
|
+
* Every postgres.js query funnels through one internal method, `Query.prototype.handle`, and this
|
|
18
|
+
* reaches that shared prototype *through the instance* — via a throwaway query, which is free
|
|
19
|
+
* because postgres.js queries are lazy and nothing executes until awaited. One call therefore also
|
|
20
|
+
* covers read-replica clients and any other client in the isolate.
|
|
21
|
+
*
|
|
22
|
+
* Apply it **outermost** if something else also wraps the client (Sentry's
|
|
23
|
+
* `instrumentPostgresJsSql`, a logging proxy): those wrappers return a proxy whose `handle` is
|
|
24
|
+
* still the shared prototype's, so ordering does not change what is captured — but keeping ours
|
|
25
|
+
* outermost matches the Node recorder's contract and stays correct if that ever changes.
|
|
26
|
+
*
|
|
27
|
+
* Record-only, deliberately. A Worker recording is replayed against the app running under Node,
|
|
28
|
+
* where `@alwaysmeticulous/backend-recorder-js` serves these queries from its own mock store —
|
|
29
|
+
* which is why the captured attributes here are exactly the ones that store reads. Outside a
|
|
30
|
+
* recorded request this is a complete pass-through, so it is safe to leave in deployed code.
|
|
31
|
+
*
|
|
32
|
+
* Returns the client unchanged; the patch is on the prototype.
|
|
33
|
+
*/
|
|
34
|
+
const withMeticulousPostgres = (sql) => {
|
|
35
|
+
try {
|
|
36
|
+
const prototype = findQueryPrototype(sql);
|
|
37
|
+
if (prototype === void 0) {
|
|
38
|
+
warnOnce("postgres-wrapper-unsupported", "withMeticulousPostgres could not reach postgres.js' Query prototype — queries will not be recorded.");
|
|
39
|
+
return sql;
|
|
40
|
+
}
|
|
41
|
+
patchHandle(prototype);
|
|
42
|
+
} catch (error) {
|
|
43
|
+
warnOnce("postgres-wrapper-failed", "withMeticulousPostgres failed — queries will not be recorded.", error);
|
|
44
|
+
}
|
|
45
|
+
return sql;
|
|
46
|
+
};
|
|
47
|
+
const HANDLE_PATCHED = Symbol.for("meticulous.workerd.postgresJsPatched");
|
|
48
|
+
/**
|
|
49
|
+
* The `Query` prototype, reached by building a query and reading its prototype.
|
|
50
|
+
*
|
|
51
|
+
* `sql.unsafe` is used rather than a tagged template because it takes the text as a plain
|
|
52
|
+
* argument: no interpolation, and postgres.js does not touch a connection until the query is
|
|
53
|
+
* awaited, which nothing here does. The query is discarded.
|
|
54
|
+
*/
|
|
55
|
+
const findQueryPrototype = (sql) => {
|
|
56
|
+
const unsafe = sql?.unsafe;
|
|
57
|
+
if (typeof unsafe !== "function") return;
|
|
58
|
+
const probe = unsafe.call(sql, "select 1");
|
|
59
|
+
if (probe === null || typeof probe !== "object") return;
|
|
60
|
+
const prototype = Object.getPrototypeOf(probe);
|
|
61
|
+
if (prototype === null) return;
|
|
62
|
+
const descriptor = Object.getOwnPropertyDescriptor(prototype, "handle");
|
|
63
|
+
return descriptor !== void 0 && typeof descriptor.value === "function" && descriptor.configurable === true ? prototype : void 0;
|
|
64
|
+
};
|
|
65
|
+
const patchHandle = (prototype) => {
|
|
66
|
+
const original = Object.getOwnPropertyDescriptor(prototype, "handle")?.value;
|
|
67
|
+
if (original === void 0 || original[HANDLE_PATCHED] === true) return;
|
|
68
|
+
const patched = function() {
|
|
69
|
+
if (this.executed || !isSupportedPostgresJsQuery(this)) return original.call(this);
|
|
70
|
+
const ctx = requestCaptureContext.getStore();
|
|
71
|
+
if (!ctx || ctx.mode !== "record") return original.call(this);
|
|
72
|
+
try {
|
|
73
|
+
captureQuery(ctx, this);
|
|
74
|
+
} catch (error) {
|
|
75
|
+
warnOnce("postgres-capture", "Failed to capture a postgres.js query.", error);
|
|
76
|
+
}
|
|
77
|
+
return original.call(this);
|
|
78
|
+
};
|
|
79
|
+
Object.defineProperty(patched, HANDLE_PATCHED, {
|
|
80
|
+
value: true,
|
|
81
|
+
enumerable: false,
|
|
82
|
+
configurable: true
|
|
83
|
+
});
|
|
84
|
+
Object.defineProperty(prototype, "handle", {
|
|
85
|
+
value: patched,
|
|
86
|
+
writable: true,
|
|
87
|
+
enumerable: false,
|
|
88
|
+
configurable: true
|
|
89
|
+
});
|
|
90
|
+
};
|
|
91
|
+
/**
|
|
92
|
+
* Wraps the query's own `resolve`/`reject` so the outcome is captured without the query being
|
|
93
|
+
* awaited here — the same trick the Node instrumentation uses, and the only place a query's input
|
|
94
|
+
* and its result are both visible.
|
|
95
|
+
*/
|
|
96
|
+
const captureQuery = (ctx, query) => {
|
|
97
|
+
const queryText = reconstructQueryText(query.strings);
|
|
98
|
+
if (queryText === void 0) return;
|
|
99
|
+
const startTimeMs = Date.now();
|
|
100
|
+
const base = {
|
|
101
|
+
kind: "postgres",
|
|
102
|
+
requestId: ctx.requestId,
|
|
103
|
+
...ctx.frontendSessionId !== void 0 ? { frontendSessionId: ctx.frontendSessionId } : {},
|
|
104
|
+
traceId: ctx.traceId,
|
|
105
|
+
serverSpanId: ctx.serverSpanId,
|
|
106
|
+
queryText,
|
|
107
|
+
params: serializePostgresJsArgs(query.args),
|
|
108
|
+
rowMode: resolveRowMode(query.isRaw),
|
|
109
|
+
startTimeMs
|
|
110
|
+
};
|
|
111
|
+
const report = (outcome) => {
|
|
112
|
+
try {
|
|
113
|
+
ctx.buffer.add({
|
|
114
|
+
...base,
|
|
115
|
+
...outcome,
|
|
116
|
+
endTimeMs: Date.now()
|
|
117
|
+
});
|
|
118
|
+
} catch (error) {
|
|
119
|
+
warnOnce("postgres-report", "Failed to report a postgres.js query.", error);
|
|
120
|
+
}
|
|
121
|
+
};
|
|
122
|
+
const originalResolve = query.resolve;
|
|
123
|
+
const originalReject = query.reject;
|
|
124
|
+
query.resolve = (result) => {
|
|
125
|
+
let json;
|
|
126
|
+
try {
|
|
127
|
+
json = serializePostgresJsResult(result);
|
|
128
|
+
} catch {
|
|
129
|
+
json = void 0;
|
|
130
|
+
}
|
|
131
|
+
report(json === void 0 ? {} : { result: {
|
|
132
|
+
body: json.length > 262144 ? json.slice(0, MAX_POSTGRES_JS_RESULT_SIZE) : json,
|
|
133
|
+
truncated: json.length > MAX_POSTGRES_JS_RESULT_SIZE
|
|
134
|
+
} });
|
|
135
|
+
return originalResolve.call(query, result);
|
|
136
|
+
};
|
|
137
|
+
query.reject = (error) => {
|
|
138
|
+
let errorJson;
|
|
139
|
+
try {
|
|
140
|
+
errorJson = serializePostgresJsError(error);
|
|
141
|
+
} catch {
|
|
142
|
+
errorJson = void 0;
|
|
143
|
+
}
|
|
144
|
+
report(errorJson === void 0 ? {} : { errorJson });
|
|
145
|
+
return originalReject.call(query, error);
|
|
146
|
+
};
|
|
147
|
+
};
|
|
148
|
+
//#endregion
|
|
149
|
+
export { withMeticulousPostgres };
|
|
150
|
+
|
|
151
|
+
//# sourceMappingURL=with-meticulous-postgres.js.map
|
|
152
|
+
//# debugId=bcb30e85-33e1-52b2-9e08-5dd84f66d61c
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"with-meticulous-postgres.js","sources":["../../src/postgres/with-meticulous-postgres.ts"],"sourcesContent":["import { type RequestCaptureContext, requestCaptureContext } from \"../context\";\nimport { warnOnce } from \"../log\";\nimport type { PostgresQueryEvent } from \"../protocol\";\nimport {\n isSupportedPostgresJsQuery,\n MAX_POSTGRES_JS_RESULT_SIZE,\n type PostgresJsQueryLike,\n reconstructQueryText,\n resolveRowMode,\n serializePostgresJsArgs,\n serializePostgresJsError,\n serializePostgresJsResult,\n} from \"./capture\";\n\n/**\n * Records postgres.js queries a deployed Worker makes — typically over Hyperdrive, which is the\n * only way a Worker reaches Postgres.\n *\n * There is no seam to patch automatically here. The Node recorder can hook Node's module loader\n * to patch `postgres/{src,cjs/src,cf/src}/query.js`, but a Worker is a single bundle: `postgres`\n * was inlined at build time and never passes through a loader. So the app hands us its client:\n *\n * const sql = withMeticulousPostgres(postgres(env.HYPERDRIVE.connectionString));\n *\n * Every postgres.js query funnels through one internal method, `Query.prototype.handle`, and this\n * reaches that shared prototype *through the instance* — via a throwaway query, which is free\n * because postgres.js queries are lazy and nothing executes until awaited. One call therefore also\n * covers read-replica clients and any other client in the isolate.\n *\n * Apply it **outermost** if something else also wraps the client (Sentry's\n * `instrumentPostgresJsSql`, a logging proxy): those wrappers return a proxy whose `handle` is\n * still the shared prototype's, so ordering does not change what is captured — but keeping ours\n * outermost matches the Node recorder's contract and stays correct if that ever changes.\n *\n * Record-only, deliberately. A Worker recording is replayed against the app running under Node,\n * where `@alwaysmeticulous/backend-recorder-js` serves these queries from its own mock store —\n * which is why the captured attributes here are exactly the ones that store reads. Outside a\n * recorded request this is a complete pass-through, so it is safe to leave in deployed code.\n *\n * Returns the client unchanged; the patch is on the prototype.\n */\nexport const withMeticulousPostgres = <Sql>(sql: Sql): Sql => {\n try {\n const prototype = findQueryPrototype(sql);\n if (prototype === undefined) {\n warnOnce(\n \"postgres-wrapper-unsupported\",\n \"withMeticulousPostgres could not reach postgres.js' Query prototype — queries will not be recorded.\",\n );\n return sql;\n }\n patchHandle(prototype);\n } catch (error) {\n warnOnce(\n \"postgres-wrapper-failed\",\n \"withMeticulousPostgres failed — queries will not be recorded.\",\n error,\n );\n }\n return sql;\n};\n\n// Symbol.for so a second copy of the shim (e.g. bundled twice) still detects the patch.\nconst HANDLE_PATCHED = Symbol.for(\"meticulous.workerd.postgresJsPatched\");\n\ntype HandleFn = (this: PostgresJsQueryLike) => unknown;\n\n/**\n * The `Query` prototype, reached by building a query and reading its prototype.\n *\n * `sql.unsafe` is used rather than a tagged template because it takes the text as a plain\n * argument: no interpolation, and postgres.js does not touch a connection until the query is\n * awaited, which nothing here does. The query is discarded.\n */\nconst findQueryPrototype = (sql: unknown): object | undefined => {\n const unsafe = (sql as { unsafe?: unknown } | null)?.unsafe;\n if (typeof unsafe !== \"function\") {\n return undefined;\n }\n const probe: unknown = (unsafe as (text: string) => unknown).call(\n sql,\n \"select 1\",\n );\n if (probe === null || typeof probe !== \"object\") {\n return undefined;\n }\n const prototype = Object.getPrototypeOf(probe) as object | null;\n if (prototype === null) {\n return undefined;\n }\n const descriptor = Object.getOwnPropertyDescriptor(prototype, \"handle\");\n return descriptor !== undefined &&\n typeof descriptor.value === \"function\" &&\n descriptor.configurable === true\n ? prototype\n : undefined;\n};\n\nconst patchHandle = (prototype: object): void => {\n const descriptor = Object.getOwnPropertyDescriptor(prototype, \"handle\");\n const original = descriptor?.value as HandleFn | undefined;\n if (\n original === undefined ||\n (original as unknown as Record<symbol, unknown>)[HANDLE_PATCHED] === true\n ) {\n return;\n }\n\n const patched = function (this: PostgresJsQueryLike): unknown {\n // `handle` is reachable from then/catch/finally/execute, so it can be called more than once\n // for one query; postgres.js itself no-ops on the repeats via `executed`. Bail before\n // instrumenting so a query is never captured twice.\n if (this.executed || !isSupportedPostgresJsQuery(this)) {\n return original.call(this);\n }\n const ctx = requestCaptureContext.getStore();\n if (!ctx || ctx.mode !== \"record\") {\n return original.call(this);\n }\n try {\n captureQuery(ctx, this);\n } catch (error) {\n warnOnce(\n \"postgres-capture\",\n \"Failed to capture a postgres.js query.\",\n error,\n );\n }\n return original.call(this);\n };\n Object.defineProperty(patched, HANDLE_PATCHED, {\n value: true,\n enumerable: false,\n configurable: true,\n });\n Object.defineProperty(prototype, \"handle\", {\n value: patched,\n writable: true,\n enumerable: false,\n configurable: true,\n });\n};\n\n/**\n * Wraps the query's own `resolve`/`reject` so the outcome is captured without the query being\n * awaited here — the same trick the Node instrumentation uses, and the only place a query's input\n * and its result are both visible.\n */\nconst captureQuery = (\n ctx: RequestCaptureContext,\n query: PostgresJsQueryLike,\n): void => {\n const queryText = reconstructQueryText(query.strings);\n if (queryText === undefined) {\n return;\n }\n const startTimeMs = Date.now();\n const base = {\n kind: \"postgres\" as const,\n requestId: ctx.requestId,\n ...(ctx.frontendSessionId !== undefined\n ? { frontendSessionId: ctx.frontendSessionId }\n : {}),\n traceId: ctx.traceId,\n serverSpanId: ctx.serverSpanId,\n queryText,\n params: serializePostgresJsArgs(query.args),\n rowMode: resolveRowMode(query.isRaw),\n startTimeMs,\n };\n\n const report = (outcome: Partial<PostgresQueryEvent>): void => {\n try {\n ctx.buffer.add({ ...base, ...outcome, endTimeMs: Date.now() });\n } catch (error) {\n warnOnce(\n \"postgres-report\",\n \"Failed to report a postgres.js query.\",\n error,\n );\n }\n };\n\n const originalResolve = query.resolve;\n const originalReject = query.reject;\n query.resolve = (result: unknown) => {\n // A result that cannot be serialized is simply not captured → it misses during replay and\n // errors hermetically, rather than serving something wrong.\n let json: string | undefined;\n try {\n json = serializePostgresJsResult(result);\n } catch {\n json = undefined;\n }\n report(\n json === undefined\n ? {}\n : {\n result: {\n body:\n json.length > MAX_POSTGRES_JS_RESULT_SIZE\n ? json.slice(0, MAX_POSTGRES_JS_RESULT_SIZE)\n : json,\n truncated: json.length > MAX_POSTGRES_JS_RESULT_SIZE,\n },\n },\n );\n return originalResolve.call(query, result);\n };\n query.reject = (error: unknown) => {\n let errorJson: string | undefined;\n try {\n errorJson = serializePostgresJsError(error);\n } catch {\n errorJson = undefined;\n }\n report(errorJson === undefined ? {} : { errorJson });\n return originalReject.call(query, error);\n };\n};\n"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAyCA,MAAa,0BAA+B,QAAkB;AAC5D,KAAI;EACF,MAAM,YAAY,mBAAmB,IAAI;AACzC,MAAI,cAAc,KAAA,GAAW;AAC3B,YACE,gCACA,sGACD;AACD,UAAO;;AAET,cAAY,UAAU;UACf,OAAO;AACd,WACE,2BACA,iEACA,MACD;;AAEH,QAAO;;AAIT,MAAM,iBAAiB,OAAO,IAAI,uCAAuC;;;;;;;;AAWzE,MAAM,sBAAsB,QAAqC;CAC/D,MAAM,SAAU,KAAqC;AACrD,KAAI,OAAO,WAAW,WACpB;CAEF,MAAM,QAAkB,OAAqC,KAC3D,KACA,WACD;AACD,KAAI,UAAU,QAAQ,OAAO,UAAU,SACrC;CAEF,MAAM,YAAY,OAAO,eAAe,MAAM;AAC9C,KAAI,cAAc,KAChB;CAEF,MAAM,aAAa,OAAO,yBAAyB,WAAW,SAAS;AACvE,QAAO,eAAe,KAAA,KACpB,OAAO,WAAW,UAAU,cAC5B,WAAW,iBAAiB,OAC1B,YACA,KAAA;;AAGN,MAAM,eAAe,cAA4B;CAE/C,MAAM,WADa,OAAO,yBAAyB,WAAW,SACnC,EAAE;AAC7B,KACE,aAAa,KAAA,KACZ,SAAgD,oBAAoB,KAErE;CAGF,MAAM,UAAU,WAA8C;AAI5D,MAAI,KAAK,YAAY,CAAC,2BAA2B,KAAK,CACpD,QAAO,SAAS,KAAK,KAAK;EAE5B,MAAM,MAAM,sBAAsB,UAAU;AAC5C,MAAI,CAAC,OAAO,IAAI,SAAS,SACvB,QAAO,SAAS,KAAK,KAAK;AAE5B,MAAI;AACF,gBAAa,KAAK,KAAK;WAChB,OAAO;AACd,YACE,oBACA,0CACA,MACD;;AAEH,SAAO,SAAS,KAAK,KAAK;;AAE5B,QAAO,eAAe,SAAS,gBAAgB;EAC7C,OAAO;EACP,YAAY;EACZ,cAAc;EACf,CAAC;AACF,QAAO,eAAe,WAAW,UAAU;EACzC,OAAO;EACP,UAAU;EACV,YAAY;EACZ,cAAc;EACf,CAAC;;;;;;;AAQJ,MAAM,gBACJ,KACA,UACS;CACT,MAAM,YAAY,qBAAqB,MAAM,QAAQ;AACrD,KAAI,cAAc,KAAA,EAChB;CAEF,MAAM,cAAc,KAAK,KAAK;CAC9B,MAAM,OAAO;EACX,MAAM;EACN,WAAW,IAAI;EACf,GAAI,IAAI,sBAAsB,KAAA,IAC1B,EAAE,mBAAmB,IAAI,mBAAmB,GAC5C,EAAE;EACN,SAAS,IAAI;EACb,cAAc,IAAI;EAClB;EACA,QAAQ,wBAAwB,MAAM,KAAK;EAC3C,SAAS,eAAe,MAAM,MAAM;EACpC;EACD;CAED,MAAM,UAAU,YAA+C;AAC7D,MAAI;AACF,OAAI,OAAO,IAAI;IAAE,GAAG;IAAM,GAAG;IAAS,WAAW,KAAK,KAAK;IAAE,CAAC;WACvD,OAAO;AACd,YACE,mBACA,yCACA,MACD;;;CAIL,MAAM,kBAAkB,MAAM;CAC9B,MAAM,iBAAiB,MAAM;AAC7B,OAAM,WAAW,WAAoB;EAGnC,IAAI;AACJ,MAAI;AACF,UAAO,0BAA0B,OAAO;UAClC;AACN,UAAO,KAAA;;AAET,SACE,SAAS,KAAA,IACL,EAAE,GACF,EACE,QAAQ;GACN,MACE,KAAK,SAAA,SACD,KAAK,MAAM,GAAG,4BAA4B,GAC1C;GACN,WAAW,KAAK,SAAS;GAC1B,EACF,CACN;AACD,SAAO,gBAAgB,KAAK,OAAO,OAAO;;AAE5C,OAAM,UAAU,UAAmB;EACjC,IAAI;AACJ,MAAI;AACF,eAAY,yBAAyB,MAAM;UACrC;AACN,eAAY,KAAA;;AAEd,SAAO,cAAc,KAAA,IAAY,EAAE,GAAG,EAAE,WAAW,CAAC;AACpD,SAAO,eAAe,KAAK,OAAO,MAAM","debugId":"bcb30e85-33e1-52b2-9e08-5dd84f66d61c"}
|
package/dist/protocol.d.ts
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* recorder sidecar. The sidecar imports these types (type-only), so this file
|
|
5
5
|
* is the single source of truth for the shape of capture events.
|
|
6
6
|
*/
|
|
7
|
-
declare const SIDECAR_PROTOCOL_VERSION = "
|
|
7
|
+
declare const SIDECAR_PROTOCOL_VERSION = "2";
|
|
8
8
|
/** Header carrying {@link SIDECAR_PROTOCOL_VERSION} on every shim → sidecar request. */
|
|
9
9
|
declare const SIDECAR_PROTOCOL_VERSION_HEADER = "x-meticulous-sidecar-protocol-version";
|
|
10
10
|
/**
|
|
@@ -12,6 +12,8 @@ declare const SIDECAR_PROTOCOL_VERSION_HEADER = "x-meticulous-sidecar-protocol-v
|
|
|
12
12
|
* under recording; its value ties backend spans to the frontend session.
|
|
13
13
|
*/
|
|
14
14
|
declare const FRONTEND_SESSION_ID_HEADER = "x-meticulous-session-id";
|
|
15
|
+
/** Header identifying one replay attempt of a recorded frontend session. */
|
|
16
|
+
declare const REPLAY_ID_HEADER = "x-meticulous-replay-id";
|
|
15
17
|
/**
|
|
16
18
|
* A request carrying this header set to `"true"` is never served from the recording and
|
|
17
19
|
* never fails on a miss — it goes to the real network. Use it for a call that must be live
|
|
@@ -24,9 +26,9 @@ declare const FRONTEND_SESSION_ID_HEADER = "x-meticulous-session-id";
|
|
|
24
26
|
declare const METICULOUS_PASSTHROUGH_HEADER = "meticulous-passthrough";
|
|
25
27
|
declare const SIDECAR_EVENTS_PATH = "/v1/events";
|
|
26
28
|
/**
|
|
27
|
-
* Replay routes. A sidecar started in record mode answers 404 on both
|
|
28
|
-
*
|
|
29
|
-
*
|
|
29
|
+
* Replay routes. A sidecar started in record mode answers 404 on both. Replay is hermetic,
|
|
30
|
+
* so an unanswered outbound-fetch lookup fails the app's call rather than letting it reach
|
|
31
|
+
* the real service — the same as any other failure to stub.
|
|
30
32
|
*/
|
|
31
33
|
declare const SIDECAR_REPLAY_SESSION_PATH = "/v1/replay/session";
|
|
32
34
|
declare const SIDECAR_REPLAY_OUTBOUND_FETCH_PATH = "/v1/replay/outbound-fetch";
|
|
@@ -42,9 +44,12 @@ declare const REPLAY_SIDECAR_URL_HEADER = "x-meticulous-backend-replay-sidecar-u
|
|
|
42
44
|
/**
|
|
43
45
|
* The only headers persisted on capture events. Everything else — notably
|
|
44
46
|
* authorization, cookie/set-cookie, and API-key headers — is dropped at
|
|
45
|
-
* capture time, before it ever leaves the worker.
|
|
46
|
-
*
|
|
47
|
-
*
|
|
47
|
+
* capture time, before it ever leaves the worker.
|
|
48
|
+
*
|
|
49
|
+
* The Node backend recorder persists the same two, plus the inbound request's
|
|
50
|
+
* `Cookie` header on SERVER spans (`recorded-request-headers.ts`). This list
|
|
51
|
+
* has not followed it there, so a workerd recording of an authenticated server
|
|
52
|
+
* render still carries no session cookie.
|
|
48
53
|
*/
|
|
49
54
|
declare const CAPTURED_HEADERS: readonly ["content-type", "x-meticulous-session-id"];
|
|
50
55
|
interface CapturedBody {
|
|
@@ -53,11 +58,33 @@ interface CapturedBody {
|
|
|
53
58
|
/** True when the body exceeded the capture cap or the read was cut short. */
|
|
54
59
|
truncated: boolean;
|
|
55
60
|
}
|
|
56
|
-
|
|
61
|
+
/**
|
|
62
|
+
* The fields every capture event carries to say which request, session and trace it belongs to.
|
|
63
|
+
*/
|
|
64
|
+
interface CorrelatedEvent {
|
|
57
65
|
/** Correlates outbound events to the inbound request being handled. */
|
|
58
66
|
requestId: string;
|
|
59
67
|
/** Value of {@link FRONTEND_SESSION_ID_HEADER} on the inbound request, if any. */
|
|
60
68
|
frontendSessionId?: string;
|
|
69
|
+
/**
|
|
70
|
+
* Trace the request's spans belong to (32 hex chars), minted by the shim.
|
|
71
|
+
*
|
|
72
|
+
* The shim mints it, rather than the sidecar deriving it from {@link requestId}, so span
|
|
73
|
+
* assembly is stateless: a sidecar that had to remember `requestId → traceId` would give a
|
|
74
|
+
* request's SERVER span and its CLIENT children different traces whenever it lost that memory
|
|
75
|
+
* between two batches — which, for a Worker-hosted sidecar, an eviction can do at any moment.
|
|
76
|
+
*
|
|
77
|
+
* Optional so a sidecar keeps working against a shim that predates this field, in which case
|
|
78
|
+
* it falls back to its own per-`requestId` map.
|
|
79
|
+
*/
|
|
80
|
+
traceId?: string;
|
|
81
|
+
/**
|
|
82
|
+
* Span id of the request's SERVER span (16 hex chars), also minted by the shim: it is the
|
|
83
|
+
* parent of every CLIENT span the request produced, and the inbound event's own span id.
|
|
84
|
+
*/
|
|
85
|
+
serverSpanId?: string;
|
|
86
|
+
}
|
|
87
|
+
interface BaseRequestEvent extends CorrelatedEvent {
|
|
61
88
|
method: string;
|
|
62
89
|
/** Full URL including query string. */
|
|
63
90
|
url: string;
|
|
@@ -76,6 +103,19 @@ interface BaseRequestEvent {
|
|
|
76
103
|
*/
|
|
77
104
|
interface InboundRequestEvent extends BaseRequestEvent {
|
|
78
105
|
kind: "inbound";
|
|
106
|
+
/**
|
|
107
|
+
* Where {@link BaseRequestEvent.frontendSessionId} came from. Absent means the browser
|
|
108
|
+
* minted it and sent it on the request, which is the normal case. `"backend"` means the
|
|
109
|
+
* shim minted it for a document navigation the browser could not tag, and the page may or
|
|
110
|
+
* may not have gone on to adopt it — a distinction ingestion needs, because an unadopted
|
|
111
|
+
* backend-minted id names a session that will never exist and must be treated as unstamped.
|
|
112
|
+
* The sidecar turns it into `meticulous.session_id_origin`.
|
|
113
|
+
*
|
|
114
|
+
* Optional, and therefore compatible with an older sidecar (which ignores it) — which is
|
|
115
|
+
* why adding it does not bump {@link SIDECAR_PROTOCOL_VERSION}, a strict-equality check
|
|
116
|
+
* whose bump would instead make this shim unusable against one.
|
|
117
|
+
*/
|
|
118
|
+
sessionIdOrigin?: "backend";
|
|
79
119
|
}
|
|
80
120
|
/**
|
|
81
121
|
* An outgoing `fetch` call made while handling an inbound request (becomes a
|
|
@@ -123,11 +163,8 @@ type KvOmittedReason = "binary" | "stream";
|
|
|
123
163
|
* {@link BaseRequestEvent} beyond correlation and timing. Values are JSON so that a single
|
|
124
164
|
* `JSON.parse` reconstructs exactly what the app saw, whichever `type` the read asked for.
|
|
125
165
|
*/
|
|
126
|
-
interface KvOperationEvent {
|
|
166
|
+
interface KvOperationEvent extends CorrelatedEvent {
|
|
127
167
|
kind: "kv";
|
|
128
|
-
/** Correlates the operation to the inbound request being handled. */
|
|
129
|
-
requestId: string;
|
|
130
|
-
frontendSessionId?: string;
|
|
131
168
|
/**
|
|
132
169
|
* The `env` key the KV namespace was found under. Always present, unlike on a binding
|
|
133
170
|
* event: a namespace has no factory equivalent to `DurableObjectNamespace.get()`, so one
|
|
@@ -164,7 +201,34 @@ interface KvOperationEvent {
|
|
|
164
201
|
/** Set when the operation threw. */
|
|
165
202
|
error?: string;
|
|
166
203
|
}
|
|
167
|
-
|
|
204
|
+
/**
|
|
205
|
+
* One postgres.js query made while handling an inbound request (becomes a CLIENT span).
|
|
206
|
+
*
|
|
207
|
+
* Deliberately carries the same fields the Node recorder puts on a postgres.js span rather than
|
|
208
|
+
* anything Worker-specific: `PostgresJsMockStore` builds its match key from those attributes at
|
|
209
|
+
* load time, so a query recorded here replays through the existing Node mock path unchanged.
|
|
210
|
+
* See `postgres/capture.ts` for the serialization, which both surfaces share.
|
|
211
|
+
*/
|
|
212
|
+
interface PostgresQueryEvent extends CorrelatedEvent {
|
|
213
|
+
kind: "postgres";
|
|
214
|
+
/** SQL rebuilt from the tagged template's chunks, with `$1`, `$2`… placeholders. */
|
|
215
|
+
queryText: string;
|
|
216
|
+
/** JSON of the interpolated values, from `serializePostgresJsArgs`. */
|
|
217
|
+
params: string;
|
|
218
|
+
/** `""`, `"raw"` or `"values"` — the row shape the query asked for. */
|
|
219
|
+
rowMode: string;
|
|
220
|
+
/** JSON of the resolved `Result`, capped at {@link MAX_POSTGRES_JS_RESULT_SIZE}. */
|
|
221
|
+
result?: CapturedBody;
|
|
222
|
+
/**
|
|
223
|
+
* JSON of the rejection, from `serializePostgresJsError`. Set instead of {@link result}, and
|
|
224
|
+
* distinct from the transport-level `error` string on the HTTP-shaped events: this one is a
|
|
225
|
+
* recorded outcome the replay reproduces, not a note that capture failed.
|
|
226
|
+
*/
|
|
227
|
+
errorJson?: string;
|
|
228
|
+
startTimeMs: number;
|
|
229
|
+
endTimeMs: number;
|
|
230
|
+
}
|
|
231
|
+
type CaptureEvent = InboundRequestEvent | OutboundRequestEvent | BindingRequestEvent | KvOperationEvent | PostgresQueryEvent;
|
|
168
232
|
interface CaptureEventsPayload {
|
|
169
233
|
events: CaptureEvent[];
|
|
170
234
|
}
|
|
@@ -183,6 +247,7 @@ interface ReplaySessionInfoResponse {
|
|
|
183
247
|
/** Lookup for a single outbound `fetch`, sent to {@link SIDECAR_REPLAY_OUTBOUND_FETCH_PATH}. */
|
|
184
248
|
interface OutboundFetchLookupRequest {
|
|
185
249
|
frontendSessionId: string;
|
|
250
|
+
replayId: string;
|
|
186
251
|
method: string;
|
|
187
252
|
/** Full URL including query string. */
|
|
188
253
|
url: string;
|
|
@@ -194,9 +259,10 @@ interface OutboundFetchLookupRequest {
|
|
|
194
259
|
requestBody?: CapturedBody;
|
|
195
260
|
}
|
|
196
261
|
/**
|
|
197
|
-
* A recorded response to serve, a
|
|
198
|
-
*
|
|
199
|
-
*
|
|
262
|
+
* A recorded response to serve, or a signal the caller must fail the request. Replay is
|
|
263
|
+
* hermetic, so the shim never turns any of these into a real call — nor an outcome it does
|
|
264
|
+
* not recognise. The only live path is the request-side `meticulous-passthrough` header,
|
|
265
|
+
* handled before the lookup, not any response here.
|
|
200
266
|
*/
|
|
201
267
|
type OutboundFetchLookupResponse = {
|
|
202
268
|
outcome: "mock";
|
|
@@ -205,21 +271,15 @@ type OutboundFetchLookupResponse = {
|
|
|
205
271
|
headers: Record<string, string>;
|
|
206
272
|
}
|
|
207
273
|
/**
|
|
208
|
-
* The store
|
|
209
|
-
*
|
|
210
|
-
* Node backend recorder's
|
|
274
|
+
* The store holds nothing for this call — a genuine miss, or a call the sidecar could not
|
|
275
|
+
* look up at all (an unparseable URL). Replay is hermetic, so the shim fails the request
|
|
276
|
+
* rather than letting it reach the real service — matching the Node backend recorder's
|
|
277
|
+
* http/undici mocks. There is deliberately no "let the call through" outcome: the only
|
|
278
|
+
* live path is the request-side `meticulous-passthrough` header.
|
|
211
279
|
*/
|
|
212
280
|
| {
|
|
213
281
|
outcome: "no-mock";
|
|
214
|
-
}
|
|
215
|
-
/**
|
|
216
|
-
* The call cannot be looked up at all (an unparseable URL), so there is no miss to report.
|
|
217
|
-
* Also what an older sidecar answers on a miss, in which case the shim degrades to the
|
|
218
|
-
* pre-`no-mock` fail-open behaviour rather than breaking the app.
|
|
219
|
-
*/
|
|
220
|
-
| {
|
|
221
|
-
outcome: "passthrough";
|
|
222
282
|
};
|
|
223
283
|
//#endregion
|
|
224
|
-
export { BindingRequestEvent, CAPTURED_HEADERS, CaptureEvent, CaptureEventsPayload, CapturedBody, FRONTEND_SESSION_ID_HEADER, InboundRequestEvent, KvOmittedReason, KvOperation, KvOperationEvent, METICULOUS_PASSTHROUGH_HEADER, OutboundFetchLookupRequest, OutboundFetchLookupResponse, OutboundRequestEvent, REPLAY_SIDECAR_URL_HEADER, ReplaySessionInfoResponse, SIDECAR_EVENTS_PATH, SIDECAR_PROTOCOL_VERSION, SIDECAR_PROTOCOL_VERSION_HEADER, SIDECAR_REPLAY_OUTBOUND_FETCH_PATH, SIDECAR_REPLAY_SESSION_PATH };
|
|
284
|
+
export { BindingRequestEvent, CAPTURED_HEADERS, CaptureEvent, CaptureEventsPayload, CapturedBody, FRONTEND_SESSION_ID_HEADER, InboundRequestEvent, KvOmittedReason, KvOperation, KvOperationEvent, METICULOUS_PASSTHROUGH_HEADER, OutboundFetchLookupRequest, OutboundFetchLookupResponse, OutboundRequestEvent, PostgresQueryEvent, REPLAY_ID_HEADER, REPLAY_SIDECAR_URL_HEADER, ReplaySessionInfoResponse, SIDECAR_EVENTS_PATH, SIDECAR_PROTOCOL_VERSION, SIDECAR_PROTOCOL_VERSION_HEADER, SIDECAR_REPLAY_OUTBOUND_FETCH_PATH, SIDECAR_REPLAY_SESSION_PATH };
|
|
225
285
|
//# sourceMappingURL=protocol.d.ts.map
|
package/dist/protocol.js
CHANGED
|
@@ -5,8 +5,8 @@
|
|
|
5
5
|
* is the single source of truth for the shape of capture events.
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
|
-
!function(){try{var e="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof globalThis?globalThis:"undefined"!=typeof self?self:{},n=(new e.Error).stack;n&&(e._sentryDebugIds=e._sentryDebugIds||{},e._sentryDebugIds[n]="
|
|
9
|
-
const SIDECAR_PROTOCOL_VERSION = "
|
|
8
|
+
!function(){try{var e="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof globalThis?globalThis:"undefined"!=typeof self?self:{},n=(new e.Error).stack;n&&(e._sentryDebugIds=e._sentryDebugIds||{},e._sentryDebugIds[n]="ca4290fa-420e-5b3e-be68-65bc78d7f094")}catch(e){}}();
|
|
9
|
+
const SIDECAR_PROTOCOL_VERSION = "2";
|
|
10
10
|
/** Header carrying {@link SIDECAR_PROTOCOL_VERSION} on every shim → sidecar request. */
|
|
11
11
|
const SIDECAR_PROTOCOL_VERSION_HEADER = "x-meticulous-sidecar-protocol-version";
|
|
12
12
|
/**
|
|
@@ -14,6 +14,8 @@ const SIDECAR_PROTOCOL_VERSION_HEADER = "x-meticulous-sidecar-protocol-version";
|
|
|
14
14
|
* under recording; its value ties backend spans to the frontend session.
|
|
15
15
|
*/
|
|
16
16
|
const FRONTEND_SESSION_ID_HEADER = "x-meticulous-session-id";
|
|
17
|
+
/** Header identifying one replay attempt of a recorded frontend session. */
|
|
18
|
+
const REPLAY_ID_HEADER = "x-meticulous-replay-id";
|
|
17
19
|
/**
|
|
18
20
|
* A request carrying this header set to `"true"` is never served from the recording and
|
|
19
21
|
* never fails on a miss — it goes to the real network. Use it for a call that must be live
|
|
@@ -26,9 +28,9 @@ const FRONTEND_SESSION_ID_HEADER = "x-meticulous-session-id";
|
|
|
26
28
|
const METICULOUS_PASSTHROUGH_HEADER = "meticulous-passthrough";
|
|
27
29
|
const SIDECAR_EVENTS_PATH = "/v1/events";
|
|
28
30
|
/**
|
|
29
|
-
* Replay routes. A sidecar started in record mode answers 404 on both
|
|
30
|
-
*
|
|
31
|
-
*
|
|
31
|
+
* Replay routes. A sidecar started in record mode answers 404 on both. Replay is hermetic,
|
|
32
|
+
* so an unanswered outbound-fetch lookup fails the app's call rather than letting it reach
|
|
33
|
+
* the real service — the same as any other failure to stub.
|
|
32
34
|
*/
|
|
33
35
|
const SIDECAR_REPLAY_SESSION_PATH = "/v1/replay/session";
|
|
34
36
|
const SIDECAR_REPLAY_OUTBOUND_FETCH_PATH = "/v1/replay/outbound-fetch";
|
|
@@ -44,13 +46,16 @@ const REPLAY_SIDECAR_URL_HEADER = "x-meticulous-backend-replay-sidecar-url";
|
|
|
44
46
|
/**
|
|
45
47
|
* The only headers persisted on capture events. Everything else — notably
|
|
46
48
|
* authorization, cookie/set-cookie, and API-key headers — is dropped at
|
|
47
|
-
* capture time, before it ever leaves the worker.
|
|
48
|
-
*
|
|
49
|
-
*
|
|
49
|
+
* capture time, before it ever leaves the worker.
|
|
50
|
+
*
|
|
51
|
+
* The Node backend recorder persists the same two, plus the inbound request's
|
|
52
|
+
* `Cookie` header on SERVER spans (`recorded-request-headers.ts`). This list
|
|
53
|
+
* has not followed it there, so a workerd recording of an authenticated server
|
|
54
|
+
* render still carries no session cookie.
|
|
50
55
|
*/
|
|
51
56
|
const CAPTURED_HEADERS = ["content-type", FRONTEND_SESSION_ID_HEADER];
|
|
52
57
|
//#endregion
|
|
53
|
-
export { CAPTURED_HEADERS, FRONTEND_SESSION_ID_HEADER, METICULOUS_PASSTHROUGH_HEADER, REPLAY_SIDECAR_URL_HEADER, SIDECAR_EVENTS_PATH, SIDECAR_PROTOCOL_VERSION, SIDECAR_PROTOCOL_VERSION_HEADER, SIDECAR_REPLAY_OUTBOUND_FETCH_PATH, SIDECAR_REPLAY_SESSION_PATH };
|
|
58
|
+
export { CAPTURED_HEADERS, FRONTEND_SESSION_ID_HEADER, METICULOUS_PASSTHROUGH_HEADER, REPLAY_ID_HEADER, REPLAY_SIDECAR_URL_HEADER, SIDECAR_EVENTS_PATH, SIDECAR_PROTOCOL_VERSION, SIDECAR_PROTOCOL_VERSION_HEADER, SIDECAR_REPLAY_OUTBOUND_FETCH_PATH, SIDECAR_REPLAY_SESSION_PATH };
|
|
54
59
|
|
|
55
60
|
//# sourceMappingURL=protocol.js.map
|
|
56
|
-
//# debugId=
|
|
61
|
+
//# debugId=ca4290fa-420e-5b3e-be68-65bc78d7f094
|