feat: Добавить VitePress

This commit is contained in:
2026-07-25 17:56:16 +03:00
parent cfcff10c58
commit 6f6e4896af
80 changed files with 4663 additions and 665 deletions

View File

@@ -1,12 +0,0 @@
# SLM Design
`docs/` - файлы документации по SLM-архитектуре.
Используй эту документацию как источник истины для задач по SLM Design: слоям, модулям, сегментам, публичному API, business factory, composition modules и монорепозиториям.
## Структура
- `canons/` - основные каноны SLM Design.
- `examples/` - дополнительные примеры реализации.
Точка входа: `canons/index.md`.

View File

@@ -1,331 +0,0 @@
---
title: Business-фабрика
description: Контракт business-модуля, runtime-зависимости, logic API, доменные ошибки и место сборки фабрик
---
# Business-фабрика
Раздел фиксирует архитектурный контракт business-модуля: фабрика описывает доменный logic API приложения и не знает о конкретном backend, SDK, storage, state/query runtime, browser API или React tree.
## Главный принцип
Business-модуль описывает домен приложения, а не форму backend API, SDK, storage, external hook, state manager или внешнего сервиса.
Фабрика должна отвечать на вопрос: какой API нужен приложению для работы с доменом. Она не должна отвечать на вопрос: какие методы есть у конкретного REST-клиента, SDK или browser API.
## Когда создавать business-модуль
Полный контракт business-модуля — фабрика, `deps`, доменные типы, доменные ошибки, adapters, сборка в `compositions/business/{domain}`, обязательные factory/assembly tests и colocated tests для внутренней runtime-safe логики. Он применяется к любому модулю, созданному в `business`.
Создавай business-модуль, когда выполняется хотя бы одно условие:
- сценарии нужны нескольким страницам, composition modules или другим доменам;
- у логики есть собственная доменная модель и доменные ошибки, которые нельзя выразить типами внешнего API;
- доменный сценарий зависит от внешних runtime-capabilities (backend, SDK, product storage, source hook, domain store, event, browser API), которые нужно изолировать за `deps`;
- домен нужно тестировать независимо от UI и конкретного backend.
Не создавай business-модуль заранее, если логика нужна одной странице, не имеет доменной модели, product I/O и domain state. Presentation-only store или browser interaction, принадлежащие UI scope, сами по себе не создают business-домен. Такая page-local orchestration живёт в соответствующем composition module.
Наличие product I/O отменяет page-local исключение. Даже если источник нужен одной странице, consumer composition не обращается к нему напрямую: объяви business-контракт, dependency adapter и domain errors.
«Облегчённого» business-модуля не существует: если модуль создан в `business/`, контракт применяется целиком. Подъём вызревшей логики из composition module в business-модуль — обычный рефакторинг: объяви доменные типы и `deps`, перенеси сценарии в services и hooks, собери фабрику и покрой её factory-level тестами.
## Обязательные правила
- Фабрика лежит в корне business-модуля: `business/{domain}/{domain}.factory.ts`.
- Фабрика принимает runtime-зависимости только через `deps`.
- Фабрика возвращает только logic API домена: hooks, selectors, command/query methods, scenario services.
- Фабрика не возвращает React-компоненты, layouts, guards, boundaries, providers или page-level wrappers.
- Business-модуль не содержит React-компоненты. UI-решения домена размещаются в `compositions`; полностью универсальные UI-контролы размещаются в `ui`.
- Business-модуль не импортирует реальные SDK, generated operations, HTTP-клиенты, storage, env, browser API или composition-сборку.
- Business-модуль не импортирует React state/effect runtime, SWR/query runtime, Zustand/Redux/MobX store runtime или event bus implementation.
- Source/query hooks, domain state stores, subscriptions, clock, random и technical services передаются через business-owned `deps`.
- Public contract business-модуля использует собственные доменные типы, а не DTO внешнего API.
- `index.ts` business-модуля экспортирует только фабрику и type-only экспорты. Исключений нет.
- Runtime-сборка business-фабрики выполняется вне `business`, обычно в `compositions/business/{domain}`.
- Для каждой runtime-capability создаётся явный dependency adapter. Adapter не пишется inline внутри builder.
- Из public API business выходят только собственные domain errors со стабильным `code`.
- Factory-level и assembly tests обязательны и входят в критерий завершения модуля.
## Проектирование фабрики
Проектирование начинается со сценариев домена, а не с API-клиента.
Порядок работы:
1. Опиши публичные сценарии, которые нужны приложению.
2. Опиши доменные типы, которыми должен оперировать UI и другие business-модули.
3. Проведи inventory всех runtime-capabilities: data sources, hooks, stores, events, browser APIs, env, clock и cross-domain APIs.
4. Опиши минимальные внешние возможности в `{Domain}Deps`.
5. Назови внешние возможности бизнес-языком, а не языком backend endpoint'ов и библиотек.
6. Опиши domain error codes для каждого публичного сценария.
7. Собери фабрику из внутренних services, hook wrappers, mappers и helpers поверх переданных deps.
8. Верни наружу только `{Domain}Api`.
9. Реализуй каждую dependency отдельным adapter в `compositions/business/{domain}`.
Правильные вопросы:
- Не `какой endpoint дернуть?`, а `какой бизнес-сценарий выполняем?`.
- Не `какой DTO пришёл?`, а `какую доменную модель отдаём наружу?`.
- Не `как называется метод SDK?`, а `какая внешняя возможность нужна домену?`.
- Не `как устроен backend сейчас?`, а `какой стабильный контракт нужен приложению?`.
## Контракт зависимостей
`{Domain}Deps` описывает не технические инструменты, а возможности, которые нужны домену.
Правила:
- имя зависимости отражает бизнес-возможность: `phoneAuth`, `session`, `profile`, `agreements`, `notifications`;
- методы внутри зависимости называют сценарное действие без повторения endpoint names: `phoneAuth.requestCode`, `phoneAuth.verifyCode`, `profile.getCurrentUser`, `agreements.saveUserAgreements`;
- параметры используют доменные типы business-модуля;
- результат внешней границы принимается как `unknown`, если данные требуют runtime-нормализации;
- пустой успешный ответ описывается как `Promise<void>`, если body не нужен;
- dependency не должна возвращать generated DTO как доменный тип;
- mapper, normalizer или domain error не передаётся через `deps`, если это часть доменной логики.
- source/query hook описывается business-owned result type, без `SWRConfiguration`, `UseQueryResult` и других library types;
- domain state описывается business-owned port, без `StoreApi` и concrete state manager types;
- subscription возвращает cleanup function;
- недетерминированные clock/random/id capabilities передаются явно;
Плохо:
```ts
import type { ApiRequestClient, BoundApi } from '@vendor/api-sdk'
import type { v1PatientProfileList } from '@vendor/api-sdk/operations'
type UserApiTree = {
patient: {
profile: typeof v1PatientProfileList
}
}
export type UserDeps = {
api: BoundApi<UserApiTree, ApiRequestClient>
}
```
Проблема: business-модуль знает про SDK, generated operation и форму внешнего клиента.
Хорошо:
```ts
import type { AuthState } from './auth-state.type'
import type { VerifyPhoneCodeData } from './verify-phone-code-data.type'
export type AuthDeps = {
phoneAuth: {
requestCode: (phone: string) => Promise<unknown>
resendCode: (challengeId: string) => Promise<unknown>
verifyCode: (data: VerifyPhoneCodeData) => Promise<unknown>
}
session: {
setToken: (token?: string | null) => void
useToken: () => string | null | undefined
}
sessionEvents: {
onInvalidated: (listener: () => void) => () => void
}
state: {
create: (initialState: AuthState) => {
get: () => AuthState
set: (state: AuthState) => void
useState: () => AuthState
}
}
}
```
Конкретный SDK, storage, source hook, store или mock подключается в `compositions/business/auth` через адаптер. Business-модуль знает только о своём `AuthDeps`.
## Публичный API фабрики
`{Domain}Api` описывает logic API домена, который фабрика возвращает наружу в runtime.
Правила:
- API говорит на языке домена;
- API не повторяет endpoint names;
- API не отдаёт DTO внешнего сервиса;
- API не раскрывает внутренние `create*` services;
- API остаётся стабильным при замене backend, SDK или storage;
- API может возвращать hooks и selectors, созданные поверх переданных dependency hooks/state ports; business не импортирует concrete hook/store runtime;
- API не возвращает React-компоненты, UI-компоненты, layouts, guards, boundaries или page-level wrappers.
Плохо:
```ts
export type AuthApi = {
AuthGuard: ComponentType<AuthGuardProps>
LoginButton: ComponentType<LoginButtonProps>
useAuth: ReturnType<typeof createAuthHook>
}
```
Проблема: factory output смешивает доменную логику с UI и начинает собирать React tree.
Хорошо:
```ts
export type AuthApi = {
requestPhoneCode: ReturnType<typeof createRequestPhoneCode>
resendPhoneCode: ReturnType<typeof createResendPhoneCode>
verifyPhoneCode: ReturnType<typeof createVerifyPhoneCode>
useAuth: ReturnType<typeof createAuthHook>
useIsAuthenticated: ReturnType<typeof createIsAuthenticatedHook>
startSessionInvalidationTracking: () => () => void
}
```
Такой API сообщает composition-слою доменное состояние и действия, но не навязывает UI-решение.
## Реализация фабрики
Фабрика связывает внутренние части business-модуля с переданными зависимостями.
```ts
import { createCurrentUserHook } from './hooks/use-current-user.hook'
import { createStoredUserAgreementsGetter } from './services/get-stored-user-agreements.service'
import { createUpdateCurrentUserProfile } from './services/update-current-user-profile.service'
import type { UserFactory } from './types/user-factory.type'
export const userFactory: UserFactory = (deps) => {
const { authApi, profile, storage } = deps
return {
getStoredUserAgreements: createStoredUserAgreementsGetter(storage),
updateCurrentUserProfile: createUpdateCurrentUserProfile(profile),
useCurrentUser: createCurrentUserHook({ authApi, profile }),
}
}
```
Фабрика не должна:
- создавать API-клиент;
- выбирать backend endpoint;
- читать env;
- обращаться к browser storage напрямую;
- делать запросы при создании API;
- импортировать composition-сборку;
- подстраивать свой API под конкретный внешний сервис;
- создавать или возвращать React-компоненты;
- импортировать React state/effect APIs;
- импортировать SWR, query library или state manager runtime;
- создавать concrete store, query client или event bus;
- подписываться на external event без dependency contract и явного cleanup.
## Runtime-границы и ошибки
Любая dependency фабрики считается ненадёжной runtime-границей.
Business-модуль защищает публичный контракт от таких случаев:
- dependency вернула `null`, `undefined`, пустой body или объект неправильной формы;
- dependency вернула rejected promise;
- dependency синхронно выбросила исключение;
- storage содержит устаревшие или битые данные;
- внешний сервис поменял форму ответа без изменения TypeScript-типов.
Защита выполняется внутри business-модуля через mappers, normalizers, type guards и domain errors. Fallback допустим только для валидного доменного исхода, явно представленного dependency contract, а не для technical failure или malformed response.
Наружу не должны протекать ошибки SDK, HTTP-клиента, storage, query hook, store, generated API или browser API. Потребитель business API всегда получает только собственную domain error со стабильным `code`, а не `message`, `status`, `response`, `stack` или форму внешней ошибки.
```ts
if (isDomainError<AuthErrorCode>(error) && error.code === 'AUTH_PHONE_CODE_VERIFY_FAILED') {
showInvalidCodeMessage()
}
```
Business всегда преобразует rejected promise, synchronous throw, source hook error и невалидную успешную структуру в собственную domain error.
## Сборка business-фабрики
Реальные runtime-зависимости подключаются в composition module `compositions/business/{domain}`.
```text
src/
├── business/
│ └── auth/
│ ├── auth.factory.ts
│ ├── types/
│ └── index.ts
└── compositions/
└── business/
└── auth/
├── create-auth-business.ts
├── adapters/
├── types/
└── index.ts
```
Если business-домены сгруппированы, сборка повторяет тот же относительный путь: `business/app/auth` соответствует `compositions/business/app/auth`, `business/cms/content` соответствует `compositions/business/cms/content`. Группировка не является default-структурой.
Правила:
- `business/{domain}` объявляет фабрику и контракт `deps`.
- `compositions/business/{domain}` отдельными adapters адаптирует SDK, storage, infra-клиенты, source hooks, stores, events и другие runtime-capabilities к этому контракту.
- Builder явно создаёт или получает scoped runtime instances без I/O, создаёт adapters поверх них, передаёт adapters фабрике и возвращает API; integration logic не пишется inline.
- `create{Domain}Business()` возвращает готовый `{Domain}Api`.
- Browser/application `create{Domain}Business()` принимает аргументы только для API других уже собранных business-фабрик.
- SDK, storage, env, HTTP-клиенты и browser API не передаются в `create{Domain}Business()` как deps.
- Если домен не зависит от других business API, `create{Domain}Business()` вызывается без аргументов.
- Request-scoped builder отделяет cross-domain API от `requestScopeInput`. В input находятся только framework/request data; concrete client factory импортируется внутри integration module. Request input используется только для создания adapters и не передаётся factory как raw dependency.
- Конечный граф API собирается в месте, которое владеет lifecycle: page composition, route composition, provider, request scope или test setup.
- `infra` не собирает business-фабрики, потому что не должен импортировать `business`.
- `app` не реализует business-сборку, а только подключает готовые composition modules к фреймворку.
```ts
import { createAuthBusiness } from '@/compositions/business/auth'
import { createUserBusiness } from '@/compositions/business/user'
const authApi = createAuthBusiness()
const userApi = createUserBusiness({ authApi })
```
Подробный пример см. в [Business composition](../examples/business-composition.md).
## Public API файла index.ts
Жёсткое правило без исключений: `business/{domain}/index.ts` экспортирует только фабрику и type-only экспорты.
```ts
export { userFactory } from './user.factory'
export type { User } from './types/user.type'
export type { UserApi } from './types/user-api.type'
export type { UserDeps } from './types/user-deps.type'
export type { UserFactory } from './types/user-factory.type'
export type { UserErrorCode } from './types/user-error-code.type'
```
## Тестирование
Business-модуль тестируется через public API фабрики. Factory-level тесты обязательны, импортируют модуль только через `business/{domain}` и не используют deep imports во внутренние `services`, `hooks`, `mappers` или `lib`.
Сборка в `compositions/business/{domain}` обязательно тестируется отдельно: эти тесты проверяют adapters, корректность передачи deps, отсутствие I/O при создании API и lifecycle cleanup, но не заменяют сценарные тесты business-модуля.
Colocated unit tests обязательны там, где есть mappers, normalizers, type guards, domain errors или другая внутренняя runtime-safe логика. Они являются дополнительным уровнем, а не заменой factory-level и assembly tests.
Подробный пример см. в [Business testing](../examples/business-testing.md).
## Чеклист
- Фабрика лежит в корне business-модуля.
- Фабрика принимает все runtime-зависимости через `deps`.
- В `deps` нет SDK, generated operations, HTTP-клиентов, backend DTO, StoreApi и query-library types.
- Контракты зависимостей названы бизнес-языком.
- Source/query hooks, stores, events и platform APIs переданы через `deps`.
- Business не импортирует React/SWR/query/store runtime.
- Все public types принадлежат business-модулю.
- Внешние ответы нормализуются перед попаданием в public API.
- Все source errors без исключений заменяются собственными domain errors.
- Public API фабрики не повторяет внешний API.
- Public API фабрики не возвращает React-компоненты.
- Business-модуль не содержит React-компоненты.
- `index.ts` экспортирует только фабрику и type-only экспорты, без исключений.
- Runtime-сборка живёт в `compositions/business/{domain}`.
- Для каждой runtime-capability существует private adapter.
- Builder не содержит inline integration logic.
- Business-модуль можно подключить к другому backend через адаптер без изменения фабрики.
- Factory-level и assembly tests созданы и выполняются.

View File

@@ -1,293 +0,0 @@
---
title: Runtime-граница business
description: Строгая изоляция business-фабрики от источников данных, state/query runtime, infra, browser API и внешних ошибок
---
# Runtime-граница business
## Главный инвариант
Business-модуль выполняет доменную композицию только над:
- собственными типами и детерминированной логикой;
- capabilities, переданными фабрике через `{Domain}Deps`.
Business не вызывает runtime-возможность, если она не была передана фабрике. Это относится не только к данным и `infra`, но и к hooks, stores, subscriptions, browser API и другим concrete runtime-механизмам.
Фабрика отвечает на вопрос «какой стабильный доменный API нужен приложению», а не «какими библиотеками и источниками он реализован».
## Что разрешено внутри business
Business может напрямую использовать:
- собственные domain types;
- собственные services, mappers, normalizers, validators и type guards;
- собственные domain errors;
- детерминированные вычисления без I/O, runtime-state и скрытого окружения;
- чистые библиотеки вроде schema validators, decimal/date utilities, если результат определяется только явными аргументами и типы библиотеки не становятся public contract;
- type-only контракты других business API для cross-domain dependencies.
## Что передаётся через deps
Через `{Domain}Deps` передавай любую runtime-capability:
| Capability | Примеры concrete implementation |
|---|---|
| Product source | REST SDK, GraphQL client, CMS, storage |
| Source/query hook | SWR, TanStack Query, Apollo hook |
| Domain state runtime | Zustand, Redux, MobX, RxJS store |
| Technical service | logger, telemetry, notifications, i18n engine |
| Platform API | `window`, navigation, clipboard, geolocation |
| Lifecycle event | unauthorized event, socket event, subscription |
| Environment | env/config provider, feature runtime configuration |
| Nondeterminism | clock, timer, random, ID generator |
| Cross-domain behavior | ограниченный API другой business-фабрики |
Не импортируй concrete implementation в business даже в том случае, если библиотека используется только в одном внутреннем файле.
## Запрещённые imports
Production-код `business/**` не импортирует напрямую ни runtime values, ни types из concrete runtimes:
- `infra`, `compositions`, `app`;
- SDK, generated operations и HTTP clients;
- storage implementation;
- React state/effect APIs;
- SWR, TanStack Query, Apollo и другие query runtimes;
- Zustand, Redux, MobX, RxJS stores;
- browser и framework runtime APIs;
- event bus implementation;
- env и process-specific configuration.
Запрет нельзя обойти через `import type`, alias, barrel, type cast или helper в `shared`. Type-only разрешены собственные contracts, детерминированный `shared`, чистые libraries и суженные public API других business-доменов.
## Доменный шлюз данных
Business API является единственной продуктовой границей для потребительского кода.
```text
page / layout / screen / widget
→ {Domain}Api
→ business scenario
→ {Domain}Deps
→ private dependency adapter
→ infra client / SDK / storage / external source
```
Внешний сервис остаётся физическим источником данных. Business является единственным источником доменной истины: он определяет модель, сценарий, нормализацию, fallback и ошибки.
Обычный consumer composition не создаёт параллельный продуктовый контракт поверх DTO или client. Единственная зона concrete product integration внутри `compositions``compositions/business/{domain}`.
## Business-owned deps
`{Domain}Deps` принадлежит business-модулю и описывает необходимые возможности доменным языком.
Требования:
- группируй методы по capability, а не по имени SDK/client;
- принимай доменные аргументы;
- принимай внешние результаты как `unknown`, если нужна runtime-проверка;
- описывай собственную минимальную форму dependency hook/result;
- описывай собственный state port, а не `StoreApi` конкретной библиотеки;
- возвращай cleanup из subscription capability;
- не передавай mapper, normalizer или domain error через deps;
- не используй generated DTO как доменную модель;
- не передавай целый client, если домену нужны два конкретных действия.
Плохо:
```ts
import type { StoreApi } from 'zustand'
import type { AdminApiClient } from '@vendor/admin-sdk'
export type AuthDeps = {
api: AdminApiClient
store: StoreApi<AuthState>
}
```
Хорошо:
```ts
import type { AuthState } from './auth-state.type'
import type { VerifyPhoneCodeData } from './verify-phone-code-data.type'
export type SourceHookResult = {
data: unknown
error: unknown
isLoading: boolean
refresh: () => Promise<void>
}
export type AuthDeps = {
phoneAuth: {
requestCode: (phone: string) => Promise<unknown>
resendCode: (challengeId: string) => Promise<unknown>
verifyCode: (data: VerifyPhoneCodeData) => Promise<unknown>
}
session: {
setToken: (token?: string | null) => void
useToken: () => string | null | undefined
}
sessionEvents: {
onInvalidated: (listener: () => void) => () => void
}
state: {
create: (initialState: AuthState) => {
get: () => AuthState
set: (state: AuthState) => void
useState: () => AuthState
}
}
}
```
## Dependency adapters
Adapter находится снаружи business и реализует конкретную часть `{Domain}Deps`.
```text
compositions/business/auth/
├── create-auth-business.ts
├── adapters/
│ ├── admin-auth-session.adapter.ts
│ ├── browser-auth-navigation.adapter.ts
│ ├── zustand-auth-state.adapter.ts
│ └── admin-auth-session-events.adapter.ts
└── index.ts
```
Правила adapter:
- импортирует type-only business contract;
- импортирует concrete infra/runtime implementation;
- преобразует доменные аргументы в transport arguments;
- возвращает raw/unknown runtime result, если business должен его проверить;
- пробрасывает source error без создания domain error;
- не реализует бизнес-правило;
- не выбирает доменный fallback;
- не экспортируется через public API integration module;
- тестируется на wiring, payload и соответствие dependency contract.
Каждая runtime-capability получает явный adapter. Не скрывай несколько разных integrations как inline-функции внутри builder.
## Dependency hooks
Если business должен предоставить hook, concrete hook передаётся через deps.
```text
SWR/TanStack hook
→ dependency adapter
→ business-owned source hook contract
→ business wrapper hook
→ domain result / domain error
```
Business wrapper может:
- вызвать переданный dependency hook;
- нормализовать `data` в доменную модель;
- заменить source error доменной ошибкой;
- вычислить domain state и selectors;
- вернуть собственный стабильный result type.
Dependency hook обязан быть non-throwing и non-Suspense: technical failure возвращается в `error: unknown`, а не выбрасывается во время вызова hook. Business wrapper обязан заменить этот `error` собственной domain error. Публичные callbacks dependency result, например `refresh`, также оборачиваются business и не могут выдать source error наружу.
Business wrapper не импортирует query library. В public contract не протекают `SWRConfiguration`, `UseQueryResult`, query keys, cache implementation или library-specific mutate API без отдельного domain contract.
## Domain state
Business владеет:
- моделью доменного state;
- допустимыми переходами;
- commands и selectors;
- реакцией на dependency results и events.
Business не владеет concrete state manager. Business выбирает initial domain state и передаёт его в `deps.state.create(initialState)`; adapter только создаёт concrete store с переданным значением.
Zustand/Redux/MobX adapter реализует business-owned state adapter factory и создаётся в `compositions/business/{domain}`. Business-фабрика получает adapter factory через `deps`, передаёт initial domain state и получает concrete port.
Локальный UI-state является другим случаем. Состояние раскрытия sidebar, выбранной вкладки или шага локального UI-flow может использовать concrete state manager непосредственно внутри владеющего composition module, если оно не подменяет доменное состояние и product data boundary.
## Доменные ошибки
Из любого публичного метода, command, query или hook business-модуля выходят только собственные доменные ошибки.
Business обязан преобразовать:
- rejected promise dependency;
- synchronous throw dependency;
- source hook error;
- ошибку store/storage/browser API;
- невалидный успешный ответ;
- неизвестную runtime-ошибку.
Domain error содержит стабильный `code`. Исходная ошибка может сохраняться только в `cause` для диагностики.
Потребитель не ориентируется на:
- `message` внешней ошибки;
- HTTP status;
- `response`;
- SDK error class;
- stack или transport code.
Adapter не импортирует и не создаёт domain error. Error mapping всегда выполняет business.
Rejected promise, synchronous throw, source error и malformed response всегда превращаются в domain error. Fallback допустим только для валидного доменного исхода, явно представленного dependency contract, например корректного отсутствия данных. Fallback не используется для поглощения technical failure или contract drift.
## Чистый builder
`compositions/business/{domain}/create-{domain}-business.ts` только:
1. явно создаёт или получает runtime instances нужного lifecycle без I/O;
2. создаёт adapters поверх этих instances;
3. передаёт adapters и API других доменов в factory;
4. возвращает готовый `{Domain}Api`.
Builder не содержит:
- inline SDK calls;
- `window`/storage operations;
- domain mapping;
- domain errors;
- Zustand/SWR setup вперемешку с другими dependencies;
- product request при создании API;
- скрытую subscription без cleanup contract.
Если capability имеет lifecycle, business API предоставляет domain-level `start`/`subscribe` operation, которая использует dependency и возвращает cleanup wrapper. Business преобразует ошибки регистрации, callback и cleanup в собственные domain errors. Builder остаётся без side effects, а владелец scope запускает operation после mount/commit и вызывает cleanup при завершении lifecycle.
Browser/application builder без cross-domain dependencies вызывается без аргументов. Для request scope integration module определяет отдельный `requestScopeInput` только с framework/request data, например headers, cookies, request ID и abort signal. Concrete client factory импортируется и вызывается внутри integration module. Request input используется только для создания adapters, не передаётся в business как raw client и не экспортируется consumer compositions.
## Graph и lifecycle
Per-domain builder не является владельцем полного graph.
Владелец graph:
- выбирает application-lifetime composition/route/page/request/test scope;
- собирает домены в ацикличном порядке;
- создаёт ровно необходимый набор API;
- использует точный graph type;
- управляет cleanup subscriptions/resources;
- не повторяет adapter wiring;
- не импортирует raw infra для «досборки» домена.
Не используй generic `Partial<Business>` с последующим `as Business`. Если scope содержит только Auth, его contract должен обещать только Auth.
## Cross-domain dependencies
Один business-домен не импортирует runtime другого домена. Он объявляет нужную capability в своих `Deps` через type-only API другого домена, по возможности суженный `Pick`.
Готовый API передаётся builder-у при сборке graph:
```text
createAuthBusiness()
→ createUserBusiness({ authApi })
→ createOrdersBusiness({ userApi })
```
Runtime-цикл означает ошибочную границу доменов. Не скрывай цикл service locator, lazy import или глобальным event bus.
Подробный контракт фабрики находится в [Business-фабрике](./business-factory.md). Практическая сборка показана в [Business composition](../examples/business-composition.md).

View File

@@ -1,216 +0,0 @@
---
title: Процесс архитектурного решения
description: Обязательный порядок классификации задачи, выбора владельца, слоя, scope и стратегии изменений
---
# Процесс архитектурного решения
Не изменяй файлы, пока не принято архитектурное решение. Название папки, существующий похожий код и удобный импорт не доказывают правильность размещения.
## Карточка решения
Перед реализацией определи:
| Вопрос | Что зафиксировать |
|---|---|
| Роль изменения | Framework wiring, продуктовый сценарий, интеграция, композиция интерфейса, технический сервис, UI или чистый фундамент |
| Владелец | Домен, route/page scope, composition module, infra-модуль, UI-модуль или локальный consumer |
| Данные | Продуктовые данные, техническое состояние, framework input, локальное UI-state или отсутствуют |
| Runtime-возможности | Источники данных, hooks, stores, SDK, browser API, events, clock, random, env и другие внешние capabilities |
| Место | Приложение или package, слой, модуль, вложенный модуль и сегмент |
| Публичная граница | Что действительно нужно экспортировать и кто будет consumer |
| Путь данных | От consumer до business API, dependency adapter и конкретного источника |
| Lifecycle | Кто создаёт instance, сколько instances допустимо и кто выполняет cleanup |
| Стратегия | Локальная правка, новый модуль, новый business-контракт, adapter, перенос или исправление public API |
| Проверки | Typecheck, тесты, import graph, public API, lifecycle и архитектурные инварианты |
Карточку не обязательно выводить пользователю, если решение очевидно. Но агент обязан уметь обосновать каждый пункт до изменения файлов.
## Сбор контекста
Перед выбором места:
1. Прочитай локальные инструкции приложения или package.
2. Найди фактическую границу SLM: `src/`, `apps/{app}/src` или другой локальный root.
3. Проверь существующие слои, группы и соседние модули. Не создавай новую параллельную структуру без необходимости.
4. Проверь aliases, package exports и реальную разрешимость импортов.
5. Найди текущих consumers, public API и runtime import graph изменяемой ответственности.
6. Проверь существующие templates или generators после архитектурного выбора. Шаблон не принимает решение за SLM.
7. Отдельно найди product I/O, hooks, stores, subscriptions, browser API и другие runtime-возможности.
8. Проверь, нет ли уже business-домена, которому принадлежит сценарий.
Не считай неиспользуемый provider, пустой context, тип будущего graph или ссылку на несуществующий домен готовой архитектурой. Решение должно быть достижимо из runtime entry point и иметь реальных consumers.
## Выбор роли
Классифицируй ответственность в следующем порядке.
### Framework wiring
Если код существует только из-за фреймворка, размести его в `app`:
- route-файл;
- bootstrap;
- framework error entry;
- подключение глобальных ресурсов;
- тонкое подключение готового composition module.
`app` не реализует продуктовую композицию, business graph, store, provider или экран.
### Продуктовый сценарий
Если код определяет пользовательский сценарий, доменную модель, продуктовый state, бизнес-правило, нормализацию внешних данных, error mapping или доменный переход после ошибки, владелец находится в `business/{domain}`.
Визуальная реакция на готовый domain error принадлежит consumer composition: сообщение, error screen, redirect, retry control и UI fallback выбираются по стабильному доменному `code`.
Любой новый внешний источник продуктовых данных требует business-контракта. Колокация внешних вызовов в page/screen/widget services не является допустимым упрощением.
### Интеграция business-домена
Если код реализует `{Domain}Deps` через SDK, HTTP, storage, browser API, state/query runtime, event bus или другой concrete runtime, размести его в `compositions/business/{domain}`.
Это интеграционный composition module, а не business-домен и не обычная page/screen/widget composition.
### Продуктовая композиция
Если код собирает route/page/layout/screen/widget, управляет UI-state, provider scope или lifecycle готового business graph, размести его в соответствующем composition module.
Потребительский composition module получает продуктовые данные только через `{Domain}Api`. Он не импортирует product SDK, generated operations, product storage adapter или конкретный источник.
### Технический сервис
Если код предоставляет техническую возможность без продуктовой модели и сценариев, размести его в `infra`:
- HTTP client;
- SDK wrapper;
- logger;
- theme engine;
- i18n engine;
- telemetry transport;
- технический realtime client.
Composition может использовать технический infra-сервис напрямую, если сервис не становится обходным путём к продуктовым данным. Если capability нужна business, она всё равно передаётся через business-owned `deps` и adapter.
### Универсальный UI
Если сущность отображает интерфейс, не знает продуктовый сценарий и применима независимо от конкретной composition, размести её в `ui`.
### Чистый фундамент
Если код детерминирован, не имеет runtime-state, не знает продукт и переиспользуется несколькими владельцами, рассмотри `shared`. По умолчанию оставляй код рядом с первым владельцем.
## Выбор scope
Выбирай минимальный scope, который полностью владеет ответственностью:
1. Нужен одному component/module и не имеет самостоятельной ответственности: оставь внутри владельца.
2. Нужен как самостоятельная часть одного module: создай nested module в `parts/`.
3. Нужен нескольким частям одной page/route ветки: подними в общий composition scope этой ветки.
4. Нужен нескольким composition modules и остаётся продуктовой композицией: создай отдельный composition module.
5. Является доменным сценарием или product data boundary: создай или расширь `business/{domain}`.
6. Является техническим сервисом: создай или расширь `infra/{service}`.
7. Является универсальным UI: создай или расширь `ui/{module}`.
8. Выноси в package только `ui`, `infra` или `shared` код с реальным вторым consumer либо явно зафиксированным межприложенческим ownership/reuse-контрактом.
Не поднимай код выше ради короткого импорта. Не создавай `shared`, общий provider, generic business context или package «на будущее».
## Component, module и group
Применяй решение последовательно:
1. Только отображает готовые props и не владеет зависимостями: component в `ui/` родительского module.
2. Владеет сценарием, данными, state, dependency, lifecycle или внутренней декомпозицией: самостоятельный module.
3. Самостоятельный module, локальный для владельца: nested module в `parts/`.
4. Папка только классифицирует конечные modules: group без `index.ts`, state и runtime logic.
5. `ui/`, `parts/`, `hooks/`, `types/`, `services/` и другие служебные папки внутри module: segments, а не modules.
Если component начинает получать данные, выбирать источник, вызывать сценарный hook или управлять процессом, не добавляй логику в component. Измени архитектурную форму сущности.
## Выбор стратегии
### Новый продуктовый сценарий
1. Найди домен-владелец.
2. Спроектируй `{Domain}Api`, доменные типы и доменные ошибки.
3. Опиши минимальные runtime-capabilities в `{Domain}Deps`.
4. Реализуй детерминированную доменную логику.
5. Создай отдельные adapters в `compositions/business/{domain}`.
6. Собери фабрику чистым builder.
7. Подключи API во владельце lifecycle graph.
8. Используй API из потребительских compositions.
9. Добавь factory-level и assembly tests.
### Прямой product I/O вне business boundary
Не расширяй существующее нарушение.
1. Определи сценарий и домен.
2. Перенеси контракт данных в business-owned `Deps`.
3. Перенеси нормализацию, fallback и error mapping в business.
4. Оставь concrete source call в dependency adapter.
5. Замени прямой вызов на `{Domain}Api`.
6. Закрой adapter и source details из public API.
### Новый store или dependency hook
Сначала определи, является state локальным UI-state или доменным state.
- State является локальным UI-state, если сбрасывается вместе с UI scope, управляет только представлением и не хранит продуктовый факт или product data cache. Такой state может принадлежать composition module и использовать выбранный state manager внутри владельца.
- State является доменным, если выражает продуктовый факт, инвариант, доступен через business API или участвует в бизнес-сценарии.
- Доменный state принадлежит business-контракту. Фабрика получает state adapter factory через `deps`, выбирает initial domain state и создаёт concrete port через adapter.
- Source/query hook реализуется adapter-ом; business вызывает только dependency hook и возвращает собственный доменный hook/result.
### Сборка graph
1. Собери каждый домен отдельным `compositions/business/{domain}` builder.
2. Определи DAG cross-domain зависимостей.
3. Выбери один явный lifecycle scope: application-lifetime composition, route, page, request или test. Слой `app` только подключает application composition.
4. Создавай graph у владельца scope, а не в случайном screen/widget или на module scope без обоснования.
5. Передавай consumers точный graph type. Не используй `Partial<Graph>` с приведением к полному типу.
6. Для subscriptions, timers и resources зафиксируй cleanup/dispose.
### Архитектурное ревью
Проверяй не только пути файлов, но и семантику:
- business-shaped код вне `business`;
- product graph в `infra`;
- type-only imports, которые фактически переносят ownership;
- provider, который не создаёт и не получает instance от явного владельца;
- orphan modules и providers, недостижимые из entry point;
- public API, раскрывающий raw store, context, adapter или generated types;
- отсутствующие tests обязательного business-контракта.
## Условия остановки
Останови реализацию и сначала исправь решение, если:
- владелец ответственности не определён;
- один state или source имеет несколько конкурирующих владельцев;
- business требует прямого runtime или type-only import concrete runtime;
- graph создаёт runtime-цикл;
- lifecycle instance или cleanup не определён;
- public API нужен только для обхода границы;
- шаблон генерирует архитектуру, противоречащую принятому решению;
- изменение требует незапрошенной миграции нескольких независимых областей.
## Локальные материалы
Основной процесс достаточен для типового решения. Открывай только материал, который нужен текущей ветке задачи.
| Ситуация | Материал |
|---|---|
| Нужна полная карта допустимых файлов, root entries, segments и tests | [Атлас файлов SLM](./file-atlas.md) |
| Задача затрагивает product I/O, source hook, domain store, event, lifecycle или external errors | [Runtime-граница business](./business-runtime-boundary.md) |
| Выполняется архитектурное ревью или финальная проверка реализации | [Архитектурная проверка](./validation.md) |
| Неясен layer, направление import или роль `app/compositions/business/infra/ui/shared` | [Слои](./layers.md) |
| Нужно отличить module, component, group, nested module или спроектировать public API | [Модули](./modules.md) |
| Проектируется factory, Api, Deps, domain error или сборка домена | [Business-фабрика](./business-factory.md) |
| Неясно размещение hook/store/service/mapper/provider/type/style | [Сегменты](./segments.md) |
| Решается вынос из `apps/*/src` в `packages/*` | [Монорепозитории](./monorepo.md) |
| Нужен полный пример adapters, builder, state runtime и graph lifecycle | [Business composition](../examples/business-composition.md) |
| Нужна матрица factory-level, assembly и colocated tests | [Тестирование business-модулей](../examples/business-testing.md) |
| Нужен page/route provider, локальный UI store и доступ к готовому graph | [Композиция через Provider](../examples/react/composition-provider.md) |
| Команда выбирает организацию groups внутри `compositions` | [Структуры compositions](../examples/react/composition-structures.md) |
Не используй карту как scaffold checklist. Наличие возможной папки не означает, что её нужно создать.

View File

@@ -1,508 +0,0 @@
---
title: Атлас файлов SLM
description: Карта слоёв, типов modules, root files, segments, public API, tests и запрещённых файловых сочетаний
---
# Атлас файлов SLM
Используй атлас после того, как определены роль изменения и владелец ответственности. Не выбирай архитектуру по желаемому имени файла.
Атлас исчерпывает стандартные архитектурные роли SLM, но не является закрытым списком framework-файлов. Команда может добавить локальный segment или suffix, если его ответственность не дублирует существующую, не нарушает направление зависимостей и закреплена в локальных инструкциях.
## Единицы структуры
| Единица | Что означает | Имеет public API | Владеет runtime |
|---|---|---|---|
| Layer | Верхнеуровневая область ответственности внутри SLM root | Нет общего требования | Зависит от layer |
| Group | Навигационная папка для modules или других groups | Нет, `index.ts` запрещён | Нет |
| Module | Самостоятельный владелец одной ответственности | Да | Может |
| Segment | Папка внутри module по назначению файлов | Нет отдельного внешнего API | Только как часть владельца |
| Component | Презентационная часть родительского module в `ui/` | Локальный `index.ts` допустим | Нет архитектурного runtime |
| Root file | Главный entry/contract конкретного module | Экспортируется module `index.ts` | Зависит от типа module |
Сначала определи module, затем root file, затем необходимые segments. Не создавай все папки из атласа заранее.
## Карта SLM root
```text
src/
├── app/ # framework wiring
├── compositions/ # product tree, graph owners и business integrations
├── business/ # доменные контракты и сценарии
├── infra/ # технические runtime-сервисы
├── ui/ # универсальные UI modules
└── shared/ # детерминированный фундамент
```
В monorepo вместо `src/` границей приложения обычно является `apps/{app}/src/`. Packages находятся выше SLM root и не являются дополнительными слоями.
## Root files modules
| Pattern | Роль | Где допустим |
|---|---|---|
| `{name}.page.tsx` | Готовая page composition | `compositions` |
| `{name}.layout.tsx` | Product layout composition | `compositions` |
| `{name}.screen.tsx` | Уникальный screen leaf/branch | `compositions` |
| `{name}.widget.tsx` | Самостоятельный composition block | `compositions` |
| `{name}.route.tsx` | Route composition и route lifecycle | `compositions` |
| `{name}.entry.tsx` | Готовая точка подключения product tree | `compositions` |
| `{scope}-business-composition.ts` | Non-visual сборка business graph/scope | `compositions` |
| `{name}.ts` | Другой non-visual root, названный по ответственности | `compositions`, nested modules |
| `{name}.tsx` | Root UI/module component без специальной роли | `compositions`, `ui`, nested modules |
| `{domainName}.factory.ts` | Единственный runtime entry business-домена | `business/{domainPath}` |
| `create-{domainName}-business.ts` | Builder одной business-фабрики | `compositions/business/{domainPath}` |
| `{name}.client.ts` | Технический client | `infra/{service}` |
| `{name}.service.ts` | Технический root service, если service и есть module entry | `infra/{service}` |
| `index.ts` | Public API конечного module | В module; запрещён у group |
Root suffix не определяет owner автоматически. Например, `profile.store.ts` остаётся domain store или page UI store в зависимости от смысла state.
## Layer App
`app` содержит только файлы, требуемые framework/runtime entry.
Возможные файлы:
| Файл или pattern | Назначение |
|---|---|
| `main.tsx`, `bootstrap.tsx` | Запуск приложения |
| `app.tsx` | Тонкое подключение application entry/providers |
| `app-router.tsx`, `router.tsx` | Framework route registry |
| `page.tsx`, `layout.tsx`, `route.ts`, `error.tsx`, `not-found.tsx` | Framework-convention files |
| `middleware.ts` | Framework middleware boundary |
| framework metadata/config files | Только framework contract |
Правила:
- framework file импортирует готовый entry/route/page composition через public API;
- product tree собирается в `compositions`, не в `app`;
- business graph, domain store, screen, widget и product provider в `app` запрещены;
- у `app` нет SLM modules и общего `index.ts`;
- framework-specific server/client правила определяет профильный framework skill.
Минимальный пример:
```text
src/app/
├── app-router.tsx
├── app.tsx
└── main.tsx
src/compositions/entries/profile/
├── profile.entry.tsx
└── index.ts
```
## Consumer composition module
Consumer composition собирает product UI и использует готовые `{Domain}Api`.
```text
compositions/{group}/{name}/
├── {name}.{page|layout|screen|widget|route|entry}.tsx # visual entry, если нужен
├── {name}-business-composition.ts # business graph, если module владеет им
├── {name}.ts # другой non-visual entry, если нужен
├── ui/ # presentation components
├── parts/ # nested modules
├── providers/ # provider владельца scope
├── guards/ # route/UI guards над готовым DomainApi
├── hooks/ # access/orchestration hooks
├── stores/ # только локальный UI-state
├── services/ # orchestration готовых API
├── mappers/ # domain result -> ViewModel
├── types/ # module-owned types
├── errors/ # только composition/UI errors, не domain errors
├── lib/ # локальные deterministic helpers
├── config/ # module configuration
├── styles/ # styles module
├── tests/ # scope/integration tests при необходимости
└── index.ts # public API
```
Не все segments обязательны. Создавай только используемые.
Composition module не обязан иметь `.tsx`: request/application business graph использует `{scope}-business-composition.ts`, а другая non-visual orchestration может иметь root `.ts`, названный по ответственности, и public `index.ts`.
Guard принадлежит composition scope, использует готовый `{Domain}Api` и выбирает route/UI outcome. Guard не получает product data напрямую, не создаёт business graph и не импортирует source runtime.
Consumer composition не содержит:
- product SDK/client/generated operation;
- product storage adapter;
- source/query adapter домена;
- domain store implementation;
- domain mapper/error;
- business factory implementation.
Product data поступает только через `{Domain}Api`. `stores/` хранит presentation-only state: sidebar, tab, local step, transient form UI. Product entities и domain state туда не копируются.
### Page/route graph owner
Если composition владеет business graph и lifecycle, возможна структура:
```text
compositions/routes/profile/
├── profile.route.tsx
├── profile-business-composition.ts
├── providers/
│ ├── profile-business.provider.tsx
│ └── profile-business.context.ts
├── hooks/
│ └── use-profile-business.hook.ts
├── types/
│ └── profile-business.type.ts
├── tests/
│ └── profile-business-lifecycle.test.tsx
└── index.ts
```
Правила graph owner:
- graph type перечисляет только реально доступные API;
- `Partial<Business>` с cast к полному graph запрещён;
- graph создаётся в lifecycle владельца;
- domain lifecycle operation запускается после commit и возвращает cleanup;
- raw SDK/client/event bus не импортируется для досборки домена;
- screen/widget не создаёт тот же graph повторно.
## Integration module business
`compositions/business/{domainPath}` является единственным module, который знает одновременно business dependency contract и concrete runtime.
`{domainPath}` означает полный относительный путь конечного business module, а `{domainName}` — имя его последней папки. При groups integration path зеркалирует business path:
```text
business/app/auth/
compositions/business/app/auth/
business/cms/content/
compositions/business/cms/content/
```
```text
compositions/business/{domainPath}/
├── create-{domainName}-business.ts # обязательный builder
├── create-{domainName}-business.test.ts # обязательный assembly test
├── adapters/ # обязательны при runtime dependencies
│ ├── {source}.adapter.ts
│ ├── {storage}.adapter.ts
│ ├── {query}-hook.adapter.ts
│ ├── {state-manager}-state.adapter.ts
│ ├── {event-source}-events.adapter.ts
│ └── {platform}-navigation.adapter.ts
├── types/ # builder/cross-domain/request input types
│ ├── create-{domainName}-business-deps.type.ts
│ └── create-{domainName}-business-request-input.type.ts
├── testing/ # assembly fixtures, не public API
└── index.ts # builder и type-only integration inputs
```
Builder:
1. Явно создаёт или получает runtime instances нужного lifecycle без I/O.
2. Создаёт adapters поверх runtime instances.
3. Передаёт adapters и cross-domain API фабрике.
4. Возвращает готовый `{Domain}Api`.
Browser/application builder без cross-domain dependencies вызывается без аргументов. Request-scoped builder отдельно принимает `requestScopeInput` с request data; concrete client factory импортируется integration module.
Integration module не содержит:
- domain mapper/normalizer;
- domain error;
- business scenario;
- React provider/layout/screen/widget;
- full application graph;
- exported private adapter.
## Business module
Каждый `business/{domainPath}` имеет полный factory contract.
```text
business/{domainPath}/
├── {domainName}.factory.ts # обязательно
├── index.ts # обязательно
├── types/ # обязательно для contracts
│ ├── {domainName}-api.type.ts
│ ├── {domainName}-deps.type.ts
│ ├── {domainName}-factory.type.ts
│ ├── {domainName}-error.type.ts
│ ├── {domainName}-error-code.type.ts
│ ├── {entity}.type.ts
│ ├── {source}-hook-result.type.ts
│ └── {domainName}-state.type.ts
├── errors/
│ └── {domainName}-business.error.ts
├── services/
│ └── {scenario}.service.ts
├── hooks/
│ └── use-{scenario}.hook.ts # wrapper над dependency hook
├── mappers/
│ └── map-{entity}.ts # unknown -> domain
├── lib/
│ └── {domain-helper}.ts
├── config/
│ └── {domainName}.config.ts # deterministic domain constants
└── tests/
└── {domainName}-factory/
├── public-api.test.ts
├── {scenario}.test.ts
└── testing/
└── create-{domainName}-deps.mock.ts
```
Обязательный минимум:
- `{domainName}.factory.ts`;
- `{Domain}Api`, `{Domain}Deps`, `{Domain}Factory`;
- domain error codes и собственная domain error для runtime failure;
- `index.ts` с одним runtime export фабрики и type-only exports;
- factory-level tests каждого public runtime operation;
- integration builder и assembly test для каждого business-модуля, включая dependency-free factory.
Условные файлы:
- `services/` только при выделенном сценарии;
- `hooks/` только для wrapper над dependency hook;
- `mappers/`/`normalizers/`/type guards при внешнем `unknown`;
- `errors/` при runtime operations;
- colocated tests обязательны для каждого mapper, normalizer, type guard и domain error;
- colocated tests services/hooks обязательны при самостоятельной branching, race или другой runtime-safe логике.
В business запрещены:
- `ui/`, React components, providers, layouts и guards;
- concrete `stores/` Zustand/Redux/MobX;
- `adapters/` concrete runtime;
- SDK/client/generated DTO;
- SWR/TanStack Query/Apollo runtime или types;
- React state/effect runtime или types;
- storage/browser/event/env implementation;
- raw external error в public API.
## Infra module
Infra module владеет техническим сервисом без продуктовой модели.
```text
infra/{service}/
├── {service}.client.ts # если module является client
├── {service}.service.ts # если module является service
├── client.ts # допустимый technical entry
├── config/
├── clients/
├── services/
├── transports/
├── hooks/ # technical hooks
├── providers/ # provider technical service
├── ui/ # только technical UI, например theme tooling
├── errors/ # transport/technical errors
├── types/ # technical contracts/DTO
├── lib/
├── tests/
└── index.ts
```
Различай два вида adapter:
| Вид | Где живёт | Что знает |
|---|---|---|
| Transport adapter/client | `infra` | Протокол, SDK, HTTP, transport types |
| Domain dependency adapter | `compositions/business/{domainPath}` | Business `Deps` и конкретный infra runtime |
Infra не содержит business graph, domain state, product provider или domain error. Type-only imports business API не дают infra права агрегировать product graph.
## UI module
UI module предоставляет универсальный UI без business logic и product I/O.
```text
ui/{name}/
├── {name}.tsx # обязательный root component
├── ui/ # внутренние presentation components
├── parts/ # nested UI modules при самостоятельной роли
├── hooks/ # presentation behavior
├── stores/ # только локальный UI-state
├── providers/ # UI scope provider
├── types/ # props и UI contracts
├── styles/
├── lib/ # presentation helpers
├── tests/
└── index.ts
```
UI module может строиться на других UI modules и `shared`. Он не импортирует business, infra или compositions, не получает product data самостоятельно и не выбирает источник.
## Shared
`shared` содержит только детерминированный фундамент без знания о продукте и runtime-state.
```text
shared/
├── lib/
│ └── {utility}/
│ ├── {utility}.ts
│ ├── {utility}.test.ts
│ └── index.ts
├── types/
├── styles/
├── config/ # только product-agnostic constants
├── assets/ # product-agnostic assets при локальном соглашении
└── sprites/ # специализированная группа assets, если используется
```
Shared не содержит:
- product/domain types;
- stores и mutable singletons;
- SDK/client wrappers;
- React providers;
- environment-dependent services;
- imports из других SLM layers.
## Components внутри ui segment
Presentation component родительского module имеет плоскую структуру:
```text
{module}/ui/{component}/
├── {component}.tsx
├── types/
│ └── {component}-props.type.ts
├── styles/
│ └── {component}.module.css
└── index.ts
```
В папке component запрещены:
- `hooks/`, `stores/`, `services/`, `providers/`, `parts/`;
- source calls и scenario hooks;
- imports project code вне parent module, кроме разрешённых UI modules;
- nested components как отдельные architectural folders.
Если это требуется, сущность становится module и перемещается в `parts/` либо на общий composition/UI уровень.
## Nested modules в parts
Каждый элемент `parts/` является полноценным module:
```text
{parent}/parts/{part}/
├── {part}.tsx # visual root, если нужен
├── {part}.ts # non-visual root, если нужен
├── ui/
├── parts/
├── hooks/
├── stores/ # только state ответственности part
├── types/
├── styles/
├── tests/
└── index.ts
```
Одиночные `.tsx`, `.ts` или style files непосредственно в `parts/` запрещены. Если nested module нужен за пределами parent, подними его в минимальный общий scope.
## Segment matrix
| Segment | Consumer composition | Business integration | Business | Infra | UI | Shared |
|---|---|---|---|---|---|---|
| `ui/` | Да | Нет | Нет | Условно, technical UI | Да | Нет |
| `parts/` | Да | Нет | Нет | Условно | Да | Нет |
| `providers/` | Да | Нет | Нет | Да | Условно | Нет |
| `guards/` | Да, над готовым DomainApi | Нет | Нет | Нет | Нет | Нет |
| `hooks/` | Да | Нет | Только wrappers над deps | Technical hooks | Presentation hooks | Нет |
| `stores/` | Только UI-state | Нет, используй `adapters/` | Нет concrete stores | Technical state | Только UI-state | Нет |
| `services/` | Orchestration готовых API | Нет, используй `adapters/` | Domain scenarios над deps | Technical services | Presentation-only | Нет |
| `adapters/` | Нет product adapters | Да | Нет | Только transport adapters по локальному соглашению | Нет | Нет |
| `mappers/` | Domain -> ViewModel | Нет, transport adaptation остаётся в adapter | `unknown` -> domain | Transport mapping | View mapping | Чистые generic transforms |
| `errors/` | UI/composition errors | Нет domain errors | Domain errors | Technical/transport errors | UI errors | Только generic errors |
| `types/` | Module contracts | Integration input types | Domain contracts | Technical contracts/DTO | Props/UI contracts | Product-agnostic types |
| `styles/` | Да | Нет | Нет | Условно | Да | Global/foundation styles |
| `lib/` | Local helpers | Assembly helpers | Domain deterministic helpers | Technical helpers | Presentation helpers | Generic deterministic helpers |
| `config/` | Composition constants | Runtime assembly config | Domain constants | Technical config/env | UI constants | Product-agnostic constants |
| `tests/` | Scope/integration tests | Обязательные assembly tests | Обязательные factory tests | Technical tests | UI tests | Unit tests |
## Имена обычных файлов
| Pattern | Назначение |
|---|---|
| `{name}.type.ts` | Module-owned type |
| `{name}-props.type.ts` | Component props |
| `{name}.hook.ts`, `use-{name}.hook.ts` | Hook владельца |
| `{name}.store.ts` | Concrete store только допустимого owner/scope |
| `{name}.service.ts` | Scenario или technical service по layer |
| `{name}.adapter.ts` | Adapter с явно определённым видом |
| `map-{name}.ts`, `normalize-{name}.ts` | Mapper/normalizer владельца |
| `{name}.provider.tsx` | Provider владельца scope |
| `{name}.guard.tsx` | Route/UI guard consumer composition |
| `{name}.context.ts`, `{name}.context.tsx` | Private context implementation |
| `{name}.error.ts` | Error соответствующего layer |
| `{name}.config.ts` | Configuration владельца |
| `{name}.constant.ts` | Константа владельца |
| `{name}.module.css` | Styles конкретного module/component |
| `{name}.test.ts`, `{name}.test.tsx` | Colocated test |
Suffix описывает техническую форму, но не переносит ownership. `user.type.ts` не становится shared только потому, что это type; `auth.store.ts` не становится infra только потому, что использует Zustand.
## Public API по типам modules
| Module | Runtime exports | Type exports | Не экспортировать |
|---|---|---|---|
| Consumer composition | Entry/provider/access hooks | Props, state/view types | Raw context/store factory/internal parts |
| Business integration | `create{Domain}Business` | Cross-domain/request input types | Adapters, clients, mocks |
| Business | Только `{domainName}Factory` | Api, Deps, Factory, domain/error types | Services, hooks, mappers, error class |
| Infra | Минимальный technical API | Technical contracts | Mutable internals и generated tree без необходимости |
| UI | Root component и доказанные UI helpers | Props/UI types | Internal components/store/context |
| Nested module | Root entry | Props/module types | Parent internals |
Если дочерние layout/screen/widget импортируют access hooks владельца scope, не экспортируй из того же public API готовый entry, который импортирует эти дочерние modules. Раздели scope API и ready entry на отдельные composition modules, чтобы не создать runtime-цикл.
## Tests map
| Проверяемая граница | Размещение | Обязательность |
|---|---|---|
| Business public contract | `business/{domainPath}/tests/{domainName}-factory/` | Обязательно |
| Business mapper/normalizer/type guard/domain error | Рядом с файлом | Обязательно для каждого такого файла |
| Business service/hook wrapper | Рядом с файлом | При самостоятельной branching/race/runtime-safe логике |
| Adapter и builder wiring | `compositions/business/{domainPath}/*.test.ts` | Обязательно для каждого business-модуля |
| Graph lifecycle/provider | Tests graph owner module | При state/subscriptions/resources |
| Consumer composition | Рядом или `tests/` module | По поведению scope |
| Infra transport/service | Внутри infra module | По technical contract |
| UI module/component | Внутри UI module | По интерактивному поведению |
## Запрещённые структуры
```text
business/auth/ui/ # React UI внутри business
business/auth/stores/auth.store.ts # concrete Zustand/Redux store
business/auth/adapters/backend.adapter.ts # concrete runtime adapter
business/auth/types/sdk-response.type.ts # generated/external DTO contract
compositions/pages/profile/services/api.ts # прямой product source
compositions/screens/profile/store.ts # product/domain cache в screen
infra/business/ # product graph/provider в infra
shared/user.type.ts # product domain type в shared
business/app/index.ts # group с public API
compositions/pages/index.ts # group с public API
{module}/parts/hero.tsx # файл вместо nested module
{module}/ui/card/hooks/ # component с собственной логикой
```
## Новый тип файла или segment
Если нужной роли нет в атласе:
1. Назови ответственность файла без технического suffix.
2. Определи module-владельца и допустимые зависимости.
3. Проверь, не является ли файл существующим `service`, `adapter`, `mapper`, `provider` или nested module.
4. Создай новый segment только для нескольких файлов с одной устойчивой ролью.
5. Не создавай global segment на уровне `src/`.
6. Зафиксируй локальное соглашение, если pattern будет повторяться.
7. Обнови template, если новый pattern стал обязательной повторяемой структурой.
Не подгоняй ответственность под красивое дерево. Минимальный корректный module лучше полного scaffold без реального поведения.

View File

@@ -1,148 +0,0 @@
---
title: SLM Design
description: Назначение архитектуры, ключевые принципы и карта разделов документации
---
# Основы SLM Design
Scoped Layered Module Design — модульная архитектура фронтенд-приложений. Код организован по слоям ответственности, а модуль содержит всё, что ему нужно: компоненты, хуки, сторы, типы, стили.
## Рабочий алгоритм
1. Заполни архитектурную карточку из [процесса принятия решения](./decision-process.md): роль, владелец, данные, runtime-capabilities, место, public API, lifecycle и проверки.
2. Сначала выбери слой ответственности, затем module scope, затем segments и только после этого конкретные файлы.
3. Для product data и domain state примени [runtime-границу business](./business-runtime-boundary.md).
4. Выбирай минимальное корректное место и не создавай общий provider, store, package или business-контракт «на будущее».
5. После реализации пройди [архитектурную проверку](./validation.md). Задача не завершена без обязательных business и assembly tests.
## Дополнительные примеры
Каноны ниже достаточны для базового архитектурного решения. Если нужен подробный пример реализации, открой конкретный файл:
- [Композиция через Provider](../examples/react/composition-provider.md) — page-level provider, store и business composition.
- [Структуры compositions](../examples/react/composition-structures.md) — допустимые структуры слоя `compositions`.
- [Business composition](../examples/business-composition.md) — runtime-сборка business-фабрик в `compositions/business/{domain}`.
- [Тестирование business-модулей](../examples/business-testing.md) — factory-level тесты и тесты сборки.
## Разделы спецификации
Спецификация SLM Design состоит из нескольких связанных разделов. Этот обзор даёт общий контекст, а детальные правила описаны дальше:
- [Слои](./layers.md) — уровни организации `src/`, направление зависимостей и зона ответственности каждого слоя.
- [Модули](./modules.md) — границы ответственности, публичный API, типы модулей и отличие модуля от компонента.
- [Атлас файлов SLM](./file-atlas.md) — root files, segments, структуры всех типов modules, public API и tests.
- [Business-фабрика](./business-factory.md) — контракт business-модуля, logic API, deps, доменные ошибки и сборка фабрик.
- [Runtime-граница business](./business-runtime-boundary.md) — capabilities фабрики, adapters, hooks, stores и безусловные domain errors.
- [Сегменты](./segments.md) — внутренние папки модуля (`ui/`, `parts/`, `hooks/`, `types/` и другие) и правила размещения файлов.
- [Монорепозитории](./monorepo.md) — применение SLM в `apps/` и `packages/`, правила выноса общих слоёв и ограничения для business/compositions.
- [Архитектурная проверка](./validation.md) — блокирующие gates создания, рефакторинга и ревью.
Рекомендуемый порядок чтения: процесс решения → runtime-граница business → архитектурная проверка → атлас или подробности только нужной ветки.
## Преимущества
### Единый слой композиции
Страницы, маршруты и крупные продуктовые части интерфейса собираются в `compositions`. Слой не навязывает жёсткую структуру: команда может использовать `pages/layouts/screens/widgets` или другую организацию под свой фреймворк и продукт.
### Вертикальная организация домена
Бизнес-домен не разбивается по техническим слоям — сценарии, сущности, типы, hooks, services и mappers живут в одном модуле. Это сокращает время навигации и упрощает сопровождение: доменная логика локализована.
### Dependency Injection без фреймворков
Runtime-зависимости business-модуля реализуются через фабрики — модуль декларирует что ему нужно, а composition-сборка предоставляет зависимости. Домены изолированы от SDK, storage и backend-клиентов без DI-контейнеров и шин событий.
### Разделение ответственности без перегрузки слоёв
Композиция приложения (`compositions/`), сервисы приложения (`infra/`), UI-кит (`ui/`) и общие ресурсы (`shared/`) — разные слои с разной природой. Ни один слой не превращается в свалку разнородного кода.
### Графовая композиция там, где она нужна
Внутри `compositions` допускается граф импортов через публичный API. Это позволяет page-level store, provider или сборку business-фабрик использовать одновременно в layout, screen и widget, не перенося продуктовый runtime-state в `infra` или `shared`.
### Горизонтальная инкапсуляция
Вложенные модули (`parts/`) и публичные API позволяют нескольким разработчикам работать над одной областью приложения параллельно, не затрагивая код друг друга.
### Колокация по умолчанию
Код начинает жизнь рядом с местом использования и поднимается в общие слои только при реальной потребности. Глобальные слои не засоряются преждевременными абстракциями.
### Масштабирование через группировку
При росте проекта слои не теряют структуру — модули группируются по естественным признакам: композиции по страницам и маршрутам, бизнес-домены по субдоменам, UI-компоненты по уровню абстракции.
### Адаптация к монорепозиториям
SLM применяется внутри каждого приложения, а `packages/*` используются только для общего кода из слоёв `ui`, `infra` и `shared`. `compositions` и бизнес-домены остаются внутри приложений, чтобы не размывать продуктовые границы.
## Происхождение
SLM Design вырос на основе:
- **Feature-Sliced Design** — слоистая структура, публичный API модуля, направление зависимостей
- **Vertical Slice Architecture** — модуль как вертикальный срез, содержащий всё необходимое
- **Screaming Architecture** — структура проекта «кричит» о назначении: открыл `business/auth` — видишь авторизацию
- **Colocation Principle** — код живёт рядом с местом использования
## Пример структуры проекта
```text
src/
├── app/
├── compositions/
│ ├── business/
│ │ ├── auth/
│ │ └── user/
│ ├── pages/
│ │ ├── home/
│ │ ├── profile/
│ │ └── product-detail/
│ ├── layouts/
│ │ ├── main/
│ │ └── dashboard/
│ ├── screens/
│ │ ├── home/
│ │ └── profile/
│ └── widgets/
│ ├── page-heading/
│ └── promo-banner/
├── business/
│ ├── auth/
│ ├── catalog/
│ ├── orders/
│ └── chat/
├── infra/
│ ├── theme/
│ ├── i18n/
│ ├── backend-api/
│ └── logger/
├── ui/
│ ├── button/
│ ├── input/
│ ├── modal/
│ ├── toast/
│ └── dropdown/
└── shared/
├── lib/
├── types/
└── styles/
```
## Принципы
- **Композиция — отдельный слой.** Страницы, маршруты и крупные продуктовые части интерфейса собираются в `compositions`.
- **Структура композиции свободна.** Команда сама выбирает организацию внутри `compositions`; базовая рекомендация — `pages/layouts/screens/widgets`.
- **Домен — единое целое.** Доменная модель, сценарии, типы, services и mappers живут в одном business-модуле. Concrete data/state/query hooks передаются фабрике через adapters.
- **Колокация.** Код рождается рядом с местом использования и поднимается только при необходимости.
- **Зависимости однонаправлены за пределами compositions.** `app` подключает `compositions`; `compositions` связывает `business`, `infra`, `ui` и `shared`; `business` вызывает concrete runtime-возможности только через переданные фабрике `deps`.
- **Product data проходит через business.** Page, layout, screen и widget не обращаются к product source напрямую.
- **Ошибки принадлежат домену.** Из business API выходят только собственные domain errors со стабильным `code`.
- **Внутри compositions допустим граф.** Composition modules могут импортировать друг друга через public API.
- **Архитектура — каркас, не клетка.** Правила фиксируют границы ответственности и public API, а внутреннюю форму композиции определяет команда.

View File

@@ -1,285 +0,0 @@
---
title: Слои
description: Иерархия слоёв от app до shared, правила зависимостей и зона ответственности каждого слоя
---
# Слои
Раздел описывает слои SLM: что такое слой, какие бывают, как между ними направлены зависимости и какие правила действуют на каждом.
## Определение
**Слой — уровень организации кода внутри `src/`. Каждый слой отвечает за свою область и задаёт правила для кода внутри: направление импортов, именование, допустимые связи между модулями.**
## Группы слоёв
Слои делятся на три группы:
| Группа | Слои | Описание |
|--------|------|----------|
| Композиция | `app`, `compositions` | Подключают приложение к фреймворку и собирают страницы, маршруты и крупные продуктовые части интерфейса |
| Ядро | `business`, `infra`, `ui` | Реализация продукта: бизнес-домены, техсервисы, UI-кит |
| Фундамент | `shared` | Общие ресурсы: утилиты, хелперы, стили, конфиги |
## Направление зависимостей
Любой импорт между модулями — только через публичный API.
```text
app → compositions
compositions → business | infra | ui | shared
business → shared
infra → infra | shared
ui → ui | shared
shared -/→ SLM-слои
```
- `app` подключает приложение к фреймворку и импортирует готовые composition modules
- `compositions` импортирует `business`, `infra`, `ui`, `shared`
- `business` импортирует только собственные файлы, детерминированный `shared`, чистые библиотеки и type-only контракты других business-модулей
- `infra` импортирует `infra` и `shared`
- `ui` импортирует `ui` и `shared`
- `shared` не импортирует другие SLM-слои
- `business`, `infra`, `ui`, `shared` не импортируют `compositions`
- Внутри `compositions` направление импортов между composition modules не фиксируется, но импорты разрешены только через публичный API и не должны создавать runtime-циклы
- Модули `business` вызывают любую runtime-capability только через `deps` фабрики; source/query hooks, stores, events и platform APIs также являются dependencies
- `import type` не разрешает переносить ownership: business не импортирует generated DTO, SDK/client/store/query types, а infra не объявляет product graph через type-only business imports
- Pure deterministic third-party libraries допустимы внутри business, если не имеют I/O/state/hidden environment и не протекают в public contract
## Слой App
Точка входа приложения. Отвечает за запуск, роутинг и подключение composition modules к фреймворку.
В отличие от остальных слоёв, `app/` не содержит модулей SLM. Здесь живут только инфраструктурные файлы, которые не могут быть никаким другим слоем: файлы фреймворка роутинга, точка запуска и код инициализации.
### Требования
- Не содержит модулей SLM — только файлы фреймворка, роутинг, инициализация
- Содержит: файлы маршрутов, bootstrap, обработку ошибок верхнего уровня (404, error boundary), подключение глобальных стилей и ассетов
- Провайдеры, guards, layouts, screens и страницы — только подключает готовые из `compositions`, не реализует
- Не содержит бизнес-логику, UI-компоненты, хуки, сторы, сервисы
- Никем не импортируется
## Слой Compositions
`compositions/` — слой сборки страниц, маршрутов и крупных продуктовых частей интерфейса.
На этом слое собираются page, layout, screen, widget и другие composition modules. Они связываются между собой и с нижними слоями: `business`, `infra`, `ui`, `shared`.
SLM не фиксирует жёсткую структуру внутри `compositions`. Команда выбирает организацию под фреймворк, роутинг, CMS и продуктовую задачу.
Базовая рекомендация:
```text
src/compositions/
├── business/
├── pages/
├── layouts/
├── screens/
└── widgets/
```
`business`, `pages`, `layouts`, `screens` и `widgets` внутри `compositions` не являются отдельными SLM-слоями. Это группы composition modules.
`compositions/business/{domain}` используется для runtime-сборки business-фабрик с реальными зависимостями приложения. Это не business-слой, а composition module, который адаптирует `infra`, SDK, storage и browser API к `deps` business-фабрики.
Внутри `compositions` различай три роли:
| Роль | Ответственность |
|---|---|
| Integration module | `compositions/business/{domain}` реализует adapters и собирает одну business-фабрику |
| Graph owner | Page/route/provider/request scope собирает готовые business API и управляет lifecycle |
| Consumer composition | Page/layout/screen/widget вызывает `{Domain}Api` и не знает concrete product source |
Consumer composition получает продуктовые данные только через business API. Прямые SDK/HTTP/generated/storage вызовы разрешены только dependency adapters интеграционного модуля домена.
Технический infra-сервис без product data можно использовать в composition напрямую. Если такой сервис нужен business, он всё равно описывается business-owned capability и передаётся фабрике через adapter.
Если business-домены сгруппированы, `compositions/business` повторяет ту же группировку: `business/app/auth` соответствует `compositions/business/app/auth`, `business/cms/content` соответствует `compositions/business/cms/content`. Это не default-структура, а способ сохранить навигацию в крупных проектах.
Composition module может содержать обычные сегменты SLM: `ui/`, `parts/`, `hooks/`, `stores/`, `services/`, `mappers/`, `types/`, `styles/`, `lib/`, `config/`, `providers/`.
Page-level store, provider, guard или business composition размещаются внутри page composition module, если они нужны всей странице.
```text
compositions/pages/profile/
├── profile.page.tsx
├── profile-business-composition.ts
├── providers/
├── hooks/
├── stores/
├── types/
└── index.ts
```
Layout, screen и widget могут получать через public API page composition локальный UI-state и готовые `{Domain}Api`. Product data не копируется в page store как параллельный источник истины.
```ts
import { useProfilePageStore } from '@/compositions/pages/profile'
```
Внутри `compositions` направление импортов между composition modules не фиксируется. Допустим граф, но все импорты идут только через public API.
```ts
// Хорошо
import { useProfilePageStore } from '@/compositions/pages/profile'
// Плохо
import { useProfilePageStore } from '@/compositions/pages/profile/hooks/use-profile-page-store.hook'
```
### Требования
- `compositions` содержит composition modules страниц, маршрутов и крупных продуктовых частей интерфейса
- Структура внутри `compositions` выбирается командой
- Базовая рекомендация: `business/`, `pages/`, `layouts/`, `screens/`, `widgets/`
- `business`, `pages`, `layouts`, `screens`, `widgets` внутри `compositions` являются группами composition modules
- `compositions/business/{domain}` собирает конкретную business-фабрику с runtime-зависимостями; при группировке используется зеркальный путь `compositions/business/{group}/{domain}`
- Concrete product sources, source/query hooks, domain stores, browser APIs и events подключаются только adapters интеграционного module
- Page/layout/screen/widget используют product data только через `{Domain}Api`
- Builder одной фабрики явно создаёт или получает scoped runtime instances без I/O, создаёт adapters поверх них и передаёт adapters factory; integration logic не пишется inline
- Providers, stores, guards и business composition размещаются внутри того composition module, которому они принадлежат
- Внутри `compositions` импорты между composition modules разрешены в любую сторону, но только через public API
- Runtime-циклы между composition modules запрещены
- Deep imports внутрь composition modules запрещены
- `business`, `infra`, `ui` и `shared` не импортируют `compositions`
## Слой Business
Бизнес-домены приложения: auth, catalog, orders, checkout, chat. Каждый домен — отдельный модуль со своими типами, hooks, services, mappers и доменной логикой.
Слой входит в группу «Ядро». Импортирует собственные файлы, детерминированный `shared/`, чистые библиотеки и type-only контракты других business-модулей. Каждый бизнес-модуль создаёт публичный API фабрики в корне. Любые runtime-capabilities передаются через аргументы фабрики.
Business объединяет то, что в FSD разделено на `features` и `entities`: пользовательские сценарии и бизнес-сущности живут вместе, внутри одного домена. Внутри домена сегменты разделяют ответственность: `types/` — доменная модель и dependency contracts, `hooks/` и `services/` — wrappers над переданными capabilities, `mappers/` — доменная нормализация, `lib/` — детерминированные доменные утилиты.
Business-модуль не содержит React-компоненты, layouts, guards, providers и page-level wrappers. Визуальный fallback, route boundary и привязка logic API к React tree размещаются в `compositions`; error mapping и допустимый доменный fallback остаются в business.
```text
src/business/
├── auth/
├── catalog/
├── orders/
├── checkout/
└── chat/
```
Когда количество доменов затрудняет навигацию — можно ввести группировку по крупным предметным областям. Группа — папка для организации, не модуль и не public API.
```text
src/business/
├── app/
│ ├── auth/
│ ├── profile/
│ └── orders/
└── cms/
├── content/
├── media/
└── navigation/
```
`app` и `cms` здесь являются группами доменов. Модулями остаются конечные папки: `auth`, `profile`, `content`, `media`, `navigation`.
### Требования
- Один модуль = один бизнес-домен
- Business-домены можно группировать при необходимости; группа не является модулем и не содержит `index.ts`
- Циклические зависимости между доменами запрещены
- Публичный API фабрики — через фабрику в корне модуля (`{name}.factory.ts`). `index.ts` экспортирует только фабрику и type-only экспорты, без исключений
- Фабрика возвращает только logic API: hooks, selectors, command/query methods, scenario services
- Business-модуль не содержит React-компоненты и не возвращает компоненты из фабрики
- Любые runtime-capabilities — только через `deps` фабрики: product sources, hooks, stores, events, technical services, env и browser APIs
- Business не импортирует React/SWR/query/store runtime; concrete hooks и stores реализуются adapters
- Реальные SDK, API-клиенты, storage, state/query runtime, env и browser API подключаются в `compositions/business/{domain}` или зеркальном пути при группировке
- Business нормализует внешние результаты и подставляет только собственные domain errors
- Доменные типы (`User`, `Product`) живут здесь, не в `shared/`
## Слой infra
Техсервисы приложения: theme, i18n, API-адаптеры, logger, realtime. Каждый сервис — отдельный модуль.
Слой входит в группу «Ядро». Импортирует `infra/` и `shared/`.
Отличие от `shared/`: infra — инфраструктура приложения (сервисы, темы, адаптеры к API), `shared/` — общие ресурсы (утилиты, хелперы, стили, конфиги).
```text
src/infra/
├── theme/
├── i18n/
├── backend-api/
├── maps-api/
├── logger/
├── feature-flags/
└── realtime/
```
### Требования
- Один модуль = один техсервис
- Импортирует `infra/` и `shared/`
- Не содержит продуктовые composition modules конкретных страниц или маршрутов
## Слой UI
UI-кит без бизнес-логики: button, carousel, toast, modal.
Слой входит в группу «Ядро». Импортирует `ui/` и `shared/`.
Компоненты строятся друг на друге: `button` использует `icon`, `carousel` использует `button`.
```text
src/ui/
├── button/
├── input/
├── icon/
├── carousel/
├── modal/
├── toast/
├── dropdown/
├── tabs/
└── tooltip/
```
Когда количество компонентов затрудняет навигацию — вводится группировка на примитивы и композиции. Примитивы (`button`, `icon`, `input`) не импортируют композиции. Композиции (`carousel`, `modal`, `dropdown`) строятся на примитивах.
```text
src/ui/
├── primitives/
│ ├── button/
│ ├── input/
│ ├── icon/
│ └── badge/
└── composites/
├── carousel/
├── modal/
├── dropdown/
├── tabs/
└── tooltip/
```
### Требования
- Не содержит бизнес-логику
- Импортирует только `ui/` и `shared/`
## Слой Shared
Общие ресурсы: утилиты, хелперы, стили, конфиги. Не знает о бизнес-домене.
Слой входит в группу «Фундамент» — ни о ком не знает, никого не импортирует.
Отличие от `infra/`: infra — инфраструктура приложения (сервисы, темы, адаптеры к API), `shared/` — общие ресурсы (утилиты, хелперы, стили, конфиги).
Отличие от `ui/`: UI-компоненты (button, carousel, modal) живут в слое `ui/`, а не здесь.
```text
src/shared/
├── lib/
├── types/
├── styles/
└── sprites/
```
### Требования
- Не имеет runtime-состояния
- Не знает о продуктовых composition modules

View File

@@ -1,300 +0,0 @@
---
title: Модули
description: Структура модуля, типы (композиционный, UI, бизнес, инфра), публичный API, отличие модуля от компонента
---
# Модули
Раздел описывает модуль как границу ответственности в SLM: что считается модулем, что такое компонент внутри модуля и как модуль взаимодействует с остальным кодом.
## Определение
**Модуль — минимальная архитектурная единица SLM. Он живёт на одном из слоёв, владеет конкретной областью ответственности и предоставляет наружу только публичный API.**
Модуль может содержать всё, что нужно этой области: компоненты, вложенные модули, хуки, сторы, сервисы, типы, стили, конфиги и утилиты. Набор сегментов не фиксирован — модуль включает только то, что реально нужно.
Модуль не обязан быть UI-блоком. Это может быть page composition, layout composition, screen composition, widget composition, бизнес-домен, инфраструктурный сервис или UI-kit сущность.
Главная граница модуля — не папка, а ответственность.
## Компонент
**Компонент — презентационная единица модуля, которая находится только в `ui/` своего родительского модуля и отвечает за отображение части интерфейса.**
Компонент не является архитектурной единицей: он не владеет сценарием, зависимостями, данными или внутренней структурой. Он работает только внутри границы родительского модуля.
> Компонент отображает. Модуль организует.
Компонент не может:
- Импортировать код проекта за пределами родительского модуля. Единственное исключение — компоненты слоя `ui`, если правила слоёв разрешают родительскому модулю импортировать `ui`.
- Владеть архитектурными зависимостями.
- Содержать вложенные компоненты: папка компонента включает только `{name}.tsx`, `index.ts`, `styles/`, `types/`.
- Содержать вложенные модули.
- Делать внешние запросы.
- Самостоятельно получать данные.
- Выбирать источник данных.
- Композировать данные.
- Вызывать сценарные хуки.
- Оркестрировать сценарий.
- Композировать модули.
- Решать, как устроен процесс.
- Содержать бизнес-логику.
- Содержать сценарную логику.
Компонент может рендерить другие компоненты: соседние компоненты из `ui/` своего модуля, компоненты слоя `ui` и элементы, переданные через props. Запрет «содержать» относится к структуре папки, а не к JSX-разметке: декомпозиция компонентов остаётся плоской внутри `ui/` родительского модуля.
Если компоненту требуется что-то из запрещённого списка, он перестаёт быть компонентом и должен быть оформлен как модуль.
```text
auth/
├── ui/
│ └── logout-button/
│ ├── logout-button.tsx
│ ├── styles/
│ │ └── logout-button.module.css
│ ├── types/
│ │ └── logout-button-props.type.ts
│ └── index.ts
└── index.ts
```
## Что считается модулем
Модулем считается папка, которая представляет самостоятельную область ответственности и имеет публичную границу.
## Группы модулей
Слой может содержать не только модули, но и группы модулей. По умолчанию модуль лежит прямо в слое; группу вводят только когда модулей много и нужна явная классификация.
Группа — навигационная папка внутри слоя или другой группы. Она классифицирует модули по типу, предметной области, продуктовой зоне или runtime-назначению, но сама не является модулем.
Жёсткие правила группы:
- группа не имеет public API;
- группа не содержит `index.ts`;
- группа не импортируется внешним кодом;
- группа не владеет логикой, состоянием, deps или runtime-сборкой;
- группа может содержать другие группы и конечные модули.
Модулем считается конечная папка с самостоятельной ответственностью и публичной границей.
```text
src/business/
├── app/ # группа
│ ├── auth/ # business-модуль
│ └── profile/ # business-модуль
└── cms/ # группа
├── content/ # business-модуль
└── media/ # business-модуль
```
Примеры модулей:
- `compositions/pages/home/` — модуль page composition.
- `compositions/layouts/main/` — модуль layout composition.
- `compositions/screens/profile/` — модуль screen composition.
- `compositions/widgets/page-heading/` — модуль widget composition.
- `business/auth/` — модуль бизнес-домена.
- `infra/theme/` — модуль инфраструктурного сервиса.
- `ui/button/` — модуль UI-kit сущности.
- `compositions/pages/home/parts/hero-section/` — вложенный модуль page composition.
Не считаются модулями:
- `ui/`, `parts/`, `hooks/`, `types/`, `styles/`, `config/`, `providers/` — это сегменты.
- `compositions/pages/`, `business/app/`, `business/cms/` — это группы, если в них нет `index.ts`.
- `compositions/pages/home/ui/user-card/` — это компонент, если он находится в `ui/` и соблюдает ограничения компонента.
## Типы модулей
Тип модуля определяет обязательный корневой файл и стартовую структуру.
### Композиционный модуль
Композиционный модуль — модуль внутри `compositions`, который участвует в сборке страниц, маршрутов и крупных продуктовых частей интерфейса.
Он может быть page, layout, screen, widget, block, entry-point, CMS-entry, route segment или другим типом композиции, выбранным командой.
```text
compositions/pages/profile/
├── profile.page.tsx
├── profile-business-composition.ts
├── providers/
├── hooks/
├── stores/
├── parts/
├── types/
└── index.ts
```
Композиционный модуль может импортировать другие composition modules через public API. Это отличие слоя `compositions`: внутри него допускается графовая композиция.
При этом deep imports запрещены.
```ts
// Хорошо
import { useProfilePageStore } from '@/compositions/pages/profile'
// Плохо
import { useProfilePageStore } from '@/compositions/pages/profile/hooks/use-profile-page-store.hook'
```
### UI-модуль
Модуль строится вокруг основного UI-компонента и обязан иметь основной `.tsx` файл в корне:
```text
button/
├── button.tsx
└── index.ts
```
`ui/` внутри такого модуля используется только для компонентов, которые помогают корневому `.tsx` файлу.
### Бизнес-модуль
Бизнес-модуль — модуль, который строится вокруг публичного API фабрики.
Business-модуль содержит доменную логику, типы, hooks, services, mappers и helpers. Он не содержит React-компоненты и не возвращает компоненты из фабрики.
Бизнес-модуль обязан иметь фабрику в корне:
```text
auth/
├── auth.factory.ts
├── index.ts
└── types/
```
Фабрика возвращает публичный API модуля для использования в runtime.
### Инфраструктурный модуль
Инфраструктурный модуль — модуль, который строится вокруг технического сервиса или интеграции.
Инфраструктурный модуль не обязан иметь фиксированный корневой файл. Его структура определяется природой сервиса.
```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/ # компоненты модуля, кроме business-модулей
├── parts/ # вложенные модули
├── providers/ # провайдеры модуля
├── 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 экспорты. Это жёсткое правило без исключений.
```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'
```
Composition module экспортирует через `index.ts` только безопасный контракт, который нужен другим composition modules или `app`: page/layout/screen/widget, provider, hooks доступа, типы. Внутренние stores, context objects и функции создания состояния не экспортируются без необходимости.
Stateful module по умолчанию не экспортирует raw context, `StoreApi`, mutable singleton, persistence key или concrete adapter. Экспортируй domain/technical commands, selectors и access hooks. Каждый mutable export требует реального внешнего consumer и отдельного обоснования.
Если layout, screen или widget импортируют hooks из page composition, не смешивайте в одном public API готовую page composition и hooks для дочерних модулей: это может создать runtime-цикл.
```ts
// compositions/pages/profile/index.ts
export { ProfilePageProvider } from './providers/profile-page.provider'
export { useProfilePageStore } from './hooks/use-profile-page-store.hook'
export { useProfileBusinessComposition } from './hooks/use-profile-business-composition.hook'
export type { ProfilePageState } from './types/profile-page-state.type'
```
## Фабрика
Business-модуль всегда экспортирует фабрику. Фабрика лежит в корне модуля (`{name}.factory.ts`), типизируется через `{Name}Factory` и возвращает публичный logic API фабрики.
Всё, что нужно внешнему коду в runtime, должно быть частью API, который возвращает фабрика.
Фабрика не возвращает React-компоненты, layouts, guards, boundaries, providers или page-level wrappers. Business-модуль не содержит React-компоненты: UI-решения домена размещаются в `compositions`, а полностью универсальные UI-компоненты — в `ui`.
Модуль без runtime-capabilities экспортирует фабрику без аргументов. Модуль с зависимостями экспортирует фабрику, принимающую `deps`: API других доменов и внешние возможности, описанные бизнес-языком. Source/query hooks, state stores, subscriptions, browser API, clock и другие runtime-механизмы также считаются dependencies. Типы всегда экспортируются напрямую через `export type`, но `import type` не разрешает протащить в business generated DTO, SDK/store/query contracts.
Runtime-сборка фабрики с реальными SDK, storage, infra-клиентами, source hooks, stores, events и browser API происходит в `compositions/business/{domain}` через отдельные adapters. Builder явно создаёт или получает scoped runtime instances без I/O, создаёт adapters поверх них и передаёт adapters factory. Конечный граф business API собирается в месте, которое владеет lifecycle: page composition, route composition, application-lifetime composition provider, request scope или test setup.
### Примеры
Подробные правила фабрики см. в [Business-фабрика](./business-factory.md).
Пример runtime-сборки business-фабрик см. в [Business composition](../examples/business-composition.md).
Пример page-level Provider в React см. в [Композиция через Provider](../examples/react/composition-provider.md).
Примеры разных структур слоя `compositions` см. в [Структуры compositions](../examples/react/composition-structures.md).
## Жизненный цикл
Модуль рождается на самом низком уровне использования и поднимается выше только при реальной потребности.
- Нужен одной странице, route branch или крупной продуктовой части интерфейса → внутри соответствующего composition module.
- Нужен нескольким частям одной страницы → внутри page composition или другого общего composition scope.
- Нужен нескольким страницам или маршрутам → отдельный composition module внутри `compositions`.
- Абстрактный UI без бизнес-логики → `ui/`.
- Сценарий, product data contract, domain state, тип или доменная логика → `business/{domain}/`.
- Concrete implementation business dependency → adapter в `compositions/business/{domain}/`.
- Технический сервис → `infra/`.
- Общая чистая утилита → `shared/`.
Подъём — обычный рефакторинг в рамках задачи, а не отдельная активность.

View File

@@ -1,229 +0,0 @@
---
title: Монорепозитории
description: Правила применения SLM Design для frontend-проектов, находящихся в монорепозитории
---
# Монорепозитории
Раздел описывает, как применять SLM Design, когда фронтенд-проекты находятся в одном монорепозитории. В нём показано, что остаётся внутри приложений, что можно выносить в `packages/` и какие ограничения действуют для общих пакетов.
## Определение
**Монорепозиторий — внешний уровень организации нескольких фронтенд-приложений и общих пакетов. SLM применяется внутри каждого приложения, а frontend-пакеты, относящиеся к SLM, содержат переиспользуемый код, вынесенный из слоёв `ui`, `infra` и `shared`.**
## Базовая структура
Каждое приложение внутри `apps/` сохраняет собственную SLM-структуру в `src/`.
```text
repo/
├── apps/
│ ├── web/
│ │ └── src/
│ │ ├── app/
│ │ ├── compositions/
│ │ ├── business/
│ │ ├── infra/
│ │ ├── ui/
│ │ └── shared/
│ └── admin/
│ └── src/
│ └── ...
└── packages/
├── ui/
│ ├── button/ # самостоятельный пакет UI-модуля
│ ├── input/ # самостоятельный пакет UI-модуля
│ └── modal/ # самостоятельный пакет UI-модуля
├── infra/
│ ├── theme/ # самостоятельный пакет infra-модуля
│ ├── backend-api/ # самостоятельный пакет infra-модуля
│ └── logger/ # самостоятельный пакет infra-модуля
└── shared/ # единый shared-пакет
├── package.json
└── src/
├── lib/ # переиспользуемые утилиты
├── helpers/ # переиспользуемые helpers
└── index.ts
```
`apps/{app}/src` — граница SLM-приложения. `packages/*` находятся выше SLM и не добавляют новые архитектурные слои.
## Группировка frontend-пакетов
Frontend-пакеты, вынесенные из SLM-приложений, рекомендуется группировать по источнику кода: `ui`, `infra`, `shared`.
```text
packages/ui/* # пакеты UI-модулей
packages/infra/* # пакеты infra-модулей
packages/shared # единый shared-пакет
```
Эта группировка повторяет названия SLM-слоёв для навигации, но сама не является слоистой архитектурой внутри `packages/`. Монорепозиторий может содержать другие пакеты: tooling, конфиги, SDK, схемы, e2e и другие технические пакеты вне SLM.
## Пакет и модуль
Пакет не равен SLM-модулю: модуль — архитектурная единица внутри слоя приложения, package — единица монорепозитория для переиспользования, владения, сборки и публикации.
В `packages/ui/*` размещаются пакеты самостоятельных UI-модулей. В `packages/infra/*` размещаются пакеты самостоятельных инфраструктурных модулей. `packages/shared` устроен иначе: это единый пакет для переиспользуемых утилит, helpers и другого фундаментального кода без привязки к конкретному приложению.
```text
packages/ui/button/
packages/ui/modal/
packages/infra/theme/
packages/infra/backend-api/
packages/shared/
```
## Что остаётся в приложении
Слои `app`, `compositions` и `business` остаются внутри конкретного приложения.
```text
apps/web/src/app/
apps/web/src/compositions/
apps/web/src/business/
```
`app` привязан к фреймворку и entry points приложения.
`compositions` привязан к страницам, маршрутам и крупным продуктовым частям интерфейса конкретного приложения. Этот слой не выносится в `packages/*`, потому что отражает продуктовую сборку приложения.
`business` не выносится в `packages/*`. Домены остаются рядом со сценариями приложения, чтобы не превращать монорепозиторий в общий бизнес-слой.
## Что можно выносить
В пакеты выносится только код из `ui`, `infra` и `shared`, который уже нужен двум фронтенд-приложениям либо имеет явно зафиксированный межприложенческий ownership/reuse-контракт.
| Группа | Что выносить | Пример |
|--------|--------------|--------|
| `packages/ui/*` | Самостоятельные UI-модули без бизнес-логики | `packages/ui/button` |
| `packages/infra/*` | Самостоятельные технические сервисы | `packages/infra/backend-api` |
| `packages/shared` | Общие утилиты, helpers и фундаментальный код | `packages/shared` |
По умолчанию начинай внутри приложения. Пакет можно создать до второго consumer только при явном архитектурном решении, известном consumer и стабильном межприложенческом контракте. Абстрактная «общая природа» сама по себе недостаточна.
## UI-пакеты
В `packages/ui/*` размещаются переиспользуемые UI-модули.
```text
packages/ui/button/
├── package.json
└── src/
├── button.tsx
├── styles/
├── types/
└── index.ts
```
UI-пакет не содержит бизнес-логику, обращения к API, сценарные хуки приложения и композицию страниц.
## Infra-пакеты
В `packages/infra/*` размещаются переиспользуемые инфраструктурные модули.
```text
packages/infra/backend-api/
├── package.json
└── src/
├── clients/
├── config/
├── types/
└── index.ts
```
Привязанные к конкретному приложению сервисы остаются в `apps/{app}/src/infra`. Например, локализация со словарями конкретного продукта остаётся в приложении; общим пакетом может быть только переиспользуемый i18n-движок.
## Shared-пакет
`packages/shared` является единым пакетом.
```text
packages/shared/
├── package.json
└── src/
├── lib/
├── helpers/
└── index.ts
```
В `packages/shared` сразу выносится общий фундаментальный код: чистые функции, helpers, утилиты, независимые константы и другой код без знания о продукте.
Проектные стили, типы приложения, продуктовые конфиги и ресурсы, завязанные на одно приложение, в общий `shared` не выносятся.
## Имена пакетов и импорты
Путь импорта задаётся `name` в `package.json`, а не расположением директории.
```json
{
"name": "@repo/theme"
}
```
```text
packages/infra/theme/package.json
```
```ts
import { ThemeProvider } from '@repo/theme'
```
Пакеты должны импортироваться только через публичный API. Deep imports внутрь пакета запрещены.
```ts
// Хорошо
import { Button } from '@repo/button'
// Плохо
import { Button } from '@repo/button/src/button'
```
## Зависимости
На уровне монорепозитория приложения зависят от пакетов, а пакеты не зависят от приложений.
```text
apps → packages
packages -/→ apps
```
Внутри приложения продолжает действовать обычное направление зависимостей SLM: `app` подключает `compositions`, `compositions` связывает `business`, `infra`, `ui` и `shared`, а `business` вызывает concrete runtime-capabilities только через переданные фабрике `deps`.
Пакеты не должны нарушать природу своей группы: `packages/ui/*` не импортирует `packages/infra/*`, `packages/shared` не импортирует другие группы, а `packages/infra/*` не знает о приложениях.
## Когда не выносить
Не выносите код в пакет, если он не может быть использован в двух и более фронтенд-приложениях, зависит от роутинга или страниц, содержит бизнес-логику, отражает продуктовую композицию конкретного интерфейса или не имеет стабильного публичного API.
Фактическое использование в одном приложении допустимо для package только при зафиксированном втором consumer или внешнем ownership/reuse-контракте. Иначе сохрани локальную колокацию.
```text
# Плохо
apps/web/src/compositions/pages/home/parts/promo-section/
packages/ui/promo-section/
```
Если блок нужен только одной странице или отражает продуктовую композицию конкретного приложения, он остаётся локальным composition module.
## Конфигурационные пакеты
Конфигурационные пакеты не относятся к SLM-архитектуре.
Если в монорепозитории есть общие настройки TypeScript, ESLint, сборки или форматирования, они относятся к tooling-инфраструктуре репозитория. Такие пакеты могут находиться в `packages/`, но их структура зависит от выбранного инструментария и не участвует в правилах слоёв внутри `src/`.
## Правила
- SLM применяется внутри каждого `apps/{app}/src`.
- Frontend-пакеты, вынесенные из SLM-приложений, группируются в `packages/ui`, `packages/infra`, `packages/shared`.
- Группы `packages/ui`, `packages/infra`, `packages/shared` не являются SLM-слоями.
- В `packages/ui/*` размещаются пакеты самостоятельных UI-модулей.
- В `packages/infra/*` размещаются пакеты самостоятельных инфраструктурных модулей.
- `packages/shared` является единым пакетом для переиспользуемых утилит и helpers.
- Модуль можно размещать в package при реальном втором consumer или явном межприложенческом ownership/reuse-контракте.
- `app`, `compositions` и `business` не выносятся в пакеты.
- Проектные стили, типы приложения и продуктовые конфиги не выносятся в `packages/shared`.
- Пакеты не импортируют приложения.
- Межпакетные импорты идут только через публичный API.
- Deep imports внутрь пакетов запрещены.
- Локальная колокация важнее преждевременного выноса в `packages/*`.

View File

@@ -1,222 +0,0 @@
---
title: Сегменты
description: Сегменты внутри модуля (ui/, parts/, hooks/ и др.), назначение и правила размещения файлов
---
# Сегменты
Раздел описывает сегменты SLM: что такое сегмент, какие бывают и что в каждом из них лежит.
## Определение
**Сегмент — папка внутри модуля, которая группирует файлы по назначению. Набор сегментов не фиксирован — модуль включает только те, которые ему нужны. Команда сама определяет какие сегменты используются в проекте — архитектура даёт рекомендацию.**
## Обзор
| Сегмент | Содержимое |
|---------|------------|
| `ui/` | Презентационные компоненты родительского модуля |
| `parts/` | Вложенные модули со своими сегментами |
| `providers/` | Провайдеры модуля |
| `hooks/` | React-хуки |
| `stores/` | Сторы состояния |
| `services/` | Сценарии и операции владельца module |
| `mappers/` | Трансформация данных между форматами |
| `types/` | TypeScript-типы и интерфейсы |
| `styles/` | Стили |
| `lib/` | Утилиты и хелперы модуля |
| `config/` | Константы и конфигурация |
Сегменты не являются обязательными. Например, `providers/` нужен только модулю, который владеет провайдерами. Если provider, store или guard относится к конкретной странице или маршруту, он размещается внутри соответствующего composition module, а не в `infra` или `shared`.
Business-модули не используют `ui/` для React-компонентов. Доменный logic API живёт в factory/services/hook wrappers/mappers. React tree, guards, layouts и visual fallbacks размещаются в consumer compositions; concrete domain source hooks/stores — в adapters `compositions/business/{domain}`.
## Сегмент ui/
Презентационные компоненты родительского модуля. `ui/` содержит только компоненты, которые отвечают за отображение части интерфейса и не выходят за границы своего модуля.
Компонент в `ui/`:
- Находится в собственной папке.
- Может содержать только `{name}.tsx`, `index.ts`, `styles/`, `types/`.
- Не содержит вложенные компоненты и модули — папка компонента остаётся плоской.
- Может рендерить соседние компоненты из `ui/` своего модуля и компоненты слоя `ui`, если правила слоёв разрешают родительскому модулю импортировать `ui`.
- Не импортирует другой код проекта за пределами родительского модуля.
- Не делает внешние запросы.
- Не вызывает сценарные хуки.
- Не получает данные самостоятельно, не выбирает источник данных и не композирует данные.
- Не содержит бизнес-логику или сценарную логику.
Если UI-сущности нужно что-то за пределами этих ограничений, она должна быть оформлена как модуль. Полная граница описана в разделе [Компонент](./modules.md#компонент).
Корневой файл модуля в `ui/` не размещается. Он лежит в корне модуля: `{module-name}.tsx`.
```text
user/
├── ui/
│ ├── 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-сущности нужна внутренняя декомпозиция, сценарная логика, получение данных или собственные архитектурные зависимости — это уже не компонент в `ui/`, а модуль в `parts/`.
## Сегмент parts/
Вложенные модули со своими сегментами. `parts/` содержит только модули: каждый элемент `parts/` — папка полноценного модуля с собственным публичным API. Отдельные `.tsx`, стили, хуки или произвольные файлы в `parts/` не размещаются.
```text
compositions/pages/home/
├── parts/
│ ├── hero-section/
│ │ ├── hero-section.tsx
│ │ ├── styles/
│ │ ├── parts/
│ │ │ └── top-banner/
│ │ │ ├── top-banner.tsx
│ │ │ └── index.ts
│ │ └── index.ts
│ └── features-section/
│ ├── features-section.tsx
│ ├── hooks/
│ └── index.ts
├── home.page.tsx
└── index.ts
```
Отличие от `ui/`: элемент `parts/` — модульная папка со своими сегментами. Элемент `ui/` — компонент родительского модуля без собственной архитектурной ответственности.
Вложенность `parts/` инкапсулирует область разработки горизонтально: каждый разработчик работает в своём `parts/`-модуле, не затрагивая чужие. Это снижает конфликты при параллельной разработке.
Если вложенный модуль обрастает своими `parts/` — это сигнал, что он достаточно самостоятельный для подъёма на уровень выше.
## Сегмент providers/
Провайдеры модуля: React Context providers, провайдеры scope-состояния, провайдеры композиции фабрик или другие обёртки, которые принадлежат модулю.
```text
providers/
├── profile-page.provider.tsx
└── profile-business-composition.provider.tsx
```
Provider размещается в том модуле, который владеет соответствующим состоянием или композицией. Page-level provider живёт в page composition module; application-level provider, завязанный на фреймворк, подключается в `app`, но реализуется в нижнем подходящем слое.
## Сегмент hooks/
React-хуки модуля. Инкапсулируют логику, состояние, подписки, побочные эффекты.
```text
hooks/
├── use-auth.hook.ts
├── use-session.hook.ts
└── use-permissions.hook.ts
```
В business-модуле `hooks/` содержит только wrappers, созданные поверх dependency hooks, переданных фабрике. Business не импортирует React state/effect APIs, SWR, TanStack Query, Apollo или другой hook runtime напрямую.
Concrete source hook реализуется adapter-ом в `compositions/business/{domain}` и возвращает business-owned result type. Business wrapper нормализует данные и заменяет source error собственной domain error.
## Сегмент stores/
Сторы состояния composition/infra/UI module. Конкретная реализация зависит от выбранного state manager (Zustand, MobX, Redux и т.д.).
```text
stores/
├── auth.store.ts
└── session.store.ts
```
Если состояние нужно всей странице, concrete store живёт в page composition module. Если состояние относится к бизнес-домену, business владеет state model, transitions и state port. Concrete Zustand/Redux/MobX adapter factory реализуется в `compositions/business/{domain}`, передаётся через `deps`, принимает initial domain state от business-фабрики и возвращает concrete port.
Для каждого store определи creator, scope, количество instances и cleanup. Module singleton допустим только для явно доказанного application/process lifetime.
## Сегмент services/
Сценарии и операции module. Содержимое зависит от слоя и владельца.
```text
services/
├── auth.service.ts
└── token.service.ts
```
Правила по слоям:
- `business/services` реализует доменные сценарии только поверх `deps` фабрики;
- `compositions/business/{domain}/adapters` реализует concrete product/runtime dependencies, а не `services/` обычной composition;
- composition `services/` может оркестрировать готовые business API и технические infra-сервисы, но не обращаться к product source напрямую;
- `infra/services` реализует технический сервис или transport;
- `ui` и `shared` не выполняют product I/O.
Business service не импортирует SDK, generated API, HTTP-клиент, storage, env, browser API, React/SWR/query runtime или concrete store напрямую.
## Сегмент mappers/
Функции трансформации данных на границе ответственности module.
```text
mappers/
├── map-user.ts
├── map-product.ts
└── map-order-to-dto.ts
```
В business-модулях mappers защищают public contract от ненадёжной runtime-границы: преобразуют `unknown` в доменную модель, отклоняют невалидные структуры и не импортируют concrete DTO SDK.
Dependency adapter может преобразовать доменные аргументы в transport payload, но не создаёт доменную модель из ответа, domain error или business fallback. Domain-to-ViewModel mapping принадлежит потребительской composition, если описывает только представление.
## Сегмент types/
TypeScript-типы и интерфейсы модуля. Доменные типы, DTO, пропсы компонентов.
```text
types/
├── user.type.ts
└── session.type.ts
```
В business-модулях `types/` содержит собственные доменные типы, `{Domain}Api`, `{Domain}Deps`, `{Domain}Factory`, dependency hook/state result types и доменные error codes. Generated DTO, SDK-типы, `StoreApi`, query-library types и типы HTTP-клиента не входят в контракт business-модуля.
## Сегмент styles/
Стили модуля. Формат зависит от выбранного подхода (CSS Modules, SCSS, CSS-in-JS и т.д.).
```text
styles/
├── auth.module.css
└── login-form.module.css
```
## Сегмент lib/
Утилиты и хелперы, специфичные для модуля. Чистые функции без побочных эффектов.
```text
lib/
├── validate-email.ts
└── format-phone.ts
```
Отличие от `shared/lib/`: здесь лежат утилиты, нужные только этому модулю. Общие утилиты — в `shared/lib/`.
## Сегмент config/
Константы и конфигурация модуля: маршруты, лимиты, дефолтные значения.
```text
config/
├── routes.ts
└── constants.ts
```

View File

@@ -1,214 +0,0 @@
---
title: Архитектурная проверка
description: Блокирующие проверки SLM перед завершением создания, рефакторинга и архитектурного ревью
---
# Архитектурная проверка
Не считай задачу завершённой только потому, что код компилируется или UI отображается. Проверь архитектурное решение, runtime-цепочку и обязательные тестовые границы.
## Проверка владельца
- У каждой ответственности один явный владелец.
- Product state и сценарии принадлежат business-домену.
- Page/route UI-state принадлежит соответствующему composition module.
- Concrete technical service принадлежит infra-модулю.
- Concrete business dependency adapter принадлежит `compositions/business/{domain}`.
- Provider находится у владельца scope, а не в generic infra-модуле.
- Код не поднят в общий слой или package без реального consumer.
## Проверка runtime-цепочки
Для каждого `{Domain}Api` проследи цепочку сборки:
```text
graph owner
→ per-domain builder
→ dependency adapters + business factory
→ {Domain}Api
→ provider/entry
```
И отдельно цепочку вызова:
```text
consumer
→ {Domain}Api
→ business scenario
→ {Domain}Deps
→ dependency adapter
→ source/runtime
```
Если отсутствует хотя бы одно необходимое звено, домен не подключён. Тип будущего API, пустой provider или неиспользуемый builder не заменяет runtime-сборку.
Для каждого route/page entry проверь, что он достигает готового composition module. `app` не должен реализовывать screen/layout/product wiring самостоятельно.
## Проверка business imports
В production-коде `business/**` не должно быть прямых runtime или type-only imports из concrete runtimes:
- `infra`, `compositions`, `app`;
- SDK/generated clients;
- SWR/TanStack Query/Apollo;
- Zustand/Redux/MobX/RxJS store runtime;
- React state/effect runtime;
- storage/browser API;
- env/event bus/clock/random implementations.
Разрешены собственные файлы, type-only business contracts, `shared` без runtime-capabilities и чистые детерминированные библиотеки.
Проверь не только import paths, но и re-export/barrel/helper, который может скрывать запрещённую зависимость.
## Проверка deps
- Каждая runtime-capability присутствует в `{Domain}Deps`.
- Контракт принадлежит business и назван доменным языком.
- В `Deps` нет client/SDK/generated operation/StoreApi/query-library types.
- Dependency hook возвращает business-owned source result.
- State dependency использует business-owned state port.
- External result имеет `unknown`, если требует runtime validation.
- Subscription возвращает cleanup.
- Cross-domain API сужен до реально используемых методов.
## Проверка adapters
- Для каждой runtime-capability есть явный adapter.
- Adapter находится в `compositions/business/{domain}`.
- Builder не содержит inline integration logic.
- Adapter не формирует domain error.
- Adapter не выполняет domain normalization.
- Adapter не выбирает business fallback.
- Private adapters отсутствуют в public `index.ts`.
- Concrete transport imports не протекают в обычные consumer compositions.
## Проверка product data
- Page/layout/screen/widget получает product data через `{Domain}Api`.
- Нет прямого вызова SDK/client/generated operation из потребительской composition.
- Нет product storage access в UI/component/composition service.
- DTO не используется как domain/view contract без business normalization.
- Один источник не имеет параллельного прямого и business-пути.
## Проверка ошибок
Для каждого public runtime operation проверь:
- rejected dependency;
- synchronous throw dependency;
- `undefined`/`null`/empty body;
- объект неправильной формы;
- source hook error;
- invalid state/storage value;
- неизвестную runtime-ошибку.
Во всех случаях наружу выходит только domain error со стабильным `code`. Source error сохраняется в `cause`, но не становится consumer contract. Technical failure и malformed response нельзя превращать в fallback; fallback разрешён только для валидного доменного исхода.
Не оставляй формулировку «domain error, если контракт это обещает». Business public contract всегда обещает только domain errors.
## Проверка state и hooks
- Business владеет domain state model, но не concrete state manager.
- Zustand/Redux/MobX store создаётся adapter-ом, не factory.
- SWR/Query hook создаётся adapter-ом, не business-модулем.
- Dependency hook является non-throwing/non-Suspense и передаёт technical error через business-owned result.
- Business wrapper вызывает dependency hook и возвращает собственный result type.
- Business wrapper преобразует error result и ошибки callbacks в domain errors.
- Store/query library types отсутствуют в public API и `Deps`.
- Определены creator, scope, количество instances и cleanup.
- Module singleton используется только при явно доказанном application/process lifetime.
- Provider не создаёт ложное впечатление владения instance, созданным на module scope.
## Проверка graph
- Per-domain builders собирают только свои фабрики.
- Graph owner назван и соответствует lifecycle.
- Домены создаются в топологическом порядке.
- Runtime-циклы отсутствуют.
- Один и тот же graph не копируется по нескольким providers без обоснования scope.
- Screen/widget не собирает graph самостоятельно.
- Provider value имеет точный тип.
- Нет `Partial<Graph>` с unchecked cast к полному graph.
- Subscription, timer, socket и другие resources имеют cleanup/dispose.
- Pending operation не может записать stale state после invalidation/unmount без явно принятой политики.
## Проверка public API
- Межмодульные импорты идут через реальный public entrypoint.
- Import alias/package export существует физически.
- Group не имеет `index.ts`.
- Business `index.ts` экспортирует runtime только factory, остальное через `export type`.
- Integration module экспортирует builder и необходимые type-only integration input contracts.
- Raw context, raw store, mutable singleton, adapter, generated operation и persistence key закрыты.
- Каждый export имеет реального внешнего consumer.
- Deep imports отсутствуют, включая tests уровня public contract.
## Проверка тестов
Business-модуль не завершён без factory-level tests.
Обязательный минимум:
1. Форма public API фабрики.
2. Happy path каждого runtime method/hook.
3. Invalid dependency response.
4. Rejected dependency.
5. Synchronous throw каждого обычного method/callback/state/lifecycle dependency. Dependency hooks проверяются отдельным non-throwing contract.
6. Domain error `code` и `cause`.
7. Порядок side effects и остановка после ошибки.
8. State transitions и concurrent calls.
`compositions/business/{domain}` не завершён без assembly tests:
1. Factory получает правильные adapters.
2. Каждый adapter вызывает нужный runtime source с правильным payload.
3. Builder не делает I/O при создании API.
4. Cross-domain API передан в правильном виде.
5. Private adapters не раскрыты public API.
6. Adapter пробрасывает source error без создания domain error.
7. Dependency hook не бросает и не использует Suspense/throw-on-error mode.
8. Lifecycle cleanup проверен, если есть subscriptions/resources.
Colocated tests обязательны для mappers, normalizers, type guards, domain errors и другой внутренней runtime-safe логики. Они дополняют, но не заменяют factory-level tests. Подробная матрица находится в [Тестировании business-модулей](../examples/business-testing.md).
Проверь наличие исполняемого test script и test runner именно в изменяемом workspace. Root task без локального script не является выполненной тестовой инфраструктурой.
## Проверка целостности репозитория
- Все imports разрешаются.
- Все упомянутые modules и public entrypoints существуют.
- Direct runtime packages объявлены в package текущего workspace.
- Старый provider/store/source path удалён после миграции, если больше не используется.
- Нет speculative scaffold с пустым graph, несуществующими доменами или placeholder contracts.
- Template исправлен, если именно он системно создаёт нарушение.
- Выполнены доступные typecheck, tests, lint/build и `git diff --check`.
## Формат архитектурного ревью
Для каждого нарушения укажи:
1. Путь и строку.
2. Нарушенный invariant.
3. Runtime или maintenance риск.
4. Минимальную корректную границу.
5. Необходимые tests.
Отделяй обязательное нарушение от необязательного улучшения. Не предлагай большую миграцию, если нарушение можно устранить локально без создания второй архитектуры.
## Финальный gate
Перед завершением ответь «да» на все вопросы:
- Архитектурная роль изменения определена?
- Владелец ответственности и state определён?
- Все runtime-capabilities проходят через правильную границу?
- Product data проходит через business API?
- Business вызывает только переданные deps и собственную детерминированную логику?
- Наружу выходят только domain errors?
- Adapters существуют и закрыты?
- Graph и lifecycle определены?
- Public API минимален и разрешим?
- Обязательные tests созданы и запущены?
- Изменение не оставило старый параллельный путь?
Если хотя бы один ответ «нет», задача не завершена.

24
docs/en/index.md Normal file
View File

@@ -0,0 +1,24 @@
---
layout: home
title: SLM Design in English
titleTemplate: false
sidebar: false
hero:
name: English edition
text: Translation is planned
tagline: The Russian specification is currently the only normative source. The English edition will preserve its structure and rule IDs.
actions:
- theme: brand
text: Open Russian specification
link: /ru/specification/
- theme: alt
text: Language selection
link: /
features:
- title: No partial translation
details: An incomplete English rule set is not published as normative documentation.
- title: Stable identifiers
details: Future translated requirements will use the same SLM rule IDs as the Russian source.
- title: Equal URL structure
details: English documentation is reserved under /en/ alongside the Russian /ru/ section.
---

View File

@@ -1,390 +0,0 @@
---
title: Business composition
description: Пример runtime-сборки business-фабрик в compositions/business
---
# Business composition
`compositions/business/{domain}` — composition module, который собирает конкретную business-фабрику с реальными runtime-зависимостями приложения.
Этот модуль не является бизнес-доменом. Он находится на слое `compositions`, потому что связывает `business`, `infra`, SDK, storage, browser API и другие внешние runtime-источники.
Это единственная integration-зона concrete product dependencies. Page, layout, screen и widget не импортируют SDK/client/storage напрямую и получают product data только через готовый `{Domain}Api`.
## Структура
```text
src/compositions/business/
├── auth/
│ ├── create-auth-business.ts
│ ├── create-auth-business.test.ts
│ ├── adapters/
│ │ ├── phone-auth.adapter.ts
│ │ ├── session.adapter.ts
│ │ ├── auth-session-events.adapter.ts
│ │ └── zustand-auth-state.adapter.ts
│ └── index.ts
├── user/
│ ├── create-user-business.ts
│ ├── create-user-business.test.ts
│ ├── adapters/
│ │ ├── user-profile.adapter.ts
│ │ └── user-storage.adapter.ts
│ ├── types/
│ │ └── create-user-business-deps.type.ts
│ └── index.ts
└── content/
├── create-content-business.ts
├── create-content-business.test.ts
├── adapters/
│ └── content-api.adapter.ts
└── index.ts
```
Если business-домены сгруппированы, `compositions/business` повторяет тот же относительный путь. Например: `business/app/auth` соответствует `compositions/business/app/auth`, `business/cms/content` соответствует `compositions/business/cms/content`.
Сегменты добавляются только по необходимости, но каждая concrete business dependency всегда оформляется отдельным файлом в `adapters/`. Не оставляй короткий adapter inline внутри builder.
## Ответственность
`compositions/business/{domain}` отвечает за adapter composition:
- создаёт или получает внешние клиенты из `infra`;
- отдельными adapters адаптирует SDK, API, storage, source/query hooks, state managers, events и browser API к `deps` business-модуля;
- вызывает business-фабрику;
- принимает API других business-модулей, если текущий домен зависит от них;
- экспортирует готовый `{Domain}Api` через builder-функцию;
- тестирует сборку и корректность адаптеров.
`compositions/business/{domain}` не должен содержать доменную логику. Если код описывает бизнес-правило, маппинг доменной модели, доменную ошибку или сценарий, он должен жить в соответствующем `business`-модуле.
`compositions/business/{domain}` не должен содержать React-компоненты, layouts, guards, providers и page-level wrappers. Применение logic API в React tree выполняется в обычных composition modules страниц, layouts, screens или widgets.
Builder не реализует dependencies inline. Он явно создаёт scoped runtime instances без I/O, создаёт adapters поверх них и передаёт adapters фабрике.
## Business-контракт
Business-модуль объявляет dependency contract.
```ts
// business/auth/types/auth-deps.type.ts
import type { AuthState } from './auth-state.type'
import type { VerifyPhoneCodeData } from './verify-phone-code-data.type'
export type AuthDeps = {
phoneAuth: {
requestCode: (phone: string) => Promise<unknown>
resendCode: (challengeId: string) => Promise<unknown>
verifyCode: (data: VerifyPhoneCodeData) => Promise<unknown>
}
session: {
setToken: (token?: string | null) => void
useToken: () => string | null | undefined
}
sessionEvents: {
onInvalidated: (listener: () => void) => () => void
}
state: {
create: (initialState: AuthState) => {
get: () => AuthState
set: (state: AuthState) => void
useState: () => AuthState
}
}
}
```
Business-модуль не знает, через какой SDK, backend или storage реализованы эти возможности.
## Adapter composition
Composition-адаптер знает про конкретный runtime и приводит его к business-контракту.
```ts
// compositions/business/auth/adapters/phone-auth.adapter.ts
import type { AuthDeps } from '@/business/auth'
import type { AuthApiClient } from '@/infra/backend-api'
export const createPhoneAuthAdapter = (authApiClient: AuthApiClient): AuthDeps['phoneAuth'] => ({
requestCode: (phone) => {
return authApiClient.authOtp.phoneStart({ body: { phone } })
},
resendCode: (challengeId) => {
return authApiClient.authOtp.phoneResend({ body: { challengeId } })
},
verifyCode: (data) => {
return authApiClient.authOtp.phoneVerify({ body: data })
},
})
```
Адаптер не формирует доменные ошибки и не выбирает доменный `code`. Он может вернуть результат внешнего вызова или пробросить ошибку dependency. Решение о доменном коде принимает business-модуль.
Плохо:
```ts
export const createVerifyPhoneCode = (
authApiClient: AuthApiClient,
): AuthDeps['phoneAuth']['verifyCode'] => async (data) => {
try {
return await authApiClient.authOtp.phoneVerify({ body: data })
} catch (error) {
throw new AuthBusinessError('AUTH_PHONE_CODE_VERIFY_FAILED', error)
}
}
```
Проблема: composition-адаптер начал владеть доменной ошибкой.
Хорошо:
```ts
export const createVerifyPhoneCode = (
authApiClient: AuthApiClient,
): AuthDeps['phoneAuth']['verifyCode'] => (data) => {
return authApiClient.authOtp.phoneVerify({ body: data })
}
```
## State adapter
Доменное состояние принадлежит business-контракту, но concrete state manager остаётся снаружи business.
```ts
// compositions/business/auth/adapters/zustand-auth-state.adapter.ts
import { useStore } from 'zustand'
import { createStore } from 'zustand/vanilla'
import type { AuthDeps, AuthState } from '@/business/auth'
export const authStateAdapter: AuthDeps['state'] = {
create: (initialState) => {
const store = createStore<AuthState>()(() => initialState)
return {
get: store.getState,
set: (state) => store.setState(state),
useState: () => useStore(store),
}
},
}
```
`authFactory` выбирает initial domain state и вызывает `deps.state.create(initialState)`. Он не импортирует Zustand и не раскрывает `StoreApi` через public contract. Adapter только создаёт concrete store с переданным состоянием и не выбирает доменную политику.
Для SWR, TanStack Query и других source hooks действует то же правило: adapter реализует business-owned hook contract, business wrapper нормализует `data`, заменяет source `error` собственной domain error и возвращает собственный result type.
## Lifecycle adapter
External event также передаётся через business-owned contract.
```ts
// compositions/business/auth/adapters/auth-session-events.adapter.ts
import type { AuthDeps } from '@/business/auth'
import { onAuthSessionInvalidated } from '@/infra/backend-api'
export const authSessionEventsAdapter: AuthDeps['sessionEvents'] = {
onInvalidated: onAuthSessionInvalidated,
}
```
Business API предоставляет domain-level operation `startSessionInvalidationTracking()`. Внутри business она вызывает `deps.sessionEvents.onInvalidated`, выполняет доменный state transition и возвращает cleanup wrapper. Ошибки регистрации, callback и cleanup заменяются `AuthBusinessError`.
Graph owner запускает operation после commit и вызывает возвращённый cleanup при unmount, как показано в полном provider ниже. Provider не импортирует raw infra event и не связывает его с business command самостоятельно.
## Builder одного домена
Builder собирает одну business-фабрику.
```ts
// compositions/business/auth/create-auth-business.ts
import { authFactory } from '@/business/auth'
import { createBackendApiClient } from '@/infra/backend-api'
import { authSessionEventsAdapter } from './adapters/auth-session-events.adapter'
import { authStateAdapter } from './adapters/zustand-auth-state.adapter'
import { createPhoneAuthAdapter } from './adapters/phone-auth.adapter'
import { createSessionAdapter } from './adapters/session.adapter'
export const createAuthBusiness = () => {
const authApiClient = createBackendApiClient()
return authFactory({
phoneAuth: createPhoneAuthAdapter(authApiClient),
session: createSessionAdapter(),
sessionEvents: authSessionEventsAdapter,
state: authStateAdapter,
})
}
```
Browser/application builder без cross-domain зависимостей вызывается без аргументов. Он явно создаёт runtime instances и передаёт их private adapter factories. Client/adapter constructors не выполняют I/O, не читают storage/env неявно и не запускают subscriptions; lifecycle каждого instance соответствует lifecycle builder result.
Request-scoped builder принимает отдельный `requestScopeInput` только с request data, а concrete client factory импортирует сам. Не используй application singleton для request credentials, cookies или tenant context.
## Cross-domain зависимости
Если один business-модуль зависит от API другого business-модуля, builder принимает уже собранный API.
```ts
// compositions/business/user/types/create-user-business-deps.type.ts
import type { AuthApi } from '@/business/auth'
export type CreateUserBusinessDeps = {
authApi: Pick<AuthApi, 'useAuth'>
}
```
```ts
// compositions/business/user/create-user-business.ts
import { userFactory } from '@/business/user'
import { createBackendApiClient } from '@/infra/backend-api'
import { createUserProfileAdapter } from './adapters/user-profile.adapter'
import { createUserStorageAdapter } from './adapters/user-storage.adapter'
import type { CreateUserBusinessDeps } from './types/create-user-business-deps.type'
export const createUserBusiness = (deps: CreateUserBusinessDeps) => {
const apiClient = createBackendApiClient()
return userFactory({
authApi: deps.authApi,
profile: createUserProfileAdapter(apiClient),
storage: createUserStorageAdapter(),
})
}
```
Правила:
- сначала создаются независимые домены;
- затем создаются домены, которым нужны API уже созданных доменов;
- browser/application builder deps содержат только API других business-фабрик;
- request-scoped builder отделяет cross-domain API от `requestScopeInput` с request data;
- зависимость сужается через `Pick`, если нужен один метод;
- циклические runtime-зависимости между business API запрещены;
- если появляется цикл, нужно пересмотреть границы доменов или вынести общий сценарий в отдельный домен.
## Сборка графа в месте использования
Конечный граф создаётся там, где понятен lifecycle: page provider, route composition, application-lifetime composition provider, request scope или test setup. Слой `app` только подключает готовую composition.
```tsx
// compositions/routes/profile/providers/profile-business.provider.tsx
'use client'
import { createContext, useEffect, useState, type ReactNode } from 'react'
import { createAuthBusiness } from '@/compositions/business/auth'
import { createUserBusiness } from '@/compositions/business/user'
type ProfileBusiness = {
authApi: ReturnType<typeof createAuthBusiness>
userApi: ReturnType<typeof createUserBusiness>
}
export const ProfileBusinessContext = createContext<ProfileBusiness | null>(null)
const createProfileBusiness = (): ProfileBusiness => {
const authApi = createAuthBusiness()
const userApi = createUserBusiness({ authApi })
return { authApi, userApi }
}
export const ProfileBusinessProvider = ({ children }: { children: ReactNode }) => {
const [business] = useState(createProfileBusiness)
useEffect(() => {
return business.authApi.startSessionInvalidationTracking()
}, [business.authApi])
return (
<ProfileBusinessContext.Provider value={business}>
{children}
</ProfileBusinessContext.Provider>
)
}
```
Route-level `ProfileBusinessProvider` владеет lifecycle graph. `compositions/business/*` только предоставляет чистые функции сборки. React Strict Mode может повторно вызвать lazy initializer в development, поэтому factory, builder и adapter constructors не выполняют I/O и не запускают subscriptions.
Graph owner импортирует builders, но не raw SDK/client/event bus для «досборки» конкретного домена. Если external event влияет на domain state, event subscription является частью `{Domain}Deps`; business API предоставляет domain-level lifecycle operation, которую provider запускает в effect и cleanup которой вызывает при unmount. Registration и cleanup errors преобразуются business-модулем в domain errors.
## Public API
`index.ts` composition-модуля экспортирует builder и type-only deps, если builder зависит от других business API.
```ts
// compositions/business/auth/index.ts
export { createAuthBusiness } from './create-auth-business'
```
```ts
// compositions/business/user/index.ts
export { createUserBusiness } from './create-user-business'
export type { CreateUserBusinessDeps } from './types/create-user-business-deps.type'
```
Не экспортируй из public API:
- внутренние SDK-клиенты;
- generated operation trees;
- private adapters;
- test mocks;
- helpers, которые нужны только для сборки.
Если адаптер нужен нескольким composition-модулям, сначала проверь, не является ли это infra-сервисом. Не поднимай адаптер в `shared` только ради удобного импорта.
## Как не превратить сборку в кашу
Признаки плохой сборки:
- один файл создаёт все API-клиенты, все dependency-адаптеры и все фабрики;
- рядом лежат unrelated helpers для разных доменов;
- dependency-адаптеры смешаны с domain mappers;
- Zustand/SWR/SDK logic написана прямо внутри builder;
- graph owner напрямую связывает raw infra event с business command;
- business-правила реализованы в `compositions/business`;
- public API экспортирует внутренние адаптеры;
- невозможно протестировать сборку одного домена отдельно.
Что делать вместо этого:
- один домен runtime-сборки — один composition module;
- dependency-адаптеры держать рядом с конкретной сборкой домена;
- большие dependency-адаптеры выносить в `adapters/`;
- типы сборщика выносить в `types/`, если они перестали быть локальными;
- доменные mappers оставлять в `business/{domain}/mappers`;
- тестировать сборку домена отдельно от полной сборки приложения.
## Тестирование сборки
Тесты `compositions/business/{domain}` не заменяют factory-level тесты business-модуля.
Они проверяют только composition-риск:
- правильные dependency-адаптеры переданы в фабрику;
- API другой фабрики передан в нужном виде;
- SDK operation вызывается с ожидаемым payload;
- storage/browser adapter соответствует dependency-контракту;
- state/query adapter соответствует business-owned contract и не раскрывает library types;
- сборка не делает запросы во время создания business API;
- client/adapter constructors не выполняют import-time I/O, storage access или subscriptions;
- lifecycle operation запускается владельцем scope и вызывает cleanup;
- минимальный API-клиент не тянет лишние generated-операции.
Factory-level поведение самого домена тестируется в `business/{domain}/tests/{domain}-factory`.
## Чеклист
- Runtime-сборка находится в `compositions/business/{domain}`.
- Business-модуль не импортирует реальные SDK, API или storage.
- Dependency-адаптер реализован на composition-слое.
- State/query runtime реализован adapter-ом, а не импортирован business-модулем.
- Файлы внутри модуля сборки разнесены по ответственности.
- Runtime-зависимости между доменами передаются через builder deps.
- Builder deps содержат только API других собранных business-фабрик.
- Request-scoped builder отделяет cross-domain API от `requestScopeInput`.
- Public API composition-модуля не раскрывает internal adapters.
- Builder не содержит inline integration logic.
- Lifecycle operation запускается после commit и имеет cleanup.
- Сборка покрыта тестами на корректность связки deps и адаптеров.
- Business-поведение покрыто factory-level тестами в business-модуле.

View File

@@ -1,364 +0,0 @@
---
title: Тестирование business-модулей
description: Factory-level и colocated unit-тесты для business-фабрик SLM
---
# Тестирование business-модулей
Business-модуль тестируется как доменный контракт приложения. Главный контракт business-модуля — фабрика и API, который она возвращает.
Factory-level тесты обязательны для каждого business-модуля. Assembly tests обязательны для `compositions/business/{domain}`. Внутренние colocated unit-тесты добавляются для runtime-safe логики и не заменяют проверку public API фабрики.
## Уровни тестов
Полное изменение домена проверяется на трёх границах:
1. Factory-level тесты.
2. Assembly tests dependency adapters и builder.
3. Colocated unit-тесты внутренней runtime-safe логики.
Factory-level тесты отвечают на вопрос: работает ли домен снаружи через публичный API фабрики.
Assembly tests отвечают на вопрос: правильно ли concrete runtime реализует `Deps` и передан фабрике.
Colocated unit-тесты отвечают на вопрос: надёжна ли внутренняя runtime-safe механика, на которой держится публичный контракт.
## Factory-level тесты
Размещение:
```text
business/{domain}/tests/{domain}-factory/
```
Пример:
```text
business/user/tests/user-factory/
├── public-api.test.tsx
├── use-current-user.test.tsx
├── update-current-user-profile.test.ts
├── get-stored-user-agreements.test.ts
└── testing/
└── create-user-deps.mock.ts
```
Factory-level тесты импортируют модуль только через public API.
```ts
import { userFactory } from '@/business/user'
```
Factory-level тесты не импортируют:
- `services/*`;
- `hooks/*`;
- `mappers/*`;
- `lib/*`;
- `errors/*`;
- любые deep imports business-модуля.
## Что покрывать на factory-level
Каждый runtime-метод, который возвращает фабрика, должен иметь factory-level тесты.
Обязательно проверяются:
- полный публичный runtime API фабрики;
- happy path каждого метода;
- edge cases публичного контракта;
- корректные ответы DI-зависимостей;
- пустые ответы DI-зависимостей;
- невалидные ответы DI-зависимостей;
- rejected promise от dependency;
- синхронное исключение dependency;
- преобразование внешних ошибок в доменные ошибки;
- преобразование ошибок source hooks, stores и subscriptions в доменные ошибки;
- стабильный доменный `code` для каждой ошибки public contract;
- сохранение исходной ошибки в `cause`;
- отсутствие зависимости public contract от `status`, `message`, `response` и других внешних полей ошибки;
- порядок side effects;
- отсутствие следующих side effects после ошибки;
- hooks, если фабрика возвращает hooks.
Пример:
```ts
const deps = createUserDepsMock({
profile: {
useCurrent: createCurrentUserSourceHookMock({ data: sourceUser }),
},
})
const userApi = userFactory(deps)
await userApi.updateCurrentUserProfile(data)
const result = renderHook(() => userApi.useCurrentUser())
```
Тест проверяет поведение `userApi`, а не внутреннее устройство `createUpdateCurrentUserProfile`.
## Public API тест
У каждого business-модуля должен быть тест, который фиксирует публичный runtime API фабрики.
```ts
it('returns stable user business API', () => {
const userApi = userFactory(createUserDepsMock())
expect(Object.keys(userApi).sort()).toEqual([
'getStoredUserAgreements',
'updateCurrentUserProfile',
'useCurrentUser',
])
})
```
Такой тест не заменяет сценарные тесты методов. Он только фиксирует форму API и защищает от случайного удаления или переименования методов.
## DI-границы
Любая dependency фабрики считается ненадёжной runtime-границей.
Для каждой dependency нужно проверить минимум:
- корректный успешный ответ;
- `undefined`;
- `null`;
- пустой объект;
- объект неправильной формы;
- rejected promise;
- синхронный throw обычного method/callback/state/lifecycle dependency;
- повторные вызовы;
- смену результата dependency hook или domain state.
Если dependency является callback'ом, проверяется порядок вызовов и payload.
Если dependency работает с storage, проверяются битые, устаревшие и отсутствующие данные.
## Hooks через фабрику
Hooks, которые возвращает фабрика, тестируются через factory API.
Проверяй:
- hook не делает запрос без готовых входных данных;
- dependency hook получает ожидаемые доменные аргументы;
- hook корректно обрабатывает смену dependency result;
- `data` имеет доменную модель;
- `error` имеет доменный контракт;
- loading/refresh state соответствует собственному API модуля;
- невалидный dependency response не попадает наружу как валидная доменная модель.
```ts
const useCurrent = createCurrentUserSourceHookMock({ data: sourceUser })
const deps = createUserDepsMock({ profile: { useCurrent } })
const userApi = userFactory(deps)
const { result } = renderHook(() => userApi.useCurrentUser())
```
Не тестируй hook business-модуля как отдельную публичную сущность, если он не является public API фабрики.
SWR/Query cache keys, provider wrapper и library-specific revalidation тестируются в assembly tests dependency adapter, а не в business factory tests.
## Command-сценарии
Для command-методов вроде `save`, `update`, `change`, `request`, `verify` проверяются:
- payload передаётся во внешнюю dependency в ожидаемой форме;
- входной payload не мутируется;
- пустой успешный ответ считается успехом, если body не нужен;
- ошибка dependency превращается в доменную ошибку;
- потребитель может принять решение по доменному `code`;
- side effects выполняются в правильном порядке;
- при ошибке одного шага следующие side effects не выполняются;
- повторный вызов не использует устаревшее состояние, если это важно для сценария.
Если сценарий использует несколько зависимостей, тест должен явно фиксировать порядок.
## Colocated unit-тесты
Colocated unit-тесты размещаются рядом с файлом, который владеет runtime-логикой.
```text
business/{domain}/
├── errors/
│ ├── {domain}-business.error.ts
│ └── {domain}-business.error.test.ts
├── lib/
│ ├── normalize-{entity}.ts
│ └── normalize-{entity}.test.ts
├── mappers/
│ ├── map-{entity}.ts
│ └── map-{entity}.test.ts
├── services/
│ ├── update-{entity}.service.ts
│ └── update-{entity}.service.test.ts
└── hooks/
├── use-{scenario}.hook.ts
└── use-{scenario}.hook.test.tsx
```
Colocated unit-тесты нужны для:
- mappers;
- normalizers;
- type guards;
- runtime-safe helpers;
- domain errors;
- сложных services;
- hook wrappers со сложной нормализацией domain result/error;
- storage parsers;
- fallback-логики.
Colocated unit-тесты не нужны для:
- type-only файлов;
- `index.ts` без runtime-логики;
- простых re-export файлов;
- статических config-файлов без branching;
- типов, которые проверяются typecheck'ом.
## Почему colocated тесты не заменяют factory-level
Colocated тест может доказать, что mapper работает правильно, но он не доказывает, что фабрика использует этот mapper в публичном сценарии.
Colocated тест может доказать, что service обрабатывает ошибку, но он не доказывает, что service реально попал в public API фабрики.
Factory-level тест проверяет интеграцию внутренних частей business-модуля как чёрный ящик.
Правило:
- сначала покрывай public API фабрики;
- затем усиливай покрытие colocated тестами там, где есть runtime-safe логика.
## Маппинг и runtime safety
Если business-модуль получает данные с любой dependency boundary, тестируй не только happy path. Boundary включает методы, source hooks, stores, events и browser capabilities.
Проверяй:
- отсутствующие обязательные поля;
- nullable-поля;
- поля неправильного runtime-типа;
- пустые строки;
- пробельные строки;
- странные идентификаторы;
- пустые массивы;
- не-массив вместо массива;
- частично валидные объекты;
- дефолтные значения;
- domain error для malformed response, если модель не может быть безопасно построена.
Fallback допустим только для валидного доменного исхода, например корректно представленного отсутствия данных. Rejection, synchronous throw, source error и malformed response всегда дают domain error.
## Доменные ошибки
Business-модуль никогда не отдаёт наружу сырые ошибки SDK, HTTP-клиента, source hook, store, storage или browser API. Public contract всегда содержит только собственные domain errors.
Factory-level тесты должны доказывать, что потребитель может работать только с доменным `code` и не знает форму внешней ошибки.
Проверяй:
- `error.name`;
- стабильный `error.code`;
- сохранение `cause`;
- отсутствие утечки DTO/HTTP-specific деталей в public contract;
- rejected promise от разных dependencies маппится в ожидаемый доменный код;
- синхронный throw dependency маппится в ожидаемый доменный код;
- невалидный успешный ответ превращается в доменный код ошибки;
- разные технические ошибки дают один код, если для потребителя это один бизнес-сценарий;
- разные пользовательские сценарии дают разные коды, если UI должен реагировать по-разному;
- safe fallback только для валидного доменного исхода, явно представленного dependency contract.
UI и i18n должны ориентироваться на `code`, а не на `message` внешней ошибки.
Пример factory-level проверки:
```ts
it('throws domain error code when phone code verification fails', async () => {
const externalError = new Error('Request failed with status code 500')
const authApi = authFactory({
phoneAuth: {
requestCode: vi.fn(),
resendCode: vi.fn(),
verifyCode: vi.fn().mockRejectedValue(externalError),
},
session,
sessionEvents: createAuthSessionEventsMock(),
state: createAuthStateAdapterMock(),
})
await expect(authApi.verifyPhoneCode(data)).rejects.toMatchObject({
name: 'AuthBusinessError',
code: 'AUTH_PHONE_CODE_VERIFY_FAILED',
cause: externalError,
})
})
```
Не проверяй в потребительских сценариях `externalError.message`, HTTP status или тип ошибки SDK как ожидаемое поведение business API.
## Тестирование compositions/business
Тесты `compositions/business/{domain}` проверяют сборку, а не бизнес-поведение.
Проверяй:
- builder вызывает нужную business-фабрику;
- adapter вызывает SDK operation с ожидаемым payload;
- storage/browser adapter соответствует dependency contract;
- API другого домена передаётся в нужном виде;
- builder deps содержат только API других собранных business-фабрик;
- builder/client/adapter constructors не выполняют I/O, storage/env reads или subscriptions во время создания API;
- state/query runtime находится в adapter и не импортируется business-модулем;
- dependency hook работает без Suspense/throw-on-error и возвращает technical error через result;
- adapter пробрасывает source error без создания domain error;
- lifecycle subscription возвращает и вызывает cleanup;
- public API composition-модуля не экспортирует внутренние adapters.
Не проверяй здесь domain errors, fallback'и и маппинг доменной модели. Это ответственность factory-level и colocated тестов в `business/{domain}`.
## Что не тестировать unit-тестами business-модуля
Unit-тесты business-модуля не проверяют:
- реальные REST-запросы;
- generated-клиенты;
- настоящий backend;
- Next.js routing;
- визуальную вёрстку;
- интеграцию с production storage;
- реальные внешние сервисы;
- e2e-поток целого приложения.
Эти проверки относятся к `infra`, `compositions`, integration или e2e уровням.
## Архитектурные импорты
Проверяй production import graph business-модуля. В `business/**` не должно быть runtime или type-only imports из concrete runtimes:
- SDK/client/infra;
- SWR/TanStack Query/Apollo;
- Zustand/Redux/MobX;
- React state/effect APIs;
- storage/browser/event implementations.
Factory-level test обязан импортировать фабрику через public API business-модуля. Deep import `../../{domain}.factory` не доказывает корректность public boundary.
## Чеклист
- Каждый runtime-метод фабрики имеет factory-level тесты.
- Factory-level тесты импортируют модуль только через public API.
- Public API фабрики зафиксирован отдельным тестом.
- DI-зависимости проверены на корректные, пустые, невалидные и ошибочные ответы.
- Hooks тестируются через API, который вернула фабрика.
- Command-сценарии проверяют порядок side effects.
- После ошибки не выполняются лишние side effects.
- Runtime-safe mappers, normalizers и guards покрыты colocated unit-тестами.
- Type-only файлы не покрываются бессмысленными unit-тестами.
- Доменные ошибки имеют стабильный `code`, сохраняют `cause` и не раскрывают внешнюю ошибку как public contract.
- Тесты `compositions/business/{domain}` проверяют сборку, а не бизнес-логику.
- Business production imports не содержат concrete state/query/source runtime.
- Тесты не требуют backend, network, env и долгоживущих процессов.

View File

@@ -1,346 +0,0 @@
---
title: Композиция через Provider
description: Пример page-level Provider для composition modules в React-проекте
---
# Композиция через Provider
Раздел показывает, как page composition может владеть provider, store и business composition, которые нужны layout, screen и другим composition modules.
## Идея
Page composition хранит состояние и композицию бизнес-доменов на уровне страницы. Layout и screen не импортируют друг друга: они получают доступ к page-level данным через публичный API page composition.
В примере `ProfilePageState` — только локальное UI-state страницы. Это не domain state и не product data cache. Доменное состояние описывается business-модулем, а concrete store/query hook передаётся его фабрике через adapter в `compositions/business/{domain}`.
В примере page composition владеет scope-контрактом страницы, но не экспортирует готовый `ProfilePage`, потому что layout и screen импортируют hooks из `pages/profile`. Дерево страницы собирается в отдельном entry-point composition module, который слой `app` только подключает.
## Принципы
1. **Владение.** Page-level store, provider и business composition принадлежат page composition module.
2. **Обычные сегменты.** Provider, hooks, stores и types лежат в обычных сегментах модуля: `providers/`, `hooks/`, `stores/`, `types/`.
3. **Публичный контракт.** Page composition экспортирует только безопасные hooks, provider и типы, которые нужны другим composition modules.
4. **Сборка снаружи business.** Business-модули не используют page-level providers. Page composition вызывает builders из `compositions/business/{domain}` и владеет lifecycle готового графа.
5. **Без deep imports.** Layout и screen импортируют hooks только из public API page composition.
## Структура модулей
```text
compositions/pages/profile/
├── profile-business-composition.ts
├── providers/
│ └── profile-page.provider.tsx
├── hooks/
│ ├── use-profile-page-store.hook.ts
│ └── use-profile-business-composition.hook.ts
├── stores/
│ └── profile-page.store.ts
├── types/
│ └── profile-page-state.type.ts
└── index.ts
compositions/layouts/profile-main/
├── profile-main.layout.tsx
└── index.ts
compositions/screens/profile/
├── profile.screen.tsx
├── ui/
│ ├── profile-error/
│ ├── profile-summary/
│ └── profile-summary-skeleton/
└── index.ts
```
## Тип состояния страницы
Файл: `compositions/pages/profile/types/profile-page-state.type.ts`.
```ts
export type ProfilePageState = {
title: string
isSidebarOpen: boolean
setSidebarOpen: (value: boolean) => void
}
```
## Store страницы
Файл: `compositions/pages/profile/stores/profile-page.store.ts`.
```ts
import { createStore } from 'zustand/vanilla'
import type { ProfilePageState } from '../types/profile-page-state.type'
export const createProfilePageStore = () =>
createStore<ProfilePageState>((set) => ({
title: 'Profile',
isSidebarOpen: false,
setSidebarOpen: (value) => set({ isSidebarOpen: value }),
}))
```
`createProfilePageStore` не экспортируется через public API модуля. Это внутренняя деталь создания состояния.
## Business composition страницы
Файл: `compositions/pages/profile/profile-business-composition.ts`.
```ts
import { createAuthBusiness } from '@/compositions/business/auth'
import { createProfileBusiness } from '@/compositions/business/profile'
export const createProfileBusinessComposition = () => {
const authApi = createAuthBusiness()
const profileApi = createProfileBusiness({ authApi })
return { authApi, profileApi }
}
```
Page composition собирает нужный для страницы граф из per-domain builders. Реальные runtime-зависимости остаются в `compositions/business/{domain}`, а не внутри `business`.
Page composition не импортирует SDK, product storage, source hook или raw infra event для дополнительной настройки домена. Такое wiring принадлежит соответствующему integration module.
## Provider страницы
Файл: `compositions/pages/profile/providers/profile-page.provider.tsx`.
```tsx
'use client'
import { createContext, useEffect, useState, type ReactNode } from 'react'
import type { StoreApi } from 'zustand/vanilla'
import { createProfileBusinessComposition } from '../profile-business-composition'
import { createProfilePageStore } from '../stores/profile-page.store'
import type { ProfilePageState } from '../types/profile-page-state.type'
type ProfileBusinessComposition = ReturnType<typeof createProfileBusinessComposition>
type ProfilePageProviderValue = {
store: StoreApi<ProfilePageState>
business: ProfileBusinessComposition
}
export const ProfilePageContext = createContext<ProfilePageProviderValue | null>(null)
type Props = {
children: ReactNode
}
const createProfilePageProviderValue = (): ProfilePageProviderValue => ({
store: createProfilePageStore(),
business: createProfileBusinessComposition(),
})
export const ProfilePageProvider = ({ children }: Props) => {
const [value] = useState(createProfilePageProviderValue)
useEffect(() => {
return value.business.authApi.startSessionInvalidationTracking()
}, [value.business.authApi])
return (
<ProfilePageContext.Provider value={value}>
{children}
</ProfilePageContext.Provider>
)
}
```
Context object остаётся технической деталью provider и не должен использоваться внешними модулями напрямую. Наружу экспортируются hooks доступа.
Lazy initializer может быть повторно вызван React Strict Mode в development. Поэтому store/business constructors не выполняют I/O и не запускают subscriptions. Domain-level lifecycle operations запускаются отдельно в effect и возвращают cleanup.
## Hooks доступа
Файл: `compositions/pages/profile/hooks/use-profile-page-store.hook.ts`.
```ts
'use client'
import { useContext } from 'react'
import { useStore } from 'zustand'
import { ProfilePageContext } from '../providers/profile-page.provider'
import type { ProfilePageState } from '../types/profile-page-state.type'
export const useProfilePageStore = <T,>(selector: (state: ProfilePageState) => T) => {
const ctx = useContext(ProfilePageContext)
if (!ctx) {
throw new Error('useProfilePageStore must be used within ProfilePageProvider')
}
return useStore(ctx.store, selector)
}
```
Файл: `compositions/pages/profile/hooks/use-profile-business-composition.hook.ts`.
```ts
'use client'
import { useContext } from 'react'
import { ProfilePageContext } from '../providers/profile-page.provider'
export const useProfileBusinessComposition = () => {
const ctx = useContext(ProfilePageContext)
if (!ctx) {
throw new Error('useProfileBusinessComposition must be used within ProfilePageProvider')
}
return ctx.business
}
```
## Layout использует page-level store
Файл: `compositions/layouts/profile-main/profile-main.layout.tsx`.
```tsx
'use client'
import type { ReactNode } from 'react'
import { useProfilePageStore } from '@/compositions/pages/profile'
type Props = {
children: ReactNode
}
export const ProfileMainLayout = ({ children }: Props) => {
const title = useProfilePageStore((state) => state.title)
const isSidebarOpen = useProfilePageStore((state) => state.isSidebarOpen)
return (
<div data-sidebar-open={isSidebarOpen}>
<header>{title}</header>
<main>{children}</main>
</div>
)
}
```
Layout импортирует hook из public API page composition. Он не импортирует screen и не лезет во внутренние файлы `pages/profile`.
## Screen использует business composition
Файл: `compositions/screens/profile/profile.screen.tsx`.
```tsx
'use client'
import { useProfileBusinessComposition } from '@/compositions/pages/profile'
import { ProfileError } from './ui/profile-error'
import { ProfileSummary } from './ui/profile-summary'
import { ProfileSummarySkeleton } from './ui/profile-summary-skeleton'
export const ProfileScreen = () => {
const { profileApi } = useProfileBusinessComposition()
const currentProfile = profileApi.useCurrentProfile()
if (currentProfile.isLoading) {
return <ProfileSummarySkeleton />
}
if (currentProfile.error) {
return <ProfileError code={currentProfile.error.code} />
}
return currentProfile.data ? <ProfileSummary profile={currentProfile.data} /> : null
}
```
Screen получает готовые доменные API из page composition и не собирает граф фабрик самостоятельно. `ProfileSummary` — компонент screen composition, а не часть `business/profile`.
## Публичный API page composition
Файл: `compositions/pages/profile/index.ts`.
```ts
export { ProfilePageProvider } from './providers/profile-page.provider'
export { useProfilePageStore } from './hooks/use-profile-page-store.hook'
export { useProfileBusinessComposition } from './hooks/use-profile-business-composition.hook'
export type { ProfilePageState } from './types/profile-page-state.type'
```
Внутренние `createProfilePageStore`, `createProfileBusinessComposition` и `ProfilePageContext` не экспортируются через public API.
Готовое дерево собирай в отдельном entry-point composition module. Не смешивай в одном public API готовую page composition и hooks, которые импортируют её дочерние layout/screen modules: это может создать runtime-цикл.
## Подключение в app
Entry composition связывает provider, layout и screen:
```tsx
// compositions/entries/profile/profile.entry.tsx
'use client'
import { ProfilePageProvider } from '@/compositions/pages/profile'
import { ProfileMainLayout } from '@/compositions/layouts/profile-main'
import { ProfileScreen } from '@/compositions/screens/profile'
export const ProfileEntry = () => (
<ProfilePageProvider>
<ProfileMainLayout>
<ProfileScreen />
</ProfileMainLayout>
</ProfilePageProvider>
)
```
React Router config только подключает готовый entry:
```tsx
import { ProfileEntry } from '@/compositions/entries/profile'
export const profileRoute = {
path: '/profile',
element: <ProfileEntry />,
}
```
Для Next App Router создай готовые layout/page entries в `compositions`, а framework files только подключают их.
```tsx
// compositions/entries/profile/profile-layout.entry.tsx
'use client'
import { ProfilePageProvider } from '@/compositions/pages/profile'
import { ProfileMainLayout } from '@/compositions/layouts/profile-main'
import type { ReactNode } from 'react'
export const ProfileLayoutEntry = ({ children }: { children: ReactNode }) => {
return (
<ProfilePageProvider>
<ProfileMainLayout>{children}</ProfileMainLayout>
</ProfilePageProvider>
)
}
```
```tsx
// compositions/entries/profile/profile-page.entry.tsx
'use client'
import { ProfileScreen } from '@/compositions/screens/profile'
export const ProfilePageEntry = () => <ProfileScreen />
```
```tsx
// app/(profile)/layout.tsx
import { ProfileLayoutEntry } from '@/compositions/entries/profile'
export default ProfileLayoutEntry
```
```tsx
// app/(profile)/page.tsx
import { ProfilePageEntry } from '@/compositions/entries/profile'
export default ProfilePageEntry
```
`app` размещает готовые entry composition modules по правилам фреймворка, но не реализует product tree внутри себя.

View File

@@ -1,83 +0,0 @@
---
title: Структуры compositions
description: Примеры организации слоя compositions под разные способы сборки React-приложения
---
# Структуры compositions
Раздел показывает, что SLM не фиксирует жёсткую структуру внутри `compositions`. Команда выбирает организацию под фреймворк, роутинг, CMS и продуктовую задачу.
## Базовая рекомендация
Подходит для большинства приложений, где есть явные страницы, layouts, screens и переиспользуемые композиционные блоки.
```text
src/compositions/
├── business/
│ ├── auth/
│ └── user/
├── pages/
│ ├── home/
│ └── profile/
├── layouts/
│ ├── main/
│ └── dashboard/
├── screens/
│ ├── home/
│ └── profile/
└── widgets/
├── page-heading/
└── promo-banner/
```
`business`, `pages`, `layouts`, `screens` и `widgets` здесь не являются отдельными SLM-слоями. Это группы composition modules внутри одного слоя `compositions`.
`compositions/business/{domain}` используется для runtime-сборки business-фабрик. Он не заменяет `business/{domain}` и не содержит доменную логику.
Только эта группа integration modules знает одновременно business dependency contract и concrete product runtime. Остальные composition modules являются graph owners или consumers готовых business API.
## Entry-points и blocks
Подходит для проектов, где точка сборки не всегда является страницей: CMS registry, embedded UI, route entries, feature entries.
```text
src/compositions/
├── entry-points/
│ ├── cms-profile/
│ └── embedded-checkout/
├── pages/
│ └── profile/
├── layouts/
│ └── profile-main/
├── screens/
│ └── profile/
└── blocks/
├── profile-summary/
└── recommended-products/
```
## Группировка вокруг продукта
Подходит, когда удобнее держать все части одной крупной области рядом.
```text
src/compositions/
└── profile/
├── page/
├── layout/
├── screen/
└── blocks/
```
## Главное правило
Любая структура допустима, если соблюдаются границы слоя:
- `app` подключает готовые composition modules к фреймворку.
- `compositions` может импортировать `business`, `infra`, `ui`, `shared`.
- `compositions/business/{domain}` отдельными adapters собирает конкретную business-фабрику с runtime-зависимостями.
- Page/layout/screen/widget получают product data только через `{Domain}Api`.
- Graph owner импортирует builders, но не raw product SDK/client/event для досборки домена.
- `business`, `infra`, `ui`, `shared` не импортируют `compositions`.
- Импорты между composition modules идут только через public API.
- Deep imports внутрь composition modules запрещены.

24
docs/index.md Normal file
View File

@@ -0,0 +1,24 @@
---
layout: home
title: SLM Design
titleTemplate: false
sidebar: false
hero:
name: SLM Design
text: Explicit architecture boundaries
tagline: A draft specification for ownership, dependencies, runtime, and lifecycle in product applications.
actions:
- theme: brand
text: Русская спецификация
link: /ru/
- theme: alt
text: English
link: /en/
features:
- title: Base SLM
details: A complete minimal architecture built around explicit ownership and five application layers.
- title: Independent overlays
details: Advanced and Pro add separate rule sets directly to base SLM without inheriting each other.
- title: Stable rules
details: Every normative requirement has a permanent rule ID suitable for reviews and automated checks.
---

28
docs/ru/index.md Normal file
View File

@@ -0,0 +1,28 @@
---
layout: home
title: SLM Design
titleTemplate: false
sidebar: false
hero:
name: Документация SLM Design
text: Один портал для правил и практики
tagline: Нормативная спецификация, реестр правил и будущий архитектурный гайд в единой структуре.
actions:
- theme: brand
text: Открыть спецификацию
link: /ru/specification/
- theme: alt
text: Найти правило
link: /ru/specification/rules
features:
- title: SLM Design Specification
details: Нормативный источник архитектурных правил, ownership boundaries и требований соответствия.
link: /ru/specification/
linkText: Читать спецификацию
- title: Реестр правил
details: Все Base, Advanced и Pro rules с фильтрами, точными anchors и копируемыми permalink-ссылками.
link: /ru/specification/rules
linkText: Открыть реестр
- title: Architecture Guide
details: Будущий учебный материал для последовательного изучения и практического применения Specification.
---

View File

@@ -0,0 +1,72 @@
---
title: Архитектурная модель
status: draft
normative: true
---
# Архитектурная Модель
## Структура приложения
```text
src/
├── app/
├── compositions/
├── infra/
├── ui/
└── shared/
```
**SLM-BASE-ARCH-001 - ОБЯЗАН.** Base SLM-приложение должно разделять код по ответственности между слоями `app`, `compositions`, `infra`, `ui` и `shared`.
Не каждый слой обязан содержать код в минимальном приложении. Пустые папки и speculative scaffolding не требуются.
## Группы ответственности
| Группа | Слои | Ответственность |
|---|---|---|
| Framework composition | `app`, `compositions` | Подключение к framework и сборка application flows |
| Product | Product owner; в base SLM - `compositions` | Product semantics, UI и flows владеющего module |
| Technical | `infra`, `ui` | Technical capabilities и универсальный UI |
| Foundation | `shared` | Детерминированный общий фундамент |
## Верхнеуровневое направление
```text
app -> compositions | shared
compositions -> compositions | infra | ui | shared
infra -> infra | shared
ui -> ui | shared
shared -/-> остальные SLM-слои
```
Framework APIs и external packages регулируются ответственностью импортирующего слоя и не показаны как SLM-слои.
**SLM-BASE-ARCH-002 - ОБЯЗАН.** Верхнеуровневое направление зависимостей между base SLM-слоями должно соблюдаться для runtime imports и type imports, кроме явно описанных исключений.
**SLM-BASE-ARCH-003 - ЗАПРЕЩЕНО.** Нижний слой не может импортировать `app` или `compositions`.
**SLM-BASE-ARCH-004 - ЗАПРЕЩЕНО.** `infra`, `ui` и `shared` не могут владеть product wiring или выступать service locator для application modules.
## Путь данных
```text
app
-> product owner public API
-> infra public API
-> external source
```
**SLM-BASE-ARCH-005 - ОБЯЗАН.** Каждый переход product data должен сохранять ownership: framework связывает, product owner определяет semantics, а technical capability не присваивает себе product model.
## Путь UI
```text
app route
-> page/layout composition
-> product UI
-> universal UI
-> shared styles/resources
```
Product UI принадлежит product owner; в base SLM таким owner является composition. Универсальный product-agnostic UI принадлежит `ui`.

View File

@@ -0,0 +1,81 @@
---
title: Архитектурные modes
status: draft
normative: true
---
# Архитектурные Modes
SLM является самостоятельной базовой архитектурой. Architecture mode - опциональный независимый overlay, который добавляет или явно заменяет отдельные правила base SLM.
```text
SLM Advanced = SLM + Advanced rules
SLM Pro = SLM + Pro rules
```
`SLM Advanced` и `SLM Pro` не наследуют друг друга. Совпадающее требование декларируется отдельно внутри каждого overlay и не создаёт общей mode-ветки.
## Выбор архитектуры
Приложение использует один из трёх вариантов:
```text
SLM
SLM + Advanced
SLM + Pro
```
**SLM-BASE-MODE-001 - ОБЯЗАН.** Приложение должно зафиксировать использование base SLM и, при наличии, ровно одного overlay: `Advanced` или `Pro`.
**SLM-BASE-MODE-002 - ЗАПРЕЩЕНО.** Одно приложение не может одновременно заявлять соответствие `SLM Advanced` и `SLM Pro`.
**SLM-BASE-MODE-003 - ОБЯЗАН.** Выбранный overlay должен применяться ко всему приложению в пределах одной SLM application boundary.
Выбор выполняет команда на стадии планирования. Сигналами могут быть количество product responsibilities, связанность modules, runtime state, client/server execution, lifecycle risks и количество команд разработки. Фиксированные числовые пороги не устанавливаются.
| Вариант | Когда рассматривать |
|---|---|
| `SLM` | Product responsibilities удобно удерживать внутри compositions без дополнительного слоя |
| `SLM Advanced` | Нужны самостоятельные domains, но команда хочет свободно выбирать их внутреннюю структуру и связи |
| `SLM Pro` | Нужны изолированные domains, явные runtime contracts, adapters, lifecycle и усиленные checks |
## Применимость правил
Base-правило имеет идентификатор вида:
```text
SLM-BASE-AREA-NNN
```
Mode-specific правила имеют идентификаторы:
```text
SLM-ADV-AREA-NNN
SLM-PRO-AREA-NNN
```
**SLM-BASE-MODE-004 - ОБЯЗАН.** Base-правила SLM применяются при любом выбранном варианте архитектуры. Если overlay явно заменяет base rule только в определённом scope, исходное base-правило продолжает действовать за пределами этого scope.
**SLM-BASE-MODE-005 - ОБЯЗАН.** Для `SLM Advanced` применяются только base-правила и правила из `modes/advanced`.
**SLM-BASE-MODE-006 - ОБЯЗАН.** Для `SLM Pro` применяются только base-правила и правила из `modes/pro`.
**SLM-BASE-MODE-007 - ЗАПРЕЩЕНО.** Правило другого overlay не может использоваться как обязательное требование, разрешение или исключение.
**SLM-BASE-MODE-008 - ОБЯЗАН.** Mode-specific правило, заменяющее base-поведение, должно явно назвать заменяемый base rule ID или нормативный раздел и точный scope замены.
## Независимые overlays
### SLM Advanced
[SLM Advanced](./modes/advanced/index.md) описывает полный Advanced-delta относительно base SLM.
### SLM Pro
[SLM Pro](./modes/pro/index.md) описывает полный Pro-delta относительно base SLM.
## Изменение overlay
**SLM-BASE-MODE-009 - МОЖЕТ.** Команда может подключить, заменить или удалить overlay при изменении требований к архитектуре.
**SLM-BASE-MODE-010 - ОБЯЗАН.** После изменения конфигурации приложение может заявлять соответствие только после выполнения применимых base-правил с учётом scoped replacements и, при наличии, полного rule set выбранного overlay.

View File

@@ -0,0 +1,39 @@
---
title: Основные инварианты
status: draft
normative: true
---
# Основные Инварианты
SLM Design организует frontend-приложение по владельцам ответственности. Архитектурная единица определяется не типом файла, а тем, кто владеет моделью, поведением, данными, runtime и lifecycle.
## Ответственность до размещения
**SLM-BASE-FND-001 - ОБЯЗАН.** Перед размещением кода необходимо определить его владельца, public boundary, runtime dependencies и lifecycle scope.
**SLM-BASE-FND-002 - СЛЕДУЕТ.** Код следует размещать в минимальном scope, который полностью владеет его ответственностью.
**SLM-BASE-FND-003 - ЗАПРЕЩЕНО.** Нельзя переносить код в общий слой или общий package только на основании предполагаемого будущего переиспользования.
## Путь продуктовых данных
Product data проходят через public boundary текущего владельца согласно [SLM-BASE-DATA-001](./state-and-data.md#product-gateway). Внешний сервис может оставаться физическим источником данных, но transport contract не становится product model автоматически.
## Явные зависимости
**SLM-BASE-FND-007 - ОБЯЗАН.** Runtime capabilities должны поступать владельцу поведения через разрешённые imports, явные arguments или contracts, а не через скрытый service locator или global mutable state.
**SLM-BASE-FND-008 - ЗАПРЕЩЕНО.** Type cast, barrel, alias, dynamic import или helper в `shared` не могут использоваться для обхода применимой архитектурной границы.
## Public API
Межмодульное взаимодействие и deep imports регулируются [SLM-BASE-API-001 - SLM-BASE-API-005](./public-api-and-imports.md#общие-правила).
## Scope и lifecycle
Создание, scope, activation и cleanup применимых runtimes и resources определены в [Runtime и lifecycle](./runtime-and-lifecycle.md).
## Overlays
Base SLM не вводит дополнительные архитектурные слои и специализированные runtime contracts. Каждый overlay самостоятельно определяет свои добавления и замены base-правил.

View File

@@ -0,0 +1,93 @@
---
title: SLM Design Specification
version: 0.1.0-draft
status: draft
normative: true
---
# SLM Design Specification
Эта директория содержит единый нормативный корпус SLM Design 2.0. Base SLM является законченной минимальной архитектурой; дополнительные ограничения подключаются независимыми overlays `SLM Advanced` или `SLM Pro`.
Пока статус равен `draft`, документы описывают проектируемую архитектуру и не заменяют действующую документацию в `old-docs/`.
## Нормативный язык
| Термин | Значение |
|---|---|
| `ОБЯЗАН` | Требование необходимо выполнить для соответствия спецификации |
| `ЗАПРЕЩЕНО` | Действие является нарушением спецификации |
| `СЛЕДУЕТ` | Рекомендуемое решение; отступление требует явного обоснования |
| `МОЖЕТ` | Допустимый, но необязательный вариант |
Правила имеют стабильные идентификаторы. Base использует формат `SLM-BASE-AREA-NNN`, Advanced - `SLM-ADV-AREA-NNN`, Pro - `SLM-PRO-AREA-NNN`. Точное нормативное требование принадлежит только той главе, где объявлен его rule ID.
Все объявления доступны в [реестре правил](./rules.md). Для прямого перехода можно открыть поиск `Ctrl/⌘ K` и ввести полный rule ID.
## Architecture modes
Base SLM не требует overlay. Если команда выбирает дополнительную архитектурную политику, она подключает ровно один независимый mode согласно [Архитектурным modes](./architecture-modes.md):
```text
SLM Advanced = SLM + Advanced rules
SLM Pro = SLM + Pro rules
```
## Приоритет
**SLM-BASE-DOC-001 - ОБЯЗАН.** При конфликте между главами спецификации и любым ненормативным материалом приоритет имеет спецификация.
**SLM-BASE-DOC-002 - ЗАПРЕЩЕНО.** Ненормативный документ не может вводить новое обязательное правило, исключение или архитектурную границу.
**SLM-BASE-DOC-003 - ОБЯЗАН.** Изменение принятого архитектурного правила должно вноситься в главу, которая владеет соответствующим rule ID.
## Base SLM
### Основы
- [Основные инварианты](./foundations.md)
- [Терминология](./terminology.md)
- [Архитектурная модель](./architecture-model.md)
### Слои
- [Обзор слоёв](./layers/index.md)
- [App](./layers/app.md)
- [Compositions](./layers/compositions.md)
- [Infra](./layers/infra.md)
- [UI](./layers/ui.md)
- [Shared](./layers/shared.md)
### Общие правила
- [Модули и группы](./modules-and-groups.md)
- [Сегменты](./segments.md)
- [Public API и импорты](./public-api-and-imports.md)
- [State и data](./state-and-data.md)
- [Runtime и lifecycle](./runtime-and-lifecycle.md)
- [Тестирование и соответствие](./testing-and-conformance.md)
- [Монорепозитории](./monorepo.md)
## Overlays
### SLM Advanced
- [Отличия Advanced от base SLM](./modes/advanced/index.md)
- [Domains в SLM Advanced](./modes/advanced/domains.md)
### SLM Pro
- [Отличия Pro от base SLM](./modes/pro/index.md)
- [Domains в SLM Pro](./modes/pro/domains/index.md)
- [Business](./modes/pro/domains/business.md)
- [Framework surface](./modes/pro/domains/framework.md)
- [Ports и adapters](./modes/pro/domains/ports-and-adapters.md)
- [Client и server assembly](./modes/pro/domains/client-and-server.md)
- [Cross-domain boundary](./modes/pro/domains/cross-domain-boundary.md)
- [Тестирование Pro domains](./modes/pro/domains/testing.md)
## Область текущего draft
Base SLM фиксирует ownership, пять основных слоёв, public boundaries, state и lifecycle. Текущие версии Advanced и Pro в первую очередь определяют собственные независимые модели слоя `domains`; будущие mode-specific правила могут относиться к любому разделу архитектуры.
Точная форма React Providers, окончательная политика package extraction и единая модель query cache не фиксируются сверх явно объявленных инвариантов base или выбранного overlay.

View File

@@ -0,0 +1,70 @@
---
title: Слой App
status: draft
normative: true
---
# Слой App
`app` является boundary между framework routing/runtime и SLM-модулями приложения.
## Ответственность
`app` может содержать:
- route files;
- framework layout/error/loading/not-found entries;
- framework metadata и route parameters;
- bootstrap imports;
- подключение global styles/assets;
- framework-required middleware и handlers.
## Правила
**SLM-BASE-APP-001 - ОБЯЗАН.** Route entry должен оставаться тонким adapter, нормализующим framework input и делегирующим готовому composition module.
```text
framework route
→ composition entry
```
**SLM-BASE-APP-002 - ЗАПРЕЩЕНО.** `app` не может владеть product page, screen, widget, product scenario, store или application wiring.
**SLM-BASE-APP-003 - ЗАПРЕЩЕНО.** Route entry не должен напрямую собирать product integrations, вызывать SDK или формировать product model.
**SLM-BASE-APP-004 - ОБЯЗАН.** Framework-specific input должен быть считан в `app` и передан вниз в минимальной нормализованной форме.
Механическая нормализация включает извлечение route params, headers и framework wrappers. Product validation, создание value objects и выбор product outcome остаются у владельца product semantics.
Запрет другим SLM-слоям импортировать `app` определяется base-правилом `SLM-BASE-ARCH-003`.
**SLM-BASE-APP-006 - СЛЕДУЕТ.** Framework behavior, которому нужны product dependencies или product UI, следует реализовать готовым composition entry и только подключить из `app`.
**SLM-BASE-APP-007 - МОЖЕТ.** `app` может напрямую импортировать framework APIs и static/global resources из `shared`, если framework требует подключить их в root entry.
## Допустимая структура
Структуру `app` определяет framework. SLM не требует превращать framework directories в SLM modules и не требует `index.ts` для route folders.
```text
app/
├── layout.tsx
├── error.tsx
├── not-found.tsx
├── api/
└── products/
└── [product]/
└── page.tsx
```
## Примеры нарушений
Следующие сущности являются примерами нарушений `SLM-BASE-APP-002` и `SLM-BASE-APP-003`:
- `ProductPage`;
- product Provider;
- application service creator;
- page-local store;
- product mapper;
- reusable product component;
- concrete product integration.

View File

@@ -0,0 +1,85 @@
---
title: Слой Compositions
status: draft
normative: true
---
# Слой Compositions
`compositions` собирает application flows из module public APIs, technical capabilities и UI modules и может владеть product logic в пределах своей ответственности.
## Ответственность
Composition может быть:
- page;
- route composition entry;
- layout;
- screen;
- widget;
- provider composition;
- multi-module hook;
- non-visual application wiring owner.
Структура слоя свободна и отражает продуктовую навигацию приложения.
```text
compositions/
├── pages/
├── layouts/
├── screens/
├── widgets/
└── providers/
```
Эти папки являются groups, а не отдельными слоями.
## Product ownership
**SLM-BASE-CMP-001 - ОБЯЗАН.** Product flow и его локальная product logic должны принадлежать минимальной composition, охватывающей всех consumers этой ответственности.
Composition может использовать public API `infra` для external operations, сохраняя product mapping, outcomes и fallback semantics у себя.
## Public boundaries
**SLM-BASE-CMP-005 - ЗАПРЕЩЕНО.** Composition не может импортировать private services, integrations, stores, Context или другие internal paths используемого module.
## Product UI
**SLM-BASE-CMP-006 - ОБЯЗАН.** UI, объединяющий несколько самостоятельных modules, route/page scope либо application flow, принадлежит `compositions`.
Примеры:
- application header;
- order flow, объединяющий несколько product responsibilities;
- page screen;
- route guard с navigation outcome;
- widget, использующий public APIs двух самостоятельных modules.
**SLM-BASE-CMP-007 - МОЖЕТ.** Composition может использовать product UI, опубликованный другими modules, и universal UI, передавая props, callbacks и slots.
## State
**SLM-BASE-CMP-008 - ОБЯЗАН.** Page-local presentation state принадлежит минимальной composition, охватывающей всех его consumers.
Примеры page-local state:
- открытие sidebar;
- активная вкладка;
- route-local wizard step;
- presentation filters;
- состояние раскрытия section.
**SLM-BASE-CMP-009 - ЗАПРЕЩЕНО.** Page store не может становиться параллельным владельцем product model или canonical product cache другого owner.
## Imports
**SLM-BASE-CMP-010 - МОЖЕТ.** Composition module может импортировать public API других composition modules, infra, ui и shared.
Runtime-циклы между composition modules запрещены base-правилом `SLM-BASE-API-016`.
**SLM-BASE-CMP-015 - ОБЯЗАН.** Client и server composition entries должны иметь раздельные public entrypoints и environment markers, если composition участвует в обоих runtime graphs.
## Scope
Composition может владеть application, route, page, request или test scope. Выбор scope должен следовать правилам [runtime и lifecycle](../runtime-and-lifecycle.md).

View File

@@ -0,0 +1,44 @@
---
title: Слои
status: draft
normative: true
---
# Слои
Слой определяет вид ответственности, допустимые зависимости и типы modules внутри верхнеуровневой папки `src`.
## Матрица ответственности
| Слой | Владеет | Не владеет |
|---|---|---|
| [`app`](./app.md) | Framework routes, bootstrap, глобальные framework boundaries | Product UI, product logic, page state, application wiring |
| [`compositions`](./compositions.md) | Pages, layouts, screens, widgets, product flows, application wiring и scope | Universal UI primitives, technical transports |
| [`infra`](./infra.md) | Technical services, transports, platform integrations | Product semantics и application wiring |
| [`ui`](./ui.md) | Product-agnostic UI modules | Product scenarios и data sources |
| [`shared`](./shared.md) | Детерминированные общие resources | Runtime state, I/O и product knowledge |
## Общие правила
**SLM-BASE-LAY-001 - ОБЯЗАН.** Module должен располагаться в слое, который владеет его основной ответственностью.
**SLM-BASE-LAY-002 - ЗАПРЕЩЕНО.** Нельзя выбирать слой по техническому типу файла без определения владельца поведения и данных.
**SLM-BASE-LAY-003 - ОБЯЗАН.** Межслойный import должен одновременно соответствовать общей dependency direction и public API импортируемого module.
**SLM-BASE-LAY-004 - ЗАПРЕЩЕНО.** Нельзя создавать proxy module в разрешённом слое только для обхода запрещённого направления import.
**SLM-BASE-LAY-005 - СЛЕДУЕТ.** При смешанной ответственности module следует разделить по реальным владельцам. Application flow и UI нескольких самостоятельных modules следует собирать в `compositions`.
## Выбор слоя
| Вопрос | Слой |
|---|---|
| Код существует только из-за framework route/bootstrap? | `app` |
| Код собирает page, route или несколько самостоятельных modules? | `compositions` |
| Код выражает product flow или product responsibility без owner, введённого overlay? | `compositions` |
| Код предоставляет technical capability приложения? | `infra` |
| Компонент не содержит product semantics и scenario? | `ui` |
| Код детерминирован, не знает продукт и не имеет runtime state? | `shared` |
Overlay может добавлять собственный слой и изменять ownership только в явно объявленном delta.

View File

@@ -0,0 +1,53 @@
---
title: Слой Infra
status: draft
normative: true
---
# Слой Infra
`infra` содержит technical capabilities приложения, не определяющие product model и scenarios.
## Примеры modules
```text
infra/
├── http/
├── backend-api/
├── realtime/
├── analytics/
├── logger/
├── app-config/
├── storage/
├── i18n/
└── theme/
```
## Правила
**SLM-BASE-INF-001 - ОБЯЗАН.** Infra module должен описывать technical capability, а не product semantics или scenario.
**SLM-BASE-INF-002 - МОЖЕТ.** Infra module может импортировать public API другого infra module и `shared`.
Запрет infra импортировать `compositions` или `app` определяется base-правилом `SLM-BASE-ARCH-003`.
**SLM-BASE-INF-004 - ЗАПРЕЩЕНО.** Infra не может владеть product wiring, собирать application graph или предоставлять generic product service locator.
**SLM-BASE-INF-005 - ЗАПРЕЩЕНО.** Infra не создаёт product errors, product fallback и product model из transport DTO.
**SLM-BASE-INF-006 - МОЖЕТ.** Infra может экспортировать technical client, transport, event source, storage primitive или platform wrapper через собственный public API.
**SLM-BASE-INF-007 - ОБЯЗАН.** Generated SDK и transport details должны оставаться внутри technical или private integration boundary владельца и не становиться частью public product contract.
## Product integration
Infra знает technical mechanism:
```text
HTTP client
WebSocket transport
local storage primitive
analytics SDK
```
Product owner определяет semantics использования capability; infra предоставляет механизм через public API. Один infra module может использоваться несколькими product owners без знания их semantics.

View File

@@ -0,0 +1,42 @@
---
title: Слой Shared
status: draft
normative: true
---
# Слой Shared
`shared` является детерминированным фундаментом приложения и не знает о SLM-модулях верхних слоёв.
## Допустимое содержимое
- pure utilities;
- value predicates;
- product-agnostic types;
- styling foundation и tokens;
- static resources;
- compile-time constants без product ownership;
- deterministic formatting primitives.
## Правила
**SLM-BASE-SHR-001 - ОБЯЗАН.** Результат shared utility должен определяться явными аргументами и не зависеть от скрытого runtime environment.
**SLM-BASE-SHR-002 - ЗАПРЕЩЕНО.** `shared` не может импортировать `app`, `compositions`, `infra` или `ui`.
**SLM-BASE-SHR-003 - ЗАПРЕЩЕНО.** `shared` не может владеть product types, product rules, runtime state, I/O, storage access или event subscriptions.
**SLM-BASE-SHR-004 - ЗАПРЕЩЕНО.** Нельзя переносить product helper, DTO, integration contract или product config в `shared` для обхода import boundary.
**SLM-BASE-SHR-005 - СЛЕДУЕТ.** Код следует поднимать в `shared` только при подтверждённой product-agnostic semantics, а не из-за повторения нескольких строк.
## Отличие от других слоёв
| Код | Владелец |
|---|---|
| Email validator с product rules | Владеющий product module |
| Generic string trim utility | `shared` |
| Browser storage wrapper | `infra` |
| Product storage integration | Product owner; storage primitive - `infra` |
| UI spacing tokens | `shared` |
| Button consuming spacing tokens | `ui` |

View File

@@ -0,0 +1,48 @@
---
title: Слой UI
status: draft
normative: true
---
# Слой UI
`ui` содержит reusable presentation modules без product scenario и product ownership.
## Примеры
```text
ui/
├── button/
├── input/
├── icon/
├── modal/
├── carousel/
├── tabs/
└── tooltip/
```
## Правила
**SLM-BASE-UI-001 - ОБЯЗАН.** UI module должен быть применим без product-specific knowledge.
**SLM-BASE-UI-002 - ЗАПРЕЩЕНО.** UI module не может импортировать `compositions`, `app` или product-specific infra.
**SLM-BASE-UI-003 - МОЖЕТ.** UI module может импортировать public API других UI modules и `shared`.
**SLM-BASE-UI-004 - ЗАПРЕЩЕНО.** UI module не выбирает product data source, не вызывает product scenario и не владеет multi-module behavior.
**SLM-BASE-UI-005 - МОЖЕТ.** UI module может владеть локальным interaction state, необходимым только для собственной presentation mechanics.
**SLM-BASE-UI-006 - ОБЯЗАН.** Компонент с product semantics должен принадлежать владеющему product module, а не `ui`.
## Классификация
| Сущность | Владелец |
|---|---|
| `Button`, `Input`, `Modal` | `ui` |
| `LoginForm` одной auth responsibility | Владеющий product module |
| Application header | `compositions` |
| Generic date picker | `ui` |
| Medication schedule | Владеющий product module согласно ownership |
Универсальность определяется отсутствием product knowledge, а не количеством текущих consumers.

View File

@@ -0,0 +1,122 @@
---
title: Domains в SLM Advanced
status: draft
normative: true
overlay: advanced
base: slm
---
# Domains в SLM Advanced
> Overlay: `SLM Advanced`. Base: [SLM](../../index.md).
Domain является законченным вертикальным product module с одной предметной ответственностью и явным public boundary. Кроме base-правил modules и segments, Advanced не предписывает обязательную внутреннюю архитектуру domain.
## Domain и group
**SLM-ADV-DOM-001 - ОБЯЗАН.** Конечный domain должен располагаться непосредственно в `domains` или внутри одной или нескольких навигационных groups.
```text
domains/{domain}
domains/{group}/{domain}
domains/{group}/{nested-group}/{domain}
```
**SLM-ADV-DOM-002 - ОБЯЗАН.** Узел domain tree с собственным public API, state, integration или runtime должен классифицироваться как конечный domain, а не domain group.
```text
domains/
├── navigation/ # domain
└── knv/ # group
├── auth/ # domain
├── user/ # domain
└── orders/ # domain
```
**SLM-ADV-DOM-003 - ОБЯЗАН.** Первой архитектурной единицей в group tree является конечная папка, владеющая самостоятельной product responsibility.
## Ownership
**SLM-ADV-DOM-004 - ОБЯЗАН.** Domain должен владеть одной сформулированной product responsibility и предоставлять её внешним consumers через собственный public API.
Domain может владеть:
- product model и value objects;
- scenarios и operations;
- domain state и transitions;
- normalization и product errors;
- product source integration;
- framework hooks и UI одного domain;
- runtime-specific setup.
**SLM-ADV-DOM-005 - ЗАПРЕЩЕНО.** Domain не может владеть framework route entry, page/layout composition, UI нескольких самостоятельных product responsibilities, universal technical capability или product-agnostic UI primitive.
## Структура
```text
domains/knv/auth/
├── hooks/
├── providers/
├── services/
├── stores/
├── mappers/
├── types/
├── ui/
├── parts/
└── index.ts
```
Это пример, а не обязательный scaffold. Небольшой domain может состоять из одного файла и public entrypoint.
Domain может хранить файлы в корне и использовать любые необходимые segments согласно base-правилам [SLM-BASE-SEG-001 - SLM-BASE-SEG-003](../../segments.md#правила).
**SLM-ADV-DOM-006 - ЗАПРЕЩЕНО.** Нельзя создавать пустые segments или копировать полную структуру другого domain без текущей ответственности.
**SLM-ADV-DOM-007 - МОЖЕТ.** Domain может владеть hooks, Providers, Context, services, stores, mappers, types, product UI и другими implementation units своей ответственности.
## Public API
Public boundary Advanced domain следует base-правилам `SLM-BASE-API-001` и `SLM-BASE-API-002`.
**SLM-ADV-DOM-009 - МОЖЕТ.** Public API domain может экспортировать выбранные командой hooks, Providers, Context, components, service APIs, store access APIs и types как стабильный contract.
**SLM-ADV-DOM-010 - ЗАПРЕЩЕНО.** Если product responsibility получила domain owner, app, composition или infra не могут создавать параллельную модель этой ответственности либо обходить её public boundary.
## Dependencies
```text
composition -> domain
domain -> domain | infra | ui | shared
```
**SLM-ADV-DOM-011 - МОЖЕТ.** Domain может runtime-импортировать public API другого Advanced domain.
**SLM-ADV-DOM-012 - МОЖЕТ.** Domain может напрямую использовать public API `infra`, `ui` и `shared` без обязательной промежуточной abstraction.
Runtime cycles запрещены base-правилом `SLM-BASE-API-016`.
**SLM-ADV-DOM-013 - ЗАПРЕЩЕНО.** Type-only dependency cycle между domains запрещён, даже если runtime graph остаётся ацикличным.
## Data flow
```text
composition
-> domain public API
-> domain hook/service
-> infra
-> external source
```
**SLM-ADV-DOM-014 - ОБЯЗАН.** Product consumers за пределами domain должны получать его данные и поведение через public API domain, а не повторять тот же integration flow напрямую через `infra`.
## Product UI
**SLM-ADV-DOM-015 - МОЖЕТ.** Product UI одной domain responsibility может принадлежать этому domain.
UI нескольких самостоятельных responsibilities остаётся в `compositions` согласно base-правилу `SLM-BASE-CMP-006`.
## Monorepo boundary
**SLM-ADV-DOM-016 - ОБЯЗАН.** Advanced domain должен оставаться внутри `apps/{app}/src/domains` до принятия отдельной package-модели.
**SLM-ADV-DOM-017 - ЗАПРЕЩЕНО.** Workspace package не может называться Advanced Domain для целей Specification, если он не соответствует application path и ownership этой главы.

View File

@@ -0,0 +1,60 @@
---
title: SLM Advanced
status: draft
normative: true
overlay: advanced
base: slm
---
# SLM Advanced
`SLM Advanced` является независимым overlay непосредственно над [base SLM](../../index.md).
```text
SLM Advanced = SLM + Advanced rules
```
## Отличия от SLM
| Область | Base SLM | SLM Advanced |
|---|---|---|
| Product ownership | Product logic принадлежит compositions | Устойчивая product responsibility может быть извлечена в domain |
| Слои | `app`, `compositions`, `infra`, `ui`, `shared` | Добавляется `domains` |
| Структура domain | Отсутствует | Свободная, внутренние роли выбирает команда |
| Domain dependencies | Отсутствуют | Ацикличные imports через public API разрешены |
| External integration | Composition использует infra | Domain может использовать infra напрямую |
## Расширение архитектурной модели
**SLM-ADV-ARCH-001 - ОБЯЗАН.** SLM Advanced должен расширять набор base-слоёв слоем `domains` для самостоятельных product responsibilities.
```text
src/
├── app/
├── compositions/
├── domains/
├── infra/
├── ui/
└── shared/
```
**SLM-ADV-ARCH-002 - ОБЯЗАН.** Дополнительные dependency edges Advanced должны соответствовать следующему направлению:
```text
compositions -> domains
domains -> domains | infra | ui | shared
```
Base dependency direction для остальных слоёв сохраняется.
## Изменение product ownership
**SLM-ADV-CMP-001 - ОБЯЗАН.** Если product responsibility получила domain owner, Advanced заменяет для этой ответственности base-правило `SLM-BASE-CMP-001`: domain владеет собственной product logic, а composition владеет application flow и связывает public APIs.
Product logic без domain owner продолжает следовать base SLM и принадлежит минимальной composition.
**SLM-ADV-CMP-010 - МОЖЕТ.** Composition module может импортировать public API Advanced domains в дополнение к imports, разрешённым base-правилом `SLM-BASE-CMP-010`.
## Advanced Domain Specification
Полная Advanced-модель слоя описана в [Domains](./domains.md). Других mode-specific отличий текущий draft Advanced не вводит.

View File

@@ -0,0 +1,130 @@
---
title: Business в SLM Pro
status: draft
normative: true
overlay: pro
base: slm
---
# Business
> Overlay: `SLM Pro`.
`business` является framework-neutral зоной domain и единственным владельцем его продуктовой semantics.
## Структура
```text
domains/{group...}/{domain}/business/
├── {domain}.factory.ts
├── index.ts
├── types/
├── ports/
├── services/
├── errors/
├── mappers/
├── selectors/
├── validators/
└── lib/
```
Конкретный набор внутренних segments определяется размером domain. Обязательны роль factory и public boundary, но не каждая папка из примера.
## Factory boundary
**SLM-PRO-BUS-001 - ОБЯЗАН.** Business должен создавать public runtime API через factory `{domain}Factory`.
**SLM-PRO-BUS-002 - ОБЯЗАН.** Factory должна принимать все runtime capabilities через business-owned dependency contracts.
**SLM-PRO-BUS-003 - ОБЯЗАН.** Factory должна возвращать framework-neutral DomainRuntime. Stateless logic API считается DomainRuntime и соблюдает тот же public boundary.
**SLM-PRO-BUS-004 - ЗАПРЕЩЕНО.** Factory не может возвращать React hooks, components, Providers, layouts, route guards или framework boundaries.
**SLM-PRO-BUS-005 - ЗАПРЕЩЕНО.** Factory constructor не может выполнять I/O, открывать socket, регистрировать subscription, запускать timer или читать hidden environment.
## Public API
**SLM-PRO-BUS-006 - ОБЯЗАН.** `business/index.ts` должен экспортировать единственное runtime value: factory.
**SLM-PRO-BUS-007 - МОЖЕТ.** `business/index.ts` может экспортировать business-owned types через `export type`.
```ts
export { authFactory } from './auth.factory'
export type {
AuthDeps,
AuthFactory,
AuthRuntime,
AuthState,
} from './types'
```
**SLM-PRO-BUS-008 - ЗАПРЕЩЕНО.** Error classes, error guards, error code constants, selectors, validators, formatters, services, mappers и port implementations не экспортируются как отдельные runtime values.
Если внешнему consumer нужна такая capability, она должна быть осмысленной частью factory runtime API, а не обходным direct export.
## Runtime API
DomainRuntime может предоставлять:
- commands;
- imperative queries;
- snapshots;
- subscriptions;
- selectors через стабильные methods;
- validation operations;
- typed outcomes;
- explicit lifecycle operations.
**SLM-PRO-BUS-009 - ОБЯЗАН.** Runtime API должен говорить на языке domain и не повторять endpoint names, SDK tree или storage schema.
**SLM-PRO-BUS-010 - ЗАПРЕЩЕНО.** Public contract не может раскрывать generated DTO, SDK client, query-library result, concrete store API, raw Context или adapter.
## Dependencies и ports
**SLM-PRO-BUS-011 - ОБЯЗАН.** Business-owned dependency описывает минимальную внешнюю возможность на языке domain.
```ts
export type AuthPhonePort = {
requestCode: (phone: string) => Promise<unknown>
verifyCode: (input: VerifyPhoneCodeInput) => Promise<unknown>
}
```
**SLM-PRO-BUS-012 - ОБЯЗАН.** Ненадёжный внешний результат должен приниматься как `unknown`, если business обязан проверить его runtime-форму.
**SLM-PRO-BUS-013 - ЗАПРЕЩЕНО.** Business dependency не может быть generated DTO, полный SDK client, `StoreApi`, QueryClient или framework hook.
**SLM-PRO-BUS-014 - ОБЯЗАН.** Subscription port должен предоставлять cleanup contract.
## Imports
Business может runtime-импортировать:
- собственные файлы;
- детерминированный `shared`;
- pure libraries без I/O, hidden state и public type leakage.
Business может type-only импортировать стабильный public contract другого domain, если dependency невозможно корректно описать локальным port. Локальный consumer-owned port является предпочтительным вариантом.
**SLM-PRO-BUS-015 - ЗАПРЕЩЕНО.** Business не импортирует React, query runtime, state manager, SDK, generated operation, HTTP client, storage implementation, browser API, infra, composition или assembly.
Cross-domain imports дополнительно регулируются правилами `SLM-PRO-XDOM-*` в [Cross-domain boundary](./cross-domain-boundary.md).
## Normalization и errors
**SLM-PRO-BUS-017 - ОБЯЗАН.** External result должен быть нормализован в business-owned model до выхода из DomainRuntime.
**SLM-PRO-BUS-018 - ОБЯЗАН.** Malformed successful response должен считаться нарушением runtime contract, а не валидным отсутствием данных.
**SLM-PRO-BUS-019 - ОБЯЗАН.** Expected domain outcome и technical failure должны быть различимы в public contract.
**SLM-PRO-BUS-020 - ЗАПРЕЩЕНО.** Source error, HTTP status, SDK error class, raw response и transport message не могут быть consumer contract.
Business может выражать ожидаемые outcomes через typed result или domain error. Эта draft-версия не предписывает единственную форму обработки ожидаемых ошибок, но требует business-owned semantics и стабильных discriminants.
## State
**SLM-PRO-BUS-021 - ОБЯЗАН.** Business владеет domain state model, допустимыми transitions и semantics commands/selectors.
Framework-neutral state runtime может быть создан самой factory или предоставлен через business-owned port. Concrete store implementation остаётся запрещённой dependency по [SLM-PRO-BUS-015](#imports) и не раскрывается в public API.

View File

@@ -0,0 +1,105 @@
---
title: Client и server assembly в SLM Pro
status: draft
normative: true
overlay: pro
base: slm
---
# Client и Server Assembly
> Overlay: `SLM Pro`.
`client` и `server` создают готовые runtime-specific instances одного domain поверх его business factory и adapters.
## Client assembly
```text
domains/{group...}/{domain}/client/
├── create-{domain}-client-runtime.ts
└── index.ts
```
```ts
export const createAuthClientRuntime = (): AuthRuntime => {
return authFactory({
phoneAuth: browserPhoneAuthAdapter,
session: browserSessionAdapter,
})
}
```
Client technical inputs ограничены client environment/config и platform capabilities, необходимыми для создания adapters собственного domain. Runtime values другого domain technical input не являются.
**SLM-PRO-ASM-001 - ОБЯЗАН.** Runtime imports client assembly должны ограничиваться собственной business factory, собственными client adapters, собственной React surface и необходимыми client technical inputs. Type-only foreign contracts допускаются по [SLM-PRO-XDOM-005](./cross-domain-boundary.md#type-only-contracts).
**SLM-PRO-ASM-002 - ОБЯЗАН.** Client assembly должна возвращать готовый runtime собственного domain.
Запрет на foreign runtime values определяется правилом [SLM-PRO-XDOM-001](./cross-domain-boundary.md#runtime-imports).
**SLM-PRO-ASM-004 - МОЖЕТ.** Client или server assembly может принимать готовую внешнюю capability через собственный input contract.
```ts
createUserClientRuntime({ auth: auth.session })
```
Такой input не даёт user domain права создавать AuthRuntime или импортировать его client entrypoint.
## Server assembly
```text
domains/{group...}/{domain}/server/
├── create-{domain}-server-runtime.ts
└── index.ts
```
Server technical inputs ограничены request/framework data, server environment/config и platform capabilities, необходимыми для создания server adapters собственного domain. Runtime values другого domain technical input не являются.
**SLM-PRO-ASM-005 - ОБЯЗАН.** Server assembly должна создавать новый runtime в scope, соответствующем request или другой явно выбранной server lifetime.
**SLM-PRO-ASM-006 - ЗАПРЕЩЕНО.** Server assembly не может повторно использовать adapter или runtime instance, захвативший request credentials, cookies, headers или user-specific state другого scope.
**SLM-PRO-ASM-007 - ОБЯЗАН.** Framework/request input используется только для создания server adapters и не протекает как raw framework object в business API.
**SLM-PRO-ASM-008 - ОБЯЗАН.** Server entrypoint должен иметь явный server-only marker, если framework предоставляет такой механизм.
**SLM-PRO-ASM-016 - ОБЯЗАН.** Runtime imports server assembly должны ограничиваться собственной business factory, собственными server adapters и необходимыми server technical inputs; runtime import React/client surface запрещён. Type-only foreign contracts допускаются по [SLM-PRO-XDOM-005](./cross-domain-boundary.md#type-only-contracts).
## Constructor и activation
Assembly определяет способ создания, но не владеет полным cross-domain graph.
Отсутствие product request, socket connection и background resource при вызове runtime creator определяется base-правилом `SLM-BASE-LIFE-002`.
```text
module import
→ определяет creator
creator call
→ создаёт runtime instance
explicit start
→ запускает resources
```
**SLM-PRO-ASM-010 - ОБЯЗАН.** Resources запускает composition scope owner в выбранном scope согласно [lifecycle rules](../../../runtime-and-lifecycle.md).
## Public entrypoints
**SLM-PRO-CMP-004 - ЗАПРЕЩЕНО.** Composition не может повторять adapter wiring, если domain public assembly уже создаёт готовый runtime.
**SLM-PRO-CMP-013 - ОБЯЗАН.** Composition должна использовать public client/server creator domain, если domain предоставляет runtime-specific assembly.
**SLM-PRO-CMP-014 - МОЖЕТ.** Composition может вызвать public business factory напрямую только для universal domain, у которого нет external ports, concrete adapters и runtime-specific input.
**SLM-PRO-ASM-011 - ОБЯЗАН.** Client и server assembly должны иметь разные public entrypoints.
**SLM-PRO-ASM-012 - ЗАПРЕЩЕНО.** Общий domain barrel не может runtime-реэкспортировать одновременно client и server surfaces.
## Server/client bridge
Client и server runtimes являются разными instances над общей business semantics.
Запрет на передачу DomainRuntime, functions, Context, store или query client через serializable server/client boundary определяется правилом [SLM-BASE-DATA-012](../../../state-and-data.md#serializable-boundaries).
**SLM-PRO-ASM-014 - МОЖЕТ.** Server может передать client assembly только serializable business-owned bootstrap data без secrets и mutable runtime objects.

View File

@@ -0,0 +1,125 @@
---
title: Cross-domain boundary
status: draft
normative: true
overlay: pro
base: slm
---
# Cross-domain Boundary
> Overlay: `SLM Pro`.
Domains не образуют скрытый runtime graph внутри слоя `domains`. Граф связывается только graph owner в `compositions`.
## Composition graph
**SLM-PRO-CMP-001 - ОБЯЗАН.** Runtime graph нескольких domains должен собираться в composition, которая владеет его scope.
```ts
const auth = createAuthRuntime()
const user = createUserRuntime({ auth: auth.session })
const orders = createOrdersRuntime({ user: user.agreements })
```
**SLM-PRO-CMP-002 - ОБЯЗАН.** Composition должна создавать domain runtimes в явном ацикличном порядке.
**SLM-PRO-CMP-003 - ОБЯЗАН.** Cross-domain dependency должна передаваться как готовая минимальная capability, а не разрешаться service locator или domain import.
**SLM-PRO-CMP-012 - ОБЯЗАН.** App-specific graph type должен отражать только реально предоставленные runtimes; `Partial<Graph>` с последующим приведением к полному graph запрещён.
## Runtime imports
**SLM-PRO-XDOM-001 - ЗАПРЕЩЕНО.** Ни одна zone domain A не может импортировать, реэкспортировать, dynamic-import или разрешать через service locator runtime value domain B.
Запрет включает foreign business API, hooks, Provider, Context, components, adapters, runtime creators и event emitters.
Foreign runtime capability может поступить только argument-ом от composition согласно разделу [Runtime capability injection](#runtime-capability-injection).
## Type-only contracts
**SLM-PRO-API-008 - СЛЕДУЕТ.** Cross-domain capability следует описывать consumer-owned structural port вместо зависимости от полного foreign API type.
**SLM-PRO-XDOM-014 - ЗАПРЕЩЕНО.** Public contract зависимого domain не может реэкспортировать полный foreign DomainRuntime type как собственную cross-domain dependency.
**SLM-PRO-XDOM-005 - МОЖЕТ.** Business и client/server input contracts domain могут type-only импортировать минимальный стабильный business contract другого domain.
Предпочтение consumer-owned port определяется правилом `SLM-PRO-API-008`.
```ts
export type UserAuthPort = {
getSessionSnapshot: () => SessionSnapshot
subscribeToSession: (listener: () => void) => () => void
}
```
Type-only import не разрешает runtime import и не переносит ownership.
**SLM-PRO-XDOM-012 - ЗАПРЕЩЕНО.** Type dependency cycle между domains запрещён, даже если не создаёт runtime cycle.
## Runtime capability injection
**SLM-PRO-XDOM-007 - МОЖЕТ.** Domain runtime creator может принять готовую structurally compatible capability, созданную другим domain и переданную composition.
```ts
const auth = createAuthClientRuntime()
const user = createUserClientRuntime({ auth: auth.session })
```
User domain знает только свой input contract. Он не знает creator, Provider, adapters и scope AuthRuntime.
**SLM-PRO-XDOM-008 - ОБЯЗАН.** Передаваемая capability должна быть минимальной и не раскрывать raw store, Context, SDK client или mutable internals foreign domain.
**SLM-PRO-XDOM-013 - МОЖЕТ.** Structurally compatible foreign capability может реализовать consumer-owned port напрямую. Wrapper adapter создаётся только при необходимости преобразовать contracts или lifecycle.
## React composition
Если React-сущность использует runtime API двух domains, она принадлежит `compositions`.
```tsx
const ProtectedOrderForm = () => {
const auth = useAuth()
const order = useOrder()
return auth.isAuthenticated
? <OrderForm order={order} />
: <AuthPrompt />
}
```
**SLM-PRO-XDOM-009 - ОБЯЗАН.** Props, callbacks и slots, передаваемые из composition в domain UI, должны оставаться domain-local или presentation-neutral. Foreign domain semantics остаётся во владеющей composition.
```tsx
<AuthRequired>
<OrderForm />
</AuthRequired>
```
Такое связывание выполняется в composition, а не внутри auth или orders.
## Events
Прямая подписка на event emitter другого domain через runtime import запрещена правилом `SLM-PRO-XDOM-001`.
Composition может передать event capability через consumer-owned port:
```ts
const orders = createOrdersClientRuntime({
userEvents: {
subscribeToIdentity: user.identity.subscribe,
},
})
```
## Cycles
**SLM-PRO-XDOM-011 - ЗАПРЕЩЕНО.** Runtime dependency cycle между domains является нарушением границы и не может скрываться event bus, lazy resolution или two-way service locator.
**SLM-PRO-LIFE-008 - ОБЯЗАН.** Cross-domain graph запускается в dependency order и освобождается в обратном порядке.
Ненормативное пояснение: при обнаружении цикла следует пересмотреть один из вариантов:
- пересмотреть границы domains;
- перенести orchestration в composition;
- выделить отдельную product responsibility;
- инвертировать зависимость через consumer-owned port.

View File

@@ -0,0 +1,81 @@
---
title: Framework surface в SLM Pro
status: draft
normative: true
overlay: pro
base: slm
---
# Framework Surface
> Overlay: `SLM Pro`.
Framework surface адаптирует готовый DomainRuntime к execution model конкретного framework. В текущей структуре React surface располагается в `react/`.
## Структура React surface
```text
domains/{group...}/{domain}/react/
├── context/
├── providers/
├── hooks/
├── ui/
└── index.ts
```
Ни один segment не обязателен без реальной потребности.
## Runtime access
**SLM-PRO-FRM-001 - ОБЯЗАН.** Framework surface должна работать с конкретным DomainRuntime через domain-owned runtime access boundary.
**SLM-PRO-FRM-002 - ЗАПРЕЩЕНО.** Framework hook или component не может самостоятельно вызывать business factory, создавать adapters или разрешать runtime из global service locator.
**SLM-PRO-FRM-003 - ОБЯЗАН.** Runtime access boundary должна получать готовый DomainRuntime извне и не создавать параллельное domain state.
Для React типичным механизмом является private Context, связывающий статически экспортированные hooks/components с переданным runtime instance. Это пояснение не предписывает точную форму или количество Providers в текущем draft.
**Domain runtime Provider** - часть framework surface, получающая готовый DomainRuntime и предоставляющая его framework consumers одного domain. Provider не создаёт cross-domain graph автоматически.
## Imports
**SLM-PRO-FRM-004 - ОБЯЗАН.** React surface должна импортировать business runtime contracts только через `import type`.
**SLM-PRO-FRM-005 - ЗАПРЕЩЕНО.** React surface не может runtime-импортировать business factory, private business services, selectors, validators, errors или constants.
**SLM-PRO-FRM-006 - ЗАПРЕЩЕНО.** React surface не может импортировать domain adapters, SDK, product infra client или assembly.
**SLM-PRO-FRM-007 - МОЖЕТ.** React surface может импортировать public API `ui`, `shared` и framework libraries, разрешённые её runtime profile.
Cross-domain runtime imports framework surface запрещены правилом [SLM-PRO-XDOM-001](./cross-domain-boundary.md#runtime-imports).
## Hooks
**SLM-PRO-FRM-009 - ОБЯЗАН.** Domain hook должен получать product data и behavior только через текущий DomainRuntime.
**SLM-PRO-FRM-010 - МОЖЕТ.** Hook может использовать framework query/cache runtime как private implementation поверх imperative DomainRuntime query.
**SLM-PRO-FRM-011 - ЗАПРЕЩЕНО.** Query hook не может использовать adapter или SDK call как fetcher в обход DomainRuntime.
**SLM-PRO-FRM-012 - ЗАПРЕЩЕНО.** Query-library types, cache keys и raw mutate API не могут становиться public business contract.
## Domain UI
Domain React UI может:
- вызывать hooks своего domain;
- использовать universal UI;
- отображать domain-owned states и outcomes;
- принимать callbacks, props и slots от composition.
**SLM-PRO-FRM-013 - ЗАПРЕЩЕНО.** Domain UI не может импортировать runtime другого domain или оркестрировать route/page flow.
Владение React UI, использующим несколько domains, определено base-правилом [SLM-BASE-CMP-006](../../../layers/compositions.md#product-ui).
## Client boundary
**SLM-PRO-FRM-015 - ОБЯЗАН.** Entry point React hooks, Context и interactive UI должен быть явно отмечен как client runtime согласно правилам используемого framework.
**SLM-PRO-FRM-016 - ЗАПРЕЩЕНО.** Server-compatible React export не может попадать в client entrypoint только из-за нахождения рядом с client hooks или Provider.
React не является синонимом client runtime; environment profile определяется фактическими dependencies export.

View File

@@ -0,0 +1,158 @@
---
title: Domains в SLM Pro
status: draft
normative: true
overlay: pro
base: slm
---
# Domains в SLM Pro
> Overlay: `SLM Pro`. Base: [SLM](../../../index.md).
Domain является изолированным вертикальным product module с одной предметной ответственностью, явным public boundary и строгими внутренними dependency zones.
## Domain и group
**SLM-PRO-DOM-001 - ОБЯЗАН.** Конечный Pro domain должен располагаться непосредственно в `domains` или внутри одной или нескольких навигационных groups.
```text
domains/{domain}
domains/{group}/{domain}
domains/{group}/{nested-group}/{domain}
```
**SLM-PRO-DOM-002 - ОБЯЗАН.** Узел domain tree с собственным public API, state, integration, assembly или runtime должен классифицироваться как конечный domain, а не domain group.
```text
domains/
├── navigation/ # domain
└── knv/ # group
├── auth/ # domain
├── user/ # domain
└── orders/ # domain
```
**SLM-PRO-DOM-003 - ОБЯЗАН.** Первой архитектурной единицей в group tree является конечная папка, владеющая самостоятельной product responsibility.
## Ownership
**SLM-PRO-DOM-004 - ОБЯЗАН.** Pro domain должен владеть одной сформулированной product responsibility и предоставлять её внешним consumers через собственные public entrypoints.
Pro domain может владеть:
- product model и value objects;
- scenarios и operations;
- domain state и transitions;
- normalization и product errors;
- business-owned ports;
- concrete integrations собственных ports;
- framework hooks и UI одного domain;
- client/server runtime assembly.
**SLM-PRO-DOM-005 - ЗАПРЕЩЕНО.** Domain не может владеть framework route entry, page/layout composition, UI нескольких самостоятельных product responsibilities, universal technical capability или product-agnostic UI primitive.
Public entrypoints Pro domain следуют base-правилам `SLM-BASE-API-001` и `SLM-BASE-API-002`; Pro-главы вводят дополнительные ограничения exports.
**SLM-PRO-DOM-007 - ЗАПРЕЩЕНО.** Если product responsibility получила Pro domain owner, app, composition или infra не могут создавать параллельную модель этой ответственности либо обходить её public boundary.
**SLM-PRO-DOM-008 - ОБЯЗАН.** Для domain-owned responsibility это правило заменяет base-правило `SLM-BASE-CMP-001`: business владеет product logic, а composition владеет application flow и runtime graph.
Product responsibility считается устойчивой, если имеет самостоятельную product model или transitions, используется несколькими application flows либо владеет external integration/lifecycle contract.
**SLM-PRO-DOM-017 - ОБЯЗАН.** Каждая устойчивая product responsibility должна иметь Pro domain owner; route/page-local presentation flow остаётся ответственностью composition.
## Внутренние zones
```text
domains/{group...}/{domain}/
├── business/
├── react/
├── adapters/
├── client/
└── server/
```
| Zone | Статус | Ответственность |
|---|---|---|
| [`business`](./business.md) | Обязательная | Product model, factory, ports, scenarios, errors |
| [`react`](./framework.md) | Опциональная | React runtime access, hooks, Providers, domain UI |
| [`adapters`](./ports-and-adapters.md) | Опциональная | Concrete реализации business-owned ports |
| [`client`](./client-and-server.md) | Опциональная | Browser/client assembly одного domain |
| [`server`](./client-and-server.md) | Опциональная | Server/request assembly одного domain |
**SLM-PRO-DOM-009 - ОБЯЗАН.** Каждый Pro domain должен содержать `business` как единственного владельца product model и business semantics.
**SLM-PRO-DOM-010 - СЛЕДУЕТ.** Опциональную zone следует добавлять только при наличии реального runtime consumer и самостоятельной ответственности.
**SLM-PRO-DOM-011 - ЗАПРЕЩЕНО.** Нельзя создавать пустые симметричные `react`, `adapters`, `client` или `server` на будущее.
**SLM-PRO-DOM-012 - ОБЯЗАН.** Domain zones должны соблюдать внутреннюю dependency direction, даже если физически находятся под одним владельцем.
**SLM-PRO-MOD-001 - ОБЯЗАН.** `business`, `react`, `adapters`, `client` и `server` являются внутренними zones одного domain, а не самостоятельными верхнеуровневыми modules.
**SLM-PRO-SEG-001 - ЗАПРЕЩЕНО.** Domain zones нельзя трактовать как взаимозаменяемые generic segments.
Внутри каждой zone могут использоваться обычные base SLM segments по фактической необходимости.
## Внутреннее направление
```text
business -> shared | pure libraries
react -> ui | shared | framework libraries
adapters -> infra | SDK | platform runtime
client -> own business factory | own client adapters | own framework surface | client technical inputs
server -> own business factory | own server adapters | server technical inputs
```
Матрица описывает runtime imports. React surface может type-only импортировать собственные business contracts, adapters - собственные business ports/types, а client/server inputs - разрешённые cross-domain contracts.
## Путь данных
```text
composition
-> domain client/server assembly при наличии runtime-specific setup
или напрямую business factory для universal domain
-> DomainRuntime
-> business scenario
-> business-owned port
-> domain adapter
-> infra / SDK / storage / external source
```
**SLM-PRO-DOM-013 - ОБЯЗАН.** DomainRuntime, созданный business factory, должен быть единственным product gateway своего Pro domain для runtime consumers.
Stateless logic API также является DomainRuntime, если он создан factory и соблюдает тот же public boundary.
## Product UI
**SLM-PRO-DOM-014 - МОЖЕТ.** Product UI одной Pro domain responsibility может принадлежать framework surface этого domain.
UI нескольких самостоятельных responsibilities остаётся в `compositions` согласно base-правилу `SLM-BASE-CMP-006`.
## Cross-domain graph
```text
composition
-> создаёт несколько domain runtimes
-> передаёт готовые capabilities
```
Pro domain не создаёт runtime другого domain и не импортирует его runtime surface. Точные правила определены в [Cross-domain boundary](./cross-domain-boundary.md).
**Graph owner** - composition, являющаяся scope owner нескольких DomainRuntime, связанных направленными dependencies в одном ацикличном graph, и определяющая порядок их создания, activation и cleanup.
## Monorepo boundary
**SLM-PRO-DOM-015 - ОБЯЗАН.** Pro domain должен оставаться внутри `apps/{app}/src/domains` до принятия отдельной package-модели.
**SLM-PRO-DOM-016 - ЗАПРЕЩЕНО.** Workspace package не может называться Pro Domain для целей Specification, если он не соответствует application path и ownership этой главы.
## Главы Pro Domain Specification
- [Business](./business.md)
- [Framework surface](./framework.md)
- [Ports и adapters](./ports-and-adapters.md)
- [Client и server assembly](./client-and-server.md)
- [Cross-domain boundary](./cross-domain-boundary.md)
- [Тестирование Pro domains](./testing.md)

View File

@@ -0,0 +1,100 @@
---
title: Ports и adapters в SLM Pro
status: draft
normative: true
overlay: pro
base: slm
---
# Ports и Adapters
> Overlay: `SLM Pro`.
Port определяет потребность business. Adapter связывает эту потребность с concrete runtime.
## Ownership
```text
domain/
├── business/
│ └── ports/
└── adapters/
```
**SLM-PRO-ADP-001 - ОБЯЗАН.** Port должен принадлежать `business` того domain, который потребляет capability.
**SLM-PRO-ADP-002 - ОБЯЗАН.** Concrete adapter должен принадлежать тому же domain, но находиться вне `business`.
**SLM-PRO-ADP-003 - ЗАПРЕЩЕНО.** Infra или external SDK не могут объявлять business port от имени domain.
**SLM-PRO-ADP-014 - ОБЯЗАН.** External technical capability из infra, SDK, storage или platform runtime должна реализовывать business port через adapter собственного domain, а этот adapter должен подключаться assembly того же domain. Готовая capability другого DomainRuntime может удовлетворять consumer-owned port напрямую только по правилу [SLM-PRO-XDOM-013](./cross-domain-boundary.md#runtime-capability-injection).
## Adapter contract
**SLM-PRO-ADP-004 - ОБЯЗАН.** Responsibilities adapter должны ограничиваться применимыми integration operations:
- импортировать type-only business port и domain input types;
- импортировать public infra API, SDK или platform runtime;
- переводить domain arguments в transport arguments;
- возвращать raw/unknown source result для business normalization;
- подписываться на concrete event source через явный lifecycle contract.
**SLM-PRO-ADP-005 - ЗАПРЕЩЕНО.** Adapter не может выполнять следующие domain/framework responsibilities:
- создавать domain error;
- выбирать domain fallback;
- реализовывать business rule;
- объявлять domain model;
- экспортировать concrete client consumer-коду;
- вызывать framework hook;
- runtime-импортировать или самостоятельно разрешать другой domain runtime.
Adapter может работать с минимальной foreign capability, явно переданной composition, только для преобразования contract или lifecycle согласно `SLM-PRO-XDOM-013`.
**SLM-PRO-ADP-006 - ОБЯЗАН.** Adapter должен реализовывать ровно тот port contract, который необходим business.
**SLM-PRO-ADP-007 - ЗАПРЕЩЕНО.** Нельзя передавать полный client, если port требует ограниченный набор capabilities.
**SLM-PRO-ADP-008 - ЗАПРЕЩЕНО.** Adapter integration logic не должна писаться inline в composition или runtime assembly.
## Client и server adapters
Adapters могут быть разделены по runtime:
```text
adapters/
├── client/
│ ├── browser-session.adapter.ts
│ └── websocket-orders.adapter.ts
└── server/
├── request-session.adapter.ts
└── server-orders-api.adapter.ts
```
**SLM-PRO-ADP-009 - ОБЯЗАН.** Client adapter не должен попадать в server graph, а server adapter - в client graph.
**SLM-PRO-ADP-010 - ОБЯЗАН.** Runtime-specific adapter должен иметь явный environment marker, если framework предоставляет такой механизм.
## Event sources
Socket, subscription и event listener реализуют event port:
```ts
export type OrdersEventsPort = {
subscribe: (listener: (event: unknown) => void) => () => void
}
```
**SLM-PRO-ADP-011 - ОБЯЗАН.** Event adapter должен возвращать cleanup и не открывать connection при module import.
**SLM-PRO-ADP-012 - ОБЯЗАН.** Wire event проходит business normalization до изменения domain state или передачи consumer-коду.
**SLM-PRO-LIFE-013 - МОЖЕТ.** Один physical transport может обслуживать adapters нескольких domains, если transport остаётся domain-agnostic, а adapters получают суженные channels.
## Public boundary
**SLM-PRO-API-014 - ЗАПРЕЩЕНО.** Public business, framework, client или server entrypoint не может реэкспортировать concrete adapter внешним consumers.
**SLM-PRO-ADP-013 - ЗАПРЕЩЕНО.** `adapters` не имеет внешнего public API для app, compositions или других domains.
Adapters доступны только assembly собственного domain и собственным contract tests.

View File

@@ -0,0 +1,63 @@
---
title: Тестирование Pro domains
status: draft
normative: true
overlay: pro
base: slm
---
# Тестирование Pro Domains
> Overlay: `SLM Pro`.
Общие правила [тестирования и соответствия](../../../testing-and-conformance.md) дополняются проверками строгих business, adapter, assembly и framework boundaries.
## Business factory tests
**SLM-PRO-TEST-001 - ОБЯЗАН.** Каждый public method runtime API, возвращаемого factory, должен иметь factory-level tests.
Factory-level tests должны проверять применимые случаи:
- happy path;
- malformed external result;
- rejected dependency;
- синхронное исключение dependency;
- domain outcome/error semantics;
- side-effect order;
- state transition;
- отсутствие constructor-time I/O;
- public API shape.
**SLM-PRO-TEST-002 - ОБЯЗАН.** Factory-level test должен создавать runtime через public `business` entrypoint, а не deep-import factory internals.
## Adapter tests
**SLM-PRO-TEST-003 - ОБЯЗАН.** Adapter с mapping, transport payload, error channel или lifecycle должен иметь contract tests на применимые responsibilities.
**SLM-PRO-TEST-004 - ЗАПРЕЩЕНО.** Adapter test не должен дублировать business scenario tests или утверждать domain fallback/error semantics.
## Assembly tests
**SLM-PRO-TEST-005 - ОБЯЗАН.** Client/server assembly tests должны проверять корректную передачу ports, runtime profile isolation и отсутствие I/O при creation.
**SLM-PRO-TEST-006 - ОБЯЗАН.** Server assembly с request data должен иметь isolation test для параллельных scopes.
## Framework tests
**SLM-PRO-TEST-007 - ОБЯЗАН.** Framework surface tests должны проверять runtime access boundary, предсказуемую ошибку при отсутствии runtime boundary, mapping public outcomes и lifecycle integration.
## Composition tests
**SLM-PRO-TEST-009 - ОБЯЗАН.** Tests cross-domain composition должны проверять topology, точный graph contract, переданные capabilities и lifecycle cleanup.
**SLM-PRO-TEST-010 - ОБЯЗАН.** Scope с неполным набором domains не должен типизироваться как полный application graph.
## Architecture checks
**SLM-PRO-TEST-019 - ОБЯЗАН.** Pro repository checks должны проверять применимые строгие domain boundaries:
- client/server markers;
- forbidden runtime imports между domains;
- private adapters;
- business entrypoint shape;
- zone dependency direction.

View File

@@ -0,0 +1,55 @@
---
title: SLM Pro
status: draft
normative: true
overlay: pro
base: slm
---
# SLM Pro
`SLM Pro` является независимым overlay непосредственно над [base SLM](../../index.md).
```text
SLM Pro = SLM + Pro rules
```
## Отличия от SLM
| Область | Base SLM | SLM Pro |
|---|---|---|
| Product ownership | Product logic принадлежит compositions | Устойчивая product responsibility принадлежит изолированному domain |
| Слои | `app`, `compositions`, `infra`, `ui`, `shared` | Добавляется `domains` |
| Структура domain | Отсутствует | `business`, framework surface, adapters, client/server assembly |
| Domain dependencies | Отсутствуют | Cross-domain runtime imports запрещены, capabilities передаются composition |
| External integration | Composition использует infra | Private domain adapter реализует business-owned port |
| Testing | Risk-based base tests | Обязательные tests для используемых factory, adapter, assembly и graph boundaries |
## Расширение архитектурной модели
**SLM-PRO-ARCH-001 - ОБЯЗАН.** SLM Pro должен расширять набор base-слоёв слоем `domains` для изолированных product responsibilities.
```text
src/
├── app/
├── compositions/
├── domains/
├── infra/
├── ui/
└── shared/
```
**SLM-PRO-ARCH-002 - ОБЯЗАН.** Дополнительные dependency edges Pro должны соответствовать следующему направлению:
```text
compositions -> domains
domains -> согласно внутренним Pro zones
```
Base dependency direction для остальных слоёв сохраняется.
**SLM-PRO-CMP-010 - МОЖЕТ.** Composition module может импортировать public entrypoints Pro domains в дополнение к imports, разрешённым base-правилом `SLM-BASE-CMP-010`.
## Pro Domain Specification
Полная Pro-модель слоя описана в [Domains](./domains/index.md). Других mode-specific отличий текущий draft Pro не вводит.

View File

@@ -0,0 +1,72 @@
---
title: Модули и группы
status: draft
normative: true
---
# Модули и Группы
## Module
Module является минимальным самостоятельным владельцем ответственности и предоставляет public boundary внешнему коду.
**SLM-BASE-MOD-001 - ОБЯЗАН.** Module должен иметь одну сформулированную ответственность и одного архитектурного owner.
**SLM-BASE-MOD-002 - ОБЯЗАН.** Внешний consumer взаимодействует с module только через его public API.
**SLM-BASE-MOD-003 - СЛЕДУЕТ.** Module следует ограничивать только теми внутренними parts и segments, которые необходимы текущей ответственности.
Типичные modules:
- page, layout, screen или widget в `compositions`;
- technical service в `infra`;
- reusable UI module в `ui`.
`app` содержит framework entries и не обязан организовываться как SLM modules. `shared` может содержать небольшие public units, но не runtime modules.
## Group
Group классифицирует modules и другие groups, но не владеет поведением.
**SLM-BASE-MOD-004 - ЗАПРЕЩЕНО.** Group не может иметь `index.ts`, public API, state, runtime, dependencies или assembly.
**SLM-BASE-MOD-005 - ЗАПРЕЩЕНО.** Внешний код не может импортировать group path.
**SLM-BASE-MOD-006 - МОЖЕТ.** Group может содержать другие groups и конечные modules.
```text
compositions/
└── pages/ # group
├── home/ # composition module
└── profile/ # composition module
```
## Component
Component является presentation unit внутри module и не считается самостоятельным архитектурным owner.
**SLM-BASE-MOD-008 - ЗАПРЕЩЕНО.** Component не может самостоятельно выбирать application-level product source, выполнять module wiring или оркестрировать несколько самостоятельных modules.
**SLM-BASE-MOD-009 - МОЖЕТ.** Component может владеть локальной presentation mechanics и рендерить другие components, разрешённые слоем владельца.
**SLM-BASE-MOD-010 - ОБЯЗАН.** Presentation unit с самостоятельной ответственностью, внешними архитектурными dependencies или внутренней modular structure должна оформляться как module или nested module. Сам факт локального hook/state не делает component модулем.
## Nested module
Самостоятельная часть родительского module может быть оформлена nested module, если имеет собственную ответственность и public boundary только внутри родителя.
```text
compositions/pages/home/
└── parts/
└── hero-section/
├── hero-section.tsx
└── index.ts
```
**SLM-BASE-MOD-011 - ЗАПРЕЩЕНО.** Nested module не может использоваться для сокрытия ответственности, которой фактически владеет другой module или layer.
## Scope evolution
**SLM-BASE-MOD-012 - СЛЕДУЕТ.** Код следует поднимать из локального owner в более широкий module только после появления реального совместного consumer или общей ответственности.
**SLM-BASE-MOD-013 - ЗАПРЕЩЕНО.** Физическое повторение само по себе не доказывает общий ownership.

View File

@@ -0,0 +1,54 @@
---
title: Монорепозитории
status: draft
normative: true
---
# Монорепозитории
SLM применяется внутри границы каждого frontend-приложения. Workspace packages имеют собственные public boundaries и ownership.
## Application boundary
```text
apps/
└── web/
└── src/
├── app/
├── compositions/
├── infra/
├── ui/
└── shared/
```
**SLM-BASE-MONO-001 - ОБЯЗАН.** Каждое приложение должно самостоятельно определять свои application compositions, product ownership и runtime wiring.
**SLM-BASE-MONO-002 - ЗАПРЕЩЕНО.** Workspace package не может импортировать код из `apps/*`.
**SLM-BASE-MONO-003 - ЗАПРЕЩЕНО.** Одно приложение не может deep-import исходники другого приложения вместо общего package contract.
## Package boundary
**SLM-BASE-MONO-004 - ОБЯЗАН.** Package должен иметь самостоятельного owner, public exports и подтверждённую reuse/ownership semantics.
**SLM-BASE-MONO-005 - ЗАПРЕЩЕНО.** Нельзя создавать package только для обхода layer direction, public API или иной объявленной dependency boundary.
**SLM-BASE-MONO-006 - ОБЯЗАН.** Consumers импортируют package через объявленный package export, а не через filesystem path к internal source.
## Типичные packages
Допустимыми кандидатами являются:
- product-agnostic UI kit;
- technical infra client;
- deterministic shared foundation;
- schema/codegen/tooling package;
- configuration package без application-specific wiring.
Base SLM не присваивает package дополнительный архитектурный статус автоматически.
## Dependency direction
**SLM-BASE-MONO-009 - ОБЯЗАН.** Package dependency graph должен оставаться ацикличным и соответствовать заявленной ответственности packages.
**SLM-BASE-MONO-010 - ЗАПРЕЩЕНО.** Shared package не может импортировать application composition или app-specific infra package.

View File

@@ -0,0 +1,55 @@
---
title: Public API и импорты
status: draft
normative: true
---
# Public API и Импорты
Public API ограничивает знание consumers о внутренней структуре module. Точная форма entrypoint определяется владельцем и не требует обязательного `index.ts`.
## Общие правила
**SLM-BASE-API-001 - ОБЯЗАН.** Межмодульный import должен использовать объявленный public entrypoint импортируемого module.
**SLM-BASE-API-002 - ЗАПРЕЩЕНО.** Deep imports во внутренние segments, files и иные private paths другого module запрещены.
**SLM-BASE-API-003 - ОБЯЗАН.** Каждый runtime export должен иметь реального consumer за пределами владеющего entrypoint и стабильную ответственность.
**SLM-BASE-API-004 - ЗАПРЕЩЕНО.** Public API не может случайно раскрывать implementation unit, который владелец считает private или lifecycle которого не является частью public contract.
**SLM-BASE-API-005 - ОБЯЗАН.** Alias или package subpath должен физически разрешаться TypeScript, tests и production build.
**SLM-BASE-API-009 - МОЖЕТ.** Public entrypoint может быть root `index.ts`, отдельным named entry, package export или другим явно объявленным path.
**SLM-BASE-API-010 - ОБЯЗАН.** Public и private paths module должны быть различимы consumers и repository tooling.
## Layer matrix
| Importer | Runtime imports |
|---|---|
| `app` | Public composition entries, shared static/global resources |
| `compositions` | Compositions, infra, ui, shared |
| `infra` | Infra, shared |
| `ui` | UI, shared |
| `shared` | External pure libraries only |
## Type-only imports
**SLM-BASE-API-006 - МОЖЕТ.** `import type` может использоваться для разрешённого contract dependency без создания runtime edge.
**SLM-BASE-API-007 - ЗАПРЕЩЕНО.** Type-only import не разрешает перенос ownership, импорт private concrete runtime type или обход layer boundary.
## Groups
Отсутствие public entrypoint у group определяется base-правилом `SLM-BASE-MOD-004`.
**SLM-BASE-API-015 - ОБЯЗАН.** Composition public API экспортирует только entry components, access APIs, types и contracts, необходимые внешним composition consumers.
## Cycles
**SLM-BASE-API-016 - ЗАПРЕЩЕНО.** Runtime import cycle между modules запрещён независимо от того, способен ли bundler его выполнить.
**SLM-BASE-API-017 - ЗАПРЕЩЕНО.** Barrel не должен создавать скрытый cycle между ready composition и access API её children.
Дополнительные entrypoints и import restrictions принадлежат overlay, который их вводит.

View File

@@ -0,0 +1,13 @@
---
title: Реестр правил
status: draft
normative: false
search: false
aside: false
---
# Реестр Правил
Реестр формируется автоматически из нормативных объявлений Specification. Для быстрого перехода к известному ID также можно открыть поиск `Ctrl/⌘ K`, ввести полный идентификатор и нажать `Enter`.
<RuleCatalog />

View File

@@ -0,0 +1,83 @@
---
title: Runtime и lifecycle
status: draft
normative: true
---
# Runtime и Lifecycle
Lifecycle является архитектурной частью любого mutable runtime, subscription и external resource. Эти правила не требуют создавать отдельный runtime или factory, если у module нет соответствующего состояния или resources.
## Definition, creation и activation
Для module с создаваемым runtime применима модель:
```text
definition
-> module объявляет creator
creation
-> creator создаёт instance без external effects
activation
-> scope owner запускает resources и получает cleanup
```
**SLM-BASE-LIFE-001 - ЗАПРЕЩЕНО.** Module import не должен выполнять product I/O, открывать connection или регистрировать global listener.
**SLM-BASE-LIFE-002 - ОБЯЗАН.** Если module предоставляет factory или runtime creator, creation должна быть side-effect free относительно external resources.
**SLM-BASE-LIFE-003 - ОБЯЗАН.** Subscription, socket, timer и listener запускаются явной operation владельца scope.
**SLM-BASE-LIFE-004 - ОБЯЗАН.** Каждый запущенный resource должен иметь cleanup или dispose contract.
## Scope
| Scope | Примеры владельца |
|---|---|
| Application | Root composition/provider |
| Route branch | Route layout composition |
| Page | Page composition/provider |
| Component flow | Nested composition module |
| Request | Server composition/request builder |
| Test | Test setup/wrapper |
**SLM-BASE-LIFE-005 - ОБЯЗАН.** Scope owner должен определить количество instances и duration каждого mutable runtime или resource.
**SLM-BASE-LIFE-006 - ЗАПРЕЩЕНО.** Module-level singleton не может использоваться как случайная замена application scope.
**SLM-BASE-LIFE-007 - МОЖЕТ.** Application singleton допустим только при явном application ownership и отсутствии request-, identity- и user-specific data.
## Activation и cleanup
**SLM-BASE-LIFE-009 - ОБЯЗАН.** Повторный mount/unmount, включая development Strict Mode, не должен оставлять duplicate subscription или abandoned resource.
**SLM-BASE-LIFE-010 - СЛЕДУЕТ.** `start` и cleanup следует проектировать idempotent либо явно защищать от повторного вызова.
**SLM-BASE-LIFE-018 - ОБЯЗАН.** Если activation составного resource set завершилась ошибкой, scope owner должен освободить уже успешно запущенную часть в обратном порядке.
**SLM-BASE-LIFE-019 - ОБЯЗАН.** Ошибка cleanup должна быть наблюдаемой и не должна препятствовать попытке освободить остальные resources scope.
## Events и sockets
Product event обрабатывается владельцем product semantics; socket остаётся technical transport.
**SLM-BASE-LIFE-011 - ЗАПРЕЩЕНО.** Framework component не может открывать product socket напрямую при render или module import.
**SLM-BASE-LIFE-012 - ОБЯЗАН.** Invalid event и connection failure должны преобразовываться в product state/outcome либо technical telemetry согласно их semantics; callback error нельзя терять через unobserved throw.
## Revalidation events
Event может содержать product update или только сообщать об устаревании данных.
**SLM-BASE-LIFE-014 - ОБЯЗАН.** Invalidation intent должен выражаться product language и не требовать import конкретной query library в public product contract.
## Server runtime
**SLM-BASE-LIFE-015 - ОБЯЗАН.** User-specific server runtime создаётся в request scope.
**SLM-BASE-LIFE-016 - ЗАПРЕЩЕНО.** Process singleton не может захватывать request headers, cookies, credentials, AbortSignal или user-specific cache.
**SLM-BASE-LIFE-017 - ОБЯЗАН.** Request cancellation должна передаваться external operations, если runtime и используемая integration поддерживают cancellation.
Overlay может вводить дополнительные lifecycle boundaries только внутри собственного delta.

View File

@@ -0,0 +1,59 @@
---
title: Сегменты
status: draft
normative: true
---
# Сегменты
Segment группирует внутренние файлы module по устойчивой роли. Segment не является самостоятельным layer или module.
## Базовые segments
| Segment | Роль |
|---|---|
| `ui/` | Presentation components текущего module |
| `parts/` | Nested modules текущего module |
| `hooks/` | Framework hooks текущей ответственности |
| `providers/` | Provider implementations текущего module |
| `stores/` | Concrete state runtime текущего owner |
| `services/` | Scenario operations и service objects |
| `mappers/` | Transformation на границе ответственности |
| `types/` | Types текущего module |
| `styles/` | Styles текущего module |
| `lib/` | Небольшие internal utilities |
| `config/` | Constants и configuration текущего module |
| `tests/` | Tests публичной границы или составного runtime |
## Правила
**SLM-BASE-SEG-001 - МОЖЕТ.** Module может использовать любые необходимые segments и не обязан создавать остальные.
**SLM-BASE-SEG-002 - ЗАПРЕЩЕНО.** Нельзя создавать полный симметричный набор segments как scaffold без реального содержимого.
**SLM-BASE-SEG-003 - ОБЯЗАН.** Если файл помещён в segment, роль segment должна соответствовать фактической роли файла, а не только его расширению или имени. Файлы могут оставаться в корне небольшого module.
**SLM-BASE-SEG-004 - ЗАПРЕЩЕНО.** Segment не имеет внешнего public API независимо от module owner.
Запрет deep import в segment другого module определяется base-правилом `SLM-BASE-API-002`.
## UI и Parts
`ui/` содержит presentation components без самостоятельного architectural ownership.
`parts/` содержит nested modules с собственной внутренней структурой и локальным public boundary.
**SLM-BASE-SEG-006 - ОБЯЗАН.** Сущность с самостоятельной ответственностью, внешними архитектурными dependencies или nested modules должна размещаться в `parts`, а не маскироваться как плоский component. Локальные presentation hooks/state сами по себе не требуют `parts`.
## Hooks
**SLM-BASE-SEG-007 - ОБЯЗАН.** Hook принадлежит тому module, чью ответственность и runtime он выражает.
Примеры:
- product hook - владеющий product module;
- page-local hook - владеющая page composition;
- reusable technical hook - соответствующий infra module;
- product-agnostic UI hook - владеющий UI module.
Segments являются только внутренними организационными ролями и не вводят дополнительных архитектурных zones.

View File

@@ -0,0 +1,70 @@
---
title: State и data
status: draft
normative: true
---
# State и Data
SLM рассматривает данные и состояние через одного владельца semantics, даже если runtime использует несколько caches и projections.
## Ownership matrix
| Вид | Владелец |
|---|---|
| Product model и transitions | Product owner |
| Product source integration | Product owner; technical mechanism остаётся в infra |
| Framework projection product data | Public surface владельца product data |
| Page-local presentation state | Composition |
| Component-local interaction | Владеющий component/module |
| Technical connection/cache state | Infra или runtime-specific owner |
| Request context | Server/framework scope |
| Universal UI state | Владеющий UI module |
## Product gateway
**SLM-BASE-DATA-001 - ОБЯЗАН.** Consumer должен получать product data через public boundary владеющего module.
**SLM-BASE-DATA-002 - ЗАПРЕЩЕНО.** Composition, UI или app не могут маппить transport DTO в параллельную product model, если модель уже имеет другого owner.
**SLM-BASE-DATA-003 - ОБЯЗАН.** Product owner владеет normalization, validation и semantics отсутствия данных.
## Product state
**SLM-BASE-DATA-004 - ОБЯЗАН.** Product state model и допустимые transitions должны определяться product owner независимо от concrete state manager.
**SLM-BASE-DATA-005 - ЗАПРЕЩЕНО.** Concrete mutable store implementation не может становиться public product contract без явно объявленного владельцем стабильного store access API.
**SLM-BASE-DATA-006 - ОБЯЗАН.** Mutable product instance должен быть привязан к явному lifecycle scope.
## Query cache
Framework или technical query cache может хранить projection результата product query.
**SLM-BASE-DATA-007 - ОБЯЗАН.** Query/cache consumer за пределами product owner должен использовать public boundary владельца и не может обходить его прямым вызовом private integration или SDK.
**SLM-BASE-DATA-008 - ЗАПРЕЩЕНО.** Query cache не может объявлять собственную product model, error taxonomy или fallback policy.
**SLM-BASE-DATA-009 - ОБЯЗАН.** User/session-scoped cache keys и invalidation должны изолировать данные разных identities и scopes без использования secret как публичного key contract.
Эта draft-версия не предписывает единственное физическое место QueryClient/SWR cache. Конкретная модель оценивается по правилам public boundary владельца, lifecycle и identity isolation.
**SLM-BASE-DATA-015 - ОБЯЗАН.** Cache instance должен иметь явного creator и scope owner в composition или runtime setup.
**SLM-BASE-DATA-016 - ОБЯЗАН.** Shared framework cache должен передаваться consumers через framework-supported runtime boundary, а не через import app-specific mutable singleton.
**SLM-BASE-DATA-017 - ОБЯЗАН.** Scope owner должен очищать или изолировать private cache при смене identity и завершении соответствующего scope.
## Presentation state
**SLM-BASE-DATA-010 - МОЖЕТ.** Composition или component может использовать concrete state manager для локального presentation state.
**SLM-BASE-DATA-011 - ЗАПРЕЩЕНО.** Presentation store не должен копировать canonical product state как второй source of truth.
## Serializable boundaries
**SLM-BASE-DATA-012 - ОБЯЗАН.** Через server/client boundary передаются только serializable product-owned data без functions, stores, clients, Context и resources.
**SLM-BASE-DATA-013 - ЗАПРЕЩЕНО.** Secrets, access tokens и request credentials не должны включаться в client bootstrap snapshot.
**SLM-BASE-DATA-014 - ОБЯЗАН.** Server и client initial snapshots должны быть согласованы, если framework выполняет hydration одного UI state.

View File

@@ -0,0 +1,61 @@
---
title: Терминология
status: draft
normative: true
---
# Терминология
## Base SLM
**Base SLM** - самостоятельная минимальная архитектура, применяемая без дополнительного overlay.
## Overlay
**Overlay** - независимое опциональное нормативное расширение, применяемое непосредственно поверх base SLM. Overlay не наследует правила другого overlay.
## Слой
**Layer** - верхнеуровневая зона `src`, определяющая вид ответственности и допустимые направления зависимостей.
Base SLM использует слои `app`, `compositions`, `infra`, `ui` и `shared`.
## Модуль
**Module** - минимальный самостоятельный владелец ответственности с public boundary. Модуль может содержать код разных технических типов, если весь этот код принадлежит одной ответственности.
## Product owner
**Product owner** - module, владеющий product semantics, model, behavior, data boundary и public API одной ответственности.
## Группа
**Group** - навигационная папка, классифицирующая модули или другие группы. Группа не является модулем, не имеет public API и не владеет runtime.
## Composition
**Composition** - product module, связывающий public APIs и technical capabilities в page, route, layout, screen, widget или другой application flow.
## Scope owner
**Scope owner** - composition, request setup, provider setup или test setup, которое выбирает runtime instances и resources, их lifetime, activation и cleanup.
## Segment
**Segment** - внутренняя папка модуля, группирующая файлы по роли, например `hooks`, `services`, `types`, `styles` или `lib`.
## Компонент
**Component** - presentation unit внутри владеющего module. Компонент не является самостоятельным архитектурным owner и не выбирает application dependencies самостоятельно.
## Продуктовые данные
**Product data** - данные, состояние и outcomes, имеющие смысл в предметной области продукта. Transport DTO, raw SDK response и browser storage schema не являются product model автоматически.
## Runtime dependency
**Runtime dependency** - dependency, необходимая выполняемому коду: API другого объекта, external source, store, query runtime, event source, clock, environment или platform capability.
`import type` не создаёт runtime dependency, но может создавать статическую связанность contracts.
Термины, вводимые `SLM Advanced` или `SLM Pro`, определяются и имеют нормативную силу только внутри соответствующего overlay.

View File

@@ -0,0 +1,58 @@
---
title: Тестирование и соответствие
status: draft
normative: true
---
# Тестирование и Соответствие
Тесты проверяют public boundaries и runtime risks каждого owner. Base SLM не требует создавать неиспользуемые архитектурные конструкции ради тестовой формы.
## Risk-based tests
**SLM-BASE-TEST-018 - ОБЯЗАН.** Tests изменённого module должны покрывать применимые риски его public behavior, data boundaries и lifecycle.
Типичные риски:
- public behavior;
- malformed external data;
- rejected dependencies;
- state transitions;
- lifecycle activation и cleanup;
- request и identity isolation;
- client/server boundary;
- отсутствие import-time I/O.
**SLM-BASE-TEST-008 - ОБЯЗАН.** Client/server import boundary должна проверяться инструментом, понимающим реальный framework module graph, если application имеет раздельные environment entries. DOM unit test не заменяет production build probe.
Mode-specific test suites принадлежат overlay, который вводит соответствующие конструкции.
## Architecture conformance
Типичные mechanically enforceable checks:
- направление imports;
- deep imports;
- public entrypoints;
- runtime cycles;
- заявленный overlay и его rule set;
- unique rule IDs документации;
- generated artifacts, если они используются.
**SLM-BASE-TEST-011 - ЗАПРЕЩЕНО.** Документированное правило не считается mechanically enforced, если repository tooling его фактически не проверяет.
## Единица соответствия
**SLM-BASE-TEST-014 - ОБЯЗАН.** Application соответствует base SLM, если выполняет все base-правила. Соответствие заявленному overlay оценивается как base-правила с учётом точного scope каждой замены плюс полный rule set выбранного overlay.
**SLM-BASE-TEST-015 - ОБЯЗАН.** Изменение соответствует заявленной архитектуре, если новые и изменённые modules не создают новых нарушений применимых base-правил или правил выбранного overlay и проходят существующие checks.
**SLM-BASE-TEST-016 - ОБЯЗАН.** Отступление от правила `СЛЕДУЕТ` должно быть зафиксировано в архитектурном review или принятом decision с указанием причины и scope.
**SLM-BASE-TEST-017 - ОБЯЗАН.** Manual conformance и mechanical enforcement должны различаться явно; отсутствие автоматической проверки не отменяет применимое нормативное правило.
## Completion gate
**SLM-BASE-TEST-012 - ОБЯЗАН.** Изменение считается завершённым только после выполнения ближайших tests, typecheck, lint, build и architecture checks, существующих в repository.
**SLM-BASE-TEST-013 - ОБЯЗАН.** Невыполненная проверка и остаточный риск должны быть явно указаны в результате работы.