cassetter 0.11.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.
Files changed (49) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +169 -0
  3. package/dist/binding.d.ts +60 -0
  4. package/dist/binding.d.ts.map +1 -0
  5. package/dist/binding.js +12 -0
  6. package/dist/binding.js.map +1 -0
  7. package/dist/cassette.d.ts +122 -0
  8. package/dist/cassette.d.ts.map +1 -0
  9. package/dist/cassette.js +377 -0
  10. package/dist/cassette.js.map +1 -0
  11. package/dist/context.d.ts +22 -0
  12. package/dist/context.d.ts.map +1 -0
  13. package/dist/context.js +70 -0
  14. package/dist/context.js.map +1 -0
  15. package/dist/index.d.ts +29 -0
  16. package/dist/index.d.ts.map +1 -0
  17. package/dist/index.js +26 -0
  18. package/dist/index.js.map +1 -0
  19. package/dist/intercept/base.d.ts +11 -0
  20. package/dist/intercept/base.d.ts.map +1 -0
  21. package/dist/intercept/base.js +20 -0
  22. package/dist/intercept/base.js.map +1 -0
  23. package/dist/intercept/fetch.d.ts +13 -0
  24. package/dist/intercept/fetch.d.ts.map +1 -0
  25. package/dist/intercept/fetch.js +104 -0
  26. package/dist/intercept/fetch.js.map +1 -0
  27. package/dist/intercept/index.d.ts +4 -0
  28. package/dist/intercept/index.d.ts.map +1 -0
  29. package/dist/intercept/index.js +3 -0
  30. package/dist/intercept/index.js.map +1 -0
  31. package/dist/recording.d.ts +22 -0
  32. package/dist/recording.d.ts.map +1 -0
  33. package/dist/recording.js +51 -0
  34. package/dist/recording.js.map +1 -0
  35. package/dist/types.d.ts +96 -0
  36. package/dist/types.d.ts.map +1 -0
  37. package/dist/types.js +35 -0
  38. package/dist/types.js.map +1 -0
  39. package/native/cassetter.darwin-arm64.node +0 -0
  40. package/native/cassetter.darwin-x64.node +0 -0
  41. package/native/cassetter.linux-arm64-gnu.node +0 -0
  42. package/native/cassetter.linux-arm64-musl.node +0 -0
  43. package/native/cassetter.linux-x64-gnu.node +0 -0
  44. package/native/cassetter.linux-x64-musl.node +0 -0
  45. package/native/cassetter.win32-arm64-msvc.node +0 -0
  46. package/native/cassetter.win32-x64-msvc.node +0 -0
  47. package/native/index.js +326 -0
  48. package/native/package.json +3 -0
  49. package/package.json +74 -0
@@ -0,0 +1,377 @@
1
+ /**
2
+ * Record/replay orchestration over the native cassette core.
3
+ *
4
+ * The cassette format, request matching, security filtering, and body
5
+ * processing all live in Rust (`cassetter-core`). This file is the thin,
6
+ * idiomatic layer on top - the same role `src/cassetter/cassette.py` plays for
7
+ * the Python binding.
8
+ */
9
+ import { existsSync, rmSync, statSync } from "node:fs";
10
+ import { native, processBody, scrubInteraction, scrubGrpcInteraction, scrubWsInteraction } from "./binding.js";
11
+ import { DISCARDING_MODES, RecordMode, parseDuration } from "./recording.js";
12
+ import { NONE_BODY, bodyToBuffer } from "./types.js";
13
+ // --- Errors ---
14
+ /** Raised when a cassette file is required but absent. */
15
+ export class CassetteNotFoundError extends Error {
16
+ /** Configure a cassette. Nothing is read until `load()`. */
17
+ constructor(message) {
18
+ super(message);
19
+ this.name = "CassetteNotFoundError";
20
+ }
21
+ }
22
+ /** Raised when a cassette exists but cannot be parsed. */
23
+ export class CassetteLoadError extends Error {
24
+ /** Configure a cassette. Nothing is read until `load()`. */
25
+ constructor(message) {
26
+ super(message);
27
+ this.name = "CassetteLoadError";
28
+ }
29
+ }
30
+ /** Raised when a cassette is older than `maxAge` and `onExpiry` is `fail`. */
31
+ export class CassetteExpiredError extends Error {
32
+ /** Configure a cassette. Nothing is read until `load()`. */
33
+ constructor(message) {
34
+ super(message);
35
+ this.name = "CassetteExpiredError";
36
+ }
37
+ }
38
+ /** Raised when no recorded interaction matches, and none may be recorded. */
39
+ export class NoMatchError extends Error {
40
+ /** Configure a cassette. Nothing is read until `load()`. */
41
+ constructor(message) {
42
+ super(message);
43
+ this.name = "NoMatchError";
44
+ }
45
+ }
46
+ export class Cassette {
47
+ _path;
48
+ _recordMode;
49
+ _matchConfig;
50
+ _securityConfig;
51
+ _maxAge;
52
+ _onExpiry;
53
+ _ignoreLocalhost;
54
+ _inner = null;
55
+ _dirty = false;
56
+ /** Order interactions were recorded in, to break ties in the output order. */
57
+ _recordOrders = [];
58
+ /** Next position `reserveRecordOrder` will hand out. */
59
+ _nextRecordOrder = 0;
60
+ /** `once` replays without recording when the cassette already existed. */
61
+ _onceReplayOnly = false;
62
+ /** Mode of the cassette `rewrite` deleted, to put back on its replacement. */
63
+ _rewrittenFileMode = null;
64
+ /** Configure a cassette. Nothing is read until `load()`. */
65
+ constructor(path, options = {}) {
66
+ this._path = path;
67
+ this._recordMode = options.recordMode ?? RecordMode.ONCE;
68
+ this._matchConfig = options.matchConfig ?? {};
69
+ this._securityConfig = options.securityConfig ?? {};
70
+ this._maxAge = options.maxAge ? parseDuration(options.maxAge) : null;
71
+ this._onExpiry = options.onExpiry ?? "warn";
72
+ this._ignoreLocalhost = options.ignoreLocalhost ?? false;
73
+ }
74
+ /** Where this cassette is read from and written to. */
75
+ get path() {
76
+ return this._path;
77
+ }
78
+ /** The record mode in force. */
79
+ get recordMode() {
80
+ return this._recordMode;
81
+ }
82
+ /** Whether localhost traffic bypasses the cassette entirely. */
83
+ get ignoreLocalhost() {
84
+ return this._ignoreLocalhost;
85
+ }
86
+ /** The recorded HTTP interactions, empty before `load()`. */
87
+ get interactions() {
88
+ return this._inner ? this._inner.interactions : [];
89
+ }
90
+ /** The recorded gRPC interactions, empty before `load()`. */
91
+ get grpcInteractions() {
92
+ return this._inner ? this._inner.grpcInteractions : [];
93
+ }
94
+ /** The recorded WebSocket interactions, empty before `load()`. */
95
+ get wsInteractions() {
96
+ return this._inner ? this._inner.wsInteractions : [];
97
+ }
98
+ /** Whether this mode may replay an existing interaction. */
99
+ get canReplay() {
100
+ return !DISCARDING_MODES.includes(this._recordMode);
101
+ }
102
+ /** Whether an unmatched request may go to the network and be recorded. */
103
+ get canRecord() {
104
+ if (this._recordMode === RecordMode.ALL ||
105
+ this._recordMode === RecordMode.NEW_EPISODES ||
106
+ this._recordMode === RecordMode.REWRITE) {
107
+ return true;
108
+ }
109
+ // `once` records only when the cassette didn't exist: with an existing
110
+ // cassette an unmatched request must raise instead of silently hitting the
111
+ // network and appending.
112
+ return this._recordMode === RecordMode.ONCE && !this._onceReplayOnly;
113
+ }
114
+ /** Load from disk, or start an empty cassette based on the record mode. */
115
+ load() {
116
+ let exists = existsSync(this._path);
117
+ // `rewrite` drops the file before recording, so a run that captures
118
+ // nothing leaves no stale cassette behind. The writer copies the mode off
119
+ // the file it replaces, so with nothing there it has to be handed over -
120
+ // otherwise a 0600 cassette comes back at the process umask.
121
+ if (this._recordMode === RecordMode.REWRITE && exists) {
122
+ try {
123
+ this._rewrittenFileMode = statSync(this._path).mode & 0o7777;
124
+ }
125
+ catch {
126
+ this._rewrittenFileMode = null;
127
+ }
128
+ rmSync(this._path, { force: true });
129
+ exists = false;
130
+ }
131
+ const discarding = DISCARDING_MODES.includes(this._recordMode);
132
+ if (discarding || !exists) {
133
+ this._inner = new native.Cassette();
134
+ this._recordOrders = [];
135
+ this._nextRecordOrder = 0;
136
+ // Loading again onto the same object must not inherit the last load's
137
+ // replay-only state: with no file there, `once` may record afresh.
138
+ this._onceReplayOnly = false;
139
+ if (discarding) {
140
+ this._dirty = true;
141
+ }
142
+ return;
143
+ }
144
+ try {
145
+ this._inner = native.Cassette.load(this._path);
146
+ }
147
+ catch (e) {
148
+ throw new CassetteLoadError(`could not parse cassette ${this._path}: ${e.message}`);
149
+ }
150
+ this._onceReplayOnly = true;
151
+ this._recordOrders = this._inner.interactions.map((_, i) => i);
152
+ this._nextRecordOrder = this._recordOrders.length;
153
+ this._checkExpiry();
154
+ }
155
+ /**
156
+ * Write to disk if modified.
157
+ *
158
+ * An empty cassette is written only when a file already exists, so a
159
+ * re-record that captured nothing truncates the stale file instead of
160
+ * leaving it behind. Interactions go out in a canonical order rather than
161
+ * the order their responses arrived in, so a concurrent suite produces the
162
+ * same file every run.
163
+ */
164
+ save() {
165
+ if (!this._inner || !this._dirty)
166
+ return;
167
+ if (this._inner.length === 0 && !existsSync(this._path))
168
+ return;
169
+ this._inner.save(this._path, this._inner.outputOrder(this._matchConfig, this._recordOrders), this._rewrittenFileMode ?? undefined);
170
+ this._dirty = false;
171
+ this._rewrittenFileMode = null;
172
+ }
173
+ /** Serialize to YAML without touching the filesystem. */
174
+ toYaml() {
175
+ return this._inner ? this._inner.toYaml() : "";
176
+ }
177
+ /** Serialize to TOML without touching the filesystem. */
178
+ toToml() {
179
+ return this._inner ? this._inner.toToml() : "";
180
+ }
181
+ // --- HTTP ---
182
+ /**
183
+ * Claim this interaction's position before its request is issued.
184
+ *
185
+ * Interceptors record once the response is back, so under concurrency the
186
+ * cassette would otherwise be written in whatever order responses arrived
187
+ * in - different on every run.
188
+ */
189
+ reserveRecordOrder() {
190
+ return this._nextRecordOrder++;
191
+ }
192
+ play(method, uri, headers, body) {
193
+ if (!this._inner) {
194
+ throw new NoMatchError("cassette not loaded");
195
+ }
196
+ if (!this.canReplay) {
197
+ throw new NoMatchError(`replay disabled in ${this._recordMode} mode`);
198
+ }
199
+ const processed = processBody(body ?? Buffer.alloc(0), getHeader(headers, "content-type"), getHeader(headers, "content-encoding"));
200
+ // Interactions are scrubbed at write time, so the live request has to be
201
+ // scrubbed with the same config before matching: a URI recorded as
202
+ // api_key=[FILTERED] would otherwise never match the real query string,
203
+ // and a scrubbed body field would never match the real one.
204
+ const probe = scrubInteraction({
205
+ request: { method, uri, headers, body: processed },
206
+ response: { status: 0, headers: {}, body: NONE_BODY },
207
+ recordedAt: "",
208
+ }, this._securityConfig).request;
209
+ const hit = this._inner.takeMatch(probe, this._matchConfig);
210
+ if (hit === null) {
211
+ throw new NoMatchError(`no matching interaction for ${method} ${uri}`);
212
+ }
213
+ return hit.interaction.response;
214
+ }
215
+ record(method, uri, requestHeaders, requestBody, status, responseHeaders, responseBody, order) {
216
+ const reqBody = processBody(requestBody ?? Buffer.alloc(0), getHeader(requestHeaders, "content-type"), getHeader(requestHeaders, "content-encoding"));
217
+ const respBody = processBody(responseBody ?? Buffer.alloc(0), getHeader(responseHeaders, "content-type"), getHeader(responseHeaders, "content-encoding"));
218
+ // The body is stored decompressed, so content-encoding must not survive.
219
+ const cleanRespHeaders = {};
220
+ for (const [k, v] of Object.entries(responseHeaders)) {
221
+ if (k.toLowerCase() !== "content-encoding") {
222
+ cleanRespHeaders[k] = v;
223
+ }
224
+ }
225
+ const interaction = retagContentLength(scrubInteraction({
226
+ request: { method, uri, headers: requestHeaders, body: reqBody },
227
+ response: { status, headers: cleanRespHeaders, body: respBody },
228
+ recordedAt: new Date().toISOString(),
229
+ }, this._securityConfig));
230
+ this._ensureInner().addInteraction(interaction);
231
+ this._recordOrders.push(order ?? this.reserveRecordOrder());
232
+ this._dirty = true;
233
+ return interaction.response;
234
+ }
235
+ // --- gRPC ---
236
+ /** Replay a gRPC response for `method`, or throw `NoMatchError`. */
237
+ playGrpc(method) {
238
+ if (!this._inner) {
239
+ throw new NoMatchError("cassette not loaded");
240
+ }
241
+ const hit = this._inner.takeGrpcMatch(method);
242
+ if (hit === null) {
243
+ throw new NoMatchError(`no matching gRPC interaction for ${method}`);
244
+ }
245
+ return hit.interaction.response;
246
+ }
247
+ recordGrpc(method, metadata, requestBody, responseBody, options = {}) {
248
+ const interaction = scrubGrpcInteraction({
249
+ request: { method, metadata, body: requestBody },
250
+ response: {
251
+ statusCode: options.statusCode ?? 0,
252
+ statusMessage: options.statusMessage ?? "OK",
253
+ metadata: options.responseMetadata ?? {},
254
+ body: responseBody,
255
+ },
256
+ recordedAt: new Date().toISOString(),
257
+ jsonDebug: options.jsonDebug,
258
+ }, this._securityConfig);
259
+ this._ensureInner().addGrpcInteraction(interaction);
260
+ this._dirty = true;
261
+ return interaction.response;
262
+ }
263
+ // --- WebSocket ---
264
+ /** Replay a WebSocket interaction for `uri`, or throw `NoMatchError`. */
265
+ playWs(uri) {
266
+ if (!this._inner) {
267
+ throw new NoMatchError("cassette not loaded");
268
+ }
269
+ const probe = scrubWsInteraction({ uri, headers: {}, frames: [], recordedAt: "" }, this._securityConfig);
270
+ const hit = this._inner.takeWsMatch(probe.uri);
271
+ if (hit === null) {
272
+ throw new NoMatchError(`no matching WebSocket interaction for ${uri}`);
273
+ }
274
+ return hit.interaction;
275
+ }
276
+ /** Record a WebSocket connection and its frames, scrubbed. */
277
+ recordWs(uri, headers, frames) {
278
+ const interaction = scrubWsInteraction({ uri, headers, frames, recordedAt: new Date().toISOString() }, this._securityConfig);
279
+ this._ensureInner().addWsInteraction(interaction);
280
+ this._dirty = true;
281
+ }
282
+ // --- Internals ---
283
+ /** The native cassette, created on first use if `load()` never ran. */
284
+ _ensureInner() {
285
+ this._inner ??= new native.Cassette();
286
+ return this._inner;
287
+ }
288
+ /** Apply `onExpiry` when the newest recording predates `maxAge`. */
289
+ _checkExpiry() {
290
+ if (this._maxAge === null || !this._inner)
291
+ return;
292
+ const newest = this._newestRecordedAt();
293
+ if (newest === null)
294
+ return;
295
+ if (newest.getTime() >= Date.now() - this._maxAge)
296
+ return;
297
+ const ageDays = Math.floor((Date.now() - newest.getTime()) / (24 * 60 * 60 * 1000));
298
+ const msg = `cassette '${this._path}' is ${ageDays} days old (maxAge=${this._maxAge}ms)`;
299
+ if (this._onExpiry === "fail") {
300
+ throw new CassetteExpiredError(msg);
301
+ }
302
+ if (this._onExpiry === "rerecord") {
303
+ this._inner = new native.Cassette();
304
+ this._recordOrders = [];
305
+ this._onceReplayOnly = false;
306
+ this._dirty = true;
307
+ return;
308
+ }
309
+ process.emitWarning(msg, "CassetteExpiredWarning");
310
+ }
311
+ /** The most recent `recordedAt` across every protocol, if any. */
312
+ _newestRecordedAt() {
313
+ if (!this._inner)
314
+ return null;
315
+ const stamps = [
316
+ ...this._inner.interactions.map((i) => i.recordedAt),
317
+ ...this._inner.grpcInteractions.map((i) => i.recordedAt),
318
+ ...this._inner.wsInteractions.map((i) => i.recordedAt),
319
+ ].filter(Boolean);
320
+ if (stamps.length === 0)
321
+ return null;
322
+ return stamps.reduce((newest, ts) => {
323
+ const d = new Date(ts);
324
+ if (Number.isNaN(d.getTime()))
325
+ return newest;
326
+ return newest === null || d > newest ? d : newest;
327
+ }, null);
328
+ }
329
+ }
330
+ /**
331
+ * Restate a response's `content-length` for the body as recorded.
332
+ *
333
+ * Decompressing a response and scrubbing a secret out of a body both change
334
+ * its length, and a client that checks the header against what it reads fails
335
+ * on replay when the two disagree.
336
+ *
337
+ * Only the response, and only when it carries a body. A request's header is
338
+ * compared against the incoming one by the `headers` matcher, so rewriting it
339
+ * would stop an identical request from replaying; and on a HEAD or 304 the
340
+ * header describes a representation that was never sent, so there is no body
341
+ * to measure it against.
342
+ */
343
+ function retagContentLength(interaction) {
344
+ const served = bodyToBuffer(interaction.response.body);
345
+ if (served.length === 0)
346
+ return interaction;
347
+ const length = String(served.length);
348
+ let changed = false;
349
+ const headers = {};
350
+ for (const [key, values] of Object.entries(interaction.response.headers)) {
351
+ if (key.toLowerCase() === "content-length") {
352
+ if (values.length !== 1 || values[0] !== length)
353
+ changed = true;
354
+ headers[key] = [length];
355
+ }
356
+ else {
357
+ headers[key] = values;
358
+ }
359
+ }
360
+ if (!changed)
361
+ return interaction;
362
+ return {
363
+ ...interaction,
364
+ response: { ...interaction.response, headers },
365
+ };
366
+ }
367
+ /** Case-insensitive header lookup returning the first value. */
368
+ export function getHeader(headers, name) {
369
+ const target = name.toLowerCase();
370
+ for (const [key, values] of Object.entries(headers)) {
371
+ if (key.toLowerCase() === target && values.length > 0) {
372
+ return values[0];
373
+ }
374
+ }
375
+ return null;
376
+ }
377
+ //# sourceMappingURL=cassette.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cassette.js","sourceRoot":"","sources":["../src/cassette.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,22 @@
1
+ /**
2
+ * `useCassette` - the main entry point for recording/replaying HTTP traffic.
3
+ *
4
+ * @example
5
+ * ```ts
6
+ * await useCassette("tests/cassettes/users.yaml", async () => {
7
+ * const res = await fetch("https://api.example.com/users");
8
+ * });
9
+ * ```
10
+ */
11
+ import { Cassette } from "./cassette.js";
12
+ import type { CassetteConfig } from "./types.js";
13
+ export type UseCassetteOptions = CassetteConfig;
14
+ /**
15
+ * Record or replay every `fetch` made inside `fn`.
16
+ *
17
+ * Interceptors are installed before the callback and removed after it, even
18
+ * if it throws; the cassette is written on the way out.
19
+ */
20
+ export declare function useCassette(path: string, fn: (cassette: Cassette) => Promise<void> | void): Promise<Cassette>;
21
+ export declare function useCassette(path: string, options: UseCassetteOptions, fn: (cassette: Cassette) => Promise<void> | void): Promise<Cassette>;
22
+ //# sourceMappingURL=context.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"context.d.ts","sourceRoot":"","sources":["../src/context.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,OAAO,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AAIzC,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;AAEjD,MAAM,MAAM,kBAAkB,GAAG,cAAc,CAAC;AAMhD;;;;;GAKG;AACH,wBAAsB,WAAW,CAC/B,IAAI,EAAE,MAAM,EACZ,EAAE,EAAE,CAAC,QAAQ,EAAE,QAAQ,KAAK,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,GAC/C,OAAO,CAAC,QAAQ,CAAC,CAAC;AACrB,wBAAsB,WAAW,CAC/B,IAAI,EAAE,MAAM,EACZ,OAAO,EAAE,kBAAkB,EAC3B,EAAE,EAAE,CAAC,QAAQ,EAAE,QAAQ,KAAK,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,GAC/C,OAAO,CAAC,QAAQ,CAAC,CAAC"}
@@ -0,0 +1,70 @@
1
+ /**
2
+ * `useCassette` - the main entry point for recording/replaying HTTP traffic.
3
+ *
4
+ * @example
5
+ * ```ts
6
+ * await useCassette("tests/cassettes/users.yaml", async () => {
7
+ * const res = await fetch("https://api.example.com/users");
8
+ * });
9
+ * ```
10
+ */
11
+ import { Cassette } from "./cassette.js";
12
+ import { FetchInterceptor } from "./intercept/fetch.js";
13
+ import { RecordMode, parseRecordMode } from "./recording.js";
14
+ const INTERCEPTORS = {
15
+ fetch: () => new FetchInterceptor(),
16
+ };
17
+ export async function useCassette(path, optionsOrFn, maybeFn) {
18
+ const options = typeof optionsOrFn === "function" ? {} : optionsOrFn;
19
+ const fn = typeof optionsOrFn === "function" ? optionsOrFn : maybeFn;
20
+ if (!fn) {
21
+ throw new TypeError("useCassette requires a callback");
22
+ }
23
+ const cassette = new Cassette(path, {
24
+ recordMode: options.recordMode
25
+ ? parseRecordMode(options.recordMode)
26
+ : RecordMode.ONCE,
27
+ matchConfig: {
28
+ matchOn: options.matchOn,
29
+ ignoreJsonPaths: options.ignoreJsonPaths,
30
+ },
31
+ securityConfig: {
32
+ filterHeaders: options.filterHeaders,
33
+ filterQueryParameters: options.filterQueryParameters,
34
+ bodyScrubPatterns: options.bodyScrubPatterns,
35
+ replacement: options.replacement,
36
+ },
37
+ maxAge: options.maxAge,
38
+ onExpiry: options.onExpiry,
39
+ ignoreLocalhost: options.ignoreLocalhost,
40
+ });
41
+ cassette.load();
42
+ const interceptors = resolveInterceptors(options.intercept);
43
+ for (const interceptor of interceptors) {
44
+ interceptor.install(cassette);
45
+ }
46
+ try {
47
+ await fn(cassette);
48
+ }
49
+ finally {
50
+ for (const interceptor of [...interceptors].reverse()) {
51
+ interceptor.uninstall();
52
+ }
53
+ cassette.save();
54
+ }
55
+ return cassette;
56
+ }
57
+ /** Instantiate the named interceptors, or the default set. */
58
+ function resolveInterceptors(names) {
59
+ if (!names || names.length === 0) {
60
+ return [new FetchInterceptor()];
61
+ }
62
+ return names.map((name) => {
63
+ const factory = INTERCEPTORS[name];
64
+ if (!factory) {
65
+ throw new Error(`unknown interceptor: '${name}' (available: ${Object.keys(INTERCEPTORS).join(", ")})`);
66
+ }
67
+ return factory();
68
+ });
69
+ }
70
+ //# sourceMappingURL=context.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"context.js","sourceRoot":"","sources":["../src/context.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,OAAO,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AACzC,OAAO,EAAE,gBAAgB,EAAE,MAAM,sBAAsB,CAAC;AAExD,OAAO,EAAE,UAAU,EAAE,eAAe,EAAE,MAAM,gBAAgB,CAAC;AAK7D,MAAM,YAAY,GAAsC;IACtD,KAAK,EAAE,GAAG,EAAE,CAAC,IAAI,gBAAgB,EAAE;CACpC,CAAC;AAiBF,MAAM,CAAC,KAAK,UAAU,WAAW,CAC/B,IAAY,EACZ,WAEkD,EAClD,OAAsD;IAEtD,MAAM,OAAO,GACX,OAAO,WAAW,KAAK,UAAU,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,WAAW,CAAC;IACvD,MAAM,EAAE,GAAG,OAAO,WAAW,KAAK,UAAU,CAAC,CAAC,CAAC,WAAW,CAAC,CAAC,CAAC,OAAO,CAAC;IAErE,IAAI,CAAC,EAAE,EAAE,CAAC;QACR,MAAM,IAAI,SAAS,CAAC,iCAAiC,CAAC,CAAC;IACzD,CAAC;IAED,MAAM,QAAQ,GAAG,IAAI,QAAQ,CAAC,IAAI,EAAE;QAClC,UAAU,EAAE,OAAO,CAAC,UAAU;YAC5B,CAAC,CAAC,eAAe,CAAC,OAAO,CAAC,UAAU,CAAC;YACrC,CAAC,CAAC,UAAU,CAAC,IAAI;QACnB,WAAW,EAAE;YACX,OAAO,EAAE,OAAO,CAAC,OAAO;YACxB,eAAe,EAAE,OAAO,CAAC,eAAe;SACzC;QACD,cAAc,EAAE;YACd,aAAa,EAAE,OAAO,CAAC,aAAa;YACpC,qBAAqB,EAAE,OAAO,CAAC,qBAAqB;YACpD,iBAAiB,EAAE,OAAO,CAAC,iBAAiB;YAC5C,WAAW,EAAE,OAAO,CAAC,WAAW;SACjC;QACD,MAAM,EAAE,OAAO,CAAC,MAAM;QACtB,QAAQ,EAAE,OAAO,CAAC,QAAQ;QAC1B,eAAe,EAAE,OAAO,CAAC,eAAe;KACzC,CAAC,CAAC;IAEH,QAAQ,CAAC,IAAI,EAAE,CAAC;IAEhB,MAAM,YAAY,GAAG,mBAAmB,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC;IAC5D,KAAK,MAAM,WAAW,IAAI,YAAY,EAAE,CAAC;QACvC,WAAW,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC;IAChC,CAAC;IAED,IAAI,CAAC;QACH,MAAM,EAAE,CAAC,QAAQ,CAAC,CAAC;IACrB,CAAC;YAAS,CAAC;QACT,KAAK,MAAM,WAAW,IAAI,CAAC,GAAG,YAAY,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC;YACtD,WAAW,CAAC,SAAS,EAAE,CAAC;QAC1B,CAAC;QACD,QAAQ,CAAC,IAAI,EAAE,CAAC;IAClB,CAAC;IAED,OAAO,QAAQ,CAAC;AAClB,CAAC;AAED,8DAA8D;AAC9D,SAAS,mBAAmB,CAAC,KAAgB;IAC3C,IAAI,CAAC,KAAK,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACjC,OAAO,CAAC,IAAI,gBAAgB,EAAE,CAAC,CAAC;IAClC,CAAC;IACD,OAAO,KAAK,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE;QACxB,MAAM,OAAO,GAAG,YAAY,CAAC,IAAI,CAAC,CAAC;QACnC,IAAI,CAAC,OAAO,EAAE,CAAC;YACb,MAAM,IAAI,KAAK,CACb,yBAAyB,IAAI,iBAAiB,MAAM,CAAC,IAAI,CAAC,YAAY,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,CACtF,CAAC;QACJ,CAAC;QACD,OAAO,OAAO,EAAE,CAAC;IACnB,CAAC,CAAC,CAAC;AACL,CAAC"}
@@ -0,0 +1,29 @@
1
+ /**
2
+ * cassetter - HTTP cassette recorder for Node.js tests. Safe by default.
3
+ *
4
+ * Cassette format, request matching, security filtering, and body processing
5
+ * are implemented in Rust (`cassetter-core`) and shared with the Python
6
+ * binding, so cassettes are interchangeable between the two.
7
+ *
8
+ * @example
9
+ * ```ts
10
+ * import { useCassette } from "cassetter";
11
+ *
12
+ * await useCassette("tests/cassettes/users.yaml", async () => {
13
+ * const res = await fetch("https://api.example.com/users");
14
+ * console.log(res.status);
15
+ * });
16
+ * ```
17
+ */
18
+ export { useCassette } from "./context.js";
19
+ export type { UseCassetteOptions } from "./context.js";
20
+ export { Cassette, CassetteExpiredError, CassetteLoadError, CassetteNotFoundError, NoMatchError, getHeader, } from "./cassette.js";
21
+ export type { CassetteOptions } from "./cassette.js";
22
+ export { DISCARDING_MODES, RecordMode, parseDuration, parseRecordMode, } from "./recording.js";
23
+ export { NONE_BODY, binaryBody, binaryBodyBytes, bodyToBuffer, } from "./types.js";
24
+ export type { Body, BodyType, CassetteConfig, GrpcInteraction, GrpcRequest, GrpcResponse, HeaderMap, HttpInteraction, HttpRequest, HttpResponse, MatchConfig, Matcher, SecurityConfig, WsFrame, WsInteraction, } from "./types.js";
25
+ export { FetchInterceptor } from "./intercept/fetch.js";
26
+ export { isLocalhost } from "./intercept/base.js";
27
+ export type { Interceptor } from "./intercept/base.js";
28
+ export { processBody, scrubGrpcInteraction, scrubInteraction, scrubWsInteraction, defaultBodyScrubPatterns, defaultFilterHeaders, defaultFilterQueryParameters, defaultMatchOn, defaultReplacement, formatVersion, knownMatchers, } from "./binding.js";
29
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAEH,OAAO,EAAE,WAAW,EAAE,MAAM,cAAc,CAAC;AAC3C,YAAY,EAAE,kBAAkB,EAAE,MAAM,cAAc,CAAC;AAEvD,OAAO,EACL,QAAQ,EACR,oBAAoB,EACpB,iBAAiB,EACjB,qBAAqB,EACrB,YAAY,EACZ,SAAS,GACV,MAAM,eAAe,CAAC;AACvB,YAAY,EAAE,eAAe,EAAE,MAAM,eAAe,CAAC;AAErD,OAAO,EACL,gBAAgB,EAChB,UAAU,EACV,aAAa,EACb,eAAe,GAChB,MAAM,gBAAgB,CAAC;AAExB,OAAO,EACL,SAAS,EACT,UAAU,EACV,eAAe,EACf,YAAY,GACb,MAAM,YAAY,CAAC;AACpB,YAAY,EACV,IAAI,EACJ,QAAQ,EACR,cAAc,EACd,eAAe,EACf,WAAW,EACX,YAAY,EACZ,SAAS,EACT,eAAe,EACf,WAAW,EACX,YAAY,EACZ,WAAW,EACX,OAAO,EACP,cAAc,EACd,OAAO,EACP,aAAa,GACd,MAAM,YAAY,CAAC;AAEpB,OAAO,EAAE,gBAAgB,EAAE,MAAM,sBAAsB,CAAC;AACxD,OAAO,EAAE,WAAW,EAAE,MAAM,qBAAqB,CAAC;AAClD,YAAY,EAAE,WAAW,EAAE,MAAM,qBAAqB,CAAC;AAGvD,OAAO,EACL,WAAW,EACX,oBAAoB,EACpB,gBAAgB,EAChB,kBAAkB,EAClB,wBAAwB,EACxB,oBAAoB,EACpB,4BAA4B,EAC5B,cAAc,EACd,kBAAkB,EAClB,aAAa,EACb,aAAa,GACd,MAAM,cAAc,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1,26 @@
1
+ /**
2
+ * cassetter - HTTP cassette recorder for Node.js tests. Safe by default.
3
+ *
4
+ * Cassette format, request matching, security filtering, and body processing
5
+ * are implemented in Rust (`cassetter-core`) and shared with the Python
6
+ * binding, so cassettes are interchangeable between the two.
7
+ *
8
+ * @example
9
+ * ```ts
10
+ * import { useCassette } from "cassetter";
11
+ *
12
+ * await useCassette("tests/cassettes/users.yaml", async () => {
13
+ * const res = await fetch("https://api.example.com/users");
14
+ * console.log(res.status);
15
+ * });
16
+ * ```
17
+ */
18
+ export { useCassette } from "./context.js";
19
+ export { Cassette, CassetteExpiredError, CassetteLoadError, CassetteNotFoundError, NoMatchError, getHeader, } from "./cassette.js";
20
+ export { DISCARDING_MODES, RecordMode, parseDuration, parseRecordMode, } from "./recording.js";
21
+ export { NONE_BODY, binaryBody, binaryBodyBytes, bodyToBuffer, } from "./types.js";
22
+ export { FetchInterceptor } from "./intercept/fetch.js";
23
+ export { isLocalhost } from "./intercept/base.js";
24
+ // Primitives from the shared Rust core.
25
+ export { processBody, scrubGrpcInteraction, scrubInteraction, scrubWsInteraction, defaultBodyScrubPatterns, defaultFilterHeaders, defaultFilterQueryParameters, defaultMatchOn, defaultReplacement, formatVersion, knownMatchers, } from "./binding.js";
26
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAEH,OAAO,EAAE,WAAW,EAAE,MAAM,cAAc,CAAC;AAG3C,OAAO,EACL,QAAQ,EACR,oBAAoB,EACpB,iBAAiB,EACjB,qBAAqB,EACrB,YAAY,EACZ,SAAS,GACV,MAAM,eAAe,CAAC;AAGvB,OAAO,EACL,gBAAgB,EAChB,UAAU,EACV,aAAa,EACb,eAAe,GAChB,MAAM,gBAAgB,CAAC;AAExB,OAAO,EACL,SAAS,EACT,UAAU,EACV,eAAe,EACf,YAAY,GACb,MAAM,YAAY,CAAC;AAmBpB,OAAO,EAAE,gBAAgB,EAAE,MAAM,sBAAsB,CAAC;AACxD,OAAO,EAAE,WAAW,EAAE,MAAM,qBAAqB,CAAC;AAGlD,wCAAwC;AACxC,OAAO,EACL,WAAW,EACX,oBAAoB,EACpB,gBAAgB,EAChB,kBAAkB,EAClB,wBAAwB,EACxB,oBAAoB,EACpB,4BAA4B,EAC5B,cAAc,EACd,kBAAkB,EAClB,aAAa,EACb,aAAa,GACd,MAAM,cAAc,CAAC"}
@@ -0,0 +1,11 @@
1
+ /**
2
+ * Base interceptor protocol and shared utilities.
3
+ */
4
+ import type { Cassette } from "../cassette.js";
5
+ export interface Interceptor {
6
+ install(cassette: Cassette): void;
7
+ uninstall(): void;
8
+ }
9
+ /** Whether a URI points at the local machine. */
10
+ export declare function isLocalhost(uri: string): boolean;
11
+ //# sourceMappingURL=base.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"base.d.ts","sourceRoot":"","sources":["../../src/intercept/base.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAC;AAE/C,MAAM,WAAW,WAAW;IAC1B,OAAO,CAAC,QAAQ,EAAE,QAAQ,GAAG,IAAI,CAAC;IAClC,SAAS,IAAI,IAAI,CAAC;CACnB;AASD,iDAAiD;AACjD,wBAAgB,WAAW,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAOhD"}
@@ -0,0 +1,20 @@
1
+ /**
2
+ * Base interceptor protocol and shared utilities.
3
+ */
4
+ const LOCALHOST_HOSTS = new Set([
5
+ "localhost",
6
+ "127.0.0.1",
7
+ "[::1]",
8
+ "::1",
9
+ ]);
10
+ /** Whether a URI points at the local machine. */
11
+ export function isLocalhost(uri) {
12
+ try {
13
+ const url = new URL(uri);
14
+ return LOCALHOST_HOSTS.has(url.hostname);
15
+ }
16
+ catch {
17
+ return false;
18
+ }
19
+ }
20
+ //# sourceMappingURL=base.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"base.js","sourceRoot":"","sources":["../../src/intercept/base.ts"],"names":[],"mappings":"AAAA;;GAEG;AASH,MAAM,eAAe,GAAG,IAAI,GAAG,CAAC;IAC9B,WAAW;IACX,WAAW;IACX,OAAO;IACP,KAAK;CACN,CAAC,CAAC;AAEH,iDAAiD;AACjD,MAAM,UAAU,WAAW,CAAC,GAAW;IACrC,IAAI,CAAC;QACH,MAAM,GAAG,GAAG,IAAI,GAAG,CAAC,GAAG,CAAC,CAAC;QACzB,OAAO,eAAe,CAAC,GAAG,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC;IAC3C,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,KAAK,CAAC;IACf,CAAC;AACH,CAAC"}
@@ -0,0 +1,13 @@
1
+ /**
2
+ * Intercepts the global `fetch` to record and replay HTTP traffic.
3
+ */
4
+ import { type Cassette } from "../cassette.js";
5
+ import { type Interceptor } from "./base.js";
6
+ export declare class FetchInterceptor implements Interceptor {
7
+ private _patched;
8
+ /** Replace the global `fetch` with one backed by `cassette`. */
9
+ install(cassette: Cassette): void;
10
+ /** Put the previous active `fetch` back if this interceptor owns the global. */
11
+ uninstall(): void;
12
+ }
13
+ //# sourceMappingURL=fetch.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"fetch.d.ts","sourceRoot":"","sources":["../../src/intercept/fetch.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAgB,KAAK,QAAQ,EAAE,MAAM,gBAAgB,CAAC;AAE7D,OAAO,EAAe,KAAK,WAAW,EAAE,MAAM,WAAW,CAAC;AAmB1D,qBAAa,gBAAiB,YAAW,WAAW;IAClD,OAAO,CAAC,QAAQ,CAAwC;IAExD,gEAAgE;IAChE,OAAO,CAAC,QAAQ,EAAE,QAAQ,GAAG,IAAI;IA8DjC,gFAAgF;IAChF,SAAS,IAAI,IAAI;CAYlB"}