commit 037e36f4f49815f362750b2cd6b35952d2749cdb Author: Lance Date: Fri Jan 16 15:33:40 2026 +0800 Initial commit diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..a2ae0da --- /dev/null +++ b/.env.example @@ -0,0 +1,13 @@ +# ENVIRONMENT can be development, production, test, or etc... +# Setting to production will disable swagger docs & will enable secure HTTP-only cookies +ENVIRONMENT=development +# Postgres connection string +POSTGRES_URL="postgresql://username:password@localhost:5432/express-starter?schema=public" +# JWT session token secret +JWT_SECRET=express-starter-session +# JWT session token duration in seconds (default is 7,200 seconds or 2 hours) +JWT_DURATION=7200 +# JWT refresh token duration in seconds (default is 14,400 seconds or 4 hours) +JWT_REFRESH_DURATION=14400 +# JWT refresh token secret +JWT_REFRESH_SECRET=express-starter-refresh \ No newline at end of file diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ad1256b --- /dev/null +++ b/.gitignore @@ -0,0 +1,8 @@ +dist +node_modules +tsconfig.tsbuildinfo + +.env +/src/generated/prisma + +coverage \ No newline at end of file diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..d456fed --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,7 @@ +{ + "editor.tabSize": 2, + "editor.codeActionsOnSave": { + "source.organizeImports.biome": "explicit", + "source.fixAll.biome": "explicit" + } +} \ No newline at end of file diff --git a/ARCHITECTURE.md b/ARCHITECTURE.md new file mode 100644 index 0000000..6b68d0a --- /dev/null +++ b/ARCHITECTURE.md @@ -0,0 +1,82 @@ +# Project Architecture + +This document outlines the architectural decisions, project structure, and key patterns used in the `express-starter` project. + +## Overview + +The project follows a **Modular Monolith** architecture combined with **Clean Architecture** (also known as Hexagonal Architecture or Ports and Adapters) principles. This ensures separation of concerns, maintainability, and testability by isolating business logic from infrastructure details. + +## Directory Structure + +The source code is located in the `src` directory and is organized into two main categories: + +- **`src/modules`**: Contains feature-specific modules (e.g., `users`, `auth`). Each module is a self-contained vertical slice of the application. +- **`src/shared`**: Contains code shared across multiple modules, such as base classes, utilities, and common infrastructure. + +### Module Anatomy (`src/modules/`) + +Each module follows a strict layering strategy: + +1. **`domain/`**: The core business logic. + - **Entities**: Pure TypeScript interfaces/classes defining the domain models (e.g., `UserEntity`). + - **Repository Interfaces**: Contracts defining how data is accessed (e.g., `IUsersRepository`). + - **Domain Services**: Business logic that doesn't fit into a single entity. + - *Dependencies*: This layer has **zero** dependencies on outer layers or external libraries (except strictly utility libraries). + +2. **`use-cases/`**: Application-specific business rules. + - Contains classes that orchestrate the flow of data to and from the domain entities. + - Implements specific user actions (e.g., `RegisterUserUseCase`, `UserSignup`). + - *Dependencies*: Depends on the `domain` layer. + +3. **`infrastructure/`**: Implementation details and adapters. + - **`persistence/`**: Database implementations of repositories (e.g., `UsersPostgresRepository`), database models, and mappers. + - **`http/`**: HTTP controllers, routes, and request validation schemas (Zod). + - **`di/`**: Dependency Injection configuration (InversifyJS modules). + - *Dependencies*: Depends on `domain` and `use-cases`. + +### Shared Kernel (`src/shared`) + +- **`core/`**: Core interfaces and types (e.g., `IBaseRepository`, `FilterQuery`). +- **`infrastructure/`**: Shared infrastructure implementations. + - **`persistence/postgres/`**: Base repository implementations and database connection logic. + - **`http/`**: Base router, HTTP utilities, and middlewares (e.g., `requestLogger`, `attachRequestContext`). + - **`crypto/`**: Cryptographic utilities (e.g., hashing, token generation). + - **`logger/`**: Logging service configuration. + - **`di/`**: Global DI container setup. + +## Key Technologies & Patterns + +### Dependency Injection (DI) +The project uses **InversifyJS** for dependency injection. +- **Container**: A global `appContainer` is defined in `src/shared/infrastructure/di/Container.ts`. +- **Modules**: Each feature module exports a `ContainerModule` (e.g., `UsersDIModule`) which binds interfaces to implementations. +- **Usage**: Dependencies are injected into classes (Repositories, Use Cases) using the `@inject` decorator. + +### Repository Pattern +Data access is abstracted using the Repository Pattern. +- **Interface**: Defined in the Domain layer (e.g., `IUsersRepository`). +- **Implementation**: Defined in the Infrastructure layer (e.g., `UsersPostgresRepository`). +- **Base Repository**: A generic `PostgresBaseRepository` in `shared` provides common CRUD operations. + +### Validation +**Zod** is used for runtime validation, particularly for HTTP request bodies in the controller/route layer. + +### Database +**Postgres** is the underlying database. The project uses **Prisma ORM** for schema definition, migrations, and type-safe database access. The repository layer wraps Prisma Client calls. + +### Authentication & Authorization +The `auth` module handles user authentication. +- **Strategy**: Likely uses JWT (JSON Web Tokens) for stateless authentication. +- **Crypto**: Shared crypto services handle password hashing (e.g., Argon2 or Bcrypt) and token signing. + +## Data Flow + +A typical request flows as follows: + +1. **HTTP Request** hits a Route defined in `infrastructure/http`. +2. **Middlewares** (Global/Route-specific) run (e.g., logging, context attachment). +3. **Validation** validates the request payload using Zod. +4. **Use Case** is resolved from the DI container and executed. +5. **Repository** is called by the Use Case to fetch/save data. +6. **Domain Entity** is returned/manipulated. +7. **Response** is sent back to the client. diff --git a/CODE_STYLE.md b/CODE_STYLE.md new file mode 100644 index 0000000..c3d1fa4 --- /dev/null +++ b/CODE_STYLE.md @@ -0,0 +1,101 @@ +# Code Style Guide + +This document outlines the code style, naming conventions, and architectural patterns used in this project. Adhering to these guidelines ensures consistency and maintainability across the codebase. + +## 1. General Principles + +- **Clean Architecture**: The project follows Clean Architecture principles, separating concerns into Domain, Use Cases, and Infrastructure layers. +- **Dependency Injection**: `inversify` is used for dependency injection. Dependencies are injected via constructor injection. In the case for route handlers and middlewares, dependencies are injected or retrieved via the `Container` or `ContainerModule`'s `get(id: Symbol)` method. +- **ES Modules**: The project uses native ES Modules. All local imports must include the `.js` extension. + +## 2. Naming Conventions + +### 2.1. Files and Folders + +- **Folders**: Use `kebab-case` (e.g., `use-cases`, `hello-world`, `infrastructure`). +- **Files**: + - **General**: Use `kebab-case` (e.g., `register-user.ts`). + - **Module Domain Files**: Often prefixed with the entity/module name (e.g., `users.entity.ts`, `users.repo.ts`, `users.service.ts`). + - **Shared Core Interfaces**: PascalCase, matching the interface name (e.g., `IUseCase.ts`, `IBaseRepository.ts`). + - **Domain Errors**: PascalCase (e.g., `InvalidEmailFormat.ts`, `UserNotFound.ts`). + - **DI Modules**: `kebab-case` with `.di` suffix (e.g., `users.di.ts`). + +### 2.2. Code Identifiers + +- **Classes**: PascalCase (e.g., `UserEntity`, `RegisterUserUseCase`). +- **Interfaces**: PascalCase with `I` prefix (e.g., `IUseCase`, `IUsersRepository`). + - **Exception**: Module augmentation for external libraries (e.g., `Request` in Express). +- **Types**: PascalCase (e.g., `RegisterUserDTO`, `FilterCriteria`). +- **Variables & Properties**: camelCase (e.g., `userRepo`, `email`, `createdAt`). +- **Functions & Methods**: camelCase (e.g., `execute`, `save`, `hashPassword`). +- **Constants**: UPPER_SNAKE_CASE for global constants; camelCase for local constants. +- **DI Symbols**: PascalCase (e.g., `UsersDomain`, `SharedDomain`). + +## 3. Project Structure + +The project is organized into `modules` and `shared` directories. + +### 3.1. Modules (`src/modules`) + +Each feature module (e.g., `users`, `auth`) is self-contained and follows a layered structure: + +- **`domain/`**: Contains business logic, entities, repository interfaces, and domain errors. + - Entities: `*.entity.ts` + - Repository Interfaces: `*.repo.ts` + - Errors: `errors/*.ts` +- **`use-cases/`**: Contains application logic. + - Use Case Classes: `kebab-case.ts` (implement `IUseCase`) + - DTOs: Defined within the use case file or separately. +- **`infrastructure/`**: Contains implementation details. + - Persistence: `*.prisma.repo.ts`, `*.in-memory.repo.ts` + - DI: `di/*.di.ts` + - HTTP: `http/*.routes.ts` + +### 3.2. Shared (`src/shared`) + +Contains code shared across modules: + +- **`core/`**: Base interfaces and abstract classes (e.g., `IUseCase`, `IBaseRepository`). +- **`infrastructure/`**: Shared infrastructure implementations (e.g., `crypto`, `logger`, `prisma`). +- **`application/`**: Application-level ports and services. + +## 4. Coding Standards + +### 4.1. Imports + +- **Extensions**: You **MUST** use the `.js` extension for all local imports. + ```typescript + import { UserEntity } from "./users.entity.js"; // Correct + import { UserEntity } from "./users.entity"; // Incorrect + ``` +- **Path Aliases**: Use `@/` to refer to the `src` directory. + ```typescript + import { IUseCase } from "@/shared/core/IUseCase.js"; + ``` + +### 4.2. Types & Interfaces + +- Prefer **Interfaces** for defining contracts (repositories, services). +- Prefer **Types** for DTOs and data structures (e.g., `RegisterUserDTO`). +- **No Enums**: Avoid TypeScript `enum`. Use union types or constant objects instead (enforced by Biome). + +### 4.3. Error Handling + +- Use custom error classes extending `Error` for domain-specific errors. +- Place domain errors in `domain/errors/`. + +### 4.4. Asynchronous Code + +- Use `async/await` for asynchronous operations. +- Avoid raw Promises (`.then()`, `.catch()`) where `await` can be used. + +## 5. Tooling + +- **Linter/Formatter**: [Biome](https://biomejs.dev/) is used for linting and formatting. + - Indentation: 2 spaces. + - Quotes: Double quotes. + - No explicit `any`. + - Run `yarn lint` to lint the codebase. + - Run `yarn format` to format the codebase. + - These two commands should be ran before committing. +- **Testing**: Vitest (implied by `*.spec.ts` files). diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..20196a9 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2026 Lance + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.md b/README.md new file mode 100644 index 0000000..c1dda0c --- /dev/null +++ b/README.md @@ -0,0 +1,174 @@ +# Express Starter Template + +A robust **Modular Monolith** template for building scalable Node.js applications using **TypeScript**, **Express**, and **Clean Architecture** principles. + +## ๐Ÿš€ Features + +- **Modular Architecture**: Vertical slice architecture ensuring separation of concerns and scalability. +- **Clean Architecture**: Domain-centric design with clear boundaries between Domain, Use Cases, and Infrastructure. +- **Type Safety**: Built with **TypeScript** in `nodenext` mode for modern ESM support. +- **Dependency Injection**: Powered by **InversifyJS** for loose coupling and testability. +- **Database**: **PostgreSQL** integration using **Prisma ORM** for type-safe database access and schema management. +- **Validation**: Runtime request validation using **Zod**. +- **Linting & Formatting**: Fast and efficient tooling with **Biome**. + +## ๐Ÿ› ๏ธ Tech Stack + +- **Runtime**: Node.js (>= 22.18.0) +- **Framework**: Express.js +- **Language**: TypeScript +- **DI Container**: InversifyJS +- **Database**: PostgreSQL + Prisma ORM +- **Validation**: Zod +- **Testing**: Vitest +- **Tooling**: Biome, tsx, Swagger + +For the first version, I'm planning of just using Express.js and InversifyJS. In the future, I plan on using the [InversifyJS framework with the Express v5 adapter](https://inversify.io/framework/docs/introduction/getting-started/) as another branch. + +The `inversify-express-utils` package is already deprecated so the focus should be on the new framework package instead. + +## ๐Ÿ Getting Started + +### Prerequisites + +- Node.js >= 22.18.0 +- npm or yarn +- PostgreSQL instance + +### Installation + +1. Clone the repository: + ```bash + git clone + cd express-starter + ``` + +2. Install dependencies: + ```bash + yarn install + ``` + +3. Set up environment variables: + Create a `.env` file in the root directory (refer to `.env.example` if available, otherwise configure your DB connection details). + +4. Create the initial Prisma migration: + > Note: Run this command every time you make changes to the Prisma schema. + ```bash + yarn prisma:migrate + ``` + +5. Generate Prisma Client: + > Note: Run this command every time you make changes to the Prisma schema. + ```bash + yarn prisma:generate + ``` + +6. Start the development server: + ```bash + yarn dev + ``` + +### Available Scripts + +- `yarn dev`: Start the development server with hot-reloading (using `tsx`). +- `yarn build`: Build the project for production. +- `yarn start`: Start the production server. +- `yarn lint`: Lint the codebase using Biome. +- `yarn format`: Format the codebase using Biome. +- `yarn test`: Run unit tests using Vitest. +- `yarn coverage`: Run tests with coverage reporting. + +## ๐Ÿงช Testing + +The project uses **Vitest** for unit and integration testing. + +### Running Tests + +```bash +# Run all tests +yarn test + +# Run tests with coverage +yarn coverage +``` + +### Test Structure + +Tests are **co-located** with the source code they test. This keeps tests close to the implementation and makes it easier to maintain. + +- **File Naming**: `*.spec.ts` +- **Location**: Same directory as the source file (e.g., `src/modules/users/domain/users.entity.ts` -> `src/modules/users/domain/users.entity.spec.ts`). + +### Writing Tests + +We use **Vitest** as our test runner, which provides a Jest-compatible API. + +1. **Domain Entities**: Test business logic and invariants within entities. + - *Example*: `src/modules/users/domain/users.entity.spec.ts` +2. **Use Cases**: Test application logic by mocking dependencies (Repositories, Services) using `vi.fn()` or `vi.spyOn()`. + - *Example*: `src/modules/auth/use-cases/login-user.spec.ts` +3. **Shared Infrastructure**: Test generic implementations (fakes) to ensure they behave correctly. + - *Example*: `src/shared/infrastructure/persistence/fakes/InMemoryRepository.spec.ts` + +## API Documentation + +The project uses **Swagger UI** to visualize and interact with the API's resources. + +### Accessing Swagger UI + +Start the development server (`yarn dev`) and navigate to: +`http://localhost:3000/docs` + +This endpoint will only be available if the env var `ENVIRONMENT` is not `production`. + +### Defining Routes + +We use `swagger-jsdoc` to generate the OpenAPI specification from JSDoc comments directly in the route files. + +**Example:** + +```typescript +/** + * @openapi + * /hello-world: + * get: + * tags: + * - Hello World + * summary: Greet the user + * responses: + * 200: + * description: Success + * content: + * text/plain: + * schema: + * type: string + */ +router.get("/", (req, res) => { + res.send("Hello world!"); +}); +``` + +## ๐Ÿ“‚ Project Structure + +The project is organized into modules and shared components. For a detailed deep-dive into the architecture, please refer to [ARCHITECTURE.md](./ARCHITECTURE.md). + +``` +src/ +โ”œโ”€โ”€ modules/ # Feature-specific modules (e.g., users, auth) +โ”‚ โ””โ”€โ”€ users/ +โ”‚ โ”œโ”€โ”€ domain/ # Entities & Repository Interfaces +โ”‚ โ”œโ”€โ”€ use-cases/ # Application Business Rules +โ”‚ โ””โ”€โ”€ infrastructure/ # DB, HTTP, DI implementations +โ”œโ”€โ”€ shared/ # Shared kernel, base classes, utilities +โ”‚ โ”œโ”€โ”€ core/ +โ”‚ โ””โ”€โ”€ infrastructure/ +โ””โ”€โ”€ app.ts # Application entry point +``` + +## ๐Ÿค Contributing + +Contributions are welcome! Please feel free to submit a Pull Request. + +## ๐Ÿ“„ License + +This project is licensed under the MIT License. diff --git a/biome.json b/biome.json new file mode 100644 index 0000000..0e069b3 --- /dev/null +++ b/biome.json @@ -0,0 +1,53 @@ +{ + "$schema": "https://biomejs.dev/schemas/2.3.11/schema.json", + "vcs": { + "enabled": false, + "clientKind": "git", + "useIgnoreFile": false + }, + "files": { + "ignoreUnknown": false, + "includes": ["src/**", "!src/generated"] + }, + "formatter": { + "enabled": true, + "indentStyle": "space", + "indentWidth": 2, + "lineWidth": 80 + }, + "linter": { + "enabled": true, + "rules": { + "recommended": true, + "style": { + "noEnum": "error" + }, + "suspicious": { + "noExplicitAny": "error", + "noEmptyInterface": "warn" + } + } + }, + "javascript": { + "formatter": { + "quoteStyle": "double" + }, + "parser": { + "unsafeParameterDecoratorsEnabled": true + } + }, + "json": { + "formatter": { + "indentStyle": "space", + "lineWidth": 80 + } + }, + "assist": { + "enabled": true, + "actions": { + "source": { + "organizeImports": "on" + } + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..95f3857 --- /dev/null +++ b/package.json @@ -0,0 +1,55 @@ +{ + "name": "express-starter", + "version": "1.0.0", + "license": "MIT", + "private": true, + "type": "module", + "scripts": { + "lint": "biome lint", + "format": "biome format --fix", + "build": "tsc --build && tsc-alias", + "start": "node dist/app.js", + "dev": "tsx watch src/app.ts", + "prisma:migrate": "prisma migrate dev", + "prisma:generate": "prisma generate", + "test": "vitest", + "coverage": "vitest run --coverage" + }, + "devDependencies": { + "@biomejs/biome": "2.3.11", + "@types/cookie-parser": "^1.4.10", + "@types/express": "^5.0.6", + "@types/jsonwebtoken": "^9.0.10", + "@types/node": "^25.0.8", + "@types/pg": "^8.16.0", + "@types/swagger-jsdoc": "^6.0.4", + "@types/swagger-ui-express": "^4.1.8", + "@vitest/coverage-v8": "^4.0.17", + "prisma": "^7.2.0", + "tsc-alias": "^1.8.16", + "tsx": "^4.21.0", + "typescript": "^5.9.3", + "vite-tsconfig-paths": "^6.0.4", + "vitest": "^4.0.17" + }, + "dependencies": { + "@prisma/adapter-pg": "^7.2.0", + "@prisma/client": "^7.2.0", + "@types/bcrypt": "^6.0.0", + "bcrypt": "^6.0.0", + "cookie-parser": "^1.4.7", + "dotenv": "^17.2.3", + "express": "^5.2.1", + "inversify": "^7.11.0", + "jsonwebtoken": "^9.0.3", + "pg": "^8.16.3", + "reflect-metadata": "^0.2.2", + "swagger-jsdoc": "^6.2.8", + "swagger-ui-express": "^5.0.1", + "uuidv7": "^1.1.0", + "zod": "^4.3.5" + }, + "engines": { + "node": ">= 22.18.0" + } +} diff --git a/prisma.config.ts b/prisma.config.ts new file mode 100644 index 0000000..b1b2df3 --- /dev/null +++ b/prisma.config.ts @@ -0,0 +1,14 @@ +// This file was generated by Prisma, and assumes you have installed the following: +// npm install --save-dev prisma dotenv +import "dotenv/config"; +import { defineConfig, env } from "prisma/config"; + +export default defineConfig({ + schema: "prisma/schema.prisma", + migrations: { + path: "prisma/migrations", + }, + datasource: { + url: env("POSTGRES_URL"), + }, +}); diff --git a/src/app.ts b/src/app.ts new file mode 100644 index 0000000..3e00107 --- /dev/null +++ b/src/app.ts @@ -0,0 +1,57 @@ +import cookieParser from "cookie-parser"; +import express from "express"; +import swaggerUi from "swagger-ui-express"; +import "dotenv/config"; +import type { Container } from "inversify"; +import type { IConfigService } from "@/shared/application/ports/IConfigService.js"; +import { SharedDomain } from "@/shared/application/ports/shared.symbols.js"; +import { appContainer } from "@/shared/infrastructure/di/Container.js"; +import { errorHandler } from "@/shared/infrastructure/http/error-handlers/catchAll.js"; +import { requestLogger } from "@/shared/infrastructure/http/middlewares/requestLogger.js"; +import { stripHeaders } from "@/shared/infrastructure/http/middlewares/stripHeaders.js"; +import baseRoutes from "@/shared/infrastructure/http/routes.js"; +import { ConsoleLogger } from "@/shared/infrastructure/logger/ConsoleLogger.js"; +import { PrismaClientWrapper } from "@/shared/infrastructure/persistence/prisma/PrismaClientWrapper.js"; +import { swaggerSpec } from "@/swagger.js"; +import type { ILogger } from "./shared/application/ports/ILogger.js"; +import { zodValidationHandler } from "./shared/infrastructure/http/error-handlers/zodValidation.js"; + +function bootstrap() { + // DI setup + configure(appContainer); + const configService = appContainer.get( + SharedDomain.IConfigService, + ); + const logger = appContainer.get(SharedDomain.ILogger); + + // web server setup + const app = express(); + app.use(cookieParser()); + app.use(requestLogger); + app.use(stripHeaders); + app.use(express.json()); + app.use(baseRoutes); + app.use(zodValidationHandler); + app.use(errorHandler); + + if (configService.isDevelopment()) { + app.use("/docs", swaggerUi.serve, swaggerUi.setup(swaggerSpec)); + logger.info({ + message: "Serving Swagger docs at /docs", + }); + } + return app; +} + +function configure(appContainer: Container) { + appContainer.bind(PrismaClientWrapper).toSelf().inSingletonScope(); + appContainer.bind(SharedDomain.ILogger).to(ConsoleLogger).inSingletonScope(); +} + +// run the app +bootstrap().listen(3000, () => { + const logger = appContainer.get(SharedDomain.ILogger); + logger.info({ + message: "Server running on port 3000", + }); +}); diff --git a/src/modules/auth/domain/errors/InvalidCredentials.ts b/src/modules/auth/domain/errors/InvalidCredentials.ts new file mode 100644 index 0000000..e69de29 diff --git a/src/modules/auth/infrastructure/di/auth.di.ts b/src/modules/auth/infrastructure/di/auth.di.ts new file mode 100644 index 0000000..be35ff9 --- /dev/null +++ b/src/modules/auth/infrastructure/di/auth.di.ts @@ -0,0 +1,10 @@ +import { ContainerModule } from "inversify"; +import { LoginUserUseCase } from "../../use-cases/login-user.js"; +import { RefreshSessionUseCase } from "../../use-cases/refresh-session.js"; +import { UserSignupUseCase } from "../../use-cases/user-signup.js"; + +export const AuthDIModule = new ContainerModule(({ bind }) => { + bind(LoginUserUseCase).toSelf().inTransientScope(); + bind(RefreshSessionUseCase).toSelf().inTransientScope(); + bind(UserSignupUseCase).toSelf().inTransientScope(); +}); diff --git a/src/modules/auth/infrastructure/http/auth.routes.ts b/src/modules/auth/infrastructure/http/auth.routes.ts new file mode 100644 index 0000000..18b6e55 --- /dev/null +++ b/src/modules/auth/infrastructure/http/auth.routes.ts @@ -0,0 +1,185 @@ +import { Router } from "express"; +import z from "zod"; +import type { IConfigService } from "@/shared/application/ports/IConfigService.js"; +import { SharedDomain } from "@/shared/application/ports/shared.symbols.js"; +import { appContainer } from "@/shared/infrastructure/di/Container.js"; +import { requireAuth } from "@/shared/infrastructure/http/middlewares/requireAuth.js"; +import { respondWithGenericError } from "@/shared/infrastructure/http/responses/respondWithGenericError.js"; +import { LoginUserUseCase } from "../../use-cases/login-user.js"; +import { RefreshSessionUseCase } from "../../use-cases/refresh-session.js"; +import { UserSignupUseCase } from "../../use-cases/user-signup.js"; + +const router = Router(); + +/** + * @openapi + * components: + * schemas: + * LoginRequest: + * type: object + * properties: + * email: + * type: string + * format: email + * password: + * type: string + * format: password + */ +const LoginRequestSchema = z.object({ + email: z.email(), + password: z.string().min(6), +}); +/** + * @openapi + * /auth/login: + * post: + * tags: + * - Auth + * summary: Login a user + * requestBody: + * required: true + * content: + * application/json: + * schema: + * $ref: '#/components/schemas/LoginRequest' + * responses: + * 200: + * description: Login successful + * 401: + * description: Invalid credentials + */ +router.post("/login", async (req, res) => { + const configService = appContainer.get( + SharedDomain.IConfigService, + ); + const { email, password } = LoginRequestSchema.parse(req.body); + const useCase = appContainer.get(LoginUserUseCase); + const { token, refreshToken } = await useCase.execute({ email, password }); + + if (token && refreshToken) { + res.cookie("token", token, { + httpOnly: true, + secure: !configService.isDevelopment, + sameSite: "strict", + }); + res.cookie("refreshToken", refreshToken, { + httpOnly: true, + secure: !configService.isDevelopment, + sameSite: "strict", + }); + res.status(200).send(); + } else { + respondWithGenericError({ + res, + response: { + message: "Invalid credentials", + }, + statusCode: 401, + }); + } +}); + +/** + * @openapi + * components: + * schemas: + * RegisterRequest: + * type: object + * properties: + * email: + * type: string + * format: email + * password: + * type: string + * format: password + */ +const RegisterRequestSchema = z.object({ + email: z.email(), + password: z.string().min(6), +}); +/** + * @openapi + * /auth/register: + * post: + * tags: + * - Auth + * summary: Register a user + * requestBody: + * required: true + * content: + * application/json: + * schema: + * $ref: '#/components/schemas/RegisterRequest' + * responses: + * 200: + * description: Register successful + */ +router.post("/register", async (req, res) => { + const { email, password } = RegisterRequestSchema.parse(req.body); + const useCase = appContainer.get(UserSignupUseCase); + await useCase.execute({ email, password }); + res.status(200).send(); +}); + +/** + * @openapi + * /auth/refresh: + * post: + * tags: + * - Auth + * summary: Refresh the current user session + * responses: + * 200: + * description: Refresh successful + */ +router.post("/refresh", requireAuth, async (req, res) => { + const configService = appContainer.get( + SharedDomain.IConfigService, + ); + const useCase = appContainer.get(RefreshSessionUseCase); + const { token, refreshToken } = await useCase.execute({ + refreshToken: req.cookies.refreshToken, + }); + if (token && refreshToken) { + res.cookie("token", token, { + httpOnly: true, + secure: !configService.isDevelopment, + sameSite: "strict", + }); + res.cookie("refreshToken", refreshToken, { + httpOnly: true, + secure: !configService.isDevelopment, + sameSite: "strict", + }); + res.status(200).send(); + } else { + respondWithGenericError({ + res, + response: { + message: "Invalid refresh token", + }, + statusCode: 401, + }); + } +}); + +/** + * @openapi + * /auth/logout: + * post: + * tags: + * - Auth + * summary: Logout the current user session + * responses: + * 200: + * description: Logout successful + * 401: + * description: Unauthorized + */ +router.post("/logout", requireAuth, async (_req, res) => { + res.clearCookie("token"); + res.clearCookie("refreshToken"); + res.status(200).send(); +}); + +export default router; diff --git a/src/modules/auth/use-cases/login-user.spec.ts b/src/modules/auth/use-cases/login-user.spec.ts new file mode 100644 index 0000000..4ae6c50 --- /dev/null +++ b/src/modules/auth/use-cases/login-user.spec.ts @@ -0,0 +1,116 @@ +import { afterEach, beforeEach, describe, expect, test, vi } from "vitest"; +import { UserEntity } from "@/modules/users/domain/users.entity.js"; +import { UsersInMemoryRepository } from "@/modules/users/infrastructure/fakes/users.in-memory.repo.js"; +import type { ICryptoService } from "@/shared/application/ports/ICryptoService.js"; +import type { ILogger } from "@/shared/application/ports/ILogger.js"; +import type { ITokenService } from "@/shared/application/ports/ITokenService.js"; +import { LoginUserUseCase } from "./login-user.js"; + +describe("Auth - Login user", () => { + let usersRepo: UsersInMemoryRepository; + const MockCryptoService = vi.fn( + class implements ICryptoService { + randomId = vi.fn().mockReturnValue("2"); + hashPassword = vi.fn().mockResolvedValue("hashed-password"); + comparePassword = vi.fn().mockResolvedValue(true); + }, + ); + const MockTokenService = vi.fn( + class implements ITokenService { + generateToken = vi.fn().mockReturnValue("token"); + generateRefreshToken = vi.fn().mockReturnValue("refresh-token"); + getSession = vi.fn().mockReturnValue({ + userId: "1", + email: "test@example.com", + isVerified: true, + loginDate: new Date(), + }); + validateRefreshToken = vi.fn().mockReturnValue({ userId: "1" }); + }, + ); + const MockLogger = vi.fn( + class implements ILogger { + info = vi.fn(); + error = vi.fn(); + warn = vi.fn(); + debug = vi.fn(); + }, + ); + const cryptoService = new MockCryptoService(); + const tokenService = new MockTokenService(); + const logger = new MockLogger(); + + beforeEach(() => { + usersRepo = new UsersInMemoryRepository(); + usersRepo.save( + new UserEntity("1", "test@example.com", "password", true, new Date()), + ); + }); + + afterEach(() => { + vi.resetAllMocks(); + }); + + test("should login a user", async () => { + const findOneSpy = vi.spyOn(usersRepo, "findOne"); + const generateTokenSpy = vi.spyOn(tokenService, "generateToken"); + const useCase = new LoginUserUseCase( + usersRepo, + cryptoService, + tokenService, + logger, + ); + const result = await useCase.execute({ + email: "test@example.com", + password: "password", + }); + expect(findOneSpy).toHaveBeenCalledTimes(1); + expect(generateTokenSpy).toHaveBeenCalledTimes(1); + expect(result).toEqual({ + token: "token", + refreshToken: "refresh-token", + }); + }); + + test("should not login a user if the user is not found", async () => { + const findOneSpy = vi.spyOn(usersRepo, "findOne"); + const generateTokenSpy = vi.spyOn(tokenService, "generateToken"); + const useCase = new LoginUserUseCase( + usersRepo, + cryptoService, + tokenService, + logger, + ); + const result = await useCase.execute({ + email: "test2@example.com", + password: "password", + }); + expect(findOneSpy).toHaveBeenCalledTimes(1); + expect(generateTokenSpy).toHaveBeenCalledTimes(0); + expect(result).toEqual({ + token: null, + refreshToken: null, + }); + }); + + test("should not login a user if the password is invalid", async () => { + const findOneSpy = vi.spyOn(usersRepo, "findOne"); + const generateTokenSpy = vi.spyOn(tokenService, "generateToken"); + const useCase = new LoginUserUseCase( + usersRepo, + cryptoService, + tokenService, + logger, + ); + const result = await useCase.execute({ + email: "test@example.com", + password: "password2", + }); + expect(findOneSpy).toHaveBeenCalledTimes(1); + expect(generateTokenSpy).toHaveBeenCalledTimes(0); + expect(result).toEqual({ + token: null, + refreshToken: null, + }); + }); +}); diff --git a/src/modules/auth/use-cases/login-user.ts b/src/modules/auth/use-cases/login-user.ts new file mode 100644 index 0000000..61c686c --- /dev/null +++ b/src/modules/auth/use-cases/login-user.ts @@ -0,0 +1,71 @@ +import { inject, injectable, optional } from "inversify"; +import type { IUsersRepository } from "@/modules/users/domain/users.repo.js"; +import { UsersDomain } from "@/modules/users/domain/users.symbols.js"; +import type { ICryptoService } from "@/shared/application/ports/ICryptoService.js"; +import type { + ILogger, + IRequestContext, +} from "@/shared/application/ports/ILogger.js"; +import type { ITokenService } from "@/shared/application/ports/ITokenService.js"; +import { SharedDomain } from "@/shared/application/ports/shared.symbols.js"; +import type { IUseCase } from "@/shared/core/IUseCase.js"; + +export type LoginUserDTO = { + email: string; + password: string; +}; +export type LoginUserResult = { + token: string | null; + refreshToken: string | null; +}; + +@injectable() +export class LoginUserUseCase + implements IUseCase +{ + constructor( + @inject(UsersDomain.IUserRepository) + private readonly userRepository: IUsersRepository, + @inject(SharedDomain.ICryptoService) + private readonly cryptoService: ICryptoService, + @inject(SharedDomain.ITokenService) + private readonly tokenService: ITokenService, + @inject(SharedDomain.ILogger) + private readonly logger: ILogger, + @inject(SharedDomain.IRequestContext) + @optional() + private readonly requestContext?: IRequestContext, + ) {} + + async execute(dto: LoginUserDTO): Promise { + const user = await this.userRepository.findOne({ email: dto.email }); + if (!user) { + this.logger.error({ + message: "Invalid credentials", + module: "LoginUserUseCase", + context: this.requestContext, + }); + return { token: null, refreshToken: null }; + } + const isPasswordValid = await this.cryptoService.comparePassword( + dto.password, + user.password, + ); + if (!isPasswordValid) { + this.logger.error({ + message: "Invalid credentials", + module: "LoginUserUseCase", + context: this.requestContext, + }); + return { token: null, refreshToken: null }; + } + const token = this.tokenService.generateToken(user); + const refreshToken = this.tokenService.generateRefreshToken(user); + this.logger.info({ + message: "User logged in", + module: "LoginUserUseCase", + context: this.requestContext, + }); + return { token, refreshToken }; + } +} diff --git a/src/modules/auth/use-cases/refresh-session.spec.ts b/src/modules/auth/use-cases/refresh-session.spec.ts new file mode 100644 index 0000000..5e2563b --- /dev/null +++ b/src/modules/auth/use-cases/refresh-session.spec.ts @@ -0,0 +1,97 @@ +import { afterEach, beforeEach, describe, expect, test, vi } from "vitest"; +import { UserEntity } from "@/modules/users/domain/users.entity.js"; +import { UsersInMemoryRepository } from "@/modules/users/infrastructure/fakes/users.in-memory.repo.js"; +import type { ILogger } from "@/shared/application/ports/ILogger.js"; +import type { ITokenService } from "@/shared/application/ports/ITokenService.js"; +import { RefreshSessionUseCase } from "./refresh-session.js"; + +describe("Auth - Refresh session", () => { + let usersRepo: UsersInMemoryRepository; + const MockTokenService = vi.fn( + class implements ITokenService { + generateToken = vi.fn().mockReturnValue("token"); + generateRefreshToken = vi.fn().mockReturnValue("refresh-token"); + getSession = vi.fn().mockReturnValue({ + userId: "1", + email: "test@example.com", + isVerified: true, + loginDate: new Date(), + }); + validateRefreshToken = vi.fn((refreshToken) => { + if (refreshToken === "refresh-token") { + return { userId: "1" }; + } + if (refreshToken === "non-existant-user") { + return { userId: "2" }; + } + return null; + }); + }, + ); + const MockLogger = vi.fn( + class implements ILogger { + info = vi.fn(); + error = vi.fn(); + warn = vi.fn(); + debug = vi.fn(); + }, + ); + const tokenService = new MockTokenService(); + const logger = new MockLogger(); + + beforeEach(() => { + usersRepo = new UsersInMemoryRepository(); + usersRepo.save( + new UserEntity("1", "test@example.com", "password", true, new Date()), + ); + }); + + afterEach(() => { + vi.resetAllMocks(); + }); + + test("should refresh a session", async () => { + const findOneSpy = vi.spyOn(usersRepo, "findOne"); + const generateTokenSpy = vi.spyOn(tokenService, "generateToken"); + const useCase = new RefreshSessionUseCase(usersRepo, tokenService, logger); + const result = await useCase.execute({ + refreshToken: "refresh-token", + }); + expect(findOneSpy).toHaveBeenCalledTimes(1); + expect(generateTokenSpy).toHaveBeenCalledTimes(1); + expect(result).toEqual({ + token: "token", + refreshToken: "refresh-token", + }); + }); + + test("should not refresh when refresh token is invalid", async () => { + const findOneSpy = vi.spyOn(usersRepo, "findOne"); + const generateTokenSpy = vi.spyOn(tokenService, "generateToken"); + const useCase = new RefreshSessionUseCase(usersRepo, tokenService, logger); + const result = await useCase.execute({ + refreshToken: "invalid-refresh-token", + }); + expect(findOneSpy).toHaveBeenCalledTimes(0); + expect(generateTokenSpy).toHaveBeenCalledTimes(0); + expect(result).toEqual({ + token: null, + refreshToken: null, + }); + }); + + test("should not refresh when user id does not exist", async () => { + const findOneSpy = vi.spyOn(usersRepo, "findOne"); + const generateTokenSpy = vi.spyOn(tokenService, "generateToken"); + const useCase = new RefreshSessionUseCase(usersRepo, tokenService, logger); + const result = await useCase.execute({ + refreshToken: "non-existant-user", + }); + expect(findOneSpy).toHaveBeenCalledTimes(1); + expect(generateTokenSpy).toHaveBeenCalledTimes(0); + expect(result).toEqual({ + token: null, + refreshToken: null, + }); + }); +}); diff --git a/src/modules/auth/use-cases/refresh-session.ts b/src/modules/auth/use-cases/refresh-session.ts new file mode 100644 index 0000000..02e0782 --- /dev/null +++ b/src/modules/auth/use-cases/refresh-session.ts @@ -0,0 +1,66 @@ +import { inject, injectable, optional } from "inversify"; +import type { IUsersRepository } from "@/modules/users/domain/users.repo.js"; +import { UsersDomain } from "@/modules/users/domain/users.symbols.js"; +import type { + ILogger, + IRequestContext, +} from "@/shared/application/ports/ILogger.js"; +import type { ITokenService } from "@/shared/application/ports/ITokenService.js"; +import { SharedDomain } from "@/shared/application/ports/shared.symbols.js"; +import type { IUseCase } from "@/shared/core/IUseCase.js"; + +export type RefreshSessionDTO = { + refreshToken: string; +}; +export type RefreshSessionResult = { + token: string | null; + refreshToken: string | null; +}; + +@injectable() +export class RefreshSessionUseCase + implements IUseCase +{ + constructor( + @inject(UsersDomain.IUserRepository) + private readonly userRepository: IUsersRepository, + @inject(SharedDomain.ITokenService) + private readonly tokenService: ITokenService, + @inject(SharedDomain.ILogger) + private readonly logger: ILogger, + @inject(SharedDomain.IRequestContext) + @optional() + private readonly requestContext?: IRequestContext, + ) {} + + async execute(dto: RefreshSessionDTO): Promise { + const refreshData = this.tokenService.validateRefreshToken( + dto.refreshToken, + ); + if (!refreshData?.userId) { + this.logger.error({ + message: "Invalid refresh token", + module: "RefreshSessionUseCase", + context: this.requestContext, + }); + return { token: null, refreshToken: null }; + } + const user = await this.userRepository.findOne({ id: refreshData.userId }); + if (!user) { + this.logger.error({ + message: "Invalid refresh token", + module: "RefreshSessionUseCase", + context: this.requestContext, + }); + return { token: null, refreshToken: null }; + } + const token = this.tokenService.generateToken(user); + const refreshToken = this.tokenService.generateRefreshToken(user); + this.logger.info({ + message: "Session refreshed", + module: "RefreshSessionUseCase", + context: this.requestContext, + }); + return { token, refreshToken }; + } +} diff --git a/src/modules/auth/use-cases/user-signup.spec.ts b/src/modules/auth/use-cases/user-signup.spec.ts new file mode 100644 index 0000000..f2f7a7d --- /dev/null +++ b/src/modules/auth/use-cases/user-signup.spec.ts @@ -0,0 +1,75 @@ +import { afterEach, beforeEach, describe, expect, test, vi } from "vitest"; +import { UserEntity } from "@/modules/users/domain/users.entity.js"; +import { UsersInMemoryRepository } from "@/modules/users/infrastructure/fakes/users.in-memory.repo.js"; +import type { ICryptoService } from "@/shared/application/ports/ICryptoService.js"; +import { UserSignupUseCase } from "./user-signup.js"; + +describe("Auth - User signup", () => { + let usersRepo: UsersInMemoryRepository; + const MockCryptoService = vi.fn( + class implements ICryptoService { + randomId = vi.fn().mockReturnValue("1"); + hashPassword = vi.fn().mockResolvedValue("hashed-password"); + comparePassword = vi.fn().mockResolvedValue(true); + }, + ); + const cryptoService: ICryptoService = new MockCryptoService(); + let useCase: UserSignupUseCase; + + beforeEach(() => { + usersRepo = new UsersInMemoryRepository(); + useCase = new UserSignupUseCase(usersRepo, cryptoService); + }); + + afterEach(() => { + vi.resetAllMocks(); + }); + + test("should signup a user", async () => { + const saveSpy = vi.spyOn(usersRepo, "save"); + const findOneSpy = vi.spyOn(usersRepo, "findOne"); + const result = await useCase.execute({ + email: "test@example.com", + password: "password", + }); + expect(cryptoService.randomId).toHaveBeenCalledTimes(1); + expect(cryptoService.hashPassword).toHaveBeenCalledTimes(1); + expect(saveSpy).toHaveBeenCalledTimes(1); + expect(findOneSpy).toHaveBeenCalledTimes(1); + expect(result).toBeUndefined(); + expect( + ( + await usersRepo.findAll({ + email: "test@example.com", + }) + ).data, + ).toHaveLength(1); + expect( + ( + await usersRepo.findAll({ + email: "test@example.com", + }) + ).data, + ).toHaveLength(1); + }); + + test("should throw an error if the user already exists", async () => { + // setup + await usersRepo.save( + new UserEntity( + "1", + "test@example.com", + "hashed-password", + true, + new Date(), + ), + ); + // act + await expect( + useCase.execute({ + email: "test@example.com", + password: "password", + }), + ).rejects.toThrow("User already exists"); + }); +}); diff --git a/src/modules/auth/use-cases/user-signup.ts b/src/modules/auth/use-cases/user-signup.ts new file mode 100644 index 0000000..2f0bb28 --- /dev/null +++ b/src/modules/auth/use-cases/user-signup.ts @@ -0,0 +1,38 @@ +import { inject, injectable } from "inversify"; +import { UserEntity } from "@/modules/users/domain/users.entity.js"; +import type { IUsersRepository } from "@/modules/users/domain/users.repo.js"; +import { UsersDomain } from "@/modules/users/domain/users.symbols.js"; +import type { ICryptoService } from "@/shared/application/ports/ICryptoService.js"; +import { SharedDomain } from "@/shared/application/ports/shared.symbols.js"; +import type { IUseCase } from "@/shared/core/IUseCase.js"; + +export type UserSignupDTO = { + email: string; + password: string; +}; + +@injectable() +export class UserSignupUseCase implements IUseCase { + constructor( + @inject(UsersDomain.IUserRepository) + private readonly usersRepository: IUsersRepository, + @inject(SharedDomain.ICryptoService) + private readonly cryptoService: ICryptoService, + ) {} + + async execute(dto: UserSignupDTO): Promise { + const user = await this.usersRepository.findOne({ email: dto.email }); + if (user) { + throw new Error("User already exists"); + } + const hashedPassword = await this.cryptoService.hashPassword(dto.password); + const userEntity = new UserEntity( + this.cryptoService.randomId(), + dto.email, + hashedPassword, + false, + new Date(), + ); + await this.usersRepository.save(userEntity); + } +} diff --git a/src/modules/hello-world/infrastructure/http/hello-world.routes.ts b/src/modules/hello-world/infrastructure/http/hello-world.routes.ts new file mode 100644 index 0000000..bfff21d --- /dev/null +++ b/src/modules/hello-world/infrastructure/http/hello-world.routes.ts @@ -0,0 +1,29 @@ +import { Router } from "express"; + +const router = Router(); +/** + * @openapi + * /hello-world: + * get: + * tags: + * - Hello World + * summary: Greet the user + * responses: + * 200: + * description: Tells whether the user is logged in or not + * content: + * text/plain: + * schema: + * type: string + */ +router.get("/", (req, res) => { + const { session, currentUser } = req; + + if (!session || !currentUser) { + return res.send("Hello world! You are not logged in."); + } + + res.send(`Hello ${currentUser.email}`); +}); + +export default router; diff --git a/src/modules/users/domain/errors/InvalidEmailFormat.ts b/src/modules/users/domain/errors/InvalidEmailFormat.ts new file mode 100644 index 0000000..32b9e7f --- /dev/null +++ b/src/modules/users/domain/errors/InvalidEmailFormat.ts @@ -0,0 +1,5 @@ +export class InvalidEmailFormat extends Error { + constructor() { + super("Invalid email format"); + } +} diff --git a/src/modules/users/domain/errors/InvalidPassword.ts b/src/modules/users/domain/errors/InvalidPassword.ts new file mode 100644 index 0000000..fa6dee4 --- /dev/null +++ b/src/modules/users/domain/errors/InvalidPassword.ts @@ -0,0 +1,5 @@ +export class InvalidPassword extends Error { + constructor() { + super("Invalid password"); + } +} diff --git a/src/modules/users/domain/errors/NewPasswordMustBeDifferent.ts b/src/modules/users/domain/errors/NewPasswordMustBeDifferent.ts new file mode 100644 index 0000000..f2d6f22 --- /dev/null +++ b/src/modules/users/domain/errors/NewPasswordMustBeDifferent.ts @@ -0,0 +1,5 @@ +export class NewPasswordMustBeDifferent extends Error { + constructor() { + super("New password must be different from the old password"); + } +} diff --git a/src/modules/users/domain/errors/UserNotFound.ts b/src/modules/users/domain/errors/UserNotFound.ts new file mode 100644 index 0000000..c61d3df --- /dev/null +++ b/src/modules/users/domain/errors/UserNotFound.ts @@ -0,0 +1,5 @@ +export class UserNotFound extends Error { + constructor() { + super("User not found"); + } +} diff --git a/src/modules/users/domain/users.entity.spec.ts b/src/modules/users/domain/users.entity.spec.ts new file mode 100644 index 0000000..54cfe29 --- /dev/null +++ b/src/modules/users/domain/users.entity.spec.ts @@ -0,0 +1,126 @@ +import { describe, expect, test, vi } from "vitest"; +import { InvalidEmailFormat } from "./errors/InvalidEmailFormat.js"; +import { InvalidPassword } from "./errors/InvalidPassword.js"; +import { NewPasswordMustBeDifferent } from "./errors/NewPasswordMustBeDifferent.js"; +import { UserEntity } from "./users.entity.js"; + +describe("Users - UserEntity", () => { + test("should create a user entity", () => { + const user = new UserEntity( + "1", + "test@example.com", + "password", + false, + new Date(), + ); + expect(user).toBeDefined(); + expect(user.id).toBe("1"); + expect(user.email).toBe("test@example.com"); + expect(user.password).toBe("password"); + expect(user.isVerified).toBe(false); + expect(user.createdAt).toBeInstanceOf(Date); + }); + + test("should throw an error if the email is invalid", () => { + expect(() => { + new UserEntity("1", "test", "password", false, new Date()); + }).toThrowError(InvalidEmailFormat); + }); + + test("should throw an error if the password is invalid", () => { + expect(() => { + new UserEntity("1", "test@example.com", "", false, new Date()); + }).toThrowError(InvalidPassword); + }); + + test("should get account age in seconds", () => { + vi.useFakeTimers(); + vi.setSystemTime(new Date(2000, 1, 1, 13, 0, 0)); + const user = new UserEntity( + "1", + "test@example.com", + "password", + false, + new Date(), + ); + // advance time by 5 seconds + vi.setSystemTime(new Date(2000, 1, 1, 13, 0, 5)); + expect(user.getAccountAge()).toBe(5); + vi.useRealTimers(); + }); + + test("should change email successfully", () => { + const user = new UserEntity( + "1", + "test@example.com", + "password", + false, + new Date(), + ); + user.changeEmail("test2@example.com"); + expect(user.email).toBe("test2@example.com"); + }); + + test("should throw an error if the new email is invalid", () => { + const user = new UserEntity( + "1", + "test@example.com", + "password", + false, + new Date(), + ); + expect(() => { + user.changeEmail("test"); + }).toThrowError(InvalidEmailFormat); + }); + + test("should change password successfully", () => { + const user = new UserEntity( + "1", + "test@example.com", + "password", + false, + new Date(), + ); + user.changePassword("password2"); + expect(user.password).toBe("password2"); + }); + + test("should throw an error if the new password is the same as the old password", () => { + const user = new UserEntity( + "1", + "test@example.com", + "password", + false, + new Date(), + ); + expect(() => { + user.changePassword("password"); + }).toThrowError(NewPasswordMustBeDifferent); + }); + + test("should throw an error if the new password is invalid", () => { + const user = new UserEntity( + "1", + "test@example.com", + "password", + false, + new Date(), + ); + expect(() => { + user.changePassword(""); + }).toThrowError(InvalidPassword); + }); + + test("should set verified status successfully", () => { + const user = new UserEntity( + "1", + "test@example.com", + "password", + false, + new Date(), + ); + user.setVerifiedStatus(true); + expect(user.isVerified).toBe(true); + }); +}); diff --git a/src/modules/users/domain/users.entity.ts b/src/modules/users/domain/users.entity.ts new file mode 100644 index 0000000..88638c5 --- /dev/null +++ b/src/modules/users/domain/users.entity.ts @@ -0,0 +1,49 @@ +import { InvalidEmailFormat } from "./errors/InvalidEmailFormat.js"; +import { InvalidPassword } from "./errors/InvalidPassword.js"; +import { NewPasswordMustBeDifferent } from "./errors/NewPasswordMustBeDifferent.js"; + +export class UserEntity { + constructor( + public id: string, + public email: string, + public password: string, + public isVerified: boolean, + public createdAt: Date, + ) { + if (!email.includes("@")) { + throw new InvalidEmailFormat(); + } + if (password.length === 0) { + throw new InvalidPassword(); + } + } + + /** + * Returns the age of the account in seconds + * @returns account age in seconds + */ + getAccountAge() { + return (Date.now() - this.createdAt.getTime()) / 1000; + } + + changeEmail(newEmail: string) { + if (!newEmail.includes("@")) { + throw new InvalidEmailFormat(); + } + this.email = newEmail; + } + + changePassword(newHashedPassword: string) { + if (this.password === newHashedPassword) { + throw new NewPasswordMustBeDifferent(); + } + if (newHashedPassword.length === 0) { + throw new InvalidPassword(); + } + this.password = newHashedPassword; + } + + setVerifiedStatus(verifiedStatus: boolean) { + this.isVerified = verifiedStatus; + } +} diff --git a/src/modules/users/domain/users.repo.ts b/src/modules/users/domain/users.repo.ts new file mode 100644 index 0000000..bffe403 --- /dev/null +++ b/src/modules/users/domain/users.repo.ts @@ -0,0 +1,4 @@ +import type { IBaseRepository } from "@/shared/core/IBaseRepository.js"; +import type { UserEntity } from "./users.entity.js"; + +export interface IUsersRepository extends IBaseRepository {} diff --git a/src/modules/users/domain/users.service.ts b/src/modules/users/domain/users.service.ts new file mode 100644 index 0000000..72f6b53 --- /dev/null +++ b/src/modules/users/domain/users.service.ts @@ -0,0 +1,15 @@ +/** biome-ignore-all lint/suspicious/noEmptyInterface: This is a placeholder reference file. If your feature does not require a domain service, you can remove this file. */ +/** biome-ignore-all lint/correctness/noUnusedPrivateClassMembers: This is a placeholder reference file. If your feature does not require a domain service, you can remove this file. */ +import { inject, injectable } from "inversify"; +import type { IUsersRepository } from "./users.repo.js"; +import { UsersDomain } from "./users.symbols.js"; + +export interface IUsersService {} + +@injectable() +export class UsersService implements IUsersService { + constructor( + @inject(UsersDomain.IUserRepository) + private readonly userRepo: IUsersRepository, + ) {} +} diff --git a/src/modules/users/domain/users.symbols.ts b/src/modules/users/domain/users.symbols.ts new file mode 100644 index 0000000..be55ab5 --- /dev/null +++ b/src/modules/users/domain/users.symbols.ts @@ -0,0 +1,4 @@ +export const UsersDomain = { + IUserRepository: Symbol.for("IUserRepository"), + IUserService: Symbol.for("IUsersService"), +}; diff --git a/src/modules/users/infrastructure/di/users.di.ts b/src/modules/users/infrastructure/di/users.di.ts new file mode 100644 index 0000000..8b0bfdd --- /dev/null +++ b/src/modules/users/infrastructure/di/users.di.ts @@ -0,0 +1,14 @@ +import { ContainerModule } from "inversify"; +import type { IUsersRepository } from "../../domain/users.repo.js"; +import type { IUsersService } from "../../domain/users.service.js"; +import { UsersService } from "../../domain/users.service.js"; +import { UsersDomain } from "../../domain/users.symbols.js"; +import { RegisterUserUseCase } from "../../use-cases/register-user.js"; +import { UsersPrismaRepository } from "../persistence/users.prisma.repo.js"; + +export const UsersDIModule = new ContainerModule(({ bind }) => { + bind(UsersDomain.IUserRepository).to(UsersPrismaRepository); + bind(UsersDomain.IUserService).to(UsersService); + + bind(RegisterUserUseCase).toSelf().inTransientScope(); +}); diff --git a/src/modules/users/infrastructure/fakes/users.in-memory.repo.ts b/src/modules/users/infrastructure/fakes/users.in-memory.repo.ts new file mode 100644 index 0000000..7039f55 --- /dev/null +++ b/src/modules/users/infrastructure/fakes/users.in-memory.repo.ts @@ -0,0 +1,7 @@ +import { InMemoryRepository } from "@/shared/infrastructure/persistence/fakes/InMemoryRepository.js"; +import type { UserEntity } from "../../domain/users.entity.js"; +import type { IUsersRepository } from "../../domain/users.repo.js"; + +export class UsersInMemoryRepository + extends InMemoryRepository + implements IUsersRepository {} diff --git a/src/modules/users/infrastructure/persistence/users.prisma.repo.ts b/src/modules/users/infrastructure/persistence/users.prisma.repo.ts new file mode 100644 index 0000000..b3da159 --- /dev/null +++ b/src/modules/users/infrastructure/persistence/users.prisma.repo.ts @@ -0,0 +1,111 @@ +import { inject, injectable } from "inversify"; +import type { User } from "@/generated/prisma/client.js"; +import type { UserWhereInput } from "@/generated/prisma/models.js"; +import type { + FilterCriteria, + PaginationOptions, + WithPagination, +} from "@/shared/core/IBaseRepository.js"; +import { + type PrismaClient, + PrismaClientWrapper, +} from "@/shared/infrastructure/persistence/prisma/PrismaClientWrapper.js"; +import { UserEntity } from "../../domain/users.entity.js"; +import type { IUsersRepository } from "../../domain/users.repo.js"; + +/** MAPPERS */ +export function fromDomain(userEntity: UserEntity): User { + return { + id: userEntity.id, + email: userEntity.email, + password: userEntity.password, + isVerified: userEntity.isVerified, + createdAt: userEntity.createdAt, + }; +} +export function toDomain(userModel: User): UserEntity { + return new UserEntity( + userModel.id, + userModel.email, + userModel.password, + userModel.isVerified, + userModel.createdAt, + ); +} +export function toUserModelFilter( + criteria: FilterCriteria, +): FilterCriteria { + const result: FilterCriteria = {}; + if (criteria.id !== undefined) result.id = criteria.id; + if (criteria.email !== undefined) result.email = criteria.email; + if (criteria.password !== undefined) result.password = criteria.password; + if (criteria.createdAt !== undefined) result.createdAt = criteria.createdAt; + return result; +} + +@injectable() +export class UsersPrismaRepository implements IUsersRepository { + private readonly prisma: PrismaClient; + constructor( + @inject(PrismaClientWrapper) + private readonly prismaClientWrapper: PrismaClientWrapper, + ) { + this.prisma = this.prismaClientWrapper.getClient(); + } + + async findOne( + criteria: FilterCriteria, + ): Promise { + const where: UserWhereInput = {}; + const modelFilter = toUserModelFilter(criteria); + if (modelFilter.id) { + where.id = modelFilter.id; + } + if (modelFilter.email) { + where.email = modelFilter.email; + } + const model = await this.prisma.user.findFirst({ where }); + return model ? toDomain(model) : null; + } + + async findById(id: string): Promise { + const row = await this.prisma.user.findUnique({ where: { id } }); + return row ? toDomain(row) : null; + } + async findAll( + criteria?: FilterCriteria, + paginationOptions?: PaginationOptions, + ): Promise> { + const where: UserWhereInput = {}; + const modelFilter = criteria ? toUserModelFilter(criteria) : {}; + if (modelFilter.id) { + where.id = modelFilter.id; + } + if (modelFilter.email) { + where.email = modelFilter.email; + } + const models = paginationOptions + ? await this.prisma.user.findMany({ + where, + take: paginationOptions.limit, + skip: paginationOptions.offset, + }) + : await this.prisma.user.findMany({ where }); + const total = await this.prisma.user.count({ where }); + return { + data: models.map(toDomain), + total, + }; + } + async save(entity: UserEntity): Promise { + const model = await this.prisma.user.upsert({ + where: { id: entity.id }, + create: fromDomain(entity), + update: fromDomain(entity), + }); + return model ? toDomain(model) : null; + } + generateId(): string { + return this.prismaClientWrapper.generateId(); + } +} diff --git a/src/modules/users/use-cases/register-user.spec.ts b/src/modules/users/use-cases/register-user.spec.ts new file mode 100644 index 0000000..d3b08e2 --- /dev/null +++ b/src/modules/users/use-cases/register-user.spec.ts @@ -0,0 +1,5 @@ +import { expect, test } from "vitest"; + +test("adds 1 + 2 to equal 3", () => { + expect(1 + 2).toBe(3); +}); diff --git a/src/modules/users/use-cases/register-user.ts b/src/modules/users/use-cases/register-user.ts new file mode 100644 index 0000000..c9c8b68 --- /dev/null +++ b/src/modules/users/use-cases/register-user.ts @@ -0,0 +1,41 @@ +import { inject, injectable } from "inversify"; +import type { ICryptoService } from "@/shared/application/ports/ICryptoService.js"; +import { SharedDomain } from "@/shared/application/ports/shared.symbols.js"; +import type { IUseCase } from "@/shared/core/IUseCase.js"; +import { UserEntity } from "../domain/users.entity.js"; +import type { IUsersRepository } from "../domain/users.repo.js"; +import { UsersDomain } from "../domain/users.symbols.js"; + +export type RegisterUserDTO = { + email: string; + password: string; + isVerified?: boolean; +}; + +@injectable() +export class RegisterUserUseCase implements IUseCase { + constructor( + @inject(UsersDomain.IUserRepository) + private readonly userRepo: IUsersRepository, + @inject(SharedDomain.ICryptoService) + private readonly cryptoService: ICryptoService, + ) {} + + async execute(inputDto: RegisterUserDTO): Promise { + const user = await this.userRepo.findOne({ email: inputDto.email }); + if (user) { + throw new Error("User already exists"); + } + const hashedPassword = await this.cryptoService.hashPassword( + inputDto.password, + ); + const newUser = new UserEntity( + this.userRepo.generateId(), + inputDto.email, + hashedPassword, + inputDto.isVerified ?? false, + new Date(), + ); + await this.userRepo.save(newUser); + } +} diff --git a/src/shared/application/ports/IConfigService.ts b/src/shared/application/ports/IConfigService.ts new file mode 100644 index 0000000..08d82a4 --- /dev/null +++ b/src/shared/application/ports/IConfigService.ts @@ -0,0 +1,4 @@ +export interface IConfigService { + get(key: string): string; + isDevelopment(): boolean; +} diff --git a/src/shared/application/ports/ICryptoService.ts b/src/shared/application/ports/ICryptoService.ts new file mode 100644 index 0000000..3c5dd52 --- /dev/null +++ b/src/shared/application/ports/ICryptoService.ts @@ -0,0 +1,5 @@ +export interface ICryptoService { + hashPassword(password: string): Promise; + comparePassword(password: string, hash: string): Promise; + randomId(): string; +} diff --git a/src/shared/application/ports/ILogger.ts b/src/shared/application/ports/ILogger.ts new file mode 100644 index 0000000..b3b3149 --- /dev/null +++ b/src/shared/application/ports/ILogger.ts @@ -0,0 +1,22 @@ +export interface IRequestContext { + route: string; + method: string; + userAgent: string; + ip: string; +} + +export type LogMessage = { + message: string; + module?: string; + context?: IRequestContext | undefined; +}; + +export type ErrorLogMessage = LogMessage & { + error?: Error | undefined; +}; + +export interface ILogger { + info(message: LogMessage): void; + error(message: ErrorLogMessage): void; + warn(message: LogMessage): void; +} diff --git a/src/shared/application/ports/ITokenService.ts b/src/shared/application/ports/ITokenService.ts new file mode 100644 index 0000000..b97c4f1 --- /dev/null +++ b/src/shared/application/ports/ITokenService.ts @@ -0,0 +1,18 @@ +import type { UserEntity } from "@/modules/users/domain/users.entity.js"; + +export interface ISession { + userId: string; + email: string; + isVerified: boolean; + loginDate: Date; +} +export interface IRefreshData { + userId: string; +} + +export interface ITokenService { + generateToken(user: UserEntity): string; + generateRefreshToken(user: UserEntity): string; + getSession(token: string): ISession | null; + validateRefreshToken(refreshToken: string): IRefreshData | null; +} diff --git a/src/shared/application/ports/shared.symbols.ts b/src/shared/application/ports/shared.symbols.ts new file mode 100644 index 0000000..ecb82bb --- /dev/null +++ b/src/shared/application/ports/shared.symbols.ts @@ -0,0 +1,7 @@ +export const SharedDomain = { + IConfigService: Symbol.for("IConfigService"), + ICryptoService: Symbol.for("ICryptoService"), + ILogger: Symbol.for("ILogger"), + ITokenService: Symbol.for("ITokenService"), + IRequestContext: Symbol.for("IRequestContext"), +}; diff --git a/src/shared/core/IBaseRepository.ts b/src/shared/core/IBaseRepository.ts new file mode 100644 index 0000000..e47981e --- /dev/null +++ b/src/shared/core/IBaseRepository.ts @@ -0,0 +1,40 @@ +export type FilterRange = { + from?: T; + to?: T; +}; + +// Helper type to determine the filter value based on the property type +type FilterValue = T extends string + ? string + : T extends number + ? number | FilterRange + : T extends Date + ? Date | FilterRange + : T extends boolean + ? boolean + : never; // Exclude types that are not string, number, Date, or boolean + +export type FilterCriteria = { + [K in keyof T]?: FilterValue | undefined; +}; + +export type PaginationOptions = { + offset: number; + limit: number; +}; + +export type WithPagination = { + data: T[]; + total: number; +}; + +export interface IBaseRepository { + findOne(criteria: FilterCriteria): Promise; + findById(id: string): Promise; + findAll( + criteria?: FilterCriteria, + paginationOptions?: PaginationOptions, + ): Promise>; + save(entity: T): Promise; + generateId(): string; +} diff --git a/src/shared/core/IUseCase.ts b/src/shared/core/IUseCase.ts new file mode 100644 index 0000000..2f8d675 --- /dev/null +++ b/src/shared/core/IUseCase.ts @@ -0,0 +1,3 @@ +export interface IUseCase { + execute(inputDto?: TInput): Promise; +} diff --git a/src/shared/core/errors/ValidationError.ts b/src/shared/core/errors/ValidationError.ts new file mode 100644 index 0000000..cf0df00 --- /dev/null +++ b/src/shared/core/errors/ValidationError.ts @@ -0,0 +1,14 @@ +type Issue = { + field: string; + message: string; +}; + +export class ValidationError extends Error { + constructor( + message: string, + public issues: Issue[], + ) { + super(message); + this.name = "ValidationError"; + } +} diff --git a/src/shared/infrastructure/config/EnvConfigService.ts b/src/shared/infrastructure/config/EnvConfigService.ts new file mode 100644 index 0000000..426885e --- /dev/null +++ b/src/shared/infrastructure/config/EnvConfigService.ts @@ -0,0 +1,13 @@ +import type { IConfigService } from "@/shared/application/ports/IConfigService.js"; + +export class EnvConfigService implements IConfigService { + get(key: string): string { + return process.env[key] ?? ""; + } + isDevelopment(): boolean { + return ( + process.env.ENVIRONMENT !== "production" || + process.env.NODE_ENV === "development" + ); + } +} diff --git a/src/shared/infrastructure/crypto/BcryptService.ts b/src/shared/infrastructure/crypto/BcryptService.ts new file mode 100644 index 0000000..457c09b --- /dev/null +++ b/src/shared/infrastructure/crypto/BcryptService.ts @@ -0,0 +1,15 @@ +import bcrypt from "bcrypt"; +import { uuidv7 } from "uuidv7"; +import type { ICryptoService } from "@/shared/application/ports/ICryptoService.js"; + +export class BcryptService implements ICryptoService { + hashPassword(password: string): Promise { + return bcrypt.hash(password, 10); + } + comparePassword(password: string, hash: string): Promise { + return bcrypt.compare(password, hash); + } + randomId(): string { + return uuidv7(); + } +} diff --git a/src/shared/infrastructure/crypto/JwtService.ts b/src/shared/infrastructure/crypto/JwtService.ts new file mode 100644 index 0000000..2635acd --- /dev/null +++ b/src/shared/infrastructure/crypto/JwtService.ts @@ -0,0 +1,142 @@ +import { inject, injectable } from "inversify"; +import jwt from "jsonwebtoken"; +import z from "zod"; +import type { UserEntity } from "@/modules/users/domain/users.entity.js"; +import type { IConfigService } from "@/shared/application/ports/IConfigService.js"; +import type { ILogger } from "@/shared/application/ports/ILogger.js"; +import type { + IRefreshData, + ISession, + ITokenService, +} from "@/shared/application/ports/ITokenService.js"; +import { SharedDomain } from "@/shared/application/ports/shared.symbols.js"; +import { appContainer } from "../di/Container.js"; + +const JWTSessionSchema = z.object({ + sub: z.string(), + email: z.string(), + isVerified: z.boolean(), + iat: z.number(), + exp: z.number(), +}); + +const JWTRefreshSchema = z.object({ + sub: z.string(), + iat: z.number(), + exp: z.number(), +}); + +@injectable() +export class JwtService implements ITokenService { + constructor( + @inject(SharedDomain.IConfigService) + private readonly configService: IConfigService, + ) {} + + generateToken( + user: UserEntity, + additionalClaims?: Record, + ): string { + const duration = Number.parseInt( + this.configService.get("JWT_DURATION"), + 10, + ); + const secret = this.configService.get("JWT_SECRET"); + const claims = { + iat: Math.ceil(Date.now() / 1000), + exp: Math.ceil(Date.now() / 1000) + duration, + sub: user.id, + email: user.email, + isVerified: user.isVerified, + ...additionalClaims, + }; + const jwtClaims = JWTSessionSchema.parse(claims); + + const token = jwt.sign(jwtClaims, secret, { + algorithm: "HS256", + }); + return token; + } + generateRefreshToken(user: UserEntity): string { + const duration = Number.parseInt( + this.configService.get("JWT_REFRESH_DURATION"), + 10, + ); + const secret = this.configService.get("JWT_REFRESH_SECRET"); + const claims = { + iat: Math.ceil(Date.now() / 1000), + exp: Math.ceil(Date.now() / 1000) + duration, + sub: user.id, + }; + const jwtClaims = JWTRefreshSchema.parse(claims); + const token = jwt.sign(jwtClaims, secret, { + algorithm: "HS256", + }); + return token; + } + getSession(token: string): ISession | null { + const secret = this.configService.get("JWT_SECRET"); + const logger = appContainer.get(SharedDomain.ILogger); + try { + const decoded = jwt.verify(token, secret); + const decodedToken = JWTSessionSchema.parse(decoded); + if (decodedToken.exp < Date.now() / 1000) { + logger.error({ + message: "Token expired", + module: "JwtService", + error: new Error("Token expired"), + }); + return null; + } + const session = { + userId: decodedToken.sub, + email: decodedToken.email, + isVerified: decodedToken.isVerified, + loginDate: new Date(decodedToken.iat * 1000), + }; + return session; + } catch (error) { + let errorInstance: Error | undefined; + if (error instanceof Error) { + errorInstance = error; + } + logger.error({ + message: "Invalid token", + module: "JwtService", + error: errorInstance, + }); + return null; + } + } + validateRefreshToken(refreshToken: string): IRefreshData | null { + const secret = this.configService.get("JWT_REFRESH_SECRET"); + const logger = appContainer.get(SharedDomain.ILogger); + try { + const decoded = jwt.verify(refreshToken, secret); + const decodedToken = JWTRefreshSchema.parse(decoded); + if (decodedToken.exp < Date.now() / 1000) { + logger.error({ + message: "Token expired", + module: "JwtService", + error: new Error("Token expired"), + }); + return null; + } + const refreshData = { + userId: decodedToken.sub, + }; + return refreshData; + } catch (error) { + let errorInstance: Error | undefined; + if (error instanceof Error) { + errorInstance = error; + } + logger.error({ + message: "Invalid refresh token", + module: "JwtService", + error: errorInstance, + }); + return null; + } + } +} diff --git a/src/shared/infrastructure/di/Container.ts b/src/shared/infrastructure/di/Container.ts new file mode 100644 index 0000000..396ccb6 --- /dev/null +++ b/src/shared/infrastructure/di/Container.ts @@ -0,0 +1,12 @@ +import { Container } from "inversify"; +import { AuthDIModule } from "@/modules/auth/infrastructure/di/auth.di.js"; +import { UsersDIModule } from "@/modules/users/infrastructure/di/users.di.js"; +import { SharedDIModule } from "./shared.di.js"; + +const appContainer = new Container(); + +appContainer.load(SharedDIModule); +appContainer.load(AuthDIModule); +appContainer.load(UsersDIModule); + +export { appContainer }; diff --git a/src/shared/infrastructure/di/shared.di.ts b/src/shared/infrastructure/di/shared.di.ts new file mode 100644 index 0000000..4b0d98c --- /dev/null +++ b/src/shared/infrastructure/di/shared.di.ts @@ -0,0 +1,14 @@ +import { ContainerModule } from "inversify"; +import type { IConfigService } from "@/shared/application/ports/IConfigService.js"; +import type { ICryptoService } from "@/shared/application/ports/ICryptoService.js"; +import type { ITokenService } from "@/shared/application/ports/ITokenService.js"; +import { SharedDomain } from "@/shared/application/ports/shared.symbols.js"; +import { EnvConfigService } from "../config/EnvConfigService.js"; +import { BcryptService } from "../crypto/BcryptService.js"; +import { JwtService } from "../crypto/JwtService.js"; + +export const SharedDIModule = new ContainerModule(({ bind }) => { + bind(SharedDomain.ICryptoService).to(BcryptService); + bind(SharedDomain.ITokenService).to(JwtService); + bind(SharedDomain.IConfigService).to(EnvConfigService); +}); diff --git a/src/shared/infrastructure/http/error-handlers/catchAll.ts b/src/shared/infrastructure/http/error-handlers/catchAll.ts new file mode 100644 index 0000000..4cd11f1 --- /dev/null +++ b/src/shared/infrastructure/http/error-handlers/catchAll.ts @@ -0,0 +1,44 @@ +import type { ErrorRequestHandler } from "express"; +import type { ILogger } from "@/shared/application/ports/ILogger.js"; +import { SharedDomain } from "@/shared/application/ports/shared.symbols.js"; +import { ValidationError } from "@/shared/core/errors/ValidationError.js"; +import { appContainer } from "../../di/Container.js"; +import { respondWithGenericError } from "../responses/respondWithGenericError.js"; +import { respondWithValidationError } from "../responses/respondWithValidationError.js"; + +export const errorHandler: ErrorRequestHandler = (err, req, res, _next) => { + const logger = appContainer.get(SharedDomain.ILogger, { + optional: true, + }); + + logger?.error({ + message: err.message, + error: err, + module: "errorHandler", + context: { + route: req.originalUrl, + method: req.method, + userAgent: req.headers["user-agent"] ?? "n/a", + ip: req.ip ?? "n/a", + }, + }); + + if (err instanceof ValidationError) { + respondWithValidationError({ + res, + response: { + message: err.message, + issues: err.issues, + }, + }); + return; + } + + respondWithGenericError({ + res, + response: { + message: err.message, + }, + statusCode: 500, + }); +}; diff --git a/src/shared/infrastructure/http/error-handlers/zodValidation.ts b/src/shared/infrastructure/http/error-handlers/zodValidation.ts new file mode 100644 index 0000000..384ea60 --- /dev/null +++ b/src/shared/infrastructure/http/error-handlers/zodValidation.ts @@ -0,0 +1,38 @@ +import type { NextFunction, Request, Response } from "express"; +import { ZodError } from "zod"; +import type { ILogger } from "@/shared/application/ports/ILogger.js"; +import { SharedDomain } from "@/shared/application/ports/shared.symbols.js"; +import { ValidationError } from "@/shared/core/errors/ValidationError.js"; +import { appContainer } from "../../di/Container.js"; + +export const zodValidationHandler = ( + err: ZodError, + req: Request, + _res: Response, + next: NextFunction, +) => { + const logger = appContainer.get(SharedDomain.ILogger, { + optional: true, + }); + if (err instanceof ZodError) { + const issues = err.issues.map((issue) => ({ + field: issue.path[0]?.toString() ?? "root", + message: issue.message, + })); + const validationError = new ValidationError("Validation error", issues); + logger?.error({ + message: "ZodError caught!", + module: "zodValidationHandler", + context: { + route: req.originalUrl, + method: req.method, + userAgent: req.headers["user-agent"] ?? "n/a", + ip: req.ip ?? "n/a", + }, + error: validationError, + }); + next(validationError); + } + + next(err); +}; diff --git a/src/shared/infrastructure/http/middlewares/attachRequestContext.ts b/src/shared/infrastructure/http/middlewares/attachRequestContext.ts new file mode 100644 index 0000000..77d89d6 --- /dev/null +++ b/src/shared/infrastructure/http/middlewares/attachRequestContext.ts @@ -0,0 +1,18 @@ +import type { NextFunction, Request, Response } from "express"; +import { SharedDomain } from "@/shared/application/ports/shared.symbols.js"; +import { appContainer } from "@/shared/infrastructure/di/Container.js"; + +export const attachRequestContext = ( + req: Request, + _res: Response, + next: NextFunction, +) => { + appContainer.bind(SharedDomain.IRequestContext).toConstantValue({ + route: req.originalUrl, + method: req.method, + userAgent: req.headers["user-agent"], + ip: req.ip, + }); + next(); + appContainer.unbind(SharedDomain.IRequestContext); +}; diff --git a/src/shared/infrastructure/http/middlewares/attachSession.ts b/src/shared/infrastructure/http/middlewares/attachSession.ts new file mode 100644 index 0000000..2e7c709 --- /dev/null +++ b/src/shared/infrastructure/http/middlewares/attachSession.ts @@ -0,0 +1,37 @@ +import type { NextFunction, Request, Response } from "express"; +import type { IUsersRepository } from "@/modules/users/domain/users.repo.js"; +import { UsersDomain } from "@/modules/users/domain/users.symbols.js"; +import type { ITokenService } from "@/shared/application/ports/ITokenService.js"; +import { SharedDomain } from "@/shared/application/ports/shared.symbols.js"; +import { appContainer } from "../../di/Container.js"; + +export const attachSession = async ( + req: Request, + _res: Response, + next: NextFunction, +) => { + const tokenService = appContainer.get( + SharedDomain.ITokenService, + ); + const userRepo = appContainer.get( + UsersDomain.IUserRepository, + ); + const token = req.cookies.token; + if (!token) { + next(); + return; + } + const session = tokenService.getSession(token); + if (!session) { + next(); + return; + } + const currentUser = await userRepo.findOne({ id: session.userId }); + if (!currentUser) { + next(); + return; + } + req.session = session; + req.currentUser = currentUser; + next(); +}; diff --git a/src/shared/infrastructure/http/middlewares/requestLogger.ts b/src/shared/infrastructure/http/middlewares/requestLogger.ts new file mode 100644 index 0000000..727bcaf --- /dev/null +++ b/src/shared/infrastructure/http/middlewares/requestLogger.ts @@ -0,0 +1,26 @@ +import type { NextFunction, Request, Response } from "express"; +import type { ILogger } from "@/shared/application/ports/ILogger.js"; +import { SharedDomain } from "@/shared/application/ports/shared.symbols.js"; +import { appContainer } from "../../di/Container.js"; + +export const requestLogger = ( + req: Request, + res: Response, + next: NextFunction, +) => { + res.on("finish", () => { + const logger = appContainer.get(SharedDomain.ILogger); + logger.info({ + message: res.statusCode.toString(), + module: "requestLogger", + context: { + route: req.originalUrl, + method: req.method, + userAgent: req.headers["user-agent"] ?? "n/a", + ip: req.ip ?? "n/a", + }, + }); + }); + + next(); +}; diff --git a/src/shared/infrastructure/http/middlewares/requireAuth.ts b/src/shared/infrastructure/http/middlewares/requireAuth.ts new file mode 100644 index 0000000..471cc9a --- /dev/null +++ b/src/shared/infrastructure/http/middlewares/requireAuth.ts @@ -0,0 +1,19 @@ +import type { NextFunction, Request, Response } from "express"; +import { respondWithGenericError } from "../responses/respondWithGenericError.js"; + +export const requireAuth = ( + req: Request, + res: Response, + next: NextFunction, +) => { + if (!req.session) { + return respondWithGenericError({ + res, + response: { + message: "Unauthorized", + }, + statusCode: 401, + }); + } + next(); +}; diff --git a/src/shared/infrastructure/http/middlewares/stripHeaders.ts b/src/shared/infrastructure/http/middlewares/stripHeaders.ts new file mode 100644 index 0000000..4ce3268 --- /dev/null +++ b/src/shared/infrastructure/http/middlewares/stripHeaders.ts @@ -0,0 +1,14 @@ +import type { NextFunction, Request, Response } from "express"; + +const HEADERS_TO_STRIP = ["x-powered-by"]; + +export const stripHeaders = ( + _req: Request, + res: Response, + next: NextFunction, +) => { + HEADERS_TO_STRIP.forEach((header) => { + res.removeHeader(header); + }); + next(); +}; diff --git a/src/shared/infrastructure/http/request.ts b/src/shared/infrastructure/http/request.ts new file mode 100644 index 0000000..b54ad1c --- /dev/null +++ b/src/shared/infrastructure/http/request.ts @@ -0,0 +1,9 @@ +import type { UserEntity } from "@/modules/users/domain/users.entity.js"; +import type { ISession } from "@/shared/application/ports/ITokenService.js"; + +declare module "express-serve-static-core" { + interface Request { + session?: ISession; + currentUser?: UserEntity; + } +} diff --git a/src/shared/infrastructure/http/responses/respondWithGenericError.ts b/src/shared/infrastructure/http/responses/respondWithGenericError.ts new file mode 100644 index 0000000..4c44330 --- /dev/null +++ b/src/shared/infrastructure/http/responses/respondWithGenericError.ts @@ -0,0 +1,18 @@ +import type { Response } from "express"; + +export type GenericErrorResponse = { + message: string; +}; + +type RespondWithGenericErrorParams = { + res: Response; + response: GenericErrorResponse; + statusCode: number; +}; +export function respondWithGenericError({ + res, + response, + statusCode, +}: RespondWithGenericErrorParams) { + res.status(statusCode).json(response); +} diff --git a/src/shared/infrastructure/http/responses/respondWithValidationError.ts b/src/shared/infrastructure/http/responses/respondWithValidationError.ts new file mode 100644 index 0000000..2d3f79d --- /dev/null +++ b/src/shared/infrastructure/http/responses/respondWithValidationError.ts @@ -0,0 +1,19 @@ +import type { Response } from "express"; + +export type ValidationErrorResponse = { + message: string; + issues: { + field: string; + message: string; + }[]; +}; +type RespondWithValidationErrorParams = { + res: Response; + response: ValidationErrorResponse; +}; +export function respondWithValidationError({ + res, + response, +}: RespondWithValidationErrorParams) { + res.status(400).json(response); +} diff --git a/src/shared/infrastructure/http/routes.ts b/src/shared/infrastructure/http/routes.ts new file mode 100644 index 0000000..f86e9a6 --- /dev/null +++ b/src/shared/infrastructure/http/routes.ts @@ -0,0 +1,15 @@ +import express from "express"; +import authRoutes from "@/modules/auth/infrastructure/http/auth.routes.js"; +import helloWorldRoutes from "@/modules/hello-world/infrastructure/http/hello-world.routes.js"; +import { attachRequestContext } from "./middlewares/attachRequestContext.js"; +import { attachSession } from "./middlewares/attachSession.js"; + +const routes = express.Router(); + +routes.use(attachRequestContext); +routes.use(attachSession); + +routes.use("/auth", authRoutes); +routes.use("/hello-world", helloWorldRoutes); + +export default routes; diff --git a/src/shared/infrastructure/logger/ConsoleLogger.ts b/src/shared/infrastructure/logger/ConsoleLogger.ts new file mode 100644 index 0000000..b983e93 --- /dev/null +++ b/src/shared/infrastructure/logger/ConsoleLogger.ts @@ -0,0 +1,30 @@ +import type { + ILogger, + LogMessage, +} from "@/shared/application/ports/ILogger.js"; + +function messageBuilder({ message, module, context }: LogMessage): string { + const fullMessage = [message]; + const localDateTime = new Date().toLocaleString(); + if (context) { + fullMessage.push(`(${context.ip}) [${context.method} ${context.route}]`); + } + if (module) { + fullMessage.push(`[${module}]`); + } + return `[${localDateTime}] ${fullMessage.reverse().join(" ")}`; +} + +export class ConsoleLogger implements ILogger { + info(message: LogMessage): void { + console.log(messageBuilder(message)); + } + + error(message: LogMessage): void { + console.error(messageBuilder(message)); + } + + warn(message: LogMessage): void { + console.warn(messageBuilder(message)); + } +} diff --git a/src/shared/infrastructure/persistence/fakes/InMemoryRepository.spec.ts b/src/shared/infrastructure/persistence/fakes/InMemoryRepository.spec.ts new file mode 100644 index 0000000..3e64ca9 --- /dev/null +++ b/src/shared/infrastructure/persistence/fakes/InMemoryRepository.spec.ts @@ -0,0 +1,402 @@ +import { beforeEach, describe, expect, it } from "vitest"; +import { InMemoryRepository } from "./InMemoryRepository.js"; + +type TestEntity = { + id: string; + name: string; + age: number; + isActive: boolean; + createdAt: Date; +}; + +class TestRepository extends InMemoryRepository {} + +describe("InMemoryRepository (Generic) - Comprehensive Tests", () => { + let repo: TestRepository; + + beforeEach(() => { + repo = new TestRepository(); + }); + + describe("Persistence Operations", () => { + it("should save and find an entity by ID", async () => { + const id = repo.generateId(); + const entity: TestEntity = { + id, + name: "Test", + age: 25, + isActive: true, + createdAt: new Date(), + }; + + await repo.save(entity); + const found = await repo.findById(id); + + expect(found).toEqual(entity); + }); + + it("should update an existing entity with the same ID", async () => { + const id = repo.generateId(); + const entity: TestEntity = { + id, + name: "Old Name", + age: 25, + isActive: true, + createdAt: new Date(), + }; + + await repo.save(entity); + + // Modify and save again + entity.name = "New Name"; + await repo.save(entity); + + const found = await repo.findById(id); + expect(found).toBeDefined(); + expect(found?.name).toBe("New Name"); + + // Ensure no duplicate records + const all = await repo.findAll(); + expect(all.total).toBe(1); + }); + + it("should return null if finding by non-existent ID", async () => { + const found = await repo.findById("non-existent-id"); + expect(found).toBeNull(); + }); + }); + + describe("Query Operations", () => { + it("should find one entity by filter criteria", async () => { + await repo.save({ + id: "1", + name: "Unique", + age: 25, + isActive: true, + createdAt: new Date(), + }); + await repo.save({ + id: "2", + name: "Other", + age: 30, + isActive: false, + createdAt: new Date(), + }); + + const found = await repo.findOne({ name: "Unique" }); + expect(found).toBeDefined(); + expect(found?.id).toBe("1"); + }); + + it("should return null if findOne matches nothing", async () => { + const found = await repo.findOne({ name: "NonExistent" }); + expect(found).toBeNull(); + }); + }); + + describe("Exact Match Filtering", () => { + it("should filter by string (exact match)", async () => { + await repo.save({ + id: "1", + name: "Alice", + age: 25, + isActive: true, + createdAt: new Date(), + }); + await repo.save({ + id: "2", + name: "Bob", + age: 30, + isActive: false, + createdAt: new Date(), + }); + + const result = await repo.findAll({ name: "Alice" }); + expect(result.data).toHaveLength(1); + expect(result.data.at(0)?.name).toBe("Alice"); + }); + + it("should filter by number (exact match)", async () => { + await repo.save({ + id: "1", + name: "Alice", + age: 25, + isActive: true, + createdAt: new Date(), + }); + await repo.save({ + id: "2", + name: "Bob", + age: 30, + isActive: false, + createdAt: new Date(), + }); + + const result = await repo.findAll({ age: 25 }); + expect(result.data).toHaveLength(1); + expect(result.data.at(0)?.age).toBe(25); + }); + + it("should filter by boolean (true)", async () => { + await repo.save({ + id: "1", + name: "Alice", + age: 25, + isActive: true, + createdAt: new Date(), + }); + await repo.save({ + id: "2", + name: "Bob", + age: 30, + isActive: false, + createdAt: new Date(), + }); + + const result = await repo.findAll({ isActive: true }); + expect(result.data).toHaveLength(1); + expect(result.data.at(0)?.isActive).toBe(true); + }); + + it("should filter by boolean (false)", async () => { + await repo.save({ + id: "1", + name: "Alice", + age: 25, + isActive: true, + createdAt: new Date(), + }); + await repo.save({ + id: "2", + name: "Bob", + age: 30, + isActive: false, + createdAt: new Date(), + }); + + const result = await repo.findAll({ isActive: false }); + expect(result.data).toHaveLength(1); + expect(result.data.at(0)?.isActive).toBe(false); + }); + + it("should filter by Date (exact match)", async () => { + const date1 = new Date("2023-01-01T00:00:00Z"); + const date2 = new Date("2023-01-02T00:00:00Z"); + + await repo.save({ + id: "1", + name: "Alice", + age: 25, + isActive: true, + createdAt: date1, + }); + await repo.save({ + id: "2", + name: "Bob", + age: 30, + isActive: false, + createdAt: date2, + }); + + const result = await repo.findAll({ createdAt: date1 }); + expect(result.data).toHaveLength(1); + expect(result.data.at(0)?.createdAt).toEqual(date1); + }); + + it("should ignore undefined filter properties", async () => { + await repo.save({ + id: "1", + name: "Alice", + age: 25, + isActive: true, + createdAt: new Date(), + }); + await repo.save({ + id: "2", + name: "Bob", + age: 30, + isActive: false, + createdAt: new Date(), + }); + + const result = await repo.findAll({ name: undefined }); + expect(result.data).toHaveLength(2); + }); + }); + + describe("Range Filtering - Number", () => { + beforeEach(async () => { + await repo.save({ + id: "1", + name: "Kid", + age: 10, + isActive: true, + createdAt: new Date(), + }); + await repo.save({ + id: "2", + name: "Teen", + age: 15, + isActive: true, + createdAt: new Date(), + }); + await repo.save({ + id: "3", + name: "Adult", + age: 25, + isActive: true, + createdAt: new Date(), + }); + }); + + it("should filter by number range (from only - tail case)", async () => { + // age >= 15 + const result = await repo.findAll({ age: { from: 15 } }); + expect(result.data).toHaveLength(2); // Teen, Adult + expect(result.data.map((i) => i.age).sort()).toEqual([15, 25]); + }); + + it("should filter by number range (to only - head case)", async () => { + // age <= 15 + const result = await repo.findAll({ age: { to: 15 } }); + expect(result.data).toHaveLength(2); // Kid, Teen + expect(result.data.map((i) => i.age).sort()).toEqual([10, 15]); + }); + + it("should filter by number range (from and to - middle case)", async () => { + // 12 <= age <= 20 + const result = await repo.findAll({ age: { from: 12, to: 20 } }); + expect(result.data).toHaveLength(1); // Teen + expect(result.data.at(0)?.age).toBe(15); + }); + }); + + describe("Range Filtering - Date", () => { + const d1 = new Date("2023-01-01T10:00:00Z"); + const d2 = new Date("2023-01-02T10:00:00Z"); + const d3 = new Date("2023-01-03T10:00:00Z"); + + beforeEach(async () => { + await repo.save({ + id: "1", + name: "First", + age: 20, + isActive: true, + createdAt: d1, + }); + await repo.save({ + id: "2", + name: "Second", + age: 20, + isActive: true, + createdAt: d2, + }); + await repo.save({ + id: "3", + name: "Third", + age: 20, + isActive: true, + createdAt: d3, + }); + }); + + it("should filter by date range (from only - tail case)", async () => { + // date >= d2 + const result = await repo.findAll({ createdAt: { from: d2 } }); + expect(result.data).toHaveLength(2); // Second, Third + expect(result.data.map((i) => i.createdAt.getTime()).sort()).toEqual([ + d2.getTime(), + d3.getTime(), + ]); + }); + + it("should filter by date range (to only - head case)", async () => { + // date <= d2 + const result = await repo.findAll({ createdAt: { to: d2 } }); + expect(result.data).toHaveLength(2); // First, Second + expect(result.data.map((i) => i.createdAt.getTime()).sort()).toEqual([ + d1.getTime(), + d2.getTime(), + ]); + }); + + it("should filter by date range (from and to - middle case)", async () => { + // d1 <= date <= d2 + const result = await repo.findAll({ createdAt: { from: d1, to: d2 } }); + expect(result.data).toHaveLength(2); // First, Second + }); + }); + + describe("Pagination with Filtering", () => { + beforeEach(async () => { + // Create 10 items + for (let i = 1; i <= 10; i++) { + await repo.save({ + id: i.toString(), + name: i % 2 === 0 ? "Even" : "Odd", + age: i * 10, // 10, 20, ..., 100 + isActive: true, + createdAt: new Date(`2023-01-${i.toString().padStart(2, "0")}`), + }); + } + }); + + it("should paginate exact match results", async () => { + // Filter: name = "Even" (5 items: 2, 4, 6, 8, 10) + // Page 1: limit 2 -> [2, 4] + const page1 = await repo.findAll( + { name: "Even" }, + { offset: 0, limit: 2 }, + ); + expect(page1.total).toBe(5); + expect(page1.data).toHaveLength(2); + expect(page1.data.at(0)?.id).toBe("2"); + expect(page1.data.at(1)?.id).toBe("4"); + + // Page 2: offset 2, limit 2 -> [6, 8] + const page2 = await repo.findAll( + { name: "Even" }, + { offset: 2, limit: 2 }, + ); + expect(page2.data).toHaveLength(2); + expect(page2.data.at(0)?.id).toBe("6"); + expect(page2.data.at(1)?.id).toBe("8"); + }); + + it("should paginate number range results", async () => { + // Filter: age >= 50 (6 items: 50, 60, 70, 80, 90, 100) + // Page 1: limit 3 -> [50, 60, 70] + const result = await repo.findAll( + { age: { from: 50 } }, + { offset: 0, limit: 3 }, + ); + expect(result.total).toBe(6); + expect(result.data).toHaveLength(3); + expect(result.data.map((i) => i.age)).toEqual([50, 60, 70]); + }); + + it("should paginate date range results", async () => { + // Filter: date <= 2023-01-05 (5 items: 1, 2, 3, 4, 5) + const targetDate = new Date("2023-01-05"); + // Page 2: offset 2, limit 2 -> [3, 4] + const result = await repo.findAll( + { createdAt: { to: targetDate } }, + { offset: 2, limit: 2 }, + ); + expect(result.total).toBe(5); + expect(result.data).toHaveLength(2); + expect(result.data.at(0)?.id).toBe("3"); + expect(result.data.at(1)?.id).toBe("4"); + }); + }); + + describe("Utility Operations", () => { + it("should generate unique IDs", () => { + const ids = new Set(); + for (let i = 0; i < 1000; i++) { + ids.add(repo.generateId()); + } + expect(ids.size).toBe(1000); + }); + }); +}); diff --git a/src/shared/infrastructure/persistence/fakes/InMemoryRepository.ts b/src/shared/infrastructure/persistence/fakes/InMemoryRepository.ts new file mode 100644 index 0000000..72048b9 --- /dev/null +++ b/src/shared/infrastructure/persistence/fakes/InMemoryRepository.ts @@ -0,0 +1,102 @@ +import { randomUUID } from "node:crypto"; +import type { + FilterCriteria, + FilterRange, + IBaseRepository, + PaginationOptions, + WithPagination, +} from "@/shared/core/IBaseRepository.js"; + +export class InMemoryRepository + implements IBaseRepository +{ + protected items: T[] = []; + + async save(entity: T): Promise { + const index = this.items.findIndex((item) => item.id === entity.id); + if (index !== -1) { + this.items[index] = entity; + } else { + this.items.push(entity); + } + return entity; + } + + async findById(id: string): Promise { + return this.items.find((item) => item.id === id) || null; + } + + async findOne(criteria: FilterCriteria): Promise { + const filtered = this.applyFilters(this.items, criteria); + return filtered[0] || null; + } + + async findAll( + criteria?: FilterCriteria, + paginationOptions?: PaginationOptions, + ): Promise> { + let filtered = this.items; + + if (criteria) { + filtered = this.applyFilters(filtered, criteria); + } + + const total = filtered.length; + + if (paginationOptions) { + const { offset, limit } = paginationOptions; + filtered = filtered.slice(offset, offset + limit); + } + + return { + data: filtered, + total, + }; + } + + generateId(): string { + return randomUUID(); + } + + protected applyFilters(items: T[], criteria: FilterCriteria): T[] { + return items.filter((item) => { + return Object.entries(criteria).every(([key, value]) => { + const itemValue = item[key as keyof T]; + + if (value === undefined) return true; + + // Handle Date Range + if ( + value && + typeof value === "object" && + ("from" in value || "to" in value) && + ((value as FilterRange).from instanceof Date || + (value as FilterRange).to instanceof Date) && + itemValue instanceof Date + ) { + const range = value as FilterRange; + if (range.from && itemValue < range.from) return false; + if (range.to && itemValue > range.to) return false; + return true; + } + + // Handle Number Range + if ( + value && + typeof value === "object" && + (typeof (value as FilterRange).from === "number" || + typeof (value as FilterRange).to === "number") && + typeof itemValue === "number" + ) { + const range = value as FilterRange; + if (range.from !== undefined && itemValue < range.from) return false; + if (range.to !== undefined && itemValue > range.to) return false; + return true; + } + + // Handle Exact Match + return itemValue === value; + }); + }); + } +} diff --git a/src/shared/infrastructure/persistence/prisma/PrismaClientWrapper.ts b/src/shared/infrastructure/persistence/prisma/PrismaClientWrapper.ts new file mode 100644 index 0000000..922138f --- /dev/null +++ b/src/shared/infrastructure/persistence/prisma/PrismaClientWrapper.ts @@ -0,0 +1,36 @@ +import { PrismaPg } from "@prisma/adapter-pg"; +import { inject, injectable } from "inversify"; +import { uuidv7 } from "uuidv7"; +import { PrismaClient as PrismaClientLib } from "@/generated/prisma/client.js"; +import type { IConfigService } from "@/shared/application/ports/IConfigService.js"; +import { SharedDomain } from "@/shared/application/ports/shared.symbols.js"; + +export type PrismaClient = PrismaClientLib; + +@injectable() +export class PrismaClientWrapper { + private readonly client: PrismaClientLib; + + constructor( + @inject(SharedDomain.IConfigService) + private readonly configService: IConfigService, + ) { + const connectionString = this.configService.get("POSTGRES_URL"); + + const adapter = new PrismaPg({ + connectionString, + }); + + this.client = new PrismaClientLib({ + adapter, + }); + } + + getClient(): PrismaClientLib { + return this.client; + } + + generateId(): string { + return uuidv7(); + } +} diff --git a/src/swagger.ts b/src/swagger.ts new file mode 100644 index 0000000..9530ebb --- /dev/null +++ b/src/swagger.ts @@ -0,0 +1,14 @@ +import swaggerJSDoc from "swagger-jsdoc"; + +const config = { + definition: { + openapi: "3.0.0", + info: { + title: "Express-Starter", + version: "1.0.0", + }, + }, + apis: ["./src/**/*.ts"], +}; + +export const swaggerSpec = swaggerJSDoc(config); diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..5e3a23c --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,52 @@ +{ + // Visit https://aka.ms/tsconfig to read more about this file + "compilerOptions": { + // File Layout + "rootDir": "./src", + "outDir": "./dist", + // Environment Settings + // See also https://aka.ms/tsconfig/module + "module": "nodenext", + "moduleResolution": "nodenext", + // For nodejs: + // "lib": ["esnext"], + // "types": ["node"], + // and npm install -D @types/node + // Other Outputs + "sourceMap": true, + "declaration": true, + "declarationMap": true, + // Stricter Typechecking Options + "noUncheckedIndexedAccess": true, + "exactOptionalPropertyTypes": true, + // Style Options + // "noImplicitReturns": true, + // "noImplicitOverride": true, + // "noUnusedLocals": true, + // "noUnusedParameters": true, + // "noFallthroughCasesInSwitch": true, + // "noPropertyAccessFromIndexSignature": true, + // Recommended Options + "strict": true, + "esModuleInterop": true, + // "jsx": "react-jsx", + "experimentalDecorators": true, + "emitDecoratorMetadata": true, + "verbatimModuleSyntax": true, + "isolatedModules": true, + "noUncheckedSideEffectImports": true, + "moduleDetection": "auto", + "skipLibCheck": true, + "baseUrl": "./", + "paths": { + "@/*": [ + "src/*" + ] + } + }, + "exclude": [ + "prisma.config.ts", + "vitest.config.ts", + "dist" + ] +} \ No newline at end of file diff --git a/vitest.config.ts b/vitest.config.ts new file mode 100644 index 0000000..c76b477 --- /dev/null +++ b/vitest.config.ts @@ -0,0 +1,9 @@ +import { defineConfig } from 'vitest/config' +import tsConfigPaths from 'vite-tsconfig-paths' + +export default defineConfig({ + plugins: [tsConfigPaths()], + test: { + // ... Specify options here. + }, +}) \ No newline at end of file diff --git a/yarn.lock b/yarn.lock new file mode 100644 index 0000000..349e644 --- /dev/null +++ b/yarn.lock @@ -0,0 +1,2710 @@ +# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. +# yarn lockfile v1 + + +"@apidevtools/json-schema-ref-parser@^9.0.6": + version "9.1.2" + resolved "https://registry.yarnpkg.com/@apidevtools/json-schema-ref-parser/-/json-schema-ref-parser-9.1.2.tgz#8ff5386b365d4c9faa7c8b566ff16a46a577d9b8" + integrity sha512-r1w81DpR+KyRWd3f+rk6TNqMgedmAxZP5v5KWlXQWlgMUUtyEJch0DKEci1SorPMiSeM8XPl7MZ3miJ60JIpQg== + dependencies: + "@jsdevtools/ono" "^7.1.3" + "@types/json-schema" "^7.0.6" + call-me-maybe "^1.0.1" + js-yaml "^4.1.0" + +"@apidevtools/openapi-schemas@^2.0.4": + version "2.1.0" + resolved "https://registry.yarnpkg.com/@apidevtools/openapi-schemas/-/openapi-schemas-2.1.0.tgz#9fa08017fb59d80538812f03fc7cac5992caaa17" + integrity sha512-Zc1AlqrJlX3SlpupFGpiLi2EbteyP7fXmUOGup6/DnkRgjP9bgMM/ag+n91rsv0U1Gpz0H3VILA/o3bW7Ua6BQ== + +"@apidevtools/swagger-methods@^3.0.2": + version "3.0.2" + resolved "https://registry.yarnpkg.com/@apidevtools/swagger-methods/-/swagger-methods-3.0.2.tgz#b789a362e055b0340d04712eafe7027ddc1ac267" + integrity sha512-QAkD5kK2b1WfjDS/UQn/qQkbwF31uqRjPTrsCs5ZG9BQGAkjwvqGFjjPqAuzac/IYzpPtRzjCP1WrTuAIjMrXg== + +"@apidevtools/swagger-parser@10.0.3": + version "10.0.3" + resolved "https://registry.yarnpkg.com/@apidevtools/swagger-parser/-/swagger-parser-10.0.3.tgz#32057ae99487872c4dd96b314a1ab4b95d89eaf5" + integrity sha512-sNiLY51vZOmSPFZA5TF35KZ2HbgYklQnTSDnkghamzLb3EkNtcQnrBQEj5AOCxHpTtXpqMCRM1CrmV2rG6nw4g== + dependencies: + "@apidevtools/json-schema-ref-parser" "^9.0.6" + "@apidevtools/openapi-schemas" "^2.0.4" + "@apidevtools/swagger-methods" "^3.0.2" + "@jsdevtools/ono" "^7.1.3" + call-me-maybe "^1.0.1" + z-schema "^5.0.1" + +"@babel/helper-string-parser@^7.27.1": + version "7.27.1" + resolved "https://registry.yarnpkg.com/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz#54da796097ab19ce67ed9f88b47bb2ec49367687" + integrity sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA== + +"@babel/helper-validator-identifier@^7.28.5": + version "7.28.5" + resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz#010b6938fab7cb7df74aa2bbc06aa503b8fe5fb4" + integrity sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q== + +"@babel/parser@^7.28.5": + version "7.28.6" + resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.28.6.tgz#f01a8885b7fa1e56dd8a155130226cd698ef13fd" + integrity sha512-TeR9zWR18BvbfPmGbLampPMW+uW1NZnJlRuuHso8i87QZNq2JRF9i6RgxRqtEq+wQGsS19NNTWr2duhnE49mfQ== + dependencies: + "@babel/types" "^7.28.6" + +"@babel/types@^7.28.5", "@babel/types@^7.28.6": + version "7.28.6" + resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.28.6.tgz#c3e9377f1b155005bcc4c46020e7e394e13089df" + integrity sha512-0ZrskXVEHSWIqZM/sQZ4EV3jZJXRkio/WCxaqKZP1g//CEWEPSfeZFcms4XeKBCHU0ZKnIkdJeU/kF+eRp5lBg== + dependencies: + "@babel/helper-string-parser" "^7.27.1" + "@babel/helper-validator-identifier" "^7.28.5" + +"@bcoe/v8-coverage@^1.0.2": + version "1.0.2" + resolved "https://registry.yarnpkg.com/@bcoe/v8-coverage/-/v8-coverage-1.0.2.tgz#bbe12dca5b4ef983a0d0af4b07b9bc90ea0ababa" + integrity sha512-6zABk/ECA/QYSCQ1NGiVwwbQerUCZ+TQbp64Q3AgmfNvurHH0j8TtXa1qbShXA6qqkpAj4V5W8pP6mLe1mcMqA== + +"@biomejs/biome@2.3.11": + version "2.3.11" + resolved "https://registry.yarnpkg.com/@biomejs/biome/-/biome-2.3.11.tgz#a8f3682b3b2c0112e2728f6d51d9c67a6c5521f8" + integrity sha512-/zt+6qazBWguPG6+eWmiELqO+9jRsMZ/DBU3lfuU2ngtIQYzymocHhKiZRyrbra4aCOoyTg/BmY+6WH5mv9xmQ== + optionalDependencies: + "@biomejs/cli-darwin-arm64" "2.3.11" + "@biomejs/cli-darwin-x64" "2.3.11" + "@biomejs/cli-linux-arm64" "2.3.11" + "@biomejs/cli-linux-arm64-musl" "2.3.11" + "@biomejs/cli-linux-x64" "2.3.11" + "@biomejs/cli-linux-x64-musl" "2.3.11" + "@biomejs/cli-win32-arm64" "2.3.11" + "@biomejs/cli-win32-x64" "2.3.11" + +"@biomejs/cli-darwin-arm64@2.3.11": + version "2.3.11" + resolved "https://registry.yarnpkg.com/@biomejs/cli-darwin-arm64/-/cli-darwin-arm64-2.3.11.tgz#3aa71d119e7216d66620282121673b95257fcc35" + integrity sha512-/uXXkBcPKVQY7rc9Ys2CrlirBJYbpESEDme7RKiBD6MmqR2w3j0+ZZXRIL2xiaNPsIMMNhP1YnA+jRRxoOAFrA== + +"@biomejs/cli-darwin-x64@2.3.11": + version "2.3.11" + resolved "https://registry.yarnpkg.com/@biomejs/cli-darwin-x64/-/cli-darwin-x64-2.3.11.tgz#3003d7c24e4fbba9693fba98692c6386884fedcf" + integrity sha512-fh7nnvbweDPm2xEmFjfmq7zSUiox88plgdHF9OIW4i99WnXrAC3o2P3ag9judoUMv8FCSUnlwJCM1B64nO5Fbg== + +"@biomejs/cli-linux-arm64-musl@2.3.11": + version "2.3.11" + resolved "https://registry.yarnpkg.com/@biomejs/cli-linux-arm64-musl/-/cli-linux-arm64-musl-2.3.11.tgz#a17fb411ba8146ba60f3592857b7c49c8f0fe936" + integrity sha512-XPSQ+XIPZMLaZ6zveQdwNjbX+QdROEd1zPgMwD47zvHV+tCGB88VH+aynyGxAHdzL+Tm/+DtKST5SECs4iwCLg== + +"@biomejs/cli-linux-arm64@2.3.11": + version "2.3.11" + resolved "https://registry.yarnpkg.com/@biomejs/cli-linux-arm64/-/cli-linux-arm64-2.3.11.tgz#643830d1e53071d594e16b919e88a46fbc5c0b55" + integrity sha512-l4xkGa9E7Uc0/05qU2lMYfN1H+fzzkHgaJoy98wO+b/7Gl78srbCRRgwYSW+BTLixTBrM6Ede5NSBwt7rd/i6g== + +"@biomejs/cli-linux-x64-musl@2.3.11": + version "2.3.11" + resolved "https://registry.yarnpkg.com/@biomejs/cli-linux-x64-musl/-/cli-linux-x64-musl-2.3.11.tgz#e9165c6d05f085829ab73a7ea0123a6a9b45dc89" + integrity sha512-vU7a8wLs5C9yJ4CB8a44r12aXYb8yYgBn+WeyzbMjaCMklzCv1oXr8x+VEyWodgJt9bDmhiaW/I0RHbn7rsNmw== + +"@biomejs/cli-linux-x64@2.3.11": + version "2.3.11" + resolved "https://registry.yarnpkg.com/@biomejs/cli-linux-x64/-/cli-linux-x64-2.3.11.tgz#3571049b1310132f1d12d67596ea5f39419836d0" + integrity sha512-/1s9V/H3cSe0r0Mv/Z8JryF5x9ywRxywomqZVLHAoa/uN0eY7F8gEngWKNS5vbbN/BsfpCG5yeBT5ENh50Frxg== + +"@biomejs/cli-win32-arm64@2.3.11": + version "2.3.11" + resolved "https://registry.yarnpkg.com/@biomejs/cli-win32-arm64/-/cli-win32-arm64-2.3.11.tgz#6769907655cd06938b00d040c6576c2fdf6c34a2" + integrity sha512-PZQ6ElCOnkYapSsysiTy0+fYX+agXPlWugh6+eQ6uPKI3vKAqNp6TnMhoM3oY2NltSB89hz59o8xIfOdyhi9Iw== + +"@biomejs/cli-win32-x64@2.3.11": + version "2.3.11" + resolved "https://registry.yarnpkg.com/@biomejs/cli-win32-x64/-/cli-win32-x64-2.3.11.tgz#71ba2fb5505b3b01dd3cf551ef329e0094636125" + integrity sha512-43VrG813EW+b5+YbDbz31uUsheX+qFKCpXeY9kfdAx+ww3naKxeVkTD9zLIWxUPfJquANMHrmW3wbe/037G0Qg== + +"@chevrotain/cst-dts-gen@10.5.0": + version "10.5.0" + resolved "https://registry.yarnpkg.com/@chevrotain/cst-dts-gen/-/cst-dts-gen-10.5.0.tgz#922ebd8cc59d97241bb01b1b17561a5c1ae0124e" + integrity sha512-lhmC/FyqQ2o7pGK4Om+hzuDrm9rhFYIJ/AXoQBeongmn870Xeb0L6oGEiuR8nohFNL5sMaQEJWCxr1oIVIVXrw== + dependencies: + "@chevrotain/gast" "10.5.0" + "@chevrotain/types" "10.5.0" + lodash "4.17.21" + +"@chevrotain/gast@10.5.0": + version "10.5.0" + resolved "https://registry.yarnpkg.com/@chevrotain/gast/-/gast-10.5.0.tgz#e4e614bc46d17a8892742f38e56cd33f1f3ad162" + integrity sha512-pXdMJ9XeDAbgOWKuD1Fldz4ieCs6+nLNmyVhe2gZVqoO7v8HXuHYs5OV2EzUtbuai37TlOAQHrTDvxMnvMJz3A== + dependencies: + "@chevrotain/types" "10.5.0" + lodash "4.17.21" + +"@chevrotain/types@10.5.0": + version "10.5.0" + resolved "https://registry.yarnpkg.com/@chevrotain/types/-/types-10.5.0.tgz#52a97d74a8cfbc197f054636d93ecd8912d33d21" + integrity sha512-f1MAia0x/pAVPWH/T73BJVyO2XU5tI4/iE7cnxb7tqdNTNhQI3Uq3XkqcoteTmD4t1aM0LbHCJOhgIDn07kl2A== + +"@chevrotain/utils@10.5.0": + version "10.5.0" + resolved "https://registry.yarnpkg.com/@chevrotain/utils/-/utils-10.5.0.tgz#0ee36f65b49b447fbac71b9e5af5c5c6c98ac057" + integrity sha512-hBzuU5+JjB2cqNZyszkDHZgOSrUUT8V3dhgRl8Q9Gp6dAj/H5+KILGjbhDpc3Iy9qmqlm/akuOI2ut9VUtzJxQ== + +"@electric-sql/pglite-socket@0.0.6": + version "0.0.6" + resolved "https://registry.yarnpkg.com/@electric-sql/pglite-socket/-/pglite-socket-0.0.6.tgz#2808e1651a6980ca6b55c6950d06702e00b59815" + integrity sha512-6RjmgzphIHIBA4NrMGJsjNWK4pu+bCWJlEWlwcxFTVY3WT86dFpKwbZaGWZV6C5Rd7sCk1Z0CI76QEfukLAUXw== + +"@electric-sql/pglite-tools@0.2.7": + version "0.2.7" + resolved "https://registry.yarnpkg.com/@electric-sql/pglite-tools/-/pglite-tools-0.2.7.tgz#f8a639a60e1fc5a8cd307aae1711db02a7781875" + integrity sha512-9dAccClqxx4cZB+Ar9B+FZ5WgxDc/Xvl9DPrTWv+dYTf0YNubLzi4wHHRGRGhrJv15XwnyKcGOZAP1VXSneSUg== + +"@electric-sql/pglite@0.3.2": + version "0.3.2" + resolved "https://registry.yarnpkg.com/@electric-sql/pglite/-/pglite-0.3.2.tgz#1962142ac261aab3c9d54b95c1fe1edc492d6fa0" + integrity sha512-zfWWa+V2ViDCY/cmUfRqeWY1yLto+EpxjXnZzenB1TyxsTiXaTWeZFIZw6mac52BsuQm0RjCnisjBtdBaXOI6w== + +"@esbuild/aix-ppc64@0.27.2": + version "0.27.2" + resolved "https://registry.yarnpkg.com/@esbuild/aix-ppc64/-/aix-ppc64-0.27.2.tgz#521cbd968dcf362094034947f76fa1b18d2d403c" + integrity sha512-GZMB+a0mOMZs4MpDbj8RJp4cw+w1WV5NYD6xzgvzUJ5Ek2jerwfO2eADyI6ExDSUED+1X8aMbegahsJi+8mgpw== + +"@esbuild/android-arm64@0.27.2": + version "0.27.2" + resolved "https://registry.yarnpkg.com/@esbuild/android-arm64/-/android-arm64-0.27.2.tgz#61ea550962d8aa12a9b33194394e007657a6df57" + integrity sha512-pvz8ZZ7ot/RBphf8fv60ljmaoydPU12VuXHImtAs0XhLLw+EXBi2BLe3OYSBslR4rryHvweW5gmkKFwTiFy6KA== + +"@esbuild/android-arm@0.27.2": + version "0.27.2" + resolved "https://registry.yarnpkg.com/@esbuild/android-arm/-/android-arm-0.27.2.tgz#554887821e009dd6d853f972fde6c5143f1de142" + integrity sha512-DVNI8jlPa7Ujbr1yjU2PfUSRtAUZPG9I1RwW4F4xFB1Imiu2on0ADiI/c3td+KmDtVKNbi+nffGDQMfcIMkwIA== + +"@esbuild/android-x64@0.27.2": + version "0.27.2" + resolved "https://registry.yarnpkg.com/@esbuild/android-x64/-/android-x64-0.27.2.tgz#a7ce9d0721825fc578f9292a76d9e53334480ba2" + integrity sha512-z8Ank4Byh4TJJOh4wpz8g2vDy75zFL0TlZlkUkEwYXuPSgX8yzep596n6mT7905kA9uHZsf/o2OJZubl2l3M7A== + +"@esbuild/darwin-arm64@0.27.2": + version "0.27.2" + resolved "https://registry.yarnpkg.com/@esbuild/darwin-arm64/-/darwin-arm64-0.27.2.tgz#2cb7659bd5d109803c593cfc414450d5430c8256" + integrity sha512-davCD2Zc80nzDVRwXTcQP/28fiJbcOwvdolL0sOiOsbwBa72kegmVU0Wrh1MYrbuCL98Omp5dVhQFWRKR2ZAlg== + +"@esbuild/darwin-x64@0.27.2": + version "0.27.2" + resolved "https://registry.yarnpkg.com/@esbuild/darwin-x64/-/darwin-x64-0.27.2.tgz#e741fa6b1abb0cd0364126ba34ca17fd5e7bf509" + integrity sha512-ZxtijOmlQCBWGwbVmwOF/UCzuGIbUkqB1faQRf5akQmxRJ1ujusWsb3CVfk/9iZKr2L5SMU5wPBi1UWbvL+VQA== + +"@esbuild/freebsd-arm64@0.27.2": + version "0.27.2" + resolved "https://registry.yarnpkg.com/@esbuild/freebsd-arm64/-/freebsd-arm64-0.27.2.tgz#2b64e7116865ca172d4ce034114c21f3c93e397c" + integrity sha512-lS/9CN+rgqQ9czogxlMcBMGd+l8Q3Nj1MFQwBZJyoEKI50XGxwuzznYdwcav6lpOGv5BqaZXqvBSiB/kJ5op+g== + +"@esbuild/freebsd-x64@0.27.2": + version "0.27.2" + resolved "https://registry.yarnpkg.com/@esbuild/freebsd-x64/-/freebsd-x64-0.27.2.tgz#e5252551e66f499e4934efb611812f3820e990bb" + integrity sha512-tAfqtNYb4YgPnJlEFu4c212HYjQWSO/w/h/lQaBK7RbwGIkBOuNKQI9tqWzx7Wtp7bTPaGC6MJvWI608P3wXYA== + +"@esbuild/linux-arm64@0.27.2": + version "0.27.2" + resolved "https://registry.yarnpkg.com/@esbuild/linux-arm64/-/linux-arm64-0.27.2.tgz#dc4acf235531cd6984f5d6c3b13dbfb7ddb303cb" + integrity sha512-hYxN8pr66NsCCiRFkHUAsxylNOcAQaxSSkHMMjcpx0si13t1LHFphxJZUiGwojB1a/Hd5OiPIqDdXONia6bhTw== + +"@esbuild/linux-arm@0.27.2": + version "0.27.2" + resolved "https://registry.yarnpkg.com/@esbuild/linux-arm/-/linux-arm-0.27.2.tgz#56a900e39240d7d5d1d273bc053daa295c92e322" + integrity sha512-vWfq4GaIMP9AIe4yj1ZUW18RDhx6EPQKjwe7n8BbIecFtCQG4CfHGaHuh7fdfq+y3LIA2vGS/o9ZBGVxIDi9hw== + +"@esbuild/linux-ia32@0.27.2": + version "0.27.2" + resolved "https://registry.yarnpkg.com/@esbuild/linux-ia32/-/linux-ia32-0.27.2.tgz#d4a36d473360f6870efcd19d52bbfff59a2ed1cc" + integrity sha512-MJt5BRRSScPDwG2hLelYhAAKh9imjHK5+NE/tvnRLbIqUWa+0E9N4WNMjmp/kXXPHZGqPLxggwVhz7QP8CTR8w== + +"@esbuild/linux-loong64@0.27.2": + version "0.27.2" + resolved "https://registry.yarnpkg.com/@esbuild/linux-loong64/-/linux-loong64-0.27.2.tgz#fcf0ab8c3eaaf45891d0195d4961cb18b579716a" + integrity sha512-lugyF1atnAT463aO6KPshVCJK5NgRnU4yb3FUumyVz+cGvZbontBgzeGFO1nF+dPueHD367a2ZXe1NtUkAjOtg== + +"@esbuild/linux-mips64el@0.27.2": + version "0.27.2" + resolved "https://registry.yarnpkg.com/@esbuild/linux-mips64el/-/linux-mips64el-0.27.2.tgz#598b67d34048bb7ee1901cb12e2a0a434c381c10" + integrity sha512-nlP2I6ArEBewvJ2gjrrkESEZkB5mIoaTswuqNFRv/WYd+ATtUpe9Y09RnJvgvdag7he0OWgEZWhviS1OTOKixw== + +"@esbuild/linux-ppc64@0.27.2": + version "0.27.2" + resolved "https://registry.yarnpkg.com/@esbuild/linux-ppc64/-/linux-ppc64-0.27.2.tgz#3846c5df6b2016dab9bc95dde26c40f11e43b4c0" + integrity sha512-C92gnpey7tUQONqg1n6dKVbx3vphKtTHJaNG2Ok9lGwbZil6DrfyecMsp9CrmXGQJmZ7iiVXvvZH6Ml5hL6XdQ== + +"@esbuild/linux-riscv64@0.27.2": + version "0.27.2" + resolved "https://registry.yarnpkg.com/@esbuild/linux-riscv64/-/linux-riscv64-0.27.2.tgz#173d4475b37c8d2c3e1707e068c174bb3f53d07d" + integrity sha512-B5BOmojNtUyN8AXlK0QJyvjEZkWwy/FKvakkTDCziX95AowLZKR6aCDhG7LeF7uMCXEJqwa8Bejz5LTPYm8AvA== + +"@esbuild/linux-s390x@0.27.2": + version "0.27.2" + resolved "https://registry.yarnpkg.com/@esbuild/linux-s390x/-/linux-s390x-0.27.2.tgz#f7a4790105edcab8a5a31df26fbfac1aa3dacfab" + integrity sha512-p4bm9+wsPwup5Z8f4EpfN63qNagQ47Ua2znaqGH6bqLlmJ4bx97Y9JdqxgGZ6Y8xVTixUnEkoKSHcpRlDnNr5w== + +"@esbuild/linux-x64@0.27.2": + version "0.27.2" + resolved "https://registry.yarnpkg.com/@esbuild/linux-x64/-/linux-x64-0.27.2.tgz#2ecc1284b1904aeb41e54c9ddc7fcd349b18f650" + integrity sha512-uwp2Tip5aPmH+NRUwTcfLb+W32WXjpFejTIOWZFw/v7/KnpCDKG66u4DLcurQpiYTiYwQ9B7KOeMJvLCu/OvbA== + +"@esbuild/netbsd-arm64@0.27.2": + version "0.27.2" + resolved "https://registry.yarnpkg.com/@esbuild/netbsd-arm64/-/netbsd-arm64-0.27.2.tgz#e2863c2cd1501845995cb11adf26f7fe4be527b0" + integrity sha512-Kj6DiBlwXrPsCRDeRvGAUb/LNrBASrfqAIok+xB0LxK8CHqxZ037viF13ugfsIpePH93mX7xfJp97cyDuTZ3cw== + +"@esbuild/netbsd-x64@0.27.2": + version "0.27.2" + resolved "https://registry.yarnpkg.com/@esbuild/netbsd-x64/-/netbsd-x64-0.27.2.tgz#93f7609e2885d1c0b5a1417885fba8d1fcc41272" + integrity sha512-HwGDZ0VLVBY3Y+Nw0JexZy9o/nUAWq9MlV7cahpaXKW6TOzfVno3y3/M8Ga8u8Yr7GldLOov27xiCnqRZf0tCA== + +"@esbuild/openbsd-arm64@0.27.2": + version "0.27.2" + resolved "https://registry.yarnpkg.com/@esbuild/openbsd-arm64/-/openbsd-arm64-0.27.2.tgz#a1985604a203cdc325fd47542e106fafd698f02e" + integrity sha512-DNIHH2BPQ5551A7oSHD0CKbwIA/Ox7+78/AWkbS5QoRzaqlev2uFayfSxq68EkonB+IKjiuxBFoV8ESJy8bOHA== + +"@esbuild/openbsd-x64@0.27.2": + version "0.27.2" + resolved "https://registry.yarnpkg.com/@esbuild/openbsd-x64/-/openbsd-x64-0.27.2.tgz#8209e46c42f1ffbe6e4ef77a32e1f47d404ad42a" + integrity sha512-/it7w9Nb7+0KFIzjalNJVR5bOzA9Vay+yIPLVHfIQYG/j+j9VTH84aNB8ExGKPU4AzfaEvN9/V4HV+F+vo8OEg== + +"@esbuild/openharmony-arm64@0.27.2": + version "0.27.2" + resolved "https://registry.yarnpkg.com/@esbuild/openharmony-arm64/-/openharmony-arm64-0.27.2.tgz#8fade4441893d9cc44cbd7dcf3776f508ab6fb2f" + integrity sha512-LRBbCmiU51IXfeXk59csuX/aSaToeG7w48nMwA6049Y4J4+VbWALAuXcs+qcD04rHDuSCSRKdmY63sruDS5qag== + +"@esbuild/sunos-x64@0.27.2": + version "0.27.2" + resolved "https://registry.yarnpkg.com/@esbuild/sunos-x64/-/sunos-x64-0.27.2.tgz#980d4b9703a16f0f07016632424fc6d9a789dfc2" + integrity sha512-kMtx1yqJHTmqaqHPAzKCAkDaKsffmXkPHThSfRwZGyuqyIeBvf08KSsYXl+abf5HDAPMJIPnbBfXvP2ZC2TfHg== + +"@esbuild/win32-arm64@0.27.2": + version "0.27.2" + resolved "https://registry.yarnpkg.com/@esbuild/win32-arm64/-/win32-arm64-0.27.2.tgz#1c09a3633c949ead3d808ba37276883e71f6111a" + integrity sha512-Yaf78O/B3Kkh+nKABUF++bvJv5Ijoy9AN1ww904rOXZFLWVc5OLOfL56W+C8F9xn5JQZa3UX6m+IktJnIb1Jjg== + +"@esbuild/win32-ia32@0.27.2": + version "0.27.2" + resolved "https://registry.yarnpkg.com/@esbuild/win32-ia32/-/win32-ia32-0.27.2.tgz#1b1e3a63ad4bef82200fef4e369e0fff7009eee5" + integrity sha512-Iuws0kxo4yusk7sw70Xa2E2imZU5HoixzxfGCdxwBdhiDgt9vX9VUCBhqcwY7/uh//78A1hMkkROMJq9l27oLQ== + +"@esbuild/win32-x64@0.27.2": + version "0.27.2" + resolved "https://registry.yarnpkg.com/@esbuild/win32-x64/-/win32-x64-0.27.2.tgz#9e585ab6086bef994c6e8a5b3a0481219ada862b" + integrity sha512-sRdU18mcKf7F+YgheI/zGf5alZatMUTKj/jNS6l744f9u3WFu4v7twcUI9vu4mknF4Y9aDlblIie0IM+5xxaqQ== + +"@hono/node-server@1.19.6": + version "1.19.6" + resolved "https://registry.yarnpkg.com/@hono/node-server/-/node-server-1.19.6.tgz#44ec78607f37d3deb9476937049c721c7dc79390" + integrity sha512-Shz/KjlIeAhfiuE93NDKVdZ7HdBVLQAfdbaXEaoAVO3ic9ibRSLGIQGkcBbFyuLr+7/1D5ZCINM8B+6IvXeMtw== + +"@inversifyjs/common@1.5.2": + version "1.5.2" + resolved "https://registry.yarnpkg.com/@inversifyjs/common/-/common-1.5.2.tgz#6836287fef1c25dddbdf6af345a33a6d6caf6835" + integrity sha512-WlzR9xGadABS9gtgZQ+luoZ8V6qm4Ii6RQfcfC9Ho2SOlE6ZuemFo7PKJvKI0ikm8cmKbU8hw5UK6E4qovH21w== + +"@inversifyjs/container@1.15.0": + version "1.15.0" + resolved "https://registry.yarnpkg.com/@inversifyjs/container/-/container-1.15.0.tgz#e92e2a881f5300f373a855fd74aa8280dc863825" + integrity sha512-U2xYsPrJTz5za2TExi5lg8qOWf8TEVBpN+pQM7B8BVA2rajtbRE9A66SLRHk8c1eGXmg+0K4Hdki6tWAsSQBUA== + dependencies: + "@inversifyjs/common" "1.5.2" + "@inversifyjs/core" "9.2.0" + "@inversifyjs/plugin" "0.2.0" + "@inversifyjs/reflect-metadata-utils" "1.4.1" + +"@inversifyjs/core@9.2.0": + version "9.2.0" + resolved "https://registry.yarnpkg.com/@inversifyjs/core/-/core-9.2.0.tgz#f587ccb917c82698ac0f9ceb2a544af42acff36f" + integrity sha512-Nm7BR6KmpgshIHpVQWuEDehqRVb6GBm8LFEuhc2s4kSZWrArZ15RmXQzROLk4m+hkj4kMXgvMm5Qbopot/D6Sg== + dependencies: + "@inversifyjs/common" "1.5.2" + "@inversifyjs/prototype-utils" "0.1.3" + "@inversifyjs/reflect-metadata-utils" "1.4.1" + +"@inversifyjs/plugin@0.2.0": + version "0.2.0" + resolved "https://registry.yarnpkg.com/@inversifyjs/plugin/-/plugin-0.2.0.tgz#03957c7d02803f32254a2ff2f8f8ffb05ec57eec" + integrity sha512-R/JAdkTSD819pV1zi0HP54mWHyX+H2m8SxldXRgPQarS3ySV4KPyRdosWcfB8Se0JJZWZLHYiUNiS6JvMWSPjw== + +"@inversifyjs/prototype-utils@0.1.3": + version "0.1.3" + resolved "https://registry.yarnpkg.com/@inversifyjs/prototype-utils/-/prototype-utils-0.1.3.tgz#c720e7d2d847e4748534c5986b25e271186898d2" + integrity sha512-EzRamZzNgE9Sn3QtZ8NncNa2lpPMZfspqbK6BWFguWnOpK8ymp2TUuH46ruFHZhrHKnknPd7fG22ZV7iF517TQ== + dependencies: + "@inversifyjs/common" "1.5.2" + +"@inversifyjs/reflect-metadata-utils@1.4.1": + version "1.4.1" + resolved "https://registry.yarnpkg.com/@inversifyjs/reflect-metadata-utils/-/reflect-metadata-utils-1.4.1.tgz#6219df487e6aab1810a0feee4a625a0e11397340" + integrity sha512-Cp77C4d2wLaHXiUB7iH6Cxb7i1lD/YDuTIHLTDzKINqGSz0DCSoL/Dg2wVkW/6Qx03r/yQMLJ+32Agl32N2X8g== + +"@jridgewell/resolve-uri@^3.1.0": + version "3.1.2" + resolved "https://registry.yarnpkg.com/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz#7a0ee601f60f99a20c7c7c5ff0c80388c1189bd6" + integrity sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw== + +"@jridgewell/sourcemap-codec@^1.4.14", "@jridgewell/sourcemap-codec@^1.5.5": + version "1.5.5" + resolved "https://registry.yarnpkg.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz#6912b00d2c631c0d15ce1a7ab57cd657f2a8f8ba" + integrity sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og== + +"@jridgewell/trace-mapping@^0.3.31": + version "0.3.31" + resolved "https://registry.yarnpkg.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz#db15d6781c931f3a251a3dac39501c98a6082fd0" + integrity sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw== + dependencies: + "@jridgewell/resolve-uri" "^3.1.0" + "@jridgewell/sourcemap-codec" "^1.4.14" + +"@jsdevtools/ono@^7.1.3": + version "7.1.3" + resolved "https://registry.yarnpkg.com/@jsdevtools/ono/-/ono-7.1.3.tgz#9df03bbd7c696a5c58885c34aa06da41c8543796" + integrity sha512-4JQNk+3mVzK3xh2rqd6RB4J46qUR19azEHBneZyTZM+c456qOrbbM/5xcR8huNCCcbVt7+UmizG6GuUvPvKUYg== + +"@mrleebo/prisma-ast@0.12.1": + version "0.12.1" + resolved "https://registry.yarnpkg.com/@mrleebo/prisma-ast/-/prisma-ast-0.12.1.tgz#cb1cac178d98e351b6f300981abc1ffeb28f7cdd" + integrity sha512-JwqeCQ1U3fvccttHZq7Tk0m/TMC6WcFAQZdukypW3AzlJYKYTGNVd1ANU2GuhKnv4UQuOFj3oAl0LLG/gxFN1w== + dependencies: + chevrotain "^10.5.0" + lilconfig "^2.1.0" + +"@nodelib/fs.scandir@2.1.5": + version "2.1.5" + resolved "https://registry.yarnpkg.com/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz#7619c2eb21b25483f6d167548b4cfd5a7488c3d5" + integrity sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g== + dependencies: + "@nodelib/fs.stat" "2.0.5" + run-parallel "^1.1.9" + +"@nodelib/fs.stat@2.0.5", "@nodelib/fs.stat@^2.0.2": + version "2.0.5" + resolved "https://registry.yarnpkg.com/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz#5bd262af94e9d25bd1e71b05deed44876a222e8b" + integrity sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A== + +"@nodelib/fs.walk@^1.2.3": + version "1.2.8" + resolved "https://registry.yarnpkg.com/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz#e95737e8bb6746ddedf69c556953494f196fe69a" + integrity sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg== + dependencies: + "@nodelib/fs.scandir" "2.1.5" + fastq "^1.6.0" + +"@prisma/adapter-pg@^7.2.0": + version "7.2.0" + resolved "https://registry.yarnpkg.com/@prisma/adapter-pg/-/adapter-pg-7.2.0.tgz#a956ae4b6b168ed2e1c687a0b45e0d1633959bf0" + integrity sha512-euIdQ13cRB2wZ3jPsnDnFhINquo1PYFPCg6yVL8b2rp3EdinQHsX9EDdCtRr489D5uhphcRk463OdQAFlsCr0w== + dependencies: + "@prisma/driver-adapter-utils" "7.2.0" + pg "^8.16.3" + postgres-array "3.0.4" + +"@prisma/client-runtime-utils@7.2.0": + version "7.2.0" + resolved "https://registry.yarnpkg.com/@prisma/client-runtime-utils/-/client-runtime-utils-7.2.0.tgz#b17c9667d0d857988f6dadf3d6fbacf787ee3109" + integrity sha512-dn7oB53v0tqkB0wBdMuTNFNPdEbfICEUe82Tn9FoKAhJCUkDH+fmyEp0ClciGh+9Hp2Tuu2K52kth2MTLstvmA== + +"@prisma/client@^7.2.0": + version "7.2.0" + resolved "https://registry.yarnpkg.com/@prisma/client/-/client-7.2.0.tgz#75681fe23843093d3b07730aaddc654ae12fbc0e" + integrity sha512-JdLF8lWZ+LjKGKpBqyAlenxd/kXjd1Abf/xK+6vUA7R7L2Suo6AFTHFRpPSdAKCan9wzdFApsUpSa/F6+t1AtA== + dependencies: + "@prisma/client-runtime-utils" "7.2.0" + +"@prisma/config@7.2.0": + version "7.2.0" + resolved "https://registry.yarnpkg.com/@prisma/config/-/config-7.2.0.tgz#cc7b81d445df9ca75d67ea7e870844597b38eef6" + integrity sha512-qmvSnfQ6l/srBW1S7RZGfjTQhc44Yl3ldvU6y3pgmuLM+83SBDs6UQVgMtQuMRe9J3gGqB0RF8wER6RlXEr6jQ== + dependencies: + c12 "3.1.0" + deepmerge-ts "7.1.5" + effect "3.18.4" + empathic "2.0.0" + +"@prisma/debug@6.8.2": + version "6.8.2" + resolved "https://registry.yarnpkg.com/@prisma/debug/-/debug-6.8.2.tgz#59fb9e0ccb0f431fe7011c36c95f9bfcbe051749" + integrity sha512-4muBSSUwJJ9BYth5N8tqts8JtiLT8QI/RSAzEogwEfpbYGFo9mYsInsVo8dqXdPO2+Rm5OG5q0qWDDE3nyUbVg== + +"@prisma/debug@7.2.0": + version "7.2.0" + resolved "https://registry.yarnpkg.com/@prisma/debug/-/debug-7.2.0.tgz#569b1cbc10eb3e8cae798b40075fd11d21f6b533" + integrity sha512-YSGTiSlBAVJPzX4ONZmMotL+ozJwQjRmZweQNIq/ER0tQJKJynNkRB3kyvt37eOfsbMCXk3gnLF6J9OJ4QWftw== + +"@prisma/dev@0.17.0": + version "0.17.0" + resolved "https://registry.yarnpkg.com/@prisma/dev/-/dev-0.17.0.tgz#0423bc4e749d080140d316d90e08353adf52ee7a" + integrity sha512-6sGebe5jxX+FEsQTpjHLzvOGPn6ypFQprcs3jcuIWv1Xp/5v6P/rjfdvAwTkP2iF6pDx2tCd8vGLNWcsWzImTA== + dependencies: + "@electric-sql/pglite" "0.3.2" + "@electric-sql/pglite-socket" "0.0.6" + "@electric-sql/pglite-tools" "0.2.7" + "@hono/node-server" "1.19.6" + "@mrleebo/prisma-ast" "0.12.1" + "@prisma/get-platform" "6.8.2" + "@prisma/query-plan-executor" "6.18.0" + foreground-child "3.3.1" + get-port-please "3.1.2" + hono "4.10.6" + http-status-codes "2.3.0" + pathe "2.0.3" + proper-lockfile "4.1.2" + remeda "2.21.3" + std-env "3.9.0" + valibot "1.2.0" + zeptomatch "2.0.2" + +"@prisma/driver-adapter-utils@7.2.0": + version "7.2.0" + resolved "https://registry.yarnpkg.com/@prisma/driver-adapter-utils/-/driver-adapter-utils-7.2.0.tgz#53d6686cdbb695338826e1c8ab5ac97c92efb676" + integrity sha512-gzrUcbI9VmHS24Uf+0+7DNzdIw7keglJsD5m/MHxQOU68OhGVzlphQRobLiDMn8CHNA2XN8uugwKjudVtnfMVQ== + dependencies: + "@prisma/debug" "7.2.0" + +"@prisma/engines-version@7.2.0-4.0c8ef2ce45c83248ab3df073180d5eda9e8be7a3": + version "7.2.0-4.0c8ef2ce45c83248ab3df073180d5eda9e8be7a3" + resolved "https://registry.yarnpkg.com/@prisma/engines-version/-/engines-version-7.2.0-4.0c8ef2ce45c83248ab3df073180d5eda9e8be7a3.tgz#37104679cfa7cc5f5434bf02fa22b4b0cf90b94f" + integrity sha512-KezsjCZDsbjNR7SzIiVlUsn9PnLePI7r5uxABlwL+xoerurZTfgQVbIjvjF2sVr3Uc0ZcsnREw3F84HvbggGdA== + +"@prisma/engines@7.2.0": + version "7.2.0" + resolved "https://registry.yarnpkg.com/@prisma/engines/-/engines-7.2.0.tgz#7451f22ad7526c8f84b7dce889d796aeffd332be" + integrity sha512-HUeOI/SvCDsHrR9QZn24cxxZcujOjcS3w1oW/XVhnSATAli5SRMOfp/WkG3TtT5rCxDA4xOnlJkW7xkho4nURA== + dependencies: + "@prisma/debug" "7.2.0" + "@prisma/engines-version" "7.2.0-4.0c8ef2ce45c83248ab3df073180d5eda9e8be7a3" + "@prisma/fetch-engine" "7.2.0" + "@prisma/get-platform" "7.2.0" + +"@prisma/fetch-engine@7.2.0": + version "7.2.0" + resolved "https://registry.yarnpkg.com/@prisma/fetch-engine/-/fetch-engine-7.2.0.tgz#956d8aff4420534dffc9c7e034243f711803e966" + integrity sha512-Z5XZztJ8Ap+wovpjPD2lQKnB8nWFGNouCrglaNFjxIWAGWz0oeHXwUJRiclIoSSXN/ptcs9/behptSk8d0Yy6w== + dependencies: + "@prisma/debug" "7.2.0" + "@prisma/engines-version" "7.2.0-4.0c8ef2ce45c83248ab3df073180d5eda9e8be7a3" + "@prisma/get-platform" "7.2.0" + +"@prisma/get-platform@6.8.2": + version "6.8.2" + resolved "https://registry.yarnpkg.com/@prisma/get-platform/-/get-platform-6.8.2.tgz#a6509de61ceab4fca80616b7e8d73705b2705a72" + integrity sha512-vXSxyUgX3vm1Q70QwzwkjeYfRryIvKno1SXbIqwSptKwqKzskINnDUcx85oX+ys6ooN2ATGSD0xN2UTfg6Zcow== + dependencies: + "@prisma/debug" "6.8.2" + +"@prisma/get-platform@7.2.0": + version "7.2.0" + resolved "https://registry.yarnpkg.com/@prisma/get-platform/-/get-platform-7.2.0.tgz#b3a92db68de6a76e840e61d2f26659aa9f915e3e" + integrity sha512-k1V0l0Td1732EHpAfi2eySTezyllok9dXb6UQanajkJQzPUGi3vO2z7jdkz67SypFTdmbnyGYxvEvYZdZsMAVA== + dependencies: + "@prisma/debug" "7.2.0" + +"@prisma/query-plan-executor@6.18.0": + version "6.18.0" + resolved "https://registry.yarnpkg.com/@prisma/query-plan-executor/-/query-plan-executor-6.18.0.tgz#231304e8b51959cb671ce8a6038f7e0e5c24bd9d" + integrity sha512-jZ8cfzFgL0jReE1R10gT8JLHtQxjWYLiQ//wHmVYZ2rVkFHoh0DT8IXsxcKcFlfKN7ak7k6j0XMNn2xVNyr5cA== + +"@prisma/studio-core@0.9.0": + version "0.9.0" + resolved "https://registry.yarnpkg.com/@prisma/studio-core/-/studio-core-0.9.0.tgz#aad8b49467adeb4100d1070a0940dc3ffce8d0ef" + integrity sha512-xA2zoR/ADu/NCSQuriBKTh6Ps4XjU0bErkEcgMfnSGh346K1VI7iWKnoq1l2DoxUqiddPHIEWwtxJ6xCHG6W7g== + +"@rollup/rollup-android-arm-eabi@4.55.1": + version "4.55.1" + resolved "https://registry.yarnpkg.com/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.55.1.tgz#76e0fef6533b3ce313f969879e61e8f21f0eeb28" + integrity sha512-9R0DM/ykwfGIlNu6+2U09ga0WXeZ9MRC2Ter8jnz8415VbuIykVuc6bhdrbORFZANDmTDvq26mJrEVTl8TdnDg== + +"@rollup/rollup-android-arm64@4.55.1": + version "4.55.1" + resolved "https://registry.yarnpkg.com/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.55.1.tgz#d3cfc675a40bbdec97bda6d7fe3b3b05f0e1cd93" + integrity sha512-eFZCb1YUqhTysgW3sj/55du5cG57S7UTNtdMjCW7LwVcj3dTTcowCsC8p7uBdzKsZYa8J7IDE8lhMI+HX1vQvg== + +"@rollup/rollup-darwin-arm64@4.55.1": + version "4.55.1" + resolved "https://registry.yarnpkg.com/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.55.1.tgz#eb912b8f59dd47c77b3c50a78489013b1d6772b4" + integrity sha512-p3grE2PHcQm2e8PSGZdzIhCKbMCw/xi9XvMPErPhwO17vxtvCN5FEA2mSLgmKlCjHGMQTP6phuQTYWUnKewwGg== + +"@rollup/rollup-darwin-x64@4.55.1": + version "4.55.1" + resolved "https://registry.yarnpkg.com/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.55.1.tgz#e7d0839fdfd1276a1d34bc5ebbbd0dfd7d0b81a0" + integrity sha512-rDUjG25C9qoTm+e02Esi+aqTKSBYwVTaoS1wxcN47/Luqef57Vgp96xNANwt5npq9GDxsH7kXxNkJVEsWEOEaQ== + +"@rollup/rollup-freebsd-arm64@4.55.1": + version "4.55.1" + resolved "https://registry.yarnpkg.com/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.55.1.tgz#7ff8118760f7351e48fd0cd3717ff80543d6aac8" + integrity sha512-+JiU7Jbp5cdxekIgdte0jfcu5oqw4GCKr6i3PJTlXTCU5H5Fvtkpbs4XJHRmWNXF+hKmn4v7ogI5OQPaupJgOg== + +"@rollup/rollup-freebsd-x64@4.55.1": + version "4.55.1" + resolved "https://registry.yarnpkg.com/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.55.1.tgz#49d330dadbda1d4e9b86b4a3951b59928a9489a9" + integrity sha512-V5xC1tOVWtLLmr3YUk2f6EJK4qksksOYiz/TCsFHu/R+woubcLWdC9nZQmwjOAbmExBIVKsm1/wKmEy4z4u4Bw== + +"@rollup/rollup-linux-arm-gnueabihf@4.55.1": + version "4.55.1" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.55.1.tgz#98c5f1f8b9776b4a36e466e2a1c9ed1ba52ef1b6" + integrity sha512-Rn3n+FUk2J5VWx+ywrG/HGPTD9jXNbicRtTM11e/uorplArnXZYsVifnPPqNNP5BsO3roI4n8332ukpY/zN7rQ== + +"@rollup/rollup-linux-arm-musleabihf@4.55.1": + version "4.55.1" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.55.1.tgz#b9acecd3672e742f70b0c8a94075c816a91ff040" + integrity sha512-grPNWydeKtc1aEdrJDWk4opD7nFtQbMmV7769hiAaYyUKCT1faPRm2av8CX1YJsZ4TLAZcg9gTR1KvEzoLjXkg== + +"@rollup/rollup-linux-arm64-gnu@4.55.1": + version "4.55.1" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.55.1.tgz#7a6ab06651bc29e18b09a50ed1a02bc972977c9b" + integrity sha512-a59mwd1k6x8tXKcUxSyISiquLwB5pX+fJW9TkWU46lCqD/GRDe9uDN31jrMmVP3feI3mhAdvcCClhV8V5MhJFQ== + +"@rollup/rollup-linux-arm64-musl@4.55.1": + version "4.55.1" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.55.1.tgz#3c8c9072ba4a4d4ef1156b85ab9a2cbb57c1fad0" + integrity sha512-puS1MEgWX5GsHSoiAsF0TYrpomdvkaXm0CofIMG5uVkP6IBV+ZO9xhC5YEN49nsgYo1DuuMquF9+7EDBVYu4uA== + +"@rollup/rollup-linux-loong64-gnu@4.55.1": + version "4.55.1" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.55.1.tgz#17a7af13530f4e4a7b12cd26276c54307a84a8b0" + integrity sha512-r3Wv40in+lTsULSb6nnoudVbARdOwb2u5fpeoOAZjFLznp6tDU8kd+GTHmJoqZ9lt6/Sys33KdIHUaQihFcu7g== + +"@rollup/rollup-linux-loong64-musl@4.55.1": + version "4.55.1" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.55.1.tgz#5cd7a900fd7b077ecd753e34a9b7ff1157fe70c1" + integrity sha512-MR8c0+UxAlB22Fq4R+aQSPBayvYa3+9DrwG/i1TKQXFYEaoW3B5b/rkSRIypcZDdWjWnpcvxbNaAJDcSbJU3Lw== + +"@rollup/rollup-linux-ppc64-gnu@4.55.1": + version "4.55.1" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.55.1.tgz#03a097e70243ddf1c07b59d3c20f38e6f6800539" + integrity sha512-3KhoECe1BRlSYpMTeVrD4sh2Pw2xgt4jzNSZIIPLFEsnQn9gAnZagW9+VqDqAHgm1Xc77LzJOo2LdigS5qZ+gw== + +"@rollup/rollup-linux-ppc64-musl@4.55.1": + version "4.55.1" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.55.1.tgz#a5389873039d4650f35b4fa060d286392eb21a94" + integrity sha512-ziR1OuZx0vdYZZ30vueNZTg73alF59DicYrPViG0NEgDVN8/Jl87zkAPu4u6VjZST2llgEUjaiNl9JM6HH1Vdw== + +"@rollup/rollup-linux-riscv64-gnu@4.55.1": + version "4.55.1" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.55.1.tgz#789e60e7d6e2b76132d001ffb24ba80007fb17d0" + integrity sha512-uW0Y12ih2XJRERZ4jAfKamTyIHVMPQnTZcQjme2HMVDAHY4amf5u414OqNYC+x+LzRdRcnIG1YodLrrtA8xsxw== + +"@rollup/rollup-linux-riscv64-musl@4.55.1": + version "4.55.1" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.55.1.tgz#3556fa88d139282e9a73c337c9a170f3c5fe7aa4" + integrity sha512-u9yZ0jUkOED1BFrqu3BwMQoixvGHGZ+JhJNkNKY/hyoEgOwlqKb62qu+7UjbPSHYjiVy8kKJHvXKv5coH4wDeg== + +"@rollup/rollup-linux-s390x-gnu@4.55.1": + version "4.55.1" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.55.1.tgz#c085995b10143c16747a67f1a5487512b2ff04b2" + integrity sha512-/0PenBCmqM4ZUd0190j7J0UsQ/1nsi735iPRakO8iPciE7BQ495Y6msPzaOmvx0/pn+eJVVlZrNrSh4WSYLxNg== + +"@rollup/rollup-linux-x64-gnu@4.55.1": + version "4.55.1" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.55.1.tgz#9563a5419dd2604841bad31a39ccfdd2891690fb" + integrity sha512-a8G4wiQxQG2BAvo+gU6XrReRRqj+pLS2NGXKm8io19goR+K8lw269eTrPkSdDTALwMmJp4th2Uh0D8J9bEV1vg== + +"@rollup/rollup-linux-x64-musl@4.55.1": + version "4.55.1" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.55.1.tgz#691bb06e6269a8959c13476b0cd2aa7458facb31" + integrity sha512-bD+zjpFrMpP/hqkfEcnjXWHMw5BIghGisOKPj+2NaNDuVT+8Ds4mPf3XcPHuat1tz89WRL+1wbcxKY3WSbiT7w== + +"@rollup/rollup-openbsd-x64@4.55.1": + version "4.55.1" + resolved "https://registry.yarnpkg.com/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.55.1.tgz#223e71224746a59ce6d955bbc403577bb5a8be9d" + integrity sha512-eLXw0dOiqE4QmvikfQ6yjgkg/xDM+MdU9YJuP4ySTibXU0oAvnEWXt7UDJmD4UkYialMfOGFPJnIHSe/kdzPxg== + +"@rollup/rollup-openharmony-arm64@4.55.1": + version "4.55.1" + resolved "https://registry.yarnpkg.com/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.55.1.tgz#0817e5d8ecbfeb8b7939bf58f8ce3c9dd67fce77" + integrity sha512-xzm44KgEP11te3S2HCSyYf5zIzWmx3n8HDCc7EE59+lTcswEWNpvMLfd9uJvVX8LCg9QWG67Xt75AuHn4vgsXw== + +"@rollup/rollup-win32-arm64-msvc@4.55.1": + version "4.55.1" + resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.55.1.tgz#de56d8f2013c84570ef5fb917aae034abda93e4a" + integrity sha512-yR6Bl3tMC/gBok5cz/Qi0xYnVbIxGx5Fcf/ca0eB6/6JwOY+SRUcJfI0OpeTpPls7f194as62thCt/2BjxYN8g== + +"@rollup/rollup-win32-ia32-msvc@4.55.1": + version "4.55.1" + resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.55.1.tgz#659aff5244312475aeea2c9479a6c7d397b517bf" + integrity sha512-3fZBidchE0eY0oFZBnekYCfg+5wAB0mbpCBuofh5mZuzIU/4jIVkbESmd2dOsFNS78b53CYv3OAtwqkZZmU5nA== + +"@rollup/rollup-win32-x64-gnu@4.55.1": + version "4.55.1" + resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.55.1.tgz#2cb09549cbb66c1b979f9238db6dd454cac14a88" + integrity sha512-xGGY5pXj69IxKb4yv/POoocPy/qmEGhimy/FoTpTSVju3FYXUQQMFCaZZXJVidsmGxRioZAwpThl/4zX41gRKg== + +"@rollup/rollup-win32-x64-msvc@4.55.1": + version "4.55.1" + resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.55.1.tgz#f79437939020b83057faf07e98365b1fa51c458b" + integrity sha512-SPEpaL6DX4rmcXtnhdrQYgzQ5W2uW3SCJch88lB2zImhJRhIIK44fkUrgIV/Q8yUNfw5oyZ5vkeQsZLhCb06lw== + +"@scarf/scarf@=1.4.0": + version "1.4.0" + resolved "https://registry.yarnpkg.com/@scarf/scarf/-/scarf-1.4.0.tgz#3bbb984085dbd6d982494538b523be1ce6562972" + integrity sha512-xxeapPiUXdZAE3che6f3xogoJPeZgig6omHEy1rIY5WVsB3H2BHNnZH+gHG6x91SCWyQCzWGsuL2Hh3ClO5/qQ== + +"@standard-schema/spec@^1.0.0": + version "1.1.0" + resolved "https://registry.yarnpkg.com/@standard-schema/spec/-/spec-1.1.0.tgz#a79b55dbaf8604812f52d140b2c9ab41bc150bb8" + integrity sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w== + +"@types/bcrypt@^6.0.0": + version "6.0.0" + resolved "https://registry.yarnpkg.com/@types/bcrypt/-/bcrypt-6.0.0.tgz#0d20587924663607fb59ae373d3d6fbc7b339a92" + integrity sha512-/oJGukuH3D2+D+3H4JWLaAsJ/ji86dhRidzZ/Od7H/i8g+aCmvkeCc6Ni/f9uxGLSQVCRZkX2/lqEFG2BvWtlQ== + dependencies: + "@types/node" "*" + +"@types/body-parser@*": + version "1.19.6" + resolved "https://registry.yarnpkg.com/@types/body-parser/-/body-parser-1.19.6.tgz#1859bebb8fd7dac9918a45d54c1971ab8b5af474" + integrity sha512-HLFeCYgz89uk22N5Qg3dvGvsv46B8GLvKKo1zKG4NybA8U2DiEO3w9lqGg29t/tfLRJpJ6iQxnVw4OnB7MoM9g== + dependencies: + "@types/connect" "*" + "@types/node" "*" + +"@types/chai@^5.2.2": + version "5.2.3" + resolved "https://registry.yarnpkg.com/@types/chai/-/chai-5.2.3.tgz#8e9cd9e1c3581fa6b341a5aed5588eb285be0b4a" + integrity sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA== + dependencies: + "@types/deep-eql" "*" + assertion-error "^2.0.1" + +"@types/connect@*": + version "3.4.38" + resolved "https://registry.yarnpkg.com/@types/connect/-/connect-3.4.38.tgz#5ba7f3bc4fbbdeaff8dded952e5ff2cc53f8d858" + integrity sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug== + dependencies: + "@types/node" "*" + +"@types/cookie-parser@^1.4.10": + version "1.4.10" + resolved "https://registry.yarnpkg.com/@types/cookie-parser/-/cookie-parser-1.4.10.tgz#a045272a383a30597a01955d4f9c790018f214e4" + integrity sha512-B4xqkqfZ8Wek+rCOeRxsjMS9OgvzebEzzLYw7NHYuvzb7IdxOkI0ZHGgeEBX4PUM7QGVvNSK60T3OvWj3YfBRg== + +"@types/deep-eql@*": + version "4.0.2" + resolved "https://registry.yarnpkg.com/@types/deep-eql/-/deep-eql-4.0.2.tgz#334311971d3a07121e7eb91b684a605e7eea9cbd" + integrity sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw== + +"@types/estree@1.0.8", "@types/estree@^1.0.0": + version "1.0.8" + resolved "https://registry.yarnpkg.com/@types/estree/-/estree-1.0.8.tgz#958b91c991b1867ced318bedea0e215ee050726e" + integrity sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w== + +"@types/express-serve-static-core@^5.0.0": + version "5.1.1" + resolved "https://registry.yarnpkg.com/@types/express-serve-static-core/-/express-serve-static-core-5.1.1.tgz#1a77faffee9572d39124933259be2523837d7eaa" + integrity sha512-v4zIMr/cX7/d2BpAEX3KNKL/JrT1s43s96lLvvdTmza1oEvDudCqK9aF/djc/SWgy8Yh0h30TZx5VpzqFCxk5A== + dependencies: + "@types/node" "*" + "@types/qs" "*" + "@types/range-parser" "*" + "@types/send" "*" + +"@types/express@*", "@types/express@^5.0.6": + version "5.0.6" + resolved "https://registry.yarnpkg.com/@types/express/-/express-5.0.6.tgz#2d724b2c990dcb8c8444063f3580a903f6d500cc" + integrity sha512-sKYVuV7Sv9fbPIt/442koC7+IIwK5olP1KWeD88e/idgoJqDm3JV/YUiPwkoKK92ylff2MGxSz1CSjsXelx0YA== + dependencies: + "@types/body-parser" "*" + "@types/express-serve-static-core" "^5.0.0" + "@types/serve-static" "^2" + +"@types/http-errors@*": + version "2.0.5" + resolved "https://registry.yarnpkg.com/@types/http-errors/-/http-errors-2.0.5.tgz#5b749ab2b16ba113423feb1a64a95dcd30398472" + integrity sha512-r8Tayk8HJnX0FztbZN7oVqGccWgw98T/0neJphO91KkmOzug1KkofZURD4UaD5uH8AqcFLfdPErnBod0u71/qg== + +"@types/json-schema@^7.0.6": + version "7.0.15" + resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.15.tgz#596a1747233694d50f6ad8a7869fcb6f56cf5841" + integrity sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA== + +"@types/jsonwebtoken@^9.0.10": + version "9.0.10" + resolved "https://registry.yarnpkg.com/@types/jsonwebtoken/-/jsonwebtoken-9.0.10.tgz#a7932a47177dcd4283b6146f3bd5c26d82647f09" + integrity sha512-asx5hIG9Qmf/1oStypjanR7iKTv0gXQ1Ov/jfrX6kS/EO0OFni8orbmGCn0672NHR3kXHwpAwR+B368ZGN/2rA== + dependencies: + "@types/ms" "*" + "@types/node" "*" + +"@types/ms@*": + version "2.1.0" + resolved "https://registry.yarnpkg.com/@types/ms/-/ms-2.1.0.tgz#052aa67a48eccc4309d7f0191b7e41434b90bb78" + integrity sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA== + +"@types/node@*": + version "25.0.5" + resolved "https://registry.yarnpkg.com/@types/node/-/node-25.0.5.tgz#7ee6356607b9c93b9d25bf5c5e8f8a8ed6240877" + integrity sha512-FuLxeLuSVOqHPxSN1fkcD8DLU21gAP7nCKqGRJ/FglbCUBs0NYN6TpHcdmyLeh8C0KwGIaZQJSv+OYG+KZz+Gw== + dependencies: + undici-types "~7.16.0" + +"@types/node@^25.0.8": + version "25.0.8" + resolved "https://registry.yarnpkg.com/@types/node/-/node-25.0.8.tgz#e54e00f94fe1db2497b3e42d292b8376a2678c8d" + integrity sha512-powIePYMmC3ibL0UJ2i2s0WIbq6cg6UyVFQxSCpaPxxzAaziRfimGivjdF943sSGV6RADVbk0Nvlm5P/FB44Zg== + dependencies: + undici-types "~7.16.0" + +"@types/pg@^8.16.0": + version "8.16.0" + resolved "https://registry.yarnpkg.com/@types/pg/-/pg-8.16.0.tgz#b7af0d642752340b7c9de1c33afd9bc5c5f0ebeb" + integrity sha512-RmhMd/wD+CF8Dfo+cVIy3RR5cl8CyfXQ0tGgW6XBL8L4LM/UTEbNXYRbLwU6w+CgrKBNbrQWt4FUtTfaU5jSYQ== + dependencies: + "@types/node" "*" + pg-protocol "*" + pg-types "^2.2.0" + +"@types/qs@*": + version "6.14.0" + resolved "https://registry.yarnpkg.com/@types/qs/-/qs-6.14.0.tgz#d8b60cecf62f2db0fb68e5e006077b9178b85de5" + integrity sha512-eOunJqu0K1923aExK6y8p6fsihYEn/BYuQ4g0CxAAgFc4b/ZLN4CrsRZ55srTdqoiLzU2B2evC+apEIxprEzkQ== + +"@types/range-parser@*": + version "1.2.7" + resolved "https://registry.yarnpkg.com/@types/range-parser/-/range-parser-1.2.7.tgz#50ae4353eaaddc04044279812f52c8c65857dbcb" + integrity sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ== + +"@types/send@*": + version "1.2.1" + resolved "https://registry.yarnpkg.com/@types/send/-/send-1.2.1.tgz#6a784e45543c18c774c049bff6d3dbaf045c9c74" + integrity sha512-arsCikDvlU99zl1g69TcAB3mzZPpxgw0UQnaHeC1Nwb015xp8bknZv5rIfri9xTOcMuaVgvabfIRA7PSZVuZIQ== + dependencies: + "@types/node" "*" + +"@types/serve-static@*", "@types/serve-static@^2": + version "2.2.0" + resolved "https://registry.yarnpkg.com/@types/serve-static/-/serve-static-2.2.0.tgz#d4a447503ead0d1671132d1ab6bd58b805d8de6a" + integrity sha512-8mam4H1NHLtu7nmtalF7eyBH14QyOASmcxHhSfEoRyr0nP/YdoesEtU+uSRvMe96TW/HPTtkoKqQLl53N7UXMQ== + dependencies: + "@types/http-errors" "*" + "@types/node" "*" + +"@types/swagger-jsdoc@^6.0.4": + version "6.0.4" + resolved "https://registry.yarnpkg.com/@types/swagger-jsdoc/-/swagger-jsdoc-6.0.4.tgz#bb4f60f3a5f103818e022f2e29ff8935113fb83d" + integrity sha512-W+Xw5epcOZrF/AooUM/PccNMSAFOKWZA5dasNyMujTwsBkU74njSJBpvCCJhHAJ95XRMzQrrW844Btu0uoetwQ== + +"@types/swagger-ui-express@^4.1.8": + version "4.1.8" + resolved "https://registry.yarnpkg.com/@types/swagger-ui-express/-/swagger-ui-express-4.1.8.tgz#3c0e0bf2543c7efb500eaa081bfde6d92f88096c" + integrity sha512-AhZV8/EIreHFmBV5wAs0gzJUNq9JbbSXgJLQubCC0jtIo6prnI9MIRRxnU4MZX9RB9yXxF1V4R7jtLl/Wcj31g== + dependencies: + "@types/express" "*" + "@types/serve-static" "*" + +"@vitest/coverage-v8@^4.0.17": + version "4.0.17" + resolved "https://registry.yarnpkg.com/@vitest/coverage-v8/-/coverage-v8-4.0.17.tgz#3bb100e9a6766de282049fba28e21a010a73509a" + integrity sha512-/6zU2FLGg0jsd+ePZcwHRy3+WpNTBBhDY56P4JTRqUN/Dp6CvOEa9HrikcQ4KfV2b2kAHUFB4dl1SuocWXSFEw== + dependencies: + "@bcoe/v8-coverage" "^1.0.2" + "@vitest/utils" "4.0.17" + ast-v8-to-istanbul "^0.3.10" + istanbul-lib-coverage "^3.2.2" + istanbul-lib-report "^3.0.1" + istanbul-reports "^3.2.0" + magicast "^0.5.1" + obug "^2.1.1" + std-env "^3.10.0" + tinyrainbow "^3.0.3" + +"@vitest/expect@4.0.17": + version "4.0.17" + resolved "https://registry.yarnpkg.com/@vitest/expect/-/expect-4.0.17.tgz#67bb0d4a7d37054590a19dcf831f7936d14a8a02" + integrity sha512-mEoqP3RqhKlbmUmntNDDCJeTDavDR+fVYkSOw8qRwJFaW/0/5zA9zFeTrHqNtcmwh6j26yMmwx2PqUDPzt5ZAQ== + dependencies: + "@standard-schema/spec" "^1.0.0" + "@types/chai" "^5.2.2" + "@vitest/spy" "4.0.17" + "@vitest/utils" "4.0.17" + chai "^6.2.1" + tinyrainbow "^3.0.3" + +"@vitest/mocker@4.0.17": + version "4.0.17" + resolved "https://registry.yarnpkg.com/@vitest/mocker/-/mocker-4.0.17.tgz#ce559098be1ae18ae5aa441a79939bcd7fc8f42f" + integrity sha512-+ZtQhLA3lDh1tI2wxe3yMsGzbp7uuJSWBM1iTIKCbppWTSBN09PUC+L+fyNlQApQoR+Ps8twt2pbSSXg2fQVEQ== + dependencies: + "@vitest/spy" "4.0.17" + estree-walker "^3.0.3" + magic-string "^0.30.21" + +"@vitest/pretty-format@4.0.17": + version "4.0.17" + resolved "https://registry.yarnpkg.com/@vitest/pretty-format/-/pretty-format-4.0.17.tgz#dde7cb2c01699d0943571137d1b482edff5fc000" + integrity sha512-Ah3VAYmjcEdHg6+MwFE17qyLqBHZ+ni2ScKCiW2XrlSBV4H3Z7vYfPfz7CWQ33gyu76oc0Ai36+kgLU3rfF4nw== + dependencies: + tinyrainbow "^3.0.3" + +"@vitest/runner@4.0.17": + version "4.0.17" + resolved "https://registry.yarnpkg.com/@vitest/runner/-/runner-4.0.17.tgz#dcc3bb4a4b1077858f3b105e91b2eeb208cee780" + integrity sha512-JmuQyf8aMWoo/LmNFppdpkfRVHJcsgzkbCA+/Bk7VfNH7RE6Ut2qxegeyx2j3ojtJtKIbIGy3h+KxGfYfk28YQ== + dependencies: + "@vitest/utils" "4.0.17" + pathe "^2.0.3" + +"@vitest/snapshot@4.0.17": + version "4.0.17" + resolved "https://registry.yarnpkg.com/@vitest/snapshot/-/snapshot-4.0.17.tgz#40d71a3dad4ac39812ed96a95fded46a920e1a31" + integrity sha512-npPelD7oyL+YQM2gbIYvlavlMVWUfNNGZPcu0aEUQXt7FXTuqhmgiYupPnAanhKvyP6Srs2pIbWo30K0RbDtRQ== + dependencies: + "@vitest/pretty-format" "4.0.17" + magic-string "^0.30.21" + pathe "^2.0.3" + +"@vitest/spy@4.0.17": + version "4.0.17" + resolved "https://registry.yarnpkg.com/@vitest/spy/-/spy-4.0.17.tgz#d0936f8908b4dae091d9b948be3bae8e19d1878d" + integrity sha512-I1bQo8QaP6tZlTomQNWKJE6ym4SHf3oLS7ceNjozxxgzavRAgZDc06T7kD8gb9bXKEgcLNt00Z+kZO6KaJ62Ew== + +"@vitest/utils@4.0.17": + version "4.0.17" + resolved "https://registry.yarnpkg.com/@vitest/utils/-/utils-4.0.17.tgz#48181deab273c87ac4ee20c1c454ffe9c4f453fe" + integrity sha512-RG6iy+IzQpa9SB8HAFHJ9Y+pTzI+h8553MrciN9eC6TFBErqrQaTas4vG+MVj8S4uKk8uTT2p0vgZPnTdxd96w== + dependencies: + "@vitest/pretty-format" "4.0.17" + tinyrainbow "^3.0.3" + +accepts@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/accepts/-/accepts-2.0.0.tgz#bbcf4ba5075467f3f2131eab3cffc73c2f5d7895" + integrity sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng== + dependencies: + mime-types "^3.0.0" + negotiator "^1.0.0" + +anymatch@~3.1.2: + version "3.1.3" + resolved "https://registry.yarnpkg.com/anymatch/-/anymatch-3.1.3.tgz#790c58b19ba1720a84205b57c618d5ad8524973e" + integrity sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw== + dependencies: + normalize-path "^3.0.0" + picomatch "^2.0.4" + +argparse@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/argparse/-/argparse-2.0.1.tgz#246f50f3ca78a3240f6c997e8a9bd1eac49e4b38" + integrity sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q== + +array-union@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/array-union/-/array-union-2.1.0.tgz#b798420adbeb1de828d84acd8a2e23d3efe85e8d" + integrity sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw== + +assertion-error@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/assertion-error/-/assertion-error-2.0.1.tgz#f641a196b335690b1070bf00b6e7593fec190bf7" + integrity sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA== + +ast-v8-to-istanbul@^0.3.10: + version "0.3.10" + resolved "https://registry.yarnpkg.com/ast-v8-to-istanbul/-/ast-v8-to-istanbul-0.3.10.tgz#ceff0094c8c64b9e04393c2377fd61857429ec04" + integrity sha512-p4K7vMz2ZSk3wN8l5o3y2bJAoZXT3VuJI5OLTATY/01CYWumWvwkUw0SqDBnNq6IiTO3qDa1eSQDibAV8g7XOQ== + dependencies: + "@jridgewell/trace-mapping" "^0.3.31" + estree-walker "^3.0.3" + js-tokens "^9.0.1" + +aws-ssl-profiles@^1.1.1: + version "1.1.2" + resolved "https://registry.yarnpkg.com/aws-ssl-profiles/-/aws-ssl-profiles-1.1.2.tgz#157dd77e9f19b1d123678e93f120e6f193022641" + integrity sha512-NZKeq9AfyQvEeNlN0zSYAaWrmBffJh3IELMZfRpJVWgrpEbtEpnjvzqBPf+mxoI287JohRDoa+/nsfqqiZmF6g== + +balanced-match@^1.0.0: + version "1.0.2" + resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.2.tgz#e83e3a7e3f300b34cb9d87f615fa0cbf357690ee" + integrity sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw== + +bcrypt@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/bcrypt/-/bcrypt-6.0.0.tgz#86643fddde9bcd0ad91400b063003fa4b0312835" + integrity sha512-cU8v/EGSrnH+HnxV2z0J7/blxH8gq7Xh2JFT6Aroax7UohdmiJJlxApMxtKfuI7z68NvvVcmR78k2LbT6efhRg== + dependencies: + node-addon-api "^8.3.0" + node-gyp-build "^4.8.4" + +binary-extensions@^2.0.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-2.3.0.tgz#f6e14a97858d327252200242d4ccfe522c445522" + integrity sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw== + +body-parser@^2.2.1: + version "2.2.2" + resolved "https://registry.yarnpkg.com/body-parser/-/body-parser-2.2.2.tgz#1a32cdb966beaf68de50a9dfbe5b58f83cb8890c" + integrity sha512-oP5VkATKlNwcgvxi0vM0p/D3n2C3EReYVX+DNYs5TjZFn/oQt2j+4sVJtSMr18pdRr8wjTcBl6LoV+FUwzPmNA== + dependencies: + bytes "^3.1.2" + content-type "^1.0.5" + debug "^4.4.3" + http-errors "^2.0.0" + iconv-lite "^0.7.0" + on-finished "^2.4.1" + qs "^6.14.1" + raw-body "^3.0.1" + type-is "^2.0.1" + +brace-expansion@^1.1.7: + version "1.1.12" + resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.12.tgz#ab9b454466e5a8cc3a187beaad580412a9c5b843" + integrity sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg== + dependencies: + balanced-match "^1.0.0" + concat-map "0.0.1" + +braces@^3.0.3, braces@~3.0.2: + version "3.0.3" + resolved "https://registry.yarnpkg.com/braces/-/braces-3.0.3.tgz#490332f40919452272d55a8480adc0c441358789" + integrity sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA== + dependencies: + fill-range "^7.1.1" + +buffer-equal-constant-time@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz#f8e71132f7ffe6e01a5c9697a4c6f3e48d5cc819" + integrity sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA== + +bytes@^3.1.2, bytes@~3.1.2: + version "3.1.2" + resolved "https://registry.yarnpkg.com/bytes/-/bytes-3.1.2.tgz#8b0beeb98605adf1b128fa4386403c009e0221a5" + integrity sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg== + +c12@3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/c12/-/c12-3.1.0.tgz#9e237970e1d3b74ebae51d25945cb59664c12c89" + integrity sha512-uWoS8OU1MEIsOv8p/5a82c3H31LsWVR5qiyXVfBNOzfffjUWtPnhAb4BYI2uG2HfGmZmFjCtui5XNWaps+iFuw== + dependencies: + chokidar "^4.0.3" + confbox "^0.2.2" + defu "^6.1.4" + dotenv "^16.6.1" + exsolve "^1.0.7" + giget "^2.0.0" + jiti "^2.4.2" + ohash "^2.0.11" + pathe "^2.0.3" + perfect-debounce "^1.0.0" + pkg-types "^2.2.0" + rc9 "^2.1.2" + +call-bind-apply-helpers@^1.0.1, call-bind-apply-helpers@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz#4b5428c222be985d79c3d82657479dbe0b59b2d6" + integrity sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ== + dependencies: + es-errors "^1.3.0" + function-bind "^1.1.2" + +call-bound@^1.0.2: + version "1.0.4" + resolved "https://registry.yarnpkg.com/call-bound/-/call-bound-1.0.4.tgz#238de935d2a2a692928c538c7ccfa91067fd062a" + integrity sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg== + dependencies: + call-bind-apply-helpers "^1.0.2" + get-intrinsic "^1.3.0" + +call-me-maybe@^1.0.1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/call-me-maybe/-/call-me-maybe-1.0.2.tgz#03f964f19522ba643b1b0693acb9152fe2074baa" + integrity sha512-HpX65o1Hnr9HH25ojC1YGs7HCQLq0GCOibSaWER0eNpgJ/Z1MZv2mTc7+xh6WOPxbRVcmgbv4hGU+uSQ/2xFZQ== + +chai@^6.2.1: + version "6.2.2" + resolved "https://registry.yarnpkg.com/chai/-/chai-6.2.2.tgz#ae41b52c9aca87734505362717f3255facda360e" + integrity sha512-NUPRluOfOiTKBKvWPtSD4PhFvWCqOi0BGStNWs57X9js7XGTprSmFoz5F0tWhR4WPjNeR9jXqdC7/UpSJTnlRg== + +chevrotain@^10.5.0: + version "10.5.0" + resolved "https://registry.yarnpkg.com/chevrotain/-/chevrotain-10.5.0.tgz#9c1dc62ef0753bb562dbe521b5f72d041bad624e" + integrity sha512-Pkv5rBY3+CsHOYfV5g/Vs5JY9WTHHDEKOlohI2XeygaZhUeqhAlldZ8Hz9cRmxu709bvS08YzxHdTPHhffc13A== + dependencies: + "@chevrotain/cst-dts-gen" "10.5.0" + "@chevrotain/gast" "10.5.0" + "@chevrotain/types" "10.5.0" + "@chevrotain/utils" "10.5.0" + lodash "4.17.21" + regexp-to-ast "0.5.0" + +chokidar@^3.5.3: + version "3.6.0" + resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-3.6.0.tgz#197c6cc669ef2a8dc5e7b4d97ee4e092c3eb0d5b" + integrity sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw== + dependencies: + anymatch "~3.1.2" + braces "~3.0.2" + glob-parent "~5.1.2" + is-binary-path "~2.1.0" + is-glob "~4.0.1" + normalize-path "~3.0.0" + readdirp "~3.6.0" + optionalDependencies: + fsevents "~2.3.2" + +chokidar@^4.0.3: + version "4.0.3" + resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-4.0.3.tgz#7be37a4c03c9aee1ecfe862a4a23b2c70c205d30" + integrity sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA== + dependencies: + readdirp "^4.0.1" + +citty@^0.1.6: + version "0.1.6" + resolved "https://registry.yarnpkg.com/citty/-/citty-0.1.6.tgz#0f7904da1ed4625e1a9ea7e0fa780981aab7c5e4" + integrity sha512-tskPPKEs8D2KPafUypv2gxwJP8h/OaJmC82QQGGDQcHvXX43xF2VDACcJVmZ0EuSxkpO9Kc4MlrA3q0+FG58AQ== + dependencies: + consola "^3.2.3" + +commander@6.2.0: + version "6.2.0" + resolved "https://registry.yarnpkg.com/commander/-/commander-6.2.0.tgz#b990bfb8ac030aedc6d11bc04d1488ffef56db75" + integrity sha512-zP4jEKbe8SHzKJYQmq8Y9gYjtO/POJLgIdKgV7B9qNmABVFVc+ctqSX6iXh4mCpJfRBOabiZ2YKPg8ciDw6C+Q== + +commander@^10.0.0: + version "10.0.1" + resolved "https://registry.yarnpkg.com/commander/-/commander-10.0.1.tgz#881ee46b4f77d1c1dccc5823433aa39b022cbe06" + integrity sha512-y4Mg2tXshplEbSGzx7amzPwKKOCGuoSRP/CjEdwwk0FOGlUbq6lKuoyDZTNZkmxHdJtp54hdfY/JUrdL7Xfdug== + +commander@^9.0.0: + version "9.5.0" + resolved "https://registry.yarnpkg.com/commander/-/commander-9.5.0.tgz#bc08d1eb5cedf7ccb797a96199d41c7bc3e60d30" + integrity sha512-KRs7WVDKg86PWiuAqhDrAQnTXZKraVcCc6vFdL14qrZ/DcWwuRo7VoiYXalXO7S5GKpqYiVEwCbgFDfxNHKJBQ== + +concat-map@0.0.1: + version "0.0.1" + resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" + integrity sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg== + +confbox@^0.2.2: + version "0.2.2" + resolved "https://registry.yarnpkg.com/confbox/-/confbox-0.2.2.tgz#8652f53961c74d9e081784beed78555974a9c110" + integrity sha512-1NB+BKqhtNipMsov4xI/NnhCKp9XG9NamYp5PVm9klAT0fsrNPjaFICsCFhNhwZJKNh7zB/3q8qXz0E9oaMNtQ== + +consola@^3.2.3, consola@^3.4.0, consola@^3.4.2: + version "3.4.2" + resolved "https://registry.yarnpkg.com/consola/-/consola-3.4.2.tgz#5af110145397bb67afdab77013fdc34cae590ea7" + integrity sha512-5IKcdX0nnYavi6G7TtOhwkYzyjfJlatbjMjuLSfE2kYT5pMDOilZ4OvMhi637CcDICTmz3wARPoyhqyX1Y+XvA== + +content-disposition@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/content-disposition/-/content-disposition-1.0.1.tgz#a8b7bbeb2904befdfb6787e5c0c086959f605f9b" + integrity sha512-oIXISMynqSqm241k6kcQ5UwttDILMK4BiurCfGEREw6+X9jkkpEe5T9FZaApyLGGOnFuyMWZpdolTXMtvEJ08Q== + +content-type@^1.0.5: + version "1.0.5" + resolved "https://registry.yarnpkg.com/content-type/-/content-type-1.0.5.tgz#8b773162656d1d1086784c8f23a54ce6d73d7918" + integrity sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA== + +cookie-parser@^1.4.7: + version "1.4.7" + resolved "https://registry.yarnpkg.com/cookie-parser/-/cookie-parser-1.4.7.tgz#e2125635dfd766888ffe90d60c286404fa0e7b26" + integrity sha512-nGUvgXnotP3BsjiLX2ypbQnWoGUPIIfHQNZkkC668ntrzGWEZVW70HDEB1qnNGMicPje6EttlIgzo51YSwNQGw== + dependencies: + cookie "0.7.2" + cookie-signature "1.0.6" + +cookie-signature@1.0.6: + version "1.0.6" + resolved "https://registry.yarnpkg.com/cookie-signature/-/cookie-signature-1.0.6.tgz#e303a882b342cc3ee8ca513a79999734dab3ae2c" + integrity sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ== + +cookie-signature@^1.2.1: + version "1.2.2" + resolved "https://registry.yarnpkg.com/cookie-signature/-/cookie-signature-1.2.2.tgz#57c7fc3cc293acab9fec54d73e15690ebe4a1793" + integrity sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg== + +cookie@0.7.2, cookie@^0.7.1: + version "0.7.2" + resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.7.2.tgz#556369c472a2ba910f2979891b526b3436237ed7" + integrity sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w== + +cross-spawn@^7.0.6: + version "7.0.6" + resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-7.0.6.tgz#8a58fe78f00dcd70c370451759dfbfaf03e8ee9f" + integrity sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA== + dependencies: + path-key "^3.1.0" + shebang-command "^2.0.0" + which "^2.0.1" + +debug@^4.1.1, debug@^4.4.0, debug@^4.4.3: + version "4.4.3" + resolved "https://registry.yarnpkg.com/debug/-/debug-4.4.3.tgz#c6ae432d9bd9662582fce08709b038c58e9e3d6a" + integrity sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA== + dependencies: + ms "^2.1.3" + +deepmerge-ts@7.1.5: + version "7.1.5" + resolved "https://registry.yarnpkg.com/deepmerge-ts/-/deepmerge-ts-7.1.5.tgz#ff818564007f5c150808d2b7b732cac83aa415ab" + integrity sha512-HOJkrhaYsweh+W+e74Yn7YStZOilkoPb6fycpwNLKzSPtruFs48nYis0zy5yJz1+ktUhHxoRDJ27RQAWLIJVJw== + +defu@^6.1.4: + version "6.1.4" + resolved "https://registry.yarnpkg.com/defu/-/defu-6.1.4.tgz#4e0c9cf9ff68fe5f3d7f2765cc1a012dfdcb0479" + integrity sha512-mEQCMmwJu317oSz8CwdIOdwf3xMif1ttiM8LTufzc3g6kR+9Pe236twL8j3IYT1F7GfRgGcW6MWxzZjLIkuHIg== + +denque@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/denque/-/denque-2.1.0.tgz#e93e1a6569fb5e66f16a3c2a2964617d349d6ab1" + integrity sha512-HVQE3AAb/pxF8fQAoiqpvg9i3evqug3hoiwakOyZAwJm+6vZehbkYXZ0l4JxS+I3QxM97v5aaRNhj8v5oBhekw== + +depd@^2.0.0, depd@~2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/depd/-/depd-2.0.0.tgz#b696163cc757560d09cf22cc8fad1571b79e76df" + integrity sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw== + +destr@^2.0.3: + version "2.0.5" + resolved "https://registry.yarnpkg.com/destr/-/destr-2.0.5.tgz#7d112ff1b925fb8d2079fac5bdb4a90973b51fdb" + integrity sha512-ugFTXCtDZunbzasqBxrK93Ik/DRYsO6S/fedkWEMKqt04xZ4csmnmwGDBAb07QWNaGMAmnTIemsYZCksjATwsA== + +dir-glob@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/dir-glob/-/dir-glob-3.0.1.tgz#56dbf73d992a4a93ba1584f4534063fd2e41717f" + integrity sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA== + dependencies: + path-type "^4.0.0" + +doctrine@3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/doctrine/-/doctrine-3.0.0.tgz#addebead72a6574db783639dc87a121773973961" + integrity sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w== + dependencies: + esutils "^2.0.2" + +dotenv@^16.6.1: + version "16.6.1" + resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-16.6.1.tgz#773f0e69527a8315c7285d5ee73c4459d20a8020" + integrity sha512-uBq4egWHTcTt33a72vpSG0z3HnPuIl6NqYcTrKEg2azoEyl2hpW0zqlxysq2pK9HlDIHyHyakeYaYnSAwd8bow== + +dotenv@^17.2.3: + version "17.2.3" + resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-17.2.3.tgz#ad995d6997f639b11065f419a22fabf567cdb9a2" + integrity sha512-JVUnt+DUIzu87TABbhPmNfVdBDt18BLOWjMUFJMSi/Qqg7NTYtabbvSNJGOJ7afbRuv9D/lngizHtP7QyLQ+9w== + +dunder-proto@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/dunder-proto/-/dunder-proto-1.0.1.tgz#d7ae667e1dc83482f8b70fd0f6eefc50da30f58a" + integrity sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A== + dependencies: + call-bind-apply-helpers "^1.0.1" + es-errors "^1.3.0" + gopd "^1.2.0" + +ecdsa-sig-formatter@1.0.11: + version "1.0.11" + resolved "https://registry.yarnpkg.com/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz#ae0f0fa2d85045ef14a817daa3ce9acd0489e5bf" + integrity sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ== + dependencies: + safe-buffer "^5.0.1" + +ee-first@1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/ee-first/-/ee-first-1.1.1.tgz#590c61156b0ae2f4f0255732a158b266bc56b21d" + integrity sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow== + +effect@3.18.4: + version "3.18.4" + resolved "https://registry.yarnpkg.com/effect/-/effect-3.18.4.tgz#e241fde5cc090608c51c5e29e2da357bb5d0e815" + integrity sha512-b1LXQJLe9D11wfnOKAk3PKxuqYshQ0Heez+y5pnkd3jLj1yx9QhM72zZ9uUrOQyNvrs2GZZd/3maL0ZV18YuDA== + dependencies: + "@standard-schema/spec" "^1.0.0" + fast-check "^3.23.1" + +empathic@2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/empathic/-/empathic-2.0.0.tgz#71d3c2b94fad49532ef98a6c34be0386659f6131" + integrity sha512-i6UzDscO/XfAcNYD75CfICkmfLedpyPDdozrLMmQc5ORaQcdMoc21OnlEylMIqI7U8eniKrPMxxtj8k0vhmJhA== + +encodeurl@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/encodeurl/-/encodeurl-2.0.0.tgz#7b8ea898077d7e409d3ac45474ea38eaf0857a58" + integrity sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg== + +es-define-property@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/es-define-property/-/es-define-property-1.0.1.tgz#983eb2f9a6724e9303f61addf011c72e09e0b0fa" + integrity sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g== + +es-errors@^1.3.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/es-errors/-/es-errors-1.3.0.tgz#05f75a25dab98e4fb1dcd5e1472c0546d5057c8f" + integrity sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw== + +es-module-lexer@^1.7.0: + version "1.7.0" + resolved "https://registry.yarnpkg.com/es-module-lexer/-/es-module-lexer-1.7.0.tgz#9159601561880a85f2734560a9099b2c31e5372a" + integrity sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA== + +es-object-atoms@^1.0.0, es-object-atoms@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/es-object-atoms/-/es-object-atoms-1.1.1.tgz#1c4f2c4837327597ce69d2ca190a7fdd172338c1" + integrity sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA== + dependencies: + es-errors "^1.3.0" + +esbuild@^0.27.0, esbuild@~0.27.0: + version "0.27.2" + resolved "https://registry.yarnpkg.com/esbuild/-/esbuild-0.27.2.tgz#d83ed2154d5813a5367376bb2292a9296fc83717" + integrity sha512-HyNQImnsOC7X9PMNaCIeAm4ISCQXs5a5YasTXVliKv4uuBo1dKrG0A+uQS8M5eXjVMnLg3WgXaKvprHlFJQffw== + optionalDependencies: + "@esbuild/aix-ppc64" "0.27.2" + "@esbuild/android-arm" "0.27.2" + "@esbuild/android-arm64" "0.27.2" + "@esbuild/android-x64" "0.27.2" + "@esbuild/darwin-arm64" "0.27.2" + "@esbuild/darwin-x64" "0.27.2" + "@esbuild/freebsd-arm64" "0.27.2" + "@esbuild/freebsd-x64" "0.27.2" + "@esbuild/linux-arm" "0.27.2" + "@esbuild/linux-arm64" "0.27.2" + "@esbuild/linux-ia32" "0.27.2" + "@esbuild/linux-loong64" "0.27.2" + "@esbuild/linux-mips64el" "0.27.2" + "@esbuild/linux-ppc64" "0.27.2" + "@esbuild/linux-riscv64" "0.27.2" + "@esbuild/linux-s390x" "0.27.2" + "@esbuild/linux-x64" "0.27.2" + "@esbuild/netbsd-arm64" "0.27.2" + "@esbuild/netbsd-x64" "0.27.2" + "@esbuild/openbsd-arm64" "0.27.2" + "@esbuild/openbsd-x64" "0.27.2" + "@esbuild/openharmony-arm64" "0.27.2" + "@esbuild/sunos-x64" "0.27.2" + "@esbuild/win32-arm64" "0.27.2" + "@esbuild/win32-ia32" "0.27.2" + "@esbuild/win32-x64" "0.27.2" + +escape-html@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/escape-html/-/escape-html-1.0.3.tgz#0258eae4d3d0c0974de1c169188ef0051d1d1988" + integrity sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow== + +estree-walker@^3.0.3: + version "3.0.3" + resolved "https://registry.yarnpkg.com/estree-walker/-/estree-walker-3.0.3.tgz#67c3e549ec402a487b4fc193d1953a524752340d" + integrity sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g== + dependencies: + "@types/estree" "^1.0.0" + +esutils@^2.0.2: + version "2.0.3" + resolved "https://registry.yarnpkg.com/esutils/-/esutils-2.0.3.tgz#74d2eb4de0b8da1293711910d50775b9b710ef64" + integrity sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g== + +etag@^1.8.1: + version "1.8.1" + resolved "https://registry.yarnpkg.com/etag/-/etag-1.8.1.tgz#41ae2eeb65efa62268aebfea83ac7d79299b0887" + integrity sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg== + +expect-type@^1.2.2: + version "1.3.0" + resolved "https://registry.yarnpkg.com/expect-type/-/expect-type-1.3.0.tgz#0d58ed361877a31bbc4dd6cf71bbfef7faf6bd68" + integrity sha512-knvyeauYhqjOYvQ66MznSMs83wmHrCycNEN6Ao+2AeYEfxUIkuiVxdEa1qlGEPK+We3n0THiDciYSsCcgW/DoA== + +express@^5.2.1: + version "5.2.1" + resolved "https://registry.yarnpkg.com/express/-/express-5.2.1.tgz#8f21d15b6d327f92b4794ecf8cb08a72f956ac04" + integrity sha512-hIS4idWWai69NezIdRt2xFVofaF4j+6INOpJlVOLDO8zXGpUVEVzIYk12UUi2JzjEzWL3IOAxcTubgz9Po0yXw== + dependencies: + accepts "^2.0.0" + body-parser "^2.2.1" + content-disposition "^1.0.0" + content-type "^1.0.5" + cookie "^0.7.1" + cookie-signature "^1.2.1" + debug "^4.4.0" + depd "^2.0.0" + encodeurl "^2.0.0" + escape-html "^1.0.3" + etag "^1.8.1" + finalhandler "^2.1.0" + fresh "^2.0.0" + http-errors "^2.0.0" + merge-descriptors "^2.0.0" + mime-types "^3.0.0" + on-finished "^2.4.1" + once "^1.4.0" + parseurl "^1.3.3" + proxy-addr "^2.0.7" + qs "^6.14.0" + range-parser "^1.2.1" + router "^2.2.0" + send "^1.1.0" + serve-static "^2.2.0" + statuses "^2.0.1" + type-is "^2.0.1" + vary "^1.1.2" + +exsolve@^1.0.7: + version "1.0.8" + resolved "https://registry.yarnpkg.com/exsolve/-/exsolve-1.0.8.tgz#7f5e34da61cd1116deda5136e62292c096f50613" + integrity sha512-LmDxfWXwcTArk8fUEnOfSZpHOJ6zOMUJKOtFLFqJLoKJetuQG874Uc7/Kki7zFLzYybmZhp1M7+98pfMqeX8yA== + +fast-check@^3.23.1: + version "3.23.2" + resolved "https://registry.yarnpkg.com/fast-check/-/fast-check-3.23.2.tgz#0129f1eb7e4f500f58e8290edc83c670e4a574a2" + integrity sha512-h5+1OzzfCC3Ef7VbtKdcv7zsstUQwUDlYpUTvjeUsJAssPgLn7QzbboPtL5ro04Mq0rPOsMzl7q5hIbRs2wD1A== + dependencies: + pure-rand "^6.1.0" + +fast-glob@^3.2.9: + version "3.3.3" + resolved "https://registry.yarnpkg.com/fast-glob/-/fast-glob-3.3.3.tgz#d06d585ce8dba90a16b0505c543c3ccfb3aeb818" + integrity sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg== + 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.8" + +fastq@^1.6.0: + version "1.20.1" + resolved "https://registry.yarnpkg.com/fastq/-/fastq-1.20.1.tgz#ca750a10dc925bc8b18839fd203e3ef4b3ced675" + integrity sha512-GGToxJ/w1x32s/D2EKND7kTil4n8OVk/9mycTc4VDza13lOvpUZTGX3mFSCtV9ksdGBVzvsyAVLM6mHFThxXxw== + dependencies: + reusify "^1.0.4" + +fdir@^6.5.0: + version "6.5.0" + resolved "https://registry.yarnpkg.com/fdir/-/fdir-6.5.0.tgz#ed2ab967a331ade62f18d077dae192684d50d350" + integrity sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg== + +fill-range@^7.1.1: + version "7.1.1" + resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-7.1.1.tgz#44265d3cac07e3ea7dc247516380643754a05292" + integrity sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg== + dependencies: + to-regex-range "^5.0.1" + +finalhandler@^2.1.0: + version "2.1.1" + resolved "https://registry.yarnpkg.com/finalhandler/-/finalhandler-2.1.1.tgz#a2c517a6559852bcdb06d1f8bd7f51b68fad8099" + integrity sha512-S8KoZgRZN+a5rNwqTxlZZePjT/4cnm0ROV70LedRHZ0p8u9fRID0hJUZQpkKLzro8LfmC8sx23bY6tVNxv8pQA== + dependencies: + debug "^4.4.0" + encodeurl "^2.0.0" + escape-html "^1.0.3" + on-finished "^2.4.1" + parseurl "^1.3.3" + statuses "^2.0.1" + +foreground-child@3.3.1: + version "3.3.1" + resolved "https://registry.yarnpkg.com/foreground-child/-/foreground-child-3.3.1.tgz#32e8e9ed1b68a3497befb9ac2b6adf92a638576f" + integrity sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw== + dependencies: + cross-spawn "^7.0.6" + signal-exit "^4.0.1" + +forwarded@0.2.0: + version "0.2.0" + resolved "https://registry.yarnpkg.com/forwarded/-/forwarded-0.2.0.tgz#2269936428aad4c15c7ebe9779a84bf0b2a81811" + integrity sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow== + +fresh@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/fresh/-/fresh-2.0.0.tgz#8dd7df6a1b3a1b3a5cf186c05a5dd267622635a4" + integrity sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A== + +fs.realpath@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f" + integrity sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw== + +fsevents@~2.3.2, fsevents@~2.3.3: + version "2.3.3" + resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.3.3.tgz#cac6407785d03675a2a5e1a5305c697b347d90d6" + integrity sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw== + +function-bind@^1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.2.tgz#2c02d864d97f3ea6c8830c464cbd11ab6eab7a1c" + integrity sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA== + +generate-function@^2.3.1: + version "2.3.1" + resolved "https://registry.yarnpkg.com/generate-function/-/generate-function-2.3.1.tgz#f069617690c10c868e73b8465746764f97c3479f" + integrity sha512-eeB5GfMNeevm/GRYq20ShmsaGcmI81kIX2K9XQx5miC8KdHaC6Jm0qQ8ZNeGOi7wYB8OsdxKs+Y2oVuTFuVwKQ== + dependencies: + is-property "^1.0.2" + +get-intrinsic@^1.2.5, get-intrinsic@^1.3.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/get-intrinsic/-/get-intrinsic-1.3.0.tgz#743f0e3b6964a93a5491ed1bffaae054d7f98d01" + integrity sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ== + dependencies: + call-bind-apply-helpers "^1.0.2" + es-define-property "^1.0.1" + es-errors "^1.3.0" + es-object-atoms "^1.1.1" + function-bind "^1.1.2" + get-proto "^1.0.1" + gopd "^1.2.0" + has-symbols "^1.1.0" + hasown "^2.0.2" + math-intrinsics "^1.1.0" + +get-port-please@3.1.2: + version "3.1.2" + resolved "https://registry.yarnpkg.com/get-port-please/-/get-port-please-3.1.2.tgz#502795e56217128e4183025c89a48c71652f4e49" + integrity sha512-Gxc29eLs1fbn6LQ4jSU4vXjlwyZhF5HsGuMAa7gqBP4Rw4yxxltyDUuF5MBclFzDTXO+ACchGQoeela4DSfzdQ== + +get-proto@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/get-proto/-/get-proto-1.0.1.tgz#150b3f2743869ef3e851ec0c49d15b1d14d00ee1" + integrity sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g== + dependencies: + dunder-proto "^1.0.1" + es-object-atoms "^1.0.0" + +get-tsconfig@^4.10.0, get-tsconfig@^4.7.5: + version "4.13.0" + resolved "https://registry.yarnpkg.com/get-tsconfig/-/get-tsconfig-4.13.0.tgz#fcdd991e6d22ab9a600f00e91c318707a5d9a0d7" + integrity sha512-1VKTZJCwBrvbd+Wn3AOgQP/2Av+TfTCOlE4AcRJE72W1ksZXbAx8PPBR9RzgTeSPzlPMHrbANMH3LbltH73wxQ== + dependencies: + resolve-pkg-maps "^1.0.0" + +giget@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/giget/-/giget-2.0.0.tgz#395fc934a43f9a7a29a29d55b99f23e30c14f195" + integrity sha512-L5bGsVkxJbJgdnwyuheIunkGatUF/zssUoxxjACCseZYAVbaqdh9Tsmmlkl8vYan09H7sbvKt4pS8GqKLBrEzA== + dependencies: + citty "^0.1.6" + consola "^3.4.0" + defu "^6.1.4" + node-fetch-native "^1.6.6" + nypm "^0.6.0" + pathe "^2.0.3" + +glob-parent@^5.1.2, glob-parent@~5.1.2: + version "5.1.2" + resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-5.1.2.tgz#869832c58034fe68a4093c17dc15e8340d8401c4" + integrity sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow== + dependencies: + is-glob "^4.0.1" + +glob@7.1.6: + version "7.1.6" + resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.6.tgz#141f33b81a7c2492e125594307480c46679278a6" + integrity sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA== + dependencies: + fs.realpath "^1.0.0" + inflight "^1.0.4" + inherits "2" + minimatch "^3.0.4" + once "^1.3.0" + path-is-absolute "^1.0.0" + +globby@^11.0.4: + version "11.1.0" + resolved "https://registry.yarnpkg.com/globby/-/globby-11.1.0.tgz#bd4be98bb042f83d796f7e3811991fbe82a0d34b" + integrity sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g== + dependencies: + array-union "^2.1.0" + dir-glob "^3.0.1" + fast-glob "^3.2.9" + ignore "^5.2.0" + merge2 "^1.4.1" + slash "^3.0.0" + +globrex@^0.1.2: + version "0.1.2" + resolved "https://registry.yarnpkg.com/globrex/-/globrex-0.1.2.tgz#dd5d9ec826232730cd6793a5e33a9302985e6098" + integrity sha512-uHJgbwAMwNFf5mLst7IWLNg14x1CkeqglJb/K3doi4dw6q2IvAAmM/Y81kevy83wP+Sst+nutFTYOGg3d1lsxg== + +gopd@^1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/gopd/-/gopd-1.2.0.tgz#89f56b8217bdbc8802bd299df6d7f1081d7e51a1" + integrity sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg== + +graceful-fs@^4.2.4: + version "4.2.11" + resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.11.tgz#4183e4e8bf08bb6e05bbb2f7d2e0c8f712ca40e3" + integrity sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ== + +grammex@^3.1.10: + version "3.1.12" + resolved "https://registry.yarnpkg.com/grammex/-/grammex-3.1.12.tgz#08f021dd2cad009e64248fb53247cc6108788404" + integrity sha512-6ufJOsSA7LcQehIJNCO7HIBykfM7DXQual0Ny780/DEcJIpBlHRvcqEBWGPYd7hrXL2GJ3oJI1MIhaXjWmLQOQ== + +has-flag@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-4.0.0.tgz#944771fd9c81c81265c4d6941860da06bb59479b" + integrity sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ== + +has-symbols@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/has-symbols/-/has-symbols-1.1.0.tgz#fc9c6a783a084951d0b971fe1018de813707a338" + integrity sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ== + +hasown@^2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/hasown/-/hasown-2.0.2.tgz#003eaf91be7adc372e84ec59dc37252cedb80003" + integrity sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ== + dependencies: + function-bind "^1.1.2" + +hono@4.10.6: + version "4.10.6" + resolved "https://registry.yarnpkg.com/hono/-/hono-4.10.6.tgz#b80cf3903b3d12b02c472dadf28d046874e89e5a" + integrity sha512-BIdolzGpDO9MQ4nu3AUuDwHZZ+KViNm+EZ75Ae55eMXMqLVhDFqEMXxtUe9Qh8hjL+pIna/frs2j6Y2yD5Ua/g== + +html-escaper@^2.0.0: + version "2.0.2" + resolved "https://registry.yarnpkg.com/html-escaper/-/html-escaper-2.0.2.tgz#dfd60027da36a36dfcbe236262c00a5822681453" + integrity sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg== + +http-errors@^2.0.0, http-errors@^2.0.1, http-errors@~2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/http-errors/-/http-errors-2.0.1.tgz#36d2f65bc909c8790018dd36fb4d93da6caae06b" + integrity sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ== + dependencies: + depd "~2.0.0" + inherits "~2.0.4" + setprototypeof "~1.2.0" + statuses "~2.0.2" + toidentifier "~1.0.1" + +http-status-codes@2.3.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/http-status-codes/-/http-status-codes-2.3.0.tgz#987fefb28c69f92a43aecc77feec2866349a8bfc" + integrity sha512-RJ8XvFvpPM/Dmc5SV+dC4y5PCeOhT3x1Hq0NU3rjGeg5a/CqlhZ7uudknPwZFz4aeAXDcbAyaeP7GAo9lvngtA== + +iconv-lite@^0.7.0, iconv-lite@~0.7.0: + version "0.7.2" + resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.7.2.tgz#d0bdeac3f12b4835b7359c2ad89c422a4d1cc72e" + integrity sha512-im9DjEDQ55s9fL4EYzOAv0yMqmMBSZp6G0VvFyTMPKWxiSBHUj9NW/qqLmXUwXrrM7AvqSlTCfvqRb0cM8yYqw== + dependencies: + safer-buffer ">= 2.1.2 < 3.0.0" + +ignore@^5.2.0: + version "5.3.2" + resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.3.2.tgz#3cd40e729f3643fd87cb04e50bf0eb722bc596f5" + integrity sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g== + +inflight@^1.0.4: + version "1.0.6" + resolved "https://registry.yarnpkg.com/inflight/-/inflight-1.0.6.tgz#49bd6331d7d02d0c09bc910a1075ba8165b56df9" + integrity sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA== + dependencies: + once "^1.3.0" + wrappy "1" + +inherits@2, inherits@~2.0.4: + version "2.0.4" + resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" + integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== + +inversify@^7.11.0: + version "7.11.0" + resolved "https://registry.yarnpkg.com/inversify/-/inversify-7.11.0.tgz#571cede6346081b5af224eaea49bb97a91268f92" + integrity sha512-yZDprSSr8TyVeMGI/AOV4ws6gwjX22hj9Z8/oHAVpJORY6WRFTcUzhnZtibBUHEw2U8ArvHcR+i863DplQ3Cwg== + dependencies: + "@inversifyjs/common" "1.5.2" + "@inversifyjs/container" "1.15.0" + "@inversifyjs/core" "9.2.0" + +ipaddr.js@1.9.1: + version "1.9.1" + resolved "https://registry.yarnpkg.com/ipaddr.js/-/ipaddr.js-1.9.1.tgz#bff38543eeb8984825079ff3a2a8e6cbd46781b3" + integrity sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g== + +is-binary-path@~2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/is-binary-path/-/is-binary-path-2.1.0.tgz#ea1f7f3b80f064236e83470f86c09c254fb45b09" + integrity sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw== + dependencies: + binary-extensions "^2.0.0" + +is-extglob@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/is-extglob/-/is-extglob-2.1.1.tgz#a88c02535791f02ed37c76a1b9ea9773c833f8c2" + integrity sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ== + +is-glob@^4.0.1, is-glob@~4.0.1: + version "4.0.3" + resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-4.0.3.tgz#64f61e42cbbb2eec2071a9dac0b28ba1e65d5084" + integrity sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg== + dependencies: + is-extglob "^2.1.1" + +is-number@^7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/is-number/-/is-number-7.0.0.tgz#7535345b896734d5f80c4d06c50955527a14f12b" + integrity sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng== + +is-promise@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/is-promise/-/is-promise-4.0.0.tgz#42ff9f84206c1991d26debf520dd5c01042dd2f3" + integrity sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ== + +is-property@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/is-property/-/is-property-1.0.2.tgz#57fe1c4e48474edd65b09911f26b1cd4095dda84" + integrity sha512-Ks/IoX00TtClbGQr4TWXemAnktAQvYB7HzcCxDGqEZU6oCmb2INHuOoKxbtR+HFkmYWBKv/dOZtGRiAjDhj92g== + +isexe@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10" + integrity sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw== + +istanbul-lib-coverage@^3.0.0, istanbul-lib-coverage@^3.2.2: + version "3.2.2" + resolved "https://registry.yarnpkg.com/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.2.tgz#2d166c4b0644d43a39f04bf6c2edd1e585f31756" + integrity sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg== + +istanbul-lib-report@^3.0.0, istanbul-lib-report@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/istanbul-lib-report/-/istanbul-lib-report-3.0.1.tgz#908305bac9a5bd175ac6a74489eafd0fc2445a7d" + integrity sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw== + dependencies: + istanbul-lib-coverage "^3.0.0" + make-dir "^4.0.0" + supports-color "^7.1.0" + +istanbul-reports@^3.2.0: + version "3.2.0" + resolved "https://registry.yarnpkg.com/istanbul-reports/-/istanbul-reports-3.2.0.tgz#cb4535162b5784aa623cee21a7252cf2c807ac93" + integrity sha512-HGYWWS/ehqTV3xN10i23tkPkpH46MLCIMFNCaaKNavAXTF1RkqxawEPtnjnGZ6XKSInBKkiOA5BKS+aZiY3AvA== + dependencies: + html-escaper "^2.0.0" + istanbul-lib-report "^3.0.0" + +jiti@^2.4.2: + version "2.6.1" + resolved "https://registry.yarnpkg.com/jiti/-/jiti-2.6.1.tgz#178ef2fc9a1a594248c20627cd820187a4d78d92" + integrity sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ== + +js-tokens@^9.0.1: + version "9.0.1" + resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-9.0.1.tgz#2ec43964658435296f6761b34e10671c2d9527f4" + integrity sha512-mxa9E9ITFOt0ban3j6L5MpjwegGz6lBQmM1IJkWeBZGcMxto50+eWdjC/52xDbS2vy0k7vIMK0Fe2wfL9OQSpQ== + +js-yaml@^4.1.0: + version "4.1.1" + resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-4.1.1.tgz#854c292467705b699476e1a2decc0c8a3458806b" + integrity sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA== + dependencies: + argparse "^2.0.1" + +jsonwebtoken@^9.0.3: + version "9.0.3" + resolved "https://registry.yarnpkg.com/jsonwebtoken/-/jsonwebtoken-9.0.3.tgz#6cd57ab01e9b0ac07cb847d53d3c9b6ee31f7ae2" + integrity sha512-MT/xP0CrubFRNLNKvxJ2BYfy53Zkm++5bX9dtuPbqAeQpTVe0MQTFhao8+Cp//EmJp244xt6Drw/GVEGCUj40g== + dependencies: + jws "^4.0.1" + lodash.includes "^4.3.0" + lodash.isboolean "^3.0.3" + lodash.isinteger "^4.0.4" + lodash.isnumber "^3.0.3" + lodash.isplainobject "^4.0.6" + lodash.isstring "^4.0.1" + lodash.once "^4.0.0" + ms "^2.1.1" + semver "^7.5.4" + +jwa@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/jwa/-/jwa-2.0.1.tgz#bf8176d1ad0cd72e0f3f58338595a13e110bc804" + integrity sha512-hRF04fqJIP8Abbkq5NKGN0Bbr3JxlQ+qhZufXVr0DvujKy93ZCbXZMHDL4EOtodSbCWxOqR8MS1tXA5hwqCXDg== + dependencies: + buffer-equal-constant-time "^1.0.1" + ecdsa-sig-formatter "1.0.11" + safe-buffer "^5.0.1" + +jws@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/jws/-/jws-4.0.1.tgz#07edc1be8fac20e677b283ece261498bd38f0690" + integrity sha512-EKI/M/yqPncGUUh44xz0PxSidXFr/+r0pA70+gIYhjv+et7yxM+s29Y+VGDkovRofQem0fs7Uvf4+YmAdyRduA== + dependencies: + jwa "^2.0.1" + safe-buffer "^5.0.1" + +lilconfig@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/lilconfig/-/lilconfig-2.1.0.tgz#78e23ac89ebb7e1bfbf25b18043de756548e7f52" + integrity sha512-utWOt/GHzuUxnLKxB6dk81RoOeoNeHgbrXiuGk4yyF5qlRz+iIVWu56E2fqGHFrXz0QNUhLB/8nKqvRH66JKGQ== + +lodash.get@^4.4.2: + version "4.4.2" + resolved "https://registry.yarnpkg.com/lodash.get/-/lodash.get-4.4.2.tgz#2d177f652fa31e939b4438d5341499dfa3825e99" + integrity sha512-z+Uw/vLuy6gQe8cfaFWD7p0wVv8fJl3mbzXh33RS+0oW2wvUqiRXiQ69gLWSLpgB5/6sU+r6BlQR0MBILadqTQ== + +lodash.includes@^4.3.0: + version "4.3.0" + resolved "https://registry.yarnpkg.com/lodash.includes/-/lodash.includes-4.3.0.tgz#60bb98a87cb923c68ca1e51325483314849f553f" + integrity sha512-W3Bx6mdkRTGtlJISOvVD/lbqjTlPPUDTMnlXZFnVwi9NKJ6tiAk6LVdlhZMm17VZisqhKcgzpO5Wz91PCt5b0w== + +lodash.isboolean@^3.0.3: + version "3.0.3" + resolved "https://registry.yarnpkg.com/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz#6c2e171db2a257cd96802fd43b01b20d5f5870f6" + integrity sha512-Bz5mupy2SVbPHURB98VAcw+aHh4vRV5IPNhILUCsOzRmsTmSQ17jIuqopAentWoehktxGd9e/hbIXq980/1QJg== + +lodash.isequal@^4.5.0: + version "4.5.0" + resolved "https://registry.yarnpkg.com/lodash.isequal/-/lodash.isequal-4.5.0.tgz#415c4478f2bcc30120c22ce10ed3226f7d3e18e0" + integrity sha512-pDo3lu8Jhfjqls6GkMgpahsF9kCyayhgykjyLMNFTKWrpVdAQtYyB4muAMWozBB4ig/dtWAmsMxLEI8wuz+DYQ== + +lodash.isinteger@^4.0.4: + version "4.0.4" + resolved "https://registry.yarnpkg.com/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz#619c0af3d03f8b04c31f5882840b77b11cd68343" + integrity sha512-DBwtEWN2caHQ9/imiNeEA5ys1JoRtRfY3d7V9wkqtbycnAmTvRRmbHKDV4a0EYc678/dia0jrte4tjYwVBaZUA== + +lodash.isnumber@^3.0.3: + version "3.0.3" + resolved "https://registry.yarnpkg.com/lodash.isnumber/-/lodash.isnumber-3.0.3.tgz#3ce76810c5928d03352301ac287317f11c0b1ffc" + integrity sha512-QYqzpfwO3/CWf3XP+Z+tkQsfaLL/EnUlXWVkIk5FUPc4sBdTehEqZONuyRt2P67PXAk+NXmTBcc97zw9t1FQrw== + +lodash.isplainobject@^4.0.6: + version "4.0.6" + resolved "https://registry.yarnpkg.com/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz#7c526a52d89b45c45cc690b88163be0497f550cb" + integrity sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA== + +lodash.isstring@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/lodash.isstring/-/lodash.isstring-4.0.1.tgz#d527dfb5456eca7cc9bb95d5daeaf88ba54a5451" + integrity sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw== + +lodash.mergewith@^4.6.2: + version "4.6.2" + resolved "https://registry.yarnpkg.com/lodash.mergewith/-/lodash.mergewith-4.6.2.tgz#617121f89ac55f59047c7aec1ccd6654c6590f55" + integrity sha512-GK3g5RPZWTRSeLSpgP8Xhra+pnjBC56q9FZYe1d5RN3TJ35dbkGy3YqBSMbyCrlbi+CM9Z3Jk5yTL7RCsqboyQ== + +lodash.once@^4.0.0: + version "4.1.1" + resolved "https://registry.yarnpkg.com/lodash.once/-/lodash.once-4.1.1.tgz#0dd3971213c7c56df880977d504c88fb471a97ac" + integrity sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg== + +lodash@4.17.21: + version "4.17.21" + resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c" + integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg== + +long@^5.2.1: + version "5.3.2" + resolved "https://registry.yarnpkg.com/long/-/long-5.3.2.tgz#1d84463095999262d7d7b7f8bfd4a8cc55167f83" + integrity sha512-mNAgZ1GmyNhD7AuqnTG3/VQ26o760+ZYBPKjPvugO8+nLbYfX6TVpJPseBvopbdY+qpZ/lKUnmEc1LeZYS3QAA== + +lru.min@^1.0.0, lru.min@^1.1.0: + version "1.1.3" + resolved "https://registry.yarnpkg.com/lru.min/-/lru.min-1.1.3.tgz#c8c3d001dfb4cbe5b8d1f4bea207d4a320e5d76f" + integrity sha512-Lkk/vx6ak3rYkRR0Nhu4lFUT2VDnQSxBe8Hbl7f36358p6ow8Bnvr8lrLt98H8J1aGxfhbX4Fs5tYg2+FTwr5Q== + +magic-string@^0.30.21: + version "0.30.21" + resolved "https://registry.yarnpkg.com/magic-string/-/magic-string-0.30.21.tgz#56763ec09a0fa8091df27879fd94d19078c00d91" + integrity sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ== + dependencies: + "@jridgewell/sourcemap-codec" "^1.5.5" + +magicast@^0.5.1: + version "0.5.1" + resolved "https://registry.yarnpkg.com/magicast/-/magicast-0.5.1.tgz#518959aea78851cd35d4bb0da92f780db3f606d3" + integrity sha512-xrHS24IxaLrvuo613F719wvOIv9xPHFWQHuvGUBmPnCA/3MQxKI3b+r7n1jAoDHmsbC5bRhTZYR77invLAxVnw== + dependencies: + "@babel/parser" "^7.28.5" + "@babel/types" "^7.28.5" + source-map-js "^1.2.1" + +make-dir@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/make-dir/-/make-dir-4.0.0.tgz#c3c2307a771277cd9638305f915c29ae741b614e" + integrity sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw== + dependencies: + semver "^7.5.3" + +math-intrinsics@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/math-intrinsics/-/math-intrinsics-1.1.0.tgz#a0dd74be81e2aa5c2f27e65ce283605ee4e2b7f9" + integrity sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g== + +media-typer@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/media-typer/-/media-typer-1.1.0.tgz#6ab74b8f2d3320f2064b2a87a38e7931ff3a5561" + integrity sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw== + +merge-descriptors@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/merge-descriptors/-/merge-descriptors-2.0.0.tgz#ea922f660635a2249ee565e0449f951e6b603808" + integrity sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g== + +merge2@^1.3.0, merge2@^1.4.1: + version "1.4.1" + resolved "https://registry.yarnpkg.com/merge2/-/merge2-1.4.1.tgz#4368892f885e907455a6fd7dc55c0c9d404990ae" + integrity sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg== + +micromatch@^4.0.8: + version "4.0.8" + resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-4.0.8.tgz#d66fa18f3a47076789320b9b1af32bd86d9fa202" + integrity sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA== + dependencies: + braces "^3.0.3" + picomatch "^2.3.1" + +mime-db@^1.54.0: + version "1.54.0" + resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.54.0.tgz#cddb3ee4f9c64530dff640236661d42cb6a314f5" + integrity sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ== + +mime-types@^3.0.0, mime-types@^3.0.2: + version "3.0.2" + resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-3.0.2.tgz#39002d4182575d5af036ffa118100f2524b2e2ab" + integrity sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A== + dependencies: + mime-db "^1.54.0" + +minimatch@^3.0.4: + version "3.1.2" + resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.1.2.tgz#19cd194bfd3e428f049a70817c038d89ab4be35b" + integrity sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw== + dependencies: + brace-expansion "^1.1.7" + +ms@^2.1.1, ms@^2.1.3: + version "2.1.3" + resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.3.tgz#574c8138ce1d2b5861f0b44579dbadd60c6615b2" + integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA== + +mylas@^2.1.9: + version "2.1.14" + resolved "https://registry.yarnpkg.com/mylas/-/mylas-2.1.14.tgz#bfa0a2bcbc4bb69f0af324dc7f38689ad56f12cc" + integrity sha512-BzQguy9W9NJgoVn2mRWzbFrFWWztGCcng2QI9+41frfk+Athwgx3qhqhvStz7ExeUUu7Kzw427sNzHpEZNINog== + +mysql2@3.15.3: + version "3.15.3" + resolved "https://registry.yarnpkg.com/mysql2/-/mysql2-3.15.3.tgz#f0348d9c7401bb98cb1f45ffc5a773b109f70808" + integrity sha512-FBrGau0IXmuqg4haEZRBfHNWB5mUARw6hNwPDXXGg0XzVJ50mr/9hb267lvpVMnhZ1FON3qNd4Xfcez1rbFwSg== + dependencies: + aws-ssl-profiles "^1.1.1" + denque "^2.1.0" + generate-function "^2.3.1" + iconv-lite "^0.7.0" + long "^5.2.1" + lru.min "^1.0.0" + named-placeholders "^1.1.3" + seq-queue "^0.0.5" + sqlstring "^2.3.2" + +named-placeholders@^1.1.3: + version "1.1.6" + resolved "https://registry.yarnpkg.com/named-placeholders/-/named-placeholders-1.1.6.tgz#c50c6920b43f258f59c16add1e56654f5cc02bb5" + integrity sha512-Tz09sEL2EEuv5fFowm419c1+a/jSMiBjI9gHxVLrVdbUkkNUUfjsVYs9pVZu5oCon/kmRh9TfLEObFtkVxmY0w== + dependencies: + lru.min "^1.1.0" + +nanoid@^3.3.11: + version "3.3.11" + resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.3.11.tgz#4f4f112cefbe303202f2199838128936266d185b" + integrity sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w== + +negotiator@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/negotiator/-/negotiator-1.0.0.tgz#b6c91bb47172d69f93cfd7c357bbb529019b5f6a" + integrity sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg== + +node-addon-api@^8.3.0: + version "8.5.0" + resolved "https://registry.yarnpkg.com/node-addon-api/-/node-addon-api-8.5.0.tgz#c91b2d7682fa457d2e1c388150f0dff9aafb8f3f" + integrity sha512-/bRZty2mXUIFY/xU5HLvveNHlswNJej+RnxBjOMkidWfwZzgTbPG1E3K5TOxRLOR+5hX7bSofy8yf1hZevMS8A== + +node-fetch-native@^1.6.6: + version "1.6.7" + resolved "https://registry.yarnpkg.com/node-fetch-native/-/node-fetch-native-1.6.7.tgz#9d09ca63066cc48423211ed4caf5d70075d76a71" + integrity sha512-g9yhqoedzIUm0nTnTqAQvueMPVOuIY16bqgAJJC8XOOubYFNwz6IER9qs0Gq2Xd0+CecCKFjtdDTMA4u4xG06Q== + +node-gyp-build@^4.8.4: + version "4.8.4" + resolved "https://registry.yarnpkg.com/node-gyp-build/-/node-gyp-build-4.8.4.tgz#8a70ee85464ae52327772a90d66c6077a900cfc8" + integrity sha512-LA4ZjwlnUblHVgq0oBF3Jl/6h/Nvs5fzBLwdEF4nuxnFdsfajde4WfxtJr3CaiH+F6ewcIB/q4jQ4UzPyid+CQ== + +normalize-path@^3.0.0, normalize-path@~3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-3.0.0.tgz#0dcd69ff23a1c9b11fd0978316644a0388216a65" + integrity sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA== + +nypm@^0.6.0: + version "0.6.2" + resolved "https://registry.yarnpkg.com/nypm/-/nypm-0.6.2.tgz#467512024948398fafa73cea30a3ed9efc5af071" + integrity sha512-7eM+hpOtrKrBDCh7Ypu2lJ9Z7PNZBdi/8AT3AX8xoCj43BBVHD0hPSTEvMtkMpfs8FCqBGhxB+uToIQimA111g== + dependencies: + citty "^0.1.6" + consola "^3.4.2" + pathe "^2.0.3" + pkg-types "^2.3.0" + tinyexec "^1.0.1" + +object-inspect@^1.13.3: + version "1.13.4" + resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.13.4.tgz#8375265e21bc20d0fa582c22e1b13485d6e00213" + integrity sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew== + +obug@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/obug/-/obug-2.1.1.tgz#2cba74ff241beb77d63055ddf4cd1e9f90b538be" + integrity sha512-uTqF9MuPraAQ+IsnPf366RG4cP9RtUi7MLO1N3KEc+wb0a6yKpeL0lmk2IB1jY5KHPAlTc6T/JRdC/YqxHNwkQ== + +ohash@^2.0.11: + version "2.0.11" + resolved "https://registry.yarnpkg.com/ohash/-/ohash-2.0.11.tgz#60b11e8cff62ca9dee88d13747a5baa145f5900b" + integrity sha512-RdR9FQrFwNBNXAr4GixM8YaRZRJ5PUWbKYbE5eOsrwAjJW0q2REGcf79oYPsLyskQCZG1PLN+S/K1V00joZAoQ== + +on-finished@^2.4.1: + version "2.4.1" + resolved "https://registry.yarnpkg.com/on-finished/-/on-finished-2.4.1.tgz#58c8c44116e54845ad57f14ab10b03533184ac3f" + integrity sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg== + dependencies: + ee-first "1.1.1" + +once@^1.3.0, once@^1.4.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1" + integrity sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w== + dependencies: + wrappy "1" + +parseurl@^1.3.3: + version "1.3.3" + resolved "https://registry.yarnpkg.com/parseurl/-/parseurl-1.3.3.tgz#9da19e7bee8d12dff0513ed5b76957793bc2e8d4" + integrity sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ== + +path-is-absolute@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f" + integrity sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg== + +path-key@^3.1.0: + version "3.1.1" + resolved "https://registry.yarnpkg.com/path-key/-/path-key-3.1.1.tgz#581f6ade658cbba65a0d3380de7753295054f375" + integrity sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q== + +path-to-regexp@^8.0.0: + version "8.3.0" + resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-8.3.0.tgz#aa818a6981f99321003a08987d3cec9c3474cd1f" + integrity sha512-7jdwVIRtsP8MYpdXSwOS0YdD0Du+qOoF/AEPIt88PcCFrZCzx41oxku1jD88hZBwbNUIEfpqvuhjFaMAqMTWnA== + +path-type@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/path-type/-/path-type-4.0.0.tgz#84ed01c0a7ba380afe09d90a8c180dcd9d03043b" + integrity sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw== + +pathe@2.0.3, pathe@^2.0.3: + version "2.0.3" + resolved "https://registry.yarnpkg.com/pathe/-/pathe-2.0.3.tgz#3ecbec55421685b70a9da872b2cff3e1cbed1716" + integrity sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w== + +perfect-debounce@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/perfect-debounce/-/perfect-debounce-1.0.0.tgz#9c2e8bc30b169cc984a58b7d5b28049839591d2a" + integrity sha512-xCy9V055GLEqoFaHoC1SoLIaLmWctgCUaBaWxDZ7/Zx4CTyX7cJQLJOok/orfjZAh9kEYpjJa4d0KcJmCbctZA== + +pg-cloudflare@^1.2.7: + version "1.2.7" + resolved "https://registry.yarnpkg.com/pg-cloudflare/-/pg-cloudflare-1.2.7.tgz#a1f3d226bab2c45ae75ea54d65ec05ac6cfafbef" + integrity sha512-YgCtzMH0ptvZJslLM1ffsY4EuGaU0cx4XSdXLRFae8bPP4dS5xL1tNB3k2o/N64cHJpwU7dxKli/nZ2lUa5fLg== + +pg-connection-string@^2.9.1: + version "2.9.1" + resolved "https://registry.yarnpkg.com/pg-connection-string/-/pg-connection-string-2.9.1.tgz#bb1fd0011e2eb76ac17360dc8fa183b2d3465238" + integrity sha512-nkc6NpDcvPVpZXxrreI/FOtX3XemeLl8E0qFr6F2Lrm/I8WOnaWNhIPK2Z7OHpw7gh5XJThi6j6ppgNoaT1w4w== + +pg-int8@1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/pg-int8/-/pg-int8-1.0.1.tgz#943bd463bf5b71b4170115f80f8efc9a0c0eb78c" + integrity sha512-WCtabS6t3c8SkpDBUlb1kjOs7l66xsGdKpIPZsg4wR+B3+u9UAum2odSsF9tnvxg80h4ZxLWMy4pRjOsFIqQpw== + +pg-pool@^3.10.1: + version "3.10.1" + resolved "https://registry.yarnpkg.com/pg-pool/-/pg-pool-3.10.1.tgz#481047c720be2d624792100cac1816f8850d31b2" + integrity sha512-Tu8jMlcX+9d8+QVzKIvM/uJtp07PKr82IUOYEphaWcoBhIYkoHpLXN3qO59nAI11ripznDsEzEv8nUxBVWajGg== + +pg-protocol@*, pg-protocol@^1.10.3: + version "1.10.3" + resolved "https://registry.yarnpkg.com/pg-protocol/-/pg-protocol-1.10.3.tgz#ac9e4778ad3f84d0c5670583bab976ea0a34f69f" + integrity sha512-6DIBgBQaTKDJyxnXaLiLR8wBpQQcGWuAESkRBX/t6OwA8YsqP+iVSiond2EDy6Y/dsGk8rh/jtax3js5NeV7JQ== + +pg-types@2.2.0, pg-types@^2.2.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/pg-types/-/pg-types-2.2.0.tgz#2d0250d636454f7cfa3b6ae0382fdfa8063254a3" + integrity sha512-qTAAlrEsl8s4OiEQY69wDvcMIdQN6wdz5ojQiOy6YRMuynxenON0O5oCpJI6lshc6scgAY8qvJ2On/p+CXY0GA== + dependencies: + pg-int8 "1.0.1" + postgres-array "~2.0.0" + postgres-bytea "~1.0.0" + postgres-date "~1.0.4" + postgres-interval "^1.1.0" + +pg@^8.16.3: + version "8.16.3" + resolved "https://registry.yarnpkg.com/pg/-/pg-8.16.3.tgz#160741d0b44fdf64680e45374b06d632e86c99fd" + integrity sha512-enxc1h0jA/aq5oSDMvqyW3q89ra6XIIDZgCX9vkMrnz5DFTw/Ny3Li2lFQ+pt3L6MCgm/5o2o8HW9hiJji+xvw== + dependencies: + pg-connection-string "^2.9.1" + pg-pool "^3.10.1" + pg-protocol "^1.10.3" + pg-types "2.2.0" + pgpass "1.0.5" + optionalDependencies: + pg-cloudflare "^1.2.7" + +pgpass@1.0.5: + version "1.0.5" + resolved "https://registry.yarnpkg.com/pgpass/-/pgpass-1.0.5.tgz#9b873e4a564bb10fa7a7dbd55312728d422a223d" + integrity sha512-FdW9r/jQZhSeohs1Z3sI1yxFQNFvMcnmfuj4WBMUTxOrAyLMaTcE1aAMBiTlbMNaXvBCQuVi0R7hd8udDSP7ug== + dependencies: + split2 "^4.1.0" + +picocolors@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/picocolors/-/picocolors-1.1.1.tgz#3d321af3eab939b083c8f929a1d12cda81c26b6b" + integrity sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA== + +picomatch@^2.0.4, picomatch@^2.2.1, picomatch@^2.3.1: + version "2.3.1" + resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.3.1.tgz#3ba3833733646d9d3e4995946c1365a67fb07a42" + integrity sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA== + +picomatch@^4.0.3: + version "4.0.3" + resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-4.0.3.tgz#796c76136d1eead715db1e7bad785dedd695a042" + integrity sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q== + +pkg-types@^2.2.0, pkg-types@^2.3.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/pkg-types/-/pkg-types-2.3.0.tgz#037f2c19bd5402966ff6810e32706558cb5b5726" + integrity sha512-SIqCzDRg0s9npO5XQ3tNZioRY1uK06lA41ynBC1YmFTmnY6FjUjVt6s4LoADmwoig1qqD0oK8h1p/8mlMx8Oig== + dependencies: + confbox "^0.2.2" + exsolve "^1.0.7" + pathe "^2.0.3" + +plimit-lit@^1.2.6: + version "1.6.1" + resolved "https://registry.yarnpkg.com/plimit-lit/-/plimit-lit-1.6.1.tgz#a34594671b31ee8e93c72d505dfb6852eb72374a" + integrity sha512-B7+VDyb8Tl6oMJT9oSO2CW8XC/T4UcJGrwOVoNGwOQsQYhlpfajmrMj5xeejqaASq3V/EqThyOeATEOMuSEXiA== + dependencies: + queue-lit "^1.5.1" + +postcss@^8.5.6: + version "8.5.6" + resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.5.6.tgz#2825006615a619b4f62a9e7426cc120b349a8f3c" + integrity sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg== + dependencies: + nanoid "^3.3.11" + picocolors "^1.1.1" + source-map-js "^1.2.1" + +postgres-array@3.0.4: + version "3.0.4" + resolved "https://registry.yarnpkg.com/postgres-array/-/postgres-array-3.0.4.tgz#4efcaf4d2c688d8bcaa8620ed13f35f299f7528c" + integrity sha512-nAUSGfSDGOaOAEGwqsRY27GPOea7CNipJPOA7lPbdEpx5Kg3qzdP0AaWC5MlhTWV9s4hFX39nomVZ+C4tnGOJQ== + +postgres-array@~2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/postgres-array/-/postgres-array-2.0.0.tgz#48f8fce054fbc69671999329b8834b772652d82e" + integrity sha512-VpZrUqU5A69eQyW2c5CA1jtLecCsN2U/bD6VilrFDWq5+5UIEVO7nazS3TEcHf1zuPYO/sqGvUvW62g86RXZuA== + +postgres-bytea@~1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/postgres-bytea/-/postgres-bytea-1.0.1.tgz#c40b3da0222c500ff1e51c5d7014b60b79697c7a" + integrity sha512-5+5HqXnsZPE65IJZSMkZtURARZelel2oXUEO8rH83VS/hxH5vv1uHquPg5wZs8yMAfdv971IU+kcPUczi7NVBQ== + +postgres-date@~1.0.4: + version "1.0.7" + resolved "https://registry.yarnpkg.com/postgres-date/-/postgres-date-1.0.7.tgz#51bc086006005e5061c591cee727f2531bf641a8" + integrity sha512-suDmjLVQg78nMK2UZ454hAG+OAW+HQPZ6n++TNDUX+L0+uUlLywnoxJKDou51Zm+zTCjrCl0Nq6J9C5hP9vK/Q== + +postgres-interval@^1.1.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/postgres-interval/-/postgres-interval-1.2.0.tgz#b460c82cb1587507788819a06aa0fffdb3544695" + integrity sha512-9ZhXKM/rw350N1ovuWHbGxnGh/SNJ4cnxHiM0rxE4VN41wsg8P8zWn9hv/buK00RP4WvlOyr/RBDiptyxVbkZQ== + dependencies: + xtend "^4.0.0" + +postgres@3.4.7: + version "3.4.7" + resolved "https://registry.yarnpkg.com/postgres/-/postgres-3.4.7.tgz#122f460a808fe300cae53f592108b9906e625345" + integrity sha512-Jtc2612XINuBjIl/QTWsV5UvE8UHuNblcO3vVADSrKsrc6RqGX6lOW1cEo3CM2v0XG4Nat8nI+YM7/f26VxXLw== + +prisma@^7.2.0: + version "7.2.0" + resolved "https://registry.yarnpkg.com/prisma/-/prisma-7.2.0.tgz#ae7a4da8de0cbd078d9400888cd0533abfdca648" + integrity sha512-jSdHWgWOgFF24+nRyyNRVBIgGDQEsMEF8KPHvhBBg3jWyR9fUAK0Nq9ThUmiGlNgq2FA7vSk/ZoCvefod+a8qg== + dependencies: + "@prisma/config" "7.2.0" + "@prisma/dev" "0.17.0" + "@prisma/engines" "7.2.0" + "@prisma/studio-core" "0.9.0" + mysql2 "3.15.3" + postgres "3.4.7" + +proper-lockfile@4.1.2: + version "4.1.2" + resolved "https://registry.yarnpkg.com/proper-lockfile/-/proper-lockfile-4.1.2.tgz#c8b9de2af6b2f1601067f98e01ac66baa223141f" + integrity sha512-TjNPblN4BwAWMXU8s9AEz4JmQxnD1NNL7bNOY/AKUzyamc379FWASUhc/K1pL2noVb+XmZKLL68cjzLsiOAMaA== + dependencies: + graceful-fs "^4.2.4" + retry "^0.12.0" + signal-exit "^3.0.2" + +proxy-addr@^2.0.7: + version "2.0.7" + resolved "https://registry.yarnpkg.com/proxy-addr/-/proxy-addr-2.0.7.tgz#f19fe69ceab311eeb94b42e70e8c2070f9ba1025" + integrity sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg== + dependencies: + forwarded "0.2.0" + ipaddr.js "1.9.1" + +pure-rand@^6.1.0: + version "6.1.0" + resolved "https://registry.yarnpkg.com/pure-rand/-/pure-rand-6.1.0.tgz#d173cf23258231976ccbdb05247c9787957604f2" + integrity sha512-bVWawvoZoBYpp6yIoQtQXHZjmz35RSVHnUOTefl8Vcjr8snTPY1wnpSPMWekcFwbxI6gtmT7rSYPFvz71ldiOA== + +qs@^6.14.0, qs@^6.14.1: + version "6.14.1" + resolved "https://registry.yarnpkg.com/qs/-/qs-6.14.1.tgz#a41d85b9d3902f31d27861790506294881871159" + integrity sha512-4EK3+xJl8Ts67nLYNwqw/dsFVnCf+qR7RgXSK9jEEm9unao3njwMDdmsdvoKBKHzxd7tCYz5e5M+SnMjdtXGQQ== + dependencies: + side-channel "^1.1.0" + +queue-lit@^1.5.1: + version "1.5.2" + resolved "https://registry.yarnpkg.com/queue-lit/-/queue-lit-1.5.2.tgz#83c24d4f4764802377b05a6e5c73017caf3f8747" + integrity sha512-tLc36IOPeMAubu8BkW8YDBV+WyIgKlYU7zUNs0J5Vk9skSZ4JfGlPOqplP0aHdfv7HL0B2Pg6nwiq60Qc6M2Hw== + +queue-microtask@^1.2.2: + version "1.2.3" + resolved "https://registry.yarnpkg.com/queue-microtask/-/queue-microtask-1.2.3.tgz#4929228bbc724dfac43e0efb058caf7b6cfb6243" + integrity sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A== + +range-parser@^1.2.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/range-parser/-/range-parser-1.2.1.tgz#3cf37023d199e1c24d1a55b84800c2f3e6468031" + integrity sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg== + +raw-body@^3.0.1: + version "3.0.2" + resolved "https://registry.yarnpkg.com/raw-body/-/raw-body-3.0.2.tgz#3e3ada5ae5568f9095d84376fd3a49b8fb000a51" + integrity sha512-K5zQjDllxWkf7Z5xJdV0/B0WTNqx6vxG70zJE4N0kBs4LovmEYWJzQGxC9bS9RAKu3bgM40lrd5zoLJ12MQ5BA== + dependencies: + bytes "~3.1.2" + http-errors "~2.0.1" + iconv-lite "~0.7.0" + unpipe "~1.0.0" + +rc9@^2.1.2: + version "2.1.2" + resolved "https://registry.yarnpkg.com/rc9/-/rc9-2.1.2.tgz#6282ff638a50caa0a91a31d76af4a0b9cbd1080d" + integrity sha512-btXCnMmRIBINM2LDZoEmOogIZU7Qe7zn4BpomSKZ/ykbLObuBdvG+mFq11DL6fjH1DRwHhrlgtYWG96bJiC7Cg== + dependencies: + defu "^6.1.4" + destr "^2.0.3" + +readdirp@^4.0.1: + version "4.1.2" + resolved "https://registry.yarnpkg.com/readdirp/-/readdirp-4.1.2.tgz#eb85801435fbf2a7ee58f19e0921b068fc69948d" + integrity sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg== + +readdirp@~3.6.0: + version "3.6.0" + resolved "https://registry.yarnpkg.com/readdirp/-/readdirp-3.6.0.tgz#74a370bd857116e245b29cc97340cd431a02a6c7" + integrity sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA== + dependencies: + picomatch "^2.2.1" + +reflect-metadata@^0.2.2: + version "0.2.2" + resolved "https://registry.yarnpkg.com/reflect-metadata/-/reflect-metadata-0.2.2.tgz#400c845b6cba87a21f2c65c4aeb158f4fa4d9c5b" + integrity sha512-urBwgfrvVP/eAyXx4hluJivBKzuEbSQs9rKWCrCkbSxNv8mxPcUZKeuoF3Uy4mJl3Lwprp6yy5/39VWigZ4K6Q== + +regexp-to-ast@0.5.0: + version "0.5.0" + resolved "https://registry.yarnpkg.com/regexp-to-ast/-/regexp-to-ast-0.5.0.tgz#56c73856bee5e1fef7f73a00f1473452ab712a24" + integrity sha512-tlbJqcMHnPKI9zSrystikWKwHkBqu2a/Sgw01h3zFjvYrMxEDYHzzoMZnUrbIfpTFEsoRnnviOXNCzFiSc54Qw== + +remeda@2.21.3: + version "2.21.3" + resolved "https://registry.yarnpkg.com/remeda/-/remeda-2.21.3.tgz#16551b02f9c7e184b7be226f576d0c6335612adf" + integrity sha512-XXrZdLA10oEOQhLLzEJEiFFSKi21REGAkHdImIb4rt/XXy8ORGXh5HCcpUOsElfPNDb+X6TA/+wkh+p2KffYmg== + dependencies: + type-fest "^4.39.1" + +resolve-pkg-maps@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz#616b3dc2c57056b5588c31cdf4b3d64db133720f" + integrity sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw== + +retry@^0.12.0: + version "0.12.0" + resolved "https://registry.yarnpkg.com/retry/-/retry-0.12.0.tgz#1b42a6266a21f07421d1b0b54b7dc167b01c013b" + integrity sha512-9LkiTwjUh6rT555DtE9rTX+BKByPfrMzEAtnlEtdEwr3Nkffwiihqe2bWADg+OQRjt9gl6ICdmB/ZFDCGAtSow== + +reusify@^1.0.4: + version "1.1.0" + resolved "https://registry.yarnpkg.com/reusify/-/reusify-1.1.0.tgz#0fe13b9522e1473f51b558ee796e08f11f9b489f" + integrity sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw== + +rollup@^4.43.0: + version "4.55.1" + resolved "https://registry.yarnpkg.com/rollup/-/rollup-4.55.1.tgz#4ec182828be440648e7ee6520dc35e9f20e05144" + integrity sha512-wDv/Ht1BNHB4upNbK74s9usvl7hObDnvVzknxqY/E/O3X6rW1U1rV1aENEfJ54eFZDTNo7zv1f5N4edCluH7+A== + dependencies: + "@types/estree" "1.0.8" + optionalDependencies: + "@rollup/rollup-android-arm-eabi" "4.55.1" + "@rollup/rollup-android-arm64" "4.55.1" + "@rollup/rollup-darwin-arm64" "4.55.1" + "@rollup/rollup-darwin-x64" "4.55.1" + "@rollup/rollup-freebsd-arm64" "4.55.1" + "@rollup/rollup-freebsd-x64" "4.55.1" + "@rollup/rollup-linux-arm-gnueabihf" "4.55.1" + "@rollup/rollup-linux-arm-musleabihf" "4.55.1" + "@rollup/rollup-linux-arm64-gnu" "4.55.1" + "@rollup/rollup-linux-arm64-musl" "4.55.1" + "@rollup/rollup-linux-loong64-gnu" "4.55.1" + "@rollup/rollup-linux-loong64-musl" "4.55.1" + "@rollup/rollup-linux-ppc64-gnu" "4.55.1" + "@rollup/rollup-linux-ppc64-musl" "4.55.1" + "@rollup/rollup-linux-riscv64-gnu" "4.55.1" + "@rollup/rollup-linux-riscv64-musl" "4.55.1" + "@rollup/rollup-linux-s390x-gnu" "4.55.1" + "@rollup/rollup-linux-x64-gnu" "4.55.1" + "@rollup/rollup-linux-x64-musl" "4.55.1" + "@rollup/rollup-openbsd-x64" "4.55.1" + "@rollup/rollup-openharmony-arm64" "4.55.1" + "@rollup/rollup-win32-arm64-msvc" "4.55.1" + "@rollup/rollup-win32-ia32-msvc" "4.55.1" + "@rollup/rollup-win32-x64-gnu" "4.55.1" + "@rollup/rollup-win32-x64-msvc" "4.55.1" + fsevents "~2.3.2" + +router@^2.2.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/router/-/router-2.2.0.tgz#019be620b711c87641167cc79b99090f00b146ef" + integrity sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ== + dependencies: + debug "^4.4.0" + depd "^2.0.0" + is-promise "^4.0.0" + parseurl "^1.3.3" + path-to-regexp "^8.0.0" + +run-parallel@^1.1.9: + version "1.2.0" + resolved "https://registry.yarnpkg.com/run-parallel/-/run-parallel-1.2.0.tgz#66d1368da7bdf921eb9d95bd1a9229e7f21a43ee" + integrity sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA== + dependencies: + queue-microtask "^1.2.2" + +safe-buffer@^5.0.1: + version "5.2.1" + resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6" + integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ== + +"safer-buffer@>= 2.1.2 < 3.0.0": + version "2.1.2" + resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a" + integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg== + +semver@^7.5.3, semver@^7.5.4: + version "7.7.3" + resolved "https://registry.yarnpkg.com/semver/-/semver-7.7.3.tgz#4b5f4143d007633a8dc671cd0a6ef9147b8bb946" + integrity sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q== + +send@^1.1.0, send@^1.2.0: + version "1.2.1" + resolved "https://registry.yarnpkg.com/send/-/send-1.2.1.tgz#9eab743b874f3550f40a26867bf286ad60d3f3ed" + integrity sha512-1gnZf7DFcoIcajTjTwjwuDjzuz4PPcY2StKPlsGAQ1+YH20IRVrBaXSWmdjowTJ6u8Rc01PoYOGHXfP1mYcZNQ== + dependencies: + debug "^4.4.3" + encodeurl "^2.0.0" + escape-html "^1.0.3" + etag "^1.8.1" + fresh "^2.0.0" + http-errors "^2.0.1" + mime-types "^3.0.2" + ms "^2.1.3" + on-finished "^2.4.1" + range-parser "^1.2.1" + statuses "^2.0.2" + +seq-queue@^0.0.5: + version "0.0.5" + resolved "https://registry.yarnpkg.com/seq-queue/-/seq-queue-0.0.5.tgz#d56812e1c017a6e4e7c3e3a37a1da6d78dd3c93e" + integrity sha512-hr3Wtp/GZIc/6DAGPDcV4/9WoZhjrkXsi5B/07QgX8tsdc6ilr7BFM6PM6rbdAX1kFSDYeZGLipIZZKyQP0O5Q== + +serve-static@^2.2.0: + version "2.2.1" + resolved "https://registry.yarnpkg.com/serve-static/-/serve-static-2.2.1.tgz#7f186a4a4e5f5b663ad7a4294ff1bf37cf0e98a9" + integrity sha512-xRXBn0pPqQTVQiC8wyQrKs2MOlX24zQ0POGaj0kultvoOCstBQM5yvOhAVSUwOMjQtTvsPWoNCHfPGwaaQJhTw== + dependencies: + encodeurl "^2.0.0" + escape-html "^1.0.3" + parseurl "^1.3.3" + send "^1.2.0" + +setprototypeof@~1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/setprototypeof/-/setprototypeof-1.2.0.tgz#66c9a24a73f9fc28cbe66b09fed3d33dcaf1b424" + integrity sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw== + +shebang-command@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/shebang-command/-/shebang-command-2.0.0.tgz#ccd0af4f8835fbdc265b82461aaf0c36663f34ea" + integrity sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA== + dependencies: + shebang-regex "^3.0.0" + +shebang-regex@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/shebang-regex/-/shebang-regex-3.0.0.tgz#ae16f1644d873ecad843b0307b143362d4c42172" + integrity sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A== + +side-channel-list@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/side-channel-list/-/side-channel-list-1.0.0.tgz#10cb5984263115d3b7a0e336591e290a830af8ad" + integrity sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA== + dependencies: + es-errors "^1.3.0" + object-inspect "^1.13.3" + +side-channel-map@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/side-channel-map/-/side-channel-map-1.0.1.tgz#d6bb6b37902c6fef5174e5f533fab4c732a26f42" + integrity sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA== + dependencies: + call-bound "^1.0.2" + es-errors "^1.3.0" + get-intrinsic "^1.2.5" + object-inspect "^1.13.3" + +side-channel-weakmap@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz#11dda19d5368e40ce9ec2bdc1fb0ecbc0790ecea" + integrity sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A== + dependencies: + call-bound "^1.0.2" + es-errors "^1.3.0" + get-intrinsic "^1.2.5" + object-inspect "^1.13.3" + side-channel-map "^1.0.1" + +side-channel@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/side-channel/-/side-channel-1.1.0.tgz#c3fcff9c4da932784873335ec9765fa94ff66bc9" + integrity sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw== + dependencies: + es-errors "^1.3.0" + object-inspect "^1.13.3" + side-channel-list "^1.0.0" + side-channel-map "^1.0.1" + side-channel-weakmap "^1.0.2" + +siginfo@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/siginfo/-/siginfo-2.0.0.tgz#32e76c70b79724e3bb567cb9d543eb858ccfaf30" + integrity sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g== + +signal-exit@^3.0.2: + version "3.0.7" + resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.7.tgz#a9a1767f8af84155114eaabd73f99273c8f59ad9" + integrity sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ== + +signal-exit@^4.0.1: + version "4.1.0" + resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-4.1.0.tgz#952188c1cbd546070e2dd20d0f41c0ae0530cb04" + integrity sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw== + +slash@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/slash/-/slash-3.0.0.tgz#6539be870c165adbd5240220dbe361f1bc4d4634" + integrity sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q== + +source-map-js@^1.2.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/source-map-js/-/source-map-js-1.2.1.tgz#1ce5650fddd87abc099eda37dcff024c2667ae46" + integrity sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA== + +split2@^4.1.0: + version "4.2.0" + resolved "https://registry.yarnpkg.com/split2/-/split2-4.2.0.tgz#c9c5920904d148bab0b9f67145f245a86aadbfa4" + integrity sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg== + +sqlstring@^2.3.2: + version "2.3.3" + resolved "https://registry.yarnpkg.com/sqlstring/-/sqlstring-2.3.3.tgz#2ddc21f03bce2c387ed60680e739922c65751d0c" + integrity sha512-qC9iz2FlN7DQl3+wjwn3802RTyjCx7sDvfQEXchwa6CWOx07/WVfh91gBmQ9fahw8snwGEWU3xGzOt4tFyHLxg== + +stackback@0.0.2: + version "0.0.2" + resolved "https://registry.yarnpkg.com/stackback/-/stackback-0.0.2.tgz#1ac8a0d9483848d1695e418b6d031a3c3ce68e3b" + integrity sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw== + +statuses@^2.0.1, statuses@^2.0.2, statuses@~2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/statuses/-/statuses-2.0.2.tgz#8f75eecef765b5e1cfcdc080da59409ed424e382" + integrity sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw== + +std-env@3.9.0: + version "3.9.0" + resolved "https://registry.yarnpkg.com/std-env/-/std-env-3.9.0.tgz#1a6f7243b339dca4c9fd55e1c7504c77ef23e8f1" + integrity sha512-UGvjygr6F6tpH7o2qyqR6QYpwraIjKSdtzyBdyytFOHmPZY917kwdwLG0RbOjWOnKmnm3PeHjaoLLMie7kPLQw== + +std-env@^3.10.0: + version "3.10.0" + resolved "https://registry.yarnpkg.com/std-env/-/std-env-3.10.0.tgz#d810b27e3a073047b2b5e40034881f5ea6f9c83b" + integrity sha512-5GS12FdOZNliM5mAOxFRg7Ir0pWz8MdpYm6AY6VPkGpbA7ZzmbzNcBJQ0GPvvyWgcY7QAhCgf9Uy89I03faLkg== + +supports-color@^7.1.0: + version "7.2.0" + resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-7.2.0.tgz#1b7dcdcb32b8138801b3e478ba6a51caa89648da" + integrity sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw== + dependencies: + has-flag "^4.0.0" + +swagger-jsdoc@^6.2.8: + version "6.2.8" + resolved "https://registry.yarnpkg.com/swagger-jsdoc/-/swagger-jsdoc-6.2.8.tgz#6d33d9fb07ff4a7c1564379c52c08989ec7d0256" + integrity sha512-VPvil1+JRpmJ55CgAtn8DIcpBs0bL5L3q5bVQvF4tAW/k/9JYSj7dCpaYCAv5rufe0vcCbBRQXGvzpkWjvLklQ== + dependencies: + commander "6.2.0" + doctrine "3.0.0" + glob "7.1.6" + lodash.mergewith "^4.6.2" + swagger-parser "^10.0.3" + yaml "2.0.0-1" + +swagger-parser@^10.0.3: + version "10.0.3" + resolved "https://registry.yarnpkg.com/swagger-parser/-/swagger-parser-10.0.3.tgz#04cb01c18c3ac192b41161c77f81e79309135d03" + integrity sha512-nF7oMeL4KypldrQhac8RyHerJeGPD1p2xDh900GPvc+Nk7nWP6jX2FcC7WmkinMoAmoO774+AFXcWsW8gMWEIg== + dependencies: + "@apidevtools/swagger-parser" "10.0.3" + +swagger-ui-dist@>=5.0.0: + version "5.31.0" + resolved "https://registry.yarnpkg.com/swagger-ui-dist/-/swagger-ui-dist-5.31.0.tgz#a2529f844c83b7e85c2caaf2c64a8277dd71db98" + integrity sha512-zSUTIck02fSga6rc0RZP3b7J7wgHXwLea8ZjgLA3Vgnb8QeOl3Wou2/j5QkzSGeoz6HusP/coYuJl33aQxQZpg== + dependencies: + "@scarf/scarf" "=1.4.0" + +swagger-ui-express@^5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/swagger-ui-express/-/swagger-ui-express-5.0.1.tgz#fb8c1b781d2793a6bd2f8a205a3f4bd6fa020dd8" + integrity sha512-SrNU3RiBGTLLmFU8GIJdOdanJTl4TOmT27tt3bWWHppqYmAZ6IDuEuBvMU6nZq0zLEe6b/1rACXCgLZqO6ZfrA== + dependencies: + swagger-ui-dist ">=5.0.0" + +tinybench@^2.9.0: + version "2.9.0" + resolved "https://registry.yarnpkg.com/tinybench/-/tinybench-2.9.0.tgz#103c9f8ba6d7237a47ab6dd1dcff77251863426b" + integrity sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg== + +tinyexec@^1.0.1, tinyexec@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/tinyexec/-/tinyexec-1.0.2.tgz#bdd2737fe2ba40bd6f918ae26642f264b99ca251" + integrity sha512-W/KYk+NFhkmsYpuHq5JykngiOCnxeVL8v8dFnqxSD8qEEdRfXk1SDM6JzNqcERbcGYj9tMrDQBYV9cjgnunFIg== + +tinyglobby@^0.2.15: + version "0.2.15" + resolved "https://registry.yarnpkg.com/tinyglobby/-/tinyglobby-0.2.15.tgz#e228dd1e638cea993d2fdb4fcd2d4602a79951c2" + integrity sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ== + dependencies: + fdir "^6.5.0" + picomatch "^4.0.3" + +tinyrainbow@^3.0.3: + version "3.0.3" + resolved "https://registry.yarnpkg.com/tinyrainbow/-/tinyrainbow-3.0.3.tgz#984a5b1c1b25854a9b6bccbe77964d0593d1ea42" + integrity sha512-PSkbLUoxOFRzJYjjxHJt9xro7D+iilgMX/C9lawzVuYiIdcihh9DXmVibBe8lmcFrRi/VzlPjBxbN7rH24q8/Q== + +to-regex-range@^5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/to-regex-range/-/to-regex-range-5.0.1.tgz#1648c44aae7c8d988a326018ed72f5b4dd0392e4" + integrity sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ== + dependencies: + is-number "^7.0.0" + +toidentifier@~1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/toidentifier/-/toidentifier-1.0.1.tgz#3be34321a88a820ed1bd80dfaa33e479fbb8dd35" + integrity sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA== + +tsc-alias@^1.8.16: + version "1.8.16" + resolved "https://registry.yarnpkg.com/tsc-alias/-/tsc-alias-1.8.16.tgz#dbc74e797071801c7284f1a478259de920f852d4" + integrity sha512-QjCyu55NFyRSBAl6+MTFwplpFcnm2Pq01rR/uxfqJoLMm6X3O14KEGtaSDZpJYaE1bJBGDjD0eSuiIWPe2T58g== + dependencies: + chokidar "^3.5.3" + commander "^9.0.0" + get-tsconfig "^4.10.0" + globby "^11.0.4" + mylas "^2.1.9" + normalize-path "^3.0.0" + plimit-lit "^1.2.6" + +tsconfck@^3.0.3: + version "3.1.6" + resolved "https://registry.yarnpkg.com/tsconfck/-/tsconfck-3.1.6.tgz#da1f0b10d82237ac23422374b3fce1edb23c3ead" + integrity sha512-ks6Vjr/jEw0P1gmOVwutM3B7fWxoWBL2KRDb1JfqGVawBmO5UsvmWOQFGHBPl5yxYz4eERr19E6L7NMv+Fej4w== + +tsx@^4.21.0: + version "4.21.0" + resolved "https://registry.yarnpkg.com/tsx/-/tsx-4.21.0.tgz#32aa6cf17481e336f756195e6fe04dae3e6308b1" + integrity sha512-5C1sg4USs1lfG0GFb2RLXsdpXqBSEhAaA/0kPL01wxzpMqLILNxIxIOKiILz+cdg/pLnOUxFYOR5yhHU666wbw== + dependencies: + esbuild "~0.27.0" + get-tsconfig "^4.7.5" + optionalDependencies: + fsevents "~2.3.3" + +type-fest@^4.39.1: + version "4.41.0" + resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-4.41.0.tgz#6ae1c8e5731273c2bf1f58ad39cbae2c91a46c58" + integrity sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA== + +type-is@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/type-is/-/type-is-2.0.1.tgz#64f6cf03f92fce4015c2b224793f6bdd4b068c97" + integrity sha512-OZs6gsjF4vMp32qrCbiVSkrFmXtG/AZhY3t0iAMrMBiAZyV9oALtXO8hsrHbMXF9x6L3grlFuwW2oAz7cav+Gw== + dependencies: + content-type "^1.0.5" + media-typer "^1.1.0" + mime-types "^3.0.0" + +typescript@^5.9.3: + version "5.9.3" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.9.3.tgz#5b4f59e15310ab17a216f5d6cf53ee476ede670f" + integrity sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw== + +undici-types@~7.16.0: + version "7.16.0" + resolved "https://registry.yarnpkg.com/undici-types/-/undici-types-7.16.0.tgz#ffccdff36aea4884cbfce9a750a0580224f58a46" + integrity sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw== + +unpipe@~1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/unpipe/-/unpipe-1.0.0.tgz#b2bf4ee8514aae6165b4817829d21b2ef49904ec" + integrity sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ== + +uuidv7@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/uuidv7/-/uuidv7-1.1.0.tgz#6ef58df2dd7377083de1f70eec29cb66a0baf30c" + integrity sha512-2VNnOC0+XQlwogChUDzy6pe8GQEys9QFZBGOh54l6qVfwoCUwwRvk7rDTgaIsRgsF5GFa5oiNg8LqXE3jofBBg== + +valibot@1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/valibot/-/valibot-1.2.0.tgz#8fc720d9e4082ba16e30a914064a39619b2f1d6f" + integrity sha512-mm1rxUsmOxzrwnX5arGS+U4T25RdvpPjPN4yR0u9pUBov9+zGVtO84tif1eY4r6zWxVxu3KzIyknJy3rxfRZZg== + +validator@^13.7.0: + version "13.15.26" + resolved "https://registry.yarnpkg.com/validator/-/validator-13.15.26.tgz#36c3deeab30e97806a658728a155c66fcaa5b944" + integrity sha512-spH26xU080ydGggxRyR1Yhcbgx+j3y5jbNXk/8L+iRvdIEQ4uTRH2Sgf2dokud6Q4oAtsbNvJ1Ft+9xmm6IZcA== + +vary@^1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/vary/-/vary-1.1.2.tgz#2299f02c6ded30d4a5961b0b9f74524a18f634fc" + integrity sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg== + +vite-tsconfig-paths@^6.0.4: + version "6.0.4" + resolved "https://registry.yarnpkg.com/vite-tsconfig-paths/-/vite-tsconfig-paths-6.0.4.tgz#637e7608065acce67962cdd4bc3bbd47817b0cc0" + integrity sha512-iIsEJ+ek5KqRTK17pmxtgIxXtqr3qDdE6OxrP9mVeGhVDNXRJTKN/l9oMbujTQNzMLe6XZ8qmpztfbkPu2TiFQ== + dependencies: + debug "^4.1.1" + globrex "^0.1.2" + tsconfck "^3.0.3" + vite "*" + +vite@*, "vite@^6.0.0 || ^7.0.0": + version "7.3.1" + resolved "https://registry.yarnpkg.com/vite/-/vite-7.3.1.tgz#7f6cfe8fb9074138605e822a75d9d30b814d6507" + integrity sha512-w+N7Hifpc3gRjZ63vYBXA56dvvRlNWRczTdmCBBa+CotUzAPf5b7YMdMR/8CQoeYE5LX3W4wj6RYTgonm1b9DA== + dependencies: + esbuild "^0.27.0" + fdir "^6.5.0" + picomatch "^4.0.3" + postcss "^8.5.6" + rollup "^4.43.0" + tinyglobby "^0.2.15" + optionalDependencies: + fsevents "~2.3.3" + +vitest@^4.0.17: + version "4.0.17" + resolved "https://registry.yarnpkg.com/vitest/-/vitest-4.0.17.tgz#0e39e67a909a132afe434ee1278bdcf0c17fd063" + integrity sha512-FQMeF0DJdWY0iOnbv466n/0BudNdKj1l5jYgl5JVTwjSsZSlqyXFt/9+1sEyhR6CLowbZpV7O1sCHrzBhucKKg== + dependencies: + "@vitest/expect" "4.0.17" + "@vitest/mocker" "4.0.17" + "@vitest/pretty-format" "4.0.17" + "@vitest/runner" "4.0.17" + "@vitest/snapshot" "4.0.17" + "@vitest/spy" "4.0.17" + "@vitest/utils" "4.0.17" + es-module-lexer "^1.7.0" + expect-type "^1.2.2" + magic-string "^0.30.21" + obug "^2.1.1" + pathe "^2.0.3" + picomatch "^4.0.3" + std-env "^3.10.0" + tinybench "^2.9.0" + tinyexec "^1.0.2" + tinyglobby "^0.2.15" + tinyrainbow "^3.0.3" + vite "^6.0.0 || ^7.0.0" + why-is-node-running "^2.3.0" + +which@^2.0.1: + version "2.0.2" + resolved "https://registry.yarnpkg.com/which/-/which-2.0.2.tgz#7c6a8dd0a636a0327e10b59c9286eee93f3f51b1" + integrity sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA== + dependencies: + isexe "^2.0.0" + +why-is-node-running@^2.3.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/why-is-node-running/-/why-is-node-running-2.3.0.tgz#a3f69a97107f494b3cdc3bdddd883a7d65cebf04" + integrity sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w== + dependencies: + siginfo "^2.0.0" + stackback "0.0.2" + +wrappy@1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" + integrity sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ== + +xtend@^4.0.0: + version "4.0.2" + resolved "https://registry.yarnpkg.com/xtend/-/xtend-4.0.2.tgz#bb72779f5fa465186b1f438f674fa347fdb5db54" + integrity sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ== + +yaml@2.0.0-1: + version "2.0.0-1" + resolved "https://registry.yarnpkg.com/yaml/-/yaml-2.0.0-1.tgz#8c3029b3ee2028306d5bcf396980623115ff8d18" + integrity sha512-W7h5dEhywMKenDJh2iX/LABkbFnBxasD27oyXWDS/feDsxiw0dD5ncXdYXgkvAsXIY2MpW/ZKkr9IU30DBdMNQ== + +z-schema@^5.0.1: + version "5.0.6" + resolved "https://registry.yarnpkg.com/z-schema/-/z-schema-5.0.6.tgz#46d6a687b15e4a4369e18d6cb1c7b8618fc256c5" + integrity sha512-+XR1GhnWklYdfr8YaZv/iu+vY+ux7V5DS5zH1DQf6bO5ufrt/5cgNhVO5qyhsjFXvsqQb/f08DWE9b6uPscyAg== + dependencies: + lodash.get "^4.4.2" + lodash.isequal "^4.5.0" + validator "^13.7.0" + optionalDependencies: + commander "^10.0.0" + +zeptomatch@2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/zeptomatch/-/zeptomatch-2.0.2.tgz#8ef6a2288a8cde753a02327305ac912914416ab4" + integrity sha512-H33jtSKf8Ijtb5BW6wua3G5DhnFjbFML36eFu+VdOoVY4HD9e7ggjqdM6639B+L87rjnR6Y+XeRzBXZdy52B/g== + dependencies: + grammex "^3.1.10" + +zod@^4.3.5: + version "4.3.5" + resolved "https://registry.yarnpkg.com/zod/-/zod-4.3.5.tgz#aeb269a6f9fc259b1212c348c7c5432aaa474d2a" + integrity sha512-k7Nwx6vuWx1IJ9Bjuf4Zt1PEllcwe7cls3VNzm4CQ1/hgtFUK2bRNG3rvnpPUhFjmqJKAKtjV576KnUkHocg/g==