@zyraxon-ai/http-recorder 19.0.5
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/LICENSE +21 -0
- package/README.md +217 -0
- package/dist/cassette.d.ts +36 -0
- package/dist/cassette.d.ts.map +1 -0
- package/dist/cassette.js +105 -0
- package/dist/cassette.js.map +1 -0
- package/dist/effect.d.ts +11 -0
- package/dist/effect.d.ts.map +1 -0
- package/dist/effect.js +18 -0
- package/dist/effect.js.map +1 -0
- package/dist/index.d.ts +18 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +5 -0
- package/dist/index.js.map +1 -0
- package/dist/internal-effect.d.ts +19 -0
- package/dist/internal-effect.d.ts.map +1 -0
- package/dist/internal-effect.js +105 -0
- package/dist/internal-effect.js.map +1 -0
- package/dist/internal.d.ts +9 -0
- package/dist/internal.d.ts.map +1 -0
- package/dist/internal.js +9 -0
- package/dist/internal.js.map +1 -0
- package/dist/matching.d.ts +14 -0
- package/dist/matching.d.ts.map +1 -0
- package/dist/matching.js +92 -0
- package/dist/matching.js.map +1 -0
- package/dist/recorder.d.ts +13 -0
- package/dist/recorder.d.ts.map +1 -0
- package/dist/recorder.js +33 -0
- package/dist/recorder.js.map +1 -0
- package/dist/redaction.d.ts +14 -0
- package/dist/redaction.d.ts.map +1 -0
- package/dist/redaction.js +100 -0
- package/dist/redaction.js.map +1 -0
- package/dist/redactor.d.ts +30 -0
- package/dist/redactor.d.ts.map +1 -0
- package/dist/redactor.js +87 -0
- package/dist/redactor.js.map +1 -0
- package/dist/schema.d.ts +330 -0
- package/dist/schema.d.ts.map +1 -0
- package/dist/schema.js +53 -0
- package/dist/schema.js.map +1 -0
- package/dist/socket.d.ts +16 -0
- package/dist/socket.d.ts.map +1 -0
- package/dist/socket.js +194 -0
- package/dist/socket.js.map +1 -0
- package/dist/types.d.ts +101 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +2 -0
- package/dist/types.js.map +1 -0
- package/dist/websocket.d.ts +29 -0
- package/dist/websocket.d.ts.map +1 -0
- package/dist/websocket.js +101 -0
- package/dist/websocket.js.map +1 -0
- package/package.json +47 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 zyraxon
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,217 @@
|
|
|
1
|
+
# @ZYRAXON-ai/http-recorder
|
|
2
|
+
|
|
3
|
+
Record real Effect HTTP and WebSocket traffic once, then replay it from deterministic JSON cassettes.
|
|
4
|
+
|
|
5
|
+
Use it for provider integrations, retries, polling, multi-step flows, and any test where hand-written HTTP mocks hide too much of the real request shape.
|
|
6
|
+
|
|
7
|
+
> Public beta. The API depends on Effect 4 beta and may change with Effect's unstable transport modules.
|
|
8
|
+
|
|
9
|
+
## Install
|
|
10
|
+
|
|
11
|
+
```sh
|
|
12
|
+
bun add effect@4.0.0-beta.74
|
|
13
|
+
bun add -d @ZYRAXON-ai/http-recorder@beta @effect/vitest vitest
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
The package supports Node.js 22+ and Bun. It is not intended for browsers, workers, or Deno.
|
|
17
|
+
|
|
18
|
+
Effect `4.0.0-beta.74` has a known declaration error (`SchemaErrorTypeId` is missing). Until that upstream declaration is fixed, TypeScript consumers need:
|
|
19
|
+
|
|
20
|
+
```json
|
|
21
|
+
{
|
|
22
|
+
"compilerOptions": {
|
|
23
|
+
"skipLibCheck": true
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
## Quick Start
|
|
29
|
+
|
|
30
|
+
```ts
|
|
31
|
+
import { assert, describe, it } from "@effect/vitest"
|
|
32
|
+
import { Effect, Schema } from "effect"
|
|
33
|
+
import { HttpClient, HttpClientRequest } from "effect/unstable/http"
|
|
34
|
+
import { HttpRecorder } from "@ZYRAXON-ai/http-recorder"
|
|
35
|
+
|
|
36
|
+
const User = Schema.Struct({
|
|
37
|
+
id: Schema.Number,
|
|
38
|
+
name: Schema.String,
|
|
39
|
+
})
|
|
40
|
+
|
|
41
|
+
const getUser = Effect.gen(function* () {
|
|
42
|
+
const http = yield* HttpClient.HttpClient
|
|
43
|
+
const response = yield* http.execute(HttpClientRequest.get("https://jsonplaceholder.typicode.com/users/1"))
|
|
44
|
+
return yield* Schema.decodeUnknownEffect(User)(yield* response.json)
|
|
45
|
+
})
|
|
46
|
+
|
|
47
|
+
describe("getUser", () => {
|
|
48
|
+
it.effect("loads a user", () =>
|
|
49
|
+
Effect.gen(function* () {
|
|
50
|
+
const user = yield* getUser
|
|
51
|
+
|
|
52
|
+
assert.strictEqual(user.id, 1)
|
|
53
|
+
assert.strictEqual(user.name, "Leanne Graham")
|
|
54
|
+
}).pipe(Effect.provide(HttpRecorder.http("users/get-one"))),
|
|
55
|
+
)
|
|
56
|
+
})
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
Run the test with Vitest. The first local run calls the real API and records:
|
|
60
|
+
|
|
61
|
+
```sh
|
|
62
|
+
bunx vitest run users.test.ts
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
```text
|
|
66
|
+
test/fixtures/recordings/users/get-one.json
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
Later runs replay that cassette without contacting the upstream server. When `CI=true`, missing cassettes fail instead of recording.
|
|
70
|
+
|
|
71
|
+
```mermaid
|
|
72
|
+
flowchart TD
|
|
73
|
+
Run[Run test] --> Recorded{Cassette recorded?}
|
|
74
|
+
Recorded -->|Yes| Replay[Replay cassette]
|
|
75
|
+
Recorded -->|No, local| Record[Call service and record cassette]
|
|
76
|
+
Recorded -->|No, CI| Fail[Fail: cassette missing]
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
Application code does not need to know whether a response is live or replayed.
|
|
80
|
+
|
|
81
|
+
## API
|
|
82
|
+
|
|
83
|
+
```ts
|
|
84
|
+
HttpRecorder.http(name, options?)
|
|
85
|
+
HttpRecorder.socket(name, options?)
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
That is the complete public API. `http` provides a fetch-backed recorded `HttpClient`. `socket` decorates a standard Effect `Socket.Socket` supplied beneath it.
|
|
89
|
+
|
|
90
|
+
## WebSockets
|
|
91
|
+
|
|
92
|
+
WebSocket cassettes preserve one ordered transcript of client and server text or binary frames. Replay follows that chronology: server frames are released until the next recorded client frame, then replay waits for the application to send the matching frame before continuing.
|
|
93
|
+
|
|
94
|
+
```ts
|
|
95
|
+
import { assert, it } from "@effect/vitest"
|
|
96
|
+
import { NodeSocket } from "@effect/platform-node"
|
|
97
|
+
import { Effect, Layer } from "effect"
|
|
98
|
+
import { Socket } from "effect/unstable/socket"
|
|
99
|
+
import { HttpRecorder } from "@ZYRAXON-ai/http-recorder"
|
|
100
|
+
|
|
101
|
+
const echo = Effect.gen(function* () {
|
|
102
|
+
const socket = yield* Socket.Socket
|
|
103
|
+
const write = yield* socket.writer
|
|
104
|
+
|
|
105
|
+
yield* socket.runString(
|
|
106
|
+
(message) =>
|
|
107
|
+
Effect.gen(function* () {
|
|
108
|
+
assert.strictEqual(message, "hello")
|
|
109
|
+
yield* write(new Socket.CloseEvent(1000))
|
|
110
|
+
}),
|
|
111
|
+
{ onOpen: write("hello") },
|
|
112
|
+
)
|
|
113
|
+
})
|
|
114
|
+
|
|
115
|
+
const recordedSocket = HttpRecorder.socket("echo/hello").pipe(
|
|
116
|
+
Layer.provide(
|
|
117
|
+
NodeSocket.layerWebSocket("wss://ws.postman-echo.com/raw", {
|
|
118
|
+
closeCodeIsError: (code) => code !== 1000,
|
|
119
|
+
}),
|
|
120
|
+
),
|
|
121
|
+
)
|
|
122
|
+
|
|
123
|
+
it.effect("exchanges WebSocket frames", () => echo.pipe(Effect.provide(recordedSocket)))
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
The application owns the WebSocket URL and protocols through normal Effect layer wiring. The recorder wraps that socket without duplicating its URL in recorder configuration. Provide separate socket layers for separate endpoints or concurrent connections.
|
|
127
|
+
|
|
128
|
+
Text frames use the same JSON-field and body redaction as HTTP bodies. Binary frames are stored losslessly as base64. Client and server frame kinds must match during replay.
|
|
129
|
+
|
|
130
|
+
## Refresh A Cassette
|
|
131
|
+
|
|
132
|
+
Delete exactly the recordings you want to replace, then rerun their tests:
|
|
133
|
+
|
|
134
|
+
```sh
|
|
135
|
+
rm test/fixtures/recordings/users/get-one.json
|
|
136
|
+
bun run test users.test.ts
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
There is intentionally no public overwrite mode. Deletion makes the set of recordings being refreshed visible and reviewable.
|
|
140
|
+
|
|
141
|
+
## Redaction
|
|
142
|
+
|
|
143
|
+
Secure defaults remove most headers and redact common credentials in headers, URLs, and JSON bodies. Extend those defaults at layer construction:
|
|
144
|
+
|
|
145
|
+
```ts
|
|
146
|
+
HttpRecorder.http("anthropic/messages", {
|
|
147
|
+
redact: {
|
|
148
|
+
headers: ["x-project-token"],
|
|
149
|
+
allowRequestHeaders: ["anthropic-version"],
|
|
150
|
+
queryParameters: ["session-id"],
|
|
151
|
+
jsonFields: ["user_id"],
|
|
152
|
+
url: (url) => url.replace(/\/accounts\/[^/]+/, "/accounts/{account}"),
|
|
153
|
+
body: (body) => body.replaceAll(/usr_[a-z0-9]+/g, "usr_redacted"),
|
|
154
|
+
},
|
|
155
|
+
})
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
| Option | Purpose |
|
|
159
|
+
| ---------------------- | -------------------------------------------------------------------- |
|
|
160
|
+
| `headers` | Add sensitive header names. They are retained as `[REDACTED]`. |
|
|
161
|
+
| `allowRequestHeaders` | Preserve additional non-sensitive request headers for matching. |
|
|
162
|
+
| `allowResponseHeaders` | Preserve additional non-sensitive response headers for replay. |
|
|
163
|
+
| `queryParameters` | Add sensitive URL query parameter names. |
|
|
164
|
+
| `jsonFields` | Recursively redact matching JSON keys in requests and responses. |
|
|
165
|
+
| `url` | Stabilize a URL after built-in redaction. |
|
|
166
|
+
| `body` | Stabilize request and response bodies after built-in JSON redaction. |
|
|
167
|
+
|
|
168
|
+
Before writing, the recorder scans the complete cassette for common credential formats and values from credential-like environment variables. Unsafe cassettes fail without replacing an existing recording.
|
|
169
|
+
|
|
170
|
+
Redaction is defense in depth, not a substitute for review. Inspect cassette diffs before committing them.
|
|
171
|
+
|
|
172
|
+
## Matching And Ordering
|
|
173
|
+
|
|
174
|
+
A cassette contains an ordered sequence of interactions. The first runtime request is checked against the first recorded request, the second against the second, and so on.
|
|
175
|
+
|
|
176
|
+
This strict ordering correctly models repeated identical requests whose responses change, including retries, polling, and cache tests. JSON object keys are canonicalized before matching.
|
|
177
|
+
|
|
178
|
+
Concurrent requests are recorded in request-start order even when their responses complete out of order.
|
|
179
|
+
|
|
180
|
+
Supply a custom equivalence rule when a request contains intentionally volatile data:
|
|
181
|
+
|
|
182
|
+
```ts
|
|
183
|
+
HttpRecorder.http("events/create", {
|
|
184
|
+
match: (incoming, recorded) =>
|
|
185
|
+
incoming.method === recorded.method && new URL(incoming.url).pathname === new URL(recorded.url).pathname,
|
|
186
|
+
})
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
## Configuration
|
|
190
|
+
|
|
191
|
+
```ts
|
|
192
|
+
interface RecorderOptions {
|
|
193
|
+
readonly directory?: string
|
|
194
|
+
readonly metadata?: Record<string, unknown>
|
|
195
|
+
readonly redact?: RedactOptions
|
|
196
|
+
readonly match?: RequestMatcher
|
|
197
|
+
}
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
`directory` defaults to `<cwd>/test/fixtures/recordings`.
|
|
201
|
+
|
|
202
|
+
## Cassettes
|
|
203
|
+
|
|
204
|
+
Cassettes are readable JSON files intended to be committed with your tests. HTTP interactions are stored in request order. WebSocket cassettes preserve the observed order of client and server frames. Text stays readable; binary bodies and frames are stored losslessly as base64.
|
|
205
|
+
|
|
206
|
+
## Current Limits
|
|
207
|
+
|
|
208
|
+
- Responses are buffered while recording and replaying, so this beta is not suitable for tests that assert streaming timing, cancellation, or backpressure.
|
|
209
|
+
- WebSocket replay preserves frame chronology and content, not real network timing or backpressure.
|
|
210
|
+
- WebSocket V1 cassettes do not reproduce terminal close codes, close reasons, or transport failures. Failed and interrupted live runs are not recorded.
|
|
211
|
+
- WebSocket transcripts are retained in memory until the connection finishes; avoid using this beta for unbounded sessions.
|
|
212
|
+
- The package currently requires the exact Effect beta listed above.
|
|
213
|
+
- Cassette format version `1` has no migration tooling yet.
|
|
214
|
+
|
|
215
|
+
## License
|
|
216
|
+
|
|
217
|
+
MIT
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { Context, Effect, FileSystem, Layer, Schema } from "effect";
|
|
2
|
+
import { type CassetteMetadata, type Interaction } from "./schema.js";
|
|
3
|
+
declare const CassetteNotFoundError_base: Schema.Class<CassetteNotFoundError, Schema.TaggedStruct<"CassetteNotFoundError", {
|
|
4
|
+
readonly cassetteName: Schema.String;
|
|
5
|
+
}>, import("effect/Cause").YieldableError>;
|
|
6
|
+
export declare class CassetteNotFoundError extends CassetteNotFoundError_base {
|
|
7
|
+
get message(): string;
|
|
8
|
+
}
|
|
9
|
+
declare const UnsafeCassetteError_base: Schema.Class<UnsafeCassetteError, Schema.TaggedStruct<"UnsafeCassetteError", {
|
|
10
|
+
readonly cassetteName: Schema.String;
|
|
11
|
+
readonly findings: Schema.$Array<Schema.Struct<{
|
|
12
|
+
readonly path: Schema.String;
|
|
13
|
+
readonly reason: Schema.String;
|
|
14
|
+
}>>;
|
|
15
|
+
}>, import("effect/Cause").YieldableError>;
|
|
16
|
+
export declare class UnsafeCassetteError extends UnsafeCassetteError_base {
|
|
17
|
+
get message(): string;
|
|
18
|
+
}
|
|
19
|
+
export interface Interface {
|
|
20
|
+
readonly read: (name: string) => Effect.Effect<ReadonlyArray<Interaction>, CassetteNotFoundError>;
|
|
21
|
+
readonly append: (name: string, interaction: Interaction, metadata?: CassetteMetadata) => Effect.Effect<void, UnsafeCassetteError>;
|
|
22
|
+
readonly exists: (name: string) => Effect.Effect<boolean>;
|
|
23
|
+
readonly list: () => Effect.Effect<ReadonlyArray<string>>;
|
|
24
|
+
}
|
|
25
|
+
declare const Service_base: Context.ServiceClass<Service, "@zyraxon-ai/http-recorder/Cassette", Interface>;
|
|
26
|
+
export declare class Service extends Service_base {
|
|
27
|
+
}
|
|
28
|
+
export declare const hasCassetteSync: (name: string, options?: {
|
|
29
|
+
readonly directory?: string;
|
|
30
|
+
}) => boolean;
|
|
31
|
+
export declare const fileSystem: (options?: {
|
|
32
|
+
readonly directory?: string;
|
|
33
|
+
}) => Layer.Layer<Service, never, FileSystem.FileSystem>;
|
|
34
|
+
export declare const memory: (initial?: Record<string, ReadonlyArray<Interaction>>) => Layer.Layer<Service>;
|
|
35
|
+
export {};
|
|
36
|
+
//# sourceMappingURL=cassette.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cassette.d.ts","sourceRoot":"","sources":["../src/cassette.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,EAAE,UAAU,EAAE,KAAK,EAAE,MAAM,EAAa,MAAM,QAAQ,CAAA;AAI9E,OAAO,EAAiD,KAAK,gBAAgB,EAAE,KAAK,WAAW,EAAE,MAAM,aAAa,CAAA;;;;AAIpH,qBAAa,qBAAsB,SAAQ,0BAEzC;IACA,IAAa,OAAO,WAEnB;CACF;;;;;;;;AAED,qBAAa,mBAAoB,SAAQ,wBAGvC;IACA,IAAa,OAAO,WAInB;CACF;AAED,MAAM,WAAW,SAAS;IACxB,QAAQ,CAAC,IAAI,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,MAAM,CAAC,MAAM,CAAC,aAAa,CAAC,WAAW,CAAC,EAAE,qBAAqB,CAAC,CAAA;IACjG,QAAQ,CAAC,MAAM,EAAE,CACf,IAAI,EAAE,MAAM,EACZ,WAAW,EAAE,WAAW,EACxB,QAAQ,CAAC,EAAE,gBAAgB,KACxB,MAAM,CAAC,MAAM,CAAC,IAAI,EAAE,mBAAmB,CAAC,CAAA;IAC7C,QAAQ,CAAC,MAAM,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,MAAM,CAAC,MAAM,CAAC,OAAO,CAAC,CAAA;IACzD,QAAQ,CAAC,IAAI,EAAE,MAAM,MAAM,CAAC,MAAM,CAAC,aAAa,CAAC,MAAM,CAAC,CAAC,CAAA;CAC1D;;AAED,qBAAa,OAAQ,SAAQ,YAA2E;CAAG;AAa3G,eAAO,MAAM,eAAe,GAAI,MAAM,MAAM,EAAE,UAAS;IAAE,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,CAAA;CAAO,YACX,CAAA;AAmBhF,eAAO,MAAM,UAAU,GACrB,UAAS;IAAE,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,CAAA;CAAO,KAC5C,KAAK,CAAC,KAAK,CAAC,OAAO,EAAE,KAAK,EAAE,UAAU,CAAC,UAAU,CAuEjD,CAAA;AAEH,eAAO,MAAM,MAAM,GAAI,UAAS,MAAM,CAAC,MAAM,EAAE,aAAa,CAAC,WAAW,CAAC,CAAM,KAAG,KAAK,CAAC,KAAK,CAAC,OAAO,CAgCjG,CAAA"}
|
package/dist/cassette.js
ADDED
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
import { Context, Effect, FileSystem, Layer, Schema, Semaphore } from "effect";
|
|
2
|
+
import * as fs from "node:fs";
|
|
3
|
+
import * as path from "node:path";
|
|
4
|
+
import { secretFindings, SecretFindingSchema } from "./redaction.js";
|
|
5
|
+
import { CassetteSchema, encodeCassette } from "./schema.js";
|
|
6
|
+
const DEFAULT_RECORDINGS_DIR = path.resolve(process.cwd(), "test", "fixtures", "recordings");
|
|
7
|
+
export class CassetteNotFoundError extends Schema.TaggedErrorClass()("CassetteNotFoundError", {
|
|
8
|
+
cassetteName: Schema.String,
|
|
9
|
+
}) {
|
|
10
|
+
get message() {
|
|
11
|
+
return `Cassette "${this.cassetteName}" not found`;
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
export class UnsafeCassetteError extends Schema.TaggedErrorClass()("UnsafeCassetteError", {
|
|
15
|
+
cassetteName: Schema.String,
|
|
16
|
+
findings: Schema.Array(SecretFindingSchema),
|
|
17
|
+
}) {
|
|
18
|
+
get message() {
|
|
19
|
+
return `Refusing to write cassette "${this.cassetteName}" because it contains possible secrets: ${this.findings
|
|
20
|
+
.map((finding) => `${finding.path} (${finding.reason})`)
|
|
21
|
+
.join(", ")}`;
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
export class Service extends Context.Service()("@zyraxon-ai/http-recorder/Cassette") {
|
|
25
|
+
}
|
|
26
|
+
const cassettePath = (directory, name) => {
|
|
27
|
+
if (!name || path.isAbsolute(name) || path.win32.isAbsolute(name) || name.split(/[\\/]/).includes(".."))
|
|
28
|
+
throw new Error(`Invalid cassette name "${name}"`);
|
|
29
|
+
const root = path.resolve(directory);
|
|
30
|
+
const target = path.resolve(root, `${name}.json`);
|
|
31
|
+
const relative = path.relative(root, target);
|
|
32
|
+
if (!relative || relative.startsWith("..") || path.isAbsolute(relative))
|
|
33
|
+
throw new Error(`Invalid cassette name "${name}"`);
|
|
34
|
+
return target;
|
|
35
|
+
};
|
|
36
|
+
export const hasCassetteSync = (name, options = {}) => fs.existsSync(cassettePath(options.directory ?? DEFAULT_RECORDINGS_DIR, name));
|
|
37
|
+
const buildCassette = (name, interactions, metadata) => ({
|
|
38
|
+
version: 1,
|
|
39
|
+
metadata: { name, recordedAt: new Date().toISOString(), ...metadata },
|
|
40
|
+
interactions,
|
|
41
|
+
});
|
|
42
|
+
const formatCassette = (cassette) => `${JSON.stringify(encodeCassette(cassette), null, 2)}\n`;
|
|
43
|
+
const parseCassette = Schema.decodeUnknownSync(Schema.fromJsonString(CassetteSchema));
|
|
44
|
+
const failIfUnsafe = (name, findings) => findings.length === 0 ? Effect.void : Effect.fail(new UnsafeCassetteError({ cassetteName: name, findings }));
|
|
45
|
+
export const fileSystem = (options = {}) => Layer.effect(Service, Effect.gen(function* () {
|
|
46
|
+
const fs = yield* FileSystem.FileSystem;
|
|
47
|
+
const directory = options.directory ?? DEFAULT_RECORDINGS_DIR;
|
|
48
|
+
const recorded = new Map();
|
|
49
|
+
const appendLock = yield* Semaphore.make(1);
|
|
50
|
+
const pathFor = (name) => cassettePath(directory, name);
|
|
51
|
+
const walk = (current) => Effect.gen(function* () {
|
|
52
|
+
const entries = yield* fs.readDirectory(current).pipe(Effect.catch(() => Effect.succeed([])));
|
|
53
|
+
const nested = yield* Effect.forEach(entries, (entry) => {
|
|
54
|
+
const full = path.join(current, entry);
|
|
55
|
+
return fs.stat(full).pipe(Effect.flatMap((stat) => (stat.type === "Directory" ? walk(full) : Effect.succeed([full]))), Effect.catch(() => Effect.succeed([])));
|
|
56
|
+
});
|
|
57
|
+
return nested.flat();
|
|
58
|
+
});
|
|
59
|
+
return Service.of({
|
|
60
|
+
read: (name) => fs.readFileString(pathFor(name)).pipe(Effect.map((raw) => parseCassette(raw).interactions), Effect.catch(() => Effect.fail(new CassetteNotFoundError({ cassetteName: name })))),
|
|
61
|
+
append: (name, interaction, metadata) => appendLock.withPermit(Effect.gen(function* () {
|
|
62
|
+
const entry = recorded.get(name) ?? { interactions: [], findings: [] };
|
|
63
|
+
const interactions = [...entry.interactions, interaction];
|
|
64
|
+
const interactionFindings = [...entry.findings, ...secretFindings(interaction)];
|
|
65
|
+
const cassette = buildCassette(name, interactions, metadata);
|
|
66
|
+
const findings = [...interactionFindings, ...secretFindings(cassette.metadata ?? {})];
|
|
67
|
+
yield* failIfUnsafe(name, findings);
|
|
68
|
+
const target = pathFor(name);
|
|
69
|
+
yield* fs.makeDirectory(path.dirname(target), { recursive: true }).pipe(Effect.orDie);
|
|
70
|
+
const temporary = `${target}.${crypto.randomUUID()}.tmp`;
|
|
71
|
+
yield* fs.writeFileString(temporary, formatCassette(cassette)).pipe(Effect.flatMap(() => fs.rename(temporary, target)), Effect.ensuring(fs.remove(temporary, { force: true }).pipe(Effect.catch(() => Effect.void))), Effect.orDie);
|
|
72
|
+
recorded.set(name, { interactions, findings: interactionFindings });
|
|
73
|
+
})),
|
|
74
|
+
exists: (name) => fs.access(pathFor(name)).pipe(Effect.as(true), Effect.catch(() => Effect.succeed(false))),
|
|
75
|
+
list: () => walk(directory).pipe(Effect.map((files) => files
|
|
76
|
+
.filter((file) => file.endsWith(".json"))
|
|
77
|
+
.map((file) => path
|
|
78
|
+
.relative(directory, file)
|
|
79
|
+
.replace(/\\/g, "/")
|
|
80
|
+
.replace(/\.json$/, ""))
|
|
81
|
+
.toSorted((a, b) => a.localeCompare(b)))),
|
|
82
|
+
});
|
|
83
|
+
}));
|
|
84
|
+
export const memory = (initial = {}) => Layer.sync(Service, () => {
|
|
85
|
+
const stored = new Map(Object.entries(initial).map(([name, interactions]) => [name, [...interactions]]));
|
|
86
|
+
const accumulatedFindings = new Map();
|
|
87
|
+
const appendLock = Semaphore.makeUnsafe(1);
|
|
88
|
+
return Service.of({
|
|
89
|
+
read: (name) => stored.has(name)
|
|
90
|
+
? Effect.succeed(stored.get(name) ?? [])
|
|
91
|
+
: Effect.fail(new CassetteNotFoundError({ cassetteName: name })),
|
|
92
|
+
append: (name, interaction, metadata) => appendLock.withPermit(Effect.suspend(() => {
|
|
93
|
+
const interactions = [...(stored.get(name) ?? []), interaction];
|
|
94
|
+
const findings = [...(accumulatedFindings.get(name) ?? []), ...secretFindings(interaction)];
|
|
95
|
+
const allFindings = metadata ? [...findings, ...secretFindings({ name, ...metadata })] : findings;
|
|
96
|
+
return failIfUnsafe(name, allFindings).pipe(Effect.tap(() => Effect.sync(() => {
|
|
97
|
+
stored.set(name, interactions);
|
|
98
|
+
accumulatedFindings.set(name, findings);
|
|
99
|
+
})));
|
|
100
|
+
})),
|
|
101
|
+
exists: (name) => Effect.sync(() => stored.has(name)),
|
|
102
|
+
list: () => Effect.sync(() => Array.from(stored.keys()).toSorted()),
|
|
103
|
+
});
|
|
104
|
+
});
|
|
105
|
+
//# sourceMappingURL=cassette.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cassette.js","sourceRoot":"","sources":["../src/cassette.ts"],"names":[],"mappings":"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"}
|
package/dist/effect.d.ts
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import * as Layer from "effect/Layer";
|
|
2
|
+
import type * as HttpClient from "effect/unstable/http/HttpClient";
|
|
3
|
+
import type { RecorderOptions } from "./types.js";
|
|
4
|
+
/**
|
|
5
|
+
* Provides a fetch-backed `HttpClient` with cassette recording and replay.
|
|
6
|
+
*
|
|
7
|
+
* Locally, a missing cassette is recorded from the real service. Existing
|
|
8
|
+
* cassettes are replayed, and `CI=true` makes a missing cassette fail.
|
|
9
|
+
*/
|
|
10
|
+
export declare const http: (name: string, options?: RecorderOptions) => Layer.Layer<HttpClient.HttpClient>;
|
|
11
|
+
//# sourceMappingURL=effect.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"effect.d.ts","sourceRoot":"","sources":["../src/effect.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,KAAK,MAAM,cAAc,CAAA;AAErC,OAAO,KAAK,KAAK,UAAU,MAAM,iCAAiC,CAAA;AAIlE,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,YAAY,CAAA;AAEjD;;;;;GAKG;AACH,eAAO,MAAM,IAAI,GAAI,MAAM,MAAM,EAAE,UAAS,eAAoB,KAAG,KAAK,CAAC,KAAK,CAAC,UAAU,CAAC,UAAU,CASjG,CAAA"}
|
package/dist/effect.js
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { NodeFileSystem } from "@effect/platform-node";
|
|
2
|
+
import * as Layer from "effect/Layer";
|
|
3
|
+
import { FetchHttpClient } from "effect/unstable/http";
|
|
4
|
+
import * as CassetteService from "./cassette.js";
|
|
5
|
+
import { recordingLayer } from "./internal-effect.js";
|
|
6
|
+
import { make } from "./redactor.js";
|
|
7
|
+
/**
|
|
8
|
+
* Provides a fetch-backed `HttpClient` with cassette recording and replay.
|
|
9
|
+
*
|
|
10
|
+
* Locally, a missing cassette is recorded from the real service. Existing
|
|
11
|
+
* cassettes are replayed, and `CI=true` makes a missing cassette fail.
|
|
12
|
+
*/
|
|
13
|
+
export const http = (name, options = {}) => recordingLayer(name, {
|
|
14
|
+
metadata: options.metadata,
|
|
15
|
+
redactor: make(options.redact),
|
|
16
|
+
match: options.match,
|
|
17
|
+
}).pipe(Layer.provide(CassetteService.fileSystem({ directory: options.directory })), Layer.provide(FetchHttpClient.layer), Layer.provide(NodeFileSystem.layer));
|
|
18
|
+
//# sourceMappingURL=effect.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"effect.js","sourceRoot":"","sources":["../src/effect.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAA;AACtD,OAAO,KAAK,KAAK,MAAM,cAAc,CAAA;AACrC,OAAO,EAAE,eAAe,EAAE,MAAM,sBAAsB,CAAA;AAEtD,OAAO,KAAK,eAAe,MAAM,eAAe,CAAA;AAChD,OAAO,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAA;AACrD,OAAO,EAAE,IAAI,EAAE,MAAM,eAAe,CAAA;AAGpC;;;;;GAKG;AACH,MAAM,CAAC,MAAM,IAAI,GAAG,CAAC,IAAY,EAAE,UAA2B,EAAE,EAAsC,EAAE,CACtG,cAAc,CAAC,IAAI,EAAE;IACnB,QAAQ,EAAE,OAAO,CAAC,QAAQ;IAC1B,QAAQ,EAAE,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC;IAC9B,KAAK,EAAE,OAAO,CAAC,KAAK;CACrB,CAAC,CAAC,IAAI,CACL,KAAK,CAAC,OAAO,CAAC,eAAe,CAAC,UAAU,CAAC,EAAE,SAAS,EAAE,OAAO,CAAC,SAAS,EAAE,CAAC,CAAC,EAC3E,KAAK,CAAC,OAAO,CAAC,eAAe,CAAC,KAAK,CAAC,EACpC,KAAK,CAAC,OAAO,CAAC,cAAc,CAAC,KAAK,CAAC,CACpC,CAAA"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/** HTTP and WebSocket cassette recording. */
|
|
2
|
+
export declare const HttpRecorder: {
|
|
3
|
+
readonly http: (name: string, options?: import("./types.js").RecorderOptions) => import("effect/Layer").Layer<import("effect/unstable/http/HttpClient").HttpClient>;
|
|
4
|
+
readonly socket: (name: string, options?: import("./types.js").RecorderOptions) => import("effect/Layer").Layer<import("effect/unstable/socket/Socket").Socket, never, import("effect/unstable/socket/Socket").Socket>;
|
|
5
|
+
};
|
|
6
|
+
export declare namespace HttpRecorder {
|
|
7
|
+
/** Additional JSON metadata stored with a cassette. */
|
|
8
|
+
type CassetteMetadata = import("./types.js").CassetteMetadata;
|
|
9
|
+
/** Recorder configuration. */
|
|
10
|
+
type RecorderOptions = import("./types.js").RecorderOptions;
|
|
11
|
+
/** Additive redaction and header-preservation policy. */
|
|
12
|
+
type RedactOptions = import("./types.js").RedactOptions;
|
|
13
|
+
/** Returns whether an incoming HTTP request matches a recorded request. */
|
|
14
|
+
type RequestMatcher = import("./types.js").RequestMatcher;
|
|
15
|
+
/** The normalized HTTP request representation used for matching. */
|
|
16
|
+
type RequestSnapshot = import("./types.js").RequestSnapshot;
|
|
17
|
+
}
|
|
18
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAGA,6CAA6C;AAC7C,eAAO,MAAM,YAAY;;;CAA4B,CAAA;AAErD,yBAAiB,YAAY,CAAC;IAC5B,uDAAuD;IACvD,KAAY,gBAAgB,GAAG,OAAO,YAAY,EAAE,gBAAgB,CAAA;IACpE,8BAA8B;IAC9B,KAAY,eAAe,GAAG,OAAO,YAAY,EAAE,eAAe,CAAA;IAClE,yDAAyD;IACzD,KAAY,aAAa,GAAG,OAAO,YAAY,EAAE,aAAa,CAAA;IAC9D,2EAA2E;IAC3E,KAAY,cAAc,GAAG,OAAO,YAAY,EAAE,cAAc,CAAA;IAChE,oEAAoE;IACpE,KAAY,eAAe,GAAG,OAAO,YAAY,EAAE,eAAe,CAAA;CACnE"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,MAAM,aAAa,CAAA;AAClC,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAA;AAEpC,6CAA6C;AAC7C,MAAM,CAAC,MAAM,YAAY,GAAG,EAAE,IAAI,EAAE,MAAM,EAAW,CAAA"}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { Layer } from "effect";
|
|
2
|
+
import { HttpClient, HttpClientRequest } from "effect/unstable/http";
|
|
3
|
+
import * as CassetteService from "./cassette.js";
|
|
4
|
+
import { defaultMatcher } from "./matching.js";
|
|
5
|
+
import { type Redactor } from "./redactor.js";
|
|
6
|
+
import type { CassetteMetadata, RequestMatcher } from "./types.js";
|
|
7
|
+
export { defaultMatcher };
|
|
8
|
+
export type RecordReplayMode = "auto" | "record" | "replay" | "passthrough";
|
|
9
|
+
export interface RecordReplayOptions {
|
|
10
|
+
readonly mode?: RecordReplayMode;
|
|
11
|
+
readonly directory?: string;
|
|
12
|
+
readonly metadata?: CassetteMetadata;
|
|
13
|
+
readonly redactor?: Redactor;
|
|
14
|
+
readonly match?: RequestMatcher;
|
|
15
|
+
}
|
|
16
|
+
export declare const redactedErrorRequest: (request: HttpClientRequest.HttpClientRequest) => HttpClientRequest.HttpClientRequest;
|
|
17
|
+
export declare const recordingLayer: (name: string, options?: Omit<RecordReplayOptions, "directory">) => Layer.Layer<HttpClient.HttpClient, never, HttpClient.HttpClient | CassetteService.Service>;
|
|
18
|
+
export declare const cassetteLayer: (name: string, options?: RecordReplayOptions) => Layer.Layer<HttpClient.HttpClient>;
|
|
19
|
+
//# sourceMappingURL=internal-effect.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"internal-effect.d.ts","sourceRoot":"","sources":["../src/internal-effect.ts"],"names":[],"mappings":"AACA,OAAO,EAAoB,KAAK,EAAe,MAAM,QAAQ,CAAA;AAC7D,OAAO,EAIL,UAAU,EAEV,iBAAiB,EAGlB,MAAM,sBAAsB,CAAA;AAC7B,OAAO,KAAK,eAAe,MAAM,eAAe,CAAA;AAChD,OAAO,EAAE,cAAc,EAAoB,MAAM,eAAe,CAAA;AAEhE,OAAO,EAAQ,KAAK,QAAQ,EAAE,MAAM,eAAe,CAAA;AAGnD,OAAO,KAAK,EAAE,gBAAgB,EAAmB,cAAc,EAAoB,MAAM,YAAY,CAAA;AAErG,OAAO,EAAE,cAAc,EAAE,CAAA;AAEzB,MAAM,MAAM,gBAAgB,GAAG,MAAM,GAAG,QAAQ,GAAG,QAAQ,GAAG,aAAa,CAAA;AAE3E,MAAM,WAAW,mBAAmB;IAClC,QAAQ,CAAC,IAAI,CAAC,EAAE,gBAAgB,CAAA;IAChC,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,CAAA;IAC3B,QAAQ,CAAC,QAAQ,CAAC,EAAE,gBAAgB,CAAA;IACpC,QAAQ,CAAC,QAAQ,CAAC,EAAE,QAAQ,CAAA;IAC5B,QAAQ,CAAC,KAAK,CAAC,EAAE,cAAc,CAAA;CAChC;AA+CD,eAAO,MAAM,oBAAoB,GAAI,SAAS,iBAAiB,CAAC,iBAAiB,wCAQ9E,CAAA;AAOH,eAAO,MAAM,cAAc,GACzB,MAAM,MAAM,EACZ,UAAS,IAAI,CAAC,mBAAmB,EAAE,WAAW,CAAM,KACnD,KAAK,CAAC,KAAK,CAAC,UAAU,CAAC,UAAU,EAAE,KAAK,EAAE,UAAU,CAAC,UAAU,GAAG,eAAe,CAAC,OAAO,CAsFzF,CAAA;AAEH,eAAO,MAAM,aAAa,GAAI,MAAM,MAAM,EAAE,UAAS,mBAAwB,KAAG,KAAK,CAAC,KAAK,CAAC,UAAU,CAAC,UAAU,CAK9G,CAAA"}
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
import { NodeFileSystem } from "@effect/platform-node";
|
|
2
|
+
import { Deferred, Effect, Layer, Option, Ref } from "effect";
|
|
3
|
+
import { FetchHttpClient, Headers, HttpBody, HttpClient, HttpClientError, HttpClientRequest, HttpClientResponse, UrlParams, } from "effect/unstable/http";
|
|
4
|
+
import * as CassetteService from "./cassette.js";
|
|
5
|
+
import { defaultMatcher, selectSequential } from "./matching.js";
|
|
6
|
+
import { makeReplayState, resolveAutoMode } from "./recorder.js";
|
|
7
|
+
import { make } from "./redactor.js";
|
|
8
|
+
import { redactUrl } from "./redaction.js";
|
|
9
|
+
import { httpInteractions } from "./schema.js";
|
|
10
|
+
export { defaultMatcher };
|
|
11
|
+
const TEXT_CONTENT_TYPES = new Set([
|
|
12
|
+
"application/graphql",
|
|
13
|
+
"application/javascript",
|
|
14
|
+
"application/json",
|
|
15
|
+
"application/sql",
|
|
16
|
+
"application/x-www-form-urlencoded",
|
|
17
|
+
"application/xml",
|
|
18
|
+
"application/yaml",
|
|
19
|
+
"image/svg+xml",
|
|
20
|
+
]);
|
|
21
|
+
const isTextContentType = (contentType) => {
|
|
22
|
+
const mediaType = contentType?.split(";", 1)[0]?.trim().toLowerCase();
|
|
23
|
+
if (!mediaType)
|
|
24
|
+
return false;
|
|
25
|
+
return (mediaType.startsWith("text/") ||
|
|
26
|
+
mediaType.endsWith("+json") ||
|
|
27
|
+
mediaType.endsWith("+xml") ||
|
|
28
|
+
TEXT_CONTENT_TYPES.has(mediaType));
|
|
29
|
+
};
|
|
30
|
+
const captureResponseBody = (response, contentType) => response.arrayBuffer.pipe(Effect.map((bytes) => isTextContentType(contentType)
|
|
31
|
+
? { body: new TextDecoder().decode(bytes) }
|
|
32
|
+
: { body: Buffer.from(bytes).toString("base64"), bodyEncoding: "base64" }));
|
|
33
|
+
const decodeResponseBody = (snapshot) => snapshot.bodyEncoding === "base64" ? Buffer.from(snapshot.body, "base64") : snapshot.body;
|
|
34
|
+
const responseFromSnapshot = (request, snapshot) => HttpClientResponse.fromWeb(request, new Response(request.method === "HEAD" || snapshot.status === 204 || snapshot.status === 205 || snapshot.status === 304
|
|
35
|
+
? null
|
|
36
|
+
: decodeResponseBody(snapshot), snapshot));
|
|
37
|
+
export const redactedErrorRequest = (request) => HttpClientRequest.makeWith(request.method, redactUrl(request.url), UrlParams.empty, Option.none(), Headers.empty, HttpBody.empty);
|
|
38
|
+
const transportError = (request, description) => new HttpClientError.HttpClientError({
|
|
39
|
+
reason: new HttpClientError.TransportError({ request: redactedErrorRequest(request), description }),
|
|
40
|
+
});
|
|
41
|
+
export const recordingLayer = (name, options = {}) => Layer.effect(HttpClient.HttpClient, Effect.gen(function* () {
|
|
42
|
+
const upstream = yield* HttpClient.HttpClient;
|
|
43
|
+
const cassetteService = yield* CassetteService.Service;
|
|
44
|
+
const redactor = options.redactor ?? make();
|
|
45
|
+
const match = options.match ?? defaultMatcher;
|
|
46
|
+
const requested = options.mode ?? "auto";
|
|
47
|
+
const mode = requested === "auto" ? yield* resolveAutoMode(cassetteService, name) : requested;
|
|
48
|
+
const snapshotRequest = (request) => Effect.gen(function* () {
|
|
49
|
+
const web = yield* HttpClientRequest.toWeb(request).pipe(Effect.orDie);
|
|
50
|
+
return redactor.request({
|
|
51
|
+
method: web.method,
|
|
52
|
+
url: web.url,
|
|
53
|
+
headers: Object.fromEntries(web.headers.entries()),
|
|
54
|
+
body: yield* Effect.promise(() => web.text()),
|
|
55
|
+
});
|
|
56
|
+
});
|
|
57
|
+
if (mode === "passthrough")
|
|
58
|
+
return upstream;
|
|
59
|
+
if (mode === "record") {
|
|
60
|
+
const initial = yield* Deferred.make();
|
|
61
|
+
yield* Deferred.succeed(initial, undefined);
|
|
62
|
+
const tail = yield* Ref.make(initial);
|
|
63
|
+
return HttpClient.make((request) => Effect.gen(function* () {
|
|
64
|
+
const completed = yield* Deferred.make();
|
|
65
|
+
const previous = yield* Ref.modify(tail, (current) => [current, completed]);
|
|
66
|
+
return yield* Effect.gen(function* () {
|
|
67
|
+
const incoming = yield* snapshotRequest(request);
|
|
68
|
+
const response = yield* upstream.execute(request);
|
|
69
|
+
const captured = yield* captureResponseBody(response, response.headers["content-type"]);
|
|
70
|
+
const responseSnapshot = {
|
|
71
|
+
status: response.status,
|
|
72
|
+
headers: response.headers,
|
|
73
|
+
...captured,
|
|
74
|
+
};
|
|
75
|
+
const interaction = {
|
|
76
|
+
transport: "http",
|
|
77
|
+
request: incoming,
|
|
78
|
+
response: redactor.response(responseSnapshot),
|
|
79
|
+
};
|
|
80
|
+
yield* Deferred.await(previous);
|
|
81
|
+
yield* cassetteService
|
|
82
|
+
.append(name, interaction, options.metadata)
|
|
83
|
+
.pipe(Effect.catchTag("UnsafeCassetteError", (error) => Effect.fail(transportError(request, error.message))));
|
|
84
|
+
return responseFromSnapshot(request, responseSnapshot);
|
|
85
|
+
}).pipe(Effect.ensuring(Deferred.succeed(completed, undefined)));
|
|
86
|
+
}));
|
|
87
|
+
}
|
|
88
|
+
const replay = yield* makeReplayState(cassetteService, name, httpInteractions);
|
|
89
|
+
return HttpClient.make((request) => Effect.gen(function* () {
|
|
90
|
+
const incoming = yield* snapshotRequest(request);
|
|
91
|
+
const claimed = yield* replay
|
|
92
|
+
.claim((interaction, index, interactions) => {
|
|
93
|
+
const result = selectSequential(interactions, incoming, match, index);
|
|
94
|
+
if (result.interaction)
|
|
95
|
+
return Effect.void;
|
|
96
|
+
return Effect.fail(transportError(request, `Fixture "${name}" does not match the current request: ${result.detail}.`));
|
|
97
|
+
})
|
|
98
|
+
.pipe(Effect.mapError((error) => error._tag === "CassetteNotFoundError"
|
|
99
|
+
? transportError(request, `Fixture "${name}" not found. Run locally to record it (CI=true forces replay).`)
|
|
100
|
+
: error));
|
|
101
|
+
return responseFromSnapshot(request, claimed.interaction.response);
|
|
102
|
+
}));
|
|
103
|
+
}));
|
|
104
|
+
export const cassetteLayer = (name, options = {}) => recordingLayer(name, options).pipe(Layer.provide(CassetteService.fileSystem({ directory: options.directory })), Layer.provide(FetchHttpClient.layer), Layer.provide(NodeFileSystem.layer));
|
|
105
|
+
//# sourceMappingURL=internal-effect.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"internal-effect.js","sourceRoot":"","sources":["../src/internal-effect.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export { CassetteNotFoundError, hasCassetteSync, UnsafeCassetteError } from "./cassette.js";
|
|
2
|
+
export { cassetteLayer, recordingLayer, type RecordReplayMode, type RecordReplayOptions } from "./internal-effect.js";
|
|
3
|
+
export { redactHeaders, redactUrl, secretFindings, type SecretFinding } from "./redaction.js";
|
|
4
|
+
export { socketLayer } from "./socket.js";
|
|
5
|
+
export { makeWebSocketExecutor, type WebSocketConnection, type WebSocketExecutor, type WebSocketRecordReplayOptions, type WebSocketRequest, } from "./websocket.js";
|
|
6
|
+
export * as Cassette from "./cassette.js";
|
|
7
|
+
export * as Redactor from "./redactor.js";
|
|
8
|
+
export * as HttpRecorderInternal from "./internal.js";
|
|
9
|
+
//# sourceMappingURL=internal.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"internal.d.ts","sourceRoot":"","sources":["../src/internal.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,qBAAqB,EAAE,eAAe,EAAE,mBAAmB,EAAE,MAAM,eAAe,CAAA;AAC3F,OAAO,EAAE,aAAa,EAAE,cAAc,EAAE,KAAK,gBAAgB,EAAE,KAAK,mBAAmB,EAAE,MAAM,sBAAsB,CAAA;AACrH,OAAO,EAAE,aAAa,EAAE,SAAS,EAAE,cAAc,EAAE,KAAK,aAAa,EAAE,MAAM,gBAAgB,CAAA;AAC7F,OAAO,EAAE,WAAW,EAAE,MAAM,aAAa,CAAA;AACzC,OAAO,EACL,qBAAqB,EACrB,KAAK,mBAAmB,EACxB,KAAK,iBAAiB,EACtB,KAAK,4BAA4B,EACjC,KAAK,gBAAgB,GACtB,MAAM,gBAAgB,CAAA;AACvB,OAAO,KAAK,QAAQ,MAAM,eAAe,CAAA;AACzC,OAAO,KAAK,QAAQ,MAAM,eAAe,CAAA;AAEzC,OAAO,KAAK,oBAAoB,MAAM,eAAe,CAAA"}
|