diff --git a/src/generators/core/endpoints/getEndpointParameter.ts b/src/generators/core/endpoints/getEndpointParameter.ts index af6721d..1ee4a55 100644 --- a/src/generators/core/endpoints/getEndpointParameter.ts +++ b/src/generators/core/endpoints/getEndpointParameter.ts @@ -8,13 +8,14 @@ import { getEnumZodSchemaCodeFromEnumNames, getZodSchema } from "@/generators/co import { resolveZodSchemaName } from "@/generators/core/zod/resolveZodSchemaName"; import { EndpointParameter } from "@/generators/types/endpoint"; import { ParameterObject } from "@/generators/types/openapi"; -import { isSchemaObject } from "@/generators/utils/openapi-schema.utils"; +import { isReferenceObject, isSchemaObject } from "@/generators/utils/openapi-schema.utils"; import { isParamMediaTypeAllowed, getParameterEnumNames, isSortingParameterObject, pathParamToVariableName, } from "@/generators/utils/openapi.utils"; +import { resolveQueryFilterComponentName } from "@/generators/utils/query-filter-component-names.utils"; import { getEnumZodSchemaName, getParamZodSchemaName, @@ -25,12 +26,14 @@ export function getEndpointParameter({ resolver, param, operationName, + operationId, isUniqueOperationName, tag, }: { resolver: SchemaResolver; param: OpenAPIV3.ReferenceObject | ParameterObject; operationName: string; + operationId?: string; isUniqueOperationName: boolean; tag: string; }): EndpointParameter | undefined { @@ -65,10 +68,17 @@ export function getEndpointParameter({ (schema as OpenAPIV3.SchemaObject).description = (paramObj.description ?? "").trim(); } - const fallbackName = getParamZodSchemaName( - getZodSchemaOperationName(operationName, isUniqueOperationName, tag), - paramObj.name, - ); + const zodSchemaOperationName = getZodSchemaOperationName(operationName, isUniqueOperationName, tag); + const legacyFilterComponentName = + paramObj.name === resolver.options.filterParamName && + !isReferenceObject(schema) && + resolver.options.queryFilterComponentNames + ? resolveQueryFilterComponentName(operationId, resolver.options.queryFilterComponentNames) + : undefined; + + const fallbackName = + legacyFilterComponentName ?? + getParamZodSchemaName(zodSchemaOperationName, paramObj.name); let parameterSortingEnumSchemaName: string | undefined = undefined; if (isSortingParameterObject(paramObj, schema, resolver)) { diff --git a/src/generators/core/endpoints/getEndpointsFromOpenAPIDoc.ts b/src/generators/core/endpoints/getEndpointsFromOpenAPIDoc.ts index 9fbc57e..8a1099f 100644 --- a/src/generators/core/endpoints/getEndpointsFromOpenAPIDoc.ts +++ b/src/generators/core/endpoints/getEndpointsFromOpenAPIDoc.ts @@ -101,6 +101,7 @@ export function getEndpointsFromOpenAPIDoc(resolver: SchemaResolver) { resolver, param, operationName, + operationId: operation.operationId, isUniqueOperationName, tag, }); diff --git a/src/generators/types/options.ts b/src/generators/types/options.ts index b9344b9..0fee3a0 100644 --- a/src/generators/types/options.ts +++ b/src/generators/types/options.ts @@ -59,6 +59,14 @@ interface BuilderConfigsGenerateOptions { dynamicColumnsImportPath: string; } +interface QueryFilterComponentNameOptions { + /** + * Optional map of OpenAPI operationId to stable filter schema names. + * Used for inline `filter` query objects when migrating away from named filter DTO refs. + */ + queryFilterComponentNames?: Record; +} + interface GenerateConfig { outputFileNameSuffix: string; namespaceSuffix: string; @@ -70,6 +78,8 @@ interface BaseGenerateOptions { clearOutput?: boolean; incremental?: boolean; splitByTags: boolean; + /** When true, non-default tags emit proxy models that re-export from CommonModels. */ + modelsInCommon?: boolean; defaultTag: string; includeTags: string[]; excludeTags: string[]; @@ -92,4 +102,5 @@ export interface GenerateOptions QueriesGenerateOptions, InfiniteQueriesGenerateOptions, ACLGenerateOptions, - BuilderConfigsGenerateOptions {} + BuilderConfigsGenerateOptions, + QueryFilterComponentNameOptions {} diff --git a/src/generators/utils/generate/generate.imports.utils.test.ts b/src/generators/utils/generate/generate.imports.utils.test.ts index 565122f..b14fd83 100644 --- a/src/generators/utils/generate/generate.imports.utils.test.ts +++ b/src/generators/utils/generate/generate.imports.utils.test.ts @@ -4,11 +4,11 @@ import { getImportPath } from "./generate.imports.utils"; describe("Utils: generate imports", () => { test("getImportPath", () => { - expect(getImportPath({ output: "output", importPath: "ts", tsPath: "@/data" })).toEqual("@/data/"); + expect(getImportPath({ output: "output", importPath: "ts", tsPath: "@/data" })).toEqual("../"); expect(getImportPath({ output: "output", importPath: "ts", tsPath: "@/output" })).toEqual("@/output/"); expect(getImportPath({ output: "output", importPath: "relative", tsPath: "@/data" })).toEqual("../"); expect(getImportPath({ output: "output", importPath: "absolute", tsPath: "@/data" })).toEqual("output/"); - expect(getImportPath({ output: "some-folder", importPath: "ts", tsPath: "@/data" })).toEqual("@/data/"); + expect(getImportPath({ output: "some-folder", importPath: "ts", tsPath: "@/data" })).toEqual("../"); expect(getImportPath({ output: "some-folder", importPath: "ts", tsPath: "@/output" })).toEqual("@/output/"); expect(getImportPath({ output: "some-folder", importPath: "relative", tsPath: "@/data" })).toEqual("../"); expect(getImportPath({ output: "some-folder", importPath: "absolute", tsPath: "@/data" })).toEqual("some-folder/"); diff --git a/src/generators/utils/generate/generate.imports.utils.ts b/src/generators/utils/generate/generate.imports.utils.ts index a8b1005..6ac9fb2 100644 --- a/src/generators/utils/generate/generate.imports.utils.ts +++ b/src/generators/utils/generate/generate.imports.utils.ts @@ -155,16 +155,44 @@ export function getEntityImports({ return Array.from(imports.values()); } +function normalizeOutputPath(output: string) { + return output.replace(/\\/g, "/").replace(/\/$/, ""); +} + +function outputMatchesTsPath(options: Pick) { + const normalizedOutput = normalizeOutputPath(options.output); + + if (options.tsPath.startsWith("@/")) { + const physicalPath = `src/${options.tsPath.slice(2)}`; + return normalizedOutput === physicalPath || normalizedOutput.endsWith(`/${physicalPath}`); + } + + return normalizedOutput === options.tsPath; +} + +function shouldUseRelativeImports(options: Pick) { + return options.importPath === "ts" && options.tsPath === "@/data" && !outputMatchesTsPath(options); +} + export function getImportPath(options: Pick, fromRoot = false) { - let importPath = options.tsPath; if (options.importPath === "relative") { - importPath = fromRoot ? "./" : "../"; - } else if (options.importPath === "absolute") { - importPath = options.output; - } else if (new RegExp(`${TEMPLATE_DATA_FILE_PATH}`, "g").test(options.output)) { - importPath = options.output.replace(new RegExp(`.*${TEMPLATE_DATA_FILE_PATH}`, "g"), options.tsPath); + return `${fromRoot ? "./" : "../"}`.replace(/\/\//g, "/"); + } + + if (options.importPath === "absolute") { + return `${options.output}/`.replace(/\/\//g, "/"); + } + + if (new RegExp(`${TEMPLATE_DATA_FILE_PATH}`, "g").test(options.output)) { + const importPath = options.output.replace(new RegExp(`.*${TEMPLATE_DATA_FILE_PATH}`, "g"), options.tsPath); + return `${importPath}/`.replace(/\/\//g, "/"); + } + + if (shouldUseRelativeImports(options)) { + return `${fromRoot ? "./" : "../"}`.replace(/\/\//g, "/"); } - return `${importPath}/`.replace(/\/\//g, "/"); + + return `${options.tsPath}/`.replace(/\/\//g, "/"); } function getImports({ diff --git a/src/generators/utils/query-filter-component-names.utils.test.ts b/src/generators/utils/query-filter-component-names.utils.test.ts new file mode 100644 index 0000000..166e7a0 --- /dev/null +++ b/src/generators/utils/query-filter-component-names.utils.test.ts @@ -0,0 +1,25 @@ +import { describe, expect, it } from "vitest"; + +import { resolveQueryFilterComponentName } from "./query-filter-component-names.utils"; + +describe("resolveQueryFilterComponentName", () => { + const queryFilterComponentNames = { + BusinessPartnersController_paginate: "BusinessPartnerFilterDto", + VatRuleController_list: "VatRuleFilterDto", + }; + + it("maps operation ids to configured filter component names", () => { + expect( + resolveQueryFilterComponentName("BusinessPartnersController_paginate", queryFilterComponentNames), + ).toBe("BusinessPartnerFilterDto"); + expect(resolveQueryFilterComponentName("VatRuleController_list", queryFilterComponentNames)).toBe( + "VatRuleFilterDto", + ); + }); + + it("returns undefined when the map or operation id is missing", () => { + expect(resolveQueryFilterComponentName("UnknownController_action", queryFilterComponentNames)).toBeUndefined(); + expect(resolveQueryFilterComponentName("VatRuleController_list", undefined)).toBeUndefined(); + expect(resolveQueryFilterComponentName(undefined, queryFilterComponentNames)).toBeUndefined(); + }); +}); diff --git a/src/generators/utils/query-filter-component-names.utils.ts b/src/generators/utils/query-filter-component-names.utils.ts new file mode 100644 index 0000000..5b0a476 --- /dev/null +++ b/src/generators/utils/query-filter-component-names.utils.ts @@ -0,0 +1,10 @@ +export function resolveQueryFilterComponentName( + operationId: string | undefined, + queryFilterComponentNames: Record | undefined, +): string | undefined { + if (!operationId || !queryFilterComponentNames) { + return undefined; + } + + return queryFilterComponentNames[operationId]; +}