@alwaysmeticulous/backend-recorder-workerd 2.343.0 → 2.350.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 +7 -1
- package/dist/agent-swarm-testing-fetch.js +40 -0
- package/dist/agent-swarm-testing-fetch.js.map +1 -0
- package/dist/base64.js +25 -0
- package/dist/base64.js.map +1 -0
- package/dist/body-capture.d.ts +11 -2
- package/dist/body-capture.js +60 -22
- package/dist/body-capture.js.map +1 -1
- package/dist/body-encoding.d.ts +49 -0
- package/dist/body-encoding.js +68 -0
- package/dist/body-encoding.js.map +1 -0
- package/dist/captured-result-codec.js +1 -15
- package/dist/captured-result-codec.js.map +1 -1
- package/dist/context.js +1 -1
- package/dist/context.js.map +1 -1
- package/dist/fetch-patch.js +4 -1
- package/dist/fetch-patch.js.map +1 -1
- package/dist/index.d.ts +17 -3
- package/dist/index.js +17 -3
- package/dist/index.js.map +1 -1
- package/dist/outbound-capture.js +2 -2
- package/dist/outbound-capture.js.map +1 -1
- package/dist/protocol.d.ts +199 -8
- package/dist/protocol.js +45 -7
- package/dist/protocol.js.map +1 -1
- package/dist/replay-fetch.js +13 -2
- package/dist/replay-fetch.js.map +1 -1
- package/dist/sidecar-client.js +32 -2
- package/dist/sidecar-client.js.map +1 -1
- package/dist/{replay-sidecar-url.js → sidecar-url.js} +9 -8
- package/dist/sidecar-url.js.map +1 -0
- package/dist/spans/span-builder.js +2 -0
- package/dist/spans/span-builder.js.map +1 -1
- package/dist/version.js +1 -1
- package/dist/with-meticulous.js +0 -0
- package/dist/with-meticulous.js.map +1 -1
- package/package.json +1 -1
- package/dist/replay-sidecar-url.js.map +0 -1
package/dist/index.d.ts
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import { SIDECAR_BINDING_ENV_KEY, SIDECAR_BINDING_ORIGIN, SIDECAR_URL_ENV_KEY, SidecarFetcher, SidecarTransport } from "./sidecar-transport.js";
|
|
2
2
|
import { MeticulousExecutionContext, WithMeticulousOptions } from "./with-meticulous.js";
|
|
3
|
-
import {
|
|
3
|
+
import { BODY_ENCODING_BASE64, BodyEncoding, EncodedBody, HTTP_RESPONSE_BODY_ENCODING_ATTR, decodeBodyBytes, encodeBodyBytes } from "./body-encoding.js";
|
|
4
|
+
import { AGENT_SWARM_TESTING_RUN_ID_ENV_KEY, AGENT_SWARM_TESTING_SIDECAR_URL_ENV_KEY, BE_MOCK_PING_INTERVAL_MS, BeMockCall, BeMockCallsReport, BeMockFailedMessage, BeMockInstallMessage, BeMockLoadSessionMessage, BeMockLogMessage, BeMockReadyMessage, BeMockRecordedRequest, BeMockSessionMessage, BeMockSidecarMessage, BeMockWorkerMessage, BindingRequestEvent, CAPTURED_HEADERS, CaptureEvent, CaptureEventsPayload, CapturedBody, CoverageReportRequest, FRONTEND_SESSION_ID_HEADER, InboundRequestEvent, KvOmittedReason, KvOperation, KvOperationEvent, METICULOUS_PASSTHROUGH_HEADER, OutboundFetchAgentSwarmTestingRequest, OutboundFetchAgentSwarmTestingResponse, OutboundFetchLookupRequest, OutboundFetchLookupResponse, OutboundRequestEvent, PostgresQueryEvent, REPLAY_ID_HEADER, REPLAY_SIDECAR_URL_HEADER, ReplaySessionInfoResponse, SIDECAR_AGENT_SWARM_TESTING_BE_MOCK_CALLS_PATH, SIDECAR_AGENT_SWARM_TESTING_BE_MOCK_PATH, SIDECAR_AGENT_SWARM_TESTING_OUTBOUND_FETCH_PATH, SIDECAR_EVENTS_PATH, SIDECAR_PROTOCOL_VERSION, SIDECAR_PROTOCOL_VERSION_HEADER, SIDECAR_REPLAY_COVERAGE_PATH, SIDECAR_REPLAY_OUTBOUND_FETCH_PATH, SIDECAR_REPLAY_SESSION_PATH, VIRTUAL_TIME_HEADER, WORKERD_SHIM_VERSION_HEADER, parseVirtualTimeMs } from "./protocol.js";
|
|
4
5
|
import { WORKERD_SHIM_VERSION } from "./version.js";
|
|
5
|
-
import { MAX_BODY_CAPTURE_SIZE, readBodyWithCap, readRequestBodyWithCap } from "./body-capture.js";
|
|
6
|
+
import { MAX_BODY_CAPTURE_SIZE, readBodyWithCap, readRequestBodyWithCap, readResponseBodyWithCap } from "./body-capture.js";
|
|
6
7
|
import { headersToRecord } from "./outbound-capture.js";
|
|
7
8
|
import { HeaderReader, SERVER_TIMING_HEADER, SERVER_TIMING_SESSION_METRIC, buildServerTimingSessionEntry, isProvisionalSessionIdCandidate, mintProvisionalSessionId } from "./provisional-session-id.js";
|
|
8
9
|
import { isHealthProbeRequest } from "./health-probe.js";
|
|
@@ -64,6 +65,19 @@ interface MeticulousWorkerHandler<Env = never> {
|
|
|
64
65
|
* an image is far more likely than a spurious replay header (nothing but the replay runner emits
|
|
65
66
|
* one), and letting the env win would silently record a replay instead of mocking it.
|
|
66
67
|
*
|
|
68
|
+
* **Agent-swarm testing** activates on two deployment env vars,
|
|
69
|
+
* `METICULOUS_AGENT_SWARM_TESTING_SIDECAR_URL` and `METICULOUS_AGENT_SWARM_TESTING_RUN_ID`,
|
|
70
|
+
* set by whoever deploys the worker for a swarm run. These must be Worker bindings,
|
|
71
|
+
* not just environment variables on the host process: forward them at startup through
|
|
72
|
+
* Wrangler vars or the `.dev.vars` file consumed by the runtime. The app calls a sidecar
|
|
73
|
+
* in the same pod through loopback; configuring the sidecar's own environment does not
|
|
74
|
+
* configure the Worker's bindings. Every outgoing `fetch` call is first
|
|
75
|
+
* offered to the interceptor service the URL names; it either supplies the response to serve
|
|
76
|
+
* or the call passes through to the real service. Unlike replay the mode is best-effort by
|
|
77
|
+
* design: a call the interceptor cannot answer is never failed, there is no frozen clock and
|
|
78
|
+
* no seeded randomness, and nothing is recorded. The sidecar URL is validated exactly like
|
|
79
|
+
* the replay one.
|
|
80
|
+
*
|
|
67
81
|
* With neither configured the wrapper is a complete pass-through, so it is safe to keep in
|
|
68
82
|
* deployed code. Requires the `nodejs_als` (or `nodejs_compat`) compatibility flag. A capture
|
|
69
83
|
* failure or unreachable sidecar never affects the app.
|
|
@@ -89,5 +103,5 @@ declare const withMeticulous: <H extends MeticulousWorkerHandler<never>>(handler
|
|
|
89
103
|
*/
|
|
90
104
|
declare const getMeticulousSessionId: () => string | undefined;
|
|
91
105
|
//#endregion
|
|
92
|
-
export { type BindingRequestEvent, CAPTURED_HEADERS, CLIENT_TECHNOLOGY_ATTR, CLIENT_TECHNOLOGY_POSTGRES_JS, CLIENT_TECHNOLOGY_WORKERD_BINDING, CLIENT_TECHNOLOGY_WORKERD_FETCH, CLIENT_TECHNOLOGY_WORKERD_KV, type CaptureEvent, type CaptureEventsPayload, type CapturedBody, type CoverageMap, type CoverageMapFile, type CoverageReportRequest, FRONTEND_SESSION_ID_ATTR, FRONTEND_SESSION_ID_HEADER, type HeaderReader, HealthProbeEventFilter, type HrTime, type InboundRequestEvent, type KvCaptureFields, type KvCaptureOutcome, type KvOmittedReason, type KvOperation, type KvOperationEvent, MAX_BODY_CAPTURE_SIZE, MAX_POSTGRES_JS_RESULT_SIZE, METICULOUS_PASSTHROUGH_HEADER, type MeticulousExecutionContext, type MeticulousPagesFunction, type MeticulousPagesFunctionContext, MeticulousWorkerHandler, type OutboundCallTarget, type OutboundFetchLookupRequest, type OutboundFetchLookupResponse, type OutboundRequestEvent, 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, type PostgresJsQueryLike, type PostgresJsRowMode, type PostgresQueryEvent, REPLAY_ID_HEADER, REPLAY_SIDECAR_URL_HEADER, type ReadableSpan, type ReplaySessionInfoResponse, SERVER_TIMING_HEADER, SERVER_TIMING_SESSION_METRIC, SESSION_ID_ORIGIN_ATTR, SESSION_ID_ORIGIN_BACKEND, SIDECAR_BINDING_ENV_KEY, SIDECAR_BINDING_ORIGIN, SIDECAR_EVENTS_PATH, SIDECAR_PROTOCOL_VERSION, SIDECAR_PROTOCOL_VERSION_HEADER, SIDECAR_REPLAY_COVERAGE_PATH, SIDECAR_REPLAY_OUTBOUND_FETCH_PATH, SIDECAR_REPLAY_SESSION_PATH, SIDECAR_URL_ENV_KEY, SPAN_KIND_CLIENT, SPAN_KIND_INTERNAL, SPAN_KIND_SERVER, SPAN_STATUS_ERROR, SPAN_STATUS_OK, SPAN_STATUS_UNSET, STR_REDACTED, type SerializedSpanBase, type SidecarFetcher, type SidecarTransport, SpanBuilder, type SpanStatus, VIRTUAL_TIME_HEADER, WORKERD_BINDING_NAME_ATTR, WORKERD_KV_ARGS_ATTR, WORKERD_KV_ARGS_TRUNCATED_ATTR, WORKERD_KV_KEY_ATTR, WORKERD_KV_OMITTED_ATTR, WORKERD_KV_OPERATION_ATTR, WORKERD_KV_RESULT_ATTR, WORKERD_KV_RESULT_TRUNCATED_ATTR, WORKERD_KV_VALUE_ATTR, WORKERD_KV_VALUE_TRUNCATED_ATTR, WORKERD_SHIM_VERSION, WORKERD_SHIM_VERSION_HEADER, type WithMeticulousOptions, __mcEnter, __mcHit, buildServerTimingSessionEntry, collectHitIds, createCoverageSink, deserializeCapturedError, deserializeCapturedResult, getCoverageMap, getMeticulousSessionId, getUnreportedCoverageFiles, headersToRecord, hrTimeToMs, isHealthProbeRequest, isOrphanedDatadogAgentRequest, isOrphanedDatadogAgentUrl, isProvisionalSessionIdCandidate, isSupportedPostgresJsQuery, markCoverageFilesReported, mintProvisionalSessionId, msToHrTime, parseVirtualTimeMs, postgresJsCommandOf, randomHex, readBodyWithCap, readRequestBodyWithCap, reconstructQueryText, redactRequestBody, redactUrlCredentials, registerCoverageFile, resolveRowMode, serializeCapturedError, serializeCapturedResult, serializeKvArgs, serializeKvCaptureFields, serializePostgresJsArgs, serializePostgresJsError, serializePostgresJsResult, serializeSpan, withMeticulous, withMeticulousPagesFunction, withMeticulousPostgres };
|
|
106
|
+
export { AGENT_SWARM_TESTING_RUN_ID_ENV_KEY, AGENT_SWARM_TESTING_SIDECAR_URL_ENV_KEY, BE_MOCK_PING_INTERVAL_MS, BODY_ENCODING_BASE64, type BeMockCall, type BeMockCallsReport, type BeMockFailedMessage, type BeMockInstallMessage, type BeMockLoadSessionMessage, type BeMockLogMessage, type BeMockReadyMessage, type BeMockRecordedRequest, type BeMockSessionMessage, type BeMockSidecarMessage, type BeMockWorkerMessage, type BindingRequestEvent, type BodyEncoding, CAPTURED_HEADERS, CLIENT_TECHNOLOGY_ATTR, CLIENT_TECHNOLOGY_POSTGRES_JS, CLIENT_TECHNOLOGY_WORKERD_BINDING, CLIENT_TECHNOLOGY_WORKERD_FETCH, CLIENT_TECHNOLOGY_WORKERD_KV, type CaptureEvent, type CaptureEventsPayload, type CapturedBody, type CoverageMap, type CoverageMapFile, type CoverageReportRequest, type EncodedBody, FRONTEND_SESSION_ID_ATTR, FRONTEND_SESSION_ID_HEADER, HTTP_RESPONSE_BODY_ENCODING_ATTR, type HeaderReader, HealthProbeEventFilter, type HrTime, type InboundRequestEvent, type KvCaptureFields, type KvCaptureOutcome, type KvOmittedReason, type KvOperation, type KvOperationEvent, MAX_BODY_CAPTURE_SIZE, MAX_POSTGRES_JS_RESULT_SIZE, METICULOUS_PASSTHROUGH_HEADER, type MeticulousExecutionContext, type MeticulousPagesFunction, type MeticulousPagesFunctionContext, MeticulousWorkerHandler, type OutboundCallTarget, type OutboundFetchAgentSwarmTestingRequest, type OutboundFetchAgentSwarmTestingResponse, type OutboundFetchLookupRequest, type OutboundFetchLookupResponse, type OutboundRequestEvent, 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, type PostgresJsQueryLike, type PostgresJsRowMode, type PostgresQueryEvent, REPLAY_ID_HEADER, REPLAY_SIDECAR_URL_HEADER, type ReadableSpan, type ReplaySessionInfoResponse, SERVER_TIMING_HEADER, SERVER_TIMING_SESSION_METRIC, SESSION_ID_ORIGIN_ATTR, SESSION_ID_ORIGIN_BACKEND, SIDECAR_AGENT_SWARM_TESTING_BE_MOCK_CALLS_PATH, SIDECAR_AGENT_SWARM_TESTING_BE_MOCK_PATH, SIDECAR_AGENT_SWARM_TESTING_OUTBOUND_FETCH_PATH, SIDECAR_BINDING_ENV_KEY, SIDECAR_BINDING_ORIGIN, SIDECAR_EVENTS_PATH, SIDECAR_PROTOCOL_VERSION, SIDECAR_PROTOCOL_VERSION_HEADER, SIDECAR_REPLAY_COVERAGE_PATH, SIDECAR_REPLAY_OUTBOUND_FETCH_PATH, SIDECAR_REPLAY_SESSION_PATH, SIDECAR_URL_ENV_KEY, SPAN_KIND_CLIENT, SPAN_KIND_INTERNAL, SPAN_KIND_SERVER, SPAN_STATUS_ERROR, SPAN_STATUS_OK, SPAN_STATUS_UNSET, STR_REDACTED, type SerializedSpanBase, type SidecarFetcher, type SidecarTransport, SpanBuilder, type SpanStatus, VIRTUAL_TIME_HEADER, WORKERD_BINDING_NAME_ATTR, WORKERD_KV_ARGS_ATTR, WORKERD_KV_ARGS_TRUNCATED_ATTR, WORKERD_KV_KEY_ATTR, WORKERD_KV_OMITTED_ATTR, WORKERD_KV_OPERATION_ATTR, WORKERD_KV_RESULT_ATTR, WORKERD_KV_RESULT_TRUNCATED_ATTR, WORKERD_KV_VALUE_ATTR, WORKERD_KV_VALUE_TRUNCATED_ATTR, WORKERD_SHIM_VERSION, WORKERD_SHIM_VERSION_HEADER, type WithMeticulousOptions, __mcEnter, __mcHit, buildServerTimingSessionEntry, collectHitIds, createCoverageSink, decodeBodyBytes, deserializeCapturedError, deserializeCapturedResult, encodeBodyBytes, getCoverageMap, getMeticulousSessionId, getUnreportedCoverageFiles, headersToRecord, hrTimeToMs, isHealthProbeRequest, isOrphanedDatadogAgentRequest, isOrphanedDatadogAgentUrl, isProvisionalSessionIdCandidate, isSupportedPostgresJsQuery, markCoverageFilesReported, mintProvisionalSessionId, msToHrTime, parseVirtualTimeMs, postgresJsCommandOf, randomHex, readBodyWithCap, readRequestBodyWithCap, readResponseBodyWithCap, reconstructQueryText, redactRequestBody, redactUrlCredentials, registerCoverageFile, resolveRowMode, serializeCapturedError, serializeCapturedResult, serializeKvArgs, serializeKvCaptureFields, serializePostgresJsArgs, serializePostgresJsError, serializePostgresJsResult, serializeSpan, withMeticulous, withMeticulousPagesFunction, withMeticulousPostgres };
|
|
93
107
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.js
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import { SIDECAR_BINDING_ENV_KEY, SIDECAR_BINDING_ORIGIN, SIDECAR_URL_ENV_KEY } from "./sidecar-transport.js";
|
|
2
2
|
import { requestCaptureContext } from "./context.js";
|
|
3
|
+
import { BODY_ENCODING_BASE64, HTTP_RESPONSE_BODY_ENCODING_ATTR, decodeBodyBytes, encodeBodyBytes } from "./body-encoding.js";
|
|
3
4
|
import { STR_REDACTED, redactRequestBody } from "./redact-body.js";
|
|
4
|
-
import { MAX_BODY_CAPTURE_SIZE, readBodyWithCap, readRequestBodyWithCap } from "./body-capture.js";
|
|
5
|
-
import { 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_COVERAGE_PATH, SIDECAR_REPLAY_OUTBOUND_FETCH_PATH, SIDECAR_REPLAY_SESSION_PATH, VIRTUAL_TIME_HEADER, WORKERD_SHIM_VERSION_HEADER, parseVirtualTimeMs } from "./protocol.js";
|
|
5
|
+
import { MAX_BODY_CAPTURE_SIZE, readBodyWithCap, readRequestBodyWithCap, readResponseBodyWithCap } from "./body-capture.js";
|
|
6
|
+
import { AGENT_SWARM_TESTING_RUN_ID_ENV_KEY, AGENT_SWARM_TESTING_SIDECAR_URL_ENV_KEY, BE_MOCK_PING_INTERVAL_MS, CAPTURED_HEADERS, FRONTEND_SESSION_ID_HEADER, METICULOUS_PASSTHROUGH_HEADER, REPLAY_ID_HEADER, REPLAY_SIDECAR_URL_HEADER, SIDECAR_AGENT_SWARM_TESTING_BE_MOCK_CALLS_PATH, SIDECAR_AGENT_SWARM_TESTING_BE_MOCK_PATH, SIDECAR_AGENT_SWARM_TESTING_OUTBOUND_FETCH_PATH, SIDECAR_EVENTS_PATH, SIDECAR_PROTOCOL_VERSION, SIDECAR_PROTOCOL_VERSION_HEADER, SIDECAR_REPLAY_COVERAGE_PATH, SIDECAR_REPLAY_OUTBOUND_FETCH_PATH, SIDECAR_REPLAY_SESSION_PATH, VIRTUAL_TIME_HEADER, WORKERD_SHIM_VERSION_HEADER, parseVirtualTimeMs } from "./protocol.js";
|
|
6
7
|
import { headersToRecord } from "./outbound-capture.js";
|
|
7
8
|
import { __mcEnter, __mcHit, collectHitIds, createCoverageSink, getCoverageMap, getUnreportedCoverageFiles, markCoverageFilesReported, registerCoverageFile } from "./coverage/runtime.js";
|
|
8
9
|
import { isOrphanedDatadogAgentRequest, isOrphanedDatadogAgentUrl } from "./datadog-agent.js";
|
|
@@ -59,6 +60,19 @@ import { serializeSpan } from "./spans/serialize-span.js";
|
|
|
59
60
|
* an image is far more likely than a spurious replay header (nothing but the replay runner emits
|
|
60
61
|
* one), and letting the env win would silently record a replay instead of mocking it.
|
|
61
62
|
*
|
|
63
|
+
* **Agent-swarm testing** activates on two deployment env vars,
|
|
64
|
+
* `METICULOUS_AGENT_SWARM_TESTING_SIDECAR_URL` and `METICULOUS_AGENT_SWARM_TESTING_RUN_ID`,
|
|
65
|
+
* set by whoever deploys the worker for a swarm run. These must be Worker bindings,
|
|
66
|
+
* not just environment variables on the host process: forward them at startup through
|
|
67
|
+
* Wrangler vars or the `.dev.vars` file consumed by the runtime. The app calls a sidecar
|
|
68
|
+
* in the same pod through loopback; configuring the sidecar's own environment does not
|
|
69
|
+
* configure the Worker's bindings. Every outgoing `fetch` call is first
|
|
70
|
+
* offered to the interceptor service the URL names; it either supplies the response to serve
|
|
71
|
+
* or the call passes through to the real service. Unlike replay the mode is best-effort by
|
|
72
|
+
* design: a call the interceptor cannot answer is never failed, there is no frozen clock and
|
|
73
|
+
* no seeded randomness, and nothing is recorded. The sidecar URL is validated exactly like
|
|
74
|
+
* the replay one.
|
|
75
|
+
*
|
|
62
76
|
* With neither configured the wrapper is a complete pass-through, so it is safe to keep in
|
|
63
77
|
* deployed code. Requires the `nodejs_als` (or `nodejs_compat`) compatibility flag. A capture
|
|
64
78
|
* failure or unreachable sidecar never affects the app.
|
|
@@ -95,6 +109,6 @@ const withMeticulous = (handler, options) => {
|
|
|
95
109
|
*/
|
|
96
110
|
const getMeticulousSessionId = () => requestCaptureContext.getStore()?.frontendSessionId;
|
|
97
111
|
//#endregion
|
|
98
|
-
export { CAPTURED_HEADERS, CLIENT_TECHNOLOGY_ATTR, CLIENT_TECHNOLOGY_POSTGRES_JS, CLIENT_TECHNOLOGY_WORKERD_BINDING, CLIENT_TECHNOLOGY_WORKERD_FETCH, CLIENT_TECHNOLOGY_WORKERD_KV, FRONTEND_SESSION_ID_ATTR, FRONTEND_SESSION_ID_HEADER, HealthProbeEventFilter, MAX_BODY_CAPTURE_SIZE, MAX_POSTGRES_JS_RESULT_SIZE, METICULOUS_PASSTHROUGH_HEADER, 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, REPLAY_ID_HEADER, REPLAY_SIDECAR_URL_HEADER, SERVER_TIMING_HEADER, SERVER_TIMING_SESSION_METRIC, SESSION_ID_ORIGIN_ATTR, SESSION_ID_ORIGIN_BACKEND, SIDECAR_BINDING_ENV_KEY, SIDECAR_BINDING_ORIGIN, SIDECAR_EVENTS_PATH, SIDECAR_PROTOCOL_VERSION, SIDECAR_PROTOCOL_VERSION_HEADER, SIDECAR_REPLAY_COVERAGE_PATH, SIDECAR_REPLAY_OUTBOUND_FETCH_PATH, SIDECAR_REPLAY_SESSION_PATH, SIDECAR_URL_ENV_KEY, SPAN_KIND_CLIENT, SPAN_KIND_INTERNAL, SPAN_KIND_SERVER, SPAN_STATUS_ERROR, SPAN_STATUS_OK, SPAN_STATUS_UNSET, STR_REDACTED, SpanBuilder, VIRTUAL_TIME_HEADER, WORKERD_BINDING_NAME_ATTR, WORKERD_KV_ARGS_ATTR, WORKERD_KV_ARGS_TRUNCATED_ATTR, WORKERD_KV_KEY_ATTR, WORKERD_KV_OMITTED_ATTR, WORKERD_KV_OPERATION_ATTR, WORKERD_KV_RESULT_ATTR, WORKERD_KV_RESULT_TRUNCATED_ATTR, WORKERD_KV_VALUE_ATTR, WORKERD_KV_VALUE_TRUNCATED_ATTR, WORKERD_SHIM_VERSION, WORKERD_SHIM_VERSION_HEADER, __mcEnter, __mcHit, buildServerTimingSessionEntry, collectHitIds, createCoverageSink, deserializeCapturedError, deserializeCapturedResult, getCoverageMap, getMeticulousSessionId, getUnreportedCoverageFiles, headersToRecord, hrTimeToMs, isHealthProbeRequest, isOrphanedDatadogAgentRequest, isOrphanedDatadogAgentUrl, isProvisionalSessionIdCandidate, isSupportedPostgresJsQuery, markCoverageFilesReported, mintProvisionalSessionId, msToHrTime, parseVirtualTimeMs, postgresJsCommandOf, randomHex, readBodyWithCap, readRequestBodyWithCap, reconstructQueryText, redactRequestBody, redactUrlCredentials, registerCoverageFile, resolveRowMode, serializeCapturedError, serializeCapturedResult, serializeKvArgs, serializeKvCaptureFields, serializePostgresJsArgs, serializePostgresJsError, serializePostgresJsResult, serializeSpan, withMeticulous, withMeticulousPagesFunction, withMeticulousPostgres };
|
|
112
|
+
export { AGENT_SWARM_TESTING_RUN_ID_ENV_KEY, AGENT_SWARM_TESTING_SIDECAR_URL_ENV_KEY, BE_MOCK_PING_INTERVAL_MS, BODY_ENCODING_BASE64, CAPTURED_HEADERS, CLIENT_TECHNOLOGY_ATTR, CLIENT_TECHNOLOGY_POSTGRES_JS, CLIENT_TECHNOLOGY_WORKERD_BINDING, CLIENT_TECHNOLOGY_WORKERD_FETCH, CLIENT_TECHNOLOGY_WORKERD_KV, FRONTEND_SESSION_ID_ATTR, FRONTEND_SESSION_ID_HEADER, HTTP_RESPONSE_BODY_ENCODING_ATTR, HealthProbeEventFilter, MAX_BODY_CAPTURE_SIZE, MAX_POSTGRES_JS_RESULT_SIZE, METICULOUS_PASSTHROUGH_HEADER, 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, REPLAY_ID_HEADER, REPLAY_SIDECAR_URL_HEADER, SERVER_TIMING_HEADER, SERVER_TIMING_SESSION_METRIC, SESSION_ID_ORIGIN_ATTR, SESSION_ID_ORIGIN_BACKEND, SIDECAR_AGENT_SWARM_TESTING_BE_MOCK_CALLS_PATH, SIDECAR_AGENT_SWARM_TESTING_BE_MOCK_PATH, SIDECAR_AGENT_SWARM_TESTING_OUTBOUND_FETCH_PATH, SIDECAR_BINDING_ENV_KEY, SIDECAR_BINDING_ORIGIN, SIDECAR_EVENTS_PATH, SIDECAR_PROTOCOL_VERSION, SIDECAR_PROTOCOL_VERSION_HEADER, SIDECAR_REPLAY_COVERAGE_PATH, SIDECAR_REPLAY_OUTBOUND_FETCH_PATH, SIDECAR_REPLAY_SESSION_PATH, SIDECAR_URL_ENV_KEY, SPAN_KIND_CLIENT, SPAN_KIND_INTERNAL, SPAN_KIND_SERVER, SPAN_STATUS_ERROR, SPAN_STATUS_OK, SPAN_STATUS_UNSET, STR_REDACTED, SpanBuilder, VIRTUAL_TIME_HEADER, WORKERD_BINDING_NAME_ATTR, WORKERD_KV_ARGS_ATTR, WORKERD_KV_ARGS_TRUNCATED_ATTR, WORKERD_KV_KEY_ATTR, WORKERD_KV_OMITTED_ATTR, WORKERD_KV_OPERATION_ATTR, WORKERD_KV_RESULT_ATTR, WORKERD_KV_RESULT_TRUNCATED_ATTR, WORKERD_KV_VALUE_ATTR, WORKERD_KV_VALUE_TRUNCATED_ATTR, WORKERD_SHIM_VERSION, WORKERD_SHIM_VERSION_HEADER, __mcEnter, __mcHit, buildServerTimingSessionEntry, collectHitIds, createCoverageSink, decodeBodyBytes, deserializeCapturedError, deserializeCapturedResult, encodeBodyBytes, getCoverageMap, getMeticulousSessionId, getUnreportedCoverageFiles, headersToRecord, hrTimeToMs, isHealthProbeRequest, isOrphanedDatadogAgentRequest, isOrphanedDatadogAgentUrl, isProvisionalSessionIdCandidate, isSupportedPostgresJsQuery, markCoverageFilesReported, mintProvisionalSessionId, msToHrTime, parseVirtualTimeMs, postgresJsCommandOf, randomHex, readBodyWithCap, readRequestBodyWithCap, readResponseBodyWithCap, reconstructQueryText, redactRequestBody, redactUrlCredentials, registerCoverageFile, resolveRowMode, serializeCapturedError, serializeCapturedResult, serializeKvArgs, serializeKvCaptureFields, serializePostgresJsArgs, serializePostgresJsError, serializePostgresJsResult, serializeSpan, withMeticulous, withMeticulousPagesFunction, withMeticulousPostgres };
|
|
99
113
|
|
|
100
114
|
//# sourceMappingURL=index.js.map
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","names":[],"sources":["../src/index.ts"],"sourcesContent":["import { requestCaptureContext } from \"./context\";\nimport {\n type MeticulousExecutionContext,\n runWithMeticulous,\n type WithMeticulousOptions,\n} from \"./with-meticulous\";\n\nexport type {\n BindingRequestEvent,\n CaptureEvent,\n CapturedBody,\n CaptureEventsPayload,\n CoverageReportRequest,\n InboundRequestEvent,\n KvOmittedReason,\n KvOperation,\n KvOperationEvent,\n OutboundFetchLookupRequest,\n OutboundFetchLookupResponse,\n OutboundRequestEvent,\n PostgresQueryEvent,\n ReplaySessionInfoResponse,\n} from \"./protocol\";\nexport {\n CAPTURED_HEADERS,\n FRONTEND_SESSION_ID_HEADER,\n METICULOUS_PASSTHROUGH_HEADER,\n parseVirtualTimeMs,\n REPLAY_ID_HEADER,\n REPLAY_SIDECAR_URL_HEADER,\n SIDECAR_EVENTS_PATH,\n SIDECAR_PROTOCOL_VERSION,\n SIDECAR_PROTOCOL_VERSION_HEADER,\n SIDECAR_REPLAY_COVERAGE_PATH,\n SIDECAR_REPLAY_OUTBOUND_FETCH_PATH,\n SIDECAR_REPLAY_SESSION_PATH,\n VIRTUAL_TIME_HEADER,\n WORKERD_SHIM_VERSION_HEADER,\n} from \"./protocol\";\nexport { WORKERD_SHIM_VERSION } from \"./version\";\nexport {\n MAX_BODY_CAPTURE_SIZE,\n readBodyWithCap,\n readRequestBodyWithCap,\n} from \"./body-capture\";\nexport { headersToRecord } from \"./outbound-capture\";\n/**\n * Exported for the Node recorder, whose `health-probe.ts` adapts this to\n * `http.IncomingMessage`. Which requests count as a health probe is a rule both surfaces\n * have to agree on, so — like the provisional-session-id rules below — it has exactly one\n * implementation.\n */\nexport { isHealthProbeRequest } from \"./health-probe\";\n/**\n * Exported for the Node recorder, whose `datadog-agent.ts` adapts these to the forked http and\n * undici instrumentations' ignore hooks. Which destinations count as a Datadog agent is a rule\n * both surfaces have to agree on, so — like the health-probe rule above — it has exactly one\n * implementation.\n */\nexport {\n isOrphanedDatadogAgentRequest,\n isOrphanedDatadogAgentUrl,\n type OutboundCallTarget,\n} from \"./datadog-agent\";\n/**\n * Exported for both sidecar implementations — the deployed Worker\n * (`@alwaysmeticulous/backend-recorder-sidecar-worker`) and the local Node one\n * (`backend-recorder-js`'s `sidecar/server.ts`) — which repeat the shim's health-probe\n * verdict on the events they receive, so the exclusion reaches a deployment whose bundled\n * shim predates it.\n */\nexport { HealthProbeEventFilter } from \"./health-probe-filter\";\nexport { redactRequestBody, STR_REDACTED } from \"./redact-body\";\nexport type { CoverageMap, CoverageMapFile } from \"./coverage/coverage.types\";\n// __mcEnter / __mcHit are the entry points the Vite plugin's injected imports\n// bind to, so they are part of the package's public surface even though no\n// human writes a call to them.\nexport {\n __mcEnter,\n __mcHit,\n collectHitIds,\n createCoverageSink,\n getCoverageMap,\n getUnreportedCoverageFiles,\n markCoverageFilesReported,\n registerCoverageFile,\n} from \"./coverage/runtime\";\nexport type { MeticulousExecutionContext, WithMeticulousOptions };\nexport {\n type MeticulousPagesFunction,\n type MeticulousPagesFunctionContext,\n withMeticulousPagesFunction,\n} from \"./pages-function\";\nexport {\n SIDECAR_BINDING_ENV_KEY,\n SIDECAR_BINDING_ORIGIN,\n SIDECAR_URL_ENV_KEY,\n type SidecarFetcher,\n type SidecarTransport,\n} from \"./sidecar-transport\";\n/**\n * Exported for the Node recorder's `withMeticulousCloudflareEnv`, which records the same KV\n * operations from a process whose bindings come from wrangler's `getPlatformProxy`. Both surfaces\n * must produce identical fields, so the contract has exactly one implementation.\n */\nexport {\n type KvCaptureFields,\n type KvCaptureOutcome,\n serializeKvArgs,\n serializeKvCaptureFields,\n} from \"./kv-capture\";\n/**\n * Exported for the same reason: the Node recorder re-exports these so a postgres.js query, a\n * captured error and a built span are byte-identical whichever surface produced them. A\n * Worker-hosted sidecar needs them too, and cannot load the Node bundle.\n */\nexport {\n deserializeCapturedError,\n serializeCapturedError,\n} from \"./error-capture\";\nexport {\n deserializeCapturedResult,\n serializeCapturedResult,\n} from \"./captured-result-codec\";\nexport {\n isSupportedPostgresJsQuery,\n MAX_POSTGRES_JS_RESULT_SIZE,\n POSTGRES_JS_ERROR_ATTR,\n POSTGRES_JS_QUERY_PARAMS_ATTR,\n POSTGRES_JS_QUERY_TEXT_ATTR,\n POSTGRES_JS_RESULT_ATTR,\n POSTGRES_JS_RESULT_TRUNCATED_ATTR,\n POSTGRES_JS_ROW_MODE_ATTR,\n postgresJsCommandOf,\n type PostgresJsQueryLike,\n type PostgresJsRowMode,\n reconstructQueryText,\n resolveRowMode,\n serializePostgresJsArgs,\n serializePostgresJsError,\n serializePostgresJsResult,\n} from \"./postgres/capture\";\nexport { withMeticulousPostgres } from \"./postgres/with-meticulous-postgres\";\nexport * from \"./spans/attributes\";\nexport {\n hrTimeToMs,\n msToHrTime,\n type HrTime,\n type ReadableSpan,\n type SerializedSpanBase,\n SPAN_KIND_CLIENT,\n SPAN_KIND_INTERNAL,\n SPAN_KIND_SERVER,\n SPAN_STATUS_ERROR,\n SPAN_STATUS_OK,\n SPAN_STATUS_UNSET,\n type SpanStatus,\n} from \"./spans/readable-span\";\nexport { serializeSpan } from \"./spans/serialize-span\";\nexport { randomHex, SpanBuilder } from \"./spans/span-builder\";\nexport { redactUrlCredentials } from \"./spans/url-redaction\";\n/**\n * Exported for the Node recorder, whose own `provisional-session-id.ts` adapts these to\n * `http.IncomingMessage` / `http.ServerResponse`. The mint format, the rule for which requests\n * get an id and the `Server-Timing` metric are all wire contracts with the page, so — as with\n * the KV capture fields above — they have exactly one implementation.\n */\nexport {\n type HeaderReader,\n SERVER_TIMING_HEADER,\n SERVER_TIMING_SESSION_METRIC,\n buildServerTimingSessionEntry,\n isProvisionalSessionIdCandidate,\n mintProvisionalSessionId,\n} from \"./provisional-session-id\";\n\n/** Structural subset of an ES-module Worker handler (`export default {...}`). */\nexport interface MeticulousWorkerHandler<Env = never> {\n fetch(\n request: Request,\n env: Env,\n ctx: MeticulousExecutionContext,\n ): Response | Promise<Response>;\n}\n\n/**\n * Wraps an ES-module Worker handler so Meticulous can record, or replay, the app's HTTP\n * behaviour:\n *\n * export default withMeticulous({\n * async fetch(request, env, ctx) { ... },\n * });\n *\n * For a Cloudflare Pages project, whose worker exports `onRequest` instead, use\n * {@link withMeticulousPagesFunction} — same behaviour, different handler shape.\n *\n * **Recording** activates on either a `METICULOUS_SIDECAR` service binding (how a **deployed**\n * Worker records: the binding points at a Meticulous recorder sidecar Worker in the same account)\n * or a `METICULOUS_SIDECAR_URL` var (how `wrangler dev` records against a local sidecar process),\n * or the matching `options`. Inbound requests, outgoing `fetch` calls, calls through\n * `fetch`-shaped bindings, KV namespace operations and — with `withMeticulousPostgres` — postgres.js\n * queries are reported to the sidecar, batched into one report per request, without affecting the\n * app. A binding beats a var if both are configured: only the binding can have been added to\n * *this* deployment's wrangler configuration deliberately, whereas a stale `.dev.vars` travels\n * with an image.\n *\n * **Replay** activates on the `x-meticulous-backend-replay-sidecar-url` header, injected by the\n * Meticulous replay runner on requests to the app under test. Outgoing `fetch` calls are then\n * served from the recording instead of reaching the real service — and a call the recording does\n * not cover fails, rather than quietly becoming live traffic; put a `meticulous-passthrough: true`\n * header on a request that must stay real. The clock is frozen at the recorded session's end so\n * recorded credentials are still valid, and `Math.random` / `crypto.randomUUID` /\n * `crypto.getRandomValues` are seeded so ids the app mints are the same in every replay of that\n * session. Workerd cannot read container environment variables, which is why per-replay config\n * arrives as a request header; the shim validates it and only honours a loopback /\n * docker-gateway / private-network `http:` origin.\n *\n * Replay takes precedence over recording when both are configured. A stale sidecar var baked into\n * an image is far more likely than a spurious replay header (nothing but the replay runner emits\n * one), and letting the env win would silently record a replay instead of mocking it.\n *\n * With neither configured the wrapper is a complete pass-through, so it is safe to keep in\n * deployed code. Requires the `nodejs_als` (or `nodejs_compat`) compatibility flag. A capture\n * failure or unreachable sidecar never affects the app.\n */\nexport const withMeticulous = <H extends MeticulousWorkerHandler<never>>(\n handler: H,\n options?: WithMeticulousOptions,\n): H => {\n const wrappedFetch = (\n request: Request,\n env: unknown,\n ctx: MeticulousExecutionContext,\n ): Promise<Response> =>\n runWithMeticulous(\n {\n request,\n env,\n ctx,\n invokeHandler: () => handler.fetch(request, env as never, ctx),\n },\n options,\n );\n\n // The wrapper only replaces `fetch` (same call signature), but TypeScript cannot verify a\n // spread against the bare generic H.\n return { ...handler, fetch: wrappedFetch } as H;\n};\n\n/**\n * The Meticulous session id of the request currently being served, or `undefined` if this\n * call cannot be tied to one — including whenever the wrapper is a pass-through.\n *\n * Exposed so a server-rendering worker can put the id into the HTML it is about to return,\n * typically as `data-session-id` on the recorder's own `<script>` tag. That is the exact,\n * cache-proof alternative to the `Server-Timing` header the shim publishes automatically;\n * doing both is fine, since the recorder prefers the rendered value. It is also how the id\n * reaches another process — a document navigation has no inbound header for the app to\n * forward, so a worker that fans out to its own services has to pass this on as\n * `x-meticulous-session-id` itself.\n *\n * During a replay this returns the replayed session's id, which is the same value the render\n * being replayed was given when it was recorded (the page adopted the minted id, so that is\n * what the recording is stored under, and it is what the replay runner injects back). An app\n * that renders the id therefore renders the same bytes both times, rather than the id turning\n * every server-rendered page into a diff.\n */\nexport const getMeticulousSessionId = (): string | undefined =>\n requestCaptureContext.getStore()?.frontendSessionId;\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAiOA,MAAa,kBACX,SACA,YACM;CACN,MAAM,gBACJ,SACA,KACA,QAEA,kBACE;EACE;EACA;EACA;EACA,qBAAqB,QAAQ,MAAM,SAAS,KAAc,IAAI;EAC/D,EACD,QACD;AAIH,QAAO;EAAE,GAAG;EAAS,OAAO;EAAc;;;;;;;;;;;;;;;;;;;;AAqB5C,MAAa,+BACX,sBAAsB,UAAU,EAAE"}
|
|
1
|
+
{"version":3,"file":"index.js","names":[],"sources":["../src/index.ts"],"sourcesContent":["import { requestCaptureContext } from \"./context\";\nimport {\n type MeticulousExecutionContext,\n runWithMeticulous,\n type WithMeticulousOptions,\n} from \"./with-meticulous\";\n\nexport type {\n BeMockCall,\n BeMockCallsReport,\n BeMockFailedMessage,\n BeMockInstallMessage,\n BeMockLoadSessionMessage,\n BeMockLogMessage,\n BeMockReadyMessage,\n BeMockRecordedRequest,\n BeMockSessionMessage,\n BeMockSidecarMessage,\n BeMockWorkerMessage,\n BindingRequestEvent,\n CaptureEvent,\n CapturedBody,\n CaptureEventsPayload,\n CoverageReportRequest,\n InboundRequestEvent,\n KvOmittedReason,\n KvOperation,\n KvOperationEvent,\n OutboundFetchAgentSwarmTestingRequest,\n OutboundFetchAgentSwarmTestingResponse,\n OutboundFetchLookupRequest,\n OutboundFetchLookupResponse,\n OutboundRequestEvent,\n PostgresQueryEvent,\n ReplaySessionInfoResponse,\n} from \"./protocol\";\nexport {\n AGENT_SWARM_TESTING_RUN_ID_ENV_KEY,\n AGENT_SWARM_TESTING_SIDECAR_URL_ENV_KEY,\n BE_MOCK_PING_INTERVAL_MS,\n CAPTURED_HEADERS,\n FRONTEND_SESSION_ID_HEADER,\n METICULOUS_PASSTHROUGH_HEADER,\n parseVirtualTimeMs,\n REPLAY_ID_HEADER,\n REPLAY_SIDECAR_URL_HEADER,\n SIDECAR_AGENT_SWARM_TESTING_BE_MOCK_CALLS_PATH,\n SIDECAR_AGENT_SWARM_TESTING_BE_MOCK_PATH,\n SIDECAR_AGENT_SWARM_TESTING_OUTBOUND_FETCH_PATH,\n SIDECAR_EVENTS_PATH,\n SIDECAR_PROTOCOL_VERSION,\n SIDECAR_PROTOCOL_VERSION_HEADER,\n SIDECAR_REPLAY_COVERAGE_PATH,\n SIDECAR_REPLAY_OUTBOUND_FETCH_PATH,\n SIDECAR_REPLAY_SESSION_PATH,\n VIRTUAL_TIME_HEADER,\n WORKERD_SHIM_VERSION_HEADER,\n} from \"./protocol\";\nexport { WORKERD_SHIM_VERSION } from \"./version\";\nexport {\n MAX_BODY_CAPTURE_SIZE,\n readBodyWithCap,\n readRequestBodyWithCap,\n readResponseBodyWithCap,\n} from \"./body-capture\";\n/**\n * Exported for the Node recorder, which captures response bodies through its own instrumentations\n * and must store them in the same form, and for its replay side, which serves them back.\n */\nexport {\n BODY_ENCODING_BASE64,\n type BodyEncoding,\n decodeBodyBytes,\n encodeBodyBytes,\n type EncodedBody,\n HTTP_RESPONSE_BODY_ENCODING_ATTR,\n} from \"./body-encoding\";\nexport { headersToRecord } from \"./outbound-capture\";\n/**\n * Exported for the Node recorder, whose `health-probe.ts` adapts this to\n * `http.IncomingMessage`. Which requests count as a health probe is a rule both surfaces\n * have to agree on, so — like the provisional-session-id rules below — it has exactly one\n * implementation.\n */\nexport { isHealthProbeRequest } from \"./health-probe\";\n/**\n * Exported for the Node recorder, whose `datadog-agent.ts` adapts these to the forked http and\n * undici instrumentations' ignore hooks. Which destinations count as a Datadog agent is a rule\n * both surfaces have to agree on, so — like the health-probe rule above — it has exactly one\n * implementation.\n */\nexport {\n isOrphanedDatadogAgentRequest,\n isOrphanedDatadogAgentUrl,\n type OutboundCallTarget,\n} from \"./datadog-agent\";\n/**\n * Exported for both sidecar implementations — the deployed Worker\n * (`@alwaysmeticulous/backend-recorder-sidecar-worker`) and the local Node one\n * (`backend-recorder-js`'s `sidecar/server.ts`) — which repeat the shim's health-probe\n * verdict on the events they receive, so the exclusion reaches a deployment whose bundled\n * shim predates it.\n */\nexport { HealthProbeEventFilter } from \"./health-probe-filter\";\nexport { redactRequestBody, STR_REDACTED } from \"./redact-body\";\nexport type { CoverageMap, CoverageMapFile } from \"./coverage/coverage.types\";\n// __mcEnter / __mcHit are the entry points the Vite plugin's injected imports\n// bind to, so they are part of the package's public surface even though no\n// human writes a call to them.\nexport {\n __mcEnter,\n __mcHit,\n collectHitIds,\n createCoverageSink,\n getCoverageMap,\n getUnreportedCoverageFiles,\n markCoverageFilesReported,\n registerCoverageFile,\n} from \"./coverage/runtime\";\nexport type { MeticulousExecutionContext, WithMeticulousOptions };\nexport {\n type MeticulousPagesFunction,\n type MeticulousPagesFunctionContext,\n withMeticulousPagesFunction,\n} from \"./pages-function\";\nexport {\n SIDECAR_BINDING_ENV_KEY,\n SIDECAR_BINDING_ORIGIN,\n SIDECAR_URL_ENV_KEY,\n type SidecarFetcher,\n type SidecarTransport,\n} from \"./sidecar-transport\";\n/**\n * Exported for the Node recorder's `withMeticulousCloudflareEnv`, which records the same KV\n * operations from a process whose bindings come from wrangler's `getPlatformProxy`. Both surfaces\n * must produce identical fields, so the contract has exactly one implementation.\n */\nexport {\n type KvCaptureFields,\n type KvCaptureOutcome,\n serializeKvArgs,\n serializeKvCaptureFields,\n} from \"./kv-capture\";\n/**\n * Exported for the same reason: the Node recorder re-exports these so a postgres.js query, a\n * captured error and a built span are byte-identical whichever surface produced them. A\n * Worker-hosted sidecar needs them too, and cannot load the Node bundle.\n */\nexport {\n deserializeCapturedError,\n serializeCapturedError,\n} from \"./error-capture\";\nexport {\n deserializeCapturedResult,\n serializeCapturedResult,\n} from \"./captured-result-codec\";\nexport {\n isSupportedPostgresJsQuery,\n MAX_POSTGRES_JS_RESULT_SIZE,\n POSTGRES_JS_ERROR_ATTR,\n POSTGRES_JS_QUERY_PARAMS_ATTR,\n POSTGRES_JS_QUERY_TEXT_ATTR,\n POSTGRES_JS_RESULT_ATTR,\n POSTGRES_JS_RESULT_TRUNCATED_ATTR,\n POSTGRES_JS_ROW_MODE_ATTR,\n postgresJsCommandOf,\n type PostgresJsQueryLike,\n type PostgresJsRowMode,\n reconstructQueryText,\n resolveRowMode,\n serializePostgresJsArgs,\n serializePostgresJsError,\n serializePostgresJsResult,\n} from \"./postgres/capture\";\nexport { withMeticulousPostgres } from \"./postgres/with-meticulous-postgres\";\nexport * from \"./spans/attributes\";\nexport {\n hrTimeToMs,\n msToHrTime,\n type HrTime,\n type ReadableSpan,\n type SerializedSpanBase,\n SPAN_KIND_CLIENT,\n SPAN_KIND_INTERNAL,\n SPAN_KIND_SERVER,\n SPAN_STATUS_ERROR,\n SPAN_STATUS_OK,\n SPAN_STATUS_UNSET,\n type SpanStatus,\n} from \"./spans/readable-span\";\nexport { serializeSpan } from \"./spans/serialize-span\";\nexport { randomHex, SpanBuilder } from \"./spans/span-builder\";\nexport { redactUrlCredentials } from \"./spans/url-redaction\";\n/**\n * Exported for the Node recorder, whose own `provisional-session-id.ts` adapts these to\n * `http.IncomingMessage` / `http.ServerResponse`. The mint format, the rule for which requests\n * get an id and the `Server-Timing` metric are all wire contracts with the page, so — as with\n * the KV capture fields above — they have exactly one implementation.\n */\nexport {\n type HeaderReader,\n SERVER_TIMING_HEADER,\n SERVER_TIMING_SESSION_METRIC,\n buildServerTimingSessionEntry,\n isProvisionalSessionIdCandidate,\n mintProvisionalSessionId,\n} from \"./provisional-session-id\";\n\n/** Structural subset of an ES-module Worker handler (`export default {...}`). */\nexport interface MeticulousWorkerHandler<Env = never> {\n fetch(\n request: Request,\n env: Env,\n ctx: MeticulousExecutionContext,\n ): Response | Promise<Response>;\n}\n\n/**\n * Wraps an ES-module Worker handler so Meticulous can record, or replay, the app's HTTP\n * behaviour:\n *\n * export default withMeticulous({\n * async fetch(request, env, ctx) { ... },\n * });\n *\n * For a Cloudflare Pages project, whose worker exports `onRequest` instead, use\n * {@link withMeticulousPagesFunction} — same behaviour, different handler shape.\n *\n * **Recording** activates on either a `METICULOUS_SIDECAR` service binding (how a **deployed**\n * Worker records: the binding points at a Meticulous recorder sidecar Worker in the same account)\n * or a `METICULOUS_SIDECAR_URL` var (how `wrangler dev` records against a local sidecar process),\n * or the matching `options`. Inbound requests, outgoing `fetch` calls, calls through\n * `fetch`-shaped bindings, KV namespace operations and — with `withMeticulousPostgres` — postgres.js\n * queries are reported to the sidecar, batched into one report per request, without affecting the\n * app. A binding beats a var if both are configured: only the binding can have been added to\n * *this* deployment's wrangler configuration deliberately, whereas a stale `.dev.vars` travels\n * with an image.\n *\n * **Replay** activates on the `x-meticulous-backend-replay-sidecar-url` header, injected by the\n * Meticulous replay runner on requests to the app under test. Outgoing `fetch` calls are then\n * served from the recording instead of reaching the real service — and a call the recording does\n * not cover fails, rather than quietly becoming live traffic; put a `meticulous-passthrough: true`\n * header on a request that must stay real. The clock is frozen at the recorded session's end so\n * recorded credentials are still valid, and `Math.random` / `crypto.randomUUID` /\n * `crypto.getRandomValues` are seeded so ids the app mints are the same in every replay of that\n * session. Workerd cannot read container environment variables, which is why per-replay config\n * arrives as a request header; the shim validates it and only honours a loopback /\n * docker-gateway / private-network `http:` origin.\n *\n * Replay takes precedence over recording when both are configured. A stale sidecar var baked into\n * an image is far more likely than a spurious replay header (nothing but the replay runner emits\n * one), and letting the env win would silently record a replay instead of mocking it.\n *\n * **Agent-swarm testing** activates on two deployment env vars,\n * `METICULOUS_AGENT_SWARM_TESTING_SIDECAR_URL` and `METICULOUS_AGENT_SWARM_TESTING_RUN_ID`,\n * set by whoever deploys the worker for a swarm run. These must be Worker bindings,\n * not just environment variables on the host process: forward them at startup through\n * Wrangler vars or the `.dev.vars` file consumed by the runtime. The app calls a sidecar\n * in the same pod through loopback; configuring the sidecar's own environment does not\n * configure the Worker's bindings. Every outgoing `fetch` call is first\n * offered to the interceptor service the URL names; it either supplies the response to serve\n * or the call passes through to the real service. Unlike replay the mode is best-effort by\n * design: a call the interceptor cannot answer is never failed, there is no frozen clock and\n * no seeded randomness, and nothing is recorded. The sidecar URL is validated exactly like\n * the replay one.\n *\n * With neither configured the wrapper is a complete pass-through, so it is safe to keep in\n * deployed code. Requires the `nodejs_als` (or `nodejs_compat`) compatibility flag. A capture\n * failure or unreachable sidecar never affects the app.\n */\nexport const withMeticulous = <H extends MeticulousWorkerHandler<never>>(\n handler: H,\n options?: WithMeticulousOptions,\n): H => {\n const wrappedFetch = (\n request: Request,\n env: unknown,\n ctx: MeticulousExecutionContext,\n ): Promise<Response> =>\n runWithMeticulous(\n {\n request,\n env,\n ctx,\n invokeHandler: () => handler.fetch(request, env as never, ctx),\n },\n options,\n );\n\n // The wrapper only replaces `fetch` (same call signature), but TypeScript cannot verify a\n // spread against the bare generic H.\n return { ...handler, fetch: wrappedFetch } as H;\n};\n\n/**\n * The Meticulous session id of the request currently being served, or `undefined` if this\n * call cannot be tied to one — including whenever the wrapper is a pass-through.\n *\n * Exposed so a server-rendering worker can put the id into the HTML it is about to return,\n * typically as `data-session-id` on the recorder's own `<script>` tag. That is the exact,\n * cache-proof alternative to the `Server-Timing` header the shim publishes automatically;\n * doing both is fine, since the recorder prefers the rendered value. It is also how the id\n * reaches another process — a document navigation has no inbound header for the app to\n * forward, so a worker that fans out to its own services has to pass this on as\n * `x-meticulous-session-id` itself.\n *\n * During a replay this returns the replayed session's id, which is the same value the render\n * being replayed was given when it was recorded (the page adopted the minted id, so that is\n * what the recording is stored under, and it is what the replay runner injects back). An app\n * that renders the id therefore renders the same bytes both times, rather than the id turning\n * every server-rendered page into a diff.\n */\nexport const getMeticulousSessionId = (): string | undefined =>\n requestCaptureContext.getStore()?.frontendSessionId;\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA8QA,MAAa,kBACX,SACA,YACM;CACN,MAAM,gBACJ,SACA,KACA,QAEA,kBACE;EACE;EACA;EACA;EACA,qBAAqB,QAAQ,MAAM,SAAS,KAAc,IAAI;EAC/D,EACD,QACD;AAIH,QAAO;EAAE,GAAG;EAAS,OAAO;EAAc;;;;;;;;;;;;;;;;;;;;AAqB5C,MAAa,+BACX,sBAAsB,UAAU,EAAE"}
|
package/dist/outbound-capture.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { warnOnce } from "./log.js";
|
|
2
|
-
import {
|
|
2
|
+
import { readRequestBodyWithCap, readResponseBodyWithCap } from "./body-capture.js";
|
|
3
3
|
import { CAPTURED_HEADERS } from "./protocol.js";
|
|
4
4
|
//#region src/outbound-capture.ts
|
|
5
5
|
const capturedHeaders = new Set(CAPTURED_HEADERS);
|
|
@@ -79,7 +79,7 @@ const captureOutboundCall = async (ctx, transport, request, invoke) => {
|
|
|
79
79
|
const statusCode = response.status;
|
|
80
80
|
const endTimeMs = Date.now();
|
|
81
81
|
ctx.buffer.track(async () => {
|
|
82
|
-
const [requestBody, responseBody] = await Promise.all([requestBodyPromise,
|
|
82
|
+
const [requestBody, responseBody] = await Promise.all([requestBodyPromise, readResponseBodyWithCap(responseClone.body).catch(() => void 0)]);
|
|
83
83
|
report(ctx, transport, {
|
|
84
84
|
...frozenMeta,
|
|
85
85
|
...correlation(ctx),
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"outbound-capture.js","names":[],"sources":["../src/outbound-capture.ts"],"sourcesContent":["import {
|
|
1
|
+
{"version":3,"file":"outbound-capture.js","names":[],"sources":["../src/outbound-capture.ts"],"sourcesContent":["import {\n readRequestBodyWithCap,\n readResponseBodyWithCap,\n} from \"./body-capture\";\nimport type { RequestCaptureContext } from \"./context\";\nimport { warnOnce } from \"./log\";\nimport {\n CAPTURED_HEADERS,\n type BindingRequestEvent,\n type CaptureEvent,\n type CapturedBody,\n type OutboundRequestEvent,\n} from \"./protocol\";\n\n/**\n * Which transport an intercepted call left the isolate through. Both are\n * Request/Response-shaped, so they share the capture tee below and differ only in the\n * event they emit.\n */\nexport type OutboundTransport =\n | { kind: \"outbound\" }\n | {\n kind: \"binding\";\n /**\n * The `env` key the binding was found under. Undefined when the instance was never\n * seen on `env` — e.g. a Durable Object stub, which is produced by `namespace.get()`\n * rather than being a binding itself.\n */\n bindingName: string | undefined;\n };\n\nconst capturedHeaders = new Set<string>(CAPTURED_HEADERS);\n\nexport const headersToRecord = (headers: Headers): Record<string, string[]> => {\n const record: Record<string, string[]> = {};\n headers.forEach((value, name) => {\n const lowerName = name.toLowerCase();\n if (capturedHeaders.has(lowerName)) {\n (record[lowerName] ??= []).push(value);\n }\n });\n return record;\n};\n\ninterface OutboundMeta {\n method: string;\n url: string;\n requestHeaders: Record<string, string[]>;\n}\n\ninterface EventFields extends OutboundMeta {\n requestId: string;\n frontendSessionId?: string;\n traceId: string;\n serverSpanId: string;\n requestBody?: CapturedBody;\n responseBody?: CapturedBody;\n responseHeaders?: Record<string, string[]>;\n statusCode?: number;\n startTimeMs: number;\n endTimeMs: number;\n error?: string;\n}\n\n/**\n * Builds the transport's event. An explicit two-arm branch rather than a spread of the\n * union, so the discriminant and the extra binding field stay obvious. (The shim has no\n * runtime dependencies, so `assertNever` is not available here.)\n */\nconst buildEvent = (\n transport: OutboundTransport,\n fields: EventFields,\n): OutboundRequestEvent | BindingRequestEvent =>\n transport.kind === \"binding\"\n ? {\n kind: \"binding\",\n ...(transport.bindingName !== undefined\n ? { bindingName: transport.bindingName }\n : {}),\n ...fields,\n }\n : { kind: \"outbound\", ...fields };\n\n/**\n * Records an outbound call without affecting it: the real transport is invoked directly\n * (the sidecar is never in the request path), and request/response clones are reported in\n * the background via `ctx.waitUntil`. Any capture failure is warned about once and\n * swallowed — recording must never break the app.\n *\n * `invoke` is what actually performs the call, so the caller keeps ownership of the\n * receiver (a binding's `fetch` must be applied to the binding, not to `globalThis`).\n */\nexport const captureOutboundCall = async (\n ctx: RequestCaptureContext,\n transport: OutboundTransport,\n request: Request,\n invoke: (request: Request) => Promise<Response>,\n): Promise<Response> => {\n const startTimeMs = Date.now();\n let meta: OutboundMeta | undefined;\n let requestBodyPromise: Promise<CapturedBody | undefined> =\n Promise.resolve(undefined);\n try {\n meta = {\n method: request.method,\n url: request.url,\n requestHeaders: headersToRecord(request.headers),\n };\n if (request.body) {\n const bodyClone = request.clone();\n requestBodyPromise = readRequestBodyWithCap(bodyClone.body).catch(\n () => undefined,\n );\n }\n } catch (error) {\n warnOnce(\n \"outbound-request-capture\",\n \"Failed to capture an outbound request.\",\n error,\n );\n meta = undefined;\n }\n\n let response: Response;\n try {\n response = await invoke(request);\n } catch (error) {\n if (meta) {\n const frozenMeta = meta;\n const endTimeMs = Date.now();\n const errorMessage = String(error);\n // Tracked rather than reported directly: the request body is still being read, and the\n // buffer's `close()` is what waits for it before sending the batch.\n ctx.buffer.track(\n async () => {\n const requestBody = await requestBodyPromise;\n report(ctx, transport, {\n ...frozenMeta,\n ...correlation(ctx),\n ...(requestBody !== undefined ? { requestBody } : {}),\n startTimeMs,\n endTimeMs,\n error: errorMessage,\n });\n },\n \"outbound-error-report\",\n \"Failed to report a failed outbound request.\",\n );\n }\n throw error;\n }\n\n if (meta) {\n const frozenMeta = meta;\n try {\n // A WebSocket upgrade has no readable body and cannot be represented as a recorded\n // response, so it is left entirely alone.\n if (hasWebSocket(response)) {\n return response;\n }\n const responseClone = response.clone();\n const responseHeaders = headersToRecord(response.headers);\n const statusCode = response.status;\n const endTimeMs = Date.now();\n ctx.buffer.track(\n async () => {\n const [requestBody, responseBody] = await Promise.all([\n requestBodyPromise,\n readResponseBodyWithCap(responseClone.body).catch(() => undefined),\n ]);\n report(ctx, transport, {\n ...frozenMeta,\n ...correlation(ctx),\n responseHeaders,\n statusCode,\n ...(requestBody !== undefined ? { requestBody } : {}),\n ...(responseBody !== undefined ? { responseBody } : {}),\n startTimeMs,\n endTimeMs,\n });\n },\n \"outbound-response-capture\",\n \"Failed to capture an outbound response.\",\n );\n } catch (error) {\n warnOnce(\n \"outbound-response-capture\",\n \"Failed to capture an outbound response.\",\n error,\n );\n }\n }\n\n return response;\n};\n\n/** The fields tying an event to its request, session and trace. */\nconst correlation = (\n ctx: RequestCaptureContext,\n): Pick<\n EventFields,\n \"requestId\" | \"frontendSessionId\" | \"traceId\" | \"serverSpanId\"\n> => ({\n requestId: ctx.requestId,\n ...(ctx.frontendSessionId !== undefined\n ? { frontendSessionId: ctx.frontendSessionId }\n : {}),\n traceId: ctx.traceId,\n serverSpanId: ctx.serverSpanId,\n});\n\nconst report = (\n ctx: RequestCaptureContext,\n transport: OutboundTransport,\n fields: EventFields,\n): void => {\n const event: CaptureEvent = buildEvent(transport, fields);\n ctx.buffer.add(event);\n};\n\n/** Detects a WebSocket upgrade response without assuming the property exists. */\nexport const hasWebSocket = (response: Response): boolean =>\n (response as Response & { webSocket?: unknown }).webSocket != null;\n"],"mappings":";;;;AA+BA,MAAM,kBAAkB,IAAI,IAAY,iBAAiB;AAEzD,MAAa,mBAAmB,YAA+C;CAC7E,MAAM,SAAmC,EAAE;AAC3C,SAAQ,SAAS,OAAO,SAAS;EAC/B,MAAM,YAAY,KAAK,aAAa;AACpC,MAAI,gBAAgB,IAAI,UAAU,CAChC,EAAC,OAAO,eAAe,EAAE,EAAE,KAAK,MAAM;GAExC;AACF,QAAO;;;;;;;AA4BT,MAAM,cACJ,WACA,WAEA,UAAU,SAAS,YACf;CACE,MAAM;CACN,GAAI,UAAU,gBAAgB,KAAA,IAC1B,EAAE,aAAa,UAAU,aAAa,GACtC,EAAE;CACN,GAAG;CACJ,GACD;CAAE,MAAM;CAAY,GAAG;CAAQ;;;;;;;;;;AAWrC,MAAa,sBAAsB,OACjC,KACA,WACA,SACA,WACsB;CACtB,MAAM,cAAc,KAAK,KAAK;CAC9B,IAAI;CACJ,IAAI,qBACF,QAAQ,QAAQ,KAAA,EAAU;AAC5B,KAAI;AACF,SAAO;GACL,QAAQ,QAAQ;GAChB,KAAK,QAAQ;GACb,gBAAgB,gBAAgB,QAAQ,QAAQ;GACjD;AACD,MAAI,QAAQ,KAEV,sBAAqB,uBADH,QAAQ,OAC2B,CAAC,KAAK,CAAC,YACpD,KAAA,EACP;UAEI,OAAO;AACd,WACE,4BACA,0CACA,MACD;AACD,SAAO,KAAA;;CAGT,IAAI;AACJ,KAAI;AACF,aAAW,MAAM,OAAO,QAAQ;UACzB,OAAO;AACd,MAAI,MAAM;GACR,MAAM,aAAa;GACnB,MAAM,YAAY,KAAK,KAAK;GAC5B,MAAM,eAAe,OAAO,MAAM;AAGlC,OAAI,OAAO,MACT,YAAY;IACV,MAAM,cAAc,MAAM;AAC1B,WAAO,KAAK,WAAW;KACrB,GAAG;KACH,GAAG,YAAY,IAAI;KACnB,GAAI,gBAAgB,KAAA,IAAY,EAAE,aAAa,GAAG,EAAE;KACpD;KACA;KACA,OAAO;KACR,CAAC;MAEJ,yBACA,8CACD;;AAEH,QAAM;;AAGR,KAAI,MAAM;EACR,MAAM,aAAa;AACnB,MAAI;AAGF,OAAI,aAAa,SAAS,CACxB,QAAO;GAET,MAAM,gBAAgB,SAAS,OAAO;GACtC,MAAM,kBAAkB,gBAAgB,SAAS,QAAQ;GACzD,MAAM,aAAa,SAAS;GAC5B,MAAM,YAAY,KAAK,KAAK;AAC5B,OAAI,OAAO,MACT,YAAY;IACV,MAAM,CAAC,aAAa,gBAAgB,MAAM,QAAQ,IAAI,CACpD,oBACA,wBAAwB,cAAc,KAAK,CAAC,YAAY,KAAA,EAAU,CACnE,CAAC;AACF,WAAO,KAAK,WAAW;KACrB,GAAG;KACH,GAAG,YAAY,IAAI;KACnB;KACA;KACA,GAAI,gBAAgB,KAAA,IAAY,EAAE,aAAa,GAAG,EAAE;KACpD,GAAI,iBAAiB,KAAA,IAAY,EAAE,cAAc,GAAG,EAAE;KACtD;KACA;KACD,CAAC;MAEJ,6BACA,0CACD;WACM,OAAO;AACd,YACE,6BACA,2CACA,MACD;;;AAIL,QAAO;;;AAIT,MAAM,eACJ,SAII;CACJ,WAAW,IAAI;CACf,GAAI,IAAI,sBAAsB,KAAA,IAC1B,EAAE,mBAAmB,IAAI,mBAAmB,GAC5C,EAAE;CACN,SAAS,IAAI;CACb,cAAc,IAAI;CACnB;AAED,MAAM,UACJ,KACA,WACA,WACS;CACT,MAAM,QAAsB,WAAW,WAAW,OAAO;AACzD,KAAI,OAAO,IAAI,MAAM;;;AAIvB,MAAa,gBAAgB,aAC1B,SAAgD,aAAa"}
|
package/dist/protocol.d.ts
CHANGED
|
@@ -1,9 +1,6 @@
|
|
|
1
|
+
import { BodyEncoding } from "./body-encoding.js";
|
|
2
|
+
|
|
1
3
|
//#region src/protocol.d.ts
|
|
2
|
-
/**
|
|
3
|
-
* Wire protocol between the in-worker shim and the Meticulous backend
|
|
4
|
-
* recorder sidecar. The sidecar imports these types (type-only), so this file
|
|
5
|
-
* is the single source of truth for the shape of capture events.
|
|
6
|
-
*/
|
|
7
4
|
declare const SIDECAR_PROTOCOL_VERSION = "2";
|
|
8
5
|
/** Header carrying {@link SIDECAR_PROTOCOL_VERSION} on every shim → sidecar request. */
|
|
9
6
|
declare const SIDECAR_PROTOCOL_VERSION_HEADER = "x-meticulous-sidecar-protocol-version";
|
|
@@ -58,7 +55,7 @@ declare const SIDECAR_REPLAY_COVERAGE_PATH = "/v1/replay/coverage";
|
|
|
58
55
|
* so an inbound header is the only way per-replay config can reach the shim.
|
|
59
56
|
*
|
|
60
57
|
* The shim validates the value before using it and only honours a loopback /
|
|
61
|
-
* `host.docker.internal` / private-network `http:` origin — see `
|
|
58
|
+
* `host.docker.internal` / private-network `http:` origin — see `sidecar-url.ts`.
|
|
62
59
|
*/
|
|
63
60
|
declare const REPLAY_SIDECAR_URL_HEADER = "x-meticulous-backend-replay-sidecar-url";
|
|
64
61
|
/**
|
|
@@ -81,10 +78,19 @@ declare const WORKERD_SHIM_VERSION_HEADER = "x-meticulous-workerd-shim-version";
|
|
|
81
78
|
*/
|
|
82
79
|
declare const CAPTURED_HEADERS: readonly ["content-type", "x-meticulous-session-id"];
|
|
83
80
|
interface CapturedBody {
|
|
84
|
-
/**
|
|
81
|
+
/**
|
|
82
|
+
* The body truncated to the capture cap: its UTF-8 text, or base64 of its bytes when
|
|
83
|
+
* `encoding` is set.
|
|
84
|
+
*/
|
|
85
85
|
body: string;
|
|
86
86
|
/** True when the body exceeded the capture cap or the read was cut short. */
|
|
87
87
|
truncated: boolean;
|
|
88
|
+
/**
|
|
89
|
+
* Set only on a response body whose bytes are not UTF-8, which is stored base64 so that replay
|
|
90
|
+
* can serve it back exactly. Additive and non-version-bumping: an older shim omits it, and its
|
|
91
|
+
* bodies are text as before.
|
|
92
|
+
*/
|
|
93
|
+
encoding?: BodyEncoding;
|
|
88
94
|
}
|
|
89
95
|
/**
|
|
90
96
|
* The fields every capture event carries to say which request, session and trace it belongs to.
|
|
@@ -324,6 +330,11 @@ type OutboundFetchLookupResponse = {
|
|
|
324
330
|
outcome: "mock";
|
|
325
331
|
statusCode: number;
|
|
326
332
|
body: string;
|
|
333
|
+
/**
|
|
334
|
+
* Set when `body` is base64 of a recorded response that was not UTF-8, so the shim must
|
|
335
|
+
* serve the decoded bytes rather than the text. An older shim ignores it.
|
|
336
|
+
*/
|
|
337
|
+
bodyEncoding?: BodyEncoding;
|
|
327
338
|
headers: Record<string, string>;
|
|
328
339
|
}
|
|
329
340
|
/**
|
|
@@ -336,6 +347,186 @@ type OutboundFetchLookupResponse = {
|
|
|
336
347
|
| {
|
|
337
348
|
outcome: "no-mock";
|
|
338
349
|
};
|
|
350
|
+
/**
|
|
351
|
+
* Env var holding the origin of the agent-swarm-testing interceptor service, as a wrangler
|
|
352
|
+
* var on the deployment. Validated exactly like the replay sidecar URL — a loopback /
|
|
353
|
+
* docker-gateway / private-network `http:` origin only — see `sidecar-url.ts`.
|
|
354
|
+
*/
|
|
355
|
+
declare const AGENT_SWARM_TESTING_SIDECAR_URL_ENV_KEY = "METICULOUS_AGENT_SWARM_TESTING_SIDECAR_URL";
|
|
356
|
+
/**
|
|
357
|
+
* Env var identifying the agent-swarm run, so an interceptor shared by several runs can tell
|
|
358
|
+
* whose traffic a call belongs to.
|
|
359
|
+
*/
|
|
360
|
+
declare const AGENT_SWARM_TESTING_RUN_ID_ENV_KEY = "METICULOUS_AGENT_SWARM_TESTING_RUN_ID";
|
|
361
|
+
/** Agent-swarm-testing route. */
|
|
362
|
+
declare const SIDECAR_AGENT_SWARM_TESTING_OUTBOUND_FETCH_PATH = "/v1/agent-swarm-testing/outbound-fetch";
|
|
363
|
+
/**
|
|
364
|
+
* Agent-swarm-testing WebSocket route that hosts one be-mock: an agent-authored
|
|
365
|
+
* `be-mock.ts` (backend mocks), compiled by the agentic session-generation
|
|
366
|
+
* worker into a self-contained sandbox bundle. The sidecar runs it in its own
|
|
367
|
+
* permission-restricted child process over the frozen RPC toolkit, and routes
|
|
368
|
+
* every intercepted outbound fetch through the module's `interceptRequest`
|
|
369
|
+
* export — see `packages/backend-recorder-js/src/sidecar/be-mock` and
|
|
370
|
+
* `packages/agentic-session-generation-worker/src/app-access/be-mocking`.
|
|
371
|
+
*
|
|
372
|
+
* One connection is one installed module: the worker opens it, sends
|
|
373
|
+
* {@link BeMockInstallMessage}, answers every {@link BeMockLoadSessionMessage}
|
|
374
|
+
* while the module loads, and receives {@link BeMockReadyMessage} (or
|
|
375
|
+
* {@link BeMockFailedMessage}) and then the module's log lines. Closing the
|
|
376
|
+
* connection — from either side — uninstalls the module.
|
|
377
|
+
*/
|
|
378
|
+
declare const SIDECAR_AGENT_SWARM_TESTING_BE_MOCK_PATH = "/v1/agent-swarm-testing/be-mock";
|
|
379
|
+
/**
|
|
380
|
+
* Agent-swarm-testing route reporting what one installed be-mock decided for
|
|
381
|
+
* each outbound call routed to it: `GET` with `runId` and `frontendSessionId`
|
|
382
|
+
* query params, answered with {@link BeMockCallsReport} — or 404 when no module
|
|
383
|
+
* is installed under that id. Calls routed to no module are never reported: no
|
|
384
|
+
* case could have mocked them.
|
|
385
|
+
*/
|
|
386
|
+
declare const SIDECAR_AGENT_SWARM_TESTING_BE_MOCK_CALLS_PATH = "/v1/agent-swarm-testing/be-mock/calls";
|
|
387
|
+
/**
|
|
388
|
+
* One outbound call an installed be-mock was offered, and what came of it. A
|
|
389
|
+
* passed-through call's real response is not observed: the sidecar only takes
|
|
390
|
+
* the module's decision.
|
|
391
|
+
*/
|
|
392
|
+
type BeMockCall = {
|
|
393
|
+
method: string;
|
|
394
|
+
url: string;
|
|
395
|
+
requestBody?: CapturedBody;
|
|
396
|
+
} & ({
|
|
397
|
+
/** interceptRequest's response was served. */outcome: "mock";
|
|
398
|
+
response: {
|
|
399
|
+
statusCode: number;
|
|
400
|
+
body: string;
|
|
401
|
+
};
|
|
402
|
+
} | {
|
|
403
|
+
/**
|
|
404
|
+
* The call went to the real service: interceptRequest returned
|
|
405
|
+
* undefined, or its answer could not be used (`error` says why).
|
|
406
|
+
*/
|
|
407
|
+
outcome: "passthrough";
|
|
408
|
+
error?: string;
|
|
409
|
+
});
|
|
410
|
+
interface BeMockCallsReport {
|
|
411
|
+
/** In completion order (the module finished deciding). */
|
|
412
|
+
calls: BeMockCall[];
|
|
413
|
+
/** Calls not kept once the report reached its bound. */
|
|
414
|
+
droppedCount: number;
|
|
415
|
+
}
|
|
416
|
+
/**
|
|
417
|
+
* How often the sidecar pings each be-mock connection (a WebSocket ping
|
|
418
|
+
* frame). The sidecar terminates a connection that misses a pong; the worker
|
|
419
|
+
* treats several missed pings as a lost module.
|
|
420
|
+
*/
|
|
421
|
+
declare const BE_MOCK_PING_INTERVAL_MS = 15000;
|
|
422
|
+
/** Worker → sidecar: the module to install. Always the connection's first message. */
|
|
423
|
+
interface BeMockInstallMessage {
|
|
424
|
+
type: "install";
|
|
425
|
+
/** Whose run this module belongs to; see {@link AGENT_SWARM_TESTING_RUN_ID_ENV_KEY}. */
|
|
426
|
+
runId: string;
|
|
427
|
+
/**
|
|
428
|
+
* The routing key: the sidecar serves this module only to outbound calls whose
|
|
429
|
+
* inbound request carried this {@link FRONTEND_SESSION_ID_HEADER}, so parallel
|
|
430
|
+
* cases sharing one backend each get their own module.
|
|
431
|
+
*/
|
|
432
|
+
frontendSessionId: string;
|
|
433
|
+
/** The compiled, self-contained sandbox bundle to install. */
|
|
434
|
+
code: string;
|
|
435
|
+
}
|
|
436
|
+
/**
|
|
437
|
+
* Worker → sidecar: one recorded session's backend spans, answering a
|
|
438
|
+
* {@link BeMockLoadSessionMessage}. `error` instead when the worker could not
|
|
439
|
+
* read the session.
|
|
440
|
+
*/
|
|
441
|
+
type BeMockSessionMessage = {
|
|
442
|
+
type: "session";
|
|
443
|
+
sessionId: string;
|
|
444
|
+
backendSpans: unknown[];
|
|
445
|
+
} | {
|
|
446
|
+
type: "session";
|
|
447
|
+
sessionId: string;
|
|
448
|
+
error: string;
|
|
449
|
+
};
|
|
450
|
+
type BeMockWorkerMessage = BeMockInstallMessage | BeMockSessionMessage;
|
|
451
|
+
/**
|
|
452
|
+
* Sidecar → worker: the loading module called `loadSessionBackendRequests`
|
|
453
|
+
* for this session. Sent only before {@link BeMockReadyMessage}: a module
|
|
454
|
+
* loads its recordings at the top level.
|
|
455
|
+
*/
|
|
456
|
+
interface BeMockLoadSessionMessage {
|
|
457
|
+
type: "loadSession";
|
|
458
|
+
sessionId: string;
|
|
459
|
+
}
|
|
460
|
+
/** Sidecar → worker: the module loaded and now answers outbound calls. */
|
|
461
|
+
interface BeMockReadyMessage {
|
|
462
|
+
type: "ready";
|
|
463
|
+
}
|
|
464
|
+
/** Sidecar → worker: the install failed; the sidecar closes the connection after it. */
|
|
465
|
+
interface BeMockFailedMessage {
|
|
466
|
+
type: "failed";
|
|
467
|
+
error: string;
|
|
468
|
+
}
|
|
469
|
+
/** Sidecar → worker: one line from the module (stdout/stderr or its `log(...)`). */
|
|
470
|
+
interface BeMockLogMessage {
|
|
471
|
+
type: "log";
|
|
472
|
+
line: string;
|
|
473
|
+
}
|
|
474
|
+
type BeMockSidecarMessage = BeMockLoadSessionMessage | BeMockReadyMessage | BeMockFailedMessage | BeMockLogMessage;
|
|
475
|
+
/**
|
|
476
|
+
* One outbound HTTP call a recorded session's backend made, as
|
|
477
|
+
* `loadSessionBackendRequests` returns it to be-mock.ts. The request mirrors
|
|
478
|
+
* {@link OutboundFetchAgentSwarmTestingRequest} so a live request compares
|
|
479
|
+
* field for field; the response is servable as-is.
|
|
480
|
+
*/
|
|
481
|
+
interface BeMockRecordedRequest {
|
|
482
|
+
method: string;
|
|
483
|
+
/**
|
|
484
|
+
* Full URL including query string, as recorded — or only the path and query
|
|
485
|
+
* when the recording stored no scheme or host (older recorders).
|
|
486
|
+
*/
|
|
487
|
+
url: string;
|
|
488
|
+
requestBody?: CapturedBody;
|
|
489
|
+
/**
|
|
490
|
+
* True when ingestion attached this call by its time alone, not by this
|
|
491
|
+
* session's id: it may belong to another user's traffic on the same backend.
|
|
492
|
+
*/
|
|
493
|
+
inferred: boolean;
|
|
494
|
+
response: {
|
|
495
|
+
statusCode: number; /** As recorded: text, or base64 when `bodyEncoding` is set. */
|
|
496
|
+
body: string;
|
|
497
|
+
bodyEncoding?: BodyEncoding; /** True when the recorder cut the body at the capture cap. */
|
|
498
|
+
bodyTruncated: boolean;
|
|
499
|
+
headers: Record<string, string>;
|
|
500
|
+
};
|
|
501
|
+
}
|
|
502
|
+
/**
|
|
503
|
+
* One outbound `fetch` offered to the interceptor, sent to
|
|
504
|
+
* {@link SIDECAR_AGENT_SWARM_TESTING_OUTBOUND_FETCH_PATH}. Unlike replay's lookup there is no
|
|
505
|
+
* consume-once mock state, so a single run id suffices.
|
|
506
|
+
*/
|
|
507
|
+
interface OutboundFetchAgentSwarmTestingRequest {
|
|
508
|
+
runId: string;
|
|
509
|
+
/** Value of {@link FRONTEND_SESSION_ID_HEADER} on the inbound request, if any. */
|
|
510
|
+
frontendSessionId?: string;
|
|
511
|
+
method: string;
|
|
512
|
+
/** Full URL including query string. */
|
|
513
|
+
url: string;
|
|
514
|
+
/** Request body as captured by `readBodyWithCap`, mirroring the replay lookup. */
|
|
515
|
+
requestBody?: CapturedBody;
|
|
516
|
+
}
|
|
517
|
+
/**
|
|
518
|
+
* A response for the interceptor to serve, or a signal to let the call through. Unlike
|
|
519
|
+
* replay's lookup response there is no failure outcome: agent-swarm testing is best-effort,
|
|
520
|
+
* so anything the shim cannot act on becomes a passthrough, never a failed call.
|
|
521
|
+
*/
|
|
522
|
+
type OutboundFetchAgentSwarmTestingResponse = {
|
|
523
|
+
outcome: "mock";
|
|
524
|
+
statusCode: number;
|
|
525
|
+
body: string;
|
|
526
|
+
headers: Record<string, string>;
|
|
527
|
+
} | {
|
|
528
|
+
outcome: "passthrough";
|
|
529
|
+
};
|
|
339
530
|
//#endregion
|
|
340
|
-
export { BindingRequestEvent, CAPTURED_HEADERS, CaptureEvent, CaptureEventsPayload, CapturedBody, CoverageReportRequest, 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_COVERAGE_PATH, SIDECAR_REPLAY_OUTBOUND_FETCH_PATH, SIDECAR_REPLAY_SESSION_PATH, VIRTUAL_TIME_HEADER, WORKERD_SHIM_VERSION_HEADER, parseVirtualTimeMs };
|
|
531
|
+
export { AGENT_SWARM_TESTING_RUN_ID_ENV_KEY, AGENT_SWARM_TESTING_SIDECAR_URL_ENV_KEY, BE_MOCK_PING_INTERVAL_MS, BeMockCall, BeMockCallsReport, BeMockFailedMessage, BeMockInstallMessage, BeMockLoadSessionMessage, BeMockLogMessage, BeMockReadyMessage, BeMockRecordedRequest, BeMockSessionMessage, BeMockSidecarMessage, BeMockWorkerMessage, BindingRequestEvent, CAPTURED_HEADERS, CaptureEvent, CaptureEventsPayload, CapturedBody, CoverageReportRequest, FRONTEND_SESSION_ID_HEADER, InboundRequestEvent, KvOmittedReason, KvOperation, KvOperationEvent, METICULOUS_PASSTHROUGH_HEADER, OutboundFetchAgentSwarmTestingRequest, OutboundFetchAgentSwarmTestingResponse, OutboundFetchLookupRequest, OutboundFetchLookupResponse, OutboundRequestEvent, PostgresQueryEvent, REPLAY_ID_HEADER, REPLAY_SIDECAR_URL_HEADER, ReplaySessionInfoResponse, SIDECAR_AGENT_SWARM_TESTING_BE_MOCK_CALLS_PATH, SIDECAR_AGENT_SWARM_TESTING_BE_MOCK_PATH, SIDECAR_AGENT_SWARM_TESTING_OUTBOUND_FETCH_PATH, SIDECAR_EVENTS_PATH, SIDECAR_PROTOCOL_VERSION, SIDECAR_PROTOCOL_VERSION_HEADER, SIDECAR_REPLAY_COVERAGE_PATH, SIDECAR_REPLAY_OUTBOUND_FETCH_PATH, SIDECAR_REPLAY_SESSION_PATH, VIRTUAL_TIME_HEADER, WORKERD_SHIM_VERSION_HEADER, parseVirtualTimeMs };
|
|
341
532
|
//# sourceMappingURL=protocol.d.ts.map
|
package/dist/protocol.js
CHANGED
|
@@ -1,9 +1,4 @@
|
|
|
1
1
|
//#region src/protocol.ts
|
|
2
|
-
/**
|
|
3
|
-
* Wire protocol between the in-worker shim and the Meticulous backend
|
|
4
|
-
* recorder sidecar. The sidecar imports these types (type-only), so this file
|
|
5
|
-
* is the single source of truth for the shape of capture events.
|
|
6
|
-
*/
|
|
7
2
|
const SIDECAR_PROTOCOL_VERSION = "2";
|
|
8
3
|
/** Header carrying {@link SIDECAR_PROTOCOL_VERSION} on every shim → sidecar request. */
|
|
9
4
|
const SIDECAR_PROTOCOL_VERSION_HEADER = "x-meticulous-sidecar-protocol-version";
|
|
@@ -63,7 +58,7 @@ const SIDECAR_REPLAY_COVERAGE_PATH = "/v1/replay/coverage";
|
|
|
63
58
|
* so an inbound header is the only way per-replay config can reach the shim.
|
|
64
59
|
*
|
|
65
60
|
* The shim validates the value before using it and only honours a loopback /
|
|
66
|
-
* `host.docker.internal` / private-network `http:` origin — see `
|
|
61
|
+
* `host.docker.internal` / private-network `http:` origin — see `sidecar-url.ts`.
|
|
67
62
|
*/
|
|
68
63
|
const REPLAY_SIDECAR_URL_HEADER = "x-meticulous-backend-replay-sidecar-url";
|
|
69
64
|
/**
|
|
@@ -85,7 +80,50 @@ const WORKERD_SHIM_VERSION_HEADER = "x-meticulous-workerd-shim-version";
|
|
|
85
80
|
* render still carries no session cookie.
|
|
86
81
|
*/
|
|
87
82
|
const CAPTURED_HEADERS = ["content-type", FRONTEND_SESSION_ID_HEADER];
|
|
83
|
+
/**
|
|
84
|
+
* Env var holding the origin of the agent-swarm-testing interceptor service, as a wrangler
|
|
85
|
+
* var on the deployment. Validated exactly like the replay sidecar URL — a loopback /
|
|
86
|
+
* docker-gateway / private-network `http:` origin only — see `sidecar-url.ts`.
|
|
87
|
+
*/
|
|
88
|
+
const AGENT_SWARM_TESTING_SIDECAR_URL_ENV_KEY = "METICULOUS_AGENT_SWARM_TESTING_SIDECAR_URL";
|
|
89
|
+
/**
|
|
90
|
+
* Env var identifying the agent-swarm run, so an interceptor shared by several runs can tell
|
|
91
|
+
* whose traffic a call belongs to.
|
|
92
|
+
*/
|
|
93
|
+
const AGENT_SWARM_TESTING_RUN_ID_ENV_KEY = "METICULOUS_AGENT_SWARM_TESTING_RUN_ID";
|
|
94
|
+
/** Agent-swarm-testing route. */
|
|
95
|
+
const SIDECAR_AGENT_SWARM_TESTING_OUTBOUND_FETCH_PATH = "/v1/agent-swarm-testing/outbound-fetch";
|
|
96
|
+
/**
|
|
97
|
+
* Agent-swarm-testing WebSocket route that hosts one be-mock: an agent-authored
|
|
98
|
+
* `be-mock.ts` (backend mocks), compiled by the agentic session-generation
|
|
99
|
+
* worker into a self-contained sandbox bundle. The sidecar runs it in its own
|
|
100
|
+
* permission-restricted child process over the frozen RPC toolkit, and routes
|
|
101
|
+
* every intercepted outbound fetch through the module's `interceptRequest`
|
|
102
|
+
* export — see `packages/backend-recorder-js/src/sidecar/be-mock` and
|
|
103
|
+
* `packages/agentic-session-generation-worker/src/app-access/be-mocking`.
|
|
104
|
+
*
|
|
105
|
+
* One connection is one installed module: the worker opens it, sends
|
|
106
|
+
* {@link BeMockInstallMessage}, answers every {@link BeMockLoadSessionMessage}
|
|
107
|
+
* while the module loads, and receives {@link BeMockReadyMessage} (or
|
|
108
|
+
* {@link BeMockFailedMessage}) and then the module's log lines. Closing the
|
|
109
|
+
* connection — from either side — uninstalls the module.
|
|
110
|
+
*/
|
|
111
|
+
const SIDECAR_AGENT_SWARM_TESTING_BE_MOCK_PATH = "/v1/agent-swarm-testing/be-mock";
|
|
112
|
+
/**
|
|
113
|
+
* Agent-swarm-testing route reporting what one installed be-mock decided for
|
|
114
|
+
* each outbound call routed to it: `GET` with `runId` and `frontendSessionId`
|
|
115
|
+
* query params, answered with {@link BeMockCallsReport} — or 404 when no module
|
|
116
|
+
* is installed under that id. Calls routed to no module are never reported: no
|
|
117
|
+
* case could have mocked them.
|
|
118
|
+
*/
|
|
119
|
+
const SIDECAR_AGENT_SWARM_TESTING_BE_MOCK_CALLS_PATH = "/v1/agent-swarm-testing/be-mock/calls";
|
|
120
|
+
/**
|
|
121
|
+
* How often the sidecar pings each be-mock connection (a WebSocket ping
|
|
122
|
+
* frame). The sidecar terminates a connection that misses a pong; the worker
|
|
123
|
+
* treats several missed pings as a lost module.
|
|
124
|
+
*/
|
|
125
|
+
const BE_MOCK_PING_INTERVAL_MS = 15e3;
|
|
88
126
|
//#endregion
|
|
89
|
-
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_COVERAGE_PATH, SIDECAR_REPLAY_OUTBOUND_FETCH_PATH, SIDECAR_REPLAY_SESSION_PATH, VIRTUAL_TIME_HEADER, WORKERD_SHIM_VERSION_HEADER, parseVirtualTimeMs };
|
|
127
|
+
export { AGENT_SWARM_TESTING_RUN_ID_ENV_KEY, AGENT_SWARM_TESTING_SIDECAR_URL_ENV_KEY, BE_MOCK_PING_INTERVAL_MS, CAPTURED_HEADERS, FRONTEND_SESSION_ID_HEADER, METICULOUS_PASSTHROUGH_HEADER, REPLAY_ID_HEADER, REPLAY_SIDECAR_URL_HEADER, SIDECAR_AGENT_SWARM_TESTING_BE_MOCK_CALLS_PATH, SIDECAR_AGENT_SWARM_TESTING_BE_MOCK_PATH, SIDECAR_AGENT_SWARM_TESTING_OUTBOUND_FETCH_PATH, SIDECAR_EVENTS_PATH, SIDECAR_PROTOCOL_VERSION, SIDECAR_PROTOCOL_VERSION_HEADER, SIDECAR_REPLAY_COVERAGE_PATH, SIDECAR_REPLAY_OUTBOUND_FETCH_PATH, SIDECAR_REPLAY_SESSION_PATH, VIRTUAL_TIME_HEADER, WORKERD_SHIM_VERSION_HEADER, parseVirtualTimeMs };
|
|
90
128
|
|
|
91
129
|
//# sourceMappingURL=protocol.js.map
|