6 Commits
Author SHA1 Message Date
ksv741 0557f4ad1f chore(release): 0.2.0
Backend image / publish (push) Successful in 5m29s
Conventional commits / commits (pull_request) Successful in 27s
Backend security / security (pull_request) Successful in 7s
Backend verify / verify (pull_request) Successful in 2m28s
Backend security / security (push) Successful in 7s
Backend verify / verify (push) Successful in 2m29s
2026-09-16 11:35:52 +03:00
ksv741 d7cb7e3e20 fix: allow reviewed container smoke fixture
Conventional commits / commits (pull_request) Successful in 26s
Backend security / security (pull_request) Successful in 6s
Backend verify / verify (pull_request) Successful in 2m19s
Backend security / security (push) Successful in 7s
Backend verify / verify (push) Successful in 2m22s
2026-09-16 10:48:27 +03:00
ksv741 1a6be432f2 feat: add backend container delivery
Backend security / security (push) Failing after 5s
Backend verify / verify (push) Successful in 2m19s
Backend security / security (pull_request) Failing after 5s
Backend verify / verify (pull_request) Successful in 2m17s
Conventional commits / commits (pull_request) Successful in 25s
2026-09-16 09:41:56 +03:00
ksv741 32c12fb10d feat: generate OpenAPI without peer repositories
Backend security / security (pull_request) Successful in 7s
Backend verify / verify (pull_request) Successful in 2m28s
Conventional commits / commits (pull_request) Successful in 25s
Backend security / security (push) Successful in 6s
Backend verify / verify (push) Successful in 2m16s
2026-09-15 20:45:39 +03:00
ksv741 016782657a docs: simplify conventions layout
Backend security / security (pull_request) Successful in 7s
Backend verify / verify (pull_request) Successful in 2m3s
Conventional commits / commits (pull_request) Successful in 24s
Backend security / security (push) Successful in 7s
Backend verify / verify (push) Successful in 2m10s
2026-09-15 05:58:33 +03:00
ksv741 9810d47e42 docs: refine backend conventions and routing
Backend security / security (push) Successful in 8s
Backend verify / verify (push) Successful in 2m12s
Conventional commits / commits (pull_request) Successful in 25s
Backend security / security (pull_request) Successful in 7s
Backend verify / verify (pull_request) Successful in 2m3s
2026-09-15 05:42:16 +03:00
30 changed files with 793 additions and 476 deletions
-25
View File
@@ -1,25 +0,0 @@
# Backend router
Объединяй triggers из смысла задачи, путей и обнаруженных boundaries.
Читай выбранные guides до решения; якорь допускает чтение только нужного раздела.
Просто backend-файл не требует всех conventions.
| Решение / сигнал | Читать |
| --- | --- |
| Новый файл/suffix/layer; поиск feature owner; progressive structure; перенос responsibility | [Architecture](conventions/backend-architecture.md) |
| TS vocabulary, domain literals, magic values | [Domain literals](conventions/backend-architecture.md#domain-literals-and-magic-values) |
| Controller, DTO, route/status, validation/exception, публичная HTTP-семантика | [HTTP API](conventions/backend-http-api.md) |
| Repository/data access; Prisma/schema/query, transaction, migration, atomic write | [Data](conventions/backend-data.md) |
| Тесты или behavior с testing risk | [Testing](conventions/backend-testing.md) |
| JWT, cookie, login/refresh/logout, guard/access, token encryption, sensitive logs | [Security](conventions/security-auth.md) |
| External client/integration boundary; T-Bank/gRPC/proto, retry/rate limit/cache | [Integrations](conventions/backend-integrations.md) |
| Logging/request context без изменения secrets/auth | [Logging](conventions/backend-architecture.md#logging) |
| Новый abstraction/dependency, refactor, неясный scope | [Общий выбор реализации](../../AGENTS.md) |
| Изменение request/response/route/status/error shape или Swagger metadata | HTTP API + [Источник HTTP-контракта](../../docs/architecture/http-api.md#источник-контракта) |
Новый endpoint обычно требует HTTP API + Testing; Architecture/Data/Security
добавляются по новым ответственности, persistence или access rules.
Изменение одного Prisma query без HTTP/auth границы не требует HTTP API/Security.
Auth refresh активирует Security + Data + Testing; HTTP API добавляется, если
меняется controller/HTTP contract. Exact docs/spec sources выбирай через SOURCES.
Повтори routing при расширении diff, включая новые и unstaged файлы.
-19
View File
@@ -1,19 +0,0 @@
# Источники backend
| Что нужно установить | Владелец |
| --- | --- |
| Implementation decision и безопасный pattern | [CONVENTIONS](CONVENTIONS.md) |
| HTTP errors, DTO semantics, public wire shape | [docs: HTTP API](../../docs/architecture/http-api.md); runtime OpenAPI из Nest — единственный editable source |
| Auth/session, account access, token encryption | [Аутентификация](../../docs/domains/authentication/overview.md), [доступ к данным](../../docs/architecture/containers.md#хранимые-данные-и-границы-доступа), [подключение брокера](../../docs/integrations/tbank.md) |
| Prisma schema/data ownership и migration context | [docs: Data](../../docs/architecture/containers.md#хранимые-данные-и-границы-доступа) |
| T-Bank/gRPC/cache, logging | [T-Bank](../../docs/integrations/tbank.md), [свежесть данных](../../docs/architecture/cross-cutting/data-freshness.md), [наблюдаемость](../../docs/architecture/http-api.md#наблюдаемость) |
| Money, account/position/income/cache meanings | [docs: Domain](../../docs/glossary.md) |
| Поведение конкретной feature | [docs: Features](../../docs/README.md#предметные-области) |
| Существующие behavioral requirements при необходимости | [specs index](../../specs/AI_INDEX.md) |
| Versions/scripts и scope автоматических checks | package.json/lockfile, ESLint/TypeScript/Jest/Prisma configs |
SOURCES — карта, не требование прочитать все ссылки. Code показывает реализацию,
ADR — rationale, docs — текущие факты; старый пример не отменяет контракт.
Исследование предлагает варианты, а actual source разрешает выбор. При missing
или conflicting security/data facts сначала установи источник. Для обычной
работы OpenSpec/SDD не требуется.
@@ -1,12 +1,54 @@
# Backend architecture # Backend architecture
## Устройство и data flow
NestJS + TypeScript modular monolith. Composition root `AppModule` подключает
бизнес-модули, validated configuration, Prisma и Pino. Общая `configureApp`
задаёт `/api/v1`, CORS/cookies, validation, request logging и error filter.
Точные versions/scripts — в [package.json](../package.json); source code
определяет фактическую реализацию.
```text
HTTP controller → application service/use case → pure domain rules
↓
repository/data access → Prisma → SQLite
↓
integration client → T-Bank gRPC / MOEX / NCR HTTP
```
`modules/auth`, `modules/banking/bank-cards` и области `modules/investments`
владеют пользовательскими возможностями. Сложные модули разделены на
presentation/application/domain/infrastructure; компактные остаются плоскими.
`*.module.ts` — Nest DI, `*.api.ts` — используемые cross-module exports.
Application использует конкретные providers; обязательных ports на каждый
класс нет.
Stocks/bonds — slices одного instruments module: catalog владеет identity/cache,
market — котировками, income — выплатами/личными позициями. Общая orchestration
в `instruments/application` использует DTO/provider records и не становится pure
domain из-за extraction в функцию. Boundary mappers преобразуют внешние records;
чистые расчёты portfolio/banking остаются у domain owner.
PrismaService владеет DB lifecycle. Самостоятельные use cases получают данные
через concrete repositories; существующие связные CRUD/transaction services
сохраняют прямые queries по [data convention](conventions/data.md).
Money — currency + decimal string; расчёты используют точные общие helpers.
Auth управляет identity/session; TbankAccessService проверяет доступ к счёту и
connection generation. Integration clients владеют protocol/deadline/retry;
provider records преобразуются перед чистыми domain calculations.
Пользовательский `tbank-connection` не владеет низкоуровневым gRPC client.
Nest DTO/metadata → `npm run openapi:generate` → игнорируемый Git `openapi.json`
для публичных consumers.
Problem Details фильтруются общей HTTP boundary; StructuredLogger добавляет
request context и ограничивает поля событий. Jest unit tests, изолированные
SQLite/HTTP suites и OpenAPI parity доказывают разные части поведения.
Локальные источники выбираются через [Источники](../AGENTS.md#источники).
## Module-first organization ## Module-first organization
Backend — domain-oriented modular monolith с лёгким разделением Сначала выбери business owner в `modules/`, затем ответственность внутри него.
по ответственности. `src/modules` группирует код по бизнес-возможности: `auth`, `banking/bank-cards`, области `investments`.
В инвестициях отдельные Nest modules владеют `brokerage-accounts`, `portfolio`,
`operations`, `income`, `upcoming-events`, `instruments`, `tbank-connection`.
`instruments/stocks` и `instruments/bonds` — slices внутри одного модуля.
### Progressive structure ### Progressive structure
@@ -36,15 +78,6 @@ feature/
Для одного controller или пары DTO дополнительная поддиректория не нужна. Для одного controller или пары DTO дополнительная поддиректория не нужна.
Пустые папки, новые barrels, CQRS и interfaces не требуются для этой организации. Пустые папки, новые barrels, CQRS и interfaces не требуются для этой организации.
В текущем коде слои используют `auth`, `bank-cards`, `portfolio`, `operations`
и slices `instruments/stocks`, `instruments/bonds`. Общие application helpers
инструментов находятся в `instruments/application`; `instruments.module.ts`
компонует оба slice. `stock-position.ts` и `instrument-position.ts` формируют
результат из portfolio DTO; `instrument-market.ts` обрабатывает внешние данные
и ошибки Nest. Это application code, а не независимая domain model.
`brokerage-accounts`, `income`, `upcoming-events`, `tbank-connection` остаются
компактными плоскими modules. Наличие tests само по себе не требует слоёв.
## Поиск и создание файла ## Поиск и создание файла
При работе над feature проходи владельцев в порядке: При работе над feature проходи владельцев в порядке:
@@ -116,7 +149,7 @@ Cross-module consumers во всех строках ограничены пуб
`*.decorator.ts`, `*.config.ts`, `*.validation.ts`) сохраняют соответствующие `*.decorator.ts`, `*.config.ts`, `*.validation.ts`) сохраняют соответствующие
существующие роли в common/config. `tbank.crypto.ts` — локальная реализация существующие роли в common/config. `tbank.crypto.ts` — локальная реализация
шифрования токена; имя не является разрешением создавать generic crypto layer. шифрования токена; имя не является разрешением создавать generic crypto layer.
HTTP/e2e naming и расположение — в [Testing](backend-testing.md). HTTP/e2e naming и расположение — в [Testing](conventions/testing.md).
Без зафиксированной необходимости не вводи `*.helper.ts`, `*.utils.ts`, Без зафиксированной необходимости не вводи `*.helper.ts`, `*.utils.ts`,
`*.manager.ts`, `*.processor.ts`, `*.handler.ts`, `*.provider.ts`, `*.factory.ts`. `*.manager.ts`, `*.processor.ts`, `*.handler.ts`, `*.provider.ts`, `*.factory.ts`.
@@ -134,6 +167,10 @@ Tests могут импортировать внутренний owner для п
Новый export требует текущего потребителя; controller и persistence остаются Новый export требует текущего потребителя; controller и persistence остаются
приватными. Не добавляй `index.ts` на каждом уровне. приватными. Не добавляй `index.ts` на каждом уровне.
Circular dependencies устраняй изменением ownership/composition. `forwardRef()`
не является стандартным исправлением boundaries; исключение требует конкретной
неизбежной связи и проверки DI initialization.
Каждый provider регистрируется у одного владельца. Потребители импортируют Каждый provider регистрируется у одного владельца. Потребители импортируют
его module, а не повторяют provider: это сохраняет единственный кэш и лимитер. его module, а не повторяют provider: это сохраняет единственный кэш и лимитер.
`AuthModule` экспортирует guard; наличие JWT не заменяет account access check. `AuthModule` экспортирует guard; наличие JWT не заменяет account access check.
@@ -162,37 +199,25 @@ Tests могут импортировать внутренний owner для п
может использовать application types/input и domain rules; новые interfaces может использовать application types/input и domain rules; новые interfaces
ради инверсии зависимостей не обязательны. Domain не импортирует presentation или ради инверсии зависимостей не обязательны. Domain не импортирует presentation или
infrastructure. Существующие связные CRUD/transaction services могут сохранять прямые queries infrastructure. Существующие связные CRUD/transaction services могут сохранять прямые queries
по [Data](backend-data.md); use case получает данные через repository и сам по [Data](conventions/data.md); use case получает данные через repository и сам
Prisma queries не выполняет. Entity/interface на каждую таблицу не требуется. Prisma queries не выполняет. Entity/interface на каждую таблицу не требуется.
DTO остаются транспортными runtime classes; существующее использование DTO DTO остаются транспортными runtime classes; существующее использование DTO
на application boundary допустимо по [HTTP API](backend-http-api.md). на application boundary допустимо по [HTTP API](conventions/http-api.md).
Сохраняй существующий error contract: HTTP exceptions остаются на application/ Сохраняй существующий error contract: HTTP exceptions остаются на application/
transport boundary. Чистому расчёту не нужен новый framework ошибок. transport boundary. Чистому расчёту не нужен новый framework ошибок.
Точные ограничения — [HTTP](backend-http-api.md), [Data](backend-data.md), Mapping и stable codes — по [Errors](conventions/error-handling.md).
[Security](security-auth.md), [Integrations](backend-integrations.md). Точные ограничения — [HTTP](conventions/http-api.md), [Data](conventions/data.md),
[Security](conventions/security-auth.md), [Integrations](conventions/integrations.md).
## Сценарии и размер сервисов ## Сценарии и размер сервисов
Разделяй независимые операции, а не строки. `BankCardsService` отвечает за карты, Разделяй независимые операции, а не строки. Пример: `BankCardsService` владеет
`BankOperationsService` — журнал/переводы/возвраты, `ExpenseCategoriesService` — картами, `BankOperationsService` — журналом/переводами, `GetBankSummaryUseCase` —
категории, `GetBankSummaryUseCase` — сводку. `BankCardsRepository` сохраняет сводкой через repository и чистый расчёт. Не разрывай денежную transaction,
общий transaction retry и owner lookup; денежные транзакции не разрываются. общий cache/limiter или pending promises при extraction.
Чистые расчёты банковских карт находятся в `domain` этого модуля.
У инструментов `StockCatalogService` / `BondCatalogService` хранят идентичность Расширение abstractions — по [change scope](conventions/engineering.md#change-scope).
листинга и общий кэш slice; market service — котировки/свечи, income service — выплаты/личные позиции.
`GetStockCompanyUseCase` владеет сведениями об эмитенте. Ключи, TTL и pending
promises разделяют прежний экземпляр кэша; extract не должен менять generation.
Portfolio и operations сохраняют связную координацию снимков/страниц и кэшей.
Расчёты портфеля находятся в `portfolio/domain`; преобразование брокерских
позиций — в `tbank-portfolio.mapper.ts`. `bond-money.mapper.ts` преобразует
ответ клиента и не является value object.
Выбирай обычную функцию для чистого вычисления; repository contract — только
для реальной границы. CQRS/buses/events, BaseRepository/BaseService, DI token
для каждого класса, mapper/presenter для каждого DTO и новые dependencies
не нужны для структурного переноса.
## Common ## Common
@@ -205,45 +230,17 @@ protobuf units/nano conversion — T-Bank integration. Domain используе
## Domain literals and magic values ## Domain literals and magic values
Finite business vocabulary must be modeled explicitly; incidental implementation Общий выбор symbolic values и числовых policies —
literals stay inline. Это относится к error codes, statuses, operation/account/ [Domain values](conventions/engineering.md#domain-values).
instrument/transaction types, permissions, roles, feature states и бизнес-режимам. Сохраняй strict TS и explicit boundary types. Для недоверенного input выбирай
`unknown` + runtime narrowing; отключённый lint `no-explicit-any` не делает
Перед новым определением найди existing owner/type/value. В branching и comparisons `any` нормой domain/application model. `declare` допустим для заполняемых извне
используй символическое значение владельца; не повторяй raw domain literals. DTO fields, не для сокрытия неинициализированного owned state.
Храни словарь у domain/module/entity с соблюдением границ слоёв, без глобальной
свалки `constants.ts`. Сохраняй принятый pattern; для нового project-owned словаря
без подходящего аналога используй `as const` с derived union:
```ts
const BankErrorCode = {
InvalidDate: "BANK_INVALID_DATE",
ImmutableOperation: "BANK_IMMUTABLE_OPERATION",
} as const;
type BankErrorCode = (typeof BankErrorCode)[keyof typeof BankErrorCode];
// Вместо code === "BANK_INVALID_DATE":
if (code === BankErrorCode.InvalidDate) { /* existing handling */ }
```
Значимые numeric policies (timeout, retry, page size, threshold, coefficient)
называй по смыслу и единицам: `REQUEST_TIMEOUT_MS`, `CACHE_TTL_SECONDS`,
`MAX_RETRY_COUNT`, `PAGE_SIZE`. Оставляй inline UI labels в принятом месте,
log messages, индексы, `index + 1`, `Math.max(value, 0)`, `page ?? 1`,
`percentage * 100` и очевидные локальные детали без semantic benefit.
При неясном owner/смысле оставь значение и укажи ограничение в review.
Не мигрируй корректные existing enum/union styles массово. Не добавляй ради
правила factories, registries, schemas, loaders, abstraction layers, dependencies,
custom lint/AST rules, generation/validation scripts или тесты существования
констант. Semantic review проверяет ownership, единое написание и неизменность
wire values; обычные behavior tests проверяют поведение.
OpenAPI enum metadata и runtime validation должны читать тот же vocabulary, OpenAPI enum metadata и runtime validation должны читать тот же vocabulary,
что application/domain. Например, `BankOperationKind` принадлежит bank-cards/domain, что application/domain. Например, `BankOperationKind` принадлежит bank-cards/domain,
а DTO получает допустимые значения через `Object.values(BankOperationKind)`. а DTO получает допустимые значения через `Object.values(BankOperationKind)`.
Frontend переиспользует generated OpenAPI types/values по возможностям генератора, Публичные enum values входят в OpenAPI artifact. Внешние/generated enums
а не определяет независимый backend-owned словарь. Внешние/generated enums
сохраняй; generated Prisma/protocol code вручную не меняй. сохраняй; generated Prisma/protocol code вручную не меняй.
`@typescript-eslint/no-magic-numbers` включён как error; базовое ESLint rule `@typescript-eslint/no-magic-numbers` включён как error; базовое ESLint rule
@@ -255,14 +252,8 @@ semantic magic strings проверяются conventions/review без ново
## Logging ## Logging
Единственный logger — nestjs-pino; application events используют существующий События, levels, correlation и safe fields — по
[StructuredLogger](../../src/common/logging/structured-logger.service.ts) и [Observability](conventions/observability.md).
request context. Выбирай стабильное имя события и разрешённые scalar fields.
Wrapper отбрасывает неизвестные поля, но это не разрешение помещать secret
в `message`/`stack`: review sensitive logging обязателен.
Не добавляй второй logger или сырой request/body/error payload.
Источник rationale — [ADR 008](../../../docs/adr/008-pino-backend-logging.md);
при secrets/user data читай также Security.
## Проверка ## Проверка
-58
View File
@@ -1,58 +0,0 @@
# Backend HTTP API
## Выбор boundary
Сначала определи, меняется wire contract или только реализация существующего
use case. Для route/request/response/status/error shape читай
[HTTP semantics и источник контракта](../../../docs/architecture/http-api.md).
| Проверка | Где выполнять |
| --- | --- |
| Тип/формат/диапазон входа | Runtime DTO class с class-validator/class-transformer |
| Согласованность полей, бизнес-ограничение | Feature service/domain helper |
| Session и identity | Existing guard/strategy |
| Доступ user к конкретному account | Existing access service |
| HTTP envelope/безопасная ошибка | Общий HttpExceptionFilter |
Controller объявляет contract и делегирует; не обращается напрямую к Prisma/gRPC
и не выполняет финансовые расчёты или сложную оркестрацию.
Controller вызывает application use case/service. Identity берётся из verified
`request.user`, не из body/userId, предоставленного клиентом. Access rules активируют Security.
## DTO и errors
DTO — runtime class: TS interface не предоставляет validation metadata.
`*.controller.ts` и `*.dto.ts` остаются около HTTP boundary, в root компактного
module или presentation. DTO можно передать application boundary при совпадении
формы; отдельный Input с теми же полями не нужен. Domain не импортирует DTO:
например, чистые денежные расчёты используют `Money`, а Swagger — `MoneyDto`.
Нормализованная форма input нужна только при смысловом отличии от wire contract;
выбор suffix принадлежит [Architecture](backend-architecture.md).
ValidationPipe уже настроен в `configureApp` (`transform`, `whitelist`).
Не обещай rejection неизвестных fields: `forbidNonWhitelisted` не включён.
Для query numbers/arrays выбирай явное преобразование и валидаторы, как в
[operations DTO](../../src/modules/investments/operations/presentation/brokerage-operation.dto.ts). Swagger metadata
описывает те же required/optional/nullable/enum/range значения.
Используй типизированные response DTO, `ApiOkResponse`/другой фактический status
и существующий `ApiProblemResponses`. При новых ошибках задай публичный stable
`code`; сообщение внутреннего exception не является UI contract. Не возвращай
raw upstream error, Prisma record с secrets или stack. Текущая форма ошибок
описана у HTTP owner; не добавляй поля RFC только потому, что они встречаются в tutorial.
**Безопасный путь:** используй exception и общий filter, а не локальный
`res.status(...).json(...)`. Исключение — транспортная операция вроде cookie;
следуй существующему auth controller с `passthrough` и явно заданным status.
## Пример и проверка
[Operations controller](../../src/modules/investments/operations/presentation/brokerage-operations.controller.ts)
показывает guard, user.sub, DTO query и service delegation.
Для нового field обновляй canonical source и generated artifacts в определённом
направлении; body type frontend не исправляется вручную.
Проверь happy path и применимые invalid input/access/not-found/domain/upstream
errors на boundary, где они возникают. Unit вызов controller не запускает pipes,
guards или serialization. `npm run test:openapi-contract` проверяет schema parity,
но не поведение invalid request; выбирай соответствующий test по
[Testing](backend-testing.md). Финальный gate не заменяет semantic review ошибок.
@@ -15,13 +15,20 @@ PrismaService — существующий shared DB provider с lifecycle пр
| Независимые reads | Не добавляй transaction ради внешнего вида | | Независимые reads | Не добавляй transaction ради внешнего вида |
| Общая persistence abstraction | Только при реальной общей ответственности; generic repository для одной Prisma реализации не нужен | | Общая persistence abstraction | Только при реальной общей ответственности; generic repository для одной Prisma реализации не нужен |
Размещение repository определяется [Architecture](backend-architecture.md): Размещение repository определяется [Architecture](../architecture.md):
в слоистом module это `infrastructure/`. в слоистом module это `infrastructure/`.
Repository — конкретный класс `*.repository.ts` у business owner, без обязательных Repository — конкретный класс `*.repository.ts` у business owner, без обязательных
interface/port/adapter. Он владеет queries, includes/filtering, DB mapping и interface/port/adapter. Он владеет queries, includes/filtering, DB mapping и
согласованным чтением. Суффиксы `*.persistence.ts`, `*.store.ts`, `*.dao.ts` согласованным чтением. Суффиксы `*.persistence.ts`, `*.store.ts`, `*.dao.ts`
для той же роли не используем. Nest module регистрирует repository приватно. для той же роли не используем. Nest module регистрирует repository приватно.
Новые business decisions (можно ли менять архивную карту, допустим ли перевод)
принадлежат application/domain; repository сохраняет persistence и ownership
ограничения. Existing checks/HTTP helpers в BankCardsRepository — migration
candidate, не повод дублировать их при новом use case или разрывать transaction.
`BaseRepository<T>`, `GenericRepository<T>` и RepositoryFactory поверх Prisma
не нужны без отдельной общей ответственности.
`BankCardsRepository.summaryData` проверяет владельца карты и получает операции `BankCardsRepository.summaryData` проверяет владельца карты и получает операции
с категориями в одной read transaction. `GetBankSummaryUseCase` выбирает период, с категориями в одной read transaction. `GetBankSummaryUseCase` выбирает период,
вызывает repository, затем чистый `bankSummary` и формирует ответ. Prisma records вызывает repository, затем чистый `bankSummary` и формирует ответ. Prisma records
@@ -44,16 +51,16 @@ application checks. Предварительный lookup не заменяет
Transaction не охватывает сетевой T-Bank вызов как будто это одна atomic DB Transaction не охватывает сетевой T-Bank вызов как будто это одна atomic DB
операция. Планируй порядок и compensation по текущей задаче; не удерживай DB операция. Планируй порядок и compensation по текущей задаче; не удерживай DB
lock во время retry внешней сети. Access/cache generation semantics — у lock во время retry внешней сети. Access/cache generation semantics — у
[T-Bank source](../../../docs/integrations/tbank.md#граница-финансового-кэша). [T-Bank integration](integrations.md#retry-cache-и-protocol).
## Деньги и точность ## Деньги и точность
Для денежных значений используй existing decimal helpers и строковый wire value, Для денежных значений используй existing decimal helpers и строковый wire value,
не JS floating-point арифметику для сумм. Currency — отдельная часть Money; не JS floating-point арифметику для сумм. Currency — отдельная часть Money;
разные валюты не складываются без явно заданного conversion contract. разные валюты не складываются без явно заданного conversion contract.
Источник: [Money](../../../docs/glossary.md#money), Источник формы — `src/common/money.types.ts`;
пример: [decimal.ts](../../src/common/decimal.ts). UI formatting не меняет пример точной арифметики: [decimal.ts](../../src/common/decimal.ts).
backend ownership расчётов и доменную семантику. Форматирование ответа не меняет доменную семантику и точность расчёта.
## Schema и migration ## Schema и migration
@@ -66,12 +73,12 @@ client обновляй из schema. Не переписывай уже прим
**Безопасный путь:** additive изменение, заполнение/проверка данных и только затем **Безопасный путь:** additive изменение, заполнение/проверка данных и только затем
удаление прежней формы, если задача этого требует. Примеры schema/migrations: удаление прежней формы, если задача этого требует. Примеры schema/migrations:
[Prisma source](../../prisma/schema.prisma); исторический rationale — [Prisma source](../../prisma/schema.prisma). SQLite — embedded storage текущего
[ADR 001](../../../docs/adr/001-prisma-sqlite-local-mvp.md), не текущий список таблиц. приложения; файл БД и encryption key требуют согласованного backup/restore.
## Проверка ## Проверка
`npx prisma validate`; после schema change `npx prisma generate` и `npx prisma validate`; после schema change `npx prisma generate` и
`npm run typecheck:app`. Invariants/atomicity/migration safety требуют tests `npm run typecheck:app`. Invariants/atomicity/migration safety требуют tests
на соответствующей boundary: mock Prisma не доказывает реальную transaction. на соответствующей boundary: mock Prisma не доказывает реальную transaction.
Выбор integration/e2e и известные ограничения — в [Testing](backend-testing.md). Выбор integration/e2e и известные ограничения — в [Testing](testing.md).
+74
View File
@@ -0,0 +1,74 @@
# Работа в репозитории
## Граница и порядок работы
Репозиторий сам содержит инструкции, исходники, конфигурацию и входы своих
проверок. Сборка, CI и обязательный маршрут чтения работают без соседних
checkout. Внешний API задаётся предоставленным публичным контрактом;
исходники или инструкции его реализации не являются зависимостью разработки.
При нехватке требования сообщи, какое решение не определено, до его реализации.
Начни с результата и affected areas. До edits проследи flow/invariants, найди
owner и 1–3 примера. Код показывает механизм; случайный аналог не меняет контракт.
Работай напрямую; OpenSpec/SDD lifecycle — только по явному запросу.
Перед Node-командами активируй местную `.nvmrc`: `nvm use --silent`.
Сохраняй чужие staged/unstaged/untracked изменения и read-only архивы.
## Change scope
Сначала расширь существующего владельца. Иначе используй standard library,
платформу или установленную dependency; небольшая новая реализация допустима
при собственной ответственности. Не создавай helper/interface/wrapper ради
возможного reuse. Общее имя не означает общую бизнес-ответственность.
Сохраняй validation, authorization, atomicity, accessibility и migration safety.
После diff выполни Boy Scout review: проверь новое дублирование, owner, callers,
небольшие связанные legacy issues и соответствие local conventions. Исправляй
непосредственно связанные low-risk проблемы; широкий cleanup — отдельная задача.
## Domain values
Finite business vocabulary принадлежит одному domain/module/entity owner.
Переиспользуй его symbols; для нового словаря без аналога — `as const` + union.
Сохраняй wire values; generated/external enums не переписывай вручную.
Numeric policy называй по смыслу и единицам. Индексы, `Math.max(value, 0)` и
обычные labels не выноси в constants без semantic benefit.
Локальный TypeScript/lint профиль определяет дополнительные ограничения.
## Dependencies
Проверь аналоги и direct dependencies в местном manifest до добавления пакета.
Новая dependency оправдана текущей потребностью и меньшей общей сложностью;
оцени compatibility, runtime/bundle и maintenance/security. Импортируемый пакет
объявляется напрямую; транзитивная установка не является контрактом.
Сохраняй exact versions и lockfile; воспроизводимая установка — `npm ci`.
Peer-конфликт решай совместимыми версиями без `--force`/`--legacy-peer-deps`.
Используй существующий analyzer; не добавляй второй для той же проверки.
## Test Intent
Перед нетривиальным тестом кратко определи наблюдаемый исход, regression risk,
значимые edge/error/concurrency cases и самую дешёвую надёжную boundary.
Отдельный artifact не нужен. Проверяй результат, а не private call order,
декларации, тривиальные wrappers или поведение framework.
При bug сначала воспроизведи исход. Mock race не доказывает DB atomicity.
Уровни и runnable commands выбирай по местной testing convention.
## Проверки и завершение
Code/dependency change: `npm ci`, полный `npm run verify` и дополнительные
risk checks из местной testing convention. Targeted tests ускоряют итерацию,
но не заменяют финальный gate. `lint:fix`/format — edits, не proof.
Markdown-only: проверь новые/изменённые тексты, локальные ссылки/якоря, маршруты
чтения и `git diff --check`; application build ради текста не нужен.
Повтори routing по staged/unstaged/new files. Запиши краткие команды, результаты
и ограничения. Зелёный lint не заменяет semantic/security review.
## Git и релизы
Работа — в `codex/<task>` от актуального `develop`, затем PR в `develop`.
Сохраняй чужие изменения; перед commit просмотри staged paths и diff.
Commits и PR titles — Conventional Commits; допустим `spec:`. Каждый commit
содержит одну связанную задачу. Root другого checkout не управляет этим repo.
Прямой push в `main`, force-push и перенос опубликованных тегов не входят в
обычную работу. Перед release/hotfix прочитай местный README, раздел «Релизы».
+40
View File
@@ -0,0 +1,40 @@
# Backend errors
## Поток
```text
application/domain outcome → application mapping → HttpExceptionFilter
→ application/problem+json
```
Сохраняй принятый профиль RFC 9457: `status`, стабильный публичный `code`,
для validation — `errors: [{ pointer, detail }]`. Точный контракт и известное
расхождение validation описаны в [HTTP API](http-api.md#источник-контракта).
Не добавляй `type/title/instance` как обязательные поля без contract change.
Чистый domain не импортирует Nest HTTP exceptions. Существующий application
service может бросить подходящий `HttpException` с public code; не нужен новый
error framework/registry. Если domain outcome требует HTTP interpretation,
маппинг принадлежит application/transport boundary.
## Codes и безопасность
- Используй existing code владельца, не сравнивай внутренние сообщения.
Новое значение имеет один symbolic owner; metadata/validation и consumers
согласуются по [domain values](engineering.md#domain-values).
- Общий envelope, default status mapping и unexpected errors принадлежат
[HttpExceptionFilter](../../src/common/filters/http-exception.filter.ts).
Не дублируй `res.status().json()` в controllers/services.
- Неожиданный exception → `500 / INTERNAL_SERVER_ERROR`, без message/stack,
SQL, credentials или raw upstream payload в ответе.
- External auth failure и временная недоступность имеют разные outcomes;
translation выполняется на existing integration/access boundary.
- Логи используют [observability](observability.md). Redaction не
делает произвольное сообщение ошибки безопасным.
## Проверка
Для изменённого кода проверь observable status/code и сохранность данных.
Для validation нужен request через `configureApp`: fabricated `ValidationError`
в unit test filter не доказывает форму, выходящую из реального ValidationPipe.
OpenAPI parity проверяет schema, а не response pipeline; [Testing](testing.md).
+88
View File
@@ -0,0 +1,88 @@
# Backend HTTP API
## Выбор boundary
Сначала определи, меняется wire contract или только реализация существующего
use case. Для route/request/response/status/error shape используй
[локальный публичный контракт](#источник-контракта) и configured HTTP tests.
| Проверка | Где выполнять |
| --- | --- |
| Тип/формат/диапазон входа | Runtime DTO class с class-validator/class-transformer |
| Согласованность полей, бизнес-ограничение | Feature service/domain helper |
| Session и identity | Existing guard/strategy |
| Доступ user к конкретному account | Existing access service |
| HTTP envelope/безопасная ошибка | Общий HttpExceptionFilter |
Controller объявляет contract и делегирует; не обращается напрямую к Prisma/gRPC
и не выполняет финансовые расчёты или сложную оркестрацию.
Controller вызывает application use case/service. Identity берётся из verified
`request.user`, не из body/userId, предоставленного клиентом. Access rules активируют Security.
## DTO и errors
DTO — runtime class: TS interface не предоставляет validation metadata.
`*.controller.ts` и `*.dto.ts` остаются около HTTP boundary, в root компактного
module или presentation. DTO можно передать application boundary при совпадении
формы; отдельный Input с теми же полями не нужен. Domain не импортирует DTO:
например, чистые денежные расчёты используют `Money`, а Swagger — `MoneyDto`.
Нормализованная форма input нужна только при смысловом отличии от wire contract;
выбор suffix принадлежит [Architecture](../architecture.md).
ValidationPipe уже настроен в `configureApp` (`transform`, `whitelist`).
Не обещай rejection неизвестных fields: `forbidNonWhitelisted` не включён.
Для query numbers/arrays выбирай явное преобразование и валидаторы, как в
[operations DTO](../../src/modules/investments/operations/presentation/brokerage-operation.dto.ts). Swagger metadata
описывает те же required/optional/nullable/enum/range значения.
Используй типизированные response DTO, `ApiOkResponse`/другой фактический status
и существующий `ApiProblemResponses`. Prisma model не является public contract:
response выбирает поля явно. Stable codes, RFC 9457 профиль и mapping принадлежат
[Errors](error-handling.md).
**Безопасный путь:** используй exception и общий filter, а не локальный
`res.status(...).json(...)`. Исключение — транспортная операция вроде cookie;
следуй существующему auth controller с `passthrough` и явно заданным status.
## Пример и проверка
Prefix `/api/v1` задаёт `configureApp`. Route/status/request/response/OpenAPI
change — изменение публичного backend contract; несовместимое изменение требует
согласованного перехода consumers, а не незаметной правки DTO. Новый API version
не вводи для внутреннего refactor.
Pagination сохраняет contract endpoint: operations использует opaque cursor,
limit и DTO bounds. Не заменяй cursor offset/page и не добавляй global pagination
helper ради первого endpoint. Новая pagination явно определяет порядок,
границу страницы и end-of-list; reused cursor не должен менять filters/owner.
[Operations controller](../../src/modules/investments/operations/presentation/brokerage-operations.controller.ts)
показывает guard, user.sub, DTO query и service delegation.
Для нового field обновляй Nest DTO/metadata и публикуемый OpenAPI artifact.
Потребители получают публичный контракт независимо от внутреннего устройства API.
Проверь happy path и применимые invalid input/access/not-found/domain/upstream
errors на boundary, где они возникают. Unit вызов controller не запускает pipes,
guards или serialization. `npm run test:openapi-contract` проверяет schema parity,
но не поведение invalid request; выбирай соответствующий test по
[Testing](testing.md). Финальный gate не заменяет semantic review ошибок.
## Источник контракта
Nest DTO/controllers/Swagger metadata определяют публичный HTTP API.
`npm run openapi:generate` собирает backend и записывает корневой `openapi.json`.
Nest preview читает metadata без запуска HTTP-порта, провайдеров, БД и T-Bank;
команда не требует deployment environment. Artifact исключён из Git.
При изменении API обнови DTO/metadata и выполни генерацию. Потребители получают
artifact отдельно; backend не читает их репозитории. `test:openapi-contract`
сравнивает вывод генератора с runtime schema configured app во временном каталоге.
Проверка подтверждает правильность генерации, но без versioned baseline не
выявляет breaking change относительно предыдущего выпуска. Для такого изменения
согласуй переход consumers и проверь relevant HTTP behavior.
HTTP prefix — `/api/v1`; Swagger доступен согласно `src/config/swagger.config.ts`.
Общий error response — `application/problem+json` со `status` и публичным `code`.
Validation требует `400 / VALIDATION_ERROR` и `errors: [{ pointer, detail }]`.
Известное ограничение: ValidationPipe сейчас формирует строковые сообщения,
а filter ожидает структурированные field errors; schema parity не доказывает
правильность `errors`. Изменение требует проверки реального HTTP pipeline.
@@ -2,12 +2,12 @@
## Выбор boundary ## Выбор boundary
T-Bank gRPC принадлежит TbankClient; MOEX metadata — MoexClient. Сервисы use case T-Bank gRPC принадлежит TbankClient; MOEX metadata — MoexClient; NCR ratings —
NcrRatingsClient. Сервисы use case
используют объявленные клиентом records; SDK clients, metadata и transport errors используют объявленные клиентом records; SDK clients, metadata и transport errors
остаются внутри integration. Frontend не вызывает остаются внутри integration. Credentials и proto client не входят в публичный
T-Bank и не получает credentials/proto client. Reference source — HTTP API. Connection lifecycle проверяется на границе соответствующего module
[ADR 003](../../../docs/adr/003-tbank-server-side-grpc-boundary.md) и и [Security](security-auth.md).
[connection behavior](../../../docs/integrations/tbank.md).
`modules/investments/tbank-connection` — пользовательская capability чтения, `modules/investments/tbank-connection` — пользовательская capability чтения,
создания/замены и удаления подключения. `integrations/tbank` — технические создания/замены и удаления подключения. `integrations/tbank` — технические
@@ -17,7 +17,7 @@ RPC, credentials/access и resilience. Эти владельцы не объед
`*.client.ts` содержит transport и его retries; `*.api.ts` только перечисляет `*.client.ts` содержит transport и его retries; `*.api.ts` только перечисляет
публичные exports. Все шесть `auth/operations/portfolio/tbank/moex/ncr.api.ts` публичные exports. Все шесть `auth/operations/portfolio/tbank/moex/ncr.api.ts`
имеют одинаковую роль программной границы, определённую в имеют одинаковую роль программной границы, определённую в
[Architecture](backend-architecture.md). Логика, DTO definitions и HTTP routes [Architecture](../architecture.md). Логика, DTO definitions и HTTP routes
в эти файлы не помещаются. Type exports обозначаются `export type`. в эти файлы не помещаются. Type exports обозначаются `export type`.
Преобразование provider records происходит на boundary: например, Преобразование provider records происходит на boundary: например,
@@ -30,7 +30,7 @@ client или mapper для простого копирования полей.
Для нового account use case сначала используй TbankAccessService: access, Для нового account use case сначала используй TbankAccessService: access,
connection generation и invalid-token translation не дублируются в callers. connection generation и invalid-token translation не дублируются в callers.
Mapper внешних финансовых значений остаётся внутри business capability, Mapper внешних финансовых значений остаётся внутри business capability,
обычно в infrastructure по [Architecture](backend-architecture.md). обычно в infrastructure по [Architecture](../architecture.md).
## Retry, cache и protocol ## Retry, cache и protocol
@@ -38,13 +38,19 @@ Existing client уже владеет bounded FIFO queue, rate limit, deadline
классификацией. Новый RPC проходит тот же путь; не добавляй вложенный retry loop классификацией. Новый RPC проходит тот же путь; не добавляй вложенный retry loop
в service. Не отключай TLS verification ради окружения: используй документированный в service. Не отключай TLS verification ради окружения: используй документированный
CA mechanism. Точные параметры и ограничения принадлежат CA mechanism. Точные параметры и ограничения принадлежат
[connection configuration](../../../docs/integrations/tbank.md#конфигурация-и-ограничения). [validated environment](../../src/config/env.validation.ts) и
[TbankClient](../../src/integrations/tbank/tbank.client.ts).
Эта queue/retry policy относится к T-Bank. MOEX/NCR HTTP clients используют
собственный bounded timeout и безопасный unavailable outcome; не приписывай им
gRPC retry policy. При изменении upstream сначала прочитай конкретный client.
При query/cache change установи key, user/account/generation ownership, При query/cache change установи key, user/account/generation ownership,
freshness и поведение late response. Старый результат не может обслужить новую freshness и поведение late response. Старый результат не может обслужить новую
connection generation. Client Cached Snapshot frontend отличается от backend connection generation. Успешная замена/deletion подключения меняет generation
cache/stale policy; semantics конкретного use case читай через даже при сохранении прежнего token; ошибка оставляет прежнее подключение.
[feature index](../../../docs/README.md#предметные-области). Кэш конкретного use case принадлежит его service и tests. Не переноси
сохранённый snapshot между пользователями или поколениями подключения.
Внешний auth failure и временная недоступность имеют разные public codes; Внешний auth failure и временная недоступность имеют разные public codes;
raw gRPC exception не уходит клиенту. Existing translation — в TbankTokenService raw gRPC exception не уходит клиенту. Existing translation — в TbankTokenService
@@ -64,4 +70,4 @@ Generated artifacts, если есть, создавай из source, без р
upstream responses. Проверь queue/deadline/retry limit, token translation, upstream responses. Проверь queue/deadline/retry limit, token translation,
cache isolation и late response для затронутых ветвей. Fixtures синтетические; cache isolation и late response для затронутых ветвей. Fixtures синтетические;
реальная сеть, секреты и пользовательские accounts не нужны. реальная сеть, секреты и пользовательские accounts не нужны.
Logging использует [existing logger](backend-architecture.md#logging). Logging использует [existing logger](../architecture.md#logging).
+22
View File
@@ -0,0 +1,22 @@
# Observability
- Единственный logger — nestjs-pino. Application events используют
[StructuredLogger](../../src/common/logging/structured-logger.service.ts),
стабильное имя события и разрешённые scalar fields. `console.log` и второй
logger в production не используются.
- `info` — значимый lifecycle/outcome; `debug` — подробности успешного RPC;
`warn` — восстановимая проблема/retry; `error` — окончательный сбой.
Не добавляй log на каждый вход/выход функции или дубликат HTTP completion.
- RequestContextService и request middleware владеют correlation: серверный
request ID возвращается как `X-Request-ID`, входящий client ID хранится отдельно.
Передавай существующий context, не создавай собственный UUID в каждом service.
- Wrapper ограничивает поля, Pino redacts request secrets. Не логируй passwords,
tokens, cookie, Authorization, credentials, connection generation, raw body
или upstream error. Secret в `message/stack` не становится безопасным из-за
allowlist; правила [Security](security-auth.md) обязательны для sensitive data.
- Для ошибки используй public code и безопасный context операции/попыток;
[error mapping](error-handling.md) не отдаёт внутренние детали клиенту.
Проверяй новые поля/redaction/correlation existing logging tests; не добавляй
тест на каждую строку log. Existing ESLint не запрещает все вызовы `console`
отдельным правилом: до настройки встроенного `no-console` это semantic review.
+21 -11
View File
@@ -10,20 +10,23 @@ account access/connection generation — TbankAccessService; token crypto —
TbankTokenService/TbankCrypto. Расширяй существующую boundary, не добавляй TbankTokenService/TbankCrypto. Расширяй существующую boundary, не добавляй
второй JWT middleware, decrypt helper или refresh coordinator. второй JWT middleware, decrypt helper или refresh coordinator.
[Authentication](../../../docs/domains/authentication/overview.md) и Access проверяется JWT strategy; refresh передаётся httpOnly cookie.
[ADR 006](../../../docs/adr/006-auth-session-policy.md)/[009](../../../docs/adr/009-concurrent-refresh-rotation-policy.md) Сроки token/session принадлежат auth source и configured HTTP tests.
задают session semantics. Access хранится только в памяти frontend; refresh Source code конфигурации cookie —
передаётся httpOnly cookie. Source code конфигурации cookie —
[AuthController](../../src/modules/auth/presentation/auth.controller.ts); изменение flags/path требует [AuthController](../../src/modules/auth/presentation/auth.controller.ts); изменение flags/path требует
проверки browser/HTTP поведения, не только TypeScript. проверки browser/HTTP поведения, не только TypeScript.
Refresh rotation атомарна: один победитель, проигравший/superseded token получает Refresh rotation атомарна: один победитель, проигравший/superseded token получает
401, но сам по себе не отзывает session победителя. Не вводи replay history/grace 401, но сам по себе не отзывает session победителя. Не вводи replay history/grace
или иной security contract без текущей задачи. Logout идемпотентен; in-memory или иной security contract без текущей задачи. Logout идемпотентен, отзывает
frontend session/cache очищается и при network failure. сессию и очищает refresh cookie на HTTP-границе.
## Trust boundaries ## Trust boundaries
Клиентский интерфейс не является security boundary. DTO runtime validation выполняется
server-side; whitelist не заменяет access checks. Environment/secret inputs
проверяет existing config owner; реальные credentials не попадают в fixtures.
Verified JWT не доказывает доступ к accountId из URL. Используй user.sub и Verified JWT не доказывает доступ к accountId из URL. Используй user.sub и
existing account access policy; не доверяй userId/token из клиента как authority. existing account access policy; не доверяй userId/token из клиента как authority.
Cross-user, invalid/expired/superseded token и concurrent requests — обязательные Cross-user, invalid/expired/superseded token и concurrent requests — обязательные
@@ -32,13 +35,20 @@ cases для соответствующего изменения.
T-Bank credentials остаются server-side и хранятся encrypted. Используй T-Bank credentials остаются server-side и хранятся encrypted. Используй
existing AES-256-GCM boundary с validated 32-byte key; не печатай plaintext, existing AES-256-GCM boundary с validated 32-byte key; не печатай plaintext,
шифротекст, cookie, Authorization или connection generation в logs/errors/API. шифротекст, cookie, Authorization или connection generation в logs/errors/API.
[Connection source](../../../docs/integrations/tbank.md) определяет Успешная замена/deletion меняет connection generation и инвалидирует её кэши;
cache invalidation при замене/deletion и ограничение key rotation. ошибка сохраняет прежнее подключение. Ключ не ротируется автоматически:
новый ключ не расшифрует сохранённые credentials. Нужен исходный ключ или
повторное сохранение token пользователем; сохраняй ключ отдельно от backup БД.
Не передавай Prisma User/Session целиком наружу; выбирай public DTO. Пароль Не передавай Prisma User/Session целиком наружу; выбирай public DTO. Пароль
обрабатывает существующий bcrypt flow. Не копируй требования о нормализации обрабатывает существующий bcrypt flow. Не копируй требования о нормализации
или длине из tutorial: сверяй runtime DTO, tests и feature source. или длине из tutorial: сверяй runtime DTO, tests и feature source.
Предпочитай Prisma query API. Raw SQL допускается только по конкретной нужде
с параметризованными значениями; не конкатенируй untrusted input и не передавай
его в `queryRawUnsafe`/`executeRawUnsafe`. Dynamic identifiers выбирай из
закрытого server-owned набора. Проверка user ownership сохраняется и в SQL.
## Безопасный путь и проверка ## Безопасный путь и проверка
При неясной ownership/crypto/session семантике прочитай exact source до edits. При неясной ownership/crypto/session семантике прочитай exact source до edits.
@@ -46,6 +56,6 @@ cache invalidation при замене/deletion и ограничение key ro
фиктивным success или log. Logger allowlist/redaction не делает произвольный фиктивным success или log. Logger allowlist/redaction не делает произвольный
message безопасным. message безопасным.
Targeted auth/access/crypto/logging tests + [Testing](backend-testing.md). Targeted auth/access/crypto/logging tests + [Testing](testing.md).
Cookie/guard/HTTP changes требуют configured HTTP proof; frontend Bearer/retry Cookie/guard/HTTP changes требуют configured HTTP proof.
проверяется MSW. Schema parity не доказывает отсутствие утечки secret. Schema parity не доказывает отсутствие утечки secret.
@@ -1,5 +1,9 @@
# Backend testing # Backend testing
Перед нетривиальными тестами сформулируй
[общий Test Intent](engineering.md#test-intent); отдельный документ
не требуется. Trivial low-risk изменения обходятся без него.
## Выбор уровня ## Выбор уровня
Проверяй риски доступа, денежных расчётов, целостности данных, конкурентных Проверяй риски доступа, денежных расчётов, целостности данных, конкурентных
@@ -11,7 +15,7 @@
| Pure mapper/decimal/domain branch | Unit со значимыми edge cases | | Pure mapper/decimal/domain branch | Unit со значимыми edge cases |
| Service orchestration/access/retry policy | Jest с mocks внешних boundaries и observable outcome | | Service orchestration/access/retry policy | Jest с mocks внешних boundaries и observable outcome |
| Pipe/guard/cookie/status/HTTP error | Configured Nest HTTP boundary; прямого вызова метода controller недостаточно | | Pipe/guard/cookie/status/HTTP error | Configured Nest HTTP boundary; прямого вызова метода controller недостаточно |
| OpenAPI metadata/wire schema | `test:openapi-contract` + потребительский contract check | | OpenAPI metadata/wire schema | `test:openapi-contract` + configured HTTP tests |
| DB constraint/CAS/transaction/migration | Integration с изолированной реальной DB | | DB constraint/CAS/transaction/migration | Integration с изолированной реальной DB |
| Только DTO declarations/thin wiring | Existing contract/type/build без искусственного unit test | | Только DTO declarations/thin wiring | Existing contract/type/build без искусственного unit test |
@@ -25,7 +29,7 @@ Unit specs colocated в `src/**/*.spec.ts`; HTTP/integration — `test/`.
- [BrokerageAccountsService tests](../../src/modules/investments/brokerage-accounts/brokerage-accounts.service.spec.ts): отсутствие connection не вызывает upstream, нормализация и точные суммы. - [BrokerageAccountsService tests](../../src/modules/investments/brokerage-accounts/brokerage-accounts.service.spec.ts): отсутствие connection не вызывает upstream, нормализация и точные суммы.
- [AuthService tests](../../src/modules/auth/application/auth.service.spec.ts): invalid/expired/superseded token и concurrent rotation; mock race test не является доказательством DB transaction. - [AuthService tests](../../src/modules/auth/application/auth.service.spec.ts): invalid/expired/superseded token и concurrent rotation; mock race test не является доказательством DB transaction.
- [HTTP filter tests](../../src/common/filters/http-exception.filter.spec.ts): безопасный error envelope. - [HTTP filter tests](../../src/common/filters/http-exception.filter.spec.ts): безопасный error envelope.
- [OpenAPI integration](../../test/openapi-contract.e2e-spec.ts): configured app + frontend snapshot, без записи snapshot и без T-Bank. - [OpenAPI integration](../../test/openapi-contract.e2e-spec.ts): генератор без сервера + configured app; временный JSON, без versioned snapshot и без T-Bank.
При bug сначала воспроизведи именно его исход, затем исправь responsible owner. При bug сначала воспроизведи именно его исход, затем исправь responsible owner.
Не создавай tests для каждой декларации, logs строки или поведения Prisma/Nest Не создавай tests для каждой декларации, logs строки или поведения Prisma/Nest
@@ -40,8 +44,8 @@ Unit specs colocated в `src/**/*.spec.ts`; HTTP/integration — `test/`.
`npm run test:e2e` запускает все HTTP suites через изолированный SQLite runner; `npm run test:e2e` запускает все HTTP suites через изолированный SQLite runner;
`npm run test:sqlite` — только auth/cards. Каждая suite получает новую временную `npm run test:sqlite` — только auth/cards. Каждая suite получает новую временную
базу со штатными migrations. Прямой Jest без runner отклоняется до DB-запросов. базу со штатными migrations. Прямой Jest без runner отклоняется до DB-запросов.
`prisma.config.ts` исключает влияние унаследованного `RUST_LOG=warn/error`, `prisma.config.ts` исключает влияние унаследованного `RUST_LOG=warn/error`
вызывающего ошибку fresh migration на Prisma 7.9.1. Подробности и регрессия запуска — на fresh migration; точная Prisma version принадлежит manifest/lockfile. Подробности и регрессия запуска —
[Backend README](../../README.md#реальная-sqlite-и-сохранность-данных). [Backend README](../../README.md#реальная-sqlite-и-сохранность-данных).
HTTP suites требуют разрешения открывать локальные порты. Ошибка sandbox HTTP suites требуют разрешения открывать локальные порты. Ошибка sandbox
не считается успешным тестом; повторите проверку в подходящем окружении. не считается успешным тестом; повторите проверку в подходящем окружении.
-3
View File
@@ -1,3 +0,0 @@
{
"frontend": "0244acf429c525b23117f165bbe52ef5403eefd3"
}
+13
View File
@@ -0,0 +1,13 @@
**
!package.json
!package-lock.json
!.nvmrc
!nest-cli.json
!tsconfig*.json
!prisma.config.ts
!prisma/
!prisma/schema.prisma
!prisma/migrations/
!prisma/migrations/**
!src/
!src/**
+59
View File
@@ -0,0 +1,59 @@
name: Backend image
on:
push:
tags: ['v*']
concurrency:
group: backend-image-publication
cancel-in-progress: false
jobs:
publish:
runs-on: ubuntu-latest
env:
IMAGE: git.personal.ksv741.ru/wealthledger/backend
RELEASE_TAG: ${{ gitea.ref_name }}
REVISION: ${{ gitea.sha }}
DOCKER_CONFIG: ${{ runner.temp }}/wealthledger-backend-docker
steps:
- uses: https://github.com/actions/checkout@v4
- uses: https://github.com/actions/setup-node@v4
with:
node-version-file: .nvmrc
- name: Validate release version
run: |
node --input-type=commonjs -e 'const p = require("./package.json"); const l = require("./package-lock.json"); if (!/^v\d+\.\d+\.\d+$/.test(process.env.RELEASE_TAG) || process.env.RELEASE_TAG !== `v${p.version}` || l.version !== p.version || l.packages[""].version !== p.version) process.exit(1)'
test "$(git rev-parse HEAD)" = "$REVISION"
docker version
docker buildx version
- run: npm ci
- run: npx --no-install prisma generate
- run: npm run verify
- run: npm run test:e2e
- name: Login with dedicated package writer
env:
REGISTRY_USERNAME: ${{ secrets.REGISTRY_USERNAME }}
REGISTRY_TOKEN: ${{ secrets.REGISTRY_TOKEN }}
run: |
test -n "$REGISTRY_USERNAME" && test -n "$REGISTRY_TOKEN"
install -d -m 700 "$DOCKER_CONFIG"
printf '%s' "$REGISTRY_TOKEN" | docker login git.personal.ksv741.ru --username "$REGISTRY_USERNAME" --password-stdin
- name: Publish immutable release tag
run: |
VERSION="${RELEASE_TAG#v}"
if docker manifest inspect "$IMAGE:$VERSION" >/dev/null 2>manifest-error.log; then
echo 'Refusing to overwrite an existing release tag'; exit 1
fi
if ! grep -Eq 'manifest unknown|no such manifest' manifest-error.log; then
echo 'Cannot prove tag absence; refusing publication'; exit 1
fi
docker buildx build --target production --build-arg VERSION="$VERSION" --build-arg REVISION="$REVISION" --tag "$IMAGE:$VERSION" --metadata-file image-metadata.json --push .
DIGEST="$(node --input-type=commonjs -p 'require("./image-metadata.json")["containerimage.digest"]')"
docker pull "$IMAGE@$DIGEST"
test "$(docker image inspect "$IMAGE@$DIGEST" --format '{{index .Config.Labels "org.opencontainers.image.revision"}}')" = "$REVISION"
npm run test:container -- "$IMAGE@$DIGEST"
printf 'Backend image: %s@%s\n' "$IMAGE" "$DIGEST"
- name: Remove registry session
if: always()
run: rm -rf "$DOCKER_CONFIG"
+3 -29
View File
@@ -8,39 +8,13 @@ on:
jobs: jobs:
verify: verify:
runs-on: ubuntu-latest runs-on: ubuntu-latest
defaults:
run:
working-directory: backend
steps: steps:
- uses: https://github.com/actions/checkout@v4 - uses: https://github.com/actions/checkout@v4
with: - name: Record checked revision
path: backend run: git rev-parse HEAD
- name: Checkout compatible peer revisions
env:
SOURCE_BRANCH: ${{ github.head_ref || github.ref_name }}
TARGET_BRANCH: ${{ github.base_ref || github.ref_name }}
run: |
set -eu
for repo in frontend; do
ref=develop
case "$SOURCE_BRANCH:$TARGET_BRANCH" in
release/*:*|*:main)
ref=$(jq -er --arg repo "$repo" '.[$repo]' .ci/refs.json)
printf '%s\n' "$ref" | grep -Eq '^[0-9a-f]{40}$'
;;
esac
target="../$repo"
if [ "$repo" = main ]; then target=..; fi
git init "$target"
git -C "$target" remote add origin "https://git.personal.ksv741.ru/WealthLedger/$repo.git"
git -C "$target" fetch --depth 1 origin "$ref"
git -C "$target" checkout --detach FETCH_HEAD
git -C "$target" rev-parse HEAD
done
git rev-parse HEAD
- uses: https://github.com/actions/setup-node@v4 - uses: https://github.com/actions/setup-node@v4
with: with:
node-version-file: backend/.nvmrc node-version-file: .nvmrc
- run: npm ci - run: npm ci
- name: Generate Prisma client before typechecks - name: Generate Prisma client before typechecks
run: npx --no-install prisma generate run: npx --no-install prisma generate
+3
View File
@@ -64,3 +64,6 @@ report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
/dev.db-* /dev.db-*
/prisma/*.db /prisma/*.db
/prisma/*.db-* /prisma/*.db-*
# Generated public API artifact
/openapi.json
+2
View File
@@ -15,3 +15,5 @@ dfca11c81f7e6dadce3ca6f961d265c5e3f8ff8e:src/tbank/portfolio.service.spec.ts:gen
# Historical CircleCI badge copied verbatim from nestjs/typescript-starter README. # Historical CircleCI badge copied verbatim from nestjs/typescript-starter README.
# Verified against https://github.com/nestjs/typescript-starter/blob/master/README.md # Verified against https://github.com/nestjs/typescript-starter/blob/master/README.md
f8dcfb71c1767696a9aa6741ddaf6575416c901e:README.md:generic-api-key:5 f8dcfb71c1767696a9aa6741ddaf6575416c901e:README.md:generic-api-key:5
# Container smoke fixture: same deterministic 32-byte test key, never a runtime secret.
1a6be432f24f757728a03415051392069112af1f:test/container-smoke.cjs:generic-api-key:14
+33 -25
View File
@@ -1,30 +1,38 @@
# Backend # Backend router
NestJS API; общие правила работы — в root `AGENTS.md`. Репозиторий самостоятельный: обязательные инструкции и проверки используют
До backend-решения прочитай [router](.ai/CONVENTIONS.md) и только его файлы и предоставленные публичные контракты. Перед работой прочитай
[sources](.ai/SOURCES.md), затем только применимые документы/разделы. [порядок работы](.ai/conventions/engineering.md#граница-и-порядок-работы), затем
объединяй triggers задачи и путей; читай активированные темы до решения.
## Проверка | Решение / сигнал | Читать |
| --- | --- |
| Устройство/data flow, module/layer/public API/DI/suffix, owner | [Architecture](.ai/architecture.md) |
| TS vocabulary, domain literals, magic values | [Types/values](.ai/architecture.md#domain-literals-and-magic-values) |
| Controller/DTO/validation, route/status/response, OpenAPI | [HTTP API](.ai/conventions/http-api.md) |
| Application/domain/HTTP error, exception, code | [Errors](.ai/conventions/error-handling.md) |
| Repository/Prisma/query, transaction, migration, atomic write | [Data](.ai/conventions/data.md) |
| Tests или behavior с regression risk | [Testing](.ai/conventions/testing.md) |
| JWT/cookie/session/guard/access, encryption, secrets | [Security](.ai/conventions/security-auth.md) |
| External client/gateway/SDK/proto, retry/rate limit/cache | [Integrations](.ai/conventions/integrations.md) |
| Logging/context/levels, sensitive logs | [Observability](.ai/conventions/observability.md); sensitive data также Security |
| Изменение публичного wire contract / Swagger metadata | [HTTP API](.ai/conventions/http-api.md#источник-контракта) |
| Helper/dependency/новый pattern, refactor/scope | [Change scope](.ai/conventions/engineering.md#change-scope), для пакета — [Dependencies](.ai/conventions/engineering.md#dependencies) |
| Выбор gate, tooling, завершение | [Проверки](.ai/conventions/engineering.md#проверки-и-завершение) |
Перед Node-командами: `nvm use --silent` по `.nvmrc`. Новый endpoint обычно требует HTTP API + Testing; Data/Security добавляются
После lockfile install `npm ci` полный gate — `npm run verify`: по persistence/access trigger. Auth refresh активирует Security + Data + Testing.
zero-warning ESLint, application/test TypeScript, Jest и build. Перед завершением повтори routing по новым и изменённым файлам. Для Git/release
`lint:fix`/`format` пишут файлы и запускаются отдельно от доказательства проверки. прочитай [Git и релизы](.ai/conventions/engineering.md#git-и-релизы).
`test:cov` — диагностика без общего threshold; scripts принадлежат `package.json`.
HTTP contract changes сохраняют [направление контракта](../docs/architecture/http-api.md#источник-контракта) ## Источники
и проходят contract check. Общий e2e запускается на отдельных временных DB;
применимость и маршрут проверки
описаны в [backend testing](.ai/conventions/backend-testing.md).
Generated Prisma client не редактируется вручную. Credentials, авторизация
доступа к счетам и внешние интеграции остаются server-side.
ESLint/TypeScript не доказывают правильность domain boundaries, security или | Вопрос | Локальный источник |
денежных вычислений: для них нужны semantic review и risk-based tests. | --- | --- |
| HTTP errors/DTO/wire shape | [HTTP API](.ai/conventions/http-api.md), генерация `npm run openapi:generate`, configured HTTP tests |
## Ветки и релизы | Auth/session, account access, secrets | [Security](.ai/conventions/security-auth.md) и auth/access tests |
| Prisma/data ownership | [Data](.ai/conventions/data.md), Prisma schema/migrations |
Работа — в отдельной ветке от `develop`, затем PR в `develop`; в `main` напрямую | T-Bank/cache/logging | [Integrations](.ai/conventions/integrations.md), [Observability](.ai/conventions/observability.md) |
не пушить. Release/hotfix и синхронизация веток — по [root README](../README.md#релизы-и-ветки). | Money и финансовые расчёты | [Data](.ai/conventions/data.md#деньги-и-точность), domain source и tests владельца |
Commits и заголовки PR — Conventional Commits; для спецификаций допустим `spec:`. | Поведение области | Module source и tests; неясное ожидание уточни до изменения |
Перед подготовкой релиза прочитай root README; версии вручную не повышать. | Versions/scripts/check scope | Manifest/lockfile, ESLint/TypeScript/Jest/Prisma configs |
+16
View File
@@ -1,5 +1,21 @@
# Changelog # Changelog
## [0.2.0](https://git.personal.ksv741.ru/WealthLedger/backend/compare/v0.1.0...v0.2.0) (2026-09-16)
### Features
* add backend container delivery ([1a6be43](https://git.personal.ksv741.ru/WealthLedger/backend/commit/1a6be432f24f757728a03415051392069112af1f))
* generate OpenAPI without peer repositories ([32c12fb](https://git.personal.ksv741.ru/WealthLedger/backend/commit/32c12fb10d4f739c6af0ce32b8f2ea6e94c42090))
### Bug Fixes
* allow reviewed container smoke fixture ([d7cb7e3](https://git.personal.ksv741.ru/WealthLedger/backend/commit/d7cb7e3e206c995ef2fa6f1ee61ef7e93599d88f))
### Documentation
* refine backend conventions and routing ([9810d47](https://git.personal.ksv741.ru/WealthLedger/backend/commit/9810d47e42d9275cf2520556f517d411058ddcb3))
* simplify conventions layout ([0167826](https://git.personal.ksv741.ru/WealthLedger/backend/commit/016782657acfd35ff3fd755462007938fac35156))
## 0.1.0 (2026-09-14) ## 0.1.0 (2026-09-14)
### Features ### Features
+41
View File
@@ -0,0 +1,41 @@
FROM node:24.21.0-bookworm-slim AS base
WORKDIR /app
FROM base AS dependencies
# Native SQLite/bcrypt builds happen only here; compilers stay out of runtime.
RUN apt-get update && apt-get install -y --no-install-recommends python3=3.11.2-1+b1 make=4.3-4.1 g++=4:12.2.0-3 ca-certificates=20250419~deb12u1 openssl=3.0.20-1~deb12u2 && rm -rf /var/lib/apt/lists/*
COPY package.json package-lock.json ./
RUN npm ci
COPY . .
RUN npx --no-install prisma generate
FROM dependencies AS development
# Nest watch uses ps to stop the previous process tree before restarting.
RUN apt-get update && apt-get install -y --no-install-recommends procps=2:4.0.2-3 && rm -rf /var/lib/apt/lists/*
RUN mkdir /data && chown node:node /data && chown -R node:node /app
USER node
ENV NODE_ENV=development DATABASE_URL=file:/data/wealthledger.db
EXPOSE 3000
CMD ["npm", "run", "start:dev"]
FROM dependencies AS build
RUN npm run build && npm prune --omit=dev --omit=optional
FROM base AS production
RUN apt-get update && apt-get install -y --no-install-recommends ca-certificates=20250419~deb12u1 openssl=3.0.20-1~deb12u2 && rm -rf /var/lib/apt/lists/* \
&& mkdir /data && chown node:node /data
ARG VERSION
ARG REVISION
LABEL org.opencontainers.image.version=$VERSION \
org.opencontainers.image.revision=$REVISION
COPY --from=build /app/node_modules ./node_modules
COPY --from=build /app/dist ./dist
COPY --from=build /app/prisma ./prisma
COPY --from=build /app/prisma.config.ts ./prisma.config.ts
COPY --from=build /app/package.json /app/package-lock.json ./
ENV NODE_ENV=production DATABASE_URL=file:/data/wealthledger.db
USER node
EXPOSE 3000
CMD ["node", "dist/src/main.js"]
HEALTHCHECK --interval=10s --timeout=3s --start-period=20s --retries=6 \
CMD node -e 'fetch("http://127.0.0.1:3000/api/v1/auth/me").then(async r=>{await r.arrayBuffer();process.exit(r.status===401?0:1)}).catch(()=>process.exit(1))'
+94 -20
View File
@@ -1,7 +1,8 @@
# WealthLedger backend # WealthLedger backend
NestJS API WealthLedger. Backend владеет auth, SQLite-данными и server-side NestJS API WealthLedger. Backend владеет auth, SQLite-данными и server-side
интеграцией T-Bank; frontend не обращается к T-Bank напрямую. интеграциями. Потребители используют публичный HTTP API и OpenAPI artifact;
их репозитории не нужны для разработки, сборки и проверок backend.
## Быстрый старт ## Быстрый старт
@@ -21,6 +22,33 @@ npm run start:dev
T-Bank token в `.env`: он вводится пользователем и хранится в зашифрованном T-Bank token в `.env`: он вводится пользователем и хранится в зашифрованном
виде. виде.
## Контейнерный image
`Dockerfile` имеет targets `development` (Nest watch, generated Prisma client и Linux native modules)
и `production` (compiled Node process от UID 1000, Prisma CLI/migrations и T-Bank proto assets). Node соответствует `.nvmrc`.
Build context ограничен `.dockerignore`; `.env`, local DB и host node_modules
не попадают в image.
```bash
docker build --target development -t wealthledger-backend:development .
docker build --target production -t wealthledger-backend:production .
nvm use --silent
npm run test:container -- wealthledger-backend:production
```
Gitea `image.yml` запускается на защищённом release tag `vX.Y.Z`, проверяет
package/lock version и canonical gates. Runner `ubuntu-latest` должен иметь
Docker/buildx. Dedicated package writer задаётся Actions secrets
`REGISTRY_USERNAME`/`REGISTRY_TOKEN`; server использует отдельную read-only
identity. Публикуются semver tags, без `latest`; существующие
release tags не перезаписываются. Production выбирает опубликованный version tag.
Compose topology принадлежит workspace, эксплуатационная последовательность —
[документация](https://git.personal.ksv741.ru/WealthLedger/docs),
страница `architecture/deployment.md`.
Проверка публикации требует настроенного registry/runner; локальный build
не доказывает package permissions.
## Проверка ## Проверка
Единый completion gate не изменяет versioned-файлы: Единый completion gate не изменяет versioned-файлы:
@@ -33,7 +61,7 @@ npm run verify
application/test typechecks, Jest и build. `npm run lint:fix` — отдельная application/test typechecks, Jest и build. `npm run lint:fix` — отдельная
записывающая команда, `npm run test:cov` — диагностика без общего числового записывающая команда, `npm run test:cov` — диагностика без общего числового
threshold. `npm run test:e2e` отдельно проверяет все HTTP suites на новых базах threshold. `npm run test:e2e` отдельно проверяет все HTTP suites на новых базах
и runtime OpenAPI с соседним frontend checkout. Для полной backend-проверки и соответствие генерируемого OpenAPI настроенному приложению. Для полной backend-проверки
выполните обе команды. выполните обе команды.
Nest 12 поставляется как ESM. Скрипты Jest включают `--experimental-vm-modules`, Nest 12 поставляется как ESM. Скрипты Jest включают `--experimental-vm-modules`,
@@ -44,11 +72,9 @@ TypeScript 7 требует поддержки со стороны `ts-jest` и
### CI и обновления зависимостей ### CI и обновления зависимостей
`.gitea/workflows/verify.yml` запускает `npm ci`, генерацию Prisma client, `.gitea/workflows/verify.yml` запускает `npm ci`, генерацию Prisma client,
`npm run verify` и `npm run test:e2e` на push и PR. Frontend snapshot для `npm run verify` и `npm run test:e2e` на push и PR. Используется только checkout
разработки берётся из `develop`; для `main`, PR в `main` и `release/*` — из backend и его зависимости; OpenAPI генерируется во время проверки, SHA записывается в лог.
полного SHA в `.ci/refs.json`. Лог сохраняет оба SHA. Изменение Успешный unit gate не заменяет SQLite/e2e; ошибка любой suite завершает job с FAIL.
контракта согласуется с этой frontend revision. Успешный unit gate не заменяет
SQLite/e2e; ошибка любой suite завершает job с FAIL.
`security.yml` сканирует всю Git-историю Gitleaks 8.30.1 с полностью скрытыми `security.yml` сканирует всю Git-историю Gitleaks 8.30.1 с полностью скрытыми
значениями и выполняет `npm audit --include=dev --audit-level=low` при push, значениями и выполняет `npm audit --include=dev --audit-level=low` при push,
@@ -78,25 +104,39 @@ runtime-валидацию. В обычном коде нужны инициал
## Документация ## Документация
- [Router conventions](.ai/CONVENTIONS.md) и [источники](.ai/SOURCES.md) - [AGENTS: правила и источники](AGENTS.md)
- [Backend architecture](.ai/conventions/backend-architecture.md) - [Backend architecture](.ai/architecture.md)
- [HTTP API](.ai/conventions/backend-http-api.md), [data](.ai/conventions/backend-data.md) и [testing](.ai/conventions/backend-testing.md) - [HTTP API](.ai/conventions/http-api.md), [data](.ai/conventions/data.md) и [testing](.ai/conventions/testing.md)
- [Security/auth](.ai/conventions/security-auth.md) и [integrations](.ai/conventions/backend-integrations.md) - [Security/auth](.ai/conventions/security-auth.md) и [integrations](.ai/conventions/integrations.md)
- [Карта проектных фактов](../docs/README.md) - [Генерация OpenAPI](#генерация-openapi)
- [API contract и runtime OpenAPI](../docs/architecture/http-api.md#источник-контракта)
- [Обзор поставленной системы](../docs/overview.md)
- [Архитектурные решения](../docs/adr/README.md)
Из backend runtime OpenAPI можно read-only сверить с frontend snapshot: Генерация без сервера сверяется с OpenAPI настроенного приложения:
```bash ```bash
npm run test:openapi-contract npm run test:openapi-contract
``` ```
Разработка идёт напрямую по задаче пользователя. Каждый affected repo проходит ## Генерация OpenAPI
свой verify; при изменении wire contract дополнительно выполняется contract check.
Обязательного OpenSpec/SDD lifecycle нет. ```bash
nvm use --silent
npm run openapi:generate
```
Команда собирает backend и записывает `openapi.json` в корень репозитория.
Сервер и HTTP-порт не запускаются; Nest preview читает metadata без создания
провайдеров и подключения к БД/T-Bank. Настроенный `.env` и реальные секреты
не нужны. `openapi.json` исключён из Git и каждый раз создаётся из DTO/Swagger
metadata; передай полученный artifact потребителю отдельно.
`npm run test:openapi-contract` собирает backend и сравнивает результат генератора
с runtime schema настроенного приложения, используя временный каталог и SQLite.
`npm run test:e2e` также сначала собирает backend. Изменение публичного API
проверяй relevant HTTP tests: без versioned snapshot сравнение не отслеживает
breaking changes относительно предыдущего выпуска.
Разработка идёт напрямую по задаче пользователя; OpenSpec/SDD — по явному запросу.
## Реальная SQLite и сохранность данных ## Реальная SQLite и сохранность данных
@@ -161,4 +201,38 @@ npm run test:durability -- "$baseline_dir"
Durability оставляет временные DB, логи и `result.json` по пути из вывода для Durability оставляет временные DB, логи и `result.json` по пути из вывода для
диагностики; он не откатывает schema пользовательской базы. диагностики; он не откатывает schema пользовательской базы.
Версии и ветки: [процесс релиза](../README.md#релизы-и-ветки). ## Релизы
Репозиторий выпускается независимо. `develop` — интеграция, `main` — принятые
релизы. Работа: `codex/<task>` от актуального `develop`, PR в `develop`.
Обычные PR допускают squash; release и обратная синхронизация сохраняют историю.
Commits и PR titles — Conventional Commits, включая `spec:`. Версии вручную не повышай.
1. Получи `main`, `develop` и теги: `git fetch origin --tags`. Убедись, что
`develop` содержит последний релиз. Создай `release/<name>` от проверенного
`develop`; `nvm use --silent`, `npm ci`. Hotfix начинается от `main` в
`release/hotfix-<name>` и проходит тот же процесс.
2. На чистой ветке выполни `npm run release:preview`, просмотри версию/changelog,
затем `npm run release:prepare`. Команда создаёт `chore(release): X.Y.Z`,
обновляет manifest/lockfile/CHANGELOG; тег и push не выполняет. Не обходи
guards ветки, чистого дерева, отсутствующих тегов или уже подготовленной версии.
3. Проверь diff и штатные gates этого репозитория. Отправь только release-ветку,
открой PR в `main` с заголовком `chore(release): X.Y.Z`. После зелёных
`verify`, `security`, `commits` выполни fast-forward-only merge. Если `main`
изменился, согласуй историю и повтори проверки кандидата.
4. Получи удалённый `main`, сравни его HEAD с проверенным release SHA и версией
manifest/lockfile. Создай аннотированный `vX.Y.Z` именно на этом SHA:
`git tag -a vX.Y.Z <release-sha> -m "Release X.Y.Z"`, затем отправь только
`git push origin refs/tags/vX.Y.Z`. Не запускай release-it повторно на `main`.
5. Верни release-коммит через PR `main → develop` без squash/rebase; проверь,
что `develop` содержит тег. Опубликованные теги не перемещай. При сбое после
merge создай отсутствующий тег без нового bump; до merge исправь и проверь
существующий кандидат без повторного повышения версии.
`feat:` повышает minor, `fix:`/`perf:` и прочие разрешённые типы — patch;
`!`/`BREAKING CHANGE:` — major, включая версии до 1.0. `chore(release):`
исключён из рекомендации и changelog. Конфигурация — `.release-it.json`.
В Gitea для `main`/`develop` должны быть запрещены прямой/force push и обход
администратором, обязательны `verify`, `security`, `commits`; теги `v*` защищены.
Workflow сам не включает эти настройки. Отдельное review approval для одного
владельца не требуется. Npm publishing, deploy и Gitea Releases не включены.
+3 -131
View File
@@ -1,12 +1,12 @@
{ {
"name": "wealth-ledger-backend", "name": "wealth-ledger-backend",
"version": "0.1.0", "version": "0.2.0",
"lockfileVersion": 3, "lockfileVersion": 3,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "wealth-ledger-backend", "name": "wealth-ledger-backend",
"version": "0.1.0", "version": "0.2.0",
"license": "UNLICENSED", "license": "UNLICENSED",
"dependencies": { "dependencies": {
"@grpc/grpc-js": "1.14.4", "@grpc/grpc-js": "1.14.4",
@@ -31,6 +31,7 @@
"passport": "0.7.0", "passport": "0.7.0",
"passport-jwt": "4.0.1", "passport-jwt": "4.0.1",
"pino-http": "11.0.0", "pino-http": "11.0.0",
"prisma": "7.10.0",
"reflect-metadata": "0.2.2", "reflect-metadata": "0.2.2",
"rxjs": "7.8.2" "rxjs": "7.8.2"
}, },
@@ -57,7 +58,6 @@
"globals": "17.12.0", "globals": "17.12.0",
"jest": "30.5.1", "jest": "30.5.1",
"prettier": "3.9.6", "prettier": "3.9.6",
"prisma": "7.10.0",
"release-it": "21.0.2", "release-it": "21.0.2",
"source-map-support": "0.5.21", "source-map-support": "0.5.21",
"supertest": "7.2.2", "supertest": "7.2.2",
@@ -1280,14 +1280,12 @@
"version": "0.4.3", "version": "0.4.3",
"resolved": "https://registry.npmjs.org/@electric-sql/pglite/-/pglite-0.4.3.tgz", "resolved": "https://registry.npmjs.org/@electric-sql/pglite/-/pglite-0.4.3.tgz",
"integrity": "sha512-ichuWTgtd4mOM1G4SpyGJa5trT03lWbMypDV0fUXUCXg5hiHqVAz/bZyV68NqmkLB7WcYmj1RMJVSp8HV/v/ZQ==", "integrity": "sha512-ichuWTgtd4mOM1G4SpyGJa5trT03lWbMypDV0fUXUCXg5hiHqVAz/bZyV68NqmkLB7WcYmj1RMJVSp8HV/v/ZQ==",
"devOptional": true,
"license": "Apache-2.0" "license": "Apache-2.0"
}, },
"node_modules/@electric-sql/pglite-socket": { "node_modules/@electric-sql/pglite-socket": {
"version": "0.1.3", "version": "0.1.3",
"resolved": "https://registry.npmjs.org/@electric-sql/pglite-socket/-/pglite-socket-0.1.3.tgz", "resolved": "https://registry.npmjs.org/@electric-sql/pglite-socket/-/pglite-socket-0.1.3.tgz",
"integrity": "sha512-LAciWM0M1dCL8hlsxu2venbVZcdxema0BtDfpWYVqr+Y468UADw0pFWidhKw1M8sfJ8rdLT71tjMmnirf/IZRQ==", "integrity": "sha512-LAciWM0M1dCL8hlsxu2venbVZcdxema0BtDfpWYVqr+Y468UADw0pFWidhKw1M8sfJ8rdLT71tjMmnirf/IZRQ==",
"devOptional": true,
"license": "Apache-2.0", "license": "Apache-2.0",
"bin": { "bin": {
"pglite-server": "dist/scripts/server.js" "pglite-server": "dist/scripts/server.js"
@@ -1300,7 +1298,6 @@
"version": "0.3.3", "version": "0.3.3",
"resolved": "https://registry.npmjs.org/@electric-sql/pglite-tools/-/pglite-tools-0.3.3.tgz", "resolved": "https://registry.npmjs.org/@electric-sql/pglite-tools/-/pglite-tools-0.3.3.tgz",
"integrity": "sha512-AlzLJTRJ8+UFgK8CmxIpyIpJ0+YaFw02IiOSdYrqxwPXdSyeIShz8aa9Tq+tYFXdPwcaMp/Fc80mQZ1dkOQ/wg==", "integrity": "sha512-AlzLJTRJ8+UFgK8CmxIpyIpJ0+YaFw02IiOSdYrqxwPXdSyeIShz8aa9Tq+tYFXdPwcaMp/Fc80mQZ1dkOQ/wg==",
"devOptional": true,
"license": "Apache-2.0", "license": "Apache-2.0",
"peerDependencies": { "peerDependencies": {
"@electric-sql/pglite": "0.4.3" "@electric-sql/pglite": "0.4.3"
@@ -3872,7 +3869,6 @@
"version": "7.10.0", "version": "7.10.0",
"resolved": "https://registry.npmjs.org/@prisma/config/-/config-7.10.0.tgz", "resolved": "https://registry.npmjs.org/@prisma/config/-/config-7.10.0.tgz",
"integrity": "sha512-Rcg828gIRE3HOQ3pOATFjV5d/P0U9OIobxhd/IMxlfWjA4vru0eGwb0AIwFw0rmcLMVShohZYWPixVxkBHsxUA==", "integrity": "sha512-Rcg828gIRE3HOQ3pOATFjV5d/P0U9OIobxhd/IMxlfWjA4vru0eGwb0AIwFw0rmcLMVShohZYWPixVxkBHsxUA==",
"devOptional": true,
"license": "Apache-2.0", "license": "Apache-2.0",
"dependencies": { "dependencies": {
"c12": "3.3.4", "c12": "3.3.4",
@@ -3891,7 +3887,6 @@
"version": "0.24.17", "version": "0.24.17",
"resolved": "https://registry.npmjs.org/@prisma/dev/-/dev-0.24.17.tgz", "resolved": "https://registry.npmjs.org/@prisma/dev/-/dev-0.24.17.tgz",
"integrity": "sha512-UvdZzmpFwknnfreh6Jije84ekkYGPYEJhXG1tFzCsCfQyzJifrOo38eZc0qajzvaC6OLUOrN9ML5XfCnEZL9DA==", "integrity": "sha512-UvdZzmpFwknnfreh6Jije84ekkYGPYEJhXG1tFzCsCfQyzJifrOo38eZc0qajzvaC6OLUOrN9ML5XfCnEZL9DA==",
"devOptional": true,
"license": "ISC", "license": "ISC",
"dependencies": { "dependencies": {
"@electric-sql/pglite": "0.4.3", "@electric-sql/pglite": "0.4.3",
@@ -3924,7 +3919,6 @@
"version": "7.10.0", "version": "7.10.0",
"resolved": "https://registry.npmjs.org/@prisma/engines/-/engines-7.10.0.tgz", "resolved": "https://registry.npmjs.org/@prisma/engines/-/engines-7.10.0.tgz",
"integrity": "sha512-KNumN6NHFwybvfdYzTee9pqwx5PvknpWAaHn6L5NsbrKdl+SQrsVZs9opKs6U6SAsvB26HDt3WybRjOhgoWOYQ==", "integrity": "sha512-KNumN6NHFwybvfdYzTee9pqwx5PvknpWAaHn6L5NsbrKdl+SQrsVZs9opKs6U6SAsvB26HDt3WybRjOhgoWOYQ==",
"devOptional": true,
"hasInstallScript": true, "hasInstallScript": true,
"license": "Apache-2.0", "license": "Apache-2.0",
"dependencies": { "dependencies": {
@@ -3938,14 +3932,12 @@
"version": "7.10.0-4.0edf323efd1d98336f3f0a68684b56f689b900d3", "version": "7.10.0-4.0edf323efd1d98336f3f0a68684b56f689b900d3",
"resolved": "https://registry.npmjs.org/@prisma/engines-version/-/engines-version-7.10.0-4.0edf323efd1d98336f3f0a68684b56f689b900d3.tgz", "resolved": "https://registry.npmjs.org/@prisma/engines-version/-/engines-version-7.10.0-4.0edf323efd1d98336f3f0a68684b56f689b900d3.tgz",
"integrity": "sha512-8OJ6RuZTZ06eFUOtBwxVmv8XMmOW6HWN5F+uxUbZkGxR0Bfab1dfAdXaHPmR5mb59E+fmUo8IOzXlbLY1SClbw==", "integrity": "sha512-8OJ6RuZTZ06eFUOtBwxVmv8XMmOW6HWN5F+uxUbZkGxR0Bfab1dfAdXaHPmR5mb59E+fmUo8IOzXlbLY1SClbw==",
"devOptional": true,
"license": "Apache-2.0" "license": "Apache-2.0"
}, },
"node_modules/@prisma/engines/node_modules/@prisma/get-platform": { "node_modules/@prisma/engines/node_modules/@prisma/get-platform": {
"version": "7.10.0", "version": "7.10.0",
"resolved": "https://registry.npmjs.org/@prisma/get-platform/-/get-platform-7.10.0.tgz", "resolved": "https://registry.npmjs.org/@prisma/get-platform/-/get-platform-7.10.0.tgz",
"integrity": "sha512-0bra1LFYi8xNw0yqV62bHJNQk4BKleOngZiqPWIQc7a3+9q6rqsnqJ15BuepP8943PFMvtmgnP52juZsyYkA6w==", "integrity": "sha512-0bra1LFYi8xNw0yqV62bHJNQk4BKleOngZiqPWIQc7a3+9q6rqsnqJ15BuepP8943PFMvtmgnP52juZsyYkA6w==",
"devOptional": true,
"license": "Apache-2.0", "license": "Apache-2.0",
"dependencies": { "dependencies": {
"@prisma/debug": "7.10.0" "@prisma/debug": "7.10.0"
@@ -3955,7 +3947,6 @@
"version": "7.10.0", "version": "7.10.0",
"resolved": "https://registry.npmjs.org/@prisma/fetch-engine/-/fetch-engine-7.10.0.tgz", "resolved": "https://registry.npmjs.org/@prisma/fetch-engine/-/fetch-engine-7.10.0.tgz",
"integrity": "sha512-Zqyu8DY14t6W/xwmAxUYWCXtHrvQnSvT644EAZSsdM8NSmCS74vJJbBKdVsK3ucFpnUWkEpbO1a0CxJXrg130g==", "integrity": "sha512-Zqyu8DY14t6W/xwmAxUYWCXtHrvQnSvT644EAZSsdM8NSmCS74vJJbBKdVsK3ucFpnUWkEpbO1a0CxJXrg130g==",
"devOptional": true,
"license": "Apache-2.0", "license": "Apache-2.0",
"dependencies": { "dependencies": {
"@prisma/debug": "7.10.0", "@prisma/debug": "7.10.0",
@@ -3967,7 +3958,6 @@
"version": "7.10.0", "version": "7.10.0",
"resolved": "https://registry.npmjs.org/@prisma/get-platform/-/get-platform-7.10.0.tgz", "resolved": "https://registry.npmjs.org/@prisma/get-platform/-/get-platform-7.10.0.tgz",
"integrity": "sha512-0bra1LFYi8xNw0yqV62bHJNQk4BKleOngZiqPWIQc7a3+9q6rqsnqJ15BuepP8943PFMvtmgnP52juZsyYkA6w==", "integrity": "sha512-0bra1LFYi8xNw0yqV62bHJNQk4BKleOngZiqPWIQc7a3+9q6rqsnqJ15BuepP8943PFMvtmgnP52juZsyYkA6w==",
"devOptional": true,
"license": "Apache-2.0", "license": "Apache-2.0",
"dependencies": { "dependencies": {
"@prisma/debug": "7.10.0" "@prisma/debug": "7.10.0"
@@ -3977,7 +3967,6 @@
"version": "7.2.0", "version": "7.2.0",
"resolved": "https://registry.npmjs.org/@prisma/get-platform/-/get-platform-7.2.0.tgz", "resolved": "https://registry.npmjs.org/@prisma/get-platform/-/get-platform-7.2.0.tgz",
"integrity": "sha512-k1V0l0Td1732EHpAfi2eySTezyllok9dXb6UQanajkJQzPUGi3vO2z7jdkz67SypFTdmbnyGYxvEvYZdZsMAVA==", "integrity": "sha512-k1V0l0Td1732EHpAfi2eySTezyllok9dXb6UQanajkJQzPUGi3vO2z7jdkz67SypFTdmbnyGYxvEvYZdZsMAVA==",
"devOptional": true,
"license": "Apache-2.0", "license": "Apache-2.0",
"dependencies": { "dependencies": {
"@prisma/debug": "7.2.0" "@prisma/debug": "7.2.0"
@@ -3987,21 +3976,18 @@
"version": "7.2.0", "version": "7.2.0",
"resolved": "https://registry.npmjs.org/@prisma/debug/-/debug-7.2.0.tgz", "resolved": "https://registry.npmjs.org/@prisma/debug/-/debug-7.2.0.tgz",
"integrity": "sha512-YSGTiSlBAVJPzX4ONZmMotL+ozJwQjRmZweQNIq/ER0tQJKJynNkRB3kyvt37eOfsbMCXk3gnLF6J9OJ4QWftw==", "integrity": "sha512-YSGTiSlBAVJPzX4ONZmMotL+ozJwQjRmZweQNIq/ER0tQJKJynNkRB3kyvt37eOfsbMCXk3gnLF6J9OJ4QWftw==",
"devOptional": true,
"license": "Apache-2.0" "license": "Apache-2.0"
}, },
"node_modules/@prisma/query-plan-executor": { "node_modules/@prisma/query-plan-executor": {
"version": "7.2.0", "version": "7.2.0",
"resolved": "https://registry.npmjs.org/@prisma/query-plan-executor/-/query-plan-executor-7.2.0.tgz", "resolved": "https://registry.npmjs.org/@prisma/query-plan-executor/-/query-plan-executor-7.2.0.tgz",
"integrity": "sha512-EOZmNzcV8uJ0mae3DhTsiHgoNCuu1J9mULQpGCh62zN3PxPTd+qI9tJvk5jOst8WHKQNwJWR3b39t0XvfBB0WQ==", "integrity": "sha512-EOZmNzcV8uJ0mae3DhTsiHgoNCuu1J9mULQpGCh62zN3PxPTd+qI9tJvk5jOst8WHKQNwJWR3b39t0XvfBB0WQ==",
"devOptional": true,
"license": "Apache-2.0" "license": "Apache-2.0"
}, },
"node_modules/@prisma/streams-local": { "node_modules/@prisma/streams-local": {
"version": "0.1.11", "version": "0.1.11",
"resolved": "https://registry.npmjs.org/@prisma/streams-local/-/streams-local-0.1.11.tgz", "resolved": "https://registry.npmjs.org/@prisma/streams-local/-/streams-local-0.1.11.tgz",
"integrity": "sha512-0TcebL559MByKqTJ+SsrFIEg228iw8UCVRFckzgfRSiJqczhs+MuAgWOF9lnOIV/IVqvu+KMnFTH0eDeTQMpUg==", "integrity": "sha512-0TcebL559MByKqTJ+SsrFIEg228iw8UCVRFckzgfRSiJqczhs+MuAgWOF9lnOIV/IVqvu+KMnFTH0eDeTQMpUg==",
"devOptional": true,
"license": "Apache-2.0", "license": "Apache-2.0",
"dependencies": { "dependencies": {
"ajv": "^8.12.0", "ajv": "^8.12.0",
@@ -4018,7 +4004,6 @@
"version": "8.20.0", "version": "8.20.0",
"resolved": "https://registry.npmjs.org/ajv/-/ajv-8.20.0.tgz", "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.20.0.tgz",
"integrity": "sha512-Thbli+OlOj+iMPYFBVBfJ3OmCAnaSyNn4M1vz9T6Gka5Jt9ba/HIR56joy65tY6kx/FCF5VXNB819Y7/GUrBGA==", "integrity": "sha512-Thbli+OlOj+iMPYFBVBfJ3OmCAnaSyNn4M1vz9T6Gka5Jt9ba/HIR56joy65tY6kx/FCF5VXNB819Y7/GUrBGA==",
"devOptional": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"fast-deep-equal": "^3.1.3", "fast-deep-equal": "^3.1.3",
@@ -4035,14 +4020,12 @@
"version": "1.0.0", "version": "1.0.0",
"resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz",
"integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==",
"devOptional": true,
"license": "MIT" "license": "MIT"
}, },
"node_modules/@prisma/studio-core": { "node_modules/@prisma/studio-core": {
"version": "0.33.0", "version": "0.33.0",
"resolved": "https://registry.npmjs.org/@prisma/studio-core/-/studio-core-0.33.0.tgz", "resolved": "https://registry.npmjs.org/@prisma/studio-core/-/studio-core-0.33.0.tgz",
"integrity": "sha512-V2fX/nKEymNTrHXwfP26PGjoLStO35Ogu+ex7CFJbLrMYEcZxxZpiSNOs7px23Hk5mzLWvM5RsqG6Ka+rha+wg==", "integrity": "sha512-V2fX/nKEymNTrHXwfP26PGjoLStO35Ogu+ex7CFJbLrMYEcZxxZpiSNOs7px23Hk5mzLWvM5RsqG6Ka+rha+wg==",
"devOptional": true,
"license": "Apache-2.0", "license": "Apache-2.0",
"dependencies": { "dependencies": {
"@radix-ui/react-toggle": "1.1.10", "@radix-ui/react-toggle": "1.1.10",
@@ -4128,14 +4111,12 @@
"version": "1.1.3", "version": "1.1.3",
"resolved": "https://registry.npmjs.org/@radix-ui/primitive/-/primitive-1.1.3.tgz", "resolved": "https://registry.npmjs.org/@radix-ui/primitive/-/primitive-1.1.3.tgz",
"integrity": "sha512-JTF99U/6XIjCBo0wqkU5sK10glYe27MRRsfwoiq5zzOEZLHU3A3KCMa5X/azekYRCJ0HlwI0crAXS/5dEHTzDg==", "integrity": "sha512-JTF99U/6XIjCBo0wqkU5sK10glYe27MRRsfwoiq5zzOEZLHU3A3KCMa5X/azekYRCJ0HlwI0crAXS/5dEHTzDg==",
"devOptional": true,
"license": "MIT" "license": "MIT"
}, },
"node_modules/@radix-ui/react-compose-refs": { "node_modules/@radix-ui/react-compose-refs": {
"version": "1.1.2", "version": "1.1.2",
"resolved": "https://registry.npmjs.org/@radix-ui/react-compose-refs/-/react-compose-refs-1.1.2.tgz", "resolved": "https://registry.npmjs.org/@radix-ui/react-compose-refs/-/react-compose-refs-1.1.2.tgz",
"integrity": "sha512-z4eqJvfiNnFMHIIvXP3CY57y2WJs5g2v3X0zm9mEJkrkNv4rDxu+sg9Jh8EkXyeqBkB7SOcboo9dMVqhyrACIg==", "integrity": "sha512-z4eqJvfiNnFMHIIvXP3CY57y2WJs5g2v3X0zm9mEJkrkNv4rDxu+sg9Jh8EkXyeqBkB7SOcboo9dMVqhyrACIg==",
"devOptional": true,
"license": "MIT", "license": "MIT",
"peerDependencies": { "peerDependencies": {
"@types/react": "*", "@types/react": "*",
@@ -4151,7 +4132,6 @@
"version": "2.1.3", "version": "2.1.3",
"resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-2.1.3.tgz", "resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-2.1.3.tgz",
"integrity": "sha512-m9gTwRkhy2lvCPe6QJp4d3G1TYEUHn/FzJUtq9MjH46an1wJU+GdoGC5VLof8RX8Ft/DlpshApkhswDLZzHIcQ==", "integrity": "sha512-m9gTwRkhy2lvCPe6QJp4d3G1TYEUHn/FzJUtq9MjH46an1wJU+GdoGC5VLof8RX8Ft/DlpshApkhswDLZzHIcQ==",
"devOptional": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@radix-ui/react-slot": "1.2.3" "@radix-ui/react-slot": "1.2.3"
@@ -4175,7 +4155,6 @@
"version": "1.2.3", "version": "1.2.3",
"resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.2.3.tgz", "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.2.3.tgz",
"integrity": "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A==", "integrity": "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A==",
"devOptional": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@radix-ui/react-compose-refs": "1.1.2" "@radix-ui/react-compose-refs": "1.1.2"
@@ -4194,7 +4173,6 @@
"version": "1.1.10", "version": "1.1.10",
"resolved": "https://registry.npmjs.org/@radix-ui/react-toggle/-/react-toggle-1.1.10.tgz", "resolved": "https://registry.npmjs.org/@radix-ui/react-toggle/-/react-toggle-1.1.10.tgz",
"integrity": "sha512-lS1odchhFTeZv3xwHH31YPObmJn8gOg7Lq12inrr0+BH/l3Tsq32VfjqH1oh80ARM3mlkfMic15n0kg4sD1poQ==", "integrity": "sha512-lS1odchhFTeZv3xwHH31YPObmJn8gOg7Lq12inrr0+BH/l3Tsq32VfjqH1oh80ARM3mlkfMic15n0kg4sD1poQ==",
"devOptional": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@radix-ui/primitive": "1.1.3", "@radix-ui/primitive": "1.1.3",
@@ -4220,7 +4198,6 @@
"version": "1.2.2", "version": "1.2.2",
"resolved": "https://registry.npmjs.org/@radix-ui/react-use-controllable-state/-/react-use-controllable-state-1.2.2.tgz", "resolved": "https://registry.npmjs.org/@radix-ui/react-use-controllable-state/-/react-use-controllable-state-1.2.2.tgz",
"integrity": "sha512-BjasUjixPFdS+NKkypcyyN5Pmg83Olst0+c6vGov0diwTEo6mgdqVR6hxcEgFuh4QrAs7Rc+9KuGJ9TVCj0Zzg==", "integrity": "sha512-BjasUjixPFdS+NKkypcyyN5Pmg83Olst0+c6vGov0diwTEo6mgdqVR6hxcEgFuh4QrAs7Rc+9KuGJ9TVCj0Zzg==",
"devOptional": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@radix-ui/react-use-effect-event": "0.0.2", "@radix-ui/react-use-effect-event": "0.0.2",
@@ -4240,7 +4217,6 @@
"version": "0.0.2", "version": "0.0.2",
"resolved": "https://registry.npmjs.org/@radix-ui/react-use-effect-event/-/react-use-effect-event-0.0.2.tgz", "resolved": "https://registry.npmjs.org/@radix-ui/react-use-effect-event/-/react-use-effect-event-0.0.2.tgz",
"integrity": "sha512-Qp8WbZOBe+blgpuUT+lw2xheLP8q0oatc9UpmiemEICxGvFLYmHm9QowVZGHtJlGbS6A6yJ3iViad/2cVjnOiA==", "integrity": "sha512-Qp8WbZOBe+blgpuUT+lw2xheLP8q0oatc9UpmiemEICxGvFLYmHm9QowVZGHtJlGbS6A6yJ3iViad/2cVjnOiA==",
"devOptional": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@radix-ui/react-use-layout-effect": "1.1.1" "@radix-ui/react-use-layout-effect": "1.1.1"
@@ -4259,7 +4235,6 @@
"version": "1.1.1", "version": "1.1.1",
"resolved": "https://registry.npmjs.org/@radix-ui/react-use-layout-effect/-/react-use-layout-effect-1.1.1.tgz", "resolved": "https://registry.npmjs.org/@radix-ui/react-use-layout-effect/-/react-use-layout-effect-1.1.1.tgz",
"integrity": "sha512-RbJRS4UWQFkzHTTwVymMTUv8EqYhOp8dOOviLj2ugtTiXRaRQS7GLGxZTLL1jWhMeoSCf5zmcZkqTl9IiYfXcQ==", "integrity": "sha512-RbJRS4UWQFkzHTTwVymMTUv8EqYhOp8dOOviLj2ugtTiXRaRQS7GLGxZTLL1jWhMeoSCf5zmcZkqTl9IiYfXcQ==",
"devOptional": true,
"license": "MIT", "license": "MIT",
"peerDependencies": { "peerDependencies": {
"@types/react": "*", "@types/react": "*",
@@ -4564,35 +4539,30 @@
"version": "3.0.3", "version": "3.0.3",
"resolved": "https://registry.npmjs.org/@types/d3-array/-/d3-array-3.0.3.tgz", "resolved": "https://registry.npmjs.org/@types/d3-array/-/d3-array-3.0.3.tgz",
"integrity": "sha512-Reoy+pKnvsksN0lQUlcH6dOGjRZ/3WRwXR//m+/8lt1BXeI4xyaUZoqULNjyXXRuh0Mj4LNpkCvhUpQlY3X5xQ==", "integrity": "sha512-Reoy+pKnvsksN0lQUlcH6dOGjRZ/3WRwXR//m+/8lt1BXeI4xyaUZoqULNjyXXRuh0Mj4LNpkCvhUpQlY3X5xQ==",
"devOptional": true,
"license": "MIT" "license": "MIT"
}, },
"node_modules/@types/d3-color": { "node_modules/@types/d3-color": {
"version": "3.1.0", "version": "3.1.0",
"resolved": "https://registry.npmjs.org/@types/d3-color/-/d3-color-3.1.0.tgz", "resolved": "https://registry.npmjs.org/@types/d3-color/-/d3-color-3.1.0.tgz",
"integrity": "sha512-HKuicPHJuvPgCD+np6Se9MQvS6OCbJmOjGvylzMJRlDwUXjKTTXs6Pwgk79O09Vj/ho3u1ofXnhFOaEWWPrlwA==", "integrity": "sha512-HKuicPHJuvPgCD+np6Se9MQvS6OCbJmOjGvylzMJRlDwUXjKTTXs6Pwgk79O09Vj/ho3u1ofXnhFOaEWWPrlwA==",
"devOptional": true,
"license": "MIT" "license": "MIT"
}, },
"node_modules/@types/d3-delaunay": { "node_modules/@types/d3-delaunay": {
"version": "6.0.1", "version": "6.0.1",
"resolved": "https://registry.npmjs.org/@types/d3-delaunay/-/d3-delaunay-6.0.1.tgz", "resolved": "https://registry.npmjs.org/@types/d3-delaunay/-/d3-delaunay-6.0.1.tgz",
"integrity": "sha512-tLxQ2sfT0p6sxdG75c6f/ekqxjyYR0+LwPrsO1mbC9YDBzPJhs2HbJJRrn8Ez1DBoHRo2yx7YEATI+8V1nGMnQ==", "integrity": "sha512-tLxQ2sfT0p6sxdG75c6f/ekqxjyYR0+LwPrsO1mbC9YDBzPJhs2HbJJRrn8Ez1DBoHRo2yx7YEATI+8V1nGMnQ==",
"devOptional": true,
"license": "MIT" "license": "MIT"
}, },
"node_modules/@types/d3-format": { "node_modules/@types/d3-format": {
"version": "3.0.1", "version": "3.0.1",
"resolved": "https://registry.npmjs.org/@types/d3-format/-/d3-format-3.0.1.tgz", "resolved": "https://registry.npmjs.org/@types/d3-format/-/d3-format-3.0.1.tgz",
"integrity": "sha512-5KY70ifCCzorkLuIkDe0Z9YTf9RR2CjBX1iaJG+rgM/cPP+sO+q9YdQ9WdhQcgPj1EQiJ2/0+yUkkziTG6Lubg==", "integrity": "sha512-5KY70ifCCzorkLuIkDe0Z9YTf9RR2CjBX1iaJG+rgM/cPP+sO+q9YdQ9WdhQcgPj1EQiJ2/0+yUkkziTG6Lubg==",
"devOptional": true,
"license": "MIT" "license": "MIT"
}, },
"node_modules/@types/d3-geo": { "node_modules/@types/d3-geo": {
"version": "3.1.0", "version": "3.1.0",
"resolved": "https://registry.npmjs.org/@types/d3-geo/-/d3-geo-3.1.0.tgz", "resolved": "https://registry.npmjs.org/@types/d3-geo/-/d3-geo-3.1.0.tgz",
"integrity": "sha512-856sckF0oP/diXtS4jNsiQw/UuK5fQG8l/a9VVLeSouf1/PPbBE1i1W852zVwKwYCBkFJJB7nCFTbk6UMEXBOQ==", "integrity": "sha512-856sckF0oP/diXtS4jNsiQw/UuK5fQG8l/a9VVLeSouf1/PPbBE1i1W852zVwKwYCBkFJJB7nCFTbk6UMEXBOQ==",
"devOptional": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@types/geojson": "*" "@types/geojson": "*"
@@ -4602,7 +4572,6 @@
"version": "3.0.1", "version": "3.0.1",
"resolved": "https://registry.npmjs.org/@types/d3-interpolate/-/d3-interpolate-3.0.1.tgz", "resolved": "https://registry.npmjs.org/@types/d3-interpolate/-/d3-interpolate-3.0.1.tgz",
"integrity": "sha512-jx5leotSeac3jr0RePOH1KdR9rISG91QIE4Q2PYTu4OymLTZfA3SrnURSLzKH48HmXVUru50b8nje4E79oQSQw==", "integrity": "sha512-jx5leotSeac3jr0RePOH1KdR9rISG91QIE4Q2PYTu4OymLTZfA3SrnURSLzKH48HmXVUru50b8nje4E79oQSQw==",
"devOptional": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@types/d3-color": "*" "@types/d3-color": "*"
@@ -4612,14 +4581,12 @@
"version": "3.1.1", "version": "3.1.1",
"resolved": "https://registry.npmjs.org/@types/d3-path/-/d3-path-3.1.1.tgz", "resolved": "https://registry.npmjs.org/@types/d3-path/-/d3-path-3.1.1.tgz",
"integrity": "sha512-VMZBYyQvbGmWyWVea0EHs/BwLgxc+MKi1zLDCONksozI4YJMcTt8ZEuIR4Sb1MMTE8MMW49v0IwI5+b7RmfWlg==", "integrity": "sha512-VMZBYyQvbGmWyWVea0EHs/BwLgxc+MKi1zLDCONksozI4YJMcTt8ZEuIR4Sb1MMTE8MMW49v0IwI5+b7RmfWlg==",
"devOptional": true,
"license": "MIT" "license": "MIT"
}, },
"node_modules/@types/d3-scale": { "node_modules/@types/d3-scale": {
"version": "4.0.2", "version": "4.0.2",
"resolved": "https://registry.npmjs.org/@types/d3-scale/-/d3-scale-4.0.2.tgz", "resolved": "https://registry.npmjs.org/@types/d3-scale/-/d3-scale-4.0.2.tgz",
"integrity": "sha512-Yk4htunhPAwN0XGlIwArRomOjdoBFXC3+kCxK2Ubg7I9shQlVSJy/pG/Ht5ASN+gdMIalpk8TJ5xV74jFsetLA==", "integrity": "sha512-Yk4htunhPAwN0XGlIwArRomOjdoBFXC3+kCxK2Ubg7I9shQlVSJy/pG/Ht5ASN+gdMIalpk8TJ5xV74jFsetLA==",
"devOptional": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@types/d3-time": "*" "@types/d3-time": "*"
@@ -4629,7 +4596,6 @@
"version": "3.1.7", "version": "3.1.7",
"resolved": "https://registry.npmjs.org/@types/d3-shape/-/d3-shape-3.1.7.tgz", "resolved": "https://registry.npmjs.org/@types/d3-shape/-/d3-shape-3.1.7.tgz",
"integrity": "sha512-VLvUQ33C+3J+8p+Daf+nYSOsjB4GXp19/S/aGo60m9h1v6XaxjiT82lKVWJCfzhtuZ3yD7i/TPeC/fuKLLOSmg==", "integrity": "sha512-VLvUQ33C+3J+8p+Daf+nYSOsjB4GXp19/S/aGo60m9h1v6XaxjiT82lKVWJCfzhtuZ3yD7i/TPeC/fuKLLOSmg==",
"devOptional": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@types/d3-path": "*" "@types/d3-path": "*"
@@ -4639,14 +4605,12 @@
"version": "3.0.0", "version": "3.0.0",
"resolved": "https://registry.npmjs.org/@types/d3-time/-/d3-time-3.0.0.tgz", "resolved": "https://registry.npmjs.org/@types/d3-time/-/d3-time-3.0.0.tgz",
"integrity": "sha512-sZLCdHvBUcNby1cB6Fd3ZBrABbjz3v1Vm90nysCQ6Vt7vd6e/h9Lt7SiJUoEX0l4Dzc7P5llKyhqSi1ycSf1Hg==", "integrity": "sha512-sZLCdHvBUcNby1cB6Fd3ZBrABbjz3v1Vm90nysCQ6Vt7vd6e/h9Lt7SiJUoEX0l4Dzc7P5llKyhqSi1ycSf1Hg==",
"devOptional": true,
"license": "MIT" "license": "MIT"
}, },
"node_modules/@types/d3-time-format": { "node_modules/@types/d3-time-format": {
"version": "2.1.0", "version": "2.1.0",
"resolved": "https://registry.npmjs.org/@types/d3-time-format/-/d3-time-format-2.1.0.tgz", "resolved": "https://registry.npmjs.org/@types/d3-time-format/-/d3-time-format-2.1.0.tgz",
"integrity": "sha512-/myT3I7EwlukNOX2xVdMzb8FRgNzRMpsZddwst9Ld/VFe6LyJyRp0s32l/V9XoUzk+Gqu56F/oGk6507+8BxrA==", "integrity": "sha512-/myT3I7EwlukNOX2xVdMzb8FRgNzRMpsZddwst9Ld/VFe6LyJyRp0s32l/V9XoUzk+Gqu56F/oGk6507+8BxrA==",
"devOptional": true,
"license": "MIT" "license": "MIT"
}, },
"node_modules/@types/esrecurse": { "node_modules/@types/esrecurse": {
@@ -4692,7 +4656,6 @@
"version": "7946.0.16", "version": "7946.0.16",
"resolved": "https://registry.npmjs.org/@types/geojson/-/geojson-7946.0.16.tgz", "resolved": "https://registry.npmjs.org/@types/geojson/-/geojson-7946.0.16.tgz",
"integrity": "sha512-6C8nqWur3j98U6+lXDfTUWIfgvZU+EumvpHKcYjujKH7woYyLj2sUmff0tRhrqM7BohUw7Pz3ZB1jj2gW9Fvmg==", "integrity": "sha512-6C8nqWur3j98U6+lXDfTUWIfgvZU+EumvpHKcYjujKH7woYyLj2sUmff0tRhrqM7BohUw7Pz3ZB1jj2gW9Fvmg==",
"devOptional": true,
"license": "MIT" "license": "MIT"
}, },
"node_modules/@types/http-errors": { "node_modules/@types/http-errors": {
@@ -4761,7 +4724,6 @@
"version": "4.17.25", "version": "4.17.25",
"resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.17.25.tgz", "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.17.25.tgz",
"integrity": "sha512-+K1NIO8I+F9/wNulfVvu23QYd0Pe9/OCqRrim4NoYIf1VoEDL90Ve4ClzpyqBLc7NpGGWRvYNCKZ1BE/Jpf8dQ==", "integrity": "sha512-+K1NIO8I+F9/wNulfVvu23QYd0Pe9/OCqRrim4NoYIf1VoEDL90Ve4ClzpyqBLc7NpGGWRvYNCKZ1BE/Jpf8dQ==",
"devOptional": true,
"license": "MIT" "license": "MIT"
}, },
"node_modules/@types/methods": { "node_modules/@types/methods": {
@@ -4843,7 +4805,6 @@
"version": "19.2.18", "version": "19.2.18",
"resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.18.tgz", "resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.18.tgz",
"integrity": "sha512-AnzbBERsrLKtk2XSfTbYRLjQPdy116Sty4q+T+Bp3IC4l6jNBvreVPAHmpq9qhXQM7CXZPjLVmGMw9sy+hxQ3w==", "integrity": "sha512-AnzbBERsrLKtk2XSfTbYRLjQPdy116Sty4q+T+Bp3IC4l6jNBvreVPAHmpq9qhXQM7CXZPjLVmGMw9sy+hxQ3w==",
"devOptional": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"csstype": "^3.2.2" "csstype": "^3.2.2"
@@ -5560,7 +5521,6 @@
"version": "4.0.1-alpha.0", "version": "4.0.1-alpha.0",
"resolved": "https://registry.npmjs.org/@visx/curve/-/curve-4.0.1-alpha.0.tgz", "resolved": "https://registry.npmjs.org/@visx/curve/-/curve-4.0.1-alpha.0.tgz",
"integrity": "sha512-jRu61Uz274pV1zyioXmboyrLutYbnKsgjj4njSGCnhdXj5GkZvZbg+ThDb6oOzoAnJOBRLz4rzPlWvNJOzuVMg==", "integrity": "sha512-jRu61Uz274pV1zyioXmboyrLutYbnKsgjj4njSGCnhdXj5GkZvZbg+ThDb6oOzoAnJOBRLz4rzPlWvNJOzuVMg==",
"devOptional": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@visx/vendor": "4.0.0-alpha.0" "@visx/vendor": "4.0.0-alpha.0"
@@ -5570,7 +5530,6 @@
"version": "4.0.1-alpha.0", "version": "4.0.1-alpha.0",
"resolved": "https://registry.npmjs.org/@visx/event/-/event-4.0.1-alpha.0.tgz", "resolved": "https://registry.npmjs.org/@visx/event/-/event-4.0.1-alpha.0.tgz",
"integrity": "sha512-EQqCMSv/s8NbFjo+hz3FKsvvYfP+2QslsFJ/24/O5l/W+7UC6J6aAvO0ujVwrTwdYbuQ+vhxKi1xdPdKR/qj1g==", "integrity": "sha512-EQqCMSv/s8NbFjo+hz3FKsvvYfP+2QslsFJ/24/O5l/W+7UC6J6aAvO0ujVwrTwdYbuQ+vhxKi1xdPdKR/qj1g==",
"devOptional": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@types/react": "*", "@types/react": "*",
@@ -5581,7 +5540,6 @@
"version": "4.0.1-alpha.0", "version": "4.0.1-alpha.0",
"resolved": "https://registry.npmjs.org/@visx/grid/-/grid-4.0.1-alpha.0.tgz", "resolved": "https://registry.npmjs.org/@visx/grid/-/grid-4.0.1-alpha.0.tgz",
"integrity": "sha512-rycutGmTHO+znNdPumheWMglm7YfpffvRwUkVy5zy4WoORIuKTMkDxwnOzHG2xMxU3EE/YCd37xFV5AxA30yeg==", "integrity": "sha512-rycutGmTHO+znNdPumheWMglm7YfpffvRwUkVy5zy4WoORIuKTMkDxwnOzHG2xMxU3EE/YCd37xFV5AxA30yeg==",
"devOptional": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@types/react": "*", "@types/react": "*",
@@ -5600,7 +5558,6 @@
"version": "4.0.1-alpha.0", "version": "4.0.1-alpha.0",
"resolved": "https://registry.npmjs.org/@visx/group/-/group-4.0.1-alpha.0.tgz", "resolved": "https://registry.npmjs.org/@visx/group/-/group-4.0.1-alpha.0.tgz",
"integrity": "sha512-V19l7iQ7jccBv8kao/EByuI6o4xtxzzLV9nqVI1hRvmdzTVsuLpqlwzYCZUXJaTVvUWf8s4D2SQFjGkj/Nw+0w==", "integrity": "sha512-V19l7iQ7jccBv8kao/EByuI6o4xtxzzLV9nqVI1hRvmdzTVsuLpqlwzYCZUXJaTVvUWf8s4D2SQFjGkj/Nw+0w==",
"devOptional": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@types/react": "*", "@types/react": "*",
@@ -5614,14 +5571,12 @@
"version": "4.0.1-alpha.0", "version": "4.0.1-alpha.0",
"resolved": "https://registry.npmjs.org/@visx/point/-/point-4.0.1-alpha.0.tgz", "resolved": "https://registry.npmjs.org/@visx/point/-/point-4.0.1-alpha.0.tgz",
"integrity": "sha512-ijTfr/Nx09f03vIj9nyTr3z4Xth4Y75427UaogJh6dnIRLMEFHQOwNu791sbfiNj0a+ZXuaE32h0vKrFe4/8Qg==", "integrity": "sha512-ijTfr/Nx09f03vIj9nyTr3z4Xth4Y75427UaogJh6dnIRLMEFHQOwNu791sbfiNj0a+ZXuaE32h0vKrFe4/8Qg==",
"devOptional": true,
"license": "MIT" "license": "MIT"
}, },
"node_modules/@visx/responsive": { "node_modules/@visx/responsive": {
"version": "4.0.1-alpha.0", "version": "4.0.1-alpha.0",
"resolved": "https://registry.npmjs.org/@visx/responsive/-/responsive-4.0.1-alpha.0.tgz", "resolved": "https://registry.npmjs.org/@visx/responsive/-/responsive-4.0.1-alpha.0.tgz",
"integrity": "sha512-o+1zGywQZY0+yOx3Iw87wc4bbPJRr/HnIukTwfOz4UVyj9pB1OQNVHB7OORO1+LBHJceWpB31co/ZV9KHncKrA==", "integrity": "sha512-o+1zGywQZY0+yOx3Iw87wc4bbPJRr/HnIukTwfOz4UVyj9pB1OQNVHB7OORO1+LBHJceWpB31co/ZV9KHncKrA==",
"devOptional": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@types/lodash": "^4.17.13", "@types/lodash": "^4.17.13",
@@ -5636,7 +5591,6 @@
"version": "4.0.1-alpha.0", "version": "4.0.1-alpha.0",
"resolved": "https://registry.npmjs.org/@visx/scale/-/scale-4.0.1-alpha.0.tgz", "resolved": "https://registry.npmjs.org/@visx/scale/-/scale-4.0.1-alpha.0.tgz",
"integrity": "sha512-nzjeE87vFSAXGWFiiNfBpNLAf0Q8Qmf6syvKLjqNi4kGZkdhbUll3E/59YsgWXmjM8+llPLWzGsP+JPvo5eq1A==", "integrity": "sha512-nzjeE87vFSAXGWFiiNfBpNLAf0Q8Qmf6syvKLjqNi4kGZkdhbUll3E/59YsgWXmjM8+llPLWzGsP+JPvo5eq1A==",
"devOptional": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@visx/vendor": "4.0.0-alpha.0" "@visx/vendor": "4.0.0-alpha.0"
@@ -5646,7 +5600,6 @@
"version": "4.0.1-alpha.0", "version": "4.0.1-alpha.0",
"resolved": "https://registry.npmjs.org/@visx/shape/-/shape-4.0.1-alpha.0.tgz", "resolved": "https://registry.npmjs.org/@visx/shape/-/shape-4.0.1-alpha.0.tgz",
"integrity": "sha512-62QeiVNmPlterQGwhkEDcbq7M0MqY0lBsK5QKXtM9ZoPZWkuGV3aykA3+Xu20B2FAvyJq4LqJzBc7Sxr+EAdbA==", "integrity": "sha512-62QeiVNmPlterQGwhkEDcbq7M0MqY0lBsK5QKXtM9ZoPZWkuGV3aykA3+Xu20B2FAvyJq4LqJzBc7Sxr+EAdbA==",
"devOptional": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@types/lodash": "^4.17.13", "@types/lodash": "^4.17.13",
@@ -5666,7 +5619,6 @@
"version": "4.0.0-alpha.0", "version": "4.0.0-alpha.0",
"resolved": "https://registry.npmjs.org/@visx/vendor/-/vendor-4.0.0-alpha.0.tgz", "resolved": "https://registry.npmjs.org/@visx/vendor/-/vendor-4.0.0-alpha.0.tgz",
"integrity": "sha512-6I+MuqXBcv9jnlcVowHoHKSdk9gXTWkHLKyqBwRWg7LY6A3Ei8SHfubpqGV5rBUSppxMq2RszPJUS6w+H0YgmQ==", "integrity": "sha512-6I+MuqXBcv9jnlcVowHoHKSdk9gXTWkHLKyqBwRWg7LY6A3Ei8SHfubpqGV5rBUSppxMq2RszPJUS6w+H0YgmQ==",
"devOptional": true,
"license": "MIT and ISC", "license": "MIT and ISC",
"dependencies": { "dependencies": {
"@types/d3-array": "3.0.3", "@types/d3-array": "3.0.3",
@@ -5698,7 +5650,6 @@
"version": "3.2.1", "version": "3.2.1",
"resolved": "https://registry.npmjs.org/d3-array/-/d3-array-3.2.1.tgz", "resolved": "https://registry.npmjs.org/d3-array/-/d3-array-3.2.1.tgz",
"integrity": "sha512-gUY/qeHq/yNqqoCKNq4vtpFLdoCdvyNpWoC/KNjhGbhDuQpAM9sIQQKkXSNpXa9h5KySs/gzm7R88WkUutgwWQ==", "integrity": "sha512-gUY/qeHq/yNqqoCKNq4vtpFLdoCdvyNpWoC/KNjhGbhDuQpAM9sIQQKkXSNpXa9h5KySs/gzm7R88WkUutgwWQ==",
"devOptional": true,
"license": "ISC", "license": "ISC",
"dependencies": { "dependencies": {
"internmap": "1 - 2" "internmap": "1 - 2"
@@ -6193,7 +6144,6 @@
"version": "1.1.2", "version": "1.1.2",
"resolved": "https://registry.npmjs.org/aws-ssl-profiles/-/aws-ssl-profiles-1.1.2.tgz", "resolved": "https://registry.npmjs.org/aws-ssl-profiles/-/aws-ssl-profiles-1.1.2.tgz",
"integrity": "sha512-NZKeq9AfyQvEeNlN0zSYAaWrmBffJh3IELMZfRpJVWgrpEbtEpnjvzqBPf+mxoI287JohRDoa+/nsfqqiZmF6g==", "integrity": "sha512-NZKeq9AfyQvEeNlN0zSYAaWrmBffJh3IELMZfRpJVWgrpEbtEpnjvzqBPf+mxoI287JohRDoa+/nsfqqiZmF6g==",
"devOptional": true,
"license": "MIT", "license": "MIT",
"engines": { "engines": {
"node": ">= 6.0.0" "node": ">= 6.0.0"
@@ -6373,7 +6323,6 @@
"version": "2.10.0", "version": "2.10.0",
"resolved": "https://registry.npmjs.org/better-result/-/better-result-2.10.0.tgz", "resolved": "https://registry.npmjs.org/better-result/-/better-result-2.10.0.tgz",
"integrity": "sha512-oQhh0y1qo2/ZKdAAEvHZAqKKiHOFU5k/bW96fE2ScgQOVkJRiHwB+nOS1SgFsYqRlxMDWvefXi9Q3px7QvgNDw==", "integrity": "sha512-oQhh0y1qo2/ZKdAAEvHZAqKKiHOFU5k/bW96fE2ScgQOVkJRiHwB+nOS1SgFsYqRlxMDWvefXi9Q3px7QvgNDw==",
"devOptional": true,
"license": "MIT" "license": "MIT"
}, },
"node_modules/better-sqlite3": { "node_modules/better-sqlite3": {
@@ -6595,7 +6544,6 @@
"version": "3.3.4", "version": "3.3.4",
"resolved": "https://registry.npmjs.org/c12/-/c12-3.3.4.tgz", "resolved": "https://registry.npmjs.org/c12/-/c12-3.3.4.tgz",
"integrity": "sha512-cM0ApFQSBXuourJejzwv/AuPRvAxordTyParRVcHjjtXirtkzM0uK2L9TTn9s0cXZbG7E55jCivRQzoxYmRAlA==", "integrity": "sha512-cM0ApFQSBXuourJejzwv/AuPRvAxordTyParRVcHjjtXirtkzM0uK2L9TTn9s0cXZbG7E55jCivRQzoxYmRAlA==",
"devOptional": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"chokidar": "^5.0.0", "chokidar": "^5.0.0",
@@ -6624,7 +6572,6 @@
"version": "5.0.0", "version": "5.0.0",
"resolved": "https://registry.npmjs.org/chokidar/-/chokidar-5.0.0.tgz", "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-5.0.0.tgz",
"integrity": "sha512-TQMmc3w+5AxjpL8iIiwebF73dRDF4fBIieAqGn9RGCWaEVwQ6Fb2cGe31Yns0RRIzii5goJ1Y7xbMwo1TxMplw==", "integrity": "sha512-TQMmc3w+5AxjpL8iIiwebF73dRDF4fBIieAqGn9RGCWaEVwQ6Fb2cGe31Yns0RRIzii5goJ1Y7xbMwo1TxMplw==",
"devOptional": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"readdirp": "^5.0.0" "readdirp": "^5.0.0"
@@ -6640,7 +6587,6 @@
"version": "5.1.1", "version": "5.1.1",
"resolved": "https://registry.npmjs.org/readdirp/-/readdirp-5.1.1.tgz", "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-5.1.1.tgz",
"integrity": "sha512-Kko+Y5XQ6fM+Ce3dq3m9YGxnacYZYl9cA1wZjaF3Vbry2L3i1qVg8+CAgNPsXRArPMUMCaOR7oa9Nqntc43JKA==", "integrity": "sha512-Kko+Y5XQ6fM+Ce3dq3m9YGxnacYZYl9cA1wZjaF3Vbry2L3i1qVg8+CAgNPsXRArPMUMCaOR7oa9Nqntc43JKA==",
"devOptional": true,
"license": "MIT", "license": "MIT",
"engines": { "engines": {
"node": ">= 20.19.0" "node": ">= 20.19.0"
@@ -6871,7 +6817,6 @@
"version": "2.5.1", "version": "2.5.1",
"resolved": "https://registry.npmjs.org/classnames/-/classnames-2.5.1.tgz", "resolved": "https://registry.npmjs.org/classnames/-/classnames-2.5.1.tgz",
"integrity": "sha512-saHYOzhIQs6wy2sVxTM6bUDsQO4F50V9RQ22qBpEdCW+I+/Wmke2HOl6lS6dTpdxVhb88/I6+Hs+438c3lfUow==", "integrity": "sha512-saHYOzhIQs6wy2sVxTM6bUDsQO4F50V9RQ22qBpEdCW+I+/Wmke2HOl6lS6dTpdxVhb88/I6+Hs+438c3lfUow==",
"devOptional": true,
"license": "MIT" "license": "MIT"
}, },
"node_modules/cli-cursor": { "node_modules/cli-cursor": {
@@ -7069,7 +7014,6 @@
"version": "0.2.4", "version": "0.2.4",
"resolved": "https://registry.npmjs.org/confbox/-/confbox-0.2.4.tgz", "resolved": "https://registry.npmjs.org/confbox/-/confbox-0.2.4.tgz",
"integrity": "sha512-ysOGlgTFbN2/Y6Cg3Iye8YKulHw+R2fNXHrgSmXISQdMnomY6eNDprVdW9R5xBguEqI954+S6709UyiO7B+6OQ==", "integrity": "sha512-ysOGlgTFbN2/Y6Cg3Iye8YKulHw+R2fNXHrgSmXISQdMnomY6eNDprVdW9R5xBguEqI954+S6709UyiO7B+6OQ==",
"devOptional": true,
"license": "MIT" "license": "MIT"
}, },
"node_modules/content-disposition": { "node_modules/content-disposition": {
@@ -7365,7 +7309,6 @@
"version": "7.0.6", "version": "7.0.6",
"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz",
"integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==",
"devOptional": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"path-key": "^3.1.0", "path-key": "^3.1.0",
@@ -7408,14 +7351,12 @@
"version": "3.2.3", "version": "3.2.3",
"resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz", "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz",
"integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==", "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==",
"devOptional": true,
"license": "MIT" "license": "MIT"
}, },
"node_modules/d3-array": { "node_modules/d3-array": {
"version": "3.2.4", "version": "3.2.4",
"resolved": "https://registry.npmjs.org/d3-array/-/d3-array-3.2.4.tgz", "resolved": "https://registry.npmjs.org/d3-array/-/d3-array-3.2.4.tgz",
"integrity": "sha512-tdQAmyA18i4J7wprpYq8ClcxZy3SC31QMeByyCFyRt7BVHdREQZ5lpzoe5mFEYZUWe+oq8HBvk9JjpibyEV4Jg==", "integrity": "sha512-tdQAmyA18i4J7wprpYq8ClcxZy3SC31QMeByyCFyRt7BVHdREQZ5lpzoe5mFEYZUWe+oq8HBvk9JjpibyEV4Jg==",
"devOptional": true,
"license": "ISC", "license": "ISC",
"dependencies": { "dependencies": {
"internmap": "1 - 2" "internmap": "1 - 2"
@@ -7428,7 +7369,6 @@
"version": "3.1.0", "version": "3.1.0",
"resolved": "https://registry.npmjs.org/d3-color/-/d3-color-3.1.0.tgz", "resolved": "https://registry.npmjs.org/d3-color/-/d3-color-3.1.0.tgz",
"integrity": "sha512-zg/chbXyeBtMQ1LbD/WSoW2DpC3I0mpmPdW+ynRTj/x2DAWYrIY7qeZIHidozwV24m4iavr15lNwIwLxRmOxhA==", "integrity": "sha512-zg/chbXyeBtMQ1LbD/WSoW2DpC3I0mpmPdW+ynRTj/x2DAWYrIY7qeZIHidozwV24m4iavr15lNwIwLxRmOxhA==",
"devOptional": true,
"license": "ISC", "license": "ISC",
"engines": { "engines": {
"node": ">=12" "node": ">=12"
@@ -7438,7 +7378,6 @@
"version": "6.0.2", "version": "6.0.2",
"resolved": "https://registry.npmjs.org/d3-delaunay/-/d3-delaunay-6.0.2.tgz", "resolved": "https://registry.npmjs.org/d3-delaunay/-/d3-delaunay-6.0.2.tgz",
"integrity": "sha512-IMLNldruDQScrcfT+MWnazhHbDJhcRJyOEBAJfwQnHle1RPh6WDuLvxNArUju2VSMSUuKlY5BGHRJ2cYyoFLQQ==", "integrity": "sha512-IMLNldruDQScrcfT+MWnazhHbDJhcRJyOEBAJfwQnHle1RPh6WDuLvxNArUju2VSMSUuKlY5BGHRJ2cYyoFLQQ==",
"devOptional": true,
"license": "ISC", "license": "ISC",
"dependencies": { "dependencies": {
"delaunator": "5" "delaunator": "5"
@@ -7451,7 +7390,6 @@
"version": "3.1.0", "version": "3.1.0",
"resolved": "https://registry.npmjs.org/d3-format/-/d3-format-3.1.0.tgz", "resolved": "https://registry.npmjs.org/d3-format/-/d3-format-3.1.0.tgz",
"integrity": "sha512-YyUI6AEuY/Wpt8KWLgZHsIU86atmikuoOmCfommt0LYHiQSPjvX2AcFc38PX0CBpr2RCyZhjex+NS/LPOv6YqA==", "integrity": "sha512-YyUI6AEuY/Wpt8KWLgZHsIU86atmikuoOmCfommt0LYHiQSPjvX2AcFc38PX0CBpr2RCyZhjex+NS/LPOv6YqA==",
"devOptional": true,
"license": "ISC", "license": "ISC",
"engines": { "engines": {
"node": ">=12" "node": ">=12"
@@ -7461,7 +7399,6 @@
"version": "3.1.0", "version": "3.1.0",
"resolved": "https://registry.npmjs.org/d3-geo/-/d3-geo-3.1.0.tgz", "resolved": "https://registry.npmjs.org/d3-geo/-/d3-geo-3.1.0.tgz",
"integrity": "sha512-JEo5HxXDdDYXCaWdwLRt79y7giK8SbhZJbFWXqbRTolCHFI5jRqteLzCsq51NKbUoX0PjBVSohxrx+NoOUujYA==", "integrity": "sha512-JEo5HxXDdDYXCaWdwLRt79y7giK8SbhZJbFWXqbRTolCHFI5jRqteLzCsq51NKbUoX0PjBVSohxrx+NoOUujYA==",
"devOptional": true,
"license": "ISC", "license": "ISC",
"dependencies": { "dependencies": {
"d3-array": "2.5.0 - 3" "d3-array": "2.5.0 - 3"
@@ -7474,7 +7411,6 @@
"version": "3.0.1", "version": "3.0.1",
"resolved": "https://registry.npmjs.org/d3-interpolate/-/d3-interpolate-3.0.1.tgz", "resolved": "https://registry.npmjs.org/d3-interpolate/-/d3-interpolate-3.0.1.tgz",
"integrity": "sha512-3bYs1rOD33uo8aqJfKP3JWPAibgw8Zm2+L9vBKEHJ2Rg+viTR7o5Mmv5mZcieN+FRYaAOWX5SJATX6k1PWz72g==", "integrity": "sha512-3bYs1rOD33uo8aqJfKP3JWPAibgw8Zm2+L9vBKEHJ2Rg+viTR7o5Mmv5mZcieN+FRYaAOWX5SJATX6k1PWz72g==",
"devOptional": true,
"license": "ISC", "license": "ISC",
"dependencies": { "dependencies": {
"d3-color": "1 - 3" "d3-color": "1 - 3"
@@ -7487,7 +7423,6 @@
"version": "3.1.0", "version": "3.1.0",
"resolved": "https://registry.npmjs.org/d3-path/-/d3-path-3.1.0.tgz", "resolved": "https://registry.npmjs.org/d3-path/-/d3-path-3.1.0.tgz",
"integrity": "sha512-p3KP5HCf/bvjBSSKuXid6Zqijx7wIfNW+J/maPs+iwR35at5JCbLUT0LzF1cnjbCHWhqzQTIN2Jpe8pRebIEFQ==", "integrity": "sha512-p3KP5HCf/bvjBSSKuXid6Zqijx7wIfNW+J/maPs+iwR35at5JCbLUT0LzF1cnjbCHWhqzQTIN2Jpe8pRebIEFQ==",
"devOptional": true,
"license": "ISC", "license": "ISC",
"engines": { "engines": {
"node": ">=12" "node": ">=12"
@@ -7497,7 +7432,6 @@
"version": "4.0.2", "version": "4.0.2",
"resolved": "https://registry.npmjs.org/d3-scale/-/d3-scale-4.0.2.tgz", "resolved": "https://registry.npmjs.org/d3-scale/-/d3-scale-4.0.2.tgz",
"integrity": "sha512-GZW464g1SH7ag3Y7hXjf8RoUuAFIqklOAq3MRl4OaWabTFJY9PN/E1YklhXLh+OQ3fM9yS2nOkCoS+WLZ6kvxQ==", "integrity": "sha512-GZW464g1SH7ag3Y7hXjf8RoUuAFIqklOAq3MRl4OaWabTFJY9PN/E1YklhXLh+OQ3fM9yS2nOkCoS+WLZ6kvxQ==",
"devOptional": true,
"license": "ISC", "license": "ISC",
"dependencies": { "dependencies": {
"d3-array": "2.10.0 - 3", "d3-array": "2.10.0 - 3",
@@ -7514,7 +7448,6 @@
"version": "3.2.0", "version": "3.2.0",
"resolved": "https://registry.npmjs.org/d3-shape/-/d3-shape-3.2.0.tgz", "resolved": "https://registry.npmjs.org/d3-shape/-/d3-shape-3.2.0.tgz",
"integrity": "sha512-SaLBuwGm3MOViRq2ABk3eLoxwZELpH6zhl3FbAoJ7Vm1gofKx6El1Ib5z23NUEhF9AsGl7y+dzLe5Cw2AArGTA==", "integrity": "sha512-SaLBuwGm3MOViRq2ABk3eLoxwZELpH6zhl3FbAoJ7Vm1gofKx6El1Ib5z23NUEhF9AsGl7y+dzLe5Cw2AArGTA==",
"devOptional": true,
"license": "ISC", "license": "ISC",
"dependencies": { "dependencies": {
"d3-path": "^3.1.0" "d3-path": "^3.1.0"
@@ -7527,7 +7460,6 @@
"version": "3.1.0", "version": "3.1.0",
"resolved": "https://registry.npmjs.org/d3-time/-/d3-time-3.1.0.tgz", "resolved": "https://registry.npmjs.org/d3-time/-/d3-time-3.1.0.tgz",
"integrity": "sha512-VqKjzBLejbSMT4IgbmVgDjpkYrNWUYJnbCGo874u7MMKIWsILRX+OpX/gTk8MqjpT1A/c6HY2dCA77ZN0lkQ2Q==", "integrity": "sha512-VqKjzBLejbSMT4IgbmVgDjpkYrNWUYJnbCGo874u7MMKIWsILRX+OpX/gTk8MqjpT1A/c6HY2dCA77ZN0lkQ2Q==",
"devOptional": true,
"license": "ISC", "license": "ISC",
"dependencies": { "dependencies": {
"d3-array": "2 - 3" "d3-array": "2 - 3"
@@ -7540,7 +7472,6 @@
"version": "4.1.0", "version": "4.1.0",
"resolved": "https://registry.npmjs.org/d3-time-format/-/d3-time-format-4.1.0.tgz", "resolved": "https://registry.npmjs.org/d3-time-format/-/d3-time-format-4.1.0.tgz",
"integrity": "sha512-dJxPBlzC7NugB2PDLwo9Q8JiTR3M3e4/XANkreKSUxF8vvXKqm1Yfq4Q5dl8budlunRVlUUaDUgFt7eA8D6NLg==", "integrity": "sha512-dJxPBlzC7NugB2PDLwo9Q8JiTR3M3e4/XANkreKSUxF8vvXKqm1Yfq4Q5dl8budlunRVlUUaDUgFt7eA8D6NLg==",
"devOptional": true,
"license": "ISC", "license": "ISC",
"dependencies": { "dependencies": {
"d3-time": "1 - 3" "d3-time": "1 - 3"
@@ -7636,7 +7567,6 @@
"version": "8.0.2", "version": "8.0.2",
"resolved": "https://registry.npmjs.org/deepmerge-ts/-/deepmerge-ts-8.0.2.tgz", "resolved": "https://registry.npmjs.org/deepmerge-ts/-/deepmerge-ts-8.0.2.tgz",
"integrity": "sha512-uqbvqLUMrc6p0MO+WBRtTxY55hmyh94WRwI5a++PZe54X+bfVh59FSN7uWCBCW1CCVjzjnrwzfI8zidE2obMMw==", "integrity": "sha512-uqbvqLUMrc6p0MO+WBRtTxY55hmyh94WRwI5a++PZe54X+bfVh59FSN7uWCBCW1CCVjzjnrwzfI8zidE2obMMw==",
"devOptional": true,
"funding": [ "funding": [
{ {
"type": "ko-fi", "type": "ko-fi",
@@ -7699,7 +7629,6 @@
"version": "6.1.7", "version": "6.1.7",
"resolved": "https://registry.npmjs.org/defu/-/defu-6.1.7.tgz", "resolved": "https://registry.npmjs.org/defu/-/defu-6.1.7.tgz",
"integrity": "sha512-7z22QmUWiQ/2d0KkdYmANbRUVABpZ9SNYyH5vx6PZ+nE5bcC0l7uFvEfHlyld/HcGBFTL536ClDt3DEcSlEJAQ==", "integrity": "sha512-7z22QmUWiQ/2d0KkdYmANbRUVABpZ9SNYyH5vx6PZ+nE5bcC0l7uFvEfHlyld/HcGBFTL536ClDt3DEcSlEJAQ==",
"devOptional": true,
"license": "MIT" "license": "MIT"
}, },
"node_modules/degenerator": { "node_modules/degenerator": {
@@ -7724,7 +7653,6 @@
"version": "5.1.0", "version": "5.1.0",
"resolved": "https://registry.npmjs.org/delaunator/-/delaunator-5.1.0.tgz", "resolved": "https://registry.npmjs.org/delaunator/-/delaunator-5.1.0.tgz",
"integrity": "sha512-AGrQ4QSgssa1NGmWmLPqN5NY2KajF5MqxetNEO+o0n3ZwZZeTmt7bBnvzHWrmkZFxGgr4HdyFgelzgi06otLuQ==", "integrity": "sha512-AGrQ4QSgssa1NGmWmLPqN5NY2KajF5MqxetNEO+o0n3ZwZZeTmt7bBnvzHWrmkZFxGgr4HdyFgelzgi06otLuQ==",
"devOptional": true,
"license": "ISC", "license": "ISC",
"dependencies": { "dependencies": {
"robust-predicates": "^3.0.2" "robust-predicates": "^3.0.2"
@@ -7753,7 +7681,6 @@
"version": "2.0.5", "version": "2.0.5",
"resolved": "https://registry.npmjs.org/destr/-/destr-2.0.5.tgz", "resolved": "https://registry.npmjs.org/destr/-/destr-2.0.5.tgz",
"integrity": "sha512-ugFTXCtDZunbzasqBxrK93Ik/DRYsO6S/fedkWEMKqt04xZ4csmnmwGDBAb07QWNaGMAmnTIemsYZCksjATwsA==", "integrity": "sha512-ugFTXCtDZunbzasqBxrK93Ik/DRYsO6S/fedkWEMKqt04xZ4csmnmwGDBAb07QWNaGMAmnTIemsYZCksjATwsA==",
"devOptional": true,
"license": "MIT" "license": "MIT"
}, },
"node_modules/detect-libc": { "node_modules/detect-libc": {
@@ -7918,7 +7845,6 @@
"version": "3.20.0", "version": "3.20.0",
"resolved": "https://registry.npmjs.org/effect/-/effect-3.20.0.tgz", "resolved": "https://registry.npmjs.org/effect/-/effect-3.20.0.tgz",
"integrity": "sha512-qMLfDJscrNG8p/aw+IkT9W7fgj50Z4wG5bLBy0Txsxz8iUHjDIkOgO3SV0WZfnQbNG2VJYb0b+rDLMrhM4+Krw==", "integrity": "sha512-qMLfDJscrNG8p/aw+IkT9W7fgj50Z4wG5bLBy0Txsxz8iUHjDIkOgO3SV0WZfnQbNG2VJYb0b+rDLMrhM4+Krw==",
"devOptional": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@standard-schema/spec": "^1.0.0", "@standard-schema/spec": "^1.0.0",
@@ -7936,7 +7862,6 @@
"version": "0.11.1", "version": "0.11.1",
"resolved": "https://registry.npmjs.org/elkjs/-/elkjs-0.11.1.tgz", "resolved": "https://registry.npmjs.org/elkjs/-/elkjs-0.11.1.tgz",
"integrity": "sha512-zxxR9k+rx5ktMwT/FwyLdPCrq7xN6e4VGGHH8hA01vVYKjTFik7nHOxBnAYtrgYUB1RpAiLvA1/U2YraWxyKKg==", "integrity": "sha512-zxxR9k+rx5ktMwT/FwyLdPCrq7xN6e4VGGHH8hA01vVYKjTFik7nHOxBnAYtrgYUB1RpAiLvA1/U2YraWxyKKg==",
"devOptional": true,
"license": "EPL-2.0" "license": "EPL-2.0"
}, },
"node_modules/emittery": { "node_modules/emittery": {
@@ -7969,7 +7894,6 @@
"version": "2.0.0", "version": "2.0.0",
"resolved": "https://registry.npmjs.org/empathic/-/empathic-2.0.0.tgz", "resolved": "https://registry.npmjs.org/empathic/-/empathic-2.0.0.tgz",
"integrity": "sha512-i6UzDscO/XfAcNYD75CfICkmfLedpyPDdozrLMmQc5ORaQcdMoc21OnlEylMIqI7U8eniKrPMxxtj8k0vhmJhA==", "integrity": "sha512-i6UzDscO/XfAcNYD75CfICkmfLedpyPDdozrLMmQc5ORaQcdMoc21OnlEylMIqI7U8eniKrPMxxtj8k0vhmJhA==",
"devOptional": true,
"license": "MIT", "license": "MIT",
"engines": { "engines": {
"node": ">=14" "node": ">=14"
@@ -8049,7 +7973,6 @@
"version": "3.0.0", "version": "3.0.0",
"resolved": "https://registry.npmjs.org/env-paths/-/env-paths-3.0.0.tgz", "resolved": "https://registry.npmjs.org/env-paths/-/env-paths-3.0.0.tgz",
"integrity": "sha512-dtJUTepzMW3Lm/NPxRf3wP4642UWhjL2sQxc+ym2YMj1m/H2zDNQOlezafzkHwn6sMstjHTwG6iQQsctDW/b1A==", "integrity": "sha512-dtJUTepzMW3Lm/NPxRf3wP4642UWhjL2sQxc+ym2YMj1m/H2zDNQOlezafzkHwn6sMstjHTwG6iQQsctDW/b1A==",
"devOptional": true,
"license": "MIT", "license": "MIT",
"engines": { "engines": {
"node": "^12.20.0 || ^14.13.1 || >=16.0.0" "node": "^12.20.0 || ^14.13.1 || >=16.0.0"
@@ -8628,14 +8551,12 @@
"version": "1.1.1", "version": "1.1.1",
"resolved": "https://registry.npmjs.org/exsolve/-/exsolve-1.1.1.tgz", "resolved": "https://registry.npmjs.org/exsolve/-/exsolve-1.1.1.tgz",
"integrity": "sha512-9U/jZUgjnSGyntRr6y5Muu1MJcwFl6kPu7k8qLF0IMNfLqvw0NZ4nnVDq0RVoZ0RvCyumib4Ez3KYrVfilrw+g==", "integrity": "sha512-9U/jZUgjnSGyntRr6y5Muu1MJcwFl6kPu7k8qLF0IMNfLqvw0NZ4nnVDq0RVoZ0RvCyumib4Ez3KYrVfilrw+g==",
"devOptional": true,
"license": "MIT" "license": "MIT"
}, },
"node_modules/fast-check": { "node_modules/fast-check": {
"version": "3.23.2", "version": "3.23.2",
"resolved": "https://registry.npmjs.org/fast-check/-/fast-check-3.23.2.tgz", "resolved": "https://registry.npmjs.org/fast-check/-/fast-check-3.23.2.tgz",
"integrity": "sha512-h5+1OzzfCC3Ef7VbtKdcv7zsstUQwUDlYpUTvjeUsJAssPgLn7QzbboPtL5ro04Mq0rPOsMzl7q5hIbRs2wD1A==", "integrity": "sha512-h5+1OzzfCC3Ef7VbtKdcv7zsstUQwUDlYpUTvjeUsJAssPgLn7QzbboPtL5ro04Mq0rPOsMzl7q5hIbRs2wD1A==",
"devOptional": true,
"funding": [ "funding": [
{ {
"type": "individual", "type": "individual",
@@ -8658,7 +8579,6 @@
"version": "6.1.0", "version": "6.1.0",
"resolved": "https://registry.npmjs.org/pure-rand/-/pure-rand-6.1.0.tgz", "resolved": "https://registry.npmjs.org/pure-rand/-/pure-rand-6.1.0.tgz",
"integrity": "sha512-bVWawvoZoBYpp6yIoQtQXHZjmz35RSVHnUOTefl8Vcjr8snTPY1wnpSPMWekcFwbxI6gtmT7rSYPFvz71ldiOA==", "integrity": "sha512-bVWawvoZoBYpp6yIoQtQXHZjmz35RSVHnUOTefl8Vcjr8snTPY1wnpSPMWekcFwbxI6gtmT7rSYPFvz71ldiOA==",
"devOptional": true,
"funding": [ "funding": [
{ {
"type": "individual", "type": "individual",
@@ -8675,14 +8595,12 @@
"version": "1.0.1", "version": "1.0.1",
"resolved": "https://registry.npmjs.org/fast-decode-uri-component/-/fast-decode-uri-component-1.0.1.tgz", "resolved": "https://registry.npmjs.org/fast-decode-uri-component/-/fast-decode-uri-component-1.0.1.tgz",
"integrity": "sha512-WKgKWg5eUxvRZGwW8FvfbaH7AXSh2cL+3j5fMGzUMCxWBJ3dV3a7Wz8y2f/uQ0e3B6WmodD3oS54jTQ9HVTIIg==", "integrity": "sha512-WKgKWg5eUxvRZGwW8FvfbaH7AXSh2cL+3j5fMGzUMCxWBJ3dV3a7Wz8y2f/uQ0e3B6WmodD3oS54jTQ9HVTIIg==",
"devOptional": true,
"license": "MIT" "license": "MIT"
}, },
"node_modules/fast-deep-equal": { "node_modules/fast-deep-equal": {
"version": "3.1.3", "version": "3.1.3",
"resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
"integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==",
"devOptional": true,
"license": "MIT" "license": "MIT"
}, },
"node_modules/fast-diff": { "node_modules/fast-diff": {
@@ -8710,7 +8628,6 @@
"version": "1.1.2", "version": "1.1.2",
"resolved": "https://registry.npmjs.org/fast-querystring/-/fast-querystring-1.1.2.tgz", "resolved": "https://registry.npmjs.org/fast-querystring/-/fast-querystring-1.1.2.tgz",
"integrity": "sha512-g6KuKWmFXc0fID8WWH0jit4g0AGBoJhCkJMb1RmbsSEUNvQ+ZC8D6CUZ+GtF8nMzSPXnhiePyyqqipzNNEnHjg==", "integrity": "sha512-g6KuKWmFXc0fID8WWH0jit4g0AGBoJhCkJMb1RmbsSEUNvQ+ZC8D6CUZ+GtF8nMzSPXnhiePyyqqipzNNEnHjg==",
"devOptional": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"fast-decode-uri-component": "^1.0.1" "fast-decode-uri-component": "^1.0.1"
@@ -8743,7 +8660,6 @@
"version": "3.1.7", "version": "3.1.7",
"resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.7.tgz", "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.7.tgz",
"integrity": "sha512-dOvZVzjdZdz7phd9v6jCbwxrBW3fK6n8Rc0CtdmM4bumzMnxywBYhuph6J819RRw/ku+rLbelwfMunktuzVVHg==", "integrity": "sha512-dOvZVzjdZdz7phd9v6jCbwxrBW3fK6n8Rc0CtdmM4bumzMnxywBYhuph6J819RRw/ku+rLbelwfMunktuzVVHg==",
"devOptional": true,
"funding": [ "funding": [
{ {
"type": "github", "type": "github",
@@ -8863,7 +8779,6 @@
"version": "9.7.0", "version": "9.7.0",
"resolved": "https://registry.npmjs.org/find-my-way/-/find-my-way-9.7.0.tgz", "resolved": "https://registry.npmjs.org/find-my-way/-/find-my-way-9.7.0.tgz",
"integrity": "sha512-f2JHn75x2JlwUwLenZypgczR7YWMb/uO9BvUXtus+JMgkbIkLADd38cI4EiV+OQqrGo1Zlq6V8wnqMJ8e62wUQ==", "integrity": "sha512-f2JHn75x2JlwUwLenZypgczR7YWMb/uO9BvUXtus+JMgkbIkLADd38cI4EiV+OQqrGo1Zlq6V8wnqMJ8e62wUQ==",
"devOptional": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"fast-deep-equal": "^3.1.3", "fast-deep-equal": "^3.1.3",
@@ -8914,7 +8829,6 @@
"version": "3.3.1", "version": "3.3.1",
"resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.1.tgz", "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.1.tgz",
"integrity": "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==", "integrity": "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==",
"devOptional": true,
"license": "ISC", "license": "ISC",
"dependencies": { "dependencies": {
"cross-spawn": "^7.0.6", "cross-spawn": "^7.0.6",
@@ -9022,7 +8936,6 @@
"version": "2.3.1", "version": "2.3.1",
"resolved": "https://registry.npmjs.org/generate-function/-/generate-function-2.3.1.tgz", "resolved": "https://registry.npmjs.org/generate-function/-/generate-function-2.3.1.tgz",
"integrity": "sha512-eeB5GfMNeevm/GRYq20ShmsaGcmI81kIX2K9XQx5miC8KdHaC6Jm0qQ8ZNeGOi7wYB8OsdxKs+Y2oVuTFuVwKQ==", "integrity": "sha512-eeB5GfMNeevm/GRYq20ShmsaGcmI81kIX2K9XQx5miC8KdHaC6Jm0qQ8ZNeGOi7wYB8OsdxKs+Y2oVuTFuVwKQ==",
"devOptional": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"is-property": "^1.0.2" "is-property": "^1.0.2"
@@ -9098,7 +9011,6 @@
"version": "3.2.0", "version": "3.2.0",
"resolved": "https://registry.npmjs.org/get-port-please/-/get-port-please-3.2.0.tgz", "resolved": "https://registry.npmjs.org/get-port-please/-/get-port-please-3.2.0.tgz",
"integrity": "sha512-I9QVvBw5U/hw3RmWpYKRumUeaDgxTPd401x364rLmWBJcOQ753eov1eTgzDqRG9bqFIfDc7gfzcQEWrUri3o1A==", "integrity": "sha512-I9QVvBw5U/hw3RmWpYKRumUeaDgxTPd401x364rLmWBJcOQ753eov1eTgzDqRG9bqFIfDc7gfzcQEWrUri3o1A==",
"devOptional": true,
"license": "MIT" "license": "MIT"
}, },
"node_modules/get-proto": { "node_modules/get-proto": {
@@ -9146,7 +9058,6 @@
"version": "3.3.1", "version": "3.3.1",
"resolved": "https://registry.npmjs.org/giget/-/giget-3.3.1.tgz", "resolved": "https://registry.npmjs.org/giget/-/giget-3.3.1.tgz",
"integrity": "sha512-r+mvuDjrjMpsdw46Kmeydb8bdHm7wOKw8wNBtTndkjbPjgAp5oUJUxRE76wZFknxIPokfWvep2qSXK37aXE6zg==", "integrity": "sha512-r+mvuDjrjMpsdw46Kmeydb8bdHm7wOKw8wNBtTndkjbPjgAp5oUJUxRE76wZFknxIPokfWvep2qSXK37aXE6zg==",
"devOptional": true,
"license": "MIT", "license": "MIT",
"bin": { "bin": {
"giget": "dist/cli.mjs" "giget": "dist/cli.mjs"
@@ -9304,21 +9215,18 @@
"version": "4.2.11", "version": "4.2.11",
"resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz",
"integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==",
"devOptional": true,
"license": "ISC" "license": "ISC"
}, },
"node_modules/grammex": { "node_modules/grammex": {
"version": "3.1.13", "version": "3.1.13",
"resolved": "https://registry.npmjs.org/grammex/-/grammex-3.1.13.tgz", "resolved": "https://registry.npmjs.org/grammex/-/grammex-3.1.13.tgz",
"integrity": "sha512-LnPnhOBLEJEVKS8WFDVaA397L9Kq55Q9oSITJiVLHVdhAclfUkWzQv74KhvZHKL2Q09Pb1XdsrOsZ4LfTFFTEg==", "integrity": "sha512-LnPnhOBLEJEVKS8WFDVaA397L9Kq55Q9oSITJiVLHVdhAclfUkWzQv74KhvZHKL2Q09Pb1XdsrOsZ4LfTFFTEg==",
"devOptional": true,
"license": "MIT" "license": "MIT"
}, },
"node_modules/graphmatch": { "node_modules/graphmatch": {
"version": "1.1.1", "version": "1.1.1",
"resolved": "https://registry.npmjs.org/graphmatch/-/graphmatch-1.1.1.tgz", "resolved": "https://registry.npmjs.org/graphmatch/-/graphmatch-1.1.1.tgz",
"integrity": "sha512-5ykVn/EXM1hF0XCaWh05VbYvEiOL2lY1kBxZtaYsyvjp7cmWOU1XsAdfQBwClraEofXDT197lFbXOEVMHpvQOg==", "integrity": "sha512-5ykVn/EXM1hF0XCaWh05VbYvEiOL2lY1kBxZtaYsyvjp7cmWOU1XsAdfQBwClraEofXDT197lFbXOEVMHpvQOg==",
"devOptional": true,
"license": "MIT" "license": "MIT"
}, },
"node_modules/handlebars": { "node_modules/handlebars": {
@@ -9630,7 +9538,6 @@
"version": "2.0.3", "version": "2.0.3",
"resolved": "https://registry.npmjs.org/internmap/-/internmap-2.0.3.tgz", "resolved": "https://registry.npmjs.org/internmap/-/internmap-2.0.3.tgz",
"integrity": "sha512-5Hh7Y1wQbvY5ooGgPbDaL5iYLAPzMTUrjMulskHLH6wnv/A+1q5rgEaiuqEjB+oxGXIVZs1FF+R/KPN3ZSQYYg==", "integrity": "sha512-5Hh7Y1wQbvY5ooGgPbDaL5iYLAPzMTUrjMulskHLH6wnv/A+1q5rgEaiuqEjB+oxGXIVZs1FF+R/KPN3ZSQYYg==",
"devOptional": true,
"license": "ISC", "license": "ISC",
"engines": { "engines": {
"node": ">=12" "node": ">=12"
@@ -9788,7 +9695,6 @@
"version": "1.0.2", "version": "1.0.2",
"resolved": "https://registry.npmjs.org/is-property/-/is-property-1.0.2.tgz", "resolved": "https://registry.npmjs.org/is-property/-/is-property-1.0.2.tgz",
"integrity": "sha512-Ks/IoX00TtClbGQr4TWXemAnktAQvYB7HzcCxDGqEZU6oCmb2INHuOoKxbtR+HFkmYWBKv/dOZtGRiAjDhj92g==", "integrity": "sha512-Ks/IoX00TtClbGQr4TWXemAnktAQvYB7HzcCxDGqEZU6oCmb2INHuOoKxbtR+HFkmYWBKv/dOZtGRiAjDhj92g==",
"devOptional": true,
"license": "MIT" "license": "MIT"
}, },
"node_modules/is-ssh": { "node_modules/is-ssh": {
@@ -9847,7 +9753,6 @@
"version": "2.0.0", "version": "2.0.0",
"resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz",
"integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==",
"devOptional": true,
"license": "ISC" "license": "ISC"
}, },
"node_modules/issue-parser": { "node_modules/issue-parser": {
@@ -10528,7 +10433,6 @@
"version": "2.7.0", "version": "2.7.0",
"resolved": "https://registry.npmjs.org/jiti/-/jiti-2.7.0.tgz", "resolved": "https://registry.npmjs.org/jiti/-/jiti-2.7.0.tgz",
"integrity": "sha512-AC/7JofJvZGrrneWNaEnJeOLUx+JlGt7tNa0wZiRPT4MY1wmfKjt2+6O2p2uz2+skll8OZZmJMNqeke7kKbNgQ==", "integrity": "sha512-AC/7JofJvZGrrneWNaEnJeOLUx+JlGt7tNa0wZiRPT4MY1wmfKjt2+6O2p2uz2+skll8OZZmJMNqeke7kKbNgQ==",
"devOptional": true,
"license": "MIT", "license": "MIT",
"bin": { "bin": {
"jiti": "lib/jiti-cli.mjs" "jiti": "lib/jiti-cli.mjs"
@@ -10754,7 +10658,6 @@
"version": "4.18.1", "version": "4.18.1",
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.18.1.tgz", "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.18.1.tgz",
"integrity": "sha512-dMInicTPVE8d1e5otfwmmjlxkZoUpiVLwyeTdUsi/Caj/gfzzblBcCE5sRHV/AsjuCmxWrte2TNGSYuCeCq+0Q==", "integrity": "sha512-dMInicTPVE8d1e5otfwmmjlxkZoUpiVLwyeTdUsi/Caj/gfzzblBcCE5sRHV/AsjuCmxWrte2TNGSYuCeCq+0Q==",
"devOptional": true,
"license": "MIT" "license": "MIT"
}, },
"node_modules/lodash.camelcase": { "node_modules/lodash.camelcase": {
@@ -10876,7 +10779,6 @@
"version": "1.1.4", "version": "1.1.4",
"resolved": "https://registry.npmjs.org/lru.min/-/lru.min-1.1.4.tgz", "resolved": "https://registry.npmjs.org/lru.min/-/lru.min-1.1.4.tgz",
"integrity": "sha512-DqC6n3QQ77zdFpCMASA1a3Jlb64Hv2N2DciFGkO/4L9+q/IpIAuRlKOvCXabtRW6cQf8usbmM6BE/TOPysCdIA==", "integrity": "sha512-DqC6n3QQ77zdFpCMASA1a3Jlb64Hv2N2DciFGkO/4L9+q/IpIAuRlKOvCXabtRW6cQf8usbmM6BE/TOPysCdIA==",
"devOptional": true,
"license": "MIT", "license": "MIT",
"engines": { "engines": {
"bun": ">=1.0.0", "bun": ">=1.0.0",
@@ -11353,7 +11255,6 @@
"version": "3.24.4", "version": "3.24.4",
"resolved": "https://registry.npmjs.org/mysql2/-/mysql2-3.24.4.tgz", "resolved": "https://registry.npmjs.org/mysql2/-/mysql2-3.24.4.tgz",
"integrity": "sha512-A2olluVlj0mvgyIRRISMEzXc51m+21mRtcMVjJyIpt2GG98+XrC9m9HzsqcMsX2LcnfccJvY5NB22g8fENBnOA==", "integrity": "sha512-A2olluVlj0mvgyIRRISMEzXc51m+21mRtcMVjJyIpt2GG98+XrC9m9HzsqcMsX2LcnfccJvY5NB22g8fENBnOA==",
"devOptional": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"aws-ssl-profiles": "^1.1.2", "aws-ssl-profiles": "^1.1.2",
@@ -11375,7 +11276,6 @@
"version": "1.1.6", "version": "1.1.6",
"resolved": "https://registry.npmjs.org/named-placeholders/-/named-placeholders-1.1.6.tgz", "resolved": "https://registry.npmjs.org/named-placeholders/-/named-placeholders-1.1.6.tgz",
"integrity": "sha512-Tz09sEL2EEuv5fFowm419c1+a/jSMiBjI9gHxVLrVdbUkkNUUfjsVYs9pVZu5oCon/kmRh9TfLEObFtkVxmY0w==", "integrity": "sha512-Tz09sEL2EEuv5fFowm419c1+a/jSMiBjI9gHxVLrVdbUkkNUUfjsVYs9pVZu5oCon/kmRh9TfLEObFtkVxmY0w==",
"devOptional": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"lru.min": "^1.1.0" "lru.min": "^1.1.0"
@@ -11609,7 +11509,6 @@
"version": "2.0.12", "version": "2.0.12",
"resolved": "https://registry.npmjs.org/ohash/-/ohash-2.0.12.tgz", "resolved": "https://registry.npmjs.org/ohash/-/ohash-2.0.12.tgz",
"integrity": "sha512-65S/5gk9YSsaRjcyf7Nfa6h/d3E8/1gslpXfI4W7Dxn/oap8IKRuNT5VXkLQ1YFKIEg4apRY4Pj6aiwFzrDdmw==", "integrity": "sha512-65S/5gk9YSsaRjcyf7Nfa6h/d3E8/1gslpXfI4W7Dxn/oap8IKRuNT5VXkLQ1YFKIEg4apRY4Pj6aiwFzrDdmw==",
"devOptional": true,
"license": "MIT" "license": "MIT"
}, },
"node_modules/on-exit-leak-free": { "node_modules/on-exit-leak-free": {
@@ -12046,7 +11945,6 @@
"version": "3.1.1", "version": "3.1.1",
"resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz",
"integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==",
"devOptional": true,
"license": "MIT", "license": "MIT",
"engines": { "engines": {
"node": ">=8" "node": ">=8"
@@ -12083,7 +11981,6 @@
"version": "2.0.3", "version": "2.0.3",
"resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz", "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz",
"integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==", "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==",
"devOptional": true,
"license": "MIT" "license": "MIT"
}, },
"node_modules/pause": { "node_modules/pause": {
@@ -12095,7 +11992,6 @@
"version": "2.1.0", "version": "2.1.0",
"resolved": "https://registry.npmjs.org/perfect-debounce/-/perfect-debounce-2.1.0.tgz", "resolved": "https://registry.npmjs.org/perfect-debounce/-/perfect-debounce-2.1.0.tgz",
"integrity": "sha512-LjgdTytVFXeUgtHZr9WYViYSM/g8MkcTPYDlPa3cDqMirHjKiSZPYd6DoL7pK8AJQr+uWkQvCjHNdiMqsrJs+g==", "integrity": "sha512-LjgdTytVFXeUgtHZr9WYViYSM/g8MkcTPYDlPa3cDqMirHjKiSZPYd6DoL7pK8AJQr+uWkQvCjHNdiMqsrJs+g==",
"devOptional": true,
"license": "MIT" "license": "MIT"
}, },
"node_modules/picocolors": { "node_modules/picocolors": {
@@ -12250,7 +12146,6 @@
"version": "2.3.3", "version": "2.3.3",
"resolved": "https://registry.npmjs.org/pkg-types/-/pkg-types-2.3.3.tgz", "resolved": "https://registry.npmjs.org/pkg-types/-/pkg-types-2.3.3.tgz",
"integrity": "sha512-j/lCFdcppV0JxWpCEITdbDltBxPP6cHT+yNJ6Go2OgoSA9518X847X9z0p6LtA4Nc16+eQzCZjRrWanTGvHJ5w==", "integrity": "sha512-j/lCFdcppV0JxWpCEITdbDltBxPP6cHT+yNJ6Go2OgoSA9518X847X9z0p6LtA4Nc16+eQzCZjRrWanTGvHJ5w==",
"devOptional": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"confbox": "^0.3.1", "confbox": "^0.3.1",
@@ -12262,7 +12157,6 @@
"version": "0.3.1", "version": "0.3.1",
"resolved": "https://registry.npmjs.org/confbox/-/confbox-0.3.1.tgz", "resolved": "https://registry.npmjs.org/confbox/-/confbox-0.3.1.tgz",
"integrity": "sha512-cKUSoKa8YxFZZSmraVi7onONx3amu77ngK3kGpsYHDH7drPwCRkQE1RYMPlLRrMtnciRj274XNRxcHxnKmDSnA==", "integrity": "sha512-cKUSoKa8YxFZZSmraVi7onONx3amu77ngK3kGpsYHDH7drPwCRkQE1RYMPlLRrMtnciRj274XNRxcHxnKmDSnA==",
"devOptional": true,
"license": "MIT" "license": "MIT"
}, },
"node_modules/pluralize": { "node_modules/pluralize": {
@@ -12279,7 +12173,6 @@
"version": "3.4.7", "version": "3.4.7",
"resolved": "https://registry.npmjs.org/postgres/-/postgres-3.4.7.tgz", "resolved": "https://registry.npmjs.org/postgres/-/postgres-3.4.7.tgz",
"integrity": "sha512-Jtc2612XINuBjIl/QTWsV5UvE8UHuNblcO3vVADSrKsrc6RqGX6lOW1cEo3CM2v0XG4Nat8nI+YM7/f26VxXLw==", "integrity": "sha512-Jtc2612XINuBjIl/QTWsV5UvE8UHuNblcO3vVADSrKsrc6RqGX6lOW1cEo3CM2v0XG4Nat8nI+YM7/f26VxXLw==",
"devOptional": true,
"license": "Unlicense", "license": "Unlicense",
"engines": { "engines": {
"node": ">=12" "node": ">=12"
@@ -12401,7 +12294,6 @@
"version": "7.10.0", "version": "7.10.0",
"resolved": "https://registry.npmjs.org/prisma/-/prisma-7.10.0.tgz", "resolved": "https://registry.npmjs.org/prisma/-/prisma-7.10.0.tgz",
"integrity": "sha512-o0ornyJOWgygVAzGCpr8PdXV8EJLHyVGDDUr/voBQt8Azzw8cYTByzzPGcA/m4tCkPcnJA8raEOv2CslsKhPEw==", "integrity": "sha512-o0ornyJOWgygVAzGCpr8PdXV8EJLHyVGDDUr/voBQt8Azzw8cYTByzzPGcA/m4tCkPcnJA8raEOv2CslsKhPEw==",
"devOptional": true,
"hasInstallScript": true, "hasInstallScript": true,
"license": "Apache-2.0", "license": "Apache-2.0",
"dependencies": { "dependencies": {
@@ -12451,7 +12343,6 @@
"version": "4.1.2", "version": "4.1.2",
"resolved": "https://registry.npmjs.org/proper-lockfile/-/proper-lockfile-4.1.2.tgz", "resolved": "https://registry.npmjs.org/proper-lockfile/-/proper-lockfile-4.1.2.tgz",
"integrity": "sha512-TjNPblN4BwAWMXU8s9AEz4JmQxnD1NNL7bNOY/AKUzyamc379FWASUhc/K1pL2noVb+XmZKLL68cjzLsiOAMaA==", "integrity": "sha512-TjNPblN4BwAWMXU8s9AEz4JmQxnD1NNL7bNOY/AKUzyamc379FWASUhc/K1pL2noVb+XmZKLL68cjzLsiOAMaA==",
"devOptional": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"graceful-fs": "^4.2.4", "graceful-fs": "^4.2.4",
@@ -12463,7 +12354,6 @@
"version": "3.0.7", "version": "3.0.7",
"resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz",
"integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==",
"devOptional": true,
"license": "ISC" "license": "ISC"
}, },
"node_modules/protobufjs": { "node_modules/protobufjs": {
@@ -12709,7 +12599,6 @@
"version": "3.1.0", "version": "3.1.0",
"resolved": "https://registry.npmjs.org/rc9/-/rc9-3.1.0.tgz", "resolved": "https://registry.npmjs.org/rc9/-/rc9-3.1.0.tgz",
"integrity": "sha512-ufjkNVzbRHKcCOmTahZkmVsyc3W+MSk3jY03m+a7tGHkIsdVMG9l10/3HvFbWkkKzY5VFp3pkRsIo/UYgmFL7Q==", "integrity": "sha512-ufjkNVzbRHKcCOmTahZkmVsyc3W+MSk3jY03m+a7tGHkIsdVMG9l10/3HvFbWkkKzY5VFp3pkRsIo/UYgmFL7Q==",
"devOptional": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"defu": "^6.1.7", "defu": "^6.1.7",
@@ -12720,7 +12609,6 @@
"version": "19.2.8", "version": "19.2.8",
"resolved": "https://registry.npmjs.org/react/-/react-19.2.8.tgz", "resolved": "https://registry.npmjs.org/react/-/react-19.2.8.tgz",
"integrity": "sha512-PWaYA1L/q9u2u7xYQi+Y3L3Yfnie7XyLeaJICV1MGD6LprsBxcAqGjYyr0eY3p+QdsA+x/Irkt4Qif8D63+Sbw==", "integrity": "sha512-PWaYA1L/q9u2u7xYQi+Y3L3Yfnie7XyLeaJICV1MGD6LprsBxcAqGjYyr0eY3p+QdsA+x/Irkt4Qif8D63+Sbw==",
"devOptional": true,
"license": "MIT", "license": "MIT",
"peer": true, "peer": true,
"engines": { "engines": {
@@ -12731,7 +12619,6 @@
"version": "19.2.8", "version": "19.2.8",
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.8.tgz", "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.8.tgz",
"integrity": "sha512-rVprimfGBG3DR+Tq0IQG2DT5PxKth1WIGDmj5yPmlzr4YBe7uyE+Du4oVqTDXZSHGGGXRtTJEGSSePyQCMBglQ==", "integrity": "sha512-rVprimfGBG3DR+Tq0IQG2DT5PxKth1WIGDmj5yPmlzr4YBe7uyE+Du4oVqTDXZSHGGGXRtTJEGSSePyQCMBglQ==",
"devOptional": true,
"license": "MIT", "license": "MIT",
"peer": true, "peer": true,
"dependencies": { "dependencies": {
@@ -12861,7 +12748,6 @@
"version": "2.33.4", "version": "2.33.4",
"resolved": "https://registry.npmjs.org/remeda/-/remeda-2.33.4.tgz", "resolved": "https://registry.npmjs.org/remeda/-/remeda-2.33.4.tgz",
"integrity": "sha512-ygHswjlc/opg2VrtiYvUOPLjxjtdKvjGz1/plDhkG66hjNjFr1xmfrs2ClNFo/E6TyUFiwYNh53bKV26oBoMGQ==", "integrity": "sha512-ygHswjlc/opg2VrtiYvUOPLjxjtdKvjGz1/plDhkG66hjNjFr1xmfrs2ClNFo/E6TyUFiwYNh53bKV26oBoMGQ==",
"devOptional": true,
"license": "MIT", "license": "MIT",
"funding": { "funding": {
"url": "https://github.com/sponsors/remeda" "url": "https://github.com/sponsors/remeda"
@@ -12880,7 +12766,6 @@
"version": "2.0.2", "version": "2.0.2",
"resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz",
"integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==",
"devOptional": true,
"license": "MIT", "license": "MIT",
"engines": { "engines": {
"node": ">=0.10.0" "node": ">=0.10.0"
@@ -12956,7 +12841,6 @@
"version": "0.5.0", "version": "0.5.0",
"resolved": "https://registry.npmjs.org/ret/-/ret-0.5.0.tgz", "resolved": "https://registry.npmjs.org/ret/-/ret-0.5.0.tgz",
"integrity": "sha512-I1XxrZSQ+oErkRR4jYbAyEEu2I0avBvvMM5JN+6EBprOGRCs63ENqZ3vjavq8fBw2+62G5LF5XelKwuJpcvcxw==", "integrity": "sha512-I1XxrZSQ+oErkRR4jYbAyEEu2I0avBvvMM5JN+6EBprOGRCs63ENqZ3vjavq8fBw2+62G5LF5XelKwuJpcvcxw==",
"devOptional": true,
"license": "MIT", "license": "MIT",
"engines": { "engines": {
"node": ">=10" "node": ">=10"
@@ -12966,7 +12850,6 @@
"version": "0.12.0", "version": "0.12.0",
"resolved": "https://registry.npmjs.org/retry/-/retry-0.12.0.tgz", "resolved": "https://registry.npmjs.org/retry/-/retry-0.12.0.tgz",
"integrity": "sha512-9LkiTwjUh6rT555DtE9rTX+BKByPfrMzEAtnlEtdEwr3Nkffwiihqe2bWADg+OQRjt9gl6ICdmB/ZFDCGAtSow==", "integrity": "sha512-9LkiTwjUh6rT555DtE9rTX+BKByPfrMzEAtnlEtdEwr3Nkffwiihqe2bWADg+OQRjt9gl6ICdmB/ZFDCGAtSow==",
"devOptional": true,
"license": "MIT", "license": "MIT",
"engines": { "engines": {
"node": ">= 4" "node": ">= 4"
@@ -12976,7 +12859,6 @@
"version": "3.0.3", "version": "3.0.3",
"resolved": "https://registry.npmjs.org/robust-predicates/-/robust-predicates-3.0.3.tgz", "resolved": "https://registry.npmjs.org/robust-predicates/-/robust-predicates-3.0.3.tgz",
"integrity": "sha512-NS3levdsRIUOmiJ8FZWCP7LG3QpJyrs/TE0Zpf1yvZu8cAJJ6QMW92H1c7kWpdIHo8RvmLxN/o2JXTKHp74lUA==", "integrity": "sha512-NS3levdsRIUOmiJ8FZWCP7LG3QpJyrs/TE0Zpf1yvZu8cAJJ6QMW92H1c7kWpdIHo8RvmLxN/o2JXTKHp74lUA==",
"devOptional": true,
"license": "Unlicense" "license": "Unlicense"
}, },
"node_modules/router": { "node_modules/router": {
@@ -13041,7 +12923,6 @@
"version": "5.1.1", "version": "5.1.1",
"resolved": "https://registry.npmjs.org/safe-regex2/-/safe-regex2-5.1.1.tgz", "resolved": "https://registry.npmjs.org/safe-regex2/-/safe-regex2-5.1.1.tgz",
"integrity": "sha512-mOSBvHGDZMuIEZMdOz/aCEYDCv0E7nfcNsIhUF+/P+xC7Hyf3FkvymqgPbg9D1EdSGu+uKbJgy09K/RKKc7kJA==", "integrity": "sha512-mOSBvHGDZMuIEZMdOz/aCEYDCv0E7nfcNsIhUF+/P+xC7Hyf3FkvymqgPbg9D1EdSGu+uKbJgy09K/RKKc7kJA==",
"devOptional": true,
"funding": [ "funding": [
{ {
"type": "github", "type": "github",
@@ -13079,7 +12960,6 @@
"version": "0.27.0", "version": "0.27.0",
"resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.27.0.tgz", "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.27.0.tgz",
"integrity": "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==", "integrity": "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==",
"devOptional": true,
"license": "MIT", "license": "MIT",
"peer": true "peer": true
}, },
@@ -13150,7 +13030,6 @@
"version": "2.0.0", "version": "2.0.0",
"resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz",
"integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==",
"devOptional": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"shebang-regex": "^3.0.0" "shebang-regex": "^3.0.0"
@@ -13163,7 +13042,6 @@
"version": "3.0.0", "version": "3.0.0",
"resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz",
"integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==",
"devOptional": true,
"license": "MIT", "license": "MIT",
"engines": { "engines": {
"node": ">=8" "node": ">=8"
@@ -13245,7 +13123,6 @@
"version": "4.1.0", "version": "4.1.0",
"resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz",
"integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==",
"devOptional": true,
"license": "ISC", "license": "ISC",
"engines": { "engines": {
"node": ">=14" "node": ">=14"
@@ -13423,7 +13300,6 @@
"version": "1.5.1", "version": "1.5.1",
"resolved": "https://registry.npmjs.org/sql-escaper/-/sql-escaper-1.5.1.tgz", "resolved": "https://registry.npmjs.org/sql-escaper/-/sql-escaper-1.5.1.tgz",
"integrity": "sha512-4toX5E1fQbBrpfXidaHnF0669nkAdETeIPTs2SUjxxD7RRIs9ICG4gtpmfc68JCEKehsdwLFqBu9VlQqZ1P1gg==", "integrity": "sha512-4toX5E1fQbBrpfXidaHnF0669nkAdETeIPTs2SUjxxD7RRIs9ICG4gtpmfc68JCEKehsdwLFqBu9VlQqZ1P1gg==",
"devOptional": true,
"license": "MIT", "license": "MIT",
"engines": { "engines": {
"bun": ">=1.0.0", "bun": ">=1.0.0",
@@ -13471,7 +13347,6 @@
"version": "3.10.0", "version": "3.10.0",
"resolved": "https://registry.npmjs.org/std-env/-/std-env-3.10.0.tgz", "resolved": "https://registry.npmjs.org/std-env/-/std-env-3.10.0.tgz",
"integrity": "sha512-5GS12FdOZNliM5mAOxFRg7Ir0pWz8MdpYm6AY6VPkGpbA7ZzmbzNcBJQ0GPvvyWgcY7QAhCgf9Uy89I03faLkg==", "integrity": "sha512-5GS12FdOZNliM5mAOxFRg7Ir0pWz8MdpYm6AY6VPkGpbA7ZzmbzNcBJQ0GPvvyWgcY7QAhCgf9Uy89I03faLkg==",
"devOptional": true,
"license": "MIT" "license": "MIT"
}, },
"node_modules/stdin-discarder": { "node_modules/stdin-discarder": {
@@ -14484,7 +14359,6 @@
"version": "1.4.2", "version": "1.4.2",
"resolved": "https://registry.npmjs.org/valibot/-/valibot-1.4.2.tgz", "resolved": "https://registry.npmjs.org/valibot/-/valibot-1.4.2.tgz",
"integrity": "sha512-gjdCvJ6d3RyHAneqxMYMW9QMCwYMb3jpOO0IyHZV1bnRHFBHrX3VkIILt5XYR0WhwHiH7Mty8ovuPZ/O3gamrg==", "integrity": "sha512-gjdCvJ6d3RyHAneqxMYMW9QMCwYMb3jpOO0IyHZV1bnRHFBHrX3VkIILt5XYR0WhwHiH7Mty8ovuPZ/O3gamrg==",
"devOptional": true,
"license": "MIT", "license": "MIT",
"peerDependencies": { "peerDependencies": {
"typescript": ">=5" "typescript": ">=5"
@@ -14737,7 +14611,6 @@
"version": "2.0.2", "version": "2.0.2",
"resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
"integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==",
"devOptional": true,
"license": "ISC", "license": "ISC",
"dependencies": { "dependencies": {
"isexe": "^2.0.0" "isexe": "^2.0.0"
@@ -15026,7 +14899,6 @@
"version": "2.1.0", "version": "2.1.0",
"resolved": "https://registry.npmjs.org/zeptomatch/-/zeptomatch-2.1.0.tgz", "resolved": "https://registry.npmjs.org/zeptomatch/-/zeptomatch-2.1.0.tgz",
"integrity": "sha512-KiGErG2J0G82LSpniV0CtIzjlJ10E04j02VOudJsPyPwNZgGnRKQy7I1R7GMyg/QswnE4l7ohSGrQbQbjXPPDA==", "integrity": "sha512-KiGErG2J0G82LSpniV0CtIzjlJ10E04j02VOudJsPyPwNZgGnRKQy7I1R7GMyg/QswnE4l7ohSGrQbQbjXPPDA==",
"devOptional": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"grammex": "^3.1.11", "grammex": "^3.1.11",
+6 -4
View File
@@ -1,6 +1,6 @@
{ {
"name": "wealth-ledger-backend", "name": "wealth-ledger-backend",
"version": "0.1.0", "version": "0.2.0",
"description": "", "description": "",
"author": "", "author": "",
"private": true, "private": true,
@@ -10,6 +10,7 @@
}, },
"scripts": { "scripts": {
"build": "prisma generate && nest build", "build": "prisma generate && nest build",
"openapi:generate": "npm run build && node scripts/generate-openapi.cjs",
"format": "prettier --write \"src/**/*.ts\" \"test/**/*.ts\"", "format": "prettier --write \"src/**/*.ts\" \"test/**/*.ts\"",
"start": "nest start", "start": "nest start",
"start:dev": "NODE_ENV=development nest start --watch --path tsconfig.dev.json", "start:dev": "NODE_ENV=development nest start --watch --path tsconfig.dev.json",
@@ -24,10 +25,11 @@
"test:watch": "npm test -- --watch", "test:watch": "npm test -- --watch",
"test:cov": "npm test -- --coverage", "test:cov": "npm test -- --coverage",
"test:debug": "node --experimental-vm-modules --inspect-brk -r tsconfig-paths/register -r ts-node/register node_modules/.bin/jest --runInBand", "test:debug": "node --experimental-vm-modules --inspect-brk -r tsconfig-paths/register -r ts-node/register node_modules/.bin/jest --runInBand",
"test:e2e": "node test/sqlite.cjs --all", "test:e2e": "npm run build && node test/sqlite.cjs --all",
"test:sqlite": "node test/sqlite.cjs", "test:sqlite": "node test/sqlite.cjs",
"test:container": "node test/container-smoke.cjs",
"test:durability": "node test/bank-cards-durability.cjs", "test:durability": "node test/bank-cards-durability.cjs",
"test:openapi-contract": "node test/sqlite.cjs openapi-contract.e2e-spec.ts", "test:openapi-contract": "npm run build && node test/sqlite.cjs openapi-contract.e2e-spec.ts",
"verify": "npm run lint && npm run typecheck:app && npm run typecheck:test && npm test -- --runInBand && npm run build", "verify": "npm run lint && npm run typecheck:app && npm run typecheck:test && npm test -- --runInBand && npm run build",
"release:preview": "release-it --dry-run --ci", "release:preview": "release-it --dry-run --ci",
"release:prepare": "release-it --ci", "release:prepare": "release-it --ci",
@@ -56,6 +58,7 @@
"passport": "0.7.0", "passport": "0.7.0",
"passport-jwt": "4.0.1", "passport-jwt": "4.0.1",
"pino-http": "11.0.0", "pino-http": "11.0.0",
"prisma": "7.10.0",
"reflect-metadata": "0.2.2", "reflect-metadata": "0.2.2",
"rxjs": "7.8.2" "rxjs": "7.8.2"
}, },
@@ -82,7 +85,6 @@
"globals": "17.12.0", "globals": "17.12.0",
"jest": "30.5.1", "jest": "30.5.1",
"prettier": "3.9.6", "prettier": "3.9.6",
"prisma": "7.10.0",
"release-it": "21.0.2", "release-it": "21.0.2",
"source-map-support": "0.5.21", "source-map-support": "0.5.21",
"supertest": "7.2.2", "supertest": "7.2.2",
+40
View File
@@ -0,0 +1,40 @@
const { randomBytes } = require('node:crypto');
const { writeFile } = require('node:fs/promises');
// Schema generation needs no deployment configuration or real credentials.
Object.assign(process.env, {
DATABASE_URL: 'file::memory:',
FRONTEND_ORIGIN: 'http://localhost',
JWT_SECRET: randomBytes(32).toString('hex'),
TOKEN_ENCRYPTION_KEY: randomBytes(32).toString('base64'),
PORT: '3000',
LOG_LEVEL: 'error',
TBANK_GRPC_ENDPOINT: 'localhost:443',
TBANK_RATE_LIMIT_PER_SECOND: '5',
TBANK_CA_CERT_PATH: '',
});
const { NestFactory } = require('@nestjs/core');
const { AppModule } = require('../dist/src/app.module.js');
const { API_PREFIX, createOpenApiDocument } = require('../dist/src/openapi.js');
async function generate() {
const app = await NestFactory.create(AppModule, {
preview: true,
logger: false,
});
try {
app.setGlobalPrefix(API_PREFIX);
await writeFile(
'openapi.json',
JSON.stringify(createOpenApiDocument(app), null, 2) + '\n',
);
} finally {
await app.close();
}
}
generate().catch((error) => {
console.error(error);
process.exitCode = 1;
});
+2 -8
View File
@@ -15,7 +15,7 @@ import {
ServiceUnavailableException, ServiceUnavailableException,
} from '@nestjs/common'; } from '@nestjs/common';
import { ConfigService } from '@nestjs/config'; import { ConfigService } from '@nestjs/config';
import { existsSync, readFileSync } from 'node:fs'; import { readFileSync } from 'node:fs';
import { join } from 'node:path'; import { join } from 'node:path';
import { StructuredLogger } from '../../common/logging/structured-logger.service'; import { StructuredLogger } from '../../common/logging/structured-logger.service';
import { Environment } from '../../config/env.validation'; import { Environment } from '../../config/env.validation';
@@ -172,13 +172,7 @@ export class TbankClient {
config: ConfigService<Environment, true>, config: ConfigService<Environment, true>,
private readonly logger: StructuredLogger, private readonly logger: StructuredLogger,
) { ) {
const sourceProtoRoot = join( const protoRoot = join(__dirname, 'proto/contracts');
process.cwd(),
'src/integrations/tbank/proto/contracts',
);
const protoRoot = existsSync(sourceProtoRoot)
? sourceProtoRoot
: join(process.cwd(), 'dist/integrations/tbank/proto/contracts');
const definition = loadSync( const definition = loadSync(
[ [
'users.proto', 'users.proto',
+4 -9
View File
@@ -2,7 +2,8 @@ import { INestApplication, ValidationPipe } from '@nestjs/common';
import { NestFactory } from '@nestjs/core'; import { NestFactory } from '@nestjs/core';
import cookieParser from 'cookie-parser'; import cookieParser from 'cookie-parser';
import { ConfigService } from '@nestjs/config'; import { ConfigService } from '@nestjs/config';
import { DocumentBuilder, SwaggerModule } from '@nestjs/swagger'; import { SwaggerModule } from '@nestjs/swagger';
import { API_PREFIX, createOpenApiDocument } from './openapi';
import { Logger } from 'nestjs-pino'; import { Logger } from 'nestjs-pino';
import { AppModule } from './app.module'; import { AppModule } from './app.module';
import { HttpExceptionFilter } from './common/filters/http-exception.filter'; import { HttpExceptionFilter } from './common/filters/http-exception.filter';
@@ -21,7 +22,7 @@ export function configureApp(app: INestApplication): void {
credentials: true, credentials: true,
exposedHeaders: ['X-Request-ID'], exposedHeaders: ['X-Request-ID'],
}); });
app.setGlobalPrefix('api/v1'); app.setGlobalPrefix(API_PREFIX);
app.use(cookieParser()); app.use(cookieParser());
const requestLogging = app.get(RequestLoggingMiddleware); const requestLogging = app.get(RequestLoggingMiddleware);
app.use(requestLogging.use.bind(requestLogging)); app.use(requestLogging.use.bind(requestLogging));
@@ -36,13 +37,7 @@ async function bootstrap() {
const config = app.get<ConfigService<Environment, true>>(ConfigService); const config = app.get<ConfigService<Environment, true>>(ConfigService);
if (shouldExposeSwagger(process.env['NODE_ENV'])) { if (shouldExposeSwagger(process.env['NODE_ENV'])) {
const document = SwaggerModule.createDocument( const document = createOpenApiDocument(app);
app,
new DocumentBuilder()
.setTitle('WealthLedger API')
.setVersion('v1')
.build(),
);
SwaggerModule.setup('api/docs', app, document); SwaggerModule.setup('api/docs', app, document);
} }
+11
View File
@@ -0,0 +1,11 @@
import { INestApplication } from '@nestjs/common';
import { DocumentBuilder, SwaggerModule } from '@nestjs/swagger';
export const API_PREFIX = 'api/v1';
export function createOpenApiDocument(app: INestApplication) {
return SwaggerModule.createDocument(
app,
new DocumentBuilder().setTitle('WealthLedger API').setVersion('v1').build(),
);
}
+69
View File
@@ -0,0 +1,69 @@
// Real image boundary: catches missing native modules, proto assets and migrations.
const assert = require('node:assert/strict');
const { execFileSync } = require('node:child_process');
const { randomUUID } = require('node:crypto');
const image = process.argv[2];
assert.ok(image, 'Pass a built backend image reference');
const name = `wealthledger-smoke-${randomUUID()}`;
const docker = (...args) => execFileSync('docker', args, { encoding: 'utf8' }).trim();
const environment = [
'-e', 'DATABASE_URL=file:/data/wealthledger.db',
'-e', 'FRONTEND_ORIGIN=http://localhost',
'-e', 'JWT_SECRET=disposable-image-smoke-secret-at-least-32-characters',
'-e', 'TOKEN_ENCRYPTION_KEY=MDEyMzQ1Njc4OWFiY2RlZjAxMjM0NTY3ODlhYmNkZWY=',
'-e', 'LOG_LEVEL=error',
];
async function ready() {
for (let attempt = 0; attempt < 90; attempt++) {
const state = JSON.parse(docker('inspect', name, '--format', '{{json .State}}'));
assert.equal(state.Running, true, 'Image exited before readiness');
if (state.Health.Status === 'healthy') return;
await new Promise((resolve) => setTimeout(resolve, 1000));
}
throw new Error('Image readiness timed out');
}
(async () => {
docker('volume', 'create', name);
try {
assert.throws(() => docker('run', '--rm', '--read-only', image),
'Missing runtime configuration must prevent startup');
const migrate = () => docker('run', '--rm', '--read-only', '-v', `${name}:/data`,
...environment, image, 'node', 'node_modules/prisma/build/index.js', 'migrate', 'deploy');
migrate();
docker('run', '-d', '--name', name, '--read-only', '-v', `${name}:/data`, ...environment, image);
await ready();
assert.deepEqual(JSON.parse(docker('inspect', name, '--format', '{{json .HostConfig.PortBindings}}')), {});
docker('exec', name, 'node', '-e', `
const assert = require('node:assert/strict');
const fs = require('node:fs');
assert.notEqual(process.getuid(), 0);
assert.equal(fs.existsSync('src'), false);
assert.equal(fs.existsSync('.env'), false);
assert.equal(fs.existsSync('node_modules/typescript'), false);
assert.equal(fs.existsSync('node_modules/@nestjs/cli'), false);
fs.accessSync('dist/src/integrations/tbank/proto/contracts/users.proto');
fetch('http://localhost:3000/api/v1/auth/register', {
method: 'POST', headers: {'content-type':'application/json'},
body: JSON.stringify({email:'image-smoke@example.test',password:'fictional-image-smoke-password'})
}).then(async r => {assert.equal(r.status, 200); await r.json()}).catch(e => {console.error(e); process.exitCode=1});
`);
docker('stop', name);
migrate();
docker('start', name);
await ready();
docker('exec', name, 'node', '-e', `
const assert = require('node:assert/strict');
const db = require('better-sqlite3')('/data/wealthledger.db');
assert.equal(db.prepare('SELECT count(*) AS n FROM User').get().n, 1);
assert.equal(db.prepare('SELECT count(*) AS n FROM Session').get().n, 1);
assert.equal(db.pragma('integrity_check', {simple:true}), 'ok');
db.close();
`);
console.log('PASS image: fresh/existing migration, non-root read-only runtime, auth, persistence, proto assets');
} finally {
try { docker('rm', '-f', name); } finally { docker('volume', 'rm', name); }
}
})().catch((error) => { console.error(error); process.exitCode = 1; });
+35 -28
View File
@@ -1,22 +1,12 @@
import { INestApplication } from '@nestjs/common'; import { INestApplication } from '@nestjs/common';
import { Test } from '@nestjs/testing'; import { Test } from '@nestjs/testing';
import { DocumentBuilder, SwaggerModule } from '@nestjs/swagger'; import { execFileSync } from 'node:child_process';
import { readFile } from 'node:fs/promises'; import { mkdtemp, readFile, rm, writeFile } from 'node:fs/promises';
import { tmpdir } from 'node:os';
import { resolve } from 'node:path'; import { resolve } from 'node:path';
import { AppModule } from '../src/app.module'; import { AppModule } from '../src/app.module';
import { configureApp } from '../src/main'; import { configureApp } from '../src/main';
import { createOpenApiDocument } from '../src/openapi';
function canonicalize(value: unknown): unknown {
if (Array.isArray(value)) return value.map(canonicalize);
if (value && typeof value === 'object') {
return Object.fromEntries(
Object.entries(value)
.sort(([left], [right]) => left.localeCompare(right))
.map(([key, nestedValue]) => [key, canonicalize(nestedValue)]),
);
}
return value;
}
describe('OpenAPI contract (integration)', () => { describe('OpenAPI contract (integration)', () => {
let app: INestApplication; let app: INestApplication;
@@ -32,21 +22,38 @@ describe('OpenAPI contract (integration)', () => {
afterAll(() => app.close()); afterAll(() => app.close());
it('matches the committed frontend OpenAPI snapshot', async () => { it('generates the runtime contract without server startup or deployment configuration', async () => {
const runtimeDocument = SwaggerModule.createDocument( const directory = await mkdtemp(resolve(tmpdir(), 'wealthledger-openapi-'));
app, try {
new DocumentBuilder() const networkGuard = resolve(directory, 'no-network.cjs');
.setTitle('WealthLedger API') await writeFile(
.setVersion('v1') networkGuard,
.build(), `
const net = require('node:net');
const forbidden = () => { throw new Error('Schema generation must not use the network'); };
net.Server.prototype.listen = forbidden;
net.Socket.prototype.connect = forbidden;
`,
); );
const snapshot = JSON.parse( execFileSync(
await readFile( process.execPath,
resolve(__dirname, '../../frontend/src/shared/api/spec/openapi.json'), [
'utf8', '--require',
), networkGuard,
resolve(__dirname, '../scripts/generate-openapi.cjs'),
],
{
cwd: directory,
env: { PATH: process.env.PATH },
timeout: 30_000,
},
);
const generated = JSON.parse(
await readFile(resolve(directory, 'openapi.json'), 'utf8'),
) as unknown; ) as unknown;
expect(generated).toEqual(createOpenApiDocument(app));
expect(canonicalize(runtimeDocument)).toEqual(canonicalize(snapshot)); } finally {
await rm(directory, { recursive: true, force: true });
}
}); });
}); });