Строгий пайплайн: сырые типы остаются в generated/backend.types.ts; домен видит только T*Backend из entities/*/types.
Правило: Не импортируйте schema OpenAPI в converters или api. Один раз алиасьте в T*Backend, затем мапьте во frontend-модели.
1{
2 "scripts": {
3 "codegen:types": "tsx --env-file=.env scripts/generate-api-types.ts",
4 "codegen:paths": "tsx --env-file=.env scripts/generate-api-paths.ts",
5 "codegen": "npm run codegen:types && npm run codegen:paths"
6 }
7}1/**
2 * Генерация TypeScript-типов из OpenAPI-спецификации.
3 * npx tsx --env-file=.env scripts/generate-api-types.ts
4 */
5import { dirname, join, resolve } from "path";
6import { fileURLToPath } from "url";
7import { generateApi } from "swagger-typescript-api";
8
9const __dirname = dirname(fileURLToPath(import.meta.url));
10const API_URL = process.env.VITE_BASE_URL;
11const OUTPUT_DIR = join(resolve(__dirname, ".."), "src/shared/api/generated");
12
13const SPEC_URL = API_URL.endsWith("/openapi.json")
14 ? API_URL
15 : `${API_URL.replace(/\/$/, "")}/openapi.json`;
16
17await generateApi({
18 fileName: "backend.types.ts",
19 output: OUTPUT_DIR,
20 url: SPEC_URL,
21 generateClient: false,
22 extractEnums: true,
23 extractRequestParams: true,
24 extractRequestBody: true
25});
261/**
2 * Группирует OpenAPI paths по tags → paths/<tag>.paths.ts
3 * Каждый entry: { url, method, _types: { body, query, response } }
4 * npx tsx --env-file=.env scripts/generate-api-paths.ts
5 */
6// См. полный скрипт в blogix-admin / MCP openapi-pattern.md
7// Итог:
8export const ADMIN_PROMOCODE_PATHS = {
9 readPromocodes: {
10 url: "/adv-admin/promocode/list",
11 method: "POST",
12 _types: {} as {
13 body: ReadPromocodesRequest;
14 query: void;
15 response: ReadPromocodesResponse;
16 }
17 } as const
18} as const;
19entities/*/types/*-backend.interface.ts может импортировать generated/backend.types. Экспортируйте алиасы TFooBackend (и re-export enum).1// entities/promocode/types/promocode-backend.interface.ts
2// ЕДИНСТВЕННОЕ место импорта schema из backend.types.ts
3import type {
4 CreatePromocodeRequest,
5 PromocodeDiscountType,
6 PromocodeResponse,
7 ReadPromocodesResponse
8} from "@/shared/api/generated/backend.types";
9import { ADMIN_PROMOCODE_PATHS } from "@/shared/api/generated/paths/admin-promocode.paths";
10
11export type TPromocodeBackend = PromocodeResponse;
12export type TPromocodesBackend = ReadPromocodesResponse;
13export type TCreatePromocodeRequestBackend = CreatePromocodeRequest;
14export type TSetPromocodeActiveQueryBackend =
15 typeof ADMIN_PROMOCODE_PATHS.setPromocodeActive._types.query;
16
17export { PromocodeDiscountType };
18export type TPromocodeDiscountTypeBackend = PromocodeDiscountType;
191// entities/promocode/converters/promocode.converters.ts — OK
2import type { TPromocodeBackend } from "../types";
3import { PromocodeDiscountType } from "../types";
4
5export const mapPromocodeToFrontend = (data: TPromocodeBackend) => ({
6 id: data.id,
7 code: data.code
8 // …
9});
101// ❌ FORBIDDEN in converters / api / handlers / UI
2import type { PromocodeResponse } from "@/shared/api";
3import { OrderStatus } from "@/shared/api/generated/backend.types";
4generated/backend.types глобально; разрешите только в entities/**/types и shared/api/generated. Лучше не реэкспортировать backend.types из @/shared/api.1// eslint.config.js — fragment
2{
3 rules: {
4 "no-restricted-imports": [
5 "error",
6 {
7 patterns: [
8 {
9 group: [
10 "**/shared/api/generated/backend.types",
11 "**/shared/api/generated/backend.types.ts",
12 "@/shared/api/generated/backend.types"
13 ],
14 message:
15 "Raw backend.types.ts types only in entities/*/types as T*Backend. Use ../types aliases elsewhere."
16 }
17 ]
18 }
19 ]
20 }
21},
22{
23 files: [
24 "src/entities/**/types/**/*.{ts,tsx}",
25 "src/shared/api/generated/**/*.{ts,tsx}"
26 ],
27 rules: { "no-restricted-imports": "off" }
28}
29