diff --git a/DRAFT/README.md b/DRAFT/README.md deleted file mode 100644 index ad1266e..0000000 --- a/DRAFT/README.md +++ /dev/null @@ -1,14 +0,0 @@ -# Черновики SLM - -> Материалы в `DRAFT` являются рабочими черновиками и не задают нормативную спецификацию SLM. - -## Материалы - -- [Архитектура](./architecture/README.md) - слои, модули, публичные API, фасеты и зависимости. -- [Правила](./rules/README.md) - канонические наборы, формат и правила формулировки. - -## Соглашение - -Черновики могут содержать определения, правила, рекомендации, примеры и открытые вопросы. - -Нормативные определения задаются [терминологией](./architecture/terminology.md). Только блокирующие правила получают код SLM; тематические черновики ссылаются на канонические правила и не повторяют их формулировки. diff --git a/DRAFT/architecture/README.md b/DRAFT/architecture/README.md deleted file mode 100644 index a5cc101..0000000 --- a/DRAFT/architecture/README.md +++ /dev/null @@ -1,42 +0,0 @@ -# Архитектура SLM - -> Статус: рабочий черновик. Документы в этой папке не являются спецификацией. - -SLM задаёт структурную основу приложения: слои, модули, публичные границы, зависимости, фасеты сред выполнения и владение жизненным циклом. - -## Область SLM - -Черновик описывает слои, модули, группы, сегменты, компоненты, публичный API, зависимости и владение жизненным циклом ресурсов. - -SLM не задаёт обязательную внутреннюю форму модуля, обязательный поток данных, правила монорепозиториев или полный файловый стайлгайд. Форма модуля следует его ответственности и реальным потребителям. - -## Виды утверждений - -- **Определение** нормативно задаёт смысл архитектурного термина, но не получает код правила. -- **Правило** задаёт блокирующий архитектурный инвариант и объявляется в каноническом реестре. -- **Рекомендация** помогает принять решение, но не делает архитектуру невалидной. -- **Пример** иллюстрирует модель и не задаёт обязательную структуру. - -Нормативные определения находятся в [терминологии](./terminology.md). Канонический набор требований находится в [реестре правил SLM](../rules/registry.md). Формат и требования к правилам описаны отдельно в разделе [Правила SLM](../rules/). - -Остальные документы объясняют и иллюстрируют модель, но не владеют точными формулировками определений и правил. - -## Основная идея - -Модуль является основной архитектурной единицей SLM. Слой задаёт его роль и направление зависимостей: модули слоя `domains` владеют предметными ответственностями на тех же основаниях, что и остальные модули. Группа помогает навигации, сегмент организует внутреннее содержимое, а компонент всегда принадлежит модулю. - -SLM требует отдельную папку и единый логический публичный API модуля. По умолчанию API представлен корневым `index`; при необходимости модуль добавляет environment-фасеты `client`, `browser` и `server`. Внутренняя файловая форма модулей, сегментов и компонентов определяется стайлгайдом проекта. - -## Карта черновика - -- [Терминология](./terminology.md) -- [Слои](./layers.md) -- [Модули слоя domains](./domains.md) -- [Зависимости](./dependencies.md) -- [Модули](./modules.md) -- [Группы](./groups.md) -- [Сегменты](./segments.md) -- [Компоненты](./components.md) -- [Вложенные модули](./nested-modules.md) -- [Жизненный цикл](./lifecycle.md) -- [Проверка](./validation.md) diff --git a/DRAFT/architecture/components.md b/DRAFT/architecture/components.md deleted file mode 100644 index 0f265de..0000000 --- a/DRAFT/architecture/components.md +++ /dev/null @@ -1,65 +0,0 @@ -# Компоненты SLM - -> Пояснение нормативной модели компонентов SLM. - -Компонент является строительным элементом интерфейса, а не самостоятельной архитектурной единицей. - -## Связанные правила - -- [`SLM-COMPONENT-R009`](../rules/registry.md#slm-component-r009) -- [`SLM-LAYER-A002`](../rules/registry.md#slm-layer-a002) -- [`SLM-MODULE-A004`](../rules/registry.md#slm-module-a004) -- [`SLM-DEPENDENCY-A005`](../rules/registry.md#slm-dependency-a005) -- [`SLM-MODULE-R011`](../rules/registry.md#slm-module-r011) -- [`SLM-LIFECYCLE-R013`](../rules/registry.md#slm-lifecycle-r013) - -## Файловая форма - -Файловую форму компонента определяет стайлгайд. Компонент может быть одним файлом фреймворка или каталогом со вспомогательными файлами. - -```text -landing/ -└── ui/ - └── hero.tsx -``` - -```text -landing/ -└── ui/ - └── hero/ - ├── hero.tsx - ├── styles/ - │ └── hero.module.css - └── types/ - └── hero-props.type.ts -``` - -Наличие каталога, типов, стилей или локального `index.ts` не превращает компонент в модуль. - -## Реализация - -Компонент может отображать входные данные, вызывать переданные обработчики, условно строить интерфейс и хранить локальное состояние представления. - -SLM не вводит отдельного запрета на импорты, выполняемые кодом компонента. Каждый такой импорт считается зависимостью родительского модуля и должен соблюдать направление слоёв, публичные фасеты и запрет циклов. - -Доступ к данным, состояние, контекст или код жизненного цикла внутри компонента сами по себе не создают новую архитектурную границу. Их источники, зависимости и область жизни определяет родительский модуль. - -Провайдер может технически реализовывать контекст и жизненный цикл фреймворка, но владельцем состояния и ресурсов остаётся родительский модуль. - -Файл в `app` может технически быть компонентом React или Vue. Архитектурно он является точкой входа фреймворка, а не компонентом SLM. - -## Компонент и модуль - -| Признак | Компонент | Модуль | -|---|---|---| -| Самостоятельная ответственность | Нет | Да | -| Собственный публичный API | Нет | Да | -| Собственная граница зависимостей | Нет | Да | -| Вспомогательные файлы | Может иметь | Может иметь | -| Сегменты и вложенные модули | Нет | Может иметь | - -Модуль может состоять всего из одного корневого компонента. Различие определяется владением, а не количеством файлов. - -## Когда нужен вложенный модуль - -Если часть интерфейса получает самостоятельную ответственность, публичный API, собственную границу зависимостей, область жизни или внутреннюю модульную декомпозицию, она является модулем. При локальном использовании такой модуль может размещаться как вложенный. diff --git a/DRAFT/architecture/dependencies.md b/DRAFT/architecture/dependencies.md deleted file mode 100644 index 15a1629..0000000 --- a/DRAFT/architecture/dependencies.md +++ /dev/null @@ -1,87 +0,0 @@ -# Зависимости SLM - -> Пояснение нормативной модели зависимостей SLM. - -Матрица слоёв задаёт допустимые зависимости между модулями. - -## Что считается зависимостью - -- Обычный импорт, импорт типа и реэкспорт одинаково создают архитектурную зависимость. -- Импорт между файлами одного модуля не пересекает модульную границу. -- Импорт любого внутреннего файла, сегмента или компонента считается зависимостью ближайшего модуля-владельца. -- Вложенный модуль имеет собственную границу зависимостей. -- Группы, сегменты и компоненты не имеют самостоятельных границ зависимостей. - -Точка входа фреймворка не является модулем. Её импорты участвуют в проверке направления слоёв. - -Ресурс `shared` также не является модулем. Его прямой импорт участвует в проверке направления слоёв, но не нарушает требование о публичном API модуля. - -## Допустимые связи - -- Модуль может импортировать модули своего слоя и слоёв, разрешённых нормативной матрицей. -- Модули одного слоя могут импортировать друг друга. -- Промежуточный слой не является обязательным посредником. -- `infra` может импортировать `ui` для визуального представления технической возможности; `ui` не импортирует `infra`. - -Модуль слоя `domains` может импортировать публичный API другого разрешённого модуля. Runtime- и type-only импорты одинаково создают архитектурную зависимость, а циклические зависимости запрещены. - -```ts -// domains/orders -import type { Product } from '@/domains/catalog' -``` - -SLM не требует отдельного механизма инъекции между модулями слоя `domains`. - -Матрица слоёв определена в [Слоях](./layers.md). - -## Связанные правила - -- [`SLM-LAYER-A002`](../rules/registry.md#slm-layer-a002) -- [`SLM-MODULE-A004`](../rules/registry.md#slm-module-a004) -- [`SLM-DEPENDENCY-A005`](../rules/registry.md#slm-dependency-a005) -- [`SLM-NESTED_MODULE-A010`](../rules/registry.md#slm-nested_module-a010) -- [`SLM-ENVIRONMENT-R016`](../rules/registry.md#slm-environment-r016) -- [`SLM-ENVIRONMENT-R017`](../rules/registry.md#slm-environment-r017) -- [`SLM-ENVIRONMENT-R018`](../rules/registry.md#slm-environment-r018) -- [`SLM-ENVIRONMENT-R019`](../rules/registry.md#slm-environment-r019) - -## Публичный API - -```ts -// Допустимо -import { Button } from '@/ui/button' - -// Недопустимо -import { Button } from '@/ui/button/button' -``` - -Если модуль объявляет специализированный фасет, его путь также является публичным: - -```ts -import type { Session } from '@/domains/auth' -import { AuthProvider } from '@/domains/auth/client' -import { getServerSession } from '@/domains/auth/server' -``` - -## Фасеты сред выполнения - -Ограничения фасета распространяются на все его runtime-импорты и реэкспорты, включая транзитивные. Type-only import остаётся архитектурной зависимостью, но не добавляет исполняемый код в среду фасета. - -`index` не импортирует и не реэкспортирует `client`, `browser` или `server`. `client` может использовать универсальную внутреннюю реализацию модуля, но не импортирует `browser` или `server`. `browser` и `server` могут использовать универсальную внутреннюю реализацию, но не импортируют друг друга. - -```ts -// Browser-only фасет подключается только за границей без SSR. -const BrowserEditor = dynamic( - () => import('@/domains/editor/browser').then(({ BrowserEditor }) => BrowserEditor), - { ssr: false }, -) -``` - -Обычный статический импорт `@/domains/editor/browser` запрещён даже внутри Client Component. Tree shaking, проверка `typeof window` и обещание не вызывать экспорт при SSR не заменяют динамическую границу с отключённым SSR. - -## Циклы - -```text -ui/modal → ui/button → ui/icon -ui/icon -/→ ui/modal -``` diff --git a/DRAFT/architecture/domains.md b/DRAFT/architecture/domains.md deleted file mode 100644 index a33ff8d..0000000 --- a/DRAFT/architecture/domains.md +++ /dev/null @@ -1,59 +0,0 @@ -# Модули слоя domains - -> Пояснение размещения предметных ответственностей в обычных SLM-модулях. - -## Связанные правила - -- [`SLM-MODULE-A004`](../rules/registry.md#slm-module-a004) -- [`SLM-MODULE-R006`](../rules/registry.md#slm-module-r006) -- [`SLM-GROUP-R007`](../rules/registry.md#slm-group-r007) -- [`SLM-LAYER-R001`](../rules/registry.md#slm-layer-r001) - -## Обычные модули SLM - -Модуль слоя `domains` является обычным SLM-модулем. Он отличается от модулей других слоёв только предметной ответственностью, а не отдельной архитектурной сущностью или обязательной файловой формой. - -```text -domains/auth/ -├── hooks/ -├── services/ -├── stores/ -├── types/ -├── ui/ -└── index.ts -``` - -Показанные каталоги являются возможными сегментами, а не обязательным каркасом. Модуль может содержать предметные типы, сценарии, состояние, framework-код, компоненты и вложенные модули. - -## Публичный API - -Внешний код использует модуль через его обычный публичный API: - -```ts -import { signOut, useSession } from '@/domains/auth' -``` - -Глубокий импорт во внутренний сегмент нарушает модульную границу: - -```ts -import { useSession } from '@/domains/auth/hooks/use-session' -``` - -## Groups - -При большом количестве модулей слой `domains` может содержать обычные навигационные Groups: - -```text -domains/ -├── shop/ # Group -│ ├── catalog/ # SLM-модуль -│ └── orders/ # SLM-модуль -└── cabinet/ # Group - └── profile/ # SLM-модуль -``` - -Group не имеет `index.ts`, реализации, состояния или публичного API. Она не создаёт dependency boundary и не реэкспортирует содержащиеся в ней модули. - -## Среды выполнения - -Если части публичного API модуля предназначены для разных сред выполнения, модуль сохраняет одну ответственность и разделяет доступ фасетами `client`, `browser` и `server`. diff --git a/DRAFT/architecture/groups.md b/DRAFT/architecture/groups.md deleted file mode 100644 index 7878925..0000000 --- a/DRAFT/architecture/groups.md +++ /dev/null @@ -1,25 +0,0 @@ -# Группы SLM - -> Пояснение нормативной модели групп SLM. - -Группа помогает ориентироваться в большом количестве модулей. Она классифицирует структуру, но ничего не реализует и не образует самостоятельную границу зависимостей. - -## Связанное правило - -- [`SLM-GROUP-R007`](../rules/registry.md#slm-group-r007) -- [`SLM-MODULE-R011`](../rules/registry.md#slm-module-r011) - -## Пример - -```text -compositions/ -├── pages/ # Группа -│ ├── landing/ # Модуль -│ └── contacts/ # Модуль -└── layouts/ # Группа - └── main/ # Модуль -``` - -`pages` и `layouts` являются возможной группировкой проекта, а не обязательной структурой SLM. - -Рекомендуется создавать группу только при реальной навигационной потребности. Если папка начинает владеть файлами реализации, состоянием, жизненным циклом или публичным API, она является модулем и должна получить модульную границу. diff --git a/DRAFT/architecture/layers.md b/DRAFT/architecture/layers.md deleted file mode 100644 index 9923638..0000000 --- a/DRAFT/architecture/layers.md +++ /dev/null @@ -1,99 +0,0 @@ -# Слои SLM - -> Пояснение нормативной модели слоёв SLM. - -## Базовая структура - -```text -src/ -├── app/ -├── compositions/ -├── domains/ -├── infra/ -├── ui/ -└── shared/ -``` - -`src/` здесь является примером SLM root. Фактическую границу определяет устройство приложения. - -Отсутствующая в проекте роль не требует пустой папки. Слой является доступной архитектурной ролью, а не обязательным элементом каркаса. - -## Роли слоёв - -### App - -`app` связывает приложение с фреймворком: запускает его, объявляет маршруты, преобразует входные данные и подключает публичные API модулей разрешённых слоёв или ресурсы `shared`. Файлы `app` являются точками входа фреймворка, а не модулями SLM. - -Точка входа может напрямую использовать `compositions`, `domains`, `infra`, `ui` или `shared`, если зависимость разрешена матрицей слоёв. Такое использование не переносит ответственность импортируемого модуля в `app`. - -### Compositions - -`compositions` содержит продуктовый интерфейс: страницы, макеты, экраны, виджеты, точки входа и другие композиционные модули. Внутреннюю группировку слоя определяет проект. - -### Domains - -`domains` содержит обычные SLM-модули, владеющие предметными ответственностями приложения: моделями, правилами, сценариями и продуктовым состоянием. - -Предметная логика не принадлежит устройству одной страницы или маршрута. Конкретный visual outcome и сборка нескольких предметных ответственностей остаются в `compositions`. - -### Infra - -`infra` содержит технические сервисы приложения: аналитику, локализацию, тему, телеметрию и другие возможности среды выполнения без самостоятельной предметной модели. - -### UI - -`ui` содержит универсальные модули интерфейса, которые не зависят от конкретной страницы или продуктовой композиции. - -### Shared - -`shared` содержит независимый детерминированный фундамент без знания о продукте, изменяемого состояния и ввода-вывода. - -В `shared` допускаются обычные модули и специальные немодульные ресурсы: небольшие чистые утилиты, общие типы, стили, конфигурация и статические файлы. Ресурс не имеет самостоятельной ответственности, публичного API или жизненного цикла и может импортироваться напрямую по пути, установленному стайлгайдом. - -Если ресурсу нужны самостоятельная ответственность, собственные архитектурные зависимости, несколько файлов реализации, изменяемое состояние, ввод-вывод или область жизни, он оформляется как модуль. Каталог ресурсов не реэкспортирует модули и не используется для обхода их публичных API. - -## Матрица зависимостей - -```text -app - | -compositions - | -domains - | -infra - | -ui - | -shared -``` - -Код слоя может импортировать модули своего слоя и слоёв, разрешённых строкой матрицы. Разрешённая зависимость может пропускать промежуточные роли. - -| Слой | Может импортировать | -|---|---| -| `app` | `app`, `compositions`, `domains`, `infra`, `ui`, `shared` | -| `compositions` | `compositions`, `domains`, `infra`, `ui`, `shared` | -| `domains` | `domains`, `infra`, `ui`, `shared` | -| `infra` | `infra`, `ui`, `shared` | -| `ui` | `ui`, `shared` | -| `shared` | `shared` | - -`infra` может импортировать `ui`, когда технической возможности требуется визуальное представление, например CAPTCHA, платёжный виджет, карта, uploader, уведомление или инструмент разработчика. Такое представление остаётся частью технической ответственности и не переносит в `infra` страницы, продуктовые тексты или композицию нескольких модулей. - -`ui` не импортирует `infra`. Универсальный UI получает локализованный текст, тему, callbacks аналитики и другие технические возможности через входной контракт. Если UI-модулю необходимо напрямую знать конкретный технический сервис приложения, интеграция размещается в `infra`, `domains` или `compositions`, а универсальная часть остаётся в `ui`. - -Импорты внутри слоя, публичный API и циклы описаны отдельно в [Зависимостях](./dependencies.md). - -## Связанные правила - -- [`SLM-LAYER-R001`](../rules/registry.md#slm-layer-r001) -- [`SLM-LAYER-A002`](../rules/registry.md#slm-layer-a002) -- [`SLM-LAYER-R003`](../rules/registry.md#slm-layer-r003) -- [`SLM-MODULE-R011`](../rules/registry.md#slm-module-r011) - -## Граница слоя - -Разрешённый импорт не переносит владение. Например, модуль слоя `domains` может использовать `infra` и `ui`, но технический сервис и универсальный интерфейс сохраняют собственных владельцев. - -Если код определяет продуктовую модель, правило или сценарий, он принадлежит `domains`, а не `shared` или `infra`. Внутренняя форма домена определяется его ответственностью и реальными потребителями. diff --git a/DRAFT/architecture/lifecycle.md b/DRAFT/architecture/lifecycle.md deleted file mode 100644 index e804278..0000000 --- a/DRAFT/architecture/lifecycle.md +++ /dev/null @@ -1,31 +0,0 @@ -# Жизненный цикл - -> Пояснение нормативной модели владения ресурсами SLM. - -Жизненный цикл является частью ответственности модуля. Файл, компонент, провайдер или точка входа фреймворка могут технически создавать и останавливать ресурс, но архитектурным владельцем остаётся модуль. - -## Связанные правила - -- [`SLM-MODULE-R011`](../rules/registry.md#slm-module-r011) -- [`SLM-LIFECYCLE-R013`](../rules/registry.md#slm-lifecycle-r013) - -## Граница ресурса - -Для ресурса определяются: - -- модуль-владелец; -- место создания; -- момент начала работы; -- область жизни; -- допустимое число экземпляров; -- способ остановки и очистки. - -Ресурс начинает работу не раньше начала своей области жизни и не остаётся активным после её завершения. Подписки, слушатели, таймеры, наблюдатели, запросы и соединения рассматриваются одинаково, если требуют явного завершения или отмены. - -## Реализация - -Очистку может выполнять сам модуль, компонент, провайдер или фреймворк. Способ реализации не меняет владельца и не переносит ответственность в технический файл. - -Одиночный экземпляр на всё приложение допустим только тогда, когда модуль действительно владеет областью жизни приложения или процесса. Размещение экземпляра на уровне файла само по себе этого не доказывает. - -Точка входа `app` может запускать или подключать ресурс через публичный API импортируемого модуля, но не становится его владельцем. diff --git a/DRAFT/architecture/modules.md b/DRAFT/architecture/modules.md deleted file mode 100644 index 21d86ff..0000000 --- a/DRAFT/architecture/modules.md +++ /dev/null @@ -1,66 +0,0 @@ -# Модули SLM - -> Пояснение нормативной модели модулей SLM. - -Модуль является основной архитектурной единицей SLM. Он размещается в отдельной папке, но может состоять только из публичной точки входа и одного файла реализации. - -## Связанные правила - -- [`SLM-MODULE-A004`](../rules/registry.md#slm-module-a004) -- [`SLM-MODULE-A014`](../rules/registry.md#slm-module-a014) -- [`SLM-MODULE-R006`](../rules/registry.md#slm-module-r006) -- [`SLM-MODULE-R011`](../rules/registry.md#slm-module-r011) -- [`SLM-MODULE-R012`](../rules/registry.md#slm-module-r012) -- [`SLM-ENVIRONMENT-R016`](../rules/registry.md#slm-environment-r016) -- [`SLM-ENVIRONMENT-R017`](../rules/registry.md#slm-environment-r017) -- [`SLM-ENVIRONMENT-R018`](../rules/registry.md#slm-environment-r018) -- [`SLM-ENVIRONMENT-R019`](../rules/registry.md#slm-environment-r019) - -## Владение - -Каждая самостоятельная ответственность имеет одного модуля-владельца. Модуль определяет её публичный API, зависимости, состояние, область жизни и внутреннее устройство независимо от того, в каком файле выполняется конкретный код. - -Точки входа `app` и нормативные ресурсы `shared` являются единственными немодульными исключениями. Остальной код внутри SLM root либо принадлежит существующему модулю, либо образует новый модуль. - -## Публичный API - -Модуль предоставляет один логический публичный API. По умолчанию он представлен корневым `index`, который служит основным barrel. Если реальные потребители требуют разделить несовместимые среды выполнения, модуль добавляет один или несколько фасетов `client`, `browser` и `server`. - -Объявленные фасеты вместе образуют один публичный API и не считаются deep imports. Внешний код использует модуль только через них. Сам API открывает только контракт, необходимый реальным внешним потребителям; внутренние механизмы, изменяемое состояние и детали жизненного цикла остаются закрытыми. - -```text -auth/ -├── index.ts # Универсальный фасет -├── client.ts # Необязательная клиентская framework-граница -├── browser.ts # Необязательная browser-only граница -├── server.ts # Необязательная server-only граница -└── ... # Внутренняя реализация -``` - -`index` экспортирует публичные типы и runtime-код, который одинаково допустимо выполнять при серверном рендеринге, включая RSC, и в клиентском runtime. Он не реэкспортирует специализированные фасеты. - -`client` экспортирует Client Components, hooks, Providers и другой код, который не может выполняться как RSC. Такой код может быть отмечен директивой вроде `use client`. - -`browser` экспортирует browser-only возможности и lazy-функциональность. Потребитель подключает этот фасет только динамически через поддерживаемую фреймворком границу с отключённым SSR. - -`server` экспортирует только server-only возможности. `index`, `client` и `browser` не импортируют и не реэкспортируют его код. - -Фасет не создаётся для симметрии или будущей потребности. Один публичный runtime-export размещается в минимально подходящем фасете и не дублируется между фасетами. - -## Внутреннее устройство - -Модуль может содержать корневые файлы, сегменты, компоненты и [вложенные модули](./nested-modules.md). Внутри своей границы он может использовать относительные импорты и не обязан обращаться к собственному публичному API; точную форму внутренних импортов определяет стайлгайд. - -SLM не требует полного каркаса или обязательного каталога сегментов. Файлы фасетов являются публичными точками входа, а не сегментами или самостоятельными модулями. - -## Визуальный модуль - -Визуальный модуль обычно имеет корневой компонент, который экспортируется через публичный API. - -```text -button/ -├── button.tsx -└── index.ts -``` - -Корневой компонент остаётся компонентом, а владельцем ответственности является модуль `button`. diff --git a/DRAFT/architecture/nested-modules.md b/DRAFT/architecture/nested-modules.md deleted file mode 100644 index 51b8707..0000000 --- a/DRAFT/architecture/nested-modules.md +++ /dev/null @@ -1,30 +0,0 @@ -# Вложенные модули - -> Пояснение нормативной модели вложенных модулей SLM. - -Вложенный модуль является обычным модулем, размещённым внутри границы родительского модуля. Он имеет собственные ответственность, публичный API и границу зависимостей и подчиняется всем общим правилам модулей. - -## Связанные правила - -- [`SLM-MODULE-A004`](../rules/registry.md#slm-module-a004) -- [`SLM-MODULE-R006`](../rules/registry.md#slm-module-r006) -- [`SLM-DEPENDENCY-A005`](../rules/registry.md#slm-dependency-a005) -- [`SLM-NESTED_MODULE-A010`](../rules/registry.md#slm-nested_module-a010) - -## Пример - -```text -landing/ -├── landing.page.tsx -├── parts/ -│ └── hero/ -│ ├── hero.tsx -│ └── index.ts -└── index.ts -``` - -`parts/` здесь является примером сегмента, а не обязательным именем. - -Код родительского модуля использует вложенный модуль через его собственный публичный API. Код за пределами родительского модуля получает доступ только через публичный API родителя. - -Если вложенный модуль становится нужен за пределами родителя, рекомендуется перенести его в минимальную общую область без изменения внутренней формы. Доступ через API родителя при этом остаётся допустимым и сам по себе не требует переноса. diff --git a/DRAFT/architecture/segments.md b/DRAFT/architecture/segments.md deleted file mode 100644 index 096d094..0000000 --- a/DRAFT/architecture/segments.md +++ /dev/null @@ -1,29 +0,0 @@ -# Сегменты SLM - -> Пояснение нормативной модели сегментов SLM. - -Сегмент организует внутреннее содержимое модуля. SLM определяет роль сегмента, но не задаёт обязательный список имён. - -## Связанное правило - -- [`SLM-SEGMENT-R008`](../rules/registry.md#slm-segment-r008) -- [`SLM-MODULE-A004`](../rules/registry.md#slm-module-a004) - -## Файловая форма - -Названия, набор и содержимое сегментов определяет стайлгайд проекта. Сегмент может группировать файлы, компоненты или вложенные модули. - -Файлы и компоненты сегмента принадлежат родительскому модулю. Вложенный модуль внутри сегмента сохраняет собственные ответственность, публичный API и границу зависимостей. - -## Пример - -```text -landing/ # Модуль -└── ui/ # Сегмент модуля - └── hero/ # Каталог компонента - ├── hero.tsx - ├── styles/ # Вспомогательный каталог компонента - └── types/ # Вспомогательный каталог компонента -``` - -`styles/` и `types/` внутри каталога компонента не обязаны считаться сегментами SLM. Их форму определяет стайлгайд компонентов. diff --git a/DRAFT/architecture/terminology.md b/DRAFT/architecture/terminology.md deleted file mode 100644 index 075aaca..0000000 --- a/DRAFT/architecture/terminology.md +++ /dev/null @@ -1,153 +0,0 @@ -# Терминология SLM - -> Нормативные определения рабочего черновика. Этот раздел не объявляет правила. - -Определения задают обязательный смысл архитектурных терминов и используются при толковании всех правил. Код получают только блокирующие требования, а не сами определения. - -## Базовые понятия - -### SLM root - -Граница структурной архитектуры одного приложения. Внутри неё определяются слои, модули и зависимости SLM. Монорепозиторий, пакеты и отношения между несколькими SLM root находятся за пределами текущего черновика. - -### Ответственность - -Связная часть приложения с одной причиной изменяться. Ответственность является самостоятельной, когда ей нужны собственные публичный API, зависимости, состояние или область жизни. - -### Владелец - -Модуль, который определяет публичный API ответственности, её зависимости, состояние, область жизни и внутреннее устройство. Место выполнения кода не переносит владение. - -### Публичный API - -Единая логическая граница внешнего доступа к модулю. Публичный API скрывает внутреннее устройство и состоит из обязательного корневого фасета `index` и только реально необходимых environment-фасетов `client`, `browser` и `server`. - -### Фасет - -Объявленная публичная точка входа модуля, которая открывает часть его единого логического API для определённой среды или способа выполнения. Импорт объявленного фасета не является deep import. Любой другой путь внутрь модуля остаётся внутренним. - -Корневой фасет `index` является основным barrel модуля. Он экспортирует публичные типы и runtime-код, совместимый как с серверным рендерингом, включая React Server Components, так и с клиентским выполнением. - -Необязательные environment-фасеты имеют следующий нормативный смысл: - -| Фасет | Среда и способ выполнения | -|---|---| -| `client` | Клиентская framework-граница, которая может участвовать в server prerender и затем выполняться при hydration и в браузере | -| `browser` | Browser-only код, подключаемый только динамически с отключённым SSR | -| `server` | Server-only код, недоступный через универсальный, клиентский и браузерный фасеты | - -Client Component, импортированный Server Component, не становится универсальным кодом и не экспортируется через `index`. Совместимость фасета со средой определяется всеми его runtime-импортами и реэкспортами, включая транзитивные. - -### Зависимость - -Статическая связь внутри одного SLM root, которую импорт или реэкспорт создаёт между архитектурными границами. Обычный импорт, импорт типа и реэкспорт одинаково создают архитектурную зависимость. - -Зависимость любого внутреннего файла, сегмента или компонента относится к ближайшему модулю-владельцу. Вложенный модуль начинает собственную границу зависимостей. - -### Область жизни - -Период, в течение которого принадлежащие модулю состояние или долгоживущий ресурс должны оставаться активными. - -### Ресурс жизненного цикла - -Ресурс, работа которого продолжается после первоначального вызова и требует остановки, отмены, отписки или освобождения. Например, подписка, слушатель событий, таймер, наблюдатель, запрос или соединение. - -### Очистка - -Гарантированное прекращение работы ресурса не позже завершения его области жизни. Автоматическая очистка фреймворка считается очисткой владельца, если модуль устанавливает и контролирует соответствующую границу. - -## Структурные сущности - -### Нормативная матрица слоёв - -Отношение допустимой зависимости между слоями одного SLM root. Матрица определяет, код каких слоёв может импортировать исходный слой; она не обязана образовывать линейный порядок. - -Для SLM нормативно отношение `app → compositions → domains → infra → ui → shared`. `infra` может импортировать `ui`, а `ui` не импортирует `infra`. Промежуточный слой не является обязательным посредником. - -### Слой - -Одна из шести верхнеуровневых ролей внутри SLM root: - -| Слой | Роль | -|---|---| -| `app` | Связь приложения с фреймворком: запуск, маршруты и преобразование входных данных | -| `compositions` | Сборка продуктового интерфейса: страницы, макеты, экраны, виджеты и другие композиции | -| `domains` | Предметные области, их модели, правила, сценарии и продуктовое состояние | -| `infra` | Технические сервисы и возможности приложения | -| `ui` | Универсальные модули интерфейса без зависимости от конкретной продуктовой композиции | -| `shared` | Независимый детерминированный фундамент без знания о продукте, изменяемого состояния и ввода-вывода | - -Полная матрица допустимых зависимостей: - -| Исходный слой | Допустимые целевые слои | -|---|---| -| `app` | `app`, `compositions`, `domains`, `infra`, `ui`, `shared` | -| `compositions` | `compositions`, `domains`, `infra`, `ui`, `shared` | -| `domains` | `domains`, `infra`, `ui`, `shared` | -| `infra` | `infra`, `ui`, `shared` | -| `ui` | `ui`, `shared` | -| `shared` | `shared` | - -### Модуль - -Минимальная самостоятельная архитектурная единица SLM. Модуль владеет одной связной ответственностью, размещается в отдельной папке и предоставляет публичный API. - -### Предметная ответственность - -Связная предметная область приложения, которая может включать собственные модели, правила, сценарии и продуктовое состояние. Количество экранов, endpoint-ов, hooks или файлов само по себе не определяет её границу. - -### Группа - -Навигационная папка для модулей и других групп. Группа не является владельцем ответственности, состояния, области жизни, публичного API или границы зависимостей. - -### Сегмент - -Внутренняя часть одного модуля, которая группирует его содержимое по назначению. Сегмент не является самостоятельным владельцем, публичным API или границей зависимостей. - -### Компонент - -Сущность фреймворка, которая реализует часть интерфейса родительского модуля. Компонент не образует собственного владельца, публичного API или границы зависимостей. - -Импорты, состояние, доступ к данным и код жизненного цикла компонента принадлежат родительскому модулю. Их наличие само по себе не создаёт новый модуль; решающим признаком является самостоятельная ответственность. - -### Вложенный модуль - -Обычный модуль, размещённый внутри границы родительского модуля. Он имеет собственные ответственность, публичный API и границу зависимостей и подчиняется всем общим правилам модулей. - -Публичный API вложенного модуля доступен коду родительской границы. Для кода за пределами родительского модуля вложенный модуль остаётся внутренней реализацией родителя. - -### Точка входа фреймворка - -Специальная немодульная единица слоя `app`, которая непосредственно связывает приложение с фреймворком. Её импорты участвуют в проверке направления слоёв, но сама точка входа не является модулем. - -### Ресурс shared - -Специальная немодульная единица слоя `shared`: небольшая детерминированная утилита, общий тип, стиль, конфигурация или статический ресурс без продуктового знания, изменяемого состояния, ввода-вывода, области жизни и собственного публичного API. - -Ресурс `shared` может импортироваться напрямую по пути, установленному стайлгайдом, и не является модулем. Доступный по этому пути файл является всей единицей и не скрывает отдельное внутреннее устройство. - -Если ресурсу нужны самостоятельная ответственность, собственные архитектурные зависимости, несколько файлов реализации, изменяемое состояние, ввод-вывод или область жизни, он оформляется как модуль. - -## Структурная модель - -```text -SLM root -├── app -│ └── точка входа фреймворка -├── compositions | domains | infra | ui -│ ├── группа -│ │ └── модуль -│ └── модуль -│ ├── корневые файлы -│ ├── сегмент -│ │ ├── файлы -│ │ ├── компоненты -│ │ └── вложенные модули -│ └── вложенный модуль -└── shared - ├── группа - ├── модуль - └── ресурс shared -``` - -Путь и имя папки сами по себе не определяют сущность. Её определяют ответственность, владелец и публичная граница. Физическое сопоставление путей с сущностями задаётся стайлгайдом или конфигурацией проверки проекта. diff --git a/DRAFT/architecture/validation.md b/DRAFT/architecture/validation.md deleted file mode 100644 index 625872e..0000000 --- a/DRAFT/architecture/validation.md +++ /dev/null @@ -1,51 +0,0 @@ -# Проверка SLM - -> Граница автоматической проверки и архитектурного ревью SLM. - -## Автоматическая проверка - -Проект, заявляющий соответствие SLM, сопоставляет физические пути с SLM root, слоями, модулями, Groups, вложенными модулями, публичными фасетами, точками входа `app` и ресурсами `shared`. Такое сопоставление задаётся стайлгайдом или конфигурацией проверки и не изменяет нормативный смысл сущностей. - -Каждое правило класса `A` должно быть реализовано проверкой проекта и блокировать её при нарушении. SLM не навязывает конкретный инструмент. - -Скрипт `draft-rules.js` проверяет только целостность документов: формат и уникальность кодов, ссылки и наличие тематических упоминаний. Он не проверяет архитектуру приложения. - -Актуальный список правил скрипт получает из [канонического реестра](../rules/registry.md). - -## Архитектурное ревью - -Правила класса `R` проверяются вручную. Статический анализ может обнаружить подозрительный код, но не способен окончательно определить: - -- ответственность и её владельца; -- связность ответственности модуля; -- соответствие кода роли слоя; -- необходимость экспортов публичного API; -- область жизни ресурса и достаточность очистки; -- наличие самостоятельной границы у компонента, группы или сегмента. - -## Проверка фасетов - -Автоматическая проверка сопоставляет публичные пути модуля с фасетами `index`, `client`, `browser` и `server`, запрещает остальные внешние пути и проверяет их runtime-импорты и реэкспорты, включая транзитивные. - -Для проверки сред инструмент различает runtime imports, type-only imports и dynamic imports. Окончательное решение о соответствии экспортируемого кода назначению фасета принимается на ревью. - -На ревью проверяется: - -- экспортирует ли `index` только универсальные типы и runtime-код; -- остаётся ли `client` совместимым с server prerender и browser hydration; -- достигается ли `browser` только через dynamic boundary с отключённым SSR; -- остаётся ли `server` недоступным через `index`, `client` и `browser`; -- существует ли каждый специализированный фасет ради реального потребителя; -- не дублируется ли один runtime-export между фасетами. - -Название файла, директива `use client`, tree shaking или локальная проверка `typeof window` сами по себе не доказывают совместимость кода со средой выполнения. - -## Проверка слоя domains - -На ревью определяется: - -- соответствует ли ответственность модуля предметной роли слоя `domains`; -- не разделена ли одна область на соседние модули без самостоятельных владельцев; -- не объединены ли в одном модуле несвязанные предметные области; -- остаются ли страницы, маршруты и UI нескольких предметных ответственностей в `compositions`; -- остаются ли самостоятельные технические сервисы без предметной модели в `infra`. diff --git a/DRAFT/index.md b/DRAFT/index.md deleted file mode 100644 index dd9cd26..0000000 --- a/DRAFT/index.md +++ /dev/null @@ -1,33 +0,0 @@ ---- -layout: home -title: SLM Design - -hero: - name: SLM Design - text: Последовательная архитектура фронтенд-приложений - tagline: Слои, модули и явные публичные границы для приложений с несколькими средами выполнения. - image: - src: /logo.svg - alt: SLM Design - actions: - - theme: brand - text: Читать архитектуру - link: /architecture/ - - theme: alt - text: Реестр правил - link: /rules/ - -features: - - title: Архитектурная база - details: Шесть слоёв, модули, публичные API, зависимости, вложенные границы и жизненный цикл ресурсов. - - title: Фасеты сред выполнения - details: Универсальный index и специализированные client, browser и server фасеты для явного разделения сред выполнения. - - title: Канонические правила - details: Точные блокирующие требования отделены от определений, рекомендаций и примеров и собраны в едином реестре. ---- - -## Что опубликовано - -Сайт содержит рабочий черновик архитектуры SLM и канонический реестр правил. Монорепозитории пока не входят в опубликованную документацию. - -Определения терминологии нормативны внутри черновика. Точные формулировки блокирующих требований находятся только в [реестре правил](/rules/registry). diff --git a/DRAFT/rules/README.md b/DRAFT/rules/README.md deleted file mode 100644 index a8787b0..0000000 --- a/DRAFT/rules/README.md +++ /dev/null @@ -1,117 +0,0 @@ -# Правила SLM - -> Статус: системный черновик. Не является нормативной спецификацией. - -Эта директория является единственным местом объявления правил SLM. Остальные черновики объясняют архитектуру и ссылаются на канонические коды, но не повторяют формулировки правил. - -## Что считается правилом - -Правило задаёт один блокирующий архитектурный инвариант. - -Определения, рекомендации, разрешения, примеры и открытые вопросы не получают код правила. - -Нормативные определения объявляются в [терминологии SLM](../architecture/terminology.md). Они обязательны для толкования правил, но нормативность определения сама по себе не превращает его в правило. - -## Код правила - -```text -SLM-{group}-{class}{number} -``` - -| Часть | Значение | -|---|---| -| `SLM` | Принадлежность архитектуре SLM | -| `group` | Раздел правил | -| `class` | Способ проверки: `A` или `R` | -| `number` | Глобально уникальный трёхзначный номер правила | - -## Способы проверки - -### `A`: автоматическая проверка - -Всё правило можно однозначно проверить программно без понимания предметного смысла кода. Нарушение такого правила должно блокировать автоматическую проверку. - -### `R`: проверка на ревью - -Для окончательного решения требуется понимание ответственности, владения или смысла зависимости. Линтер может проверять отдельные признаки, но не заменяет решение на ревью. - -Одно правило не разделяется на автоматическую и ручную копии только из-за разных способов проверки. Если существенная часть инварианта требует смыслового решения, всё правило получает класс `R`. - -## Разделы правил - -| Код | Раздел | -|---|---| -| `LAYER` | Слои | -| `DEPENDENCY` | Зависимости | -| `MODULE` | Модули | -| `GROUP` | Группы | -| `SEGMENT` | Сегменты | -| `COMPONENT` | Компоненты | -| `NESTED_MODULE` | Вложенные модули | -| `LIFECYCLE` | Жизненный цикл | -| `ENVIRONMENT` | Границы сред выполнения | - -Код раздела записывается полным английским именем в `UPPER_SNAKE_CASE`. Новый код добавляется в таблицу до первого использования. - -## Формат записи - -```md -### SLM-MODULE-A004 - -> **Публичный API модуля** -> -> Каждый модуль предоставляет единый логический публичный API через обязательный корневой фасет `index` и, при необходимости, фасеты `client`, `browser` и `server`; код за пределами модуля импортирует его содержимое только через эти фасеты. -``` - -Код является заголовком третьего уровня и автоматически получает адрес для ссылки `#slm-module-a004`. - -Название и описание входят в одну цитату. Название выделяется жирным и служит кратким именем правила. Описание полностью формулирует требование и занимает одну физическую строку. - -Ссылка из тематического черновика: - -```md -[`SLM-MODULE-A004`](./registry.md#slm-module-a004) -``` - -## Как формулировать правила - -1. Правило понятно без чтения тематической главы и опирается только на нормативные термины SLM. -2. Правило защищает один архитектурный инвариант. -3. Один инвариант получает один код независимо от числа участников и способов проверки. -4. Название является кратким и устойчивым именем правила. -5. Название обозначает предмет правила, а описание полностью формулирует требование. -6. Описание объясняет допустимую границу и то, что считается нарушением. -7. Описание раскрывает названный инвариант и не вводит второе независимое требование. -8. Описание использует нормативные определения и не пересказывает их без необходимости. -9. Название и описание используют человеческий язык и только необходимые архитектурные термины. -10. Обоснование, подробности, примеры и исключения размещаются в тематическом черновике, а не в описании. -11. Правило не создаётся отдельно с позиции владельца и потребителя, если обе формулировки защищают одну границу. -12. Перед добавлением правила реестр проверяется на дубли и противоречия. -13. Код присваивается после проверки правила на примерах и контрпримерах. - -## Нумерация - -1. Номер глобально уникален независимо от раздела и способа проверки. -2. Номер не обозначает важность или порядок выполнения. -3. Удалённый номер не переиспользуется для другого правила. -4. При изменении способа проверки номер сохраняется, но меняется полный код. - -## Проверка качества - -Перед принятием правила нужно ответить «да»: - -- Понятно, о чём правило? -- Название кратко и однозначно называет правило? -- Понятно, что оно требует? -- Понятно, что является нарушением? -- Нельзя ли объединить его с существующим правилом? -- Не содержит ли оно рекомендацию или разрешение? -- Соответствует ли класс способу окончательной проверки? - -## Проверка документов - -Корневой скрипт `draft-rules.js` читает объявления только из этой директории, проверяет формат и уникальность кодов, валидирует ссылки из остальных черновиков и выводит правила разделами «Автоматические» и «Для ревью». Скрипт проверяет документы, а не архитектуру приложения. - -## Реестр - -- [Единый реестр правил SLM](./registry.md) diff --git a/DRAFT/rules/registry.md b/DRAFT/rules/registry.md deleted file mode 100644 index 098943e..0000000 --- a/DRAFT/rules/registry.md +++ /dev/null @@ -1,129 +0,0 @@ -# Реестр правил SLM - -Здесь собраны правила SLM. Это единственное место, где они формулируются; тематические черновики объясняют их и ссылаются на коды. - -## Размещение кода по слоям - -### SLM-LAYER-R001 - -> **Назначение слоёв** -> -> Код внутри SLM root размещается в слое, нормативная роль которого соответствует ответственности этого кода. - -### SLM-LAYER-A002 - -> **Направление зависимостей** -> -> Внутри одного SLM root код каждого слоя может зависеть только от кода целевых слоёв, разрешённых для него нормативной матрицей слоёв. - -### SLM-LAYER-R003 - -> **Граница слоя `app`** -> -> В `app` размещаются только точки входа фреймворка для запуска, маршрутов, преобразования входных данных и подключения публичных API модулей разрешённых слоёв или ресурсов `shared`; ответственности этих модулей остаются за пределами `app`. - -## Границы модулей - -### SLM-MODULE-A004 - -> **Публичный API модуля** -> -> Каждый модуль предоставляет единый логический публичный API через обязательный корневой фасет `index` и, при необходимости, фасеты `client`, `browser` и `server`; код за пределами модуля импортирует его содержимое только через эти фасеты. - -### SLM-MODULE-A014 - -> **Папка модуля** -> -> Каждый модуль размещается в отдельной папке; его публичный API и внутренняя реализация находятся внутри этой границы, а вложенные модули образуют собственные папки. - -### SLM-MODULE-R006 - -> **Ответственность модуля** -> -> Одна модульная граница содержит код одной связной ответственности; части, которые изменяются по несвязанным причинам, размещаются в разных модулях. - -### SLM-MODULE-R011 - -> **Владелец ответственности** -> -> Каждая самостоятельная ответственность и относящийся к ней код принадлежат ровно одному модулю; вне модульной границы допускаются только точки входа `app` и нормативные ресурсы `shared`. - -### SLM-MODULE-R012 - -> **Состав публичного API** -> -> Публичный API модуля открывает только контракт, необходимый реальным внешним потребителям; детали реализации и изменяемые внутренние механизмы остаются закрытыми. - -## Зависимости между модулями - -### SLM-DEPENDENCY-A005 - -> **Циклические зависимости** -> -> Зависимости между модулями внутри одного SLM root, включая вложенные модули, не образуют циклов. - -## Назначение групп - -### SLM-GROUP-R007 - -> **Назначение группы** -> -> Группа содержит только модули и другие группы, не владеет файлами реализации, состоянием, жизненным циклом или публичным API и не импортируется внешним кодом. - -## Назначение сегментов - -### SLM-SEGMENT-R008 - -> **Граница сегмента** -> -> Сегмент организует код только внутри одного модуля и не имеет собственной ответственности, публичного API или границы зависимостей. - -## Ответственность компонентов - -### SLM-COMPONENT-R009 - -> **Ответственность компонента** -> -> Компонент реализует часть ответственности одного родительского модуля; все его зависимости, состояние и жизненный цикл принадлежат этому модулю и не образуют самостоятельную архитектурную границу. - -## Границы вложенных модулей - -### SLM-NESTED_MODULE-A010 - -> **Доступ к вложенному модулю** -> -> Код за пределами родительского модуля не импортирует вложенный модуль напрямую и получает его экспорты только через публичный API родителя. - -## Жизненный цикл - -### SLM-LIFECYCLE-R013 - -> **Жизненный цикл ресурсов** -> -> Для каждого ресурса жизненного цикла модуль-владелец определяет создание, область жизни, число экземпляров и очистку; ресурс активен только внутри своей области жизни. - -## Границы сред выполнения - -### SLM-ENVIRONMENT-R016 - -> **Универсальный фасет** -> -> Корневой фасет `index` экспортирует только публичный код, совместимый как с серверным рендерингом, включая RSC, так и с клиентским выполнением, и не импортирует или реэкспортирует код фасетов `client`, `browser` или `server` прямо либо транзитивно. - -### SLM-ENVIRONMENT-R017 - -> **Клиентский фасет** -> -> Фасет `client` экспортирует только клиентский код, который не может выполняться как RSC, и не импортирует или реэкспортирует код фасетов `browser` или `server` прямо либо транзитивно. - -### SLM-ENVIRONMENT-R018 - -> **Браузерный фасет** -> -> Фасет `browser` экспортирует только browser-only код, а потребители импортируют его только динамически с отключённым SSR. - -### SLM-ENVIRONMENT-R019 - -> **Серверный фасет** -> -> Фасет `server` экспортирует только server-only код и не импортируется или реэкспортируется фасетами `index`, `client` или `browser` прямо либо транзитивно. diff --git a/examples/demo-frontend/.env.example b/examples/demo-frontend/.env.example deleted file mode 100644 index a7cf7df..0000000 --- a/examples/demo-frontend/.env.example +++ /dev/null @@ -1 +0,0 @@ -NEXT_PUBLIC_SIMPLE_API_URL=http://localhost:3001 diff --git a/examples/demo-frontend/.gitignore b/examples/demo-frontend/.gitignore deleted file mode 100644 index 7b8da95..0000000 --- a/examples/demo-frontend/.gitignore +++ /dev/null @@ -1,42 +0,0 @@ -# See https://help.github.com/articles/ignoring-files/ for more about ignoring files. - -# dependencies -/node_modules -/.pnp -.pnp.* -.yarn/* -!.yarn/patches -!.yarn/plugins -!.yarn/releases -!.yarn/versions - -# testing -/coverage - -# next.js -/.next/ -/out/ - -# production -/build - -# misc -.DS_Store -*.pem - -# debug -npm-debug.log* -yarn-debug.log* -yarn-error.log* -.pnpm-debug.log* - -# env files (can opt-in for committing if needed) -.env* -!.env.example - -# vercel -.vercel - -# typescript -*.tsbuildinfo -next-env.d.ts diff --git a/examples/demo-frontend/AGENTS.md b/examples/demo-frontend/AGENTS.md deleted file mode 100644 index 8bd0e39..0000000 --- a/examples/demo-frontend/AGENTS.md +++ /dev/null @@ -1,5 +0,0 @@ - -# This is NOT the Next.js you know - -This version has breaking changes — APIs, conventions, and file structure may all differ from your training data. Read the relevant guide in `node_modules/next/dist/docs/` before writing any code. Heed deprecation notices. - diff --git a/examples/demo-frontend/CLAUDE.md b/examples/demo-frontend/CLAUDE.md deleted file mode 100644 index 43c994c..0000000 --- a/examples/demo-frontend/CLAUDE.md +++ /dev/null @@ -1 +0,0 @@ -@AGENTS.md diff --git a/examples/demo-frontend/README.md b/examples/demo-frontend/README.md deleted file mode 100644 index 2e1b53f..0000000 --- a/examples/demo-frontend/README.md +++ /dev/null @@ -1,102 +0,0 @@ -# Demo frontend - -Полноценное Next.js 16 приложение поверх меньшего из двух fixture API: `../demo-backend/openapi/simple.json`. - -Пример показывает SLM Level 1 на каталоге: public data, JWT login/refresh/logout, persisted cart, checkout, protected orders, RBAC, admin CRUD, optimistic locking и детерминированные network outcomes. - -## Запуск - -Требуются Node.js 20+, npm 10+ и современный browser с Web Locks API. Auth и cart transitions работают fail-closed без cross-tab lock; `localhost` считается secure context для локальной разработки. - -Сначала запустите Simple API: - -```bash -cd ../demo-backend -npm install -npm run dev:simple -``` - -Затем запустите frontend: - -```bash -npm install -npm run dev -``` - -Откройте `http://localhost:3000`. Backend по умолчанию доступен на `http://localhost:3001`. - -Чтобы изменить URL backend, создайте `.env.local` рядом с `package.json`: - -```bash -NEXT_PUBLIC_SIMPLE_API_URL=http://localhost:3001 -``` - -## Demo accounts - -| Email | Password | Role | Scenarios | -|---|---|---|---| -| `admin@demo.local` | `demo1234` | admin | Product CRUD, all orders | -| `customer@demo.local` | `demo1234` | customer | Cart, checkout, own orders | - -## Routes - -| Route | Composition responsibility | -|---|---| -| `/` | Product search, category filter, sorting and pagination | -| `/products/[productId]` | Product detail and add-to-cart | -| `/cart` | Multi-domain checkout coordination | -| `/orders` | Protected order history and cancellation | -| `/sign-in` | JWT session lifecycle and account selection | -| `/admin/products` | RBAC-gated CRUD and optimistic locking | - -Плавающий `Demo controls` widget переключает `X-Demo-Scenario`: slow, timeout, 500, 429, empty, expired auth, forbidden и conflict. Отдельное действие `Seed 250` загружает настоящий большой seed с интерактивными product IDs; synthetic `large-dataset` header намеренно не используется для mutation UI. - -## SLM boundary - -`src` является SLM root: - -```text -src/ -├── app/ Next.js bootstrap and route entries -├── compositions/ Layout, screens and demo widget -├── domains/ Auth, catalog, cart, orders and demo-control -├── infra/ Generated REST client, JWT storage and browser storage -├── ui/ Product-agnostic button, form field and feedback panel -└── shared/ Deterministic predicates, formatting and Result type -``` - -Пример использует Level 1 осознанно: у каждого домена одна browser runtime integration. Дополнительные factories, adapters и assemblies Level 2 не окупили бы стоимость. Checkout остаётся в composition, поэтому `orders` не импортирует `cart`, а module graph остаётся ацикличным. - -State и lifecycle распределены по владельцам: - -- `auth` владеет пользовательской сессией; `infra/simple-auth-session` хранит technical JWT pair, стабильный `sessionId` и CAS revision. -- `cart` владеет строками, totals и persisted snapshot validation; monotonic revision и conditional clear сериализованы между вкладками. -- SWR cache принадлежит REST infra module, создаётся отдельным provider и remount-ится при смене logical auth session. -- React providers создаются один раз в application scope и очищают свои subscriptions при unmount. -- Route entries только адаптируют Next.js params и подключают public APIs compositions. -- Reset, seed, role и request-scenario transitions синхронизируют cache, auth, cart и route-local pagination между вкладками. - -## REST client - -Split SDK генерируется из committed OpenAPI: - -```bash -npm run codegen:simple-rest-api -``` - -Generated-код живёт только в `src/infra/simple-rest-api/generated` и не редактируется вручную. GET hooks вызывают точечные operations через `simpleHttpClient`; submit-сценарии используют полный `simpleRestApi`. Внешний код импортирует REST capability только через `@/infra/simple-rest-api`. - -OpenAPI fixture описывает числовые `page` и `limit` как `object`. Исправление generated type изолировано в `types/to-generated-query.ts`; runtime query остаётся числовым. - -JWT refresh выполняется в transport `onError`, ограничен одним retry и дедуплицирует конкурентные refresh requests одной revision. CAS не позволяет позднему refresh воскресить logout или перезаписать новый login. Хранение refresh token в `localStorage` допустимо только для этой архитектурной fixture; production-приложение должно выбрать threat model и более безопасную session strategy. - -Checkout привязан к captured auth session и persisted cart revision. Backend атомарно проверяет product version, unit price, USD currency, aggregate stock и уникальность product lines. Timeout-сценарий откладывает mutation handler и отменяет его при disconnect, поэтому frontend timeout не скрывает завершившийся POST. - -## Проверка - -```bash -npm run check -npm run build -``` - -`npm run check` запускает architecture constraints, ESLint, TypeScript и Vitest. Architecture script строит import graph через TypeScript AST и явный module manifest, затем проверяет направление слоёв, public module imports, side-effect/dynamic imports, запрет утечки generated SDK и циклы. diff --git a/examples/demo-frontend/eslint.config.mjs b/examples/demo-frontend/eslint.config.mjs deleted file mode 100644 index fccd7e8..0000000 --- a/examples/demo-frontend/eslint.config.mjs +++ /dev/null @@ -1,37 +0,0 @@ -import js from '@eslint/js' -import nextPlugin from '@next/eslint-plugin-next' -import { defineConfig, globalIgnores } from 'eslint/config' -import reactHooks from 'eslint-plugin-react-hooks' -import globals from 'globals' -import tseslint from 'typescript-eslint' - -export default defineConfig([ - globalIgnores([ - '.next/**', - 'out/**', - 'build/**', - 'coverage/**', - 'next-env.d.ts', - 'src/infra/simple-rest-api/generated/**' - ]), - js.configs.recommended, - ...tseslint.configs.recommended, - { - files: ['**/*.{js,mjs,mts,ts,tsx}'], - plugins: { - '@next/next': nextPlugin, - 'react-hooks': reactHooks - }, - languageOptions: { - globals: { - ...globals.browser, - ...globals.node - } - }, - rules: { - ...nextPlugin.configs.recommended.rules, - ...nextPlugin.configs['core-web-vitals'].rules, - ...reactHooks.configs.flat.recommended.rules - } - } -]) diff --git a/examples/demo-frontend/next.config.ts b/examples/demo-frontend/next.config.ts deleted file mode 100644 index 66ce684..0000000 --- a/examples/demo-frontend/next.config.ts +++ /dev/null @@ -1,24 +0,0 @@ -import { fileURLToPath } from 'node:url' -import type { NextConfig } from 'next' - -const nextConfig: NextConfig = { - turbopack: { - root: fileURLToPath(new URL('.', import.meta.url)) - }, - images: { - remotePatterns: [ - { - protocol: 'https', - hostname: 'picsum.photos', - pathname: '/**' - }, - { - protocol: 'https', - hostname: 'i.pravatar.cc', - pathname: '/**' - } - ] - } -} - -export default nextConfig diff --git a/examples/demo-frontend/package-lock.json b/examples/demo-frontend/package-lock.json deleted file mode 100644 index dd9d3ef..0000000 --- a/examples/demo-frontend/package-lock.json +++ /dev/null @@ -1,4280 +0,0 @@ -{ - "name": "demo-frontend", - "version": "0.1.0", - "lockfileVersion": 3, - "requires": true, - "packages": { - "": { - "name": "demo-frontend", - "version": "0.1.0", - "dependencies": { - "clsx": "2.1.1", - "next": "16.2.12", - "react": "19.2.8", - "react-dom": "19.2.8", - "swr": "2.4.2" - }, - "devDependencies": { - "@eslint/js": "10.0.1", - "@next/eslint-plugin-next": "16.2.12", - "@types/node": "^20", - "@types/react": "^19", - "@types/react-dom": "^19", - "eslint": "10.8.0", - "eslint-plugin-react-hooks": "7.1.1", - "globals": "17.8.0", - "typescript": "^5", - "typescript-eslint": "8.65.0", - "vitest": "4.1.10" - } - }, - "node_modules/@babel/code-frame": { - "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.7.tgz", - "integrity": "sha512-Aup7aUOfpbAUg2ROOJN6Iw5f9DMBlzu0mIkm/malLQFN/YQgO48wCj0Kxa3sEHJvPVFg7siR+qRInwXd2qhQKw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-validator-identifier": "^7.29.7", - "js-tokens": "^4.0.0", - "picocolors": "^1.1.1" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/compat-data": { - "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.29.7.tgz", - "integrity": "sha512-locTkQyKvwIEgBzVrn8693ebc97F2U8ZHjbXwDXJ5Fn2TCpNwTlKcaKLkdHop5c/icOFE7qt7Q9JC5hnKNa6Gg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/core": { - "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.29.7.tgz", - "integrity": "sha512-RgHBCvtjbOK2gXSNBNIkNoEc9qoVEtau3hj8gEqKQuL3HZAibKarWFEI3Lfm6EYKkLalOh8eSrj9b+ch9H/VBA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/code-frame": "^7.29.7", - "@babel/generator": "^7.29.7", - "@babel/helper-compilation-targets": "^7.29.7", - "@babel/helper-module-transforms": "^7.29.7", - "@babel/helpers": "^7.29.7", - "@babel/parser": "^7.29.7", - "@babel/template": "^7.29.7", - "@babel/traverse": "^7.29.7", - "@babel/types": "^7.29.7", - "@jridgewell/remapping": "^2.3.5", - "convert-source-map": "^2.0.0", - "debug": "^4.1.0", - "gensync": "^1.0.0-beta.2", - "json5": "^2.2.3", - "semver": "^6.3.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/babel" - } - }, - "node_modules/@babel/generator": { - "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.29.7.tgz", - "integrity": "sha512-DkXD5OJQaAQIdZ1bt3UZdEnHAn9Imd3IVBdX03UFe+ony9Ojw5pzr9YVKGDY1jt+Gcn/FnGkNf8r+Vj5NOJWtQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/parser": "^7.29.7", - "@babel/types": "^7.29.7", - "@jridgewell/gen-mapping": "^0.3.12", - "@jridgewell/trace-mapping": "^0.3.28", - "jsesc": "^3.0.2" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-compilation-targets": { - "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.29.7.tgz", - "integrity": "sha512-wem6WaBj4NaVYVdNhLPPVacES6ZJ+KBBfSkTMD3YZxbP3rm3Di85tJU5ljaUNhaOynt+Aj0xruhYuzQBt8n71g==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/compat-data": "^7.29.7", - "@babel/helper-validator-option": "^7.29.7", - "browserslist": "^4.24.0", - "lru-cache": "^5.1.1", - "semver": "^6.3.1" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-globals": { - "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.29.7.tgz", - "integrity": "sha512-3nQVUAtvkKH9zahfWgw96Jc/uFOmjACE1kQz82E2lqWmHBgjzbNlsC22nuQTfahmWeQtTq5nQ/4Nnd2A1wj4zA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-module-imports": { - "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.29.7.tgz", - "integrity": "sha512-ejHwrQQYcm9xnTivShn2IDOlIzInN34AXskvq9QicvCtEzq1Vzclu/tKF8Jq1Cg8JG2GL6/EmjgsCT7lXepE3g==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/traverse": "^7.29.7", - "@babel/types": "^7.29.7" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-module-transforms": { - "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.29.7.tgz", - "integrity": "sha512-UPUVSyXbOh627KiCIGQSgwWzGeBKLkaJ9PJEdrngIwMSzxLR4jS4+f1f1jb7VzBbg8nFLaYotvVPFCTqdrmTAg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-module-imports": "^7.29.7", - "@babel/helper-validator-identifier": "^7.29.7", - "@babel/traverse": "^7.29.7" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" - } - }, - "node_modules/@babel/helper-string-parser": { - "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.29.7.tgz", - "integrity": "sha512-Pb5ijPrZ89GDH8223L4UP8i6QApWxs04RbPQJTeWDV0/keR2E36MeKnyr6LYmUUvqRRI+Iv87SuF1W6ErINzYw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-validator-identifier": { - "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.29.7.tgz", - "integrity": "sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-validator-option": { - "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.29.7.tgz", - "integrity": "sha512-N9ZErrD+yW5geCDtBqnOoxmR8+tNKiGuxKlDpuJxfsqpa2dFcexaziGAE/qoHLiDDreVNMupxGmSoNlyvsA3gw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helpers": { - "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.29.7.tgz", - "integrity": "sha512-1k2lAGRMfHTcwuNYcCNUmaUffmQv8KWMfh2iJUUeRlwlwH4FdNG7mfPI10NPfLHJFThE4Tyr4mv7kTNZOiPuBg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/template": "^7.29.7", - "@babel/types": "^7.29.7" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/parser": { - "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.7.tgz", - "integrity": "sha512-hnORnjP/1P/zFEndoeX+n+t1RwWRJiJpM/jO7FW32Kn9r5+sJB2JWOdYo4L6k78j15eCwY3Gm/7364B1EMwtNg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/types": "^7.29.7" - }, - "bin": { - "parser": "bin/babel-parser.js" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@babel/template": { - "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.29.7.tgz", - "integrity": "sha512-puq+Gf35oI24FeN11LkoUQFqv9uwNeWpxXZi/Ji3rRIoKAzKnxRaZ+Gkj0vKS9ZCiTESfng1N9LyOyXvo+m+Gg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/code-frame": "^7.29.7", - "@babel/parser": "^7.29.7", - "@babel/types": "^7.29.7" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/traverse": { - "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.29.7.tgz", - "integrity": "sha512-EhlfNQtZ+NK22w5BM61ciuiq1m58ed33Wr1Xan//ZRTy6hgjnwyCffRYwzsGXdASJSUJ1guZILsErh1eQcl+zw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/code-frame": "^7.29.7", - "@babel/generator": "^7.29.7", - "@babel/helper-globals": "^7.29.7", - "@babel/parser": "^7.29.7", - "@babel/template": "^7.29.7", - "@babel/types": "^7.29.7", - "debug": "^4.3.1" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/types": { - "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.7.tgz", - "integrity": "sha512-4zBIxpPzowiZpusoFkyGVwakdRJUyuH5PxQ/PrqghfdFWWasvnCdPfQXHrenDai+gyLARulZjZowCOj6fjT4pA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-string-parser": "^7.29.7", - "@babel/helper-validator-identifier": "^7.29.7" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@emnapi/core": { - "version": "1.10.0", - "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.10.0.tgz", - "integrity": "sha512-yq6OkJ4p82CAfPl0u9mQebQHKPJkY7WrIuk205cTYnYe+k2Z8YBh11FrbRG/H6ihirqcacOgl2BIO8oyMQLeXw==", - "dev": true, - "license": "MIT", - "optional": true, - "peer": true, - "dependencies": { - "@emnapi/wasi-threads": "1.2.1", - "tslib": "^2.4.0" - } - }, - "node_modules/@emnapi/runtime": { - "version": "1.11.3", - "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.11.3.tgz", - "integrity": "sha512-Xz4Tpyki7XyrpbUK1jR1AhdAdaXyhhY4lZ3neLodmhpuWfy2PAQN5B46sAiU4liOXGLkHypn/qU+jvfWSCYYLA==", - "license": "MIT", - "optional": true, - "dependencies": { - "tslib": "^2.4.0" - } - }, - "node_modules/@emnapi/wasi-threads": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.2.1.tgz", - "integrity": "sha512-uTII7OYF+/Mes/MrcIOYp5yOtSMLBWSIoLPpcgwipoiKbli6k322tcoFsxoIIxPDqW01SQGAgko4EzZi2BNv2w==", - "dev": true, - "license": "MIT", - "optional": true, - "peer": true, - "dependencies": { - "tslib": "^2.4.0" - } - }, - "node_modules/@eslint-community/eslint-utils": { - "version": "4.10.1", - "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.10.1.tgz", - "integrity": "sha512-cuadcxVFE8sDK6iWJbs8Sn0av2Nrh2QSGQhVlBW9AaAHqHwjWsZHT8LJ4hFGPh7ASBV2deFdM7H/DPjulmh8rg==", - "dev": true, - "license": "MIT", - "dependencies": { - "eslint-visitor-keys": "^3.4.3" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - }, - "peerDependencies": { - "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" - } - }, - "node_modules/@eslint-community/eslint-utils/node_modules/eslint-visitor-keys": { - "version": "3.4.3", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", - "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/@eslint-community/regexpp": { - "version": "4.12.2", - "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.2.tgz", - "integrity": "sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew==", - "dev": true, - "license": "MIT", - "engines": { - "node": "^12.0.0 || ^14.0.0 || >=16.0.0" - } - }, - "node_modules/@eslint/config-array": { - "version": "0.23.5", - "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.23.5.tgz", - "integrity": "sha512-Y3kKLvC1dvTOT+oGlqNQ1XLqK6D1HU2YXPc52NmAlJZbMMWDzGYXMiPRJ8TYD39muD/OTjlZmNJ4ib7dvSrMBA==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@eslint/object-schema": "^3.0.5", - "debug": "^4.3.1", - "minimatch": "^10.2.4" - }, - "engines": { - "node": "^20.19.0 || ^22.13.0 || >=24" - } - }, - "node_modules/@eslint/config-helpers": { - "version": "0.7.0", - "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.7.0.tgz", - "integrity": "sha512-DObd/KKUsU+FaFv4PLxSRenpXfQWmPXXP3pPZ6/K1PCrMu2vQpMDMuQe/BqYeoLcz8ro0bVDF1RxOJgfVEdhUw==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@eslint/core": "^1.2.1" - }, - "engines": { - "node": "^20.19.0 || ^22.13.0 || >=24" - } - }, - "node_modules/@eslint/core": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/@eslint/core/-/core-1.2.1.tgz", - "integrity": "sha512-MwcE1P+AZ4C6DWlpin/OmOA54mmIZ/+xZuJiQd4SyB29oAJjN30UW9wkKNptW2ctp4cEsvhlLY/CsQ1uoHDloQ==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@types/json-schema": "^7.0.15" - }, - "engines": { - "node": "^20.19.0 || ^22.13.0 || >=24" - } - }, - "node_modules/@eslint/js": { - "version": "10.0.1", - "resolved": "https://registry.npmjs.org/@eslint/js/-/js-10.0.1.tgz", - "integrity": "sha512-zeR9k5pd4gxjZ0abRoIaxdc7I3nDktoXZk2qOv9gCNWx3mVwEn32VRhyLaRsDiJjTs0xq/T8mfPtyuXu7GWBcA==", - "dev": true, - "license": "MIT", - "engines": { - "node": "^20.19.0 || ^22.13.0 || >=24" - }, - "funding": { - "url": "https://eslint.org/donate" - }, - "peerDependencies": { - "eslint": "^10.0.0" - }, - "peerDependenciesMeta": { - "eslint": { - "optional": true - } - } - }, - "node_modules/@eslint/object-schema": { - "version": "3.0.5", - "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-3.0.5.tgz", - "integrity": "sha512-vqTaUEgxzm+YDSdElad6PiRoX4t8VGDjCtt05zn4nU810UIx/uNEV7/lZJ6KwFThKZOzOxzXy48da+No7HZaMw==", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": "^20.19.0 || ^22.13.0 || >=24" - } - }, - "node_modules/@eslint/plugin-kit": { - "version": "0.7.2", - "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.7.2.tgz", - "integrity": "sha512-+CNAzxglkrpNf/kKywqQfk74QjtceuOE7Qm+AF8miRvPF/wmmK5+OJOgVh3AVTT3RP2mH3+FOaxlE5v72owk0A==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@eslint/core": "^1.2.1", - "levn": "^0.4.1" - }, - "engines": { - "node": "^20.19.0 || ^22.13.0 || >=24" - } - }, - "node_modules/@humanfs/core": { - "version": "0.19.2", - "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.2.tgz", - "integrity": "sha512-UhXNm+CFMWcbChXywFwkmhqjs3PRCmcSa/hfBgLIb7oQ5HNb1wS0icWsGtSAUNgefHeI+eBrA8I1fxmbHsGdvA==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@humanfs/types": "^0.15.0" - }, - "engines": { - "node": ">=18.18.0" - } - }, - "node_modules/@humanfs/node": { - "version": "0.16.8", - "resolved": "https://registry.npmjs.org/@humanfs/node/-/node-0.16.8.tgz", - "integrity": "sha512-gE1eQNZ3R++kTzFUpdGlpmy8kDZD/MLyHqDwqjkVQI0JMdI1D51sy1H958PNXYkM2rAac7e5/CnIKZrHtPh3BQ==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@humanfs/core": "^0.19.2", - "@humanfs/types": "^0.15.0", - "@humanwhocodes/retry": "^0.4.0" - }, - "engines": { - "node": ">=18.18.0" - } - }, - "node_modules/@humanfs/types": { - "version": "0.15.0", - "resolved": "https://registry.npmjs.org/@humanfs/types/-/types-0.15.0.tgz", - "integrity": "sha512-ZZ1w0aoQkwuUuC7Yf+7sdeaNfqQiiLcSRbfI08oAxqLtpXQr9AIVX7Ay7HLDuiLYAaFPu8oBYNq/QIi9URHJ3Q==", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": ">=18.18.0" - } - }, - "node_modules/@humanwhocodes/module-importer": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", - "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": ">=12.22" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/nzakas" - } - }, - "node_modules/@humanwhocodes/retry": { - "version": "0.4.3", - "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.4.3.tgz", - "integrity": "sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": ">=18.18" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/nzakas" - } - }, - "node_modules/@img/colour": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@img/colour/-/colour-1.1.0.tgz", - "integrity": "sha512-Td76q7j57o/tLVdgS746cYARfSyxk8iEfRxewL9h4OMzYhbW4TAcppl0mT4eyqXddh6L/jwoM75mo7ixa/pCeQ==", - "license": "MIT", - "optional": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@img/sharp-darwin-arm64": { - "version": "0.35.3", - "resolved": "https://registry.npmjs.org/@img/sharp-darwin-arm64/-/sharp-darwin-arm64-0.35.3.tgz", - "integrity": "sha512-RMnFX7YQsMoh7lWfcM4NEHHymBX/rLuKNPVM84XE9ONPcaSCDgE7CHIHpSgPcO2xcRthgBy1HfNO319mwhIAkg==", - "cpu": [ - "arm64" - ], - "license": "Apache-2.0", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">=20.9.0" - }, - "funding": { - "url": "https://opencollective.com/libvips" - }, - "optionalDependencies": { - "@img/sharp-libvips-darwin-arm64": "1.3.2" - } - }, - "node_modules/@img/sharp-darwin-x64": { - "version": "0.35.3", - "resolved": "https://registry.npmjs.org/@img/sharp-darwin-x64/-/sharp-darwin-x64-0.35.3.tgz", - "integrity": "sha512-Xo+5uFBtLN0BKqieTxiFzFPQAUlBbbH5iBKyRX/z1JrbnYsHTfKJnUfL8+p2TPXr1pXqao4eeL4Rl144uDpK9w==", - "cpu": [ - "x64" - ], - "license": "Apache-2.0", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">=20.9.0" - }, - "funding": { - "url": "https://opencollective.com/libvips" - }, - "optionalDependencies": { - "@img/sharp-libvips-darwin-x64": "1.3.2" - } - }, - "node_modules/@img/sharp-freebsd-wasm32": { - "version": "0.35.3", - "resolved": "https://registry.npmjs.org/@img/sharp-freebsd-wasm32/-/sharp-freebsd-wasm32-0.35.3.tgz", - "integrity": "sha512-lUxcqWIj2wMQ9BrwNjngcr1gWUr5xgaGThBRqPPalIC2n67Cqj1uPh8NnA/ZhAg8hUbKl+kVHKwgUIwe6ZYPrg==", - "license": "Apache-2.0", - "optional": true, - "os": [ - "freebsd" - ], - "dependencies": { - "@img/sharp-wasm32": "0.35.3" - }, - "engines": { - "node": ">=20.9.0" - }, - "funding": { - "url": "https://opencollective.com/libvips" - } - }, - "node_modules/@img/sharp-libvips-darwin-arm64": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-arm64/-/sharp-libvips-darwin-arm64-1.3.2.tgz", - "integrity": "sha512-9J6ypZFpQBj4YnePGoq/S38w6nz+vqg5WZLrLGY4YuSemdMq47GMLBPO42MzwdGwpg/agZ7xzZcFHa48xlywfg==", - "cpu": [ - "arm64" - ], - "license": "LGPL-3.0-or-later", - "optional": true, - "os": [ - "darwin" - ], - "funding": { - "url": "https://opencollective.com/libvips" - } - }, - "node_modules/@img/sharp-libvips-darwin-x64": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-x64/-/sharp-libvips-darwin-x64-1.3.2.tgz", - "integrity": "sha512-m2pW1n6cns9VaubNwsZ+c3CRYjxNQWgJ5gPlnL1nbBcpkBvFm6SCFN5o0psFHI8w9n11NKhFkeEDns98tiqbEw==", - "cpu": [ - "x64" - ], - "license": "LGPL-3.0-or-later", - "optional": true, - "os": [ - "darwin" - ], - "funding": { - "url": "https://opencollective.com/libvips" - } - }, - "node_modules/@img/sharp-libvips-linux-arm": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm/-/sharp-libvips-linux-arm-1.3.2.tgz", - "integrity": "sha512-1eMLzy92I4J6rmi4mAT8yC3HxOtniyGELlzGbNMLLeqe052ahFQ0h6LFq+lh5DsDIdYViIDst08abvSbcEdLXQ==", - "cpu": [ - "arm" - ], - "libc": [ - "glibc" - ], - "license": "LGPL-3.0-or-later", - "optional": true, - "os": [ - "linux" - ], - "funding": { - "url": "https://opencollective.com/libvips" - } - }, - "node_modules/@img/sharp-libvips-linux-arm64": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm64/-/sharp-libvips-linux-arm64-1.3.2.tgz", - "integrity": "sha512-dqVSFynCox4C/J8kT16V7SIFAns0IjgLwkvYT7p8LQVmJ5OS5b6tI9IGflxTeuBS//zXeFIUbwt5dwxyZ17cnA==", - "cpu": [ - "arm64" - ], - "libc": [ - "glibc" - ], - "license": "LGPL-3.0-or-later", - "optional": true, - "os": [ - "linux" - ], - "funding": { - "url": "https://opencollective.com/libvips" - } - }, - "node_modules/@img/sharp-libvips-linux-ppc64": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-ppc64/-/sharp-libvips-linux-ppc64-1.3.2.tgz", - "integrity": "sha512-3z0NHDxD6n5I9gc05U1eW1AyRm+Gznzq3naMrthPNqE6oYykcogW0l/jfpJdjYnuNl8R7yI9pNbE1XiUeyq0Aw==", - "cpu": [ - "ppc64" - ], - "libc": [ - "glibc" - ], - "license": "LGPL-3.0-or-later", - "optional": true, - "os": [ - "linux" - ], - "funding": { - "url": "https://opencollective.com/libvips" - } - }, - "node_modules/@img/sharp-libvips-linux-riscv64": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-riscv64/-/sharp-libvips-linux-riscv64-1.3.2.tgz", - "integrity": "sha512-bsb4rI+NldGOsXuej2r8OdSS8+zXDVaCWxyWrcv6kneTOlgAHtZABRzBBCwdsPiD90J4myNJuHpg6kA20ImW/w==", - "cpu": [ - "riscv64" - ], - "libc": [ - "glibc" - ], - "license": "LGPL-3.0-or-later", - "optional": true, - "os": [ - "linux" - ], - "funding": { - "url": "https://opencollective.com/libvips" - } - }, - "node_modules/@img/sharp-libvips-linux-s390x": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-s390x/-/sharp-libvips-linux-s390x-1.3.2.tgz", - "integrity": "sha512-/ABshyj8gCpyIrNXnHn4LorDJ0HHm1VhXPBlxZ8zAtfVPAaSafXPGn+sUSIRiwaSBy0mmFjSjiXI5mkcwdChKQ==", - "cpu": [ - "s390x" - ], - "libc": [ - "glibc" - ], - "license": "LGPL-3.0-or-later", - "optional": true, - "os": [ - "linux" - ], - "funding": { - "url": "https://opencollective.com/libvips" - } - }, - "node_modules/@img/sharp-libvips-linux-x64": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-x64/-/sharp-libvips-linux-x64-1.3.2.tgz", - "integrity": "sha512-ITPEtgffGJ0S6G9dRyw/366tJQqFRcHWPHhC+Stpg3Z8AEMrDrTr2lhdz4f/Y/HMbRh//7Z5mBzEpVdi62Oc3w==", - "cpu": [ - "x64" - ], - "libc": [ - "glibc" - ], - "license": "LGPL-3.0-or-later", - "optional": true, - "os": [ - "linux" - ], - "funding": { - "url": "https://opencollective.com/libvips" - } - }, - "node_modules/@img/sharp-libvips-linuxmusl-arm64": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-arm64/-/sharp-libvips-linuxmusl-arm64-1.3.2.tgz", - "integrity": "sha512-zE9EdiUzUmg5mDT5a1rk5fYJ6GWPloTwWBYDS14naqHsL+EaMpDj1AWnpLgh3u0YCORv2Tt50wrcrpYqkP97Kw==", - "cpu": [ - "arm64" - ], - "libc": [ - "musl" - ], - "license": "LGPL-3.0-or-later", - "optional": true, - "os": [ - "linux" - ], - "funding": { - "url": "https://opencollective.com/libvips" - } - }, - "node_modules/@img/sharp-libvips-linuxmusl-x64": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-x64/-/sharp-libvips-linuxmusl-x64-1.3.2.tgz", - "integrity": "sha512-m0lrLiUt+lBYnCFr8qV/65yMR4E/c7/wf78I5eKTdkEakFAlZ9QlzEM3QIhhAwVeUhLAHLcCq7a7Vszq/oFNZQ==", - "cpu": [ - "x64" - ], - "libc": [ - "musl" - ], - "license": "LGPL-3.0-or-later", - "optional": true, - "os": [ - "linux" - ], - "funding": { - "url": "https://opencollective.com/libvips" - } - }, - "node_modules/@img/sharp-linux-arm": { - "version": "0.35.3", - "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm/-/sharp-linux-arm-0.35.3.tgz", - "integrity": "sha512-affVWCTLooy8TSxbDx2qkzuDeaWLNVBA+P//FNBirHsXpP2fuBhk5AuboYUnrDnzoXes8GFjpTx0SBFOCRg+FA==", - "cpu": [ - "arm" - ], - "libc": [ - "glibc" - ], - "license": "Apache-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=20.9.0" - }, - "funding": { - "url": "https://opencollective.com/libvips" - }, - "optionalDependencies": { - "@img/sharp-libvips-linux-arm": "1.3.2" - } - }, - "node_modules/@img/sharp-linux-arm64": { - "version": "0.35.3", - "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm64/-/sharp-linux-arm64-0.35.3.tgz", - "integrity": "sha512-QgKDspHPnrU+GQ55XPhGwyhC8acLVOOSyAvo1oVfFmrIXLkDNmGWzAfDZ4xK8oSA1qBQrALcHX0G5UZni/SuFQ==", - "cpu": [ - "arm64" - ], - "libc": [ - "glibc" - ], - "license": "Apache-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=20.9.0" - }, - "funding": { - "url": "https://opencollective.com/libvips" - }, - "optionalDependencies": { - "@img/sharp-libvips-linux-arm64": "1.3.2" - } - }, - "node_modules/@img/sharp-linux-ppc64": { - "version": "0.35.3", - "resolved": "https://registry.npmjs.org/@img/sharp-linux-ppc64/-/sharp-linux-ppc64-0.35.3.tgz", - "integrity": "sha512-sMd8rDxmpLOwv/7N44klFjOD5DUO7FLdjiXDI0hoxYaf7Ar262dQIEkosE98bps+5HPLtp/EvNqeqQtOycP/IA==", - "cpu": [ - "ppc64" - ], - "libc": [ - "glibc" - ], - "license": "Apache-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=20.9.0" - }, - "funding": { - "url": "https://opencollective.com/libvips" - }, - "optionalDependencies": { - "@img/sharp-libvips-linux-ppc64": "1.3.2" - } - }, - "node_modules/@img/sharp-linux-riscv64": { - "version": "0.35.3", - "resolved": "https://registry.npmjs.org/@img/sharp-linux-riscv64/-/sharp-linux-riscv64-0.35.3.tgz", - "integrity": "sha512-0Eob78yjlYPfL5vMNWAW55l3R9Y6BQS/gOfe0ZcP9mEz9ohhKSt4im1hayiknXgf8AWrFqMvJcKIdmLmEe7yeQ==", - "cpu": [ - "riscv64" - ], - "libc": [ - "glibc" - ], - "license": "Apache-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=20.9.0" - }, - "funding": { - "url": "https://opencollective.com/libvips" - }, - "optionalDependencies": { - "@img/sharp-libvips-linux-riscv64": "1.3.2" - } - }, - "node_modules/@img/sharp-linux-s390x": { - "version": "0.35.3", - "resolved": "https://registry.npmjs.org/@img/sharp-linux-s390x/-/sharp-linux-s390x-0.35.3.tgz", - "integrity": "sha512-KgAxQ0DxpNOq1rG2t5cgTgShJFGSuU7XO45cqC+1NVOuZnP6tlgZRuSYOfNupGkHID0o3cJOsw4DVeJpMovcGw==", - "cpu": [ - "s390x" - ], - "libc": [ - "glibc" - ], - "license": "Apache-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=20.9.0" - }, - "funding": { - "url": "https://opencollective.com/libvips" - }, - "optionalDependencies": { - "@img/sharp-libvips-linux-s390x": "1.3.2" - } - }, - "node_modules/@img/sharp-linux-x64": { - "version": "0.35.3", - "resolved": "https://registry.npmjs.org/@img/sharp-linux-x64/-/sharp-linux-x64-0.35.3.tgz", - "integrity": "sha512-8pqvxubL2PGdhlPy6GLqzDYMUjyRmKAwKHYKixpdJYBUK7PJ0C029XdsnpFIdgRZG68fZiGdHVWcKPvtiPB4cA==", - "cpu": [ - "x64" - ], - "libc": [ - "glibc" - ], - "license": "Apache-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=20.9.0" - }, - "funding": { - "url": "https://opencollective.com/libvips" - }, - "optionalDependencies": { - "@img/sharp-libvips-linux-x64": "1.3.2" - } - }, - "node_modules/@img/sharp-linuxmusl-arm64": { - "version": "0.35.3", - "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-arm64/-/sharp-linuxmusl-arm64-0.35.3.tgz", - "integrity": "sha512-Vz0iQjzzcSX3HCbfwFfCSG/9SCIqyO0mH2sXyiHaAYfBk0cRsCWXRyQYX0ovCK/PAQBbTzQ0dsPQHh5MAFL59w==", - "cpu": [ - "arm64" - ], - "libc": [ - "musl" - ], - "license": "Apache-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=20.9.0" - }, - "funding": { - "url": "https://opencollective.com/libvips" - }, - "optionalDependencies": { - "@img/sharp-libvips-linuxmusl-arm64": "1.3.2" - } - }, - "node_modules/@img/sharp-linuxmusl-x64": { - "version": "0.35.3", - "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-x64/-/sharp-linuxmusl-x64-0.35.3.tgz", - "integrity": "sha512-6O1NPKcDVj9QEdg7Hx549EX8U0rp6yXQERqru6yRN7fGBn32UvIRJUlWnk+8xDCiG76hXVBbX82NZ/ZKr0euIg==", - "cpu": [ - "x64" - ], - "libc": [ - "musl" - ], - "license": "Apache-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=20.9.0" - }, - "funding": { - "url": "https://opencollective.com/libvips" - }, - "optionalDependencies": { - "@img/sharp-libvips-linuxmusl-x64": "1.3.2" - } - }, - "node_modules/@img/sharp-wasm32": { - "version": "0.35.3", - "resolved": "https://registry.npmjs.org/@img/sharp-wasm32/-/sharp-wasm32-0.35.3.tgz", - "integrity": "sha512-cZ0XkcYGpHZkqW6iCkqTcmUC0CD9DhD5d/qeZlZkfRBn6GnHniZXLUo5+9xw8Iv76YE6LQFN9YNBlKREcCG76w==", - "license": "Apache-2.0 AND LGPL-3.0-or-later AND MIT", - "optional": true, - "dependencies": { - "@emnapi/runtime": "^1.11.1" - }, - "engines": { - "node": ">=20.9.0" - }, - "funding": { - "url": "https://opencollective.com/libvips" - } - }, - "node_modules/@img/sharp-webcontainers-wasm32": { - "version": "0.35.3", - "resolved": "https://registry.npmjs.org/@img/sharp-webcontainers-wasm32/-/sharp-webcontainers-wasm32-0.35.3.tgz", - "integrity": "sha512-2rnq7bX3NzeR2T4YWgz8qiG4h3TSdMe+vN1iQXpJleSJ3SM5zQ8Fy2SyyXAWlbxpEZ2Y+Z4u1BePgJEYbSy80Q==", - "cpu": [ - "wasm32" - ], - "license": "Apache-2.0", - "optional": true, - "dependencies": { - "@img/sharp-wasm32": "0.35.3" - }, - "engines": { - "node": ">=20.9.0" - }, - "funding": { - "url": "https://opencollective.com/libvips" - } - }, - "node_modules/@img/sharp-win32-arm64": { - "version": "0.35.3", - "resolved": "https://registry.npmjs.org/@img/sharp-win32-arm64/-/sharp-win32-arm64-0.35.3.tgz", - "integrity": "sha512-4bPwFdMbeC4JQ8L8LOyWp6nsHcboP5fxkp6iPOXz2Vg49R42TuMs2whkJ5OAP4/Ul035qOzy0AecOF9VOscn4w==", - "cpu": [ - "arm64" - ], - "license": "Apache-2.0 AND LGPL-3.0-or-later", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=20.9.0" - }, - "funding": { - "url": "https://opencollective.com/libvips" - } - }, - "node_modules/@img/sharp-win32-ia32": { - "version": "0.35.3", - "resolved": "https://registry.npmjs.org/@img/sharp-win32-ia32/-/sharp-win32-ia32-0.35.3.tgz", - "integrity": "sha512-r53mXsBN6lFUDiST764SvgwUdHAqM4rPAiDzAmf4fLoB6X/rkfyTrLCg6+g17wJJiCmB3JYgHuUldCWUIRFSXw==", - "cpu": [ - "ia32" - ], - "license": "Apache-2.0 AND LGPL-3.0-or-later", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": "^20.9.0" - }, - "funding": { - "url": "https://opencollective.com/libvips" - } - }, - "node_modules/@img/sharp-win32-x64": { - "version": "0.35.3", - "resolved": "https://registry.npmjs.org/@img/sharp-win32-x64/-/sharp-win32-x64-0.35.3.tgz", - "integrity": "sha512-D4y1vNeZrIIJCN+uHaWVtH86B+aCrdMYYjicy9pXHvbGZeGYLLSd3wdVuC37FxVXlU1ARsk84eKWfWMXGYEqvA==", - "cpu": [ - "x64" - ], - "license": "Apache-2.0 AND LGPL-3.0-or-later", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=20.9.0" - }, - "funding": { - "url": "https://opencollective.com/libvips" - } - }, - "node_modules/@jridgewell/gen-mapping": { - "version": "0.3.13", - "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", - "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jridgewell/sourcemap-codec": "^1.5.0", - "@jridgewell/trace-mapping": "^0.3.24" - } - }, - "node_modules/@jridgewell/remapping": { - "version": "2.3.5", - "resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz", - "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jridgewell/gen-mapping": "^0.3.5", - "@jridgewell/trace-mapping": "^0.3.24" - } - }, - "node_modules/@jridgewell/resolve-uri": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", - "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@jridgewell/sourcemap-codec": { - "version": "1.5.5", - "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", - "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", - "dev": true, - "license": "MIT" - }, - "node_modules/@jridgewell/trace-mapping": { - "version": "0.3.31", - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", - "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jridgewell/resolve-uri": "^3.1.0", - "@jridgewell/sourcemap-codec": "^1.4.14" - } - }, - "node_modules/@napi-rs/wasm-runtime": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-1.2.1.tgz", - "integrity": "sha512-KjZdi8Q1wh89gsVmghvbrMgWl6ZWmRmHV6wjB7/g4Zf0dyO+hH3neZUtuDNPO00qq5YE5RITVWvrIZKRaAmzGQ==", - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "@tybys/wasm-util": "^0.10.3" - }, - "engines": { - "node": "^20.19.0 || ^22.13.0 || >=23.5.0" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/Brooooooklyn" - }, - "peerDependencies": { - "@emnapi/core": "^1.7.1 || ^2.0.0-alpha.3", - "@emnapi/runtime": "^1.7.1 || ^2.0.0-alpha.3" - } - }, - "node_modules/@next/env": { - "version": "16.2.12", - "resolved": "https://registry.npmjs.org/@next/env/-/env-16.2.12.tgz", - "integrity": "sha512-d0Z5Bc13Fa4nR8pFAKx2jay2yhJM16vlfHbTzYnUQAxlNb6B6lmn4hjt69lYNt4kRtyYP6gEM49lPRHNbIyneg==", - "license": "MIT" - }, - "node_modules/@next/eslint-plugin-next": { - "version": "16.2.12", - "resolved": "https://registry.npmjs.org/@next/eslint-plugin-next/-/eslint-plugin-next-16.2.12.tgz", - "integrity": "sha512-uF2z/qAK2q7B5/6CpnFcBRX6jOq5iCO+Uqh1UkJhXljX1JwLarLYhhoJadO6dPb6moTprOKewMXheBcbIoSbug==", - "dev": true, - "license": "MIT", - "dependencies": { - "fast-glob": "3.3.1" - } - }, - "node_modules/@next/swc-darwin-arm64": { - "version": "16.2.12", - "resolved": "https://registry.npmjs.org/@next/swc-darwin-arm64/-/swc-darwin-arm64-16.2.12.tgz", - "integrity": "sha512-0W1R0teHWJrqKX0FH20IzzIWAOuGtBxPGuObrxy1lE8hQvCFj49KE8a3WUg0D7sq6rn6zkM4c7YGUnhudBS6oA==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@next/swc-darwin-x64": { - "version": "16.2.12", - "resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-16.2.12.tgz", - "integrity": "sha512-Hy5Ls099+aFUmOLmIgPfLqNi6iCwhL3uQCssz5rWk+5Nkc6TUKCE83DY5BbNylfm3+mfwcSFnLRfrZDJhVxdtw==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@next/swc-linux-arm64-gnu": { - "version": "16.2.12", - "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-16.2.12.tgz", - "integrity": "sha512-+YqU2h1cQkHsGfvjAsrSmst8UIFBibBGm5x3Xgel8NLMiDQtNOM4sM2GOEMvG5YiOBNeN/Ykk8cQC2S0Xrqljg==", - "cpu": [ - "arm64" - ], - "libc": [ - "glibc" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@next/swc-linux-arm64-musl": { - "version": "16.2.12", - "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-16.2.12.tgz", - "integrity": "sha512-0qjhiYBaKAqF63LA1ZWAAnKTzFUguAaZiRa5etMLGGPj/B6uEVjtIZldIzFEp3wHlB0koK6aTzqPtSdplTCjoA==", - "cpu": [ - "arm64" - ], - "libc": [ - "musl" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@next/swc-linux-x64-gnu": { - "version": "16.2.12", - "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-16.2.12.tgz", - "integrity": "sha512-7A3q26W+h7gnA15uqBToNuDqBEFZZcqh0mW2mn4AJh/G5pdg2RVE3n4slzLEliASZFG3NmsbEzng/x2Sh09mBg==", - "cpu": [ - "x64" - ], - "libc": [ - "glibc" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@next/swc-linux-x64-musl": { - "version": "16.2.12", - "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-16.2.12.tgz", - "integrity": "sha512-qSjL/uppm+cbh21s72Ss8gkiOhQ4dExWHNGOWy6eZV7STj5WsKehgxT61beSsOj+YYQuTplL376lOCdMQU5T8w==", - "cpu": [ - "x64" - ], - "libc": [ - "musl" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@next/swc-win32-arm64-msvc": { - "version": "16.2.12", - "resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-16.2.12.tgz", - "integrity": "sha512-X6hzsOUJac/e7AWSbn9gQ9nzHld1xWP5iyjHpYWvud8pufB679O1xg4JDyKr8Xd69Jvd+kM2Der6uftiZCmjYA==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@next/swc-win32-x64-msvc": { - "version": "16.2.12", - "resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-16.2.12.tgz", - "integrity": "sha512-F6fakeHuFTLOPt0bslQJdf+xtT+WIP9DVn/m4y1w1mRnVPyh3D/cNvzlRkxM444xfm+IvvYNSOrKiA2CDJ0Uxw==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@nodelib/fs.scandir": { - "version": "2.1.5", - "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", - "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", - "dev": true, - "license": "MIT", - "dependencies": { - "@nodelib/fs.stat": "2.0.5", - "run-parallel": "^1.1.9" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/@nodelib/fs.stat": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", - "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 8" - } - }, - "node_modules/@nodelib/fs.walk": { - "version": "1.2.8", - "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", - "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@nodelib/fs.scandir": "2.1.5", - "fastq": "^1.6.0" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/@oxc-project/types": { - "version": "0.142.0", - "resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.142.0.tgz", - "integrity": "sha512-7W+2q5AKQVU36fkaryontrHn3YDt1RyUYXatw9i5H8ocYe2sPKSFB6eS8WNPeRKiN1qAWWZUPm7gwFzJGrccqQ==", - "dev": true, - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/Boshen" - } - }, - "node_modules/@rolldown/binding-android-arm64": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.2.1.tgz", - "integrity": "sha512-02hOeOSryYxVrOIphmLAsqnCJWxwlzFk+pEt/N/i6OgT3lShHO7xGCU5cpgchRDHboAEbSjzgGh+O/u1GswQmA==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@rolldown/binding-darwin-arm64": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.2.1.tgz", - "integrity": "sha512-fMsTOnN0OjFm3CyppWPitKnc8UlliVARUULW6cfU6AIqjdtgmSFWSk9vecHzZduv/yMWIHDlRhM1e8Iff9uAfA==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@rolldown/binding-darwin-x64": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.2.1.tgz", - "integrity": "sha512-1wjKdz/XLGKHaTNHjQveQ/B23TKx4ItAqm1JbyVuvNPc4Ze0Fb48s49TAd/2zcplPl8okE/UbTgmlVfwT7eFeQ==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@rolldown/binding-freebsd-x64": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.2.1.tgz", - "integrity": "sha512-Fa0jHR07E7YBN4vOEsbVf2briYNsuOowfLJaXULZM0ldMlaCaj2LJgLMbMe4iacRyZmvR8efFhgR9wKuGclQUg==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@rolldown/binding-linux-arm-gnueabihf": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.2.1.tgz", - "integrity": "sha512-pzkgu1SSHGgRRyRZ4fbmSgmajbVt+epaLP99NDjFft69v/ypfTi6swBMiVdh2EkQ0OSnHE1lZDM7DRGkyAzUpA==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@rolldown/binding-linux-arm64-gnu": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.2.1.tgz", - "integrity": "sha512-QI5SEDY8cbiYWHx0VO4vIc3UlS6a32vXHjU8Qy/17adEmZIPuByJg13UEvo9c/UCiUkdcVWY83C+b+JrwnNyUg==", - "cpu": [ - "arm64" - ], - "dev": true, - "libc": [ - "glibc" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@rolldown/binding-linux-arm64-musl": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.2.1.tgz", - "integrity": "sha512-Sm41FyCeXqmYcERoYOCbGIL5hNfd8w9LQ7Y61Bev48HkcjaJqV/iiVOaiDxjVTRMS+QKrZmD8cfPt4uMVnvM+A==", - "cpu": [ - "arm64" - ], - "dev": true, - "libc": [ - "musl" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@rolldown/binding-linux-ppc64-gnu": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.2.1.tgz", - "integrity": "sha512-2x+WhXTGl9yJYPbltW/BSEPTVz9OIWQyER4N+gJEDWkkn904eRcBzELqh/Hf7K0w/ubGbKNMv0ZC+94QK/IFEg==", - "cpu": [ - "ppc64" - ], - "dev": true, - "libc": [ - "glibc" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@rolldown/binding-linux-s390x-gnu": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.2.1.tgz", - "integrity": "sha512-eEjmQpuRQayHPWWnywaWHkFT3ToPbP3RYy42VVd/B9aBGDA+Ol25EIWHxKQST3IiWJjikCWUF7KtbfqwZrzVwQ==", - "cpu": [ - "s390x" - ], - "dev": true, - "libc": [ - "glibc" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@rolldown/binding-linux-x64-gnu": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.2.1.tgz", - "integrity": "sha512-/Orga1fZYkLc/56jBICcHrKchl8Z2UKdDSr3LG9ToWO1lQ6a4Livk9Xz+9WN91zsz5QR3XQz2NNoSDEvP6qadw==", - "cpu": [ - "x64" - ], - "dev": true, - "libc": [ - "glibc" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@rolldown/binding-linux-x64-musl": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.2.1.tgz", - "integrity": "sha512-xxBJRL+0q0Kce7orznGWLuylHDY65vuARXZRpX+hPdv+DqK2c3NlCsVA98tlWzWNEE7yPqA/1NQ5nnCrj49Y5A==", - "cpu": [ - "x64" - ], - "dev": true, - "libc": [ - "musl" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@rolldown/binding-openharmony-arm64": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.2.1.tgz", - "integrity": "sha512-M6AdXIXw3s+/8XpKMzdGDEXGS1S7kwUsy+rcTIUIOx5Ge4nXKCtAFHFV9YKkXvGcC5WMoTjAteLzlsQROVI0Yw==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "openharmony" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@rolldown/binding-wasm32-wasi": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/@rolldown/binding-wasm32-wasi/-/binding-wasm32-wasi-1.2.1.tgz", - "integrity": "sha512-/TX0SoRGojHzSAHpfVBbavRVSazg5U3h3Y3VXfcc0cdugq6kxdqw8LPGFiPr+/7gE/60zRcsOY2Vi9b9eT0jww==", - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "@emnapi/core": "2.0.0-alpha.3", - "@emnapi/runtime": "2.0.0-alpha.3", - "@napi-rs/wasm-runtime": "^1.2.0" - }, - "engines": { - "node": "^20.19.0 || ^22.13.0 || >=23.5.0" - } - }, - "node_modules/@rolldown/binding-wasm32-wasi/node_modules/@emnapi/core": { - "version": "2.0.0-alpha.3", - "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-2.0.0-alpha.3.tgz", - "integrity": "sha512-AZypUeJ/yByuxyS7BlSNRDOMLMlROYtjYdIAuBmJssVz1UJDSeYxLrdizhXCFYhedC5bqd/ASy8EuNXbVVXp9g==", - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "@emnapi/wasi-threads": "2.0.1", - "tslib": "^2.4.0" - } - }, - "node_modules/@rolldown/binding-wasm32-wasi/node_modules/@emnapi/runtime": { - "version": "2.0.0-alpha.3", - "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-2.0.0-alpha.3.tgz", - "integrity": "sha512-hFPAhMUjJD9BSyCANEISPOogeXC9Zo9ZQl7L6vKnaVsMkCtzznaW/naYypeyl0Gv5rYfWYsZbpixTMpjDJzQeA==", - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "tslib": "^2.4.0" - } - }, - "node_modules/@rolldown/binding-wasm32-wasi/node_modules/@emnapi/wasi-threads": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-2.0.1.tgz", - "integrity": "sha512-9DsSk+o5NBX0CCJT8s0EROGSGxjR/tKu6aBTaVyq+SjAEQH4XcdcRxPBRzsBLizTTJ49MJjF+jgu3qnO9GLQcQ==", - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "tslib": "^2.4.0" - } - }, - "node_modules/@rolldown/binding-win32-arm64-msvc": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.2.1.tgz", - "integrity": "sha512-EvRrivJieyHG+AO9lleZWgq+g0+S7oV2C51yuqlcyU/R9net+sI4Pj0F+lUoP2bEr6TWX3SqFaaS0SzfLxSzkw==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@rolldown/binding-win32-x64-msvc": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.2.1.tgz", - "integrity": "sha512-Z4eCmn5QJ/5+azF9knpLWKfVd9aidn0mAe9TpJgvBLId9Ax3t0+JVxBmT25Bv7NBbVW1TZyKjQjQReouMeH5UQ==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@rolldown/pluginutils": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.1.tgz", - "integrity": "sha512-2j9bGt5Jh8hj+vPtgzPtl72j0yRxHAyumoo6TNfAjsLB04UtpSvPbPcDcBMxz7n+9CYB0c1GxQFxYRg2jimqGw==", - "dev": true, - "license": "MIT" - }, - "node_modules/@standard-schema/spec": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@standard-schema/spec/-/spec-1.1.0.tgz", - "integrity": "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==", - "dev": true, - "license": "MIT" - }, - "node_modules/@swc/helpers": { - "version": "0.5.15", - "resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.15.tgz", - "integrity": "sha512-JQ5TuMi45Owi4/BIMAJBoSQoOJu12oOk/gADqlcUL9JEdHB8vyjUSsxqeNXnmXHjYKMi2WcYtezGEEhqUI/E2g==", - "license": "Apache-2.0", - "dependencies": { - "tslib": "^2.8.0" - } - }, - "node_modules/@tybys/wasm-util": { - "version": "0.10.3", - "resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.3.tgz", - "integrity": "sha512-F3fo1MYrRJYL3zER0OUOmkutjr1Vp23m7OsSgp7nq4SP6OqX6C/56XFIPAl5bt3zaBRjmW7SGz3u/6LwFpYcOg==", - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "tslib": "^2.4.0" - } - }, - "node_modules/@types/chai": { - "version": "5.2.3", - "resolved": "https://registry.npmjs.org/@types/chai/-/chai-5.2.3.tgz", - "integrity": "sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/deep-eql": "*", - "assertion-error": "^2.0.1" - } - }, - "node_modules/@types/deep-eql": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/@types/deep-eql/-/deep-eql-4.0.2.tgz", - "integrity": "sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==", - "dev": true, - "license": "MIT" - }, - "node_modules/@types/esrecurse": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/@types/esrecurse/-/esrecurse-4.3.1.tgz", - "integrity": "sha512-xJBAbDifo5hpffDBuHl0Y8ywswbiAp/Wi7Y/GtAgSlZyIABppyurxVueOPE8LUQOxdlgi6Zqce7uoEpqNTeiUw==", - "dev": true, - "license": "MIT" - }, - "node_modules/@types/estree": { - "version": "1.0.9", - "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.9.tgz", - "integrity": "sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==", - "dev": true, - "license": "MIT" - }, - "node_modules/@types/json-schema": { - "version": "7.0.15", - "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", - "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", - "dev": true, - "license": "MIT" - }, - "node_modules/@types/node": { - "version": "20.19.43", - "resolved": "https://registry.npmjs.org/@types/node/-/node-20.19.43.tgz", - "integrity": "sha512-6oYBAi5ikg4Pl+kGsoYtawUMBT2zZMCvPNF7pVLnHZfd1zf38DRiWn/gT01RYCdUqkv7Fhr+C9ot4/tb+2sVvA==", - "dev": true, - "license": "MIT", - "dependencies": { - "undici-types": "~6.21.0" - } - }, - "node_modules/@types/react": { - "version": "19.2.18", - "resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.18.tgz", - "integrity": "sha512-AnzbBERsrLKtk2XSfTbYRLjQPdy116Sty4q+T+Bp3IC4l6jNBvreVPAHmpq9qhXQM7CXZPjLVmGMw9sy+hxQ3w==", - "dev": true, - "license": "MIT", - "dependencies": { - "csstype": "^3.2.2" - } - }, - "node_modules/@types/react-dom": { - "version": "19.2.4", - "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.2.4.tgz", - "integrity": "sha512-Bsc+QHgp+P/F02XDzNCY9jnZNCUuLki36KT7VKrTXXLdHf+vHMNZnW1rVu5DNW/rCK+fya3DATySbLM4yhtKUw==", - "dev": true, - "license": "MIT", - "peerDependencies": { - "@types/react": "^19.2.0" - } - }, - "node_modules/@typescript-eslint/eslint-plugin": { - "version": "8.65.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.65.0.tgz", - "integrity": "sha512-IEgob78X12rHpUmtcwFsXhZdVGJtwTVP8FiCLZkR6GlYVrl2PcuB+KhCE5BlVC/eQpQnu8WXRtkHZuPar+gCRA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@eslint-community/regexpp": "^4.12.2", - "@typescript-eslint/scope-manager": "8.65.0", - "@typescript-eslint/type-utils": "8.65.0", - "@typescript-eslint/utils": "8.65.0", - "@typescript-eslint/visitor-keys": "8.65.0", - "ignore": "^7.0.5", - "natural-compare": "^1.4.0", - "ts-api-utils": "^2.5.0" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "@typescript-eslint/parser": "^8.65.0", - "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", - "typescript": ">=4.8.4 <6.1.0" - } - }, - "node_modules/@typescript-eslint/eslint-plugin/node_modules/ignore": { - "version": "7.0.6", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.6.tgz", - "integrity": "sha512-BAg6QkE8W+TuQLrrw0Ugr7HegXduRuuj8/ti2kSOc+jz1dmx8/WNcjr6XGnq5YpDWxFwwaavqD0+jIUOKelTsw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 4" - } - }, - "node_modules/@typescript-eslint/parser": { - "version": "8.65.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.65.0.tgz", - "integrity": "sha512-CZ4nMxWwgu1HEEFNkeaCptra9QCtkmKdgf3sWh1rl1trIhmxLilgTV4cwcbQ4wemnT4sWQN8CaKOmdYx+g2gMA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@typescript-eslint/scope-manager": "8.65.0", - "@typescript-eslint/types": "8.65.0", - "@typescript-eslint/typescript-estree": "8.65.0", - "@typescript-eslint/visitor-keys": "8.65.0", - "debug": "^4.4.3" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", - "typescript": ">=4.8.4 <6.1.0" - } - }, - "node_modules/@typescript-eslint/project-service": { - "version": "8.65.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.65.0.tgz", - "integrity": "sha512-SxnPhbTsGahizDgbu7oqFH/xVtzIqMd/s+WtnSxNxJZJpLbdT5IPdzg8EZxO3+PoKahXmwJLeNQOpKJb3/bi7Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "@typescript-eslint/tsconfig-utils": "^8.65.0", - "@typescript-eslint/types": "^8.65.0", - "debug": "^4.4.3" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "typescript": ">=4.8.4 <6.1.0" - } - }, - "node_modules/@typescript-eslint/scope-manager": { - "version": "8.65.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.65.0.tgz", - "integrity": "sha512-Esbl8OSYiVxBokYgWPf7VVWg/BE798wXhimnn9ML9Pt5qoDf8bfQlgjlKXR/k98+AcNzlLKYrpCcrcuZ9DZLgg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@typescript-eslint/types": "8.65.0", - "@typescript-eslint/visitor-keys": "8.65.0" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - } - }, - "node_modules/@typescript-eslint/tsconfig-utils": { - "version": "8.65.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.65.0.tgz", - "integrity": "sha512-j6GzGqCiRdA7Qhur2VVmKZAkBLfnHFQfx4TaJGL9RMveZqCo48jSHHO0DTgizEnGhtWnqmbtCUSrqSkdiY/0Hg==", - "dev": true, - "license": "MIT", - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "typescript": ">=4.8.4 <6.1.0" - } - }, - "node_modules/@typescript-eslint/type-utils": { - "version": "8.65.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.65.0.tgz", - "integrity": "sha512-YjaZ7PRI5qY7ax2L3PbvX0rRyGtipAReCWs0mhhDBHjH/vl0g0BonaGXrKdKpMbIIsMIwDgbk/xzkBTyAltS5g==", - "dev": true, - "license": "MIT", - "dependencies": { - "@typescript-eslint/types": "8.65.0", - "@typescript-eslint/typescript-estree": "8.65.0", - "@typescript-eslint/utils": "8.65.0", - "debug": "^4.4.3", - "ts-api-utils": "^2.5.0" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", - "typescript": ">=4.8.4 <6.1.0" - } - }, - "node_modules/@typescript-eslint/types": { - "version": "8.65.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.65.0.tgz", - "integrity": "sha512-JSSwWNy+H0E/01jJEM+hrX6N0OFDzFzeIhHFSAS01tlVaevpG8cFyYRPhS5yjGOvBUx3sqQHVMjCL1CAZZMxBg==", - "dev": true, - "license": "MIT", - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - } - }, - "node_modules/@typescript-eslint/typescript-estree": { - "version": "8.65.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.65.0.tgz", - "integrity": "sha512-JboAE2swaYt4tb1fHhHTABE2K+OLy09XfcTbhnk4Pw96f9dd2e9iYsJ28gBggHlo5z5x1rkyWvcPoTuNTd4oGg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@typescript-eslint/project-service": "8.65.0", - "@typescript-eslint/tsconfig-utils": "8.65.0", - "@typescript-eslint/types": "8.65.0", - "@typescript-eslint/visitor-keys": "8.65.0", - "debug": "^4.4.3", - "minimatch": "^10.2.2", - "semver": "^7.7.3", - "tinyglobby": "^0.2.15", - "ts-api-utils": "^2.5.0" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "typescript": ">=4.8.4 <6.1.0" - } - }, - "node_modules/@typescript-eslint/typescript-estree/node_modules/semver": { - "version": "7.8.5", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.5.tgz", - "integrity": "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==", - "dev": true, - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/@typescript-eslint/utils": { - "version": "8.65.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.65.0.tgz", - "integrity": "sha512-gXiwIHsYreboxeJucHKPvgwl7dXt50mF8s1/c00cP/WoVTyWKFdtfhRWwZiXYFU5H2O8vVoSLNrexFZjYS/SGA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@eslint-community/eslint-utils": "^4.9.1", - "@typescript-eslint/scope-manager": "8.65.0", - "@typescript-eslint/types": "8.65.0", - "@typescript-eslint/typescript-estree": "8.65.0" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", - "typescript": ">=4.8.4 <6.1.0" - } - }, - "node_modules/@typescript-eslint/visitor-keys": { - "version": "8.65.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.65.0.tgz", - "integrity": "sha512-8C71BQkGjiMmXtop7pHVJu1l2NNShFdkCyD6a2ezzs5vU/L3LRtb69EtcteFwz0mYMPzIgOw0n6OV4VBUWZd7A==", - "dev": true, - "license": "MIT", - "dependencies": { - "@typescript-eslint/types": "8.65.0", - "eslint-visitor-keys": "^5.0.0" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - } - }, - "node_modules/@vitest/expect": { - "version": "4.1.10", - "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-4.1.10.tgz", - "integrity": "sha512-YsCn+qAk1GWjQOWFEsEcL2gNQ0zmVmQu3T03qP6UyjhtmdtwtbuI+DASn/7iQB3HGTXkdBwGddzxPlmiql5vlA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@standard-schema/spec": "^1.1.0", - "@types/chai": "^5.2.2", - "@vitest/spy": "4.1.10", - "@vitest/utils": "4.1.10", - "chai": "^6.2.2", - "tinyrainbow": "^3.1.0" - }, - "funding": { - "url": "https://opencollective.com/vitest" - } - }, - "node_modules/@vitest/mocker": { - "version": "4.1.10", - "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-4.1.10.tgz", - "integrity": "sha512-v0xaezt+DKEmKfaxg133ldzADrwLGd7Ze1MfQQTYfvs8OqZIwbxyxaYURivwV7sWy5fqn3rH5uOrSp07bp44Ow==", - "dev": true, - "license": "MIT", - "dependencies": { - "@vitest/spy": "4.1.10", - "estree-walker": "^3.0.3", - "magic-string": "^0.30.21" - }, - "funding": { - "url": "https://opencollective.com/vitest" - }, - "peerDependencies": { - "msw": "^2.4.9", - "vite": "^6.0.0 || ^7.0.0 || ^8.0.0" - }, - "peerDependenciesMeta": { - "msw": { - "optional": true - }, - "vite": { - "optional": true - } - } - }, - "node_modules/@vitest/pretty-format": { - "version": "4.1.10", - "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-4.1.10.tgz", - "integrity": "sha512-W1HsjSH4MXQ9YfmmhLAoIYf1HRfekQCGngeIgcei6MP5QQGWUe0gkopdZQaVCFO+JDJMrAJGwa5pRpNpvy4P8Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "tinyrainbow": "^3.1.0" - }, - "funding": { - "url": "https://opencollective.com/vitest" - } - }, - "node_modules/@vitest/runner": { - "version": "4.1.10", - "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-4.1.10.tgz", - "integrity": "sha512-IKI6kpIH+LmpROplyLwBBaCfMgOZOMsygVa6BARD6ahA04VRuJSa6OaVG7kRvSEMD870Vd91rSSw0eegtWyLGg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@vitest/utils": "4.1.10", - "pathe": "^2.0.3" - }, - "funding": { - "url": "https://opencollective.com/vitest" - } - }, - "node_modules/@vitest/snapshot": { - "version": "4.1.10", - "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-4.1.10.tgz", - "integrity": "sha512-xRkfOT1qpTAi/Ti4Y1LtfRc3kEuqxGw59eN2jN9pRWMtS/XDevekhcFSqvQqjUNGksfjMJu3Y+oJ+4Ypn2OaJw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@vitest/pretty-format": "4.1.10", - "@vitest/utils": "4.1.10", - "magic-string": "^0.30.21", - "pathe": "^2.0.3" - }, - "funding": { - "url": "https://opencollective.com/vitest" - } - }, - "node_modules/@vitest/spy": { - "version": "4.1.10", - "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-4.1.10.tgz", - "integrity": "sha512-PLf/Ugvoq5wO/b4rwYCR1h2PSIdXz7wnkQFMiUpLdtM7l6pqVFcQIBEHyT1+l+cj7mNwAfZHzqXqDyjvOuwbDw==", - "dev": true, - "license": "MIT", - "funding": { - "url": "https://opencollective.com/vitest" - } - }, - "node_modules/@vitest/utils": { - "version": "4.1.10", - "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-4.1.10.tgz", - "integrity": "sha512-fy9am/HWxbaGt/Sawrp90vt6Y6jQwf1RX77cz3uwoJwJVMli/e1IEwRPnMNJ7vKfPTwo0diXifkpPvwH9v7nGA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@vitest/pretty-format": "4.1.10", - "convert-source-map": "^2.0.0", - "tinyrainbow": "^3.1.0" - }, - "funding": { - "url": "https://opencollective.com/vitest" - } - }, - "node_modules/acorn": { - "version": "8.18.0", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.18.0.tgz", - "integrity": "sha512-lGq+9yr1/GuAWaVYIHRjvvySG5/4VfKIvC8EWxStPdcDh/Ka7FG3twP6v4d5BkravUilhIAsG4Qj83t02LWUPQ==", - "dev": true, - "license": "MIT", - "bin": { - "acorn": "bin/acorn" - }, - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/acorn-jsx": { - "version": "5.3.2", - "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", - "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", - "dev": true, - "license": "MIT", - "peerDependencies": { - "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" - } - }, - "node_modules/ajv": { - "version": "6.15.0", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.15.0.tgz", - "integrity": "sha512-fgFx7Hfoq60ytK2c7DhnF8jIvzYgOMxfugjLOSMHjLIPgenqa7S7oaagATUq99mV6IYvN2tRmC0wnTYX6iPbMw==", - "dev": true, - "license": "MIT", - "dependencies": { - "fast-deep-equal": "^3.1.1", - "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.4.1", - "uri-js": "^4.2.2" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" - } - }, - "node_modules/assertion-error": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-2.0.1.tgz", - "integrity": "sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" - } - }, - "node_modules/balanced-match": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", - "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==", - "dev": true, - "license": "MIT", - "engines": { - "node": "18 || 20 || >=22" - } - }, - "node_modules/baseline-browser-mapping": { - "version": "2.11.8", - "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.11.8.tgz", - "integrity": "sha512-zAgkquC2WYF0PIc6XbNYkA2uuxxFavzgmX61R+dHDUa558V8Ejf8ozTZFR6QzM24RWu4kBcRkhJ5kpz77j9fnQ==", - "license": "Apache-2.0", - "bin": { - "baseline-browser-mapping": "dist/cli.cjs" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/brace-expansion": { - "version": "5.0.9", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.9.tgz", - "integrity": "sha512-ScQ4IuvIEF1TMlP7Zt+vjJ//9zlPb2SDcxWxM3bk8s6t6GGdJ7KO1dCcTidOPJKePW30LE/2cT7wCyPho9/Wxg==", - "dev": true, - "license": "MIT", - "dependencies": { - "balanced-match": "^4.0.2" - }, - "engines": { - "node": "20 || >=22" - } - }, - "node_modules/braces": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", - "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", - "dev": true, - "license": "MIT", - "dependencies": { - "fill-range": "^7.1.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/browserslist": { - "version": "4.28.7", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.7.tgz", - "integrity": "sha512-JxV13hNrFxqjOc8alRbq9dK1MM79NEXYpma2B2J4wAtpWS5zIEIKqWPGCl7N4o7Uc7B7itylh7SuDujATRyyTw==", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/browserslist" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "dependencies": { - "baseline-browser-mapping": "^2.10.44", - "caniuse-lite": "^1.0.30001806", - "electron-to-chromium": "^1.5.393", - "node-releases": "^2.0.51", - "update-browserslist-db": "^1.2.3" - }, - "bin": { - "browserslist": "cli.js" - }, - "engines": { - "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" - } - }, - "node_modules/caniuse-lite": { - "version": "1.0.30001806", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001806.tgz", - "integrity": "sha512-72Cuvd95zbSYPKq6Fhg8eDJRlzgWDf7/mtoZv6Qe/DYNCEBdNxoA3+rZAU2ZhGCpZlns3EssFavaZomckT5Uuw==", - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/caniuse-lite" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "CC-BY-4.0" - }, - "node_modules/chai": { - "version": "6.2.2", - "resolved": "https://registry.npmjs.org/chai/-/chai-6.2.2.tgz", - "integrity": "sha512-NUPRluOfOiTKBKvWPtSD4PhFvWCqOi0BGStNWs57X9js7XGTprSmFoz5F0tWhR4WPjNeR9jXqdC7/UpSJTnlRg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=18" - } - }, - "node_modules/client-only": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/client-only/-/client-only-0.0.1.tgz", - "integrity": "sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA==", - "license": "MIT" - }, - "node_modules/clsx": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz", - "integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==", - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/convert-source-map": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", - "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", - "dev": true, - "license": "MIT" - }, - "node_modules/cross-spawn": { - "version": "7.0.6", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", - "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", - "dev": true, - "license": "MIT", - "dependencies": { - "path-key": "^3.1.0", - "shebang-command": "^2.0.0", - "which": "^2.0.1" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/csstype": { - "version": "3.2.3", - "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz", - "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/debug": { - "version": "4.4.3", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", - "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", - "dev": true, - "license": "MIT", - "dependencies": { - "ms": "^2.1.3" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/deep-is": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", - "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/dequal": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz", - "integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==", - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/detect-libc": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", - "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==", - "devOptional": true, - "license": "Apache-2.0", - "engines": { - "node": ">=8" - } - }, - "node_modules/electron-to-chromium": { - "version": "1.5.398", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.398.tgz", - "integrity": "sha512-AsvhAxopJGh6museTDMIjn6JpDYOfgu4RLlygomt87MUwBUqTfd/1EiPtx10/LZE8xpTvkP2E9Gafq7lkLtodQ==", - "dev": true, - "license": "ISC" - }, - "node_modules/es-module-lexer": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-2.3.1.tgz", - "integrity": "sha512-shc1dbU90Yl/xq1QrC7QRtfcwURZuVRfPhZbDoldJ1cn1gzDvBaBWlv0eFolj5+0znnPJz5TXLxsN77X/12KTA==", - "dev": true, - "license": "MIT" - }, - "node_modules/escalade": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", - "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/escape-string-regexp": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", - "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/eslint": { - "version": "10.8.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-10.8.0.tgz", - "integrity": "sha512-nuKKvN+oIBO0koN7Tm7dlkmnkc21mtt0QJLwAKzjLq14y6lRTdVG36MZHJ8eQHwdJMwZbQNMlPOYedMq/oVJvQ==", - "dev": true, - "license": "MIT", - "workspaces": [ - "packages/*" - ], - "dependencies": { - "@eslint-community/eslint-utils": "^4.8.0", - "@eslint-community/regexpp": "^4.12.2", - "@eslint/config-array": "^0.23.5", - "@eslint/config-helpers": "^0.7.0", - "@eslint/core": "^1.2.1", - "@eslint/plugin-kit": "^0.7.2", - "@humanfs/node": "^0.16.6", - "@humanwhocodes/module-importer": "^1.0.1", - "@humanwhocodes/retry": "^0.4.2", - "@types/estree": "^1.0.6", - "ajv": "^6.14.0", - "cross-spawn": "^7.0.6", - "debug": "^4.3.2", - "escape-string-regexp": "^4.0.0", - "eslint-scope": "^9.1.2", - "eslint-visitor-keys": "^5.0.1", - "espree": "^11.2.0", - "esquery": "^1.7.0", - "esutils": "^2.0.2", - "fast-deep-equal": "^3.1.3", - "file-entry-cache": "^8.0.0", - "find-up": "^5.0.0", - "glob-parent": "^6.0.2", - "ignore": "^5.2.0", - "imurmurhash": "^0.1.4", - "is-glob": "^4.0.0", - "json-stable-stringify-without-jsonify": "^1.0.1", - "minimatch": "^10.2.5", - "natural-compare": "^1.4.0", - "optionator": "^0.9.3" - }, - "bin": { - "eslint": "bin/eslint.js" - }, - "engines": { - "node": "^20.19.0 || ^22.13.0 || >=24" - }, - "funding": { - "url": "https://eslint.org/donate" - }, - "peerDependencies": { - "jiti": "*" - }, - "peerDependenciesMeta": { - "jiti": { - "optional": true - } - } - }, - "node_modules/eslint-plugin-react-hooks": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-7.1.1.tgz", - "integrity": "sha512-f2I7Gw6JbvCexzIInuSbZpfdQ44D7iqdWX01FKLvrPgqxoE7oMj8clOfto8U6vYiz4yd5oKu39rRSVOe1zRu0g==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/core": "^7.24.4", - "@babel/parser": "^7.24.4", - "hermes-parser": "^0.25.1", - "zod": "^3.25.0 || ^4.0.0", - "zod-validation-error": "^3.5.0 || ^4.0.0" - }, - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "eslint": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0 || ^9.0.0 || ^10.0.0" - } - }, - "node_modules/eslint-scope": { - "version": "9.1.2", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-9.1.2.tgz", - "integrity": "sha512-xS90H51cKw0jltxmvmHy2Iai1LIqrfbw57b79w/J7MfvDfkIkFZ+kj6zC3BjtUwh150HsSSdxXZcsuv72miDFQ==", - "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "@types/esrecurse": "^4.3.1", - "@types/estree": "^1.0.8", - "esrecurse": "^4.3.0", - "estraverse": "^5.2.0" - }, - "engines": { - "node": "^20.19.0 || ^22.13.0 || >=24" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/eslint-visitor-keys": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-5.0.1.tgz", - "integrity": "sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA==", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": "^20.19.0 || ^22.13.0 || >=24" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/espree": { - "version": "11.2.0", - "resolved": "https://registry.npmjs.org/espree/-/espree-11.2.0.tgz", - "integrity": "sha512-7p3DrVEIopW1B1avAGLuCSh1jubc01H2JHc8B4qqGblmg5gI9yumBgACjWo4JlIc04ufug4xJ3SQI8HkS/Rgzw==", - "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "acorn": "^8.16.0", - "acorn-jsx": "^5.3.2", - "eslint-visitor-keys": "^5.0.1" - }, - "engines": { - "node": "^20.19.0 || ^22.13.0 || >=24" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/esquery": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.7.0.tgz", - "integrity": "sha512-Ap6G0WQwcU/LHsvLwON1fAQX9Zp0A2Y6Y/cJBl9r/JbW90Zyg4/zbG6zzKa2OTALELarYHmKu0GhpM5EO+7T0g==", - "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "estraverse": "^5.1.0" - }, - "engines": { - "node": ">=0.10" - } - }, - "node_modules/esrecurse": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", - "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", - "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "estraverse": "^5.2.0" - }, - "engines": { - "node": ">=4.0" - } - }, - "node_modules/estraverse": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", - "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", - "dev": true, - "license": "BSD-2-Clause", - "engines": { - "node": ">=4.0" - } - }, - "node_modules/estree-walker": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz", - "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/estree": "^1.0.0" - } - }, - "node_modules/esutils": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", - "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", - "dev": true, - "license": "BSD-2-Clause", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/expect-type": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/expect-type/-/expect-type-1.4.0.tgz", - "integrity": "sha512-KfYbmpRm0VbLjEvVa9yGwCi9GI34xvi7A/HXYWQO65CSD2u3MczUJSuwXKFIxlGsgBQizV9q5J9NHj4VG0n+pA==", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": ">=12.0.0" - } - }, - "node_modules/fast-deep-equal": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", - "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", - "dev": true, - "license": "MIT" - }, - "node_modules/fast-glob": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.1.tgz", - "integrity": "sha512-kNFPyjhh5cKjrUltxs+wFx+ZkbRaxxmZ+X0ZU31SOsxCEtP9VPgtq2teZw1DebupL5GmDaNQ6yKMMVcM41iqDg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@nodelib/fs.stat": "^2.0.2", - "@nodelib/fs.walk": "^1.2.3", - "glob-parent": "^5.1.2", - "merge2": "^1.3.0", - "micromatch": "^4.0.4" - }, - "engines": { - "node": ">=8.6.0" - } - }, - "node_modules/fast-glob/node_modules/glob-parent": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", - "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", - "dev": true, - "license": "ISC", - "dependencies": { - "is-glob": "^4.0.1" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/fast-json-stable-stringify": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", - "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", - "dev": true, - "license": "MIT" - }, - "node_modules/fast-levenshtein": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", - "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", - "dev": true, - "license": "MIT" - }, - "node_modules/fastq": { - "version": "1.20.1", - "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.20.1.tgz", - "integrity": "sha512-GGToxJ/w1x32s/D2EKND7kTil4n8OVk/9mycTc4VDza13lOvpUZTGX3mFSCtV9ksdGBVzvsyAVLM6mHFThxXxw==", - "dev": true, - "license": "ISC", - "dependencies": { - "reusify": "^1.0.4" - } - }, - "node_modules/file-entry-cache": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz", - "integrity": "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "flat-cache": "^4.0.0" - }, - "engines": { - "node": ">=16.0.0" - } - }, - "node_modules/fill-range": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", - "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", - "dev": true, - "license": "MIT", - "dependencies": { - "to-regex-range": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/find-up": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", - "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", - "dev": true, - "license": "MIT", - "dependencies": { - "locate-path": "^6.0.0", - "path-exists": "^4.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/flat-cache": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-4.0.1.tgz", - "integrity": "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==", - "dev": true, - "license": "MIT", - "dependencies": { - "flatted": "^3.2.9", - "keyv": "^4.5.4" - }, - "engines": { - "node": ">=16" - } - }, - "node_modules/flatted": { - "version": "3.4.4", - "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.4.4.tgz", - "integrity": "sha512-5+ybhBZANEJxaH3X5evAFatUxLfEHSr7n6kYJ+1Qd0mUqr4eu9gIf6GDbWHf8RJijHrjjO8G+la14SlL2SeS1Q==", - "dev": true, - "license": "ISC" - }, - "node_modules/fsevents": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", - "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", - "dev": true, - "hasInstallScript": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": "^8.16.0 || ^10.6.0 || >=11.0.0" - } - }, - "node_modules/gensync": { - "version": "1.0.0-beta.2", - "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", - "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/glob-parent": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", - "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", - "dev": true, - "license": "ISC", - "dependencies": { - "is-glob": "^4.0.3" - }, - "engines": { - "node": ">=10.13.0" - } - }, - "node_modules/globals": { - "version": "17.8.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-17.8.0.tgz", - "integrity": "sha512-Zz/LMDZScFmkakeL2cTHzf+PbWKdpU3uclqkZT7TjDG58j5WPt0PpA+n9uPI24fZtlw07q0OtEi84K+umsRzqQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/hermes-estree": { - "version": "0.25.1", - "resolved": "https://registry.npmjs.org/hermes-estree/-/hermes-estree-0.25.1.tgz", - "integrity": "sha512-0wUoCcLp+5Ev5pDW2OriHC2MJCbwLwuRx+gAqMTOkGKJJiBCLjtrvy4PWUGn6MIVefecRpzoOZ/UV6iGdOr+Cw==", - "dev": true, - "license": "MIT" - }, - "node_modules/hermes-parser": { - "version": "0.25.1", - "resolved": "https://registry.npmjs.org/hermes-parser/-/hermes-parser-0.25.1.tgz", - "integrity": "sha512-6pEjquH3rqaI6cYAXYPcz9MS4rY6R4ngRgrgfDshRptUZIc3lw0MCIJIGDj9++mfySOuPTHB4nrSW99BCvOPIA==", - "dev": true, - "license": "MIT", - "dependencies": { - "hermes-estree": "0.25.1" - } - }, - "node_modules/ignore": { - "version": "5.3.2", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", - "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 4" - } - }, - "node_modules/imurmurhash": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", - "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.8.19" - } - }, - "node_modules/is-extglob": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", - "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-glob": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", - "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", - "dev": true, - "license": "MIT", - "dependencies": { - "is-extglob": "^2.1.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-number": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", - "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.12.0" - } - }, - "node_modules/isexe": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", - "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", - "dev": true, - "license": "ISC" - }, - "node_modules/js-tokens": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", - "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/jsesc": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", - "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==", - "dev": true, - "license": "MIT", - "bin": { - "jsesc": "bin/jsesc" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/json-buffer": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", - "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/json-schema-traverse": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", - "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", - "dev": true, - "license": "MIT" - }, - "node_modules/json-stable-stringify-without-jsonify": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", - "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", - "dev": true, - "license": "MIT" - }, - "node_modules/json5": { - "version": "2.2.3", - "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", - "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", - "dev": true, - "license": "MIT", - "bin": { - "json5": "lib/cli.js" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/keyv": { - "version": "4.5.4", - "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", - "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", - "dev": true, - "license": "MIT", - "dependencies": { - "json-buffer": "3.0.1" - } - }, - "node_modules/levn": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", - "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "prelude-ls": "^1.2.1", - "type-check": "~0.4.0" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/lightningcss": { - "version": "1.33.0", - "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.33.0.tgz", - "integrity": "sha512-WkUDrojuJs0xkgGf2udWxa3yGBRxPtxUkB79i6aCZLRgc7PM8fZe9TosfPDcvEpQZbuFASnHYmRLBLUbmLOIIA==", - "dev": true, - "license": "MPL-2.0", - "dependencies": { - "detect-libc": "^2.0.3" - }, - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - }, - "optionalDependencies": { - "lightningcss-android-arm64": "1.33.0", - "lightningcss-darwin-arm64": "1.33.0", - "lightningcss-darwin-x64": "1.33.0", - "lightningcss-freebsd-x64": "1.33.0", - "lightningcss-linux-arm-gnueabihf": "1.33.0", - "lightningcss-linux-arm64-gnu": "1.33.0", - "lightningcss-linux-arm64-musl": "1.33.0", - "lightningcss-linux-x64-gnu": "1.33.0", - "lightningcss-linux-x64-musl": "1.33.0", - "lightningcss-win32-arm64-msvc": "1.33.0", - "lightningcss-win32-x64-msvc": "1.33.0" - } - }, - "node_modules/lightningcss-android-arm64": { - "version": "1.33.0", - "resolved": "https://registry.npmjs.org/lightningcss-android-arm64/-/lightningcss-android-arm64-1.33.0.tgz", - "integrity": "sha512-gEpRTalKdosp4Bb8qWtc2iOgE5SeIHlpS1up9bFq2wAyYhl1UdTObYiHe98zEM9SQvSoqQZ1IQD0JNpg3Ml5pg==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MPL-2.0", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-darwin-arm64": { - "version": "1.33.0", - "resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.33.0.tgz", - "integrity": "sha512-Sciaz8eenNTKn9b3t7+xr0ipTp9YxKQY4npwQ3mrRuL0BAVHBLyZxofhaKBAVtzmtRZ/zTyo0/to4B1uWG/Djg==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MPL-2.0", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-darwin-x64": { - "version": "1.33.0", - "resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.33.0.tgz", - "integrity": "sha512-Z5UPAxzrjlWNNyGy6i65cJzzvgJ5D3T6wMvs+gWpY9d7qRhANrxqAp6LhxIgZhWEw18RfJTGcRxjuLIBr+m8XQ==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MPL-2.0", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-freebsd-x64": { - "version": "1.33.0", - "resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.33.0.tgz", - "integrity": "sha512-QQM/Ti/hQajJwCY+RiWuCZ9sdtI/XQk7nDK5vC8kkdwixezOlDgvDx7+RT+QjK6FcFT4MpsuoBnHIo/O3StRRg==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MPL-2.0", - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-linux-arm-gnueabihf": { - "version": "1.33.0", - "resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.33.0.tgz", - "integrity": "sha512-N7FVBe6iS24MlM6R/4RBTxGhQheZGs7tiQ9U32UtF75NzP5Q7xWPRqLBCKxlRQRk3rY1jCIPLzx7WzOhuUIRLQ==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MPL-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-linux-arm64-gnu": { - "version": "1.33.0", - "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.33.0.tgz", - "integrity": "sha512-j2v/itmy4HlNxlc6voKXYgBqNi0Ng2LShg4z7GufpEgs05P+2suBVyi9I6YHq5uoVFx9ETin3eCEhLVyXGQnKg==", - "cpu": [ - "arm64" - ], - "dev": true, - "libc": [ - "glibc" - ], - "license": "MPL-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-linux-arm64-musl": { - "version": "1.33.0", - "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.33.0.tgz", - "integrity": "sha512-yiO5ROMuYQgXbC60yjZU5CYSFZGKXL0HFATXt9mHJn1+zW55oCtMI9NfcVhYLMFDL7gV7oBPon/EmMMGg2OvtQ==", - "cpu": [ - "arm64" - ], - "dev": true, - "libc": [ - "musl" - ], - "license": "MPL-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-linux-x64-gnu": { - "version": "1.33.0", - "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.33.0.tgz", - "integrity": "sha512-ar+Ju7LmcN0Jo4FpL4hpFybwNG9/3A/Br5KW2n2jyODg3MEZXaDYADdemoNS+BDNfMgKvylJLj4S5tyRActuAg==", - "cpu": [ - "x64" - ], - "dev": true, - "libc": [ - "glibc" - ], - "license": "MPL-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-linux-x64-musl": { - "version": "1.33.0", - "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.33.0.tgz", - "integrity": "sha512-RYiYbkokw0trfKqqzfF55lginwEPrD3OJDfTuJzFs1MK6iFnDenaz1fqLLtX4ITG3OktJQXOeTaw1awrBAlZPw==", - "cpu": [ - "x64" - ], - "dev": true, - "libc": [ - "musl" - ], - "license": "MPL-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-win32-arm64-msvc": { - "version": "1.33.0", - "resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.33.0.tgz", - "integrity": "sha512-1K+MPfLSFVpphzpdbfkhlWk6wBrTObBzS2T6db10PNOZgR9GoVsAWzwNyuhUYYbTp23j+4RrncfujZ4uAzXvwA==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MPL-2.0", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-win32-x64-msvc": { - "version": "1.33.0", - "resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.33.0.tgz", - "integrity": "sha512-OlEICDx/Xl0FqSp4bry8zFnCvGpig3Gl4gCquvYwHuqJKEC1+n9NgDniFvqHGmMv1ZkqDJrDqKKSykTDX+ehuA==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MPL-2.0", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/locate-path": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", - "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", - "dev": true, - "license": "MIT", - "dependencies": { - "p-locate": "^5.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/lru-cache": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", - "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", - "dev": true, - "license": "ISC", - "dependencies": { - "yallist": "^3.0.2" - } - }, - "node_modules/magic-string": { - "version": "0.30.21", - "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz", - "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jridgewell/sourcemap-codec": "^1.5.5" - } - }, - "node_modules/merge2": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", - "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 8" - } - }, - "node_modules/micromatch": { - "version": "4.0.8", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", - "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", - "dev": true, - "license": "MIT", - "dependencies": { - "braces": "^3.0.3", - "picomatch": "^2.3.1" - }, - "engines": { - "node": ">=8.6" - } - }, - "node_modules/minimatch": { - "version": "10.2.6", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.6.tgz", - "integrity": "sha512-vpLQEs+VLCr1nU0BXS07maYoFwlDAH0gngQuuttxIwutDFEMHq2blX+8vpgxDdK3J1PwjCJiep77OitTZ4Ll1A==", - "dev": true, - "license": "BlueOak-1.0.0", - "dependencies": { - "brace-expansion": "^5.0.8" - }, - "engines": { - "node": "18 || 20 || >=22" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/ms": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", - "dev": true, - "license": "MIT" - }, - "node_modules/nanoid": { - "version": "3.3.16", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.16.tgz", - "integrity": "sha512-bzlKTyNJ7+LdGIIwy8ijFpIqEQIvafahV7eYykJ8Cvh42EdJeODoJ6gUJXpQJvej1BddH8OqTXZNE/KfbWAu8Q==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "bin": { - "nanoid": "bin/nanoid.cjs" - }, - "engines": { - "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" - } - }, - "node_modules/natural-compare": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", - "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", - "dev": true, - "license": "MIT" - }, - "node_modules/next": { - "version": "16.2.12", - "resolved": "https://registry.npmjs.org/next/-/next-16.2.12.tgz", - "integrity": "sha512-iD59eYQWmbFcEbX7v/acG5DRym9iw1DdaPoD0WTA920naWsE25wShzJW4+UvAs8MK9EC2kBfIH6vtto1H1PHGw==", - "license": "MIT", - "dependencies": { - "@next/env": "16.2.12", - "@swc/helpers": "0.5.15", - "baseline-browser-mapping": "^2.9.19", - "caniuse-lite": "^1.0.30001579", - "postcss": "8.4.31", - "styled-jsx": "5.1.6" - }, - "bin": { - "next": "dist/bin/next" - }, - "engines": { - "node": ">=20.9.0" - }, - "optionalDependencies": { - "@next/swc-darwin-arm64": "16.2.12", - "@next/swc-darwin-x64": "16.2.12", - "@next/swc-linux-arm64-gnu": "16.2.12", - "@next/swc-linux-arm64-musl": "16.2.12", - "@next/swc-linux-x64-gnu": "16.2.12", - "@next/swc-linux-x64-musl": "16.2.12", - "@next/swc-win32-arm64-msvc": "16.2.12", - "@next/swc-win32-x64-msvc": "16.2.12", - "sharp": "^0.34.5" - }, - "peerDependencies": { - "@opentelemetry/api": "^1.1.0", - "@playwright/test": "^1.51.1", - "babel-plugin-react-compiler": "*", - "react": "^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0", - "react-dom": "^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0", - "sass": "^1.3.0" - }, - "peerDependenciesMeta": { - "@opentelemetry/api": { - "optional": true - }, - "@playwright/test": { - "optional": true - }, - "babel-plugin-react-compiler": { - "optional": true - }, - "sass": { - "optional": true - } - } - }, - "node_modules/node-releases": { - "version": "2.0.51", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.51.tgz", - "integrity": "sha512-wRNIrw4DmVLKQlbgOMdkMx27Wrpzes2hh5Jtbi2bjPd+4wJstWIqP5A+lscnqbm0xxmT5Bpg8Lec5ItEBwx6BQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=18" - } - }, - "node_modules/obug": { - "version": "2.1.4", - "resolved": "https://registry.npmjs.org/obug/-/obug-2.1.4.tgz", - "integrity": "sha512-4a+OsYv9UktOJKE+l1A4OufDgdRF9PifWj+tJnHURo/P+WOxpG4GzUFL9qCalmWauao6ogiG+QvnCovwPoyAWA==", - "dev": true, - "funding": [ - "https://github.com/sponsors/sxzz", - "https://opencollective.com/debug" - ], - "license": "MIT", - "engines": { - "node": ">=12.20.0" - } - }, - "node_modules/optionator": { - "version": "0.9.4", - "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", - "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==", - "dev": true, - "license": "MIT", - "dependencies": { - "deep-is": "^0.1.3", - "fast-levenshtein": "^2.0.6", - "levn": "^0.4.1", - "prelude-ls": "^1.2.1", - "type-check": "^0.4.0", - "word-wrap": "^1.2.5" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/p-limit": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", - "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "yocto-queue": "^0.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/p-locate": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", - "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", - "dev": true, - "license": "MIT", - "dependencies": { - "p-limit": "^3.0.2" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/path-exists": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", - "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/path-key": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", - "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/pathe": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz", - "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==", - "dev": true, - "license": "MIT" - }, - "node_modules/picocolors": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", - "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", - "license": "ISC" - }, - "node_modules/picomatch": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.2.tgz", - "integrity": "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8.6" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" - } - }, - "node_modules/postcss": { - "version": "8.5.25", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.25.tgz", - "integrity": "sha512-DTPx3RWSSnWyzLxQnlH0rJP+EW5ekl16ZU4/psbIhA0e53kJfdgaN5vKM+xP7yJtXVu+nfdVFmlgFDEKAe4Pyw==", - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/postcss/" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/postcss" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "dependencies": { - "nanoid": "^3.3.16", - "picocolors": "^1.1.1", - "source-map-js": "^1.2.1" - }, - "engines": { - "node": "^10 || ^12 || >=14" - } - }, - "node_modules/prelude-ls": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", - "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/punycode": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", - "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/queue-microtask": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", - "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "MIT" - }, - "node_modules/react": { - "version": "19.2.8", - "resolved": "https://registry.npmjs.org/react/-/react-19.2.8.tgz", - "integrity": "sha512-PWaYA1L/q9u2u7xYQi+Y3L3Yfnie7XyLeaJICV1MGD6LprsBxcAqGjYyr0eY3p+QdsA+x/Irkt4Qif8D63+Sbw==", - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/react-dom": { - "version": "19.2.8", - "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.8.tgz", - "integrity": "sha512-rVprimfGBG3DR+Tq0IQG2DT5PxKth1WIGDmj5yPmlzr4YBe7uyE+Du4oVqTDXZSHGGGXRtTJEGSSePyQCMBglQ==", - "license": "MIT", - "dependencies": { - "scheduler": "^0.27.0" - }, - "peerDependencies": { - "react": "^19.2.8" - } - }, - "node_modules/reusify": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz", - "integrity": "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==", - "dev": true, - "license": "MIT", - "engines": { - "iojs": ">=1.0.0", - "node": ">=0.10.0" - } - }, - "node_modules/rolldown": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.2.1.tgz", - "integrity": "sha512-4FKJhg8d3OiyQOA6Q1Q0hoFFpW9/OoX+VsHzpECsdsIZoOArrAK90gl59YK/Z+gnDel45bgJZK03ozH/9bCqEw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@oxc-project/types": "=0.142.0", - "@rolldown/pluginutils": "^1.0.0" - }, - "bin": { - "rolldown": "bin/cli.mjs" - }, - "engines": { - "node": "^20.19.0 || >=22.12.0" - }, - "optionalDependencies": { - "@rolldown/binding-android-arm64": "1.2.1", - "@rolldown/binding-darwin-arm64": "1.2.1", - "@rolldown/binding-darwin-x64": "1.2.1", - "@rolldown/binding-freebsd-x64": "1.2.1", - "@rolldown/binding-linux-arm-gnueabihf": "1.2.1", - "@rolldown/binding-linux-arm64-gnu": "1.2.1", - "@rolldown/binding-linux-arm64-musl": "1.2.1", - "@rolldown/binding-linux-ppc64-gnu": "1.2.1", - "@rolldown/binding-linux-s390x-gnu": "1.2.1", - "@rolldown/binding-linux-x64-gnu": "1.2.1", - "@rolldown/binding-linux-x64-musl": "1.2.1", - "@rolldown/binding-openharmony-arm64": "1.2.1", - "@rolldown/binding-wasm32-wasi": "1.2.1", - "@rolldown/binding-win32-arm64-msvc": "1.2.1", - "@rolldown/binding-win32-x64-msvc": "1.2.1" - } - }, - "node_modules/run-parallel": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", - "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "MIT", - "dependencies": { - "queue-microtask": "^1.2.2" - } - }, - "node_modules/scheduler": { - "version": "0.27.0", - "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.27.0.tgz", - "integrity": "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==", - "license": "MIT" - }, - "node_modules/semver": { - "version": "6.3.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", - "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", - "dev": true, - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - } - }, - "node_modules/sharp": { - "version": "0.35.3", - "resolved": "https://registry.npmjs.org/sharp/-/sharp-0.35.3.tgz", - "integrity": "sha512-ej0zVHuZGHCiABXcNxeYhpRnPNPAcvbG8RMdBAhDAxLKkCRVSpK3Iyu7qbqw3JMzoj0REeM6f3tJLtVwl0023Q==", - "license": "Apache-2.0", - "optional": true, - "dependencies": { - "@img/colour": "^1.1.0", - "detect-libc": "^2.1.2", - "semver": "^7.8.5" - }, - "engines": { - "node": ">=20.9.0" - }, - "funding": { - "url": "https://opencollective.com/libvips" - }, - "optionalDependencies": { - "@img/sharp-darwin-arm64": "0.35.3", - "@img/sharp-darwin-x64": "0.35.3", - "@img/sharp-freebsd-wasm32": "0.35.3", - "@img/sharp-libvips-darwin-arm64": "1.3.2", - "@img/sharp-libvips-darwin-x64": "1.3.2", - "@img/sharp-libvips-linux-arm": "1.3.2", - "@img/sharp-libvips-linux-arm64": "1.3.2", - "@img/sharp-libvips-linux-ppc64": "1.3.2", - "@img/sharp-libvips-linux-riscv64": "1.3.2", - "@img/sharp-libvips-linux-s390x": "1.3.2", - "@img/sharp-libvips-linux-x64": "1.3.2", - "@img/sharp-libvips-linuxmusl-arm64": "1.3.2", - "@img/sharp-libvips-linuxmusl-x64": "1.3.2", - "@img/sharp-linux-arm": "0.35.3", - "@img/sharp-linux-arm64": "0.35.3", - "@img/sharp-linux-ppc64": "0.35.3", - "@img/sharp-linux-riscv64": "0.35.3", - "@img/sharp-linux-s390x": "0.35.3", - "@img/sharp-linux-x64": "0.35.3", - "@img/sharp-linuxmusl-arm64": "0.35.3", - "@img/sharp-linuxmusl-x64": "0.35.3", - "@img/sharp-webcontainers-wasm32": "0.35.3", - "@img/sharp-win32-arm64": "0.35.3", - "@img/sharp-win32-ia32": "0.35.3", - "@img/sharp-win32-x64": "0.35.3" - }, - "peerDependenciesMeta": { - "@types/node": { - "optional": true - } - } - }, - "node_modules/sharp/node_modules/semver": { - "version": "7.8.5", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.5.tgz", - "integrity": "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==", - "license": "ISC", - "optional": true, - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/shebang-command": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", - "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", - "dev": true, - "license": "MIT", - "dependencies": { - "shebang-regex": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/shebang-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", - "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/siginfo": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/siginfo/-/siginfo-2.0.0.tgz", - "integrity": "sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==", - "dev": true, - "license": "ISC" - }, - "node_modules/source-map-js": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", - "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", - "license": "BSD-3-Clause", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/stackback": { - "version": "0.0.2", - "resolved": "https://registry.npmjs.org/stackback/-/stackback-0.0.2.tgz", - "integrity": "sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==", - "dev": true, - "license": "MIT" - }, - "node_modules/std-env": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/std-env/-/std-env-4.2.0.tgz", - "integrity": "sha512-oCUKSupKTHX53EyjDtuZQ64pjLJ6yYCtpmEw0goYxtjG9KpbRe8KAsl2tBUGU9DyMcJ0RwJ8GqJAFzMXcXW1Rw==", - "dev": true, - "license": "MIT" - }, - "node_modules/styled-jsx": { - "version": "5.1.6", - "resolved": "https://registry.npmjs.org/styled-jsx/-/styled-jsx-5.1.6.tgz", - "integrity": "sha512-qSVyDTeMotdvQYoHWLNGwRFJHC+i+ZvdBRYosOFgC+Wg1vx4frN2/RG/NA7SYqqvKNLf39P2LSRA2pu6n0XYZA==", - "license": "MIT", - "dependencies": { - "client-only": "0.0.1" - }, - "engines": { - "node": ">= 12.0.0" - }, - "peerDependencies": { - "react": ">= 16.8.0 || 17.x.x || ^18.0.0-0 || ^19.0.0-0" - }, - "peerDependenciesMeta": { - "@babel/core": { - "optional": true - }, - "babel-plugin-macros": { - "optional": true - } - } - }, - "node_modules/swr": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/swr/-/swr-2.4.2.tgz", - "integrity": "sha512-ej644Y2bvkIajfR32KGeSSdBXQW+ScjGjkybZgSE7kFpk9eGnV44XY9FJylXi+W75pavSX1PVNB57W5EbhGIYw==", - "license": "MIT", - "dependencies": { - "dequal": "^2.0.3", - "use-sync-external-store": "^1.6.0" - }, - "peerDependencies": { - "react": "^16.11.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" - } - }, - "node_modules/tinybench": { - "version": "2.9.0", - "resolved": "https://registry.npmjs.org/tinybench/-/tinybench-2.9.0.tgz", - "integrity": "sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==", - "dev": true, - "license": "MIT" - }, - "node_modules/tinyexec": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-1.2.4.tgz", - "integrity": "sha512-SHf/r48b7vOrjve9PxJo3MN5v5yuyjHvdUcrQffT3WXMUfnGmHDVbC4k3sHJaJTgZCwpUplIaAo5ANtMyp3YHg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=18" - } - }, - "node_modules/tinyglobby": { - "version": "0.2.17", - "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.17.tgz", - "integrity": "sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==", - "dev": true, - "license": "MIT", - "dependencies": { - "fdir": "^6.5.0", - "picomatch": "^4.0.4" - }, - "engines": { - "node": ">=12.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/SuperchupuDev" - } - }, - "node_modules/tinyglobby/node_modules/fdir": { - "version": "6.5.0", - "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", - "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12.0.0" - }, - "peerDependencies": { - "picomatch": "^3 || ^4" - }, - "peerDependenciesMeta": { - "picomatch": { - "optional": true - } - } - }, - "node_modules/tinyglobby/node_modules/picomatch": { - "version": "4.0.5", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.5.tgz", - "integrity": "sha512-RvwwcruNjI1ncT5xRakeyS9Lf8lcItv34KD+aif+VH9kduAyfYBipGh12274xtenIPZ119/R9BdTBa8gAwSh0A==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" - } - }, - "node_modules/tinyrainbow": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/tinyrainbow/-/tinyrainbow-3.1.1.tgz", - "integrity": "sha512-yau8yJdTt989Mm0Bd/236QnzEiPf2xLLTqUZRUJOo/3CB078LSwzei343DgtJVmfJKJE3TMINY1u42SQsP6mXw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/to-regex-range": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", - "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "is-number": "^7.0.0" - }, - "engines": { - "node": ">=8.0" - } - }, - "node_modules/ts-api-utils": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.5.0.tgz", - "integrity": "sha512-OJ/ibxhPlqrMM0UiNHJ/0CKQkoKF243/AEmplt3qpRgkW8VG7IfOS41h7V8TjITqdByHzrjcS/2si+y4lIh8NA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=18.12" - }, - "peerDependencies": { - "typescript": ">=4.8.4" - } - }, - "node_modules/tslib": { - "version": "2.8.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", - "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", - "license": "0BSD" - }, - "node_modules/type-check": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", - "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", - "dev": true, - "license": "MIT", - "dependencies": { - "prelude-ls": "^1.2.1" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/typescript": { - "version": "5.9.3", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", - "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", - "dev": true, - "license": "Apache-2.0", - "bin": { - "tsc": "bin/tsc", - "tsserver": "bin/tsserver" - }, - "engines": { - "node": ">=14.17" - } - }, - "node_modules/typescript-eslint": { - "version": "8.65.0", - "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.65.0.tgz", - "integrity": "sha512-/ggrHAwyjENDusvyxbuqxAC2dTnZg/Z8F+fgQtYIz+L6n/9HfSlEZcFGV/NsMNa6CkGk0xUjUAFwC0vHOflvIA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@typescript-eslint/eslint-plugin": "8.65.0", - "@typescript-eslint/parser": "8.65.0", - "@typescript-eslint/typescript-estree": "8.65.0", - "@typescript-eslint/utils": "8.65.0" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", - "typescript": ">=4.8.4 <6.1.0" - } - }, - "node_modules/undici-types": { - "version": "6.21.0", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", - "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/update-browserslist-db": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.2.3.tgz", - "integrity": "sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/browserslist" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "dependencies": { - "escalade": "^3.2.0", - "picocolors": "^1.1.1" - }, - "bin": { - "update-browserslist-db": "cli.js" - }, - "peerDependencies": { - "browserslist": ">= 4.21.0" - } - }, - "node_modules/uri-js": { - "version": "4.4.1", - "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", - "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", - "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "punycode": "^2.1.0" - } - }, - "node_modules/use-sync-external-store": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.6.0.tgz", - "integrity": "sha512-Pp6GSwGP/NrPIrxVFAIkOQeyw8lFenOHijQWkUTrDvrF4ALqylP2C/KCkeS9dpUM3KvYRQhna5vt7IL95+ZQ9w==", - "license": "MIT", - "peerDependencies": { - "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" - } - }, - "node_modules/vite": { - "version": "8.2.0", - "resolved": "https://registry.npmjs.org/vite/-/vite-8.2.0.tgz", - "integrity": "sha512-pn+CFpM0lwDeKwmOq1ZaBK/9sjorZcgqxki6MbY/jPEVd9vichIlmlD4HmQ5wdP5EgqQCFRaACBxMC7uEGc6lQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "lightningcss": "^1.33.0", - "picomatch": "^4.0.5", - "postcss": "^8.5.23", - "rolldown": "~1.2.0", - "tinyglobby": "^0.2.17" - }, - "bin": { - "vite": "bin/vite.js" - }, - "engines": { - "node": "^20.19.0 || >=22.12.0" - }, - "funding": { - "url": "https://github.com/vitejs/vite?sponsor=1" - }, - "optionalDependencies": { - "fsevents": "~2.3.3" - }, - "peerDependencies": { - "@types/node": "^20.19.0 || >=22.12.0", - "@vitejs/devtools": "^0.4.0", - "esbuild": "^0.27.0 || ^0.28.0", - "jiti": ">=1.21.0", - "less": "^4.0.0", - "sass": "^1.70.0", - "sass-embedded": "^1.70.0", - "stylus": ">=0.54.8", - "sugarss": "^5.0.0", - "terser": "^5.16.0", - "tsx": "^4.8.1", - "yaml": "^2.4.2" - }, - "peerDependenciesMeta": { - "@types/node": { - "optional": true - }, - "@vitejs/devtools": { - "optional": true - }, - "esbuild": { - "optional": true - }, - "jiti": { - "optional": true - }, - "less": { - "optional": true - }, - "sass": { - "optional": true - }, - "sass-embedded": { - "optional": true - }, - "stylus": { - "optional": true - }, - "sugarss": { - "optional": true - }, - "terser": { - "optional": true - }, - "tsx": { - "optional": true - }, - "yaml": { - "optional": true - } - } - }, - "node_modules/vite/node_modules/picomatch": { - "version": "4.0.5", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.5.tgz", - "integrity": "sha512-RvwwcruNjI1ncT5xRakeyS9Lf8lcItv34KD+aif+VH9kduAyfYBipGh12274xtenIPZ119/R9BdTBa8gAwSh0A==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" - } - }, - "node_modules/vitest": { - "version": "4.1.10", - "resolved": "https://registry.npmjs.org/vitest/-/vitest-4.1.10.tgz", - "integrity": "sha512-R9jUTe5S4Qb0HCd4TNqpC7oGcrMssMRGXLW80ubjWsW9VH5GF8y1Y0SFLY9AbqSk6nt0PnOx4H4WNJYZ13GUPw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@vitest/expect": "4.1.10", - "@vitest/mocker": "4.1.10", - "@vitest/pretty-format": "4.1.10", - "@vitest/runner": "4.1.10", - "@vitest/snapshot": "4.1.10", - "@vitest/spy": "4.1.10", - "@vitest/utils": "4.1.10", - "es-module-lexer": "^2.0.0", - "expect-type": "^1.3.0", - "magic-string": "^0.30.21", - "obug": "^2.1.1", - "pathe": "^2.0.3", - "picomatch": "^4.0.3", - "std-env": "^4.0.0-rc.1", - "tinybench": "^2.9.0", - "tinyexec": "^1.0.2", - "tinyglobby": "^0.2.15", - "tinyrainbow": "^3.1.0", - "vite": "^6.0.0 || ^7.0.0 || ^8.0.0", - "why-is-node-running": "^2.3.0" - }, - "bin": { - "vitest": "vitest.mjs" - }, - "engines": { - "node": "^20.0.0 || ^22.0.0 || >=24.0.0" - }, - "funding": { - "url": "https://opencollective.com/vitest" - }, - "peerDependencies": { - "@edge-runtime/vm": "*", - "@opentelemetry/api": "^1.9.0", - "@types/node": "^20.0.0 || ^22.0.0 || >=24.0.0", - "@vitest/browser-playwright": "4.1.10", - "@vitest/browser-preview": "4.1.10", - "@vitest/browser-webdriverio": "4.1.10", - "@vitest/coverage-istanbul": "4.1.10", - "@vitest/coverage-v8": "4.1.10", - "@vitest/ui": "4.1.10", - "happy-dom": "*", - "jsdom": "*", - "vite": "^6.0.0 || ^7.0.0 || ^8.0.0" - }, - "peerDependenciesMeta": { - "@edge-runtime/vm": { - "optional": true - }, - "@opentelemetry/api": { - "optional": true - }, - "@types/node": { - "optional": true - }, - "@vitest/browser-playwright": { - "optional": true - }, - "@vitest/browser-preview": { - "optional": true - }, - "@vitest/browser-webdriverio": { - "optional": true - }, - "@vitest/coverage-istanbul": { - "optional": true - }, - "@vitest/coverage-v8": { - "optional": true - }, - "@vitest/ui": { - "optional": true - }, - "happy-dom": { - "optional": true - }, - "jsdom": { - "optional": true - }, - "vite": { - "optional": false - } - } - }, - "node_modules/vitest/node_modules/picomatch": { - "version": "4.0.5", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.5.tgz", - "integrity": "sha512-RvwwcruNjI1ncT5xRakeyS9Lf8lcItv34KD+aif+VH9kduAyfYBipGh12274xtenIPZ119/R9BdTBa8gAwSh0A==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" - } - }, - "node_modules/which": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", - "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", - "dev": true, - "license": "ISC", - "dependencies": { - "isexe": "^2.0.0" - }, - "bin": { - "node-which": "bin/node-which" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/why-is-node-running": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/why-is-node-running/-/why-is-node-running-2.3.0.tgz", - "integrity": "sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==", - "dev": true, - "license": "MIT", - "dependencies": { - "siginfo": "^2.0.0", - "stackback": "0.0.2" - }, - "bin": { - "why-is-node-running": "cli.js" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/word-wrap": { - "version": "1.2.5", - "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", - "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/yallist": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", - "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", - "dev": true, - "license": "ISC" - }, - "node_modules/yocto-queue": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", - "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/zod": { - "version": "4.4.3", - "resolved": "https://registry.npmjs.org/zod/-/zod-4.4.3.tgz", - "integrity": "sha512-ytENFjIJFl2UwYglde2jchW2Hwm4GJFLDiSXWdTrJQBIN9Fcyp7n4DhxJEiWNAJMV1/BqWfW/kkg71UDcHJyTQ==", - "dev": true, - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/colinhacks" - } - }, - "node_modules/zod-validation-error": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/zod-validation-error/-/zod-validation-error-4.0.2.tgz", - "integrity": "sha512-Q6/nZLe6jxuU80qb/4uJ4t5v2VEZ44lzQjPDhYJNztRQ4wyWc6VF3D3Kb/fAuPetZQnhS3hnajCf9CsWesghLQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=18.0.0" - }, - "peerDependencies": { - "zod": "^3.25.0 || ^4.0.0" - } - } - } -} diff --git a/examples/demo-frontend/package.json b/examples/demo-frontend/package.json deleted file mode 100644 index 0e7546b..0000000 --- a/examples/demo-frontend/package.json +++ /dev/null @@ -1,40 +0,0 @@ -{ - "name": "demo-frontend", - "version": "0.1.0", - "private": true, - "scripts": { - "dev": "next dev", - "build": "next build", - "start": "next start", - "lint": "eslint .", - "typecheck": "tsc --noEmit", - "test": "vitest run", - "check:architecture": "node scripts/check-architecture.mjs", - "check": "npm run check:architecture && npm run lint && npm run typecheck && npm run test", - "codegen:simple-rest-api": "npx @gromlab/api-codegen@5.1.1 -i ../demo-backend/openapi/simple.json -o src/infra/simple-rest-api/generated" - }, - "dependencies": { - "clsx": "2.1.1", - "next": "16.2.12", - "react": "19.2.8", - "react-dom": "19.2.8", - "swr": "2.4.2" - }, - "devDependencies": { - "@eslint/js": "10.0.1", - "@next/eslint-plugin-next": "16.2.12", - "@types/node": "^20", - "@types/react": "^19", - "@types/react-dom": "^19", - "eslint": "10.8.0", - "eslint-plugin-react-hooks": "7.1.1", - "globals": "17.8.0", - "typescript": "^5", - "typescript-eslint": "8.65.0", - "vitest": "4.1.10" - }, - "overrides": { - "postcss": "8.5.25", - "sharp": "0.35.3" - } -} diff --git a/examples/demo-frontend/public/product-placeholder.svg b/examples/demo-frontend/public/product-placeholder.svg deleted file mode 100644 index 6a703d6..0000000 --- a/examples/demo-frontend/public/product-placeholder.svg +++ /dev/null @@ -1,8 +0,0 @@ - - Product image unavailable - Neutral geometric placeholder for a missing product image. - - - - - diff --git a/examples/demo-frontend/scripts/check-architecture.mjs b/examples/demo-frontend/scripts/check-architecture.mjs deleted file mode 100644 index 684e550..0000000 --- a/examples/demo-frontend/scripts/check-architecture.mjs +++ /dev/null @@ -1,360 +0,0 @@ -import { existsSync, readdirSync, readFileSync } from 'node:fs' -import { dirname, extname, join, relative, resolve, sep } from 'node:path' -import { fileURLToPath } from 'node:url' -import ts from 'typescript' - -const SCRIPT_DIRECTORY = dirname(fileURLToPath(import.meta.url)) -const PROJECT_ROOT = resolve(SCRIPT_DIRECTORY, '..') -const SOURCE_ROOT = join(PROJECT_ROOT, 'src') -const SOURCE_EXTENSIONS = new Set(['.ts', '.tsx']) -const MODULE_ROOTS = [ - 'compositions/layouts/store-shell', - 'compositions/screens/cart', - 'compositions/screens/catalog', - 'compositions/screens/orders', - 'compositions/screens/product-admin', - 'compositions/screens/product-detail', - 'compositions/screens/sign-in', - 'compositions/widgets/demo-toolbar', - 'domains/auth', - 'domains/cart', - 'domains/catalog', - 'domains/demo-control', - 'domains/orders', - 'infra/browser-storage', - 'infra/simple-auth-session', - 'infra/simple-rest-api', - 'shared/lib/value-predicates', - 'ui/button', - 'ui/feedback-panel', - 'ui/form-field' -].sort((left, right) => right.length - left.length) -const MODULAR_LAYERS = new Set(['compositions', 'domains', 'infra', 'ui']) -const ALLOWED_TARGETS = { - app: new Set(['app', 'compositions', 'domains', 'infra', 'ui', 'shared']), - compositions: new Set(['compositions', 'domains', 'infra', 'ui', 'shared']), - domains: new Set(['domains', 'infra', 'ui', 'shared']), - infra: new Set(['infra', 'shared']), - ui: new Set(['ui', 'shared']), - shared: new Set(['shared']) -} - -/** - * Рекурсивно собирает TypeScript-файлы SLM root. - */ -const collectSourceFiles = (directory) => { - return readdirSync(directory, { withFileTypes: true }).flatMap((entry) => { - const entryPath = join(directory, entry.name) - - if (entry.isDirectory()) { - return collectSourceFiles(entryPath) - } - - return SOURCE_EXTENSIONS.has(extname(entry.name)) ? [entryPath] : [] - }) -} - -/** - * Нормализует filesystem path к формату architecture manifest. - */ -const normalizePath = (filePath) => { - return filePath.split(sep).join('/') -} - -/** - * Возвращает путь файла относительно SLM root. - */ -const getSourcePath = (filePath) => { - return normalizePath(relative(SOURCE_ROOT, filePath)) -} - -/** - * Определяет слой относительного source path. - */ -const getLayer = (sourcePath) => { - return sourcePath.split('/')[0] -} - -/** - * Находит явно объявленного module-owner по longest-prefix rule. - */ -const getModuleId = (sourcePath) => { - return MODULE_ROOTS.find( - (moduleRoot) => sourcePath === moduleRoot || sourcePath.startsWith(`${moduleRoot}/`) - ) ?? null -} - -/** - * Проверяет deep import относительно явно объявленного public entry. - */ -const isDeepModuleImport = (targetPath, sourceModuleId) => { - const targetModuleId = getModuleId(targetPath) - - return targetModuleId !== null && - targetModuleId !== sourceModuleId && - targetPath !== targetModuleId -} - -/** - * Проверяет направление слоя независимо от синтаксиса import path. - */ -const isLayerImportAllowed = (sourcePath, targetPath) => { - const sourceLayer = getLayer(sourcePath) - const targetLayer = getLayer(targetPath) - - return ALLOWED_TARGETS[sourceLayer]?.has(targetLayer) === true -} - -/** - * Извлекает static, side-effect, reexport и dynamic module specifiers через TypeScript AST. - */ -const getModuleSpecifiers = (filePath, sourceCode) => { - const scriptKind = extname(filePath) === '.tsx' ? ts.ScriptKind.TSX : ts.ScriptKind.TS - const sourceFile = ts.createSourceFile( - filePath, - sourceCode, - ts.ScriptTarget.Latest, - true, - scriptKind - ) - const specifiers = [] - - /** - * Обходит один AST node и собирает строковые module references. - */ - const visit = (node) => { - if ( - (ts.isImportDeclaration(node) || ts.isExportDeclaration(node)) && - node.moduleSpecifier && - ts.isStringLiteral(node.moduleSpecifier) - ) { - specifiers.push(node.moduleSpecifier.text) - } - - if ( - ts.isImportEqualsDeclaration(node) && - ts.isExternalModuleReference(node.moduleReference) && - node.moduleReference.expression && - ts.isStringLiteral(node.moduleReference.expression) - ) { - specifiers.push(node.moduleReference.expression.text) - } - - if ( - ts.isCallExpression(node) && - node.arguments.length === 1 && - ts.isStringLiteral(node.arguments[0]) && - (node.expression.kind === ts.SyntaxKind.ImportKeyword || - (ts.isIdentifier(node.expression) && node.expression.text === 'require')) - ) { - specifiers.push(node.arguments[0].text) - } - - if ( - ts.isImportTypeNode(node) && - ts.isLiteralTypeNode(node.argument) && - ts.isStringLiteral(node.argument.literal) - ) { - specifiers.push(node.argument.literal.text) - } - - ts.forEachChild(node, visit) - } - - visit(sourceFile) - - return specifiers -} - -/** - * Разрешает относительный TypeScript import. - */ -const resolveRelativeImport = (sourceFile, specifier) => { - const basePath = resolve(dirname(sourceFile), specifier) - const candidates = [ - basePath, - `${basePath}.ts`, - `${basePath}.tsx`, - join(basePath, 'index.ts'), - join(basePath, 'index.tsx') - ] - - return candidates.find((candidate) => existsSync(candidate)) ?? null -} - -/** - * Находит первый cycle в module graph. - */ -const findCycle = (graph) => { - const visited = new Set() - const active = new Set() - const path = [] - - /** - * Обходит один module node depth-first. - */ - const visit = (moduleId) => { - if (active.has(moduleId)) { - const cycleStart = path.indexOf(moduleId) - - return [...path.slice(cycleStart), moduleId] - } - - if (visited.has(moduleId)) { - return null - } - - visited.add(moduleId) - active.add(moduleId) - path.push(moduleId) - - for (const dependency of graph.get(moduleId) ?? []) { - const cycle = visit(dependency) - - if (cycle !== null) { - return cycle - } - } - - path.pop() - active.delete(moduleId) - - return null - } - - for (const moduleId of graph.keys()) { - const cycle = visit(moduleId) - - if (cycle !== null) { - return cycle - } - } - - return null -} - -const violations = [] -const graph = new Map() -const sourceFiles = collectSourceFiles(SOURCE_ROOT) -const parserProbe = getModuleSpecifiers( - 'architecture-probe.ts', - ` - import '@/infra/simple-rest-api' - export { useAuth } from '@/domains/auth' - type Auth = import('@/domains/auth').AuthContextValue - const lazy = import('@/compositions/screens/catalog') - ` -) -const expectedProbeSpecifiers = [ - '@/infra/simple-rest-api', - '@/domains/auth', - '@/domains/auth', - '@/compositions/screens/catalog' -] - -expectedProbeSpecifiers.forEach((specifier) => { - if (!parserProbe.includes(specifier)) { - violations.push(`architecture parser fixture missed ${specifier}`) - } -}) - -if (!isDeepModuleImport('shared/lib/value-predicates/value-predicates', 'domains/cart')) { - violations.push('architecture boundary fixture did not detect shared module deep import') -} - -if (isLayerImportAllowed('shared/bridge.ts', 'domains/auth/index.ts')) { - violations.push('architecture boundary fixture allowed shared to import domains relatively') -} - -MODULE_ROOTS.forEach((moduleRoot) => { - const moduleDirectory = join(SOURCE_ROOT, moduleRoot) - const publicEntry = join(moduleDirectory, 'index.ts') - - if (!existsSync(moduleDirectory) || !existsSync(publicEntry)) { - violations.push(`${moduleRoot}: declared module must be a folder with index.ts public API`) - } -}) - -sourceFiles.forEach((sourceFile) => { - const sourcePath = getSourcePath(sourceFile) - const sourceLayer = getLayer(sourcePath) - const sourceModuleId = getModuleId(sourcePath) - const relativeSource = relative(PROJECT_ROOT, sourceFile) - const sourceCode = readFileSync(sourceFile, 'utf8') - - if (MODULAR_LAYERS.has(sourceLayer) && sourceModuleId === null) { - violations.push(`${relativeSource}: implementation file has no declared module-owner`) - } - - getModuleSpecifiers(sourceFile, sourceCode).forEach((specifier) => { - if (specifier.startsWith('@/')) { - const targetPath = specifier.slice(2) - const targetLayer = getLayer(targetPath) - const targetModuleId = getModuleId(targetPath) - - if (!isLayerImportAllowed(sourcePath, targetPath)) { - violations.push(`${relativeSource}: ${sourceLayer} cannot import ${targetLayer} via ${specifier}`) - } - - if (isDeepModuleImport(targetPath, sourceModuleId)) { - violations.push(`${relativeSource}: deep import into ${targetModuleId} via ${specifier}`) - } - - if (sourceModuleId !== null && targetModuleId !== null && sourceModuleId !== targetModuleId) { - const dependencies = graph.get(sourceModuleId) ?? new Set() - dependencies.add(targetModuleId) - graph.set(sourceModuleId, dependencies) - } - - return - } - - if (specifier.startsWith('.')) { - const targetFile = resolveRelativeImport(sourceFile, specifier) - - if (targetFile === null) { - return - } - - const targetModuleId = getModuleId(getSourcePath(targetFile)) - const targetPath = getSourcePath(targetFile) - const targetLayer = getLayer(targetPath) - - if (!isLayerImportAllowed(sourcePath, targetPath)) { - violations.push( - `${relativeSource}: ${sourceLayer} cannot import ${targetLayer} via ${specifier}` - ) - } - - if ( - sourceModuleId !== null && - targetModuleId !== null && - sourceModuleId !== targetModuleId - ) { - violations.push(`${relativeSource}: relative import crosses into ${targetModuleId}`) - } - } - }) - - const isRestModuleFile = sourceModuleId === 'infra/simple-rest-api' - - if (sourceCode.includes('/generated') && !isRestModuleFile) { - violations.push(`${relativeSource}: generated SDK import escaped infra/simple-rest-api`) - } -}) - -const cycle = findCycle(graph) - -if (cycle !== null) { - violations.push(`module dependency cycle: ${cycle.join(' -> ')}`) -} - -if (violations.length > 0) { - console.error('SLM architecture check failed:') - violations.forEach((violation) => console.error(`- ${violation}`)) - process.exitCode = 1 -} else { - console.log( - `SLM architecture check passed for ${sourceFiles.length} files and ${MODULE_ROOTS.length} declared modules.` - ) -} diff --git a/examples/demo-frontend/src/app/(store)/admin/products/page.tsx b/examples/demo-frontend/src/app/(store)/admin/products/page.tsx deleted file mode 100644 index 3e7c937..0000000 --- a/examples/demo-frontend/src/app/(store)/admin/products/page.tsx +++ /dev/null @@ -1,17 +0,0 @@ -import type { Metadata } from 'next' - -import { ProductAdminScreen } from '@/compositions/screens/product-admin' - -/** - * Metadata catalog administration route. - */ -export const metadata: Metadata = { - title: 'Product admin' -} - -/** - * Подключает admin composition к framework route. - */ -export default function ProductAdminPage() { - return -} diff --git a/examples/demo-frontend/src/app/(store)/cart/page.tsx b/examples/demo-frontend/src/app/(store)/cart/page.tsx deleted file mode 100644 index e8d4739..0000000 --- a/examples/demo-frontend/src/app/(store)/cart/page.tsx +++ /dev/null @@ -1,17 +0,0 @@ -import type { Metadata } from 'next' - -import { CartScreen } from '@/compositions/screens/cart' - -/** - * Metadata cart route. - */ -export const metadata: Metadata = { - title: 'Cart' -} - -/** - * Подключает multi-domain checkout composition к cart route. - */ -export default function CartPage() { - return -} diff --git a/examples/demo-frontend/src/app/(store)/layout.tsx b/examples/demo-frontend/src/app/(store)/layout.tsx deleted file mode 100644 index 4818274..0000000 --- a/examples/demo-frontend/src/app/(store)/layout.tsx +++ /dev/null @@ -1,12 +0,0 @@ -import { StoreShellLayout } from '@/compositions/layouts/store-shell' - -import type { AppLayoutProps } from '../types/app-layout-props.type' - -/** - * Подключает общий storefront layout ко всем leaf routes группы. - */ -export default function StoreLayout(props: AppLayoutProps) { - const { children } = props - - return {children} -} diff --git a/examples/demo-frontend/src/app/(store)/orders/page.tsx b/examples/demo-frontend/src/app/(store)/orders/page.tsx deleted file mode 100644 index 8797d4e..0000000 --- a/examples/demo-frontend/src/app/(store)/orders/page.tsx +++ /dev/null @@ -1,17 +0,0 @@ -import type { Metadata } from 'next' - -import { OrdersScreen } from '@/compositions/screens/orders' - -/** - * Metadata protected orders route. - */ -export const metadata: Metadata = { - title: 'Orders' -} - -/** - * Подключает protected orders composition к framework route. - */ -export default function OrdersPage() { - return -} diff --git a/examples/demo-frontend/src/app/(store)/page.tsx b/examples/demo-frontend/src/app/(store)/page.tsx deleted file mode 100644 index d5b6de6..0000000 --- a/examples/demo-frontend/src/app/(store)/page.tsx +++ /dev/null @@ -1,8 +0,0 @@ -import { CatalogScreen } from '@/compositions/screens/catalog' - -/** - * Подключает catalog composition к корневому route. - */ -export default function CatalogPage() { - return -} diff --git a/examples/demo-frontend/src/app/(store)/products/[productId]/page.tsx b/examples/demo-frontend/src/app/(store)/products/[productId]/page.tsx deleted file mode 100644 index e7deb90..0000000 --- a/examples/demo-frontend/src/app/(store)/products/[productId]/page.tsx +++ /dev/null @@ -1,21 +0,0 @@ -import type { Metadata } from 'next' - -import { ProductDetailScreen } from '@/compositions/screens/product-detail' - -import type { ProductPageProps } from '../../../types/product-page-props.type' - -/** - * Metadata dynamic product route без запроса к runtime fixture во время build. - */ -export const metadata: Metadata = { - title: 'Product detail' -} - -/** - * Адаптирует async dynamic segment к product detail composition. - */ -export default async function ProductPage(props: ProductPageProps) { - const { productId } = await props.params - - return -} diff --git a/examples/demo-frontend/src/app/(store)/sign-in/page.tsx b/examples/demo-frontend/src/app/(store)/sign-in/page.tsx deleted file mode 100644 index 897dbc4..0000000 --- a/examples/demo-frontend/src/app/(store)/sign-in/page.tsx +++ /dev/null @@ -1,17 +0,0 @@ -import type { Metadata } from 'next' - -import { SignInScreen } from '@/compositions/screens/sign-in' - -/** - * Metadata sign-in route. - */ -export const metadata: Metadata = { - title: 'Sign in' -} - -/** - * Подключает auth composition к framework route. - */ -export default function SignInPage() { - return -} diff --git a/examples/demo-frontend/src/app/favicon.ico b/examples/demo-frontend/src/app/favicon.ico deleted file mode 100644 index 718d6fe..0000000 Binary files a/examples/demo-frontend/src/app/favicon.ico and /dev/null differ diff --git a/examples/demo-frontend/src/app/globals.css b/examples/demo-frontend/src/app/globals.css deleted file mode 100644 index b4902ce..0000000 --- a/examples/demo-frontend/src/app/globals.css +++ /dev/null @@ -1,85 +0,0 @@ -:root { - --color-paper: #f3efe4; - --color-surface: #faf7ef; - --color-surface-strong: #e6e0d4; - --color-ink: #191918; - --color-ink-muted: #65615a; - --color-ink-faint: #67635c; - --color-line: #d7d0c3; - --color-line-strong: #aaa297; - --color-blue: #2847f4; - --color-acid: #c9ff50; - --color-acid-dark: #6f9c00; - --color-orange: #ff8a3d; - --color-error: #b83228; - --color-focus: #8dacff; - --font-body: Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif; - --font-display: 'Arial Black', 'Helvetica Neue', Arial, sans-serif; - --font-mono: 'SFMono-Regular', Consolas, 'Liberation Mono', monospace; - --shadow-card: 0 10px 40px rgb(37 31 22 / 7%); - --shadow-card-hover: 0 20px 55px rgb(37 31 22 / 14%); -} - -html { - color: var(--color-ink); - background: var(--color-paper); - scroll-behavior: smooth; - height: 100%; -} - -html, -body { - max-width: 100vw; - overflow-x: hidden; -} - -body { - min-height: 100%; - display: flex; - flex-direction: column; - color: var(--color-ink); - background: - radial-gradient(circle at 15% 0%, rgb(255 255 255 / 58%), transparent 25%), - var(--color-paper); - font-family: var(--font-body); - -webkit-font-smoothing: antialiased; - -moz-osx-font-smoothing: grayscale; -} - -* { - box-sizing: border-box; - padding: 0; - margin: 0; -} - -a { - color: inherit; - text-decoration: none; -} - -button, -input, -select, -textarea { - font: inherit; -} - -::selection { - color: var(--color-ink); - background: var(--color-acid); -} - -@media (prefers-reduced-motion: reduce) { - html { - scroll-behavior: auto; - } - - *, - *::before, - *::after { - scroll-behavior: auto !important; - transition-duration: 0.01ms !important; - animation-duration: 0.01ms !important; - animation-iteration-count: 1 !important; - } -} diff --git a/examples/demo-frontend/src/app/layout.tsx b/examples/demo-frontend/src/app/layout.tsx deleted file mode 100644 index ba38117..0000000 --- a/examples/demo-frontend/src/app/layout.tsx +++ /dev/null @@ -1,31 +0,0 @@ -import type { Metadata } from 'next' - -import { AppProviders } from './providers' -import type { AppLayoutProps } from './types/app-layout-props.type' -import './globals.css' - -/** - * Статические metadata архитектурного demo-приложения. - */ -export const metadata: Metadata = { - title: { - default: 'Layer Supply', - template: '%s / Layer Supply' - }, - description: 'A complete Next.js storefront demonstrating Scoped Layered Module Design.' -} - -/** - * Корневая Next.js entry point с application-scoped providers. - */ -export default function RootLayout(props: AppLayoutProps) { - const { children } = props - - return ( - - - {children} - - - ) -} diff --git a/examples/demo-frontend/src/app/providers.tsx b/examples/demo-frontend/src/app/providers.tsx deleted file mode 100644 index 9b0343f..0000000 --- a/examples/demo-frontend/src/app/providers.tsx +++ /dev/null @@ -1,49 +0,0 @@ -'use client' - -import { AuthProvider, useAuth } from '@/domains/auth' -import { CartProvider } from '@/domains/cart' -import { SimpleRestApiProvider } from '@/infra/simple-rest-api' - -import type { AppLayoutProps } from './types/app-layout-props.type' - -/** - * Создаёт новый REST cache при каждой смене logical auth session. - * - * Используется для: - * - исключения DTO предыдущего пользователя из нового session scope - */ -const AuthScopedRestApiProvider = (props: AppLayoutProps) => { - const { children } = props - const auth = useAuth() - const cacheScopeKey = auth.status === 'checking' - ? `checking:${auth.sessionKey ?? 'anonymous'}` - : auth.sessionKey ?? auth.status - - return ( - - {children} - - ) -} - -/** - * Подключает application-scoped domain state и auth-scoped REST cache. - * - * Используется для: - * - сохранения auth/cart state между route transitions - * - пересоздания technical cache на границе пользовательской сессии - */ -export const AppProviders = (props: AppLayoutProps) => { - const { children } = props - - return ( - - - {children} - - - ) -} diff --git a/examples/demo-frontend/src/app/types/app-layout-props.type.ts b/examples/demo-frontend/src/app/types/app-layout-props.type.ts deleted file mode 100644 index d7802e3..0000000 --- a/examples/demo-frontend/src/app/types/app-layout-props.type.ts +++ /dev/null @@ -1,9 +0,0 @@ -import type { ReactNode } from 'react' - -/** - * Props framework layouts, передающих готовое route subtree. - */ -export type AppLayoutProps = { - /** Вложенное route subtree. */ - children: ReactNode -} diff --git a/examples/demo-frontend/src/app/types/product-page-props.type.ts b/examples/demo-frontend/src/app/types/product-page-props.type.ts deleted file mode 100644 index 5e0f54b..0000000 --- a/examples/demo-frontend/src/app/types/product-page-props.type.ts +++ /dev/null @@ -1,10 +0,0 @@ -/** - * Props dynamic product route в Next.js 16. - */ -export type ProductPageProps = { - /** Асинхронные параметры dynamic segment. */ - params: Promise<{ - /** Идентификатор продукта из URL. */ - productId: string - }> -} diff --git a/examples/demo-frontend/src/compositions/layouts/store-shell/index.ts b/examples/demo-frontend/src/compositions/layouts/store-shell/index.ts deleted file mode 100644 index f211945..0000000 --- a/examples/demo-frontend/src/compositions/layouts/store-shell/index.ts +++ /dev/null @@ -1,2 +0,0 @@ -export { StoreShellLayout } from './store-shell.layout' -export type { StoreShellLayoutProps } from './types/store-shell-layout-props.type' diff --git a/examples/demo-frontend/src/compositions/layouts/store-shell/store-shell.layout.tsx b/examples/demo-frontend/src/compositions/layouts/store-shell/store-shell.layout.tsx deleted file mode 100644 index 829afc6..0000000 --- a/examples/demo-frontend/src/compositions/layouts/store-shell/store-shell.layout.tsx +++ /dev/null @@ -1,134 +0,0 @@ -'use client' - -import cl from 'clsx' -import Link from 'next/link' -import { usePathname } from 'next/navigation' - -import { useAuth } from '@/domains/auth' -import { useCart } from '@/domains/cart' -import { DemoToolbarWidget } from '@/compositions/widgets/demo-toolbar' -import { Button } from '@/ui/button' - -import type { StoreShellLayoutProps } from './types/store-shell-layout-props.type' -import styles from './styles/store-shell.module.css' - -/** - * Общий каркас навигации и application-scoped controls магазина. - * - * Используется для: - * - сохранения auth/cart контекста между App Router страницами - * - доступа к deterministic demo controls - */ -export const StoreShellLayout = (props: StoreShellLayoutProps) => { - const { children, className, ...rootAttrs } = props - const pathname = usePathname() - const auth = useAuth() - const cart = useCart() - const isAdmin = auth.status === 'authenticated' && auth.user?.role === 'admin' - const hasSession = auth.sessionKey !== null - const accountLabel = auth.user?.name ?? 'Sign in' - - /** - * Определяет активный верхнеуровневый navigation item. - */ - const isActivePath = (path: string): boolean => { - return path === '/' ? pathname === '/' : pathname.startsWith(path) - } - - const isCatalogActive = isActivePath('/') - const isCartActive = isActivePath('/cart') - const isOrdersActive = isActivePath('/orders') - const isAdminActive = isActivePath('/admin') - - /** - * Завершает текущую сессию из общего header. - */ - const handleSignOut = (): void => { - void auth.signOut() - } - - /** - * Пытается восстановить unreadable cart явным empty snapshot. - */ - const handleCartReset = (): void => { - void cart.clearCart() - } - - return ( -
-
- - LS - - Layer -
- Supply -
- - - - -
- {auth.status} - - {accountLabel} - - {hasSession && ( - - )} -
- - {cart.error && ( -
- {cart.error.message} - -
- )} -
- -
{children}
- -
- Next.js 16 + SLM Level 1 - Simple API / localhost:3001 -
- - -
- ) -} diff --git a/examples/demo-frontend/src/compositions/layouts/store-shell/styles/store-shell.module.css b/examples/demo-frontend/src/compositions/layouts/store-shell/styles/store-shell.module.css deleted file mode 100644 index d04b9c6..0000000 --- a/examples/demo-frontend/src/compositions/layouts/store-shell/styles/store-shell.module.css +++ /dev/null @@ -1,186 +0,0 @@ -.root { - min-height: 100vh; -} - -.header { - position: sticky; - top: 0; - z-index: 30; - display: grid; - grid-template-columns: auto 1fr auto; - align-items: center; - gap: clamp(18px, 4vw, 56px); - min-height: 82px; - padding: 12px clamp(18px, 5vw, 72px); - border-bottom: 1px solid var(--color-line); - background: color-mix(in srgb, var(--color-paper) 88%, transparent); - backdrop-filter: blur(18px); -} - -.brand { - display: inline-flex; - align-items: center; - gap: 10px; -} - -.brandMark { - display: grid; - place-items: center; - width: 42px; - height: 42px; - color: var(--color-paper); - border-radius: 50%; - background: var(--color-blue); - font-family: var(--font-display); - font-size: 0.9rem; -} - -.brandText { - font-family: var(--font-display); - font-size: 0.86rem; - font-weight: 820; - letter-spacing: 0.04em; - line-height: 0.82; - text-transform: uppercase; -} - -.nav { - display: flex; - align-items: center; - gap: clamp(14px, 3vw, 34px); -} - -.navLink { - position: relative; - padding: 10px 0; - color: var(--color-ink-muted); - font-size: 0.76rem; - font-weight: 760; - letter-spacing: 0.08em; - text-transform: uppercase; -} - -.navLink::after { - position: absolute; - right: 0; - bottom: 3px; - left: 0; - height: 3px; - border-radius: 3px; - background: var(--color-orange); - transform: scaleX(0); - transition: transform 160ms ease; - content: ''; -} - -.navLink:hover, -.active { - color: var(--color-ink); -} - -.active::after { - transform: scaleX(1); -} - -.count { - display: inline-grid; - place-items: center; - min-width: 20px; - height: 20px; - margin-left: 3px; - padding: 0 5px; - color: var(--color-ink); - border-radius: 999px; - background: var(--color-acid); - font-family: var(--font-mono); - font-size: 0.66rem; -} - -.account { - display: grid; - grid-template-columns: auto auto; - align-items: center; - justify-items: end; - gap: 2px 10px; -} - -.accountMeta { - grid-column: 1 / -1; - color: var(--color-ink-faint); - font-family: var(--font-mono); - font-size: 0.6rem; - letter-spacing: 0.08em; - text-transform: uppercase; -} - -.accountLink { - max-width: 160px; - overflow: hidden; - font-size: 0.82rem; - font-weight: 720; - text-overflow: ellipsis; - white-space: nowrap; -} - -.cartError { - grid-column: 1 / -1; - display: flex; - align-items: center; - justify-content: space-between; - gap: 12px; - width: 100%; - padding: 8px 12px; - color: var(--color-error); - border-left: 3px solid var(--color-error); - background: color-mix(in srgb, var(--color-error) 8%, transparent); - font-size: 0.75rem; -} - -.page { - min-height: calc(100vh - 160px); -} - -.footer { - display: flex; - justify-content: space-between; - gap: 24px; - padding: 28px clamp(18px, 5vw, 72px) 90px; - color: var(--color-ink-faint); - border-top: 1px solid var(--color-line); - font-family: var(--font-mono); - font-size: 0.68rem; - letter-spacing: 0.06em; - text-transform: uppercase; -} - -@media (max-width: 850px) { - .header { - grid-template-columns: 1fr auto; - } - - .nav { - grid-column: 1 / -1; - grid-row: 2; - overflow-x: auto; - } -} - -@media (max-width: 540px) { - .header { - padding-inline: 14px; - } - - .brandText, - .accountMeta { - display: none; - } - - .nav { - gap: 18px; - } - - .footer { - flex-direction: column; - padding-inline: 18px; - } -} diff --git a/examples/demo-frontend/src/compositions/layouts/store-shell/types/store-shell-layout-props.type.ts b/examples/demo-frontend/src/compositions/layouts/store-shell/types/store-shell-layout-props.type.ts deleted file mode 100644 index afe0a47..0000000 --- a/examples/demo-frontend/src/compositions/layouts/store-shell/types/store-shell-layout-props.type.ts +++ /dev/null @@ -1,16 +0,0 @@ -import type { ComponentPropsWithoutRef } from 'react' - -/** - * Параметры общего storefront layout. - */ -export type StoreShellLayoutParams = object - -/** - * Атрибуты корневого контейнера. - */ -type RootAttrs = ComponentPropsWithoutRef<'div'> - -/** - * Props общего storefront layout. - */ -export type StoreShellLayoutProps = RootAttrs & StoreShellLayoutParams diff --git a/examples/demo-frontend/src/compositions/screens/cart/cart.screen.tsx b/examples/demo-frontend/src/compositions/screens/cart/cart.screen.tsx deleted file mode 100644 index 9ba08cf..0000000 --- a/examples/demo-frontend/src/compositions/screens/cart/cart.screen.tsx +++ /dev/null @@ -1,374 +0,0 @@ -'use client' - -import type { ChangeEvent, FocusEvent } from 'react' -import { useEffect, useRef, useState } from 'react' -import cl from 'clsx' -import Image from 'next/image' -import Link from 'next/link' -import { useRouter } from 'next/navigation' - -import { useAuth } from '@/domains/auth' -import { getCartProductQuantityLimit, useCart } from '@/domains/cart' -import { PRODUCT_IMAGE_PLACEHOLDER, useCatalogCommands } from '@/domains/catalog' -import { useOrderCommands, validateOrderDraft } from '@/domains/orders' -import type { OrderDraftLine } from '@/domains/orders' -import { formatMoney } from '@/shared/lib/format-money' -import { isEmptyArray, isNonEmptyArray } from '@/shared/lib/value-predicates' -import { Button } from '@/ui/button' -import { FeedbackPanel } from '@/ui/feedback-panel' - -import type { CartScreenProps } from './types/cart-screen-props.type' -import styles from './styles/cart.module.css' - -/** - * Multi-domain cart and checkout composition. - * - * Используется для: - * - координации cart, auth и orders без междоменного цикла - * - отображения stock и checkout errors - */ -export const CartScreen = (props: CartScreenProps) => { - const { className, ...rootAttrs } = props - const cart = useCart() - const auth = useAuth() - const catalogCommands = useCatalogCommands() - const orderCommands = useOrderCommands() - const router = useRouter() - const [isSubmitting, setIsSubmitting] = useState(false) - const [checkoutError, setCheckoutError] = useState(null) - const [quantityDraftsById, setQuantityDraftsById] = useState>({}) - const isMountedRef = useRef(false) - const checkoutAttemptRef = useRef(0) - const hasLines = isNonEmptyArray(cart.lines) - const orderDraft: OrderDraftLine[] = cart.lines.map((line) => ({ - productId: line.product.id, - quantity: line.quantity, - availableStock: line.product.stock, - currency: line.product.currency, - expectedVersion: line.product.version, - expectedUnitPriceCents: line.product.priceCents - })) - const draftError = validateOrderDraft(orderDraft) - const canCheckout = draftError === null - const totalLabel = cart.currency === null - ? 'Mixed currencies' - : formatMoney(cart.subtotalCents, cart.currency) - const checkoutLabel = auth.status === 'authenticated' - ? 'Place order' - : auth.status === 'unavailable' - ? 'Retry session first' - : 'Sign in to checkout' - - useEffect(() => { - isMountedRef.current = true - - return () => { - isMountedRef.current = false - checkoutAttemptRef.current += 1 - } - }, []) - - /** - * Сохраняет локальный quantity draft до commit на blur. - */ - const handleQuantityChange = ( - event: ChangeEvent, - productId: string - ): void => { - const value = event.currentTarget.value - - setQuantityDraftsById((currentDrafts) => ({ - ...currentDrafts, - [productId]: value - })) - } - - /** - * Применяет завершённое редактирование quantity без удаления на пустом draft. - */ - const handleQuantityCommit = ( - event: FocusEvent, - productId: string - ): void => { - const value = event.currentTarget.value.trim() - - if (value === '') { - setQuantityDraftsById((currentDrafts) => { - const nextDrafts = { ...currentDrafts } - - delete nextDrafts[productId] - - return nextDrafts - }) - return - } - - const quantity = Number(value) - - if (Number.isFinite(quantity)) { - void cart.setQuantity(productId, quantity) - } - - setQuantityDraftsById((currentDrafts) => { - const nextDrafts = { ...currentDrafts } - - delete nextDrafts[productId] - - return nextDrafts - }) - } - - /** - * Проверяет lifecycle текущей checkout-попытки после async boundary. - */ - const isCheckoutAttemptActive = (attemptId: number): boolean => { - return isMountedRef.current && checkoutAttemptRef.current === attemptId - } - - /** - * Создаёт order из cart snapshot или направляет гостя к auth route. - */ - const handleCheckout = async (): Promise => { - if (auth.status === 'unavailable') { - const result = await auth.refreshCurrentUser() - - if (!result.isSuccess) { - setCheckoutError(result.error.message) - } - return - } - - if (auth.status !== 'authenticated') { - router.push('/sign-in') - return - } - - const sessionKey = auth.sessionKey - - if (sessionKey === null || isSubmitting) { - return - } - - const cartRevision = cart.revision - const attemptId = checkoutAttemptRef.current + 1 - - checkoutAttemptRef.current = attemptId - - setIsSubmitting(true) - setCheckoutError(null) - - const productsResult = await catalogCommands.loadProducts( - orderDraft.map((line) => line.productId) - ) - - if (!isCheckoutAttemptActive(attemptId)) { - return - } - - if (!productsResult.isSuccess) { - setIsSubmitting(false) - setCheckoutError(productsResult.error.message) - return - } - - if (!auth.isCurrentSession(sessionKey)) { - setIsSubmitting(false) - setCheckoutError('The active session changed. Start checkout again.') - return - } - - const reconciliationStatus = await cart.reconcileProducts( - productsResult.data, - cartRevision - ) - - if (!isCheckoutAttemptActive(attemptId)) { - return - } - - if (reconciliationStatus === 'stale') { - setIsSubmitting(false) - setCheckoutError('The cart changed during checkout. Review it and try again.') - return - } - - if (reconciliationStatus === 'unavailable') { - setIsSubmitting(false) - setCheckoutError('Cart storage is unavailable. Checkout was not started.') - return - } - - if (reconciliationStatus === 'updated') { - setIsSubmitting(false) - setCheckoutError('Inventory changed. Review the refreshed totals before checkout.') - return - } - - const cartRevisionStatus = await cart.isCurrentRevision(cartRevision) - - if (!isCheckoutAttemptActive(attemptId)) { - return - } - - if (cartRevisionStatus === 'unavailable') { - setIsSubmitting(false) - setCheckoutError('Cart storage is unavailable. Checkout was not started.') - return - } - - if (cartRevisionStatus === 'stale' || !auth.isCurrentSession(sessionKey)) { - setIsSubmitting(false) - setCheckoutError('The checkout scope changed. Review the cart and try again.') - return - } - - const result = await orderCommands.createOrder(orderDraft, sessionKey) - - if (result.isSuccess) { - const clearStatus = await cart.clearCart(cartRevision) - - if (!isCheckoutAttemptActive(attemptId)) { - return - } - - setIsSubmitting(false) - - if (clearStatus === 'unavailable') { - setCheckoutError('The order was created, but cart storage could not be updated.') - return - } - - if (clearStatus === 'stale') { - setCheckoutError('The order was created, but the cart changed in another tab.') - return - } - - if (auth.isCurrentSession(sessionKey)) { - router.push('/orders') - } - return - } - - if (!isCheckoutAttemptActive(attemptId)) { - return - } - - setIsSubmitting(false) - setCheckoutError(result.error.message) - } - - return ( -
-
-
-

Cart / composition-owned checkout

-

Selected objects

-
- {cart.itemCount} units -
- - {isEmptyArray(cart.lines) && cart.isHydrated && ( - - - Open catalog - - - )} - - {hasLines && ( -
-
- {cart.lines.map((line) => ( -
- - { - event.currentTarget.srcset = '' - event.currentTarget.src = PRODUCT_IMAGE_PLACEHOLDER - }} - /> - -
- {line.product.categoryId.replace('category-', '')} - -

{line.product.name}

- - {formatMoney(line.product.priceCents, line.product.currency)} -
- - -
- ))} -
- - -
- )} -
- ) -} diff --git a/examples/demo-frontend/src/compositions/screens/cart/index.ts b/examples/demo-frontend/src/compositions/screens/cart/index.ts deleted file mode 100644 index 67e9737..0000000 --- a/examples/demo-frontend/src/compositions/screens/cart/index.ts +++ /dev/null @@ -1,2 +0,0 @@ -export { CartScreen } from './cart.screen' -export type { CartScreenProps } from './types/cart-screen-props.type' diff --git a/examples/demo-frontend/src/compositions/screens/cart/styles/cart.module.css b/examples/demo-frontend/src/compositions/screens/cart/styles/cart.module.css deleted file mode 100644 index c23c7a2..0000000 --- a/examples/demo-frontend/src/compositions/screens/cart/styles/cart.module.css +++ /dev/null @@ -1,200 +0,0 @@ -.root { - display: grid; - gap: 34px; - padding: clamp(42px, 7vw, 100px) clamp(18px, 6vw, 90px) 100px; -} - -.header { - display: flex; - align-items: end; - justify-content: space-between; - gap: 20px; - padding-bottom: 28px; - border-bottom: 1px solid var(--color-line); -} - -.kicker, -.sequence, -.lineInfo > span, -.quantity span { - color: var(--color-blue); - font-family: var(--font-mono); - font-size: 0.66rem; - letter-spacing: 0.1em; - text-transform: uppercase; -} - -.header h1 { - margin-top: 13px; - font-family: var(--font-display); - font-size: clamp(3rem, 8vw, 7.5rem); - letter-spacing: -0.055em; - line-height: 0.82; -} - -.count { - padding: 10px 14px; - border: 1px solid var(--color-line-strong); - border-radius: 999px; - font-family: var(--font-mono); - font-size: 0.72rem; - text-transform: uppercase; -} - -.layout { - display: grid; - grid-template-columns: minmax(0, 1fr) minmax(300px, 0.38fr); - gap: clamp(30px, 6vw, 90px); - align-items: start; -} - -.lines { - display: grid; -} - -.line { - display: grid; - grid-template-columns: 132px minmax(0, 1fr) 100px auto; - align-items: center; - gap: 22px; - padding: 18px 0; - border-bottom: 1px solid var(--color-line); -} - -.imageWrap { - position: relative; - overflow: hidden; - aspect-ratio: 1; - border-radius: 15px; - background: var(--color-surface-strong); -} - -.image { - object-fit: cover; -} - -.lineInfo { - display: grid; - gap: 7px; -} - -.lineInfo h2 { - font-family: var(--font-display); - font-size: clamp(1.4rem, 3vw, 2.25rem); - line-height: 0.95; -} - -.lineInfo strong { - font-family: var(--font-mono); - font-size: 0.76rem; -} - -.quantity { - display: grid; - gap: 7px; -} - -.quantity input { - width: 80px; - min-height: 42px; - padding: 0 10px; - border: 1px solid var(--color-line-strong); - border-radius: 10px; - background: var(--color-surface); - font: inherit; -} - -.summary { - position: sticky; - top: 110px; - display: grid; - gap: 24px; - padding: clamp(24px, 4vw, 42px); - border-radius: 24px; - background: var(--color-acid); -} - -.sequence { - color: color-mix(in srgb, var(--color-ink) 58%, transparent); -} - -.summary h2 { - font-family: var(--font-display); - font-size: clamp(2rem, 4vw, 3.6rem); - line-height: 0.9; -} - -.summary dl { - display: grid; -} - -.summary dl div { - display: flex; - justify-content: space-between; - gap: 20px; - padding: 13px 0; - border-bottom: 1px solid color-mix(in srgb, var(--color-ink) 18%, transparent); -} - -.summary dt { - color: color-mix(in srgb, var(--color-ink) 60%, transparent); - font-size: 0.8rem; -} - -.summary dd { - font-family: var(--font-mono); - font-size: 0.76rem; -} - -.warning, -.error { - padding: 11px 13px; - border-radius: 10px; - font-size: 0.78rem; - line-height: 1.45; -} - -.warning { - background: rgb(255 255 255 / 38%); -} - -.error { - color: var(--color-error); - background: rgb(255 255 255 / 55%); -} - -.catalogLink { - padding: 14px 20px; - color: var(--color-paper); - border-radius: 999px; - background: var(--color-ink); - font-size: 0.72rem; - font-weight: 760; - letter-spacing: 0.08em; - text-transform: uppercase; -} - -@media (max-width: 980px) { - .layout { - grid-template-columns: 1fr; - } - - .summary { - position: static; - } -} - -@media (max-width: 700px) { - .line { - grid-template-columns: 92px 1fr; - } - - .quantity, - .line > button { - grid-column: 2; - } - - .quantity input { - width: 100%; - } -} diff --git a/examples/demo-frontend/src/compositions/screens/cart/types/cart-screen-props.type.ts b/examples/demo-frontend/src/compositions/screens/cart/types/cart-screen-props.type.ts deleted file mode 100644 index 0649ee0..0000000 --- a/examples/demo-frontend/src/compositions/screens/cart/types/cart-screen-props.type.ts +++ /dev/null @@ -1,16 +0,0 @@ -import type { ComponentPropsWithoutRef } from 'react' - -/** - * Параметры cart and checkout screen. - */ -export type CartScreenParams = object - -/** - * Атрибуты корневого main без внешнего содержимого. - */ -type RootAttrs = Omit, 'children'> - -/** - * Props cart and checkout screen. - */ -export type CartScreenProps = RootAttrs & CartScreenParams diff --git a/examples/demo-frontend/src/compositions/screens/catalog/catalog.screen.tsx b/examples/demo-frontend/src/compositions/screens/catalog/catalog.screen.tsx deleted file mode 100644 index 2895ba0..0000000 --- a/examples/demo-frontend/src/compositions/screens/catalog/catalog.screen.tsx +++ /dev/null @@ -1,217 +0,0 @@ -'use client' - -import type { ChangeEvent } from 'react' -import { useDeferredValue, useState } from 'react' -import cl from 'clsx' - -import { useCategories, useProductCatalog } from '@/domains/catalog' -import type { CatalogFilters } from '@/domains/catalog' -import { useOnDemoFixtureChange } from '@/domains/demo-control' -import { isEmptyArray, isNonEmptyArray, isOneOf } from '@/shared/lib/value-predicates' -import { Button } from '@/ui/button' -import { FeedbackPanel } from '@/ui/feedback-panel' - -import { ProductCard } from './ui/product-card' -import type { CatalogScreenProps } from './types/catalog-screen-props.type' -import styles from './styles/catalog.module.css' - -const CATALOG_SORTS = ['newest', 'price-asc', 'price-desc', 'name'] as const -const SKELETON_IDS = ['one', 'two', 'three', 'four', 'five', 'six'] as const - -/** - * Публичная витрина с фильтрами и offset pagination. - * - * Используется для: - * - поиска и сравнения fixture-продуктов - * - демонстрации loading, empty и transport error outcomes - */ -export const CatalogScreen = (props: CatalogScreenProps) => { - const { className, ...rootAttrs } = props - const [filters, setFilters] = useState({ - page: 1, - limit: 12, - search: '', - categoryId: '', - sort: 'newest' - }) - const deferredSearch = useDeferredValue(filters.search) - const catalog = useProductCatalog({ ...filters, search: deferredSearch }) - const categoriesState = useCategories() - const hasProducts = isNonEmptyArray(catalog.products) - const shouldShowEmpty = - !catalog.isLoading && catalog.error === null && isEmptyArray(catalog.products) - const resultSummary = catalog.pagination - ? `${catalog.pagination.total} objects / page ${catalog.pagination.page}` - : 'Connecting to inventory' - const canGoBack = (catalog.pagination?.page ?? 1) > 1 - const canGoForward = - catalog.pagination !== null && catalog.pagination.page < catalog.pagination.totalPages - - useOnDemoFixtureChange((kind) => { - if (kind === 'data') { - setFilters((current) => ({ ...current, page: 1 })) - } - }) - - /** - * Обновляет search-фильтр, сохраняя ввод отзывчивым через deferred value. - */ - const handleSearchChange = (event: ChangeEvent): void => { - setFilters((current) => ({ ...current, search: event.target.value, page: 1 })) - } - - /** - * Применяет category-фильтр и возвращает выдачу на первую страницу. - */ - const handleCategoryChange = (event: ChangeEvent): void => { - setFilters((current) => ({ ...current, categoryId: event.target.value, page: 1 })) - } - - /** - * Применяет только поддерживаемую доменом сортировку. - */ - const handleSortChange = (event: ChangeEvent): void => { - const value = event.target.value - - if (isOneOf(value, CATALOG_SORTS)) { - setFilters((current) => ({ ...current, sort: value, page: 1 })) - } - } - - /** - * Загружает предыдущую страницу выдачи. - */ - const handlePreviousPage = (): void => { - setFilters((current) => ({ ...current, page: Math.max(1, current.page - 1) })) - } - - /** - * Загружает следующую страницу выдачи. - */ - const handleNextPage = (): void => { - setFilters((current) => ({ ...current, page: current.page + 1 })) - } - - /** - * Повторяет неуспешный catalog query. - */ - const handleReload = (): void => { - void catalog.reload() - } - - return ( -
-
-

Deterministic objects for frontend systems

-

- Useful things, -
- visible boundaries. -

-

- A storefront where every loading state, cache edge, auth transition and conflict can be - reproduced on demand. -

- 01 / CATALOG -
- -
-
- - - - - -
- -
- {resultSummary} - {catalog.isRefreshing && Refreshing} -
- - {catalog.isLoading && ( -
- {SKELETON_IDS.map((id) => ( -
- ))} -
- )} - - {catalog.error && ( - - - - )} - - {shouldShowEmpty && ( - - )} - - {hasProducts && ( -
- {catalog.products.map((product, index) => ( - - ))} -
- )} - - {catalog.pagination && (catalog.pagination.totalPages > 1 || filters.page > 1) && ( -
- - - {catalog.pagination.page} / {catalog.pagination.totalPages} - - -
- )} -
-
- ) -} diff --git a/examples/demo-frontend/src/compositions/screens/catalog/index.ts b/examples/demo-frontend/src/compositions/screens/catalog/index.ts deleted file mode 100644 index e49ebba..0000000 --- a/examples/demo-frontend/src/compositions/screens/catalog/index.ts +++ /dev/null @@ -1,2 +0,0 @@ -export { CatalogScreen } from './catalog.screen' -export type { CatalogScreenProps } from './types/catalog-screen-props.type' diff --git a/examples/demo-frontend/src/compositions/screens/catalog/styles/catalog.module.css b/examples/demo-frontend/src/compositions/screens/catalog/styles/catalog.module.css deleted file mode 100644 index b3bba7c..0000000 --- a/examples/demo-frontend/src/compositions/screens/catalog/styles/catalog.module.css +++ /dev/null @@ -1,212 +0,0 @@ -.root { - padding-bottom: 80px; -} - -.hero { - position: relative; - display: grid; - grid-template-columns: minmax(0, 1fr) minmax(250px, 0.34fr); - gap: 20px 60px; - min-height: 520px; - padding: clamp(72px, 11vw, 150px) clamp(20px, 7vw, 110px) 80px; - overflow: hidden; - border-bottom: 1px solid var(--color-line); -} - -.hero::before { - position: absolute; - top: 18%; - right: -8vw; - width: clamp(230px, 34vw, 520px); - aspect-ratio: 1; - border: 1px solid var(--color-line-strong); - border-radius: 50%; - box-shadow: - inset 0 0 0 38px var(--color-paper), - inset 0 0 0 39px var(--color-line); - content: ''; -} - -.hero::after { - position: absolute; - right: 15%; - bottom: -140px; - width: 240px; - height: 300px; - background: var(--color-acid); - transform: rotate(24deg); - content: ''; -} - -.kicker, -.sequence { - position: relative; - z-index: 1; - font-family: var(--font-mono); - font-size: 0.7rem; - letter-spacing: 0.12em; - text-transform: uppercase; -} - -.kicker { - grid-column: 1 / -1; - color: var(--color-blue); -} - -.title { - position: relative; - z-index: 1; - max-width: 10ch; - font-family: var(--font-display); - font-size: clamp(3.3rem, 9vw, 8.8rem); - letter-spacing: -0.065em; - line-height: 0.78; -} - -.lede { - position: relative; - z-index: 1; - align-self: end; - max-width: 33ch; - color: var(--color-ink-muted); - font-size: clamp(1rem, 1.6vw, 1.3rem); - line-height: 1.58; -} - -.sequence { - position: absolute; - bottom: 30px; - left: clamp(20px, 7vw, 110px); - color: var(--color-ink-faint); -} - -.catalog { - display: grid; - gap: 26px; - padding: 54px clamp(18px, 5vw, 72px) 0; -} - -.filters { - display: grid; - grid-template-columns: minmax(240px, 1fr) repeat(2, minmax(180px, 0.35fr)); - gap: 14px; -} - -.searchField, -.selectField { - display: grid; - gap: 8px; -} - -.filters label > span { - color: var(--color-ink-faint); - font-size: 0.66rem; - font-weight: 760; - letter-spacing: 0.1em; - text-transform: uppercase; -} - -.filters :is(input, select) { - width: 100%; - min-height: 50px; - padding: 0 15px; - color: var(--color-ink); - border: 1px solid var(--color-line-strong); - border-radius: 13px; - outline: none; - background: var(--color-surface); - font: inherit; -} - -.filters :is(input, select):focus { - border-color: var(--color-blue); - box-shadow: 0 0 0 3px color-mix(in srgb, var(--color-blue) 14%, transparent); -} - -.resultBar { - display: flex; - justify-content: space-between; - min-height: 24px; - color: var(--color-ink-faint); - font-family: var(--font-mono); - font-size: 0.68rem; - letter-spacing: 0.08em; - text-transform: uppercase; -} - -.refreshing { - color: var(--color-blue); -} - -.productGrid { - display: grid; - grid-template-columns: repeat(3, minmax(0, 1fr)); - gap: clamp(14px, 2vw, 26px); -} - -.skeleton { - min-height: 410px; - border: 1px solid var(--color-line); - border-radius: 22px; - background: - linear-gradient(105deg, transparent 30%, rgb(255 255 255 / 48%) 45%, transparent 60%) - 0 0 / 220% 100%, - var(--color-surface-strong); - animation: sweep 1.4s linear infinite; -} - -.pagination { - display: flex; - align-items: center; - justify-content: center; - gap: 24px; - padding-top: 18px; - font-family: var(--font-mono); - font-size: 0.72rem; -} - -@keyframes sweep { - to { - background-position: -220% 0; - } -} - -@media (max-width: 980px) { - .hero { - grid-template-columns: 1fr; - } - - .lede { - max-width: 50ch; - } - - .filters, - .productGrid { - grid-template-columns: repeat(2, minmax(0, 1fr)); - } - - .searchField { - grid-column: 1 / -1; - } -} - -@media (max-width: 640px) { - .hero { - min-height: 440px; - padding-top: 70px; - } - - .hero::after { - right: -40px; - width: 130px; - } - - .filters, - .productGrid { - grid-template-columns: 1fr; - } - - .searchField { - grid-column: auto; - } -} diff --git a/examples/demo-frontend/src/compositions/screens/catalog/types/catalog-screen-props.type.ts b/examples/demo-frontend/src/compositions/screens/catalog/types/catalog-screen-props.type.ts deleted file mode 100644 index 66d3a1a..0000000 --- a/examples/demo-frontend/src/compositions/screens/catalog/types/catalog-screen-props.type.ts +++ /dev/null @@ -1,16 +0,0 @@ -import type { ComponentPropsWithoutRef } from 'react' - -/** - * Параметры storefront catalog screen. - */ -export type CatalogScreenParams = object - -/** - * Атрибуты корневого main без внешнего содержимого. - */ -type RootAttrs = Omit, 'children'> - -/** - * Props storefront catalog screen. - */ -export type CatalogScreenProps = RootAttrs & CatalogScreenParams diff --git a/examples/demo-frontend/src/compositions/screens/catalog/ui/product-card/index.ts b/examples/demo-frontend/src/compositions/screens/catalog/ui/product-card/index.ts deleted file mode 100644 index db1e46f..0000000 --- a/examples/demo-frontend/src/compositions/screens/catalog/ui/product-card/index.ts +++ /dev/null @@ -1,2 +0,0 @@ -export { ProductCard } from './product-card' -export type { ProductCardProps } from './types/product-card-props.type' diff --git a/examples/demo-frontend/src/compositions/screens/catalog/ui/product-card/product-card.tsx b/examples/demo-frontend/src/compositions/screens/catalog/ui/product-card/product-card.tsx deleted file mode 100644 index d0721fd..0000000 --- a/examples/demo-frontend/src/compositions/screens/catalog/ui/product-card/product-card.tsx +++ /dev/null @@ -1,74 +0,0 @@ -'use client' - -import cl from 'clsx' -import Image from 'next/image' -import Link from 'next/link' - -import { useCart } from '@/domains/cart' -import { PRODUCT_IMAGE_PLACEHOLDER } from '@/domains/catalog' -import { formatMoney } from '@/shared/lib/format-money' -import { Button } from '@/ui/button' - -import type { ProductCardProps } from './types/product-card-props.type' -import styles from './styles/product-card.module.css' - -/** - * Карточка продукта для grid-выдачи catalog screen. - * - * Используется для: - * - перехода к detail route - * - добавления доступного продукта в application cart - */ -export const ProductCard = (props: ProductCardProps) => { - const { product, index, className, ...rootAttrs } = props - const cart = useCart() - const isSoldOut = product.stock === 0 - const stockLabel = isSoldOut ? 'Out of stock' : `${product.stock} ready` - - /** - * Добавляет продукт в cart domain без transport-зависимости компонента. - */ - const handleAdd = (): void => { - void cart.addProduct(product) - } - - return ( -
- - { - event.currentTarget.srcset = '' - event.currentTarget.src = PRODUCT_IMAGE_PLACEHOLDER - }} - /> - {String(index + 1).padStart(2, '0')} - - -
-
- {stockLabel} - {product.rating.toFixed(1)} / 5 -
- -

{product.name}

- -

{product.description}

-
- {formatMoney(product.priceCents, product.currency)} - -
-
-
- ) -} diff --git a/examples/demo-frontend/src/compositions/screens/catalog/ui/product-card/styles/product-card.module.css b/examples/demo-frontend/src/compositions/screens/catalog/ui/product-card/styles/product-card.module.css deleted file mode 100644 index a14023b..0000000 --- a/examples/demo-frontend/src/compositions/screens/catalog/ui/product-card/styles/product-card.module.css +++ /dev/null @@ -1,98 +0,0 @@ -.root { - display: grid; - overflow: hidden; - min-width: 0; - border: 1px solid var(--color-line); - border-radius: 22px; - background: var(--color-surface); - box-shadow: var(--shadow-card); - transition: - transform 200ms ease, - box-shadow 200ms ease; -} - -.root:hover { - box-shadow: var(--shadow-card-hover); - transform: translateY(-5px); -} - -.imageLink { - position: relative; - display: block; - overflow: hidden; - aspect-ratio: 4 / 3; - background: var(--color-surface-strong); -} - -.image { - object-fit: cover; - filter: saturate(0.78) contrast(1.04); - transition: transform 420ms cubic-bezier(0.2, 0.8, 0.2, 1); -} - -.root:hover .image { - transform: scale(1.035); -} - -.index { - position: absolute; - top: 12px; - left: 12px; - display: grid; - place-items: center; - width: 38px; - height: 38px; - color: var(--color-paper); - border-radius: 50%; - background: var(--color-blue); - font-family: var(--font-mono); - font-size: 0.7rem; -} - -.body { - display: grid; - grid-template-rows: auto auto 1fr auto; - gap: 13px; - padding: 20px; -} - -.meta, -.footer { - display: flex; - align-items: center; - justify-content: space-between; - gap: 14px; -} - -.meta { - color: var(--color-ink-faint); - font-family: var(--font-mono); - font-size: 0.64rem; - letter-spacing: 0.07em; - text-transform: uppercase; -} - -.name { - font-family: var(--font-display); - font-size: clamp(1.35rem, 2.5vw, 2rem); - line-height: 0.98; -} - -.description { - display: -webkit-box; - overflow: hidden; - color: var(--color-ink-muted); - font-size: 0.9rem; - line-height: 1.55; - -webkit-box-orient: vertical; - -webkit-line-clamp: 2; -} - -.footer { - padding-top: 4px; -} - -.price { - font-family: var(--font-mono); - font-size: 0.94rem; -} diff --git a/examples/demo-frontend/src/compositions/screens/catalog/ui/product-card/types/product-card-props.type.ts b/examples/demo-frontend/src/compositions/screens/catalog/ui/product-card/types/product-card-props.type.ts deleted file mode 100644 index b810789..0000000 --- a/examples/demo-frontend/src/compositions/screens/catalog/ui/product-card/types/product-card-props.type.ts +++ /dev/null @@ -1,23 +0,0 @@ -import type { ComponentPropsWithoutRef } from 'react' - -import type { Product } from '@/domains/catalog' - -/** - * Собственные параметры product card внутри catalog screen. - */ -export type ProductCardParams = { - /** Catalog-модель продукта. */ - product: Product - /** Позиция карточки для editorial numbering. */ - index: number -} - -/** - * Атрибуты корневой article-карточки без внешнего содержимого. - */ -type RootAttrs = Omit, 'children'> - -/** - * Props product card внутри catalog screen. - */ -export type ProductCardProps = RootAttrs & ProductCardParams diff --git a/examples/demo-frontend/src/compositions/screens/orders/index.ts b/examples/demo-frontend/src/compositions/screens/orders/index.ts deleted file mode 100644 index e60a5a9..0000000 --- a/examples/demo-frontend/src/compositions/screens/orders/index.ts +++ /dev/null @@ -1,2 +0,0 @@ -export { OrdersScreen } from './orders.screen' -export type { OrdersScreenProps } from './types/orders-screen-props.type' diff --git a/examples/demo-frontend/src/compositions/screens/orders/orders.screen.tsx b/examples/demo-frontend/src/compositions/screens/orders/orders.screen.tsx deleted file mode 100644 index a825044..0000000 --- a/examples/demo-frontend/src/compositions/screens/orders/orders.screen.tsx +++ /dev/null @@ -1,212 +0,0 @@ -'use client' - -import { useState } from 'react' -import cl from 'clsx' -import Link from 'next/link' - -import { useAuth } from '@/domains/auth' -import { canCancelOrder, useOrderCommands, useOrders } from '@/domains/orders' -import type { OrderError } from '@/domains/orders' -import { formatDate } from '@/shared/lib/format-date' -import { formatMoney } from '@/shared/lib/format-money' -import { isEmptyArray, isNonEmptyArray } from '@/shared/lib/value-predicates' -import { Button } from '@/ui/button' -import { FeedbackPanel } from '@/ui/feedback-panel' - -import type { OrdersScreenProps } from './types/orders-screen-props.type' -import styles from './styles/orders.module.css' - -/** - * Protected история заказов с допустимыми state transitions. - * - * Используется для: - * - проверки auth-aware deferred query - * - демонстрации успешной и конфликтной отмены заказа - */ -export const OrdersScreen = (props: OrdersScreenProps) => { - const { className, ...rootAttrs } = props - const auth = useAuth() - const isAuthenticated = auth.status === 'authenticated' - const ordersState = useOrders(isAuthenticated) - const orderCommands = useOrderCommands() - const [cancellingOrderId, setCancellingOrderId] = useState(null) - const [actionError, setActionError] = useState(null) - const hasOrders = isNonEmptyArray(ordersState.orders) - - /** - * Повторяет неуспешный protected orders query. - */ - const handleReload = (): void => { - void ordersState.reload() - } - - /** - * Повторяет recoverable auth check перед protected query. - */ - const handleSessionRetry = (): void => { - void auth.refreshCurrentUser() - } - - /** - * Запрашивает допустимый переход заказа в cancelled. - */ - const handleCancel = async (orderId: string): Promise => { - const sessionKey = auth.sessionKey - - if (sessionKey === null) { - return - } - - setCancellingOrderId(orderId) - setActionError(null) - - const result = await orderCommands.cancelOrder(orderId, sessionKey) - - setCancellingOrderId(null) - - if (!result.isSuccess) { - setActionError(result.error) - } - } - - if (auth.status === 'checking') { - return ( -
- -
- ) - } - - if (!isAuthenticated) { - if (auth.status === 'unavailable') { - return ( -
- - - -
- ) - } - - return ( -
- - - Choose account - - -
- ) - } - - return ( -
-
-
-

Orders / {auth.user?.role}

-

State in motion

-
-

- Backend transitions remain authoritative. The UI only offers cancellation where the - current domain model allows it. -

-
- - {ordersState.isLoading && ( -
- )} - - {ordersState.error && ( - - - - )} - - {actionError &&

{actionError.message}

} - - {isEmptyArray(ordersState.orders) && !ordersState.isLoading && ordersState.error === null && ( - - - Browse catalog - - - )} - - {hasOrders && ( -
- {ordersState.orders.map((order, index) => { - const canCancel = canCancelOrder(order) - const isCancelling = cancellingOrderId === order.id - - return ( -
-
{String(index + 1).padStart(2, '0')}
-
-
- {formatDate(order.createdAt)} -

{order.id}

-
- - {order.status} - -
- -
- {order.lines.map((line) => ( -
- {line.quantity} x - {line.productName} - {formatMoney(line.unitPriceCents, order.currency)} -
- ))} -
- -
- {formatMoney(order.totalCents, order.currency)} - {canCancel && ( - - )} -
-
- ) - })} -
- )} -
- ) -} diff --git a/examples/demo-frontend/src/compositions/screens/orders/styles/orders.module.css b/examples/demo-frontend/src/compositions/screens/orders/styles/orders.module.css deleted file mode 100644 index b4f4ad5..0000000 --- a/examples/demo-frontend/src/compositions/screens/orders/styles/orders.module.css +++ /dev/null @@ -1,187 +0,0 @@ -.root { - display: grid; - gap: 38px; - padding: clamp(48px, 8vw, 112px) clamp(18px, 7vw, 110px) 110px; -} - -.header { - display: grid; - grid-template-columns: minmax(0, 1fr) minmax(260px, 0.42fr); - gap: 40px; - align-items: end; - padding-bottom: 34px; - border-bottom: 1px solid var(--color-line); -} - -.kicker { - color: var(--color-blue); - font-family: var(--font-mono); - font-size: 0.68rem; - letter-spacing: 0.1em; - text-transform: uppercase; -} - -.header h1 { - max-width: 9ch; - margin-top: 14px; - font-family: var(--font-display); - font-size: clamp(3rem, 8vw, 7.4rem); - letter-spacing: -0.055em; - line-height: 0.8; -} - -.lede { - color: var(--color-ink-muted); - font-size: 1rem; - line-height: 1.7; -} - -.orders { - display: grid; - grid-template-columns: repeat(2, minmax(0, 1fr)); - gap: 20px; -} - -.order { - position: relative; - display: grid; - gap: 26px; - overflow: hidden; - padding: clamp(24px, 4vw, 38px); - border: 1px solid var(--color-line); - border-radius: 24px; - background: var(--color-surface); - box-shadow: var(--shadow-card); -} - -.orderIndex { - position: absolute; - right: -8px; - bottom: -22px; - color: color-mix(in srgb, var(--color-ink) 5%, transparent); - font-family: var(--font-display); - font-size: 9rem; - line-height: 1; - pointer-events: none; -} - -.orderHeader, -.orderFooter, -.line { - position: relative; - z-index: 1; - display: flex; - align-items: center; - justify-content: space-between; - gap: 18px; -} - -.orderHeader span, -.line span { - color: var(--color-ink-faint); - font-family: var(--font-mono); - font-size: 0.66rem; -} - -.orderHeader h2 { - margin-top: 6px; - font-family: var(--font-display); - font-size: 1.7rem; -} - -.status { - padding: 8px 11px; - border-radius: 999px; - background: var(--color-surface-strong); - letter-spacing: 0.08em; - text-transform: uppercase; -} - -.status[data-status='pending'] { - color: #845300; - background: #ffe5ad; -} - -.status[data-status='paid'] { - color: #215a00; - background: #dfffa0; -} - -.status[data-status='shipped'] { - color: #123ec0; - background: #dae4ff; -} - -.status[data-status='cancelled'] { - color: #8b2920; - background: #ffd9d5; -} - -.lines { - position: relative; - z-index: 1; - display: grid; -} - -.line { - display: grid; - grid-template-columns: auto 1fr auto; - padding: 11px 0; - border-bottom: 1px solid var(--color-line); -} - -.line strong { - font-size: 0.84rem; -} - -.orderFooter { - padding-top: 4px; -} - -.orderFooter > strong { - font-family: var(--font-mono); - font-size: 1rem; -} - -.actionError { - padding: 14px 18px; - color: var(--color-error); - border-left: 5px solid var(--color-error); - background: color-mix(in srgb, var(--color-error) 8%, transparent); -} - -.loading { - min-height: 380px; - border-radius: 24px; - background: var(--color-surface-strong); - animation: pulse 1s ease-in-out infinite alternate; -} - -.outcome { - min-height: calc(100vh - 82px); - place-items: center; -} - -.primaryLink { - padding: 14px 20px; - color: var(--color-paper); - border-radius: 999px; - background: var(--color-ink); - font-size: 0.72rem; - font-weight: 760; - letter-spacing: 0.08em; - text-transform: uppercase; -} - -@keyframes pulse { - to { - opacity: 0.45; - } -} - -@media (max-width: 850px) { - .header, - .orders { - grid-template-columns: 1fr; - } -} diff --git a/examples/demo-frontend/src/compositions/screens/orders/types/orders-screen-props.type.ts b/examples/demo-frontend/src/compositions/screens/orders/types/orders-screen-props.type.ts deleted file mode 100644 index 62aac45..0000000 --- a/examples/demo-frontend/src/compositions/screens/orders/types/orders-screen-props.type.ts +++ /dev/null @@ -1,16 +0,0 @@ -import type { ComponentPropsWithoutRef } from 'react' - -/** - * Параметры protected orders screen. - */ -export type OrdersScreenParams = object - -/** - * Атрибуты корневого main без внешнего содержимого. - */ -type RootAttrs = Omit, 'children'> - -/** - * Props protected orders screen. - */ -export type OrdersScreenProps = RootAttrs & OrdersScreenParams diff --git a/examples/demo-frontend/src/compositions/screens/product-admin/index.ts b/examples/demo-frontend/src/compositions/screens/product-admin/index.ts deleted file mode 100644 index 287840d..0000000 --- a/examples/demo-frontend/src/compositions/screens/product-admin/index.ts +++ /dev/null @@ -1,2 +0,0 @@ -export { ProductAdminScreen } from './product-admin.screen' -export type { ProductAdminScreenProps } from './types/product-admin-screen-props.type' diff --git a/examples/demo-frontend/src/compositions/screens/product-admin/product-admin.screen.tsx b/examples/demo-frontend/src/compositions/screens/product-admin/product-admin.screen.tsx deleted file mode 100644 index d913d8c..0000000 --- a/examples/demo-frontend/src/compositions/screens/product-admin/product-admin.screen.tsx +++ /dev/null @@ -1,351 +0,0 @@ -'use client' - -import { useState } from 'react' -import cl from 'clsx' -import Link from 'next/link' - -import { useAuth } from '@/domains/auth' -import { - useCategories, - useCatalogCommands, - useProductCatalog -} from '@/domains/catalog' -import type { - CatalogError, - CreateProductInput, - Product, - UpdateProductInput -} from '@/domains/catalog' -import { useOnDemoFixtureChange } from '@/domains/demo-control' -import { formatMoney } from '@/shared/lib/format-money' -import { isNonEmptyArray } from '@/shared/lib/value-predicates' -import { Button } from '@/ui/button' -import { FeedbackPanel } from '@/ui/feedback-panel' - -import { ProductForm } from './ui/product-form' -import type { ProductAdminScreenProps } from './types/product-admin-screen-props.type' -import styles from './styles/product-admin.module.css' - -/** - * Admin-only catalog mutation workspace. - * - * Используется для: - * - create/update/delete RBAC-сценариев - * - проверки optimistic-lock conflict outcome - */ -export const ProductAdminScreen = (props: ProductAdminScreenProps) => { - const { className, ...rootAttrs } = props - const auth = useAuth() - const catalogCommands = useCatalogCommands() - const [adminPage, setAdminPage] = useState(1) - const catalog = useProductCatalog({ - page: adminPage, - limit: 100, - search: '', - categoryId: '', - sort: 'newest' - }) - const categoriesState = useCategories() - const [selectedProduct, setSelectedProduct] = useState(null) - const [formGeneration, setFormGeneration] = useState(0) - const [isSubmitting, setIsSubmitting] = useState(false) - const [deletingProductId, setDeletingProductId] = useState(null) - const [mutationError, setMutationError] = useState(null) - const [successMessage, setSuccessMessage] = useState(null) - const isAdmin = auth.status === 'authenticated' && auth.user?.role === 'admin' - const hasProducts = isNonEmptyArray(catalog.products) - const canGoBack = (catalog.pagination?.page ?? 1) > 1 - const canGoForward = - catalog.pagination !== null && catalog.pagination.page < catalog.pagination.totalPages - const isSelectedProductAvailable = - selectedProduct !== null && - ( - catalog.isLoading || - catalog.error !== null || - catalog.products.some((product) => product.id === selectedProduct.id) - ) - const formProduct = isSelectedProductAvailable ? selectedProduct : null - const formKey = `${formProduct?.id ?? 'new'}-${formProduct?.version ?? 0}-${categoriesState.categories.length}-${formGeneration}` - - useOnDemoFixtureChange((kind) => { - if (kind === 'data') { - setAdminPage(1) - setSelectedProduct(null) - setMutationError(null) - setFormGeneration((value) => value + 1) - } - }) - - /** - * Переключает product form в edit mode с последним catalog snapshot. - */ - const handleEdit = (product: Product): void => { - setSelectedProduct(product) - setMutationError(null) - setSuccessMessage(null) - } - - /** - * Возвращает product form в create mode. - */ - const handleCancelEdit = (): void => { - setSelectedProduct(null) - setMutationError(null) - setFormGeneration((value) => value + 1) - } - - /** - * Выбирает create или update domain scenario по форме input. - */ - const handleSubmit = async ( - input: CreateProductInput | UpdateProductInput - ): Promise => { - const sessionKey = auth.sessionKey - - if (sessionKey === null) { - return - } - - setIsSubmitting(true) - setMutationError(null) - setSuccessMessage(null) - - const result = 'id' in input - ? await catalogCommands.updateProduct(input, sessionKey) - : await catalogCommands.createProduct(input, sessionKey) - - setIsSubmitting(false) - - if (!result.isSuccess) { - setMutationError(result.error) - return - } - - setSuccessMessage(`${result.data.name} saved at version ${result.data.version}.`) - setSelectedProduct(null) - setFormGeneration((value) => value + 1) - } - - /** - * Удаляет продукт после явного browser confirmation. - */ - const handleRemove = async (product: Product): Promise => { - const sessionKey = auth.sessionKey - - if (sessionKey === null) { - return - } - - const shouldRemove = window.confirm(`Remove ${product.name}?`) - - if (!shouldRemove) { - return - } - - setDeletingProductId(product.id) - setMutationError(null) - setSuccessMessage(null) - - const result = await catalogCommands.removeProduct(product.id, sessionKey) - - setDeletingProductId(null) - - if (!result.isSuccess) { - setMutationError(result.error) - return - } - - setSuccessMessage(`${product.name} removed.`) - - if (selectedProduct?.id === product.id) { - setSelectedProduct(null) - setFormGeneration((value) => value + 1) - } - } - - /** - * Повторяет catalog query после transport failure. - */ - const handleReload = (): void => { - void catalog.reload() - } - - /** - * Открывает предыдущую admin inventory page. - */ - const handlePreviousPage = (): void => { - setAdminPage((page) => Math.max(1, page - 1)) - } - - /** - * Открывает следующую admin inventory page. - */ - const handleNextPage = (): void => { - setAdminPage((page) => page + 1) - } - - /** - * Повторяет recoverable auth check перед admin workspace. - */ - const handleSessionRetry = (): void => { - void auth.refreshCurrentUser() - } - - /** - * Загружает authority snapshot после optimistic-lock conflict. - */ - const handleLoadLatest = async (): Promise => { - if (formProduct === null) { - return - } - - setIsSubmitting(true) - const result = await catalogCommands.loadProducts([formProduct.id]) - setIsSubmitting(false) - - if (!result.isSuccess) { - setMutationError(result.error) - return - } - - const latestProduct = result.data[0] - - if (latestProduct !== undefined) { - setSelectedProduct(latestProduct) - setMutationError(null) - setSuccessMessage('Loaded the latest product version. Review changes before saving.') - } - } - - if (auth.status === 'checking') { - return ( -
- -
- ) - } - - if (!isAdmin) { - if (auth.status === 'unavailable') { - return ( -
- - - -
- ) - } - - return ( -
- - - Sign in as admin - - -
- ) - } - - return ( -
-
-
-

Catalog / protected mutations

-

Versioned inventory

-
-

- Every update carries the last-read version. Use the conflict demo scenario to inspect - the domain error without exposing transport payloads. -

-
- - {catalog.error && ( - - - - )} - -
-
- - {mutationError?.code === 'conflict' && formProduct !== null && ( - - )} -
- -
-
-
- Live snapshot -

Inventory

-
- {catalog.pagination?.total ?? 0} products -
- - {successMessage &&

{successMessage}

} - - {hasProducts && ( -
- {catalog.products.map((product) => ( -
-
- v{product.version} / {product.stock} stock -

{product.name}

- {formatMoney(product.priceCents, product.currency)} -
-
- - -
-
- ))} -
- )} - - {catalog.pagination && (catalog.pagination.totalPages > 1 || adminPage > 1) && ( -
- - {catalog.pagination.page} / {catalog.pagination.totalPages} - -
- )} -
-
-
- ) -} diff --git a/examples/demo-frontend/src/compositions/screens/product-admin/styles/product-admin.module.css b/examples/demo-frontend/src/compositions/screens/product-admin/styles/product-admin.module.css deleted file mode 100644 index 4a0004b..0000000 --- a/examples/demo-frontend/src/compositions/screens/product-admin/styles/product-admin.module.css +++ /dev/null @@ -1,162 +0,0 @@ -.root { - display: grid; - gap: 42px; - padding: clamp(46px, 7vw, 100px) clamp(18px, 6vw, 90px) 110px; -} - -.header { - display: grid; - grid-template-columns: minmax(0, 1fr) minmax(280px, 0.42fr); - gap: 42px; - align-items: end; - padding-bottom: 34px; - border-bottom: 1px solid var(--color-line); -} - -.kicker, -.inventoryHeader span, -.productRow span { - color: var(--color-blue); - font-family: var(--font-mono); - font-size: 0.65rem; - letter-spacing: 0.09em; - text-transform: uppercase; -} - -.header h1 { - max-width: 10ch; - margin-top: 14px; - font-family: var(--font-display); - font-size: clamp(3rem, 7.5vw, 7.2rem); - letter-spacing: -0.055em; - line-height: 0.8; -} - -.header > p { - color: var(--color-ink-muted); - line-height: 1.7; -} - -.workspace { - display: grid; - grid-template-columns: minmax(360px, 0.78fr) minmax(0, 1.22fr); - gap: clamp(24px, 5vw, 70px); - align-items: start; -} - -.formColumn { - display: grid; - gap: 12px; -} - -.inventory { - display: grid; - gap: 18px; -} - -.inventoryHeader { - display: flex; - align-items: end; - justify-content: space-between; - gap: 20px; - padding-bottom: 18px; - border-bottom: 1px solid var(--color-line); -} - -.inventoryHeader h2 { - margin-top: 7px; - font-family: var(--font-display); - font-size: clamp(2rem, 4vw, 3.6rem); - line-height: 0.9; -} - -.inventoryHeader strong { - color: var(--color-ink-faint); - font-family: var(--font-mono); - font-size: 0.68rem; - text-transform: uppercase; -} - -.productList { - display: grid; -} - -.productRow { - display: grid; - grid-template-columns: 1fr auto; - align-items: center; - gap: 18px; - padding: 17px 0; - border-bottom: 1px solid var(--color-line); -} - -.productRow h3 { - margin: 6px 0; - font-family: var(--font-display); - font-size: 1.45rem; - line-height: 1; -} - -.productRow small { - color: var(--color-ink-muted); - font-family: var(--font-mono); - font-size: 0.72rem; -} - -.rowActions { - display: flex; - flex-wrap: wrap; - justify-content: end; - gap: 8px; -} - -.success { - padding: 12px 14px; - color: #315c00; - border-left: 4px solid var(--color-acid-dark); - background: color-mix(in srgb, var(--color-acid) 28%, transparent); - font-size: 0.82rem; -} - -.pagination { - display: flex; - align-items: center; - justify-content: center; - gap: 14px; - padding-top: 12px; - font-family: var(--font-mono); - font-size: 0.68rem; -} - -.outcome { - min-height: calc(100vh - 82px); - place-items: center; -} - -.primaryLink { - padding: 14px 20px; - color: var(--color-paper); - border-radius: 999px; - background: var(--color-ink); - font-size: 0.72rem; - font-weight: 760; - letter-spacing: 0.08em; - text-transform: uppercase; -} - -@media (max-width: 980px) { - .header, - .workspace { - grid-template-columns: 1fr; - } -} - -@media (max-width: 560px) { - .productRow { - grid-template-columns: 1fr; - } - - .rowActions { - justify-content: start; - } -} diff --git a/examples/demo-frontend/src/compositions/screens/product-admin/types/product-admin-screen-props.type.ts b/examples/demo-frontend/src/compositions/screens/product-admin/types/product-admin-screen-props.type.ts deleted file mode 100644 index c8e06ae..0000000 --- a/examples/demo-frontend/src/compositions/screens/product-admin/types/product-admin-screen-props.type.ts +++ /dev/null @@ -1,16 +0,0 @@ -import type { ComponentPropsWithoutRef } from 'react' - -/** - * Параметры product administration screen. - */ -export type ProductAdminScreenParams = object - -/** - * Атрибуты корневого main без внешнего содержимого. - */ -type RootAttrs = Omit, 'children'> - -/** - * Props product administration screen. - */ -export type ProductAdminScreenProps = RootAttrs & ProductAdminScreenParams diff --git a/examples/demo-frontend/src/compositions/screens/product-admin/ui/product-form/index.ts b/examples/demo-frontend/src/compositions/screens/product-admin/ui/product-form/index.ts deleted file mode 100644 index 0ce90e6..0000000 --- a/examples/demo-frontend/src/compositions/screens/product-admin/ui/product-form/index.ts +++ /dev/null @@ -1,2 +0,0 @@ -export { ProductForm } from './product-form' -export type { ProductFormProps } from './types/product-form-props.type' diff --git a/examples/demo-frontend/src/compositions/screens/product-admin/ui/product-form/product-form.tsx b/examples/demo-frontend/src/compositions/screens/product-admin/ui/product-form/product-form.tsx deleted file mode 100644 index 52d5418..0000000 --- a/examples/demo-frontend/src/compositions/screens/product-admin/ui/product-form/product-form.tsx +++ /dev/null @@ -1,272 +0,0 @@ -'use client' - -import type { ChangeEvent, FormEvent } from 'react' -import { useState } from 'react' -import cl from 'clsx' - -import type { CatalogCurrency, CreateProductInput, UpdateProductInput } from '@/domains/catalog' -import { isOneOf } from '@/shared/lib/value-predicates' -import { Button } from '@/ui/button' -import { FormField } from '@/ui/form-field' - -import type { ProductFormProps } from './types/product-form-props.type' -import styles from './styles/product-form.module.css' - -const CURRENCIES = ['USD', 'EUR'] as const - -/** - * Локальное редактируемое состояние admin-формы. - */ -type ProductFormValues = { - /** Название продукта. */ - name: string - /** Полное описание. */ - description: string - /** Цена в cents как form string. */ - priceCents: string - /** Валюта. */ - currency: CatalogCurrency - /** Выбранная категория. */ - categoryId: string - /** Stock как form string. */ - stock: string - /** URL удалённого изображения. */ - imageUrl: string -} - -/** - * Создаёт initial form state из create mode или catalog snapshot. - */ -const createInitialValues = (props: ProductFormProps): ProductFormValues => { - const { product, categories } = props - - if (product !== null) { - return { - name: product.name, - description: product.description, - priceCents: String(product.priceCents), - currency: product.currency, - categoryId: product.categoryId, - stock: String(product.stock), - imageUrl: product.imageUrl - } - } - - return { - name: '', - description: '', - priceCents: '9900', - currency: 'USD', - categoryId: categories[0]?.id ?? '', - stock: '10', - imageUrl: 'https://picsum.photos/seed/new-object/640/480' - } -} - -/** - * Форма create/update с сохранением optimistic-lock версии продукта. - * - * Используется для: - * - создания fixture-продукта - * - редактирования последнего catalog snapshot - */ -export const ProductForm = (props: ProductFormProps) => { - const { - categories, - product, - isSubmitting, - error, - onSubmit, - onCancel, - className, - ...rootAttrs - } = props - const [values, setValues] = useState(() => createInitialValues(props)) - const isEditMode = product !== null - const title = isEditMode ? `Edit ${product.name}` : 'Create object' - const submitLabel = isEditMode ? 'Save version' : 'Create product' - - /** - * Обновляет текстовое поле формы по имени control. - */ - const handleTextChange = ( - event: ChangeEvent - ): void => { - const field = event.target.name - const value = event.target.value - - if (field === 'name' || field === 'description' || field === 'imageUrl') { - setValues((current) => ({ ...current, [field]: value })) - } - } - - /** - * Обновляет числовое поле, сохраняя browser form representation. - */ - const handleNumberChange = (event: ChangeEvent): void => { - const field = event.target.name - const value = event.target.value - - if (field === 'priceCents' || field === 'stock') { - setValues((current) => ({ ...current, [field]: value })) - } - } - - /** - * Обновляет выбранную category. - */ - const handleCategoryChange = (event: ChangeEvent): void => { - setValues((current) => ({ ...current, categoryId: event.target.value })) - } - - /** - * Обновляет только поддерживаемую backend currency. - */ - const handleCurrencyChange = (event: ChangeEvent): void => { - const value = event.target.value - - if (isOneOf(value, CURRENCIES)) { - setValues((current) => ({ ...current, currency: value })) - } - } - - /** - * Передаёт нормализованный domain input screen-владельцу. - */ - const handleSubmit = async (event: FormEvent): Promise => { - event.preventDefault() - - const baseInput: CreateProductInput = { - name: values.name.trim(), - description: values.description.trim(), - priceCents: Number(values.priceCents), - currency: values.currency, - categoryId: values.categoryId, - stock: Number(values.stock), - imageUrl: values.imageUrl.trim() - } - - if (product !== null) { - const updateInput: UpdateProductInput = { - ...baseInput, - id: product.id, - version: product.version - } - - await onSubmit(updateInput) - return - } - - await onSubmit(baseInput) - } - - return ( -
-
-
- {isEditMode ? `Version ${product.version}` : 'New catalog entry'} -

{title}

-
- {isEditMode && ( - - )} -
- - - - - - -