@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,803 @@
1
+ import chalk from 'chalk';
2
+ import ts from 'typescript';
3
+
4
+ import { HTTP_METHODS, HttpMethod } from '@/types/schema';
5
+ import { Override } from '@/types/utils';
6
+ import { logWithPrefix } from '@/utils/console';
7
+ import { isDefined } from '@/utils/data';
8
+
9
+ import { isUnknownType, isNeverType, isNullType } from '../utils/types';
10
+ import { renameComponentReferences } from './components';
11
+ import { TypeTransformContext } from './context';
12
+ import { createOperationsIdentifier } from './operations';
13
+
14
+ type Method = Override<
15
+ ts.PropertySignature,
16
+ {
17
+ type: ts.TypeLiteralNode | ts.IndexedAccessTypeNode;
18
+ name: ts.Identifier;
19
+ }
20
+ >;
21
+
22
+ function isMethod(node: ts.Node): node is Method {
23
+ return (
24
+ ts.isPropertySignature(node) &&
25
+ ts.isIdentifier(node.name) &&
26
+ node.type !== undefined &&
27
+ (ts.isTypeLiteralNode(node.type) || ts.isIndexedAccessTypeNode(node.type))
28
+ );
29
+ }
30
+
31
+ type MethodMember = Override<
32
+ ts.PropertySignature,
33
+ {
34
+ name: ts.Identifier | ts.StringLiteral;
35
+ type: ts.TypeLiteralNode | ts.IndexedAccessTypeNode;
36
+ }
37
+ >;
38
+
39
+ function isMethodMember(node: ts.Node): node is MethodMember {
40
+ return (
41
+ ts.isPropertySignature(node) &&
42
+ ts.isIdentifier(node.name) &&
43
+ node.type !== undefined &&
44
+ (ts.isTypeLiteralNode(node.type) || ts.isIndexedAccessTypeNode(node.type) || isNeverType(node.type))
45
+ );
46
+ }
47
+
48
+ type RequestMember = Override<
49
+ ts.PropertySignature,
50
+ {
51
+ name: ts.Identifier;
52
+ type: ts.TypeNode;
53
+ }
54
+ >;
55
+
56
+ function isRequestMember(node: ts.Node): node is RequestMember {
57
+ return (
58
+ ts.isPropertySignature(node) && ts.isIdentifier(node.name) && node.type !== undefined && !isNeverType(node.type)
59
+ );
60
+ }
61
+
62
+ type RequestHeaders = Override<RequestMember, { type: ts.TypeLiteralNode }>;
63
+
64
+ function isRequestHeaders(node: ts.Node): node is RequestHeaders {
65
+ return isRequestMember(node) && node.name.text === 'headers' && ts.isTypeLiteralNode(node.type);
66
+ }
67
+
68
+ type NormalizedRequestHeaders = Override<
69
+ RequestHeaders,
70
+ {
71
+ type: Override<ts.TypeReferenceNode, { typeArguments: ts.NodeArray<ts.TypeLiteralNode> }>;
72
+ }
73
+ >;
74
+
75
+ function isNormalizedRequestHeaders(node: ts.Node): node is NormalizedRequestHeaders {
76
+ return (
77
+ isRequestMember(node) &&
78
+ node.name.text === 'headers' &&
79
+ ts.isTypeReferenceNode(node.type) &&
80
+ node.type.typeArguments !== undefined &&
81
+ node.type.typeArguments.length === 1 &&
82
+ ts.isTypeLiteralNode(node.type.typeArguments[0])
83
+ );
84
+ }
85
+
86
+ type RequestParameters = Override<RequestMember, { type: ts.TypeLiteralNode }>;
87
+
88
+ function isRequestParameters(node: ts.Node): node is RequestParameters {
89
+ return isRequestMember(node) && node.name.text === 'parameters' && ts.isTypeLiteralNode(node.type);
90
+ }
91
+
92
+ type Content = Override<RequestMember, { type: ts.TypeLiteralNode }>;
93
+
94
+ function isContentPropertySignature(node: ts.Node): node is Content {
95
+ return isRequestMember(node) && node.name.text === 'content' && ts.isTypeLiteralNode(node.type);
96
+ }
97
+
98
+ type ContentMember = Override<
99
+ ts.PropertySignature,
100
+ {
101
+ name: ts.Identifier | ts.StringLiteral;
102
+ type: ts.TypeNode;
103
+ }
104
+ >;
105
+
106
+ function isContentMember(node: ts.Node): node is ContentMember {
107
+ return (
108
+ ts.isPropertySignature(node) &&
109
+ (ts.isIdentifier(node.name) || ts.isStringLiteral(node.name)) &&
110
+ node.type !== undefined &&
111
+ !isNeverType(node.type)
112
+ );
113
+ }
114
+
115
+ type Response = Override<
116
+ ts.PropertySignature,
117
+ {
118
+ name: ts.Identifier | ts.StringLiteral | ts.NumericLiteral;
119
+ type: ts.TypeNode;
120
+ }
121
+ >;
122
+
123
+ function isResponse(node: ts.Node): node is Response {
124
+ return (
125
+ ts.isPropertySignature(node) &&
126
+ (ts.isIdentifier(node.name) || ts.isStringLiteral(node.name) || ts.isNumericLiteral(node.name)) &&
127
+ node.type !== undefined
128
+ );
129
+ }
130
+
131
+ function removeRedundantNullUnionIfNecessary(type: ts.TypeNode) {
132
+ const containsRedundantNullUnion =
133
+ ts.isUnionTypeNode(type) &&
134
+ type.types.some((type) => {
135
+ const isNull = ts.isLiteralTypeNode(type) && isNullType(type.literal);
136
+ return isNull;
137
+ }) &&
138
+ type.types.some((type) => {
139
+ return (
140
+ ts.isParenthesizedTypeNode(type) &&
141
+ ts.isUnionTypeNode(type.type) &&
142
+ type.type.types.some((subType) => {
143
+ const isNull = ts.isLiteralTypeNode(subType) && isNullType(subType.literal);
144
+ return isNull;
145
+ })
146
+ );
147
+ });
148
+
149
+ if (!containsRedundantNullUnion) {
150
+ return type;
151
+ }
152
+
153
+ const typesWithoutRedundantNullUnion = type.types
154
+ .filter((type) => {
155
+ const isNull = ts.isLiteralTypeNode(type) && isNullType(type.literal);
156
+ return !isNull;
157
+ })
158
+ .flatMap((type) => {
159
+ /* istanbul ignore else -- @preserve */
160
+ if (ts.isParenthesizedTypeNode(type) && ts.isUnionTypeNode(type.type)) {
161
+ return type.type.types;
162
+ }
163
+ /* istanbul ignore next -- @preserve
164
+ * Member types are always expected to be a union type or a parenthesized union type. */
165
+ return [type];
166
+ });
167
+
168
+ return ts.factory.createUnionTypeNode(typesWithoutRedundantNullUnion);
169
+ }
170
+
171
+ function wrapFormDataContentType(type: ts.TypeNode, context: TypeTransformContext) {
172
+ context.typeImports.http.add('HttpFormData');
173
+ context.typeImports.http.add('HttpFormDataSerialized');
174
+
175
+ return ts.factory.createTypeReferenceNode(ts.factory.createIdentifier('HttpFormData'), [
176
+ ts.factory.createTypeReferenceNode(ts.factory.createIdentifier('HttpFormDataSerialized'), [
177
+ renameComponentReferences(type, context),
178
+ ]),
179
+ ]);
180
+ }
181
+
182
+ function wrapURLEncodedContentType(type: ts.TypeNode, context: TypeTransformContext) {
183
+ context.typeImports.http.add('HttpSearchParams');
184
+ context.typeImports.http.add('HttpSearchParamsSerialized');
185
+
186
+ return ts.factory.createTypeReferenceNode(ts.factory.createIdentifier('HttpSearchParams'), [
187
+ ts.factory.createTypeReferenceNode(ts.factory.createIdentifier('HttpSearchParamsSerialized'), [
188
+ renameComponentReferences(type, context),
189
+ ]),
190
+ ]);
191
+ }
192
+
193
+ function normalizeRequestBodyMember(
194
+ requestBodyMember: ts.TypeElement,
195
+ context: TypeTransformContext,
196
+ options: { questionToken: ts.QuestionToken | undefined },
197
+ ) {
198
+ /* istanbul ignore if -- @preserve
199
+ * Request body members are always expected to be a request body. */
200
+ if (!isContentMember(requestBodyMember)) {
201
+ return undefined;
202
+ }
203
+
204
+ const newIdentifier = ts.factory.createIdentifier('body');
205
+
206
+ const contentType = requestBodyMember.name.text;
207
+ let newType = removeRedundantNullUnionIfNecessary(renameComponentReferences(requestBodyMember.type, context));
208
+
209
+ if (contentType === 'multipart/form-data') {
210
+ newType = wrapFormDataContentType(newType, context);
211
+ } else if (contentType === 'x-www-form-urlencoded') {
212
+ newType = wrapURLEncodedContentType(newType, context);
213
+ }
214
+
215
+ return {
216
+ contentTypeName: contentType,
217
+ propertySignature: ts.factory.updatePropertySignature(
218
+ requestBodyMember,
219
+ requestBodyMember.modifiers,
220
+ newIdentifier,
221
+ options.questionToken,
222
+ newType,
223
+ ),
224
+ };
225
+ }
226
+
227
+ function wrapHeadersType(type: ts.TypeLiteralNode, context: TypeTransformContext): NormalizedRequestHeaders['type'] {
228
+ context.typeImports.http.add('HttpHeadersSerialized');
229
+
230
+ const serializedWrapper = ts.factory.createIdentifier('HttpHeadersSerialized');
231
+
232
+ return ts.factory.createTypeReferenceNode(
233
+ serializedWrapper,
234
+ ts.factory.createNodeArray([type]) satisfies NormalizedRequestHeaders['type']['typeArguments'],
235
+ ) as NormalizedRequestHeaders['type'];
236
+ }
237
+
238
+ function normalizeHeaders(headers: ts.TypeLiteralNode, context: TypeTransformContext) {
239
+ const newHeaderMembers = headers.members.filter((header) => {
240
+ if (ts.isIndexSignatureDeclaration(header)) {
241
+ return false;
242
+ }
243
+ /* istanbul ignore else -- @preserve */
244
+ if (ts.isPropertySignature(header)) {
245
+ return header.type !== undefined && !isUnknownType(header.type);
246
+ }
247
+ /* istanbul ignore next -- @preserve
248
+ * Headers are always expected to be property signatures or index signatures. */
249
+ return true;
250
+ });
251
+
252
+ if (newHeaderMembers.length === 0) {
253
+ return undefined;
254
+ }
255
+
256
+ const newHeaders = ts.factory.updateTypeLiteralNode(headers, ts.factory.createNodeArray(newHeaderMembers));
257
+ return wrapHeadersType(newHeaders, context);
258
+ }
259
+
260
+ function normalizeRequestHeaders(
261
+ requestHeader: ts.TypeElement,
262
+ context: TypeTransformContext,
263
+ ): NormalizedRequestHeaders | undefined {
264
+ if (!isRequestHeaders(requestHeader)) {
265
+ return undefined;
266
+ }
267
+
268
+ const newType = normalizeHeaders(requestHeader.type, context);
269
+
270
+ if (!newType) {
271
+ return undefined;
272
+ }
273
+
274
+ return ts.factory.updatePropertySignature(
275
+ requestHeader,
276
+ requestHeader.modifiers,
277
+ requestHeader.name,
278
+ requestHeader.questionToken,
279
+ newType satisfies NormalizedRequestHeaders['type'],
280
+ ) satisfies ts.PropertySignature as NormalizedRequestHeaders;
281
+ }
282
+
283
+ function createHeaderForUnionByContentType(
284
+ existingHeader: NormalizedRequestHeaders | undefined,
285
+ contentTypeName: string,
286
+ context: TypeTransformContext,
287
+ ) {
288
+ const existingHeaderMembers = existingHeader ? existingHeader.type.typeArguments[0].members : [];
289
+
290
+ const contentTypeIdentifier = ts.factory.createIdentifier('"content-type"');
291
+ const contentTypeValue = ts.factory.createLiteralTypeNode(ts.factory.createStringLiteral(contentTypeName));
292
+
293
+ const newHeaderType = wrapHeadersType(
294
+ ts.factory.createTypeLiteralNode([
295
+ ts.factory.createPropertySignature(undefined, contentTypeIdentifier, undefined, contentTypeValue),
296
+ ...existingHeaderMembers,
297
+ ]),
298
+ context,
299
+ );
300
+
301
+ return ts.factory.createPropertySignature(
302
+ existingHeader?.modifiers,
303
+ ts.factory.createIdentifier('headers'),
304
+ undefined,
305
+ newHeaderType,
306
+ );
307
+ }
308
+
309
+ export function normalizeContentType(
310
+ contentType: ts.TypeNode,
311
+ context: TypeTransformContext,
312
+ options: { bodyQuestionToken: ts.QuestionToken | undefined },
313
+ ) {
314
+ const { bodyQuestionToken } = options;
315
+
316
+ if (ts.isIndexedAccessTypeNode(contentType)) {
317
+ return renameComponentReferences(contentType, context);
318
+ }
319
+
320
+ if (!ts.isTypeLiteralNode(contentType)) {
321
+ return contentType;
322
+ }
323
+
324
+ const newHeader = contentType.members.map((member) => normalizeRequestHeaders(member, context)).find(isDefined);
325
+
326
+ const newBodyMembers = contentType.members.flatMap((body) => {
327
+ if (isContentPropertySignature(body)) {
328
+ return body.type.members
329
+ .map((member) => normalizeRequestBodyMember(member, context, { questionToken: bodyQuestionToken }))
330
+ .filter(isDefined);
331
+ }
332
+ return [];
333
+ });
334
+
335
+ if (newBodyMembers.length <= 1) {
336
+ const newBodyMemberPropertySignatures = newBodyMembers.map((bodyMember) => bodyMember.propertySignature);
337
+ const newMembers = [newHeader, ...newBodyMemberPropertySignatures].filter(isDefined);
338
+
339
+ return ts.factory.updateTypeLiteralNode(contentType, ts.factory.createNodeArray(newMembers));
340
+ } else {
341
+ const bodyMemberUnionTypes = newBodyMembers.map((bodyMember) => {
342
+ const headerMember = createHeaderForUnionByContentType(newHeader, bodyMember.contentTypeName, context);
343
+ return ts.factory.createTypeLiteralNode([headerMember, bodyMember.propertySignature]);
344
+ });
345
+
346
+ return ts.factory.createUnionTypeNode(bodyMemberUnionTypes);
347
+ }
348
+ }
349
+
350
+ function normalizeRequest(request: MethodMember, context: TypeTransformContext) {
351
+ const newIdentifier = ts.factory.createIdentifier('request');
352
+
353
+ const newType = normalizeContentType(request.type, context, {
354
+ bodyQuestionToken: request.questionToken,
355
+ });
356
+
357
+ if (
358
+ request.questionToken &&
359
+ ts.isIndexedAccessTypeNode(request.type) &&
360
+ ts.isLiteralTypeNode(request.type.indexType) &&
361
+ (ts.isIdentifier(request.type.indexType.literal) || ts.isStringLiteral(request.type.indexType.literal))
362
+ ) {
363
+ const referencedComponentName = request.type.indexType.literal.text;
364
+ context.pendingActions.components.requests.toMarkBodyAsOptional.add(referencedComponentName);
365
+ }
366
+
367
+ return ts.factory.updatePropertySignature(request, request.modifiers, newIdentifier, undefined, newType);
368
+ }
369
+
370
+ function wrapResponseType(type: ts.TypeNode, context: TypeTransformContext) {
371
+ context.typeImports.http.add('HttpSchema');
372
+
373
+ const httpSchemaResponseWrapper = ts.factory.createQualifiedName(
374
+ ts.factory.createIdentifier('HttpSchema'),
375
+ ts.factory.createIdentifier('Response'),
376
+ );
377
+ return ts.factory.createTypeReferenceNode(httpSchemaResponseWrapper, [type]);
378
+ }
379
+
380
+ function normalizeResponseType(
381
+ responseType: ts.TypeNode,
382
+ context: TypeTransformContext,
383
+ options: { isComponent: boolean; questionToken?: ts.QuestionToken },
384
+ ) {
385
+ const { isComponent, questionToken } = options;
386
+
387
+ if (!ts.isTypeLiteralNode(responseType)) {
388
+ return responseType;
389
+ }
390
+
391
+ const newType = normalizeContentType(responseType, context, { bodyQuestionToken: questionToken });
392
+ return isComponent ? wrapResponseType(newType, context) : newType;
393
+ }
394
+
395
+ const NON_NUMERIC_RESPONSE_STATUS_TO_MAPPED_TYPE: Record<string, string | undefined> = {
396
+ default: 'HttpStatusCode',
397
+ '1xx': 'HttpStatusCode.Information',
398
+ '2xx': 'HttpStatusCode.Success',
399
+ '3xx': 'HttpStatusCode.Redirection',
400
+ '4xx': 'HttpStatusCode.ClientError',
401
+ '5xx': 'HttpStatusCode.ServerError',
402
+ };
403
+
404
+ export function normalizeResponse(
405
+ response: ts.TypeElement,
406
+ context: TypeTransformContext,
407
+ options: { isComponent?: boolean } = {},
408
+ ) {
409
+ const { isComponent = false } = options;
410
+
411
+ /* istanbul ignore if -- @preserve
412
+ * Response members are always expected to be a response. */
413
+ if (!isResponse(response)) {
414
+ return undefined;
415
+ }
416
+
417
+ const newType = normalizeResponseType(response.type, context, {
418
+ isComponent,
419
+ questionToken: response.questionToken,
420
+ });
421
+
422
+ const statusCodeOrComponentName = response.name.text;
423
+ const isNumericStatusCode = /^\d+$/.test(statusCodeOrComponentName);
424
+ const shouldReuseIdentifier = isComponent || isNumericStatusCode;
425
+
426
+ let newSignature: ts.PropertySignature;
427
+
428
+ if (shouldReuseIdentifier) {
429
+ newSignature = ts.factory.updatePropertySignature(
430
+ response,
431
+ response.modifiers,
432
+ response.name,
433
+ response.questionToken,
434
+ newType,
435
+ );
436
+ } else {
437
+ const statusCode = statusCodeOrComponentName.toLowerCase();
438
+ const mappedType = NON_NUMERIC_RESPONSE_STATUS_TO_MAPPED_TYPE[statusCode];
439
+
440
+ if (!mappedType) {
441
+ logWithPrefix(
442
+ `Warning: Response has a non-standard status code: ${chalk.yellow(response.name.text)}. ` +
443
+ "Consider replacing it with a number (e.g. '200'), a pattern ('1xx', '2xx', '3xx', '4xx', or '5xx'), " +
444
+ "or 'default'.",
445
+ { method: 'warn' },
446
+ );
447
+
448
+ return undefined;
449
+ }
450
+
451
+ context.typeImports.http.add('HttpStatusCode');
452
+ const newIdentifier = ts.factory.createIdentifier(`[StatusCode in ${mappedType}]`);
453
+
454
+ newSignature = ts.factory.updatePropertySignature(
455
+ response,
456
+ response.modifiers,
457
+ newIdentifier,
458
+ response.questionToken,
459
+ newType,
460
+ );
461
+ }
462
+
463
+ return {
464
+ newSignature,
465
+ statusCode: {
466
+ value: statusCodeOrComponentName,
467
+ isNumeric: isNumericStatusCode,
468
+ },
469
+ };
470
+ }
471
+
472
+ export function normalizeResponses(responses: MethodMember, context: TypeTransformContext) {
473
+ if (isNeverType(responses.type) || !ts.isTypeLiteralNode(responses.type)) {
474
+ return undefined;
475
+ }
476
+
477
+ const newIdentifier = ts.factory.createIdentifier('response');
478
+ const newQuestionToken = undefined;
479
+
480
+ const newMembers = responses.type.members
481
+ .map((response) => normalizeResponse(response, context), context)
482
+ .filter(isDefined);
483
+
484
+ const sortedNewMembers = Array.from(newMembers).sort((response, otherResponse) => {
485
+ return response.statusCode.value.localeCompare(otherResponse.statusCode.value);
486
+ });
487
+
488
+ const isEveryStatusCodeNumeric = sortedNewMembers.every((response) => response.statusCode.isNumeric);
489
+ let newType: ts.TypeLiteralNode | ts.TypeReferenceNode;
490
+
491
+ if (isEveryStatusCodeNumeric) {
492
+ newType = ts.factory.updateTypeLiteralNode(
493
+ responses.type,
494
+ ts.factory.createNodeArray(sortedNewMembers.map((response) => response.newSignature)),
495
+ );
496
+ } else {
497
+ context.typeImports.http.add('MergeHttpResponsesByStatusCode');
498
+
499
+ const typeMembersToMerge = sortedNewMembers.reduce<{
500
+ numeric: ts.PropertySignature[];
501
+ nonNumeric: ts.PropertySignature[];
502
+ }>(
503
+ (members, response) => {
504
+ if (response.statusCode.isNumeric) {
505
+ members.numeric.push(response.newSignature);
506
+ } else {
507
+ members.nonNumeric.push(response.newSignature);
508
+ }
509
+ return members;
510
+ },
511
+ { numeric: [], nonNumeric: [] },
512
+ );
513
+
514
+ const numericTypeLiteral = ts.factory.createTypeLiteralNode(typeMembersToMerge.numeric);
515
+ const nonNumericTypeLiterals = typeMembersToMerge.nonNumeric.map((response) =>
516
+ ts.factory.createTypeLiteralNode([response]),
517
+ );
518
+
519
+ const mergeWrapper = ts.factory.createIdentifier('MergeHttpResponsesByStatusCode');
520
+ newType = ts.factory.createTypeReferenceNode(mergeWrapper, [
521
+ ts.factory.createTupleTypeNode([numericTypeLiteral, ...nonNumericTypeLiterals]),
522
+ ]);
523
+ }
524
+
525
+ return ts.factory.updatePropertySignature(
526
+ responses,
527
+ responses.modifiers,
528
+ newIdentifier,
529
+ newQuestionToken,
530
+ renameComponentReferences(newType, context),
531
+ );
532
+ }
533
+
534
+ function normalizeMethodMember(methodMember: ts.TypeElement, context: TypeTransformContext) {
535
+ /* istanbul ignore else -- @preserve */
536
+ if (isMethodMember(methodMember)) {
537
+ if (methodMember.name.text === 'requestBody') {
538
+ return normalizeRequest(methodMember, context);
539
+ }
540
+ if (methodMember.name.text === 'responses') {
541
+ return normalizeResponses(methodMember, context);
542
+ }
543
+ return methodMember;
544
+ }
545
+
546
+ /* istanbul ignore next -- @preserve
547
+ * Method members are always expected as property signatures in methods. */
548
+ return undefined;
549
+ }
550
+
551
+ function normalizeRequestQueryWithParameters(requestMember: RequestMember, context: TypeTransformContext) {
552
+ const newIdentifier = ts.factory.createIdentifier('searchParams');
553
+ const newQuestionToken = undefined;
554
+
555
+ const newType = renameComponentReferences(requestMember.type, context);
556
+
557
+ context.typeImports.http.add('HttpSearchParamsSerialized');
558
+
559
+ const serializedWrapper = ts.factory.createIdentifier('HttpSearchParamsSerialized');
560
+ const wrappedNewType = ts.factory.createTypeReferenceNode(serializedWrapper, [newType]);
561
+
562
+ return ts.factory.updatePropertySignature(
563
+ requestMember,
564
+ requestMember.modifiers,
565
+ newIdentifier,
566
+ newQuestionToken,
567
+ wrappedNewType,
568
+ );
569
+ }
570
+
571
+ function normalizeRequestHeadersWithParameters(requestMember: RequestMember, context: TypeTransformContext) {
572
+ const newIdentifier = ts.factory.createIdentifier('headers');
573
+ const newQuestionToken = undefined;
574
+
575
+ const newType = renameComponentReferences(requestMember.type, context);
576
+
577
+ context.typeImports.http.add('HttpHeadersSerialized');
578
+
579
+ const serializedWrapper = ts.factory.createIdentifier('HttpHeadersSerialized');
580
+ const wrappedNewType = ts.factory.createTypeReferenceNode(serializedWrapper, [newType]);
581
+
582
+ return ts.factory.updatePropertySignature(
583
+ requestMember,
584
+ requestMember.modifiers,
585
+ newIdentifier,
586
+ newQuestionToken,
587
+ wrappedNewType,
588
+ );
589
+ }
590
+
591
+ function normalizeRequestMemberWithParameters(requestMember: ts.TypeElement, context: TypeTransformContext) {
592
+ if (!isRequestMember(requestMember) || requestMember.name.text === 'path') {
593
+ return undefined;
594
+ }
595
+
596
+ if (requestMember.name.text === 'query') {
597
+ return normalizeRequestQueryWithParameters(requestMember, context);
598
+ }
599
+ if (requestMember.name.text === 'header') {
600
+ return normalizeRequestHeadersWithParameters(requestMember, context);
601
+ }
602
+
603
+ return requestMember;
604
+ }
605
+
606
+ function mergeRequestHeadersMember(headers: NormalizedRequestHeaders, otherHeaders: NormalizedRequestHeaders) {
607
+ const headersTypeLiteral = headers.type.typeArguments[0];
608
+ const otherHeadersTypeLiteral = otherHeaders.type.typeArguments[0];
609
+
610
+ const newType = ts.factory.updateTypeReferenceNode(
611
+ headers.type,
612
+ headers.type.typeName,
613
+ ts.factory.createNodeArray([
614
+ ts.factory.createTypeLiteralNode([...otherHeadersTypeLiteral.members, ...headersTypeLiteral.members]),
615
+ ]) satisfies NormalizedRequestHeaders['type']['typeArguments'],
616
+ ) as NormalizedRequestHeaders['type'];
617
+
618
+ return ts.factory.updatePropertySignature(
619
+ headers,
620
+ headers.modifiers,
621
+ headers.name,
622
+ headers.questionToken,
623
+ newType satisfies NormalizedRequestHeaders['type'],
624
+ ) satisfies ts.PropertySignature as NormalizedRequestHeaders;
625
+ }
626
+
627
+ function mergeRequestHeadersMembers(members: (ts.PropertySignature | undefined)[]) {
628
+ let mergedHeaders: NormalizedRequestHeaders | undefined;
629
+ let firstHeadersIndex: number | undefined;
630
+
631
+ const mergedHeadersMembers = members.map((member, index) => {
632
+ if (!member || !isNormalizedRequestHeaders(member)) {
633
+ return member;
634
+ }
635
+
636
+ if (firstHeadersIndex === undefined || !mergedHeaders) {
637
+ firstHeadersIndex = index;
638
+ mergedHeaders = member;
639
+ return member;
640
+ }
641
+
642
+ mergedHeaders = mergeRequestHeadersMember(mergedHeaders, member);
643
+ return undefined;
644
+ });
645
+
646
+ if (firstHeadersIndex !== undefined) {
647
+ mergedHeadersMembers[firstHeadersIndex] = mergedHeaders;
648
+ }
649
+
650
+ return mergedHeadersMembers.filter(isDefined);
651
+ }
652
+
653
+ function mergeRequestAndParameterTypes(
654
+ requestType: ts.TypeNode,
655
+ methodMembers: ts.TypeElement[],
656
+ context: TypeTransformContext,
657
+ ) {
658
+ const parameters = methodMembers.find(isRequestParameters);
659
+ /* istanbul ignore next -- @preserve
660
+ * Parameters member is always expected to be found. */
661
+ const parametersMembers = parameters ? parameters.type.members : [];
662
+
663
+ const requestMembers = ts.isTypeLiteralNode(requestType) ? requestType.members : [];
664
+
665
+ const newMembers = mergeRequestHeadersMembers(
666
+ [...parametersMembers, ...requestMembers].map((member) => {
667
+ return normalizeRequestMemberWithParameters(member, context);
668
+ }),
669
+ );
670
+
671
+ if (newMembers.length === 0) {
672
+ return undefined;
673
+ }
674
+
675
+ return ts.factory.createTypeLiteralNode(newMembers);
676
+ }
677
+
678
+ function normalizeRequestTypeWithParameters(
679
+ requestType: ts.TypeNode,
680
+ methodMembers: ts.TypeElement[],
681
+ context: TypeTransformContext,
682
+ ): ts.TypeNode | undefined {
683
+ if (ts.isUnionTypeNode(requestType)) {
684
+ const newTypes = requestType.types
685
+ .map((type) => normalizeRequestTypeWithParameters(type, methodMembers, context))
686
+ .filter(isDefined);
687
+ return ts.factory.updateUnionTypeNode(requestType, ts.factory.createNodeArray(newTypes));
688
+ }
689
+
690
+ if (ts.isIndexedAccessTypeNode(requestType)) {
691
+ const newType = normalizeRequestTypeWithParameters(ts.factory.createTypeLiteralNode([]), methodMembers, context);
692
+ return ts.factory.createIntersectionTypeNode([requestType, newType].filter(isDefined));
693
+ }
694
+
695
+ return mergeRequestAndParameterTypes(requestType, methodMembers, context);
696
+ }
697
+
698
+ function normalizeMethodMemberWithParameters(
699
+ methodMember: ts.PropertySignature,
700
+ methodMembers: ts.TypeElement[],
701
+ context: TypeTransformContext,
702
+ ) {
703
+ /* istanbul ignore if -- @preserve
704
+ * Method members are always expected to have a name and type at this point. */
705
+ if (!ts.isIdentifier(methodMember.name) || !methodMember.type) {
706
+ return undefined;
707
+ }
708
+
709
+ if (methodMember.name.text === 'request') {
710
+ const newType = normalizeRequestTypeWithParameters(methodMember.type, methodMembers, context);
711
+
712
+ if (!newType) {
713
+ return undefined;
714
+ }
715
+
716
+ return ts.factory.updatePropertySignature(
717
+ methodMember,
718
+ methodMember.modifiers,
719
+ methodMember.name,
720
+ undefined,
721
+ newType,
722
+ );
723
+ }
724
+
725
+ if (methodMember.name.text === 'response') {
726
+ return methodMember;
727
+ }
728
+
729
+ return undefined;
730
+ }
731
+
732
+ export function normalizeTypeLiteralMethodType(methodType: ts.TypeLiteralNode, context: TypeTransformContext) {
733
+ const newMembers = methodType.members
734
+ .map((member) => normalizeMethodMember(member, context))
735
+ .filter(isDefined)
736
+ .map((member, _index, partialMembers) => normalizeMethodMemberWithParameters(member, partialMembers, context))
737
+ .filter(isDefined);
738
+
739
+ return ts.factory.updateTypeLiteralNode(methodType, ts.factory.createNodeArray(newMembers));
740
+ }
741
+
742
+ function normalizeIndexedAccessMethodType(methodType: ts.IndexedAccessTypeNode, context: TypeTransformContext) {
743
+ const isOperationsReference =
744
+ ts.isTypeReferenceNode(methodType.objectType) &&
745
+ ts.isIdentifier(methodType.objectType.typeName) &&
746
+ methodType.objectType.typeName.text === 'operations';
747
+
748
+ /* istanbul ignore if -- @preserve
749
+ * In indexed access method types, the reference is always expected to be an operation. */
750
+ if (!isOperationsReference) {
751
+ return methodType;
752
+ }
753
+
754
+ const newIdentifier = createOperationsIdentifier(context.serviceName);
755
+ const newObjectType = ts.factory.createTypeReferenceNode(newIdentifier, methodType.objectType.typeArguments);
756
+
757
+ const hasIndexTypeName =
758
+ ts.isLiteralTypeNode(methodType.indexType) &&
759
+ (ts.isIdentifier(methodType.indexType.literal) || ts.isStringLiteral(methodType.indexType.literal));
760
+
761
+ /* istanbul ignore else -- @preserve
762
+ * In indexed access method types referencing operations, an index type name is always expected. */
763
+ if (hasIndexTypeName) {
764
+ const operationName = methodType.indexType.literal.text;
765
+ context.referencedTypes.operations.add(operationName);
766
+ }
767
+
768
+ return ts.factory.updateIndexedAccessTypeNode(methodType, newObjectType, methodType.indexType);
769
+ }
770
+
771
+ export function normalizeMethod(method: ts.TypeElement, context: TypeTransformContext, options: { pathName: string }) {
772
+ if (!isMethod(method)) {
773
+ return undefined;
774
+ }
775
+
776
+ const methodName = method.name.text.toUpperCase<HttpMethod>();
777
+
778
+ if (!HTTP_METHODS.includes(methodName)) {
779
+ return undefined;
780
+ }
781
+
782
+ const pathMethodCompareString = `${methodName} ${options.pathName}`;
783
+
784
+ const matchesPositiveFilters =
785
+ context.filters.paths.positive.length === 0 ||
786
+ context.filters.paths.positive.some((filter) => filter.test(pathMethodCompareString));
787
+
788
+ const matchesNegativeFilters =
789
+ context.filters.paths.negative.length > 0 &&
790
+ context.filters.paths.negative.some((filter) => filter.test(pathMethodCompareString));
791
+
792
+ if (!matchesPositiveFilters || matchesNegativeFilters) {
793
+ return undefined;
794
+ }
795
+
796
+ const newIdentifier = ts.factory.createIdentifier(methodName);
797
+
798
+ const newType = ts.isTypeLiteralNode(method.type)
799
+ ? normalizeTypeLiteralMethodType(method.type, context)
800
+ : normalizeIndexedAccessMethodType(method.type, context);
801
+
802
+ return ts.factory.updatePropertySignature(method, method.modifiers, newIdentifier, method.questionToken, newType);
803
+ }