runtime

Runtime

src/main.ts · src/app.module.ts · src/common/*

Цей документ описує не бізнес-домен, а те, як працює HTTP runtime stack-ai: global prefix, wrapper response, помилки, validation, Swagger, CORS і request timing.

Що це

stack-ai - NestJS backend. Вхідна точка - src/main.ts, кореневий модуль - src/app.module.ts.

Під час старту застосунок:

  1. Створює Nest app.
  2. Підключає global interceptors.
  3. Підключає global exception filter.
  4. Ставить global prefix.
  5. Вмикає cookies, CORS і JSON body limit.
  6. Вмикає validation pipe.
  7. Генерує Swagger UI.
  8. Слухає порт із налаштувань.

Global prefix

Усі routes живуть під:

/stack-ai

Приклади:

  • /stack-ai/tips/generate;
  • /stack-ai/tips/status;
  • /stack-ai/legend/generate-profile;
  • /stack-ai/legend/status;
  • /stack-ai/api-docs.

Якщо route у controller виглядає як @Controller('tips') + @Post('generate'), реальний URL буде /stack-ai/tips/generate.

Response wrapper

ResponseWrapperInterceptor обгортає успішні відповіді:

{
  "success": true,
  "data": {}
}

Controller methods повертають “чисті” дані, але клієнт майже завжди отримує wrapper. Це важливо для документації і frontend: DTO описує data, а фактичний HTTP body містить ще success.

Помилки

AllExceptionsFilter ловить усі exceptions і повертає:

{
  "success": false,
  "data": null,
  "message": "..."
}

Legacy-конвенція проєкту: навіть при помилці HTTP status виставляється 200.

Практичне правило для клієнтів:

  • не покладатися тільки на HTTP status;
  • завжди перевіряти success;
  • якщо success=false, показувати message;
  • не очікувати стандартні 400/401/500 як основний signal.

Request timing

RequestTimingInterceptor підключений глобально. Його задача - логувати тривалість запитів і допомагати бачити важкі endpoints. Це особливо важливо для:

  • LLM workflows;
  • embeddings ingest;
  • Supabase retrieval;
  • Legend async job starts;
  • upstream fetch.

Validation

Глобальний validation pipe описаний у src/common/pipes/validation.pipe.ts.

Сенс:

  • DTO decorators (@IsString, @IsEnum, @IsObject, @Min, @Max) реально беруть участь у перевірці body.
  • Validation errors проходять через загальний exception filter і стають { success:false, message }.
  • Деякі DTO мають index signature [key: string]: unknown, тому strict whitelist не завжди ріже зайві поля. Це legacy-компроміс для гнучких payloads.

Swagger

Swagger UI доступний за:

/stack-ai/api-docs

Swagger описує DTO і route-level contracts. Markdown docs описують бізнес-логіку, етапи, side effects і нюанси, яких Swagger не показує.

Правило підтримки документації:

  • якщо змінюється DTO або route - оновити Swagger decorators;
  • якщо змінюється поведінка, side effect, порядок викликів, fallback або бізнес-правило - оновити markdown docs.

CORS і body limit

Runtime вмикає:

  • cors({ origin: true, credentials: true });
  • express.json({ limit: '50mb' });
  • cookieParser().

Великий JSON limit потрібен через:

  • великі dialog payloads;
  • profile JSON;
  • notes;
  • legacy debug endpoints;
  • можливі base64/large nested payloads.

AppModule

AppModule збирає основні модулі:

МодульРоль
ConfigModuleEnv/config
RequestContextModuleДоступ до auth token поточного request
DatabaseModuleSupabase client/config
UpstreamModuleHTTP client до зовнішніх Stack/family API
LegendModuleLady Legend pipeline
TipsMemoryModuleExperimental memory/assistant API
TipsModuleНовий tips status/generate flow

Request context

RequestContext зберігає дані поточного запиту, насамперед authorization token. Upstream layer використовує цей token, щоб прокидати авторизацію у зовнішні Stack/family API.

Це важливо: stack-ai не завжди ходить в upstream як окремий service account. У частині flows він прокидає auth користувача/оператора далі.

Що важливо пам’ятати

  • Усі реальні endpoints мають prefix /stack-ai.
  • Успішні відповіді обгортаються в { success:true, data }.
  • Помилки теж приходять з HTTP 200, але success:false.
  • Swagger і markdown docs доповнюють одне одного.
  • @UseGuards на controller недостатньо: route має мати @ApiRoles, якщо він повинен бути закритий роллю.
  • Великі payloads дозволені навмисно, бо dialog/profile data може бути об’ємною.

Зв’язки