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

Merge pull request #1615 from hey-api/chore/compiler-updates

authored by

Lubos and committed by
GitHub
3d17490b 5cd4bb25

+136 -55
+3
packages/openapi-ts/src/compiler/index.ts
··· 38 38 isTsNode: utils.isTsNode, 39 39 keywordTypeNode: types.createKeywordTypeNode, 40 40 literalTypeNode: types.createLiteralTypeNode, 41 + mappedTypeNode: types.createMappedTypeNode, 41 42 methodDeclaration: classes.createMethodDeclaration, 42 43 namedImportDeclarations: module.createNamedImportDeclarations, 43 44 namespaceDeclaration: types.createNamespaceDeclaration, ··· 68 69 typeIntersectionNode: typedef.createTypeIntersectionNode, 69 70 typeNode: types.createTypeNode, 70 71 typeOfExpression: types.createTypeOfExpression, 72 + typeOperatorNode: types.createTypeOperatorNode, 73 + typeParameterDeclaration: types.createTypeParameterDeclaration, 71 74 typeParenthesizedNode: types.createTypeParenthesizedNode, 72 75 typeRecordNode: typedef.createTypeRecordNode, 73 76 typeReferenceNode: types.createTypeReferenceNode,
+72 -12
packages/openapi-ts/src/compiler/types.ts
··· 287 287 288 288 export const toTypeParameters = (types: FunctionTypeParameter[]) => 289 289 types.map((type) => 290 - ts.factory.createTypeParameterDeclaration( 291 - undefined, 292 - type.name, 290 + createTypeParameterDeclaration({ 293 291 // TODO: support other extends values 294 - type.extends 292 + constraint: type.extends 295 293 ? typeof type.extends === 'string' 296 294 ? createKeywordTypeNode({ keyword: 'boolean' }) 297 295 : type.extends 298 296 : undefined, 299 297 // TODO: support other default types 300 - type.default !== undefined 301 - ? isTsNode(type.default) 302 - ? (type.default as unknown as ts.TypeNode) 303 - : ts.factory.createLiteralTypeNode( 304 - type.default ? ts.factory.createTrue() : ts.factory.createFalse(), 305 - ) 306 - : undefined, 307 - ), 298 + defaultType: 299 + type.default !== undefined 300 + ? isTsNode(type.default) 301 + ? (type.default as unknown as ts.TypeNode) 302 + : ts.factory.createLiteralTypeNode( 303 + type.default 304 + ? ts.factory.createTrue() 305 + : ts.factory.createFalse(), 306 + ) 307 + : undefined, 308 + name: type.name, 309 + }), 310 + ); 311 + 312 + export const createTypeOperatorNode = ({ 313 + operator, 314 + type, 315 + }: { 316 + operator: 'keyof' | 'readonly' | 'unique'; 317 + type: ts.TypeNode; 318 + }) => { 319 + const operatorKeyword = 320 + operator === 'keyof' 321 + ? ts.SyntaxKind.KeyOfKeyword 322 + : operator === 'readonly' 323 + ? ts.SyntaxKind.ReadonlyKeyword 324 + : ts.SyntaxKind.UniqueKeyword; 325 + return ts.factory.createTypeOperatorNode(operatorKeyword, type); 326 + }; 327 + 328 + export const createTypeParameterDeclaration = ({ 329 + constraint, 330 + defaultType, 331 + modifiers, 332 + name, 333 + }: { 334 + constraint?: ts.TypeNode; 335 + defaultType?: ts.TypeNode; 336 + modifiers?: Array<ts.Modifier>; 337 + name: string | ts.Identifier; 338 + }) => 339 + ts.factory.createTypeParameterDeclaration( 340 + modifiers, 341 + name, 342 + constraint, 343 + defaultType, 344 + ); 345 + 346 + export const createMappedTypeNode = ({ 347 + members, 348 + nameType, 349 + questionToken, 350 + readonlyToken, 351 + type, 352 + typeParameter, 353 + }: { 354 + members?: ts.NodeArray<ts.TypeElement>; 355 + nameType?: ts.TypeNode; 356 + questionToken?: ts.QuestionToken | ts.PlusToken | ts.MinusToken; 357 + readonlyToken?: ts.ReadonlyKeyword | ts.PlusToken | ts.MinusToken; 358 + type?: ts.TypeNode; 359 + typeParameter: ts.TypeParameterDeclaration; 360 + }) => 361 + ts.factory.createMappedTypeNode( 362 + readonlyToken, 363 + typeParameter, 364 + nameType, 365 + questionToken, 366 + type, 367 + members, 308 368 ); 309 369 310 370 export const createLiteralTypeNode = ({
+8 -14
packages/openapi-ts/src/index.ts
··· 23 23 PluginNames, 24 24 } from './plugins/types'; 25 25 import type { Client } from './types/client'; 26 - import type { 27 - ClientConfig, 28 - Config, 29 - Formatters, 30 - Linters, 31 - UserConfig, 32 - } from './types/config'; 26 + import type { Config, Formatters, Linters, UserConfig } from './types/config'; 33 27 import { CLIENTS } from './types/config'; 34 28 import { 35 29 isLegacyClient, ··· 103 97 } 104 98 }; 105 99 106 - const getClient = (userConfig: ClientConfig): Config['client'] => { 100 + const getClient = (userConfig: UserConfig): Config['client'] => { 107 101 let client: Config['client'] = { 108 102 bundle: false, 109 103 name: '' as Config['client']['name'], ··· 119 113 return client; 120 114 }; 121 115 122 - const getInput = (userConfig: ClientConfig): Config['input'] => { 116 + const getInput = (userConfig: UserConfig): Config['input'] => { 123 117 let input: Config['input'] = { 124 118 path: '', 125 119 }; ··· 139 133 return input; 140 134 }; 141 135 142 - const getLogs = (userConfig: ClientConfig): Config['logs'] => { 136 + const getLogs = (userConfig: UserConfig): Config['logs'] => { 143 137 let logs: Config['logs'] = { 144 138 level: 'info', 145 139 path: process.cwd(), ··· 155 149 return logs; 156 150 }; 157 151 158 - const getOutput = (userConfig: ClientConfig): Config['output'] => { 152 + const getOutput = (userConfig: UserConfig): Config['output'] => { 159 153 let output: Config['output'] = { 160 154 clean: true, 161 155 format: false, ··· 271 265 }; 272 266 273 267 const getPlugins = ( 274 - userConfig: ClientConfig, 268 + userConfig: UserConfig, 275 269 ): Pick<Config, 'plugins' | 'pluginOrder'> => { 276 270 const userPluginsConfig: Config['plugins'] = {}; 277 271 ··· 438 432 }; 439 433 440 434 const getWatch = ( 441 - userConfig: Pick<ClientConfig, 'watch'> & Pick<Config, 'input'>, 435 + userConfig: Pick<UserConfig, 'watch'> & Pick<Config, 'input'>, 442 436 ): Config['watch'] => { 443 437 let watch: Config['watch'] = { 444 438 enabled: false, ··· 476 470 name: 'openapi-ts', 477 471 }); 478 472 479 - const userConfigs: ClientConfig[] = Array.isArray(userConfig) 473 + const userConfigs: UserConfig[] = Array.isArray(userConfig) 480 474 ? userConfig 481 475 : Array.isArray(configFromFile) 482 476 ? configFromFile.map((config) => ({
+2
packages/openapi-ts/src/plugins/@hey-api/schemas/types.d.ts
··· 25 25 ) => string; 26 26 /** 27 27 * Name of the generated file. 28 + * 28 29 * @default 'schemas' 29 30 */ 30 31 output?: string; ··· 32 33 * Choose schema type to generate. Select 'form' if you don't want 33 34 * descriptions to reduce bundle size and you plan to use schemas 34 35 * for form validation 36 + * 35 37 * @default 'json' 36 38 */ 37 39 type?: 'form' | 'json';
+2 -2
packages/openapi-ts/src/plugins/@hey-api/sdk/types.d.ts
··· 59 59 */ 60 60 output?: string; 61 61 /** 62 + * @deprecated 63 + * 62 64 * Define shape of returned value from service calls 63 65 * 64 66 * @default 'body' 65 - * 66 - * @deprecated 67 67 */ 68 68 response?: 'body' | 'response'; 69 69 /**
+3 -2
packages/openapi-ts/src/plugins/@hey-api/transformers/plugin.ts
··· 7 7 import { stringCase } from '../../../utils/stringCase'; 8 8 import { operationIrRef } from '../../shared/utils/ref'; 9 9 import type { Plugin } from '../../types'; 10 + import { typesId } from '../typescript/ref'; 10 11 import type { Config } from './types'; 11 12 12 13 interface OperationIRRef { ··· 460 461 return; 461 462 } 462 463 463 - const identifierResponse = context.file({ id: 'types' })!.identifier({ 464 + const identifierResponse = context.file({ id: typesId })!.identifier({ 464 465 $ref: operationIrRef({ id: operation.id, type: 'response' }), 465 466 namespace: 'type', 466 467 }); ··· 486 487 if (nodes.length) { 487 488 file.import({ 488 489 asType: true, 489 - module: file.relativePathToFile({ context, id: 'types' }), 490 + module: file.relativePathToFile({ context, id: typesId }), 490 491 name: identifierResponse.name, 491 492 }); 492 493 const responseTransformerNode = compiler.constVariable({
+6 -6
packages/openapi-ts/src/plugins/@hey-api/typescript/ref.ts
··· 26 26 file: TypeScriptFile; 27 27 operation: IR.OperationObject; 28 28 }): Identifier => { 29 - const identifierData = context.file({ id: 'types' })!.identifier({ 29 + const identifierData = context.file({ id: typesId })!.identifier({ 30 30 $ref: operationIrRef({ id: operation.id, type: 'data' }), 31 31 namespace: 'type', 32 32 }); ··· 34 34 if (identifier.name) { 35 35 file.import({ 36 36 asType: true, 37 - module: file.relativePathToFile({ context, id: 'types' }), 37 + module: file.relativePathToFile({ context, id: typesId }), 38 38 name: identifier.name, 39 39 }); 40 40 } ··· 50 50 file: TypeScriptFile; 51 51 operation: IR.OperationObject; 52 52 }): Identifier => { 53 - const identifierError = context.file({ id: 'types' })!.identifier({ 53 + const identifierError = context.file({ id: typesId })!.identifier({ 54 54 $ref: operationIrRef({ id: operation.id, type: 'error' }), 55 55 namespace: 'type', 56 56 }); ··· 58 58 if (identifier.name) { 59 59 file.import({ 60 60 asType: true, 61 - module: file.relativePathToFile({ context, id: 'types' }), 61 + module: file.relativePathToFile({ context, id: typesId }), 62 62 name: identifier.name, 63 63 }); 64 64 } ··· 74 74 file: TypeScriptFile; 75 75 operation: IR.OperationObject; 76 76 }): Identifier => { 77 - const identifierResponse = context.file({ id: 'types' })!.identifier({ 77 + const identifierResponse = context.file({ id: typesId })!.identifier({ 78 78 $ref: operationIrRef({ id: operation.id, type: 'response' }), 79 79 namespace: 'type', 80 80 }); ··· 82 82 if (identifier.name) { 83 83 file.import({ 84 84 asType: true, 85 - module: file.relativePathToFile({ context, id: 'types' }), 85 + module: file.relativePathToFile({ context, id: typesId }), 86 86 name: identifier.name, 87 87 }); 88 88 }
+4
packages/openapi-ts/src/plugins/@tanstack/angular-query-experimental/types.d.ts
··· 4 4 extends Plugin.Name<'@tanstack/angular-query-experimental'> { 5 5 /** 6 6 * Generate {@link https://tanstack.com/query/v5/docs/framework/angular/reference/infiniteQueryOptions `infiniteQueryOptions()`} helpers? These will be generated from GET and POST requests where a pagination parameter is detected. 7 + * 7 8 * @default true 8 9 */ 9 10 infiniteQueryOptions?: boolean; 10 11 /** 11 12 * Generate {@link https://tanstack.com/query/v5/docs/framework/angular/reference/useMutation `useMutation()`} helpers? These will be generated from DELETE, PATCH, POST, and PUT requests. 13 + * 12 14 * @default true 13 15 */ 14 16 mutationOptions?: boolean; 15 17 /** 16 18 * Name of the generated file. 19 + * 17 20 * @default '@tanstack/angular-query-experimental' 18 21 */ 19 22 output?: string; 20 23 /** 21 24 * Generate {@link https://tanstack.com/query/v5/docs/framework/angular/reference/queryOptions `queryOptions()`} helpers? 22 25 * These will be generated from all requests. 26 + * 23 27 * @default true 24 28 */ 25 29 queryOptions?: boolean;
+4
packages/openapi-ts/src/plugins/@tanstack/react-query/types.d.ts
··· 3 3 export interface Config extends Plugin.Name<'@tanstack/react-query'> { 4 4 /** 5 5 * Generate {@link https://tanstack.com/query/v5/docs/framework/react/reference/infiniteQueryOptions `infiniteQueryOptions()`} helpers? These will be generated from GET and POST requests where a pagination parameter is detected. 6 + * 6 7 * @default true 7 8 */ 8 9 infiniteQueryOptions?: boolean; 9 10 /** 10 11 * Generate {@link https://tanstack.com/query/v5/docs/framework/react/reference/useMutation `useMutation()`} helpers? These will be generated from DELETE, PATCH, POST, and PUT requests. 12 + * 11 13 * @default true 12 14 */ 13 15 mutationOptions?: boolean; 14 16 /** 15 17 * Name of the generated file. 18 + * 16 19 * @default '@tanstack/react-query' 17 20 */ 18 21 output?: string; 19 22 /** 20 23 * Generate {@link https://tanstack.com/query/v5/docs/framework/react/reference/queryOptions `queryOptions()`} helpers? 21 24 * These will be generated from all requests. 25 + * 22 26 * @default true 23 27 */ 24 28 queryOptions?: boolean;
+4
packages/openapi-ts/src/plugins/@tanstack/solid-query/types.d.ts
··· 3 3 export interface Config extends Plugin.Name<'@tanstack/solid-query'> { 4 4 /** 5 5 * Generate `createInfiniteQuery()` helpers? These will be generated from GET and POST requests where a pagination parameter is detected. 6 + * 6 7 * @default true 7 8 */ 8 9 infiniteQueryOptions?: boolean; 9 10 /** 10 11 * Generate `createMutation()` helpers? These will be generated from DELETE, PATCH, POST, and PUT requests. 12 + * 11 13 * @default true 12 14 */ 13 15 mutationOptions?: boolean; 14 16 /** 15 17 * Name of the generated file. 18 + * 16 19 * @default '@tanstack/solid-query' 17 20 */ 18 21 output?: string; 19 22 /** 20 23 * Generate {@link https://tanstack.com/query/v5/docs/framework/solid/reference/createQuery `createQuery()`} helpers? 21 24 * These will be generated from all requests. 25 + * 22 26 * @default true 23 27 */ 24 28 queryOptions?: boolean;
+4
packages/openapi-ts/src/plugins/@tanstack/svelte-query/types.d.ts
··· 3 3 export interface Config extends Plugin.Name<'@tanstack/svelte-query'> { 4 4 /** 5 5 * Generate `createInfiniteQuery()` helpers? These will be generated from GET and POST requests where a pagination parameter is detected. 6 + * 6 7 * @default true 7 8 */ 8 9 infiniteQueryOptions?: boolean; 9 10 /** 10 11 * Generate {@link https://tanstack.com/query/v5/docs/framework/svelte/reference/functions/createmutation `createMutation()`} helpers? These will be generated from DELETE, PATCH, POST, and PUT requests. 12 + * 11 13 * @default true 12 14 */ 13 15 mutationOptions?: boolean; 14 16 /** 15 17 * Name of the generated file. 18 + * 16 19 * @default '@tanstack/svelte-query' 17 20 */ 18 21 output?: string; 19 22 /** 20 23 * Generate {@link https://tanstack.com/query/v5/docs/framework/svelte/reference/functions/createquery `createQuery()`} helpers? 21 24 * These will be generated from all requests. 25 + * 22 26 * @default true 23 27 */ 24 28 queryOptions?: boolean;
+4
packages/openapi-ts/src/plugins/@tanstack/vue-query/types.d.ts
··· 3 3 export interface Config extends Plugin.Name<'@tanstack/vue-query'> { 4 4 /** 5 5 * Generate {@link https://tanstack.com/query/v5/docs/framework/vue/reference/infiniteQueryOptions `infiniteQueryOptions()`} helpers? These will be generated from GET and POST requests where a pagination parameter is detected. 6 + * 6 7 * @default true 7 8 */ 8 9 infiniteQueryOptions?: boolean; 9 10 /** 10 11 * Generate {@link https://tanstack.com/query/v5/docs/framework/vue/reference/useMutation `useMutation()`} helpers? These will be generated from DELETE, PATCH, POST, and PUT requests. 12 + * 11 13 * @default true 12 14 */ 13 15 mutationOptions?: boolean; 14 16 /** 15 17 * Name of the generated file. 18 + * 16 19 * @default '@tanstack/vue-query' 17 20 */ 18 21 output?: string; 19 22 /** 20 23 * Generate {@link https://tanstack.com/query/v5/docs/framework/vue/guides/query-options `queryOptions()`} helpers? 21 24 * These will be generated from all requests. 25 + * 22 26 * @default true 23 27 */ 24 28 queryOptions?: boolean;
+10 -9
packages/openapi-ts/src/plugins/fastify/plugin.ts
··· 4 4 import { operationResponsesMap } from '../../ir/operation'; 5 5 import { hasParameterGroupObjectRequired } from '../../ir/parameter'; 6 6 import type { IR } from '../../ir/types'; 7 + import { typesId } from '../@hey-api/typescript/ref'; 7 8 import { operationIrRef } from '../shared/utils/ref'; 8 9 import type { Plugin } from '../types'; 9 10 import type { Config } from './types'; ··· 18 19 operation: IR.OperationObject; 19 20 }): Property | undefined => { 20 21 const file = context.file({ id: fastifyId })!; 21 - const fileTypes = context.file({ id: 'types' })!; 22 + const fileTypes = context.file({ id: typesId })!; 22 23 23 24 const properties: Array<Property> = []; 24 25 ··· 30 31 if (operation.body) { 31 32 file.import({ 32 33 asType: true, 33 - module: file.relativePathToFile({ context, id: 'types' }), 34 + module: file.relativePathToFile({ context, id: typesId }), 34 35 name: identifierData.name, 35 36 }); 36 37 properties.push({ ··· 44 45 if (operation.parameters.header) { 45 46 file.import({ 46 47 asType: true, 47 - module: file.relativePathToFile({ context, id: 'types' }), 48 + module: file.relativePathToFile({ context, id: typesId }), 48 49 name: identifierData.name, 49 50 }); 50 51 properties.push({ ··· 59 60 if (operation.parameters.path) { 60 61 file.import({ 61 62 asType: true, 62 - module: file.relativePathToFile({ context, id: 'types' }), 63 + module: file.relativePathToFile({ context, id: typesId }), 63 64 name: identifierData.name, 64 65 }); 65 66 properties.push({ ··· 74 75 if (operation.parameters.query) { 75 76 file.import({ 76 77 asType: true, 77 - module: file.relativePathToFile({ context, id: 'types' }), 78 + module: file.relativePathToFile({ context, id: typesId }), 78 79 name: identifierData.name, 79 80 }); 80 81 properties.push({ ··· 102 103 if (!hasDefaultResponse) { 103 104 file.import({ 104 105 asType: true, 105 - module: file.relativePathToFile({ context, id: 'types' }), 106 + module: file.relativePathToFile({ context, id: typesId }), 106 107 name: identifierErrors.name, 107 108 }); 108 109 errorsTypeReference = compiler.typeReferenceNode({ ··· 111 112 } else if (keys.length > 1) { 112 113 file.import({ 113 114 asType: true, 114 - module: file.relativePathToFile({ context, id: 'types' }), 115 + module: file.relativePathToFile({ context, id: typesId }), 115 116 name: identifierErrors.name, 116 117 }); 117 118 const errorsType = compiler.typeReferenceNode({ ··· 140 141 if (!hasDefaultResponse) { 141 142 file.import({ 142 143 asType: true, 143 - module: file.relativePathToFile({ context, id: 'types' }), 144 + module: file.relativePathToFile({ context, id: typesId }), 144 145 name: identifierResponses.name, 145 146 }); 146 147 responsesTypeReference = compiler.typeReferenceNode({ ··· 149 150 } else if (keys.length > 1) { 150 151 file.import({ 151 152 asType: true, 152 - module: file.relativePathToFile({ context, id: 'types' }), 153 + module: file.relativePathToFile({ context, id: typesId }), 153 154 name: identifierResponses.name, 154 155 }); 155 156 const responsesType = compiler.typeReferenceNode({
+1
packages/openapi-ts/src/plugins/fastify/types.d.ts
··· 3 3 export interface Config extends Plugin.Name<'fastify'> { 4 4 /** 5 5 * Name of the generated file. 6 + * 6 7 * @default 'fastify' 7 8 */ 8 9 output?: string;
+1
packages/openapi-ts/src/plugins/zod/types.d.ts
··· 9 9 // nameBuilder?: (model: IR.OperationObject | IR.SchemaObject) => string; 10 10 /** 11 11 * Name of the generated file. 12 + * 12 13 * @default 'zod' 13 14 */ 14 15 output?: string;
+8 -10
packages/openapi-ts/src/types/config.ts
··· 29 29 | 'snake_case' 30 30 | 'SCREAMING_SNAKE_CASE'; 31 31 32 - export interface ClientConfig { 32 + export interface UserConfig { 33 33 /** 34 34 * HTTP client to generate 35 35 */ ··· 274 274 useOptions?: boolean; 275 275 } 276 276 277 - export type UserConfig = ClientConfig; 278 - 279 277 export type Config = Omit< 280 - Required<ClientConfig>, 278 + Required<UserConfig>, 281 279 | 'base' 282 280 | 'client' 283 281 | 'input' ··· 288 286 | 'request' 289 287 | 'watch' 290 288 > & 291 - Pick<ClientConfig, 'base' | 'name' | 'request'> & { 292 - client: Extract<Required<ClientConfig>['client'], object>; 293 - input: ExtractWithDiscriminator<ClientConfig['input'], { path: unknown }>; 294 - logs: Extract<Required<ClientConfig['logs']>, object>; 295 - output: Extract<ClientConfig['output'], object>; 289 + Pick<UserConfig, 'base' | 'name' | 'request'> & { 290 + client: Extract<Required<UserConfig>['client'], object>; 291 + input: ExtractWithDiscriminator<UserConfig['input'], { path: unknown }>; 292 + logs: Extract<Required<UserConfig['logs']>, object>; 293 + output: Extract<UserConfig['output'], object>; 296 294 pluginOrder: ReadonlyArray<ClientPlugins['name']>; 297 295 plugins: ArrayOfObjectsToObjectMap< 298 296 ExtractArrayOfObjects<ReadonlyArray<ClientPlugins>, { name: string }>, 299 297 'name' 300 298 >; 301 - watch: Extract<ClientConfig['watch'], object>; 299 + watch: Extract<UserConfig['watch'], object>; 302 300 };