@zimic/fetch 0.1.0-canary.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE.md +16 -0
- package/README.md +230 -0
- package/dist/index.d.ts +116 -0
- package/dist/index.js +191 -0
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +188 -0
- package/dist/index.mjs.map +1 -0
- package/index.d.ts +1 -0
- package/package.json +92 -0
- package/src/client/FetchClient.ts +232 -0
- package/src/client/errors/FetchResponseError.ts +26 -0
- package/src/client/factory.ts +13 -0
- package/src/client/types/json.ts +15 -0
- package/src/client/types/public.ts +83 -0
- package/src/client/types/requests.ts +149 -0
- package/src/index.ts +13 -0
- package/src/types/requests.ts +99 -0
- package/src/types/strings.d.ts +9 -0
- package/src/types/utils.ts +11 -0
- package/src/utils/files.ts +23 -0
- package/src/utils/imports.ts +14 -0
- package/src/utils/urls.ts +43 -0
package/LICENSE.md
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2023 Diego Aquino
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
|
|
6
|
+
documentation files (the "Software"), to deal in the Software without restriction, including without limitation the
|
|
7
|
+
rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit
|
|
8
|
+
persons to whom the Software is furnished to do so, subject to the following conditions:
|
|
9
|
+
|
|
10
|
+
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the
|
|
11
|
+
Software.
|
|
12
|
+
|
|
13
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
|
|
14
|
+
WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
|
15
|
+
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
|
|
16
|
+
OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,230 @@
|
|
|
1
|
+
<p align="center">
|
|
2
|
+
<img src="../../docs/zimic.png" align="center" width="100px" height="100px">
|
|
3
|
+
</p>
|
|
4
|
+
|
|
5
|
+
<h1 align="center">
|
|
6
|
+
Zimic
|
|
7
|
+
</h1>
|
|
8
|
+
|
|
9
|
+
<p align="center">
|
|
10
|
+
TypeScript-first HTTP request mocking
|
|
11
|
+
</p>
|
|
12
|
+
|
|
13
|
+
<p align="center">
|
|
14
|
+
<a href="https://www.npmjs.com/package/zimic">npm</a>
|
|
15
|
+
<span> • </span>
|
|
16
|
+
<a href="https://github.com/zimicjs/zimic/wiki">Docs</a>
|
|
17
|
+
<span> • </span>
|
|
18
|
+
<a href="#examples">Examples</a>
|
|
19
|
+
<span> • </span>
|
|
20
|
+
<a href="https://github.com/zimicjs/zimic/issues">Issues</a>
|
|
21
|
+
<span> • </span>
|
|
22
|
+
<a href="https://github.com/orgs/zimicjs/projects/1/views/5">Roadmap</a>
|
|
23
|
+
</p>
|
|
24
|
+
|
|
25
|
+
<div align="center">
|
|
26
|
+
|
|
27
|
+
[](https://github.com/zimicjs/zimic/actions/workflows/ci.yaml)
|
|
28
|
+
[](https://github.com/zimicjs/zimic/actions)
|
|
29
|
+
[](https://github.com/zimicjs/zimic/blob/canary/LICENSE.md)
|
|
30
|
+
[](https://www.npmjs.com/package/zimic)
|
|
31
|
+
[](https://github.com/zimicjs/zimic)
|
|
32
|
+
|
|
33
|
+
</div>
|
|
34
|
+
|
|
35
|
+
---
|
|
36
|
+
|
|
37
|
+
Zimic is a lightweight, thoroughly tested, TypeScript-first HTTP request mocking library, inspired by
|
|
38
|
+
[Zod](https://github.com/colinhacks/zod)'s type inference and using [MSW](https://github.com/mswjs/msw) under the hood.
|
|
39
|
+
|
|
40
|
+
## Features
|
|
41
|
+
|
|
42
|
+
Zimic provides a flexible and type-safe way to mock HTTP requests.
|
|
43
|
+
|
|
44
|
+
- :zap: **Statically-typed mocks**: Declare the
|
|
45
|
+
[schema](https://github.com/zimicjs/zimic/wiki/api‐zimic‐interceptor‐http‐schemas) of your HTTP endpoints and create
|
|
46
|
+
fully typed mocks. If you have an [OpenAPI v3](https://swagger.io/specification) schema, use
|
|
47
|
+
[`zimic typegen`](https://github.com/zimicjs/zimic/wiki/cli‐zimic‐typegen) to automatically generate types and keep
|
|
48
|
+
your mocks in sync with your API.
|
|
49
|
+
- :link: **Network-level intercepts**: Internally, Zimic combines [MSW](https://github.com/mswjs/msw) and
|
|
50
|
+
[interceptor servers](https://github.com/zimicjs/zimic/wiki/cli‐zimic‐server) to act on real HTTP requests. From you
|
|
51
|
+
application's point of view, the mocked responses are indistinguishable from the real ones.
|
|
52
|
+
- :wrench: **Flexibility**: Mock external services and reliably test how your application behaves. Simulate success,
|
|
53
|
+
loading, and error states with ease using [standard web APIs](https://developer.mozilla.org/docs/Web/API).
|
|
54
|
+
- :bulb: **Simplicity**: Zimic was designed to encourage clarity, simplicity, and robustness in your mocks. Check our
|
|
55
|
+
[getting started guide](https://github.com/zimicjs/zimic/wiki/getting‐started) and starting mocking!
|
|
56
|
+
|
|
57
|
+
```ts
|
|
58
|
+
import { type HttpSchema } from '@zimic/http';
|
|
59
|
+
import { httpInterceptor } from 'zimic/interceptor/http';
|
|
60
|
+
|
|
61
|
+
// 1. Declare your types:
|
|
62
|
+
interface User {
|
|
63
|
+
username: string;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
interface RequestError {
|
|
67
|
+
code: string;
|
|
68
|
+
message: string;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// 2. Declare your HTTP schema:
|
|
72
|
+
// https://bit.ly/zimic-interceptor-http-schemas
|
|
73
|
+
type MySchema = HttpSchema<{
|
|
74
|
+
'/users': {
|
|
75
|
+
POST: {
|
|
76
|
+
request: { body: User };
|
|
77
|
+
response: {
|
|
78
|
+
201: { body: User };
|
|
79
|
+
400: { body: RequestError };
|
|
80
|
+
409: { body: RequestError };
|
|
81
|
+
};
|
|
82
|
+
};
|
|
83
|
+
|
|
84
|
+
GET: {
|
|
85
|
+
request: {
|
|
86
|
+
headers: { authorization: string };
|
|
87
|
+
searchParams: {
|
|
88
|
+
username?: string;
|
|
89
|
+
limit?: `${number}`;
|
|
90
|
+
};
|
|
91
|
+
};
|
|
92
|
+
response: {
|
|
93
|
+
200: { body: User[] };
|
|
94
|
+
400: { body: RequestError };
|
|
95
|
+
401: { body: RequestError };
|
|
96
|
+
};
|
|
97
|
+
};
|
|
98
|
+
};
|
|
99
|
+
|
|
100
|
+
'/users/:userId': {
|
|
101
|
+
PATCH: {
|
|
102
|
+
request: {
|
|
103
|
+
headers: { authorization: string };
|
|
104
|
+
body: Partial<User>;
|
|
105
|
+
};
|
|
106
|
+
response: {
|
|
107
|
+
204: {};
|
|
108
|
+
400: { body: RequestError };
|
|
109
|
+
};
|
|
110
|
+
};
|
|
111
|
+
};
|
|
112
|
+
}>;
|
|
113
|
+
|
|
114
|
+
// 3. Create your interceptor:
|
|
115
|
+
// https://bit.ly/zimic-interceptor-http#httpinterceptorcreateoptions
|
|
116
|
+
const myInterceptor = httpInterceptor.create<MySchema>({
|
|
117
|
+
type: 'local',
|
|
118
|
+
baseURL: 'http://localhost:3000',
|
|
119
|
+
saveRequests: true, // Allow access to `handler.requests()`
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
// 4. Manage your interceptor lifecycle:
|
|
123
|
+
// https://bit.ly/zimic-guides-testing
|
|
124
|
+
beforeAll(async () => {
|
|
125
|
+
// 4.1. Start intercepting requests:
|
|
126
|
+
// https://bit.ly/zimic-interceptor-http#http-interceptorstart
|
|
127
|
+
await myInterceptor.start();
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
beforeEach(() => {
|
|
131
|
+
// 4.2. Clear interceptors so that no tests affect each other:
|
|
132
|
+
// https://bit.ly/zimic-interceptor-http#http-interceptorclear
|
|
133
|
+
myInterceptor.clear();
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
afterEach(() => {
|
|
137
|
+
// 4.3. Check that all expected requests were made:
|
|
138
|
+
// https://bit.ly/zimic-interceptor-http#http-interceptorchecktimes
|
|
139
|
+
myInterceptor.checkTimes();
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
afterAll(async () => {
|
|
143
|
+
// 4.4. Stop intercepting requests:
|
|
144
|
+
// https://bit.ly/zimic-interceptor-http#http-interceptorstop
|
|
145
|
+
await myInterceptor.stop();
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
// Enjoy mocking!
|
|
149
|
+
test('example', async () => {
|
|
150
|
+
const users: User[] = [{ username: 'my-user' }];
|
|
151
|
+
|
|
152
|
+
// 5. Declare your mocks:
|
|
153
|
+
// https://bit.ly/zimic-interceptor-http#http-interceptormethodpath
|
|
154
|
+
const myHandler = myInterceptor
|
|
155
|
+
.get('/users')
|
|
156
|
+
// 5.1. Use restrictions to make declarative assertions and narrow down your mocks:
|
|
157
|
+
// https://bit.ly/zimic-interceptor-http#http-handlerwithrestriction
|
|
158
|
+
.with({
|
|
159
|
+
headers: { authorization: 'Bearer my-token' },
|
|
160
|
+
searchParams: { username: 'my' },
|
|
161
|
+
})
|
|
162
|
+
// 5.2. Respond with your mock data:
|
|
163
|
+
// https://bit.ly/zimic-interceptor-http#http-handlerresponddeclaration
|
|
164
|
+
.respond({
|
|
165
|
+
status: 200,
|
|
166
|
+
body: users,
|
|
167
|
+
})
|
|
168
|
+
// 5.3. Define how many requests you expect your application to make:
|
|
169
|
+
// https://bit.ly/zimic-interceptor-http#http-handlertimes
|
|
170
|
+
.times(1);
|
|
171
|
+
|
|
172
|
+
// 6. Run your application and make requests:
|
|
173
|
+
// ...
|
|
174
|
+
|
|
175
|
+
// 7. Check the requests you expect:
|
|
176
|
+
// https://bit.ly/zimic-interceptor-http#http-handlerrequests
|
|
177
|
+
//
|
|
178
|
+
// NOTE: The code below checks the requests manually. This is optional in this
|
|
179
|
+
// example because the `with` and `times` calls act as a declarative validation,
|
|
180
|
+
// meaning that exactly one request is expected with specific data. If fewer or
|
|
181
|
+
// more requests are received, the test will fail when `myInterceptor.checkTimes()`
|
|
182
|
+
// is called in the `afterEach` hook.
|
|
183
|
+
const requests = myHandler.requests();
|
|
184
|
+
expect(requests).toHaveLength(1);
|
|
185
|
+
|
|
186
|
+
expect(requests[0].headers.get('authorization')).toBe('Bearer my-token');
|
|
187
|
+
|
|
188
|
+
expect(requests[0].searchParams.size).toBe(1);
|
|
189
|
+
expect(requests[0].searchParams.get('username')).toBe('my');
|
|
190
|
+
|
|
191
|
+
expect(requests[0].body).toBe(null);
|
|
192
|
+
});
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
## Documentation
|
|
196
|
+
|
|
197
|
+
- [Introduction](https://github.com/zimicjs/zimic/wiki)
|
|
198
|
+
- [Getting started](https://github.com/zimicjs/zimic/wiki/getting‐started)
|
|
199
|
+
- [API reference](https://github.com/zimicjs/zimic/wiki/api‐zimic)
|
|
200
|
+
- [CLI reference](https://github.com/zimicjs/zimic/wiki/cli‐zimic)
|
|
201
|
+
- Guides
|
|
202
|
+
- [Testing](https://github.com/zimicjs/zimic/wiki/guides‐testing)
|
|
203
|
+
|
|
204
|
+
> [!NOTE]
|
|
205
|
+
>
|
|
206
|
+
> Zimic has gone a long way in v0, but we're not yet v1!
|
|
207
|
+
>
|
|
208
|
+
> Reviews and improvements to the public API are possible, so breaking changes may **_exceptionally_** land without a
|
|
209
|
+
> major release during v0. Despite of that, we do not expect big mental model shifts. Usually, migrating to a new Zimic
|
|
210
|
+
> release requires minimal to no refactoring. During v0, we will follow these guidelines:
|
|
211
|
+
>
|
|
212
|
+
> - Breaking changes, if any, will be delivered in the next **_minor_** version.
|
|
213
|
+
> - Breaking changes, if any, will be documented in the [version release](https://github.com/zimicjs/zimic/releases),
|
|
214
|
+
> along with a migration guide detailing the introduced changes and suggesting steps to migrate.
|
|
215
|
+
>
|
|
216
|
+
> From v0.8 onwards, we expect Zimic's public API to become more stable. If you'd like to share any feedback, please
|
|
217
|
+
> feel free to [open an issue](https://github.com/zimicjs/zimic/issues) or
|
|
218
|
+
> [create a discussion](https://github.com/zimicjs/zimic/discussions/new/choose)!
|
|
219
|
+
|
|
220
|
+
## Examples
|
|
221
|
+
|
|
222
|
+
Visit our [examples](../../examples/README.md) to see how to use Zimic with popular frameworks, libraries, and use cases!
|
|
223
|
+
|
|
224
|
+
## Changelog
|
|
225
|
+
|
|
226
|
+
The changelog is available on our [GitHub Releases](https://github.com/zimicjs/zimic/releases) page.
|
|
227
|
+
|
|
228
|
+
## Contributing
|
|
229
|
+
|
|
230
|
+
Interested in contributing to Zimic? Check out our [contributing guide](../../CONTRIBUTING.md) to get started!
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
import { HttpSchema, HttpSchemaPath, HttpSchemaMethod, HttpMethod, HttpSearchParams, HttpMethodSchema, HttpRequest, HttpRequestHeadersSchema, AllowAnyStringInPathParams, LiteralHttpSchemaPathFromNonLiteral, HttpRequestSchema, HttpStatusCode, HttpResponse, HttpResponseBodySchema, HttpResponseHeadersSchema, HttpHeaders, JSONValue, HttpResponseSchemaStatusCode } from '@zimic/http';
|
|
2
|
+
|
|
3
|
+
type Default<Type, IfEmpty = never> = [undefined | void] extends [Type] ? IfEmpty : Exclude<Type, undefined | void>;
|
|
4
|
+
type DefaultNoExclude<Type, IfEmpty = never> = [undefined | void] extends Type ? IfEmpty : Type;
|
|
5
|
+
type IfNever<Type, Yes, No = Type> = [Type] extends [never] ? Yes : No;
|
|
6
|
+
type PossiblePromise<Type> = Type | PromiseLike<Type>;
|
|
7
|
+
type ReplaceBy<Type, Source, Target> = Type extends Source ? Target : Type;
|
|
8
|
+
|
|
9
|
+
declare const value: unique symbol;
|
|
10
|
+
type JSONStringified<Value> = string & {
|
|
11
|
+
[value]: Value;
|
|
12
|
+
};
|
|
13
|
+
declare global {
|
|
14
|
+
interface JSON {
|
|
15
|
+
stringify<Value>(value: Value, replacer?: ((this: any, key: string, value: Value) => any) | (number | string)[] | null, space?: string | number): JSONStringified<Value>;
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
type FetchRequestInitWithHeaders<RequestSchema extends HttpRequestSchema> = [RequestSchema['headers']] extends [never] ? {
|
|
20
|
+
headers?: undefined;
|
|
21
|
+
} : undefined extends RequestSchema['headers'] ? {
|
|
22
|
+
headers?: RequestSchema['headers'] | HttpHeaders<Default<RequestSchema['headers']>>;
|
|
23
|
+
} : {
|
|
24
|
+
headers: RequestSchema['headers'] | HttpHeaders<Default<RequestSchema['headers']>>;
|
|
25
|
+
};
|
|
26
|
+
type FetchRequestInitWithSearchParams<RequestSchema extends HttpRequestSchema> = [
|
|
27
|
+
RequestSchema['searchParams']
|
|
28
|
+
] extends [never] ? {
|
|
29
|
+
searchParams?: undefined;
|
|
30
|
+
} : undefined extends RequestSchema['searchParams'] ? {
|
|
31
|
+
searchParams?: RequestSchema['searchParams'] | HttpSearchParams<Default<RequestSchema['searchParams']>>;
|
|
32
|
+
} : {
|
|
33
|
+
searchParams: RequestSchema['searchParams'] | HttpSearchParams<Default<RequestSchema['searchParams']>>;
|
|
34
|
+
};
|
|
35
|
+
type FetchRequestInitWithBody<RequestSchema extends HttpRequestSchema> = [RequestSchema['body']] extends [never] ? {
|
|
36
|
+
body?: null;
|
|
37
|
+
} : undefined extends RequestSchema['body'] ? {
|
|
38
|
+
body?: ReplaceBy<RequestSchema['body'], undefined, null>;
|
|
39
|
+
} : RequestSchema['body'] extends string ? {
|
|
40
|
+
body: RequestSchema['body'];
|
|
41
|
+
} : RequestSchema['body'] extends JSONValue ? {
|
|
42
|
+
body: JSONStringified<RequestSchema['body']>;
|
|
43
|
+
} : {
|
|
44
|
+
body: RequestSchema['body'];
|
|
45
|
+
};
|
|
46
|
+
type FetchRequestInitPerPath<RequestSchema extends HttpRequestSchema> = FetchRequestInitWithHeaders<RequestSchema> & FetchRequestInitWithSearchParams<RequestSchema> & FetchRequestInitWithBody<RequestSchema>;
|
|
47
|
+
type FetchRequestInit<Schema extends HttpSchema, Path extends HttpSchemaPath<Schema, Method>, Method extends HttpSchemaMethod<Schema>> = RequestInit & {
|
|
48
|
+
baseURL?: string;
|
|
49
|
+
method: Method;
|
|
50
|
+
} & (Path extends Path ? FetchRequestInitPerPath<Default<Default<Schema[Path][Method]>['request']>> : never);
|
|
51
|
+
declare namespace FetchRequestInit {
|
|
52
|
+
interface Defaults extends RequestInit {
|
|
53
|
+
baseURL: string;
|
|
54
|
+
method?: HttpMethod;
|
|
55
|
+
searchParams?: HttpSearchParams;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
type AllFetchResponseStatusCode<MethodSchema extends HttpMethodSchema> = HttpResponseSchemaStatusCode<Default<MethodSchema['response']>>;
|
|
59
|
+
type FetchResponseStatusCode<MethodSchema extends HttpMethodSchema, ErrorOnly extends boolean> = ErrorOnly extends true ? AllFetchResponseStatusCode<MethodSchema> & (HttpStatusCode.ClientError | HttpStatusCode.ServerError) : AllFetchResponseStatusCode<MethodSchema>;
|
|
60
|
+
type HttpRequestBodySchema<MethodSchema extends HttpMethodSchema> = ReplaceBy<ReplaceBy<IfNever<DefaultNoExclude<Default<MethodSchema['request']>['body']>, null>, undefined, null>, ArrayBuffer, Blob>;
|
|
61
|
+
interface FetchRequest<Path extends string = string, Method extends HttpMethod = HttpMethod, MethodSchema extends HttpMethodSchema = HttpMethodSchema> extends HttpRequest<HttpRequestBodySchema<MethodSchema>, HttpRequestHeadersSchema<MethodSchema>> {
|
|
62
|
+
path: AllowAnyStringInPathParams<Path>;
|
|
63
|
+
method: Method;
|
|
64
|
+
}
|
|
65
|
+
declare namespace FetchRequest {
|
|
66
|
+
interface Loose extends Request {
|
|
67
|
+
path: string;
|
|
68
|
+
method: HttpMethod;
|
|
69
|
+
clone: () => Loose;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
interface FetchResponsePerStatusCode<Path extends string = string, Method extends HttpMethod = HttpMethod, MethodSchema extends HttpMethodSchema = HttpMethodSchema, StatusCode extends HttpStatusCode = HttpStatusCode> extends HttpResponse<HttpResponseBodySchema<MethodSchema, StatusCode>, StatusCode, HttpResponseHeadersSchema<MethodSchema, StatusCode>> {
|
|
73
|
+
request: FetchRequest<Path, Method, MethodSchema>;
|
|
74
|
+
error: StatusCode extends HttpStatusCode.ClientError | HttpStatusCode.ServerError ? FetchResponseError<Path, Method, MethodSchema> : null;
|
|
75
|
+
}
|
|
76
|
+
type FetchResponse<Path extends string = string, Method extends HttpMethod = HttpMethod, MethodSchema extends HttpMethodSchema = HttpMethodSchema, ErrorOnly extends boolean = false, StatusCode extends FetchResponseStatusCode<MethodSchema, ErrorOnly> = FetchResponseStatusCode<MethodSchema, ErrorOnly>> = StatusCode extends StatusCode ? FetchResponsePerStatusCode<Path, Method, MethodSchema, StatusCode> : never;
|
|
77
|
+
declare namespace FetchResponse {
|
|
78
|
+
interface Loose extends Response {
|
|
79
|
+
request: FetchRequest.Loose;
|
|
80
|
+
error: AnyFetchRequestError | null;
|
|
81
|
+
clone: () => Loose;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
type FetchRequestConstructor<Schema extends HttpSchema> = new <Path extends HttpSchemaPath.NonLiteral<Schema, Method>, Method extends HttpSchemaMethod<Schema>>(input: FetchInput<Schema, Path, Method>, init: FetchRequestInit<Schema, LiteralHttpSchemaPathFromNonLiteral<Schema, Method, Path>, Method>) => FetchRequest<LiteralHttpSchemaPathFromNonLiteral<Schema, Method, Path>, Method, Default<Schema[LiteralHttpSchemaPathFromNonLiteral<Schema, Method, Path>][Method]>>;
|
|
85
|
+
|
|
86
|
+
declare class FetchResponseError<Path extends string = string, Method extends HttpMethod = HttpMethod, MethodSchema extends HttpMethodSchema = HttpMethodSchema> extends Error {
|
|
87
|
+
request: FetchRequest<Path, Method, MethodSchema>;
|
|
88
|
+
response: FetchResponse<Path, Method, MethodSchema, true>;
|
|
89
|
+
constructor(request: FetchRequest<Path, Method, MethodSchema>, response: FetchResponse<Path, Method, MethodSchema, true>);
|
|
90
|
+
get cause(): FetchResponse<Path, Method, MethodSchema, true>;
|
|
91
|
+
}
|
|
92
|
+
type AnyFetchRequestError = FetchResponseError<any, any, any>;
|
|
93
|
+
|
|
94
|
+
type FetchInput<Schema extends HttpSchema, Path extends HttpSchemaPath.NonLiteral<Schema, Method>, Method extends HttpSchemaMethod<Schema>> = Path | URL | FetchRequest<LiteralHttpSchemaPathFromNonLiteral<Schema, Method, Path>, Method, Default<Schema[LiteralHttpSchemaPathFromNonLiteral<Schema, Method, Path>][Method]>>;
|
|
95
|
+
interface FetchFunction<Schema extends HttpSchema> {
|
|
96
|
+
<Path extends HttpSchemaPath.NonLiteral<Schema, Method>, Method extends HttpSchemaMethod<Schema>>(input: Path | URL, init: FetchRequestInit<Schema, LiteralHttpSchemaPathFromNonLiteral<Schema, Method, Path>, Method>): Promise<FetchResponse<LiteralHttpSchemaPathFromNonLiteral<Schema, Method, Path>, Method, Default<Schema[LiteralHttpSchemaPathFromNonLiteral<Schema, Method, Path>][Method]>>>;
|
|
97
|
+
<Path extends HttpSchemaPath.NonLiteral<Schema, Method>, Method extends HttpSchemaMethod<Schema>>(input: FetchRequest<LiteralHttpSchemaPathFromNonLiteral<Schema, Method, Path>, Method, Default<Schema[LiteralHttpSchemaPathFromNonLiteral<Schema, Method, Path>][Method]>>, init?: FetchRequestInit<Schema, LiteralHttpSchemaPathFromNonLiteral<Schema, Method, Path>, Method>): Promise<FetchResponse<LiteralHttpSchemaPathFromNonLiteral<Schema, Method, Path>, Method, Default<Schema[LiteralHttpSchemaPathFromNonLiteral<Schema, Method, Path>][Method]>>>;
|
|
98
|
+
}
|
|
99
|
+
interface FetchOptions<Schema extends HttpSchema> extends Omit<FetchRequestInit.Defaults, 'method'> {
|
|
100
|
+
onRequest?: (request: FetchRequest.Loose, fetch: Fetch<Schema>) => PossiblePromise<Request>;
|
|
101
|
+
onResponse?: (response: FetchResponse.Loose, fetch: Fetch<Schema>) => PossiblePromise<Response>;
|
|
102
|
+
}
|
|
103
|
+
interface FetchClient<Schema extends HttpSchema> {
|
|
104
|
+
defaults: FetchRequestInit.Defaults;
|
|
105
|
+
Request: FetchRequestConstructor<Schema>;
|
|
106
|
+
onRequest?: FetchOptions<Schema>['onRequest'];
|
|
107
|
+
onResponse?: FetchOptions<Schema>['onResponse'];
|
|
108
|
+
isRequest: <Path extends HttpSchemaPath<Schema, Method>, Method extends HttpSchemaMethod<Schema>>(request: unknown, path: Path, method: Method) => request is FetchRequest<Path, Method, Default<Schema[Path][Method]>>;
|
|
109
|
+
isResponse: <Path extends HttpSchemaPath<Schema, Method>, Method extends HttpSchemaMethod<Schema>>(response: unknown, path: Path, method: Method) => response is FetchResponse<Path, Method, Default<Schema[Path][Method]>>;
|
|
110
|
+
isResponseError: <Path extends HttpSchemaPath<Schema, Method>, Method extends HttpSchemaMethod<Schema>>(error: unknown, path: Path, method: Method) => error is FetchResponseError<Path, Method, Default<Schema[Path][Method]>>;
|
|
111
|
+
}
|
|
112
|
+
type Fetch<Schema extends HttpSchema> = FetchFunction<Schema> & FetchClient<Schema>;
|
|
113
|
+
|
|
114
|
+
declare function createFetch<Schema extends HttpSchema>(options: FetchOptions<HttpSchema<Schema>>): Fetch<HttpSchema<Schema>>;
|
|
115
|
+
|
|
116
|
+
export { type Fetch, type FetchClient, type FetchOptions as FetchClientOptions, type FetchFunction, type FetchInput, FetchRequest, type FetchRequestConstructor, FetchRequestInit, FetchResponse, FetchResponseError, createFetch };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var http = require('@zimic/http');
|
|
4
|
+
|
|
5
|
+
var __defProp = Object.defineProperty;
|
|
6
|
+
var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
|
|
7
|
+
|
|
8
|
+
// src/client/errors/FetchResponseError.ts
|
|
9
|
+
var FetchResponseError = class extends Error {
|
|
10
|
+
constructor(request, response) {
|
|
11
|
+
super(`${request.method} ${request.url} failed with status ${response.status}: ${response.statusText}`);
|
|
12
|
+
this.request = request;
|
|
13
|
+
this.response = response;
|
|
14
|
+
this.name = "FetchResponseError";
|
|
15
|
+
}
|
|
16
|
+
static {
|
|
17
|
+
__name(this, "FetchResponseError");
|
|
18
|
+
}
|
|
19
|
+
get cause() {
|
|
20
|
+
return this.response;
|
|
21
|
+
}
|
|
22
|
+
};
|
|
23
|
+
var FetchResponseError_default = FetchResponseError;
|
|
24
|
+
|
|
25
|
+
// src/utils/urls.ts
|
|
26
|
+
function excludeNonPathParams(url) {
|
|
27
|
+
url.hash = "";
|
|
28
|
+
url.search = "";
|
|
29
|
+
url.username = "";
|
|
30
|
+
url.password = "";
|
|
31
|
+
return url;
|
|
32
|
+
}
|
|
33
|
+
__name(excludeNonPathParams, "excludeNonPathParams");
|
|
34
|
+
function prepareURLForRegex(url) {
|
|
35
|
+
const encodedURL = encodeURI(url);
|
|
36
|
+
return encodedURL.replace(/([.()*?+$\\])/g, "\\$1");
|
|
37
|
+
}
|
|
38
|
+
__name(prepareURLForRegex, "prepareURLForRegex");
|
|
39
|
+
var URL_PATH_PARAM_REGEX = /\/:([^/]+)/g;
|
|
40
|
+
function createRegexFromURL(url) {
|
|
41
|
+
const urlWithReplacedPathParams = prepareURLForRegex(url).replace(URL_PATH_PARAM_REGEX, "/(?<$1>[^/]+)").replace(/(\/+)$/, "(?:/+)?");
|
|
42
|
+
return new RegExp(`^${urlWithReplacedPathParams}$`);
|
|
43
|
+
}
|
|
44
|
+
__name(createRegexFromURL, "createRegexFromURL");
|
|
45
|
+
function joinURL(...parts) {
|
|
46
|
+
return parts.map((part, index) => {
|
|
47
|
+
const isFirstPart = index === 0;
|
|
48
|
+
const isLastPart = index === parts.length - 1;
|
|
49
|
+
let partAsString = part.toString();
|
|
50
|
+
if (!isFirstPart) {
|
|
51
|
+
partAsString = partAsString.replace(/^\//, "");
|
|
52
|
+
}
|
|
53
|
+
if (!isLastPart) {
|
|
54
|
+
partAsString = partAsString.replace(/\/$/, "");
|
|
55
|
+
}
|
|
56
|
+
return partAsString;
|
|
57
|
+
}).filter((part) => part.length > 0).join("/");
|
|
58
|
+
}
|
|
59
|
+
__name(joinURL, "joinURL");
|
|
60
|
+
|
|
61
|
+
// src/client/FetchClient.ts
|
|
62
|
+
var FetchClient = class {
|
|
63
|
+
static {
|
|
64
|
+
__name(this, "FetchClient");
|
|
65
|
+
}
|
|
66
|
+
fetch;
|
|
67
|
+
constructor({ onRequest, onResponse, ...defaults }) {
|
|
68
|
+
this.fetch = this.createFetchFunction();
|
|
69
|
+
this.fetch.defaults = defaults;
|
|
70
|
+
this.fetch.Request = this.createRequestClass(defaults);
|
|
71
|
+
this.fetch.onRequest = onRequest;
|
|
72
|
+
this.fetch.onResponse = onResponse;
|
|
73
|
+
}
|
|
74
|
+
createFetchFunction() {
|
|
75
|
+
const fetch = /* @__PURE__ */ __name(async (input, init) => {
|
|
76
|
+
const request = await this.createFetchRequest(input, init);
|
|
77
|
+
const requestClone = request.clone();
|
|
78
|
+
const rawResponse = await globalThis.fetch(
|
|
79
|
+
// Optimize type checking by narrowing the type of request
|
|
80
|
+
requestClone
|
|
81
|
+
);
|
|
82
|
+
const response = await this.createFetchResponse(request, rawResponse);
|
|
83
|
+
return response;
|
|
84
|
+
}, "fetch");
|
|
85
|
+
Object.setPrototypeOf(fetch, this);
|
|
86
|
+
return fetch;
|
|
87
|
+
}
|
|
88
|
+
async createFetchRequest(input, init) {
|
|
89
|
+
let request = input instanceof Request ? input : new this.fetch.Request(input, init);
|
|
90
|
+
if (this.fetch.onRequest) {
|
|
91
|
+
const requestAfterInterceptor = await this.fetch.onRequest(
|
|
92
|
+
// Optimize type checking by narrowing the type of request
|
|
93
|
+
request,
|
|
94
|
+
this.fetch
|
|
95
|
+
);
|
|
96
|
+
if (requestAfterInterceptor !== request) {
|
|
97
|
+
const isFetchRequest = requestAfterInterceptor instanceof this.fetch.Request;
|
|
98
|
+
request = isFetchRequest ? requestAfterInterceptor : new this.fetch.Request(requestAfterInterceptor, init);
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
return request;
|
|
102
|
+
}
|
|
103
|
+
async createFetchResponse(fetchRequest, rawResponse) {
|
|
104
|
+
let response = this.defineFetchResponseProperties(fetchRequest, rawResponse);
|
|
105
|
+
if (this.fetch.onResponse) {
|
|
106
|
+
const responseAfterInterceptor = await this.fetch.onResponse(
|
|
107
|
+
// Optimize type checking by narrowing the type of response
|
|
108
|
+
response,
|
|
109
|
+
this.fetch
|
|
110
|
+
);
|
|
111
|
+
const isFetchResponse = responseAfterInterceptor instanceof Response && "request" in responseAfterInterceptor && responseAfterInterceptor.request instanceof this.fetch.Request;
|
|
112
|
+
response = isFetchResponse ? responseAfterInterceptor : this.defineFetchResponseProperties(fetchRequest, responseAfterInterceptor);
|
|
113
|
+
}
|
|
114
|
+
return response;
|
|
115
|
+
}
|
|
116
|
+
defineFetchResponseProperties(fetchRequest, response) {
|
|
117
|
+
const fetchResponse = response;
|
|
118
|
+
Object.defineProperty(fetchResponse, "request", {
|
|
119
|
+
value: fetchRequest,
|
|
120
|
+
writable: false,
|
|
121
|
+
enumerable: true,
|
|
122
|
+
configurable: false
|
|
123
|
+
});
|
|
124
|
+
const responseError = fetchResponse.ok ? null : new FetchResponseError_default(fetchRequest, fetchResponse);
|
|
125
|
+
Object.defineProperty(fetchResponse, "error", {
|
|
126
|
+
value: responseError,
|
|
127
|
+
writable: false,
|
|
128
|
+
enumerable: true,
|
|
129
|
+
configurable: false
|
|
130
|
+
});
|
|
131
|
+
return fetchResponse;
|
|
132
|
+
}
|
|
133
|
+
createRequestClass(defaults) {
|
|
134
|
+
class Request2 extends globalThis.Request {
|
|
135
|
+
static {
|
|
136
|
+
__name(this, "Request");
|
|
137
|
+
}
|
|
138
|
+
path;
|
|
139
|
+
constructor(input, rawInit) {
|
|
140
|
+
const init = { ...defaults, ...rawInit };
|
|
141
|
+
let url;
|
|
142
|
+
if (input instanceof globalThis.Request) {
|
|
143
|
+
super(
|
|
144
|
+
// Optimize type checking by narrowing the type of input
|
|
145
|
+
input,
|
|
146
|
+
init
|
|
147
|
+
);
|
|
148
|
+
url = new URL(input.url);
|
|
149
|
+
} else {
|
|
150
|
+
url = input instanceof URL ? new URL(input) : new URL(joinURL(init.baseURL, input));
|
|
151
|
+
if (init.searchParams) {
|
|
152
|
+
url.search = new http.HttpSearchParams(init.searchParams).toString();
|
|
153
|
+
}
|
|
154
|
+
super(url, init);
|
|
155
|
+
}
|
|
156
|
+
this.path = excludeNonPathParams(url).toString().replace(init.baseURL, "");
|
|
157
|
+
}
|
|
158
|
+
clone() {
|
|
159
|
+
const rawClone = super.clone();
|
|
160
|
+
return new Request2(
|
|
161
|
+
rawClone,
|
|
162
|
+
rawClone
|
|
163
|
+
);
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
return Request2;
|
|
167
|
+
}
|
|
168
|
+
isRequest(request, path, method) {
|
|
169
|
+
return request instanceof Request && request.method === method && "path" in request && typeof request.path === "string" && createRegexFromURL(path).test(request.path);
|
|
170
|
+
}
|
|
171
|
+
isResponse(response, path, method) {
|
|
172
|
+
return response instanceof Response && "request" in response && "error" in response && this.isRequest(response.request, path, method);
|
|
173
|
+
}
|
|
174
|
+
isResponseError(error, path, method) {
|
|
175
|
+
return error instanceof FetchResponseError_default && error.request.method === method && typeof error.request.path === "string" && createRegexFromURL(path).test(error.request.path);
|
|
176
|
+
}
|
|
177
|
+
};
|
|
178
|
+
var FetchClient_default = FetchClient;
|
|
179
|
+
|
|
180
|
+
// src/client/factory.ts
|
|
181
|
+
function createFetch(options) {
|
|
182
|
+
const { fetch } = new FetchClient_default(options);
|
|
183
|
+
return fetch;
|
|
184
|
+
}
|
|
185
|
+
__name(createFetch, "createFetch");
|
|
186
|
+
var factory_default = createFetch;
|
|
187
|
+
|
|
188
|
+
exports.FetchResponseError = FetchResponseError_default;
|
|
189
|
+
exports.createFetch = factory_default;
|
|
190
|
+
//# sourceMappingURL=index.js.map
|
|
191
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/client/errors/FetchResponseError.ts","../src/utils/urls.ts","../src/client/FetchClient.ts","../src/client/factory.ts"],"names":["Request","HttpSearchParams"],"mappings":"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","file":"index.js","sourcesContent":["import { HttpMethod, HttpMethodSchema } from '@zimic/http';\n\nimport { FetchRequest, FetchResponse } from '../types/requests';\n\nclass FetchResponseError<\n Path extends string = string,\n Method extends HttpMethod = HttpMethod,\n MethodSchema extends HttpMethodSchema = HttpMethodSchema,\n> extends Error {\n constructor(\n public request: FetchRequest<Path, Method, MethodSchema>,\n public response: FetchResponse<Path, Method, MethodSchema, true>,\n ) {\n super(`${request.method} ${request.url} failed with status ${response.status}: ${response.statusText}`);\n this.name = 'FetchResponseError';\n }\n\n get cause() {\n return this.response;\n }\n}\n\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nexport type AnyFetchRequestError = FetchResponseError<any, any, any>;\n\nexport default FetchResponseError;\n","export function excludeNonPathParams(url: URL) {\n url.hash = '';\n url.search = '';\n url.username = '';\n url.password = '';\n return url;\n}\n\nfunction prepareURLForRegex(url: string) {\n const encodedURL = encodeURI(url);\n return encodedURL.replace(/([.()*?+$\\\\])/g, '\\\\$1');\n}\n\nconst URL_PATH_PARAM_REGEX = /\\/:([^/]+)/g;\n\nexport function createRegexFromURL(url: string) {\n const urlWithReplacedPathParams = prepareURLForRegex(url)\n .replace(URL_PATH_PARAM_REGEX, '/(?<$1>[^/]+)')\n .replace(/(\\/+)$/, '(?:/+)?');\n\n return new RegExp(`^${urlWithReplacedPathParams}$`);\n}\n\nexport function joinURL(...parts: (string | URL)[]) {\n return parts\n .map((part, index) => {\n const isFirstPart = index === 0;\n const isLastPart = index === parts.length - 1;\n\n let partAsString = part.toString();\n\n if (!isFirstPart) {\n partAsString = partAsString.replace(/^\\//, '');\n }\n if (!isLastPart) {\n partAsString = partAsString.replace(/\\/$/, '');\n }\n\n return partAsString;\n })\n .filter((part) => part.length > 0)\n .join('/');\n}\n","import {\n HttpSchemaPath,\n HttpSchemaMethod,\n HttpSearchParams,\n LiteralHttpSchemaPathFromNonLiteral,\n HttpSchema,\n} from '@zimic/http';\n\nimport { Default } from '@/types/utils';\nimport { createRegexFromURL, excludeNonPathParams, joinURL } from '@/utils/urls';\n\nimport FetchResponseError from './errors/FetchResponseError';\nimport { FetchInput, FetchOptions, Fetch } from './types/public';\nimport { FetchRequestConstructor, FetchRequestInit, FetchRequest, FetchResponse } from './types/requests';\n\nclass FetchClient<Schema extends HttpSchema> {\n fetch: Fetch<Schema>;\n\n constructor({ onRequest, onResponse, ...defaults }: FetchOptions<Schema>) {\n this.fetch = this.createFetchFunction();\n this.fetch.defaults = defaults;\n this.fetch.Request = this.createRequestClass(defaults);\n this.fetch.onRequest = onRequest;\n this.fetch.onResponse = onResponse;\n }\n\n private createFetchFunction() {\n const fetch = async <\n Path extends HttpSchemaPath.NonLiteral<Schema, Method>,\n Method extends HttpSchemaMethod<Schema>,\n >(\n input: FetchInput<Schema, Path, Method>,\n init: FetchRequestInit<Schema, LiteralHttpSchemaPathFromNonLiteral<Schema, Method, Path>, Method>,\n ) => {\n const request = await this.createFetchRequest<Path, Method>(input, init);\n const requestClone = request.clone();\n\n const rawResponse = await globalThis.fetch(\n // Optimize type checking by narrowing the type of request\n requestClone as Request,\n );\n const response = await this.createFetchResponse<\n LiteralHttpSchemaPathFromNonLiteral<Schema, Method, Path>,\n Method\n >(request, rawResponse);\n\n return response;\n };\n\n Object.setPrototypeOf(fetch, this);\n\n return fetch as Fetch<Schema>;\n }\n\n private async createFetchRequest<\n Path extends HttpSchemaPath.NonLiteral<Schema, Method>,\n Method extends HttpSchemaMethod<Schema>,\n >(\n input: FetchInput<Schema, Path, Method>,\n init: FetchRequestInit<Schema, LiteralHttpSchemaPathFromNonLiteral<Schema, Method, Path>, Method>,\n ) {\n let request = input instanceof Request ? input : new this.fetch.Request(input, init);\n\n if (this.fetch.onRequest) {\n const requestAfterInterceptor = await this.fetch.onRequest(\n // Optimize type checking by narrowing the type of request\n request as FetchRequest.Loose,\n this.fetch,\n );\n\n if (requestAfterInterceptor !== request) {\n const isFetchRequest = requestAfterInterceptor instanceof this.fetch.Request;\n\n request = isFetchRequest\n ? (requestAfterInterceptor as Request as typeof request)\n : new this.fetch.Request(requestAfterInterceptor as FetchInput<Schema, Path, Method>, init);\n }\n }\n\n return request;\n }\n\n private async createFetchResponse<\n Path extends HttpSchemaPath<Schema, Method>,\n Method extends HttpSchemaMethod<Schema>,\n >(fetchRequest: FetchRequest<Path, Method, Default<Schema[Path][Method]>>, rawResponse: Response) {\n let response = this.defineFetchResponseProperties<Path, Method>(fetchRequest, rawResponse);\n\n if (this.fetch.onResponse) {\n const responseAfterInterceptor = await this.fetch.onResponse(\n // Optimize type checking by narrowing the type of response\n response as FetchResponse.Loose,\n this.fetch,\n );\n\n const isFetchResponse =\n responseAfterInterceptor instanceof Response &&\n 'request' in responseAfterInterceptor &&\n responseAfterInterceptor.request instanceof this.fetch.Request;\n\n response = isFetchResponse\n ? (responseAfterInterceptor as typeof response)\n : this.defineFetchResponseProperties<Path, Method>(fetchRequest, responseAfterInterceptor);\n }\n\n return response;\n }\n\n private defineFetchResponseProperties<\n Path extends HttpSchemaPath<Schema, Method>,\n Method extends HttpSchemaMethod<Schema>,\n >(fetchRequest: FetchRequest<Path, Method, Default<Schema[Path][Method]>>, response: Response) {\n const fetchResponse = response as FetchResponse<Path, Method, Default<Schema[Path][Method]>>;\n\n Object.defineProperty(fetchResponse, 'request', {\n value: fetchRequest satisfies FetchResponse.Loose['request'],\n writable: false,\n enumerable: true,\n configurable: false,\n });\n\n const responseError = (\n fetchResponse.ok ? null : new FetchResponseError(fetchRequest, fetchResponse)\n ) satisfies FetchResponse.Loose['error'];\n\n Object.defineProperty(fetchResponse, 'error', {\n value: responseError,\n writable: false,\n enumerable: true,\n configurable: false,\n });\n\n return fetchResponse;\n }\n\n private createRequestClass(defaults: FetchRequestInit.Defaults) {\n class Request<\n Path extends HttpSchemaPath.NonLiteral<Schema, Method>,\n Method extends HttpSchemaMethod<Schema>,\n > extends globalThis.Request {\n path: LiteralHttpSchemaPathFromNonLiteral<Schema, Method, Path>;\n\n constructor(\n input: FetchInput<Schema, Path, Method>,\n rawInit: FetchRequestInit<Schema, LiteralHttpSchemaPathFromNonLiteral<Schema, Method, Path>, Method>,\n ) {\n const init = { ...defaults, ...rawInit };\n\n let url: URL;\n\n if (input instanceof globalThis.Request) {\n super(\n // Optimize type checking by narrowing the type of input\n input as globalThis.Request,\n init,\n );\n\n url = new URL(input.url);\n } else {\n url = input instanceof URL ? new URL(input) : new URL(joinURL(init.baseURL, input));\n\n if (init.searchParams) {\n url.search = new HttpSearchParams(init.searchParams).toString();\n }\n\n super(url, init);\n }\n\n this.path = excludeNonPathParams(url)\n .toString()\n .replace(init.baseURL, '') as LiteralHttpSchemaPathFromNonLiteral<Schema, Method, Path>;\n }\n\n clone(): Request<Path, Method> {\n const rawClone = super.clone();\n\n return new Request<Path, Method>(\n rawClone as unknown as FetchInput<Schema, Path, Method>,\n rawClone as unknown as FetchRequestInit<\n Schema,\n LiteralHttpSchemaPathFromNonLiteral<Schema, Method, Path>,\n Method\n >,\n );\n }\n }\n\n return Request as FetchRequestConstructor<Schema>;\n }\n\n isRequest<Path extends HttpSchemaPath<Schema, Method>, Method extends HttpSchemaMethod<Schema>>(\n request: unknown,\n path: Path,\n method: Method,\n ): request is FetchRequest<Path, Method, Default<Schema[Path][Method]>> {\n return (\n request instanceof Request &&\n request.method === method &&\n 'path' in request &&\n typeof request.path === 'string' &&\n createRegexFromURL(path).test(request.path)\n );\n }\n\n isResponse<Path extends HttpSchemaPath<Schema, Method>, Method extends HttpSchemaMethod<Schema>>(\n response: unknown,\n path: Path,\n method: Method,\n ): response is FetchResponse<Path, Method, Default<Schema[Path][Method]>> {\n return (\n response instanceof Response &&\n 'request' in response &&\n 'error' in response &&\n this.isRequest(response.request, path, method)\n );\n }\n\n isResponseError<Path extends HttpSchemaPath<Schema, Method>, Method extends HttpSchemaMethod<Schema>>(\n error: unknown,\n path: Path,\n method: Method,\n ): error is FetchResponseError<Path, Method, Default<Schema[Path][Method]>> {\n return (\n error instanceof FetchResponseError &&\n error.request.method === method &&\n typeof error.request.path === 'string' &&\n createRegexFromURL(path).test(error.request.path)\n );\n }\n}\n\nexport default FetchClient;\n","import { HttpSchema } from '@zimic/http';\n\nimport FetchClient from './FetchClient';\nimport { FetchOptions, Fetch as PublicFetch } from './types/public';\n\nfunction createFetch<Schema extends HttpSchema>(\n options: FetchOptions<HttpSchema<Schema>>,\n): PublicFetch<HttpSchema<Schema>> {\n const { fetch } = new FetchClient<HttpSchema<Schema>>(options);\n return fetch;\n}\n\nexport default createFetch;\n"]}
|