@yolk-sdk/conformance 0.1.0-canary.96

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.
@@ -0,0 +1,212 @@
1
+ import { headerRecord, isCredentialHeaderName, isNullBodyStatus, mediaType, parseJsonText, recordBytes, requestBodyText } from "./wire-internal.mjs";
2
+ import { decodeWireFixture, scanFixtureForSecrets } from "./fixture.mjs";
3
+ import { Context, Data, Effect, Exit, Layer, Option, Ref, Stream } from "effect";
4
+ import { HttpClient, HttpClientRequest, HttpClientResponse } from "effect/unstable/http";
5
+ //#region src/record.ts
6
+ /**
7
+ * Record live HTTP exchanges as wire fixtures.
8
+ *
9
+ * This module never constructs a network client: hosts supply the real
10
+ * `HttpClient` (for example `FetchHttpClient.layer`) and this wrapper records
11
+ * what flows through it without changing the bytes the caller receives.
12
+ *
13
+ * @experimental
14
+ */
15
+ /** Default allowlisted request headers. Credential headers are always dropped. */
16
+ const defaultRecordedRequestHeaders = ["content-type", "accept"];
17
+ /**
18
+ * Default allowlisted response headers. Entries ending in `*` are prefixes.
19
+ * Credential headers (including `set-cookie`) are always dropped.
20
+ */
21
+ const defaultRecordedResponseHeaders = [
22
+ "content-type",
23
+ "retry-after",
24
+ "retry-after-ms",
25
+ "x-ratelimit-*",
26
+ "ratelimit-*",
27
+ "anthropic-ratelimit-*"
28
+ ];
29
+ /** Response media types recorded as `chunks` instead of a single `body`. */
30
+ const defaultStreamMediaTypes = ["text/event-stream"];
31
+ /** Raised by `drain` when a recorded request failed or its body was not read to the end. */
32
+ var WireRecordingIncomplete = class extends Data.TaggedError("WireRecordingIncomplete") {
33
+ get message() {
34
+ return `Recording incomplete for ${this.requests.length} request(s): ${this.requests.join(", ")}`;
35
+ }
36
+ };
37
+ var WireRecorder = class WireRecorder extends Context.Service()("@yolk-sdk/conformance/WireRecorder") {
38
+ /**
39
+ * Wrap the `HttpClient` already in context and provide the recording client
40
+ * plus its `WireRecorder`. Provide the real client below this layer.
41
+ */
42
+ static layer = (options = {}) => Layer.unwrap(Effect.gen(function* () {
43
+ const { client, recorder } = yield* makeRecordingHttpClient(yield* HttpClient.HttpClient, options);
44
+ return Layer.mergeAll(Layer.succeed(HttpClient.HttpClient, client), Layer.succeed(WireRecorder, recorder));
45
+ }));
46
+ };
47
+ const recordedChunk = (bytes) => {
48
+ const recorded = recordBytes(bytes);
49
+ return "text" in recorded ? recorded.text : { base64: recorded.base64 };
50
+ };
51
+ const recordedBody = (status, headers, bytes) => {
52
+ const recorded = recordBytes(bytes);
53
+ return "text" in recorded ? {
54
+ status,
55
+ headers,
56
+ body: recorded.text
57
+ } : {
58
+ status,
59
+ headers,
60
+ bodyBase64: recorded.base64
61
+ };
62
+ };
63
+ const headerAllowed = (allowlist, name) => !isCredentialHeaderName(name) && allowlist.some((entry) => {
64
+ const lower = entry.toLowerCase();
65
+ return lower.endsWith("*") ? name.startsWith(lower.slice(0, -1)) : name === lower;
66
+ });
67
+ const allowlistHeaders = (headers, allowlist) => {
68
+ const recorded = {};
69
+ for (const [name, value] of Object.entries(headerRecord(headers))) if (headerAllowed(allowlist, name)) recorded[name] = value;
70
+ return recorded;
71
+ };
72
+ const recordRequest = (request, allowlist) => Effect.gen(function* () {
73
+ const url = Option.match(HttpClientRequest.toUrl(request), {
74
+ onNone: () => request.url,
75
+ onSome: (resolved) => {
76
+ resolved.hash = "";
77
+ return resolved.toString();
78
+ }
79
+ });
80
+ const fields = {
81
+ method: request.method,
82
+ url
83
+ };
84
+ const headers = allowlistHeaders(request.headers, allowlist);
85
+ if (Object.keys(headers).length > 0) fields.headers = headers;
86
+ const text = requestBodyText(request);
87
+ if (text !== void 0 && text.length > 0) {
88
+ const json = yield* parseJsonText(text);
89
+ fields.body = Option.getOrElse(json, () => text);
90
+ }
91
+ return fields;
92
+ });
93
+ /**
94
+ * Wrap a host-provided `HttpClient` so every exchange is recorded losslessly.
95
+ * Streamed responses (see `streamMediaTypes`) are teed chunk by chunk with
96
+ * network boundaries preserved: each chunk is stored as text when it is valid
97
+ * UTF-8 on its own (empty chunks as `""`), otherwise as `{ base64 }` of its
98
+ * exact bytes. Other responses are recorded as one `body` (valid UTF-8) or
99
+ * `bodyBase64` (anything else). The caller receives the same status, headers,
100
+ * and bytes. Body read failures still reach the caller as `HttpClientError`s
101
+ * (the upstream error is kept as the cause), matching how `FetchHttpClient`
102
+ * reports them.
103
+ */
104
+ const makeRecordingHttpClient = (upstream, options = {}) => Effect.gen(function* () {
105
+ const requestAllowlist = options.requestHeaders ?? defaultRecordedRequestHeaders;
106
+ const responseAllowlist = options.responseHeaders ?? defaultRecordedResponseHeaders;
107
+ const streamMediaTypes = (options.streamMediaTypes ?? defaultStreamMediaTypes).map((type) => type.toLowerCase());
108
+ const state = yield* Ref.make({
109
+ nextId: 0,
110
+ entries: []
111
+ });
112
+ const reserve = (label) => Ref.modify(state, (current) => [current.nextId, {
113
+ nextId: current.nextId + 1,
114
+ entries: [...current.entries, {
115
+ id: current.nextId,
116
+ entry: {
117
+ status: "pending",
118
+ label
119
+ }
120
+ }]
121
+ }]);
122
+ const settle = (id, entry) => Ref.update(state, (current) => ({
123
+ ...current,
124
+ entries: current.entries.map((item) => item.id === id && item.entry.status === "pending" ? {
125
+ id,
126
+ entry
127
+ } : item)
128
+ }));
129
+ return {
130
+ client: HttpClient.transform(upstream, (effect, request) => Effect.gen(function* () {
131
+ const wireRequest = yield* recordRequest(request, requestAllowlist);
132
+ const label = `${wireRequest.method} ${wireRequest.url.split("?", 1)[0]}`;
133
+ const id = yield* reserve(label);
134
+ const failed = settle(id, {
135
+ status: "failed",
136
+ label
137
+ });
138
+ const response = yield* effect.pipe(Effect.tapError(() => failed));
139
+ const headers = headerRecord(response.headers);
140
+ const recordedHeaders = allowlistHeaders(headers, responseAllowlist);
141
+ const init = {
142
+ status: response.status,
143
+ headers
144
+ };
145
+ const complete = (wireResponse) => settle(id, {
146
+ status: "complete",
147
+ label,
148
+ exchange: {
149
+ request: wireRequest,
150
+ response: wireResponse
151
+ }
152
+ });
153
+ const type = mediaType(headers["content-type"]);
154
+ if (type !== void 0 && streamMediaTypes.includes(type)) {
155
+ const chunks = [];
156
+ const teed = response.stream.pipe(Stream.tap((bytes) => Effect.sync(() => chunks.push(recordedChunk(bytes)))), Stream.onExit((exit) => Exit.isFailure(exit) ? failed : complete({
157
+ status: response.status,
158
+ headers: recordedHeaders,
159
+ chunks
160
+ })));
161
+ const readable = Stream.toReadableStream(teed, { strategy: { highWaterMark: 0 } });
162
+ return HttpClientResponse.fromWeb(request, new Response(readable, init));
163
+ }
164
+ const bytes = yield* response.arrayBuffer.pipe(Effect.tapError(() => failed));
165
+ yield* complete(recordedBody(response.status, recordedHeaders, new Uint8Array(bytes)));
166
+ return HttpClientResponse.fromWeb(request, new Response(isNullBodyStatus(response.status) ? null : bytes, init));
167
+ })),
168
+ recorder: { drain: Ref.modify(state, (current) => [current.entries, {
169
+ nextId: current.nextId,
170
+ entries: []
171
+ }]).pipe(Effect.flatMap((drained) => {
172
+ const exchanges = [];
173
+ const incomplete = [];
174
+ for (const { entry } of drained) if (entry.status === "complete") exchanges.push(entry.exchange);
175
+ else incomplete.push(entry.label);
176
+ return incomplete.length > 0 ? Effect.fail(new WireRecordingIncomplete({ requests: incomplete })) : Effect.succeed(exchanges);
177
+ })) }
178
+ };
179
+ });
180
+ /** A fixture failed schema validation (for example an empty exchange list or bad date). */
181
+ var WireFixtureInvalid = class extends Data.TaggedError("WireFixtureInvalid") {
182
+ get message() {
183
+ return `Wire fixture ${this.fixtureId} is invalid`;
184
+ }
185
+ };
186
+ /** The secret scan found credentials or credential-like values in a fixture. */
187
+ var WireFixtureSecretsFound = class extends Data.TaggedError("WireFixtureSecretsFound") {
188
+ get message() {
189
+ return `Wire fixture ${this.fixtureId} contains ${this.issues.length} secret-scan issue(s)`;
190
+ }
191
+ };
192
+ /**
193
+ * Validate a fixture and run `scanFixtureForSecrets`. Fails with
194
+ * `WireFixtureInvalid` or `WireFixtureSecretsFound`; never returns a fixture
195
+ * with scan issues.
196
+ */
197
+ const makeWireFixture = (input) => Effect.gen(function* () {
198
+ const fixture = yield* decodeWireFixture(input).pipe(Effect.mapError((cause) => new WireFixtureInvalid({
199
+ fixtureId: input.id,
200
+ cause
201
+ })));
202
+ const issues = scanFixtureForSecrets(fixture);
203
+ if (issues.length > 0) return yield* Effect.fail(new WireFixtureSecretsFound({
204
+ fixtureId: fixture.id,
205
+ issues
206
+ }));
207
+ return fixture;
208
+ });
209
+ //#endregion
210
+ export { WireFixtureInvalid, WireFixtureSecretsFound, WireRecorder, WireRecordingIncomplete, defaultRecordedRequestHeaders, defaultRecordedResponseHeaders, defaultStreamMediaTypes, makeRecordingHttpClient, makeWireFixture };
211
+
212
+ //# sourceMappingURL=record.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"record.mjs","names":[],"sources":["../src/record.ts"],"sourcesContent":["/**\n * Record live HTTP exchanges as wire fixtures.\n *\n * This module never constructs a network client: hosts supply the real\n * `HttpClient` (for example `FetchHttpClient.layer`) and this wrapper records\n * what flows through it without changing the bytes the caller receives.\n *\n * @experimental\n */\nimport { Context, Data, Effect, Exit, Layer, Option, Ref, Stream } from 'effect'\nimport {\n HttpClient,\n HttpClientRequest,\n HttpClientResponse,\n type HttpClientError\n} from 'effect/unstable/http'\nimport {\n decodeWireFixture,\n scanFixtureForSecrets,\n type FixtureSecretIssue,\n type WireBodyResponse,\n type WireChunk,\n type WireExchange,\n type WireFixture,\n type WireHeaders,\n type WireRequest,\n type WireResponse\n} from './fixture.ts'\nimport {\n headerRecord,\n isCredentialHeaderName,\n isNullBodyStatus,\n mediaType,\n parseJsonText,\n recordBytes,\n requestBodyText\n} from './wire-internal.ts'\n\n/** Default allowlisted request headers. Credential headers are always dropped. */\nexport const defaultRecordedRequestHeaders: ReadonlyArray<string> = ['content-type', 'accept']\n\n/**\n * Default allowlisted response headers. Entries ending in `*` are prefixes.\n * Credential headers (including `set-cookie`) are always dropped.\n */\nexport const defaultRecordedResponseHeaders: ReadonlyArray<string> = [\n 'content-type',\n 'retry-after',\n 'retry-after-ms',\n 'x-ratelimit-*',\n 'ratelimit-*',\n 'anthropic-ratelimit-*'\n]\n\n/** Response media types recorded as `chunks` instead of a single `body`. */\nexport const defaultStreamMediaTypes: ReadonlyArray<string> = ['text/event-stream']\n\nexport type WireRecorderOptions = {\n readonly requestHeaders?: ReadonlyArray<string>\n readonly responseHeaders?: ReadonlyArray<string>\n readonly streamMediaTypes?: ReadonlyArray<string>\n}\n\n/** Raised by `drain` when a recorded request failed or its body was not read to the end. */\nexport class WireRecordingIncomplete extends Data.TaggedError('WireRecordingIncomplete')<{\n /** `METHOD url` (query string removed) of each incomplete exchange. */\n readonly requests: ReadonlyArray<string>\n}> {\n override get message(): string {\n return `Recording incomplete for ${this.requests.length} request(s): ${this.requests.join(', ')}`\n }\n}\n\nexport type WireRecorderApi = {\n /**\n * Take every recorded exchange, in request order, and clear the recorder.\n * Fails with `WireRecordingIncomplete` (and discards the drained entries)\n * when any request failed or its body was not fully read.\n *\n * A request still pending at drain time is reported in that drain's\n * `WireRecordingIncomplete` and is then dropped: if its body finishes later,\n * the result is ignored and never appears in, or overwrites an entry of, a\n * later drain.\n */\n readonly drain: Effect.Effect<ReadonlyArray<WireExchange>, WireRecordingIncomplete>\n}\n\nexport class WireRecorder extends Context.Service<WireRecorder, WireRecorderApi>()(\n '@yolk-sdk/conformance/WireRecorder'\n) {\n /**\n * Wrap the `HttpClient` already in context and provide the recording client\n * plus its `WireRecorder`. Provide the real client below this layer.\n */\n static layer = (\n options: WireRecorderOptions = {}\n ): Layer.Layer<HttpClient.HttpClient | WireRecorder, never, HttpClient.HttpClient> =>\n Layer.unwrap(\n Effect.gen(function* () {\n const upstream = yield* HttpClient.HttpClient\n const { client, recorder } = yield* makeRecordingHttpClient(upstream, options)\n\n return Layer.mergeAll(\n Layer.succeed(HttpClient.HttpClient, client),\n Layer.succeed(WireRecorder, recorder)\n )\n })\n )\n}\n\ntype RecordingEntry =\n | { readonly status: 'pending'; readonly label: string }\n | { readonly status: 'failed'; readonly label: string }\n | { readonly status: 'complete'; readonly label: string; readonly exchange: WireExchange }\n\n// `id` is a monotonic reservation id that is never reused, so a finalizer from\n// a request reserved before a `drain` cannot settle an entry reserved after it.\ntype RecordingState = {\n readonly nextId: number\n readonly entries: ReadonlyArray<{ readonly id: number; readonly entry: RecordingEntry }>\n}\n\nconst recordedChunk = (bytes: Uint8Array): WireChunk => {\n const recorded = recordBytes(bytes)\n\n return 'text' in recorded ? recorded.text : { base64: recorded.base64 }\n}\n\nconst recordedBody = (\n status: number,\n headers: WireHeaders,\n bytes: Uint8Array\n): WireBodyResponse => {\n const recorded = recordBytes(bytes)\n\n return 'text' in recorded\n ? { status, headers, body: recorded.text }\n : { status, headers, bodyBase64: recorded.base64 }\n}\n\nconst headerAllowed = (allowlist: ReadonlyArray<string>, name: string): boolean =>\n !isCredentialHeaderName(name) &&\n allowlist.some(entry => {\n const lower = entry.toLowerCase()\n\n return lower.endsWith('*') ? name.startsWith(lower.slice(0, -1)) : name === lower\n })\n\nconst allowlistHeaders = (\n headers: Readonly<Record<string, string | undefined>>,\n allowlist: ReadonlyArray<string>\n): WireHeaders => {\n const recorded: Record<string, string> = {}\n\n for (const [name, value] of Object.entries(headerRecord(headers))) {\n if (headerAllowed(allowlist, name)) {\n recorded[name] = value\n }\n }\n\n return recorded\n}\n\ntype WireRequestFields = {\n method: string\n url: string\n headers?: WireHeaders\n body?: WireRequest['body']\n}\n\nconst recordRequest = (\n request: HttpClientRequest.HttpClientRequest,\n allowlist: ReadonlyArray<string>\n): Effect.Effect<WireRequest> =>\n Effect.gen(function* () {\n const url = Option.match(HttpClientRequest.toUrl(request), {\n onNone: () => request.url,\n onSome: resolved => {\n resolved.hash = ''\n\n return resolved.toString()\n }\n })\n\n const fields: WireRequestFields = { method: request.method, url }\n const headers = allowlistHeaders(request.headers, allowlist)\n\n if (Object.keys(headers).length > 0) {\n fields.headers = headers\n }\n\n const text = requestBodyText(request)\n\n if (text !== undefined && text.length > 0) {\n const json = yield* parseJsonText(text)\n\n fields.body = Option.getOrElse(json, () => text)\n }\n\n return fields\n })\n\n/**\n * Wrap a host-provided `HttpClient` so every exchange is recorded losslessly.\n * Streamed responses (see `streamMediaTypes`) are teed chunk by chunk with\n * network boundaries preserved: each chunk is stored as text when it is valid\n * UTF-8 on its own (empty chunks as `\"\"`), otherwise as `{ base64 }` of its\n * exact bytes. Other responses are recorded as one `body` (valid UTF-8) or\n * `bodyBase64` (anything else). The caller receives the same status, headers,\n * and bytes. Body read failures still reach the caller as `HttpClientError`s\n * (the upstream error is kept as the cause), matching how `FetchHttpClient`\n * reports them.\n */\nexport const makeRecordingHttpClient = (\n upstream: HttpClient.HttpClient,\n options: WireRecorderOptions = {}\n): Effect.Effect<{ readonly client: HttpClient.HttpClient; readonly recorder: WireRecorderApi }> =>\n Effect.gen(function* () {\n const requestAllowlist = options.requestHeaders ?? defaultRecordedRequestHeaders\n const responseAllowlist = options.responseHeaders ?? defaultRecordedResponseHeaders\n\n const streamMediaTypes = (options.streamMediaTypes ?? defaultStreamMediaTypes).map(type =>\n type.toLowerCase()\n )\n\n const state = yield* Ref.make<RecordingState>({ nextId: 0, entries: [] })\n\n const reserve = (label: string) =>\n Ref.modify(state, (current): [number, RecordingState] => [\n current.nextId,\n {\n nextId: current.nextId + 1,\n entries: [...current.entries, { id: current.nextId, entry: { status: 'pending', label } }]\n }\n ])\n\n // Settles only a still-pending entry with this id; an entry already drained\n // (or settled) is left untouched.\n const settle = (id: number, entry: RecordingEntry) =>\n Ref.update(state, current => ({\n ...current,\n entries: current.entries.map(item =>\n item.id === id && item.entry.status === 'pending' ? { id, entry } : item\n )\n }))\n\n const client = HttpClient.transform(\n upstream,\n (\n effect: Effect.Effect<\n HttpClientResponse.HttpClientResponse,\n HttpClientError.HttpClientError\n >,\n request\n ) =>\n Effect.gen(function* () {\n const wireRequest = yield* recordRequest(request, requestAllowlist)\n // Query strings may carry credentials: labels keep origin and path only.\n const label = `${wireRequest.method} ${wireRequest.url.split('?', 1)[0]}`\n const id = yield* reserve(label)\n const failed = settle(id, { status: 'failed', label })\n const response = yield* effect.pipe(Effect.tapError(() => failed))\n const headers = headerRecord(response.headers)\n const recordedHeaders = allowlistHeaders(headers, responseAllowlist)\n const init = { status: response.status, headers }\n\n const complete = (wireResponse: WireResponse) =>\n settle(id, {\n status: 'complete',\n label,\n exchange: { request: wireRequest, response: wireResponse }\n })\n\n const type = mediaType(headers['content-type'])\n\n if (type !== undefined && streamMediaTypes.includes(type)) {\n const chunks: Array<WireChunk> = []\n\n // Each network chunk is recorded standalone (no decoder carry-over),\n // so replay can reproduce the original bytes and boundaries.\n const teed = response.stream.pipe(\n Stream.tap(bytes => Effect.sync(() => chunks.push(recordedChunk(bytes)))),\n Stream.onExit(exit =>\n Exit.isFailure(exit)\n ? failed\n : complete({ status: response.status, headers: recordedHeaders, chunks })\n )\n )\n\n const readable = Stream.toReadableStream(teed, { strategy: { highWaterMark: 0 } })\n\n return HttpClientResponse.fromWeb(request, new Response(readable, init))\n }\n\n const bytes = yield* response.arrayBuffer.pipe(Effect.tapError(() => failed))\n\n yield* complete(recordedBody(response.status, recordedHeaders, new Uint8Array(bytes)))\n\n return HttpClientResponse.fromWeb(\n request,\n new Response(isNullBodyStatus(response.status) ? null : bytes, init)\n )\n })\n )\n\n const recorder: WireRecorderApi = {\n drain: Ref.modify(state, (current): [RecordingState['entries'], RecordingState] => [\n current.entries,\n { nextId: current.nextId, entries: [] }\n ]).pipe(\n Effect.flatMap(drained => {\n const exchanges: Array<WireExchange> = []\n const incomplete: Array<string> = []\n\n for (const { entry } of drained) {\n if (entry.status === 'complete') {\n exchanges.push(entry.exchange)\n } else {\n incomplete.push(entry.label)\n }\n }\n\n return incomplete.length > 0\n ? Effect.fail(new WireRecordingIncomplete({ requests: incomplete }))\n : Effect.succeed(exchanges)\n })\n )\n }\n\n return { client, recorder }\n })\n\n/** A fixture failed schema validation (for example an empty exchange list or bad date). */\nexport class WireFixtureInvalid extends Data.TaggedError('WireFixtureInvalid')<{\n readonly fixtureId: string\n readonly cause: unknown\n}> {\n override get message(): string {\n return `Wire fixture ${this.fixtureId} is invalid`\n }\n}\n\n/** The secret scan found credentials or credential-like values in a fixture. */\nexport class WireFixtureSecretsFound extends Data.TaggedError('WireFixtureSecretsFound')<{\n readonly fixtureId: string\n readonly issues: ReadonlyArray<FixtureSecretIssue>\n}> {\n override get message(): string {\n return `Wire fixture ${this.fixtureId} contains ${this.issues.length} secret-scan issue(s)`\n }\n}\n\nexport type WireFixtureInput = Omit<WireFixture, 'exchanges'> & {\n readonly exchanges: ReadonlyArray<WireExchange>\n}\n\n/**\n * Validate a fixture and run `scanFixtureForSecrets`. Fails with\n * `WireFixtureInvalid` or `WireFixtureSecretsFound`; never returns a fixture\n * with scan issues.\n */\nexport const makeWireFixture = (\n input: WireFixtureInput\n): Effect.Effect<WireFixture, WireFixtureInvalid | WireFixtureSecretsFound> =>\n Effect.gen(function* () {\n const fixture = yield* decodeWireFixture(input).pipe(\n Effect.mapError(cause => new WireFixtureInvalid({ fixtureId: input.id, cause }))\n )\n\n const issues = scanFixtureForSecrets(fixture)\n\n if (issues.length > 0) {\n return yield* Effect.fail(new WireFixtureSecretsFound({ fixtureId: fixture.id, issues }))\n }\n\n return fixture\n })\n"],"mappings":"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"}
@@ -0,0 +1,316 @@
1
+ import { WireFixture, WireHeaders } from "./fixture.mjs";
2
+ import { Cause, Context, Data, Effect, Layer } from "effect";
3
+ import * as Schema from "effect/Schema";
4
+ import { HttpClient } from "effect/unstable/http";
5
+
6
+ //#region src/replay.d.ts
7
+ /**
8
+ * Optional fault filter. `method` compares case-insensitively. `url` matches
9
+ * the normalized request URL exactly, or as a prefix when it ends with `*`.
10
+ * An omitted field matches every request.
11
+ */
12
+ type WireFaultMatch = {
13
+ readonly method?: string;
14
+ readonly url?: string;
15
+ };
16
+ /**
17
+ * Wire faults injected by the replay client. `attempt` is 1-based and counted
18
+ * per method + normalized URL.
19
+ *
20
+ * - `StatusOnAttempt`: respond with this status instead of consuming a recorded
21
+ * exchange, so the next attempt still gets the recording. It fires only when
22
+ * an unconsumed recorded exchange exists for the method + normalized URL;
23
+ * unknown or exhausted requests still fail closed as `unmatched`.
24
+ * - `FailAfterChunks`: emit the first `chunks` recorded chunks, then fail the
25
+ * body stream the way a dropped connection does under `FetchHttpClient`.
26
+ * - `TruncateAfterChunks`: emit the first `chunks` recorded chunks, then end
27
+ * the body cleanly.
28
+ * - `HoldAfterChunks`: emit the first `chunks` recorded chunks, run `release`,
29
+ * then emit the rest.
30
+ *
31
+ * `attempt` omitted means every matching attempt. A chunk fault that cannot
32
+ * take effect fails the request with a typed `HttpClientError` (and a ledger
33
+ * `invalid` outcome) instead of silently doing nothing: any chunk fault matched
34
+ * against a whole-body response, `TruncateAfterChunks`/`HoldAfterChunks` with
35
+ * `chunks` >= the recorded chunk count, or `FailAfterChunks` with `chunks` >
36
+ * the recorded chunk count. That error's reason is a `TransportError`, so
37
+ * transient-retry policies may retry past it: assert the ledger has no
38
+ * `invalid` outcome when testing through a retrying client.
39
+ */
40
+ type WireFault = Data.TaggedEnum<{
41
+ StatusOnAttempt: {
42
+ readonly match?: WireFaultMatch;
43
+ readonly attempt: number;
44
+ readonly status: number;
45
+ readonly headers?: WireHeaders;
46
+ readonly body?: string;
47
+ };
48
+ FailAfterChunks: {
49
+ readonly match?: WireFaultMatch;
50
+ readonly attempt?: number;
51
+ readonly chunks: number;
52
+ };
53
+ TruncateAfterChunks: {
54
+ readonly match?: WireFaultMatch;
55
+ readonly attempt?: number;
56
+ readonly chunks: number;
57
+ };
58
+ HoldAfterChunks: {
59
+ readonly match?: WireFaultMatch;
60
+ readonly attempt?: number;
61
+ readonly chunks: number;
62
+ readonly release: Effect.Effect<void>;
63
+ };
64
+ }>;
65
+ declare const WireFault: {
66
+ readonly StatusOnAttempt: Data.TaggedEnum.ConstructorFrom<{
67
+ readonly _tag: "StatusOnAttempt";
68
+ readonly match?: WireFaultMatch | undefined;
69
+ readonly attempt: number;
70
+ readonly status: number;
71
+ readonly headers?: WireHeaders | undefined;
72
+ readonly body?: string | undefined;
73
+ }, "_tag">;
74
+ readonly FailAfterChunks: Data.TaggedEnum.ConstructorFrom<{
75
+ readonly _tag: "FailAfterChunks";
76
+ readonly match?: WireFaultMatch | undefined;
77
+ readonly attempt?: number | undefined;
78
+ readonly chunks: number;
79
+ }, "_tag">;
80
+ readonly TruncateAfterChunks: Data.TaggedEnum.ConstructorFrom<{
81
+ readonly _tag: "TruncateAfterChunks";
82
+ readonly match?: WireFaultMatch | undefined;
83
+ readonly attempt?: number | undefined;
84
+ readonly chunks: number;
85
+ }, "_tag">;
86
+ readonly HoldAfterChunks: Data.TaggedEnum.ConstructorFrom<{
87
+ readonly _tag: "HoldAfterChunks";
88
+ readonly match?: WireFaultMatch | undefined;
89
+ readonly attempt?: number | undefined;
90
+ readonly chunks: number;
91
+ readonly release: Effect.Effect<void>;
92
+ }, "_tag">;
93
+ readonly $is: <Tag extends "StatusOnAttempt" | "FailAfterChunks" | "TruncateAfterChunks" | "HoldAfterChunks">(tag: Tag) => (u: unknown) => u is Extract<{
94
+ readonly _tag: "StatusOnAttempt";
95
+ readonly match?: WireFaultMatch | undefined;
96
+ readonly attempt: number;
97
+ readonly status: number;
98
+ readonly headers?: WireHeaders | undefined;
99
+ readonly body?: string | undefined;
100
+ }, {
101
+ readonly _tag: Tag;
102
+ }> | Extract<{
103
+ readonly _tag: "FailAfterChunks";
104
+ readonly match?: WireFaultMatch | undefined;
105
+ readonly attempt?: number | undefined;
106
+ readonly chunks: number;
107
+ }, {
108
+ readonly _tag: Tag;
109
+ }> | Extract<{
110
+ readonly _tag: "TruncateAfterChunks";
111
+ readonly match?: WireFaultMatch | undefined;
112
+ readonly attempt?: number | undefined;
113
+ readonly chunks: number;
114
+ }, {
115
+ readonly _tag: Tag;
116
+ }> | Extract<{
117
+ readonly _tag: "HoldAfterChunks";
118
+ readonly match?: WireFaultMatch | undefined;
119
+ readonly attempt?: number | undefined;
120
+ readonly chunks: number;
121
+ readonly release: Effect.Effect<void>;
122
+ }, {
123
+ readonly _tag: Tag;
124
+ }>;
125
+ readonly $match: {
126
+ <Cases extends {
127
+ readonly StatusOnAttempt: (args: {
128
+ readonly _tag: "StatusOnAttempt";
129
+ readonly match?: WireFaultMatch | undefined;
130
+ readonly attempt: number;
131
+ readonly status: number;
132
+ readonly headers?: WireHeaders | undefined;
133
+ readonly body?: string | undefined;
134
+ }) => any;
135
+ readonly FailAfterChunks: (args: {
136
+ readonly _tag: "FailAfterChunks";
137
+ readonly match?: WireFaultMatch | undefined;
138
+ readonly attempt?: number | undefined;
139
+ readonly chunks: number;
140
+ }) => any;
141
+ readonly TruncateAfterChunks: (args: {
142
+ readonly _tag: "TruncateAfterChunks";
143
+ readonly match?: WireFaultMatch | undefined;
144
+ readonly attempt?: number | undefined;
145
+ readonly chunks: number;
146
+ }) => any;
147
+ readonly HoldAfterChunks: (args: {
148
+ readonly _tag: "HoldAfterChunks";
149
+ readonly match?: WireFaultMatch | undefined;
150
+ readonly attempt?: number | undefined;
151
+ readonly chunks: number;
152
+ readonly release: Effect.Effect<void>;
153
+ }) => any;
154
+ }>(cases: Cases): (value: {
155
+ readonly _tag: "StatusOnAttempt";
156
+ readonly match?: WireFaultMatch | undefined;
157
+ readonly attempt: number;
158
+ readonly status: number;
159
+ readonly headers?: WireHeaders | undefined;
160
+ readonly body?: string | undefined;
161
+ } | {
162
+ readonly _tag: "FailAfterChunks";
163
+ readonly match?: WireFaultMatch | undefined;
164
+ readonly attempt?: number | undefined;
165
+ readonly chunks: number;
166
+ } | {
167
+ readonly _tag: "TruncateAfterChunks";
168
+ readonly match?: WireFaultMatch | undefined;
169
+ readonly attempt?: number | undefined;
170
+ readonly chunks: number;
171
+ } | {
172
+ readonly _tag: "HoldAfterChunks";
173
+ readonly match?: WireFaultMatch | undefined;
174
+ readonly attempt?: number | undefined;
175
+ readonly chunks: number;
176
+ readonly release: Effect.Effect<void>;
177
+ }) => import("effect/Unify").Unify<ReturnType<Cases["StatusOnAttempt" | "FailAfterChunks" | "TruncateAfterChunks" | "HoldAfterChunks"]>>;
178
+ <Cases extends {
179
+ readonly StatusOnAttempt: (args: {
180
+ readonly _tag: "StatusOnAttempt";
181
+ readonly match?: WireFaultMatch | undefined;
182
+ readonly attempt: number;
183
+ readonly status: number;
184
+ readonly headers?: WireHeaders | undefined;
185
+ readonly body?: string | undefined;
186
+ }) => any;
187
+ readonly FailAfterChunks: (args: {
188
+ readonly _tag: "FailAfterChunks";
189
+ readonly match?: WireFaultMatch | undefined;
190
+ readonly attempt?: number | undefined;
191
+ readonly chunks: number;
192
+ }) => any;
193
+ readonly TruncateAfterChunks: (args: {
194
+ readonly _tag: "TruncateAfterChunks";
195
+ readonly match?: WireFaultMatch | undefined;
196
+ readonly attempt?: number | undefined;
197
+ readonly chunks: number;
198
+ }) => any;
199
+ readonly HoldAfterChunks: (args: {
200
+ readonly _tag: "HoldAfterChunks";
201
+ readonly match?: WireFaultMatch | undefined;
202
+ readonly attempt?: number | undefined;
203
+ readonly chunks: number;
204
+ readonly release: Effect.Effect<void>;
205
+ }) => any;
206
+ }>(value: {
207
+ readonly _tag: "StatusOnAttempt";
208
+ readonly match?: WireFaultMatch | undefined;
209
+ readonly attempt: number;
210
+ readonly status: number;
211
+ readonly headers?: WireHeaders | undefined;
212
+ readonly body?: string | undefined;
213
+ } | {
214
+ readonly _tag: "FailAfterChunks";
215
+ readonly match?: WireFaultMatch | undefined;
216
+ readonly attempt?: number | undefined;
217
+ readonly chunks: number;
218
+ } | {
219
+ readonly _tag: "TruncateAfterChunks";
220
+ readonly match?: WireFaultMatch | undefined;
221
+ readonly attempt?: number | undefined;
222
+ readonly chunks: number;
223
+ } | {
224
+ readonly _tag: "HoldAfterChunks";
225
+ readonly match?: WireFaultMatch | undefined;
226
+ readonly attempt?: number | undefined;
227
+ readonly chunks: number;
228
+ readonly release: Effect.Effect<void>;
229
+ }, cases: Cases): import("effect/Unify").Unify<ReturnType<Cases["StatusOnAttempt" | "FailAfterChunks" | "TruncateAfterChunks" | "HoldAfterChunks"]>>;
230
+ };
231
+ };
232
+ type WireFaultTag = WireFault['_tag'];
233
+ declare const WireTransportFault_base: new <A extends Record<string, any> = {}>(args: import("effect/Types").VoidIfEmpty<{ readonly [P in keyof A as P extends "_tag" ? never : P]: A[P] }>) => Cause.YieldableError & {
234
+ readonly _tag: "WireTransportFault";
235
+ } & Readonly<A>;
236
+ /** Cause attached to a body stream failed by `FailAfterChunks`. */
237
+ declare class WireTransportFault extends WireTransportFault_base<{
238
+ readonly afterChunks: number;
239
+ }> {
240
+ get message(): string;
241
+ }
242
+ /**
243
+ * How a replayed request was answered: a recorded exchange, a response
244
+ * injected by a `StatusOnAttempt` fault, nothing (fail closed), or a matched
245
+ * exchange that could not be replayed (`invalid`: a chunk fault that cannot
246
+ * take effect, or undecodable base64). `invalid` requests fail with a typed
247
+ * `HttpClientError` and do not consume the exchange.
248
+ */
249
+ type ReplayLedgerMatch = {
250
+ readonly outcome: 'matched';
251
+ readonly fixtureId: string;
252
+ readonly exchangeIndex: number;
253
+ } | {
254
+ readonly outcome: 'injected';
255
+ } | {
256
+ readonly outcome: 'unmatched';
257
+ } | {
258
+ readonly outcome: 'invalid';
259
+ readonly fixtureId: string;
260
+ readonly exchangeIndex: number; /** Why the recording could not be replayed; never contains request or response data. */
261
+ readonly reason: string;
262
+ };
263
+ type ReplayLedgerEntry = {
264
+ readonly method: string; /** Normalized absolute URL. */
265
+ readonly url: string; /** Request headers with credential headers replaced by `<redacted>`. */
266
+ readonly headers: Readonly<Record<string, string>>;
267
+ readonly bodyText?: string; /** Parsed request body when `bodyText` is valid JSON. */
268
+ readonly bodyJson?: Schema.Json; /** 1-based attempt number for this method + URL. */
269
+ readonly attempt: number;
270
+ readonly match: ReplayLedgerMatch; /** Tag of the fault that shaped this response, if any (never set for a fault that did not apply). */
271
+ readonly fault?: WireFaultTag;
272
+ };
273
+ type ReplayExchangeRef = {
274
+ readonly fixtureId: string;
275
+ readonly exchangeIndex: number;
276
+ };
277
+ type ReplayLedgerApi = {
278
+ /** Every request seen by the replay client, in arrival order. */readonly entries: Effect.Effect<ReadonlyArray<ReplayLedgerEntry>>; /** Recorded exchanges not consumed yet, in fixture order. */
279
+ readonly remaining: Effect.Effect<ReadonlyArray<ReplayExchangeRef>>;
280
+ };
281
+ declare const ReplayLedger_base: Context.ServiceClass<ReplayLedger, "@yolk-sdk/conformance/ReplayLedger", ReplayLedgerApi>;
282
+ declare class ReplayLedger extends ReplayLedger_base {}
283
+ type ReplayHttpClientOptions = {
284
+ readonly faults?: ReadonlyArray<WireFault>;
285
+ };
286
+ declare const WireReplayInvalid_base: new <A extends Record<string, any> = {}>(args: import("effect/Types").VoidIfEmpty<{ readonly [P in keyof A as P extends "_tag" ? never : P]: A[P] }>) => Cause.YieldableError & {
287
+ readonly _tag: "WireReplayInvalid";
288
+ } & Readonly<A>;
289
+ /**
290
+ * Cause attached to the `HttpClientError` (reason `TransportError`) of a request
291
+ * whose recording could not be replayed. Retrying clients may retry past it;
292
+ * check the ledger for an `invalid` outcome.
293
+ */
294
+ declare class WireReplayInvalid extends WireReplayInvalid_base<{
295
+ readonly reason: string;
296
+ }> {
297
+ get message(): string;
298
+ }
299
+ /**
300
+ * Build a replay `HttpClient` and its ledger over the given fixtures. Each
301
+ * call has independent consumption, attempt, and ledger state.
302
+ */
303
+ declare const makeReplayHttpClient: (fixtures: ReadonlyArray<WireFixture>, options?: ReplayHttpClientOptions) => Effect.Effect<{
304
+ readonly client: HttpClient.HttpClient;
305
+ readonly ledger: ReplayLedgerApi;
306
+ }>;
307
+ declare const ReplayHttpClient: {
308
+ /**
309
+ * Layer providing a replay `HttpClient` and its `ReplayLedger`. Each layer
310
+ * build gets fresh consumption and ledger state.
311
+ */
312
+ readonly layer: (fixtures: ReadonlyArray<WireFixture>, options?: ReplayHttpClientOptions) => Layer.Layer<HttpClient.HttpClient | ReplayLedger>;
313
+ };
314
+ //#endregion
315
+ export { ReplayExchangeRef, ReplayHttpClient, ReplayHttpClientOptions, ReplayLedger, ReplayLedgerApi, ReplayLedgerEntry, ReplayLedgerMatch, WireFault, WireFaultMatch, WireFaultTag, WireReplayInvalid, WireTransportFault, makeReplayHttpClient };
316
+ //# sourceMappingURL=replay.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"replay.d.mts","names":[],"sources":["../src/replay.ts"],"mappings":"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"}