diff --git a/bin/index.js b/bin/index.js index decf79420..cc5ca5748 100755 --- a/bin/index.js +++ b/bin/index.js @@ -12,6 +12,17 @@ const params = program .version(pkg.version) .requiredOption('-i, --input ', 'OpenAPI specification, can be a path, url or string content (required)') .requiredOption('-o, --output ', 'Output directory (required)') + .option('--modelsDirName ', 'Models directory name') + .option('--serverOutput ', 'Server output directory') + .option('--serverDirName ', 'Server directory name') + .option('--serverModelImportPath ', 'Server model import path') + .option('--serverApiTypesImportPath ', 'Server Api Types import path') + .option('--serverReqTypeName ', 'Name of Server Request type to use') + .option('--serverResTypeName ', 'Name of Server Response type to use') + .option('--exportRouteHandler', 'create a route handler to map routes to controller methods') + .option('--transformReqFuncName ', 'Name of request transformation function') + .option('--transformResFuncName ', 'Name of response transformation function') + .option('--transformFuncPath ', 'Import path of request transformation function') .option('-c, --client ', 'HTTP client to generate [fetch, xhr, node, axios, angular]', 'fetch') .option('--name ', 'Custom client class name') .option('--useOptions', 'Use options instead of arguments') @@ -23,6 +34,7 @@ const params = program .option('--indent ', 'Indentation options [4, 2, tabs]', '4') .option('--postfix ', 'Service name postfix', 'Service') .option('--request ', 'Path to custom request file') + .option('--createIndex ', 'Generate barrel index file', true) .parse(process.argv) .opts(); @@ -32,6 +44,17 @@ if (OpenAPI) { OpenAPI.generate({ input: params.input, output: params.output, + serverOutput: params.serverOutput, + modelsDirName: params.modelsDirName, + serverDirName: params.serverDirName, + serverModelImportPath: params.serverModelImportPath, + serverApiTypesImportPath: params.serverApiTypesImportPath, + serverReqTypeName: params.serverReqTypeName, + serverResTypeName: params.serverResTypeName, + exportRouteHandler: params.exportRouteHandler, + transformReqFuncName: params.transformReqFuncName, + transformResFuncName: params.transformResFuncName, + transformFuncPath: params.transformFuncPath, httpClient: params.client, clientName: params.name, useOptions: params.useOptions, @@ -43,6 +66,7 @@ if (OpenAPI) { indent: params.indent, postfix: params.postfix, request: params.request, + createIndex: params.createIndex, }) .then(() => { process.exit(0); diff --git a/package-lock.json b/package-lock.json index f4b0b6ad7..6205028d9 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "openapi-typescript-codegen", - "version": "0.21.0", + "version": "0.2.11", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "openapi-typescript-codegen", - "version": "0.21.0", + "version": "0.2.11", "license": "MIT", "dependencies": { "camelcase": "^6.3.0", diff --git a/package.json b/package.json index 53dd7cd54..31a2ee203 100644 --- a/package.json +++ b/package.json @@ -1,15 +1,15 @@ { - "name": "openapi-typescript-codegen", - "version": "0.22.0", + "name": "@yrefaeli/openapi-typescript-codegen", + "version": "0.2.12", "description": "Library that generates Typescript clients based on the OpenAPI specification.", - "author": "Ferdi Koomen", - "homepage": "https://github.com/ferdikoomen/openapi-typescript-codegen", + "author": "Yon Refaeli", + "homepage": "https://github.com/yrefaeli/openapi-typescript-codegen", "repository": { "type": "git", - "url": "git+https://github.com/ferdikoomen/openapi-typescript-codegen.git" + "url": "git+https://github.com/yrefaeli/openapi-typescript-codegen.git" }, "bugs": { - "url": "https://github.com/ferdikoomen/openapi-typescript-codegen/issues" + "url": "https://github.com/yrefaeli/openapi-typescript-codegen/issues" }, "license": "MIT", "keywords": [ @@ -27,8 +27,8 @@ ], "maintainers": [ { - "name": "Ferdi Koomen", - "email": "info@madebyferdi.com" + "name": "Yon Refaeli", + "email": "yondev001@gmail.com" } ], "main": "dist/index.js", diff --git a/rollup.config.js b/rollup.config.js index 6f085f8d8..98b784f66 100644 --- a/rollup.config.js +++ b/rollup.config.js @@ -37,6 +37,9 @@ const handlebarsPlugin = () => ({ escapeComment: true, escapeDescription: true, camelCase: true, + pascalCase: true, + hyphenCase: true, + hasNonBodyParams: true, }, }); return `export default ${templateSpec};`; diff --git a/src/index.ts b/src/index.ts index ef7a8b1bf..f9a674354 100644 --- a/src/index.ts +++ b/src/index.ts @@ -15,18 +15,32 @@ export { Indent } from './Indent'; export type Options = { input: string | Record; output: string; + modelsDirName?: string; + serverOutput?: string; + serverDirName?: string; + serverModelImportPath?: string; + serverApiTypesImportPath?: string; + serverReqTypeName?: string; + serverResTypeName?: string; + exportRouteHandler?: boolean; + transformReqFuncName?: string; + transformResFuncName?: string; + transformFuncPath?: string; httpClient?: HttpClient; clientName?: string; useOptions?: boolean; useUnionTypes?: boolean; exportCore?: boolean; exportServices?: boolean; + exportServerInterfaces?: boolean; exportModels?: boolean; + exportQueryModels?: boolean; exportSchemas?: boolean; indent?: Indent; postfix?: string; request?: string; write?: boolean; + createIndex?: boolean; }; /** @@ -35,6 +49,7 @@ export type Options = { * service layer, etc. * @param input The relative location of the OpenAPI spec * @param output The relative location of the output directory + * @param modelsDirName Models directory name * @param httpClient The selected httpClient (fetch, xhr, node or axios) * @param clientName Custom client class name * @param useOptions Use options or arguments functions @@ -47,10 +62,22 @@ export type Options = { * @param postfix Service name postfix * @param request Path to custom request file * @param write Write the files to disk (true or false) + * @param createIndex Generate barrel index file */ export const generate = async ({ input, output, + modelsDirName = 'models', + serverOutput, + serverDirName = 'server', + serverModelImportPath = '', + serverApiTypesImportPath = '', + serverReqTypeName = 'ApiRequestWrapper', + serverResTypeName = 'ApiResponseWrapper', + exportRouteHandler = false, + transformReqFuncName = 'transformApiRequest', + transformResFuncName = 'transformApiResponse', + transformFuncPath = './util/transform-api-request', httpClient = HttpClient.FETCH, clientName, useOptions = false, @@ -58,11 +85,14 @@ export const generate = async ({ exportCore = true, exportServices = true, exportModels = true, + exportQueryModels = true, exportSchemas = false, + exportServerInterfaces = true, indent = Indent.SPACE_4, postfix = 'Service', request, write = true, + createIndex = true, }: Options): Promise => { const openApi = isString(input) ? await getOpenApiSpec(input) : input; const openApiVersion = getOpenApiVersion(openApi); @@ -81,17 +111,31 @@ export const generate = async ({ clientFinal, templates, output, + modelsDirName, + serverOutput ?? output, + serverDirName, + serverModelImportPath, + serverApiTypesImportPath, + serverReqTypeName, + serverResTypeName, + exportRouteHandler, + transformReqFuncName, + transformResFuncName, + transformFuncPath, httpClient, useOptions, useUnionTypes, exportCore, exportServices, + exportServerInterfaces, exportModels, + exportQueryModels, exportSchemas, indent, postfix, clientName, - request + request, + createIndex ); break; } @@ -104,12 +148,25 @@ export const generate = async ({ clientFinal, templates, output, + modelsDirName, + serverOutput ?? output, + serverDirName, + serverModelImportPath, + serverApiTypesImportPath, + serverReqTypeName, + serverResTypeName, + exportRouteHandler, + transformReqFuncName, + transformResFuncName, + transformFuncPath, httpClient, useOptions, useUnionTypes, exportCore, exportServices, + exportServerInterfaces, exportModels, + exportQueryModels, exportSchemas, indent, postfix, diff --git a/src/templates/core/functions/hasNonBodyParams.hbs b/src/templates/core/functions/hasNonBodyParams.hbs new file mode 100644 index 000000000..b155e6686 --- /dev/null +++ b/src/templates/core/functions/hasNonBodyParams.hbs @@ -0,0 +1,4 @@ +const hasNonBodyParams = (op: any): boolean => { + op.parameters + && op.parameters.filter(param => param.in !== 'body').length > 0 +}; diff --git a/src/templates/core/functions/hasRequestBody.hbs b/src/templates/core/functions/hasRequestBody.hbs new file mode 100644 index 000000000..2510302e2 --- /dev/null +++ b/src/templates/core/functions/hasRequestBody.hbs @@ -0,0 +1,3 @@ +const hasRequestBody = (values: any): boolean => { + return values.some((val, key) => key === 'requestBody'); +}; diff --git a/src/templates/exportModel.hbs b/src/templates/exportModel.hbs index 7ddb2c3e8..e6cf2d659 100644 --- a/src/templates/exportModel.hbs +++ b/src/templates/exportModel.hbs @@ -3,7 +3,7 @@ {{#if imports}} {{#each imports}} -import type { {{{this}}} } from './{{{this}}}'; +import type { {{{this}}} } from './{{{hyphenCase this}}}'; {{/each}} {{/if}} diff --git a/src/templates/exportQueryModel.hbs b/src/templates/exportQueryModel.hbs new file mode 100644 index 000000000..e03a54f5b --- /dev/null +++ b/src/templates/exportQueryModel.hbs @@ -0,0 +1,24 @@ +{{>header}} + +{{#each parameters}} +{{#notEquals this.in 'body'}} +{{#if this.imports}} +{{#each this.imports}} +import { {{{this}}} } from './{{{hyphenCase this}}}'; +{{/each}} +{{/if}} +{{/notEquals}} +{{/each}} + +export interface {{{pascalCase name}}}Params { + {{#each parameters}} + {{#notEquals this.in 'body'}} + {{#if description}} + /** + * {{{escapeComment description}}} + */ + {{/if}} + {{{prop}}}{{>isRequired}}: {{{type}}}, + {{/notEquals}} + {{/each}} +} diff --git a/src/templates/exportRouteHandler.hbs b/src/templates/exportRouteHandler.hbs new file mode 100644 index 000000000..348d4af17 --- /dev/null +++ b/src/templates/exportRouteHandler.hbs @@ -0,0 +1,29 @@ +{{#each services}} +import { I{{{pascalCase name}}}{{{@root.postfix}}} } from './{{{@root.serverDirName}}}/i-{{{hyphenCase name}}}-{{{hyphenCase @root.postfix}}}'; +{{/each}} +import { + {{{transformReqFuncName}}} as transformRequest, + {{{transformResFuncName}}} as transformResponse +} from '{{{transformFuncPath}}}'; +import { inject, injectable } from 'tsyringe'; + +@injectable() +export class RouteHandler { + constructor( + {{#each services}} + @inject('I{{{pascalCase name}}}{{{@root.postfix}}}') private {{{camelCase name}}}: I{{{pascalCase name}}}{{{@root.postfix}}}, + {{/each}} + ) { } + + private async applyTransforms(req, reply, handlerFunc) { + const transformedRequest = transformRequest(req); + const handlerResponse = await handlerFunc(transformedRequest); + return transformResponse(handlerResponse, reply); + } + +{{#each services}} +{{#each operations}} + {{{name}}} = async (req, reply) => await this.applyTransforms(req, reply, this.{{{camelCase service}}}.{{{name}}}); +{{/each}} +{{/each}} +} diff --git a/src/templates/exportServerInterface.hbs b/src/templates/exportServerInterface.hbs new file mode 100644 index 000000000..15a5c01b2 --- /dev/null +++ b/src/templates/exportServerInterface.hbs @@ -0,0 +1,38 @@ +{{>header}} + +import { {{{serverReqTypeName}}}, {{{serverResTypeName}}} } from '{{{serverApiTypesImportPath}}}'; + +{{#if imports}} +{{#each imports}} +import type { {{{this}}} } from '{{{@root.serverModelImportPath}}}/{{{hyphenCase this}}}'; +{{/each}} +{{/if}} + +{{#each operations}} +{{#hasNonBodyParams this}} +import { {{{pascalCase name}}}Params } from '{{{@root.serverModelImportPath}}}/{{{hyphenCase name}}}-params'; +{{/hasNonBodyParams}} +{{/each}} + +export interface I{{{name}}}{{{@root.postfix}}} { + {{#each operations}} + /** + {{#if deprecated}} + * @deprecated + {{/if}} + {{#if summary}} + * {{{escapeComment summary}}} + {{/if}} + {{#if description}} + * {{{escapeComment description}}} + {{/if}} + * @param req {{{escapeComment 'The API request'}}} + {{#each results}} + * @returns {{{type}}} {{#if description}}{{{escapeComment description}}}{{/if}} + {{/each}} + * @throws ApiError + */ + {{{name}}}(req: {{{@root.serverReqTypeName}}}<{{#hasNonBodyParams this}}{{{pascalCase name}}}Params{{else}}never{{/hasNonBodyParams}}{{#if parametersBody}}, {{{parametersBody.type}}}{{/if}}>): Promise<{{{@root.serverResTypeName}}}<{{>result}}>>; + + {{/each}} +} diff --git a/src/utils/formatCode.ts b/src/utils/formatCode.ts index 42cfd5cf4..9791b7163 100644 --- a/src/utils/formatCode.ts +++ b/src/utils/formatCode.ts @@ -19,5 +19,5 @@ export const formatCode = (s: string): string => { } return result; }); - return lines.join(EOL); + return lines.join(EOL) + EOL; }; diff --git a/src/utils/hyphenCase.ts b/src/utils/hyphenCase.ts new file mode 100644 index 000000000..6fe324aa7 --- /dev/null +++ b/src/utils/hyphenCase.ts @@ -0,0 +1,5 @@ +export const toHyphenCase = (str: string): string => + str + .replace(/([a-z])([A-Z])/g, '$1-$2') + .replace(/[\s_]+/g, '-') + .toLowerCase(); diff --git a/src/utils/registerHandlebarHelpers.spec.ts b/src/utils/registerHandlebarHelpers.spec.ts index f71ca0008..d9b4a9bd0 100644 --- a/src/utils/registerHandlebarHelpers.spec.ts +++ b/src/utils/registerHandlebarHelpers.spec.ts @@ -20,5 +20,6 @@ describe('registerHandlebarHelpers', () => { expect(helpers).toContain('escapeComment'); expect(helpers).toContain('escapeDescription'); expect(helpers).toContain('camelCase'); + expect(helpers).toContain('pascalCase'); }); }); diff --git a/src/utils/registerHandlebarHelpers.ts b/src/utils/registerHandlebarHelpers.ts index 58b2ad189..35eba109f 100644 --- a/src/utils/registerHandlebarHelpers.ts +++ b/src/utils/registerHandlebarHelpers.ts @@ -5,6 +5,7 @@ import { EOL } from 'os'; import type { Enum } from '../client/interfaces/Enum'; import type { Model } from '../client/interfaces/Model'; import type { HttpClient } from '../HttpClient'; +import { toHyphenCase } from './hyphenCase'; import { unique } from './unique'; export const registerHandlebarHelpers = (root: { @@ -96,4 +97,21 @@ export const registerHandlebarHelpers = (root: { Handlebars.registerHelper('camelCase', function (value: string): string { return camelCase(value); }); + + Handlebars.registerHelper('pascalCase', function (value: string): string { + return camelCase(value, { pascalCase: true }); + }); + + Handlebars.registerHelper('hyphenCase', function (value: string): string { + return toHyphenCase(value); + }); + + Handlebars.registerHelper( + 'hasNonBodyParams', + function (this: any, op: any, options: Handlebars.HelperOptions): string { + return op.parameters && op.parameters.length > 0 && !op.parametersBody + ? options.fn(this) + : options.inverse(this); + } + ); }; diff --git a/src/utils/registerHandlebarTemplates.ts b/src/utils/registerHandlebarTemplates.ts index bf77cbdc1..418e2cb37 100644 --- a/src/utils/registerHandlebarTemplates.ts +++ b/src/utils/registerHandlebarTemplates.ts @@ -30,6 +30,7 @@ import functionCatchErrorCodes from '../templates/core/functions/catchErrorCodes import functionGetFormData from '../templates/core/functions/getFormData.hbs'; import functionGetQueryString from '../templates/core/functions/getQueryString.hbs'; import functionGetUrl from '../templates/core/functions/getUrl.hbs'; +import functionHasNonBodyParams from '../templates/core/functions/hasNonBodyParams.hbs'; import functionIsBlob from '../templates/core/functions/isBlob.hbs'; import functionIsDefined from '../templates/core/functions/isDefined.hbs'; import functionIsFormData from '../templates/core/functions/isFormData.hbs'; @@ -53,7 +54,10 @@ import xhrGetResponseHeader from '../templates/core/xhr/getResponseHeader.hbs'; import xhrRequest from '../templates/core/xhr/request.hbs'; import xhrSendRequest from '../templates/core/xhr/sendRequest.hbs'; import templateExportModel from '../templates/exportModel.hbs'; +import templateExportQueryModel from '../templates/exportQueryModel.hbs'; +import templateExportRouteHandler from '../templates/exportRouteHandler.hbs'; import templateExportSchema from '../templates/exportSchema.hbs'; +import templateExportServerInterface from '../templates/exportServerInterface.hbs'; import templateExportService from '../templates/exportService.hbs'; import templateIndex from '../templates/index.hbs'; import partialBase from '../templates/partials/base.hbs'; @@ -90,8 +94,11 @@ export interface Templates { client: Handlebars.TemplateDelegate; exports: { model: Handlebars.TemplateDelegate; + queryModel: Handlebars.TemplateDelegate; schema: Handlebars.TemplateDelegate; service: Handlebars.TemplateDelegate; + serverInterface: Handlebars.TemplateDelegate; + routeHandler: Handlebars.TemplateDelegate; }; core: { settings: Handlebars.TemplateDelegate; @@ -122,8 +129,11 @@ export const registerHandlebarTemplates = (root: { client: Handlebars.template(templateClient), exports: { model: Handlebars.template(templateExportModel), + queryModel: Handlebars.template(templateExportQueryModel), schema: Handlebars.template(templateExportSchema), service: Handlebars.template(templateExportService), + serverInterface: Handlebars.template(templateExportServerInterface), + routeHandler: Handlebars.template(templateExportRouteHandler), }, core: { settings: Handlebars.template(templateCoreSettings), @@ -170,6 +180,7 @@ export const registerHandlebarTemplates = (root: { Handlebars.registerPartial('functions/catchErrorCodes', Handlebars.template(functionCatchErrorCodes)); Handlebars.registerPartial('functions/getFormData', Handlebars.template(functionGetFormData)); Handlebars.registerPartial('functions/getQueryString', Handlebars.template(functionGetQueryString)); + Handlebars.registerPartial('functions/hasNonBodyParams', Handlebars.template(functionHasNonBodyParams)); Handlebars.registerPartial('functions/getUrl', Handlebars.template(functionGetUrl)); Handlebars.registerPartial('functions/isBlob', Handlebars.template(functionIsBlob)); Handlebars.registerPartial('functions/isDefined', Handlebars.template(functionIsDefined)); diff --git a/src/utils/writeClient.spec.ts b/src/utils/writeClient.spec.ts index 3c06a95a5..64933eb75 100644 --- a/src/utils/writeClient.spec.ts +++ b/src/utils/writeClient.spec.ts @@ -21,8 +21,11 @@ describe('writeClient', () => { client: () => 'client', exports: { model: () => 'model', + queryModel: () => 'model', schema: () => 'schema', service: () => 'service', + serverInterface: () => 'serverInterface', + routeHandler: () => 'routeHandler', }, core: { settings: () => 'settings', @@ -40,6 +43,17 @@ describe('writeClient', () => { client, templates, './dist', + 'models', + './dist', + 'server', + '../models', + '', + 'ApiRequestWrapper', + 'ApiResponseWrapper', + false, + '', + '', + '', HttpClient.FETCH, false, false, @@ -47,6 +61,8 @@ describe('writeClient', () => { true, true, true, + true, + true, Indent.SPACE_4, 'Service', 'AppClient' diff --git a/src/utils/writeClient.ts b/src/utils/writeClient.ts index a0ffc1821..897ae7234 100644 --- a/src/utils/writeClient.ts +++ b/src/utils/writeClient.ts @@ -13,6 +13,9 @@ import { writeClientIndex } from './writeClientIndex'; import { writeClientModels } from './writeClientModels'; import { writeClientSchemas } from './writeClientSchemas'; import { writeClientServices } from './writeClientServices'; +import { writeQueryModels } from './writeQueryModels'; +import { writeRouteHandler } from './writeRouteHandler'; +import { writeServerInterfaces } from './writeServerInterfaces'; /** * Write our OpenAPI client, using the given templates at the given output @@ -24,35 +27,53 @@ import { writeClientServices } from './writeClientServices'; * @param useUnionTypes Use union types instead of enums * @param exportCore Generate core client classes * @param exportServices Generate services + * @param exportServerInterfaces Generate server interfaces * @param exportModels Generate models - * @param exportSchemas Generate schemas + * @param exportQueryModels Generate query models * @param exportSchemas Generate schemas * @param indent Indentation options (4, 2 or tab) * @param postfix Service name postfix * @param clientName Custom client class name * @param request Path to custom request file + * @param createIndex Generate barrel index file */ export const writeClient = async ( client: Client, templates: Templates, output: string, + modelsDirName: string, + serverOutput: string, + serverDirName: string, + serverModelImportPath: string, + serverApiTypesImportPath: string, + serverReqTypeName: string, + serverResTypeName: string, + exportRouteHandler: boolean, + transformReqFuncName: string, + transformResFuncName: string, + transformFuncPath: string, httpClient: HttpClient, useOptions: boolean, useUnionTypes: boolean, exportCore: boolean, exportServices: boolean, + exportServerInterfaces: boolean, exportModels: boolean, + exportQueryModels: boolean, exportSchemas: boolean, indent: Indent, postfix: string, clientName?: string, - request?: string + request?: string, + createIndex?: boolean ): Promise => { const outputPath = resolve(process.cwd(), output); + const serverOutputPath = resolve(process.cwd(), serverOutput); const outputPathCore = resolve(outputPath, 'core'); - const outputPathModels = resolve(outputPath, 'models'); + const outputPathModels = resolve(outputPath, modelsDirName); const outputPathSchemas = resolve(outputPath, 'schemas'); const outputPathServices = resolve(outputPath, 'services'); + const outputPathServerInterfaces = resolve(serverOutputPath, serverDirName); if (!isSubDirectory(process.cwd(), output)) { throw new Error(`Output folder is not a subdirectory of the current working directory`); @@ -80,6 +101,48 @@ export const writeClient = async ( ); } + if (exportServerInterfaces) { + // await rmdir(outputPathServerInterfaces); + // await mkdir(outputPathServerInterfaces); + await writeServerInterfaces( + client.services, + templates, + outputPathServerInterfaces, + serverModelImportPath, + serverApiTypesImportPath, + serverReqTypeName, + serverResTypeName, + httpClient, + useUnionTypes, + useOptions, + indent, + postfix, + clientName + ); + } + + if (exportRouteHandler) { + await writeRouteHandler( + client.services, + templates, + serverOutputPath, + serverDirName, + serverModelImportPath, + serverApiTypesImportPath, + serverReqTypeName, + serverResTypeName, + transformReqFuncName, + transformResFuncName, + transformFuncPath, + httpClient, + useUnionTypes, + useOptions, + indent, + postfix, + clientName + ); + } + if (exportSchemas) { await rmdir(outputPathSchemas); await mkdir(outputPathSchemas); @@ -87,17 +150,21 @@ export const writeClient = async ( } if (exportModels) { - await rmdir(outputPathModels); - await mkdir(outputPathModels); + // await rmdir(outputPathModels); + // await mkdir(outputPathModels); await writeClientModels(client.models, templates, outputPathModels, httpClient, useUnionTypes, indent); } + if (exportModels && exportQueryModels) { + await writeQueryModels(client.services, templates, outputPathModels, httpClient, useUnionTypes, indent); + } + if (isDefined(clientName)) { await mkdir(outputPath); await writeClientClass(client, templates, outputPath, httpClient, clientName, indent, postfix); } - if (exportCore || exportServices || exportSchemas || exportModels) { + if (createIndex && (exportCore || exportServices || exportSchemas || exportModels)) { await mkdir(outputPath); await writeClientIndex( client, diff --git a/src/utils/writeClientClass.spec.ts b/src/utils/writeClientClass.spec.ts index 102f2eb57..a1cbb6a90 100644 --- a/src/utils/writeClientClass.spec.ts +++ b/src/utils/writeClientClass.spec.ts @@ -21,8 +21,11 @@ describe('writeClientClass', () => { client: () => 'client', exports: { model: () => 'model', + queryModel: () => 'model', schema: () => 'schema', service: () => 'service', + serverInterface: () => 'serverInterface', + routeHandler: () => 'routeHandler', }, core: { settings: () => 'settings', diff --git a/src/utils/writeClientCore.spec.ts b/src/utils/writeClientCore.spec.ts index cb2cc2925..c700f6863 100644 --- a/src/utils/writeClientCore.spec.ts +++ b/src/utils/writeClientCore.spec.ts @@ -21,8 +21,11 @@ describe('writeClientCore', () => { client: () => 'client', exports: { model: () => 'model', + queryModel: () => 'model', schema: () => 'schema', service: () => 'service', + serverInterface: () => 'serverInterface', + routeHandler: () => 'routeHandler', }, core: { settings: () => 'settings', diff --git a/src/utils/writeClientIndex.spec.ts b/src/utils/writeClientIndex.spec.ts index 6284dfd2e..09dcbec4f 100644 --- a/src/utils/writeClientIndex.spec.ts +++ b/src/utils/writeClientIndex.spec.ts @@ -19,8 +19,11 @@ describe('writeClientIndex', () => { client: () => 'client', exports: { model: () => 'model', + queryModel: () => 'model', schema: () => 'schema', service: () => 'service', + serverInterface: () => 'serverInterface', + routeHandler: () => 'routeHandler', }, core: { settings: () => 'settings', diff --git a/src/utils/writeClientModels.spec.ts b/src/utils/writeClientModels.spec.ts index 81b7b58b6..4e40b4bd9 100644 --- a/src/utils/writeClientModels.spec.ts +++ b/src/utils/writeClientModels.spec.ts @@ -34,8 +34,11 @@ describe('writeClientModels', () => { client: () => 'client', exports: { model: () => 'model', + queryModel: () => 'model', schema: () => 'schema', service: () => 'service', + serverInterface: () => 'serverInterface', + routeHandler: () => 'routeHandler', }, core: { settings: () => 'settings', diff --git a/src/utils/writeClientModels.ts b/src/utils/writeClientModels.ts index 997569b9f..fe39571ad 100644 --- a/src/utils/writeClientModels.ts +++ b/src/utils/writeClientModels.ts @@ -6,6 +6,7 @@ import type { Indent } from '../Indent'; import { writeFile } from './fileSystem'; import { formatCode as f } from './formatCode'; import { formatIndentation as i } from './formatIndentation'; +import { toHyphenCase } from './hyphenCase'; import type { Templates } from './registerHandlebarTemplates'; /** @@ -26,7 +27,7 @@ export const writeClientModels = async ( indent: Indent ): Promise => { for (const model of models) { - const file = resolve(outputPath, `${model.name}.ts`); + const file = resolve(outputPath, `${toHyphenCase(model.name)}.ts`); const templateResult = templates.exports.model({ ...model, httpClient, diff --git a/src/utils/writeClientSchemas.spec.ts b/src/utils/writeClientSchemas.spec.ts index 499aba3e8..083c462db 100644 --- a/src/utils/writeClientSchemas.spec.ts +++ b/src/utils/writeClientSchemas.spec.ts @@ -34,8 +34,11 @@ describe('writeClientSchemas', () => { client: () => 'client', exports: { model: () => 'model', + queryModel: () => 'model', schema: () => 'schema', service: () => 'service', + serverInterface: () => 'serverInterface', + routeHandler: () => 'routeHandler', }, core: { settings: () => 'settings', diff --git a/src/utils/writeClientServices.spec.ts b/src/utils/writeClientServices.spec.ts index 38a649483..89707737d 100644 --- a/src/utils/writeClientServices.spec.ts +++ b/src/utils/writeClientServices.spec.ts @@ -22,8 +22,11 @@ describe('writeClientServices', () => { client: () => 'client', exports: { model: () => 'model', + queryModel: () => 'model', schema: () => 'schema', service: () => 'service', + serverInterface: () => 'serverInterface', + routeHandler: () => 'routeHandler', }, core: { settings: () => 'settings', diff --git a/src/utils/writeQueryModels.ts b/src/utils/writeQueryModels.ts new file mode 100644 index 000000000..b9fe31419 --- /dev/null +++ b/src/utils/writeQueryModels.ts @@ -0,0 +1,48 @@ +import camelcase from 'camelcase'; +import { resolve } from 'path'; + +import type { Model } from '../client/interfaces/Model'; +import { Service } from '../client/interfaces/Service'; +import type { HttpClient } from '../HttpClient'; +import type { Indent } from '../Indent'; +import { writeFile } from './fileSystem'; +import { formatCode as f } from './formatCode'; +import { formatIndentation as i } from './formatIndentation'; +import { toHyphenCase } from './hyphenCase'; +import type { Templates } from './registerHandlebarTemplates'; + +/** + * Generate Models using the Handlebar template and write to disk. + * @param models Array of Models to write + * @param templates The loaded handlebar templates + * @param outputPath Directory to write the generated files to + * @param httpClient The selected httpClient (fetch, xhr, node or axios) + * @param useUnionTypes Use union types instead of enums + * @param indent Indentation options (4, 2 or tab) + */ +export const writeQueryModels = async ( + services: Service[], + templates: Templates, + outputPath: string, + httpClient: HttpClient, + useUnionTypes: boolean, + indent: Indent +): Promise => { + for (const service of services) { + for (const operation of service.operations) { + if (operation.parameters.filter(param => param.in !== 'body').length === 0) { + continue; + } + const modelName = camelcase(operation.name, { pascalCase: true }); + const modelNameHyphens = toHyphenCase(modelName); + const file = resolve(outputPath, `${modelNameHyphens}-params.ts`); + + const templateResult = templates.exports.queryModel({ + ...operation, + httpClient, + useUnionTypes, + }); + await writeFile(file, i(f(templateResult), indent)); + } + } +}; diff --git a/src/utils/writeRouteHandler.ts b/src/utils/writeRouteHandler.ts new file mode 100644 index 000000000..830e2736d --- /dev/null +++ b/src/utils/writeRouteHandler.ts @@ -0,0 +1,63 @@ +import { resolve } from 'path'; + +import type { Service } from '../client/interfaces/Service'; +import type { HttpClient } from '../HttpClient'; +import type { Indent } from '../Indent'; +import { writeFile } from './fileSystem'; +import { formatCode as f } from './formatCode'; +import { formatIndentation as i } from './formatIndentation'; +import { isDefined } from './isDefined'; +import type { Templates } from './registerHandlebarTemplates'; + +/** + * Generate Services using the Handlebar template and write to disk. + * @param services Array of Services to write + * @param templates The loaded handlebar templates + * @param outputPath Directory to write the generated files to + * @param httpClient The selected httpClient (fetch, xhr, node or axios) + * @param useUnionTypes Use union types instead of enums + * @param useOptions Use options or arguments functions + * @param indent Indentation options (4, 2 or tab) + * @param postfix Service name postfix + * @param clientName Custom client class name + */ +export const writeRouteHandler = async ( + services: Service[], + templates: Templates, + outputPath: string, + serverDirName: string, + serverModelImportPath: string, + serverApiTypesImportPath: string, + serverReqTypeName: string, + serverResTypeName: string, + transformReqFuncName: string, + transformResFuncName: string, + transformFuncPath: string, + httpClient: HttpClient, + useUnionTypes: boolean, + useOptions: boolean, + indent: Indent, + postfix: string, + clientName?: string +): Promise => { + // const serviceNameHyphens = toHyphenCase(service.name); + const file = resolve(outputPath, `route-handler.ts`); + serverModelImportPath = serverModelImportPath; + const templateResult = templates.exports.routeHandler({ + services, + serverDirName, + serverModelImportPath, + serverApiTypesImportPath, + serverReqTypeName, + serverResTypeName, + transformReqFuncName, + transformResFuncName, + transformFuncPath, + httpClient, + useUnionTypes, + useOptions, + postfix, + exportClient: isDefined(clientName), + }); + await writeFile(file, i(f(templateResult), indent)); +}; diff --git a/src/utils/writeServerInterfaces.ts b/src/utils/writeServerInterfaces.ts new file mode 100644 index 000000000..b691846a4 --- /dev/null +++ b/src/utils/writeServerInterfaces.ts @@ -0,0 +1,58 @@ +import { resolve } from 'path'; + +import type { Service } from '../client/interfaces/Service'; +import type { HttpClient } from '../HttpClient'; +import type { Indent } from '../Indent'; +import { writeFile } from './fileSystem'; +import { formatCode as f } from './formatCode'; +import { formatIndentation as i } from './formatIndentation'; +import { toHyphenCase } from './hyphenCase'; +import { isDefined } from './isDefined'; +import type { Templates } from './registerHandlebarTemplates'; + +/** + * Generate Services using the Handlebar template and write to disk. + * @param services Array of Services to write + * @param templates The loaded handlebar templates + * @param outputPath Directory to write the generated files to + * @param httpClient The selected httpClient (fetch, xhr, node or axios) + * @param useUnionTypes Use union types instead of enums + * @param useOptions Use options or arguments functions + * @param indent Indentation options (4, 2 or tab) + * @param postfix Service name postfix + * @param clientName Custom client class name + */ +export const writeServerInterfaces = async ( + services: Service[], + templates: Templates, + outputPath: string, + serverModelImportPath: string, + serverApiTypesImportPath: string, + serverReqTypeName: string, + serverResTypeName: string, + httpClient: HttpClient, + useUnionTypes: boolean, + useOptions: boolean, + indent: Indent, + postfix: string, + clientName?: string +): Promise => { + for (const service of services) { + const serviceNameHyphens = toHyphenCase(service.name); + const file = resolve(outputPath, `i-${serviceNameHyphens}-${toHyphenCase(postfix)}.ts`); + serverModelImportPath = serverModelImportPath; + const templateResult = templates.exports.serverInterface({ + ...service, + serverModelImportPath, + serverApiTypesImportPath, + serverReqTypeName, + serverResTypeName, + httpClient, + useUnionTypes, + useOptions, + postfix, + exportClient: isDefined(clientName), + }); + await writeFile(file, i(f(templateResult), indent)); + } +};