Skip to content

Commit

Permalink
Merge pull request #1522 from hey-api/refactor/import-identifier
Browse files Browse the repository at this point in the history
refactor: extract import identifier functions
  • Loading branch information
mrlubos authored Jan 1, 2025
2 parents 37c4c23 + 170082c commit 8d563b5
Show file tree
Hide file tree
Showing 10 changed files with 147 additions and 118 deletions.
2 changes: 1 addition & 1 deletion docs/openapi-ts/configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,8 @@
"license": "MIT",
"author": {
"email": "[email protected]",
"name": "Lubos Menus",
"url": "https://lmen.us"
"name": "Hey API",
"url": "https://heyapi.dev"
},
"funding": "https://github.com/sponsors/hey-api",
"type": "module",
Expand Down
4 changes: 2 additions & 2 deletions packages/client-axios/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,8 @@
"license": "MIT",
"author": {
"email": "[email protected]",
"name": "Lubos Menus",
"url": "https://lmen.us"
"name": "Hey API",
"url": "https://heyapi.dev"
},
"funding": "https://github.com/sponsors/hey-api",
"keywords": [
Expand Down
4 changes: 2 additions & 2 deletions packages/client-fetch/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,8 @@
"license": "MIT",
"author": {
"email": "[email protected]",
"name": "Lubos Menus",
"url": "https://lmen.us"
"name": "Hey API",
"url": "https://heyapi.dev"
},
"funding": "https://github.com/sponsors/hey-api",
"keywords": [
Expand Down
1 change: 1 addition & 0 deletions packages/openapi-ts/src/generate/files.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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}`;
}

Expand Down
5 changes: 4 additions & 1 deletion packages/openapi-ts/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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');
}
Expand Down
106 changes: 40 additions & 66 deletions packages/openapi-ts/src/plugins/@hey-api/sdk/plugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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<TypeScriptFile['identifier']>;
// TODO: refactor this so we don't need to import error type unless it's used here
identifierError?: ReturnType<TypeScriptFile['identifier']>;
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';
Expand Down Expand Up @@ -103,31 +115,13 @@ const operationStatements = ({
}): Array<ts.Statement> => {
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 = {
Expand Down Expand Up @@ -409,22 +403,12 @@ const generateClassSdk = ({
plugin: Plugin.Instance<Config>;
}) => {
const file = context.file({ id: sdkId })!;
const typesModule = file.relativePathToFile({ context, id: 'types' });

const sdks = new Map<string, Array<ts.MethodDeclaration>>();

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',
Expand All @@ -445,7 +429,9 @@ const generateClassSdk = ({
isRequired: hasOperationDataRequired(operation),
name: 'options',
type: operationOptionsType({
importedType: identifierData.name,
context,
identifierData,
// identifierError,
throwOnError: 'ThrowOnError',
}),
},
Expand Down Expand Up @@ -501,20 +487,11 @@ const generateFlatSdk = ({
plugin: Plugin.Instance<Config>;
}) => {
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: [
Expand All @@ -529,7 +506,9 @@ const generateFlatSdk = ({
isRequired: hasOperationDataRequired(operation),
name: 'options',
type: operationOptionsType({
importedType: identifierData.name,
context,
identifierData,
// identifierError,
throwOnError: 'ThrowOnError',
}),
},
Expand Down Expand Up @@ -574,26 +553,21 @@ export const handler: Plugin.Handler<Config> = ({ 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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,15 +12,14 @@ 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<T extends Required<IR.SchemaObject>['type']>
extends Omit<IR.SchemaObject, 'type'> {
type: Extract<Required<IR.SchemaObject>['type'], T>;
}

const typesId = 'types';

const parseSchemaJsDoc = ({ schema }: { schema: IR.SchemaObject }) => {
const comments = [
schema.description && escapeComment(schema.description),
Expand Down
74 changes: 74 additions & 0 deletions packages/openapi-ts/src/plugins/@hey-api/typescript/ref.ts
Original file line number Diff line number Diff line change
@@ -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<TypeScriptFile['identifier']> => {
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<TypeScriptFile['identifier']> => {
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<TypeScriptFile['identifier']> => {
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;
};
Loading

0 comments on commit 8d563b5

Please sign in to comment.