diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..92c795e --- /dev/null +++ b/.env.example @@ -0,0 +1,3 @@ +DATABASE_URL="postgresql://postgres:postgres@localhost:5432/locations_api" +PORT="8080" +PAGE_SIZE="10" diff --git a/.eslintrc.js b/.eslintrc.js deleted file mode 100644 index f6c62be..0000000 --- a/.eslintrc.js +++ /dev/null @@ -1,24 +0,0 @@ -module.exports = { - parser: '@typescript-eslint/parser', - parserOptions: { - project: 'tsconfig.json', - sourceType: 'module', - }, - plugins: ['@typescript-eslint/eslint-plugin'], - extends: [ - 'plugin:@typescript-eslint/recommended', - 'plugin:prettier/recommended', - ], - root: true, - env: { - node: true, - jest: true, - }, - ignorePatterns: ['.eslintrc.js'], - rules: { - '@typescript-eslint/interface-name-prefix': 'off', - '@typescript-eslint/explicit-function-return-type': 'off', - '@typescript-eslint/explicit-module-boundary-types': 'off', - '@typescript-eslint/no-explicit-any': 'off', - }, -}; diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 0000000..1bfd42d --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,33 @@ +version: 2 +updates: + - package-ecosystem: npm + directory: / + schedule: + interval: weekly + day: monday + time: '06:00' + timezone: Africa/Dar_es_Salaam + open-pull-requests-limit: 10 + groups: + npm-minor-patch: + patterns: + - '*' + update-types: + - minor + - patch + + - package-ecosystem: github-actions + directory: / + schedule: + interval: weekly + day: monday + time: '06:15' + timezone: Africa/Dar_es_Salaam + open-pull-requests-limit: 5 + groups: + github-actions-minor-patch: + patterns: + - '*' + update-types: + - minor + - patch diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..f79ac81 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,64 @@ +name: CI + +on: + pull_request: + push: + branches: + - main + - master + +jobs: + verify: + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + node-version: + - '20.19.0' + - '22' + services: + postgres: + image: postgres:16 + env: + POSTGRES_DB: locations_api + POSTGRES_PASSWORD: postgres + POSTGRES_USER: postgres + options: >- + --health-cmd="pg_isready -U postgres -d locations_api" + --health-interval=10s + --health-timeout=5s + --health-retries=5 + ports: + - 5432:5432 + env: + DATABASE_URL: postgresql://postgres:postgres@localhost:5432/locations_api + NODE_ENV: test + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Setup pnpm + uses: pnpm/action-setup@v4 + with: + version: 10.7.0 + + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: ${{ matrix.node-version }} + cache: pnpm + + - name: Install dependencies + run: pnpm install --frozen-lockfile + + - name: Lint, typecheck, and build + run: pnpm build:ci + + - name: Run migrations + run: pnpm db:migrate + + - name: Seed database + run: pnpm db:seed + + - name: Run tests + run: pnpm test diff --git a/.gitignore b/.gitignore index ec80321..64764fd 100644 --- a/.gitignore +++ b/.gitignore @@ -2,6 +2,9 @@ /dist /node_modules /prisma/node_modules +/src/generated +/generated +/coverage # Logs logs diff --git a/README.md b/README.md index 9385f68..a666012 100644 --- a/README.md +++ b/README.md @@ -1,135 +1,153 @@ # Location Data API -A RESTful API for Tanzania location data including countries, regions, districts, wards, and places. +Compatibility-first REST API for Tanzania location data backed by PostgreSQL and Prisma ORM 7. -## Features +## What Changed -- Hierarchical location data with proper relationships -- RESTful API with clean structure -- Input validation and error handling -- Pagination and search support +- Prisma ORM 7 with a generated client in `src/generated/prisma` +- Dual API base paths: `/v1` is canonical and `/api` remains as a compatibility alias +- Reproducible Prisma migration + seed flow for local development and CI +- Request IDs, structured HTTP logs, cache headers, and safer full-text search +- Dependabot and GitHub Actions for ongoing dependency updates and verification -## Tech Stack +## Requirements -- Node.js / Express -- PostgreSQL -- Prisma ORM -- Jest & Supertest for testing +- Node.js `>=20.19.0` +- pnpm `10.7.0+` +- PostgreSQL `16+` recommended -## Getting Started +## Quick Start -### Prerequisites +1. Install dependencies. -- Node.js LTS -- [Tanzania Locations Database](https://github.com/HackEAC/tanzania-locations-db) running πŸƒπŸΏβ€β™‚οΈπŸƒπŸΏβ€β™€οΈ -- npm or yarn - -### Installation + ```bash + pnpm install + ``` -1. Clone the repository +2. Create your environment file. ```bash - git clone https://github.com/yourusername/locations-API.git - cd locations-API + cp .env.example .env ``` -2. Install dependencies +3. Start PostgreSQL and update `DATABASE_URL` if needed. + +4. Apply the checked-in schema and seed deterministic fixture data. ```bash - npm install + pnpm db:migrate + pnpm db:seed ``` -3. Create `.env` for your environment + > ⚠️ **WARNING**: `pnpm db:seed` is destructive β€” it truncates all tables before inserting fixture data. Do not run it against a database you need to preserve. + +5. Start the development server. ```bash - echo DATABASE_URL="postgresql://postgres:password@localhost:5433/locations" > .env + pnpm dev ``` - The above `DATABASE_URL` is for the [Tanzania-locations-database](https://github.com/HackEAC/tanzania-locations-db) Docker container provision. +## Useful Scripts -4. Sync up your API with the locations database: +```bash +pnpm db:migrate +pnpm db:seed +pnpm lint +pnpm typecheck +pnpm build +pnpm test +pnpm test:ci +pnpm openapi:json +``` - - **a.** Pull existing DB schema into your Prisma schema +## Migration Behavior - ```bash - pnpx prisma db pull - ``` +- `pnpm db:migrate` is the supported entrypoint for schema changes in this repo +- On a fresh database it bootstraps the historical `init` migration, marks that baseline as applied, and then deploys later migrations +- On an existing database that already has the older Prisma migration history, it only applies the new additive migrations +- Prefer `pnpm db:migrate` over calling `prisma migrate deploy` directly - - **b.** Create migration init files +## Testing - ```bash - mkdir prisma/migrations/init - ``` +- `pnpm test` expects a database that has already been migrated and seeded +- `pnpm test:ci` runs `generate`, `db:migrate`, `db:seed`, and the Jest suite in one command +- For a clean local verification flow, run: - - **c.** Mark the current schema as baseline + ```bash + pnpm db:migrate + pnpm db:seed + pnpm test + ``` - ```bash - pnpx prisma migrate diff \ - --from-empty \ - --to-schema-datamodel prisma/schema.prisma \ - --script > prisma/migrations/init/migration.sql - ``` +## API Base Paths - - **d.** Create migration history manually +- `/v1`: canonical path for current integrations +- `/api`: compatibility alias for older consumers - ```bash - pnpx prisma migrate resolve --applied init - ``` +Both base paths return the same payload shapes. - βœ… Now you're synced! Future `prisma migrate dev` or `migrate deploy` will work cleanly. +## Main Endpoints -5. Start development server +### Collections - ```bash - npm run dev - ``` +- `GET /v1/countries` +- `GET /v1/regions` +- `GET /v1/districts` +- `GET /v1/wards` +- `GET /v1/places` -6. Build application +### Detail Routes - ```bash - npm run build - ``` +- `GET /v1/countries/:id` +- `GET /v1/regions/:regionCode` +- `GET /v1/districts/:districtCode` +- `GET /v1/wards/:wardCode` +- `GET /v1/places/:id` -7. Start production server +### Nested Routes - ```bash - npm run start - ``` +- `GET /v1/countries/:countryCode/regions` +- `GET /v1/regions/:regionCode/districts` +- `GET /v1/districts/:districtCode/wards` +- `GET /v1/wards/:wardCode/places` -## API Endpoints +### Search -### Countries -- `GET /v1/countries` - Get all countries -- `GET /v1/countries/:id` - Get country by ID +- `GET /v1/search?q=nzuguni` -### Regions -- `GET /v1/regions` - Get all regions -- `GET /v1/regions/:regionCode` - Get region by code -- `GET /v1/regions/:regionCode/districts` - Get districts in a region +## Collection Query Parameters -### Districts -- `GET /v1/districts` - Get all districts -- `GET /v1/districts/:districtCode` - Get district by code -- `GET /v1/districts/:districtCode/wards` - Get wards in a district +All collection endpoints support: -### Wards -- `GET /v1/wards` - Get all wards -- `GET /v1/wards/:wardCode` - Get ward by code -- `GET /v1/wards/:wardCode/places` - Get places in a ward +- `page` +- `limit` +- `search` -### Places -- `GET /v1/places` - Get all places -- `GET /v1/places/:id` - Get place by ID +Additional filters: -### Search -- `GET /v1/search?q=nzuguni` - Fulltext search for locations by name +- `/regions`: `countryId` +- `/districts`: `countryId`, `regionCode` +- `/wards`: `countryId`, `regionCode`, `districtCode` +- `/places`: `countryId`, `regionCode`, `districtCode`, `wardCode` -## Running Tests +## Docs -```bash -npm test -``` +- Swagger UI: `http://localhost:8080/api-docs` +- OpenAPI JSON: `http://localhost:8080/openapi.json` +- `pnpm openapi:json` exports the spec to `generated/openapi/openapi.json` + +## Database Notes + +- Prisma configuration lives in [prisma.config.ts](./prisma.config.ts) +- The checked-in migration chain now creates the `general.search_vector` column, trigger, and GIN index used by `/search` +- Seed data is intentionally small and deterministic so CI and tests can assert exact results +- The seed is destructive by design for local/CI fixture setup; do not run it against a database you expect to preserve unchanged + +## Dependency Automation + +- `.github/dependabot.yml` opens weekly update PRs for npm packages and GitHub Actions +- `.github/workflows/ci.yml` validates every PR against Postgres on Node `20.19.0` and `22` ## License -This project is licensed under the CopyLeft License – see the LICENSE file for details. +This project is licensed under the CopyLeft License. See [LICENSE](./LICENSE). diff --git a/eslint.config.js b/eslint.config.js new file mode 100644 index 0000000..071e9d0 --- /dev/null +++ b/eslint.config.js @@ -0,0 +1,34 @@ +import js from '@eslint/js'; +import globals from 'globals'; +import tseslint from 'typescript-eslint'; + +export default tseslint.config( + { + ignores: ['coverage/**', 'dist/**', 'node_modules/**', 'src/generated/**'], + }, + js.configs.recommended, + ...tseslint.configs.recommendedTypeChecked, + { + files: ['**/*.ts'], + languageOptions: { + globals: { + ...globals.node, + ...globals.jest, + }, + parserOptions: { + project: './tsconfig.json', + tsconfigRootDir: import.meta.dirname, + }, + }, + rules: { + '@typescript-eslint/no-misused-promises': 'off', + '@typescript-eslint/no-namespace': 'off', + '@typescript-eslint/no-explicit-any': 'off', + '@typescript-eslint/no-unsafe-argument': 'off', + '@typescript-eslint/no-unsafe-assignment': 'off', + '@typescript-eslint/no-unsafe-member-access': 'off', + '@typescript-eslint/no-floating-promises': 'error', + '@typescript-eslint/require-await': 'off', + }, + }, +); diff --git a/jest.config.cjs b/jest.config.cjs new file mode 100644 index 0000000..8d11c9f --- /dev/null +++ b/jest.config.cjs @@ -0,0 +1,18 @@ +/** @type {import('ts-jest').JestConfigWithTsJest} */ +module.exports = { + testEnvironment: 'node', + extensionsToTreatAsEsm: ['.ts'], + transform: { + '^.+\\.tsx?$': [ + 'ts-jest', + { + tsconfig: './tsconfig.test.json', + useESM: true, + }, + ], + }, + moduleNameMapper: { + '^(\\.{1,2}/.*)\\.js$': '$1', + }, + setupFiles: ['dotenv/config'], +}; diff --git a/jest.config.js b/jest.config.js deleted file mode 100644 index fa584a2..0000000 --- a/jest.config.js +++ /dev/null @@ -1,7 +0,0 @@ -/** @type {import('ts-jest').JestConfigWithTsJest} **/ -module.exports = { - testEnvironment: "node", - transform: { - "^.+\.tsx?$": ["ts-jest",{}], - }, -}; diff --git a/package.json b/package.json index 77c5199..c43fb38 100644 --- a/package.json +++ b/package.json @@ -2,19 +2,25 @@ "name": "locations_api", "version": "2.0.0", "description": "Tanzania Locations API is a gateway that provides information about various locations in Tanzania, including regions, districts, wards, and places.", + "type": "module", "main": "./dist/server.js", "engines": { - "node": ">=18.0.0" + "node": ">=20.19.0" }, "scripts": { - "dev": "node --experimental-strip-types --watch ./server.ts", - "generate": "prisma generate --no-engine", - "generate:ci": "prisma generate", - "build:ci": "npm run generate:ci && tsc --noEmit", - "build": "npm run generate && tsc", + "dev": "tsx watch server.ts", + "generate": "prisma generate", + "db:migrate": "tsx scripts/migrate.ts", + "db:seed": "prisma db seed", + "lint": "pnpm generate && eslint server.ts \"src/**/*.ts\" \"tests/**/*.ts\" \"scripts/**/*.ts\" \"prisma/**/*.ts\"", + "typecheck": "pnpm generate && tsc --noEmit", + "build:ci": "pnpm generate && pnpm lint && pnpm typecheck && pnpm build", + "build": "pnpm generate && tsc -p tsconfig.build.json", "start": "node ./dist/server.js", - "test": "jest", - "test:watch": "jest --watch" + "test": "pnpm generate && node --experimental-vm-modules ./node_modules/jest/bin/jest.js --runInBand", + "test:ci": "pnpm generate && pnpm db:migrate && pnpm db:seed && node --experimental-vm-modules ./node_modules/jest/bin/jest.js --runInBand", + "test:watch": "node --experimental-vm-modules ./node_modules/jest/bin/jest.js --watch", + "openapi:json": "tsx scripts/export-openapi.ts" }, "keywords": [ "Tanzania", @@ -29,30 +35,37 @@ "license": "copyleft", "packageManager": "pnpm@10.7.0", "dependencies": { - "@prisma/client": "^6.6.0", - "body-parser": "^2.2.0", - "cors": "^2.8.5", - "dotenv": "^16.5.0", - "express": "^5.1.0", + "@prisma/adapter-pg": "^7.5.0", + "@prisma/client": "^7.5.0", + "cors": "^2.8.6", + "dotenv": "^17.3.1", + "express": "^5.2.1", "helmet": "^8.1.0", - "morgan": "^1.10.0", + "morgan": "^1.10.1", + "pg": "^8.20.0", "swagger-jsdoc": "^6.2.8", "swagger-ui-express": "^5.0.1", "zod": "^3.24.2" }, "devDependencies": { - "@types/cors": "^2.8.17", - "@types/express": "^5.0.1", + "@eslint/js": "^9.39.4", + "@types/cors": "^2.8.19", + "@types/express": "^5.0.6", "@types/jest": "^29.5.14", - "@types/morgan": "^1.9.9", - "@types/node": "^22.14.0", - "@types/supertest": "^6.0.3", + "@types/morgan": "^1.9.10", + "@types/node": "^25.5.0", + "@types/pg": "^8.15.6", + "@types/supertest": "^7.0.0", "@types/swagger-jsdoc": "^6.0.4", "@types/swagger-ui-express": "^4.1.8", + "eslint": "^9.39.4", + "globals": "^15.15.0", "jest": "^29.7.0", - "prisma": "^6.6.0", - "supertest": "^7.1.0", - "ts-jest": "^29.3.1", - "typescript": "^5.8.3" + "prisma": "^7.5.0", + "supertest": "^7.2.2", + "ts-jest": "^29.4.6", + "tsx": "^4.21.0", + "typescript": "^5.9.3", + "typescript-eslint": "^8.57.0" } } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 849e22f..011b29b 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -8,76 +8,97 @@ importers: .: dependencies: + '@prisma/adapter-pg': + specifier: ^7.5.0 + version: 7.5.0 '@prisma/client': - specifier: ^6.6.0 - version: 6.6.0(prisma@6.6.0(typescript@5.8.3))(typescript@5.8.3) - body-parser: - specifier: ^2.2.0 - version: 2.2.0 + specifier: ^7.5.0 + version: 7.5.0(prisma@7.5.0(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(typescript@5.9.3))(typescript@5.9.3) cors: - specifier: ^2.8.5 - version: 2.8.5 + specifier: ^2.8.6 + version: 2.8.6 dotenv: - specifier: ^16.5.0 - version: 16.5.0 + specifier: ^17.3.1 + version: 17.3.1 express: - specifier: ^5.1.0 - version: 5.1.0 + specifier: ^5.2.1 + version: 5.2.1 helmet: specifier: ^8.1.0 version: 8.1.0 morgan: - specifier: ^1.10.0 - version: 1.10.0 + specifier: ^1.10.1 + version: 1.10.1 + pg: + specifier: ^8.20.0 + version: 8.20.0 swagger-jsdoc: specifier: ^6.2.8 version: 6.2.8(openapi-types@12.1.3) swagger-ui-express: specifier: ^5.0.1 - version: 5.0.1(express@5.1.0) + version: 5.0.1(express@5.2.1) zod: specifier: ^3.24.2 version: 3.24.2 devDependencies: + '@eslint/js': + specifier: ^9.39.4 + version: 9.39.4 '@types/cors': - specifier: ^2.8.17 - version: 2.8.17 + specifier: ^2.8.19 + version: 2.8.19 '@types/express': - specifier: ^5.0.1 - version: 5.0.1 + specifier: ^5.0.6 + version: 5.0.6 '@types/jest': specifier: ^29.5.14 version: 29.5.14 '@types/morgan': - specifier: ^1.9.9 - version: 1.9.9 + specifier: ^1.9.10 + version: 1.9.10 '@types/node': - specifier: ^22.14.0 - version: 22.14.0 + specifier: ^25.5.0 + version: 25.5.0 + '@types/pg': + specifier: ^8.15.6 + version: 8.18.0 '@types/supertest': - specifier: ^6.0.3 - version: 6.0.3 + specifier: ^7.0.0 + version: 7.2.0 '@types/swagger-jsdoc': specifier: ^6.0.4 version: 6.0.4 '@types/swagger-ui-express': specifier: ^4.1.8 version: 4.1.8 + eslint: + specifier: ^9.39.4 + version: 9.39.4(jiti@2.6.1) + globals: + specifier: ^15.15.0 + version: 15.15.0 jest: specifier: ^29.7.0 - version: 29.7.0(@types/node@22.14.0) + version: 29.7.0(@types/node@25.5.0) prisma: - specifier: ^6.6.0 - version: 6.6.0(typescript@5.8.3) + specifier: ^7.5.0 + version: 7.5.0(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(typescript@5.9.3) supertest: - specifier: ^7.1.0 - version: 7.1.0 + specifier: ^7.2.2 + version: 7.2.2 ts-jest: - specifier: ^29.3.1 - version: 29.3.1(@babel/core@7.26.10)(@jest/transform@29.7.0)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@7.26.10))(esbuild@0.25.2)(jest@29.7.0(@types/node@22.14.0))(typescript@5.8.3) + specifier: ^29.4.6 + version: 29.4.6(@babel/core@7.26.10)(@jest/transform@29.7.0)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@7.26.10))(jest-util@29.7.0)(jest@29.7.0(@types/node@25.5.0))(typescript@5.9.3) + tsx: + specifier: ^4.21.0 + version: 4.21.0 typescript: - specifier: ^5.8.3 - version: 5.8.3 + specifier: ^5.9.3 + version: 5.9.3 + typescript-eslint: + specifier: ^8.57.0 + version: 8.57.0(eslint@9.39.4(jiti@2.6.1))(typescript@5.9.3) packages: @@ -261,156 +282,248 @@ packages: '@bcoe/v8-coverage@0.2.3': resolution: {integrity: sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==} - '@esbuild/aix-ppc64@0.25.2': - resolution: {integrity: sha512-wCIboOL2yXZym2cgm6mlA742s9QeJ8DjGVaL39dLN4rRwrOgOyYSnOaFPhKZGLb2ngj4EyfAFjsNJwPXZvseag==} + '@chevrotain/cst-dts-gen@10.5.0': + resolution: {integrity: sha512-lhmC/FyqQ2o7pGK4Om+hzuDrm9rhFYIJ/AXoQBeongmn870Xeb0L6oGEiuR8nohFNL5sMaQEJWCxr1oIVIVXrw==} + + '@chevrotain/gast@10.5.0': + resolution: {integrity: sha512-pXdMJ9XeDAbgOWKuD1Fldz4ieCs6+nLNmyVhe2gZVqoO7v8HXuHYs5OV2EzUtbuai37TlOAQHrTDvxMnvMJz3A==} + + '@chevrotain/types@10.5.0': + resolution: {integrity: sha512-f1MAia0x/pAVPWH/T73BJVyO2XU5tI4/iE7cnxb7tqdNTNhQI3Uq3XkqcoteTmD4t1aM0LbHCJOhgIDn07kl2A==} + + '@chevrotain/utils@10.5.0': + resolution: {integrity: sha512-hBzuU5+JjB2cqNZyszkDHZgOSrUUT8V3dhgRl8Q9Gp6dAj/H5+KILGjbhDpc3Iy9qmqlm/akuOI2ut9VUtzJxQ==} + + '@electric-sql/pglite-socket@0.0.20': + resolution: {integrity: sha512-J5nLGsicnD9wJHnno9r+DGxfcZWh+YJMCe0q/aCgtG6XOm9Z7fKeite8IZSNXgZeGltSigM9U/vAWZQWdgcSFg==} + hasBin: true + peerDependencies: + '@electric-sql/pglite': 0.3.15 + + '@electric-sql/pglite-tools@0.2.20': + resolution: {integrity: sha512-BK50ZnYa3IG7ztXhtgYf0Q7zijV32Iw1cYS8C+ThdQlwx12V5VZ9KRJ42y82Hyb4PkTxZQklVQA9JHyUlex33A==} + peerDependencies: + '@electric-sql/pglite': 0.3.15 + + '@electric-sql/pglite@0.3.15': + resolution: {integrity: sha512-Cj++n1Mekf9ETfdc16TlDi+cDDQF0W7EcbyRHYOAeZdsAe8M/FJg18itDTSwyHfar2WIezawM9o0EKaRGVKygQ==} + + '@esbuild/aix-ppc64@0.27.4': + resolution: {integrity: sha512-cQPwL2mp2nSmHHJlCyoXgHGhbEPMrEEU5xhkcy3Hs/O7nGZqEpZ2sUtLaL9MORLtDfRvVl2/3PAuEkYZH0Ty8Q==} engines: {node: '>=18'} cpu: [ppc64] os: [aix] - '@esbuild/android-arm64@0.25.2': - resolution: {integrity: sha512-5ZAX5xOmTligeBaeNEPnPaeEuah53Id2tX4c2CVP3JaROTH+j4fnfHCkr1PjXMd78hMst+TlkfKcW/DlTq0i4w==} + '@esbuild/android-arm64@0.27.4': + resolution: {integrity: sha512-gdLscB7v75wRfu7QSm/zg6Rx29VLdy9eTr2t44sfTW7CxwAtQghZ4ZnqHk3/ogz7xao0QAgrkradbBzcqFPasw==} engines: {node: '>=18'} cpu: [arm64] os: [android] - '@esbuild/android-arm@0.25.2': - resolution: {integrity: sha512-NQhH7jFstVY5x8CKbcfa166GoV0EFkaPkCKBQkdPJFvo5u+nGXLEH/ooniLb3QI8Fk58YAx7nsPLozUWfCBOJA==} + '@esbuild/android-arm@0.27.4': + resolution: {integrity: sha512-X9bUgvxiC8CHAGKYufLIHGXPJWnr0OCdR0anD2e21vdvgCI8lIfqFbnoeOz7lBjdrAGUhqLZLcQo6MLhTO2DKQ==} engines: {node: '>=18'} cpu: [arm] os: [android] - '@esbuild/android-x64@0.25.2': - resolution: {integrity: sha512-Ffcx+nnma8Sge4jzddPHCZVRvIfQ0kMsUsCMcJRHkGJ1cDmhe4SsrYIjLUKn1xpHZybmOqCWwB0zQvsjdEHtkg==} + '@esbuild/android-x64@0.27.4': + resolution: {integrity: sha512-PzPFnBNVF292sfpfhiyiXCGSn9HZg5BcAz+ivBuSsl6Rk4ga1oEXAamhOXRFyMcjwr2DVtm40G65N3GLeH1Lvw==} engines: {node: '>=18'} cpu: [x64] os: [android] - '@esbuild/darwin-arm64@0.25.2': - resolution: {integrity: sha512-MpM6LUVTXAzOvN4KbjzU/q5smzryuoNjlriAIx+06RpecwCkL9JpenNzpKd2YMzLJFOdPqBpuub6eVRP5IgiSA==} + '@esbuild/darwin-arm64@0.27.4': + resolution: {integrity: sha512-b7xaGIwdJlht8ZFCvMkpDN6uiSmnxxK56N2GDTMYPr2/gzvfdQN8rTfBsvVKmIVY/X7EM+/hJKEIbbHs9oA4tQ==} engines: {node: '>=18'} cpu: [arm64] os: [darwin] - '@esbuild/darwin-x64@0.25.2': - resolution: {integrity: sha512-5eRPrTX7wFyuWe8FqEFPG2cU0+butQQVNcT4sVipqjLYQjjh8a8+vUTfgBKM88ObB85ahsnTwF7PSIt6PG+QkA==} + '@esbuild/darwin-x64@0.27.4': + resolution: {integrity: sha512-sR+OiKLwd15nmCdqpXMnuJ9W2kpy0KigzqScqHI3Hqwr7IXxBp3Yva+yJwoqh7rE8V77tdoheRYataNKL4QrPw==} engines: {node: '>=18'} cpu: [x64] os: [darwin] - '@esbuild/freebsd-arm64@0.25.2': - resolution: {integrity: sha512-mLwm4vXKiQ2UTSX4+ImyiPdiHjiZhIaE9QvC7sw0tZ6HoNMjYAqQpGyui5VRIi5sGd+uWq940gdCbY3VLvsO1w==} + '@esbuild/freebsd-arm64@0.27.4': + resolution: {integrity: sha512-jnfpKe+p79tCnm4GVav68A7tUFeKQwQyLgESwEAUzyxk/TJr4QdGog9sqWNcUbr/bZt/O/HXouspuQDd9JxFSw==} engines: {node: '>=18'} cpu: [arm64] os: [freebsd] - '@esbuild/freebsd-x64@0.25.2': - resolution: {integrity: sha512-6qyyn6TjayJSwGpm8J9QYYGQcRgc90nmfdUb0O7pp1s4lTY+9D0H9O02v5JqGApUyiHOtkz6+1hZNvNtEhbwRQ==} + '@esbuild/freebsd-x64@0.27.4': + resolution: {integrity: sha512-2kb4ceA/CpfUrIcTUl1wrP/9ad9Atrp5J94Lq69w7UwOMolPIGrfLSvAKJp0RTvkPPyn6CIWrNy13kyLikZRZQ==} engines: {node: '>=18'} cpu: [x64] os: [freebsd] - '@esbuild/linux-arm64@0.25.2': - resolution: {integrity: sha512-gq/sjLsOyMT19I8obBISvhoYiZIAaGF8JpeXu1u8yPv8BE5HlWYobmlsfijFIZ9hIVGYkbdFhEqC0NvM4kNO0g==} + '@esbuild/linux-arm64@0.27.4': + resolution: {integrity: sha512-7nQOttdzVGth1iz57kxg9uCz57dxQLHWxopL6mYuYthohPKEK0vU0C3O21CcBK6KDlkYVcnDXY099HcCDXd9dA==} engines: {node: '>=18'} cpu: [arm64] os: [linux] - '@esbuild/linux-arm@0.25.2': - resolution: {integrity: sha512-UHBRgJcmjJv5oeQF8EpTRZs/1knq6loLxTsjc3nxO9eXAPDLcWW55flrMVc97qFPbmZP31ta1AZVUKQzKTzb0g==} + '@esbuild/linux-arm@0.27.4': + resolution: {integrity: sha512-aBYgcIxX/wd5n2ys0yESGeYMGF+pv6g0DhZr3G1ZG4jMfruU9Tl1i2Z+Wnj9/KjGz1lTLCcorqE2viePZqj4Eg==} engines: {node: '>=18'} cpu: [arm] os: [linux] - '@esbuild/linux-ia32@0.25.2': - resolution: {integrity: sha512-bBYCv9obgW2cBP+2ZWfjYTU+f5cxRoGGQ5SeDbYdFCAZpYWrfjjfYwvUpP8MlKbP0nwZ5gyOU/0aUzZ5HWPuvQ==} + '@esbuild/linux-ia32@0.27.4': + resolution: {integrity: sha512-oPtixtAIzgvzYcKBQM/qZ3R+9TEUd1aNJQu0HhGyqtx6oS7qTpvjheIWBbes4+qu1bNlo2V4cbkISr8q6gRBFA==} engines: {node: '>=18'} cpu: [ia32] os: [linux] - '@esbuild/linux-loong64@0.25.2': - resolution: {integrity: sha512-SHNGiKtvnU2dBlM5D8CXRFdd+6etgZ9dXfaPCeJtz+37PIUlixvlIhI23L5khKXs3DIzAn9V8v+qb1TRKrgT5w==} + '@esbuild/linux-loong64@0.27.4': + resolution: {integrity: sha512-8mL/vh8qeCoRcFH2nM8wm5uJP+ZcVYGGayMavi8GmRJjuI3g1v6Z7Ni0JJKAJW+m0EtUuARb6Lmp4hMjzCBWzA==} engines: {node: '>=18'} cpu: [loong64] os: [linux] - '@esbuild/linux-mips64el@0.25.2': - resolution: {integrity: sha512-hDDRlzE6rPeoj+5fsADqdUZl1OzqDYow4TB4Y/3PlKBD0ph1e6uPHzIQcv2Z65u2K0kpeByIyAjCmjn1hJgG0Q==} + '@esbuild/linux-mips64el@0.27.4': + resolution: {integrity: sha512-1RdrWFFiiLIW7LQq9Q2NES+HiD4NyT8Itj9AUeCl0IVCA459WnPhREKgwrpaIfTOe+/2rdntisegiPWn/r/aAw==} engines: {node: '>=18'} cpu: [mips64el] os: [linux] - '@esbuild/linux-ppc64@0.25.2': - resolution: {integrity: sha512-tsHu2RRSWzipmUi9UBDEzc0nLc4HtpZEI5Ba+Omms5456x5WaNuiG3u7xh5AO6sipnJ9r4cRWQB2tUjPyIkc6g==} + '@esbuild/linux-ppc64@0.27.4': + resolution: {integrity: sha512-tLCwNG47l3sd9lpfyx9LAGEGItCUeRCWeAx6x2Jmbav65nAwoPXfewtAdtbtit/pJFLUWOhpv0FpS6GQAmPrHA==} engines: {node: '>=18'} cpu: [ppc64] os: [linux] - '@esbuild/linux-riscv64@0.25.2': - resolution: {integrity: sha512-k4LtpgV7NJQOml/10uPU0s4SAXGnowi5qBSjaLWMojNCUICNu7TshqHLAEbkBdAszL5TabfvQ48kK84hyFzjnw==} + '@esbuild/linux-riscv64@0.27.4': + resolution: {integrity: sha512-BnASypppbUWyqjd1KIpU4AUBiIhVr6YlHx/cnPgqEkNoVOhHg+YiSVxM1RLfiy4t9cAulbRGTNCKOcqHrEQLIw==} engines: {node: '>=18'} cpu: [riscv64] os: [linux] - '@esbuild/linux-s390x@0.25.2': - resolution: {integrity: sha512-GRa4IshOdvKY7M/rDpRR3gkiTNp34M0eLTaC1a08gNrh4u488aPhuZOCpkF6+2wl3zAN7L7XIpOFBhnaE3/Q8Q==} + '@esbuild/linux-s390x@0.27.4': + resolution: {integrity: sha512-+eUqgb/Z7vxVLezG8bVB9SfBie89gMueS+I0xYh2tJdw3vqA/0ImZJ2ROeWwVJN59ihBeZ7Tu92dF/5dy5FttA==} engines: {node: '>=18'} cpu: [s390x] os: [linux] - '@esbuild/linux-x64@0.25.2': - resolution: {integrity: sha512-QInHERlqpTTZ4FRB0fROQWXcYRD64lAoiegezDunLpalZMjcUcld3YzZmVJ2H/Cp0wJRZ8Xtjtj0cEHhYc/uUg==} + '@esbuild/linux-x64@0.27.4': + resolution: {integrity: sha512-S5qOXrKV8BQEzJPVxAwnryi2+Iq5pB40gTEIT69BQONqR7JH1EPIcQ/Uiv9mCnn05jff9umq/5nqzxlqTOg9NA==} engines: {node: '>=18'} cpu: [x64] os: [linux] - '@esbuild/netbsd-arm64@0.25.2': - resolution: {integrity: sha512-talAIBoY5M8vHc6EeI2WW9d/CkiO9MQJ0IOWX8hrLhxGbro/vBXJvaQXefW2cP0z0nQVTdQ/eNyGFV1GSKrxfw==} + '@esbuild/netbsd-arm64@0.27.4': + resolution: {integrity: sha512-xHT8X4sb0GS8qTqiwzHqpY00C95DPAq7nAwX35Ie/s+LO9830hrMd3oX0ZMKLvy7vsonee73x0lmcdOVXFzd6Q==} engines: {node: '>=18'} cpu: [arm64] os: [netbsd] - '@esbuild/netbsd-x64@0.25.2': - resolution: {integrity: sha512-voZT9Z+tpOxrvfKFyfDYPc4DO4rk06qamv1a/fkuzHpiVBMOhpjK+vBmWM8J1eiB3OLSMFYNaOaBNLXGChf5tg==} + '@esbuild/netbsd-x64@0.27.4': + resolution: {integrity: sha512-RugOvOdXfdyi5Tyv40kgQnI0byv66BFgAqjdgtAKqHoZTbTF2QqfQrFwa7cHEORJf6X2ht+l9ABLMP0dnKYsgg==} engines: {node: '>=18'} cpu: [x64] os: [netbsd] - '@esbuild/openbsd-arm64@0.25.2': - resolution: {integrity: sha512-dcXYOC6NXOqcykeDlwId9kB6OkPUxOEqU+rkrYVqJbK2hagWOMrsTGsMr8+rW02M+d5Op5NNlgMmjzecaRf7Tg==} + '@esbuild/openbsd-arm64@0.27.4': + resolution: {integrity: sha512-2MyL3IAaTX+1/qP0O1SwskwcwCoOI4kV2IBX1xYnDDqthmq5ArrW94qSIKCAuRraMgPOmG0RDTA74mzYNQA9ow==} engines: {node: '>=18'} cpu: [arm64] os: [openbsd] - '@esbuild/openbsd-x64@0.25.2': - resolution: {integrity: sha512-t/TkWwahkH0Tsgoq1Ju7QfgGhArkGLkF1uYz8nQS/PPFlXbP5YgRpqQR3ARRiC2iXoLTWFxc6DJMSK10dVXluw==} + '@esbuild/openbsd-x64@0.27.4': + resolution: {integrity: sha512-u8fg/jQ5aQDfsnIV6+KwLOf1CmJnfu1ShpwqdwC0uA7ZPwFws55Ngc12vBdeUdnuWoQYx/SOQLGDcdlfXhYmXQ==} engines: {node: '>=18'} cpu: [x64] os: [openbsd] - '@esbuild/sunos-x64@0.25.2': - resolution: {integrity: sha512-cfZH1co2+imVdWCjd+D1gf9NjkchVhhdpgb1q5y6Hcv9TP6Zi9ZG/beI3ig8TvwT9lH9dlxLq5MQBBgwuj4xvA==} + '@esbuild/openharmony-arm64@0.27.4': + resolution: {integrity: sha512-JkTZrl6VbyO8lDQO3yv26nNr2RM2yZzNrNHEsj9bm6dOwwu9OYN28CjzZkH57bh4w0I2F7IodpQvUAEd1mbWXg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [openharmony] + + '@esbuild/sunos-x64@0.27.4': + resolution: {integrity: sha512-/gOzgaewZJfeJTlsWhvUEmUG4tWEY2Spp5M20INYRg2ZKl9QPO3QEEgPeRtLjEWSW8FilRNacPOg8R1uaYkA6g==} engines: {node: '>=18'} cpu: [x64] os: [sunos] - '@esbuild/win32-arm64@0.25.2': - resolution: {integrity: sha512-7Loyjh+D/Nx/sOTzV8vfbB3GJuHdOQyrOryFdZvPHLf42Tk9ivBU5Aedi7iyX+x6rbn2Mh68T4qq1SDqJBQO5Q==} + '@esbuild/win32-arm64@0.27.4': + resolution: {integrity: sha512-Z9SExBg2y32smoDQdf1HRwHRt6vAHLXcxD2uGgO/v2jK7Y718Ix4ndsbNMU/+1Qiem9OiOdaqitioZwxivhXYg==} engines: {node: '>=18'} cpu: [arm64] os: [win32] - '@esbuild/win32-ia32@0.25.2': - resolution: {integrity: sha512-WRJgsz9un0nqZJ4MfhabxaD9Ft8KioqU3JMinOTvobbX6MOSUigSBlogP8QB3uxpJDsFS6yN+3FDBdqE5lg9kg==} + '@esbuild/win32-ia32@0.27.4': + resolution: {integrity: sha512-DAyGLS0Jz5G5iixEbMHi5KdiApqHBWMGzTtMiJ72ZOLhbu/bzxgAe8Ue8CTS3n3HbIUHQz/L51yMdGMeoxXNJw==} engines: {node: '>=18'} cpu: [ia32] os: [win32] - '@esbuild/win32-x64@0.25.2': - resolution: {integrity: sha512-kM3HKb16VIXZyIeVrM1ygYmZBKybX8N4p754bw390wGO3Tf2j4L2/WYL+4suWujpgf6GBYs3jv7TyUivdd05JA==} + '@esbuild/win32-x64@0.27.4': + resolution: {integrity: sha512-+knoa0BDoeXgkNvvV1vvbZX4+hizelrkwmGJBdT17t8FNPwG2lKemmuMZlmaNQ3ws3DKKCxpb4zRZEIp3UxFCg==} engines: {node: '>=18'} cpu: [x64] os: [win32] + '@eslint-community/eslint-utils@4.9.1': + resolution: {integrity: sha512-phrYmNiYppR7znFEdqgfWHXR6NCkZEK7hwWDHZUjit/2/U0r6XvkDl0SYnoM51Hq7FhCGdLDT6zxCCOY1hexsQ==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + peerDependencies: + eslint: ^6.0.0 || ^7.0.0 || >=8.0.0 + + '@eslint-community/regexpp@4.12.2': + resolution: {integrity: sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew==} + engines: {node: ^12.0.0 || ^14.0.0 || >=16.0.0} + + '@eslint/config-array@0.21.2': + resolution: {integrity: sha512-nJl2KGTlrf9GjLimgIru+V/mzgSK0ABCDQRvxw5BjURL7WfH5uoWmizbH7QB6MmnMBd8cIC9uceWnezL1VZWWw==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@eslint/config-helpers@0.4.2': + resolution: {integrity: sha512-gBrxN88gOIf3R7ja5K9slwNayVcZgK6SOUORm2uBzTeIEfeVaIhOpCtTox3P6R7o2jLFwLFTLnC7kU/RGcYEgw==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@eslint/core@0.17.0': + resolution: {integrity: sha512-yL/sLrpmtDaFEiUj1osRP4TI2MDz1AddJL+jZ7KSqvBuliN4xqYY54IfdN8qD8Toa6g1iloph1fxQNkjOxrrpQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@eslint/eslintrc@3.3.5': + resolution: {integrity: sha512-4IlJx0X0qftVsN5E+/vGujTRIFtwuLbNsVUe7TO6zYPDR1O6nFwvwhIKEKSrl6dZchmYBITazxKoUYOjdtjlRg==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@eslint/js@9.39.4': + resolution: {integrity: sha512-nE7DEIchvtiFTwBw4Lfbu59PG+kCofhjsKaCWzxTpt4lfRjRMqG6uMBzKXuEcyXhOHoUp9riAm7/aWYGhXZ9cw==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@eslint/object-schema@2.1.7': + resolution: {integrity: sha512-VtAOaymWVfZcmZbp6E2mympDIHvyjXs/12LqWYjVw6qjrfF+VK+fyG33kChz3nnK+SU5/NeHOqrTEHS8sXO3OA==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@eslint/plugin-kit@0.4.1': + resolution: {integrity: sha512-43/qtrDUokr7LJqoF2c3+RInu/t4zfrpYdoSDfYyhg52rwLV6TnOvdG4fXm7IkSB3wErkcmJS9iEhjVtOSEjjA==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@hono/node-server@1.19.9': + resolution: {integrity: sha512-vHL6w3ecZsky+8P5MD+eFfaGTyCeOHUIFYMGpQGbrBTSmNNoxv0if69rEZ5giu36weC5saFuznL411gRX7bJDw==} + engines: {node: '>=18.14.1'} + peerDependencies: + hono: ^4 + + '@humanfs/core@0.19.1': + resolution: {integrity: sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==} + engines: {node: '>=18.18.0'} + + '@humanfs/node@0.16.7': + resolution: {integrity: sha512-/zUx+yOsIrG4Y43Eh2peDeKCxlRt/gET6aHfaKpuq267qXdYDFViVHfMaLyygZOnl0kGWxFIgsBy8QFuTLUXEQ==} + engines: {node: '>=18.18.0'} + + '@humanwhocodes/module-importer@1.0.1': + resolution: {integrity: sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==} + engines: {node: '>=12.22'} + + '@humanwhocodes/retry@0.4.3': + resolution: {integrity: sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==} + engines: {node: '>=18.18'} + '@istanbuljs/load-nyc-config@1.1.0': resolution: {integrity: sha512-VjeHSlIzpv/NyD3N0YuHfXOPDIixcA1q2ZV98wsMqcYlPmv2n3Yb2lYP9XMElnaFVXg5A7YLTeLu6V84uQDjmQ==} engines: {node: '>=8'} @@ -506,35 +619,75 @@ packages: '@jsdevtools/ono@7.1.3': resolution: {integrity: sha512-4JQNk+3mVzK3xh2rqd6RB4J46qUR19azEHBneZyTZM+c456qOrbbM/5xcR8huNCCcbVt7+UmizG6GuUvPvKUYg==} - '@prisma/client@6.6.0': - resolution: {integrity: sha512-vfp73YT/BHsWWOAuthKQ/1lBgESSqYqAWZEYyTdGXyFAHpmewwWL2Iz6ErIzkj4aHbuc6/cGSsE6ZY+pBO04Cg==} - engines: {node: '>=18.18'} + '@mrleebo/prisma-ast@0.13.1': + resolution: {integrity: sha512-XyroGQXcHrZdvmrGJvsA9KNeOOgGMg1Vg9OlheUsBOSKznLMDl+YChxbkboRHvtFYJEMRYmlV3uoo/njCw05iw==} + engines: {node: '>=16'} + + '@noble/hashes@1.8.0': + resolution: {integrity: sha512-jCs9ldd7NwzpgXDIf6P3+NrHh9/sD6CQdxHyjQI+h/6rDNo88ypBxxz45UDuZHz9r3tNz7N/VInSVoVdtXEI4A==} + engines: {node: ^14.21.3 || >=16} + + '@paralleldrive/cuid2@2.3.1': + resolution: {integrity: sha512-XO7cAxhnTZl0Yggq6jOgjiOHhbgcO4NqFqwSmQpjK3b6TEE6Uj/jfSk6wzYyemh3+I0sHirKSetjQwn5cZktFw==} + + '@prisma/adapter-pg@7.5.0': + resolution: {integrity: sha512-EJx7OLULahcC3IjJgdx2qRDNCT+ToY2v66UkeETMCLhNOTgqVzRzYvOEphY7Zp0eHyzfkC33Edd/qqeadf9R4A==} + + '@prisma/client-runtime-utils@7.5.0': + resolution: {integrity: sha512-KnJ2b4Si/pcWEtK68uM+h0h1oh80CZt2suhLTVuLaSKg4n58Q9jBF/A42Kw6Ma+aThy1yAhfDeTC0JvEmeZnFQ==} + + '@prisma/client@7.5.0': + resolution: {integrity: sha512-h4hF9ctp+kSRs7ENHGsFQmHAgHcfkOCxbYt6Ti9Xi8x7D+kP4tTi9x51UKmiTH/OqdyJAO+8V+r+JA5AWdav7w==} + engines: {node: ^20.19 || ^22.12 || >=24.0} peerDependencies: prisma: '*' - typescript: '>=5.1.0' + typescript: '>=5.4.0' peerDependenciesMeta: prisma: optional: true typescript: optional: true - '@prisma/config@6.6.0': - resolution: {integrity: sha512-d8FlXRHsx72RbN8nA2QCRORNv5AcUnPXgtPvwhXmYkQSMF/j9cKaJg+9VcUzBRXGy9QBckNzEQDEJZdEOZ+ubA==} + '@prisma/config@7.5.0': + resolution: {integrity: sha512-1J/9YEX7A889xM46PYg9e8VAuSL1IUmXJW3tEhMv7XQHDWlfC9YSkIw9sTYRaq5GswGlxZ+GnnyiNsUZ9JJhSQ==} + + '@prisma/debug@7.2.0': + resolution: {integrity: sha512-YSGTiSlBAVJPzX4ONZmMotL+ozJwQjRmZweQNIq/ER0tQJKJynNkRB3kyvt37eOfsbMCXk3gnLF6J9OJ4QWftw==} + + '@prisma/debug@7.5.0': + resolution: {integrity: sha512-163+nffny0JoPEkDhfNco0vcuT3ymIJc9+WX7MHSQhfkeKUmKe9/wqvGk5SjppT93DtBjVwr5HPJYlXbzm6qtg==} - '@prisma/debug@6.6.0': - resolution: {integrity: sha512-DL6n4IKlW5k2LEXzpN60SQ1kP/F6fqaCgU/McgaYsxSf43GZ8lwtmXLke9efS+L1uGmrhtBUP4npV/QKF8s2ZQ==} + '@prisma/dev@0.20.0': + resolution: {integrity: sha512-ovlBYwWor0OzG+yH4J3Ot+AneD818BttLA+Ii7wjbcLHUrnC4tbUPVGyNd3c/+71KETPKZfjhkTSpdS15dmXNQ==} - '@prisma/engines-version@6.6.0-53.f676762280b54cd07c770017ed3711ddde35f37a': - resolution: {integrity: sha512-JzRaQ5Em1fuEcbR3nUsMNYaIYrOT1iMheenjCvzZblJcjv/3JIuxXN7RCNT5i6lRkLodW5ojCGhR7n5yvnNKrw==} + '@prisma/driver-adapter-utils@7.5.0': + resolution: {integrity: sha512-B79N/amgV677mFesFDBAdrW0OIaqawap9E0sjgLBtzIz2R3hIMS1QB8mLZuUEiS4q5Y8Oh3I25Kw4SLxMypk9Q==} - '@prisma/engines@6.6.0': - resolution: {integrity: sha512-nC0IV4NHh7500cozD1fBoTwTD1ydJERndreIjpZr/S3mno3P6tm8qnXmIND5SwUkibNeSJMpgl4gAnlqJ/gVlg==} + '@prisma/engines-version@7.5.0-15.280c870be64f457428992c43c1f6d557fab6e29e': + resolution: {integrity: sha512-E+iRV/vbJLl8iGjVr6g/TEWokA+gjkV/doZkaQN1i/ULVdDwGnPJDfLUIFGS3BVwlG/m6L8T4x1x5isl8hGMxA==} - '@prisma/fetch-engine@6.6.0': - resolution: {integrity: sha512-Ohfo8gKp05LFLZaBlPUApM0M7k43a0jmo86YY35u1/4t+vuQH9mRGU7jGwVzGFY3v+9edeb/cowb1oG4buM1yw==} + '@prisma/engines@7.5.0': + resolution: {integrity: sha512-ondGRhzoaVpRWvFaQ5wH5zS1BIbhzbKqczKjCn6j3L0Zfe/LInjcEg8+xtB49AuZBX30qyx1ZtGoootUohz2pw==} - '@prisma/get-platform@6.6.0': - resolution: {integrity: sha512-3qCwmnT4Jh5WCGUrkWcc6VZaw0JY7eWN175/pcb5Z6FiLZZ3ygY93UX0WuV41bG51a6JN/oBH0uywJ90Y+V5eA==} + '@prisma/fetch-engine@7.5.0': + resolution: {integrity: sha512-kZCl2FV54qnyrVdnII8MI6qvt7HfU6Cbiz8dZ8PXz4f4lbSw45jEB9/gEMK2SGdiNhBKyk/Wv95uthoLhGMLYA==} + + '@prisma/get-platform@7.2.0': + resolution: {integrity: sha512-k1V0l0Td1732EHpAfi2eySTezyllok9dXb6UQanajkJQzPUGi3vO2z7jdkz67SypFTdmbnyGYxvEvYZdZsMAVA==} + + '@prisma/get-platform@7.5.0': + resolution: {integrity: sha512-7I+2y1nu/gkEKSiHHbcZ1HPe/euGdEqJZxEEMT0246q4De1+hla0ZzlTgvaT9dHcVCgLSuCG8v39db5qUUWNgw==} + + '@prisma/query-plan-executor@7.2.0': + resolution: {integrity: sha512-EOZmNzcV8uJ0mae3DhTsiHgoNCuu1J9mULQpGCh62zN3PxPTd+qI9tJvk5jOst8WHKQNwJWR3b39t0XvfBB0WQ==} + + '@prisma/studio-core@0.21.1': + resolution: {integrity: sha512-bOGqG/eMQtKC0XVvcVLRmhWWzm/I+0QUWqAEhEBtetpuS3k3V4IWqKGUONkAIT223DNXJMxMtZp36b1FmcdPeg==} + engines: {node: ^20.19 || ^22.12 || ^24.0, pnpm: '8'} + peerDependencies: + '@types/react': ^18.0.0 || ^19.0.0 + react: ^18.0.0 || ^19.0.0 + react-dom: ^18.0.0 || ^19.0.0 '@scarf/scarf@1.4.0': resolution: {integrity: sha512-xxeapPiUXdZAE3che6f3xogoJPeZgig6omHEy1rIY5WVsB3H2BHNnZH+gHG6x91SCWyQCzWGsuL2Hh3ClO5/qQ==} @@ -548,6 +701,9 @@ packages: '@sinonjs/fake-timers@10.3.0': resolution: {integrity: sha512-V4BG07kuYSUkTCSBHG8G8TNhM+F19jXFWnQtzj+we8DrkpSBCee9Z3Ms8yiGer/dlmhe35/Xdgyo3/0rQKg7YA==} + '@standard-schema/spec@1.1.0': + resolution: {integrity: sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==} + '@types/babel__core@7.20.5': resolution: {integrity: sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==} @@ -569,14 +725,17 @@ packages: '@types/cookiejar@2.1.5': resolution: {integrity: sha512-he+DHOWReW0nghN24E1WUqM0efK4kI9oTqDm6XmK8ZPe2djZ90BSNdGnIyCLzCPw7/pogPlGbzI2wHGGmi4O/Q==} - '@types/cors@2.8.17': - resolution: {integrity: sha512-8CGDvrBj1zgo2qE+oS3pOCyYNqCPryMWY2bGfwA0dcfopWGgxs+78df0Rs3rc9THP4JkOhLsAa+15VdpAqkcUA==} + '@types/cors@2.8.19': + resolution: {integrity: sha512-mFNylyeyqN93lfe/9CSxOGREz8cpzAhH+E93xJ4xWQf62V8sQ/24reV2nyzUWM6H6Xji+GGHpkbLe7pVoUEskg==} + + '@types/estree@1.0.8': + resolution: {integrity: sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==} '@types/express-serve-static-core@5.0.6': resolution: {integrity: sha512-3xhRnjJPkULekpSzgtoNYYcTWgEZkp4myc+Saevii5JPnHNvHMRlBSHDbs7Bh1iPPoVTERHEZXyhyLbMEsExsA==} - '@types/express@5.0.1': - resolution: {integrity: sha512-UZUw8vjpWFXuDnjFTh7/5c2TWDlQqeXHi6hcN7F2XSVT5P+WmUnnbFS3KA6Jnc6IsEqI2qCVu2bK0R0J4A8ZQQ==} + '@types/express@5.0.6': + resolution: {integrity: sha512-sKYVuV7Sv9fbPIt/442koC7+IIwK5olP1KWeD88e/idgoJqDm3JV/YUiPwkoKK92ylff2MGxSz1CSjsXelx0YA==} '@types/graceful-fs@4.1.9': resolution: {integrity: sha512-olP3sd1qOEe5dXTSaFvQG+02VdRXcdytWLAZsAq1PecU8uqQAhkrnbli7DagjtXKW/Bl7YJbUsa8MPcuc8LHEQ==} @@ -605,11 +764,17 @@ packages: '@types/mime@1.3.5': resolution: {integrity: sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w==} - '@types/morgan@1.9.9': - resolution: {integrity: sha512-iRYSDKVaC6FkGSpEVVIvrRGw0DfJMiQzIn3qr2G5B3C//AWkulhXgaBd7tS9/J79GWSYMTHGs7PfI5b3Y8m+RQ==} + '@types/morgan@1.9.10': + resolution: {integrity: sha512-sS4A1zheMvsADRVfT0lYbJ4S9lmsey8Zo2F7cnbYjWHP67Q0AwMYuuzLlkIM2N8gAbb9cubhIVFwcIN2XyYCkA==} + + '@types/node@25.5.0': + resolution: {integrity: sha512-jp2P3tQMSxWugkCUKLRPVUpGaL5MVFwF8RDuSRztfwgN1wmqJeMSbKlnEtQqU8UrhTmzEmZdu2I6v2dpp7XIxw==} - '@types/node@22.14.0': - resolution: {integrity: sha512-Kmpl+z84ILoG+3T/zQFyAJsU6EPTmOCj8/2+83fSN6djd6I4o7uOuGIH6vq3PrjY5BGitSbFuMN18j3iknubbA==} + '@types/pg@8.11.11': + resolution: {integrity: sha512-kGT1qKM8wJQ5qlawUrEkXgvMSXoV213KfMGXcwfDwUIfUHXqXYXOfS1nE1LINRJVVVx5wCm70XnFlMHaIcQAfw==} + + '@types/pg@8.18.0': + resolution: {integrity: sha512-gT+oueVQkqnj6ajGJXblFR4iavIXWsGAFCk3dP4Kki5+a9R4NMt0JARdk6s8cUKcfUoqP5dAtDSLU8xYUTFV+Q==} '@types/qs@6.9.18': resolution: {integrity: sha512-kK7dgTYDyGqS+e2Q4aK9X3D7q234CIZ1Bv0q/7Z5IwRDoADNU81xXJK/YVyLbLTZCoIwUoDoffFeF+p/eIklAA==} @@ -617,20 +782,26 @@ packages: '@types/range-parser@1.2.7': resolution: {integrity: sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ==} + '@types/react@19.2.14': + resolution: {integrity: sha512-ilcTH/UniCkMdtexkoCN0bI7pMcJDvmQFPvuPvmEaYA/NSfFTAgdUSLAoVjaRJm7+6PvcM+q1zYOwS4wTYMF9w==} + '@types/send@0.17.4': resolution: {integrity: sha512-x2EM6TJOybec7c52BX0ZspPodMsQUd5L6PRwOunVyVUhXiBSKf3AezDL8Dgvgt5o0UfKNfuA0eMLr2wLT4AiBA==} '@types/serve-static@1.15.7': resolution: {integrity: sha512-W8Ym+h8nhuRwaKPaDw34QUkwsGi6Rc4yYqvKFo5rm2FUEhCFbzVWrxXUxuKK8TASjWsysJY0nsmNCGhCOIsrOw==} + '@types/serve-static@2.2.0': + resolution: {integrity: sha512-8mam4H1NHLtu7nmtalF7eyBH14QyOASmcxHhSfEoRyr0nP/YdoesEtU+uSRvMe96TW/HPTtkoKqQLl53N7UXMQ==} + '@types/stack-utils@2.0.3': resolution: {integrity: sha512-9aEbYZ3TbYMznPdcdr3SmIrLXwC/AKZXQeCf9Pgao5CKb8CyHuEX5jzWPTkvregvhRJHcpRO6BFoGW9ycaOkYw==} '@types/superagent@8.1.9': resolution: {integrity: sha512-pTVjI73witn+9ILmoJdajHGW2jkSaOzhiFYF1Rd3EQ94kymLqB9PjD9ISg7WaALC7+dCHT0FGe9T2LktLq/3GQ==} - '@types/supertest@6.0.3': - resolution: {integrity: sha512-8WzXq62EXFhJ7QsH3Ocb/iKQ/Ty9ZVWnVzoTKc9tyyFRRF3a74Tk2+TLFgaFFw364Ere+npzHKEJ6ga2LzIL7w==} + '@types/supertest@7.2.0': + resolution: {integrity: sha512-uh2Lv57xvggst6lCqNdFAmDSvoMG7M/HDtX4iUCquxQ5EGPtaPM5PL5Hmi7LCvOG8db7YaCPNJEeoI8s/WzIQw==} '@types/swagger-jsdoc@6.0.4': resolution: {integrity: sha512-W+Xw5epcOZrF/AooUM/PccNMSAFOKWZA5dasNyMujTwsBkU74njSJBpvCCJhHAJ95XRMzQrrW844Btu0uoetwQ==} @@ -644,10 +815,82 @@ packages: '@types/yargs@17.0.33': resolution: {integrity: sha512-WpxBCKWPLr4xSsHgz511rFJAM+wS28w2zEO1QDNY5zM/S8ok70NNfztH0xwhqKyaK0OHCbN98LDAZuy1ctxDkA==} + '@typescript-eslint/eslint-plugin@8.57.0': + resolution: {integrity: sha512-qeu4rTHR3/IaFORbD16gmjq9+rEs9fGKdX0kF6BKSfi+gCuG3RCKLlSBYzn/bGsY9Tj7KE/DAQStbp8AHJGHEQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + '@typescript-eslint/parser': ^8.57.0 + eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 + typescript: '>=4.8.4 <6.0.0' + + '@typescript-eslint/parser@8.57.0': + resolution: {integrity: sha512-XZzOmihLIr8AD1b9hL9ccNMzEMWt/dE2u7NyTY9jJG6YNiNthaD5XtUHVF2uCXZ15ng+z2hT3MVuxnUYhq6k1g==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 + typescript: '>=4.8.4 <6.0.0' + + '@typescript-eslint/project-service@8.57.0': + resolution: {integrity: sha512-pR+dK0BlxCLxtWfaKQWtYr7MhKmzqZxuii+ZjuFlZlIGRZm22HnXFqa2eY+90MUz8/i80YJmzFGDUsi8dMOV5w==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + typescript: '>=4.8.4 <6.0.0' + + '@typescript-eslint/scope-manager@8.57.0': + resolution: {integrity: sha512-nvExQqAHF01lUM66MskSaZulpPL5pgy5hI5RfrxviLgzZVffB5yYzw27uK/ft8QnKXI2X0LBrHJFr1TaZtAibw==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@typescript-eslint/tsconfig-utils@8.57.0': + resolution: {integrity: sha512-LtXRihc5ytjJIQEH+xqjB0+YgsV4/tW35XKX3GTZHpWtcC8SPkT/d4tqdf1cKtesryHm2bgp6l555NYcT2NLvA==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + typescript: '>=4.8.4 <6.0.0' + + '@typescript-eslint/type-utils@8.57.0': + resolution: {integrity: sha512-yjgh7gmDcJ1+TcEg8x3uWQmn8ifvSupnPfjP21twPKrDP/pTHlEQgmKcitzF/rzPSmv7QjJ90vRpN4U+zoUjwQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 + typescript: '>=4.8.4 <6.0.0' + + '@typescript-eslint/types@8.57.0': + resolution: {integrity: sha512-dTLI8PEXhjUC7B9Kre+u0XznO696BhXcTlOn0/6kf1fHaQW8+VjJAVHJ3eTI14ZapTxdkOmc80HblPQLaEeJdg==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@typescript-eslint/typescript-estree@8.57.0': + resolution: {integrity: sha512-m7faHcyVg0BT3VdYTlX8GdJEM7COexXxS6KqGopxdtkQRvBanK377QDHr4W/vIPAR+ah9+B/RclSW5ldVniO1Q==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + typescript: '>=4.8.4 <6.0.0' + + '@typescript-eslint/utils@8.57.0': + resolution: {integrity: sha512-5iIHvpD3CZe06riAsbNxxreP+MuYgVUsV0n4bwLH//VJmgtt54sQeY2GszntJ4BjYCpMzrfVh2SBnUQTtys2lQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 + typescript: '>=4.8.4 <6.0.0' + + '@typescript-eslint/visitor-keys@8.57.0': + resolution: {integrity: sha512-zm6xx8UT/Xy2oSr2ZXD0pZo7Jx2XsCoID2IUh9YSTFRu7z+WdwYTRk6LhUftm1crwqbuoF6I8zAFeCMw0YjwDg==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + accepts@2.0.0: resolution: {integrity: sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==} engines: {node: '>= 0.6'} + acorn-jsx@5.3.2: + resolution: {integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==} + peerDependencies: + acorn: ^6.0.0 || ^7.0.0 || ^8.0.0 + + acorn@8.16.0: + resolution: {integrity: sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==} + engines: {node: '>=0.4.0'} + hasBin: true + + ajv@6.14.0: + resolution: {integrity: sha512-IWrosm/yrn43eiKqkfkHis7QioDleaXQHdDVPKg0FSwwd/DuvyX79TZnFOnYpB7dcsFAMmtFztZuXPDvSePkFw==} + ansi-escapes@4.3.2: resolution: {integrity: sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==} engines: {node: '>=8'} @@ -677,12 +920,13 @@ packages: asap@2.0.6: resolution: {integrity: sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA==} - async@3.2.6: - resolution: {integrity: sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA==} - asynckit@0.4.0: resolution: {integrity: sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==} + aws-ssl-profiles@1.1.2: + resolution: {integrity: sha512-NZKeq9AfyQvEeNlN0zSYAaWrmBffJh3IELMZfRpJVWgrpEbtEpnjvzqBPf+mxoI287JohRDoa+/nsfqqiZmF6g==} + engines: {node: '>= 6.0.0'} + babel-jest@29.7.0: resolution: {integrity: sha512-BrvGY3xZSwEcCzKvKsCi2GgHqDqsYkOP4/by5xCgIwGXQxIEh+8ew3gmrE1y7XRR6LHZIj6yLYnUi/mm2KXKBg==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} @@ -711,19 +955,24 @@ packages: balanced-match@1.0.2: resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} + balanced-match@4.0.4: + resolution: {integrity: sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==} + engines: {node: 18 || 20 || >=22} + basic-auth@2.0.1: resolution: {integrity: sha512-NF+epuEdnUYVlGuhaxbbq+dvJttwLnGY+YixlXlME5KpQ5W3CnXA5cVTneY3SPbPDRkcjMbifrwmFYcClgOZeg==} engines: {node: '>= 0.8'} - body-parser@2.2.0: - resolution: {integrity: sha512-02qvAaxv8tp7fBa/mw1ga98OGm+eCbqzJOKoRt70sLmfEEi+jyBYVTDGfCL/k06/4EMk/z01gCe7HoCH/f2LTg==} + body-parser@2.2.2: + resolution: {integrity: sha512-oP5VkATKlNwcgvxi0vM0p/D3n2C3EReYVX+DNYs5TjZFn/oQt2j+4sVJtSMr18pdRr8wjTcBl6LoV+FUwzPmNA==} engines: {node: '>=18'} brace-expansion@1.1.11: resolution: {integrity: sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==} - brace-expansion@2.0.1: - resolution: {integrity: sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==} + brace-expansion@5.0.4: + resolution: {integrity: sha512-h+DEnpVvxmfVefa4jFbCf5HdH5YMDXRsmKflpf1pILZWRFlTbJpxeU55nJl4Smt5HQaGzg1o6RHFPJaOqnmBDg==} + engines: {node: 18 || 20 || >=22} braces@3.0.3: resolution: {integrity: sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==} @@ -748,6 +997,14 @@ packages: resolution: {integrity: sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==} engines: {node: '>= 0.8'} + c12@3.1.0: + resolution: {integrity: sha512-uWoS8OU1MEIsOv8p/5a82c3H31LsWVR5qiyXVfBNOzfffjUWtPnhAb4BYI2uG2HfGmZmFjCtui5XNWaps+iFuw==} + peerDependencies: + magicast: ^0.3.5 + peerDependenciesMeta: + magicast: + optional: true + call-bind-apply-helpers@1.0.2: resolution: {integrity: sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==} engines: {node: '>= 0.4'} @@ -782,10 +1039,23 @@ packages: resolution: {integrity: sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw==} engines: {node: '>=10'} + chevrotain@10.5.0: + resolution: {integrity: sha512-Pkv5rBY3+CsHOYfV5g/Vs5JY9WTHHDEKOlohI2XeygaZhUeqhAlldZ8Hz9cRmxu709bvS08YzxHdTPHhffc13A==} + + chokidar@4.0.3: + resolution: {integrity: sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==} + engines: {node: '>= 14.16.0'} + ci-info@3.9.0: resolution: {integrity: sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ==} engines: {node: '>=8'} + citty@0.1.6: + resolution: {integrity: sha512-tskPPKEs8D2KPafUypv2gxwJP8h/OaJmC82QQGGDQcHvXX43xF2VDACcJVmZ0EuSxkpO9Kc4MlrA3q0+FG58AQ==} + + citty@0.2.1: + resolution: {integrity: sha512-kEV95lFBhQgtogAPlQfJJ0WGVSokvLr/UEoFPiKKOXF7pl98HfUVUD0ejsuTCld/9xH9vogSywZ5KqHzXrZpqg==} + cjs-module-lexer@1.4.3: resolution: {integrity: sha512-9z8TZaGM1pfswYeXrUpzPrkx8UnWYdhJclsiYMm6x/w5+nN+8Tf/LnAgfLGQCm59qAOxU8WwHEq2vNwF6i4j+Q==} @@ -825,6 +1095,13 @@ packages: concat-map@0.0.1: resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==} + confbox@0.2.4: + resolution: {integrity: sha512-ysOGlgTFbN2/Y6Cg3Iye8YKulHw+R2fNXHrgSmXISQdMnomY6eNDprVdW9R5xBguEqI954+S6709UyiO7B+6OQ==} + + consola@3.4.2: + resolution: {integrity: sha512-5IKcdX0nnYavi6G7TtOhwkYzyjfJlatbjMjuLSfE2kYT5pMDOilZ4OvMhi637CcDICTmz3wARPoyhqyX1Y+XvA==} + engines: {node: ^14.18.0 || >=16.10.0} + content-disposition@1.0.0: resolution: {integrity: sha512-Au9nRL8VNUut/XSzbQA38+M78dzP4D+eqg3gfJHMIHHYa3bg067xj1KxMUWj+VULbiZMowKngFFbKczUrNJ1mg==} engines: {node: '>= 0.6'} @@ -847,8 +1124,8 @@ packages: cookiejar@2.1.4: resolution: {integrity: sha512-LDx6oHrK+PhzLKJU9j5S7/Y3jM/mUHvD/DeI1WQmJn652iPC5Y4TBzC9l+5OMOXlyTTA+SmVUPm0HQUwpD5Jqw==} - cors@2.8.5: - resolution: {integrity: sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==} + cors@2.8.6: + resolution: {integrity: sha512-tJtZBBHA6vjIAaF6EnIaq6laBBP9aq/Y3ouVJjEfoHbRBcHBAHYcMh/w8LDrk2PvIMMq8gmopa5D4V8RmbrxGw==} engines: {node: '>= 0.10'} create-jest@29.7.0: @@ -860,6 +1137,9 @@ packages: resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==} engines: {node: '>= 8'} + csstype@3.2.3: + resolution: {integrity: sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==} + debug@2.6.9: resolution: {integrity: sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==} peerDependencies: @@ -877,6 +1157,15 @@ packages: supports-color: optional: true + debug@4.4.3: + resolution: {integrity: sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==} + engines: {node: '>=6.0'} + peerDependencies: + supports-color: '*' + peerDependenciesMeta: + supports-color: + optional: true + dedent@1.5.3: resolution: {integrity: sha512-NHQtfOOW68WD8lgypbLA5oT+Bt0xXJhiYvoR6SmmNXZfpzOGXwdKWmcwG8N7PwVVWV3eF/68nmD9BaJSsTBhyQ==} peerDependencies: @@ -885,18 +1174,35 @@ packages: babel-plugin-macros: optional: true + deep-is@0.1.4: + resolution: {integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==} + + deepmerge-ts@7.1.5: + resolution: {integrity: sha512-HOJkrhaYsweh+W+e74Yn7YStZOilkoPb6fycpwNLKzSPtruFs48nYis0zy5yJz1+ktUhHxoRDJ27RQAWLIJVJw==} + engines: {node: '>=16.0.0'} + deepmerge@4.3.1: resolution: {integrity: sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==} engines: {node: '>=0.10.0'} + defu@6.1.4: + resolution: {integrity: sha512-mEQCMmwJu317oSz8CwdIOdwf3xMif1ttiM8LTufzc3g6kR+9Pe236twL8j3IYT1F7GfRgGcW6MWxzZjLIkuHIg==} + delayed-stream@1.0.0: resolution: {integrity: sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==} engines: {node: '>=0.4.0'} + denque@2.1.0: + resolution: {integrity: sha512-HVQE3AAb/pxF8fQAoiqpvg9i3evqug3hoiwakOyZAwJm+6vZehbkYXZ0l4JxS+I3QxM97v5aaRNhj8v5oBhekw==} + engines: {node: '>=0.10'} + depd@2.0.0: resolution: {integrity: sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==} engines: {node: '>= 0.8'} + destr@2.0.5: + resolution: {integrity: sha512-ugFTXCtDZunbzasqBxrK93Ik/DRYsO6S/fedkWEMKqt04xZ4csmnmwGDBAb07QWNaGMAmnTIemsYZCksjATwsA==} + detect-newline@3.1.0: resolution: {integrity: sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA==} engines: {node: '>=8'} @@ -912,8 +1218,12 @@ packages: resolution: {integrity: sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==} engines: {node: '>=6.0.0'} - dotenv@16.5.0: - resolution: {integrity: sha512-m/C+AwOAr9/W1UOIZUo232ejMNnJAJtYQjUbHoNTBNTJSvqzzDh7vnrei3o3r3m9blf6ZoDkvcw0VmozNRFJxg==} + dotenv@16.6.1: + resolution: {integrity: sha512-uBq4egWHTcTt33a72vpSG0z3HnPuIl6NqYcTrKEg2azoEyl2hpW0zqlxysq2pK9HlDIHyHyakeYaYnSAwd8bow==} + engines: {node: '>=12'} + + dotenv@17.3.1: + resolution: {integrity: sha512-IO8C/dzEb6O3F9/twg6ZLXz164a2fhTnEWb95H23Dm4OuN+92NmEAlTrupP9VW6Jm3sO26tQlqyvyi4CsnY9GA==} engines: {node: '>=12'} dunder-proto@1.0.1: @@ -923,10 +1233,8 @@ packages: ee-first@1.1.1: resolution: {integrity: sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==} - ejs@3.1.10: - resolution: {integrity: sha512-UeJmFfOrAQS8OJWPZ4qtgHyWExa088/MtK5UEyoJGFH67cDEXkZSviOiKRCZ4Xij0zxI3JECgYs3oKx+AizQBA==} - engines: {node: '>=0.10.0'} - hasBin: true + effect@3.18.4: + resolution: {integrity: sha512-b1LXQJLe9D11wfnOKAk3PKxuqYshQ0Heez+y5pnkd3jLj1yx9QhM72zZ9uUrOQyNvrs2GZZd/3maL0ZV18YuDA==} electron-to-chromium@1.5.136: resolution: {integrity: sha512-kL4+wUTD7RSA5FHx5YwWtjDnEEkIIikFgWHR4P6fqjw1PPLlqYkxeOb++wAauAssat0YClCy8Y3C5SxgSkjibQ==} @@ -938,6 +1246,10 @@ packages: emoji-regex@8.0.0: resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==} + empathic@2.0.0: + resolution: {integrity: sha512-i6UzDscO/XfAcNYD75CfICkmfLedpyPDdozrLMmQc5ORaQcdMoc21OnlEylMIqI7U8eniKrPMxxtj8k0vhmJhA==} + engines: {node: '>=14'} + encodeurl@2.0.0: resolution: {integrity: sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==} engines: {node: '>= 0.8'} @@ -961,13 +1273,8 @@ packages: resolution: {integrity: sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==} engines: {node: '>= 0.4'} - esbuild-register@3.6.0: - resolution: {integrity: sha512-H2/S7Pm8a9CL1uhp9OvjwrBh5Pvx0H8qVOxNu8Wed9Y7qv56MPtq+GGM8RJpq6glYJn9Wspr8uw7l55uyinNeg==} - peerDependencies: - esbuild: '>=0.12 <1' - - esbuild@0.25.2: - resolution: {integrity: sha512-16854zccKPnC+toMywC+uKNeYSv+/eXkevRAfwRD/G9Cleq66m8XFIrigkbvauLLlCfDL45Q2cWegSg53gGBnQ==} + esbuild@0.27.4: + resolution: {integrity: sha512-Rq4vbHnYkK5fws5NF7MYTU68FPRE1ajX7heQ/8QXXWqNgqqJ/GkmmyxIzUnf2Sr/bakf8l54716CcMGHYhMrrQ==} engines: {node: '>=18'} hasBin: true @@ -982,11 +1289,57 @@ packages: resolution: {integrity: sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==} engines: {node: '>=8'} + escape-string-regexp@4.0.0: + resolution: {integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==} + engines: {node: '>=10'} + + eslint-scope@8.4.0: + resolution: {integrity: sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + eslint-visitor-keys@3.4.3: + resolution: {integrity: sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + + eslint-visitor-keys@4.2.1: + resolution: {integrity: sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + eslint-visitor-keys@5.0.1: + resolution: {integrity: sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA==} + engines: {node: ^20.19.0 || ^22.13.0 || >=24} + + eslint@9.39.4: + resolution: {integrity: sha512-XoMjdBOwe/esVgEvLmNsD3IRHkm7fbKIUGvrleloJXUZgDHig2IPWNniv+GwjyJXzuNqVjlr5+4yVUZjycJwfQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + hasBin: true + peerDependencies: + jiti: '*' + peerDependenciesMeta: + jiti: + optional: true + + espree@10.4.0: + resolution: {integrity: sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + esprima@4.0.1: resolution: {integrity: sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==} engines: {node: '>=4'} hasBin: true + esquery@1.7.0: + resolution: {integrity: sha512-Ap6G0WQwcU/LHsvLwON1fAQX9Zp0A2Y6Y/cJBl9r/JbW90Zyg4/zbG6zzKa2OTALELarYHmKu0GhpM5EO+7T0g==} + engines: {node: '>=0.10'} + + esrecurse@4.3.0: + resolution: {integrity: sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==} + engines: {node: '>=4.0'} + + estraverse@5.3.0: + resolution: {integrity: sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==} + engines: {node: '>=4.0'} + esutils@2.0.3: resolution: {integrity: sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==} engines: {node: '>=0.10.0'} @@ -1007,21 +1360,44 @@ packages: resolution: {integrity: sha512-2Zks0hf1VLFYI1kbh0I5jP3KHHyCHpkfyHBzsSXRFgl/Bg9mWYfMW8oD+PdMPlEwy5HNsR9JutYy6pMeOh61nw==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - express@5.1.0: - resolution: {integrity: sha512-DT9ck5YIRU+8GYzzU5kT3eHGA5iL+1Zd0EutOmTE9Dtk+Tvuzd23VBU+ec7HPNSTxXYO55gPV/hq4pSBJDjFpA==} + express@5.2.1: + resolution: {integrity: sha512-hIS4idWWai69NezIdRt2xFVofaF4j+6INOpJlVOLDO8zXGpUVEVzIYk12UUi2JzjEzWL3IOAxcTubgz9Po0yXw==} engines: {node: '>= 18'} + exsolve@1.0.8: + resolution: {integrity: sha512-LmDxfWXwcTArk8fUEnOfSZpHOJ6zOMUJKOtFLFqJLoKJetuQG874Uc7/Kki7zFLzYybmZhp1M7+98pfMqeX8yA==} + + fast-check@3.23.2: + resolution: {integrity: sha512-h5+1OzzfCC3Ef7VbtKdcv7zsstUQwUDlYpUTvjeUsJAssPgLn7QzbboPtL5ro04Mq0rPOsMzl7q5hIbRs2wD1A==} + engines: {node: '>=8.0.0'} + + fast-deep-equal@3.1.3: + resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==} + fast-json-stable-stringify@2.1.0: resolution: {integrity: sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==} + fast-levenshtein@2.0.6: + resolution: {integrity: sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==} + fast-safe-stringify@2.1.1: resolution: {integrity: sha512-W+KJc2dmILlPplD/H4K9l9LcAHAfPtP6BY84uVLXQ6Evcz9Lcg33Y2z1IVblT6xdY54PXYVHEv+0Wpq8Io6zkA==} fb-watchman@2.0.2: resolution: {integrity: sha512-p5161BqbuCaSnB8jIbzQHOlpgsPmK5rJVDfDKO91Axs5NC1uu3HRQm6wt9cd9/+GtQQIO53JdGXXoyDpTAsgYA==} - filelist@1.0.4: - resolution: {integrity: sha512-w1cEuf3S+DrLCQL7ET6kz+gmlJdbq9J7yXCSjK/OZCPA+qEN1WyF4ZAf0YYJa4/shHJra2t/d/r8SV4Ji+x+8Q==} + fdir@6.5.0: + resolution: {integrity: sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==} + engines: {node: '>=12.0.0'} + peerDependencies: + picomatch: ^3 || ^4 + peerDependenciesMeta: + picomatch: + optional: true + + file-entry-cache@8.0.0: + resolution: {integrity: sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==} + engines: {node: '>=16.0.0'} fill-range@7.1.1: resolution: {integrity: sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==} @@ -1035,12 +1411,32 @@ packages: resolution: {integrity: sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==} engines: {node: '>=8'} + find-up@5.0.0: + resolution: {integrity: sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==} + engines: {node: '>=10'} + + flat-cache@4.0.1: + resolution: {integrity: sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==} + engines: {node: '>=16'} + + flatted@3.4.1: + resolution: {integrity: sha512-IxfVbRFVlV8V/yRaGzk0UVIcsKKHMSfYw66T/u4nTwlWteQePsxe//LjudR1AMX4tZW3WFCh3Zqa/sjlqpbURQ==} + + foreground-child@3.3.1: + resolution: {integrity: sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==} + engines: {node: '>=14'} + form-data@4.0.2: resolution: {integrity: sha512-hGfm/slu0ZabnNt4oaRZ6uREyfCj6P4fT/n6A1rGV+Z0VdGXjfOhVUpkn6qVQONHGIFwmveGXyDs75+nr6FM8w==} engines: {node: '>= 6'} - formidable@3.5.2: - resolution: {integrity: sha512-Jqc1btCy3QzRbJaICGwKcBfGWuLADRerLzDqi2NwSt/UkXLsHJw2TVResiaoBufHVHy9aSgClOHCeJsSsFLTbg==} + form-data@4.0.5: + resolution: {integrity: sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w==} + engines: {node: '>= 6'} + + formidable@3.5.4: + resolution: {integrity: sha512-YikH+7CUTOtP44ZTnUhR7Ic2UASBPOqmaRkRKxRbywPTe5VxF7RRCck4af9wutiZ/QKM5nME9Bie2fFaPz5Gug==} + engines: {node: '>=14.0.0'} forwarded@0.2.0: resolution: {integrity: sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==} @@ -1061,6 +1457,9 @@ packages: function-bind@1.1.2: resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==} + generate-function@2.3.1: + resolution: {integrity: sha512-eeB5GfMNeevm/GRYq20ShmsaGcmI81kIX2K9XQx5miC8KdHaC6Jm0qQ8ZNeGOi7wYB8OsdxKs+Y2oVuTFuVwKQ==} + gensync@1.0.0-beta.2: resolution: {integrity: sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==} engines: {node: '>=6.9.0'} @@ -1077,6 +1476,9 @@ packages: resolution: {integrity: sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q==} engines: {node: '>=8.0.0'} + get-port-please@3.2.0: + resolution: {integrity: sha512-I9QVvBw5U/hw3RmWpYKRumUeaDgxTPd401x364rLmWBJcOQ753eov1eTgzDqRG9bqFIfDc7gfzcQEWrUri3o1A==} + get-proto@1.0.1: resolution: {integrity: sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==} engines: {node: '>= 0.4'} @@ -1085,6 +1487,17 @@ packages: resolution: {integrity: sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==} engines: {node: '>=10'} + get-tsconfig@4.13.6: + resolution: {integrity: sha512-shZT/QMiSHc/YBLxxOkMtgSid5HFoauqCE3/exfsEcwg1WkeqjG+V40yBbBrsD+jW2HDXcs28xOfcbm2jI8Ddw==} + + giget@2.0.0: + resolution: {integrity: sha512-L5bGsVkxJbJgdnwyuheIunkGatUF/zssUoxxjACCseZYAVbaqdh9Tsmmlkl8vYan09H7sbvKt4pS8GqKLBrEzA==} + hasBin: true + + glob-parent@6.0.2: + resolution: {integrity: sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==} + engines: {node: '>=10.13.0'} + glob@7.1.6: resolution: {integrity: sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==} deprecated: Glob versions prior to v9 are no longer supported @@ -1093,6 +1506,14 @@ packages: resolution: {integrity: sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==} engines: {node: '>=4'} + globals@14.0.0: + resolution: {integrity: sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==} + engines: {node: '>=18'} + + globals@15.15.0: + resolution: {integrity: sha512-7ACyT3wmyp3I61S4fG682L0VA2RGD9otkqGJIwNUMF1SWUombIIk+af1unuDYgMm082aHYwD+mzJvv9Iu8dsgg==} + engines: {node: '>=18'} + gopd@1.2.0: resolution: {integrity: sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==} engines: {node: '>= 0.4'} @@ -1100,6 +1521,17 @@ packages: graceful-fs@4.2.11: resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==} + grammex@3.1.12: + resolution: {integrity: sha512-6ufJOsSA7LcQehIJNCO7HIBykfM7DXQual0Ny780/DEcJIpBlHRvcqEBWGPYd7hrXL2GJ3oJI1MIhaXjWmLQOQ==} + + graphmatch@1.1.1: + resolution: {integrity: sha512-5ykVn/EXM1hF0XCaWh05VbYvEiOL2lY1kBxZtaYsyvjp7cmWOU1XsAdfQBwClraEofXDT197lFbXOEVMHpvQOg==} + + handlebars@4.7.8: + resolution: {integrity: sha512-vafaFqs8MZkRrSX7sFVUdo3ap/eNiLnb4IakshzvP56X5Nr1iGKAIqdX6tMlm6HcNRIkr6AxO5jFEoJzzpT8aQ==} + engines: {node: '>=0.4.7'} + hasBin: true + has-flag@4.0.0: resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==} engines: {node: '>=8'} @@ -1120,9 +1552,9 @@ packages: resolution: {integrity: sha512-jOiHyAZsmnr8LqoPGmCjYAaiuWwjAPLgY8ZX2XrmHawt99/u1y6RgrZMTeoPfpUbV96HOalYgz1qzkRbw54Pmg==} engines: {node: '>=18.0.0'} - hexoid@2.0.0: - resolution: {integrity: sha512-qlspKUK7IlSQv2o+5I7yhUd7TxlOG2Vr5LTa3ve2XSNVKAL/n/u/7KLvKmFNimomDIKvZFXWHv0T12mv7rT8Aw==} - engines: {node: '>=8'} + hono@4.11.4: + resolution: {integrity: sha512-U7tt8JsyrxSRKspfhtLET79pU8K+tInj5QZXs1jSugO1Vq5dFj3kmZsRldo29mTBfcjDRVRXrEZ6LS63Cog9ZA==} + engines: {node: '>=16.9.0'} html-escaper@2.0.2: resolution: {integrity: sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==} @@ -1131,14 +1563,33 @@ packages: resolution: {integrity: sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==} engines: {node: '>= 0.8'} + http-errors@2.0.1: + resolution: {integrity: sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==} + engines: {node: '>= 0.8'} + + http-status-codes@2.3.0: + resolution: {integrity: sha512-RJ8XvFvpPM/Dmc5SV+dC4y5PCeOhT3x1Hq0NU3rjGeg5a/CqlhZ7uudknPwZFz4aeAXDcbAyaeP7GAo9lvngtA==} + human-signals@2.1.0: resolution: {integrity: sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==} engines: {node: '>=10.17.0'} - iconv-lite@0.6.3: - resolution: {integrity: sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==} + iconv-lite@0.7.2: + resolution: {integrity: sha512-im9DjEDQ55s9fL4EYzOAv0yMqmMBSZp6G0VvFyTMPKWxiSBHUj9NW/qqLmXUwXrrM7AvqSlTCfvqRb0cM8yYqw==} engines: {node: '>=0.10.0'} + ignore@5.3.2: + resolution: {integrity: sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==} + engines: {node: '>= 4'} + + ignore@7.0.5: + resolution: {integrity: sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==} + engines: {node: '>= 4'} + + import-fresh@3.3.1: + resolution: {integrity: sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==} + engines: {node: '>=6'} + import-local@3.2.0: resolution: {integrity: sha512-2SPlun1JUPWoM6t3F0dw0FkCF/jWY8kttcY4f599GLTSjh2OCuuhdTkJQsEcZzBqbXZGKMK2OqW1oZsjtf/gQA==} engines: {node: '>=8'} @@ -1166,6 +1617,10 @@ packages: resolution: {integrity: sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==} engines: {node: '>= 0.4'} + is-extglob@2.1.1: + resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==} + engines: {node: '>=0.10.0'} + is-fullwidth-code-point@3.0.0: resolution: {integrity: sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==} engines: {node: '>=8'} @@ -1174,6 +1629,10 @@ packages: resolution: {integrity: sha512-cTIB4yPYL/Grw0EaSzASzg6bBy9gqCofvWN8okThAYIxKJZC+udlRAmGbM0XLeniEJSs8uEgHPGuHSe1XsOLSQ==} engines: {node: '>=6'} + is-glob@4.0.3: + resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==} + engines: {node: '>=0.10.0'} + is-number@7.0.0: resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==} engines: {node: '>=0.12.0'} @@ -1181,6 +1640,9 @@ packages: is-promise@4.0.0: resolution: {integrity: sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==} + is-property@1.0.2: + resolution: {integrity: sha512-Ks/IoX00TtClbGQr4TWXemAnktAQvYB7HzcCxDGqEZU6oCmb2INHuOoKxbtR+HFkmYWBKv/dOZtGRiAjDhj92g==} + is-stream@2.0.1: resolution: {integrity: sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==} engines: {node: '>=8'} @@ -1212,11 +1674,6 @@ packages: resolution: {integrity: sha512-BewmUXImeuRk2YY0PVbxgKAysvhRPUQE0h5QRM++nVWyubKGV0l8qQ5op8+B2DOmwSe63Jivj0BjkPQVf8fP5g==} engines: {node: '>=8'} - jake@10.9.2: - resolution: {integrity: sha512-2P4SQ0HrLQ+fw6llpLnOaGAvN2Zu6778SJMrCUwns4fOoG9ayrTiZk3VV8sCPkVZF8ab0zksVpS8FDY5pRCNBA==} - engines: {node: '>=10'} - hasBin: true - jest-changed-files@29.7.0: resolution: {integrity: sha512-fEArFiwf1BpQ+4bXSprcDc3/x4HSzL4al2tozwVpDFpsxALjLYdyiIK4e5Vz66GQJIbXJ82+35PtysofptNX2w==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} @@ -1346,6 +1803,10 @@ packages: node-notifier: optional: true + jiti@2.6.1: + resolution: {integrity: sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ==} + hasBin: true + js-tokens@4.0.0: resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} @@ -1357,19 +1818,35 @@ packages: resolution: {integrity: sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==} hasBin: true + js-yaml@4.1.1: + resolution: {integrity: sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==} + hasBin: true + jsesc@3.1.0: resolution: {integrity: sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==} engines: {node: '>=6'} hasBin: true + json-buffer@3.0.1: + resolution: {integrity: sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==} + json-parse-even-better-errors@2.3.1: resolution: {integrity: sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==} + json-schema-traverse@0.4.1: + resolution: {integrity: sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==} + + json-stable-stringify-without-jsonify@1.0.1: + resolution: {integrity: sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==} + json5@2.2.3: resolution: {integrity: sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==} engines: {node: '>=6'} hasBin: true + keyv@4.5.4: + resolution: {integrity: sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==} + kleur@3.0.3: resolution: {integrity: sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==} engines: {node: '>=6'} @@ -1378,6 +1855,14 @@ packages: resolution: {integrity: sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==} engines: {node: '>=6'} + levn@0.4.1: + resolution: {integrity: sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==} + engines: {node: '>= 0.8.0'} + + lilconfig@2.1.0: + resolution: {integrity: sha512-utWOt/GHzuUxnLKxB6dk81RoOeoNeHgbrXiuGk4yyF5qlRz+iIVWu56E2fqGHFrXz0QNUhLB/8nKqvRH66JKGQ==} + engines: {node: '>=10'} + lines-and-columns@1.2.4: resolution: {integrity: sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==} @@ -1385,6 +1870,10 @@ packages: resolution: {integrity: sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==} engines: {node: '>=8'} + locate-path@6.0.0: + resolution: {integrity: sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==} + engines: {node: '>=10'} + lodash.get@4.4.2: resolution: {integrity: sha512-z+Uw/vLuy6gQe8cfaFWD7p0wVv8fJl3mbzXh33RS+0oW2wvUqiRXiQ69gLWSLpgB5/6sU+r6BlQR0MBILadqTQ==} deprecated: This package is deprecated. Use the optional chaining (?.) operator instead. @@ -1396,12 +1885,25 @@ packages: lodash.memoize@4.1.2: resolution: {integrity: sha512-t7j+NzmgnQzTAYXcsHYLgimltOV1MXHtlOWf6GjL9Kj8GK5FInw5JotxvbOs+IvV1/Dzo04/fCGfLVs7aXb4Ag==} + lodash.merge@4.6.2: + resolution: {integrity: sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==} + lodash.mergewith@4.6.2: resolution: {integrity: sha512-GK3g5RPZWTRSeLSpgP8Xhra+pnjBC56q9FZYe1d5RN3TJ35dbkGy3YqBSMbyCrlbi+CM9Z3Jk5yTL7RCsqboyQ==} + lodash@4.17.21: + resolution: {integrity: sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==} + + long@5.3.2: + resolution: {integrity: sha512-mNAgZ1GmyNhD7AuqnTG3/VQ26o760+ZYBPKjPvugO8+nLbYfX6TVpJPseBvopbdY+qpZ/lKUnmEc1LeZYS3QAA==} + lru-cache@5.1.1: resolution: {integrity: sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==} + lru.min@1.1.4: + resolution: {integrity: sha512-DqC6n3QQ77zdFpCMASA1a3Jlb64Hv2N2DciFGkO/4L9+q/IpIAuRlKOvCXabtRW6cQf8usbmM6BE/TOPysCdIA==} + engines: {bun: '>=1.0.0', deno: '>=1.30.0', node: '>=8.0.0'} + make-dir@4.0.0: resolution: {integrity: sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==} engines: {node: '>=10'} @@ -1460,15 +1962,21 @@ packages: resolution: {integrity: sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==} engines: {node: '>=6'} + minimatch@10.2.4: + resolution: {integrity: sha512-oRjTw/97aTBN0RHbYCdtF1MQfvusSIBQM0IZEgzl6426+8jSC0nF1a/GmnVLpfB9yyr6g6FTqWqiZVbxrtaCIg==} + engines: {node: 18 || 20 || >=22} + minimatch@3.1.2: resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==} - minimatch@5.1.6: - resolution: {integrity: sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==} - engines: {node: '>=10'} + minimatch@3.1.5: + resolution: {integrity: sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==} - morgan@1.10.0: - resolution: {integrity: sha512-AbegBVI4sh6El+1gNwvD5YIck7nSA36weD7xvIxG4in80j/UoK8AEGaWnnz8v1GxonMCltmlNs5ZKbGvl9b1XQ==} + minimist@1.2.8: + resolution: {integrity: sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==} + + morgan@1.10.1: + resolution: {integrity: sha512-223dMRJtI/l25dJKWpgij2cMtywuG/WiUKXdvwfbhGKBhy1puASqXwFzmWZ7+K73vUPoR7SS2Qz2cI/g9MKw0A==} engines: {node: '>= 0.8.0'} ms@2.0.0: @@ -1477,6 +1985,14 @@ packages: ms@2.1.3: resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} + mysql2@3.15.3: + resolution: {integrity: sha512-FBrGau0IXmuqg4haEZRBfHNWB5mUARw6hNwPDXXGg0XzVJ50mr/9hb267lvpVMnhZ1FON3qNd4Xfcez1rbFwSg==} + engines: {node: '>= 8.0'} + + named-placeholders@1.1.6: + resolution: {integrity: sha512-Tz09sEL2EEuv5fFowm419c1+a/jSMiBjI9gHxVLrVdbUkkNUUfjsVYs9pVZu5oCon/kmRh9TfLEObFtkVxmY0w==} + engines: {node: '>=8.0.0'} + natural-compare@1.4.0: resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==} @@ -1484,6 +2000,12 @@ packages: resolution: {integrity: sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==} engines: {node: '>= 0.6'} + neo-async@2.6.2: + resolution: {integrity: sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==} + + node-fetch-native@1.6.7: + resolution: {integrity: sha512-g9yhqoedzIUm0nTnTqAQvueMPVOuIY16bqgAJJC8XOOubYFNwz6IER9qs0Gq2Xd0+CecCKFjtdDTMA4u4xG06Q==} + node-int64@0.4.0: resolution: {integrity: sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw==} @@ -1498,6 +2020,11 @@ packages: resolution: {integrity: sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==} engines: {node: '>=8'} + nypm@0.6.5: + resolution: {integrity: sha512-K6AJy1GMVyfyMXRVB88700BJqNUkByijGJM8kEHpLdcAt+vSQAVfkWWHYzuRXHSY6xA2sNc5RjTj0p9rE2izVQ==} + engines: {node: '>=18'} + hasBin: true + object-assign@4.1.1: resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==} engines: {node: '>=0.10.0'} @@ -1506,6 +2033,12 @@ packages: resolution: {integrity: sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==} engines: {node: '>= 0.4'} + obuf@1.1.2: + resolution: {integrity: sha512-PX1wu0AmAdPqOL1mWhqmlOd8kOIZQwGZw6rh7uby9fTc5lhaOWFLX3I6R1hrF9k3zUY40e6igsLGkDXK92LJNg==} + + ohash@2.0.11: + resolution: {integrity: sha512-RdR9FQrFwNBNXAr4GixM8YaRZRJ5PUWbKYbE5eOsrwAjJW0q2REGcf79oYPsLyskQCZG1PLN+S/K1V00joZAoQ==} + on-finished@2.3.0: resolution: {integrity: sha512-ikqdkGAAyf/X/gPhXGvfgAytDZtDbr+bkNUJ0N9h5MI/dmdgCs3l6hoHrcUv41sRKew3jIwrp4qQDXiK99Utww==} engines: {node: '>= 0.8'} @@ -1514,8 +2047,8 @@ packages: resolution: {integrity: sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==} engines: {node: '>= 0.8'} - on-headers@1.0.2: - resolution: {integrity: sha512-pZAE+FJLoyITytdqK0U5s+FIpjN0JP3OzFi/u8Rx+EV5/W+JTWGXG8xFzevE7AjBfDqHv/8vL8qQsIhHnqRkrA==} + on-headers@1.1.0: + resolution: {integrity: sha512-737ZY3yNnXy37FHkQxPzt4UZ2UWPWiCZWLvFZ4fu5cueciegX0zGPnrlY6bwRg4FdQOe9YU8MkmJwGhoMybl8A==} engines: {node: '>= 0.8'} once@1.4.0: @@ -1528,6 +2061,10 @@ packages: openapi-types@12.1.3: resolution: {integrity: sha512-N4YtSYJqghVu4iek2ZUvcN/0aqH1kRDuNqzcycDxhOUpg7GdvLa2F3DgS6yBNhInhv2r/6I0Flkn7CqL8+nIcw==} + optionator@0.9.4: + resolution: {integrity: sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==} + engines: {node: '>= 0.8.0'} + p-limit@2.3.0: resolution: {integrity: sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==} engines: {node: '>=6'} @@ -1540,10 +2077,18 @@ packages: resolution: {integrity: sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==} engines: {node: '>=8'} + p-locate@5.0.0: + resolution: {integrity: sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==} + engines: {node: '>=10'} + p-try@2.2.0: resolution: {integrity: sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==} engines: {node: '>=6'} + parent-module@1.0.1: + resolution: {integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==} + engines: {node: '>=6'} + parse-json@5.2.0: resolution: {integrity: sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==} engines: {node: '>=8'} @@ -1571,6 +2116,54 @@ packages: resolution: {integrity: sha512-TdrF7fW9Rphjq4RjrW0Kp2AW0Ahwu9sRGTkS6bvDi0SCwZlEZYmcfDbEsTz8RVk0EHIS/Vd1bv3JhG+1xZuAyQ==} engines: {node: '>=16'} + pathe@2.0.3: + resolution: {integrity: sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==} + + perfect-debounce@1.0.0: + resolution: {integrity: sha512-xCy9V055GLEqoFaHoC1SoLIaLmWctgCUaBaWxDZ7/Zx4CTyX7cJQLJOok/orfjZAh9kEYpjJa4d0KcJmCbctZA==} + + pg-cloudflare@1.3.0: + resolution: {integrity: sha512-6lswVVSztmHiRtD6I8hw4qP/nDm1EJbKMRhf3HCYaqud7frGysPv7FYJ5noZQdhQtN2xJnimfMtvQq21pdbzyQ==} + + pg-connection-string@2.12.0: + resolution: {integrity: sha512-U7qg+bpswf3Cs5xLzRqbXbQl85ng0mfSV/J0nnA31MCLgvEaAo7CIhmeyrmJpOr7o+zm0rXK+hNnT5l9RHkCkQ==} + + pg-int8@1.0.1: + resolution: {integrity: sha512-WCtabS6t3c8SkpDBUlb1kjOs7l66xsGdKpIPZsg4wR+B3+u9UAum2odSsF9tnvxg80h4ZxLWMy4pRjOsFIqQpw==} + engines: {node: '>=4.0.0'} + + pg-numeric@1.0.2: + resolution: {integrity: sha512-BM/Thnrw5jm2kKLE5uJkXqqExRUY/toLHda65XgFTBTFYZyopbKjBe29Ii3RbkvlsMoFwD+tHeGaCjjv0gHlyw==} + engines: {node: '>=4'} + + pg-pool@3.13.0: + resolution: {integrity: sha512-gB+R+Xud1gLFuRD/QgOIgGOBE2KCQPaPwkzBBGC9oG69pHTkhQeIuejVIk3/cnDyX39av2AxomQiyPT13WKHQA==} + peerDependencies: + pg: '>=8.0' + + pg-protocol@1.13.0: + resolution: {integrity: sha512-zzdvXfS6v89r6v7OcFCHfHlyG/wvry1ALxZo4LqgUoy7W9xhBDMaqOuMiF3qEV45VqsN6rdlcehHrfDtlCPc8w==} + + pg-types@2.2.0: + resolution: {integrity: sha512-qTAAlrEsl8s4OiEQY69wDvcMIdQN6wdz5ojQiOy6YRMuynxenON0O5oCpJI6lshc6scgAY8qvJ2On/p+CXY0GA==} + engines: {node: '>=4'} + + pg-types@4.1.0: + resolution: {integrity: sha512-o2XFanIMy/3+mThw69O8d4n1E5zsLhdO+OPqswezu7Z5ekP4hYDqlDjlmOpYMbzY2Br0ufCwJLdDIXeNVwcWFg==} + engines: {node: '>=10'} + + pg@8.20.0: + resolution: {integrity: sha512-ldhMxz2r8fl/6QkXnBD3CR9/xg694oT6DZQ2s6c/RI28OjtSOpxnPrUCGOBJ46RCUxcWdx3p6kw/xnDHjKvaRA==} + engines: {node: '>= 16.0.0'} + peerDependencies: + pg-native: '>=3.0.1' + peerDependenciesMeta: + pg-native: + optional: true + + pgpass@1.0.5: + resolution: {integrity: sha512-FdW9r/jQZhSeohs1Z3sI1yxFQNFvMcnmfuj4WBMUTxOrAyLMaTcE1aAMBiTlbMNaXvBCQuVi0R7hd8udDSP7ug==} + picocolors@1.1.1: resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==} @@ -1578,6 +2171,10 @@ packages: resolution: {integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==} engines: {node: '>=8.6'} + picomatch@4.0.3: + resolution: {integrity: sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==} + engines: {node: '>=12'} + pirates@4.0.7: resolution: {integrity: sha512-TfySrs/5nm8fQJDcBDuUng3VOUKsd7S+zqvbOTiGXHfxX4wK31ard+hoNuvkicM/2YFzlpDgABOevKSsB4G/FA==} engines: {node: '>= 6'} @@ -1586,17 +2183,66 @@ packages: resolution: {integrity: sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==} engines: {node: '>=8'} + pkg-types@2.3.0: + resolution: {integrity: sha512-SIqCzDRg0s9npO5XQ3tNZioRY1uK06lA41ynBC1YmFTmnY6FjUjVt6s4LoADmwoig1qqD0oK8h1p/8mlMx8Oig==} + + postgres-array@2.0.0: + resolution: {integrity: sha512-VpZrUqU5A69eQyW2c5CA1jtLecCsN2U/bD6VilrFDWq5+5UIEVO7nazS3TEcHf1zuPYO/sqGvUvW62g86RXZuA==} + engines: {node: '>=4'} + + postgres-array@3.0.4: + resolution: {integrity: sha512-nAUSGfSDGOaOAEGwqsRY27GPOea7CNipJPOA7lPbdEpx5Kg3qzdP0AaWC5MlhTWV9s4hFX39nomVZ+C4tnGOJQ==} + engines: {node: '>=12'} + + postgres-bytea@1.0.1: + resolution: {integrity: sha512-5+5HqXnsZPE65IJZSMkZtURARZelel2oXUEO8rH83VS/hxH5vv1uHquPg5wZs8yMAfdv971IU+kcPUczi7NVBQ==} + engines: {node: '>=0.10.0'} + + postgres-bytea@3.0.0: + resolution: {integrity: sha512-CNd4jim9RFPkObHSjVHlVrxoVQXz7quwNFpz7RY1okNNme49+sVyiTvTRobiLV548Hx/hb1BG+iE7h9493WzFw==} + engines: {node: '>= 6'} + + postgres-date@1.0.7: + resolution: {integrity: sha512-suDmjLVQg78nMK2UZ454hAG+OAW+HQPZ6n++TNDUX+L0+uUlLywnoxJKDou51Zm+zTCjrCl0Nq6J9C5hP9vK/Q==} + engines: {node: '>=0.10.0'} + + postgres-date@2.1.0: + resolution: {integrity: sha512-K7Juri8gtgXVcDfZttFKVmhglp7epKb1K4pgrkLxehjqkrgPhfG6OO8LHLkfaqkbpjNRnra018XwAr1yQFWGcA==} + engines: {node: '>=12'} + + postgres-interval@1.2.0: + resolution: {integrity: sha512-9ZhXKM/rw350N1ovuWHbGxnGh/SNJ4cnxHiM0rxE4VN41wsg8P8zWn9hv/buK00RP4WvlOyr/RBDiptyxVbkZQ==} + engines: {node: '>=0.10.0'} + + postgres-interval@3.0.0: + resolution: {integrity: sha512-BSNDnbyZCXSxgA+1f5UU2GmwhoI0aU5yMxRGO8CdFEcY2BQF9xm/7MqKnYoM1nJDk8nONNWDk9WeSmePFhQdlw==} + engines: {node: '>=12'} + + postgres-range@1.1.4: + resolution: {integrity: sha512-i/hbxIE9803Alj/6ytL7UHQxRvZkI9O4Sy+J3HGc4F4oo/2eQAjTSNJ0bfxyse3bH0nuVesCk+3IRLaMtG3H6w==} + + postgres@3.4.7: + resolution: {integrity: sha512-Jtc2612XINuBjIl/QTWsV5UvE8UHuNblcO3vVADSrKsrc6RqGX6lOW1cEo3CM2v0XG4Nat8nI+YM7/f26VxXLw==} + engines: {node: '>=12'} + + prelude-ls@1.2.1: + resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==} + engines: {node: '>= 0.8.0'} + pretty-format@29.7.0: resolution: {integrity: sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - prisma@6.6.0: - resolution: {integrity: sha512-SYCUykz+1cnl6Ugd8VUvtTQq5+j1Q7C0CtzKPjQ8JyA2ALh0EEJkMCS+KgdnvKW1lrxjtjCyJSHOOT236mENYg==} - engines: {node: '>=18.18'} + prisma@7.5.0: + resolution: {integrity: sha512-n30qZpWehaYQzigLjmuPisyEsvOzHt7bZeRyg8gZ5DvJo9FGjD+gNaY59Ns3hlLD5/jZH5GBeftIss0jDbUoLg==} + engines: {node: ^20.19 || ^22.12 || >=24.0} hasBin: true peerDependencies: - typescript: '>=5.1.0' + better-sqlite3: '>=9.0.0' + typescript: '>=5.4.0' peerDependenciesMeta: + better-sqlite3: + optional: true typescript: optional: true @@ -1604,10 +2250,17 @@ packages: resolution: {integrity: sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q==} engines: {node: '>= 6'} + proper-lockfile@4.1.2: + resolution: {integrity: sha512-TjNPblN4BwAWMXU8s9AEz4JmQxnD1NNL7bNOY/AKUzyamc379FWASUhc/K1pL2noVb+XmZKLL68cjzLsiOAMaA==} + proxy-addr@2.0.7: resolution: {integrity: sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==} engines: {node: '>= 0.10'} + punycode@2.3.1: + resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==} + engines: {node: '>=6'} + pure-rand@6.1.0: resolution: {integrity: sha512-bVWawvoZoBYpp6yIoQtQXHZjmz35RSVHnUOTefl8Vcjr8snTPY1wnpSPMWekcFwbxI6gtmT7rSYPFvz71ldiOA==} @@ -1615,17 +2268,43 @@ packages: resolution: {integrity: sha512-YWWTjgABSKcvs/nWBi9PycY/JiPJqOD4JA6o9Sej2AtvSGarXxKC3OQSk4pAarbdQlKAh5D4FCQkJNkW+GAn3w==} engines: {node: '>=0.6'} + qs@6.15.0: + resolution: {integrity: sha512-mAZTtNCeetKMH+pSjrb76NAM8V9a05I9aBZOHztWy/UqcJdQYNsf59vrRKWnojAT9Y+GbIvoTBC++CPHqpDBhQ==} + engines: {node: '>=0.6'} + range-parser@1.2.1: resolution: {integrity: sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==} engines: {node: '>= 0.6'} - raw-body@3.0.0: - resolution: {integrity: sha512-RmkhL8CAyCRPXCE28MMH0z2PNWQBNk2Q09ZdxM9IOOXwxwZbN+qbWaatPkdkWIKL2ZVDImrN/pK5HTRz2PcS4g==} - engines: {node: '>= 0.8'} + raw-body@3.0.2: + resolution: {integrity: sha512-K5zQjDllxWkf7Z5xJdV0/B0WTNqx6vxG70zJE4N0kBs4LovmEYWJzQGxC9bS9RAKu3bgM40lrd5zoLJ12MQ5BA==} + engines: {node: '>= 0.10'} + + rc9@2.1.2: + resolution: {integrity: sha512-btXCnMmRIBINM2LDZoEmOogIZU7Qe7zn4BpomSKZ/ykbLObuBdvG+mFq11DL6fjH1DRwHhrlgtYWG96bJiC7Cg==} + + react-dom@19.2.4: + resolution: {integrity: sha512-AXJdLo8kgMbimY95O2aKQqsz2iWi9jMgKJhRBAxECE4IFxfcazB2LmzloIoibJI3C12IlY20+KFaLv+71bUJeQ==} + peerDependencies: + react: ^19.2.4 react-is@18.3.1: resolution: {integrity: sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==} + react@19.2.4: + resolution: {integrity: sha512-9nfp2hYpCwOjAN+8TZFGhtWEwgvWHXqESH8qT89AT/lWklpLON22Lc8pEtnpsZz7VmawabSU0gCjnj8aC0euHQ==} + engines: {node: '>=0.10.0'} + + readdirp@4.1.2: + resolution: {integrity: sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==} + engines: {node: '>= 14.18.0'} + + regexp-to-ast@0.5.0: + resolution: {integrity: sha512-tlbJqcMHnPKI9zSrystikWKwHkBqu2a/Sgw01h3zFjvYrMxEDYHzzoMZnUrbIfpTFEsoRnnviOXNCzFiSc54Qw==} + + remeda@2.33.4: + resolution: {integrity: sha512-ygHswjlc/opg2VrtiYvUOPLjxjtdKvjGz1/plDhkG66hjNjFr1xmfrs2ClNFo/E6TyUFiwYNh53bKV26oBoMGQ==} + require-directory@2.1.1: resolution: {integrity: sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==} engines: {node: '>=0.10.0'} @@ -1634,10 +2313,17 @@ packages: resolution: {integrity: sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg==} engines: {node: '>=8'} + resolve-from@4.0.0: + resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==} + engines: {node: '>=4'} + resolve-from@5.0.0: resolution: {integrity: sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==} engines: {node: '>=8'} + resolve-pkg-maps@1.0.0: + resolution: {integrity: sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==} + resolve.exports@2.0.3: resolution: {integrity: sha512-OcXjMsGdhL4XnbShKpAcSqPMzQoYkYyhbEaeSko47MjRP9NfEQMhZkXL1DoFlt9LWQn4YttrdnV6X2OiyzBi+A==} engines: {node: '>=10'} @@ -1647,6 +2333,10 @@ packages: engines: {node: '>= 0.4'} hasBin: true + retry@0.12.0: + resolution: {integrity: sha512-9LkiTwjUh6rT555DtE9rTX+BKByPfrMzEAtnlEtdEwr3Nkffwiihqe2bWADg+OQRjt9gl6ICdmB/ZFDCGAtSow==} + engines: {node: '>= 4'} + router@2.2.0: resolution: {integrity: sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ==} engines: {node: '>= 18'} @@ -1660,6 +2350,9 @@ packages: safer-buffer@2.1.2: resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==} + scheduler@0.27.0: + resolution: {integrity: sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==} + semver@6.3.1: resolution: {integrity: sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==} hasBin: true @@ -1669,10 +2362,18 @@ packages: engines: {node: '>=10'} hasBin: true + semver@7.7.4: + resolution: {integrity: sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==} + engines: {node: '>=10'} + hasBin: true + send@1.2.0: resolution: {integrity: sha512-uaW0WwXKpL9blXE2o0bRhoL2EGXIrZxQ2ZQ4mgcfoBxdFmQold+qWsD2jLrfZ0trjKL6vOw0j//eAwcALFjKSw==} engines: {node: '>= 18'} + seq-queue@0.0.5: + resolution: {integrity: sha512-hr3Wtp/GZIc/6DAGPDcV4/9WoZhjrkXsi5B/07QgX8tsdc6ilr7BFM6PM6rbdAX1kFSDYeZGLipIZZKyQP0O5Q==} + serve-static@2.2.0: resolution: {integrity: sha512-61g9pCh0Vnh7IutZjtLGGpTA355+OPn2TyDv/6ivP2h/AdAVX9azsoxmg2/M6nZeQZNYBEwIcsne1mJd9oQItQ==} engines: {node: '>= 18'} @@ -1707,6 +2408,10 @@ packages: signal-exit@3.0.7: resolution: {integrity: sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==} + signal-exit@4.1.0: + resolution: {integrity: sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==} + engines: {node: '>=14'} + sisteransi@1.0.5: resolution: {integrity: sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==} @@ -1721,9 +2426,17 @@ packages: resolution: {integrity: sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==} engines: {node: '>=0.10.0'} + split2@4.2.0: + resolution: {integrity: sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==} + engines: {node: '>= 10.x'} + sprintf-js@1.0.3: resolution: {integrity: sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==} + sqlstring@2.3.3: + resolution: {integrity: sha512-qC9iz2FlN7DQl3+wjwn3802RTyjCx7sDvfQEXchwa6CWOx07/WVfh91gBmQ9fahw8snwGEWU3xGzOt4tFyHLxg==} + engines: {node: '>= 0.6'} + stack-utils@2.0.6: resolution: {integrity: sha512-XlkWvfIm6RmsWtNJx+uqtKLS8eqFbxUg0ZzLXqY0caEy9l7hruX8IpiDnjsLavoBgqCCR71TqWO8MaXYheJ3RQ==} engines: {node: '>=10'} @@ -1732,6 +2445,13 @@ packages: resolution: {integrity: sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==} engines: {node: '>= 0.8'} + statuses@2.0.2: + resolution: {integrity: sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==} + engines: {node: '>= 0.8'} + + std-env@3.10.0: + resolution: {integrity: sha512-5GS12FdOZNliM5mAOxFRg7Ir0pWz8MdpYm6AY6VPkGpbA7ZzmbzNcBJQ0GPvvyWgcY7QAhCgf9Uy89I03faLkg==} + string-length@4.0.2: resolution: {integrity: sha512-+l6rNN5fYHNhZZy41RXsYptCjA2Igmq4EG7kZAYFQI1E1VTXarr6ZPXBg6eq7Y6eK4FEhY6AJlyuFIb/v/S0VQ==} engines: {node: '>=10'} @@ -1756,12 +2476,12 @@ packages: resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==} engines: {node: '>=8'} - superagent@9.0.2: - resolution: {integrity: sha512-xuW7dzkUpcJq7QnhOsnNUgtYp3xRwpt2F7abdRYIpCsAt0hhUqia0EdxyXZQQpNmGtsCzYHryaKSV3q3GJnq7w==} + superagent@10.3.0: + resolution: {integrity: sha512-B+4Ik7ROgVKrQsXTV0Jwp2u+PXYLSlqtDAhYnkkD+zn3yg8s/zjA2MeGayPoY/KICrbitwneDHrjSotxKL+0XQ==} engines: {node: '>=14.18.0'} - supertest@7.1.0: - resolution: {integrity: sha512-5QeSO8hSrKghtcWEoPiO036fxH0Ii2wVQfFZSP0oqQhmjk8bOLhDFXr4JrvaFmPuEWUoq4znY3uSi8UzLKxGqw==} + supertest@7.2.2: + resolution: {integrity: sha512-oK8WG9diS3DlhdUkcFn4tkNIiIbBx9lI2ClF8K+b2/m8Eyv47LSawxUzZQSNKUrVb2KsqeTDCcjAAVPYaSLVTA==} engines: {node: '>=14.18.0'} supports-color@7.2.0: @@ -1798,6 +2518,14 @@ packages: resolution: {integrity: sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w==} engines: {node: '>=8'} + tinyexec@1.0.4: + resolution: {integrity: sha512-u9r3uZC0bdpGOXtlxUIdwf9pkmvhqJdrVCH9fapQtgy/OeTTMZ1nqH7agtvEfmGui6e1XxjcdrlxvxJvc3sMqw==} + engines: {node: '>=18'} + + tinyglobby@0.2.15: + resolution: {integrity: sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==} + engines: {node: '>=12.0.0'} + tmpl@1.0.5: resolution: {integrity: sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw==} @@ -1809,17 +2537,24 @@ packages: resolution: {integrity: sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==} engines: {node: '>=0.6'} - ts-jest@29.3.1: - resolution: {integrity: sha512-FT2PIRtZABwl6+ZCry8IY7JZ3xMuppsEV9qFVHOVe8jDzggwUZ9TsM4chyJxL9yi6LvkqcZYU3LmapEE454zBQ==} + ts-api-utils@2.4.0: + resolution: {integrity: sha512-3TaVTaAv2gTiMB35i3FiGJaRfwb3Pyn/j3m/bfAvGe8FB7CF6u+LMYqYlDh7reQf7UNvoTvdfAqHGmPGOSsPmA==} + engines: {node: '>=18.12'} + peerDependencies: + typescript: '>=4.8.4' + + ts-jest@29.4.6: + resolution: {integrity: sha512-fSpWtOO/1AjSNQguk43hb/JCo16oJDnMJf3CdEGNkqsEX3t0KX96xvyX1D7PfLCpVoKu4MfVrqUkFyblYoY4lA==} engines: {node: ^14.15.0 || ^16.10.0 || ^18.0.0 || >=20.0.0} hasBin: true peerDependencies: '@babel/core': '>=7.0.0-beta.0 <8' - '@jest/transform': ^29.0.0 - '@jest/types': ^29.0.0 - babel-jest: ^29.0.0 + '@jest/transform': ^29.0.0 || ^30.0.0 + '@jest/types': ^29.0.0 || ^30.0.0 + babel-jest: ^29.0.0 || ^30.0.0 esbuild: '*' - jest: ^29.0.0 + jest: ^29.0.0 || ^30.0.0 + jest-util: ^29.0.0 || ^30.0.0 typescript: '>=4.3 <6' peerDependenciesMeta: '@babel/core': @@ -1832,6 +2567,17 @@ packages: optional: true esbuild: optional: true + jest-util: + optional: true + + tsx@4.21.0: + resolution: {integrity: sha512-5C1sg4USs1lfG0GFb2RLXsdpXqBSEhAaA/0kPL01wxzpMqLILNxIxIOKiILz+cdg/pLnOUxFYOR5yhHU666wbw==} + engines: {node: '>=18.0.0'} + hasBin: true + + type-check@0.4.0: + resolution: {integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==} + engines: {node: '>= 0.8.0'} type-detect@4.0.8: resolution: {integrity: sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==} @@ -1841,21 +2587,33 @@ packages: resolution: {integrity: sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==} engines: {node: '>=10'} - type-fest@4.39.1: - resolution: {integrity: sha512-uW9qzd66uyHYxwyVBYiwS4Oi0qZyUqwjU+Oevr6ZogYiXt99EOYtwvzMSLw1c3lYo2HzJsep/NB23iEVEgjG/w==} + type-fest@4.41.0: + resolution: {integrity: sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA==} engines: {node: '>=16'} type-is@2.0.1: resolution: {integrity: sha512-OZs6gsjF4vMp32qrCbiVSkrFmXtG/AZhY3t0iAMrMBiAZyV9oALtXO8hsrHbMXF9x6L3grlFuwW2oAz7cav+Gw==} engines: {node: '>= 0.6'} - typescript@5.8.3: - resolution: {integrity: sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==} + typescript-eslint@8.57.0: + resolution: {integrity: sha512-W8GcigEMEeB07xEZol8oJ26rigm3+bfPHxHvwbYUlu1fUDsGuQ7Hiskx5xGW/xM4USc9Ephe3jtv7ZYPQntHeA==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 + typescript: '>=4.8.4 <6.0.0' + + typescript@5.9.3: + resolution: {integrity: sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==} engines: {node: '>=14.17'} hasBin: true - undici-types@6.21.0: - resolution: {integrity: sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==} + uglify-js@3.19.3: + resolution: {integrity: sha512-v3Xu+yuwBXisp6QYTcH4UbH+xYJXqnq2m/LtQVWKWzYc1iehYnLixoQDN9FH6/j9/oybfd6W9Ghwkl8+UMKTKQ==} + engines: {node: '>=0.8.0'} + hasBin: true + + undici-types@7.18.2: + resolution: {integrity: sha512-AsuCzffGHJybSaRrmr5eHr81mwJU3kjw6M+uprWvCXiNeN9SOGwQ3Jn8jb8m3Z6izVgknn1R0FTCEAP2QrLY/w==} unpipe@1.0.0: resolution: {integrity: sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==} @@ -1867,10 +2625,21 @@ packages: peerDependencies: browserslist: '>= 4.21.0' + uri-js@4.4.1: + resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==} + v8-to-istanbul@9.3.0: resolution: {integrity: sha512-kiGUalWN+rgBJ/1OHZsBtU4rXZOfj/7rKQxULKlIzwzQSvMJUUNgPwJEEh7gU6xEVxC0ahoOBvN2YI8GH6FNgA==} engines: {node: '>=10.12.0'} + valibot@1.2.0: + resolution: {integrity: sha512-mm1rxUsmOxzrwnX5arGS+U4T25RdvpPjPN4yR0u9pUBov9+zGVtO84tif1eY4r6zWxVxu3KzIyknJy3rxfRZZg==} + peerDependencies: + typescript: '>=5' + peerDependenciesMeta: + typescript: + optional: true + validator@13.15.0: resolution: {integrity: sha512-36B2ryl4+oL5QxZ3AzD0t5SsMNGvTtQHpjgFO5tbNxfXbMFkY822ktCDe1MnlqV3301QQI9SLHDNJokDI+Z9pA==} engines: {node: '>= 0.10'} @@ -1887,6 +2656,13 @@ packages: engines: {node: '>= 8'} hasBin: true + word-wrap@1.2.5: + resolution: {integrity: sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==} + engines: {node: '>=0.10.0'} + + wordwrap@1.0.0: + resolution: {integrity: sha512-gvVzJFlPycKc5dZN4yPkP8w7Dc37BtP1yczEneOb4uq34pXZcvrtRTmWV8W+Ume+XCxKgbjM+nevkyFPMybd4Q==} + wrap-ansi@7.0.0: resolution: {integrity: sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==} engines: {node: '>=10'} @@ -1898,6 +2674,10 @@ packages: resolution: {integrity: sha512-7KxauUdBmSdWnmpaGFg+ppNjKF8uNLry8LyzjauQDOVONfFLNKrKvQOxZ/VuTIcS/gge/YNahf5RIIQWTSarlg==} engines: {node: ^12.13.0 || ^14.15.0 || >=16.0.0} + xtend@4.0.2: + resolution: {integrity: sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==} + engines: {node: '>=0.4'} + y18n@5.0.8: resolution: {integrity: sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==} engines: {node: '>=10'} @@ -1926,6 +2706,9 @@ packages: engines: {node: '>=8.0.0'} hasBin: true + zeptomatch@2.1.0: + resolution: {integrity: sha512-KiGErG2J0G82LSpniV0CtIzjlJ10E04j02VOudJsPyPwNZgGnRKQy7I1R7GMyg/QswnE4l7ohSGrQbQbjXPPDA==} + zod@3.24.2: resolution: {integrity: sha512-lY7CDW43ECgW9u1TcT3IoXHflywfVqDYze4waEz812jR/bZ8FHDsl7pFQoSZTz5N+2NqRXs8GBwnAwo3ZNxqhQ==} @@ -2144,81 +2927,170 @@ snapshots: '@bcoe/v8-coverage@0.2.3': {} - '@esbuild/aix-ppc64@0.25.2': + '@chevrotain/cst-dts-gen@10.5.0': + dependencies: + '@chevrotain/gast': 10.5.0 + '@chevrotain/types': 10.5.0 + lodash: 4.17.21 + + '@chevrotain/gast@10.5.0': + dependencies: + '@chevrotain/types': 10.5.0 + lodash: 4.17.21 + + '@chevrotain/types@10.5.0': {} + + '@chevrotain/utils@10.5.0': {} + + '@electric-sql/pglite-socket@0.0.20(@electric-sql/pglite@0.3.15)': + dependencies: + '@electric-sql/pglite': 0.3.15 + + '@electric-sql/pglite-tools@0.2.20(@electric-sql/pglite@0.3.15)': + dependencies: + '@electric-sql/pglite': 0.3.15 + + '@electric-sql/pglite@0.3.15': {} + + '@esbuild/aix-ppc64@0.27.4': optional: true - '@esbuild/android-arm64@0.25.2': + '@esbuild/android-arm64@0.27.4': optional: true - '@esbuild/android-arm@0.25.2': + '@esbuild/android-arm@0.27.4': optional: true - '@esbuild/android-x64@0.25.2': + '@esbuild/android-x64@0.27.4': optional: true - '@esbuild/darwin-arm64@0.25.2': + '@esbuild/darwin-arm64@0.27.4': optional: true - '@esbuild/darwin-x64@0.25.2': + '@esbuild/darwin-x64@0.27.4': optional: true - '@esbuild/freebsd-arm64@0.25.2': + '@esbuild/freebsd-arm64@0.27.4': optional: true - '@esbuild/freebsd-x64@0.25.2': + '@esbuild/freebsd-x64@0.27.4': optional: true - '@esbuild/linux-arm64@0.25.2': + '@esbuild/linux-arm64@0.27.4': optional: true - '@esbuild/linux-arm@0.25.2': + '@esbuild/linux-arm@0.27.4': optional: true - '@esbuild/linux-ia32@0.25.2': + '@esbuild/linux-ia32@0.27.4': optional: true - '@esbuild/linux-loong64@0.25.2': + '@esbuild/linux-loong64@0.27.4': optional: true - '@esbuild/linux-mips64el@0.25.2': + '@esbuild/linux-mips64el@0.27.4': optional: true - '@esbuild/linux-ppc64@0.25.2': + '@esbuild/linux-ppc64@0.27.4': optional: true - '@esbuild/linux-riscv64@0.25.2': + '@esbuild/linux-riscv64@0.27.4': optional: true - '@esbuild/linux-s390x@0.25.2': + '@esbuild/linux-s390x@0.27.4': optional: true - '@esbuild/linux-x64@0.25.2': + '@esbuild/linux-x64@0.27.4': optional: true - '@esbuild/netbsd-arm64@0.25.2': + '@esbuild/netbsd-arm64@0.27.4': optional: true - '@esbuild/netbsd-x64@0.25.2': + '@esbuild/netbsd-x64@0.27.4': optional: true - '@esbuild/openbsd-arm64@0.25.2': + '@esbuild/openbsd-arm64@0.27.4': optional: true - '@esbuild/openbsd-x64@0.25.2': + '@esbuild/openbsd-x64@0.27.4': optional: true - '@esbuild/sunos-x64@0.25.2': + '@esbuild/openharmony-arm64@0.27.4': optional: true - '@esbuild/win32-arm64@0.25.2': + '@esbuild/sunos-x64@0.27.4': optional: true - '@esbuild/win32-ia32@0.25.2': + '@esbuild/win32-arm64@0.27.4': optional: true - '@esbuild/win32-x64@0.25.2': + '@esbuild/win32-ia32@0.27.4': optional: true + '@esbuild/win32-x64@0.27.4': + optional: true + + '@eslint-community/eslint-utils@4.9.1(eslint@9.39.4(jiti@2.6.1))': + dependencies: + eslint: 9.39.4(jiti@2.6.1) + eslint-visitor-keys: 3.4.3 + + '@eslint-community/regexpp@4.12.2': {} + + '@eslint/config-array@0.21.2': + dependencies: + '@eslint/object-schema': 2.1.7 + debug: 4.4.0 + minimatch: 3.1.5 + transitivePeerDependencies: + - supports-color + + '@eslint/config-helpers@0.4.2': + dependencies: + '@eslint/core': 0.17.0 + + '@eslint/core@0.17.0': + dependencies: + '@types/json-schema': 7.0.15 + + '@eslint/eslintrc@3.3.5': + dependencies: + ajv: 6.14.0 + debug: 4.4.0 + espree: 10.4.0 + globals: 14.0.0 + ignore: 5.3.2 + import-fresh: 3.3.1 + js-yaml: 4.1.1 + minimatch: 3.1.5 + strip-json-comments: 3.1.1 + transitivePeerDependencies: + - supports-color + + '@eslint/js@9.39.4': {} + + '@eslint/object-schema@2.1.7': {} + + '@eslint/plugin-kit@0.4.1': + dependencies: + '@eslint/core': 0.17.0 + levn: 0.4.1 + + '@hono/node-server@1.19.9(hono@4.11.4)': + dependencies: + hono: 4.11.4 + + '@humanfs/core@0.19.1': {} + + '@humanfs/node@0.16.7': + dependencies: + '@humanfs/core': 0.19.1 + '@humanwhocodes/retry': 0.4.3 + + '@humanwhocodes/module-importer@1.0.1': {} + + '@humanwhocodes/retry@0.4.3': {} + '@istanbuljs/load-nyc-config@1.1.0': dependencies: camelcase: 5.3.1 @@ -2232,7 +3104,7 @@ snapshots: '@jest/console@29.7.0': dependencies: '@jest/types': 29.6.3 - '@types/node': 22.14.0 + '@types/node': 25.5.0 chalk: 4.1.2 jest-message-util: 29.7.0 jest-util: 29.7.0 @@ -2245,14 +3117,14 @@ snapshots: '@jest/test-result': 29.7.0 '@jest/transform': 29.7.0 '@jest/types': 29.6.3 - '@types/node': 22.14.0 + '@types/node': 25.5.0 ansi-escapes: 4.3.2 chalk: 4.1.2 ci-info: 3.9.0 exit: 0.1.2 graceful-fs: 4.2.11 jest-changed-files: 29.7.0 - jest-config: 29.7.0(@types/node@22.14.0) + jest-config: 29.7.0(@types/node@25.5.0) jest-haste-map: 29.7.0 jest-message-util: 29.7.0 jest-regex-util: 29.6.3 @@ -2277,7 +3149,7 @@ snapshots: dependencies: '@jest/fake-timers': 29.7.0 '@jest/types': 29.6.3 - '@types/node': 22.14.0 + '@types/node': 25.5.0 jest-mock: 29.7.0 '@jest/expect-utils@29.7.0': @@ -2295,7 +3167,7 @@ snapshots: dependencies: '@jest/types': 29.6.3 '@sinonjs/fake-timers': 10.3.0 - '@types/node': 22.14.0 + '@types/node': 25.5.0 jest-message-util: 29.7.0 jest-mock: 29.7.0 jest-util: 29.7.0 @@ -2317,7 +3189,7 @@ snapshots: '@jest/transform': 29.7.0 '@jest/types': 29.6.3 '@jridgewell/trace-mapping': 0.3.25 - '@types/node': 22.14.0 + '@types/node': 25.5.0 chalk: 4.1.2 collect-v8-coverage: 1.0.2 exit: 0.1.2 @@ -2387,7 +3259,7 @@ snapshots: '@jest/schemas': 29.6.3 '@types/istanbul-lib-coverage': 2.0.6 '@types/istanbul-reports': 3.0.4 - '@types/node': 22.14.0 + '@types/node': 25.5.0 '@types/yargs': 17.0.33 chalk: 4.1.2 @@ -2410,38 +3282,104 @@ snapshots: '@jsdevtools/ono@7.1.3': {} - '@prisma/client@6.6.0(prisma@6.6.0(typescript@5.8.3))(typescript@5.8.3)': + '@mrleebo/prisma-ast@0.13.1': + dependencies: + chevrotain: 10.5.0 + lilconfig: 2.1.0 + + '@noble/hashes@1.8.0': {} + + '@paralleldrive/cuid2@2.3.1': + dependencies: + '@noble/hashes': 1.8.0 + + '@prisma/adapter-pg@7.5.0': + dependencies: + '@prisma/driver-adapter-utils': 7.5.0 + '@types/pg': 8.11.11 + pg: 8.20.0 + postgres-array: 3.0.4 + transitivePeerDependencies: + - pg-native + + '@prisma/client-runtime-utils@7.5.0': {} + + '@prisma/client@7.5.0(prisma@7.5.0(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(typescript@5.9.3))(typescript@5.9.3)': + dependencies: + '@prisma/client-runtime-utils': 7.5.0 optionalDependencies: - prisma: 6.6.0(typescript@5.8.3) - typescript: 5.8.3 + prisma: 7.5.0(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(typescript@5.9.3) + typescript: 5.9.3 - '@prisma/config@6.6.0': + '@prisma/config@7.5.0': dependencies: - esbuild: 0.25.2 - esbuild-register: 3.6.0(esbuild@0.25.2) + c12: 3.1.0 + deepmerge-ts: 7.1.5 + effect: 3.18.4 + empathic: 2.0.0 transitivePeerDependencies: - - supports-color + - magicast + + '@prisma/debug@7.2.0': {} + + '@prisma/debug@7.5.0': {} + + '@prisma/dev@0.20.0(typescript@5.9.3)': + dependencies: + '@electric-sql/pglite': 0.3.15 + '@electric-sql/pglite-socket': 0.0.20(@electric-sql/pglite@0.3.15) + '@electric-sql/pglite-tools': 0.2.20(@electric-sql/pglite@0.3.15) + '@hono/node-server': 1.19.9(hono@4.11.4) + '@mrleebo/prisma-ast': 0.13.1 + '@prisma/get-platform': 7.2.0 + '@prisma/query-plan-executor': 7.2.0 + foreground-child: 3.3.1 + get-port-please: 3.2.0 + hono: 4.11.4 + http-status-codes: 2.3.0 + pathe: 2.0.3 + proper-lockfile: 4.1.2 + remeda: 2.33.4 + std-env: 3.10.0 + valibot: 1.2.0(typescript@5.9.3) + zeptomatch: 2.1.0 + transitivePeerDependencies: + - typescript + + '@prisma/driver-adapter-utils@7.5.0': + dependencies: + '@prisma/debug': 7.5.0 - '@prisma/debug@6.6.0': {} + '@prisma/engines-version@7.5.0-15.280c870be64f457428992c43c1f6d557fab6e29e': {} - '@prisma/engines-version@6.6.0-53.f676762280b54cd07c770017ed3711ddde35f37a': {} + '@prisma/engines@7.5.0': + dependencies: + '@prisma/debug': 7.5.0 + '@prisma/engines-version': 7.5.0-15.280c870be64f457428992c43c1f6d557fab6e29e + '@prisma/fetch-engine': 7.5.0 + '@prisma/get-platform': 7.5.0 + + '@prisma/fetch-engine@7.5.0': + dependencies: + '@prisma/debug': 7.5.0 + '@prisma/engines-version': 7.5.0-15.280c870be64f457428992c43c1f6d557fab6e29e + '@prisma/get-platform': 7.5.0 - '@prisma/engines@6.6.0': + '@prisma/get-platform@7.2.0': dependencies: - '@prisma/debug': 6.6.0 - '@prisma/engines-version': 6.6.0-53.f676762280b54cd07c770017ed3711ddde35f37a - '@prisma/fetch-engine': 6.6.0 - '@prisma/get-platform': 6.6.0 + '@prisma/debug': 7.2.0 - '@prisma/fetch-engine@6.6.0': + '@prisma/get-platform@7.5.0': dependencies: - '@prisma/debug': 6.6.0 - '@prisma/engines-version': 6.6.0-53.f676762280b54cd07c770017ed3711ddde35f37a - '@prisma/get-platform': 6.6.0 + '@prisma/debug': 7.5.0 + + '@prisma/query-plan-executor@7.2.0': {} - '@prisma/get-platform@6.6.0': + '@prisma/studio-core@0.21.1(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': dependencies: - '@prisma/debug': 6.6.0 + '@types/react': 19.2.14 + react: 19.2.4 + react-dom: 19.2.4(react@19.2.4) '@scarf/scarf@1.4.0': {} @@ -2455,6 +3393,8 @@ snapshots: dependencies: '@sinonjs/commons': 3.0.1 + '@standard-schema/spec@1.1.0': {} + '@types/babel__core@7.20.5': dependencies: '@babel/parser': 7.27.0 @@ -2479,34 +3419,36 @@ snapshots: '@types/body-parser@1.19.5': dependencies: '@types/connect': 3.4.38 - '@types/node': 22.14.0 + '@types/node': 25.5.0 '@types/connect@3.4.38': dependencies: - '@types/node': 22.14.0 + '@types/node': 25.5.0 '@types/cookiejar@2.1.5': {} - '@types/cors@2.8.17': + '@types/cors@2.8.19': dependencies: - '@types/node': 22.14.0 + '@types/node': 25.5.0 + + '@types/estree@1.0.8': {} '@types/express-serve-static-core@5.0.6': dependencies: - '@types/node': 22.14.0 + '@types/node': 25.5.0 '@types/qs': 6.9.18 '@types/range-parser': 1.2.7 '@types/send': 0.17.4 - '@types/express@5.0.1': + '@types/express@5.0.6': dependencies: '@types/body-parser': 1.19.5 '@types/express-serve-static-core': 5.0.6 - '@types/serve-static': 1.15.7 + '@types/serve-static': 2.2.0 '@types/graceful-fs@4.1.9': dependencies: - '@types/node': 22.14.0 + '@types/node': 25.5.0 '@types/http-errors@2.0.4': {} @@ -2531,39 +3473,60 @@ snapshots: '@types/mime@1.3.5': {} - '@types/morgan@1.9.9': + '@types/morgan@1.9.10': dependencies: - '@types/node': 22.14.0 + '@types/node': 25.5.0 - '@types/node@22.14.0': + '@types/node@25.5.0': dependencies: - undici-types: 6.21.0 + undici-types: 7.18.2 + + '@types/pg@8.11.11': + dependencies: + '@types/node': 25.5.0 + pg-protocol: 1.13.0 + pg-types: 4.1.0 + + '@types/pg@8.18.0': + dependencies: + '@types/node': 25.5.0 + pg-protocol: 1.13.0 + pg-types: 2.2.0 '@types/qs@6.9.18': {} '@types/range-parser@1.2.7': {} + '@types/react@19.2.14': + dependencies: + csstype: 3.2.3 + '@types/send@0.17.4': dependencies: '@types/mime': 1.3.5 - '@types/node': 22.14.0 + '@types/node': 25.5.0 '@types/serve-static@1.15.7': dependencies: '@types/http-errors': 2.0.4 - '@types/node': 22.14.0 + '@types/node': 25.5.0 '@types/send': 0.17.4 + '@types/serve-static@2.2.0': + dependencies: + '@types/http-errors': 2.0.4 + '@types/node': 25.5.0 + '@types/stack-utils@2.0.3': {} '@types/superagent@8.1.9': dependencies: '@types/cookiejar': 2.1.5 '@types/methods': 1.1.4 - '@types/node': 22.14.0 + '@types/node': 25.5.0 form-data: 4.0.2 - '@types/supertest@6.0.3': + '@types/supertest@7.2.0': dependencies: '@types/methods': 1.1.4 '@types/superagent': 8.1.9 @@ -2572,7 +3535,7 @@ snapshots: '@types/swagger-ui-express@4.1.8': dependencies: - '@types/express': 5.0.1 + '@types/express': 5.0.6 '@types/serve-static': 1.15.7 '@types/yargs-parser@21.0.3': {} @@ -2581,11 +3544,115 @@ snapshots: dependencies: '@types/yargs-parser': 21.0.3 + '@typescript-eslint/eslint-plugin@8.57.0(@typescript-eslint/parser@8.57.0(eslint@9.39.4(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.4(jiti@2.6.1))(typescript@5.9.3)': + dependencies: + '@eslint-community/regexpp': 4.12.2 + '@typescript-eslint/parser': 8.57.0(eslint@9.39.4(jiti@2.6.1))(typescript@5.9.3) + '@typescript-eslint/scope-manager': 8.57.0 + '@typescript-eslint/type-utils': 8.57.0(eslint@9.39.4(jiti@2.6.1))(typescript@5.9.3) + '@typescript-eslint/utils': 8.57.0(eslint@9.39.4(jiti@2.6.1))(typescript@5.9.3) + '@typescript-eslint/visitor-keys': 8.57.0 + eslint: 9.39.4(jiti@2.6.1) + ignore: 7.0.5 + natural-compare: 1.4.0 + ts-api-utils: 2.4.0(typescript@5.9.3) + typescript: 5.9.3 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/parser@8.57.0(eslint@9.39.4(jiti@2.6.1))(typescript@5.9.3)': + dependencies: + '@typescript-eslint/scope-manager': 8.57.0 + '@typescript-eslint/types': 8.57.0 + '@typescript-eslint/typescript-estree': 8.57.0(typescript@5.9.3) + '@typescript-eslint/visitor-keys': 8.57.0 + debug: 4.4.3 + eslint: 9.39.4(jiti@2.6.1) + typescript: 5.9.3 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/project-service@8.57.0(typescript@5.9.3)': + dependencies: + '@typescript-eslint/tsconfig-utils': 8.57.0(typescript@5.9.3) + '@typescript-eslint/types': 8.57.0 + debug: 4.4.3 + typescript: 5.9.3 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/scope-manager@8.57.0': + dependencies: + '@typescript-eslint/types': 8.57.0 + '@typescript-eslint/visitor-keys': 8.57.0 + + '@typescript-eslint/tsconfig-utils@8.57.0(typescript@5.9.3)': + dependencies: + typescript: 5.9.3 + + '@typescript-eslint/type-utils@8.57.0(eslint@9.39.4(jiti@2.6.1))(typescript@5.9.3)': + dependencies: + '@typescript-eslint/types': 8.57.0 + '@typescript-eslint/typescript-estree': 8.57.0(typescript@5.9.3) + '@typescript-eslint/utils': 8.57.0(eslint@9.39.4(jiti@2.6.1))(typescript@5.9.3) + debug: 4.4.3 + eslint: 9.39.4(jiti@2.6.1) + ts-api-utils: 2.4.0(typescript@5.9.3) + typescript: 5.9.3 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/types@8.57.0': {} + + '@typescript-eslint/typescript-estree@8.57.0(typescript@5.9.3)': + dependencies: + '@typescript-eslint/project-service': 8.57.0(typescript@5.9.3) + '@typescript-eslint/tsconfig-utils': 8.57.0(typescript@5.9.3) + '@typescript-eslint/types': 8.57.0 + '@typescript-eslint/visitor-keys': 8.57.0 + debug: 4.4.3 + minimatch: 10.2.4 + semver: 7.7.4 + tinyglobby: 0.2.15 + ts-api-utils: 2.4.0(typescript@5.9.3) + typescript: 5.9.3 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/utils@8.57.0(eslint@9.39.4(jiti@2.6.1))(typescript@5.9.3)': + dependencies: + '@eslint-community/eslint-utils': 4.9.1(eslint@9.39.4(jiti@2.6.1)) + '@typescript-eslint/scope-manager': 8.57.0 + '@typescript-eslint/types': 8.57.0 + '@typescript-eslint/typescript-estree': 8.57.0(typescript@5.9.3) + eslint: 9.39.4(jiti@2.6.1) + typescript: 5.9.3 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/visitor-keys@8.57.0': + dependencies: + '@typescript-eslint/types': 8.57.0 + eslint-visitor-keys: 5.0.1 + accepts@2.0.0: dependencies: mime-types: 3.0.1 negotiator: 1.0.0 + acorn-jsx@5.3.2(acorn@8.16.0): + dependencies: + acorn: 8.16.0 + + acorn@8.16.0: {} + + ajv@6.14.0: + dependencies: + fast-deep-equal: 3.1.3 + fast-json-stable-stringify: 2.1.0 + json-schema-traverse: 0.4.1 + uri-js: 4.4.1 + ansi-escapes@4.3.2: dependencies: type-fest: 0.21.3 @@ -2611,10 +3678,10 @@ snapshots: asap@2.0.6: {} - async@3.2.6: {} - asynckit@0.4.0: {} + aws-ssl-profiles@1.1.2: {} + babel-jest@29.7.0(@babel/core@7.26.10): dependencies: '@babel/core': 7.26.10 @@ -2672,20 +3739,22 @@ snapshots: balanced-match@1.0.2: {} + balanced-match@4.0.4: {} + basic-auth@2.0.1: dependencies: safe-buffer: 5.1.2 - body-parser@2.2.0: + body-parser@2.2.2: dependencies: bytes: 3.1.2 content-type: 1.0.5 - debug: 4.4.0 + debug: 4.4.3 http-errors: 2.0.0 - iconv-lite: 0.6.3 + iconv-lite: 0.7.2 on-finished: 2.4.1 - qs: 6.14.0 - raw-body: 3.0.0 + qs: 6.15.0 + raw-body: 3.0.2 type-is: 2.0.1 transitivePeerDependencies: - supports-color @@ -2695,9 +3764,9 @@ snapshots: balanced-match: 1.0.2 concat-map: 0.0.1 - brace-expansion@2.0.1: + brace-expansion@5.0.4: dependencies: - balanced-match: 1.0.2 + balanced-match: 4.0.4 braces@3.0.3: dependencies: @@ -2722,6 +3791,21 @@ snapshots: bytes@3.1.2: {} + c12@3.1.0: + dependencies: + chokidar: 4.0.3 + confbox: 0.2.4 + defu: 6.1.4 + dotenv: 16.6.1 + exsolve: 1.0.8 + giget: 2.0.0 + jiti: 2.6.1 + ohash: 2.0.11 + pathe: 2.0.3 + perfect-debounce: 1.0.0 + pkg-types: 2.3.0 + rc9: 2.1.2 + call-bind-apply-helpers@1.0.2: dependencies: es-errors: 1.3.0 @@ -2749,8 +3833,27 @@ snapshots: char-regex@1.0.2: {} + chevrotain@10.5.0: + 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@4.0.3: + dependencies: + readdirp: 4.1.2 + ci-info@3.9.0: {} + citty@0.1.6: + dependencies: + consola: 3.4.2 + + citty@0.2.1: {} + cjs-module-lexer@1.4.3: {} cliui@8.0.1: @@ -2782,6 +3885,10 @@ snapshots: concat-map@0.0.1: {} + confbox@0.2.4: {} + + consola@3.4.2: {} + content-disposition@1.0.0: dependencies: safe-buffer: 5.2.1 @@ -2796,18 +3903,18 @@ snapshots: cookiejar@2.1.4: {} - cors@2.8.5: + cors@2.8.6: dependencies: object-assign: 4.1.1 vary: 1.1.2 - create-jest@29.7.0(@types/node@22.14.0): + create-jest@29.7.0(@types/node@25.5.0): dependencies: '@jest/types': 29.6.3 chalk: 4.1.2 exit: 0.1.2 graceful-fs: 4.2.11 - jest-config: 29.7.0(@types/node@22.14.0) + jest-config: 29.7.0(@types/node@25.5.0) jest-util: 29.7.0 prompts: 2.4.2 transitivePeerDependencies: @@ -2822,6 +3929,8 @@ snapshots: shebang-command: 2.0.0 which: 2.0.2 + csstype@3.2.3: {} + debug@2.6.9: dependencies: ms: 2.0.0 @@ -2830,14 +3939,28 @@ snapshots: dependencies: ms: 2.1.3 + debug@4.4.3: + dependencies: + ms: 2.1.3 + dedent@1.5.3: {} + deep-is@0.1.4: {} + + deepmerge-ts@7.1.5: {} + deepmerge@4.3.1: {} + defu@6.1.4: {} + delayed-stream@1.0.0: {} + denque@2.1.0: {} + depd@2.0.0: {} + destr@2.0.5: {} + detect-newline@3.1.0: {} dezalgo@1.0.4: @@ -2851,7 +3974,9 @@ snapshots: dependencies: esutils: 2.0.3 - dotenv@16.5.0: {} + dotenv@16.6.1: {} + + dotenv@17.3.1: {} dunder-proto@1.0.1: dependencies: @@ -2861,9 +3986,10 @@ snapshots: ee-first@1.1.1: {} - ejs@3.1.10: + effect@3.18.4: dependencies: - jake: 10.9.2 + '@standard-schema/spec': 1.1.0 + fast-check: 3.23.2 electron-to-chromium@1.5.136: {} @@ -2871,6 +3997,8 @@ snapshots: emoji-regex@8.0.0: {} + empathic@2.0.0: {} + encodeurl@2.0.0: {} error-ex@1.3.2: @@ -2892,40 +4020,34 @@ snapshots: has-tostringtag: 1.0.2 hasown: 2.0.2 - esbuild-register@3.6.0(esbuild@0.25.2): - dependencies: - debug: 4.4.0 - esbuild: 0.25.2 - transitivePeerDependencies: - - supports-color - - esbuild@0.25.2: + esbuild@0.27.4: optionalDependencies: - '@esbuild/aix-ppc64': 0.25.2 - '@esbuild/android-arm': 0.25.2 - '@esbuild/android-arm64': 0.25.2 - '@esbuild/android-x64': 0.25.2 - '@esbuild/darwin-arm64': 0.25.2 - '@esbuild/darwin-x64': 0.25.2 - '@esbuild/freebsd-arm64': 0.25.2 - '@esbuild/freebsd-x64': 0.25.2 - '@esbuild/linux-arm': 0.25.2 - '@esbuild/linux-arm64': 0.25.2 - '@esbuild/linux-ia32': 0.25.2 - '@esbuild/linux-loong64': 0.25.2 - '@esbuild/linux-mips64el': 0.25.2 - '@esbuild/linux-ppc64': 0.25.2 - '@esbuild/linux-riscv64': 0.25.2 - '@esbuild/linux-s390x': 0.25.2 - '@esbuild/linux-x64': 0.25.2 - '@esbuild/netbsd-arm64': 0.25.2 - '@esbuild/netbsd-x64': 0.25.2 - '@esbuild/openbsd-arm64': 0.25.2 - '@esbuild/openbsd-x64': 0.25.2 - '@esbuild/sunos-x64': 0.25.2 - '@esbuild/win32-arm64': 0.25.2 - '@esbuild/win32-ia32': 0.25.2 - '@esbuild/win32-x64': 0.25.2 + '@esbuild/aix-ppc64': 0.27.4 + '@esbuild/android-arm': 0.27.4 + '@esbuild/android-arm64': 0.27.4 + '@esbuild/android-x64': 0.27.4 + '@esbuild/darwin-arm64': 0.27.4 + '@esbuild/darwin-x64': 0.27.4 + '@esbuild/freebsd-arm64': 0.27.4 + '@esbuild/freebsd-x64': 0.27.4 + '@esbuild/linux-arm': 0.27.4 + '@esbuild/linux-arm64': 0.27.4 + '@esbuild/linux-ia32': 0.27.4 + '@esbuild/linux-loong64': 0.27.4 + '@esbuild/linux-mips64el': 0.27.4 + '@esbuild/linux-ppc64': 0.27.4 + '@esbuild/linux-riscv64': 0.27.4 + '@esbuild/linux-s390x': 0.27.4 + '@esbuild/linux-x64': 0.27.4 + '@esbuild/netbsd-arm64': 0.27.4 + '@esbuild/netbsd-x64': 0.27.4 + '@esbuild/openbsd-arm64': 0.27.4 + '@esbuild/openbsd-x64': 0.27.4 + '@esbuild/openharmony-arm64': 0.27.4 + '@esbuild/sunos-x64': 0.27.4 + '@esbuild/win32-arm64': 0.27.4 + '@esbuild/win32-ia32': 0.27.4 + '@esbuild/win32-x64': 0.27.4 escalade@3.2.0: {} @@ -2933,8 +4055,78 @@ snapshots: escape-string-regexp@2.0.0: {} + escape-string-regexp@4.0.0: {} + + eslint-scope@8.4.0: + dependencies: + esrecurse: 4.3.0 + estraverse: 5.3.0 + + eslint-visitor-keys@3.4.3: {} + + eslint-visitor-keys@4.2.1: {} + + eslint-visitor-keys@5.0.1: {} + + eslint@9.39.4(jiti@2.6.1): + dependencies: + '@eslint-community/eslint-utils': 4.9.1(eslint@9.39.4(jiti@2.6.1)) + '@eslint-community/regexpp': 4.12.2 + '@eslint/config-array': 0.21.2 + '@eslint/config-helpers': 0.4.2 + '@eslint/core': 0.17.0 + '@eslint/eslintrc': 3.3.5 + '@eslint/js': 9.39.4 + '@eslint/plugin-kit': 0.4.1 + '@humanfs/node': 0.16.7 + '@humanwhocodes/module-importer': 1.0.1 + '@humanwhocodes/retry': 0.4.3 + '@types/estree': 1.0.8 + ajv: 6.14.0 + chalk: 4.1.2 + cross-spawn: 7.0.6 + debug: 4.4.0 + escape-string-regexp: 4.0.0 + eslint-scope: 8.4.0 + eslint-visitor-keys: 4.2.1 + espree: 10.4.0 + esquery: 1.7.0 + esutils: 2.0.3 + fast-deep-equal: 3.1.3 + file-entry-cache: 8.0.0 + find-up: 5.0.0 + glob-parent: 6.0.2 + ignore: 5.3.2 + imurmurhash: 0.1.4 + is-glob: 4.0.3 + json-stable-stringify-without-jsonify: 1.0.1 + lodash.merge: 4.6.2 + minimatch: 3.1.5 + natural-compare: 1.4.0 + optionator: 0.9.4 + optionalDependencies: + jiti: 2.6.1 + transitivePeerDependencies: + - supports-color + + espree@10.4.0: + dependencies: + acorn: 8.16.0 + acorn-jsx: 5.3.2(acorn@8.16.0) + eslint-visitor-keys: 4.2.1 + esprima@4.0.1: {} + esquery@1.7.0: + dependencies: + estraverse: 5.3.0 + + esrecurse@4.3.0: + dependencies: + estraverse: 5.3.0 + + estraverse@5.3.0: {} + esutils@2.0.3: {} etag@1.8.1: {} @@ -2961,15 +4153,16 @@ snapshots: jest-message-util: 29.7.0 jest-util: 29.7.0 - express@5.1.0: + express@5.2.1: dependencies: accepts: 2.0.0 - body-parser: 2.2.0 + body-parser: 2.2.2 content-disposition: 1.0.0 content-type: 1.0.5 cookie: 0.7.2 cookie-signature: 1.2.2 debug: 4.4.0 + depd: 2.0.0 encodeurl: 2.0.0 escape-html: 1.0.3 etag: 1.8.1 @@ -2993,17 +4186,31 @@ snapshots: transitivePeerDependencies: - supports-color + exsolve@1.0.8: {} + + fast-check@3.23.2: + dependencies: + pure-rand: 6.1.0 + + fast-deep-equal@3.1.3: {} + fast-json-stable-stringify@2.1.0: {} + fast-levenshtein@2.0.6: {} + fast-safe-stringify@2.1.1: {} fb-watchman@2.0.2: dependencies: bser: 2.1.1 - filelist@1.0.4: + fdir@6.5.0(picomatch@4.0.3): + optionalDependencies: + picomatch: 4.0.3 + + file-entry-cache@8.0.0: dependencies: - minimatch: 5.1.6 + flat-cache: 4.0.1 fill-range@7.1.1: dependencies: @@ -3025,6 +4232,23 @@ snapshots: locate-path: 5.0.0 path-exists: 4.0.0 + find-up@5.0.0: + dependencies: + locate-path: 6.0.0 + path-exists: 4.0.0 + + flat-cache@4.0.1: + dependencies: + flatted: 3.4.1 + keyv: 4.5.4 + + flatted@3.4.1: {} + + foreground-child@3.3.1: + dependencies: + cross-spawn: 7.0.6 + signal-exit: 4.1.0 + form-data@4.0.2: dependencies: asynckit: 0.4.0 @@ -3032,10 +4256,18 @@ snapshots: es-set-tostringtag: 2.1.0 mime-types: 2.1.35 - formidable@3.5.2: + form-data@4.0.5: dependencies: + asynckit: 0.4.0 + combined-stream: 1.0.8 + es-set-tostringtag: 2.1.0 + hasown: 2.0.2 + mime-types: 2.1.35 + + formidable@3.5.4: + dependencies: + '@paralleldrive/cuid2': 2.3.1 dezalgo: 1.0.4 - hexoid: 2.0.0 once: 1.4.0 forwarded@0.2.0: {} @@ -3049,6 +4281,10 @@ snapshots: function-bind@1.1.2: {} + generate-function@2.3.1: + dependencies: + is-property: 1.0.2 + gensync@1.0.0-beta.2: {} get-caller-file@2.0.5: {} @@ -3068,6 +4304,8 @@ snapshots: get-package-type@0.1.0: {} + get-port-please@3.2.0: {} + get-proto@1.0.1: dependencies: dunder-proto: 1.0.1 @@ -3075,6 +4313,23 @@ snapshots: get-stream@6.0.1: {} + get-tsconfig@4.13.6: + dependencies: + resolve-pkg-maps: 1.0.0 + + giget@2.0.0: + dependencies: + citty: 0.1.6 + consola: 3.4.2 + defu: 6.1.4 + node-fetch-native: 1.6.7 + nypm: 0.6.5 + pathe: 2.0.3 + + glob-parent@6.0.2: + dependencies: + is-glob: 4.0.3 + glob@7.1.6: dependencies: fs.realpath: 1.0.0 @@ -3086,10 +4341,27 @@ snapshots: globals@11.12.0: {} + globals@14.0.0: {} + + globals@15.15.0: {} + gopd@1.2.0: {} graceful-fs@4.2.11: {} + grammex@3.1.12: {} + + graphmatch@1.1.1: {} + + handlebars@4.7.8: + dependencies: + minimist: 1.2.8 + neo-async: 2.6.2 + source-map: 0.6.1 + wordwrap: 1.0.0 + optionalDependencies: + uglify-js: 3.19.3 + has-flag@4.0.0: {} has-symbols@1.1.0: {} @@ -3104,7 +4376,7 @@ snapshots: helmet@8.1.0: {} - hexoid@2.0.0: {} + hono@4.11.4: {} html-escaper@2.0.2: {} @@ -3116,12 +4388,31 @@ snapshots: statuses: 2.0.1 toidentifier: 1.0.1 + http-errors@2.0.1: + 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: {} + human-signals@2.1.0: {} - iconv-lite@0.6.3: + iconv-lite@0.7.2: dependencies: safer-buffer: 2.1.2 + ignore@5.3.2: {} + + ignore@7.0.5: {} + + import-fresh@3.3.1: + dependencies: + parent-module: 1.0.1 + resolve-from: 4.0.0 + import-local@3.2.0: dependencies: pkg-dir: 4.2.0 @@ -3144,14 +4435,22 @@ snapshots: dependencies: hasown: 2.0.2 + is-extglob@2.1.1: {} + is-fullwidth-code-point@3.0.0: {} is-generator-fn@2.1.0: {} + is-glob@4.0.3: + dependencies: + is-extglob: 2.1.1 + is-number@7.0.0: {} is-promise@4.0.0: {} + is-property@1.0.2: {} + is-stream@2.0.1: {} isexe@2.0.0: {} @@ -3197,13 +4496,6 @@ snapshots: html-escaper: 2.0.2 istanbul-lib-report: 3.0.1 - jake@10.9.2: - dependencies: - async: 3.2.6 - chalk: 4.1.2 - filelist: 1.0.4 - minimatch: 3.1.2 - jest-changed-files@29.7.0: dependencies: execa: 5.1.1 @@ -3216,7 +4508,7 @@ snapshots: '@jest/expect': 29.7.0 '@jest/test-result': 29.7.0 '@jest/types': 29.6.3 - '@types/node': 22.14.0 + '@types/node': 25.5.0 chalk: 4.1.2 co: 4.6.0 dedent: 1.5.3 @@ -3236,16 +4528,16 @@ snapshots: - babel-plugin-macros - supports-color - jest-cli@29.7.0(@types/node@22.14.0): + jest-cli@29.7.0(@types/node@25.5.0): dependencies: '@jest/core': 29.7.0 '@jest/test-result': 29.7.0 '@jest/types': 29.6.3 chalk: 4.1.2 - create-jest: 29.7.0(@types/node@22.14.0) + create-jest: 29.7.0(@types/node@25.5.0) exit: 0.1.2 import-local: 3.2.0 - jest-config: 29.7.0(@types/node@22.14.0) + jest-config: 29.7.0(@types/node@25.5.0) jest-util: 29.7.0 jest-validate: 29.7.0 yargs: 17.7.2 @@ -3255,7 +4547,7 @@ snapshots: - supports-color - ts-node - jest-config@29.7.0(@types/node@22.14.0): + jest-config@29.7.0(@types/node@25.5.0): dependencies: '@babel/core': 7.26.10 '@jest/test-sequencer': 29.7.0 @@ -3280,7 +4572,7 @@ snapshots: slash: 3.0.0 strip-json-comments: 3.1.1 optionalDependencies: - '@types/node': 22.14.0 + '@types/node': 25.5.0 transitivePeerDependencies: - babel-plugin-macros - supports-color @@ -3309,7 +4601,7 @@ snapshots: '@jest/environment': 29.7.0 '@jest/fake-timers': 29.7.0 '@jest/types': 29.6.3 - '@types/node': 22.14.0 + '@types/node': 25.5.0 jest-mock: 29.7.0 jest-util: 29.7.0 @@ -3319,7 +4611,7 @@ snapshots: dependencies: '@jest/types': 29.6.3 '@types/graceful-fs': 4.1.9 - '@types/node': 22.14.0 + '@types/node': 25.5.0 anymatch: 3.1.3 fb-watchman: 2.0.2 graceful-fs: 4.2.11 @@ -3358,7 +4650,7 @@ snapshots: jest-mock@29.7.0: dependencies: '@jest/types': 29.6.3 - '@types/node': 22.14.0 + '@types/node': 25.5.0 jest-util: 29.7.0 jest-pnp-resolver@1.2.3(jest-resolve@29.7.0): @@ -3393,7 +4685,7 @@ snapshots: '@jest/test-result': 29.7.0 '@jest/transform': 29.7.0 '@jest/types': 29.6.3 - '@types/node': 22.14.0 + '@types/node': 25.5.0 chalk: 4.1.2 emittery: 0.13.1 graceful-fs: 4.2.11 @@ -3421,7 +4713,7 @@ snapshots: '@jest/test-result': 29.7.0 '@jest/transform': 29.7.0 '@jest/types': 29.6.3 - '@types/node': 22.14.0 + '@types/node': 25.5.0 chalk: 4.1.2 cjs-module-lexer: 1.4.3 collect-v8-coverage: 1.0.2 @@ -3467,7 +4759,7 @@ snapshots: jest-util@29.7.0: dependencies: '@jest/types': 29.6.3 - '@types/node': 22.14.0 + '@types/node': 25.5.0 chalk: 4.1.2 ci-info: 3.9.0 graceful-fs: 4.2.11 @@ -3486,7 +4778,7 @@ snapshots: dependencies: '@jest/test-result': 29.7.0 '@jest/types': 29.6.3 - '@types/node': 22.14.0 + '@types/node': 25.5.0 ansi-escapes: 4.3.2 chalk: 4.1.2 emittery: 0.13.1 @@ -3495,23 +4787,25 @@ snapshots: jest-worker@29.7.0: dependencies: - '@types/node': 22.14.0 + '@types/node': 25.5.0 jest-util: 29.7.0 merge-stream: 2.0.0 supports-color: 8.1.1 - jest@29.7.0(@types/node@22.14.0): + jest@29.7.0(@types/node@25.5.0): dependencies: '@jest/core': 29.7.0 '@jest/types': 29.6.3 import-local: 3.2.0 - jest-cli: 29.7.0(@types/node@22.14.0) + jest-cli: 29.7.0(@types/node@25.5.0) transitivePeerDependencies: - '@types/node' - babel-plugin-macros - supports-color - ts-node + jiti@2.6.1: {} + js-tokens@4.0.0: {} js-yaml@3.14.1: @@ -3523,34 +4817,67 @@ snapshots: dependencies: argparse: 2.0.1 + js-yaml@4.1.1: + dependencies: + argparse: 2.0.1 + jsesc@3.1.0: {} + json-buffer@3.0.1: {} + json-parse-even-better-errors@2.3.1: {} + json-schema-traverse@0.4.1: {} + + json-stable-stringify-without-jsonify@1.0.1: {} + json5@2.2.3: {} + keyv@4.5.4: + dependencies: + json-buffer: 3.0.1 + kleur@3.0.3: {} leven@3.1.0: {} + levn@0.4.1: + dependencies: + prelude-ls: 1.2.1 + type-check: 0.4.0 + + lilconfig@2.1.0: {} + lines-and-columns@1.2.4: {} locate-path@5.0.0: dependencies: p-locate: 4.1.0 + locate-path@6.0.0: + dependencies: + p-locate: 5.0.0 + lodash.get@4.4.2: {} lodash.isequal@4.5.0: {} lodash.memoize@4.1.2: {} + lodash.merge@4.6.2: {} + lodash.mergewith@4.6.2: {} + lodash@4.17.21: {} + + long@5.3.2: {} + lru-cache@5.1.1: dependencies: yallist: 3.1.1 + lru.min@1.1.4: {} + make-dir@4.0.0: dependencies: semver: 7.7.1 @@ -3592,21 +4919,27 @@ snapshots: mimic-fn@2.1.0: {} + minimatch@10.2.4: + dependencies: + brace-expansion: 5.0.4 + minimatch@3.1.2: dependencies: brace-expansion: 1.1.11 - minimatch@5.1.6: + minimatch@3.1.5: dependencies: - brace-expansion: 2.0.1 + brace-expansion: 1.1.11 - morgan@1.10.0: + minimist@1.2.8: {} + + morgan@1.10.1: dependencies: basic-auth: 2.0.1 debug: 2.6.9 depd: 2.0.0 on-finished: 2.3.0 - on-headers: 1.0.2 + on-headers: 1.1.0 transitivePeerDependencies: - supports-color @@ -3614,10 +4947,30 @@ snapshots: ms@2.1.3: {} + mysql2@3.15.3: + dependencies: + aws-ssl-profiles: 1.1.2 + denque: 2.1.0 + generate-function: 2.3.1 + iconv-lite: 0.7.2 + long: 5.3.2 + lru.min: 1.1.4 + named-placeholders: 1.1.6 + seq-queue: 0.0.5 + sqlstring: 2.3.3 + + named-placeholders@1.1.6: + dependencies: + lru.min: 1.1.4 + natural-compare@1.4.0: {} negotiator@1.0.0: {} + neo-async@2.6.2: {} + + node-fetch-native@1.6.7: {} + node-int64@0.4.0: {} node-releases@2.0.19: {} @@ -3628,10 +4981,20 @@ snapshots: dependencies: path-key: 3.1.1 + nypm@0.6.5: + dependencies: + citty: 0.2.1 + pathe: 2.0.3 + tinyexec: 1.0.4 + object-assign@4.1.1: {} object-inspect@1.13.4: {} + obuf@1.1.2: {} + + ohash@2.0.11: {} + on-finished@2.3.0: dependencies: ee-first: 1.1.1 @@ -3640,7 +5003,7 @@ snapshots: dependencies: ee-first: 1.1.1 - on-headers@1.0.2: {} + on-headers@1.1.0: {} once@1.4.0: dependencies: @@ -3652,6 +5015,15 @@ snapshots: openapi-types@12.1.3: {} + optionator@0.9.4: + dependencies: + deep-is: 0.1.4 + fast-levenshtein: 2.0.6 + levn: 0.4.1 + prelude-ls: 1.2.1 + type-check: 0.4.0 + word-wrap: 1.2.5 + p-limit@2.3.0: dependencies: p-try: 2.2.0 @@ -3664,8 +5036,16 @@ snapshots: dependencies: p-limit: 2.3.0 + p-locate@5.0.0: + dependencies: + p-limit: 3.1.0 + p-try@2.2.0: {} + parent-module@1.0.1: + dependencies: + callsites: 3.1.0 + parse-json@5.2.0: dependencies: '@babel/code-frame': 7.26.2 @@ -3685,67 +5065,192 @@ snapshots: path-to-regexp@8.2.0: {} + pathe@2.0.3: {} + + perfect-debounce@1.0.0: {} + + pg-cloudflare@1.3.0: + optional: true + + pg-connection-string@2.12.0: {} + + pg-int8@1.0.1: {} + + pg-numeric@1.0.2: {} + + pg-pool@3.13.0(pg@8.20.0): + dependencies: + pg: 8.20.0 + + pg-protocol@1.13.0: {} + + pg-types@2.2.0: + dependencies: + pg-int8: 1.0.1 + postgres-array: 2.0.0 + postgres-bytea: 1.0.1 + postgres-date: 1.0.7 + postgres-interval: 1.2.0 + + pg-types@4.1.0: + dependencies: + pg-int8: 1.0.1 + pg-numeric: 1.0.2 + postgres-array: 3.0.4 + postgres-bytea: 3.0.0 + postgres-date: 2.1.0 + postgres-interval: 3.0.0 + postgres-range: 1.1.4 + + pg@8.20.0: + dependencies: + pg-connection-string: 2.12.0 + pg-pool: 3.13.0(pg@8.20.0) + pg-protocol: 1.13.0 + pg-types: 2.2.0 + pgpass: 1.0.5 + optionalDependencies: + pg-cloudflare: 1.3.0 + + pgpass@1.0.5: + dependencies: + split2: 4.2.0 + picocolors@1.1.1: {} picomatch@2.3.1: {} + picomatch@4.0.3: {} + pirates@4.0.7: {} pkg-dir@4.2.0: dependencies: find-up: 4.1.0 + pkg-types@2.3.0: + dependencies: + confbox: 0.2.4 + exsolve: 1.0.8 + pathe: 2.0.3 + + postgres-array@2.0.0: {} + + postgres-array@3.0.4: {} + + postgres-bytea@1.0.1: {} + + postgres-bytea@3.0.0: + dependencies: + obuf: 1.1.2 + + postgres-date@1.0.7: {} + + postgres-date@2.1.0: {} + + postgres-interval@1.2.0: + dependencies: + xtend: 4.0.2 + + postgres-interval@3.0.0: {} + + postgres-range@1.1.4: {} + + postgres@3.4.7: {} + + prelude-ls@1.2.1: {} + pretty-format@29.7.0: dependencies: '@jest/schemas': 29.6.3 ansi-styles: 5.2.0 react-is: 18.3.1 - prisma@6.6.0(typescript@5.8.3): + prisma@7.5.0(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(typescript@5.9.3): dependencies: - '@prisma/config': 6.6.0 - '@prisma/engines': 6.6.0 + '@prisma/config': 7.5.0 + '@prisma/dev': 0.20.0(typescript@5.9.3) + '@prisma/engines': 7.5.0 + '@prisma/studio-core': 0.21.1(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + mysql2: 3.15.3 + postgres: 3.4.7 optionalDependencies: - fsevents: 2.3.3 - typescript: 5.8.3 + typescript: 5.9.3 transitivePeerDependencies: - - supports-color + - '@types/react' + - magicast + - react + - react-dom prompts@2.4.2: dependencies: kleur: 3.0.3 sisteransi: 1.0.5 + proper-lockfile@4.1.2: + dependencies: + graceful-fs: 4.2.11 + retry: 0.12.0 + signal-exit: 3.0.7 + proxy-addr@2.0.7: dependencies: forwarded: 0.2.0 ipaddr.js: 1.9.1 + punycode@2.3.1: {} + pure-rand@6.1.0: {} qs@6.14.0: dependencies: side-channel: 1.1.0 + qs@6.15.0: + dependencies: + side-channel: 1.1.0 + range-parser@1.2.1: {} - raw-body@3.0.0: + raw-body@3.0.2: dependencies: bytes: 3.1.2 - http-errors: 2.0.0 - iconv-lite: 0.6.3 + http-errors: 2.0.1 + iconv-lite: 0.7.2 unpipe: 1.0.0 + rc9@2.1.2: + dependencies: + defu: 6.1.4 + destr: 2.0.5 + + react-dom@19.2.4(react@19.2.4): + dependencies: + react: 19.2.4 + scheduler: 0.27.0 + react-is@18.3.1: {} + react@19.2.4: {} + + readdirp@4.1.2: {} + + regexp-to-ast@0.5.0: {} + + remeda@2.33.4: {} + require-directory@2.1.1: {} resolve-cwd@3.0.0: dependencies: resolve-from: 5.0.0 + resolve-from@4.0.0: {} + resolve-from@5.0.0: {} + resolve-pkg-maps@1.0.0: {} + resolve.exports@2.0.3: {} resolve@1.22.10: @@ -3754,6 +5259,8 @@ snapshots: path-parse: 1.0.7 supports-preserve-symlinks-flag: 1.0.0 + retry@0.12.0: {} + router@2.2.0: dependencies: debug: 4.4.0 @@ -3770,10 +5277,14 @@ snapshots: safer-buffer@2.1.2: {} + scheduler@0.27.0: {} + semver@6.3.1: {} semver@7.7.1: {} + semver@7.7.4: {} + send@1.2.0: dependencies: debug: 4.4.0 @@ -3790,6 +5301,8 @@ snapshots: transitivePeerDependencies: - supports-color + seq-queue@0.0.5: {} + serve-static@2.2.0: dependencies: encodeurl: 2.0.0 @@ -3837,6 +5350,8 @@ snapshots: signal-exit@3.0.7: {} + signal-exit@4.1.0: {} + sisteransi@1.0.5: {} slash@3.0.0: {} @@ -3848,14 +5363,22 @@ snapshots: source-map@0.6.1: {} + split2@4.2.0: {} + sprintf-js@1.0.3: {} + sqlstring@2.3.3: {} + stack-utils@2.0.6: dependencies: escape-string-regexp: 2.0.0 statuses@2.0.1: {} + statuses@2.0.2: {} + + std-env@3.10.0: {} + string-length@4.0.2: dependencies: char-regex: 1.0.2 @@ -3877,24 +5400,25 @@ snapshots: strip-json-comments@3.1.1: {} - superagent@9.0.2: + superagent@10.3.0: dependencies: component-emitter: 1.3.1 cookiejar: 2.1.4 debug: 4.4.0 fast-safe-stringify: 2.1.1 - form-data: 4.0.2 - formidable: 3.5.2 + form-data: 4.0.5 + formidable: 3.5.4 methods: 1.1.2 mime: 2.6.0 - qs: 6.14.0 + qs: 6.15.0 transitivePeerDependencies: - supports-color - supertest@7.1.0: + supertest@7.2.2: dependencies: + cookie-signature: 1.2.2 methods: 1.1.2 - superagent: 9.0.2 + superagent: 10.3.0 transitivePeerDependencies: - supports-color @@ -3929,9 +5453,9 @@ snapshots: dependencies: '@scarf/scarf': 1.4.0 - swagger-ui-express@5.0.1(express@5.1.0): + swagger-ui-express@5.0.1(express@5.2.1): dependencies: - express: 5.1.0 + express: 5.2.1 swagger-ui-dist: 5.20.8 test-exclude@6.0.0: @@ -3940,6 +5464,13 @@ snapshots: glob: 7.1.6 minimatch: 3.1.2 + tinyexec@1.0.4: {} + + tinyglobby@0.2.15: + dependencies: + fdir: 6.5.0(picomatch@4.0.3) + picomatch: 4.0.3 + tmpl@1.0.5: {} to-regex-range@5.0.1: @@ -3948,32 +5479,46 @@ snapshots: toidentifier@1.0.1: {} - ts-jest@29.3.1(@babel/core@7.26.10)(@jest/transform@29.7.0)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@7.26.10))(esbuild@0.25.2)(jest@29.7.0(@types/node@22.14.0))(typescript@5.8.3): + ts-api-utils@2.4.0(typescript@5.9.3): + dependencies: + typescript: 5.9.3 + + ts-jest@29.4.6(@babel/core@7.26.10)(@jest/transform@29.7.0)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@7.26.10))(jest-util@29.7.0)(jest@29.7.0(@types/node@25.5.0))(typescript@5.9.3): dependencies: bs-logger: 0.2.6 - ejs: 3.1.10 fast-json-stable-stringify: 2.1.0 - jest: 29.7.0(@types/node@22.14.0) - jest-util: 29.7.0 + handlebars: 4.7.8 + jest: 29.7.0(@types/node@25.5.0) json5: 2.2.3 lodash.memoize: 4.1.2 make-error: 1.3.6 - semver: 7.7.1 - type-fest: 4.39.1 - typescript: 5.8.3 + semver: 7.7.4 + type-fest: 4.41.0 + typescript: 5.9.3 yargs-parser: 21.1.1 optionalDependencies: '@babel/core': 7.26.10 '@jest/transform': 29.7.0 '@jest/types': 29.6.3 babel-jest: 29.7.0(@babel/core@7.26.10) - esbuild: 0.25.2 + jest-util: 29.7.0 + + tsx@4.21.0: + dependencies: + esbuild: 0.27.4 + get-tsconfig: 4.13.6 + optionalDependencies: + fsevents: 2.3.3 + + type-check@0.4.0: + dependencies: + prelude-ls: 1.2.1 type-detect@4.0.8: {} type-fest@0.21.3: {} - type-fest@4.39.1: {} + type-fest@4.41.0: {} type-is@2.0.1: dependencies: @@ -3981,9 +5526,23 @@ snapshots: media-typer: 1.1.0 mime-types: 3.0.1 - typescript@5.8.3: {} + typescript-eslint@8.57.0(eslint@9.39.4(jiti@2.6.1))(typescript@5.9.3): + dependencies: + '@typescript-eslint/eslint-plugin': 8.57.0(@typescript-eslint/parser@8.57.0(eslint@9.39.4(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.4(jiti@2.6.1))(typescript@5.9.3) + '@typescript-eslint/parser': 8.57.0(eslint@9.39.4(jiti@2.6.1))(typescript@5.9.3) + '@typescript-eslint/typescript-estree': 8.57.0(typescript@5.9.3) + '@typescript-eslint/utils': 8.57.0(eslint@9.39.4(jiti@2.6.1))(typescript@5.9.3) + eslint: 9.39.4(jiti@2.6.1) + typescript: 5.9.3 + transitivePeerDependencies: + - supports-color + + typescript@5.9.3: {} + + uglify-js@3.19.3: + optional: true - undici-types@6.21.0: {} + undici-types@7.18.2: {} unpipe@1.0.0: {} @@ -3993,12 +5552,20 @@ snapshots: escalade: 3.2.0 picocolors: 1.1.1 + uri-js@4.4.1: + dependencies: + punycode: 2.3.1 + v8-to-istanbul@9.3.0: dependencies: '@jridgewell/trace-mapping': 0.3.25 '@types/istanbul-lib-coverage': 2.0.6 convert-source-map: 2.0.0 + valibot@1.2.0(typescript@5.9.3): + optionalDependencies: + typescript: 5.9.3 + validator@13.15.0: {} vary@1.1.2: {} @@ -4011,6 +5578,10 @@ snapshots: dependencies: isexe: 2.0.0 + word-wrap@1.2.5: {} + + wordwrap@1.0.0: {} + wrap-ansi@7.0.0: dependencies: ansi-styles: 4.3.0 @@ -4024,6 +5595,8 @@ snapshots: imurmurhash: 0.1.4 signal-exit: 3.0.7 + xtend@4.0.2: {} + y18n@5.0.8: {} yallist@3.1.1: {} @@ -4052,4 +5625,9 @@ snapshots: optionalDependencies: commander: 9.5.0 + zeptomatch@2.1.0: + dependencies: + grammex: 3.1.12 + graphmatch: 1.1.1 + zod@3.24.2: {} diff --git a/prisma.config.ts b/prisma.config.ts new file mode 100644 index 0000000..e5e83d7 --- /dev/null +++ b/prisma.config.ts @@ -0,0 +1,13 @@ +import 'dotenv/config'; +import { defineConfig } from 'prisma/config'; + +export default defineConfig({ + schema: 'prisma/schema.prisma', + migrations: { + path: 'prisma/migrations', + seed: 'tsx prisma/seed.ts', + }, + datasource: { + url: process.env.DATABASE_URL ?? 'postgresql://postgres:postgres@localhost:5432/locations_api', + }, +}); diff --git a/prisma/migrations/20260316190000_add_general_search_vector/migration.sql b/prisma/migrations/20260316190000_add_general_search_vector/migration.sql new file mode 100644 index 0000000..72d2bf9 --- /dev/null +++ b/prisma/migrations/20260316190000_add_general_search_vector/migration.sql @@ -0,0 +1,24 @@ +ALTER TABLE "general" +ADD COLUMN "search_vector" tsvector; + +UPDATE "general" +SET "search_vector" = to_tsvector( + 'simple'::regconfig, + concat_ws(' '::text, "region", "district", "ward", "street", "places") +); + +CREATE FUNCTION update_general_search_vector() RETURNS trigger +LANGUAGE plpgsql +AS $$ +BEGIN + NEW.search_vector := to_tsvector('simple'::regconfig, concat_ws(' '::text, NEW.region, NEW.district, NEW.ward, NEW.street, NEW.places)); + RETURN NEW; +END +$$; + +CREATE TRIGGER set_general_search_vector +BEFORE INSERT OR UPDATE ON "general" +FOR EACH ROW +EXECUTE FUNCTION update_general_search_vector(); + +CREATE INDEX "idx_general_search_vector" ON "general" USING GIN ("search_vector"); diff --git a/prisma/migrations/init/migration.sql b/prisma/migrations/init/migration.sql index 5825673..0c477af 100644 --- a/prisma/migrations/init/migration.sql +++ b/prisma/migrations/init/migration.sql @@ -112,4 +112,3 @@ ALTER TABLE "wards" ADD CONSTRAINT "wards_general_locations_id_fkey" FOREIGN KEY -- AddForeignKey ALTER TABLE "wards" ADD CONSTRAINT "wards_region_id_fkey" FOREIGN KEY ("region_id") REFERENCES "regions"("region_code") ON DELETE NO ACTION ON UPDATE NO ACTION; - diff --git a/prisma/schema.prisma b/prisma/schema.prisma index 5abddf3..452c434 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -1,11 +1,12 @@ generator client { - provider = "prisma-client-js" - previewFeatures = ["fullTextSearchPostgres"] + provider = "prisma-client" + output = "../src/generated/prisma" + moduleFormat = "esm" + engineType = "client" } datasource db { provider = "postgresql" - url = env("DATABASE_URL") } model Countries { @@ -103,7 +104,7 @@ model General { wardcode Int street String? @db.VarChar places String? @db.VarChar - search_vector Unsupported("tsvector")? @default(dbgenerated("to_tsvector('simple'::regconfig, (((((((((COALESCE(region, ''::character varying))::text || ' '::text) || (COALESCE(district, ''::character varying))::text) || ' '::text) || (COALESCE(ward, ''::character varying))::text) || ' '::text) || (COALESCE(street, ''::character varying))::text) || ' '::text) || (COALESCE(places, ''::character varying))::text))")) + search_vector Unsupported("tsvector")? districts Districts[] @relation("districtsTogeneral") places_generalToplaces Places[] @relation("generalToplaces") regions Regions[] @relation("generalToregions") diff --git a/prisma/seed.ts b/prisma/seed.ts new file mode 100644 index 0000000..d0afa24 --- /dev/null +++ b/prisma/seed.ts @@ -0,0 +1,178 @@ +import { disconnectPrisma, prisma } from '../src/db/prisma.js'; + +async function seed() { + await prisma.$transaction(async (tx) => { + await tx.$executeRawUnsafe( + 'TRUNCATE TABLE "places", "wards", "districts", "regions", "general", "countries" RESTART IDENTITY CASCADE', + ); + + const tanzania = await tx.countries.create({ + data: { + iso: 'TZ', + iso3: 'TZA', + name: 'Tanzania', + nicename: 'United Republic of Tanzania', + numcode: 834, + phonecode: 255, + }, + }); + + const kenya = await tx.countries.create({ + data: { + iso: 'KE', + iso3: 'KEN', + name: 'Kenya', + nicename: 'Republic of Kenya', + numcode: 404, + phonecode: 254, + }, + }); + + const dodomaGeneral = await tx.general.create({ + data: { + countryId: tanzania.id, + region: 'Dodoma', + regioncode: 12, + district: 'Dodoma Urban', + districtcode: 1201, + ward: 'Nzuguni', + wardcode: 120101, + street: 'Nzuguni Road', + places: 'Nzuguni Center', + }, + }); + + const arushaGeneral = await tx.general.create({ + data: { + countryId: tanzania.id, + region: 'Arusha', + regioncode: 11, + district: 'Arusha Urban', + districtcode: 1101, + ward: 'Kaloleni', + wardcode: 110101, + street: 'Clock Tower Avenue', + places: 'Arusha Clock Tower', + }, + }); + + const nairobiGeneral = await tx.general.create({ + data: { + countryId: kenya.id, + region: 'Nairobi', + regioncode: 21, + district: 'Westlands', + districtcode: 2101, + ward: 'Parklands', + wardcode: 210101, + street: 'Westlands Road', + places: 'Sarit Centre', + }, + }); + + await tx.regions.createMany({ + data: [ + { countryId: tanzania.id, general_locations_id: arushaGeneral.id, regionCode: 11, regionName: 'Arusha' }, + { countryId: tanzania.id, general_locations_id: dodomaGeneral.id, regionCode: 12, regionName: 'Dodoma' }, + { countryId: kenya.id, general_locations_id: nairobiGeneral.id, regionCode: 21, regionName: 'Nairobi' }, + ], + }); + + await tx.districts.createMany({ + data: [ + { + country_id: tanzania.id, + districtCode: 1101, + districtName: 'Arusha Urban', + general_locations_id: arushaGeneral.id, + regionId: 11, + }, + { + country_id: tanzania.id, + districtCode: 1201, + districtName: 'Dodoma Urban', + general_locations_id: dodomaGeneral.id, + regionId: 12, + }, + { + country_id: kenya.id, + districtCode: 2101, + districtName: 'Westlands', + general_locations_id: nairobiGeneral.id, + regionId: 21, + }, + ], + }); + + await tx.wards.createMany({ + data: [ + { + country_id: tanzania.id, + districtId: 1101, + general_locations_id: arushaGeneral.id, + region_id: 11, + wardCode: 110101, + wardName: 'Kaloleni', + }, + { + country_id: tanzania.id, + districtId: 1201, + general_locations_id: dodomaGeneral.id, + region_id: 12, + wardCode: 120101, + wardName: 'Nzuguni', + }, + { + country_id: kenya.id, + districtId: 2101, + general_locations_id: nairobiGeneral.id, + region_id: 21, + wardCode: 210101, + wardName: 'Parklands', + }, + ], + }); + + await tx.places.create({ + data: { + country_id: tanzania.id, + district_id: 1101, + general_locations_id: arushaGeneral.id, + placeName: 'Arusha Clock Tower', + region_id: 11, + wardId: 110101, + }, + }); + + await tx.places.create({ + data: { + country_id: tanzania.id, + district_id: 1201, + general_locations_id: dodomaGeneral.id, + placeName: 'Nzuguni Center', + region_id: 12, + wardId: 120101, + }, + }); + + await tx.places.create({ + data: { + country_id: kenya.id, + district_id: 2101, + general_locations_id: nairobiGeneral.id, + placeName: 'Sarit Centre', + region_id: 21, + wardId: 210101, + }, + }); + }); +} + +seed() + .catch((error) => { + console.error('Seed failed', error); + process.exitCode = 1; + }) + .finally(async () => { + await disconnectPrisma(); + }); diff --git a/scripts/export-openapi.ts b/scripts/export-openapi.ts new file mode 100644 index 0000000..e41f8b2 --- /dev/null +++ b/scripts/export-openapi.ts @@ -0,0 +1,10 @@ +import { mkdir, writeFile } from 'node:fs/promises'; +import path from 'node:path'; +import { fileURLToPath } from 'node:url'; +import { openApiSpec } from '../src/docs/swagger.js'; + +const __dirname = path.dirname(fileURLToPath(import.meta.url)); +const outputDir = path.resolve(__dirname, '../generated/openapi'); + +await mkdir(outputDir, { recursive: true }); +await writeFile(path.join(outputDir, 'openapi.json'), `${JSON.stringify(openApiSpec, null, 2)}\n`, 'utf8'); diff --git a/scripts/migrate.ts b/scripts/migrate.ts new file mode 100644 index 0000000..a5a25c7 --- /dev/null +++ b/scripts/migrate.ts @@ -0,0 +1,71 @@ +import { spawnSync } from 'node:child_process'; +import { Pool } from 'pg'; +import config from '../src/config.js'; + +const pnpmCommand = process.platform === 'win32' ? 'pnpm.cmd' : 'pnpm'; + +function runPrisma(args: string[]) { + const result = spawnSync( + pnpmCommand, + ['exec', 'prisma', ...args], + { + env: process.env, + stdio: 'inherit', + }, + ); + + if (result.status !== 0) { + process.exit(result.status ?? 1); + } +} + +async function bootstrapIfNeeded() { + const pool = new Pool({ + connectionString: config.databaseUrl, + }); + + try { + const [{ migrationsTableExists, countriesTableExists }] = ( + await pool.query<{ + migrationsTableExists: string | null; + countriesTableExists: string | null; + }>( + ` + SELECT + to_regclass('public._prisma_migrations') AS "migrationsTableExists", + to_regclass('public.countries') AS "countriesTableExists" + `, + ) + ).rows; + + const initApplied = migrationsTableExists + ? ( + await pool.query<{ exists: boolean }>( + `SELECT EXISTS( + SELECT 1 + FROM "_prisma_migrations" + WHERE migration_name = 'init' + ) AS "exists"`, + ) + ).rows[0]?.exists ?? false + : false; + + if (!initApplied) { + if (!countriesTableExists) { + runPrisma([ + 'db', + 'execute', + '--file', + 'prisma/migrations/init/migration.sql', + ]); + } + + runPrisma(['migrate', 'resolve', '--applied', 'init']); + } + } finally { + await pool.end(); + } +} + +await bootstrapIfNeeded(); +runPrisma(['migrate', 'deploy']); diff --git a/server.ts b/server.ts index 50acb30..3c6e6e3 100644 --- a/server.ts +++ b/server.ts @@ -1,22 +1,38 @@ -import { PrismaClient } from '@prisma/client'; -import app from './src/app'; +import app from './src/app.js'; +import config from './src/config.js'; +import { disconnectPrisma } from './src/db/prisma.js'; -const PORT = process.env.PORT || 8080; -const prisma = new PrismaClient(); - -process.on('SIGINT', async () => { - console.log('SIGINT received. Shutting down gracefully'); - await prisma.$disconnect(); - process.exit(0); +const server = app.listen(config.port, () => { + console.log( + JSON.stringify({ + environment: config.nodeEnv, + message: 'Server started', + openApiUrl: `http://localhost:${config.port}/openapi.json`, + port: config.port, + swaggerUrl: `http://localhost:${config.port}/api-docs`, + }), + ); }); -process.on('SIGTERM', async () => { - console.log('SIGTERM received. Shutting down gracefully'); - await prisma.$disconnect(); - process.exit(0); +async function shutdown(signal: NodeJS.Signals) { + console.log(JSON.stringify({ message: 'Graceful shutdown requested', signal })); + + server.close(() => { + void disconnectPrisma() + .then(() => { + process.exit(0); + }) + .catch((error: unknown) => { + console.error(JSON.stringify({ error, message: 'Failed to disconnect Prisma cleanly' })); + process.exit(1); + }); + }); +} + +process.on('SIGINT', () => { + void shutdown('SIGINT'); }); -app.listen(PORT, () => { - console.log(`Server running on port ${PORT} in ${process.env.NODE_ENV || 'development'} mode`); - console.log(`API Documentation available at http://localhost:${PORT}/api-docs`); +process.on('SIGTERM', () => { + void shutdown('SIGTERM'); }); diff --git a/src/app.ts b/src/app.ts index b134df7..e61cf92 100644 --- a/src/app.ts +++ b/src/app.ts @@ -1,34 +1,46 @@ -import express, { Request, Response, NextFunction } from 'express'; +import express from 'express'; import cors from 'cors'; import helmet from 'helmet'; import morgan from 'morgan'; -import { PrismaClient } from '@prisma/client'; -import { errorHandler } from './middleware/errorHandler'; -import { setupSwagger } from './docs/swagger'; -import routes from './routes'; +import type { Request, Response } from 'express'; +import config from './config.js'; +import { setupSwagger } from './docs/swagger.js'; +import { errorHandler } from './middleware/errorHandler.js'; +import { + apiCompatibilityHeaders, + attachRequestContext, +} from './middleware/requestContext.js'; +import routes from './routes.js'; const app = express(); -export const prisma = new PrismaClient(); -app.use(helmet()); -app.use(cors()); -app.use(morgan('dev')); +morgan.token('request-id', (req) => (req as Request).requestId ?? '-'); -if (process.env.NODE_ENV !== 'production') { - app.use((req: Request, _: Response, next: NextFunction) => { - console.log(`[${new Date().toISOString()}] ${req.method} ${req.url}`); - next(); +const logFormatter: morgan.FormatFn = (tokens, req, res) => { + return JSON.stringify({ + contentLength: tokens.res(req, res, 'content-length') ?? '0', + method: tokens.method(req, res), + path: tokens.url(req, res), + requestId: tokens['request-id'](req, res), + responseTimeMs: Number(tokens['response-time'](req, res)), + status: Number(tokens.status(req, res)), }); -} +}; + +app.use(helmet()); +app.use(cors()); +app.disable('x-powered-by'); +app.use(attachRequestContext); +app.use(morgan(logFormatter)); app.use(express.json()); app.use(express.urlencoded({ extended: true })); app.get('/health', (_: Request, res: Response) => { - res.status(200).json({ - status: 'UP', + res.status(200).json({ + status: 'UP', timestamp: new Date().toISOString(), - environment: process.env.NODE_ENV || 'development', + environment: config.nodeEnv, version: process.env.npm_package_version || '1.0.0' }); }); @@ -38,6 +50,9 @@ app.get('/', (_: Request, res: Response) => { }); app.use('/v1', routes); +app.use('/api', apiCompatibilityHeaders, routes); + +setupSwagger(app); app.use((req, res) => { res.status(404).json({ @@ -50,5 +65,4 @@ app.use((req, res) => { app.use(errorHandler); -setupSwagger(app); export default app; diff --git a/src/config.ts b/src/config.ts index d20b2c2..3d4abe1 100644 --- a/src/config.ts +++ b/src/config.ts @@ -1,11 +1,22 @@ import dotenv from 'dotenv'; +import { z } from 'zod'; + dotenv.config(); +const envSchema = z.object({ + DATABASE_URL: z.string().min(1, 'DATABASE_URL is required'), + NODE_ENV: z.enum(['development', 'test', 'production']).default('development'), + PAGE_SIZE: z.coerce.number().int().positive().max(100).default(10), + PORT: z.coerce.number().int().positive().default(8080), +}); + +const env = envSchema.parse(process.env); + const config = { - nodeEnv: process.env.NODE_ENV || 'development', - port: process.env.PORT || 8080, - databaseUrl: process.env.DATABASE_URL, - pageSize: parseInt(process.env.PAGE_SIZE || '10', 10) + databaseUrl: env.DATABASE_URL, + nodeEnv: env.NODE_ENV, + pageSize: env.PAGE_SIZE, + port: env.PORT, }; export default config; diff --git a/src/db/prisma.ts b/src/db/prisma.ts new file mode 100644 index 0000000..5ca1c0f --- /dev/null +++ b/src/db/prisma.ts @@ -0,0 +1,75 @@ +import { PrismaPg } from '@prisma/adapter-pg'; +import { Pool } from 'pg'; +import { PrismaClient } from '../generated/prisma/client.js'; +import config from '../config.js'; + +const globalForPrisma = globalThis as typeof globalThis & { + pgPool?: Pool; + prismaClient?: PrismaClient; +}; + +let pool = globalForPrisma.pgPool; +let prismaClient = globalForPrisma.prismaClient; + +function createPool() { + return new Pool({ + connectionString: config.databaseUrl, + }); +} + +function createPrismaClient(nextPool: Pool) { + return new PrismaClient({ + adapter: new PrismaPg(nextPool as unknown as ConstructorParameters[0]), + }); +} + +function cacheInstances() { + if (config.nodeEnv !== 'production') { + globalForPrisma.pgPool = pool; + globalForPrisma.prismaClient = prismaClient; + } +} + +function ensurePrismaClient(): PrismaClient { + if (!pool) { + pool = createPool(); + } + + if (!prismaClient) { + prismaClient = createPrismaClient(pool); + cacheInstances(); + } + + return prismaClient; +} + +export const prisma = new Proxy({} as PrismaClient, { + get(_, property, receiver) { + return Reflect.get(ensurePrismaClient() as object, property, receiver) as unknown; + }, + set(_, property, value, receiver) { + return Reflect.set(ensurePrismaClient() as object, property, value, receiver); + }, +}); + +if (pool && prismaClient && config.nodeEnv !== 'production') { + cacheInstances(); +} + +export async function disconnectPrisma() { + if (prismaClient) { + await prismaClient.$disconnect(); + } + + if (pool) { + await pool.end(); + } + + pool = undefined; + prismaClient = undefined; + + if (config.nodeEnv !== 'production') { + delete globalForPrisma.pgPool; + delete globalForPrisma.prismaClient; + } +} diff --git a/src/docs/swagger.ts b/src/docs/swagger.ts index 0208708..4b2ff15 100644 --- a/src/docs/swagger.ts +++ b/src/docs/swagger.ts @@ -1,36 +1,243 @@ -import swaggerJsdoc from 'swagger-jsdoc'; +import type { Express } from 'express'; import swaggerUi from 'swagger-ui-express'; -import { Express } from 'express'; -const options = { - definition: { - openapi: '3.0.0', - info: { - title: 'Tanzania Location API', - version: '2.0.0', - description: 'API for retrieving location data in Tanzania including countries, regions, districts, and wards.', - contact: { - name: 'HackEAC', - url: 'https://maotora.com', - email: 'maotoramm@gmail.com' - }, - license: { - name: 'copyleft', - url: 'https://opensource.org/licenses/GNU' - } - }, - servers: [ - { - url: 'http://localhost:8080/api', - description: 'Local development server' - } - ] +export const openApiSpec = { + openapi: '3.1.0', + info: { + title: 'Tanzania Location API', + version: '2.0.0', + description: + 'Compatibility-first API for Tanzania and East Africa location data with dual base paths.', + contact: { + name: 'HackEAC', + url: 'https://maotora.com', + email: 'maotoramm@gmail.com', + }, + }, + servers: [ + { + url: '/v1', + description: 'Canonical API base path', + }, + { + url: '/api', + description: 'Compatibility alias', + }, + ], + components: { + schemas: { + ErrorResponse: { + type: 'object', + properties: { + error: { + type: 'object', + properties: { + message: { type: 'string' }, + }, + required: ['message'], + }, + }, + required: ['error'], + }, + Pagination: { + type: 'object', + properties: { + page: { type: 'integer' }, + limit: { type: 'integer' }, + total: { type: 'integer' }, + pages: { type: 'integer' }, + }, + required: ['page', 'limit', 'total', 'pages'], + }, + }, + }, + paths: { + '/countries': { + get: { + summary: 'List countries', + parameters: [ + { in: 'query', name: 'page', schema: { type: 'integer', minimum: 1 } }, + { in: 'query', name: 'limit', schema: { type: 'integer', minimum: 1, maximum: 100 } }, + { in: 'query', name: 'search', schema: { type: 'string' } }, + ], + responses: { + '200': { description: 'Countries list' }, + }, + }, + }, + '/countries/{id}': { + get: { + summary: 'Get a country by id', + parameters: [{ in: 'path', name: 'id', required: true, schema: { type: 'integer' } }], + responses: { + '200': { description: 'Country details' }, + '404': { description: 'Country not found' }, + }, + }, + }, + '/countries/{countryCode}/regions': { + get: { + summary: 'List country regions', + parameters: [{ in: 'path', name: 'countryCode', required: true, schema: { type: 'integer' } }], + responses: { + '200': { description: 'Country regions' }, + '404': { description: 'Country not found' }, + }, + }, + }, + '/regions': { + get: { + summary: 'List regions', + parameters: [ + { in: 'query', name: 'page', schema: { type: 'integer', minimum: 1 } }, + { in: 'query', name: 'limit', schema: { type: 'integer', minimum: 1, maximum: 100 } }, + { in: 'query', name: 'search', schema: { type: 'string' } }, + { in: 'query', name: 'countryId', schema: { type: 'integer', minimum: 1 } }, + ], + responses: { + '200': { description: 'Regions list' }, + }, + }, + }, + '/regions/{regionCode}': { + get: { + summary: 'Get a region by code', + parameters: [{ in: 'path', name: 'regionCode', required: true, schema: { type: 'integer' } }], + responses: { + '200': { description: 'Region details' }, + '404': { description: 'Region not found' }, + }, + }, + }, + '/regions/{regionCode}/districts': { + get: { + summary: 'List region districts', + parameters: [ + { in: 'path', name: 'regionCode', required: true, schema: { type: 'integer' } }, + { in: 'query', name: 'page', schema: { type: 'integer', minimum: 1 } }, + { in: 'query', name: 'limit', schema: { type: 'integer', minimum: 1, maximum: 100 } }, + ], + responses: { + '200': { description: 'District list for the region' }, + '404': { description: 'Region not found' }, + }, + }, + }, + '/districts': { + get: { + summary: 'List districts', + parameters: [ + { in: 'query', name: 'page', schema: { type: 'integer', minimum: 1 } }, + { in: 'query', name: 'limit', schema: { type: 'integer', minimum: 1, maximum: 100 } }, + { in: 'query', name: 'search', schema: { type: 'string' } }, + { in: 'query', name: 'countryId', schema: { type: 'integer', minimum: 1 } }, + { in: 'query', name: 'regionCode', schema: { type: 'integer', minimum: 1 } }, + ], + responses: { + '200': { description: 'Districts list' }, + }, + }, + }, + '/districts/{districtCode}': { + get: { + summary: 'Get a district by code', + parameters: [{ in: 'path', name: 'districtCode', required: true, schema: { type: 'integer' } }], + responses: { + '200': { description: 'District details' }, + '404': { description: 'District not found' }, + }, + }, + }, + '/districts/{districtCode}/wards': { + get: { + summary: 'List district wards', + parameters: [{ in: 'path', name: 'districtCode', required: true, schema: { type: 'integer' } }], + responses: { + '200': { description: 'Wards for the district' }, + '404': { description: 'District not found' }, + }, + }, + }, + '/wards': { + get: { + summary: 'List wards', + parameters: [ + { in: 'query', name: 'page', schema: { type: 'integer', minimum: 1 } }, + { in: 'query', name: 'limit', schema: { type: 'integer', minimum: 1, maximum: 100 } }, + { in: 'query', name: 'search', schema: { type: 'string' } }, + { in: 'query', name: 'countryId', schema: { type: 'integer', minimum: 1 } }, + { in: 'query', name: 'regionCode', schema: { type: 'integer', minimum: 1 } }, + { in: 'query', name: 'districtCode', schema: { type: 'integer', minimum: 1 } }, + ], + responses: { + '200': { description: 'Wards list' }, + }, + }, + }, + '/wards/{wardCode}': { + get: { + summary: 'Get a ward by code', + parameters: [{ in: 'path', name: 'wardCode', required: true, schema: { type: 'integer' } }], + responses: { + '200': { description: 'Ward details' }, + '404': { description: 'Ward not found' }, + }, + }, + }, + '/wards/{wardCode}/places': { + get: { + summary: 'List ward places', + parameters: [{ in: 'path', name: 'wardCode', required: true, schema: { type: 'integer' } }], + responses: { + '200': { description: 'Places for the ward' }, + '404': { description: 'Ward not found' }, + }, + }, + }, + '/places': { + get: { + summary: 'List places', + parameters: [ + { in: 'query', name: 'page', schema: { type: 'integer', minimum: 1 } }, + { in: 'query', name: 'limit', schema: { type: 'integer', minimum: 1, maximum: 100 } }, + { in: 'query', name: 'search', schema: { type: 'string' } }, + { in: 'query', name: 'countryId', schema: { type: 'integer', minimum: 1 } }, + { in: 'query', name: 'regionCode', schema: { type: 'integer', minimum: 1 } }, + { in: 'query', name: 'districtCode', schema: { type: 'integer', minimum: 1 } }, + { in: 'query', name: 'wardCode', schema: { type: 'integer', minimum: 1 } }, + ], + responses: { + '200': { description: 'Places list' }, + }, + }, + }, + '/places/{id}': { + get: { + summary: 'Get a place by id', + parameters: [{ in: 'path', name: 'id', required: true, schema: { type: 'integer' } }], + responses: { + '200': { description: 'Place details' }, + '404': { description: 'Place not found' }, + }, + }, + }, + '/search': { + get: { + summary: 'Full-text search across the general locations view', + parameters: [{ in: 'query', name: 'q', required: true, schema: { type: 'string', minLength: 2 } }], + responses: { + '200': { description: 'Search results' }, + '400': { description: 'Invalid query', content: { 'application/json': { schema: { $ref: '#/components/schemas/ErrorResponse' } } } }, + }, + }, + }, }, - apis: ['./src/routes.ts'], }; -const swaggerSpec = swaggerJsdoc(options); +export function setupSwagger(app: Express) { + app.get('/openapi.json', (_, res) => { + res.json(openApiSpec); + }); -export const setupSwagger = (app: Express) => { - app.use('/api-docs', swaggerUi.serve, swaggerUi.setup(swaggerSpec)); -}; + app.use('/api-docs', swaggerUi.serve, swaggerUi.setup(openApiSpec)); +} diff --git a/src/middleware/errorHandler.ts b/src/middleware/errorHandler.ts index c349d12..06ac9f4 100644 --- a/src/middleware/errorHandler.ts +++ b/src/middleware/errorHandler.ts @@ -1,6 +1,6 @@ -import { Request, Response } from 'express'; +import type { Request, Response } from 'express'; import { ZodError } from 'zod'; -import { ErrorResponse } from '../types'; +import type { ErrorResponse } from '../types.js'; export class ApiError extends Error { statusCode: number; @@ -15,44 +15,52 @@ export class ApiError extends Error { } export const errorHandler = ( - err: Error | ApiError | ZodError, - _: Request, - res: Response, + err: Error | ApiError | ZodError, + req: Request, + res: Response, ) => { - console.error(`[${new Date().toISOString()}] Error:`, err); - + console.error( + JSON.stringify({ + level: 'error', + message: err.message, + name: err.name, + requestId: req.requestId, + stack: err.stack, + }), + ); + let statusCode = 500; let message = 'Something went wrong'; - + if (err instanceof ApiError) { statusCode = err.statusCode; message = err.message; } - + if (err instanceof ZodError) { statusCode = 400; message = `Validation error: ${err.errors.map(e => e.message).join(', ')}`; } - + if ('code' in err && err.code === 'P2025') { statusCode = 404; message = 'Requested resource not found'; } - + if (err instanceof SyntaxError || err instanceof TypeError) { statusCode = 400; message = 'Invalid request data'; } - + const isProduction = process.env.NODE_ENV === 'production'; - + res.status(statusCode).json({ error: { message, - ...(isProduction ? {} : { + ...(isProduction ? {} : { stack: err.stack, - details: err instanceof ZodError ? err.errors : err - }) - } + details: err instanceof ZodError ? err.errors : err, + }), + }, }); }; diff --git a/src/middleware/requestContext.ts b/src/middleware/requestContext.ts new file mode 100644 index 0000000..2a914d1 --- /dev/null +++ b/src/middleware/requestContext.ts @@ -0,0 +1,24 @@ +import { randomUUID } from 'node:crypto'; +import type { RequestHandler } from 'express'; + +export const attachRequestContext: RequestHandler = (req, res, next) => { + const requestId = req.header('x-request-id')?.trim() || randomUUID(); + + req.requestId = requestId; + res.setHeader('X-Request-Id', requestId); + + next(); +}; + +export const apiCompatibilityHeaders: RequestHandler = (_, res, next) => { + res.setHeader('X-API-Base-Path', 'compatibility'); + res.setHeader('Link', '; rel="canonical"'); + next(); +}; + +export const cacheControl = (value: string): RequestHandler => { + return (_, res, next) => { + res.setHeader('Cache-Control', value); + next(); + }; +}; diff --git a/src/middleware/validation.ts b/src/middleware/validation.ts index dbb0da6..d1fea8d 100644 --- a/src/middleware/validation.ts +++ b/src/middleware/validation.ts @@ -1,4 +1,4 @@ -import { RequestHandler } from 'express'; +import type { RequestHandler } from 'express'; import { ZodSchema, ZodError } from 'zod'; export const validate = (schemas: { diff --git a/src/routes.ts b/src/routes.ts index a99ea4e..f7503e8 100644 --- a/src/routes.ts +++ b/src/routes.ts @@ -1,143 +1,198 @@ import { Router } from 'express'; -import { validate } from './middleware/validation'; -import { prisma } from './app'; -import { ApiError } from './middleware/errorHandler'; -import { +import { Prisma } from './generated/prisma/client.js'; +import { prisma } from './db/prisma.js'; +import { ApiError } from './middleware/errorHandler.js'; +import { cacheControl } from './middleware/requestContext.js'; +import { validate } from './middleware/validation.js'; +import { codeParamSchema, + countryCodeParamSchema, + districtsQuerySchema, idParamSchema, paginationSchema, -} from './types'; + placesQuerySchema, + regionsQuerySchema, + searchQuerySchema, + wardsQuerySchema, +} from './types.js'; const router = Router(); -// ==================== COUNTRIES ROUTES ==================== +const RESOURCE_CACHE = 'public, max-age=300, stale-while-revalidate=60'; +const SEARCH_CACHE = 'public, max-age=60, stale-while-revalidate=30'; + +interface SearchResult { + id: number; + region: string; + district: string; + ward: string; + street: string | null; + places: string | null; + regioncode: number; + districtcode: number; + wardcode: number; +} + +function toPagination(page: number, limit: number, total: number) { + return { + page, + limit, + total, + pages: Math.ceil(total / limit), + }; +} + +function contains(search?: string) { + if (!search) { + return undefined; + } + + return { + contains: search, + mode: 'insensitive' as const, + }; +} -/** - * @route GET /api/countries - * @description Get all countries with optional pagination and search - */ -router.get('/countries', validate({ query: paginationSchema }), async (req, res, next) => { +router.get('/countries', cacheControl(RESOURCE_CACHE), validate({ query: paginationSchema }), async (req, res, next) => { try { - const { page, limit, search } = req.validatedQuery; + const { limit, page, search } = req.validatedQuery; const skip = (page - 1) * limit; - const whereClause = search ? { name: { contains: search } } : {}; - - const total = await prisma.countries.count({ where: whereClause }); - - const countries = await prisma.countries.findMany({ - skip, - take: limit, - where: whereClause, - select: { - id: true, - name: true, - iso: true, - nicename: true, - phonecode: true, - numcode: true, - }, - orderBy: { name: 'asc' } - }); - + const where = search + ? { + OR: [{ name: contains(search) }, { nicename: contains(search) }, { iso: contains(search) }], + } + : {}; + + const [total, countries] = await Promise.all([ + prisma.countries.count({ where }), + prisma.countries.findMany({ + skip, + take: limit, + where, + select: { + id: true, + iso: true, + name: true, + nicename: true, + phonecode: true, + numcode: true, + }, + orderBy: { name: 'asc' }, + }), + ]); + res.json({ data: countries, - pagination: { - page, - limit, - total, - pages: Math.ceil(total / limit) - } + pagination: toPagination(page, limit, total), }); } catch (error) { next(error); } }); -/** - * @route GET /api/countries/:id - * @description Get country by ID - */ -router.get('/countries/:id', validate({ params: idParamSchema }), async (req, res, next) => { +router.get('/countries/:id', cacheControl(RESOURCE_CACHE), validate({ params: idParamSchema }), async (req, res, next) => { try { - const countryId = req.params.id; - + const { id } = req.validatedParams; + const country = await prisma.countries.findUnique({ - where: { id: +countryId }, + where: { id }, select: { id: true, - name: true, iso: true, + name: true, nicename: true, phonecode: true, - numcode: true - } + numcode: true, + }, }); - + if (!country) { throw new ApiError(404, 'Country not found'); } - + res.json({ data: country }); } catch (error) { next(error); } }); -// ==================== REGIONS ROUTES ==================== +router.get( + '/countries/:countryCode/regions', + cacheControl(RESOURCE_CACHE), + validate({ params: countryCodeParamSchema }), + async (req, res, next) => { + try { + const { countryCode } = req.validatedParams; + + const country = await prisma.countries.findUnique({ + where: { id: countryCode }, + select: { id: true }, + }); + + if (!country) { + throw new ApiError(404, 'Country not found'); + } + + const regions = await prisma.regions.findMany({ + where: { countryId: countryCode }, + select: { + regionCode: true, + regionName: true, + countryId: true, + }, + orderBy: { regionName: 'asc' }, + }); + + res.json({ data: regions }); + } catch (error) { + next(error); + } + }, +); -/** - * @route GET /api/regions - * @description Get all regions with pagination and optional filtering - */ -router.get('/regions', validate({ query: paginationSchema }), async (req, res, next) => { +router.get('/regions', cacheControl(RESOURCE_CACHE), validate({ query: regionsQuerySchema }), async (req, res, next) => { try { - const { page, limit, search } = req.validatedQuery; + const { countryId, limit, page, search } = req.validatedQuery; const skip = (page - 1) * limit; - - // Handle search query if present - const whereClause = search ? { regionName: { contains: search } } : {}; - - const total = await prisma.regions.count({ where: whereClause }); - - const regions = await prisma.regions.findMany({ - skip, - take: limit, - where: whereClause, - select: { - regionCode: true, - regionName: true, - countryId: true, - countries: { - select: { - name: true - } - } - }, - orderBy: { regionName: 'asc' } - }); - + const where = { + ...(countryId ? { countryId } : {}), + ...(search ? { regionName: contains(search) } : {}), + }; + + const [total, regions] = await Promise.all([ + prisma.regions.count({ where }), + prisma.regions.findMany({ + skip, + take: limit, + where, + select: { + regionCode: true, + regionName: true, + countryId: true, + countries: { + select: { + iso: true, + name: true, + }, + }, + }, + orderBy: { regionName: 'asc' }, + }), + ]); + res.json({ data: regions, - pagination: { - page, - limit, - total, - pages: Math.ceil(total / limit) - } + pagination: toPagination(page, limit, total), }); } catch (error) { next(error); } }); -/** - * @route GET /api/regions/:regionCode - * @description Get a specific region by code - */ -router.get('/regions/:regionCode', validate({ params: codeParamSchema.pick({ regionCode: true }) }), async (req, res, next) => { +router.get('/regions/:regionCode', cacheControl(RESOURCE_CACHE), validate({ params: codeParamSchema.pick({ regionCode: true }) }), async (req, res, next) => { try { - const regionCode = +req.params.regionCode; - + const { regionCode } = req.validatedParams; + const region = await prisma.regions.findUnique({ where: { regionCode }, select: { @@ -146,135 +201,117 @@ router.get('/regions/:regionCode', validate({ params: codeParamSchema.pick({ reg countryId: true, countries: { select: { + iso: true, name: true, - nicename: true - } - } - } + nicename: true, + }, + }, + }, }); - - if (!region || Object.keys(region).length === 0) { + + if (!region) { throw new ApiError(404, 'Region not found'); } - + res.json({ data: region }); } catch (error) { next(error); } }); -/** - * @route GET /api/regions/:regionCode/districts - * @description Get all districts in a region - */ -router.get('/regions/:regionCode/districts', - validate({ params: codeParamSchema.pick({ regionCode: true }) }), - validate({ query: paginationSchema }), +router.get( + '/regions/:regionCode/districts', + cacheControl(RESOURCE_CACHE), + validate({ + params: codeParamSchema.pick({ regionCode: true }), + query: paginationSchema, + }), async (req, res, next) => { try { - const regionCode = +req.params.regionCode; - const { page, limit } = req.validatedQuery; + const { regionCode } = req.validatedParams; + const { limit, page } = req.validatedQuery; const skip = (page - 1) * limit; - - // Verify region exists - const regionExists = await prisma.regions.findUnique({ + + const region = await prisma.regions.findUnique({ where: { regionCode }, - select: { regionCode: true } + select: { regionCode: true }, }); - - if (!regionExists) { + + if (!region) { throw new ApiError(404, 'Region not found'); } - - // Get districts count - const total = await prisma.districts.count({ - where: { regionId: regionCode } - }); - - // Get districts - const districts = await prisma.districts.findMany({ - where: { regionId: regionCode }, - skip, - take: limit, - select: { - districtCode: true, - districtName: true, - regionId: true - }, - orderBy: { districtName: 'asc' } - }); - + + const [total, districts] = await Promise.all([ + prisma.districts.count({ where: { regionId: regionCode } }), + prisma.districts.findMany({ + where: { regionId: regionCode }, + skip, + take: limit, + select: { + districtCode: true, + districtName: true, + regionId: true, + country_id: true, + }, + orderBy: { districtName: 'asc' }, + }), + ]); + res.json({ data: districts, - pagination: { - page, - limit, - total, - pages: Math.ceil(total / limit) - } + pagination: toPagination(page, limit, total), }); } catch (error) { next(error); } - } + }, ); -// ==================== DISTRICTS ROUTES ==================== - -/** - * @route GET /api/districts - * @description Get all districts with pagination and optional search - */ -router.get('/districts', validate({ query: paginationSchema }), async (req, res, next) => { +router.get('/districts', cacheControl(RESOURCE_CACHE), validate({ query: districtsQuerySchema }), async (req, res, next) => { try { - const { page, limit, search } = req.validatedQuery; + const { countryId, limit, page, regionCode, search } = req.validatedQuery; const skip = (page - 1) * limit; - - // Handle search if present - const whereClause = search ? { districtName: { contains: search } } : {}; - - const total = await prisma.districts.count({ where: whereClause }); - - const districts = await prisma.districts.findMany({ - skip, - take: limit, - where: whereClause, - select: { - districtCode: true, - districtName: true, - regionId: true, - country_id: true, - regions: { - select: { - regionName: true - } - } - }, - orderBy: { districtName: 'asc' } - }); - + const where = { + ...(countryId ? { country_id: countryId } : {}), + ...(regionCode ? { regionId: regionCode } : {}), + ...(search ? { districtName: contains(search) } : {}), + }; + + const [total, districts] = await Promise.all([ + prisma.districts.count({ where }), + prisma.districts.findMany({ + skip, + take: limit, + where, + select: { + districtCode: true, + districtName: true, + regionId: true, + country_id: true, + regions: { + select: { + regionCode: true, + regionName: true, + }, + }, + }, + orderBy: { districtName: 'asc' }, + }), + ]); + res.json({ data: districts, - pagination: { - page, - limit, - total, - pages: Math.ceil(total / limit) - } + pagination: toPagination(page, limit, total), }); } catch (error) { next(error); } }); -/** - * @route GET /api/districts/:districtCode - * @description Get district by code - */ -router.get('/districts/:districtCode', validate({ params: codeParamSchema.pick({ districtCode: true }) }), async (req, res, next) => { +router.get('/districts/:districtCode', cacheControl(RESOURCE_CACHE), validate({ params: codeParamSchema.pick({ districtCode: true }) }), async (req, res, next) => { try { - const districtCode = +req.params.districtCode; - + const { districtCode } = req.validatedParams; + const district = await prisma.districts.findUnique({ where: { districtCode }, select: { @@ -284,84 +321,112 @@ router.get('/districts/:districtCode', validate({ params: codeParamSchema.pick({ country_id: true, regions: { select: { - regionName: true - } + regionCode: true, + regionName: true, + }, }, countries: { select: { - name: true - } - } - } + iso: true, + name: true, + }, + }, + }, }); - + if (!district) { throw new ApiError(404, 'District not found'); } - + res.json({ data: district }); } catch (error) { next(error); } }); -// ==================== WARDS ROUTES ==================== +router.get( + '/districts/:districtCode/wards', + cacheControl(RESOURCE_CACHE), + validate({ params: codeParamSchema.pick({ districtCode: true }) }), + async (req, res, next) => { + try { + const { districtCode } = req.validatedParams; + + const district = await prisma.districts.findUnique({ + where: { districtCode }, + select: { districtCode: true }, + }); + + if (!district) { + throw new ApiError(404, 'District not found'); + } + + const wards = await prisma.wards.findMany({ + where: { districtId: districtCode }, + select: { + wardCode: true, + wardName: true, + districtId: true, + region_id: true, + country_id: true, + }, + orderBy: { wardName: 'asc' }, + }); + + res.json({ data: wards }); + } catch (error) { + next(error); + } + }, +); -/** - * @route GET /api/wards - * @description Get all wards with pagination and search - */ -router.get('/wards', validate({ query: paginationSchema }), async (req, res, next) => { +router.get('/wards', cacheControl(RESOURCE_CACHE), validate({ query: wardsQuerySchema }), async (req, res, next) => { try { - const { page, limit, search } = req.validatedQuery; + const { countryId, districtCode, limit, page, regionCode, search } = req.validatedQuery; const skip = (page - 1) * limit; - - // Handle search if present - const whereClause = search ? { wardName: { contains: search } } : {}; - - const total = await prisma.wards.count({ where: whereClause }); - - const wards = await prisma.wards.findMany({ - skip, - take: limit, - where: whereClause, - select: { - wardCode: true, - wardName: true, - districtId: true, - region_id: true, - country_id: true, - districts: { - select: { - districtName: true - } - } - }, - orderBy: { wardName: 'asc' } - }); - + const where = { + ...(countryId ? { country_id: countryId } : {}), + ...(districtCode ? { districtId: districtCode } : {}), + ...(regionCode ? { region_id: regionCode } : {}), + ...(search ? { wardName: contains(search) } : {}), + }; + + const [total, wards] = await Promise.all([ + prisma.wards.count({ where }), + prisma.wards.findMany({ + skip, + take: limit, + where, + select: { + wardCode: true, + wardName: true, + districtId: true, + region_id: true, + country_id: true, + districts: { + select: { + districtCode: true, + districtName: true, + }, + }, + }, + orderBy: { wardName: 'asc' }, + }), + ]); + res.json({ data: wards, - pagination: { - page, - limit, - total, - pages: Math.ceil(total / limit) - } + pagination: toPagination(page, limit, total), }); } catch (error) { next(error); } }); -/** - * @route GET /api/wards/:wardCode - * @description Get ward by code - */ -router.get('/wards/:wardCode', validate({ params: codeParamSchema.pick({ wardCode: true }) }), async (req, res, next) => { +router.get('/wards/:wardCode', cacheControl(RESOURCE_CACHE), validate({ params: codeParamSchema.pick({ wardCode: true }) }), async (req, res, next) => { try { - const wardCode = +req.params.wardCode; - + const { wardCode } = req.validatedParams; + const ward = await prisma.wards.findUnique({ where: { wardCode }, select: { @@ -372,222 +437,160 @@ router.get('/wards/:wardCode', validate({ params: codeParamSchema.pick({ wardCod country_id: true, districts: { select: { - districtName: true - } + districtCode: true, + districtName: true, + }, }, regions: { select: { - regionName: true - } + regionCode: true, + regionName: true, + }, }, countries: { select: { - name: true - } - } - } + iso: true, + name: true, + }, + }, + }, }); - + if (!ward) { throw new ApiError(404, 'Ward not found'); } - + res.json({ data: ward }); } catch (error) { next(error); } }); -// ==================== PLACES ROUTES ==================== +router.get( + '/wards/:wardCode/places', + cacheControl(RESOURCE_CACHE), + validate({ params: codeParamSchema.pick({ wardCode: true }) }), + async (req, res, next) => { + try { + const { wardCode } = req.validatedParams; + + const ward = await prisma.wards.findUnique({ + where: { wardCode }, + select: { wardCode: true }, + }); + + if (!ward) { + throw new ApiError(404, 'Ward not found'); + } + + const places = await prisma.places.findMany({ + where: { wardId: wardCode }, + select: { + id: true, + placeName: true, + wardId: true, + district_id: true, + region_id: true, + country_id: true, + }, + orderBy: { placeName: 'asc' }, + }); + + res.json({ data: places }); + } catch (error) { + next(error); + } + }, +); -/** - * @route GET /api/places - * @description Get all places with pagination and search - */ -router.get('/places', validate({ query: paginationSchema }), async (req, res, next) => { +router.get('/places', cacheControl(RESOURCE_CACHE), validate({ query: placesQuerySchema }), async (req, res, next) => { try { - const { page, limit, search } = req.validatedQuery; + const { countryId, districtCode, limit, page, regionCode, search, wardCode } = req.validatedQuery; const skip = (page - 1) * limit; - - // Handle search if present - const whereClause = search ? { placeName: { contains: search } } : {}; - - const total = await prisma.places.count({ where: whereClause }); - - const places = await prisma.places.findMany({ - skip, - take: limit, - where: whereClause, - select: { - id: true, - placeName: true, - wardId: true, - district_id: true, - region_id: true, - country_id: true - }, - orderBy: { placeName: 'asc' } - }); - + const where = { + ...(countryId ? { country_id: countryId } : {}), + ...(districtCode ? { district_id: districtCode } : {}), + ...(regionCode ? { region_id: regionCode } : {}), + ...(wardCode ? { wardId: wardCode } : {}), + ...(search ? { placeName: contains(search) } : {}), + }; + + const [total, places] = await Promise.all([ + prisma.places.count({ where }), + prisma.places.findMany({ + skip, + take: limit, + where, + select: { + id: true, + placeName: true, + wardId: true, + district_id: true, + region_id: true, + country_id: true, + }, + orderBy: { placeName: 'asc' }, + }), + ]); + res.json({ data: places, - pagination: { - page, - limit, - total, - pages: Math.ceil(total / limit) - } + pagination: toPagination(page, limit, total), }); } catch (error) { next(error); } }); -/** - * @route GET /api/places/:id - * @description Get place by ID - */ -router.get('/places/:id', validate({ params: idParamSchema }), async (req, res, next) => { +router.get('/places/:id', cacheControl(RESOURCE_CACHE), validate({ params: idParamSchema }), async (req, res, next) => { try { - const placeId = +req.params.id; - + const { id } = req.validatedParams; + const place = await prisma.places.findUnique({ - where: { id: placeId }, + where: { id }, select: { id: true, placeName: true, wardId: true, district_id: true, region_id: true, - country_id: true - } + country_id: true, + }, }); - + if (!place) { throw new ApiError(404, 'Place not found'); } - - res.json({ data: place }); - } catch (error) { - next(error); - } -}); - -// ==================== NESTED ROUTES ==================== - -/** - * @route GET /api/countries/:countryCode/regions - * @description Get all regions in a given country - */ -router.get('/countries/:countryCode/regions', async (req, res, next) => { - try { - const countryCode = Number(req.params.countryCode); - const regions = await prisma.regions.findMany({ - where: { countryId: countryCode }, - orderBy: { regionName: 'asc' } - }); - - if(!regions || regions.length === 0) { - throw new ApiError(404, 'No regions found for this country'); - } - res.json({ data: regions }); - } catch (error) { - next(error); - } -}); - -/** - * @route GET /api/regions/:regionCode/districts - * @description Get all districts in a given region - */ -router.get('/regions/:regionCode/districts', async (req, res, next) => { - try { - const regionCode = Number(req.params.regionCode); - const districts = await prisma.districts.findMany({ - where: { regionId: regionCode }, - orderBy: { districtName: 'asc' } - }); - - if(!districts || districts.length === 0) { - throw new ApiError(404, 'No districts found for this region'); - } - res.json({ data: districts }); - } catch (error) { - next(error); - } -}); - -/** - * @route GET /api/districts/:districtCode/wards - * @description Get all wards in a given district - */ -router.get('/districts/:districtCode/wards', async (req, res, next) => { - try { - const districtCode = Number(req.params.districtCode); - const wards = await prisma.wards.findMany({ - where: { districtId: districtCode }, - orderBy: { wardName: 'asc' } - }); - if(!wards || wards.length === 0) { - throw new ApiError(404, 'No wards found for this district'); - } - res.json({ data: wards }); + res.json({ data: place }); } catch (error) { next(error); } }); - -/** - * @route GET /api/wards/:wardCode/places - * @description Get all places in a given ward - */ -router.get('/wards/:wardCode/places', async (req, res, next) => { +router.get('/search', cacheControl(SEARCH_CACHE), validate({ query: searchQuerySchema }), async (req, res, next) => { try { - const wardCode = Number(req.params.wardCode); - const places = await prisma.places.findMany({ - where: { wardId: wardCode }, - orderBy: { placeName: 'asc' } - }); + const { q } = req.validatedQuery; + + // Strip null bytes that could bypass text processing + const sanitised = String(q).replace(/\0/g, ''); + + const results = await prisma.$queryRaw(Prisma.sql` + WITH query AS ( + SELECT plainto_tsquery('simple', ${sanitised}::text) AS tsq + ) + SELECT g.id, g.region, g.district, g.ward, g.street, g.places, + g.regioncode, g.districtcode, g.wardcode + FROM "general" g, query + WHERE g."search_vector" @@ query.tsq + ORDER BY ts_rank(g."search_vector", query.tsq) DESC + LIMIT 15 + `); - if(!places || places.length === 0) { - throw new ApiError(404, 'No places found for this ward'); - } - res.json({ data: places }); + res.json({ data: results }); } catch (error) { next(error); } }); -/** - * @route GET /api/search - * @description Search for places, wards, districts, regions, or countries - */ - -router.get('/search', async (req, res: any, next) => { - try { - const q = req.query.q?.toString().trim(); - if (!q || q.length < 2) { - return res.status(400).json({ error: 'Query too short' }); - } - - const escapedQuery = q.replace(/'/g, "''"); //- Escapes single quotes - const sql = ` - SELECT id, region, district, ward, street, places, regioncode, districtcode, wardcode - FROM "general" - WHERE "search_vector" @@ plainto_tsquery('simple', $1) - ORDER BY ts_rank("search_vector", plainto_tsquery('simple', $1)) DESC - LIMIT 15; - `; - - const results = await prisma.$queryRawUnsafe(sql, escapedQuery); - - res.json({ data: results }); - } catch (err) { - next(err); - } -}); - - export default router; diff --git a/src/types.ts b/src/types.ts index 9041026..9661c56 100644 --- a/src/types.ts +++ b/src/types.ts @@ -1,19 +1,52 @@ -import { z, ZodIssue } from 'zod'; +import { z } from 'zod'; +import type { ZodIssue } from 'zod'; + +const positiveInt = z.coerce.number().int().positive(); export const paginationSchema = z.object({ - page: z.coerce.number().int().positive().optional().default(1), - limit: z.coerce.number().int().positive().max(100).optional().default(10), - search: z.string().optional(), + page: positiveInt.optional().default(1), + limit: positiveInt.max(100).optional().default(10), + search: z.string().trim().min(1).optional(), }); export const idParamSchema = z.object({ - id: z.coerce.number().int().positive(), + id: positiveInt, }); export const codeParamSchema = z.object({ - regionCode: z.coerce.number().int().positive(), - districtCode: z.coerce.number().int().positive(), - wardCode: z.coerce.number().int().positive(), + regionCode: positiveInt, + districtCode: positiveInt, + wardCode: positiveInt, +}); + +export const countryCodeParamSchema = z.object({ + countryCode: positiveInt, +}); + +export const regionsQuerySchema = paginationSchema.extend({ + countryId: positiveInt.optional(), +}); + +export const districtsQuerySchema = paginationSchema.extend({ + countryId: positiveInt.optional(), + regionCode: positiveInt.optional(), +}); + +export const wardsQuerySchema = paginationSchema.extend({ + countryId: positiveInt.optional(), + districtCode: positiveInt.optional(), + regionCode: positiveInt.optional(), +}); + +export const placesQuerySchema = paginationSchema.extend({ + countryId: positiveInt.optional(), + districtCode: positiveInt.optional(), + regionCode: positiveInt.optional(), + wardCode: positiveInt.optional(), +}); + +export const searchQuerySchema = z.object({ + q: z.string().trim().min(2, 'Query must be at least 2 characters long'), }); export type IdParam = z.infer; @@ -38,7 +71,7 @@ export interface ErrorResponse { error: { message: string; stack?: string; - details?: any; + details?: unknown; validationErrors?: ZodIssue[]; }; } @@ -50,6 +83,7 @@ declare global { ReqBody = any, ReqQuery = any > { + requestId?: string; validatedQuery?: ReqQuery; validatedParams?: Params; validatedBody?: ReqBody; diff --git a/tests/locations-api.test.ts b/tests/locations-api.test.ts index 9f01531..36554d7 100644 --- a/tests/locations-api.test.ts +++ b/tests/locations-api.test.ts @@ -1,90 +1,127 @@ import request from 'supertest'; -import app from '../src/app'; - -describe('Tanzania Locations API', () => { - // Countries - describe('GET /api/countries', () => { - it('should return a list of countries', async () => { - const res = await request(app).get('/api/countries'); - expect(res.statusCode).toEqual(200); - expect(res.body).toHaveProperty('data'); - expect(Array.isArray(res.body.data)).toBeTruthy(); - }); +import app from '../src/app.js'; +import { disconnectPrisma, prisma } from '../src/db/prisma.js'; - it('should support pagination', async () => { - const res = await request(app).get('/api/countries?page=1&limit=5'); - expect(res.statusCode).toEqual(200); - expect(res.body.pagination).toMatchObject({ page: 1, limit: 5 }); - expect(res.body.data.length).toBeLessThanOrEqual(5); - }); +afterAll(async () => { + await disconnectPrisma(); +}); + +describe.each(['/v1', '/api'])('Tanzania Locations API (%s)', (basePath) => { + it('lists countries with pagination metadata', async () => { + const res = await request(app).get(`${basePath}/countries?page=1&limit=1`); + + expect(res.statusCode).toBe(200); + expect(res.body.pagination).toMatchObject({ page: 1, limit: 1, total: 2, pages: 2 }); + expect(res.body.data).toHaveLength(1); }); - // Regions - describe('GET /api/regions/:code', () => { - it('should return a specific region', async () => { - const res = await request(app).get('/api/regions/41'); - expect(res.statusCode).toBe(200); - expect(res.body.data).toHaveProperty('regionCode', 41); - }); + it('gets a specific region', async () => { + const res = await request(app).get(`${basePath}/regions/12`); - it('should return 404 for non-existent region', async () => { - const res = await request(app).get('/api/regions/40'); - expect(res.statusCode).toBe(404); + expect(res.statusCode).toBe(200); + expect(res.body.data).toMatchObject({ + regionCode: 12, + regionName: 'Dodoma', + countryId: 1, }); + }); - it('should return 400 for invalid region code', async () => { - const res = await request(app).get('/api/regions/invalid'); - expect(res.statusCode).toBe(400); - expect(res.body).toHaveProperty('error'); + it('supports collection filters without changing response envelopes', async () => { + const res = await request(app).get(`${basePath}/places?countryId=1®ionCode=12&districtCode=1201&wardCode=120101`); + + expect(res.statusCode).toBe(200); + expect(res.body.pagination.total).toBe(1); + expect(res.body.data[0]).toMatchObject({ + id: 2, + placeName: 'Nzuguni Center', }); }); - // Regions β†’ Districts - describe('GET /api/regions/:code/districts', () => { - it('should return districts of a specific region', async () => { - const res = await request(app).get('/api/regions/41/districts'); - expect(res.statusCode).toBe(200); - expect(Array.isArray(res.body.data)).toBeTruthy(); - }); + it('returns region districts with pagination', async () => { + const res = await request(app).get(`${basePath}/regions/12/districts?page=1&limit=10`); - it('should return 404 for region that doesn’t exist', async () => { - const res = await request(app).get('/api/regions/40/districts'); - expect(res.statusCode).toBe(404); + expect(res.statusCode).toBe(200); + expect(res.body.pagination).toMatchObject({ page: 1, limit: 10, total: 1, pages: 1 }); + expect(res.body.data[0]).toMatchObject({ + districtCode: 1201, + districtName: 'Dodoma Urban', }); }); - // Districts β†’ Wards - describe('GET /api/districts/:code/wards', () => { - it('should return wards for a district', async () => { - const res = await request(app).get('/api/districts/411/wards'); - expect(res.statusCode).toBe(200); - expect(Array.isArray(res.body.data)).toBe(true); - }); + it('returns district wards', async () => { + const res = await request(app).get(`${basePath}/districts/1201/wards`); - it('should return 404 if district not found', async () => { - const res = await request(app).get('/api/districts/00000/wards'); - expect(res.statusCode).toBe(404); - }); + expect(res.statusCode).toBe(200); + expect(res.body.data).toEqual([ + expect.objectContaining({ + wardCode: 120101, + wardName: 'Nzuguni', + }), + ]); }); - // Ward Details - describe('GET /api/wards/:code', () => { - it('should return a specific ward', async () => { - const res = await request(app).get('/api/wards/40000'); - if (res.statusCode === 200) { - expect(res.body.data).toHaveProperty('wardCode', 41115); - } else { - expect([404, 400]).toContain(res.statusCode); - } - }); + it('returns ward places', async () => { + const res = await request(app).get(`${basePath}/wards/120101/places`); + + expect(res.statusCode).toBe(200); + expect(res.body.data).toEqual([ + expect.objectContaining({ + id: 2, + placeName: 'Nzuguni Center', + }), + ]); }); - // 404 NOT FOUND - describe('Fallback Route', () => { - it('should return 404 for unknown route', async () => { - const res = await request(app).get('/api/unknown/route'); - expect(res.statusCode).toBe(404); - expect(res.body).toHaveProperty('error'); + it('returns 400 for invalid region code', async () => { + const res = await request(app).get(`${basePath}/regions/invalid`); + + expect(res.statusCode).toBe(400); + expect(res.body.error).toBeDefined(); + }); +}); + +describe('Shared API behavior', () => { + it('keeps the /api alias active', async () => { + const res = await request(app).get('/api/countries'); + + expect(res.statusCode).toBe(200); + expect(res.headers['x-api-base-path']).toBe('compatibility'); + }); + + it('searches safely when the query contains quotes', async () => { + const res = await request(app).get('/v1/search').query({ q: "nzuguni's" }); + + expect(res.statusCode).toBe(200); + expect(Array.isArray(res.body.data)).toBe(true); + }); + + it('returns seeded matches for a normal search query', async () => { + const res = await request(app).get('/v1/search').query({ q: 'nzuguni' }); + + expect(res.statusCode).toBe(200); + expect(res.body.data[0]).toMatchObject({ + ward: 'Nzuguni', + places: 'Nzuguni Center', + regioncode: 12, }); }); + + it('returns 404 for an unknown route', async () => { + const res = await request(app).get('/v1/unknown/route'); + + expect(res.statusCode).toBe(404); + expect(res.body.error).toBeDefined(); + }); + + it('reuses the Prisma singleton', async () => { + const imported = await import('../src/db/prisma.js'); + + expect(imported.prisma).toBe(prisma); + }); + + it('recreates the Prisma client after disconnect', async () => { + await disconnectPrisma(); + + await expect(prisma.countries.count()).resolves.toBe(2); + }); }); diff --git a/tsconfig.build.json b/tsconfig.build.json new file mode 100644 index 0000000..abdc4dc --- /dev/null +++ b/tsconfig.build.json @@ -0,0 +1,9 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "noEmit": false, + "outDir": "./dist" + }, + "include": ["server.ts", "src/**/*.ts"], + "exclude": ["dist", "node_modules", "tests", "scripts", "prisma", "src/generated"] +} diff --git a/tsconfig.json b/tsconfig.json index fcb8c8d..2be5142 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,18 +1,22 @@ { "compilerOptions": { - "target": "es2016", - "lib": ["es2020", "dom"], - "module": "commonjs", - "moduleResolution": "node", - "outDir": "./dist", + "target": "ES2022", + "lib": ["ES2023"], + "module": "NodeNext", + "moduleResolution": "NodeNext", "sourceMap": true, "esModuleInterop": true, "allowSyntheticDefaultImports": true, "forceConsistentCasingInFileNames": true, "strict": true, "skipLibCheck": true, - "useDefineForClassFields": true + "useDefineForClassFields": true, + "types": ["node", "jest"], + "rootDir": ".", + "resolveJsonModule": true, + "verbatimModuleSyntax": true, + "noEmit": true }, - "include": ["src/**/*.ts", "server.ts"], - "exclude": ["node_modules"] + "include": ["server.ts", "src/**/*.ts", "tests/**/*.ts", "scripts/**/*.ts", "prisma/**/*.ts"], + "exclude": ["dist", "node_modules", "src/generated"] } diff --git a/tsconfig.test.json b/tsconfig.test.json new file mode 100644 index 0000000..9c8b996 --- /dev/null +++ b/tsconfig.test.json @@ -0,0 +1,7 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "isolatedModules": true, + "verbatimModuleSyntax": false + } +}