fork of hey-api/openapi-ts because I need some additional things
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

Merge pull request #1173 from hey-api/feat/services-class-parser

feat: use new parser for services

authored by

Lubos and committed by
GitHub
dc198166 25e0331c

+536 -122
+1
packages/client-fetch/src/utils.ts
··· 332 332 return; 333 333 } 334 334 335 + // TODO: parser - better detection of MIME types 335 336 if (content.startsWith('application/json') || content.endsWith('+json')) { 336 337 return 'json'; 337 338 }
+363 -62
packages/openapi-ts/src/generate/services.ts
··· 1 + import type ts from 'typescript'; 2 + 1 3 import type { 2 4 ClassElement, 3 5 Comments, ··· 12 14 IRPathItemObject, 13 15 IRPathsObject, 14 16 } from '../ir/ir'; 17 + import { hasOperationDataRequired } from '../ir/operation'; 15 18 import { isOperationParameterRequired } from '../openApi'; 16 19 import type { 17 20 Client, ··· 25 28 import { camelCase } from '../utils/camelCase'; 26 29 import { getConfig, isLegacyClient } from '../utils/config'; 27 30 import { escapeComment, escapeName } from '../utils/escape'; 31 + import { getServiceName } from '../utils/postprocess'; 28 32 import { reservedWordsRegExp } from '../utils/regexp'; 29 33 import { transformServiceName } from '../utils/transform'; 30 34 import { setUniqueTypeName } from '../utils/type'; ··· 135 139 * @param importedType unique type name returned from `setUniqueTypeName()` 136 140 * @returns options type 137 141 */ 138 - export const operationOptionsType = ( 139 - importedType?: string, 140 - throwOnError?: string, 141 - ) => { 142 + export const operationOptionsType = ({ 143 + importedType, 144 + throwOnError, 145 + }: { 146 + importedType?: string; 147 + throwOnError?: string; 148 + }) => { 142 149 const optionsName = clientOptionsTypeName(); 143 150 // TODO: refactor this to be more generic, works for now 144 151 if (throwOnError) { ··· 171 178 { 172 179 isRequired, 173 180 name: 'options', 174 - type: operationOptionsType(importedType, 'ThrowOnError'), 181 + type: operationOptionsType({ 182 + importedType, 183 + throwOnError: 'ThrowOnError', 184 + }), 175 185 }, 176 186 ]; 177 187 } ··· 514 524 }); 515 525 }; 516 526 517 - export const toOperationName = ({ 527 + export const serviceFunctionIdentifier = ({ 518 528 config, 519 529 id, 520 530 operation, ··· 707 717 comment: toOperationComment(operation), 708 718 exportConst: true, 709 719 expression, 710 - name: toOperationName({ 720 + name: serviceFunctionIdentifier({ 711 721 config, 712 722 handleIllegal: true, 713 723 id: operation.name, ··· 725 735 comment: toOperationComment(operation), 726 736 isStatic: 727 737 config.name === undefined && config.client.name !== 'legacy/angular', 728 - name: toOperationName({ 738 + name: serviceFunctionIdentifier({ 729 739 config, 730 740 id: operation.name, 731 741 operation, ··· 961 971 } 962 972 }; 963 973 974 + const requestOptions = ({ 975 + context, 976 + operation, 977 + path, 978 + }: { 979 + context: IRContext; 980 + operation: IROperationObject; 981 + path: string; 982 + }) => { 983 + const file = context.file({ id: servicesId })!; 984 + const servicesOutput = file.getName(false); 985 + // const typesModule = `./${context.file({ id: 'types' })!.getName(false)}` 986 + 987 + // TODO: parser - add response transformers 988 + // const operationName = operationResponseTypeName(operation.name); 989 + // const { name: responseTransformerName } = setUniqueTypeName({ 990 + // client, 991 + // meta: { 992 + // $ref: `transformers/${operationName}`, 993 + // name: operationName, 994 + // }, 995 + // nameTransformer: operationResponseTransformerTypeName, 996 + // }); 997 + 998 + // if (responseTransformerName) { 999 + // file.import({ 1000 + // // this detection could be done safer, but it shouldn't cause any issues 1001 + // asType: !responseTransformerName.endsWith('Transformer'), 1002 + // module: typesModule, 1003 + // name: responseTransformerName, 1004 + // }); 1005 + // } 1006 + 1007 + const obj: ObjectValue[] = [{ spread: 'options' }]; 1008 + 1009 + if (operation.body) { 1010 + switch (operation.body.type) { 1011 + case 'form-data': 1012 + obj.push({ spread: 'formDataBodySerializer' }); 1013 + file.import({ 1014 + module: clientModulePath({ 1015 + config: context.config, 1016 + sourceOutput: servicesOutput, 1017 + }), 1018 + name: 'formDataBodySerializer', 1019 + }); 1020 + break; 1021 + case 'json': 1022 + break; 1023 + case 'url-search-params': 1024 + obj.push({ spread: 'urlSearchParamsBodySerializer' }); 1025 + file.import({ 1026 + module: clientModulePath({ 1027 + config: context.config, 1028 + sourceOutput: servicesOutput, 1029 + }), 1030 + name: 'urlSearchParamsBodySerializer', 1031 + }); 1032 + break; 1033 + } 1034 + 1035 + obj.push({ 1036 + key: 'headers', 1037 + value: [ 1038 + { 1039 + key: 'Content-Type', 1040 + // form-data does not need Content-Type header, browser will set it automatically 1041 + value: 1042 + operation.body.type === 'form-data' 1043 + ? null 1044 + : operation.body.mediaType, 1045 + }, 1046 + { 1047 + spread: 'options?.headers', 1048 + }, 1049 + ], 1050 + }); 1051 + } 1052 + 1053 + // TODO: parser - set parseAs to skip inference if every response has the same 1054 + // content type. currently impossible because successes do not contain 1055 + // header information 1056 + 1057 + obj.push({ 1058 + key: 'url', 1059 + value: path, 1060 + }); 1061 + 1062 + // TODO: parser - add response transformers 1063 + // if (responseTransformerName) { 1064 + // obj = [ 1065 + // ...obj, 1066 + // { 1067 + // key: 'responseTransformer', 1068 + // value: responseTransformerName, 1069 + // }, 1070 + // ]; 1071 + // } 1072 + 1073 + return compiler.objectExpression({ 1074 + identifiers: ['responseTransformer'], 1075 + obj, 1076 + }); 1077 + }; 1078 + 1079 + const generateClassServices = ({ context }: { context: IRContext }) => { 1080 + const file = context.file({ id: servicesId })!; 1081 + const typesModule = `./${context.file({ id: 'types' })!.getName(false)}`; 1082 + 1083 + const services = new Map<string, Array<ts.MethodDeclaration>>(); 1084 + 1085 + for (const path in context.ir.paths) { 1086 + const pathItem = context.ir.paths[path as keyof IRPathsObject]; 1087 + 1088 + for (const _method in pathItem) { 1089 + const method = _method as keyof IRPathItemObject; 1090 + const operation = pathItem[method]!; 1091 + 1092 + const identifierData = context.file({ id: 'types' })!.identifier({ 1093 + $ref: operationDataRef({ id: operation.id }), 1094 + namespace: 'type', 1095 + }); 1096 + if (identifierData.name) { 1097 + file.import({ 1098 + // this detection could be done safer, but it shouldn't cause any issues 1099 + asType: !identifierData.name.endsWith('Transformer'), 1100 + module: typesModule, 1101 + name: identifierData.name, 1102 + }); 1103 + } 1104 + 1105 + const identifierError = context.file({ id: 'types' })!.identifier({ 1106 + $ref: operationErrorRef({ id: operation.id }), 1107 + namespace: 'type', 1108 + }); 1109 + if (identifierError.name) { 1110 + file.import({ 1111 + // this detection could be done safer, but it shouldn't cause any issues 1112 + asType: !identifierError.name.endsWith('Transformer'), 1113 + module: typesModule, 1114 + name: identifierError.name, 1115 + }); 1116 + } 1117 + 1118 + const identifierResponse = context.file({ id: 'types' })!.identifier({ 1119 + $ref: operationResponseRef({ id: operation.id }), 1120 + namespace: 'type', 1121 + }); 1122 + if (identifierResponse.name) { 1123 + file.import({ 1124 + // this detection could be done safer, but it shouldn't cause any issues 1125 + asType: !identifierResponse.name.endsWith('Transformer'), 1126 + module: typesModule, 1127 + name: identifierResponse.name, 1128 + }); 1129 + } 1130 + 1131 + const node = compiler.methodDeclaration({ 1132 + accessLevel: 'public', 1133 + comment: [ 1134 + operation.deprecated && '@deprecated', 1135 + operation.summary && escapeComment(operation.summary), 1136 + operation.description && escapeComment(operation.description), 1137 + ], 1138 + isStatic: true, 1139 + name: serviceFunctionIdentifier({ 1140 + config: context.config, 1141 + handleIllegal: false, 1142 + id: operation.id, 1143 + operation, 1144 + }), 1145 + parameters: [ 1146 + { 1147 + isRequired: hasOperationDataRequired(operation), 1148 + name: 'options', 1149 + type: operationOptionsType({ 1150 + importedType: identifierData.name, 1151 + throwOnError: 'ThrowOnError', 1152 + }), 1153 + }, 1154 + ], 1155 + returnType: undefined, 1156 + statements: [ 1157 + compiler.returnFunctionCall({ 1158 + args: [ 1159 + requestOptions({ 1160 + context, 1161 + operation, 1162 + path, 1163 + }), 1164 + ], 1165 + name: `(options?.client ?? client).${method}`, 1166 + types: [ 1167 + identifierResponse.name || 'unknown', 1168 + identifierError.name || 'unknown', 1169 + 'ThrowOnError', 1170 + ], 1171 + }), 1172 + ], 1173 + types: [ 1174 + { 1175 + default: false, 1176 + extends: 'boolean', 1177 + name: 'ThrowOnError', 1178 + }, 1179 + ], 1180 + }); 1181 + 1182 + const uniqueTags = Array.from(new Set(operation.tags)); 1183 + if (!uniqueTags.length) { 1184 + uniqueTags.push('default'); 1185 + } 1186 + 1187 + for (const tag of uniqueTags) { 1188 + const serviceName = getServiceName(tag); 1189 + const nodes = services.get(serviceName) ?? []; 1190 + nodes.push(node); 1191 + services.set(serviceName, nodes); 1192 + } 1193 + } 1194 + } 1195 + 1196 + for (const [serviceName, nodes] of services) { 1197 + const node = compiler.classDeclaration({ 1198 + decorator: undefined, 1199 + members: nodes, 1200 + name: transformServiceName({ 1201 + config: context.config, 1202 + name: serviceName, 1203 + }), 1204 + }); 1205 + file.add(node); 1206 + } 1207 + }; 1208 + 1209 + const generateFlatServices = ({ context }: { context: IRContext }) => { 1210 + const file = context.file({ id: servicesId })!; 1211 + const typesModule = `./${context.file({ id: 'types' })!.getName(false)}`; 1212 + 1213 + for (const path in context.ir.paths) { 1214 + const pathItem = context.ir.paths[path as keyof IRPathsObject]; 1215 + 1216 + for (const _method in pathItem) { 1217 + const method = _method as keyof IRPathItemObject; 1218 + const operation = pathItem[method]!; 1219 + 1220 + const identifierData = context.file({ id: 'types' })!.identifier({ 1221 + $ref: operationDataRef({ id: operation.id }), 1222 + namespace: 'type', 1223 + }); 1224 + if (identifierData.name) { 1225 + file.import({ 1226 + // this detection could be done safer, but it shouldn't cause any issues 1227 + asType: !identifierData.name.endsWith('Transformer'), 1228 + module: typesModule, 1229 + name: identifierData.name, 1230 + }); 1231 + } 1232 + 1233 + const identifierError = context.file({ id: 'types' })!.identifier({ 1234 + $ref: operationErrorRef({ id: operation.id }), 1235 + namespace: 'type', 1236 + }); 1237 + if (identifierError.name) { 1238 + file.import({ 1239 + // this detection could be done safer, but it shouldn't cause any issues 1240 + asType: !identifierError.name.endsWith('Transformer'), 1241 + module: typesModule, 1242 + name: identifierError.name, 1243 + }); 1244 + } 1245 + 1246 + const identifierResponse = context.file({ id: 'types' })!.identifier({ 1247 + $ref: operationResponseRef({ id: operation.id }), 1248 + namespace: 'type', 1249 + }); 1250 + if (identifierResponse.name) { 1251 + file.import({ 1252 + // this detection could be done safer, but it shouldn't cause any issues 1253 + asType: !identifierResponse.name.endsWith('Transformer'), 1254 + module: typesModule, 1255 + name: identifierResponse.name, 1256 + }); 1257 + } 1258 + 1259 + const node = compiler.constVariable({ 1260 + comment: [ 1261 + operation.deprecated && '@deprecated', 1262 + operation.summary && escapeComment(operation.summary), 1263 + operation.description && escapeComment(operation.description), 1264 + ], 1265 + exportConst: true, 1266 + expression: compiler.arrowFunction({ 1267 + parameters: [ 1268 + { 1269 + isRequired: hasOperationDataRequired(operation), 1270 + name: 'options', 1271 + type: operationOptionsType({ 1272 + importedType: identifierData.name, 1273 + throwOnError: 'ThrowOnError', 1274 + }), 1275 + }, 1276 + ], 1277 + returnType: undefined, 1278 + statements: [ 1279 + compiler.returnFunctionCall({ 1280 + args: [ 1281 + requestOptions({ 1282 + context, 1283 + operation, 1284 + path, 1285 + }), 1286 + ], 1287 + name: `(options?.client ?? client).${method}`, 1288 + types: [ 1289 + identifierResponse.name || 'unknown', 1290 + identifierError.name || 'unknown', 1291 + 'ThrowOnError', 1292 + ], 1293 + }), 1294 + ], 1295 + types: [ 1296 + { 1297 + default: false, 1298 + extends: 'boolean', 1299 + name: 'ThrowOnError', 1300 + }, 1301 + ], 1302 + }), 1303 + name: serviceFunctionIdentifier({ 1304 + config: context.config, 1305 + handleIllegal: true, 1306 + id: operation.id, 1307 + operation, 1308 + }), 1309 + }); 1310 + file.add(node); 1311 + } 1312 + } 1313 + }; 1314 + 964 1315 export const generateServices = ({ context }: { context: IRContext }) => { 965 1316 // TODO: parser - once services are a plugin, this logic can be simplified 966 1317 if (!context.config.services.export) { ··· 1014 1365 }); 1015 1366 file.add(statement); 1016 1367 1017 - // TODO: parser - generate services 1018 - for (const path in context.ir.paths) { 1019 - const pathItem = context.ir.paths[path as keyof IRPathsObject]; 1020 - // console.warn(pathItem) 1021 - 1022 - for (const method in pathItem) { 1023 - const operation = pathItem[method as keyof IRPathItemObject]!; 1024 - 1025 - if (operation.parameters) { 1026 - const identifier = context.file({ id: 'types' })!.identifier({ 1027 - $ref: operationDataRef({ id: operation.id }), 1028 - namespace: 'type', 1029 - }); 1030 - if (identifier.name) { 1031 - file.import({ 1032 - // this detection could be done safer, but it shouldn't cause any issues 1033 - asType: !identifier.name.endsWith('Transformer'), 1034 - module: `./${context.file({ id: 'types' })!.getName(false)}`, 1035 - name: identifier.name, 1036 - }); 1037 - } 1038 - } 1039 - 1040 - // if (!isLegacy) { 1041 - // generateImport({ 1042 - // client, 1043 - // meta: { 1044 - // // TODO: this should be exact ref to operation for consistency, 1045 - // // but name should work too as operation ID is unique 1046 - // $ref: operation.name, 1047 - // name: operation.name, 1048 - // }, 1049 - // nameTransformer: operationErrorTypeName, 1050 - // onImport, 1051 - // }); 1052 - // } 1053 - 1054 - // const successResponses = operation.responses.filter((response) => 1055 - // response.responseTypes.includes('success'), 1056 - // ); 1057 - // if (successResponses.length) { 1058 - // generateImport({ 1059 - // client, 1060 - // meta: { 1061 - // // TODO: this should be exact ref to operation for consistency, 1062 - // // but name should work too as operation ID is unique 1063 - // $ref: operation.name, 1064 - // name: operation.name, 1065 - // }, 1066 - // nameTransformer: operationResponseTypeName, 1067 - // onImport, 1068 - // }); 1069 - // } 1070 - } 1368 + if (context.config.services.asClass) { 1369 + generateClassServices({ context }); 1370 + } else { 1371 + generateFlatServices({ context }); 1071 1372 } 1072 1373 };
+1 -1
packages/openapi-ts/src/index.ts
··· 355 355 operationParameter: operationParameterNameFn, 356 356 }, 357 357 }; 358 - if (config.experimental_parser && !isLegacyClient(config)) { 358 + if (config.experimental_parser && !isLegacyClient(config) && !config.name) { 359 359 context = parseExperimental({ 360 360 config, 361 361 parserConfig,
+3
packages/openapi-ts/src/ir/ir.d.ts
··· 1 1 import type { JsonSchemaDraft2020_12 } from '../openApi/3.1.0/types/json-schema-draft-2020-12'; 2 + import type { IRMediaType } from './mediaType'; 2 3 3 4 export interface IR { 4 5 components?: IRComponentsObject; ··· 40 41 } 41 42 42 43 export interface IRBodyObject { 44 + mediaType: string; 43 45 required?: boolean; 44 46 schema: IRSchemaObject; 47 + type?: IRMediaType; 45 48 } 46 49 47 50 export interface IRParametersObject {
+27
packages/openapi-ts/src/ir/mediaType.ts
··· 1 + const jsonMimeRegExp = /^application\/(.*\+)?json(;.*)?$/i; 2 + const multipartFormDataMimeRegExp = /^multipart\/form-data(;.*)?$/i; 3 + const xWwwFormUrlEncodedMimeRegExp = 4 + /^application\/x-www-form-urlencoded(;.*)?$/i; 5 + 6 + export type IRMediaType = 'form-data' | 'json' | 'url-search-params'; 7 + 8 + export const mediaTypeToIrMediaType = ({ 9 + mediaType, 10 + }: { 11 + mediaType: string; 12 + }): IRMediaType | undefined => { 13 + jsonMimeRegExp.lastIndex = 0; 14 + if (jsonMimeRegExp.test(mediaType)) { 15 + return 'json'; 16 + } 17 + 18 + multipartFormDataMimeRegExp.lastIndex = 0; 19 + if (multipartFormDataMimeRegExp.test(mediaType)) { 20 + return 'form-data'; 21 + } 22 + 23 + xWwwFormUrlEncodedMimeRegExp.lastIndex = 0; 24 + if (xWwwFormUrlEncodedMimeRegExp.test(mediaType)) { 25 + return 'url-search-params'; 26 + } 27 + };
+16
packages/openapi-ts/src/ir/operation.ts
··· 1 + import type { IROperationObject } from './ir'; 2 + import { hasParametersObjectRequired } from './parameter'; 3 + 4 + export const hasOperationDataRequired = ( 5 + operation: IROperationObject, 6 + ): boolean => { 7 + if (hasParametersObjectRequired(operation.parameters)) { 8 + return true; 9 + } 10 + 11 + if (operation.body?.required) { 12 + return true; 13 + } 14 + 15 + return false; 16 + };
+12 -28
packages/openapi-ts/src/openApi/3.1.0/parser/mediaType.ts
··· 1 + import type { IRMediaType } from '../../../ir/mediaType'; 2 + import { mediaTypeToIrMediaType } from '../../../ir/mediaType'; 1 3 import type { MediaTypeObject, SchemaObject } from '../types/spec'; 2 4 3 - const SUPPORTED_MEDIA_TYPES = [ 4 - 'application/json-patch+json', 5 - 'application/json', 6 - 'application/ld+json', 7 - 'application/x-www-form-urlencoded', 8 - 'audio/*', 9 - 'multipart/batch', 10 - 'multipart/form-data', 11 - 'multipart/mixed', 12 - 'multipart/related', 13 - 'text/json', 14 - 'text/plain', 15 - 'video/*', 16 - ] as const; 17 - 18 - type MediaType = (typeof SUPPORTED_MEDIA_TYPES)[number]; 19 - 20 5 interface Content { 21 - mediaType: MediaType; 6 + mediaType: string; 22 7 schema: SchemaObject | undefined; 8 + type: IRMediaType | undefined; 23 9 } 24 10 25 - export const getMediaTypeSchema = ({ 11 + export const mediaTypeObject = ({ 26 12 content, 27 13 }: { 28 14 content: Record<string, MediaTypeObject> | undefined; 29 15 }): Content | undefined => { 30 - for (const rawMediaType in content) { 31 - const mediaTypeContent = content[rawMediaType]; 32 - const mediaType: MediaType = rawMediaType.split(';')[0].trim() as MediaType; 33 - if (SUPPORTED_MEDIA_TYPES.includes(mediaType)) { 34 - return { 35 - mediaType, 36 - schema: mediaTypeContent.schema, 37 - }; 38 - } 16 + // return the first supported MIME type 17 + for (const mediaType in content) { 18 + return { 19 + mediaType, 20 + schema: content[mediaType].schema, 21 + type: mediaTypeToIrMediaType({ mediaType }), 22 + }; 39 23 } 40 24 };
+8 -3
packages/openapi-ts/src/openApi/3.1.0/parser/operation.ts
··· 6 6 RequestBodyObject, 7 7 ResponseObject, 8 8 } from '../types/spec'; 9 - import { getMediaTypeSchema } from './mediaType'; 9 + import { mediaTypeObject } from './mediaType'; 10 10 import { schemaToIrSchema } from './schema'; 11 11 12 12 interface Operation ··· 72 72 '$ref' in operation.requestBody 73 73 ? context.resolveRef<RequestBodyObject>(operation.requestBody.$ref) 74 74 : operation.requestBody; 75 - const content = getMediaTypeSchema({ 75 + const content = mediaTypeObject({ 76 76 content: requestBodyObject.content, 77 77 }); 78 78 if (content) { 79 79 irOperation.body = { 80 + mediaType: content.mediaType, 80 81 schema: schemaToIrSchema({ 81 82 context, 82 83 schema: { ··· 89 90 if (requestBodyObject.required) { 90 91 irOperation.body.required = requestBodyObject.required; 91 92 } 93 + 94 + if (content.type) { 95 + irOperation.body.type = content.type; 96 + } 92 97 } 93 98 } 94 99 ··· 98 103 '$ref' in response 99 104 ? context.resolveRef<ResponseObject>(response.$ref) 100 105 : response; 101 - const content = getMediaTypeSchema({ 106 + const content = mediaTypeObject({ 102 107 content: responseObject.content, 103 108 }); 104 109 if (content) {
+2 -2
packages/openapi-ts/src/openApi/3.1.0/parser/parameter.ts
··· 1 1 import type { IRContext } from '../../../ir/context'; 2 2 import type { IRParameterObject, IRParametersObject } from '../../../ir/ir'; 3 3 import type { ParameterObject, ReferenceObject } from '../types/spec'; 4 - import { getMediaTypeSchema } from './mediaType'; 4 + import { mediaTypeObject } from './mediaType'; 5 5 import { schemaToIrSchema } from './schema'; 6 6 7 7 export const parametersArrayToObject = ({ ··· 109 109 let schema = parameter.schema; 110 110 111 111 if (!schema) { 112 - const content = getMediaTypeSchema({ 112 + const content = mediaTypeObject({ 113 113 content: parameter.content, 114 114 }); 115 115 if (content) {
+18 -3
packages/openapi-ts/src/openApi/3.1.0/parser/schema.ts
··· 15 15 schema, 16 16 }: { 17 17 schema: SchemaObject; 18 - }): ReadonlyArray<SchemaType> => 19 - typeof schema.type === 'string' ? [schema.type] : schema.type ?? []; 18 + }): ReadonlyArray<SchemaType> => { 19 + if (typeof schema.type === 'string') { 20 + return [schema.type]; 21 + } 22 + 23 + if (schema.type) { 24 + return schema.type; 25 + } 26 + 27 + // infer object based on the presence of properties 28 + if (schema.properties) { 29 + return ['object']; 30 + } 31 + 32 + return []; 33 + }; 20 34 21 35 const parseSchemaMeta = ({ 22 36 irSchema, ··· 750 764 }); 751 765 } 752 766 753 - if (schema.type) { 767 + // infer object based on the presence of properties 768 + if (schema.type || schema.properties) { 754 769 return parseType({ 755 770 context, 756 771 schema: schema as SchemaWithRequired<'type'>,
+10 -10
packages/openapi-ts/src/plugins/@tanstack/query-core/plugin.ts
··· 13 13 operationErrorTypeName, 14 14 operationOptionsType, 15 15 operationResponseTypeName, 16 - toOperationName, 16 + serviceFunctionIdentifier, 17 17 } from '../../../generate/services'; 18 18 import { relativeModulePath } from '../../../generate/utils'; 19 19 import type { IROperationObject, IRPathsObject } from '../../../ir/ir'; 20 - import { hasParametersObjectRequired } from '../../../ir/parameter'; 20 + import { hasOperationDataRequired } from '../../../ir/operation'; 21 21 import { isOperationParameterRequired } from '../../../openApi'; 22 22 import { getOperationKey } from '../../../openApi/common/parser/operation'; 23 23 import type { ··· 39 39 import type { PluginConfig as VueQueryPluginConfig } from '../vue-query'; 40 40 41 41 const toInfiniteQueryOptionsName = (operation: Operation) => 42 - `${toOperationName({ 42 + `${serviceFunctionIdentifier({ 43 43 config: getConfig(), 44 44 id: operation.name, 45 45 operation, 46 46 })}InfiniteOptions`; 47 47 48 48 const toMutationOptionsName = (operation: Operation) => 49 - `${toOperationName({ 49 + `${serviceFunctionIdentifier({ 50 50 config: getConfig(), 51 51 id: operation.name, 52 52 operation, ··· 61 61 id: string; 62 62 operation: IROperationObject | Operation; 63 63 }) => 64 - `${toOperationName({ 64 + `${serviceFunctionIdentifier({ 65 65 config, 66 66 id, 67 67 operation, ··· 78 78 isInfinite?: boolean; 79 79 operation: IROperationObject | Operation; 80 80 }) => 81 - `${toOperationName({ 81 + `${serviceFunctionIdentifier({ 82 82 config, 83 83 id, 84 84 operation, ··· 564 564 }, 565 565 }); 566 566 567 - const typeData = operationOptionsType(nameTypeData); 567 + const typeData = operationOptionsType({ importedType: nameTypeData }); 568 568 569 569 return { typeData }; 570 570 }; ··· 738 738 config, 739 739 name: service.name, 740 740 }), 741 - toOperationName({ 741 + serviceFunctionIdentifier({ 742 742 config, 743 743 handleIllegal: !config.services.asClass, 744 744 id: operation.name, ··· 1375 1375 // name: service.name, 1376 1376 name: getServiceName('TODO'), 1377 1377 }), 1378 - toOperationName({ 1378 + serviceFunctionIdentifier({ 1379 1379 config: context.config, 1380 1380 handleIllegal: !context.config.services.asClass, 1381 1381 id: operation.id, ··· 1411 1411 // typesModulePath, 1412 1412 // }); 1413 1413 1414 - const isRequired = hasParametersObjectRequired(operation.parameters); 1414 + const isRequired = hasOperationDataRequired(operation); 1415 1415 1416 1416 const queryKeyStatement = compiler.constVariable({ 1417 1417 exportConst: true,
+9 -3
packages/openapi-ts/src/types/config.ts
··· 74 74 */ 75 75 input: string | Record<string, unknown>; 76 76 /** 77 - * Custom client class name 77 + * Custom client class name. Please note this option is deprecated and 78 + * will be removed in favor of clients. 79 + * @link https://heyapi.dev/openapi-ts/migrating.html#deprecated-name 78 80 * @deprecated 79 81 */ 80 82 name?: string; ··· 104 106 */ 105 107 plugins?: ReadonlyArray<UserPlugins['name'] | UserPlugins>; 106 108 /** 107 - * Path to custom request file 109 + * Path to custom request file. Please note this option is deprecated and 110 + * will be removed in favor of clients. 111 + * @link https://heyapi.dev/openapi-ts/migrating.html#deprecated-request 108 112 * @deprecated 109 113 */ 110 114 request?: string; ··· 241 245 tree?: boolean; 242 246 }; 243 247 /** 244 - * Use options or arguments functions 248 + * Use options or arguments functions. Please note this option is deprecated and 249 + * will be removed in favor of clients. 250 + * @link https://heyapi.dev/openapi-ts/migrating.html#deprecated-useoptions 245 251 * @deprecated 246 252 * @default true 247 253 */
+1 -1
packages/openapi-ts/src/utils/type.ts
··· 187 187 return compiler.typeInterfaceNode({ 188 188 isNullable: model.isNullable, 189 189 properties, 190 - useLegacyResolution: !config.experimental_parser, 190 + useLegacyResolution: true, 191 191 }); 192 192 }; 193 193
+11
packages/openapi-ts/test/3.1.0.spec.ts
··· 45 45 }, 46 46 { 47 47 config: createConfig({ 48 + input: 'object-properties-any-of.json', 49 + output: 'object-properties-any-of', 50 + services: { 51 + export: false, 52 + }, 53 + }), 54 + description: 55 + 'sets correct logical operator and brackets on object with properties and anyOf composition', 56 + }, 57 + { 58 + config: createConfig({ 48 59 input: 'required-all-of-ref.json', 49 60 output: 'required-all-of-ref', 50 61 services: {
+2
packages/openapi-ts/test/__snapshots__/3.1.0/object-properties-any-of/index.ts
··· 1 + // This file is auto-generated by @hey-api/openapi-ts 2 + export * from './types.gen';
+13
packages/openapi-ts/test/__snapshots__/3.1.0/object-properties-any-of/types.gen.ts
··· 1 + // This file is auto-generated by @hey-api/openapi-ts 2 + 3 + export type Foo = { 4 + bar: string; 5 + } | { 6 + baz: string; 7 + } | { 8 + foo: string; 9 + bar?: string; 10 + baz?: string; 11 + }; 12 + 13 + export type $OpenApiTs = unknown;
+1
packages/openapi-ts/test/__snapshots__/test/generated/v3-hey-api-client-fetch-bundle/client/utils.ts.snap
··· 332 332 return; 333 333 } 334 334 335 + // TODO: parser - better detection of MIME types 335 336 if (content.startsWith('application/json') || content.endsWith('+json')) { 336 337 return 'json'; 337 338 }
+1
packages/openapi-ts/test/__snapshots__/test/generated/v3-hey-api-client-fetch-bundle_transform/client/utils.ts.snap
··· 332 332 return; 333 333 } 334 334 335 + // TODO: parser - better detection of MIME types 335 336 if (content.startsWith('application/json') || content.endsWith('+json')) { 336 337 return 'json'; 337 338 }
+1 -1
packages/openapi-ts/test/sample.cjs
··· 31 31 }, 32 32 services: { 33 33 // export: false, 34 - // asClass: true, 34 + asClass: true, 35 35 // filter: '^GET /api/v{api-version}/simple:operation$', 36 36 // export: false, 37 37 // name: '^Parameters',
+8 -8
packages/openapi-ts/test/spec/3.1.0/full.json
··· 781 781 } 782 782 }, 783 783 "/api/v{api-version}/duplicate": { 784 + "delete": { 785 + "tags": ["Duplicate", "Duplicate"], 786 + "operationId": "DuplicateName" 787 + }, 784 788 "get": { 785 789 "tags": ["Duplicate"], 786 - "operationId": "DuplicateName" 790 + "operationId": "DuplicateName2" 787 791 }, 788 792 "post": { 789 793 "tags": ["Duplicate"], 790 - "operationId": "DuplicateName" 794 + "operationId": "DuplicateName3" 791 795 }, 792 796 "put": { 793 797 "tags": ["Duplicate"], 794 - "operationId": "DuplicateName" 795 - }, 796 - "delete": { 797 - "tags": ["Duplicate"], 798 - "operationId": "DuplicateName" 798 + "operationId": "DuplicateName4" 799 799 } 800 800 }, 801 801 "/api/v{api-version}/no-content": { 802 802 "get": { 803 - "tags": ["NoContent"], 803 + "tags": ["noContent"], 804 804 "operationId": "CallWithNoContentResponse", 805 805 "responses": { 806 806 "204": {
+28
packages/openapi-ts/test/spec/3.1.0/object-properties-any-of.json
··· 1 + { 2 + "openapi": "3.1.0", 3 + "info": { 4 + "version": "1.0.0" 5 + }, 6 + "components": { 7 + "schemas": { 8 + "Foo": { 9 + "properties": { 10 + "foo": { "type": "string" }, 11 + "bar": { "type": "string" }, 12 + "baz": { "type": "string" } 13 + }, 14 + "required": ["foo"], 15 + "anyOf": [ 16 + { 17 + "properties": { "bar": { "type": "string" } }, 18 + "required": ["bar"] 19 + }, 20 + { 21 + "properties": { "baz": { "type": "string" } }, 22 + "required": ["baz"] 23 + } 24 + ] 25 + } 26 + } 27 + } 28 + }