@zimic/http 0.0.1-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.
Files changed (61) hide show
  1. package/LICENSE.md +16 -0
  2. package/README.md +230 -0
  3. package/dist/chunk-VHQRAQPQ.mjs +1371 -0
  4. package/dist/chunk-VHQRAQPQ.mjs.map +1 -0
  5. package/dist/chunk-VUDGONB5.js +1382 -0
  6. package/dist/chunk-VUDGONB5.js.map +1 -0
  7. package/dist/cli.js +116 -0
  8. package/dist/cli.js.map +1 -0
  9. package/dist/cli.mjs +109 -0
  10. package/dist/cli.mjs.map +1 -0
  11. package/dist/index.d.ts +1306 -0
  12. package/dist/index.js +544 -0
  13. package/dist/index.js.map +1 -0
  14. package/dist/index.mjs +537 -0
  15. package/dist/index.mjs.map +1 -0
  16. package/dist/typegen.d.ts +86 -0
  17. package/dist/typegen.js +12 -0
  18. package/dist/typegen.js.map +1 -0
  19. package/dist/typegen.mjs +3 -0
  20. package/dist/typegen.mjs.map +1 -0
  21. package/index.d.ts +1 -0
  22. package/package.json +110 -0
  23. package/src/cli/cli.ts +92 -0
  24. package/src/cli/index.ts +4 -0
  25. package/src/cli/typegen/openapi.ts +24 -0
  26. package/src/formData/HttpFormData.ts +300 -0
  27. package/src/formData/types.ts +110 -0
  28. package/src/headers/HttpHeaders.ts +217 -0
  29. package/src/headers/types.ts +65 -0
  30. package/src/index.ts +55 -0
  31. package/src/pathParams/types.ts +67 -0
  32. package/src/searchParams/HttpSearchParams.ts +258 -0
  33. package/src/searchParams/types.ts +133 -0
  34. package/src/typegen/index.ts +12 -0
  35. package/src/typegen/namespace/TypegenNamespace.ts +18 -0
  36. package/src/typegen/openapi/generate.ts +168 -0
  37. package/src/typegen/openapi/transform/components.ts +481 -0
  38. package/src/typegen/openapi/transform/context.ts +67 -0
  39. package/src/typegen/openapi/transform/filters.ts +71 -0
  40. package/src/typegen/openapi/transform/imports.ts +15 -0
  41. package/src/typegen/openapi/transform/io.ts +86 -0
  42. package/src/typegen/openapi/transform/methods.ts +803 -0
  43. package/src/typegen/openapi/transform/operations.ts +120 -0
  44. package/src/typegen/openapi/transform/paths.ts +119 -0
  45. package/src/typegen/openapi/utils/types.ts +45 -0
  46. package/src/types/arrays.d.ts +4 -0
  47. package/src/types/json.ts +89 -0
  48. package/src/types/objects.d.ts +14 -0
  49. package/src/types/requests.ts +96 -0
  50. package/src/types/schema.ts +834 -0
  51. package/src/types/strings.d.ts +9 -0
  52. package/src/types/utils.ts +64 -0
  53. package/src/utils/console.ts +7 -0
  54. package/src/utils/data.ts +13 -0
  55. package/src/utils/files.ts +28 -0
  56. package/src/utils/imports.ts +12 -0
  57. package/src/utils/prettier.ts +13 -0
  58. package/src/utils/strings.ts +3 -0
  59. package/src/utils/time.ts +25 -0
  60. package/src/utils/urls.ts +52 -0
  61. package/typegen.d.ts +1 -0
@@ -0,0 +1,300 @@
1
+ import { ArrayItemIfArray, ReplaceBy } from '@/types/utils';
2
+ import { fileEquals } from '@/utils/files';
3
+
4
+ import { HttpFormDataSchema, HttpFormDataSchemaName } from './types';
5
+
6
+ /**
7
+ * An extended HTTP form data object with a strictly-typed schema. Fully compatible with the built-in
8
+ * {@link https://developer.mozilla.org/docs/Web/API/FormData `FormData`} class.
9
+ *
10
+ * **IMPORTANT**: the input of `HttpFormData` and all of its internal types must be declared inline or as a type aliases
11
+ * (`type`). They cannot be interfaces.
12
+ *
13
+ * @example
14
+ * import { HttpFormData } from '@zimic/http';
15
+ *
16
+ * const formData = new HttpFormData<{
17
+ * files: File[];
18
+ * description?: string;
19
+ * }>();
20
+ *
21
+ * formData.append('file', new File(['content'], 'file.txt', { type: 'text/plain' }));
22
+ * formData.append('description', 'My file');
23
+ *
24
+ * const files = formData.getAll('file');
25
+ * console.log(files); // [File { name: 'file.txt', type: 'text/plain' }]
26
+ *
27
+ * const description = formData.get('description');
28
+ * console.log(description); // 'My file'
29
+ *
30
+ * @see {@link https://github.com/zimicjs/zimic/wiki/api‐zimic‐http#httpformdata `HttpFormData` API reference}
31
+ */
32
+ class HttpFormData<Schema extends HttpFormDataSchema = HttpFormDataSchema> extends FormData {
33
+ /** @see {@link https://developer.mozilla.org/docs/Web/API/FormData/set MDN Reference} */
34
+ set<Name extends HttpFormDataSchemaName<Schema>>(
35
+ name: Name,
36
+ value: Exclude<ArrayItemIfArray<NonNullable<Schema[Name]>>, Blob>,
37
+ ): void;
38
+ set<Name extends HttpFormDataSchemaName<Schema>>(
39
+ name: Name,
40
+ blob: Exclude<ArrayItemIfArray<NonNullable<Schema[Name]>>, string>,
41
+ fileName?: string,
42
+ ): void;
43
+ set<Name extends HttpFormDataSchemaName<Schema>>(
44
+ name: Name,
45
+ blobOrValue: ArrayItemIfArray<NonNullable<Schema[Name]>>,
46
+ fileName?: string,
47
+ ): void {
48
+ if (fileName === undefined) {
49
+ super.set(name, blobOrValue as Blob);
50
+ } else {
51
+ super.set(name, blobOrValue as Blob, fileName);
52
+ }
53
+ }
54
+
55
+ /** @see {@link https://developer.mozilla.org/docs/Web/API/FormData/append MDN Reference} */
56
+ append<Name extends HttpFormDataSchemaName<Schema>>(
57
+ name: Name,
58
+ value: Exclude<ArrayItemIfArray<NonNullable<Schema[Name]>>, Blob>,
59
+ ): void;
60
+ append<Name extends HttpFormDataSchemaName<Schema>>(
61
+ name: Name,
62
+ blob: Exclude<ArrayItemIfArray<NonNullable<Schema[Name]>>, string>,
63
+ fileName?: string,
64
+ ): void;
65
+ append<Name extends HttpFormDataSchemaName<Schema>>(
66
+ name: Name,
67
+ blobOrValue: ArrayItemIfArray<NonNullable<Schema[Name]>>,
68
+ fileName?: string,
69
+ ): void {
70
+ if (fileName === undefined) {
71
+ super.append(name, blobOrValue as Blob);
72
+ } else {
73
+ super.append(name, blobOrValue as Blob, fileName);
74
+ }
75
+ }
76
+
77
+ /**
78
+ * Get the value of the entry associated to a key name.
79
+ *
80
+ * If the key might have multiple values, use {@link HttpFormData#getAll} instead.
81
+ *
82
+ * @param name The name of the key to get the value of.
83
+ * @returns The value associated with the key name, or `null` if the key does not exist.
84
+ * @see {@link https://developer.mozilla.org/docs/Web/API/FormData/get MDN Reference}
85
+ */
86
+ get<Name extends HttpFormDataSchemaName.NonArray<Schema>>(
87
+ name: Name,
88
+ ): ReplaceBy<ReplaceBy<ArrayItemIfArray<Schema[Name]>, undefined, null>, Blob, File> {
89
+ return super.get(name) as ReplaceBy<ReplaceBy<ArrayItemIfArray<Schema[Name]>, undefined, null>, Blob, File>;
90
+ }
91
+
92
+ /**
93
+ * Get all the values of the entry associated with a key name.
94
+ *
95
+ * If the key has at most a single value, use {@link HttpFormData#get} instead.
96
+ *
97
+ * @param name The name of the key to get the values of.
98
+ * @returns An array of values associated with the key name, or an empty array if the key does not exist.
99
+ * @see {@link https://developer.mozilla.org/docs/Web/API/FormData/getAll MDN Reference}
100
+ */
101
+ getAll<Name extends HttpFormDataSchemaName.Array<Schema>>(
102
+ name: Name,
103
+ ): ReplaceBy<ArrayItemIfArray<NonNullable<Schema[Name]>>, Blob, File>[] {
104
+ return super.getAll(name) as ReplaceBy<ArrayItemIfArray<NonNullable<Schema[Name]>>, Blob, File>[];
105
+ }
106
+
107
+ /** @see {@link https://developer.mozilla.org/docs/Web/API/FormData/has MDN Reference} */
108
+ has<Name extends HttpFormDataSchemaName<Schema>>(name: Name): boolean {
109
+ return super.has(name);
110
+ }
111
+
112
+ /** @see {@link https://developer.mozilla.org/docs/Web/API/FormData/delete MDN Reference} */
113
+ delete<Name extends HttpFormDataSchemaName<Schema>>(name: Name): void {
114
+ super.delete(name);
115
+ }
116
+
117
+ forEach<This extends HttpFormData<Schema>>(
118
+ callback: <Key extends HttpFormDataSchemaName<Schema>>(
119
+ value: ReplaceBy<ArrayItemIfArray<NonNullable<Schema[Key]>>, Blob, File>,
120
+ key: Key,
121
+ parent: HttpFormData<Schema>,
122
+ ) => void,
123
+ thisArg?: This,
124
+ ): void {
125
+ super.forEach(callback as (value: FormDataEntryValue, key: string, parent: FormData) => void, thisArg);
126
+ }
127
+
128
+ /** @see {@link https://developer.mozilla.org/docs/Web/API/FormData/keys MDN Reference} */
129
+ keys(): FormDataIterator<HttpFormDataSchemaName<Schema>> {
130
+ return super.keys() as FormDataIterator<HttpFormDataSchemaName<Schema>>;
131
+ }
132
+
133
+ /** @see {@link https://developer.mozilla.org/docs/Web/API/FormData/values MDN Reference} */
134
+ values(): FormDataIterator<
135
+ ReplaceBy<ArrayItemIfArray<NonNullable<Schema[HttpFormDataSchemaName<Schema>]>>, Blob, File>
136
+ > {
137
+ return super.values() as FormDataIterator<
138
+ ReplaceBy<ArrayItemIfArray<NonNullable<Schema[HttpFormDataSchemaName<Schema>]>>, Blob, File>
139
+ >;
140
+ }
141
+
142
+ /** @see {@link https://developer.mozilla.org/docs/Web/API/FormData/entries MDN Reference} */
143
+ entries(): FormDataIterator<
144
+ [
145
+ HttpFormDataSchemaName<Schema>,
146
+ ReplaceBy<ArrayItemIfArray<NonNullable<Schema[HttpFormDataSchemaName<Schema>]>>, Blob, File>,
147
+ ]
148
+ > {
149
+ return super.entries() as FormDataIterator<
150
+ [
151
+ HttpFormDataSchemaName<Schema>,
152
+ ReplaceBy<ArrayItemIfArray<NonNullable<Schema[HttpFormDataSchemaName<Schema>]>>, Blob, File>,
153
+ ]
154
+ >;
155
+ }
156
+
157
+ [Symbol.iterator](): FormDataIterator<
158
+ [
159
+ HttpFormDataSchemaName<Schema>,
160
+ ReplaceBy<ArrayItemIfArray<NonNullable<Schema[HttpFormDataSchemaName<Schema>]>>, Blob, File>,
161
+ ]
162
+ > {
163
+ return super[Symbol.iterator]() as FormDataIterator<
164
+ [
165
+ HttpFormDataSchemaName<Schema>,
166
+ ReplaceBy<ArrayItemIfArray<NonNullable<Schema[HttpFormDataSchemaName<Schema>]>>, Blob, File>,
167
+ ]
168
+ >;
169
+ }
170
+
171
+ /**
172
+ * Checks if the data is equal to the other data. Equality is defined as having the same keys and values, regardless
173
+ * of the order of keys.
174
+ *
175
+ * @param otherData The other data to compare.
176
+ * @returns A promise that resolves with `true` if the data is equal to the other data, or `false` otherwise.
177
+ * Important: both form data might be read while comparing.
178
+ */
179
+ async equals<OtherSchema extends Schema>(otherData: HttpFormData<OtherSchema>): Promise<boolean> {
180
+ for (const [otherKey, otherValue] of otherData.entries()) {
181
+ const values = super.getAll.call(this, otherKey);
182
+
183
+ const haveSameNumberOfValues = values.length === super.getAll.call(otherData, otherKey).length;
184
+ if (!haveSameNumberOfValues) {
185
+ return false;
186
+ }
187
+
188
+ let valueExists = false;
189
+
190
+ for (const value of values) {
191
+ if (
192
+ value === otherValue ||
193
+ (value instanceof Blob && otherValue instanceof Blob && (await fileEquals(value, otherValue)))
194
+ ) {
195
+ valueExists = true;
196
+ break;
197
+ }
198
+ }
199
+
200
+ if (!valueExists) {
201
+ return false;
202
+ }
203
+ }
204
+
205
+ for (const key of this.keys()) {
206
+ const otherHasKey = super.has.call(otherData, key);
207
+ if (!otherHasKey) {
208
+ return false;
209
+ }
210
+ }
211
+
212
+ return true;
213
+ }
214
+
215
+ /**
216
+ * Checks if the data contains the other data. This method is less strict than {@link HttpFormData#equals} and only
217
+ * requires that all keys and values in the other data are present in this data.
218
+ *
219
+ * @param otherData The other data to compare.
220
+ * @returns A promise that resolves with `true` if this data contains the other data, or `false` otherwise. Important:
221
+ * both form data might be read while comparing.
222
+ */
223
+ async contains<OtherSchema extends Schema>(otherData: HttpFormData<OtherSchema>): Promise<boolean> {
224
+ for (const [otherKey, otherValue] of otherData.entries()) {
225
+ const values = super.getAll.call(this, otherKey);
226
+
227
+ const haveCompatibleNumberOfValues = values.length >= super.getAll.call(otherData, otherKey).length;
228
+ if (!haveCompatibleNumberOfValues) {
229
+ return false;
230
+ }
231
+
232
+ let valueExists = false;
233
+
234
+ for (const value of values) {
235
+ if (
236
+ value === otherValue ||
237
+ (typeof value === 'string' && typeof otherValue === 'string' && value === otherValue) ||
238
+ (value instanceof Blob && otherValue instanceof Blob && (await fileEquals(value, otherValue)))
239
+ ) {
240
+ valueExists = true;
241
+ break;
242
+ }
243
+ }
244
+
245
+ if (!valueExists) {
246
+ return false;
247
+ }
248
+ }
249
+
250
+ return true;
251
+ }
252
+
253
+ /**
254
+ * Converts this form data into a plain object. This method is useful for serialization and debugging purposes.
255
+ *
256
+ * **NOTE**: If a key has multiple values, the object will contain an array of values for that key. If the key has
257
+ * only one value, the object will contain its value directly, without an array, regardless of how the value was
258
+ * initialized when creating the form data.
259
+ *
260
+ * @example
261
+ * const formData = new HttpFormData<{
262
+ * title: string;
263
+ * descriptions: string[];
264
+ * content: Blob;
265
+ * }>();
266
+ *
267
+ * formData.set('title', 'My title');
268
+ * formData.append('descriptions', 'Description 1');
269
+ * formData.append('descriptions', 'Description 2');
270
+ * formData.set('content', new Blob(['content'], { type: 'text/plain' }));
271
+ *
272
+ * const object = formData.toObject();
273
+ * console.log(object); // { title: 'My title', descriptions: ['Description 1', 'Description 2'], content: Blob { type: 'text/plain' } }
274
+ *
275
+ * @returns A plain object representation of this form data.
276
+ */
277
+ toObject() {
278
+ const object = {} as Schema;
279
+
280
+ type SchemaValue = Schema[HttpFormDataSchemaName<Schema>];
281
+
282
+ for (const [key, value] of this.entries()) {
283
+ if (key in object) {
284
+ const existingValue = object[key];
285
+
286
+ if (Array.isArray<SchemaValue>(existingValue)) {
287
+ existingValue.push(value as SchemaValue);
288
+ } else {
289
+ object[key] = [existingValue, value] as SchemaValue;
290
+ }
291
+ } else {
292
+ object[key] = value as SchemaValue;
293
+ }
294
+ }
295
+
296
+ return object;
297
+ }
298
+ }
299
+
300
+ export default HttpFormData;
@@ -0,0 +1,110 @@
1
+ import { ArrayKey, IfNever, NonArrayKey } from '@/types/utils';
2
+
3
+ /** A schema for strict HTTP form data. */
4
+ export interface HttpFormDataSchema {
5
+ [fieldName: string]: string | string[] | Blob | Blob[] | null | undefined;
6
+ }
7
+
8
+ export namespace HttpFormDataSchema {
9
+ /** A schema for loose HTTP form data. Field values are not strictly typed. */
10
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
11
+ export type Loose = Record<string, any>;
12
+ }
13
+
14
+ export namespace HttpFormDataSchemaName {
15
+ /** Extracts the names of the form data fields defined in a {@link HttpFormDataSchema} that are arrays. */
16
+ export type Array<Schema extends HttpFormDataSchema> = IfNever<Schema, never, ArrayKey<Schema> & string>;
17
+
18
+ /** Extracts the names of the form data fields defined in a {@link HttpFormDataSchema} that are not arrays. */
19
+ export type NonArray<Schema extends HttpFormDataSchema> = IfNever<Schema, never, NonArrayKey<Schema> & string>;
20
+ }
21
+
22
+ /**
23
+ * Extracts the names of the form data fields defined in a {@link HttpFormDataSchema}. Each key is considered a field
24
+ * name. `HttpFormDataSchemaName.Array` can be used to extract the names of array form data fields, whereas
25
+ * `HttpFormDataSchemaName.NonArray` extracts the names of non-array form data fields.
26
+ *
27
+ * @example
28
+ * import { type HttpFormDataSchemaName } from '@zimic/http';
29
+ *
30
+ * type FormDataName = HttpFormDataSchemaName<{
31
+ * title: string;
32
+ * descriptions: string[];
33
+ * content: Blob;
34
+ * }>;
35
+ * // "title" | "descriptions" | "content"
36
+ *
37
+ * type ArrayFormDataName = HttpFormDataSchemaName.Array<{
38
+ * title: string;
39
+ * descriptions: string[];
40
+ * content: Blob;
41
+ * }>;
42
+ * // "descriptions"
43
+ *
44
+ * type NonArrayFormDataName = HttpFormDataSchemaName.NonArray<{
45
+ * title: string;
46
+ * descriptions: string[];
47
+ * content: Blob;
48
+ * }>;
49
+ * // "title" | "content"
50
+ */
51
+ export type HttpFormDataSchemaName<Schema extends HttpFormDataSchema> = IfNever<Schema, never, keyof Schema & string>;
52
+
53
+ type PrimitiveHttpFormDataSerialized<Type> = Type extends HttpFormDataSchema[string]
54
+ ? Type
55
+ : Type extends (infer ArrayItem)[]
56
+ ? ArrayItem extends (infer _InternalArrayItem)[]
57
+ ? never
58
+ : PrimitiveHttpFormDataSerialized<ArrayItem>[]
59
+ : Type extends number
60
+ ? `${number}`
61
+ : Type extends boolean
62
+ ? `${boolean}`
63
+ : never;
64
+
65
+ /**
66
+ * Recursively converts a schema to its {@link https://developer.mozilla.org/docs/Web/API/FormData FormData}-serialized
67
+ * version. Numbers and booleans are converted to `${number}` and `${boolean}` respectively, and not serializable values
68
+ * are excluded, such as functions and dates.
69
+ *
70
+ * @example
71
+ * import { type HttpFormDataSerialized } from '@zimic/http';
72
+ *
73
+ * type Schema = HttpFormDataSerialized<{
74
+ * contentTitle: string | null;
75
+ * contentSize: number;
76
+ * content: Blob;
77
+ * full?: boolean;
78
+ * date: Date;
79
+ * method: () => void;
80
+ * }>;
81
+ * // {
82
+ * // contentTitle: string | null;
83
+ * // contentSize? `${number}`;
84
+ * // content: Blob;
85
+ * // full?: "false" | "true";
86
+ * // }
87
+ */
88
+ export type HttpFormDataSerialized<Type> = Type extends HttpFormDataSchema
89
+ ? Type
90
+ : Type extends (infer _ArrayItem)[]
91
+ ? never
92
+ : Type extends Date
93
+ ? never
94
+ : Type extends (...parameters: never[]) => unknown
95
+ ? never
96
+ : Type extends symbol
97
+ ? never
98
+ : Type extends Map<infer _Key, infer _Value>
99
+ ? never
100
+ : Type extends Set<infer _Value>
101
+ ? never
102
+ : Type extends object
103
+ ? {
104
+ [Key in keyof Type as IfNever<
105
+ PrimitiveHttpFormDataSerialized<Type[Key]>,
106
+ never,
107
+ Key
108
+ >]: PrimitiveHttpFormDataSerialized<Type[Key]>;
109
+ }
110
+ : never;
@@ -0,0 +1,217 @@
1
+ import { Default, ReplaceBy } from '@/types/utils';
2
+
3
+ import { HttpHeadersSchema, HttpHeadersInit, HttpHeadersSchemaName } from './types';
4
+
5
+ function pickPrimitiveProperties<Schema extends HttpHeadersSchema>(schema: Schema) {
6
+ return Object.entries(schema).reduce<Record<string, string>>((accumulated, [key, value]) => {
7
+ if (value !== undefined) {
8
+ accumulated[key] = value;
9
+ }
10
+ return accumulated;
11
+ }, {});
12
+ }
13
+
14
+ /**
15
+ * An extended HTTP headers object with a strictly-typed schema. Fully compatible with the built-in
16
+ * {@link https://developer.mozilla.org/docs/Web/API/Headers `Headers`} class.
17
+ *
18
+ * **IMPORTANT**: the input of `HttpHeaders` and all of its internal types must be declared inline or as a type aliases
19
+ * (`type`). They cannot be interfaces.
20
+ *
21
+ * @example
22
+ * import { HttpHeaders } from '@zimic/http';
23
+ *
24
+ * const headers = new HttpHeaders<{
25
+ * accept?: string;
26
+ * 'content-type'?: string;
27
+ * }>({
28
+ * accept: '*',
29
+ * 'content-type': 'application/json',
30
+ * });
31
+ *
32
+ * const contentType = headers.get('content-type');
33
+ * console.log(contentType); // 'application/json'
34
+ *
35
+ * @see {@link https://github.com/zimicjs/zimic/wiki/api‐zimic‐http#httpheaders `HttpHeaders` API reference}
36
+ */
37
+ class HttpHeaders<Schema extends HttpHeadersSchema = HttpHeadersSchema> extends Headers {
38
+ constructor(init?: HttpHeadersInit<Schema>) {
39
+ if (init instanceof Headers || Array.isArray(init) || !init) {
40
+ super(init);
41
+ } else {
42
+ super(pickPrimitiveProperties(init));
43
+ }
44
+ }
45
+
46
+ /** @see {@link https://developer.mozilla.org/docs/Web/API/Headers/set MDN Reference} */
47
+ set<Name extends HttpHeadersSchemaName<Schema>>(name: Name, value: NonNullable<Schema[Name]>): void {
48
+ super.set(name, value);
49
+ }
50
+
51
+ /** @see {@link https://developer.mozilla.org/docs/Web/API/Headers/append MDN Reference} */
52
+ append<Name extends HttpHeadersSchemaName<Schema>>(name: Name, value: NonNullable<Schema[Name]>): void {
53
+ super.append(name, value);
54
+ }
55
+
56
+ /** @see {@link https://developer.mozilla.org/docs/Web/API/Headers/get MDN Reference} */
57
+ get<Name extends HttpHeadersSchemaName<Schema>>(name: Name): ReplaceBy<Schema[Name], undefined, null> {
58
+ return super.get(name) as ReplaceBy<Schema[Name], undefined, null>;
59
+ }
60
+
61
+ /** @see {@link https://developer.mozilla.org/docs/Web/API/Headers/has MDN Reference} */
62
+ getSetCookie(): NonNullable<Default<Schema['Set-Cookie'], string>>[] {
63
+ return super.getSetCookie() as NonNullable<Default<Schema['Set-Cookie'], string>>[];
64
+ }
65
+
66
+ /** @see {@link https://developer.mozilla.org/docs/Web/API/Headers/has MDN Reference} */
67
+ has<Name extends HttpHeadersSchemaName<Schema>>(name: Name): boolean {
68
+ return super.has(name);
69
+ }
70
+
71
+ /** @see {@link https://developer.mozilla.org/docs/Web/API/Headers/delete MDN Reference} */
72
+ delete<Name extends HttpHeadersSchemaName<Schema>>(name: Name): void {
73
+ super.delete(name);
74
+ }
75
+
76
+ forEach<This extends HttpHeaders<Schema>>(
77
+ callback: <Key extends HttpHeadersSchemaName<Schema>>(
78
+ value: NonNullable<Schema[Key]>,
79
+ key: Key,
80
+ parent: Headers,
81
+ ) => void,
82
+ thisArg?: This,
83
+ ): void {
84
+ super.forEach(callback as (value: string, key: string, parent: Headers) => void, thisArg);
85
+ }
86
+
87
+ /** @see {@link https://developer.mozilla.org/docs/Web/API/Headers/keys MDN Reference} */
88
+ keys(): HeadersIterator<HttpHeadersSchemaName<Schema>> {
89
+ return super.keys() as HeadersIterator<HttpHeadersSchemaName<Schema>>;
90
+ }
91
+
92
+ /** @see {@link https://developer.mozilla.org/docs/Web/API/Headers/values MDN Reference} */
93
+ values(): HeadersIterator<NonNullable<Schema[HttpHeadersSchemaName<Schema>]>> {
94
+ return super.values() as HeadersIterator<NonNullable<Schema[HttpHeadersSchemaName<Schema>]>>;
95
+ }
96
+
97
+ /** @see {@link https://developer.mozilla.org/docs/Web/API/Headers/entries MDN Reference} */
98
+ entries(): HeadersIterator<[HttpHeadersSchemaName<Schema>, NonNullable<Schema[HttpHeadersSchemaName<Schema>]>]> {
99
+ return super.entries() as HeadersIterator<
100
+ [HttpHeadersSchemaName<Schema>, NonNullable<Schema[HttpHeadersSchemaName<Schema>]>]
101
+ >;
102
+ }
103
+
104
+ [Symbol.iterator](): HeadersIterator<
105
+ [HttpHeadersSchemaName<Schema>, NonNullable<Schema[HttpHeadersSchemaName<Schema>]>]
106
+ > {
107
+ return super[Symbol.iterator]() as HeadersIterator<
108
+ [HttpHeadersSchemaName<Schema>, NonNullable<Schema[HttpHeadersSchemaName<Schema>]>]
109
+ >;
110
+ }
111
+
112
+ /**
113
+ * Checks if this headers object is equal to another set of headers. Equality is defined as having the same keys and
114
+ * values, regardless of the order of keys.
115
+ *
116
+ * @param otherHeaders The other headers object to compare against.
117
+ * @returns `true` if the headers are equal, `false` otherwise.
118
+ */
119
+ equals<OtherSchema extends Schema>(otherHeaders: HttpHeaders<OtherSchema>): boolean {
120
+ for (const [key, otherValue] of otherHeaders.entries()) {
121
+ const value = super.get.call(this, key);
122
+
123
+ if (value === null) {
124
+ return false;
125
+ }
126
+
127
+ const valueItems = this.splitHeaderValues(value);
128
+ const otherValueItems = this.splitHeaderValues(otherValue);
129
+
130
+ const haveCompatibleNumberOfValues = valueItems.length === otherValueItems.length;
131
+ if (!haveCompatibleNumberOfValues) {
132
+ return false;
133
+ }
134
+
135
+ for (const otherValueItem of otherValueItems) {
136
+ if (!valueItems.includes(otherValueItem)) {
137
+ return false;
138
+ }
139
+ }
140
+ }
141
+
142
+ for (const key of this.keys()) {
143
+ const otherHasKey = super.has.call(otherHeaders, key);
144
+ if (!otherHasKey) {
145
+ return false;
146
+ }
147
+ }
148
+
149
+ return true;
150
+ }
151
+
152
+ /**
153
+ * Checks if this headers object contains another set of headers. This method is less strict than
154
+ * {@link HttpHeaders#equals} and only requires that all keys and values in the other headers are present in these
155
+ * headers.
156
+ *
157
+ * @param otherHeaders The other headers object to compare against.
158
+ * @returns `true` if these headers contain the other headers, `false` otherwise.
159
+ */
160
+ contains<OtherSchema extends Schema>(otherHeaders: HttpHeaders<OtherSchema>): boolean {
161
+ for (const [key, otherValue] of otherHeaders.entries()) {
162
+ const value = super.get.call(this, key);
163
+
164
+ if (value === null) {
165
+ return false;
166
+ }
167
+
168
+ const valueItems = this.splitHeaderValues(value);
169
+ const otherValueItems = this.splitHeaderValues(otherValue);
170
+
171
+ const haveCompatibleNumberOfValues = valueItems.length >= otherValueItems.length;
172
+ if (!haveCompatibleNumberOfValues) {
173
+ return false;
174
+ }
175
+
176
+ for (const otherValueItem of otherValueItems) {
177
+ if (!valueItems.includes(otherValueItem)) {
178
+ return false;
179
+ }
180
+ }
181
+ }
182
+
183
+ return true;
184
+ }
185
+
186
+ /**
187
+ * Converts these headers into a plain object. This method is useful for serialization and debugging purposes.
188
+ *
189
+ * @example
190
+ * const headers = new HttpHeaders({
191
+ * accept: 'application/json',
192
+ * 'content-type': 'application/json',
193
+ * });
194
+ * const object = headers.toObject();
195
+ * console.log(object); // { accept: 'application/json', 'content-type': 'application/json' }
196
+ *
197
+ * @returns A plain object representation of these headers.
198
+ */
199
+ toObject(): Schema {
200
+ const object = {} as Schema;
201
+
202
+ for (const [key, value] of this.entries()) {
203
+ object[key] = value;
204
+ }
205
+
206
+ return object;
207
+ }
208
+
209
+ private splitHeaderValues(value: string) {
210
+ return value
211
+ .split(',')
212
+ .map((item) => item.trim())
213
+ .filter((item) => item.length > 0);
214
+ }
215
+ }
216
+
217
+ export default HttpHeaders;
@@ -0,0 +1,65 @@
1
+ import { IfNever } from '@/types/utils';
2
+
3
+ import { HttpPathParamsSerialized } from '../pathParams/types';
4
+ import HttpHeaders from './HttpHeaders';
5
+
6
+ /** A schema for strict HTTP headers. */
7
+ export interface HttpHeadersSchema {
8
+ [headerName: string]: string | undefined;
9
+ }
10
+
11
+ export namespace HttpHeadersSchema {
12
+ /** A schema for loose HTTP headers. Header values are not strictly typed. */
13
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
14
+ export type Loose = Record<string, any>;
15
+ }
16
+
17
+ /** A strict tuple representation of a {@link HttpHeadersSchema}. */
18
+ export type HttpHeadersSchemaTuple<Schema extends HttpHeadersSchema = HttpHeadersSchema> = {
19
+ [Key in keyof Schema & string]: [Key, NonNullable<Schema[Key]>];
20
+ }[keyof Schema & string];
21
+
22
+ /** An initialization value for {@link https://github.com/zimicjs/zimic/wiki/api‐zimic‐http#httpheaders `HttpHeaders`}. */
23
+ export type HttpHeadersInit<Schema extends HttpHeadersSchema = HttpHeadersSchema> =
24
+ | Headers
25
+ | Schema
26
+ | HttpHeaders<Schema>
27
+ | HttpHeadersSchemaTuple<Schema>[];
28
+
29
+ /**
30
+ * Extracts the names of the headers defined in a {@link HttpHeadersSchema}. Each key is considered a header name.
31
+ *
32
+ * @example
33
+ * import { type HttpHeadersSchemaName } from '@zimic/http';
34
+ *
35
+ * type HeaderName = HttpHeadersSchemaName<{
36
+ * 'content-type': string;
37
+ * 'content-length'?: string;
38
+ * }>;
39
+ * // "content-type" | "content-length"
40
+ */
41
+ export type HttpHeadersSchemaName<Schema extends HttpHeadersSchema> = IfNever<Schema, never, keyof Schema & string>;
42
+
43
+ /**
44
+ * Recursively converts a schema to its
45
+ * {@link https://developer.mozilla.org/docs/Web/API/Headers HTTP headers}-serialized version. Numbers and booleans are
46
+ * converted to `${number}` and `${boolean}` respectively, null becomes undefined and not serializable values are
47
+ * excluded, such as functions and dates.
48
+ *
49
+ * @example
50
+ * import { type HttpHeadersSerialized } from '@zimic/http';
51
+ *
52
+ * type Params = HttpHeadersSerialized<{
53
+ * 'content-type': string;
54
+ * 'x-remaining-tries': number;
55
+ * 'x-full'?: boolean;
56
+ * 'x-date': Date;
57
+ * method: () => void;
58
+ * }>;
59
+ * // {
60
+ * // 'content-type': string;
61
+ * // 'x-remaining-tries': `${number}`;
62
+ * // 'x-full'?: "false" | "true";
63
+ * // }
64
+ */
65
+ export type HttpHeadersSerialized<Type> = HttpPathParamsSerialized<Type>;