From 170082cfef06706a84a7f4cdee06ed286d7987a1 Mon Sep 17 00:00:00 2001 From: Lubos Date: Wed, 1 Jan 2025 02:26:16 +0100 Subject: [PATCH] refactor: extract import identifier functions --- docs/openapi-ts/configuration.md | 2 +- package.json | 4 +- packages/client-axios/package.json | 4 +- packages/client-fetch/package.json | 4 +- packages/openapi-ts/src/generate/files.ts | 1 + packages/openapi-ts/src/index.ts | 5 +- .../src/plugins/@hey-api/sdk/plugin.ts | 106 +++++++----------- .../src/plugins/@hey-api/typescript/plugin.ts | 3 +- .../src/plugins/@hey-api/typescript/ref.ts | 74 ++++++++++++ .../plugins/@tanstack/query-core/useType.ts | 62 ++++------ 10 files changed, 147 insertions(+), 118 deletions(-) create mode 100644 packages/openapi-ts/src/plugins/@hey-api/typescript/ref.ts diff --git a/docs/openapi-ts/configuration.md b/docs/openapi-ts/configuration.md index cabf1f74c..847a894e5 100644 --- a/docs/openapi-ts/configuration.md +++ b/docs/openapi-ts/configuration.md @@ -46,7 +46,7 @@ Alternatively, you can use `openapi-ts.config.js` and configure the export state Input is the first thing you must define. It can be a path, URL, or a string content resolving to an OpenAPI specification. Hey API supports all valid OpenAPI versions and file formats. ::: info -We use [`@hey-api/json-schema-ref-parser`](https://github.com/hey-api/json-schema-ref-parser) to resolve file locations. Please note that accessing a HTTPS URL on localhost has a known [workaround](https://github.com/hey-api/openapi-ts/issues/276). +If you use an HTTPS URL with a self-signed certificate in development, you will need to set [`NODE_TLS_REJECT_UNAUTHORIZED=0`](https://github.com/hey-api/openapi-ts/issues/276#issuecomment-2043143501) in your environment. ::: ## Output diff --git a/package.json b/package.json index 3280fa870..0e0329d22 100644 --- a/package.json +++ b/package.json @@ -14,8 +14,8 @@ "license": "MIT", "author": { "email": "lubos@heyapi.dev", - "name": "Lubos Menus", - "url": "https://lmen.us" + "name": "Hey API", + "url": "https://heyapi.dev" }, "funding": "https://github.com/sponsors/hey-api", "type": "module", diff --git a/packages/client-axios/package.json b/packages/client-axios/package.json index 366334dea..a21161a85 100644 --- a/packages/client-axios/package.json +++ b/packages/client-axios/package.json @@ -13,8 +13,8 @@ "license": "MIT", "author": { "email": "lubos@heyapi.dev", - "name": "Lubos Menus", - "url": "https://lmen.us" + "name": "Hey API", + "url": "https://heyapi.dev" }, "funding": "https://github.com/sponsors/hey-api", "keywords": [ diff --git a/packages/client-fetch/package.json b/packages/client-fetch/package.json index 90d1be59e..5cf395535 100644 --- a/packages/client-fetch/package.json +++ b/packages/client-fetch/package.json @@ -13,8 +13,8 @@ "license": "MIT", "author": { "email": "lubos@heyapi.dev", - "name": "Lubos Menus", - "url": "https://lmen.us" + "name": "Hey API", + "url": "https://heyapi.dev" }, "funding": "https://github.com/sponsors/hey-api", "keywords": [ diff --git a/packages/openapi-ts/src/generate/files.ts b/packages/openapi-ts/src/generate/files.ts index ddb8558c2..492198046 100644 --- a/packages/openapi-ts/src/generate/files.ts +++ b/packages/openapi-ts/src/generate/files.ts @@ -204,6 +204,7 @@ export class TypeScriptFile { const outputParts = thisRelativePath.split(path.sep); const relativePath = new Array(outputParts.length).fill('').join('../') || './'; + // TODO: parser - cache responses return `${relativePath}${splitNameAndExtension(fileRelativePath).name}`; } diff --git a/packages/openapi-ts/src/index.ts b/packages/openapi-ts/src/index.ts index 1ed6f85c9..22b660f4e 100644 --- a/packages/openapi-ts/src/index.ts +++ b/packages/openapi-ts/src/index.ts @@ -641,7 +641,10 @@ export async function createClient( if (!config.dryRun) { processOutput({ config }); - console.log(`🚀 Done! Your output is in ${config.output.path}`); + const outputPath = process.env.INIT_CWD + ? `./${path.relative(process.env.INIT_CWD, config.output.path)}` + : config.output.path; + console.log(`🚀 Done! Your output is in ${outputPath}`); } Performance.end('postprocess'); } diff --git a/packages/openapi-ts/src/plugins/@hey-api/sdk/plugin.ts b/packages/openapi-ts/src/plugins/@hey-api/sdk/plugin.ts index 32001ecee..48cd678b8 100644 --- a/packages/openapi-ts/src/plugins/@hey-api/sdk/plugin.ts +++ b/packages/openapi-ts/src/plugins/@hey-api/sdk/plugin.ts @@ -3,6 +3,7 @@ import type ts from 'typescript'; import { compiler } from '../../../compiler'; import type { ObjectValue } from '../../../compiler/types'; import { clientApi, clientModulePath } from '../../../generate/client'; +import type { TypeScriptFile } from '../../../generate/files'; import { hasOperationDataRequired, statusCodeToGroup, @@ -18,22 +19,33 @@ import { operationTransformerIrRef, transformersId, } from '../transformers/plugin'; +import { + importIdentifierData, + importIdentifierError, + importIdentifierResponse, +} from '../typescript/ref'; import { serviceFunctionIdentifier } from './plugin-legacy'; import type { Config } from './types'; export const operationOptionsType = ({ - importedType, + identifierData, throwOnError, }: { - importedType?: string | false; + context: IR.Context; + identifierData?: ReturnType; + // TODO: refactor this so we don't need to import error type unless it's used here + identifierError?: ReturnType; throwOnError?: string; }) => { const optionsName = clientApi.Options.name; + // TODO: refactor this to be more generic, works for now if (throwOnError) { - return `${optionsName}<${importedType || 'unknown'}, ${throwOnError}>`; + return `${optionsName}<${identifierData?.name || 'unknown'}, ${throwOnError}>`; } - return importedType ? `${optionsName}<${importedType}>` : optionsName; + return identifierData + ? `${optionsName}<${identifierData.name}>` + : optionsName; }; const sdkId = 'sdk'; @@ -103,31 +115,13 @@ const operationStatements = ({ }): Array => { const file = context.file({ id: sdkId })!; const sdkOutput = file.nameWithoutExtension(); - const typesModule = file.relativePathToFile({ context, id: 'types' }); - const identifierError = context.file({ id: 'types' })!.identifier({ - $ref: operationIrRef({ id: operation.id, type: 'error' }), - namespace: 'type', + const identifierError = importIdentifierError({ context, file, operation }); + const identifierResponse = importIdentifierResponse({ + context, + file, + operation, }); - if (identifierError.name) { - file.import({ - asType: true, - module: typesModule, - name: identifierError.name, - }); - } - - const identifierResponse = context.file({ id: 'types' })!.identifier({ - $ref: operationIrRef({ id: operation.id, type: 'response' }), - namespace: 'type', - }); - if (identifierResponse.name) { - file.import({ - asType: true, - module: typesModule, - name: identifierResponse.name, - }); - } // TODO: transform parameters // const query = { @@ -409,22 +403,12 @@ const generateClassSdk = ({ plugin: Plugin.Instance; }) => { const file = context.file({ id: sdkId })!; - const typesModule = file.relativePathToFile({ context, id: 'types' }); - const sdks = new Map>(); context.subscribe('operation', ({ operation }) => { - const identifierData = context.file({ id: 'types' })!.identifier({ - $ref: operationIrRef({ id: operation.id, type: 'data' }), - namespace: 'type', - }); - if (identifierData.name) { - file.import({ - asType: true, - module: typesModule, - name: identifierData.name, - }); - } + const identifierData = importIdentifierData({ context, file, operation }); + // TODO: import error type only if we are sure we are going to use it + // const identifierError = importIdentifierError({ context, file, operation }); const node = compiler.methodDeclaration({ accessLevel: 'public', @@ -445,7 +429,9 @@ const generateClassSdk = ({ isRequired: hasOperationDataRequired(operation), name: 'options', type: operationOptionsType({ - importedType: identifierData.name, + context, + identifierData, + // identifierError, throwOnError: 'ThrowOnError', }), }, @@ -501,20 +487,11 @@ const generateFlatSdk = ({ plugin: Plugin.Instance; }) => { const file = context.file({ id: sdkId })!; - const typesModule = file.relativePathToFile({ context, id: 'types' }); context.subscribe('operation', ({ operation }) => { - const identifierData = context.file({ id: 'types' })!.identifier({ - $ref: operationIrRef({ id: operation.id, type: 'data' }), - namespace: 'type', - }); - if (identifierData.name) { - file.import({ - asType: true, - module: typesModule, - name: identifierData.name, - }); - } + const identifierData = importIdentifierData({ context, file, operation }); + // TODO: import error type only if we are sure we are going to use it + // const identifierError = importIdentifierError({ context, file, operation }); const node = compiler.constVariable({ comment: [ @@ -529,7 +506,9 @@ const generateFlatSdk = ({ isRequired: hasOperationDataRequired(operation), name: 'options', type: operationOptionsType({ - importedType: identifierData.name, + context, + identifierData, + // identifierError, throwOnError: 'ThrowOnError', }), }, @@ -574,26 +553,21 @@ export const handler: Plugin.Handler = ({ context, plugin }) => { const sdkOutput = file.nameWithoutExtension(); // import required packages and core files + const clientModule = clientModulePath({ + config: context.config, + sourceOutput: sdkOutput, + }); file.import({ - module: clientModulePath({ - config: context.config, - sourceOutput: sdkOutput, - }), + module: clientModule, name: 'createClient', }); file.import({ - module: clientModulePath({ - config: context.config, - sourceOutput: sdkOutput, - }), + module: clientModule, name: 'createConfig', }); file.import({ ...clientApi.Options, - module: clientModulePath({ - config: context.config, - sourceOutput: sdkOutput, - }), + module: clientModule, }); // define client first diff --git a/packages/openapi-ts/src/plugins/@hey-api/typescript/plugin.ts b/packages/openapi-ts/src/plugins/@hey-api/typescript/plugin.ts index 1f99a6f16..4fb060496 100644 --- a/packages/openapi-ts/src/plugins/@hey-api/typescript/plugin.ts +++ b/packages/openapi-ts/src/plugins/@hey-api/typescript/plugin.ts @@ -12,6 +12,7 @@ import { stringCase } from '../../../utils/stringCase'; import { fieldName } from '../../shared/utils/case'; import { operationIrRef } from '../../shared/utils/ref'; import type { Plugin } from '../../types'; +import { typesId } from './ref'; import type { Config } from './types'; interface SchemaWithType['type']> @@ -19,8 +20,6 @@ interface SchemaWithType['type']> type: Extract['type'], T>; } -const typesId = 'types'; - const parseSchemaJsDoc = ({ schema }: { schema: IR.SchemaObject }) => { const comments = [ schema.description && escapeComment(schema.description), diff --git a/packages/openapi-ts/src/plugins/@hey-api/typescript/ref.ts b/packages/openapi-ts/src/plugins/@hey-api/typescript/ref.ts new file mode 100644 index 000000000..f127485c1 --- /dev/null +++ b/packages/openapi-ts/src/plugins/@hey-api/typescript/ref.ts @@ -0,0 +1,74 @@ +import type { TypeScriptFile } from '../../../generate/files'; +import type { IR } from '../../../ir/types'; +import { operationIrRef } from '../../shared/utils/ref'; + +export const typesId = 'types'; + +export const importIdentifierData = ({ + context, + file, + operation, +}: { + context: IR.Context; + file: TypeScriptFile; + operation: IR.OperationObject; +}): ReturnType => { + const identifierData = context.file({ id: 'types' })!.identifier({ + $ref: operationIrRef({ id: operation.id, type: 'data' }), + namespace: 'type', + }); + if (identifierData.name) { + file.import({ + asType: true, + module: file.relativePathToFile({ context, id: 'types' }), + name: identifierData.name, + }); + } + return identifierData; +}; + +export const importIdentifierError = ({ + context, + file, + operation, +}: { + context: IR.Context; + file: TypeScriptFile; + operation: IR.OperationObject; +}): ReturnType => { + const identifierError = context.file({ id: 'types' })!.identifier({ + $ref: operationIrRef({ id: operation.id, type: 'error' }), + namespace: 'type', + }); + if (identifierError.name) { + file.import({ + asType: true, + module: file.relativePathToFile({ context, id: 'types' }), + name: identifierError.name, + }); + } + return identifierError; +}; + +export const importIdentifierResponse = ({ + context, + file, + operation, +}: { + context: IR.Context; + file: TypeScriptFile; + operation: IR.OperationObject; +}): ReturnType => { + const identifierResponse = context.file({ id: 'types' })!.identifier({ + $ref: operationIrRef({ id: operation.id, type: 'response' }), + namespace: 'type', + }); + if (identifierResponse.name) { + file.import({ + asType: true, + module: file.relativePathToFile({ context, id: 'types' }), + name: identifierResponse.name, + }); + } + return identifierResponse; +}; diff --git a/packages/openapi-ts/src/plugins/@tanstack/query-core/useType.ts b/packages/openapi-ts/src/plugins/@tanstack/query-core/useType.ts index bda71b38f..12a05e930 100644 --- a/packages/openapi-ts/src/plugins/@tanstack/query-core/useType.ts +++ b/packages/openapi-ts/src/plugins/@tanstack/query-core/useType.ts @@ -1,7 +1,11 @@ import type { ImportExportItemObject } from '../../../compiler/utils'; import type { IR } from '../../../ir/types'; import { operationOptionsType } from '../../@hey-api/sdk/plugin'; -import { operationIrRef } from '../../shared/utils/ref'; +import { + importIdentifierData, + importIdentifierError, + importIdentifierResponse, +} from '../../@hey-api/typescript/ref'; import type { PluginInstance } from './types'; export const useTypeData = ({ @@ -13,22 +17,16 @@ export const useTypeData = ({ operation: IR.OperationObject; plugin: PluginInstance; }) => { - const identifierData = context.file({ id: 'types' })!.identifier({ - $ref: operationIrRef({ id: operation.id, type: 'data' }), - namespace: 'type', - }); - if (identifierData.name) { - const file = context.file({ id: plugin.name })!; - file.import({ - asType: true, - module: context - .file({ id: plugin.name })! - .relativePathToFile({ context, id: 'types' }), - name: identifierData.name, - }); - } + const file = context.file({ id: plugin.name })!; + + const identifierData = importIdentifierData({ context, file, operation }); + // TODO: import error type only if we are sure we are going to use it + // const identifierError = importIdentifierError({ context, file, operation }); + const typeData = operationOptionsType({ - importedType: identifierData.name, + context, + identifierData, + // identifierError, }); return typeData; }; @@ -43,19 +41,7 @@ export const useTypeError = ({ plugin: PluginInstance; }) => { const file = context.file({ id: plugin.name })!; - const identifierError = context.file({ id: 'types' })!.identifier({ - $ref: operationIrRef({ id: operation.id, type: 'error' }), - namespace: 'type', - }); - if (identifierError.name) { - file.import({ - asType: true, - module: context - .file({ id: plugin.name })! - .relativePathToFile({ context, id: 'types' }), - name: identifierError.name, - }); - } + const identifierError = importIdentifierError({ context, file, operation }); let typeError: ImportExportItemObject = { asType: true, name: identifierError.name || '', @@ -90,20 +76,12 @@ export const useTypeResponse = ({ operation: IR.OperationObject; plugin: PluginInstance; }) => { - const identifierResponse = context.file({ id: 'types' })!.identifier({ - $ref: operationIrRef({ id: operation.id, type: 'response' }), - namespace: 'type', + const file = context.file({ id: plugin.name })!; + const identifierResponse = importIdentifierResponse({ + context, + file, + operation, }); - if (identifierResponse.name) { - const file = context.file({ id: plugin.name })!; - file.import({ - asType: true, - module: context - .file({ id: plugin.name })! - .relativePathToFile({ context, id: 'types' }), - name: identifierResponse.name, - }); - } const typeResponse = identifierResponse.name || 'unknown'; return typeResponse; };