diff --git a/ai/nextjs-style-guide/DEVELOP.md b/ai/nextjs-style-guide/DEVELOP.md index 265090d..523abfd 100644 --- a/ai/nextjs-style-guide/DEVELOP.md +++ b/ai/nextjs-style-guide/DEVELOP.md @@ -22,9 +22,9 @@ description: Что AI-агент обязан прочитать перед н ### 1. Архитектура (КРИТИЧЕСКИ ВАЖНО) * [Архитектура: Обзор](./basics/architecture/index.md) -* [Архитектура: Слои](./basics/architecture/reference/layers.md) -* [Архитектура: Модули](./basics/architecture/reference/modules.md) -* [Архитектура: Сегменты](./basics/architecture/reference/segments.md) +* [Архитектура: Слои](./basics/architecture/layers.md) +* [Архитектура: Модули](./basics/architecture/modules.md) +* [Архитектура: Сегменты](./basics/architecture/segments.md) **Архитектура — это самое важное в проекте.** diff --git a/ai/nextjs-style-guide/MAP.md b/ai/nextjs-style-guide/MAP.md index 2f022c8..f4d196a 100644 --- a/ai/nextjs-style-guide/MAP.md +++ b/ai/nextjs-style-guide/MAP.md @@ -12,9 +12,9 @@ - [Технологии и библиотеки](./basics/tech-stack.md) — Какие библиотеки и инструменты используются в проекте. - [Именование](./basics/naming.md) — Как называть переменные, файлы и прочие сущности в коде. - [Архитектура: Обзор](./basics/architecture/index.md) — Архитектурный подход проекта: что такое SLM и как он устроен. -- [Архитектура: Слои](./basics/architecture/reference/layers.md) — Из каких слоёв состоит SLM-архитектура и как они связаны. -- [Архитектура: Модули](./basics/architecture/reference/modules.md) — Что такое модуль в SLM-архитектуре и как он устроен. -- [Архитектура: Сегменты](./basics/architecture/reference/segments.md) — Что такое сегмент модуля в SLM-архитектуре и какие они бывают. +- [Архитектура: Слои](./basics/architecture/layers.md) — Из каких слоёв состоит SLM-архитектура и как они связаны. +- [Архитектура: Модули](./basics/architecture/modules.md) — Что такое модуль в SLM-архитектуре и как он устроен. +- [Архитектура: Сегменты](./basics/architecture/segments.md) — Что такое сегмент модуля в SLM-архитектуре и какие они бывают. - [Стиль кода](./basics/code-style.md) — Как оформляется код в проекте. - [Документирование](./basics/documentation.md) — Что и как документировать в коде. - [Типизация](./basics/typing.md) — Как типизируется код в проекте. @@ -63,4 +63,4 @@ - [Biome](./applied/biome.md) — Установка и настройка линтера-форматтера в новом проекте. - [PostCSS](./applied/postcss.md) — Установка и настройка CSS-процессора в новом проекте. - [VS Code](./applied/vscode.md) — Единые настройки редактора и расширений для команды. -- [Локализация](./applied/localization.md) — Как организовать локализацию как infrastructure-модуль. +- [Локализация](./applied/localization.md) — Как организовать локализацию как infra-модуль. diff --git a/ai/nextjs-style-guide/VERSION b/ai/nextjs-style-guide/VERSION index 2ff96d1..138d54c 100644 --- a/ai/nextjs-style-guide/VERSION +++ b/ai/nextjs-style-guide/VERSION @@ -1,2 +1,2 @@ -e835210 -2026-04-30T13:02:04.343Z +eadc462 +2026-05-08T04:14:35.127Z diff --git a/ai/nextjs-style-guide/applied/aliases.md b/ai/nextjs-style-guide/applied/aliases.md index da6ccab..49383d6 100644 --- a/ai/nextjs-style-guide/applied/aliases.md +++ b/ai/nextjs-style-guide/applied/aliases.md @@ -1,7 +1,7 @@ --- title: Алиасы импортов description: Какие алиасы импортов есть в проекте и как ими пользоваться. -keywords: [алиасы, aliases, paths, tsconfig, импорты, baseUrl, app, layouts, screens, widgets, business, infrastructure, ui, shared] +keywords: [алиасы, aliases, paths, tsconfig, импорты, baseUrl, app, layouts, screens, widgets, business, infra, ui, shared] --- # Алиасы импортов @@ -21,7 +21,7 @@ keywords: [алиасы, aliases, paths, tsconfig, импорты, baseUrl, app, "screens/*": ["./src/screens/*"], "widgets/*": ["./src/widgets/*"], "business/*": ["./src/business/*"], - "infrastructure/*": ["./src/infrastructure/*"], + "infra/*": ["./src/infra/*"], "ui/*": ["./src/ui/*"], "shared/*": ["./src/shared/*"] } @@ -36,7 +36,7 @@ keywords: [алиасы, aliases, paths, tsconfig, импорты, baseUrl, app, - **Каждый импорт между модулями — через алиас слоя.** Относительные пути (`../../`) запрещены за пределами своего модуля. - **Внутри одного модуля** допустимы относительные импорты (`./model`, `./ui/button`) — это часть инкапсуляции модуля. - **Префикс `@/` не используется.** Имя слоя — само по себе адрес. -- **Направление импортов** определяется архитектурой, не алиасами. Алиас разрешает импорт технически, но не отменяет правила слоёв (→ [Слои](../basics/architecture/reference/layers.md)). +- **Направление импортов** определяется архитектурой, не алиасами. Алиас разрешает импорт технически, но не отменяет правила слоёв (→ [Слои](../basics/architecture/layers.md)). **Хорошо** diff --git a/ai/nextjs-style-guide/applied/component.md b/ai/nextjs-style-guide/applied/component.md index 4e825c3..4575d7b 100644 --- a/ai/nextjs-style-guide/applied/component.md +++ b/ai/nextjs-style-guide/applied/component.md @@ -1,201 +1,165 @@ --- title: Компонент -description: Как создавать React-компоненты внутри SLM-модулей. +description: Как должен выглядеть сгенерированный React-компонент внутри SLM-модуля. --- # Компонент -Как создавать React-компоненты внутри SLM-модулей. +Как должен выглядеть сгенерированный React-компонент внутри SLM-модуля. ## Назначение -Компонент — минимальная UI-единица проекта. Это один `.tsx` файл без собственной папки, сегментов и публичного API. +Архитектурное определение компонента описано в разделе [Модули → Компонент](../basics/architecture/modules.md#компонент), а структура сегмента `ui/` — в разделе [Сегменты → ui/](../basics/architecture/segments.md#сегмент-ui). -Компонент может использовать стили, типы, хуки и другие ресурсы родительского модуля. Наличие связанных файлов в `styles/` или `types/` не превращает компонент в модуль. +Эта страница не повторяет архитектурные ограничения. Она показывает, каким должен быть результат генерации компонента: структура папки, `.tsx`, типы, стили и локальный экспорт. -## Компонент или модуль +::: danger Компоненты не создаются вручную +Компоненты в проекте создаются только через кодогенератор: через [VS Code](./templates/templates-usage.md#через-vs-code) или [CLI](./templates/templates-usage.md#через-cli). -Классификация определяется границей владения: +Ручное создание компонента запрещено. Это грубое нарушение правил работы в проекте для разработчика и AI-ассистента. -- `component` — один `.tsx` файл внутри модуля; -- `module` — папка с `index.ts`, сегментами и собственной публичной границей. +Если в проекте нет шаблона `.templates/component`, сначала создайте шаблон по разделу [Создание шаблонов](./templates/templates-create.md), и только потом генерируйте компонент на его основе. +::: + +## Создание + +1. Проверьте, что в проекте есть шаблон `.templates/component`. +2. Если шаблона нет — создайте его по разделу [Создание шаблонов](./templates/templates-create.md). +3. Сгенерируйте компонент через [VS Code или CLI](./templates/templates-usage.md). + +Структура и код ниже показывают ожидаемый результат генерации. Их нельзя использовать как инструкцию для ручного создания файлов. + +## Структура + +Компонент размещается в `ui/{component-name}/` родительского модуля. + +Для каждого компонента обязательны `.tsx`, типы, стили и локальный `index.ts`. ```text -user/ -├── ui/ -│ └── user-avatar.tsx # компонент -├── styles/ -│ └── user-avatar.module.css # ресурс родительского модуля -├── types/ -│ └── user-avatar.type.ts # ресурс родительского модуля -└── user.tsx # корневой компонент модуля -``` - -`user-avatar.tsx` остаётся компонентом, потому что у него нет собственной папки, `index.ts` и сегментов. - -## Где размещать - -Компонент размещается внутри модуля: - -- В корне модуля, если это главная UI-сущность модуля. -- В `ui/`, если это вспомогательный компонент модуля. - -```text -user/ -├── ui/ -│ └── user-avatar.tsx -├── styles/ -│ ├── user.module.css -│ └── user-avatar.module.css -├── types/ -│ ├── user.type.ts -│ └── user-avatar.type.ts -├── user.tsx -└── index.ts -``` - -`user.tsx` — корневой компонент модуля. `ui/user-avatar.tsx` — вспомогательный компонент этого же модуля. - -## Что запрещено - -- Заворачивать компонент в папку: `ui/header/header.tsx`. -- Создавать для компонента отдельный `index.ts`. -- Создавать собственные сегменты внутри папки компонента: `ui/header/styles/`, `ui/header/types/`, `ui/header/hooks/` и т.п. -- Декларировать внутри `.tsx` сторы, сервисы, API-клиенты, мапперы или утилиты. Для этого есть сегменты родительского модуля. -- Размещать бизнес-правила прямо в компоненте. Компонент может использовать готовые зависимости модуля, но не определяет их. -- Размещать компонент в `parts/` напрямую. `parts/` содержит только модули. - -**Плохо** - -```text -user/ +user-card/ └── ui/ - └── user-avatar/ + └── user-status/ ├── styles/ - │ └── user-avatar.module.css - ├── user-avatar.tsx + │ └── user-status.module.css + ├── types/ + │ └── user-status-props.type.ts + ├── user-status.tsx └── index.ts ``` -**Хорошо** - -```text -user/ -├── ui/ -│ └── user-avatar.tsx -├── styles/ -│ └── user-avatar.module.css -└── types/ - └── user-avatar.type.ts -``` - -## Стили и типы - -Компонент использует ресурсы родительского модуля. - -`styles/` и `types/` рядом с корневым компонентом — это сегменты модуля, а не собственные папки `.tsx` файла. - -- CSS Module компонента лежит в `styles/` родительского модуля и называется по компоненту: `user-avatar.module.css`. -- Если у компонента есть CSS Module, его корневой класс всегда называется `.root`. -- Типы компонента лежат в `types/` родительского модуля и называются по компоненту: `user-avatar.type.ts`. -- Локальный `type Props` внутри `.tsx` не используется. Типы пропсов всегда выносятся в `types/` родительского модуля. -- Экспорт типа из `types/` не делает его публичным API. Публичным он становится только при реэкспорте из `index.ts` модуля. - -Причина `.root`: в DevTools проще находить корневой DOM-узел компонента и отличать его от внутренних элементов. - ## Реализация -- Компоненты объявляются через `const`. -- `React.FC` не используется. -- JSDoc-комментарий обязателен для компонента. -- Пропсы деструктурируются в теле компонента. -- `className` объединяется с `styles.root` через `cl()`. -- Побочные эффекты и состояние выносятся в хуки модуля, если перестают быть тривиальными. -- Компонент возвращает JSX и не содержит orchestration-код страницы или бизнес-домена. +Пример ниже показывает файлы базового компонента. -`user/types/user-avatar.type.ts` +### Типы + +Файл типов делится на три части: + +- `UserStatusParams` — собственные параметры компонента. Здесь лежат только данные, которые нужны именно этому компоненту. +- `RootAttrs` — параметры корневой обёртки: `div`, `span`, `a`, `button` или другого HTML-элемента. Если компонент сам управляет `children`, они исключаются через `Omit`. +- `UserStatusProps` — итоговые пропсы компонента. Тип объединяет собственные параметры и параметры корневой обёртки. + +Собственные параметры и их поля документируются по правилам раздела [Документирование → Типы, интерфейсы, enum](../basics/documentation.md#типы-интерфейсы-enum). + +`user-card/ui/user-status/types/user-status-props.type.ts` ```ts -import type { ImageProps } from 'next/image' +import type { ComponentPropsWithoutRef } from 'react' /** - * Параметры UserAvatar. + * Параметры UserStatus. */ -export type UserAvatarParams = {} +export type UserStatusParams = { + /** Текст статуса пользователя. */ + label: string + /** Доступен ли пользователь сейчас. */ + isOnline: boolean +} -/** Пропсы базового изображения. */ -type RootAttrs = ImageProps +/** Атрибуты корневого элемента без children. */ +type RootAttrs = Omit, 'children'> -export type UserAvatarProps = RootAttrs & UserAvatarParams +export type UserStatusProps = RootAttrs & UserStatusParams ``` -`user/ui/user-avatar.tsx` +### TSX + +В `.tsx` лежит только сам компонент: + +- Компонент объявляется через `const` и именованный экспорт. +- `React.FC` не используется. +- Параметры компонента типизируются через `Props`. +- Возвращаемый тип не указывается: TypeScript корректно выводит JSX-результат, а явный `ReactElement` сужает допустимые варианты возврата. +- JSDoc-комментарий обязателен и пишется по правилам раздела [Документирование → Компоненты](../basics/documentation.md#компоненты). +- Пропсы деструктурируются в теле компонента, а не в сигнатуре. +- Из пропсов обязательно выделяются `className` и `...rootAttrs`. +- Функция конкатенации CSS-классов импортируется и именуется `cl`. +- Корневой CSS-класс всегда называется `.root`. + +Комментарий описывает назначение и сценарии применения компонента, а не DOM-разметку или внутреннюю реализацию. + +`className` — внешний CSS-класс, который родитель может передать компоненту. `rootAttrs` — остальные атрибуты корневой обёртки: `id`, `aria-*`, `data-*`, обработчики событий и другие HTML-атрибуты. Они прокидываются на корневой DOM-элемент компонента. + +`.root` нужен, чтобы в DevTools быстро находить корневой DOM-узел компонента и одинаково подключать внешний `className` к реальному корню. + +`user-card/ui/user-status/user-status.tsx` ```tsx import cl from 'clsx' -import Image from 'next/image' -import type { UserAvatarProps } from '../types/user-avatar.type' -import styles from '../styles/user-avatar.module.css' +import type { UserStatusProps } from './types/user-status-props.type' +import styles from './styles/user-status.module.css' /** - * Аватар пользователя. + * Статус пользователя в карточке профиля. * * Используется для: - * - отображения пользователя в карточке - * - отображения пользователя в шапке профиля + * - отображения текущей доступности пользователя + * - визуального выделения онлайн- и офлайн-состояний */ -export const UserAvatar = (props: UserAvatarProps) => { - const { className, ...imageProps } = props +export const UserStatus = (props: UserStatusProps) => { + const { label, isOnline, className, ...rootAttrs } = props - return + return ( + + {label} + + ) } ``` -`user/styles/user-avatar.module.css` +### Стили + +`user-card/ui/user-status/styles/user-status.module.css` ```css .root { - display: block; + display: inline-flex; + align-items: center; + gap: 6px; + color: var(--color-text-muted); +} + +.root::before { + content: ''; + width: 6px; + height: 6px; border-radius: 50%; + background: currentColor; +} + +.online { + color: var(--color-success); } ``` -## Когда нужен модуль +### Локальный экспорт -Решение о выделении модуля остаётся за разработчиком. Поднимать компонент в модуль стоит, если он становится самостоятельной областью: +`user-card/ui/user-status/index.ts` -- получает свои вложенные компоненты; -- получает свои хуки, стор или сервисы; -- получает внутренние мапперы или утилиты; -- требует собственного публичного API; -- начинает переиспользоваться вне родительского модуля; -- становится отдельной зоной параллельной разработки. - -Пример: страница — это screen-модуль, а самостоятельные секции страницы — вложенные модули в `parts/`. - -```text -screens/home/ -├── parts/ -│ ├── hero-section/ -│ │ ├── styles/ -│ │ │ └── hero-section.module.css -│ │ ├── types/ -│ │ │ └── hero-section.type.ts -│ │ ├── hero-section.tsx -│ │ └── index.ts -│ └── features-section/ -│ ├── styles/ -│ │ └── features-section.module.css -│ ├── types/ -│ │ └── features-section.type.ts -│ ├── features-section.tsx -│ └── index.ts -├── styles/ -│ └── home.module.css -├── types/ -│ └── home.type.ts -├── home.screen.tsx -└── index.ts +```ts +export { UserStatus } from './user-status' +export type { UserStatusProps } from './types/user-status-props.type' ``` - -`hero-section` и `features-section` — модули, потому что это самостоятельные части страницы со своей структурой и публичной точкой входа. diff --git a/ai/nextjs-style-guide/applied/localization.md b/ai/nextjs-style-guide/applied/localization.md index c65b7f6..6b58703 100644 --- a/ai/nextjs-style-guide/applied/localization.md +++ b/ai/nextjs-style-guide/applied/localization.md @@ -1,22 +1,22 @@ --- title: Локализация -description: Как организовать локализацию как infrastructure-модуль. +description: Как организовать локализацию как infra-модуль. --- # Локализация -Как организовать локализацию как infrastructure-модуль. +Как организовать локализацию как infra-модуль. ## Назначение Локализация — инфраструктурная подсистема приложения. Она отвечает за текущую локаль, словари, форматирование переводов и API для компонентов. -Код локализации живёт в `src/infrastructure/i18n/`. Компоненты и модули не читают словари напрямую — они используют публичный API infrastructure-модуля. +Код локализации живёт в `src/infra/i18n/`. Компоненты и модули не читают словари напрямую — они используют публичный API infra-модуля. ## Структура ```text -src/infrastructure/i18n/ +src/infra/i18n/ ├── config/ │ └── i18n.config.ts ├── dictionaries/ @@ -31,16 +31,16 @@ src/infrastructure/i18n/ └── index.ts ``` -Набор сегментов может отличаться, но публичная точка входа остаётся одна — `infrastructure/i18n`. +Набор сегментов может отличаться, но публичная точка входа остаётся одна — `infra/i18n`. ## Подключение -`app/` только подключает готовый провайдер локализации. Реализация провайдера, словари и конфиг остаются в `infrastructure/i18n/`. +`app/` только подключает готовый провайдер локализации. Реализация провайдера, словари и конфиг остаются в `infra/i18n/`. ```tsx // src/app/layout.tsx import type { ReactNode } from 'react' -import { I18nProvider } from 'infrastructure/i18n' +import { I18nProvider } from 'infra/i18n' type RootLayoutProps = { children: ReactNode @@ -62,7 +62,7 @@ export default function RootLayout({ children }: RootLayoutProps) { Компоненты получают переводы через готовый API модуля локализации: ```tsx -import { useTranslation } from 'infrastructure/i18n' +import { useTranslation } from 'infra/i18n' export const ProfileTitle = () => { const { t } = useTranslation() @@ -73,9 +73,9 @@ export const ProfileTitle = () => { ## Правила -- Локализация живёт в `infrastructure/i18n/`. +- Локализация живёт в `infra/i18n/`. - `app/` только подключает готовый provider и передаёт locale. - Словари не импортируются напрямую в компоненты, screens или business-модули. - Ключи переводов не собираются динамически из строк, если это ломает типизацию и поиск. - Тексты интерфейса не хардкодятся в переиспользуемых компонентах, если они должны переводиться. -- Форматирование дат, чисел и валют должно проходить через API локализации или отдельные утилиты infrastructure-модуля. +- Форматирование дат, чисел и валют должно проходить через API локализации или отдельные утилиты infra-модуля. diff --git a/ai/nextjs-style-guide/applied/module.md b/ai/nextjs-style-guide/applied/module.md index 5e6ca5c..b76b121 100644 --- a/ai/nextjs-style-guide/applied/module.md +++ b/ai/nextjs-style-guide/applied/module.md @@ -1,162 +1,156 @@ --- title: Модуль -description: Как создавать и организовывать SLM-модули в проекте. +description: Как должен выглядеть сгенерированный SLM-модуль в проекте. --- # Модуль -Как создавать и организовывать SLM-модули в проекте. +Как должен выглядеть сгенерированный SLM-модуль в проекте. ## Назначение -Модуль — основной строительный блок SLM-архитектуры. Это папка с публичным API (`index.ts`) и опциональными сегментами: компонентами, стилями, типами, хуками, сторами, сервисами и вложенными модулями. +Архитектурное определение модуля описано в разделе [Архитектура → Модули](../basics/architecture/modules.md). Список сегментов описан в разделе [Архитектура → Сегменты](../basics/architecture/segments.md). -Если UI-сущность остаётся одним `.tsx` файлом и использует ресурсы родительского модуля — это [компонент](./component.md), а не модуль. Связанные файлы в `styles/` и `types/` родителя не создают новую модульную границу. +Эта страница показывает прикладное оформление трёх типов модулей: UI, бизнес и инфраструктурный. -Архитектурное определение: [Модули SLM](../basics/architecture/reference/modules.md). Список сегментов: [Сегменты SLM](../basics/architecture/reference/segments.md). +## Создание -## Когда нужен модуль +1. Проверьте, что в проекте есть нужный шаблон в `.templates/`. +2. Если шаблона нет — создайте его по разделу [Создание шаблонов](./templates/templates-create.md). +3. Сгенерируйте модуль через [VS Code или CLI](./templates/templates-usage.md). -Создавайте модуль, если сущности нужны: +## Типы модулей -- публичный API; -- хуки, сторы, сервисы, мапперы или утилиты; -- вложенные части; -- переиспользование вне родительского модуля; -- самостоятельная ответственность на слое. +Архитектура определяет три типа модулей ([Типы модулей](../basics/architecture/modules.md#типы-модулей)): -Если понадобилась папка вокруг компонента — это сигнал, что нужен модуль. +| Тип | Обязательный файл | Описание | +|---|---|---| +| UI-модуль | `{name}.tsx` | Модуль, выросший из компонента | +| Бизнес-модуль | `{name}.factory.ts` | Модуль вокруг публичного runtime API | +| Инфраструктурный модуль | нет | Модуль вокруг технического сервиса | -## Где размещать +## UI-модуль -Модуль размещается на самом низком уровне использования. +UI-модуль — это компонент, который перерос ограничения компонента: получил собственные хуки, вложенные модули в `parts/`, сценарную логику или публичный API. Внутренняя структура та же, что у компонента: корневой `.tsx`, типы, стили, `ui/`. Но без ограничений компонента. -- Нужен только одному модулю — размещается в `parts/` родителя. -- Нужен одной странице — размещается в `screens/{name}/parts/`. -- Нужен одному layout — размещается в `layouts/{name}/parts/`. -- Переиспользуется между страницами или layout — поднимается в `widgets/`. -- Представляет бизнес-домен — размещается в `business/`. -- Является UI-китом — размещается в `ui/`. +Подробное оформление компонентов внутри `ui/` описано в разделе [Компонент](./component.md). -`app/` не содержит модулей. Это слой файлового роутинга и инициализации. +## Бизнес-модуль -## Структура +Бизнес-модуль строится вокруг публичного runtime API. Ключевой файл — фабрика (`{name}.factory.ts`), которая возвращает всё, что нужно внешнему коду в runtime. -Минимальный UI-модуль: +Архитектурное описание фабрики: [Архитектура → Фабрика](../basics/architecture/modules.md#фабрика). + +### Структура ```text -user/ -├── user.tsx -└── index.ts +business/customer/ +├── customer.factory.ts +├── index.ts +└── types/ + ├── customer.type.ts + ├── customer-api.type.ts + ├── customer-deps.type.ts + └── customer-factory.type.ts ``` -Модуль расширяется сегментами только при реальной потребности: +### Типы -```text -user/ -├── ui/ -│ └── user-avatar.tsx -├── parts/ -│ └── user-posts/ -│ ├── user-posts.tsx -│ └── index.ts -├── styles/ -│ ├── user.module.css -│ └── user-avatar.module.css -├── types/ -│ ├── user.type.ts -│ └── user-avatar.type.ts -├── user.tsx -└── index.ts -``` - -Корневой компонент опционален. Business- и infrastructure-модули могут состоять только из хуков, сервисов, типов и публичного API. - -## `ui/` и `parts/` - -`ui/` содержит только компоненты: отдельные `.tsx` файлы без собственных сегментов. - -`parts/` содержит только модули: каждая запись внутри `parts/` — папка с собственным `index.ts`. Отдельные `.tsx`, стили, хуки или произвольные файлы в `parts/` не кладутся. - -```text -user/ -├── ui/ -│ └── user-avatar.tsx # компонент -└── parts/ - └── user-posts/ # модуль - ├── styles/ - │ └── user-posts.module.css - ├── user-posts.tsx - └── index.ts -``` - -Если компоненту в `ui/` понадобились стили или типы, они добавляются в `styles/` и `types/` родительского модуля. Если компоненту нужны собственные хуки, вложенные части или публичная граница — он переносится в `parts/` как модуль. - -## Публичный API - -`index.ts` — единственная точка входа в модуль. Внешние импорты внутренних файлов запрещены. +`business/customer/types/customer-api.type.ts` ```ts -// user/index.ts -export { User } from './user' -export type { UserProps } from './types/user.type' +export type CustomerApi = { + useCustomer: () => Customer + CustomerCard: (props: CustomerCardProps) => ReactNode +} ``` +`business/order/types/order-deps.type.ts` + ```ts -// Плохо: импорт в обход публичного API. -import { UserPosts } from 'screens/user/parts/user-posts/user-posts' - -// Хорошо: импорт через публичный API родительского модуля. -import { User } from 'screens/user' +export type OrderDeps = { + customer: Pick +} ``` -Вложенный модуль имеет свой `index.ts`, но наружу родителя экспортируется только при необходимости. +`business/order/types/order-factory.type.ts` -## Именование - -Базовые правила описаны в разделе [Именование](../basics/naming.md). - -- Папка модуля — `kebab-case`: `user-posts/`. -- Файл корневого компонента повторяет имя папки: `user-posts/user-posts.tsx`. -- Корневые модули слоёв наследуют роль слоя в имени файла: `screens/profile/profile.screen.tsx`, `layouts/main/main.layout.tsx`. -- Корневой компонент именуется в `PascalCase`: `UserPosts`. -- Если имя без контекста слишком общее, добавляется префикс родителя или роль слоя: `ProfileUserPosts`, `ProfileScreen`, `MainLayout`. - -## Примеры - -### Screen-модуль - -```text -screens/profile/ -├── ui/ -│ └── profile-heading.tsx -├── parts/ -│ └── activity-feed/ -│ ├── styles/ -│ │ └── activity-feed.module.css -│ ├── activity-feed.tsx -│ └── index.ts -├── styles/ -│ ├── profile.module.css -│ └── profile-heading.module.css -├── types/ -│ ├── profile.type.ts -│ └── profile-heading.type.ts -├── profile.screen.tsx -└── index.ts +```ts +export type OrderFactory = (deps: OrderDeps) => OrderApi ``` -### Business-модуль без корневого компонента +### Фабрика без зависимостей + +`business/customer/customer.factory.ts` + +```ts +import type { CustomerFactory } from './types/customer-factory.type' + +export const customerFactory: CustomerFactory = () => { + return { + useCustomer, + CustomerCard, + } +} +``` + +### Фабрика с зависимостями + +`business/order/order.factory.ts` + +```ts +import type { OrderFactory } from './types/order-factory.type' + +export const orderFactory: OrderFactory = (deps) => { + return { + useOrder, + OrderCard, + } +} +``` + +### Композиция на уровне screen + +```tsx +// screens/home/home.screen.tsx +import { customerFactory } from '@/business/customer' +import { orderFactory } from '@/business/order' + +const customer = customerFactory() +const order = orderFactory({ customer }) + +const { useOrder, OrderCard } = order + +export const HomeScreen = () => { + const currentOrder = useOrder() + + return +} +``` + +## Инфраструктурный модуль + +Инфраструктурный модуль строится вокруг технического сервиса или интеграции. Его структура определяется природой сервиса — фиксированного корневого файла нет. + +Архитектурное описание: [Архитектура → Типы модулей → Инфраструктурный модуль](../basics/architecture/modules.md#инфраструктурный-модуль). + +Пример модуля темы: ```text -business/auth/ +theme/ +├── index.ts +├── config/ ├── hooks/ -│ └── use-auth.hook.ts -├── services/ -│ └── auth.service.ts -├── stores/ -│ └── auth.store.ts +├── styles/ +└── ui/ +``` + +Пример модуля API-клиента: + +```text +backend-api/ +├── backend-api.client.ts +├── config/ ├── types/ -│ └── auth.type.ts └── index.ts ``` diff --git a/ai/nextjs-style-guide/applied/page-level.md b/ai/nextjs-style-guide/applied/page-level.md index 27ffea9..5a96eb6 100644 --- a/ai/nextjs-style-guide/applied/page-level.md +++ b/ai/nextjs-style-guide/applied/page-level.md @@ -13,7 +13,7 @@ description: Как работать со страницами и другими Файлы роутинга не реализуют интерфейс. Они описывают маршрут: читают параметры, получают данные первого рендера, подготавливают кеш или состояние и передают результат в screen. -Границы слоя описаны в [Архитектура → Слои → App](../basics/architecture/reference/layers.md#слой-app). +Границы слоя описаны в [Архитектура → Слои → App](../basics/architecture/layers.md#слой-app). ## Граница ответственности @@ -25,7 +25,7 @@ description: Как работать со страницами и другими | Прогрев SWR-кеша, начальное состояние, подключение провайдеров | `src/app/**`, только через готовые обёртки из нижних слоёв | | UI страницы | `screens/` | | Каркас страницы: header, footer, sidebar | `layouts/` | -| Провайдеры, сторы, хуки, API-клиенты, сервисы | нижние слои (`screens/`, `business/`, `infrastructure/`, `shared/`) | +| Провайдеры, сторы, хуки, API-клиенты, сервисы | нижние слои (`screens/`, `business/`, `infra/`, `shared/`) | | CSS Modules и стили компонентов | рядом с компонентами, не в `src/app/**` | ## Что можно делать в `page.tsx` @@ -79,7 +79,7 @@ export default async function ProfilePage({ params }: ProfilePageProps) { ```tsx import { notFound } from 'next/navigation' -import { userApi } from 'infrastructure/backend-api' +import { userApi } from 'infra/backend-api' import { UserScreen } from 'screens/user' type UserPageProps = { @@ -109,7 +109,7 @@ import { backendApi, getCurrentUserKey, getPostListKey, -} from 'infrastructure/backend-api' +} from 'infra/backend-api' type FeedLayoutProps = { children: ReactNode diff --git a/ai/nextjs-style-guide/applied/project-structure.md b/ai/nextjs-style-guide/applied/project-structure.md index 02d50f4..0bf210f 100644 --- a/ai/nextjs-style-guide/applied/project-structure.md +++ b/ai/nextjs-style-guide/applied/project-structure.md @@ -46,7 +46,7 @@ src/ ├── screens/ # Контент конкретной страницы ├── widgets/ # Составные блоки интерфейса, не привязанные к домену ├── business/ # Бизнес-домены (auth, catalog, orders) -├── infrastructure/ # Техсервисы (theme, i18n, API-адаптеры) +├── infra/ # Техсервисы (theme, i18n, API-адаптеры) ├── ui/ # UI-кит без бизнес-логики └── shared/ # Общие ресурсы (утилиты, типы, стили) ``` @@ -58,7 +58,7 @@ src/ Точка входа приложения и файловый роутинг Next.js (`layout.tsx`, `page.tsx`, route-сегменты). `app/` подключает готовую инициализацию из нижних слоёв, но не реализует провайдеры, стили, UI-компоненты, хуки, сторы или сервисы. -Подробнее о границах слоя: [Архитектура → Слои → App](../basics/architecture/reference/layers.md#слой-app). +Подробнее о границах слоя: [Архитектура → Слои → App](../basics/architecture/layers.md#слой-app). ```text src/app/ diff --git a/ai/nextjs-style-guide/applied/templates/templates-create.md b/ai/nextjs-style-guide/applied/templates/templates-create.md index 1715c5d..d8d0953 100644 --- a/ai/nextjs-style-guide/applied/templates/templates-create.md +++ b/ai/nextjs-style-guide/applied/templates/templates-create.md @@ -22,7 +22,7 @@ keywords: [шаблоны, templates, .templates, syntax, переменные, │ ├── styles/ │ │ └── {{name.kebabCase}}.module.css │ ├── types/ -│ │ └── {{name.kebabCase}}.type.ts +│ │ └── {{name.kebabCase}}-props.type.ts │ ├── {{name.kebabCase}}.tsx │ └── index.ts └── store/ # шаблон Zustand стора @@ -32,6 +32,12 @@ keywords: [шаблоны, templates, .templates, syntax, переменные, └── index.ts ``` +## Обязательный шаблон компонента + +Перед созданием компонентов в проекте должен существовать шаблон `.templates/component`. + +Если шаблона нет, компонент не создаётся вручную. Сначала создаётся шаблон компонента, затем компонент генерируется через [VS Code или CLI](./templates-usage.md). + ## Синтаксис шаблонов ### Переменные diff --git a/ai/nextjs-style-guide/applied/templates/templates-usage.md b/ai/nextjs-style-guide/applied/templates/templates-usage.md index 1d2e2d8..80ff431 100644 --- a/ai/nextjs-style-guide/applied/templates/templates-usage.md +++ b/ai/nextjs-style-guide/applied/templates/templates-usage.md @@ -8,6 +8,12 @@ keywords: [шаблоны, templates, generate, VS Code, CLI, gromlab/create, np Генерация файлов из шаблонов через VS Code плагин и CLI. +::: danger Ручное создание запрещено +Файлы, для которых есть шаблоны в `.templates/`, создаются только генератором. Ручное создание компонента, модуля, стора или другого шаблонного блока запрещено. + +Если нужного шаблона нет, сначала создайте шаблон в `.templates/`, затем сгенерируйте код на его основе. +::: + ## Через VS Code Template File Generator | gromlab ([Marketplace](https://marketplace.visualstudio.com/items?itemName=gromlab.vscode-templateFileGenerator), [Open VSX](https://open-vsx.org/extension/gromlab/vscode-templateFileGenerator)) — расширение для генерации файлов и папок из шаблонов через интерфейс редактора. diff --git a/ai/nextjs-style-guide/basics/architecture/index.md b/ai/nextjs-style-guide/basics/architecture/index.md index 4653ce2..09a7173 100644 --- a/ai/nextjs-style-guide/basics/architecture/index.md +++ b/ai/nextjs-style-guide/basics/architecture/index.md @@ -1,11 +1,20 @@ --- title: SLM Design -description: "Архитектурный подход проекта: что такое SLM и как он устроен." +description: Назначение архитектуры, ключевые принципы и карта разделов документации --- # SLM Design +Scoped Layered Module Design — модульная архитектура фронтенд-приложений. Код организован по слоям ответственности, а модуль содержит всё, что ему нужно: компоненты, хуки, сторы, типы, стили. -Архитектурный подход проекта: что такое SLM и как он устроен. +## Разделы спецификации + +Спецификация SLM Design состоит из нескольких связанных разделов. Этот обзор даёт общий контекст, а детальные правила описаны дальше: + +- [Слои](./layers.md) — уровни организации `src/`, направление зависимостей и зона ответственности каждого слоя. +- [Модули](./modules.md) — границы ответственности, публичный API, типы модулей и отличие модуля от компонента. +- [Сегменты](./segments.md) — внутренние папки модуля (`ui/`, `parts/`, `hooks/`, `types/` и другие) и правила размещения файлов. + +Рекомендуемый порядок чтения: обзор → слои → модули → сегменты. ## Преимущества @@ -19,7 +28,7 @@ Cross-domain зависимости в бизнес-слое реализуют ### Разделение ответственности без перегрузки слоёв -Сервисы приложения (`infrastructure/`), UI-кит (`ui/`) и общие ресурсы (`shared/`) — три разных слоя с разной природой. Ни один слой не превращается в свалку разнородного кода. +Сервисы приложения (`infra/`), UI-кит (`ui/`) и общие ресурсы (`shared/`) — три разных слоя с разной природой. Ни один слой не превращается в свалку разнородного кода. ### Горизонтальная инкапсуляция @@ -73,7 +82,7 @@ src/ │ ├── orders/ │ └── chat/ │ -├── infrastructure/ +├── infra/ │ ├── theme/ │ ├── i18n/ │ ├── backend-api/ diff --git a/ai/nextjs-style-guide/basics/architecture/reference/layers.md b/ai/nextjs-style-guide/basics/architecture/layers.md similarity index 82% rename from ai/nextjs-style-guide/basics/architecture/reference/layers.md rename to ai/nextjs-style-guide/basics/architecture/layers.md index c9f3407..92e7438 100644 --- a/ai/nextjs-style-guide/basics/architecture/reference/layers.md +++ b/ai/nextjs-style-guide/basics/architecture/layers.md @@ -1,11 +1,11 @@ --- -title: Слои SLM -description: Из каких слоёв состоит SLM-архитектура и как они связаны. +title: Слои +description: Иерархия слоёв от app до shared, правила зависимостей и зона ответственности каждого слоя --- -# Слои SLM +# Слои -Из каких слоёв состоит SLM-архитектура и как они связаны. +Раздел описывает слои SLM: что такое слой, какие бывают, как между ними направлены зависимости и какие правила действуют на каждом. ## Определение @@ -18,7 +18,7 @@ description: Из каких слоёв состоит SLM-архитектур | Группа | Слои | Описание | |--------|------|----------| | Композиция | `app`, `layouts`, `screens`, `widgets` | Собирают интерфейс из готовых блоков: маршруты, каркасы, страницы | -| Ядро | `business`, `infrastructure`, `ui` | Реализация продукта: бизнес-домены, техсервисы, UI-кит | +| Ядро | `business`, `infra`, `ui` | Реализация продукта: бизнес-домены, техсервисы, UI-кит | | Фундамент | `shared` | Общие ресурсы: утилиты, хелперы, стили, конфиги | ## Направление зависимостей @@ -26,12 +26,12 @@ description: Из каких слоёв состоит SLM-архитектур Любой импорт между модулями — только через публичный API. ``` -app → [ layouts | screens ] → widgets → business → infrastructure → ui → shared +app → [ layouts | screens ] → widgets → business → infra → ui → shared ``` - `layouts` и `screens` — параллельные слои, не импортируют друг друга - Модули одного слоя в группе «Композиция» изолированы друг от друга -- Модули одного слоя `infrastructure` и `ui` могут импортировать друг друга через публичный API +- Модули одного слоя `infra` и `ui` могут импортировать друг друга через публичный API - Модули `business` — cross-domain зависимости по коду через фабрику, `import type` напрямую - Импорт типов (`import type`) в «Ядре» разрешён в обоих направлениях @@ -129,7 +129,7 @@ src/widgets/ Бизнес-домены приложения: auth, catalog, orders, checkout, chat. Каждый домен — отдельный модуль со своими типами, логикой, UI и сервисами. -Слой входит в группу «Ядро». Импортирует `infrastructure/`, `ui/`, `shared/`. Cross-domain зависимости по коду реализуются через фабрику. `import type` между доменами разрешён напрямую. +Слой входит в группу «Ядро». Импортирует `infra/`, `ui/`, `shared/`. Каждый бизнес-модуль создаёт публичный runtime API через фабрику в корне. Cross-domain зависимости: runtime — через аргументы фабрики, типы — напрямую через `import type`. Business объединяет то, что в FSD разделено на `features` и `entities`: пользовательские сценарии и бизнес-сущности живут вместе, внутри одного домена. Внутри домена сегменты разделяют ответственность: `types/` — доменная модель, `hooks/` и `services/` — сценарии и логика, `mappers/` — трансформация данных, `parts/` — составные блоки. @@ -160,19 +160,20 @@ src/business/ - Один модуль = один бизнес-домен - Циклические зависимости между доменами запрещены -- Импорт кода между доменами — через фабрику. `import type` — напрямую +- Публичный runtime API — через фабрику в корне модуля (`{name}.factory.ts`). `index.ts` экспортирует только фабрику и type-only экспорты +- Импорт runtime-кода между доменами — через фабрику. `import type` — напрямую - Доменные типы (`User`, `Product`) живут здесь, не в `shared/` -## Слой Infrastructure +## Слой infra Техсервисы приложения: theme, i18n, API-адаптеры, logger, realtime. Каждый сервис — отдельный модуль. -Слой входит в группу «Ядро». Импортирует `infrastructure/`, `ui/`, `shared/`. +Слой входит в группу «Ядро». Импортирует `infra/`, `ui/`, `shared/`. -Отличие от `shared/`: infrastructure — инфраструктура приложения (сервисы, темы, адаптеры к API), `shared/` — общие ресурсы (утилиты, хелперы, стили, конфиги). +Отличие от `shared/`: infra — инфраструктура приложения (сервисы, темы, адаптеры к API), `shared/` — общие ресурсы (утилиты, хелперы, стили, конфиги). ```text -src/infrastructure/ +src/infra/ ├── theme/ ├── i18n/ ├── backend-api/ @@ -185,7 +186,7 @@ src/infrastructure/ ### Требования - Один модуль = один техсервис -- Импортирует `infrastructure/`, `ui/`, `shared/` +- Импортирует `infra/`, `ui/`, `shared/` ## Слой UI @@ -236,7 +237,7 @@ src/ui/ Слой входит в группу «Фундамент» — ни о ком не знает, никого не импортирует. -Отличие от `infrastructure/`: infrastructure — инфраструктура приложения (сервисы, темы, адаптеры к API), `shared/` — общие ресурсы (утилиты, хелперы, стили, конфиги). +Отличие от `infra/`: infra — инфраструктура приложения (сервисы, темы, адаптеры к API), `shared/` — общие ресурсы (утилиты, хелперы, стили, конфиги). Отличие от `ui/`: UI-компоненты (button, carousel, modal) живут в слое `ui/`, а не здесь. diff --git a/ai/nextjs-style-guide/basics/architecture/modules.md b/ai/nextjs-style-guide/basics/architecture/modules.md new file mode 100644 index 0000000..4950d7c --- /dev/null +++ b/ai/nextjs-style-guide/basics/architecture/modules.md @@ -0,0 +1,289 @@ +--- +title: Модули +description: Структура модуля, типы (UI, бизнес, инфра), публичный API, отличие модуля от компонента +--- + +# Модули + +Раздел описывает модуль как границу ответственности в SLM: что считается модулем, что такое компонент внутри модуля и как модуль взаимодействует с остальным кодом. + +## Определение + +**Модуль — минимальная архитектурная единица SLM. Он живёт на одном из слоёв, владеет конкретной областью ответственности и предоставляет наружу только публичный API.** + +Модуль может содержать всё, что нужно этой области: компоненты, вложенные модули, хуки, сторы, сервисы, типы, стили, конфиги и утилиты. Набор сегментов не фиксирован — модуль включает только то, что реально нужно. + +Модуль не обязан быть UI-блоком. Это может быть страница, виджет, бизнес-домен, инфраструктурный сервис или UI-kit сущность. + +Главная граница модуля — не папка, а ответственность. + +## Компонент + +**Компонент — презентационная единица модуля, которая находится только в `ui/` своего родительского модуля и отвечает за отображение части интерфейса.** + +Компонент не является архитектурной единицей: он не владеет сценарием, зависимостями, данными или внутренней структурой. Он работает только внутри границы родительского модуля. + +> Компонент отображает. Модуль организует. + +Компонент не может: + +- Импортировать код проекта за пределами родительского модуля. +- Владеть архитектурными зависимостями. +- Содержать любые компоненты. +- Содержать любые модули. +- Делать внешние запросы. +- Самостоятельно получать данные. +- Выбирать источник данных. +- Композировать данные. +- Вызывать сценарные хуки. +- Оркестрировать сценарий. +- Композировать модули. +- Решать, как устроен процесс. +- Содержать бизнес-логику. +- Содержать сценарную логику. + +Если компоненту требуется что-то из этого списка, он перестаёт быть компонентом и должен быть оформлен как модуль. + +```text +auth/ +├── ui/ +│ └── logout-button/ +│ ├── logout-button.tsx +│ ├── styles/ +│ │ └── logout-button.module.css +│ ├── types/ +│ │ └── logout-button-props.type.ts +│ └── index.ts +└── index.ts +``` + +## Что считается модулем + +Модулем считается папка, которая представляет самостоятельную область ответственности и имеет публичную границу. + +Примеры модулей: + +- `screens/home/` — модуль страницы. +- `widgets/page-heading/` — модуль виджета. +- `business/auth/` — модуль бизнес-домена. +- `infra/theme/` — модуль инфраструктурного сервиса. +- `ui/button/` — модуль UI-kit сущности. +- `screens/home/parts/hero-section/` — вложенный модуль страницы. + +Не считаются модулями: + +- `ui/`, `parts/`, `hooks/`, `types/`, `styles/`, `config/` — это сегменты. +- `screens/shop/`, `business/commerce/` — это группы, если в них нет `index.ts`. +- `screens/home/ui/user-card/` — это компонент, если он находится в `ui/` и соблюдает ограничения компонента. + +## Типы модулей + +Тип модуля определяет обязательный корневой файл и стартовую структуру. + +### UI-модуль + +Модуль строится вокруг основного UI-компонента и обязан иметь основной `.tsx` файл в корне: + +```text +header/ +├── header.tsx +└── index.ts +``` + +`ui/` внутри такого модуля используется только для компонентов, которые помогают корневому `.tsx` файлу. + +### Бизнес-модуль + +Бизнес-модуль — модуль, который строится вокруг публичного runtime API. + +Бизнес-модуль обязан иметь фабрику в корне: + +```text +auth/ +├── auth.factory.ts +├── index.ts +└── types/ +``` + +Фабрика возвращает публичный runtime API модуля. + +### Инфраструктурный модуль + +Инфраструктурный модуль — модуль, который строится вокруг технического сервиса или интеграции. + +Инфраструктурный модуль не обязан иметь фиксированный корневой файл. Его структура определяется природой сервиса. + +```text +theme/ +├── index.ts +├── config/ +├── hooks/ +├── styles/ +└── ui/ +``` + +```text +backend-api/ +├── backend-api.client.ts +├── config/ +├── types/ +└── index.ts +``` + +## Структура + +Модуль состоит из сегментов. Ни один сегмент не обязателен — модуль включает только те части, которые нужны его ответственности. + +```text +{module-name}/ +├── {module-name}.factory.ts # фабрика (для business-модулей) +├── {module-name}.tsx # корневой файл модуля (опционален) +├── ui/ # компоненты модуля +├── parts/ # вложенные модули +├── hooks/ # хуки +├── stores/ # сторы состояния +├── services/ # внешние источники данных +├── mappers/ # трансформация данных между форматами +├── types/ # типы +├── styles/ # стили +├── lib/ # утилиты модуля +├── config/ # константы и конфигурация +└── index.ts # публичный API +``` + +Подробное описание сегментов — в разделе [Сегменты](./segments.md). + +## Публичный API + +Внешний код импортирует модуль только через публичный API. + +```ts +// Хорошо +import { customerFactory } from '@/business/customer' +import type { Customer } from '@/business/customer' +``` + +```ts +// Плохо +import { validateToken } from '@/business/auth/lib/tokens' +``` + +`index.ts` модуля не обязан экспортировать всё содержимое. Он экспортирует только то, что действительно нужно снаружи. + +Внутренние сегменты модуля остаются деталями реализации. + +Business-модуль экспортирует из `index.ts` только фабрику и type-only экспорты. Хуки, компоненты, сервисы, мапперы и утилиты напрямую из `index.ts` не экспортируются — они доступны через API, который возвращает фабрика. + +```ts +// business/customer/index.ts +export { customerFactory } from './customer.factory' + +export type { Customer } from './types/customer.type' +export type { CustomerApi } from './types/customer-api.type' +export type { CustomerDeps } from './types/customer-deps.type' +export type { CustomerFactory } from './types/customer-factory.type' +``` + +## Фабрика + +Business-модуль всегда экспортирует фабрику. Фабрика лежит в корне модуля (`{name}.factory.ts`), типизируется через `{Name}Factory` и возвращает публичный runtime API модуля. + +Всё, что нужно внешнему коду в runtime, должно быть частью API, который возвращает фабрика. + +Модуль без cross-domain зависимостей экспортирует фабрику без аргументов. Модуль с зависимостями — фабрику, принимающую зависимости доменными именами. Типы всегда экспортируются напрямую через `export type` — `import type` не является runtime-зависимостью. + +Компоновка фабрик происходит на уровне модуля-потребителя: screen, layout, widget или любой другой модуль группы «Композиция». + +### Структура business-модуля + +```text +business/customer/ +├── customer.factory.ts +├── index.ts +└── types/ + ├── customer.type.ts + ├── customer-api.type.ts + ├── customer-deps.type.ts + └── customer-factory.type.ts +``` + +### Типы + +```ts +// business/customer/types/customer-api.type.ts +export type CustomerApi = { + useCustomer: () => Customer + CustomerCard: (props: CustomerCardProps) => ReactNode +} +``` + +```ts +// business/order/types/order-deps.type.ts +export type OrderDeps = { + customer: Pick +} +``` + +```ts +// business/order/types/order-factory.type.ts +export type OrderFactory = (deps: OrderDeps) => OrderApi +``` + +### Фабрика без зависимостей + +```ts +// business/customer/customer.factory.ts +import type { CustomerFactory } from './types/customer-factory.type' + +export const customerFactory: CustomerFactory = () => { + return { + useCustomer, + CustomerCard, + } +} +``` + +### Фабрика с зависимостями + +```ts +// business/order/order.factory.ts +import type { OrderFactory } from './types/order-factory.type' + +export const orderFactory: OrderFactory = (deps) => { + return { + useOrder, + OrderCard, + } +} +``` + +### Композиция на уровне screen + +```tsx +// screens/home/home.screen.tsx +import { customerFactory } from '@/business/customer' +import { orderFactory } from '@/business/order' + +const customer = customerFactory() +const order = orderFactory({ customer }) + +const { useOrder, OrderCard } = order + +export const HomeScreen = () => { + const currentOrder = useOrder() + + return +} +``` + +## Жизненный цикл + +Модуль рождается на самом низком уровне использования и поднимается выше только при реальной потребности. + +- Нужен на одной странице → `screens/{name}/parts/` +- Появился в 2+ местах → поднимается по природе: + - абстрактный UI → `ui/` + - блок с данными/логикой → `widgets/` + - представление бизнес-домена → `business/{area}/parts/` + +Подъём — обычный рефакторинг в рамках задачи, а не отдельная активность. diff --git a/ai/nextjs-style-guide/basics/architecture/reference/modules.md b/ai/nextjs-style-guide/basics/architecture/reference/modules.md deleted file mode 100644 index c5a424a..0000000 --- a/ai/nextjs-style-guide/basics/architecture/reference/modules.md +++ /dev/null @@ -1,165 +0,0 @@ ---- -title: Модули SLM -description: Что такое модуль в SLM-архитектуре и как он устроен. ---- - -# Модули SLM - -Что такое модуль в SLM-архитектуре и как он устроен. - -## Определение - -**Модуль — универсальный строительный блок архитектуры. Живёт на слое и содержит всё необходимое для своей работы: компоненты, хуки, сторы, сервисы, типы, стили. Набор содержимого не фиксирован — включаются только нужные части.** - -## Модуль vs компонент - -**Компонент** — один `.tsx` файл. Не имеет своих сегментов, использует сегменты родительского модуля. Живёт в корне или `ui/` сегменте модуля. - -**Модуль** — папка, которая может содержать корневой компонент, сегменты (`hooks/`, `types/`, `styles/`, `ui/`, `parts/` и т.д.) и публичный API (`index.ts`). - -```text -auth/ -├── ui/ -│ ├── auth-guard.tsx -│ └── logout-button.tsx -├── parts/ -│ ├── login-form/ -│ ├── registration-form/ -│ └── restore-form/ -├── hooks/ -├── stores/ -├── types/ -├── auth.tsx # корневой компонент (опционален) -└── index.ts -``` - -## Структура - -Модуль состоит из сегментов. Ни один сегмент не обязателен — модуль может состоять даже из одного `index.ts` с реэкспортом типов. - -```text -{module-name}/ -├── {module-name}.tsx # корневой компонент (опционален) -├── ui/ # компоненты модуля (только .tsx) -├── parts/ # вложенные модули (со своими сегментами) -├── hooks/ # хуки -├── stores/ # сторы состояния -├── services/ # внешние источники данных -├── mappers/ # трансформация данных между форматами -├── types/ # типы -├── styles/ # стили -├── lib/ # утилиты модуля -├── config/ # константы -└── index.ts # публичный API -``` - -Подробное описание каждого сегмента — в разделе [Сегменты](./segments.md). - -## Публичный API - -Модуль экспортирует наружу только то, что нужно другим. Всё остальное — внутреннее. - -```ts -// business/auth/index.ts -export type { User, Session } from './types/user.types' -export { useAuth } from './hooks/use-auth.hook' -export { AuthGuard } from './ui/auth-guard' -``` - -Импорт в обход `index.ts` запрещён: - -```ts -// Плохо -import { validateToken } from '@/business/auth/lib/tokens' - -// Хорошо -import { useAuth } from '@/business/auth' -``` - -## Фабрика - -Если модуль зависит от кода другого бизнес-домена — он экспортирует фабрику. Фабрика декларирует необходимые зависимости и возвращает API модуля. Точка использования (screen, widget, layout) предоставляет зависимости при вызове. - -Модуль без cross-domain зависимостей экспортирует API напрямую. Типы всегда экспортируются напрямую — `import type` не является runtime-зависимостью. - -### Модуль без зависимостей — прямой экспорт: - -```ts -// business/auth/index.ts -export { useAuth } from './hooks/use-auth' -export { useCurrentUser } from './hooks/use-current-user' -export type { User, Session } from './types' -``` - -### Модуль с зависимостями — фабрика: - -```ts -// business/chat/types/deps.ts -import type { User } from '@/business/auth' - -export interface ChatDeps { - useCurrentUser: () => User | null -} -``` - -```ts -// business/chat/index.ts -import type { ChatDeps } from './types/deps' - -export function chatFactory(deps: ChatDeps) { - return { - useMessages: (roomId: string) => { - const user = deps.useCurrentUser() - // ... - }, - useSendMessage: (roomId: string) => { - const user = deps.useCurrentUser() - return (text: string) => { /* ... */ } - }, - useChatRooms: () => { - const user = deps.useCurrentUser() - // ... - }, - ChatBadge: ({ count }: { count: number }) => { /* ... */ }, - } -} - -export type { Message, ChatRoom } from './types' -export type { ChatDeps } from './types/deps' -``` - -### Использование на странице: - -```tsx -// screens/support/support.tsx -import { useCurrentUser } from '@/business/auth' -import { chatFactory } from '@/business/chat' - -const chat = chatFactory({ useCurrentUser }) - -export function SupportScreen() { - const { useMessages, useSendMessage, ChatBadge } = chat - const messages = useMessages('support') - const sendMessage = useSendMessage('support') - - return ( -
- - {messages.map(m => )} - -
- ) -} -``` - -## Жизненный цикл - -Модуль рождается на самом низком уровне использования и поднимается выше только при реальной потребности. - -- Нужен на одной странице → `screens/{name}/parts/` -- Появился в 2+ местах → поднимается по природе: - - абстрактный UI → `ui/` - - блок с данными/логикой → `widgets/` - - представление бизнес-домена → `business/{area}/parts/` - -Подъём — обычный рефакторинг в рамках задачи, а не отдельная активность. diff --git a/ai/nextjs-style-guide/basics/architecture/reference/segments.md b/ai/nextjs-style-guide/basics/architecture/segments.md similarity index 64% rename from ai/nextjs-style-guide/basics/architecture/reference/segments.md rename to ai/nextjs-style-guide/basics/architecture/segments.md index 45af86b..eeb1b52 100644 --- a/ai/nextjs-style-guide/basics/architecture/reference/segments.md +++ b/ai/nextjs-style-guide/basics/architecture/segments.md @@ -1,11 +1,11 @@ --- -title: Сегменты SLM -description: Что такое сегмент модуля в SLM-архитектуре и какие они бывают. +title: Сегменты +description: Сегменты внутри модуля (ui/, model/, lib/ и др.), назначение и правила размещения файлов --- -# Сегменты SLM +# Сегменты -Что такое сегмент модуля в SLM-архитектуре и какие они бывают. +Раздел описывает сегменты SLM: что такое сегмент, какие бывают и что в каждом из них лежит. ## Определение @@ -15,7 +15,7 @@ description: Что такое сегмент модуля в SLM-архитек | Сегмент | Содержимое | |---------|------------| -| `ui/` | Вспомогательные компоненты модуля — только `.tsx` файлы | +| `ui/` | Презентационные компоненты родительского модуля | | `parts/` | Вложенные модули со своими сегментами | | `hooks/` | React-хуки | | `stores/` | Сторы состояния | @@ -28,22 +28,44 @@ description: Что такое сегмент модуля в SLM-архитек ## Сегмент ui/ -Вспомогательные компоненты модуля. Содержит только `.tsx` файлы — без своих сегментов, стилей, типов, хуков и публичного API. Использует сегменты родительского модуля. +Презентационные компоненты родительского модуля. `ui/` содержит только компоненты, которые отвечают за отображение части интерфейса и не выходят за границы своего модуля. -Корневой компонент модуля в `ui/` не размещается. Он лежит в корне модуля: `{module-name}.tsx`. +Компонент в `ui/`: + +- Находится в собственной папке. +- Может содержать только `{name}.tsx`, `index.ts`, `styles/`, `types/`. +- Не содержит любые компоненты. +- Не содержит любые модули. +- Не импортирует код проекта за пределами родительского модуля. +- Не делает внешние запросы. +- Не вызывает сценарные хуки. +- Не получает данные самостоятельно, не выбирает источник данных и не композирует данные. +- Не содержит бизнес-логику или сценарную логику. + +Если UI-сущности нужно что-то за пределами этих ограничений, она должна быть оформлена как модуль. Полная граница описана в разделе [Компонент](./modules.md#компонент). + +Корневой файл модуля в `ui/` не размещается. Он лежит в корне модуля: `{module-name}.tsx`. ```text user/ ├── ui/ -│ ├── user-avatar.tsx -│ └── user-status.tsx +│ ├── user-avatar/ +│ │ ├── user-avatar.tsx +│ │ ├── styles/ +│ │ │ └── user-avatar.module.css +│ │ ├── types/ +│ │ │ └── user-avatar-props.type.ts +│ │ └── index.ts +│ └── user-status/ +│ ├── user-status.tsx +│ └── index.ts ├── types/ ├── hooks/ ├── user.tsx └── index.ts ``` -Если компоненту нужны собственные сегменты — это уже не `ui/`, а `parts/`. +Если UI-сущности нужна внутренняя декомпозиция, сценарная логика, получение данных или собственные архитектурные зависимости — это уже не компонент в `ui/`, а модуль в `parts/`. ## Сегмент parts/ @@ -68,7 +90,7 @@ home/ └── index.ts ``` -Отличие от `ui/`: элемент `parts/` — модульная папка со своими сегментами. Элемент `ui/` — вспомогательный компонент, один `.tsx` файл. +Отличие от `ui/`: элемент `parts/` — модульная папка со своими сегментами. Элемент `ui/` — компонент родительского модуля без собственной архитектурной ответственности. Вложенность `parts/` инкапсулирует область разработки горизонтально: каждый разработчик работает в своём `parts/`-модуле, не затрагивая чужие. Это снижает конфликты при параллельной разработке. diff --git a/ai/nextjs-style-guide/basics/typing.md b/ai/nextjs-style-guide/basics/typing.md index c14743d..840765f 100644 --- a/ai/nextjs-style-guide/basics/typing.md +++ b/ai/nextjs-style-guide/basics/typing.md @@ -9,11 +9,16 @@ description: Как типизируется код в проекте. ## Общие правила -- Указывать типы для параметров компонентов, возвращаемых значений и параметров функций. +- Указывать типы для параметров компонентов и параметров функций. - Предпочитать `type` для описания сущностей и `interface` для расширяемых контрактов. - Избегать `any` и `unknown` без необходимости. - Не использовать `ts-ignore`, кроме крайних случаев с явным комментарием причины. +## React-компоненты + +- Пропсы компонента типизировать через отдельный `Props`. +- Возвращаемый тип компонента не указывать: TypeScript корректно выводит JSX-результат, а явный `ReactElement` сужает допустимые варианты возврата. + ## Функции - Для публичных функций указывать возвращаемый тип. diff --git a/ai/nextjs-style-guide/creating-project/manual.md b/ai/nextjs-style-guide/creating-project/manual.md index fb89536..d345a50 100644 --- a/ai/nextjs-style-guide/creating-project/manual.md +++ b/ai/nextjs-style-guide/creating-project/manual.md @@ -25,7 +25,7 @@ keywords: [создать проект, новый проект, с нуля, in ## Канон раскладки -В `src/` допустимы только слои SLM: `app/`, `layouts/`, `screens/`, `widgets/`, `business/`, `infrastructure/`, `ui/`, `shared/`. Любая другая папка в `src/` — нарушение канона ([Структура проекта](../applied/project-structure.md), [Архитектура](../basics/architecture/index.md)). +В `src/` допустимы только слои SLM: `app/`, `layouts/`, `screens/`, `widgets/`, `business/`, `infra/`, `ui/`, `shared/`. Любая другая папка в `src/` — нарушение канона ([Структура проекта](../applied/project-structure.md), [Архитектура](../basics/architecture/index.md)). В частности: `src/app/` содержит только файлы роутинга Next.js и инициализации, без каталогов `styles/`, `assets/`, `components/`. @@ -85,6 +85,6 @@ CSS-процессор поверх базовых стилей: `@custom-media` - **Порядок шагов фиксирован.** Перестановка ломает зависимости (PostCSS требует базовых стилей, VS Code — установленного Biome). - **Между шагами обязательна проверка** из соответствующего раздела. Не переходить дальше, пока чеклист текущего шага не пройден. -- **Слои `src/`** (`layouts/`, `screens/`, `widgets/`, `business/`, `infrastructure/`, `ui/`) не создавать авансом. Появляются по мере первого модуля. Исключения — `src/app/` (создаётся `create-next-app`), `src/shared/styles/` (шаг 1) и `src/shared/sprites/icons/` (шаг 6). +- **Слои `src/`** (`layouts/`, `screens/`, `widgets/`, `business/`, `infra/`, `ui/`) не создавать авансом. Появляются по мере первого модуля. Исключения — `src/app/` (создаётся `create-next-app`), `src/shared/styles/` (шаг 1) и `src/shared/sprites/icons/` (шаг 6). - **Посторонние каталоги в `src/`** (`assets/`, `utils/`, `lib/`, `components/` и т.п.) — запрещены. - **Подмножество шагов допустимо.** Можно ставить только Next.js и часть инструментов; полный набор — это эталон, а не обязательство. diff --git a/ai/nextjs-style-guide/creating-project/nextjs.md b/ai/nextjs-style-guide/creating-project/nextjs.md index dc3b9d7..546f9f1 100644 --- a/ai/nextjs-style-guide/creating-project/nextjs.md +++ b/ai/nextjs-style-guide/creating-project/nextjs.md @@ -89,7 +89,7 @@ export default function RootLayout({ children }: { children: React.ReactNode }) mkdir -p src/shared/styles ``` -Остальные слои (`layouts/`, `screens/`, `widgets/`, `business/`, `infrastructure/`, `ui/`) заводятся при появлении первого модуля в них. `src/shared/styles/` — единственный подкаталог `shared/`, который заводится сразу: без него не настроить стили на следующих шагах. +Остальные слои (`layouts/`, `screens/`, `widgets/`, `business/`, `infra/`, `ui/`) заводятся при появлении первого модуля в них. `src/shared/styles/` — единственный подкаталог `shared/`, который заводится сразу: без него не настроить стили на следующих шагах. ## Правила diff --git a/ai/nextjs-style-guide/data/index.md b/ai/nextjs-style-guide/data/index.md index 906a5be..75b1a09 100644 --- a/ai/nextjs-style-guide/data/index.md +++ b/ai/nextjs-style-guide/data/index.md @@ -1,7 +1,7 @@ --- title: Источники данных description: Какие источники данных используются в проекте и как с ними работать. -keywords: [данные, api, rest, realtime, клиент, swr, infrastructure, введение, карта раздела] +keywords: [данные, api, rest, realtime, клиент, swr, infra, введение, карта раздела] --- # Источники данных @@ -10,7 +10,7 @@ keywords: [данные, api, rest, realtime, клиент, swr, infrastructure, ## Принципы раздела -- **Клиент — в `infrastructure/`.** Каждый внешний сервис — отдельный модуль слоя `infrastructure/{service-name}/`. +- **Клиент — в `infra/`.** Каждый внешний сервис — отдельный модуль слоя `infra/{service-name}/`. - **Прямой `fetch` запрещён.** Запросы идут только через клиент модуля. Исключения — точечные и обоснованные. - **Источник данных диктует канал.** REST, realtime и т.п. — независимые подразделы, у каждого своя модель клиента и своё потребление. - **Серверные и клиентские компоненты потребляют по-разному.** Server Components — прямой `await` метода клиента, клиентские — через готовые GET-хуки REST-клиента (`useGetUserList`, `useGetPostDetail` и т.п.). SWR инкапсулирован в хуке, компонент про него не знает. @@ -40,7 +40,7 @@ keywords: [данные, api, rest, realtime, клиент, swr, infrastructure, Канал push-данных: WebSocket, SSE, событийные шины. Транспорт не зашит в правила — важна абстракция «подписка». -- [Realtime](./realtime.md) — клиент realtime в `infrastructure/`, потребление через `useSWRSubscription` или прямые подписки. +- [Realtime](./realtime.md) — клиент realtime в `infra/`, потребление через `useSWRSubscription` или прямые подписки. ## Что даёт раздел @@ -48,7 +48,7 @@ keywords: [данные, api, rest, realtime, клиент, swr, infrastructure, - Где живёт код работы с API и почему именно там. - Когда генерировать клиент автоматически, а когда писать вручную, и как структурирован каждый из вариантов. -- Какие GET-хуки относятся к REST-клиенту и почему они живут в `infrastructure/{service-name}/hooks/`. +- Какие GET-хуки относятся к REST-клиенту и почему они живут в `infra/{service-name}/hooks/`. - Как выбрать стратегию получения REST-данных под конкретную ситуацию. - Как подключать realtime-источники в общую модель работы с данными. - Какие правила обязательны и какие отклонения допустимы. diff --git a/ai/nextjs-style-guide/data/realtime.md b/ai/nextjs-style-guide/data/realtime.md index 7463cfe..f284c2b 100644 --- a/ai/nextjs-style-guide/data/realtime.md +++ b/ai/nextjs-style-guide/data/realtime.md @@ -10,7 +10,7 @@ keywords: [realtime, websocket, sse, подписка, swr subscription, useSWRS ## Принципы -- **Клиент realtime — в `infrastructure/`** отдельным модулем по имени канала. То же правило, что и для REST: никаких прямых соединений в коде приложения. +- **Клиент realtime — в `infra/`** отдельным модулем по имени канала. То же правило, что и для REST: никаких прямых соединений в коде приложения. - **Подписка — единица потребления.** Клиент даёт функцию `subscribe(topic, handler) → unsubscribe`. Внутри — конкретный транспорт. - **Использование на клиенте — два сценария:** - **`useSWRSubscription`** — для данных, которые показываются в UI и должны кешироваться/синхронизироваться с REST. @@ -19,7 +19,7 @@ keywords: [realtime, websocket, sse, подписка, swr subscription, useSWRS ## Размещение клиента ```text -src/infrastructure/ +src/infra/ └── {channel-name}/ ├── connection.ts # установление соединения, реконнект ├── subscribe.ts # subscribe(topic, handler) → unsubscribe @@ -33,7 +33,7 @@ src/infrastructure/ 'use client' import useSWRSubscription from 'swr/subscription' -import { subscribe } from 'infrastructure/notifications' +import { subscribe } from 'infra/notifications' export function NotificationCounter() { const { data: count } = useSWRSubscription( @@ -56,7 +56,7 @@ export function NotificationCounter() { 'use client' import { useEffect } from 'react' -import { subscribe } from 'infrastructure/notifications' +import { subscribe } from 'infra/notifications' import { showToast } from 'ui/toast' export function NotificationsToaster() { @@ -74,6 +74,6 @@ export function NotificationsToaster() { ## Запрет прямых соединений -Создавать `new WebSocket(...)`, `new EventSource(...)` или подписываться на событийные шины напрямую в коде приложения — запрещено. Все соединения проходят через клиент в `infrastructure/`. +Создавать `new WebSocket(...)`, `new EventSource(...)` или подписываться на событийные шины напрямую в коде приложения — запрещено. Все соединения проходят через клиент в `infra/`. Исключения — точечные и обоснованные (например, диагностический скрипт), помечаются комментарием. diff --git a/ai/nextjs-style-guide/data/rest/clients/auto.md b/ai/nextjs-style-guide/data/rest/clients/auto.md index 2d4b06a..5098f16 100644 --- a/ai/nextjs-style-guide/data/rest/clients/auto.md +++ b/ai/nextjs-style-guide/data/rest/clients/auto.md @@ -19,7 +19,7 @@ https://petstore3.swagger.io/api/v3/openapi.json Имена модуля: ```text -src/infrastructure/pet-store-api/ +src/infra/pet-store-api/ petStoreApi pet-store-api.generated.ts ``` @@ -31,7 +31,7 @@ pet-store-api.generated.ts ```json { "scripts": { - "codegen:pet-store-api": "npx @gromlab/api-codegen@latest -i https://petstore3.swagger.io/api/v3/openapi.json -o src/infrastructure/pet-store-api/generated -n pet-store-api.generated" + "codegen:pet-store-api": "npx @gromlab/api-codegen@latest -i https://petstore3.swagger.io/api/v3/openapi.json -o src/infra/pet-store-api/generated -n pet-store-api.generated" } } ``` @@ -53,7 +53,7 @@ npm run codegen:pet-store-api Ожидаемый результат: ```text -src/infrastructure/pet-store-api/generated/ +src/infra/pet-store-api/generated/ └── pet-store-api.generated.ts ``` @@ -77,7 +77,7 @@ petStoreApi.pet.getPetById(...) Сгенерированный код не должен напрямую использоваться из приложения. Сначала создаётся настроенный инстанс клиента. ```ts -// src/infrastructure/pet-store-api/client.ts +// src/infra/pet-store-api/client.ts import { Api, HttpClient } from './generated/pet-store-api.generated' const httpClient = new HttpClient({ @@ -102,7 +102,7 @@ export const petStoreApi = new Api(httpClient) Сгенерированный файл не правится руками. Если OpenAPI-спецификация неполная или генератор дал слишком общий тип (`object`, `unknown`, отсутствующее поле), расширения живут в `types/`. ```text -src/infrastructure/biocad-less-api/ +src/infra/biocad-less-api/ ├── generated/ │ └── biocad-less-api.generated.ts ├── types/ @@ -115,7 +115,7 @@ src/infrastructure/biocad-less-api/ Пример расширения generated-типа: ```ts -// src/infrastructure/biocad-less-api/types/term.ts +// src/infra/biocad-less-api/types/term.ts import type { TermRecordItem } from '../generated/biocad-less-api.generated' declare module '../generated/biocad-less-api.generated' { @@ -149,7 +149,7 @@ export type TermRecordItemExtended = Omit< ``` ```ts -// src/infrastructure/biocad-less-api/types/index.ts +// src/infra/biocad-less-api/types/index.ts export type { TermRecordItemExtended } from './term' ``` @@ -158,18 +158,18 @@ export type { TermRecordItemExtended } from './term' ## Публичный API ```ts -// src/infrastructure/pet-store-api/index.ts +// src/infra/pet-store-api/index.ts export { petStoreApi } from './client' export type { Pet } from './generated/pet-store-api.generated' export * from './hooks' ``` -Наружу импортируют только из `infrastructure/pet-store-api`, не из `generated/`. +Наружу импортируют только из `infra/pet-store-api`, не из `generated/`. Если у модуля есть расширенные типы, они тоже реэкспортируются через `index.ts`: ```ts -// src/infrastructure/biocad-less-api/index.ts +// src/infra/biocad-less-api/index.ts export type { TermRecordItemExtended } from './types' ``` diff --git a/ai/nextjs-style-guide/data/rest/clients/hooks.md b/ai/nextjs-style-guide/data/rest/clients/hooks.md index 83dbaae..7b4f271 100644 --- a/ai/nextjs-style-guide/data/rest/clients/hooks.md +++ b/ai/nextjs-style-guide/data/rest/clients/hooks.md @@ -1,7 +1,7 @@ --- title: GET-хуки REST-клиента description: Прозрачные SWR-обёртки над GET-методами REST-клиента. -keywords: [rest, swr, get-хуки, client components, infrastructure] +keywords: [rest, swr, get-хуки, client components, infra] --- # GET-хуки REST-клиента @@ -13,7 +13,7 @@ GET-хуки REST-клиента — прозрачные SWR-обёртки н GET-хуки принадлежат REST-клиенту конкретного сервиса и живут рядом с ним: ```text -src/infrastructure/ +src/infra/ └── pet-store-api/ ├── client.ts ├── generated/ @@ -41,7 +41,7 @@ src/infrastructure/ ## Пример списка ```ts -// src/infrastructure/pet-store-api/hooks/use-get-pet-list.hook.ts +// src/infra/pet-store-api/hooks/use-get-pet-list.hook.ts import useSWR from 'swr' import type { SWRConfiguration } from 'swr' import { petStoreApi } from '../client' @@ -74,7 +74,7 @@ import { SWRConfig, unstable_serialize } from 'swr' import { getPetListKey, petStoreApi, -} from 'infrastructure/pet-store-api' +} from 'infra/pet-store-api' export default function PetsLayout({ children }: { children: ReactNode }) { const petsPromise = petStoreApi.pet.findPetsByStatus({ status: 'available' }) @@ -102,7 +102,7 @@ const { data: pets } = useGetPetList('available') ## Пример detail-запроса ```ts -// src/infrastructure/pet-store-api/hooks/use-get-pet-detail.hook.ts +// src/infra/pet-store-api/hooks/use-get-pet-detail.hook.ts import useSWR from 'swr' import type { SWRConfiguration } from 'swr' import { petStoreApi } from '../client' @@ -141,23 +141,23 @@ const key = isReady ? getPetDetailKey(id) : null ## Экспорт ```ts -// src/infrastructure/pet-store-api/hooks/index.ts +// src/infra/pet-store-api/hooks/index.ts export { getPetListKey, useGetPetList } from './use-get-pet-list.hook' export type { PetStatus } from './use-get-pet-list.hook' export { getPetDetailKey, useGetPetDetail } from './use-get-pet-detail.hook' ``` ```ts -// src/infrastructure/pet-store-api/index.ts +// src/infra/pet-store-api/index.ts export { petStoreApi } from './client' export type { Pet } from './generated/pet-store-api.generated' export * from './hooks' ``` -## Где заканчивается infrastructure +## Где заканчивается infra ```ts -// Хорошо: infrastructure, прозрачный GET-хук +// Хорошо: infra, прозрачный GET-хук const { data: pets } = useGetPetList('available') ``` @@ -184,7 +184,7 @@ const { data } = useSWR( () => petStoreApi.pet.findPetsByStatus({ status }), ) -// Плохо — несколько GET внутри infrastructure-хука +// Плохо — несколько GET внутри infra-хука export const usePetDashboard = () => { const available = useGetPetList('available') const sold = useGetPetList('sold') diff --git a/ai/nextjs-style-guide/data/rest/clients/index.md b/ai/nextjs-style-guide/data/rest/clients/index.md index 9e2a601..b0e8ff8 100644 --- a/ai/nextjs-style-guide/data/rest/clients/index.md +++ b/ai/nextjs-style-guide/data/rest/clients/index.md @@ -1,12 +1,12 @@ --- title: Создание клиента description: Из чего состоит REST-клиент и какие части нужно подготовить перед использованием API. -keywords: [rest, клиент, infrastructure, методы, openapi, get-хуки, swr] +keywords: [rest, клиент, infra, методы, openapi, get-хуки, swr] --- # Создание клиента -REST-клиент — это infrastructure-модуль, через который проект работает с внешним REST API. +REST-клиент — это infra-модуль, через который проект работает с внешним REST API. На этом этапе нужно подготовить клиент сервиса: создать оболочку клиента, получить методы API и добавить GET-хуки для клиентских компонентов. @@ -57,7 +57,7 @@ GET-хуки именуются с префиксом `useGet`: `useGetPetList`, ## Структура модуля ```text -src/infrastructure/{service-name}/ +src/infra/{service-name}/ ├── client.ts # самописная оболочка и инстанс клиента ├── generated/ или methods/ # методы API ├── hooks/ # GET-хуки REST-клиента diff --git a/ai/nextjs-style-guide/data/rest/clients/manual.md b/ai/nextjs-style-guide/data/rest/clients/manual.md index f5545dc..cb9820b 100644 --- a/ai/nextjs-style-guide/data/rest/clients/manual.md +++ b/ai/nextjs-style-guide/data/rest/clients/manual.md @@ -1,7 +1,7 @@ --- title: Ручное создание description: Создание REST-клиента вручную, когда OpenAPI нет или он неполный. -keywords: [rest, ручной клиент, fetch, methods, dto, errors, infrastructure] +keywords: [rest, ручной клиент, fetch, methods, dto, errors, infra] --- # Ручное создание @@ -13,7 +13,7 @@ keywords: [rest, ручной клиент, fetch, methods, dto, errors, infrast ## Что нужно создать ```text -src/infrastructure/ +src/infra/ └── pet-project-api/ ├── methods/ │ └── posts.ts @@ -43,7 +43,7 @@ src/infrastructure/ DTO запросов и ответов живут в `types/`. `client.ts` не содержит DTO и доменные типы. ```ts -// src/infrastructure/pet-project-api/types/post.ts +// src/infra/pet-project-api/types/post.ts export type PostDto = { id: string slug: string @@ -57,14 +57,14 @@ export type PostListQueryDto = { ``` ```ts -// src/infrastructure/pet-project-api/types/index.ts +// src/infra/pet-project-api/types/index.ts export type { PostDto, PostListQueryDto } from './post' ``` Типы, которые нужны только базовому транспорту, можно держать отдельно: ```ts -// src/infrastructure/pet-project-api/types/client.ts +// src/infra/pet-project-api/types/client.ts export type QueryParams = Record ``` @@ -73,7 +73,7 @@ export type QueryParams = Record Ошибка API тоже относится к REST-модулю. ```ts -// src/infrastructure/pet-project-api/errors/pet-project-api.error.ts +// src/infra/pet-project-api/errors/pet-project-api.error.ts export class PetProjectApiError extends Error { constructor( public readonly status: number, @@ -90,7 +90,7 @@ export class PetProjectApiError extends Error { `client.ts` содержит только транспортную оболочку и сборку инстанса. Прямой `fetch` живёт здесь, а не в компонентах и не в методах верхних слоёв. ```ts -// src/infrastructure/pet-project-api/client.ts +// src/infra/pet-project-api/client.ts import { PetProjectApiError } from './errors/pet-project-api.error' import type { QueryParams } from './types/client' @@ -131,7 +131,7 @@ export class PetProjectApiClient { Методы группируются по сущностям в `methods/`. Они не знают про React, SWR и UI. ```ts -// src/infrastructure/pet-project-api/methods/posts.ts +// src/infra/pet-project-api/methods/posts.ts import type { PetProjectApiClient } from '../client' import type { PostDto, PostListQueryDto } from '../types/post' @@ -155,7 +155,7 @@ export function postsMethods(client: PetProjectApiClient) { `index.ts` собирает именованный API-объект и открывает наружу только публичные части модуля. ```ts -// src/infrastructure/pet-project-api/index.ts +// src/infra/pet-project-api/index.ts import { PetProjectApiClient } from './client' import { postsMethods } from './methods/posts' @@ -173,7 +173,7 @@ export type { PostDto, PostListQueryDto } from './types' export * from './hooks' ``` -Внешний код импортирует только из `infrastructure/pet-project-api`, не из внутренних файлов модуля. +Внешний код импортирует только из `infra/pet-project-api`, не из внутренних файлов модуля. ## Правила diff --git a/ai/nextjs-style-guide/data/rest/index.md b/ai/nextjs-style-guide/data/rest/index.md index db7df17..174a7d5 100644 --- a/ai/nextjs-style-guide/data/rest/index.md +++ b/ai/nextjs-style-guide/data/rest/index.md @@ -1,7 +1,7 @@ --- title: REST description: Как правильно работать с REST API в проекте. -keywords: [rest, api, данные, infrastructure, клиент, swr, стратегии] +keywords: [rest, api, данные, infra, клиент, swr, стратегии] --- # REST @@ -15,7 +15,7 @@ REST в проекте проходит через два главных эта ## 1. Создание клиента -На этом этапе внешний API оформляется как модуль слоя `infrastructure/`. +На этом этапе внешний API оформляется как модуль слоя `infra/`. Клиент отвечает за: diff --git a/ai/nextjs-style-guide/data/rest/strategies/business-composition.md b/ai/nextjs-style-guide/data/rest/strategies/business-composition.md index 5234443..b9250e5 100644 --- a/ai/nextjs-style-guide/data/rest/strategies/business-composition.md +++ b/ai/nextjs-style-guide/data/rest/strategies/business-composition.md @@ -15,13 +15,13 @@ Business-композиция используется, когда просто - Нужно преобразовать DTO в доменную модель. - Нужно спрятать бизнес-сценарий за доменным API. -Такая логика не пишется в `infrastructure/`. REST-клиент остаётся прозрачным адаптером к API. +Такая логика не пишется в `infra/`. REST-клиент остаётся прозрачным адаптером к API. ## Пример поверх одного GET-хука ```ts // src/business/pets/hooks/use-available-pets.hook.ts -import { useGetPetList } from 'infrastructure/pet-store-api' +import { useGetPetList } from 'infra/pet-store-api' /** * Доменный список доступных питомцев. @@ -36,13 +36,13 @@ export const useAvailablePets = () => { } ``` -`useGetPetList` — infrastructure-хук. `hasPets` — бизнес-интерпретация, поэтому она появляется в `business/pets`. +`useGetPetList` — infra-хук. `hasPets` — бизнес-интерпретация, поэтому она появляется в `business/pets`. ## Пример композиции нескольких GET-хуков ```ts // src/business/pets/hooks/use-pets-dashboard.hook.ts -import { useGetPetList } from 'infrastructure/pet-store-api' +import { useGetPetList } from 'infra/pet-store-api' /** * Данные dashboard по питомцам. @@ -64,13 +64,13 @@ export const usePetsDashboard = () => { } ``` -Композиция нескольких запросов не добавляется в `infrastructure/pet-store-api/hooks/`, потому что это уже сценарий потребления данных. +Композиция нескольких запросов не добавляется в `infra/pet-store-api/hooks/`, потому что это уже сценарий потребления данных. ## Пример auth-состояния ```ts // src/business/auth/hooks/use-auth-state.hook.ts -import { useGetCurrentUser } from 'infrastructure/backend-api' +import { useGetCurrentUser } from 'infra/backend-api' /** * Состояние авторизации текущего пользователя. @@ -107,7 +107,7 @@ src/business/ ## Что запрещено ```ts -// Плохо — business-смысл внутри infrastructure-хука +// Плохо — business-смысл внутри infra-хука export const useGetPetList = (status: PetStatus) => { const query = useSWR(...) diff --git a/ai/nextjs-style-guide/data/rest/strategies/client-get-hook.md b/ai/nextjs-style-guide/data/rest/strategies/client-get-hook.md index be3265c..6dde3e1 100644 --- a/ai/nextjs-style-guide/data/rest/strategies/client-get-hook.md +++ b/ai/nextjs-style-guide/data/rest/strategies/client-get-hook.md @@ -21,8 +21,8 @@ keywords: [rest, client components, swr, get-хук, client state] 'use client' import { useState } from 'react' -import { useGetPetList } from 'infrastructure/pet-store-api' -import type { PetStatus } from 'infrastructure/pet-store-api' +import { useGetPetList } from 'infra/pet-store-api' +import type { PetStatus } from 'infra/pet-store-api' const statuses: PetStatus[] = ['available', 'pending', 'sold'] @@ -60,7 +60,7 @@ export function PetTabs() { Хук добавляется в REST-модуль сервиса: ```text -src/infrastructure/pet-store-api/hooks/use-get-pet-list.hook.ts +src/infra/pet-store-api/hooks/use-get-pet-list.hook.ts ``` Не создавайте локальный `useSWR` в компоненте. diff --git a/ai/nextjs-style-guide/data/rest/strategies/client-hooks-initial-data.md b/ai/nextjs-style-guide/data/rest/strategies/client-hooks-initial-data.md index 1f22fa5..610eb87 100644 --- a/ai/nextjs-style-guide/data/rest/strategies/client-hooks-initial-data.md +++ b/ai/nextjs-style-guide/data/rest/strategies/client-hooks-initial-data.md @@ -30,7 +30,7 @@ keywords: [rest, swr, fallback, initial data, client hooks, unstable_serialize, ## Ключ хука ```ts -// src/infrastructure/pet-store-api/hooks/use-get-pet-list.hook.ts +// src/infra/pet-store-api/hooks/use-get-pet-list.hook.ts export const getPetListKey = (status: PetStatus) => ['pet-store-api', 'pet', 'list', status] as const ``` @@ -46,7 +46,7 @@ import { SWRConfig, unstable_serialize } from 'swr' import { getPetListKey, petStoreApi, -} from 'infrastructure/pet-store-api' +} from 'infra/pet-store-api' type PetsLayoutProps = { children: ReactNode @@ -78,7 +78,7 @@ export default async function PetsLayout({ children }: PetsLayoutProps) { ```tsx 'use client' -import { useGetPetList } from 'infrastructure/pet-store-api' +import { useGetPetList } from 'infra/pet-store-api' export function PetList() { const { data: pets, isLoading } = useGetPetList('available') diff --git a/ai/nextjs-style-guide/data/rest/strategies/parallel-server-requests.md b/ai/nextjs-style-guide/data/rest/strategies/parallel-server-requests.md index c7c0e7f..fec2efb 100644 --- a/ai/nextjs-style-guide/data/rest/strategies/parallel-server-requests.md +++ b/ai/nextjs-style-guide/data/rest/strategies/parallel-server-requests.md @@ -17,7 +17,7 @@ keywords: [rest, promise.all, параллельные запросы, server co ## Хорошо ```tsx -import { petStoreApi } from 'infrastructure/pet-store-api' +import { petStoreApi } from 'infra/pet-store-api' import { PetsDashboardScreen } from 'screens/pets-dashboard' export default async function PetsDashboardPage() { diff --git a/ai/nextjs-style-guide/data/rest/strategies/pass-promise-down.md b/ai/nextjs-style-guide/data/rest/strategies/pass-promise-down.md index 56fa7e3..d2e5004 100644 --- a/ai/nextjs-style-guide/data/rest/strategies/pass-promise-down.md +++ b/ai/nextjs-style-guide/data/rest/strategies/pass-promise-down.md @@ -19,10 +19,10 @@ keywords: [rest, promise, suspense, streaming, server components] ```tsx // src/app/(routes)/pets/page.tsx import { Suspense } from 'react' -import { petStoreApi } from 'infrastructure/pet-store-api' +import { petStoreApi } from 'infra/pet-store-api' import { PetListSection } from 'widgets/pet-list-section' import { PetListSkeleton } from 'widgets/pet-list-section' -import type { Pet } from 'infrastructure/pet-store-api' +import type { Pet } from 'infra/pet-store-api' export default function PetsPage() { const petsPromise = petStoreApi.pet.findPetsByStatus({ status: 'available' }) diff --git a/ai/nextjs-style-guide/data/rest/strategies/server-await.md b/ai/nextjs-style-guide/data/rest/strategies/server-await.md index a882857..87c68b5 100644 --- a/ai/nextjs-style-guide/data/rest/strategies/server-await.md +++ b/ai/nextjs-style-guide/data/rest/strategies/server-await.md @@ -29,7 +29,7 @@ SSR/dynamic rendering нужен, когда данные зависят от т ```tsx // src/app/(routes)/pets/page.tsx -import { petStoreApi } from 'infrastructure/pet-store-api' +import { petStoreApi } from 'infra/pet-store-api' import { PetsScreen } from 'screens/pets' export default async function PetsPage() { @@ -48,7 +48,7 @@ export default async function PetsPage() { ```tsx // src/app/(routes)/pets/[id]/page.tsx import { notFound } from 'next/navigation' -import { petStoreApi } from 'infrastructure/pet-store-api' +import { petStoreApi } from 'infra/pet-store-api' import { PetDetailScreen } from 'screens/pet-detail' type PetPageProps = {