@zimic/interceptor 1.1.4 → 1.1.5-canary.2

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/dist/server.js CHANGED
@@ -1,29 +1,29 @@
1
1
  'use strict';
2
2
 
3
- var chunkWBOIAXNW_js = require('./chunk-WBOIAXNW.js');
3
+ var chunkTDULAKH2_js = require('./chunk-TDULAKH2.js');
4
4
  require('./chunk-DGUM43GV.js');
5
5
 
6
6
 
7
7
 
8
8
  Object.defineProperty(exports, "DEFAULT_ACCESS_CONTROL_HEADERS", {
9
9
  enumerable: true,
10
- get: function () { return chunkWBOIAXNW_js.DEFAULT_ACCESS_CONTROL_HEADERS; }
10
+ get: function () { return chunkTDULAKH2_js.DEFAULT_ACCESS_CONTROL_HEADERS; }
11
11
  });
12
12
  Object.defineProperty(exports, "DEFAULT_PREFLIGHT_STATUS_CODE", {
13
13
  enumerable: true,
14
- get: function () { return chunkWBOIAXNW_js.DEFAULT_PREFLIGHT_STATUS_CODE; }
14
+ get: function () { return chunkTDULAKH2_js.DEFAULT_PREFLIGHT_STATUS_CODE; }
15
15
  });
16
16
  Object.defineProperty(exports, "NotRunningInterceptorServerError", {
17
17
  enumerable: true,
18
- get: function () { return chunkWBOIAXNW_js.NotRunningInterceptorServerError_default; }
18
+ get: function () { return chunkTDULAKH2_js.NotRunningInterceptorServerError_default; }
19
19
  });
20
20
  Object.defineProperty(exports, "RunningInterceptorServerError", {
21
21
  enumerable: true,
22
- get: function () { return chunkWBOIAXNW_js.RunningInterceptorServerError_default; }
22
+ get: function () { return chunkTDULAKH2_js.RunningInterceptorServerError_default; }
23
23
  });
24
24
  Object.defineProperty(exports, "createInterceptorServer", {
25
25
  enumerable: true,
26
- get: function () { return chunkWBOIAXNW_js.createInterceptorServer; }
26
+ get: function () { return chunkTDULAKH2_js.createInterceptorServer; }
27
27
  });
28
28
  //# sourceMappingURL=server.js.map
29
29
  //# sourceMappingURL=server.js.map
package/dist/server.mjs CHANGED
@@ -1,4 +1,4 @@
1
- export { DEFAULT_ACCESS_CONTROL_HEADERS, DEFAULT_PREFLIGHT_STATUS_CODE, NotRunningInterceptorServerError_default as NotRunningInterceptorServerError, RunningInterceptorServerError_default as RunningInterceptorServerError, createInterceptorServer } from './chunk-KIM2YGPM.mjs';
1
+ export { DEFAULT_ACCESS_CONTROL_HEADERS, DEFAULT_PREFLIGHT_STATUS_CODE, NotRunningInterceptorServerError_default as NotRunningInterceptorServerError, RunningInterceptorServerError_default as RunningInterceptorServerError, createInterceptorServer } from './chunk-CL2KWDK6.mjs';
2
2
  import './chunk-BJTO5JO5.mjs';
3
3
  //# sourceMappingURL=server.mjs.map
4
4
  //# sourceMappingURL=server.mjs.map
package/package.json CHANGED
@@ -14,7 +14,7 @@
14
14
  "api",
15
15
  "static"
16
16
  ],
17
- "version": "1.1.4",
17
+ "version": "1.1.5-canary.2",
18
18
  "homepage": "https://zimic.dev/docs/interceptor",
19
19
  "repository": {
20
20
  "type": "git",
@@ -79,6 +79,23 @@
79
79
  },
80
80
  "./package.json": "./package.json"
81
81
  },
82
+ "scripts": {
83
+ "dev": "tsup --watch",
84
+ "cli": "node --enable-source-maps ./dist/cli.js",
85
+ "build": "tsup",
86
+ "lint": "eslint --cache --no-error-on-unmatched-pattern --no-warn-ignored --fix",
87
+ "lint:turbo": "pnpm lint . --max-warnings 0",
88
+ "style": "prettier --log-level warn --ignore-unknown --no-error-on-unmatched-pattern --cache",
89
+ "style:check": "pnpm style --check",
90
+ "style:format": "pnpm style --write",
91
+ "test": "dotenv -v NODE_ENV=test -v FORCE_COLOR=1 -- vitest",
92
+ "test:turbo": "dotenv -v CI=true -- pnpm run test run --coverage",
93
+ "types:check": "tsc --noEmit",
94
+ "deps:install-playwright": "playwright install chromium",
95
+ "deps:init-msw": "msw init ./public --no-save",
96
+ "deps:prepare": "pnpm deps:install-playwright && pnpm deps:init-msw",
97
+ "postinstall": "node --enable-source-maps -e \"try{require('./dist/scripts/postinstall')}catch(error){console.error(error)}\""
98
+ },
82
99
  "dependencies": {
83
100
  "@whatwg-node/server": "0.10.12",
84
101
  "execa": "9.6.0",
@@ -98,41 +115,24 @@
98
115
  "@types/yargs": "^17.0.33",
99
116
  "@vitest/browser": "^3.2.4",
100
117
  "@vitest/coverage-istanbul": "^3.2.4",
118
+ "@zimic/eslint-config-node": "workspace:*",
119
+ "@zimic/lint-staged-config": "workspace:*",
120
+ "@zimic/tsconfig": "workspace:*",
121
+ "@zimic/utils": "workspace:*",
101
122
  "dotenv-cli": "^10.0.0",
102
123
  "eslint": "^9.37.0",
103
124
  "playwright": "^1.55.1",
104
125
  "tsup": "^8.4.0",
105
126
  "typescript": "^5.9.3",
106
- "vitest": "^3.2.4",
107
- "@zimic/eslint-config-node": "0.0.0",
108
- "@zimic/lint-staged-config": "0.0.0",
109
- "@zimic/tsconfig": "0.0.0",
110
- "@zimic/utils": "0.0.0"
127
+ "vitest": "^3.2.4"
111
128
  },
112
129
  "peerDependencies": {
113
- "typescript": ">=5.0.0",
114
- "@zimic/http": "^1.2.0 || 1.2.0"
130
+ "@zimic/http": "^1.2.0 || workspace:*",
131
+ "typescript": ">=5.0.0"
115
132
  },
116
133
  "peerDependenciesMeta": {
117
134
  "typescript": {
118
135
  "optional": true
119
136
  }
120
- },
121
- "scripts": {
122
- "dev": "tsup --watch",
123
- "cli": "node --enable-source-maps ./dist/cli.js",
124
- "build": "tsup",
125
- "lint": "eslint --cache --no-error-on-unmatched-pattern --no-warn-ignored --fix",
126
- "lint:turbo": "pnpm lint . --max-warnings 0",
127
- "style": "prettier --log-level warn --ignore-unknown --no-error-on-unmatched-pattern --cache",
128
- "style:check": "pnpm style --check",
129
- "style:format": "pnpm style --write",
130
- "test": "dotenv -v NODE_ENV=test -v FORCE_COLOR=1 -- vitest",
131
- "test:turbo": "dotenv -v CI=true -- pnpm run test run --coverage",
132
- "types:check": "tsc --noEmit",
133
- "deps:install-playwright": "playwright install chromium",
134
- "deps:init-msw": "msw init ./public --no-save",
135
- "deps:prepare": "pnpm deps:install-playwright && pnpm deps:init-msw",
136
- "postinstall": "node --enable-source-maps -e \"try{require('./dist/scripts/postinstall')}catch(error){console.error(error)}\""
137
137
  }
138
- }
138
+ }
@@ -15,7 +15,10 @@ import validateURLProtocol from '@zimic/utils/url/validateURLProtocol';
15
15
  import { isServerSide } from '@/utils/environment';
16
16
 
17
17
  import HttpInterceptorWorker from '../interceptorWorker/HttpInterceptorWorker';
18
- import HttpRequestHandlerClient, { AnyHttpRequestHandlerClient } from '../requestHandler/HttpRequestHandlerClient';
18
+ import HttpRequestHandlerClient, {
19
+ AnyHttpRequestHandlerClient,
20
+ HttpRequestHandlerRequestMatch,
21
+ } from '../requestHandler/HttpRequestHandlerClient';
19
22
  import LocalHttpRequestHandler from '../requestHandler/LocalHttpRequestHandler';
20
23
  import RemoteHttpRequestHandler from '../requestHandler/RemoteHttpRequestHandler';
21
24
  import { HttpRequestHandler, InternalHttpRequestHandler } from '../requestHandler/types/public';
@@ -54,7 +57,7 @@ class HttpInterceptorClient<
54
57
 
55
58
  private Handler: HandlerConstructor;
56
59
 
57
- private handlerClientsByMethod: {
60
+ private handlers: {
58
61
  [Method in HttpMethod]: Map<string, AnyHttpRequestHandlerClient[]>;
59
62
  } = {
60
63
  GET: new Map(),
@@ -229,21 +232,23 @@ class HttpInterceptorClient<
229
232
  Path extends HttpSchemaPath<Schema, Method>,
230
233
  StatusCode extends HttpStatusCode = never,
231
234
  >(handler: InternalHttpRequestHandler<Schema, Method, Path, StatusCode>) {
232
- const handlerClients = this.handlerClientsByMethod[handler.method].get(handler.path) ?? [];
235
+ const pathHandlers = this.handlers[handler.method].get(handler.path) ?? [];
236
+
237
+ const isAlreadyRegistered = pathHandlers.includes(handler.client);
233
238
 
234
- const isAlreadyRegistered = handlerClients.includes(handler.client);
235
239
  if (isAlreadyRegistered) {
236
240
  return;
237
241
  }
238
242
 
239
- handlerClients.push(handler.client);
243
+ pathHandlers.push(handler.client);
244
+
245
+ const isFirstHandlerForMethodPath = pathHandlers.length === 1;
240
246
 
241
- const isFirstHandlerForMethodPath = handlerClients.length === 1;
242
247
  if (!isFirstHandlerForMethodPath) {
243
248
  return;
244
249
  }
245
250
 
246
- this.handlerClientsByMethod[handler.method].set(handler.path, handlerClients);
251
+ this.handlers[handler.method].set(handler.path, pathHandlers);
247
252
 
248
253
  const pathRegex = createRegexFromPath(handler.path);
249
254
 
@@ -312,18 +317,45 @@ class HttpInterceptorClient<
312
317
  >(
313
318
  method: Method,
314
319
  path: Path,
315
- parsedRequest: HttpInterceptorRequest<Path, Default<Schema[Path][Method]>>,
320
+ request: HttpInterceptorRequest<Path, Default<Schema[Path][Method]>>,
316
321
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
317
322
  ): Promise<HttpRequestHandlerClient<Schema, Method, Path, any> | undefined> {
318
323
  /* istanbul ignore next -- @preserve
319
324
  * Ignoring because there will always be a handler for the given method and path at this point. */
320
- const handlersByPath = this.handlerClientsByMethod[method].get(path) ?? [];
325
+ const pathHandlers = this.handlers[method].get(path) ?? [];
326
+
327
+ const failedRequestMatches = new Map<
328
+ AnyHttpRequestHandlerClient,
329
+ Extract<HttpRequestHandlerRequestMatch, { success: false }>
330
+ >();
331
+
332
+ // If we find a matching handler that can accept more requests, we return it immediately.
333
+ for (let handlerIndex = pathHandlers.length - 1; handlerIndex >= 0; handlerIndex--) {
334
+ const handler = pathHandlers[handlerIndex];
335
+ const requestMatch = await handler.matchesRequest(request);
321
336
 
322
- for (let handlerIndex = handlersByPath.length - 1; handlerIndex >= 0; handlerIndex--) {
323
- const handler = handlersByPath[handlerIndex];
324
- if (await handler.matchesRequest(parsedRequest)) {
337
+ if (requestMatch.success) {
338
+ handler.markRequestAsMatched(request);
325
339
  return handler;
326
340
  }
341
+
342
+ failedRequestMatches.set(handler, requestMatch);
343
+ }
344
+
345
+ // If no handler matched or could accept more requests, we iterate again over the handlers to check which ones
346
+ // could have matched considering only restrictions.
347
+ for (let handlerIndex = pathHandlers.length - 1; handlerIndex >= 0; handlerIndex--) {
348
+ const handler = pathHandlers[handlerIndex];
349
+ const requestMatch = failedRequestMatches.get(handler);
350
+
351
+ // Handlers that did not match due to anything other than restrictions are still marked as matched to trigger a
352
+ // times check error.
353
+ if (requestMatch?.cause === 'unmatchedRestrictions') {
354
+ handler.markRequestAsUnmatched(request, { diff: requestMatch.diff });
355
+ } else {
356
+ handler.markRequestAsMatched(request);
357
+ break;
358
+ }
327
359
  }
328
360
 
329
361
  return undefined;
@@ -331,10 +363,11 @@ class HttpInterceptorClient<
331
363
 
332
364
  checkTimes() {
333
365
  for (const method of HTTP_METHODS) {
334
- const handlersByPath = this.handlerClientsByMethod[method];
366
+ const pathHandlers = this.handlers[method];
335
367
 
336
- for (const handlers of handlersByPath.values()) {
337
- for (const handler of handlers) {
368
+ for (const handlers of pathHandlers.values()) {
369
+ for (let handlerIndex = handlers.length - 1; handlerIndex >= 0; handlerIndex--) {
370
+ const handler = handlers[handlerIndex];
338
371
  handler.checkTimes();
339
372
  }
340
373
  }
@@ -353,8 +386,8 @@ class HttpInterceptorClient<
353
386
  clearResults.push(Promise.resolve(result));
354
387
  }
355
388
 
356
- const handlersByPath = this.handlerClientsByMethod[method];
357
- handlersByPath.clear();
389
+ const pathHandlers = this.handlers[method];
390
+ pathHandlers.clear();
358
391
  }
359
392
 
360
393
  if (options.onCommitSuccess) {
@@ -363,10 +396,10 @@ class HttpInterceptorClient<
363
396
  }
364
397
 
365
398
  private clearMethodHandlers(method: HttpMethod) {
366
- const handlersByPath = this.handlerClientsByMethod[method];
399
+ const pathHandlers = this.handlers[method];
367
400
  const clearResults: PossiblePromise<AnyHttpRequestHandlerClient>[] = [];
368
401
 
369
- for (const handlers of handlersByPath.values()) {
402
+ for (const handlers of pathHandlers.values()) {
370
403
  for (const handler of handlers) {
371
404
  clearResults.push(handler.clear());
372
405
  }
@@ -43,6 +43,11 @@ const DEFAULT_NUMBER_OF_REQUEST_LIMITS: Range<number> = Object.freeze({
43
43
  max: Infinity,
44
44
  });
45
45
 
46
+ export type HttpRequestHandlerRequestMatch =
47
+ | { success: true }
48
+ | { success: false; cause: 'missingResponseDeclaration' | 'exceededNumberOfRequests' }
49
+ | { success: false; cause: 'unmatchedRestrictions'; diff: RestrictionDiffs };
50
+
46
51
  class HttpRequestHandlerClient<
47
52
  Schema extends HttpSchema,
48
53
  Method extends HttpSchemaMethod<Schema>,
@@ -158,33 +163,49 @@ class HttpRequestHandlerClient<
158
163
  return this;
159
164
  }
160
165
 
161
- async matchesRequest(request: HttpInterceptorRequest<Path, Default<Schema[Path][Method]>>): Promise<boolean> {
162
- const hasDeclaredResponse = this.createResponseDeclaration !== undefined;
166
+ async matchesRequest(
167
+ request: HttpInterceptorRequest<Path, Default<Schema[Path][Method]>>,
168
+ ): Promise<HttpRequestHandlerRequestMatch> {
169
+ const restrictionsMatch = await this.matchesRestrictions(request);
163
170
 
164
- const restrictionsMatch = await this.matchesRequestRestrictions(request);
171
+ if (!restrictionsMatch.success) {
172
+ return { success: false, cause: 'unmatchedRestrictions', diff: restrictionsMatch.diff };
173
+ }
165
174
 
166
- if (restrictionsMatch.success) {
167
- this.numberOfMatchedRequests++;
168
- } else {
169
- const shouldSaveUnmatchedGroup =
170
- this.interceptor.requestSaving.enabled &&
171
- this.restrictions.length > 0 &&
172
- this.timesDeclarationPointer !== undefined;
175
+ const hasResponseDeclaration = this.createResponseDeclaration !== undefined;
173
176
 
174
- if (shouldSaveUnmatchedGroup) {
175
- this.unmatchedRequestGroups.push({ request, diff: restrictionsMatch.diff });
176
- }
177
+ if (!hasResponseDeclaration) {
178
+ return { success: false, cause: 'missingResponseDeclaration' };
177
179
  }
178
180
 
179
- if (!hasDeclaredResponse) {
180
- return false;
181
+ const canAcceptMoreRequests = this.numberOfMatchedRequests < this.limits.numberOfRequests.max;
182
+
183
+ if (!canAcceptMoreRequests) {
184
+ return { success: false, cause: 'exceededNumberOfRequests' };
181
185
  }
182
186
 
183
- const isWithinLimits = this.numberOfMatchedRequests <= this.limits.numberOfRequests.max;
184
- return restrictionsMatch.success && isWithinLimits;
187
+ return { success: true };
188
+ }
189
+
190
+ markRequestAsMatched(_request: HttpInterceptorRequest<Path, Default<Schema[Path][Method]>>) {
191
+ this.numberOfMatchedRequests++;
185
192
  }
186
193
 
187
- private async matchesRequestRestrictions(
194
+ markRequestAsUnmatched(
195
+ request: HttpInterceptorRequest<Path, Default<Schema[Path][Method]>>,
196
+ options: { diff: RestrictionDiffs },
197
+ ) {
198
+ const shouldSaveUnmatchedRequests =
199
+ this.interceptor.requestSaving.enabled &&
200
+ this.restrictions.length > 0 &&
201
+ this.timesDeclarationPointer !== undefined;
202
+
203
+ if (shouldSaveUnmatchedRequests) {
204
+ this.unmatchedRequestGroups.push({ request, diff: options.diff });
205
+ }
206
+ }
207
+
208
+ async matchesRestrictions(
188
209
  request: HttpInterceptorRequest<Path, Default<Schema[Path][Method]>>,
189
210
  ): Promise<RestrictionMatchResult<RestrictionDiffs>> {
190
211
  for (const restriction of this.restrictions) {
@@ -392,6 +413,7 @@ class HttpRequestHandlerClient<
392
413
  if (!this.createResponseDeclaration) {
393
414
  throw new NoResponseDefinitionError();
394
415
  }
416
+
395
417
  const appliedDeclaration = await this.createResponseDeclaration(request);
396
418
  return appliedDeclaration;
397
419
  }
@@ -70,8 +70,18 @@ class LocalHttpRequestHandler<
70
70
  return this.client.requests;
71
71
  }
72
72
 
73
- matchesRequest(request: HttpInterceptorRequest<Path, Default<Schema[Path][Method]>>): Promise<boolean> {
74
- return this.client.matchesRequest(request);
73
+ async matchesRequest(request: HttpInterceptorRequest<Path, Default<Schema[Path][Method]>>) {
74
+ const requestMatch = await this.client.matchesRequest(request);
75
+
76
+ if (requestMatch.success) {
77
+ this.client.markRequestAsMatched(request);
78
+ } else if (requestMatch.cause === 'unmatchedRestrictions') {
79
+ this.client.markRequestAsUnmatched(request, { diff: requestMatch.diff });
80
+ } else {
81
+ this.client.markRequestAsMatched(request);
82
+ }
83
+
84
+ return requestMatch;
75
85
  }
76
86
 
77
87
  async applyResponseDeclaration(
@@ -110,8 +110,18 @@ class RemoteHttpRequestHandler<
110
110
  return this.client.requests;
111
111
  }
112
112
 
113
- matchesRequest(request: HttpInterceptorRequest<Path, Default<Schema[Path][Method]>>): Promise<boolean> {
114
- return this.client.matchesRequest(request);
113
+ async matchesRequest(request: HttpInterceptorRequest<Path, Default<Schema[Path][Method]>>) {
114
+ const requestMatch = await this.client.matchesRequest(request);
115
+
116
+ if (requestMatch.success) {
117
+ this.client.markRequestAsMatched(request);
118
+ } else if (requestMatch.cause === 'unmatchedRestrictions') {
119
+ this.client.markRequestAsUnmatched(request, { diff: requestMatch.diff });
120
+ } else {
121
+ this.client.markRequestAsMatched(request);
122
+ }
123
+
124
+ return requestMatch;
115
125
  }
116
126
 
117
127
  async applyResponseDeclaration(
@@ -106,8 +106,8 @@ export type RestrictionMatchResult<Value> = { success: true; diff?: undefined }
106
106
 
107
107
  export interface RestrictionDiffs {
108
108
  computed?: RestrictionDiff<boolean>;
109
- headers?: RestrictionDiff<HttpHeaders<any>>; // eslint-disable-line @typescript-eslint/no-explicit-any
110
- searchParams?: RestrictionDiff<HttpSearchParams<any>>; // eslint-disable-line @typescript-eslint/no-explicit-any
109
+ headers?: RestrictionDiff<HttpHeaders>;
110
+ searchParams?: RestrictionDiff<HttpSearchParams>;
111
111
  body?: RestrictionDiff<unknown>;
112
112
  }
113
113
 
package/src/utils/http.ts CHANGED
@@ -94,10 +94,6 @@ export function getHttpServerPort(server: HttpServer) {
94
94
 
95
95
  export const HTTP_METHODS_WITH_REQUEST_BODY = new Set<HttpMethod>(['POST', 'PUT', 'PATCH', 'DELETE']);
96
96
 
97
- export function methodCanHaveRequestBody(method: HttpMethod) {
98
- return HTTP_METHODS_WITH_REQUEST_BODY.has(method);
99
- }
100
-
101
97
  export const HTTP_METHODS_WITH_RESPONSE_BODY = new Set<HttpMethod>([
102
98
  'GET',
103
99
  'POST',