using System;
using Ban.Sdaid.Notation.Documents;
using Ban.Sdaid.Icd;
namespace Ban.Sdaid.Icd.Arch.Tech.Angular
{
/// <summary>
/// ADR-UI-014: Рантайм-конфигурация. Адрес API и прочие параметры среды приложение
/// узнаёт при старте из `config.json` — одна сборка работает на всех средах.
/// </summary>
public class ADR_UI_014_RuntimeConfig : IAdrDocument, IFolder<ArchAngularFolder>
{
public string Name => "ADR-UI-014. Рантайм-конфигурация";
public string Description =>
@"Фронт должен знать адрес API (и другие параметры среды) в момент старта. Параметры различаются
по средам (local/dev/stage/prod), но **сборка одна**. Фиксируем: параметры грузятся из `config.json` в
рантайме (не build-time `environment.ts`) через `provideAppInitializer` до bootstrap; `apiBaseUrl` берётся
только из `RuntimeConfigService`; деплой подменяет только `config.json`.";
public string Version => "1.0";
public string Status => "accepted";
public string[] Comments => new[]
{
"2026-08-13. Принято перед разворачиванием на стенде: фронт и API на разных origin, адрес API — из рантайм-конфига.",
};
public Type? Supersedes => null;
public static string S1_Context = """
## Контекст и постановка задачи
Приложению нужен адрес API (и потенциально другие параметры) на старте. Эти параметры
**различаются между средами** (локальная разработка, dev-стенд, stage, прод), но **артефакт
сборки должен быть один** — иначе выпуск в каждую среду требует отдельного билда, что усложняет
релиз и повышает риск расхождений.
Стандартный шаблон Angular (`environments/environment.ts`) не подходит: он работает на этапе
**сборки**, а не старта. Нужен способ подменять конфиг у уже собранного приложения.
У нас фронт и API на стенде — на **разных origin**, поэтому `apiBaseUrl` — абсолютный, а бэк
держит CORS-allowlist по среде (см. связанный backend-ADR).
""";
public static string S2_DecisionDrivers = """
## Драйверы решения
1. **Один билд на все среды** — конфиг подменяется без пересборки.
2. **Конфиг гарантированно готов к первому запросу** — никакой код не обратится к API раньше,
чем конфиг загружен.
3. **Прозрачный доступ** — `apiBaseUrl` берётся инъекцией очевидного сервиса, без чтения
`window` и глобальных объектов.
4. **Стандартный механизм Angular** — без хаков в `index.html`.
""";
public static string S3_ConsideredOptions = """
## Рассмотренные варианты
- **A. `config.json` + `provideAppInitializer` + `fetch`** — Angular ждёт резолва инициализатора
до bootstrap; конфиг кладётся в сервис-сигнал.
- **B. `<script src="config.js">` в `index.html`** — синхронно выставляет глобальный объект до
bootstrap. Без асинхронного ожидания, но завязан на `window` и тег `<script>`.
- **C. `environment.ts`** — build-time; требует пересборки на каждую среду. Отклонено.
""";
public static string S4_DecisionOutcome = """
## Решение
Выбран **Вариант A**: `config.json` грузится через `provideAppInitializer` и кладётся в
`RuntimeConfigService`.
### Контракт `config.json`
Файл — в `src-ui/public/config.json` (Angular кладёт содержимое `public/` в корень dist).
Это **рантайм-артефакт**: на каждой среде заменяется своей версией средствами деплоя.
```json
{ "apiBaseUrl": "https://sok-api.dev.example" }
```
| Ключ | Тип | Назначение |
|---|---|---|
| `apiBaseUrl` | string | Origin API — без завершающего слэша и без `/api`. |
Расширение состава ключей — дополнением этого раздела, без нового ADR.
### `RuntimeConfigService`
- `providedIn: 'root'`, хранит конфиг в signal;
- `load()` — `fetch('config.json', { cache: 'no-store' })`, вызывается из `provideAppInitializer`;
- `apiBaseUrl` — геттер; при обращении до инициализации бросает явную ошибку (не «тихий» undefined).
```ts
provideAppInitializer(() => inject(RuntimeConfigService).load()),
```
### Связка с генерированным API-клиентом
Базовый URL сгенерированного клиента (`@api/icd`) задаётся токеном `BASE_PATH` — через фабрику
из `RuntimeConfigService` (ADR-UI-012):
```ts
{ provide: BASE_PATH, useFactory: () => inject(RuntimeConfigService).apiBaseUrl },
```
Токен резолвится при первом создании api-сервиса (открытие страницы) — уже после инициализатора,
конфиг гарантированно загружен.
### Правила
1. **`apiBaseUrl` — только из `RuntimeConfigService`.** Никаких `window.*`, прямого чтения
`config.json`, импорта из `environment.ts`.
2. **`config.json` не кэшируется** — `no-store` в `fetch` и на отдаче с сервера; иначе смена
конфига потребует жёсткого обновления у каждого пользователя.
3. **В репозитории лежит `config.json` с локальными значениями** (`http://localhost:5080`);
реальные значения сред — в артефактах деплоя (генерятся `devops/configs-generator`).
4. **Секретов в `config.json` нет** — значения видны в network tab; только публичные параметры.
""";
public static string S5_PositiveConsequences = """
## Положительные следствия
- Один билд → все среды; деплой меняет только `config.json`.
- К первому обращению к API конфиг уже загружен — нет гонок.
- Расширение конфига — поле в интерфейсе и в `config.json`, без пересборки потребителей.
""";
public static string S6_NegativeConsequences = """
## Отрицательные следствия и компромиссы
- Старт добавляет один HTTP-запрос (сотни байт). На медленной сети заметно; при необходимости —
переход на синхронный `<script>` (вариант B).
- Значения конфига видны в браузере — секреты туда класть нельзя.
""";
public static string S7_Validation = """
## Проверка
- Подмена `config.json` в собранном dist меняет адрес API без пересборки.
- Обращение к `apiBaseUrl` до инициализации даёт явную ошибку, не `undefined`.
- В коде нет обращений к API мимо `RuntimeConfigService` / токена `BASE_PATH`.
""";
public static string S8_OpenQuestions = """
## Открытые вопросы / отложено
- **Доп. ключи** (`appVersion`, флаги фич) — по мере необходимости, дополнением этого ADR.
- **Отдача `config.json` без кэша на CDN/прокси** — настройка инфраструктуры; `no-store` обязателен.
""";
public static string S99_Related = $"""
## Связанные артефакты
- {nameof(Ban.Sdaid.Icd.Arch.Tech.CSharp.ADR_SRV_004_AppConfigAndCors)} — конфиг бэка и CORS-allowlist по среде.
- {nameof(Ban.Sdaid.Icd.Arch.Tech.Angular.ADR_UI_012_ApiAndModels)} — регистрация api-клиента и токен базового URL `BASE_PATH`.
""";
}
}