@zimic/fetch 0.1.0-canary.2 → 0.1.0-canary.21

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.
@@ -17,133 +17,341 @@ import {
17
17
  HttpResponseBodySchema,
18
18
  HttpResponseHeadersSchema,
19
19
  HttpRequestHeadersSchema,
20
+ HttpHeadersSchema,
21
+ HttpSearchParamsSchema,
20
22
  } from '@zimic/http';
21
-
22
- import { Default, DefaultNoExclude, IfNever, ReplaceBy } from '@/types/utils';
23
+ import { Default, DefaultNoExclude, IfNever, ReplaceBy } from '@zimic/utils/types';
23
24
 
24
25
  import FetchResponseError, { AnyFetchRequestError } from '../errors/FetchResponseError';
25
26
  import { JSONStringified } from './json';
26
27
  import { FetchInput } from './public';
27
28
 
29
+ type FetchRequestInitHeaders<RequestSchema extends HttpRequestSchema> =
30
+ | RequestSchema['headers']
31
+ | HttpHeaders<Default<RequestSchema['headers']>>;
32
+
28
33
  type FetchRequestInitWithHeaders<RequestSchema extends HttpRequestSchema> = [RequestSchema['headers']] extends [never]
29
34
  ? { headers?: undefined }
30
35
  : undefined extends RequestSchema['headers']
31
- ? { headers?: RequestSchema['headers'] | HttpHeaders<Default<RequestSchema['headers']>> }
32
- : { headers: RequestSchema['headers'] | HttpHeaders<Default<RequestSchema['headers']>> };
36
+ ? { headers?: FetchRequestInitHeaders<RequestSchema> }
37
+ : { headers: FetchRequestInitHeaders<RequestSchema> };
38
+
39
+ type FetchRequestInitSearchParams<RequestSchema extends HttpRequestSchema> =
40
+ | RequestSchema['searchParams']
41
+ | HttpSearchParams<Default<RequestSchema['searchParams']>>;
33
42
 
34
43
  type FetchRequestInitWithSearchParams<RequestSchema extends HttpRequestSchema> = [
35
44
  RequestSchema['searchParams'],
36
45
  ] extends [never]
37
46
  ? { searchParams?: undefined }
38
47
  : undefined extends RequestSchema['searchParams']
39
- ? { searchParams?: RequestSchema['searchParams'] | HttpSearchParams<Default<RequestSchema['searchParams']>> }
40
- : { searchParams: RequestSchema['searchParams'] | HttpSearchParams<Default<RequestSchema['searchParams']>> };
48
+ ? { searchParams?: FetchRequestInitSearchParams<RequestSchema> }
49
+ : { searchParams: FetchRequestInitSearchParams<RequestSchema> };
41
50
 
42
51
  type FetchRequestInitWithBody<RequestSchema extends HttpRequestSchema> = [RequestSchema['body']] extends [never]
43
52
  ? { body?: null }
44
- : undefined extends RequestSchema['body']
45
- ? { body?: ReplaceBy<RequestSchema['body'], undefined, null> }
46
- : RequestSchema['body'] extends string
47
- ? { body: RequestSchema['body'] }
48
- : RequestSchema['body'] extends JSONValue
49
- ? { body: JSONStringified<RequestSchema['body']> }
53
+ : RequestSchema['body'] extends string
54
+ ? undefined extends RequestSchema['body']
55
+ ? { body?: ReplaceBy<RequestSchema['body'], undefined, null> }
56
+ : { body: RequestSchema['body'] }
57
+ : RequestSchema['body'] extends JSONValue
58
+ ? undefined extends RequestSchema['body']
59
+ ? { body?: JSONStringified<ReplaceBy<RequestSchema['body'], undefined, null>> }
60
+ : { body: JSONStringified<RequestSchema['body']> }
61
+ : undefined extends RequestSchema['body']
62
+ ? { body?: ReplaceBy<RequestSchema['body'], undefined, null> }
50
63
  : { body: RequestSchema['body'] };
51
64
 
52
65
  type FetchRequestInitPerPath<RequestSchema extends HttpRequestSchema> = FetchRequestInitWithHeaders<RequestSchema> &
53
66
  FetchRequestInitWithSearchParams<RequestSchema> &
54
67
  FetchRequestInitWithBody<RequestSchema>;
55
68
 
69
+ /**
70
+ * The options to create a {@link FetchRequest} instance, compatible with
71
+ * {@link https://developer.mozilla.org/docs/Web/API/RequestInit `RequestInit`}.
72
+ *
73
+ * @see {@link https://github.com/zimicjs/zimic/wiki/api‐zimic‐fetch#fetch `fetch` API reference}
74
+ * @see {@link https://developer.mozilla.org/docs/Web/API/RequestInit `RequestInit`}
75
+ */
56
76
  export type FetchRequestInit<
57
77
  Schema extends HttpSchema,
58
- Path extends HttpSchemaPath<Schema, Method>,
59
78
  Method extends HttpSchemaMethod<Schema>,
60
- > = RequestInit & { baseURL?: string; method: Method } & (Path extends Path
61
- ? FetchRequestInitPerPath<Default<Default<Schema[Path][Method]>['request']>>
62
- : never);
79
+ Path extends HttpSchemaPath<Schema, Method>,
80
+ Redirect extends RequestRedirect = 'follow',
81
+ > = Omit<RequestInit, 'method' | 'headers' | 'body'> & {
82
+ /** The HTTP method of the request. */
83
+ method: Method;
84
+ /** The base URL to prefix the path of the request. */
85
+ baseURL?: string;
86
+ redirect?: Redirect;
87
+ } & (Path extends Path ? FetchRequestInitPerPath<Default<Default<Schema[Path][Method]>['request']>> : never);
63
88
 
64
89
  export namespace FetchRequestInit {
65
- export interface Defaults extends RequestInit {
90
+ /** The default options for each request sent by a fetch instance. */
91
+ export interface Defaults extends Omit<RequestInit, 'headers'> {
66
92
  baseURL: string;
93
+ /** The HTTP method of the request. */
67
94
  method?: HttpMethod;
68
- searchParams?: HttpSearchParams;
95
+ /** The headers of the request. */
96
+ headers?: HttpHeadersSchema;
97
+ /** The search parameters of the request. */
98
+ searchParams?: HttpSearchParamsSchema;
69
99
  }
100
+
101
+ /** A loosely typed version of {@link FetchRequestInit `FetchRequestInit`}. */
102
+ export type Loose = Partial<Defaults>;
70
103
  }
71
104
 
72
105
  type AllFetchResponseStatusCode<MethodSchema extends HttpMethodSchema> = HttpResponseSchemaStatusCode<
73
106
  Default<MethodSchema['response']>
74
107
  >;
75
108
 
76
- type FetchResponseStatusCode<MethodSchema extends HttpMethodSchema, ErrorOnly extends boolean> = ErrorOnly extends true
77
- ? AllFetchResponseStatusCode<MethodSchema> & (HttpStatusCode.ClientError | HttpStatusCode.ServerError)
78
- : AllFetchResponseStatusCode<MethodSchema>;
109
+ type FilterFetchResponseStatusCodeByError<
110
+ StatusCode extends HttpStatusCode,
111
+ ErrorOnly extends boolean,
112
+ > = ErrorOnly extends true ? Extract<StatusCode, HttpStatusCode.ClientError | HttpStatusCode.ServerError> : StatusCode;
113
+
114
+ type FilterFetchResponseStatusCodeByRedirect<
115
+ StatusCode extends HttpStatusCode,
116
+ Redirect extends RequestRedirect,
117
+ > = Redirect extends 'error'
118
+ ? FilterFetchResponseStatusCodeByRedirect<StatusCode, 'follow'>
119
+ : Redirect extends 'follow'
120
+ ? Exclude<StatusCode, Exclude<HttpStatusCode.Redirection, 304>>
121
+ : StatusCode;
79
122
 
80
- export type HttpRequestBodySchema<MethodSchema extends HttpMethodSchema> = ReplaceBy<
123
+ type FetchResponseStatusCode<
124
+ MethodSchema extends HttpMethodSchema,
125
+ ErrorOnly extends boolean,
126
+ Redirect extends RequestRedirect,
127
+ > = FilterFetchResponseStatusCodeByRedirect<
128
+ FilterFetchResponseStatusCodeByError<AllFetchResponseStatusCode<MethodSchema>, ErrorOnly>,
129
+ Redirect
130
+ >;
131
+
132
+ type HttpRequestBodySchema<MethodSchema extends HttpMethodSchema> = ReplaceBy<
81
133
  ReplaceBy<IfNever<DefaultNoExclude<Default<MethodSchema['request']>['body']>, null>, undefined, null>,
82
134
  ArrayBuffer,
83
135
  Blob
84
136
  >;
85
137
 
138
+ /**
139
+ * A request instance typed with an HTTP schema, closely compatible with the
140
+ * {@link https://developer.mozilla.org/docs/Web/API/Request native Request class}.
141
+ *
142
+ * On top of the properties available in native {@link https://developer.mozilla.org/docs/Web/API/Request `Request`}
143
+ * instances, fetch requests have their URL automatically prefixed with the base URL of their fetch instance. Default
144
+ * options are also applied, if present in the fetch instance.
145
+ *
146
+ * The path of the request is extracted from the URL, excluding the base URL, and is available in the `path` property.
147
+ *
148
+ * @example
149
+ * import { type HttpSchema } from '@zimic/http';
150
+ * import { createFetch } from '@zimic/fetch';
151
+ *
152
+ * interface User {
153
+ * id: string;
154
+ * username: string;
155
+ * }
156
+ *
157
+ * type Schema = HttpSchema<{
158
+ * '/users': {
159
+ * POST: {
160
+ * request: {
161
+ * headers: { 'content-type': 'application/json' };
162
+ * body: { username: string };
163
+ * };
164
+ * response: {
165
+ * 201: { body: User };
166
+ * };
167
+ * };
168
+ * };
169
+ * }>;
170
+ *
171
+ * const fetch = createFetch<Schema>({
172
+ * baseURL: 'http://localhost:3000',
173
+ * });
174
+ *
175
+ * const request = new fetch.Request('/users', {
176
+ * method: 'POST',
177
+ * headers: { 'content-type': 'application/json' },
178
+ * body: JSON.stringify({ username: 'me' }),
179
+ * });
180
+ *
181
+ * console.log(request); // FetchRequest<Schema, 'POST', '/users'>
182
+ * console.log(request.path); // '/users'
183
+ *
184
+ * @see {@link https://github.com/zimicjs/zimic/wiki/api‐zimic‐fetch#fetchrequest `FetchRequest` API reference}
185
+ * @see {@link https://developer.mozilla.org/docs/Web/API/Request}
186
+ */
86
187
  export interface FetchRequest<
87
- Path extends string = string,
88
- Method extends HttpMethod = HttpMethod,
89
- MethodSchema extends HttpMethodSchema = HttpMethodSchema,
90
- > extends HttpRequest<HttpRequestBodySchema<MethodSchema>, HttpRequestHeadersSchema<MethodSchema>> {
188
+ Schema extends HttpSchema,
189
+ Method extends HttpSchemaMethod<Schema>,
190
+ Path extends HttpSchemaPath.Literal<Schema, Method>,
191
+ > extends HttpRequest<
192
+ HttpRequestBodySchema<Default<Schema[Path][Method]>>,
193
+ HttpRequestHeadersSchema<Default<Schema[Path][Method]>>
194
+ > {
195
+ /** The path of the request, excluding the base URL. */
91
196
  path: AllowAnyStringInPathParams<Path>;
197
+ /** The HTTP method of the request. */
92
198
  method: Method;
93
199
  }
94
200
 
95
201
  export namespace FetchRequest {
202
+ /** A loosely typed version of {@link FetchRequest `FetchRequest`}. */
96
203
  export interface Loose extends Request {
204
+ /** The path of the request, excluding the base URL. */
97
205
  path: string;
206
+ /** The HTTP method of the request. */
98
207
  method: HttpMethod;
208
+ /** Clones the request instance, returning a new instance with the same properties. */
99
209
  clone: () => Loose;
100
210
  }
101
211
  }
102
212
 
103
213
  export interface FetchResponsePerStatusCode<
104
- Path extends string = string,
105
- Method extends HttpMethod = HttpMethod,
106
- MethodSchema extends HttpMethodSchema = HttpMethodSchema,
214
+ Schema extends HttpSchema,
215
+ Method extends HttpSchemaMethod<Schema>,
216
+ Path extends HttpSchemaPath.Literal<Schema, Method>,
107
217
  StatusCode extends HttpStatusCode = HttpStatusCode,
108
218
  > extends HttpResponse<
109
- HttpResponseBodySchema<MethodSchema, StatusCode>,
219
+ HttpResponseBodySchema<Default<Schema[Path][Method]>, StatusCode>,
110
220
  StatusCode,
111
- HttpResponseHeadersSchema<MethodSchema, StatusCode>
221
+ HttpResponseHeadersSchema<Default<Schema[Path][Method]>, StatusCode>
112
222
  > {
113
- request: FetchRequest<Path, Method, MethodSchema>;
223
+ /** The request that originated the response. */
224
+ request: FetchRequest<Schema, Method, Path>;
114
225
 
226
+ /**
227
+ * An error representing a response with a failure status code (4XX or 5XX). It can be thrown to handle the error
228
+ * upper in the call stack.
229
+ *
230
+ * If the response has a success status code (1XX, 2XX or 3XX), this property will be null.
231
+ */
115
232
  error: StatusCode extends HttpStatusCode.ClientError | HttpStatusCode.ServerError
116
- ? FetchResponseError<Path, Method, MethodSchema>
233
+ ? FetchResponseError<Schema, Method, Path>
117
234
  : null;
118
235
  }
119
236
 
237
+ /**
238
+ * A response instance typed with an HTTP schema, closely compatible with the
239
+ * {@link https://developer.mozilla.org/docs/Web/API/Response native Response class}.
240
+ *
241
+ * On top of the properties available in native Response instances, fetch responses have a reference to the request that
242
+ * originated them, available in the `request` property.
243
+ *
244
+ * If the response has a failure status code (4XX or 5XX), an error is available in the `error` property.
245
+ *
246
+ * @example
247
+ * import { type HttpSchema } from '@zimic/http';
248
+ * import { createFetch } from '@zimic/fetch';
249
+ *
250
+ * interface User {
251
+ * id: string;
252
+ * username: string;
253
+ * }
254
+ *
255
+ * type Schema = HttpSchema<{
256
+ * '/users/:userId': {
257
+ * GET: {
258
+ * response: {
259
+ * 200: { body: User };
260
+ * 404: { body: { message: string } };
261
+ * };
262
+ * };
263
+ * };
264
+ * }>;
265
+ *
266
+ * const fetch = createFetch<Schema>({
267
+ * baseURL: 'http://localhost:3000',
268
+ * });
269
+ *
270
+ * const response = await fetch(`/users/${userId}`, {
271
+ * method: 'GET',
272
+ * });
273
+ *
274
+ * console.log(response); // FetchResponse<Schema, 'GET', '/users'>
275
+ *
276
+ * if (response.status === 404) {
277
+ * const errorBody = await response.json(); // { message: string }
278
+ * console.error(errorBody.message);
279
+ * return null;
280
+ * } else {
281
+ * const user = await response.json(); // User
282
+ * return user;
283
+ * }
284
+ *
285
+ * @see {@link https://github.com/zimicjs/zimic/wiki/api‐zimic‐fetch#fetchresponse `FetchResponse` API reference}
286
+ * @see {@link https://developer.mozilla.org/docs/Web/API/Response}
287
+ */
120
288
  export type FetchResponse<
121
- Path extends string = string,
122
- Method extends HttpMethod = HttpMethod,
123
- MethodSchema extends HttpMethodSchema = HttpMethodSchema,
289
+ Schema extends HttpSchema,
290
+ Method extends HttpSchemaMethod<Schema>,
291
+ Path extends HttpSchemaPath.Literal<Schema, Method>,
124
292
  ErrorOnly extends boolean = false,
125
- StatusCode extends FetchResponseStatusCode<MethodSchema, ErrorOnly> = FetchResponseStatusCode<
126
- MethodSchema,
127
- ErrorOnly
128
- >,
129
- > = StatusCode extends StatusCode ? FetchResponsePerStatusCode<Path, Method, MethodSchema, StatusCode> : never;
293
+ Redirect extends RequestRedirect = 'follow',
294
+ StatusCode extends FetchResponseStatusCode<
295
+ Default<Schema[Path][Method]>,
296
+ ErrorOnly,
297
+ Redirect
298
+ > = FetchResponseStatusCode<Default<Schema[Path][Method]>, ErrorOnly, Redirect>,
299
+ > = StatusCode extends StatusCode ? FetchResponsePerStatusCode<Schema, Method, Path, StatusCode> : never;
130
300
 
131
301
  export namespace FetchResponse {
302
+ /** A loosely typed version of {@link FetchResponse}. */
132
303
  export interface Loose extends Response {
304
+ /** The request that originated the response. */
133
305
  request: FetchRequest.Loose;
306
+
307
+ /**
308
+ * An error representing a response with a failure status code (4XX or 5XX). It can be thrown to handle the error
309
+ * upper in the call stack.
310
+ *
311
+ * If the response has a success status code (1XX, 2XX or 3XX), this property will be null.
312
+ */
134
313
  error: AnyFetchRequestError | null;
314
+
315
+ /** Clones the request instance, returning a new instance with the same properties. */
135
316
  clone: () => Loose;
136
317
  }
137
318
  }
138
319
 
320
+ /**
321
+ * A constructor for {@link FetchRequest} instances, typed with an HTTP schema and compatible with the
322
+ * {@link https://developer.mozilla.org/docs/Web/API/Request Request class constructor}.
323
+ *
324
+ * @example
325
+ * import { type HttpSchema } from '@zimic/http';
326
+ * import { createFetch } from '@zimic/fetch';
327
+ *
328
+ * type Schema = HttpSchema<{
329
+ * // ...
330
+ * }>;
331
+ *
332
+ * const fetch = createFetch<Schema>({
333
+ * baseURL: 'http://localhost:3000',
334
+ * });
335
+ *
336
+ * const request = new fetch.Request('POST', '/users', {
337
+ * body: JSON.stringify({ username: 'me' }),
338
+ * });
339
+ * console.log(request); // FetchRequest<Schema, 'POST', '/users'>
340
+ *
341
+ * @param input The resource to fetch, either a path, a URL, or a {@link FetchRequest request}. If a path is provided, it
342
+ * is automatically prefixed with the base URL of the fetch instance when the request is sent. If a URL or a request
343
+ * is provided, it is used as is.
344
+ * @param init The request options. If a path or a URL is provided as the first argument, this argument is required and
345
+ * should contain at least the method of the request. If the first argument is a {@link FetchRequest request}, this
346
+ * argument is optional.
347
+ * @returns A promise that resolves to the response to the request.
348
+ * @see {@link https://github.com/zimicjs/zimic/wiki/api‐zimic‐fetch#fetchresponse `FetchResponse` API reference}
349
+ * @see {@link https://developer.mozilla.org/docs/Web/API/Request}
350
+ */
139
351
  export type FetchRequestConstructor<Schema extends HttpSchema> = new <
140
- Path extends HttpSchemaPath.NonLiteral<Schema, Method>,
141
352
  Method extends HttpSchemaMethod<Schema>,
353
+ Path extends HttpSchemaPath.NonLiteral<Schema, Method>,
142
354
  >(
143
- input: FetchInput<Schema, Path, Method>,
144
- init: FetchRequestInit<Schema, LiteralHttpSchemaPathFromNonLiteral<Schema, Method, Path>, Method>,
145
- ) => FetchRequest<
146
- LiteralHttpSchemaPathFromNonLiteral<Schema, Method, Path>,
147
- Method,
148
- Default<Schema[LiteralHttpSchemaPathFromNonLiteral<Schema, Method, Path>][Method]>
149
- >;
355
+ input: FetchInput<Schema, Method, Path>,
356
+ init: FetchRequestInit<Schema, Method, LiteralHttpSchemaPathFromNonLiteral<Schema, Method, Path>>,
357
+ ) => FetchRequest<Schema, Method, LiteralHttpSchemaPathFromNonLiteral<Schema, Method, Path>>;
package/src/index.ts CHANGED
@@ -1,12 +1,8 @@
1
- export type {
2
- Fetch,
3
- FetchFunction,
4
- FetchClient,
5
- FetchOptions as FetchClientOptions,
6
- FetchInput,
7
- } from './client/types/public';
8
-
9
- export type { FetchRequestInit, FetchRequest, FetchRequestConstructor, FetchResponse } from './client/types/requests';
1
+ export type { JSONStringified } from './client/types/json';
2
+
3
+ export type { Fetch, InferFetchSchema, FetchOptions, FetchDefaults, FetchInput } from './client/types/public';
4
+
5
+ export type { FetchRequest, FetchRequestInit, FetchResponse, FetchRequestConstructor } from './client/types/requests';
10
6
 
11
7
  export { default as FetchResponseError } from './client/errors/FetchResponseError';
12
8
 
@@ -9,8 +9,7 @@ import {
9
9
  JSONValue,
10
10
  HttpStatusCode,
11
11
  } from '@zimic/http';
12
-
13
- import { ReplaceBy } from '@/types/utils';
12
+ import { ReplaceBy } from '@zimic/utils/types';
14
13
 
15
14
  /** The body type for HTTP requests and responses. */
16
15
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
@@ -0,0 +1,3 @@
1
+ export function isClientSide() {
2
+ return typeof window !== 'undefined';
3
+ }
@@ -1,23 +1,8 @@
1
- import { createCachedDynamicImport } from './imports';
2
-
3
- export const importBuffer = createCachedDynamicImport(
4
- /* istanbul ignore next -- @preserve
5
- * Ignoring as Node.js >=20 provides a global file and the buffer import won't run. */
6
- () => import('buffer'),
7
- );
8
-
9
- let FileSingleton: typeof File | undefined;
10
-
11
- export async function importFile() {
12
- /* istanbul ignore if -- @preserve
13
- * Ignoring as this will only run if this function is called more than once. */
14
- if (FileSingleton) {
15
- return FileSingleton;
16
- }
1
+ import createCachedDynamicImport from '@zimic/utils/import/createCachedDynamicImport';
17
2
 
3
+ export const importFile = createCachedDynamicImport(
18
4
  /* istanbul ignore next -- @preserve
19
5
  * Ignoring as Node.js >=20 provides a global File and the import fallback won't run. */
20
6
  // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
21
- FileSingleton = globalThis.File ?? (await importBuffer()).File;
22
- return FileSingleton;
23
- }
7
+ async () => globalThis.File ?? (await import('buffer')).File,
8
+ );
@@ -1,9 +0,0 @@
1
- interface String {
2
- // Using the original method signature style to correctly apply the overload.
3
- // eslint-disable-next-line @typescript-eslint/method-signature-style
4
- toLowerCase<Type = string>(): Lowercase<Type>;
5
-
6
- // Using the original method signature style to correctly apply the overload.
7
- // eslint-disable-next-line @typescript-eslint/method-signature-style
8
- toUpperCase<Type = string>(): Uppercase<Type>;
9
- }
@@ -1,11 +0,0 @@
1
- export type Default<Type, IfEmpty = never> = [undefined | void] extends [Type]
2
- ? IfEmpty
3
- : Exclude<Type, undefined | void>;
4
-
5
- export type DefaultNoExclude<Type, IfEmpty = never> = [undefined | void] extends Type ? IfEmpty : Type;
6
-
7
- export type IfNever<Type, Yes, No = Type> = [Type] extends [never] ? Yes : No;
8
-
9
- export type PossiblePromise<Type> = Type | PromiseLike<Type>;
10
-
11
- export type ReplaceBy<Type, Source, Target> = Type extends Source ? Target : Type;
@@ -1,14 +0,0 @@
1
- /* istanbul ignore next -- @preserve
2
- * Ignoring as Node.js >=20 provides globals that may cause this dynamic import to not run. */
3
- export function createCachedDynamicImport<ImportType>(
4
- importModuleDynamically: () => Promise<ImportType>,
5
- ): () => Promise<ImportType> {
6
- let cachedImportResult: ImportType | undefined;
7
-
8
- return async function importModuleDynamicallyWithCache() {
9
- if (cachedImportResult === undefined) {
10
- cachedImportResult = await importModuleDynamically();
11
- }
12
- return cachedImportResult;
13
- };
14
- }
package/src/utils/urls.ts DELETED
@@ -1,43 +0,0 @@
1
- export function excludeNonPathParams(url: URL) {
2
- url.hash = '';
3
- url.search = '';
4
- url.username = '';
5
- url.password = '';
6
- return url;
7
- }
8
-
9
- function prepareURLForRegex(url: string) {
10
- const encodedURL = encodeURI(url);
11
- return encodedURL.replace(/([.()*?+$\\])/g, '\\$1');
12
- }
13
-
14
- const URL_PATH_PARAM_REGEX = /\/:([^/]+)/g;
15
-
16
- export function createRegexFromURL(url: string) {
17
- const urlWithReplacedPathParams = prepareURLForRegex(url)
18
- .replace(URL_PATH_PARAM_REGEX, '/(?<$1>[^/]+)')
19
- .replace(/(\/+)$/, '(?:/+)?');
20
-
21
- return new RegExp(`^${urlWithReplacedPathParams}$`);
22
- }
23
-
24
- export function joinURL(...parts: (string | URL)[]) {
25
- return parts
26
- .map((part, index) => {
27
- const isFirstPart = index === 0;
28
- const isLastPart = index === parts.length - 1;
29
-
30
- let partAsString = part.toString();
31
-
32
- if (!isFirstPart) {
33
- partAsString = partAsString.replace(/^\//, '');
34
- }
35
- if (!isLastPart) {
36
- partAsString = partAsString.replace(/\/$/, '');
37
- }
38
-
39
- return partAsString;
40
- })
41
- .filter((part) => part.length > 0)
42
- .join('/');
43
- }