diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 4d501a7..a7b022f 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -38,8 +38,8 @@ jobs: - name: Build frontend run: npm run build env: - VITE_APP_URL: http://localhost:2000 - VITE_APP_front_end_url: http://localhost:5173 + REACT_APP_URL: http://localhost:2000 + REACT_APP_front_end_url: http://localhost:5173 backend: name: Backend dependency check @@ -82,5 +82,5 @@ jobs: env: MONGO_URI: mongodb://127.0.0.1:27017/piperchat_ci_auth ACCESS_TOKEN: ci-test-jwt-secret-do-not-use-in-production-32chars-min - DEFAULT_PROFILE_PIC: https://example.com/default.png + default_profile_pic: https://example.com/default.png run: npm run test:auth diff --git a/README.md b/README.md index 5f8233b..604460b 100644 --- a/README.md +++ b/README.md @@ -29,191 +29,251 @@ PiperChat is a Discord-style chat app with: - Email OTP verification - Profile updates (display name + avatar) with Supabase storage - Optional Redis caching (Upstash supported) -- Structured logging with Winston and optional Logtail integration - -## Project structure - -```text -PiperChat01/ - frontend/ - src/ - package.json - .env.example - - server/ - src/ - config/ - lib/ - middleware/ - models/ - routes/ - services/ - socket/ - scripts/ - package.json - .env.example -``` + +--- + +# Project Structure - `server/` → Express + MongoDB + Socket.IO API (ESM) - `frontend/` → Vite + Tailwind UI -## System Architecture +--- -To help contributors understand the data flow, here is the technical visualization of how PiperChat components interact: +# System Architecture ```mermaid graph TD - %% User and Frontend User((User)) -->|Interacts| UI[Vite + React Frontend] - - %% Connection Layer + UI <-->|Real-time Events| Socket[Socket.io Layer] UI -->|HTTP Requests| API[Express API] - %% Backend Logic subgraph "Server Logic (Node.js)" API Socket Auth[JWT Authentication] Email[Gmail OAuth2 Email Service] - Logger[Winston + Logtail] end - %% Database Layer Socket <-->|Caching/Presence| Redis[(Redis)] API -->|Chat History & Users| Mongo[(MongoDB)] Email -->|Send OTP via Gmail| GoogleOAuth[(Google OAuth2 API)] - %% Styling style UI fill:#9b5de5,stroke:#333,stroke-width:2px,color:#fff style Redis fill:#FF4438,color:#fff style Mongo fill:#47A248,color:#fff style GoogleOAuth fill:#4285F4,stroke:#333,stroke-width:2px,color:#fff ``` -## Quick start +--- + +# Quick Start -### 1) Install dependencies +## 1) Install Dependencies ```bash cd server && npm install cd ../frontend && npm install ``` -### 2) Environment variables +--- -- Copy `server/.env.example` → `server/.env` -- Copy `frontend/.env.example` → `frontend/.env` +## 2) Environment Variables -### 3) Run the apps +- Copy `PiperChat01/.env.example` → `PiperChat01/.env` +- Copy `PiperChat01/frontend/.env.example` → `PiperChat01/frontend/.env` + +--- + +## 3) Run the Applications + +### Backend ```bash -cd server && npm run dev +cd server +npm start ``` +### Frontend + ```bash -cd frontend && npm run dev +cd frontend +npm run dev ``` -Frontend runs on `http://localhost:5173` -Server runs on `http://localhost:2000` +Frontend runs on: + +```txt +http://localhost:5173 +``` -API base URL: +Backend runs on: -```text -http://localhost:2000/api/v1 +```txt +http://localhost:2000 ``` -## Environment variables +--- + +# Environment Variables -### Server (`server/.env`) +## Server (`PiperChat01/.env`) -| Key | Required | Notes | +| Key | Required | Notes | | ---------------------------------------------------------------- | -------: | -------------------------------------- | -| `MONGO_URI` | ✅ | MongoDB connection string | -| `ACCESS_TOKEN` | ✅ | JWT secret | -| `PORT` | ❌ | Default `2000` | -| `NODE_ENV` | ❌ | `development` or `production` | -| `DEFAULT_PROFILE_PIC` | ❌ | Used on signup | -| `FRONTEND_ORIGINS` | ❌ | Comma-separated CORS whitelist | -| `MAIL_TRANSPORT` | ❌ | `auto`, `console`, `gmail_api`, `password`, or `smtp` | -| `MAIL_USER` | ❌ | Sender email address | -| `MAIL_PASS` | ❌ | Gmail App Password | -| `OAUTH_CLIENT_ID` / `OAUTH_CLIENT_SECRET` / `OAUTH_REFRESH_TOKEN` | ❌ | OAuth2 email sending | -| `SMTP_HOST` / `SMTP_PORT` / `SMTP_USER` / `SMTP_PASS` | ❌ | SMTP configuration | -| `REDIS_URL` | ❌ | Upstash URL supported (`rediss://...`) | -| `REDIS_CACHE_TTL_SECONDS` | ❌ | Default `30` | -| `UPSTASH_REDIS_URL` / `UPSTASH_REDIS_TLS_URL` | ❌ | Upstash Redis aliases | -| `OTP_TTL_MS` | ❌ | OTP expiry duration | -| `LOGTAIL_SOURCE_TOKEN` | ⚠️ | Required in production for Logtail logging | -| `LOGTAIL_INGESTING_HOST` | ⚠️ | Required in production for Logtail logging | -| `DICEBEAR_API` | ❌ | DiceBear avatar API URL | -| `DICEBEAR_STYLE` | ❌ | DiceBear avatar style | -| `SMTP_SECURE` | ❌ | Enables secure SMTP connection | -| `REDIS_HOST` | ❌ | Redis host fallback | -| `REDIS_PORT` | ❌ | Redis port fallback | -| `RATE_LIMIT_WINDOW_MS` | ❌ | Express rate-limit time window | - -### Frontend (`frontend/.env`) - -| Key | Required | Notes | +| `MONGO_URI` | ✅ | MongoDB connection string | +| `ACCESS_TOKEN` | ✅ | JWT secret | +| `PORT` | ❌ | Default `2000` | +| `default_profile_pic` | ✅ | Used on signup | +| `MAIL_USER` / `MAIL_PASS` | ✅ | Gmail App Password flow | +| `OAUTH_CLIENTID` / `OAUTH_CLIENT_SECRET` / `OAUTH_REFRESH_TOKEN` | ❌ | Optional OAuth2 email sending | +| `REDIS_URL` | ❌ | Upstash URL supported (`rediss://...`) | +| `REDIS_CACHE_TTL_SECONDS` | ❌ | Default `30` | + +--- + +## Frontend (`PiperChat01/frontend/.env`) + +| Key | Required | Notes | | ----------------------------- | -------: | -------------------------------------- | -| `VITE_URL` | ✅ | Backend URL (`http://localhost:2000`) | -| `VITE_FRONT_END_URL` | ✅ | Frontend URL (`http://localhost:5173`) | -| `VITE_SUPABASE_URL` | ❌ | For avatar uploads | -| `VITE_SUPABASE_ANON_KEY` | ❌ | For avatar uploads | -| `VITE_SUPABASE_BUCKET` | ❌ | For avatar uploads | +| `REACT_APP_URL` | ✅ | Backend URL (`http://localhost:2000`) | +| `REACT_APP_front_end_url` | ✅ | Frontend URL (`http://localhost:5173`) | +| `REACT_APP_SUPABASE_URL` | ❌ | For avatar uploads | +| `REACT_APP_SUPABASE_ANON_KEY` | ❌ | For avatar uploads | +| `REACT_APP_SUPABASE_BUCKET` | ❌ | For avatar uploads | + +--- -## API Routes +# Scripts -All backend APIs are mounted under: +## Server -```text -/api/v1 +```bash +npm start +npm test ``` -## Scripts +### Available Commands -### Server +| Command | Description | +| --- | --- | +| `npm start` | Runs backend using nodemon | +| `npm test` | Runs backend integration tests | -- `npm start` → runs production server -- `npm run dev` → runs backend with nodemon -- `npm run test:auth` → auth integration tests -- `npm run test:auth:unit` → auth unit tests -- `npm run gmail:oauth-setup` → Gmail OAuth setup helper +--- -### Frontend +## Frontend + +```bash +npm run dev +npm run build +npm run lint +``` + +--- + +# Backend Testing + +The backend now includes integration testing support using: + +- Vitest +- Supertest +- MongoMemoryServer + +--- + +## Running Backend Tests + +```bash +cd server +npm install +npm test +``` + +--- + +## Testing Features -- `npm run dev` → Vite dev server -- `npm run build` → production build -- `npm run lint` → ESLint +### Current Integration Coverage -## Logging +- Authentication signup flow +- OTP verification flow +- Signin flow +- Friend request send flow +- Friend request accept flow +- Friend request ignore flow -The backend uses Winston for structured logging. +--- -- Development logs are printed to the console -- Production environments can optionally forward logs to Logtail -- Logtail requires: - - `LOGTAIL_SOURCE_TOKEN` - - `LOGTAIL_INGESTING_HOST` +## Testing Architecture + +### Isolated Database + +Tests run using: + +```txt +MongoMemoryServer +``` -## CI checks +This means: -This repository uses GitHub Actions to run automated checks on every pull -request and every push to `main`. +- No production MongoDB Atlas database is used +- No external database credentials are required +- Each test suite runs in isolation +- Database state is automatically cleaned after tests -The workflow lives at `.github/workflows/ci.yml` and currently runs: +--- + +### Mocked External Services + +External email services are mocked during tests. + +This ensures: + +- No real emails are sent +- Faster test execution +- Deterministic OTP verification +- Stable CI/CD behavior + +--- + +## Test Structure + +```txt +server/tests/ +├── auth.test.js +├── friend.test.js +├── mocks.js +└── setup.js +``` + +--- + +# CI Checks + +This repository uses GitHub Actions to run automated checks on every pull request and push to `main`. + +Current CI checks include: - Frontend dependency install with `npm ci` - Frontend linting with `npm run lint` - Frontend production build with `npm run build` - Backend dependency install with `npm ci` -These checks help contributors catch broken builds, lint errors, and dependency -issues before maintainers review the pull request. +Future backend CI can additionally run: + +```bash +cd server +npm test +``` + +to validate integration test coverage automatically. + +--- -To run the same checks locally: +# Local CI Validation + +## Frontend ```bash cd frontend @@ -222,17 +282,12 @@ npm run lint npm run build ``` +--- + +## Backend + ```bash cd server npm ci -npm run test:auth -npm run test:auth:unit -``` - -## Deployment notes - -- Configure `FRONTEND_ORIGINS` with deployed frontend URLs -- Set `NODE_ENV=production` -- Use a production MongoDB connection string -- Configure Logtail variables if production logging is needed -- Prefer `MAIL_TRANSPORT=gmail_api` for production deployments \ No newline at end of file +npm test +``` \ No newline at end of file diff --git a/frontend/package-lock.json b/frontend/package-lock.json index babe2a6..afc1b23 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -69,7 +69,6 @@ "integrity": "sha512-CGOfOJqWjg2qW/Mb6zNsDm+u5vFQ8DxXfbM09z69p5Z6+mE1ikP2jUXw+j42Pf1XTYED2Rni5f95npYeuwMDQA==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@babel/code-frame": "^7.29.0", "@babel/generator": "^7.29.0", @@ -2192,7 +2191,8 @@ "version": "15.7.15", "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.15.tgz", "integrity": "sha512-F6bEyamV9jKGAFBEmlQnesRPGOQqS2+Uwi0Em15xenOxHaf2hv6L8YCVn3rPdPJOiJfPiCnLIRyvwVaqMY3MIw==", - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/@types/react": { "version": "18.3.28", @@ -2247,7 +2247,6 @@ "integrity": "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==", "dev": true, "license": "MIT", - "peer": true, "bin": { "acorn": "bin/acorn" }, @@ -2569,7 +2568,6 @@ } ], "license": "MIT", - "peer": true, "dependencies": { "baseline-browser-mapping": "^2.9.0", "caniuse-lite": "^1.0.30001759", @@ -2737,7 +2735,8 @@ "version": "3.2.3", "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz", "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==", - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/damerau-levenshtein": { "version": "1.0.8", @@ -3238,7 +3237,6 @@ "integrity": "sha512-XoMjdBOwe/esVgEvLmNsD3IRHkm7fbKIUGvrleloJXUZgDHig2IPWNniv+GwjyJXzuNqVjlr5+4yVUZjycJwfQ==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@eslint-community/eslint-utils": "^4.8.0", "@eslint-community/regexpp": "^4.12.1", @@ -5246,7 +5244,6 @@ "resolved": "https://registry.npmjs.org/react/-/react-18.2.0.tgz", "integrity": "sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ==", "license": "MIT", - "peer": true, "dependencies": { "loose-envify": "^1.1.0" }, @@ -5259,7 +5256,6 @@ "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.2.0.tgz", "integrity": "sha512-6IMTriUmvsjHUjNtEDudZfuDQUoWXVxKHhlEGSk81n4YFS+r/Kl99wXiwlVXtPBtJenozv2P+hxDsw9eA7Xo6g==", "license": "MIT", - "peer": true, "dependencies": { "loose-envify": "^1.1.0", "scheduler": "^0.23.0" @@ -5288,7 +5284,6 @@ "resolved": "https://registry.npmjs.org/react-redux/-/react-redux-8.0.5.tgz", "integrity": "sha512-Q2f6fCKxPFpkXt1qNRZdEDLlScsDWyrgSj0mliK59qU6W5gvBiKkdMEG2lJzhd1rCctf0hb6EtePPLZ2e0m1uw==", "license": "MIT", - "peer": true, "dependencies": { "@babel/runtime": "^7.12.1", "@types/hoist-non-react-statics": "^3.3.1", @@ -5439,7 +5434,6 @@ "resolved": "https://registry.npmjs.org/redux/-/redux-4.2.1.tgz", "integrity": "sha512-LAUYz4lc+Do8/g7aeRa8JkyDErK6ekstQaqWQrNRW//MY1TvCEpMtpTWvlQ+FPbWCx+Xixu/6SHt5N0HR+SB4w==", "license": "MIT", - "peer": true, "dependencies": { "@babel/runtime": "^7.9.2" } @@ -6286,7 +6280,6 @@ "integrity": "sha512-o5a9xKjbtuhY6Bi5S3+HvbRERmouabWbyUcpXXUA1u+GNUKoROi9byOJ8M0nHbHYHkYICiMlqxkg1KkYmm25Sw==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "esbuild": "^0.21.3", "postcss": "^8.4.43", diff --git a/frontend/src/components/auth/Auth.jsx b/frontend/src/components/auth/Auth.jsx index b449b98..1e64a6f 100644 --- a/frontend/src/components/auth/Auth.jsx +++ b/frontend/src/components/auth/Auth.jsx @@ -1,14 +1,13 @@ import { useCallback, useEffect, useState } from "react"; import { Navigate, Outlet, useLocation } from "react-router-dom"; import Loading from "../loading/Loading"; -import { API_BASE_URL } from "../../config"; const Auth = () => { const location = useLocation(); const [auth_check, setauth_check] = useState(null); const [token, setToken] = useState(() => localStorage.getItem("token")); - const url = API_BASE_URL; + const url = import.meta.env.VITE_URL; const private_routes = useCallback(async () => { if (!url || !token) { @@ -17,7 +16,7 @@ const Auth = () => { } try { - const res = await fetch(`${url}/auth/verify_route`, { + const res = await fetch(`${url}/verify_route`, { method: "POST", headers: { "Content-Type": "application/json", diff --git a/frontend/src/components/chat/messages/ValidChat.jsx b/frontend/src/components/chat/messages/ValidChat.jsx index 9105249..dbf423f 100644 --- a/frontend/src/components/chat/messages/ValidChat.jsx +++ b/frontend/src/components/chat/messages/ValidChat.jsx @@ -7,11 +7,10 @@ import { clear_channel_unread } from "../../../store/unreadSlice"; import { Input } from "../../ui/input"; import { Button } from "../../ui/button"; import { resolveProfilePic, handleImageError } from "../../../shared/imageFallbacks"; -import { API_BASE_URL } from "../../../config"; function ValidChat() { const dispatch = useDispatch(); - const url = API_BASE_URL; + const url = import.meta.env.VITE_URL; const { server_id } = useParams(); // channel creds from redux @@ -49,7 +48,7 @@ function ValidChat() { }; const store_message = async (chat_message, timestamp) => { - const res = await fetch(`${url}/chat/store_message`, { + const res = await fetch(`${url}/store_message`, { method: "POST", headers: { "Content-Type": "application/json", @@ -80,7 +79,7 @@ function ValidChat() { setError(null); dispatch(clear_channel_unread({ server_id, channel_id })); - fetch(`${url}/notifications/mark_channel_read`, { + fetch(`${url}/mark_channel_read`, { method: "POST", headers: { "Content-Type": "application/json", @@ -97,7 +96,7 @@ function ValidChat() { try { setIsLoading(true); setError(null); - const res = await fetch(`${url}/chat/get_messages`, { + const res = await fetch(`${url}/get_messages`, { method: "POST", headers: { "Content-Type": "application/json", @@ -123,7 +122,7 @@ function ValidChat() { }; const editMessage = async (message) => { - const res = await fetch(`${url}/chat/edit_server_message`, { + const res = await fetch(`${url}/edit_server_message`, { method: "POST", headers: { "Content-Type": "application/json", @@ -152,7 +151,7 @@ function ValidChat() { }; const deleteMessage = async (message) => { - const res = await fetch(`${url}/chat/delete_server_message`, { + const res = await fetch(`${url}/delete_server_message`, { method: "POST", headers: { "Content-Type": "application/json", @@ -400,4 +399,4 @@ function ValidChat() { ); } -export default ValidChat; +export default ValidChat; \ No newline at end of file diff --git a/frontend/src/components/chat/serverDetails/ServerDetails.jsx b/frontend/src/components/chat/serverDetails/ServerDetails.jsx index 4c3e056..fd2da64 100644 --- a/frontend/src/components/chat/serverDetails/ServerDetails.jsx +++ b/frontend/src/components/chat/serverDetails/ServerDetails.jsx @@ -13,7 +13,6 @@ import { } from "../../ui/dialog"; import { Button } from "../../ui/button"; import { Input } from "../../ui/input"; -import { API_BASE_URL } from "../../../config"; function ServerDetails({ new_req_recieved, elem, onNavigate }) { const dispatch = useDispatch(); @@ -21,7 +20,7 @@ function ServerDetails({ new_req_recieved, elem, onNavigate }) { const [selectedValue, setSelectedValue] = useState("text"); const [category_name, setcategory_name] = useState(""); const [new_channel_name, setnew_channel_name] = useState(""); - const url = API_BASE_URL; + const url = import.meta.env.VITE_URL; const [channel_creation_progess, setchannel_creation_progess] = useState({ text: "Create Channel", disabled: false, @@ -42,7 +41,7 @@ function ServerDetails({ new_req_recieved, elem, onNavigate }) { } const create_channel = async () => { - const res = await fetch(`${url}/servers/add_new_channel`, { + const res = await fetch(`${url}/add_new_channel`, { method: "POST", headers: { "Content-Type": "application/json", @@ -68,7 +67,7 @@ function ServerDetails({ new_req_recieved, elem, onNavigate }) { dispatch(change_page_id(channel_id)); onNavigate?.(); dispatch(clear_channel_unread({ server_id, channel_id })); - fetch(`${url}/notifications/mark_channel_read`, { + fetch(`${url}/mark_channel_read`, { method: "POST", headers: { "Content-Type": "application/json", diff --git a/frontend/src/components/chat/serverSidebar/Navbar2ChatValid.jsx b/frontend/src/components/chat/serverSidebar/Navbar2ChatValid.jsx index 09c3471..f0e2e5d 100644 --- a/frontend/src/components/chat/serverSidebar/Navbar2ChatValid.jsx +++ b/frontend/src/components/chat/serverSidebar/Navbar2ChatValid.jsx @@ -66,7 +66,7 @@ function Navbar2ChatValid({ onNavigate }) { }; const create_invite_link = async () => { - const res = await fetch(`${url}/invites/create_invite_link`, { + const res = await fetch(`${url}/create_invite_link`, { method: "POST", headers: { "Content-Type": "application/json", @@ -87,7 +87,7 @@ function Navbar2ChatValid({ onNavigate }) { }; const delete_server = async () => { - const res = await fetch(`${url}/servers/delete_server`, { + const res = await fetch(`${url}/delete_server`, { method: "POST", headers: { "Content-Type": "application/json", @@ -105,7 +105,7 @@ function Navbar2ChatValid({ onNavigate }) { }; const leave_server = async () => { - const res = await fetch(`${url}/servers/leave_server`, { + const res = await fetch(`${url}/leave_server`, { method: "POST", headers: { "Content-Type": "application/json", @@ -131,7 +131,7 @@ function Navbar2ChatValid({ onNavigate }) { } const server_info = useCallback(async () => { - const res = await fetch(`${url}/servers/server_info`, { + const res = await fetch(`${url}/server_info`, { method: "POST", headers: { "Content-Type": "application/json", @@ -173,7 +173,7 @@ function Navbar2ChatValid({ onNavigate }) { }, [Navigate, activeChannelId, dispatch, server_id, url]); const create_category = async () => { - const res = await fetch(`${url}/servers/add_new_category`, { + const res = await fetch(`${url}/add_new_category`, { method: "POST", headers: { "Content-Type": "application/json", diff --git a/frontend/src/components/dashboard/Dashboard.jsx b/frontend/src/components/dashboard/Dashboard.jsx index 7fce119..efd22b3 100644 --- a/frontend/src/components/dashboard/Dashboard.jsx +++ b/frontend/src/components/dashboard/Dashboard.jsx @@ -56,7 +56,7 @@ function Dashboard() { const user_relations = useCallback(async () => { try { - const res = await fetch(`${url}/friends/user_relations`, { + const res = await fetch(`${url}/user_relations`, { method: "GET", headers: { "Content-Type": "application/json", diff --git a/frontend/src/components/directMessages/DirectMessage.jsx b/frontend/src/components/directMessages/DirectMessage.jsx index cdd75ce..93ea8fa 100644 --- a/frontend/src/components/directMessages/DirectMessage.jsx +++ b/frontend/src/components/directMessages/DirectMessage.jsx @@ -27,7 +27,7 @@ function DirectMessage() { return; } - const res = await fetch(`${url}/direct-messages/get_direct_messages`, { + const res = await fetch(`${url}/get_direct_messages`, { method: "POST", headers: { "Content-Type": "application/json", @@ -45,7 +45,7 @@ function DirectMessage() { loadMessages(); if (activeFriend) { dispatch(clear_dm_unread({ friend_id: activeFriend.id })); - fetch(`${url}/notifications/mark_direct_messages_read`, { + fetch(`${url}/mark_direct_messages_read`, { method: "POST", headers: { "Content-Type": "application/json", @@ -167,7 +167,7 @@ function DirectMessage() { isTypingRef.current = false; socket.emit("dm_stop_typing", { to: activeFriend.id }); - const res = await fetch(`${url}/direct-messages/send_direct_message`, { + const res = await fetch(`${url}/send_direct_message`, { method: "POST", headers: { "Content-Type": "application/json", @@ -190,7 +190,7 @@ function DirectMessage() { }; const editMessage = async (message) => { - const res = await fetch(`${url}/direct-messages/edit_direct_message`, { + const res = await fetch(`${url}/edit_direct_message`, { method: "POST", headers: { "Content-Type": "application/json", @@ -219,7 +219,7 @@ function DirectMessage() { }; const deleteMessage = async (message) => { - const res = await fetch(`${url}/direct-messages/delete_direct_message`, { + const res = await fetch(`${url}/delete_direct_message`, { method: "POST", headers: { "Content-Type": "application/json", diff --git a/frontend/src/components/friends/main/MainDashboard.jsx b/frontend/src/components/friends/main/MainDashboard.jsx index 02e6085..594804a 100644 --- a/frontend/src/components/friends/main/MainDashboard.jsx +++ b/frontend/src/components/friends/main/MainDashboard.jsx @@ -97,7 +97,7 @@ function MainDashboard({ user_relations }) { setalert({ ...alert, style: "none" }); try { - const res = await fetch(`${url}/friends/process_req`, { + const res = await fetch(`${url}/process_req`, { method: "POST", headers: { "Content-Type": "application/json", @@ -220,7 +220,7 @@ function MainDashboard({ user_relations }) { const add_friend = async (e) => { e.preventDefault(); - const res = await fetch(`${url}/friends/add_friend`, { + const res = await fetch(`${url}/add_friend`, { method: "POST", headers: { "Content-Type": "application/json", diff --git a/frontend/src/components/invite/Invite.jsx b/frontend/src/components/invite/Invite.jsx index 6d37cf0..45009fe 100644 --- a/frontend/src/components/invite/Invite.jsx +++ b/frontend/src/components/invite/Invite.jsx @@ -5,7 +5,6 @@ import invalid_link_image from "../../images/invalid_invite.svg"; import jwt from "jwt-decode"; import { useNavigate } from "react-router-dom"; import { Button } from "../ui/button"; -import { API_BASE_URL } from "../../config"; function Invite() { const navigate = useNavigate(); @@ -14,12 +13,12 @@ function Invite() { const { username, tag, profile_pic, id } = user_creds || {}; const { invite_link } = useParams(); - const url = API_BASE_URL; + const url = import.meta.env.VITE_URL; const [invite_details, setinvite_details] = useState(null); const [invalid_invite_link, setinvalid_invite_link] = useState(null); const accept_invite = async () => { - const res = await fetch(`${url}/invites/accept_invite`, { + const res = await fetch(`${url}/accept_invite`, { method: "POST", headers: { "Content-Type": "application/json", @@ -37,7 +36,7 @@ function Invite() { }; const invite_link_info = useCallback(async () => { - const res = await fetch(`${url}/invites/invite_link_info`, { + const res = await fetch(`${url}/invite_link_info`, { method: "POST", headers: { "Content-Type": "application/json", diff --git a/frontend/src/components/login/Login.jsx b/frontend/src/components/login/Login.jsx index cf089b3..ba578aa 100644 --- a/frontend/src/components/login/Login.jsx +++ b/frontend/src/components/login/Login.jsx @@ -3,7 +3,6 @@ import { Link as RouterLink, useLocation, useNavigate } from "react-router-dom"; import AuthShell from "../auth/AuthShell"; import { motion, AnimatePresence } from "framer-motion"; import { FiEye, FiEyeOff } from "react-icons/fi"; -import { API_BASE_URL } from "../../config"; function Label({ children }) { @@ -112,7 +111,7 @@ function Login() { const [alert_box, setalert_box] = useState(false); const [alert_message, setalert_message] = useState(""); const [submitting, setSubmitting] = useState(false); - const url = API_BASE_URL; + const url = import.meta.env.VITE_URL; const canSubmit = useMemo( () => user_values.email.trim().length > 0 && user_values.password.length > 0, @@ -134,7 +133,7 @@ function Login() { try { setSubmitting(true); setalert_box(false); - const res = await fetch(`${url}/auth/signin`, { + const res = await fetch(`${url}/signin`, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ email, password }), @@ -273,4 +272,4 @@ function Login() { ); } -export default Login; +export default Login; \ No newline at end of file diff --git a/frontend/src/components/navbar/Navbar.jsx b/frontend/src/components/navbar/Navbar.jsx index dedfa48..6c085ba 100644 --- a/frontend/src/components/navbar/Navbar.jsx +++ b/frontend/src/components/navbar/Navbar.jsx @@ -18,7 +18,6 @@ import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogTitle } f import { Button } from "../ui/button"; import { Input } from "../ui/input"; import { close_direct_message } from "../../store/directMessageSlice"; -import { API_BASE_URL } from "../../config"; function Navbar({ new_req_recieved, user_cred, onNavigate }) { const dispatch = useDispatch(); @@ -104,7 +103,7 @@ function Navbar({ new_req_recieved, user_cred, onNavigate }) { const create_server = async () => { const image_url = await upload_server_image(); - const res = await fetch(`${API_BASE_URL}/servers/create_server`, { + const res = await fetch(`${import.meta.env.VITE_URL}/create_server`, { method: "POST", headers: { "Content-Type": "application/json", diff --git a/frontend/src/components/notifications/NotificationListener.jsx b/frontend/src/components/notifications/NotificationListener.jsx index 60fe2d8..d03511b 100644 --- a/frontend/src/components/notifications/NotificationListener.jsx +++ b/frontend/src/components/notifications/NotificationListener.jsx @@ -14,7 +14,6 @@ import { set_online_users, set_user_presence, } from "../../store/presenceSlice"; -import { API_BASE_URL } from "../../config"; function NotificationListener() { const dispatch = useDispatch(); @@ -23,7 +22,7 @@ function NotificationListener() { const notificationPrefs = useSelector((state) => state.user_info.notification_preferences); const activeFriend = useSelector((state) => state.direct_message.activeFriend); const activeChannelId = useSelector((state) => state.currentPage.page_id); - const url = API_BASE_URL; + const url = import.meta.env.VITE_URL; const canReceiveDMs = notificationPrefs?.direct_messages ?? true; const canReceiveServerMessages = notificationPrefs?.server_messages ?? true; @@ -59,7 +58,7 @@ function NotificationListener() { socket.on("user_servers_updated", handleUserServersUpdated); const fetchUnreadSummary = async () => { - const res = await fetch(`${url}/notifications/unread_summary`, { + const res = await fetch(`${url}/unread_summary`, { method: "GET", headers: { "Content-Type": "application/json", @@ -94,7 +93,7 @@ function NotificationListener() { (prev?.server_messages === false && canReceiveServerMessages); if (reEnabled) { - const res = fetch(`${url}/notifications/unread_summary`, { + const res = fetch(`${url}/unread_summary`, { method: "GET", headers: { "Content-Type": "application/json", @@ -122,7 +121,7 @@ function NotificationListener() { const handleDmNotification = ({ friend_id }) => { if (activeFriend?.id === friend_id && isDashboard) { dispatch(clear_dm_unread({ friend_id })); - fetch(`${url}/notifications/mark_direct_messages_read`, { + fetch(`${url}/mark_direct_messages_read`, { method: "POST", headers: { "Content-Type": "application/json", @@ -145,7 +144,7 @@ function NotificationListener() { activeChannelId === channel_id ) { dispatch(clear_channel_unread({ server_id, channel_id })); - fetch(`${url}/notifications/mark_channel_read`, { + fetch(`${url}/mark_channel_read`, { method: "POST", headers: { "Content-Type": "application/json", diff --git a/frontend/src/components/register/Register.jsx b/frontend/src/components/register/Register.jsx index 3ad6632..7d94053 100644 --- a/frontend/src/components/register/Register.jsx +++ b/frontend/src/components/register/Register.jsx @@ -3,7 +3,6 @@ import { Link as RouterLink, useNavigate } from "react-router-dom"; import AuthShell from "../auth/AuthShell"; import { motion, AnimatePresence } from "framer-motion"; import { FiEye, FiEyeOff } from "react-icons/fi"; -import { API_BASE_URL } from "../../config"; import { Dialog, DialogContent, @@ -200,7 +199,7 @@ function Register() { "January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December", ]; - const url = API_BASE_URL; + const url = import.meta.env.VITE_URL; const canSubmit = useMemo( () => @@ -259,7 +258,7 @@ function Register() { try { setSubmitting(true); setalert_box(false); - const res = await fetch(`${url}/auth/signup`, { + const res = await fetch(`${url}/signup`, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ email, password, username, dob }), @@ -302,7 +301,7 @@ function Register() { try { setVerifying(true); setotp_alert_box(false); - const res = await fetch(`${url}/auth/verify`, { + const res = await fetch(`${url}/verify`, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ otp_value: otp_value.trim(), email: user_values.email }), @@ -338,7 +337,7 @@ function Register() { if (!url) return; try { setVerifying(true); - const res = await fetch(`${url}/auth/resend_otp`, { + const res = await fetch(`${url}/resend_otp`, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ email: user_values.email }), @@ -709,4 +708,4 @@ function Register() { ); } -export default Register; +export default Register; \ No newline at end of file diff --git a/frontend/src/config/index.js b/frontend/src/config/index.js index b8dd352..376bd5e 100644 --- a/frontend/src/config/index.js +++ b/frontend/src/config/index.js @@ -1,3 +1 @@ -const API_ORIGIN = import.meta.env.VITE_URL || ""; - -export const API_BASE_URL = API_ORIGIN ? `${API_ORIGIN}/api/v1` : ""; +export const API_BASE_URL = import.meta.env.VITE_URL || ""; diff --git a/frontend/vite.config.js.timestamp-1778826075991-bb3d5bba2495f.mjs b/frontend/vite.config.js.timestamp-1778826075991-bb3d5bba2495f.mjs deleted file mode 100644 index 32b7f49..0000000 --- a/frontend/vite.config.js.timestamp-1778826075991-bb3d5bba2495f.mjs +++ /dev/null @@ -1,31 +0,0 @@ -// vite.config.js -import { defineConfig, loadEnv } from "vite"; -import react from "@vitejs/plugin-react"; -import tailwindcss from "@tailwindcss/vite"; -var vite_config_default = defineConfig(({ mode }) => { - const env = loadEnv(mode, process.cwd(), ""); - const processEnvShim = { - REACT_APP_URL: env.REACT_APP_URL || env.VITE_API_URL || "", - REACT_APP_front_end_url: env.REACT_APP_front_end_url || env.VITE_FRONTEND_URL || "", - REACT_APP_SUPABASE_URL: env.REACT_APP_SUPABASE_URL || "", - REACT_APP_SUPABASE_ANON_KEY: env.REACT_APP_SUPABASE_ANON_KEY || "", - REACT_APP_SUPABASE_BUCKET: env.REACT_APP_SUPABASE_BUCKET || "" - }; - return { - plugins: [react(), tailwindcss()], - define: { - "process.env": processEnvShim - }, - server: { - port: 5173, - strictPort: true - }, - build: { - sourcemap: false - } - }; -}); -export { - vite_config_default as default -}; - diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..b80fa6b --- /dev/null +++ b/package-lock.json @@ -0,0 +1,6 @@ +{ + "name": "PiperChat01", + "lockfileVersion": 3, + "requires": true, + "packages": {} +} diff --git a/server/.env.example b/server/.env.example deleted file mode 100644 index eb956eb..0000000 --- a/server/.env.example +++ /dev/null @@ -1,56 +0,0 @@ -# Server -PORT=2000 -NODE_ENV=development - -# Database and auth -MONGO_URI="mongodb+srv://:@/?retryWrites=true&w=majority" -ACCESS_TOKEN="" -OTP_TTL_MS=600000 - -# Email delivery -# MAIL_TRANSPORT can be auto, console, gmail_api, password, or smtp. -MAIL_TRANSPORT=console -MAIL_USER="you@gmail.com" - -# Gmail API OAuth credentials. Required when MAIL_TRANSPORT=gmail_api. -OAUTH_CLIENT_ID="" -OAUTH_CLIENT_SECRET="" -OAUTH_REFRESH_TOKEN="" - -# Gmail app-password fallback. Used when MAIL_TRANSPORT=password. -MAIL_PASS="" - -# Generic SMTP fallback. Used when MAIL_TRANSPORT=smtp. -SMTP_HOST="" -SMTP_USER="" -SMTP_PASS="" -SMTP_PORT=587 -SMTP_SECURE=false - -# Avatar defaults -DICEBEAR_API="https://api.dicebear.com/9.x" -DICEBEAR_STYLE="avataaars" -DEFAULT_PROFILE_PIC="https://example.com/default_profile_avatar.png" - -# Redis cache and unread counters -# Prefer REDIS_URL when available. REDIS_HOST/REDIS_PORT are fallback options. -REDIS_URL="" -REDIS_HOST="" -REDIS_PORT=6379 - -# Upstash Redis URL aliases, used when REDIS_URL is empty. -UPSTASH_REDIS_URL="" -UPSTASH_REDIS_TLS_URL="" -REDIS_CACHE_TTL_SECONDS=30 - -# CORS -# Comma-separate multiple frontend origins. -FRONTEND_ORIGINS="http://localhost:5173" - -# Rate limiting -RATE_LIMIT_WINDOW_MS=900000 - -# Logging -# Required when NODE_ENV=production. -LOGTAIL_SOURCE_TOKEN="" -LOGTAIL_INGESTING_HOST="" diff --git a/server/app.js b/server/app.js new file mode 100644 index 0000000..f7e948c --- /dev/null +++ b/server/app.js @@ -0,0 +1,37 @@ +import cors from "cors"; +import express from "express"; + +import authRoutes from "./routes/auth.js"; +import chatRoutes from "./routes/chat.js"; +import directMessageRoutes from "./routes/directMessages.js"; +import friendsRoutes from "./routes/friends.js"; +import invitesRoutes from "./routes/invites.js"; +import notificationRoutes from "./routes/notifications.js"; +import profileRoutes from "./routes/profile.js"; +import serversRoutes from "./routes/servers.js"; + +const app = express(); + +app.use(cors()); + +app.use(express.json({ limit: "10kb" })); +app.use(express.urlencoded({ extended: true, limit: "10kb" })); + +app.get("/", (req, res) => { + res.status(200).json({ + success: true, + message: "Server is up and running!", + status: "ok", + }); +}); + +app.use("/", authRoutes); +app.use("/", friendsRoutes); +app.use("/", serversRoutes); +app.use("/", invitesRoutes); +app.use("/", chatRoutes); +app.use("/", directMessageRoutes); +app.use("/", notificationRoutes); +app.use("/", profileRoutes); + +export default app; \ No newline at end of file diff --git a/server/config/constants.js b/server/config/constants.js new file mode 100644 index 0000000..b8c3965 --- /dev/null +++ b/server/config/constants.js @@ -0,0 +1,3 @@ +const OTP_TTL_MS = Number(process.env.OTP_TTL_MS) || 10 * 60 * 1000; // 10 minutes + +export { OTP_TTL_MS }; diff --git a/server/config/db.js b/server/config/db.js new file mode 100644 index 0000000..218a249 --- /dev/null +++ b/server/config/db.js @@ -0,0 +1,25 @@ +import "./env.js"; +import mongoose from "mongoose"; + +mongoose.set("strictQuery", true); + +const mongoUri = process.env.MONGO_URI; +if (!mongoUri) { + throw new Error("MONGO_URI is not set in .env"); +} + +export function connect(options = {}) { + return mongoose + .connect(mongoUri, { + serverSelectionTimeoutMS: 8000, + ...options, + }) + .catch((err) => { + if (err.message?.includes("auth")) { + console.error( + "[MongoDB] Auth failed. Check: special chars in password need URL-encoding (@ → %40, # → %23), and no spaces around = in .env" + ); + } + throw err; + }); +} \ No newline at end of file diff --git a/server/config/env.js b/server/config/env.js new file mode 100644 index 0000000..c49b79e --- /dev/null +++ b/server/config/env.js @@ -0,0 +1,8 @@ +import path from "path"; +import { fileURLToPath } from "url"; +import dotenv from "dotenv"; + +const __filename = fileURLToPath(import.meta.url); +const __dirname = path.dirname(__filename); + +dotenv.config({ path: path.join(__dirname, "..", "..", ".env") }); diff --git a/server/index.js b/server/index.js new file mode 100644 index 0000000..a09e7b6 --- /dev/null +++ b/server/index.js @@ -0,0 +1,46 @@ +import "./config/env.js"; + +import { Server as SocketIOServer } from "socket.io"; + +import app from "./app.js"; +import { connect } from "./config/db.js"; +import { attachSocketHandlers } from "./socket/index.js"; +import { setIO } from "./socket/runtime.js"; +import { verifyMailTransport } from "./services/email.js"; + +const port = process.env.PORT || 2000; + +async function start() { + await connect(); + + await verifyMailTransport(); + + const server = app.listen(port, () => { + console.log(`listening on port ${port}`); + console.log("Connected to DB"); + }); + + const allowedOrigins = ( + process.env.FRONTEND_ORIGINS || + "http://localhost:3000,http://localhost:5173" + ) + .split(",") + .map((value) => value.trim()) + .filter(Boolean); + + const io = new SocketIOServer(server, { + pingTimeout: 20000, + cors: { + origin: allowedOrigins, + }, + }); + + setIO(io); + + attachSocketHandlers(io); +} + +start().catch((err) => { + console.error("Failed to start server:", err); + process.exit(1); +}); \ No newline at end of file diff --git a/server/src/lib/authJwtPayload.js b/server/lib/authJwtPayload.js similarity index 100% rename from server/src/lib/authJwtPayload.js rename to server/lib/authJwtPayload.js diff --git a/server/src/lib/cache.js b/server/lib/cache.js similarity index 77% rename from server/src/lib/cache.js rename to server/lib/cache.js index ea585d6..a48af50 100644 --- a/server/src/lib/cache.js +++ b/server/lib/cache.js @@ -1,13 +1,13 @@ -import config from "../config/index.js"; import { createClient } from "redis"; -import logger from "./winston.js"; const redisUrl = - config.REDIS_URL || - config.UPSTASH_REDIS_URL || - config.UPSTASH_REDIS_TLS_URL || + process.env.REDIS_URL || + process.env.UPSTASH_REDIS_URL || + process.env.UPSTASH_REDIS_TLS_URL || ""; +const defaultTtlSeconds = Number(process.env.REDIS_CACHE_TTL_SECONDS || 30); + let redisConnectPromise = null; async function getRedis() { @@ -16,14 +16,14 @@ async function getRedis() { if (!redisConnectPromise) { const client = createClient({ url: redisUrl }); client.on("error", (err) => { - logger.warn(`[redis] client error: ${err?.message || err}`); + console.warn("[redis] client error:", err?.message || err); }); redisConnectPromise = client .connect() .then(() => client) .catch((err) => { - logger.warn(`[redis] connect failed: ${err?.message || err}`); + console.warn("[redis] connect failed:", err?.message || err); redisConnectPromise = null; return null; }); @@ -45,7 +45,7 @@ async function getJson(key) { } } -async function setJson(key, value, ttlSeconds = config.REDIS_CACHE_TTL_SECONDS) { +async function setJson(key, value, ttlSeconds = defaultTtlSeconds) { const client = await getRedis(); if (!client) return false; diff --git a/server/src/lib/gmailOAuth.js b/server/lib/gmailOAuth.js similarity index 60% rename from server/src/lib/gmailOAuth.js rename to server/lib/gmailOAuth.js index 85e83a6..6312da0 100644 --- a/server/src/lib/gmailOAuth.js +++ b/server/lib/gmailOAuth.js @@ -1,15 +1,15 @@ -import config from "../config/index.js"; import { OAuth2Client } from "google-auth-library"; export const GMAIL_SEND_SCOPE = "https://www.googleapis.com/auth/gmail.send"; export function getMailUser() { - return (config.EMAIL_USER || config.MAIL_USER || "").trim(); + return (process.env.EMAIL_USER || process.env.MAIL_USER || "").trim(); } export function getOAuthClientId() { return ( - config.OAUTH_CLIENT_ID || + process.env.OAUTH_CLIENT_ID || + process.env.OAUTH_CLIENTID || "" ).trim(); } @@ -18,18 +18,18 @@ export function hasGmailOAuthCredentials() { return Boolean( getMailUser() && getOAuthClientId() && - config.OAUTH_CLIENT_SECRET?.trim() && - config.OAUTH_REFRESH_TOKEN?.trim() + process.env.OAUTH_CLIENT_SECRET?.trim() && + process.env.OAUTH_REFRESH_TOKEN?.trim() ); } export function createGmailOAuth2Client() { const client = new OAuth2Client( getOAuthClientId(), - config.OAUTH_CLIENT_SECRET.trim() + process.env.OAUTH_CLIENT_SECRET.trim() ); client.setCredentials({ - refresh_token: config.OAUTH_REFRESH_TOKEN.trim(), + refresh_token: process.env.OAUTH_REFRESH_TOKEN.trim(), }); return client; } diff --git a/server/src/middleware/auth.js b/server/middleware/auth.js similarity index 78% rename from server/src/middleware/auth.js rename to server/middleware/auth.js index a7f0d53..bc13273 100644 --- a/server/src/middleware/auth.js +++ b/server/middleware/auth.js @@ -1,4 +1,3 @@ -import config from "../config/index.js"; import jwt from "jsonwebtoken"; export const authToken = async (req, res, next) => { @@ -7,7 +6,7 @@ export const authToken = async (req, res, next) => { if (!authHeader) { return res.status(401).json({ message: "No token provided", status: 401 }); } - const decoded = jwt.verify(authHeader, config.ACCESS_TOKEN); + const decoded = jwt.verify(authHeader, process.env.ACCESS_TOKEN); req.user = decoded; next(); } catch (err) { diff --git a/server/src/models/Chat.js b/server/models/Chat.js similarity index 100% rename from server/src/models/Chat.js rename to server/models/Chat.js diff --git a/server/src/models/DirectMessageThread.js b/server/models/DirectMessageThread.js similarity index 100% rename from server/src/models/DirectMessageThread.js rename to server/models/DirectMessageThread.js diff --git a/server/src/models/Invite.js b/server/models/Invite.js similarity index 100% rename from server/src/models/Invite.js rename to server/models/Invite.js diff --git a/server/src/models/Server.js b/server/models/Server.js similarity index 100% rename from server/src/models/Server.js rename to server/models/Server.js diff --git a/server/src/models/User.js b/server/models/User.js similarity index 100% rename from server/src/models/User.js rename to server/models/User.js diff --git a/server/package-lock.json b/server/package-lock.json index 919aa65..2d28b8e 100644 --- a/server/package-lock.json +++ b/server/package-lock.json @@ -9,17 +9,12 @@ "version": "1.0.0", "license": "ISC", "dependencies": { - "@logtail/node": "^0.5.8", - "@logtail/winston": "^0.5.8", "bcryptjs": "^3.0.3", - "compression": "^1.8.1", - "cors": "^2.8.5", + "cors": "^2.8.6", "dotenv": "^16.0.2", "express": "^4.18.1", - "express-rate-limit": "^8.5.2", "google-auth-library": "^10.5.0", "googleapis": "^171.4.0", - "helmet": "^8.1.0", "jsonwebtoken": "^9.0.3", "mongoose": "^9.3.1", "nanoid": "^5.1.6", @@ -27,133 +22,114 @@ "nodemon": "^3.1.14", "redis": "^5.11.0", "shortid": "^2.2.16", - "socket.io": "^4.5.4", - "winston": "^3.19.0" + "socket.io": "^4.5.4" }, "devDependencies": { - "mongodb-memory-server": "^11.1.0" + "mongodb-memory-server": "^11.1.0", + "supertest": "^7.2.2", + "vitest": "^4.1.6" } }, - "node_modules/@colors/colors": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/@colors/colors/-/colors-1.6.0.tgz", - "integrity": "sha512-Ir+AOibqzrIsL6ajt3Rz3LskB7OiMVHqltZmspbW/TJuTVuyOMirVqAkjfY6JISiLHgyNqicAC8AyHHGzNd/dA==", - "license": "MIT", - "engines": { - "node": ">=0.1.90" - } - }, - "node_modules/@dabh/diagnostics": { - "version": "2.0.8", - "resolved": "https://registry.npmjs.org/@dabh/diagnostics/-/diagnostics-2.0.8.tgz", - "integrity": "sha512-R4MSXTVnuMzGD7bzHdW2ZhhdPC/igELENcq5IjEverBvq5hn1SXCWcsi6eSsdWP0/Ur+SItRRjAktmdoX/8R/Q==", + "node_modules/@emnapi/core": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.10.0.tgz", + "integrity": "sha512-yq6OkJ4p82CAfPl0u9mQebQHKPJkY7WrIuk205cTYnYe+k2Z8YBh11FrbRG/H6ihirqcacOgl2BIO8oyMQLeXw==", + "dev": true, "license": "MIT", + "optional": true, "dependencies": { - "@so-ric/colorspace": "^1.1.6", - "enabled": "2.0.x", - "kuler": "^2.0.0" + "@emnapi/wasi-threads": "1.2.1", + "tslib": "^2.4.0" } }, - "node_modules/@logtail/core": { - "version": "0.5.8", - "resolved": "https://registry.npmjs.org/@logtail/core/-/core-0.5.8.tgz", - "integrity": "sha512-1LxjI46LCXeVRyENwjO4s0a1CWSIqHjYH6w2YVNhQLc5b7mVN0jhzxvmg9mps54aEuCSuK2SVCNmgvuNTDtAug==", - "license": "ISC", + "node_modules/@emnapi/runtime": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.10.0.tgz", + "integrity": "sha512-ewvYlk86xUoGI0zQRNq/mC+16R1QeDlKQy21Ki3oSYXNgLb45GV1P6A0M+/s6nyCuNDqe5VpaY84BzXGwVbwFA==", + "dev": true, + "license": "MIT", + "optional": true, "dependencies": { - "@logtail/tools": "^0.5.8", - "@logtail/types": "^0.5.8", - "serialize-error": "8.1.0" + "tslib": "^2.4.0" } }, - "node_modules/@logtail/node": { - "version": "0.5.8", - "resolved": "https://registry.npmjs.org/@logtail/node/-/node-0.5.8.tgz", - "integrity": "sha512-9XMxeToOL1QHVNp7IQlPxbRsu3iYUBtXl/SFCyOQQmtnwzS7z+tbiV39DYkXfwkzig6wjwTfLYDQj673Bqplqg==", - "license": "ISC", + "node_modules/@emnapi/wasi-threads": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.2.1.tgz", + "integrity": "sha512-uTII7OYF+/Mes/MrcIOYp5yOtSMLBWSIoLPpcgwipoiKbli6k322tcoFsxoIIxPDqW01SQGAgko4EzZi2BNv2w==", + "dev": true, + "license": "MIT", + "optional": true, "dependencies": { - "@logtail/core": "^0.5.8", - "@logtail/types": "^0.5.8", - "@msgpack/msgpack": "^2.5.1", - "@types/stack-trace": "^0.0.33", - "minimatch": "^9.0.5", - "stack-trace": "0.0.10" + "tslib": "^2.4.0" } }, - "node_modules/@logtail/node/node_modules/balanced-match": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", - "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", + "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", + "dev": true, "license": "MIT" }, - "node_modules/@logtail/node/node_modules/brace-expansion": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.0.tgz", - "integrity": "sha512-TN1kCZAgdgweJhWWpgKYrQaMNHcDULHkWwQIspdtjV4Y5aurRdZpjAqn6yX3FPqTA9ngHCc4hJxMAMgGfve85w==", + "node_modules/@mongodb-js/saslprep": { + "version": "1.4.6", + "resolved": "https://registry.npmjs.org/@mongodb-js/saslprep/-/saslprep-1.4.6.tgz", + "integrity": "sha512-y+x3H1xBZd38n10NZF/rEBlvDOOMQ6LKUTHqr8R9VkJ+mmQOYtJFxIlkkK8fZrtOiL6VixbOBWMbZGBdal3Z1g==", "license": "MIT", "dependencies": { - "balanced-match": "^1.0.0" + "sparse-bitfield": "^3.0.3" } }, - "node_modules/@logtail/node/node_modules/minimatch": { - "version": "9.0.9", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.9.tgz", - "integrity": "sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg==", - "license": "ISC", + "node_modules/@napi-rs/wasm-runtime": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-1.1.4.tgz", + "integrity": "sha512-3NQNNgA1YSlJb/kMH1ildASP9HW7/7kYnRI2szWJaofaS1hWmbGI4H+d3+22aGzXXN9IJ+n+GiFVcGipJP18ow==", + "dev": true, + "license": "MIT", + "optional": true, "dependencies": { - "brace-expansion": "^2.0.2" - }, - "engines": { - "node": ">=16 || 14 >=14.17" + "@tybys/wasm-util": "^0.10.1" }, "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/@logtail/tools": { - "version": "0.5.8", - "resolved": "https://registry.npmjs.org/@logtail/tools/-/tools-0.5.8.tgz", - "integrity": "sha512-eAbbHaYSpRoB7Udp301xVUVDeKdgXTP2/s9FOeKX5tPLMM3hgbvuIx50bYJoV/to7bHGYBi36Gdsnj8ElcPXbg==", - "license": "ISC", - "dependencies": { - "@logtail/types": "^0.5.8" + "type": "github", + "url": "https://github.com/sponsors/Brooooooklyn" + }, + "peerDependencies": { + "@emnapi/core": "^1.7.1", + "@emnapi/runtime": "^1.7.1" } }, - "node_modules/@logtail/types": { - "version": "0.5.8", - "resolved": "https://registry.npmjs.org/@logtail/types/-/types-0.5.8.tgz", - "integrity": "sha512-CJIijD/2vqK8XwPRZJeUkiiBEhKTviHs5p1WI40WvW21kU18QiQ8PupFJ2uhrItxhwtlkXmRXul5+cNtv3cAFQ==", - "license": "ISC" - }, - "node_modules/@logtail/winston": { - "version": "0.5.8", - "resolved": "https://registry.npmjs.org/@logtail/winston/-/winston-0.5.8.tgz", - "integrity": "sha512-nL08rAGiKJC4blEBfe4z8sYN4d3G9FCWKlsp8zjcsu42Bj7345hHsgyEfftnhhPWyfmF+5q+i1XucQhjNRSbYQ==", - "license": "ISC", - "dependencies": { - "@logtail/node": "^0.5.8", - "@logtail/types": "^0.5.8", - "winston-transport": "^4.3.0" + "node_modules/@noble/hashes": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.8.0.tgz", + "integrity": "sha512-jCs9ldd7NwzpgXDIf6P3+NrHh9/sD6CQdxHyjQI+h/6rDNo88ypBxxz45UDuZHz9r3tNz7N/VInSVoVdtXEI4A==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^14.21.3 || >=16" }, - "peerDependencies": { - "winston": ">=3.2.1" + "funding": { + "url": "https://paulmillr.com/funding/" } }, - "node_modules/@mongodb-js/saslprep": { - "version": "1.4.6", - "resolved": "https://registry.npmjs.org/@mongodb-js/saslprep/-/saslprep-1.4.6.tgz", - "integrity": "sha512-y+x3H1xBZd38n10NZF/rEBlvDOOMQ6LKUTHqr8R9VkJ+mmQOYtJFxIlkkK8fZrtOiL6VixbOBWMbZGBdal3Z1g==", + "node_modules/@oxc-project/types": { + "version": "0.130.0", + "resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.130.0.tgz", + "integrity": "sha512-ibD2usx9JRu7f5pu2tMKMI4cpA4NgXJQoYRP4pQ7Pxmn1l6k/53qWtQWZayhYy3X4QZkt90Ot+mJEaeXouio6Q==", + "dev": true, "license": "MIT", - "dependencies": { - "sparse-bitfield": "^3.0.3" + "funding": { + "url": "https://github.com/sponsors/Boshen" } }, - "node_modules/@msgpack/msgpack": { - "version": "2.8.0", - "resolved": "https://registry.npmjs.org/@msgpack/msgpack/-/msgpack-2.8.0.tgz", - "integrity": "sha512-h9u4u/jiIRKbq25PM+zymTyW6bhTzELvOoUd+AvYriWOAKpLGnIamaET3pnHYoI5iYphAHBI4ayx0MehR+VVPQ==", - "license": "ISC", - "engines": { - "node": ">= 10" + "node_modules/@paralleldrive/cuid2": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/@paralleldrive/cuid2/-/cuid2-2.3.1.tgz", + "integrity": "sha512-XO7cAxhnTZl0Yggq6jOgjiOHhbgcO4NqFqwSmQpjK3b6TEE6Uj/jfSk6wzYyemh3+I0sHirKSetjQwn5cZktFw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@noble/hashes": "^1.1.5" } }, "node_modules/@redis/bloom": { @@ -173,7 +149,6 @@ "resolved": "https://registry.npmjs.org/@redis/client/-/client-5.11.0.tgz", "integrity": "sha512-GHoprlNQD51Xq2Ztd94HHV94MdFZQ3CVrpA04Fz8MVoHM0B7SlbmPEVIjwTbcv58z8QyjnrOuikS0rWF03k5dQ==", "license": "MIT", - "peer": true, "dependencies": { "cluster-key-slot": "1.1.2" }, @@ -225,22 +200,305 @@ "@redis/client": "^5.11.0" } }, - "node_modules/@so-ric/colorspace": { - "version": "1.1.6", - "resolved": "https://registry.npmjs.org/@so-ric/colorspace/-/colorspace-1.1.6.tgz", - "integrity": "sha512-/KiKkpHNOBgkFJwu9sh48LkHSMYGyuTcSFK/qMBdnOAlrRJzRSXAOFB5qwzaVQuDl8wAvHVMkaASQDReTahxuw==", + "node_modules/@rolldown/binding-android-arm64": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.0.1.tgz", + "integrity": "sha512-fJI3I0r3C3Oj/zdBCpaCmBRZYf07xpaq4yCfDDoSFm+beWNzbIl26puW8RraUdugoJw/95zerNOn6jasAhzSmg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-darwin-arm64": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.0.1.tgz", + "integrity": "sha512-cKnAhWEsV7TPcA/5EAteDp6KcJZBQ2G+BqE7zayMMi7kMvwRsbv7WT9aOnn0WNl4SKEIf43vjS31iUPu80nzXg==", + "cpu": [ + "arm64" + ], + "dev": true, "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-darwin-x64": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.0.1.tgz", + "integrity": "sha512-YKrVwQjIRBPo+5G/u03wGjbdy4q7pyzCe93DK9VJ7zkVmeg8LJ7GbgsiHWdR4xSoe4CAXRD7Bcjgbtr64bkXNg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-freebsd-x64": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.0.1.tgz", + "integrity": "sha512-z/oBsREo46SsFqBwYtFe0kpJeBijAT48O/WXLI4suiCLBkr03RTtTJMCzSdDd2znlh8VJizL09XVkQgk8IZonw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-arm-gnueabihf": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.0.1.tgz", + "integrity": "sha512-ik8q7GM11zxvYxFc2PeDcT6TBvhCQMaUxfph/M5l9sKuTs/Sjg3L+Byw0F7w0ZVLBZmx30P+gG0ECzzN+MFcmQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-arm64-gnu": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.0.1.tgz", + "integrity": "sha512-QoSx2EkyrrdZ6kcyE8stqZ62t0Yra8Fs5ia9lOxJrh6TMQJK7gQKmscdTHf7pOXKREKrVwOtJcQG3qVSfc866A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-arm64-musl": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.0.1.tgz", + "integrity": "sha512-uwNwFpwKeNiZawfAWBgg0VIztPTV3ihhh1vV334h9ivnNLorxnQMU6Fz8wG1Zb4Qh9LC1/MkcyT3YlDXG3Rsgg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-ppc64-gnu": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.0.1.tgz", + "integrity": "sha512-zY1bul7OWr7DFBiJ++wofXvnr8B45ce3QsQUhKrIhXsygAh7bTkwyeM1bi1a2g5C/yC/N8TZyGDEoMfm/l9mpg==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-s390x-gnu": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.0.1.tgz", + "integrity": "sha512-0frlsT/f4Ft6I7SMESTKnF3cZsdicQn1dCMkF/jT9wDLE+gGoiQfv1nmT9e+s7s/fekvvy6tZM2jHvI2tkbJDQ==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-x64-gnu": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.0.1.tgz", + "integrity": "sha512-XABVmGp9Tg0WspTVvwduTc4fpqy6JnAUrSQe6OuyqD/03nI7r0O9OWUkMIwFrjKAIqolvqoA4ZrJppgwE0Gxmw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-x64-musl": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.0.1.tgz", + "integrity": "sha512-bV4fzswuzVcKD90o/VM6QqKxnxlDq0g2BISDLNVmxrnhpv1DDbyPhCIjYfvzYLV+MvkKKnQt2Q6AO86SEBULUQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-openharmony-arm64": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.0.1.tgz", + "integrity": "sha512-/Mh0Zhq3OP7fVs0kcQHZP6lZEthMGTaSf8UBQYSFEZDWGXXlEC+nJ6EqenaK2t4LBXMe3A+K/G2BVXXdtOr4PQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-wasm32-wasi": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@rolldown/binding-wasm32-wasi/-/binding-wasm32-wasi-1.0.1.tgz", + "integrity": "sha512-+1xc9X45l8ufsBAm6Gjvx2qDRIY9lTVt0cgWNcJ+1gdhXvkbxePA60yRTwSTuXL09CMhyJmjpV7E3NoyxbqFQQ==", + "cpu": [ + "wasm32" + ], + "dev": true, + "license": "MIT", + "optional": true, "dependencies": { - "color": "^5.0.2", - "text-hex": "1.0.x" + "@emnapi/core": "1.10.0", + "@emnapi/runtime": "1.10.0", + "@napi-rs/wasm-runtime": "^1.1.4" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" } }, + "node_modules/@rolldown/binding-win32-arm64-msvc": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.0.1.tgz", + "integrity": "sha512-1D+UqZdfnuR+Jy1GgMJwi85bD40H21uNmOPRWQhw4oRSuolZ/B5rixZ45DK2KXOTCvmVCecauWgEhbw8bI7tOw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-win32-x64-msvc": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.0.1.tgz", + "integrity": "sha512-INAycaWuhlOK3wk4mRHGsdgwYWmd9cChdPdE9bwWmy6rn9VqVNYNFGhOdXrofXUxwHIncSiPNb8tNm8knDVIeQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/pluginutils": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.1.tgz", + "integrity": "sha512-2j9bGt5Jh8hj+vPtgzPtl72j0yRxHAyumoo6TNfAjsLB04UtpSvPbPcDcBMxz7n+9CYB0c1GxQFxYRg2jimqGw==", + "dev": true, + "license": "MIT" + }, "node_modules/@socket.io/component-emitter": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/@socket.io/component-emitter/-/component-emitter-3.1.2.tgz", "integrity": "sha512-9BCxFwvbGg/RsZK9tjXd8s4UcwR0MWeFQ1XEKIQVVvAGJyINdrqKMcTRyLoK8Rse1GjzLV9cwjWV1olXRWEXVA==", "license": "MIT" }, + "node_modules/@standard-schema/spec": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@standard-schema/spec/-/spec-1.1.0.tgz", + "integrity": "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==", + "dev": true, + "license": "MIT" + }, + "node_modules/@tybys/wasm-util": { + "version": "0.10.2", + "resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.2.tgz", + "integrity": "sha512-RoBvJ2X0wuKlWFIjrwffGw1IqZHKQqzIchKaadZZfnNpsAYp2mM0h36JtPCjNDAHGgYez/15uMBpfGwchhiMgg==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@types/chai": { + "version": "5.2.3", + "resolved": "https://registry.npmjs.org/@types/chai/-/chai-5.2.3.tgz", + "integrity": "sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/deep-eql": "*", + "assertion-error": "^2.0.1" + } + }, "node_modules/@types/cors": { "version": "2.8.19", "resolved": "https://registry.npmjs.org/@types/cors/-/cors-2.8.19.tgz", @@ -250,6 +508,20 @@ "@types/node": "*" } }, + "node_modules/@types/deep-eql": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@types/deep-eql/-/deep-eql-4.0.2.tgz", + "integrity": "sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/estree": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.9.tgz", + "integrity": "sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/node": { "version": "25.3.5", "resolved": "https://registry.npmjs.org/@types/node/-/node-25.3.5.tgz", @@ -259,18 +531,6 @@ "undici-types": "~7.18.0" } }, - "node_modules/@types/stack-trace": { - "version": "0.0.33", - "resolved": "https://registry.npmjs.org/@types/stack-trace/-/stack-trace-0.0.33.tgz", - "integrity": "sha512-O7in6531Bbvlb2KEsJ0dq0CHZvc3iWSR5ZYMtvGgnHA56VgriAN/AU2LorfmcvAl2xc9N5fbCTRyMRRl8nd74g==", - "license": "MIT" - }, - "node_modules/@types/triple-beam": { - "version": "1.3.5", - "resolved": "https://registry.npmjs.org/@types/triple-beam/-/triple-beam-1.3.5.tgz", - "integrity": "sha512-6WaYesThRMCl19iryMYP7/x2OVgCtbIVflDGFpWnb9irXI3UjYE4AzmYuiUKY1AJstGijoY+MgUszMgRxIYTYw==", - "license": "MIT" - }, "node_modules/@types/webidl-conversions": { "version": "7.0.3", "resolved": "https://registry.npmjs.org/@types/webidl-conversions/-/webidl-conversions-7.0.3.tgz", @@ -286,6 +546,119 @@ "@types/webidl-conversions": "*" } }, + "node_modules/@vitest/expect": { + "version": "4.1.6", + "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-4.1.6.tgz", + "integrity": "sha512-7EHDquPthALSV0jhhjgEW8FXaviMx7rSqu8W6oqCoAuOhKov814P99QDV1pxMA3QPv21YudvJngIhjrNI4opLg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@standard-schema/spec": "^1.1.0", + "@types/chai": "^5.2.2", + "@vitest/spy": "4.1.6", + "@vitest/utils": "4.1.6", + "chai": "^6.2.2", + "tinyrainbow": "^3.1.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/mocker": { + "version": "4.1.6", + "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-4.1.6.tgz", + "integrity": "sha512-MCFc63czMjEInOlcY2cpQCvCN+KgbAn+60xu9cMgP4sKaLC5JNAKw7JH8QdAnoAC88hW1IiSNZ+GgVXlN1UcMQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/spy": "4.1.6", + "estree-walker": "^3.0.3", + "magic-string": "^0.30.21" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "msw": "^2.4.9", + "vite": "^6.0.0 || ^7.0.0 || ^8.0.0" + }, + "peerDependenciesMeta": { + "msw": { + "optional": true + }, + "vite": { + "optional": true + } + } + }, + "node_modules/@vitest/pretty-format": { + "version": "4.1.6", + "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-4.1.6.tgz", + "integrity": "sha512-h5SxD/IzNhZYnrSZRsUZQIC+vD0GY8cUvq0iwsmkFKixRCKLLWqCXa/FIQ4S1R+sI+PGoojkHsdNrbZiM9Qpgw==", + "dev": true, + "license": "MIT", + "dependencies": { + "tinyrainbow": "^3.1.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/runner": { + "version": "4.1.6", + "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-4.1.6.tgz", + "integrity": "sha512-nOPCmn2+yD0ZNmKdsXGv/UxMMWbMuKeD6GyYncNwdkYDxpQvrPSKYj2rWuDjC2Y4b6w6hjip5dBKFzEUuZe3vA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/utils": "4.1.6", + "pathe": "^2.0.3" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/snapshot": { + "version": "4.1.6", + "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-4.1.6.tgz", + "integrity": "sha512-YhsdE6xAVfTDmzjxL2ZDUvjj+ZsgyOKe+TdQzqkD72wIOmHka8NuGQ6NpTNZv9D2Z63fbwWKJPeVpEw4EQgYxw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/pretty-format": "4.1.6", + "@vitest/utils": "4.1.6", + "magic-string": "^0.30.21", + "pathe": "^2.0.3" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/spy": { + "version": "4.1.6", + "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-4.1.6.tgz", + "integrity": "sha512-JFKxMx6udhwKh/Ldo270e17QX710vgunMkuPAvXjHSvC6oqLWAHhVhjg/I71q0u0CBSErIODV1Kjv0FQNSWjdg==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/utils": { + "version": "4.1.6", + "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-4.1.6.tgz", + "integrity": "sha512-FxIY+U81R3LGKCxaHHFRQ5+g6/iRgGLmeHWdp2Amj4ljQRrEIWHmZyDfDYBRZlpyqA7qKxtS9DD1dhk8RnRIVQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/pretty-format": "4.1.6", + "convert-source-map": "^2.0.0", + "tinyrainbow": "^3.1.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, "node_modules/abbrev": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", @@ -329,12 +702,23 @@ "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==" }, - "node_modules/async": { - "version": "3.2.6", - "resolved": "https://registry.npmjs.org/async/-/async-3.2.6.tgz", - "integrity": "sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA==", + "node_modules/asap": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/asap/-/asap-2.0.6.tgz", + "integrity": "sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA==", + "dev": true, "license": "MIT" }, + "node_modules/assertion-error": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-2.0.1.tgz", + "integrity": "sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + } + }, "node_modules/async-mutex": { "version": "0.5.0", "resolved": "https://registry.npmjs.org/async-mutex/-/async-mutex-0.5.0.tgz", @@ -345,6 +729,13 @@ "tslib": "^2.4.0" } }, + "node_modules/asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", + "dev": true, + "license": "MIT" + }, "node_modules/b4a": { "version": "1.8.1", "resolved": "https://registry.npmjs.org/b4a/-/b4a-1.8.1.tgz", @@ -375,7 +766,6 @@ "integrity": "sha512-riJjyv1/mHLIPX4RwiK+oW9/4c3TEUeORHKefKAKnZ5kyslbN+HXowtbaVEqt4IMUB7OXlfixcs6gsFeo/jhiQ==", "dev": true, "license": "Apache-2.0", - "peer": true, "peerDependencies": { "bare-abort-controller": "*" }, @@ -646,6 +1036,16 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/chai": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/chai/-/chai-6.2.2.tgz", + "integrity": "sha512-NUPRluOfOiTKBKvWPtSD4PhFvWCqOi0BGStNWs57X9js7XGTprSmFoz5F0tWhR4WPjNeR9jXqdC7/UpSJTnlRg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, "node_modules/chokidar": { "version": "3.5.3", "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz", @@ -681,50 +1081,17 @@ "node": ">=0.10.0" } }, - "node_modules/color": { - "version": "5.0.3", - "resolved": "https://registry.npmjs.org/color/-/color-5.0.3.tgz", - "integrity": "sha512-ezmVcLR3xAVp8kYOm4GS45ZLLgIE6SPAFoduLr6hTDajwb3KZ2F46gulK3XpcwRFb5KKGCSezCBAY4Dw4HsyXA==", - "license": "MIT", - "dependencies": { - "color-convert": "^3.1.3", - "color-string": "^2.1.3" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/color-convert": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-3.1.3.tgz", - "integrity": "sha512-fasDH2ont2GqF5HpyO4w0+BcewlhHEZOFn9c1ckZdHpJ56Qb7MHhH/IcJZbBGgvdtwdwNbLvxiBEdg336iA9Sg==", - "license": "MIT", - "dependencies": { - "color-name": "^2.0.0" - }, - "engines": { - "node": ">=14.6" - } - }, - "node_modules/color-name": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-2.1.0.tgz", - "integrity": "sha512-1bPaDNFm0axzE4MEAzKPuqKWeRaT43U/hyxKPBdqTfmPF+d6n7FSoTFxLVULUJOmiLp01KjhIPPH+HrXZJN4Rg==", - "license": "MIT", - "engines": { - "node": ">=12.20" - } - }, - "node_modules/color-string": { - "version": "2.1.4", - "resolved": "https://registry.npmjs.org/color-string/-/color-string-2.1.4.tgz", - "integrity": "sha512-Bb6Cq8oq0IjDOe8wJmi4JeNn763Xs9cfrBcaylK1tPypWzyoy2G3l90v9k64kjphl/ZJjPIShFztenRomi8WTg==", + "node_modules/combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "dev": true, "license": "MIT", "dependencies": { - "color-name": "^2.0.0" + "delayed-stream": "~1.0.0" }, "engines": { - "node": ">=18" + "node": ">= 0.8" } }, "node_modules/commondir": { @@ -734,43 +1101,14 @@ "dev": true, "license": "MIT" }, - "node_modules/compressible": { - "version": "2.0.18", - "resolved": "https://registry.npmjs.org/compressible/-/compressible-2.0.18.tgz", - "integrity": "sha512-AF3r7P5dWxL8MxyITRMlORQNaOA2IkAFaTr4k7BUumjPtRpGDTZpl0Pb1XCO6JeDCBdp126Cgs9sMxqSjgYyRg==", - "license": "MIT", - "dependencies": { - "mime-db": ">= 1.43.0 < 2" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/compression": { - "version": "1.8.1", - "resolved": "https://registry.npmjs.org/compression/-/compression-1.8.1.tgz", - "integrity": "sha512-9mAqGPHLakhCLeNyxPkK4xVo746zQ/czLH1Ky+vkitMnWfWZps8r0qXuwhwizagCRttsL4lfG4pIOvaWLpAP0w==", - "license": "MIT", - "dependencies": { - "bytes": "3.1.2", - "compressible": "~2.0.18", - "debug": "2.6.9", - "negotiator": "~0.6.4", - "on-headers": "~1.1.0", - "safe-buffer": "5.2.1", - "vary": "~1.1.2" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/compression/node_modules/negotiator": { - "version": "0.6.4", - "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.4.tgz", - "integrity": "sha512-myRT3DiWPHqho5PrJaIRyaMv2kgYf0mUVgBNOYMuCH5Ki1yEiQaf/ZJuQ62nvpc44wL5WDbTX7yGJi1Neevw8w==", + "node_modules/component-emitter": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.3.1.tgz", + "integrity": "sha512-T0+barUSQRTUQASh8bx02dl+DhF54GtIDY13Y3m9oWTklKbb3Wv974meRpeZ3lp1JpLVECWWNHC4vaG2XHXouQ==", + "dev": true, "license": "MIT", - "engines": { - "node": ">= 0.6" + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/content-disposition": { @@ -793,6 +1131,13 @@ "node": ">= 0.6" } }, + "node_modules/convert-source-map": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", + "dev": true, + "license": "MIT" + }, "node_modules/cookie": { "version": "0.7.2", "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz", @@ -807,6 +1152,13 @@ "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==" }, + "node_modules/cookiejar": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/cookiejar/-/cookiejar-2.1.4.tgz", + "integrity": "sha512-LDx6oHrK+PhzLKJU9j5S7/Y3jM/mUHvD/DeI1WQmJn652iPC5Y4TBzC9l+5OMOXlyTTA+SmVUPm0HQUwpD5Jqw==", + "dev": true, + "license": "MIT" + }, "node_modules/cors": { "version": "2.8.6", "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.6.tgz", @@ -841,6 +1193,16 @@ "ms": "2.0.0" } }, + "node_modules/delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.4.0" + } + }, "node_modules/depd": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", @@ -860,6 +1222,27 @@ "npm": "1.2.8000 || >= 1.4.16" } }, + "node_modules/detect-libc": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", + "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=8" + } + }, + "node_modules/dezalgo": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/dezalgo/-/dezalgo-1.0.4.tgz", + "integrity": "sha512-rXSP0bf+5n0Qonsb+SVVfNfIsimO4HEtmnIpPHY8Q1UCzKlQrDMfdobr8nJOOsRgWCyMRqeSBQzmWUMq7zvVig==", + "dev": true, + "license": "ISC", + "dependencies": { + "asap": "^2.0.0", + "wrappy": "1" + } + }, "node_modules/dotenv": { "version": "16.0.2", "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.0.2.tgz", @@ -897,12 +1280,6 @@ "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==", "license": "MIT" }, - "node_modules/enabled": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/enabled/-/enabled-2.0.0.tgz", - "integrity": "sha512-AKrN98kuwOzMIdAizXGI86UFBoo26CL21UM763y1h/GMSJ4/OHU9k2YlsmBpyScFo/wbLzWQJBMCW4+IO3/+OQ==", - "license": "MIT" - }, "node_modules/encodeurl": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", @@ -982,6 +1359,13 @@ "node": ">= 0.4" } }, + "node_modules/es-module-lexer": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-2.1.0.tgz", + "integrity": "sha512-n27zTYMjYu1aj4MjCWzSP7G9r75utsaoc8m61weK+W8JMBGGQybd43GstCXZ3WNmSFtGT9wi59qQTW6mhTR5LQ==", + "dev": true, + "license": "MIT" + }, "node_modules/es-object-atoms": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", @@ -994,11 +1378,37 @@ "node": ">= 0.4" } }, + "node_modules/es-set-tostringtag": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", + "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/escape-html": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==" }, + "node_modules/estree-walker": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz", + "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0" + } + }, "node_modules/etag": { "version": "1.8.1", "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", @@ -1018,12 +1428,21 @@ "bare-events": "^2.7.0" } }, + "node_modules/expect-type": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/expect-type/-/expect-type-1.3.0.tgz", + "integrity": "sha512-knvyeauYhqjOYvQ66MznSMs83wmHrCycNEN6Ao+2AeYEfxUIkuiVxdEa1qlGEPK+We3n0THiDciYSsCcgW/DoA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=12.0.0" + } + }, "node_modules/express": { "version": "4.22.1", "resolved": "https://registry.npmjs.org/express/-/express-4.22.1.tgz", "integrity": "sha512-F2X8g9P1X7uCPZMA3MVf9wcTqlyNp7IhH5qPCI0izhaOIYXaW9L535tGA3qmjRzpH+bZczqq7hVKxTR4NWnu+g==", "license": "MIT", - "peer": true, "dependencies": { "accepts": "~1.3.8", "array-flatten": "1.1.1", @@ -1065,24 +1484,6 @@ "url": "https://opencollective.com/express" } }, - "node_modules/express-rate-limit": { - "version": "8.5.2", - "resolved": "https://registry.npmjs.org/express-rate-limit/-/express-rate-limit-8.5.2.tgz", - "integrity": "sha512-5Kb34ipNX694DH48vN9irak1Qx30nb0PLYHXfJgw4YEjiC3ZEmZJhwOp+VfiCYwFzvFTdB9QkArYS5kXa2cx2A==", - "license": "MIT", - "dependencies": { - "ip-address": "^10.2.0" - }, - "engines": { - "node": ">= 16" - }, - "funding": { - "url": "https://github.com/sponsors/express-rate-limit" - }, - "peerDependencies": { - "express": ">= 4.11" - } - }, "node_modules/extend": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", @@ -1096,10 +1497,11 @@ "dev": true, "license": "MIT" }, - "node_modules/fecha": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/fecha/-/fecha-4.2.3.tgz", - "integrity": "sha512-OP2IUU6HeYKJi3i0z4A19kHMQoLVs4Hc+DPqqxI2h/DPZHTm/vjsfC6P0b4jCMy14XizLBqvndQ+UilD7707Jw==", + "node_modules/fast-safe-stringify": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/fast-safe-stringify/-/fast-safe-stringify-2.1.1.tgz", + "integrity": "sha512-W+KJc2dmILlPplD/H4K9l9LcAHAfPtP6BY84uVLXQ6Evcz9Lcg33Y2z1IVblT6xdY54PXYVHEv+0Wpq8Io6zkA==", + "dev": true, "license": "MIT" }, "node_modules/fetch-blob": { @@ -1187,12 +1589,6 @@ "node": ">=8" } }, - "node_modules/fn.name": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/fn.name/-/fn.name-1.1.0.tgz", - "integrity": "sha512-GRnmB5gPyJpAhTQdSZTSp9uaPSvl09KoYcMQtsB9rQoOmzs9dH6ffeccH+Z+cv6P68Hu5bC6JjRh4Ah/mHSNRw==", - "license": "MIT" - }, "node_modules/follow-redirects": { "version": "1.16.0", "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.16.0.tgz", @@ -1208,10 +1604,27 @@ "engines": { "node": ">=4.0" }, - "peerDependenciesMeta": { - "debug": { - "optional": true - } + "peerDependenciesMeta": { + "debug": { + "optional": true + } + } + }, + "node_modules/form-data": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.5.tgz", + "integrity": "sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w==", + "dev": true, + "license": "MIT", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "es-set-tostringtag": "^2.1.0", + "hasown": "^2.0.2", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 6" } }, "node_modules/formdata-polyfill": { @@ -1226,6 +1639,24 @@ "node": ">=12.20.0" } }, + "node_modules/formidable": { + "version": "3.5.4", + "resolved": "https://registry.npmjs.org/formidable/-/formidable-3.5.4.tgz", + "integrity": "sha512-YikH+7CUTOtP44ZTnUhR7Ic2UASBPOqmaRkRKxRbywPTe5VxF7RRCck4af9wutiZ/QKM5nME9Bie2fFaPz5Gug==", + "dev": true, + "license": "MIT", + "dependencies": { + "@paralleldrive/cuid2": "^2.2.2", + "dezalgo": "^1.0.4", + "once": "^1.4.0" + }, + "engines": { + "node": ">=14.0.0" + }, + "funding": { + "url": "https://ko-fi.com/tunnckoCore/commissions" + } + }, "node_modules/forwarded": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", @@ -1244,10 +1675,11 @@ } }, "node_modules/fsevents": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", - "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", "hasInstallScript": true, + "license": "MIT", "optional": true, "os": [ "darwin" @@ -1428,6 +1860,22 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/has-tostringtag": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", + "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-symbols": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/hasown": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", @@ -1440,15 +1888,6 @@ "node": ">= 0.4" } }, - "node_modules/helmet": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/helmet/-/helmet-8.1.0.tgz", - "integrity": "sha512-jOiHyAZsmnr8LqoPGmCjYAaiuWwjAPLgY8ZX2XrmHawt99/u1y6RgrZMTeoPfpUbV96HOalYgz1qzkRbw54Pmg==", - "license": "MIT", - "engines": { - "node": ">=18.0.0" - } - }, "node_modules/http-errors": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.1.tgz", @@ -1528,15 +1967,6 @@ "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", "license": "ISC" }, - "node_modules/ip-address": { - "version": "10.2.0", - "resolved": "https://registry.npmjs.org/ip-address/-/ip-address-10.2.0.tgz", - "integrity": "sha512-/+S6j4E9AHvW9SWMSEY9Xfy66O5PWvVEJ08O0y5JGyEKQpojb0K0GKpz/v5HJ/G0vi3D2sjGK78119oXZeE0qA==", - "license": "MIT", - "engines": { - "node": ">= 12" - } - }, "node_modules/ipaddr.js": { "version": "1.9.1", "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", @@ -1584,18 +2014,6 @@ "node": ">=0.12.0" } }, - "node_modules/is-stream": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", - "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", - "license": "MIT", - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/json-bigint": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/json-bigint/-/json-bigint-1.0.0.tgz", @@ -1662,11 +2080,266 @@ "node": ">=18.0.0" } }, - "node_modules/kuler": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/kuler/-/kuler-2.0.0.tgz", - "integrity": "sha512-Xq9nH7KlWZmXAtodXDDRE7vs6DU1gTU8zYDHDiWLSip45Egwq3plLHzPn27NgvzL2r1LMPC1vdqh98sQxtqj4A==", - "license": "MIT" + "node_modules/lightningcss": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.32.0.tgz", + "integrity": "sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ==", + "dev": true, + "license": "MPL-2.0", + "dependencies": { + "detect-libc": "^2.0.3" + }, + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + }, + "optionalDependencies": { + "lightningcss-android-arm64": "1.32.0", + "lightningcss-darwin-arm64": "1.32.0", + "lightningcss-darwin-x64": "1.32.0", + "lightningcss-freebsd-x64": "1.32.0", + "lightningcss-linux-arm-gnueabihf": "1.32.0", + "lightningcss-linux-arm64-gnu": "1.32.0", + "lightningcss-linux-arm64-musl": "1.32.0", + "lightningcss-linux-x64-gnu": "1.32.0", + "lightningcss-linux-x64-musl": "1.32.0", + "lightningcss-win32-arm64-msvc": "1.32.0", + "lightningcss-win32-x64-msvc": "1.32.0" + } + }, + "node_modules/lightningcss-android-arm64": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-android-arm64/-/lightningcss-android-arm64-1.32.0.tgz", + "integrity": "sha512-YK7/ClTt4kAK0vo6w3X+Pnm0D2cf2vPHbhOXdoNti1Ga0al1P4TBZhwjATvjNwLEBCnKvjJc2jQgHXH0NEwlAg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-darwin-arm64": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.32.0.tgz", + "integrity": "sha512-RzeG9Ju5bag2Bv1/lwlVJvBE3q6TtXskdZLLCyfg5pt+HLz9BqlICO7LZM7VHNTTn/5PRhHFBSjk5lc4cmscPQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-darwin-x64": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.32.0.tgz", + "integrity": "sha512-U+QsBp2m/s2wqpUYT/6wnlagdZbtZdndSmut/NJqlCcMLTWp5muCrID+K5UJ6jqD2BFshejCYXniPDbNh73V8w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-freebsd-x64": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.32.0.tgz", + "integrity": "sha512-JCTigedEksZk3tHTTthnMdVfGf61Fky8Ji2E4YjUTEQX14xiy/lTzXnu1vwiZe3bYe0q+SpsSH/CTeDXK6WHig==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm-gnueabihf": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.32.0.tgz", + "integrity": "sha512-x6rnnpRa2GL0zQOkt6rts3YDPzduLpWvwAF6EMhXFVZXD4tPrBkEFqzGowzCsIWsPjqSK+tyNEODUBXeeVHSkw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm64-gnu": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.32.0.tgz", + "integrity": "sha512-0nnMyoyOLRJXfbMOilaSRcLH3Jw5z9HDNGfT/gwCPgaDjnx0i8w7vBzFLFR1f6CMLKF8gVbebmkUN3fa/kQJpQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm64-musl": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.32.0.tgz", + "integrity": "sha512-UpQkoenr4UJEzgVIYpI80lDFvRmPVg6oqboNHfoH4CQIfNA+HOrZ7Mo7KZP02dC6LjghPQJeBsvXhJod/wnIBg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-x64-gnu": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.32.0.tgz", + "integrity": "sha512-V7Qr52IhZmdKPVr+Vtw8o+WLsQJYCTd8loIfpDaMRWGUZfBOYEJeyJIkqGIDMZPwPx24pUMfwSxxI8phr/MbOA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-x64-musl": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.32.0.tgz", + "integrity": "sha512-bYcLp+Vb0awsiXg/80uCRezCYHNg1/l3mt0gzHnWV9XP1W5sKa5/TCdGWaR/zBM2PeF/HbsQv/j2URNOiVuxWg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-win32-arm64-msvc": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.32.0.tgz", + "integrity": "sha512-8SbC8BR40pS6baCM8sbtYDSwEVQd4JlFTOlaD3gWGHfThTcABnNDBda6eTZeqbofalIJhFx0qKzgHJmcPTnGdw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-win32-x64-msvc": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.32.0.tgz", + "integrity": "sha512-Amq9B/SoZYdDi1kFrojnoqPLxYhQ4Wo5XiL8EVJrVsB8ARoC1PWW6VGtT0WKCemjy8aC+louJnjS7U18x3b06Q==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } }, "node_modules/locate-path": { "version": "5.0.0", @@ -1716,29 +2389,16 @@ "resolved": "https://registry.npmjs.org/lodash.once/-/lodash.once-4.1.1.tgz", "integrity": "sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg==" }, - "node_modules/logform": { - "version": "2.7.0", - "resolved": "https://registry.npmjs.org/logform/-/logform-2.7.0.tgz", - "integrity": "sha512-TFYA4jnP7PVbmlBIfhlSe+WKxs9dklXMTEGcBCIvLhE/Tn3H6Gk1norupVW7m5Cnd4bLcr08AytbyV/xj7f/kQ==", + "node_modules/magic-string": { + "version": "0.30.21", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz", + "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==", + "dev": true, "license": "MIT", "dependencies": { - "@colors/colors": "1.6.0", - "@types/triple-beam": "^1.3.2", - "fecha": "^4.2.0", - "ms": "^2.1.1", - "safe-stable-stringify": "^2.3.1", - "triple-beam": "^1.3.0" - }, - "engines": { - "node": ">= 12.0.0" + "@jridgewell/sourcemap-codec": "^1.5.5" } }, - "node_modules/logform/node_modules/ms": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", - "license": "MIT" - }, "node_modules/make-dir": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", @@ -2275,6 +2935,17 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/obug": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/obug/-/obug-2.1.1.tgz", + "integrity": "sha512-uTqF9MuPraAQ+IsnPf366RG4cP9RtUi7MLO1N3KEc+wb0a6yKpeL0lmk2IB1jY5KHPAlTc6T/JRdC/YqxHNwkQ==", + "dev": true, + "funding": [ + "https://github.com/sponsors/sxzz", + "https://opencollective.com/debug" + ], + "license": "MIT" + }, "node_modules/on-finished": { "version": "2.4.1", "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", @@ -2287,22 +2958,14 @@ "node": ">= 0.8" } }, - "node_modules/on-headers": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.1.0.tgz", - "integrity": "sha512-737ZY3yNnXy37FHkQxPzt4UZ2UWPWiCZWLvFZ4fu5cueciegX0zGPnrlY6bwRg4FdQOe9YU8MkmJwGhoMybl8A==", - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/one-time": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/one-time/-/one-time-1.0.0.tgz", - "integrity": "sha512-5DXOiRKwuSEcQ/l0kGCF6Q3jcADFv5tSmRaJck/OqkVFcOzutB134KRSfF0xDrL39MNnqxbHBbUUcjZIhTgb2g==", - "license": "MIT", + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "dev": true, + "license": "ISC", "dependencies": { - "fn.name": "1.x.x" + "wrappy": "1" } }, "node_modules/p-limit": { @@ -2368,6 +3031,13 @@ "integrity": "sha512-RA1GjUVMnvYFxuqovrEqZoxxW5NUZqbwKtYz/Tt7nXerk0LbLblQmrsgdeOxV5SFHf0UDggjS/bSeOZwt1pmEQ==", "license": "MIT" }, + "node_modules/pathe": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz", + "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==", + "dev": true, + "license": "MIT" + }, "node_modules/pend": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/pend/-/pend-1.2.0.tgz", @@ -2375,6 +3045,13 @@ "dev": true, "license": "MIT" }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "dev": true, + "license": "ISC" + }, "node_modules/picomatch": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", @@ -2392,11 +3069,59 @@ "integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==", "dev": true, "license": "MIT", - "dependencies": { - "find-up": "^4.0.0" + "dependencies": { + "find-up": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/postcss": { + "version": "8.5.15", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.15.tgz", + "integrity": "sha512-FfR8sjd4em2T6fb3I2MwAJU7HWVMr9zba+enmQeeWFfCbm+UOC/0X4DS8XtpUTMwWMGbjKYP7xjfNekzyGmB3A==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.12", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/postcss/node_modules/nanoid": { + "version": "3.3.12", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.12.tgz", + "integrity": "sha512-ZB9RH/39qpq5Vu6Y+NmUaFhQR6pp+M2Xt76XBnEwDaGcVAqhlvxrl3B2bKS5D3NH3QR76v3aSrKaF/Kiy7lEtQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" }, "engines": { - "node": ">=8" + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" } }, "node_modules/proxy-addr": { @@ -2463,20 +3188,6 @@ "node": ">= 0.8" } }, - "node_modules/readable-stream": { - "version": "3.6.2", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", - "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", - "license": "MIT", - "dependencies": { - "inherits": "^2.0.3", - "string_decoder": "^1.1.1", - "util-deprecate": "^1.0.1" - }, - "engines": { - "node": ">= 6" - } - }, "node_modules/readdirp": { "version": "3.6.0", "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", @@ -2504,6 +3215,40 @@ "node": ">= 18" } }, + "node_modules/rolldown": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.0.1.tgz", + "integrity": "sha512-X0KQHljNnEkWNqqiz9zJrGunh1B0HgOxLXvnFpCOcadzcy5qohZ3tqMEUg00vncoRovXuK3ZqCT9KnnKzoInFQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@oxc-project/types": "=0.130.0", + "@rolldown/pluginutils": "^1.0.0" + }, + "bin": { + "rolldown": "bin/cli.mjs" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "optionalDependencies": { + "@rolldown/binding-android-arm64": "1.0.1", + "@rolldown/binding-darwin-arm64": "1.0.1", + "@rolldown/binding-darwin-x64": "1.0.1", + "@rolldown/binding-freebsd-x64": "1.0.1", + "@rolldown/binding-linux-arm-gnueabihf": "1.0.1", + "@rolldown/binding-linux-arm64-gnu": "1.0.1", + "@rolldown/binding-linux-arm64-musl": "1.0.1", + "@rolldown/binding-linux-ppc64-gnu": "1.0.1", + "@rolldown/binding-linux-s390x-gnu": "1.0.1", + "@rolldown/binding-linux-x64-gnu": "1.0.1", + "@rolldown/binding-linux-x64-musl": "1.0.1", + "@rolldown/binding-openharmony-arm64": "1.0.1", + "@rolldown/binding-wasm32-wasi": "1.0.1", + "@rolldown/binding-win32-arm64-msvc": "1.0.1", + "@rolldown/binding-win32-x64-msvc": "1.0.1" + } + }, "node_modules/safe-buffer": { "version": "5.2.1", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", @@ -2523,15 +3268,6 @@ } ] }, - "node_modules/safe-stable-stringify": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/safe-stable-stringify/-/safe-stable-stringify-2.5.0.tgz", - "integrity": "sha512-b3rppTKm9T+PsVCBEOUR46GWI7fdOs00VKZ1+9c1EWDaDMvjQc6tUwuFyIprgGgTcWoVHSKrU8H31ZHA2e0RHA==", - "license": "MIT", - "engines": { - "node": ">=10" - } - }, "node_modules/safer-buffer": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", @@ -2580,21 +3316,6 @@ "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", "license": "MIT" }, - "node_modules/serialize-error": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/serialize-error/-/serialize-error-8.1.0.tgz", - "integrity": "sha512-3NnuWfM6vBYoy5gZFvHiYsVbafvI9vZv/+jlIigFn4oP4zjNPK3LhcY0xSCgeb1a5L8jO71Mit9LlNoi2UfDDQ==", - "license": "MIT", - "dependencies": { - "type-fest": "^0.20.2" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/serve-static": { "version": "1.16.3", "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.16.3.tgz", @@ -2721,6 +3442,13 @@ "integrity": "sha512-Rtlj66/b0ICeFzYTuNvX/EF1igRbbnGSvEyT79McoZa/DeGhMyC5pWKOEsZKnpkqtSeovd5FL/bjHWC3CIIvCQ==", "license": "MIT" }, + "node_modules/siginfo": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/siginfo/-/siginfo-2.0.0.tgz", + "integrity": "sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==", + "dev": true, + "license": "ISC" + }, "node_modules/simple-update-notifier": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/simple-update-notifier/-/simple-update-notifier-2.0.0.tgz", @@ -2843,6 +3571,16 @@ "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", "license": "MIT" }, + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/sparse-bitfield": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/sparse-bitfield/-/sparse-bitfield-3.0.3.tgz", @@ -2852,14 +3590,12 @@ "memory-pager": "^1.0.2" } }, - "node_modules/stack-trace": { - "version": "0.0.10", - "resolved": "https://registry.npmjs.org/stack-trace/-/stack-trace-0.0.10.tgz", - "integrity": "sha512-KGzahc7puUKkzyMt+IqAep+TVNbKP+k2Lmwhub39m1AsTSkaDutx56aDCo+HLDzf/D26BIHTJWNiTG1KAJiQCg==", - "license": "MIT", - "engines": { - "node": "*" - } + "node_modules/stackback": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/stackback/-/stackback-0.0.2.tgz", + "integrity": "sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==", + "dev": true, + "license": "MIT" }, "node_modules/statuses": { "version": "2.0.2", @@ -2870,6 +3606,13 @@ "node": ">= 0.8" } }, + "node_modules/std-env": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/std-env/-/std-env-4.1.0.tgz", + "integrity": "sha512-Rq7ybcX2RuC55r9oaPVEW7/xu3tj8u4GeBYHBWCychFtzMIr86A7e3PPEBPT37sHStKX3+TiX/Fr/ACmJLVlLQ==", + "dev": true, + "license": "MIT" + }, "node_modules/streamx": { "version": "2.25.0", "resolved": "https://registry.npmjs.org/streamx/-/streamx-2.25.0.tgz", @@ -2882,13 +3625,88 @@ "text-decoder": "^1.1.0" } }, - "node_modules/string_decoder": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", - "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "node_modules/superagent": { + "version": "10.3.0", + "resolved": "https://registry.npmjs.org/superagent/-/superagent-10.3.0.tgz", + "integrity": "sha512-B+4Ik7ROgVKrQsXTV0Jwp2u+PXYLSlqtDAhYnkkD+zn3yg8s/zjA2MeGayPoY/KICrbitwneDHrjSotxKL+0XQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "component-emitter": "^1.3.1", + "cookiejar": "^2.1.4", + "debug": "^4.3.7", + "fast-safe-stringify": "^2.1.1", + "form-data": "^4.0.5", + "formidable": "^3.5.4", + "methods": "^1.1.2", + "mime": "2.6.0", + "qs": "^6.14.1" + }, + "engines": { + "node": ">=14.18.0" + } + }, + "node_modules/superagent/node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/superagent/node_modules/mime": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-2.6.0.tgz", + "integrity": "sha512-USPkMeET31rOMiarsBNIHZKLGgvKc/LrjofAnBlOttf5ajRvqiRA8QsenbcooctK6d6Ts6aqZXBA+XbkKthiQg==", + "dev": true, + "license": "MIT", + "bin": { + "mime": "cli.js" + }, + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/superagent/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true, + "license": "MIT" + }, + "node_modules/supertest": { + "version": "7.2.2", + "resolved": "https://registry.npmjs.org/supertest/-/supertest-7.2.2.tgz", + "integrity": "sha512-oK8WG9diS3DlhdUkcFn4tkNIiIbBx9lI2ClF8K+b2/m8Eyv47LSawxUzZQSNKUrVb2KsqeTDCcjAAVPYaSLVTA==", + "dev": true, "license": "MIT", "dependencies": { - "safe-buffer": "~5.2.0" + "cookie-signature": "^1.2.2", + "methods": "^1.1.2", + "superagent": "^10.3.0" + }, + "engines": { + "node": ">=14.18.0" + } + }, + "node_modules/supertest/node_modules/cookie-signature": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.2.2.tgz", + "integrity": "sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.6.0" } }, "node_modules/supports-color": { @@ -2935,12 +3753,81 @@ "b4a": "^1.6.4" } }, - "node_modules/text-hex": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/text-hex/-/text-hex-1.0.0.tgz", - "integrity": "sha512-uuVGNWzgJ4yhRaNSiubPY7OjISw4sw4E5Uv0wbjp+OzcbmVU/rsT8ujgcXJhn9ypzsgr5vlzpPqP+MBBKcGvbg==", + "node_modules/tinybench": { + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/tinybench/-/tinybench-2.9.0.tgz", + "integrity": "sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==", + "dev": true, "license": "MIT" }, + "node_modules/tinyexec": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-1.1.2.tgz", + "integrity": "sha512-dAqSqE/RabpBKI8+h26GfLq6Vb3JVXs30XYQjdMjaj/c2tS8IYYMbIzP599KtRj7c57/wYApb3QjgRgXmrCukA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/tinyglobby": { + "version": "0.2.16", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.16.tgz", + "integrity": "sha512-pn99VhoACYR8nFHhxqix+uvsbXineAasWm5ojXoN8xEwK5Kd3/TrhNn1wByuD52UxWRLy8pu+kRMniEi6Eq9Zg==", + "dev": true, + "license": "MIT", + "dependencies": { + "fdir": "^6.5.0", + "picomatch": "^4.0.4" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" + } + }, + "node_modules/tinyglobby/node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/tinyglobby/node_modules/picomatch": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", + "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/tinyrainbow": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/tinyrainbow/-/tinyrainbow-3.1.0.tgz", + "integrity": "sha512-Bf+ILmBgretUrdJxzXM0SgXLZ3XfiaUuOj/IKQHuTXip+05Xn+uyEYdVg0kYDipTBcLrCVyUzAPz7QmArb0mmw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.0.0" + } + }, "node_modules/to-regex-range": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", @@ -2985,15 +3872,6 @@ "node": ">=18" } }, - "node_modules/triple-beam": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/triple-beam/-/triple-beam-1.4.1.tgz", - "integrity": "sha512-aZbgViZrg1QNcG+LULa7nhZpJTZSLm/mXnHXnbAbjmN5aSa0y7V+wvv6+4WaBtpISJzThKy+PIPxc1Nq1EJ9mg==", - "license": "MIT", - "engines": { - "node": ">= 14.0.0" - } - }, "node_modules/tslib": { "version": "2.8.1", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", @@ -3001,77 +3879,253 @@ "dev": true, "license": "0BSD" }, - "node_modules/type-fest": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", - "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", - "license": "(MIT OR CC0-1.0)", + "node_modules/type-is": { + "version": "1.6.18", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", + "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", + "license": "MIT", + "dependencies": { + "media-typer": "0.3.0", + "mime-types": "~2.1.24" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/undefsafe": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/undefsafe/-/undefsafe-2.0.5.tgz", + "integrity": "sha512-WxONCrssBM8TSPRqN5EmsjVrsv4A8X12J4ArBiiayv3DyyG3ZlIg6yysuuSYdZsVz3TKcTg2fd//Ujd4CHV1iA==" + }, + "node_modules/undici-types": { + "version": "7.18.2", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.18.2.tgz", + "integrity": "sha512-AsuCzffGHJybSaRrmr5eHr81mwJU3kjw6M+uprWvCXiNeN9SOGwQ3Jn8jb8m3Z6izVgknn1R0FTCEAP2QrLY/w==", + "license": "MIT" + }, + "node_modules/unpipe": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", + "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/url-template": { + "version": "2.0.8", + "resolved": "https://registry.npmjs.org/url-template/-/url-template-2.0.8.tgz", + "integrity": "sha512-XdVKMF4SJ0nP/O7XIPB0JwAEuT9lDIYnNsK8yGVe43y0AWoKeJNdv3ZNWh7ksJ6KqQFjOO6ox/VEitLnaVNufw==", + "license": "BSD" + }, + "node_modules/utils-merge": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", + "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==", + "engines": { + "node": ">= 0.4.0" + } + }, + "node_modules/vary": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", + "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/vite": { + "version": "8.0.13", + "resolved": "https://registry.npmjs.org/vite/-/vite-8.0.13.tgz", + "integrity": "sha512-MFtjBYgzmSxmgA4RAfjIyXWpGe1oALnjgUTzzV7QLx/TKxCzjtMH6Fd9/eVK+5Fg1qNoz5VAwsmMs/NofrmJvw==", + "dev": true, + "license": "MIT", + "dependencies": { + "lightningcss": "^1.32.0", + "picomatch": "^4.0.4", + "postcss": "^8.5.14", + "rolldown": "1.0.1", + "tinyglobby": "^0.2.16" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^20.19.0 || >=22.12.0", + "@vitejs/devtools": "^0.1.18", + "esbuild": "^0.27.0 || ^0.28.0", + "jiti": ">=1.21.0", + "less": "^4.0.0", + "sass": "^1.70.0", + "sass-embedded": "^1.70.0", + "stylus": ">=0.54.8", + "sugarss": "^5.0.0", + "terser": "^5.16.0", + "tsx": "^4.8.1", + "yaml": "^2.4.2" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "@vitejs/devtools": { + "optional": true + }, + "esbuild": { + "optional": true + }, + "jiti": { + "optional": true + }, + "less": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + }, + "tsx": { + "optional": true + }, + "yaml": { + "optional": true + } + } + }, + "node_modules/vite/node_modules/picomatch": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", + "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", + "dev": true, + "license": "MIT", "engines": { - "node": ">=10" + "node": ">=12" }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "url": "https://github.com/sponsors/jonschlinkert" } }, - "node_modules/type-is": { - "version": "1.6.18", - "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", - "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", + "node_modules/vitest": { + "version": "4.1.6", + "resolved": "https://registry.npmjs.org/vitest/-/vitest-4.1.6.tgz", + "integrity": "sha512-6lvjbS3p9b4CrdCmguzbh2/4uoXhGE2q71R4OX5sqF9R1bo9Xd6fGrMAfvp5wnCzlBnFVdCOp6onuTQVbo8iUQ==", + "dev": true, "license": "MIT", "dependencies": { - "media-typer": "0.3.0", - "mime-types": "~2.1.24" + "@vitest/expect": "4.1.6", + "@vitest/mocker": "4.1.6", + "@vitest/pretty-format": "4.1.6", + "@vitest/runner": "4.1.6", + "@vitest/snapshot": "4.1.6", + "@vitest/spy": "4.1.6", + "@vitest/utils": "4.1.6", + "es-module-lexer": "^2.0.0", + "expect-type": "^1.3.0", + "magic-string": "^0.30.21", + "obug": "^2.1.1", + "pathe": "^2.0.3", + "picomatch": "^4.0.3", + "std-env": "^4.0.0-rc.1", + "tinybench": "^2.9.0", + "tinyexec": "^1.0.2", + "tinyglobby": "^0.2.15", + "tinyrainbow": "^3.1.0", + "vite": "^6.0.0 || ^7.0.0 || ^8.0.0", + "why-is-node-running": "^2.3.0" + }, + "bin": { + "vitest": "vitest.mjs" }, "engines": { - "node": ">= 0.6" + "node": "^20.0.0 || ^22.0.0 || >=24.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "@edge-runtime/vm": "*", + "@opentelemetry/api": "^1.9.0", + "@types/node": "^20.0.0 || ^22.0.0 || >=24.0.0", + "@vitest/browser-playwright": "4.1.6", + "@vitest/browser-preview": "4.1.6", + "@vitest/browser-webdriverio": "4.1.6", + "@vitest/coverage-istanbul": "4.1.6", + "@vitest/coverage-v8": "4.1.6", + "@vitest/ui": "4.1.6", + "happy-dom": "*", + "jsdom": "*", + "vite": "^6.0.0 || ^7.0.0 || ^8.0.0" + }, + "peerDependenciesMeta": { + "@edge-runtime/vm": { + "optional": true + }, + "@opentelemetry/api": { + "optional": true + }, + "@types/node": { + "optional": true + }, + "@vitest/browser-playwright": { + "optional": true + }, + "@vitest/browser-preview": { + "optional": true + }, + "@vitest/browser-webdriverio": { + "optional": true + }, + "@vitest/coverage-istanbul": { + "optional": true + }, + "@vitest/coverage-v8": { + "optional": true + }, + "@vitest/ui": { + "optional": true + }, + "happy-dom": { + "optional": true + }, + "jsdom": { + "optional": true + }, + "vite": { + "optional": false + } } }, - "node_modules/undefsafe": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/undefsafe/-/undefsafe-2.0.5.tgz", - "integrity": "sha512-WxONCrssBM8TSPRqN5EmsjVrsv4A8X12J4ArBiiayv3DyyG3ZlIg6yysuuSYdZsVz3TKcTg2fd//Ujd4CHV1iA==" - }, - "node_modules/undici-types": { - "version": "7.18.2", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.18.2.tgz", - "integrity": "sha512-AsuCzffGHJybSaRrmr5eHr81mwJU3kjw6M+uprWvCXiNeN9SOGwQ3Jn8jb8m3Z6izVgknn1R0FTCEAP2QrLY/w==", - "license": "MIT" - }, - "node_modules/unpipe": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", - "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", + "node_modules/vitest/node_modules/picomatch": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", + "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", + "dev": true, "license": "MIT", "engines": { - "node": ">= 0.8" - } - }, - "node_modules/url-template": { - "version": "2.0.8", - "resolved": "https://registry.npmjs.org/url-template/-/url-template-2.0.8.tgz", - "integrity": "sha512-XdVKMF4SJ0nP/O7XIPB0JwAEuT9lDIYnNsK8yGVe43y0AWoKeJNdv3ZNWh7ksJ6KqQFjOO6ox/VEitLnaVNufw==", - "license": "BSD" - }, - "node_modules/util-deprecate": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", - "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", - "license": "MIT" - }, - "node_modules/utils-merge": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", - "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==", - "engines": { - "node": ">= 0.4.0" - } - }, - "node_modules/vary": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", - "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", - "engines": { - "node": ">= 0.8" + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" } }, "node_modules/web-streams-polyfill": { @@ -3105,43 +4159,30 @@ "node": ">=18" } }, - "node_modules/winston": { - "version": "3.19.0", - "resolved": "https://registry.npmjs.org/winston/-/winston-3.19.0.tgz", - "integrity": "sha512-LZNJgPzfKR+/J3cHkxcpHKpKKvGfDZVPS4hfJCc4cCG0CgYzvlD6yE/S3CIL/Yt91ak327YCpiF/0MyeZHEHKA==", + "node_modules/why-is-node-running": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/why-is-node-running/-/why-is-node-running-2.3.0.tgz", + "integrity": "sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==", + "dev": true, "license": "MIT", - "peer": true, "dependencies": { - "@colors/colors": "^1.6.0", - "@dabh/diagnostics": "^2.0.8", - "async": "^3.2.3", - "is-stream": "^2.0.0", - "logform": "^2.7.0", - "one-time": "^1.0.0", - "readable-stream": "^3.4.0", - "safe-stable-stringify": "^2.3.1", - "stack-trace": "0.0.x", - "triple-beam": "^1.3.0", - "winston-transport": "^4.9.0" + "siginfo": "^2.0.0", + "stackback": "0.0.2" }, - "engines": { - "node": ">= 12.0.0" - } - }, - "node_modules/winston-transport": { - "version": "4.9.0", - "resolved": "https://registry.npmjs.org/winston-transport/-/winston-transport-4.9.0.tgz", - "integrity": "sha512-8drMJ4rkgaPo1Me4zD/3WLfI/zPdA9o2IipKODunnGDcuqbHwjsbB79ylv04LCGGzU0xQ6vTznOMpQGaLhhm6A==", - "license": "MIT", - "dependencies": { - "logform": "^2.7.0", - "readable-stream": "^3.6.2", - "triple-beam": "^1.3.0" + "bin": { + "why-is-node-running": "cli.js" }, "engines": { - "node": ">= 12.0.0" + "node": ">=8" } }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "dev": true, + "license": "ISC" + }, "node_modules/ws": { "version": "8.18.3", "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.3.tgz", @@ -3179,89 +4220,42 @@ } }, "dependencies": { - "@colors/colors": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/@colors/colors/-/colors-1.6.0.tgz", - "integrity": "sha512-Ir+AOibqzrIsL6ajt3Rz3LskB7OiMVHqltZmspbW/TJuTVuyOMirVqAkjfY6JISiLHgyNqicAC8AyHHGzNd/dA==" - }, - "@dabh/diagnostics": { - "version": "2.0.8", - "resolved": "https://registry.npmjs.org/@dabh/diagnostics/-/diagnostics-2.0.8.tgz", - "integrity": "sha512-R4MSXTVnuMzGD7bzHdW2ZhhdPC/igELENcq5IjEverBvq5hn1SXCWcsi6eSsdWP0/Ur+SItRRjAktmdoX/8R/Q==", - "requires": { - "@so-ric/colorspace": "^1.1.6", - "enabled": "2.0.x", - "kuler": "^2.0.0" - } - }, - "@logtail/core": { - "version": "0.5.8", - "resolved": "https://registry.npmjs.org/@logtail/core/-/core-0.5.8.tgz", - "integrity": "sha512-1LxjI46LCXeVRyENwjO4s0a1CWSIqHjYH6w2YVNhQLc5b7mVN0jhzxvmg9mps54aEuCSuK2SVCNmgvuNTDtAug==", + "@emnapi/core": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.10.0.tgz", + "integrity": "sha512-yq6OkJ4p82CAfPl0u9mQebQHKPJkY7WrIuk205cTYnYe+k2Z8YBh11FrbRG/H6ihirqcacOgl2BIO8oyMQLeXw==", + "dev": true, + "optional": true, "requires": { - "@logtail/tools": "^0.5.8", - "@logtail/types": "^0.5.8", - "serialize-error": "8.1.0" + "@emnapi/wasi-threads": "1.2.1", + "tslib": "^2.4.0" } }, - "@logtail/node": { - "version": "0.5.8", - "resolved": "https://registry.npmjs.org/@logtail/node/-/node-0.5.8.tgz", - "integrity": "sha512-9XMxeToOL1QHVNp7IQlPxbRsu3iYUBtXl/SFCyOQQmtnwzS7z+tbiV39DYkXfwkzig6wjwTfLYDQj673Bqplqg==", + "@emnapi/runtime": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.10.0.tgz", + "integrity": "sha512-ewvYlk86xUoGI0zQRNq/mC+16R1QeDlKQy21Ki3oSYXNgLb45GV1P6A0M+/s6nyCuNDqe5VpaY84BzXGwVbwFA==", + "dev": true, + "optional": true, "requires": { - "@logtail/core": "^0.5.8", - "@logtail/types": "^0.5.8", - "@msgpack/msgpack": "^2.5.1", - "@types/stack-trace": "^0.0.33", - "minimatch": "^9.0.5", - "stack-trace": "0.0.10" - }, - "dependencies": { - "balanced-match": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", - "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==" - }, - "brace-expansion": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.0.tgz", - "integrity": "sha512-TN1kCZAgdgweJhWWpgKYrQaMNHcDULHkWwQIspdtjV4Y5aurRdZpjAqn6yX3FPqTA9ngHCc4hJxMAMgGfve85w==", - "requires": { - "balanced-match": "^1.0.0" - } - }, - "minimatch": { - "version": "9.0.9", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.9.tgz", - "integrity": "sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg==", - "requires": { - "brace-expansion": "^2.0.2" - } - } + "tslib": "^2.4.0" } }, - "@logtail/tools": { - "version": "0.5.8", - "resolved": "https://registry.npmjs.org/@logtail/tools/-/tools-0.5.8.tgz", - "integrity": "sha512-eAbbHaYSpRoB7Udp301xVUVDeKdgXTP2/s9FOeKX5tPLMM3hgbvuIx50bYJoV/to7bHGYBi36Gdsnj8ElcPXbg==", + "@emnapi/wasi-threads": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.2.1.tgz", + "integrity": "sha512-uTII7OYF+/Mes/MrcIOYp5yOtSMLBWSIoLPpcgwipoiKbli6k322tcoFsxoIIxPDqW01SQGAgko4EzZi2BNv2w==", + "dev": true, + "optional": true, "requires": { - "@logtail/types": "^0.5.8" + "tslib": "^2.4.0" } }, - "@logtail/types": { - "version": "0.5.8", - "resolved": "https://registry.npmjs.org/@logtail/types/-/types-0.5.8.tgz", - "integrity": "sha512-CJIijD/2vqK8XwPRZJeUkiiBEhKTviHs5p1WI40WvW21kU18QiQ8PupFJ2uhrItxhwtlkXmRXul5+cNtv3cAFQ==" - }, - "@logtail/winston": { - "version": "0.5.8", - "resolved": "https://registry.npmjs.org/@logtail/winston/-/winston-0.5.8.tgz", - "integrity": "sha512-nL08rAGiKJC4blEBfe4z8sYN4d3G9FCWKlsp8zjcsu42Bj7345hHsgyEfftnhhPWyfmF+5q+i1XucQhjNRSbYQ==", - "requires": { - "@logtail/node": "^0.5.8", - "@logtail/types": "^0.5.8", - "winston-transport": "^4.3.0" - } + "@jridgewell/sourcemap-codec": { + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", + "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", + "dev": true }, "@mongodb-js/saslprep": { "version": "1.4.6", @@ -3271,10 +4265,36 @@ "sparse-bitfield": "^3.0.3" } }, - "@msgpack/msgpack": { - "version": "2.8.0", - "resolved": "https://registry.npmjs.org/@msgpack/msgpack/-/msgpack-2.8.0.tgz", - "integrity": "sha512-h9u4u/jiIRKbq25PM+zymTyW6bhTzELvOoUd+AvYriWOAKpLGnIamaET3pnHYoI5iYphAHBI4ayx0MehR+VVPQ==" + "@napi-rs/wasm-runtime": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-1.1.4.tgz", + "integrity": "sha512-3NQNNgA1YSlJb/kMH1ildASP9HW7/7kYnRI2szWJaofaS1hWmbGI4H+d3+22aGzXXN9IJ+n+GiFVcGipJP18ow==", + "dev": true, + "optional": true, + "requires": { + "@tybys/wasm-util": "^0.10.1" + } + }, + "@noble/hashes": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.8.0.tgz", + "integrity": "sha512-jCs9ldd7NwzpgXDIf6P3+NrHh9/sD6CQdxHyjQI+h/6rDNo88ypBxxz45UDuZHz9r3tNz7N/VInSVoVdtXEI4A==", + "dev": true + }, + "@oxc-project/types": { + "version": "0.130.0", + "resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.130.0.tgz", + "integrity": "sha512-ibD2usx9JRu7f5pu2tMKMI4cpA4NgXJQoYRP4pQ7Pxmn1l6k/53qWtQWZayhYy3X4QZkt90Ot+mJEaeXouio6Q==", + "dev": true + }, + "@paralleldrive/cuid2": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/@paralleldrive/cuid2/-/cuid2-2.3.1.tgz", + "integrity": "sha512-XO7cAxhnTZl0Yggq6jOgjiOHhbgcO4NqFqwSmQpjK3b6TEE6Uj/jfSk6wzYyemh3+I0sHirKSetjQwn5cZktFw==", + "dev": true, + "requires": { + "@noble/hashes": "^1.1.5" + } }, "@redis/bloom": { "version": "5.11.0", @@ -3286,7 +4306,6 @@ "version": "5.11.0", "resolved": "https://registry.npmjs.org/@redis/client/-/client-5.11.0.tgz", "integrity": "sha512-GHoprlNQD51Xq2Ztd94HHV94MdFZQ3CVrpA04Fz8MVoHM0B7SlbmPEVIjwTbcv58z8QyjnrOuikS0rWF03k5dQ==", - "peer": true, "requires": { "cluster-key-slot": "1.1.2" } @@ -3309,20 +4328,153 @@ "integrity": "sha512-TWFeOcU4xkj0DkndnOyhtxvX1KWD+78UHT3XX3x3XRBUGWeQrKo3jqzDsZwxbggUgf9yLJr/akFHXru66X5UQA==", "requires": {} }, - "@so-ric/colorspace": { - "version": "1.1.6", - "resolved": "https://registry.npmjs.org/@so-ric/colorspace/-/colorspace-1.1.6.tgz", - "integrity": "sha512-/KiKkpHNOBgkFJwu9sh48LkHSMYGyuTcSFK/qMBdnOAlrRJzRSXAOFB5qwzaVQuDl8wAvHVMkaASQDReTahxuw==", + "@rolldown/binding-android-arm64": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.0.1.tgz", + "integrity": "sha512-fJI3I0r3C3Oj/zdBCpaCmBRZYf07xpaq4yCfDDoSFm+beWNzbIl26puW8RraUdugoJw/95zerNOn6jasAhzSmg==", + "dev": true, + "optional": true + }, + "@rolldown/binding-darwin-arm64": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.0.1.tgz", + "integrity": "sha512-cKnAhWEsV7TPcA/5EAteDp6KcJZBQ2G+BqE7zayMMi7kMvwRsbv7WT9aOnn0WNl4SKEIf43vjS31iUPu80nzXg==", + "dev": true, + "optional": true + }, + "@rolldown/binding-darwin-x64": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.0.1.tgz", + "integrity": "sha512-YKrVwQjIRBPo+5G/u03wGjbdy4q7pyzCe93DK9VJ7zkVmeg8LJ7GbgsiHWdR4xSoe4CAXRD7Bcjgbtr64bkXNg==", + "dev": true, + "optional": true + }, + "@rolldown/binding-freebsd-x64": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.0.1.tgz", + "integrity": "sha512-z/oBsREo46SsFqBwYtFe0kpJeBijAT48O/WXLI4suiCLBkr03RTtTJMCzSdDd2znlh8VJizL09XVkQgk8IZonw==", + "dev": true, + "optional": true + }, + "@rolldown/binding-linux-arm-gnueabihf": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.0.1.tgz", + "integrity": "sha512-ik8q7GM11zxvYxFc2PeDcT6TBvhCQMaUxfph/M5l9sKuTs/Sjg3L+Byw0F7w0ZVLBZmx30P+gG0ECzzN+MFcmQ==", + "dev": true, + "optional": true + }, + "@rolldown/binding-linux-arm64-gnu": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.0.1.tgz", + "integrity": "sha512-QoSx2EkyrrdZ6kcyE8stqZ62t0Yra8Fs5ia9lOxJrh6TMQJK7gQKmscdTHf7pOXKREKrVwOtJcQG3qVSfc866A==", + "dev": true, + "optional": true + }, + "@rolldown/binding-linux-arm64-musl": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.0.1.tgz", + "integrity": "sha512-uwNwFpwKeNiZawfAWBgg0VIztPTV3ihhh1vV334h9ivnNLorxnQMU6Fz8wG1Zb4Qh9LC1/MkcyT3YlDXG3Rsgg==", + "dev": true, + "optional": true + }, + "@rolldown/binding-linux-ppc64-gnu": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.0.1.tgz", + "integrity": "sha512-zY1bul7OWr7DFBiJ++wofXvnr8B45ce3QsQUhKrIhXsygAh7bTkwyeM1bi1a2g5C/yC/N8TZyGDEoMfm/l9mpg==", + "dev": true, + "optional": true + }, + "@rolldown/binding-linux-s390x-gnu": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.0.1.tgz", + "integrity": "sha512-0frlsT/f4Ft6I7SMESTKnF3cZsdicQn1dCMkF/jT9wDLE+gGoiQfv1nmT9e+s7s/fekvvy6tZM2jHvI2tkbJDQ==", + "dev": true, + "optional": true + }, + "@rolldown/binding-linux-x64-gnu": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.0.1.tgz", + "integrity": "sha512-XABVmGp9Tg0WspTVvwduTc4fpqy6JnAUrSQe6OuyqD/03nI7r0O9OWUkMIwFrjKAIqolvqoA4ZrJppgwE0Gxmw==", + "dev": true, + "optional": true + }, + "@rolldown/binding-linux-x64-musl": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.0.1.tgz", + "integrity": "sha512-bV4fzswuzVcKD90o/VM6QqKxnxlDq0g2BISDLNVmxrnhpv1DDbyPhCIjYfvzYLV+MvkKKnQt2Q6AO86SEBULUQ==", + "dev": true, + "optional": true + }, + "@rolldown/binding-openharmony-arm64": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.0.1.tgz", + "integrity": "sha512-/Mh0Zhq3OP7fVs0kcQHZP6lZEthMGTaSf8UBQYSFEZDWGXXlEC+nJ6EqenaK2t4LBXMe3A+K/G2BVXXdtOr4PQ==", + "dev": true, + "optional": true + }, + "@rolldown/binding-wasm32-wasi": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@rolldown/binding-wasm32-wasi/-/binding-wasm32-wasi-1.0.1.tgz", + "integrity": "sha512-+1xc9X45l8ufsBAm6Gjvx2qDRIY9lTVt0cgWNcJ+1gdhXvkbxePA60yRTwSTuXL09CMhyJmjpV7E3NoyxbqFQQ==", + "dev": true, + "optional": true, "requires": { - "color": "^5.0.2", - "text-hex": "1.0.x" + "@emnapi/core": "1.10.0", + "@emnapi/runtime": "1.10.0", + "@napi-rs/wasm-runtime": "^1.1.4" } }, + "@rolldown/binding-win32-arm64-msvc": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.0.1.tgz", + "integrity": "sha512-1D+UqZdfnuR+Jy1GgMJwi85bD40H21uNmOPRWQhw4oRSuolZ/B5rixZ45DK2KXOTCvmVCecauWgEhbw8bI7tOw==", + "dev": true, + "optional": true + }, + "@rolldown/binding-win32-x64-msvc": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.0.1.tgz", + "integrity": "sha512-INAycaWuhlOK3wk4mRHGsdgwYWmd9cChdPdE9bwWmy6rn9VqVNYNFGhOdXrofXUxwHIncSiPNb8tNm8knDVIeQ==", + "dev": true, + "optional": true + }, + "@rolldown/pluginutils": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.1.tgz", + "integrity": "sha512-2j9bGt5Jh8hj+vPtgzPtl72j0yRxHAyumoo6TNfAjsLB04UtpSvPbPcDcBMxz7n+9CYB0c1GxQFxYRg2jimqGw==", + "dev": true + }, "@socket.io/component-emitter": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/@socket.io/component-emitter/-/component-emitter-3.1.2.tgz", "integrity": "sha512-9BCxFwvbGg/RsZK9tjXd8s4UcwR0MWeFQ1XEKIQVVvAGJyINdrqKMcTRyLoK8Rse1GjzLV9cwjWV1olXRWEXVA==" }, + "@standard-schema/spec": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@standard-schema/spec/-/spec-1.1.0.tgz", + "integrity": "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==", + "dev": true + }, + "@tybys/wasm-util": { + "version": "0.10.2", + "resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.2.tgz", + "integrity": "sha512-RoBvJ2X0wuKlWFIjrwffGw1IqZHKQqzIchKaadZZfnNpsAYp2mM0h36JtPCjNDAHGgYez/15uMBpfGwchhiMgg==", + "dev": true, + "optional": true, + "requires": { + "tslib": "^2.4.0" + } + }, + "@types/chai": { + "version": "5.2.3", + "resolved": "https://registry.npmjs.org/@types/chai/-/chai-5.2.3.tgz", + "integrity": "sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA==", + "dev": true, + "requires": { + "@types/deep-eql": "*", + "assertion-error": "^2.0.1" + } + }, "@types/cors": { "version": "2.8.19", "resolved": "https://registry.npmjs.org/@types/cors/-/cors-2.8.19.tgz", @@ -3331,6 +4483,18 @@ "@types/node": "*" } }, + "@types/deep-eql": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@types/deep-eql/-/deep-eql-4.0.2.tgz", + "integrity": "sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==", + "dev": true + }, + "@types/estree": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.9.tgz", + "integrity": "sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==", + "dev": true + }, "@types/node": { "version": "25.3.5", "resolved": "https://registry.npmjs.org/@types/node/-/node-25.3.5.tgz", @@ -3339,27 +4503,90 @@ "undici-types": "~7.18.0" } }, - "@types/stack-trace": { - "version": "0.0.33", - "resolved": "https://registry.npmjs.org/@types/stack-trace/-/stack-trace-0.0.33.tgz", - "integrity": "sha512-O7in6531Bbvlb2KEsJ0dq0CHZvc3iWSR5ZYMtvGgnHA56VgriAN/AU2LorfmcvAl2xc9N5fbCTRyMRRl8nd74g==" - }, - "@types/triple-beam": { - "version": "1.3.5", - "resolved": "https://registry.npmjs.org/@types/triple-beam/-/triple-beam-1.3.5.tgz", - "integrity": "sha512-6WaYesThRMCl19iryMYP7/x2OVgCtbIVflDGFpWnb9irXI3UjYE4AzmYuiUKY1AJstGijoY+MgUszMgRxIYTYw==" - }, "@types/webidl-conversions": { "version": "7.0.3", "resolved": "https://registry.npmjs.org/@types/webidl-conversions/-/webidl-conversions-7.0.3.tgz", "integrity": "sha512-CiJJvcRtIgzadHCYXw7dqEnMNRjhGZlYK05Mj9OyktqV8uVT8fD2BFOB7S1uwBE3Kj2Z+4UyPmFw/Ixgw/LAlA==" }, - "@types/whatwg-url": { - "version": "13.0.0", - "resolved": "https://registry.npmjs.org/@types/whatwg-url/-/whatwg-url-13.0.0.tgz", - "integrity": "sha512-N8WXpbE6Wgri7KUSvrmQcqrMllKZ9uxkYWMt+mCSGwNc0Hsw9VQTW7ApqI4XNrx6/SaM2QQJCzMPDEXE058s+Q==", + "@types/whatwg-url": { + "version": "13.0.0", + "resolved": "https://registry.npmjs.org/@types/whatwg-url/-/whatwg-url-13.0.0.tgz", + "integrity": "sha512-N8WXpbE6Wgri7KUSvrmQcqrMllKZ9uxkYWMt+mCSGwNc0Hsw9VQTW7ApqI4XNrx6/SaM2QQJCzMPDEXE058s+Q==", + "requires": { + "@types/webidl-conversions": "*" + } + }, + "@vitest/expect": { + "version": "4.1.6", + "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-4.1.6.tgz", + "integrity": "sha512-7EHDquPthALSV0jhhjgEW8FXaviMx7rSqu8W6oqCoAuOhKov814P99QDV1pxMA3QPv21YudvJngIhjrNI4opLg==", + "dev": true, + "requires": { + "@standard-schema/spec": "^1.1.0", + "@types/chai": "^5.2.2", + "@vitest/spy": "4.1.6", + "@vitest/utils": "4.1.6", + "chai": "^6.2.2", + "tinyrainbow": "^3.1.0" + } + }, + "@vitest/mocker": { + "version": "4.1.6", + "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-4.1.6.tgz", + "integrity": "sha512-MCFc63czMjEInOlcY2cpQCvCN+KgbAn+60xu9cMgP4sKaLC5JNAKw7JH8QdAnoAC88hW1IiSNZ+GgVXlN1UcMQ==", + "dev": true, + "requires": { + "@vitest/spy": "4.1.6", + "estree-walker": "^3.0.3", + "magic-string": "^0.30.21" + } + }, + "@vitest/pretty-format": { + "version": "4.1.6", + "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-4.1.6.tgz", + "integrity": "sha512-h5SxD/IzNhZYnrSZRsUZQIC+vD0GY8cUvq0iwsmkFKixRCKLLWqCXa/FIQ4S1R+sI+PGoojkHsdNrbZiM9Qpgw==", + "dev": true, + "requires": { + "tinyrainbow": "^3.1.0" + } + }, + "@vitest/runner": { + "version": "4.1.6", + "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-4.1.6.tgz", + "integrity": "sha512-nOPCmn2+yD0ZNmKdsXGv/UxMMWbMuKeD6GyYncNwdkYDxpQvrPSKYj2rWuDjC2Y4b6w6hjip5dBKFzEUuZe3vA==", + "dev": true, + "requires": { + "@vitest/utils": "4.1.6", + "pathe": "^2.0.3" + } + }, + "@vitest/snapshot": { + "version": "4.1.6", + "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-4.1.6.tgz", + "integrity": "sha512-YhsdE6xAVfTDmzjxL2ZDUvjj+ZsgyOKe+TdQzqkD72wIOmHka8NuGQ6NpTNZv9D2Z63fbwWKJPeVpEw4EQgYxw==", + "dev": true, + "requires": { + "@vitest/pretty-format": "4.1.6", + "@vitest/utils": "4.1.6", + "magic-string": "^0.30.21", + "pathe": "^2.0.3" + } + }, + "@vitest/spy": { + "version": "4.1.6", + "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-4.1.6.tgz", + "integrity": "sha512-JFKxMx6udhwKh/Ldo270e17QX710vgunMkuPAvXjHSvC6oqLWAHhVhjg/I71q0u0CBSErIODV1Kjv0FQNSWjdg==", + "dev": true + }, + "@vitest/utils": { + "version": "4.1.6", + "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-4.1.6.tgz", + "integrity": "sha512-FxIY+U81R3LGKCxaHHFRQ5+g6/iRgGLmeHWdp2Amj4ljQRrEIWHmZyDfDYBRZlpyqA7qKxtS9DD1dhk8RnRIVQ==", + "dev": true, "requires": { - "@types/webidl-conversions": "*" + "@vitest/pretty-format": "4.1.6", + "convert-source-map": "^2.0.0", + "tinyrainbow": "^3.1.0" } }, "abbrev": { @@ -3395,10 +4622,17 @@ "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==" }, - "async": { - "version": "3.2.6", - "resolved": "https://registry.npmjs.org/async/-/async-3.2.6.tgz", - "integrity": "sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA==" + "asap": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/asap/-/asap-2.0.6.tgz", + "integrity": "sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA==", + "dev": true + }, + "assertion-error": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-2.0.1.tgz", + "integrity": "sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==", + "dev": true }, "async-mutex": { "version": "0.5.0", @@ -3409,6 +4643,12 @@ "tslib": "^2.4.0" } }, + "asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", + "dev": true + }, "b4a": { "version": "1.8.1", "resolved": "https://registry.npmjs.org/b4a/-/b4a-1.8.1.tgz", @@ -3426,7 +4666,6 @@ "resolved": "https://registry.npmjs.org/bare-events/-/bare-events-2.8.2.tgz", "integrity": "sha512-riJjyv1/mHLIPX4RwiK+oW9/4c3TEUeORHKefKAKnZ5kyslbN+HXowtbaVEqt4IMUB7OXlfixcs6gsFeo/jhiQ==", "dev": true, - "peer": true, "requires": {} }, "bare-fs": { @@ -3581,6 +4820,12 @@ "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", "dev": true }, + "chai": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/chai/-/chai-6.2.2.tgz", + "integrity": "sha512-NUPRluOfOiTKBKvWPtSD4PhFvWCqOi0BGStNWs57X9js7XGTprSmFoz5F0tWhR4WPjNeR9jXqdC7/UpSJTnlRg==", + "dev": true + }, "chokidar": { "version": "3.5.3", "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz", @@ -3601,34 +4846,13 @@ "resolved": "https://registry.npmjs.org/cluster-key-slot/-/cluster-key-slot-1.1.2.tgz", "integrity": "sha512-RMr0FhtfXemyinomL4hrWcYJxmX6deFdCxpJzhDttxgO1+bcCnkk+9drydLVDmAMG7NE6aN/fl4F7ucU/90gAA==" }, - "color": { - "version": "5.0.3", - "resolved": "https://registry.npmjs.org/color/-/color-5.0.3.tgz", - "integrity": "sha512-ezmVcLR3xAVp8kYOm4GS45ZLLgIE6SPAFoduLr6hTDajwb3KZ2F46gulK3XpcwRFb5KKGCSezCBAY4Dw4HsyXA==", - "requires": { - "color-convert": "^3.1.3", - "color-string": "^2.1.3" - } - }, - "color-convert": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-3.1.3.tgz", - "integrity": "sha512-fasDH2ont2GqF5HpyO4w0+BcewlhHEZOFn9c1ckZdHpJ56Qb7MHhH/IcJZbBGgvdtwdwNbLvxiBEdg336iA9Sg==", - "requires": { - "color-name": "^2.0.0" - } - }, - "color-name": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-2.1.0.tgz", - "integrity": "sha512-1bPaDNFm0axzE4MEAzKPuqKWeRaT43U/hyxKPBdqTfmPF+d6n7FSoTFxLVULUJOmiLp01KjhIPPH+HrXZJN4Rg==" - }, - "color-string": { - "version": "2.1.4", - "resolved": "https://registry.npmjs.org/color-string/-/color-string-2.1.4.tgz", - "integrity": "sha512-Bb6Cq8oq0IjDOe8wJmi4JeNn763Xs9cfrBcaylK1tPypWzyoy2G3l90v9k64kjphl/ZJjPIShFztenRomi8WTg==", + "combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "dev": true, "requires": { - "color-name": "^2.0.0" + "delayed-stream": "~1.0.0" } }, "commondir": { @@ -3637,34 +4861,11 @@ "integrity": "sha512-W9pAhw0ja1Edb5GVdIF1mjZw/ASI0AlShXM83UUGe2DVr5TdAPEA1OA8m/g8zWp9x6On7gqufY+FatDbC3MDQg==", "dev": true }, - "compressible": { - "version": "2.0.18", - "resolved": "https://registry.npmjs.org/compressible/-/compressible-2.0.18.tgz", - "integrity": "sha512-AF3r7P5dWxL8MxyITRMlORQNaOA2IkAFaTr4k7BUumjPtRpGDTZpl0Pb1XCO6JeDCBdp126Cgs9sMxqSjgYyRg==", - "requires": { - "mime-db": ">= 1.43.0 < 2" - } - }, - "compression": { - "version": "1.8.1", - "resolved": "https://registry.npmjs.org/compression/-/compression-1.8.1.tgz", - "integrity": "sha512-9mAqGPHLakhCLeNyxPkK4xVo746zQ/czLH1Ky+vkitMnWfWZps8r0qXuwhwizagCRttsL4lfG4pIOvaWLpAP0w==", - "requires": { - "bytes": "3.1.2", - "compressible": "~2.0.18", - "debug": "2.6.9", - "negotiator": "~0.6.4", - "on-headers": "~1.1.0", - "safe-buffer": "5.2.1", - "vary": "~1.1.2" - }, - "dependencies": { - "negotiator": { - "version": "0.6.4", - "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.4.tgz", - "integrity": "sha512-myRT3DiWPHqho5PrJaIRyaMv2kgYf0mUVgBNOYMuCH5Ki1yEiQaf/ZJuQ62nvpc44wL5WDbTX7yGJi1Neevw8w==" - } - } + "component-emitter": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.3.1.tgz", + "integrity": "sha512-T0+barUSQRTUQASh8bx02dl+DhF54GtIDY13Y3m9oWTklKbb3Wv974meRpeZ3lp1JpLVECWWNHC4vaG2XHXouQ==", + "dev": true }, "content-disposition": { "version": "0.5.4", @@ -3679,6 +4880,12 @@ "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==" }, + "convert-source-map": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", + "dev": true + }, "cookie": { "version": "0.7.2", "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz", @@ -3689,6 +4896,12 @@ "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==" }, + "cookiejar": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/cookiejar/-/cookiejar-2.1.4.tgz", + "integrity": "sha512-LDx6oHrK+PhzLKJU9j5S7/Y3jM/mUHvD/DeI1WQmJn652iPC5Y4TBzC9l+5OMOXlyTTA+SmVUPm0HQUwpD5Jqw==", + "dev": true + }, "cors": { "version": "2.8.6", "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.6.tgz", @@ -3711,6 +4924,12 @@ "ms": "2.0.0" } }, + "delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "dev": true + }, "depd": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", @@ -3721,6 +4940,22 @@ "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==" }, + "detect-libc": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", + "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==", + "dev": true + }, + "dezalgo": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/dezalgo/-/dezalgo-1.0.4.tgz", + "integrity": "sha512-rXSP0bf+5n0Qonsb+SVVfNfIsimO4HEtmnIpPHY8Q1UCzKlQrDMfdobr8nJOOsRgWCyMRqeSBQzmWUMq7zvVig==", + "dev": true, + "requires": { + "asap": "^2.0.0", + "wrappy": "1" + } + }, "dotenv": { "version": "16.0.2", "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.0.2.tgz", @@ -3749,11 +4984,6 @@ "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==" }, - "enabled": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/enabled/-/enabled-2.0.0.tgz", - "integrity": "sha512-AKrN98kuwOzMIdAizXGI86UFBoo26CL21UM763y1h/GMSJ4/OHU9k2YlsmBpyScFo/wbLzWQJBMCW4+IO3/+OQ==" - }, "encodeurl": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", @@ -3805,6 +5035,12 @@ "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==" }, + "es-module-lexer": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-2.1.0.tgz", + "integrity": "sha512-n27zTYMjYu1aj4MjCWzSP7G9r75utsaoc8m61weK+W8JMBGGQybd43GstCXZ3WNmSFtGT9wi59qQTW6mhTR5LQ==", + "dev": true + }, "es-object-atoms": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", @@ -3813,11 +5049,32 @@ "es-errors": "^1.3.0" } }, + "es-set-tostringtag": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", + "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", + "dev": true, + "requires": { + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" + } + }, "escape-html": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==" }, + "estree-walker": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz", + "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==", + "dev": true, + "requires": { + "@types/estree": "^1.0.0" + } + }, "etag": { "version": "1.8.1", "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", @@ -3832,11 +5089,16 @@ "bare-events": "^2.7.0" } }, + "expect-type": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/expect-type/-/expect-type-1.3.0.tgz", + "integrity": "sha512-knvyeauYhqjOYvQ66MznSMs83wmHrCycNEN6Ao+2AeYEfxUIkuiVxdEa1qlGEPK+We3n0THiDciYSsCcgW/DoA==", + "dev": true + }, "express": { "version": "4.22.1", "resolved": "https://registry.npmjs.org/express/-/express-4.22.1.tgz", "integrity": "sha512-F2X8g9P1X7uCPZMA3MVf9wcTqlyNp7IhH5qPCI0izhaOIYXaW9L535tGA3qmjRzpH+bZczqq7hVKxTR4NWnu+g==", - "peer": true, "requires": { "accepts": "~1.3.8", "array-flatten": "1.1.1", @@ -3871,14 +5133,6 @@ "vary": "~1.1.2" } }, - "express-rate-limit": { - "version": "8.5.2", - "resolved": "https://registry.npmjs.org/express-rate-limit/-/express-rate-limit-8.5.2.tgz", - "integrity": "sha512-5Kb34ipNX694DH48vN9irak1Qx30nb0PLYHXfJgw4YEjiC3ZEmZJhwOp+VfiCYwFzvFTdB9QkArYS5kXa2cx2A==", - "requires": { - "ip-address": "^10.2.0" - } - }, "extend": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", @@ -3890,10 +5144,11 @@ "integrity": "sha512-/d9sfos4yxzpwkDkuN7k2SqFKtYNmCTzgfEpz82x34IM9/zc8KGxQoXg1liNC/izpRM/MBdt44Nmx41ZWqk+FQ==", "dev": true }, - "fecha": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/fecha/-/fecha-4.2.3.tgz", - "integrity": "sha512-OP2IUU6HeYKJi3i0z4A19kHMQoLVs4Hc+DPqqxI2h/DPZHTm/vjsfC6P0b4jCMy14XizLBqvndQ+UilD7707Jw==" + "fast-safe-stringify": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/fast-safe-stringify/-/fast-safe-stringify-2.1.1.tgz", + "integrity": "sha512-W+KJc2dmILlPplD/H4K9l9LcAHAfPtP6BY84uVLXQ6Evcz9Lcg33Y2z1IVblT6xdY54PXYVHEv+0Wpq8Io6zkA==", + "dev": true }, "fetch-blob": { "version": "3.2.0", @@ -3947,17 +5202,25 @@ "path-exists": "^4.0.0" } }, - "fn.name": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/fn.name/-/fn.name-1.1.0.tgz", - "integrity": "sha512-GRnmB5gPyJpAhTQdSZTSp9uaPSvl09KoYcMQtsB9rQoOmzs9dH6ffeccH+Z+cv6P68Hu5bC6JjRh4Ah/mHSNRw==" - }, "follow-redirects": { "version": "1.16.0", "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.16.0.tgz", "integrity": "sha512-y5rN/uOsadFT/JfYwhxRS5R7Qce+g3zG97+JrtFZlC9klX/W5hD7iiLzScI4nZqUS7DNUdhPgw4xI8W2LuXlUw==", "dev": true }, + "form-data": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.5.tgz", + "integrity": "sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w==", + "dev": true, + "requires": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "es-set-tostringtag": "^2.1.0", + "hasown": "^2.0.2", + "mime-types": "^2.1.12" + } + }, "formdata-polyfill": { "version": "4.0.10", "resolved": "https://registry.npmjs.org/formdata-polyfill/-/formdata-polyfill-4.0.10.tgz", @@ -3966,6 +5229,17 @@ "fetch-blob": "^3.1.2" } }, + "formidable": { + "version": "3.5.4", + "resolved": "https://registry.npmjs.org/formidable/-/formidable-3.5.4.tgz", + "integrity": "sha512-YikH+7CUTOtP44ZTnUhR7Ic2UASBPOqmaRkRKxRbywPTe5VxF7RRCck4af9wutiZ/QKM5nME9Bie2fFaPz5Gug==", + "dev": true, + "requires": { + "@paralleldrive/cuid2": "^2.2.2", + "dezalgo": "^1.0.4", + "once": "^1.4.0" + } + }, "forwarded": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", @@ -3977,9 +5251,9 @@ "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==" }, "fsevents": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", - "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", "optional": true }, "function-bind": { @@ -4097,6 +5371,15 @@ "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==" }, + "has-tostringtag": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", + "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", + "dev": true, + "requires": { + "has-symbols": "^1.0.3" + } + }, "hasown": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", @@ -4105,11 +5388,6 @@ "function-bind": "^1.1.2" } }, - "helmet": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/helmet/-/helmet-8.1.0.tgz", - "integrity": "sha512-jOiHyAZsmnr8LqoPGmCjYAaiuWwjAPLgY8ZX2XrmHawt99/u1y6RgrZMTeoPfpUbV96HOalYgz1qzkRbw54Pmg==" - }, "http-errors": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.1.tgz", @@ -4164,11 +5442,6 @@ "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" }, - "ip-address": { - "version": "10.2.0", - "resolved": "https://registry.npmjs.org/ip-address/-/ip-address-10.2.0.tgz", - "integrity": "sha512-/+S6j4E9AHvW9SWMSEY9Xfy66O5PWvVEJ08O0y5JGyEKQpojb0K0GKpz/v5HJ/G0vi3D2sjGK78119oXZeE0qA==" - }, "ipaddr.js": { "version": "1.9.1", "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", @@ -4200,11 +5473,6 @@ "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==" }, - "is-stream": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", - "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==" - }, "json-bigint": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/json-bigint/-/json-bigint-1.0.0.tgz", @@ -4261,10 +5529,102 @@ "resolved": "https://registry.npmjs.org/kareem/-/kareem-3.2.0.tgz", "integrity": "sha512-VS8MWZz/cT+SqBCpVfNN4zoVz5VskR3N4+sTmUXme55e9avQHntpwpNq0yjnosISXqwJ3AQVjlbI4Dyzv//JtA==" }, - "kuler": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/kuler/-/kuler-2.0.0.tgz", - "integrity": "sha512-Xq9nH7KlWZmXAtodXDDRE7vs6DU1gTU8zYDHDiWLSip45Egwq3plLHzPn27NgvzL2r1LMPC1vdqh98sQxtqj4A==" + "lightningcss": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.32.0.tgz", + "integrity": "sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ==", + "dev": true, + "requires": { + "detect-libc": "^2.0.3", + "lightningcss-android-arm64": "1.32.0", + "lightningcss-darwin-arm64": "1.32.0", + "lightningcss-darwin-x64": "1.32.0", + "lightningcss-freebsd-x64": "1.32.0", + "lightningcss-linux-arm-gnueabihf": "1.32.0", + "lightningcss-linux-arm64-gnu": "1.32.0", + "lightningcss-linux-arm64-musl": "1.32.0", + "lightningcss-linux-x64-gnu": "1.32.0", + "lightningcss-linux-x64-musl": "1.32.0", + "lightningcss-win32-arm64-msvc": "1.32.0", + "lightningcss-win32-x64-msvc": "1.32.0" + } + }, + "lightningcss-android-arm64": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-android-arm64/-/lightningcss-android-arm64-1.32.0.tgz", + "integrity": "sha512-YK7/ClTt4kAK0vo6w3X+Pnm0D2cf2vPHbhOXdoNti1Ga0al1P4TBZhwjATvjNwLEBCnKvjJc2jQgHXH0NEwlAg==", + "dev": true, + "optional": true + }, + "lightningcss-darwin-arm64": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.32.0.tgz", + "integrity": "sha512-RzeG9Ju5bag2Bv1/lwlVJvBE3q6TtXskdZLLCyfg5pt+HLz9BqlICO7LZM7VHNTTn/5PRhHFBSjk5lc4cmscPQ==", + "dev": true, + "optional": true + }, + "lightningcss-darwin-x64": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.32.0.tgz", + "integrity": "sha512-U+QsBp2m/s2wqpUYT/6wnlagdZbtZdndSmut/NJqlCcMLTWp5muCrID+K5UJ6jqD2BFshejCYXniPDbNh73V8w==", + "dev": true, + "optional": true + }, + "lightningcss-freebsd-x64": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.32.0.tgz", + "integrity": "sha512-JCTigedEksZk3tHTTthnMdVfGf61Fky8Ji2E4YjUTEQX14xiy/lTzXnu1vwiZe3bYe0q+SpsSH/CTeDXK6WHig==", + "dev": true, + "optional": true + }, + "lightningcss-linux-arm-gnueabihf": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.32.0.tgz", + "integrity": "sha512-x6rnnpRa2GL0zQOkt6rts3YDPzduLpWvwAF6EMhXFVZXD4tPrBkEFqzGowzCsIWsPjqSK+tyNEODUBXeeVHSkw==", + "dev": true, + "optional": true + }, + "lightningcss-linux-arm64-gnu": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.32.0.tgz", + "integrity": "sha512-0nnMyoyOLRJXfbMOilaSRcLH3Jw5z9HDNGfT/gwCPgaDjnx0i8w7vBzFLFR1f6CMLKF8gVbebmkUN3fa/kQJpQ==", + "dev": true, + "optional": true + }, + "lightningcss-linux-arm64-musl": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.32.0.tgz", + "integrity": "sha512-UpQkoenr4UJEzgVIYpI80lDFvRmPVg6oqboNHfoH4CQIfNA+HOrZ7Mo7KZP02dC6LjghPQJeBsvXhJod/wnIBg==", + "dev": true, + "optional": true + }, + "lightningcss-linux-x64-gnu": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.32.0.tgz", + "integrity": "sha512-V7Qr52IhZmdKPVr+Vtw8o+WLsQJYCTd8loIfpDaMRWGUZfBOYEJeyJIkqGIDMZPwPx24pUMfwSxxI8phr/MbOA==", + "dev": true, + "optional": true + }, + "lightningcss-linux-x64-musl": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.32.0.tgz", + "integrity": "sha512-bYcLp+Vb0awsiXg/80uCRezCYHNg1/l3mt0gzHnWV9XP1W5sKa5/TCdGWaR/zBM2PeF/HbsQv/j2URNOiVuxWg==", + "dev": true, + "optional": true + }, + "lightningcss-win32-arm64-msvc": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.32.0.tgz", + "integrity": "sha512-8SbC8BR40pS6baCM8sbtYDSwEVQd4JlFTOlaD3gWGHfThTcABnNDBda6eTZeqbofalIJhFx0qKzgHJmcPTnGdw==", + "dev": true, + "optional": true + }, + "lightningcss-win32-x64-msvc": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.32.0.tgz", + "integrity": "sha512-Amq9B/SoZYdDi1kFrojnoqPLxYhQ4Wo5XiL8EVJrVsB8ARoC1PWW6VGtT0WKCemjy8aC+louJnjS7U18x3b06Q==", + "dev": true, + "optional": true }, "locate-path": { "version": "5.0.0", @@ -4310,24 +5670,13 @@ "resolved": "https://registry.npmjs.org/lodash.once/-/lodash.once-4.1.1.tgz", "integrity": "sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg==" }, - "logform": { - "version": "2.7.0", - "resolved": "https://registry.npmjs.org/logform/-/logform-2.7.0.tgz", - "integrity": "sha512-TFYA4jnP7PVbmlBIfhlSe+WKxs9dklXMTEGcBCIvLhE/Tn3H6Gk1norupVW7m5Cnd4bLcr08AytbyV/xj7f/kQ==", + "magic-string": { + "version": "0.30.21", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz", + "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==", + "dev": true, "requires": { - "@colors/colors": "1.6.0", - "@types/triple-beam": "^1.3.2", - "fecha": "^4.2.0", - "ms": "^2.1.1", - "safe-stable-stringify": "^2.3.1", - "triple-beam": "^1.3.0" - }, - "dependencies": { - "ms": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" - } + "@jridgewell/sourcemap-codec": "^1.5.5" } }, "make-dir": { @@ -4621,6 +5970,12 @@ "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz", "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==" }, + "obug": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/obug/-/obug-2.1.1.tgz", + "integrity": "sha512-uTqF9MuPraAQ+IsnPf366RG4cP9RtUi7MLO1N3KEc+wb0a6yKpeL0lmk2IB1jY5KHPAlTc6T/JRdC/YqxHNwkQ==", + "dev": true + }, "on-finished": { "version": "2.4.1", "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", @@ -4629,17 +5984,13 @@ "ee-first": "1.1.1" } }, - "on-headers": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.1.0.tgz", - "integrity": "sha512-737ZY3yNnXy37FHkQxPzt4UZ2UWPWiCZWLvFZ4fu5cueciegX0zGPnrlY6bwRg4FdQOe9YU8MkmJwGhoMybl8A==" - }, - "one-time": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/one-time/-/one-time-1.0.0.tgz", - "integrity": "sha512-5DXOiRKwuSEcQ/l0kGCF6Q3jcADFv5tSmRaJck/OqkVFcOzutB134KRSfF0xDrL39MNnqxbHBbUUcjZIhTgb2g==", + "once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "dev": true, "requires": { - "fn.name": "1.x.x" + "wrappy": "1" } }, "p-limit": { @@ -4682,12 +6033,24 @@ "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.12.tgz", "integrity": "sha512-RA1GjUVMnvYFxuqovrEqZoxxW5NUZqbwKtYz/Tt7nXerk0LbLblQmrsgdeOxV5SFHf0UDggjS/bSeOZwt1pmEQ==" }, + "pathe": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz", + "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==", + "dev": true + }, "pend": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/pend/-/pend-1.2.0.tgz", "integrity": "sha512-F3asv42UuXchdzt+xXqfW1OGlVBe+mxa2mqI0pg5yAHZPvFmY3Y6drSf/GQ1A86WgWEN9Kzh/WrgKa6iGcHXLg==", "dev": true }, + "picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "dev": true + }, "picomatch": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", @@ -4702,6 +6065,25 @@ "find-up": "^4.0.0" } }, + "postcss": { + "version": "8.5.15", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.15.tgz", + "integrity": "sha512-FfR8sjd4em2T6fb3I2MwAJU7HWVMr9zba+enmQeeWFfCbm+UOC/0X4DS8XtpUTMwWMGbjKYP7xjfNekzyGmB3A==", + "dev": true, + "requires": { + "nanoid": "^3.3.12", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "dependencies": { + "nanoid": { + "version": "3.3.12", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.12.tgz", + "integrity": "sha512-ZB9RH/39qpq5Vu6Y+NmUaFhQR6pp+M2Xt76XBnEwDaGcVAqhlvxrl3B2bKS5D3NH3QR76v3aSrKaF/Kiy7lEtQ==", + "dev": true + } + } + }, "proxy-addr": { "version": "2.0.7", "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", @@ -4745,16 +6127,6 @@ "unpipe": "~1.0.0" } }, - "readable-stream": { - "version": "3.6.2", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", - "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", - "requires": { - "inherits": "^2.0.3", - "string_decoder": "^1.1.1", - "util-deprecate": "^1.0.1" - } - }, "readdirp": { "version": "3.6.0", "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", @@ -4775,16 +6147,36 @@ "@redis/time-series": "5.11.0" } }, + "rolldown": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.0.1.tgz", + "integrity": "sha512-X0KQHljNnEkWNqqiz9zJrGunh1B0HgOxLXvnFpCOcadzcy5qohZ3tqMEUg00vncoRovXuK3ZqCT9KnnKzoInFQ==", + "dev": true, + "requires": { + "@oxc-project/types": "=0.130.0", + "@rolldown/binding-android-arm64": "1.0.1", + "@rolldown/binding-darwin-arm64": "1.0.1", + "@rolldown/binding-darwin-x64": "1.0.1", + "@rolldown/binding-freebsd-x64": "1.0.1", + "@rolldown/binding-linux-arm-gnueabihf": "1.0.1", + "@rolldown/binding-linux-arm64-gnu": "1.0.1", + "@rolldown/binding-linux-arm64-musl": "1.0.1", + "@rolldown/binding-linux-ppc64-gnu": "1.0.1", + "@rolldown/binding-linux-s390x-gnu": "1.0.1", + "@rolldown/binding-linux-x64-gnu": "1.0.1", + "@rolldown/binding-linux-x64-musl": "1.0.1", + "@rolldown/binding-openharmony-arm64": "1.0.1", + "@rolldown/binding-wasm32-wasi": "1.0.1", + "@rolldown/binding-win32-arm64-msvc": "1.0.1", + "@rolldown/binding-win32-x64-msvc": "1.0.1", + "@rolldown/pluginutils": "^1.0.0" + } + }, "safe-buffer": { "version": "5.2.1", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==" }, - "safe-stable-stringify": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/safe-stable-stringify/-/safe-stable-stringify-2.5.0.tgz", - "integrity": "sha512-b3rppTKm9T+PsVCBEOUR46GWI7fdOs00VKZ1+9c1EWDaDMvjQc6tUwuFyIprgGgTcWoVHSKrU8H31ZHA2e0RHA==" - }, "safer-buffer": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", @@ -4822,14 +6214,6 @@ } } }, - "serialize-error": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/serialize-error/-/serialize-error-8.1.0.tgz", - "integrity": "sha512-3NnuWfM6vBYoy5gZFvHiYsVbafvI9vZv/+jlIigFn4oP4zjNPK3LhcY0xSCgeb1a5L8jO71Mit9LlNoi2UfDDQ==", - "requires": { - "type-fest": "^0.20.2" - } - }, "serve-static": { "version": "1.16.3", "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.16.3.tgz", @@ -4910,6 +6294,12 @@ "resolved": "https://registry.npmjs.org/sift/-/sift-17.1.3.tgz", "integrity": "sha512-Rtlj66/b0ICeFzYTuNvX/EF1igRbbnGSvEyT79McoZa/DeGhMyC5pWKOEsZKnpkqtSeovd5FL/bjHWC3CIIvCQ==" }, + "siginfo": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/siginfo/-/siginfo-2.0.0.tgz", + "integrity": "sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==", + "dev": true + }, "simple-update-notifier": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/simple-update-notifier/-/simple-update-notifier-2.0.0.tgz", @@ -4995,6 +6385,12 @@ } } }, + "source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "dev": true + }, "sparse-bitfield": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/sparse-bitfield/-/sparse-bitfield-3.0.3.tgz", @@ -5003,16 +6399,23 @@ "memory-pager": "^1.0.2" } }, - "stack-trace": { - "version": "0.0.10", - "resolved": "https://registry.npmjs.org/stack-trace/-/stack-trace-0.0.10.tgz", - "integrity": "sha512-KGzahc7puUKkzyMt+IqAep+TVNbKP+k2Lmwhub39m1AsTSkaDutx56aDCo+HLDzf/D26BIHTJWNiTG1KAJiQCg==" + "stackback": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/stackback/-/stackback-0.0.2.tgz", + "integrity": "sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==", + "dev": true }, "statuses": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz", "integrity": "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==" }, + "std-env": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/std-env/-/std-env-4.1.0.tgz", + "integrity": "sha512-Rq7ybcX2RuC55r9oaPVEW7/xu3tj8u4GeBYHBWCychFtzMIr86A7e3PPEBPT37sHStKX3+TiX/Fr/ACmJLVlLQ==", + "dev": true + }, "streamx": { "version": "2.25.0", "resolved": "https://registry.npmjs.org/streamx/-/streamx-2.25.0.tgz", @@ -5024,12 +6427,63 @@ "text-decoder": "^1.1.0" } }, - "string_decoder": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", - "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "superagent": { + "version": "10.3.0", + "resolved": "https://registry.npmjs.org/superagent/-/superagent-10.3.0.tgz", + "integrity": "sha512-B+4Ik7ROgVKrQsXTV0Jwp2u+PXYLSlqtDAhYnkkD+zn3yg8s/zjA2MeGayPoY/KICrbitwneDHrjSotxKL+0XQ==", + "dev": true, + "requires": { + "component-emitter": "^1.3.1", + "cookiejar": "^2.1.4", + "debug": "^4.3.7", + "fast-safe-stringify": "^2.1.1", + "form-data": "^4.0.5", + "formidable": "^3.5.4", + "methods": "^1.1.2", + "mime": "2.6.0", + "qs": "^6.14.1" + }, + "dependencies": { + "debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "dev": true, + "requires": { + "ms": "^2.1.3" + } + }, + "mime": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-2.6.0.tgz", + "integrity": "sha512-USPkMeET31rOMiarsBNIHZKLGgvKc/LrjofAnBlOttf5ajRvqiRA8QsenbcooctK6d6Ts6aqZXBA+XbkKthiQg==", + "dev": true + }, + "ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true + } + } + }, + "supertest": { + "version": "7.2.2", + "resolved": "https://registry.npmjs.org/supertest/-/supertest-7.2.2.tgz", + "integrity": "sha512-oK8WG9diS3DlhdUkcFn4tkNIiIbBx9lI2ClF8K+b2/m8Eyv47LSawxUzZQSNKUrVb2KsqeTDCcjAAVPYaSLVTA==", + "dev": true, "requires": { - "safe-buffer": "~5.2.0" + "cookie-signature": "^1.2.2", + "methods": "^1.1.2", + "superagent": "^10.3.0" + }, + "dependencies": { + "cookie-signature": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.2.2.tgz", + "integrity": "sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg==", + "dev": true + } } }, "supports-color": { @@ -5070,10 +6524,48 @@ "b4a": "^1.6.4" } }, - "text-hex": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/text-hex/-/text-hex-1.0.0.tgz", - "integrity": "sha512-uuVGNWzgJ4yhRaNSiubPY7OjISw4sw4E5Uv0wbjp+OzcbmVU/rsT8ujgcXJhn9ypzsgr5vlzpPqP+MBBKcGvbg==" + "tinybench": { + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/tinybench/-/tinybench-2.9.0.tgz", + "integrity": "sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==", + "dev": true + }, + "tinyexec": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-1.1.2.tgz", + "integrity": "sha512-dAqSqE/RabpBKI8+h26GfLq6Vb3JVXs30XYQjdMjaj/c2tS8IYYMbIzP599KtRj7c57/wYApb3QjgRgXmrCukA==", + "dev": true + }, + "tinyglobby": { + "version": "0.2.16", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.16.tgz", + "integrity": "sha512-pn99VhoACYR8nFHhxqix+uvsbXineAasWm5ojXoN8xEwK5Kd3/TrhNn1wByuD52UxWRLy8pu+kRMniEi6Eq9Zg==", + "dev": true, + "requires": { + "fdir": "^6.5.0", + "picomatch": "^4.0.4" + }, + "dependencies": { + "fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "dev": true, + "requires": {} + }, + "picomatch": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", + "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", + "dev": true + } + } + }, + "tinyrainbow": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/tinyrainbow/-/tinyrainbow-3.1.0.tgz", + "integrity": "sha512-Bf+ILmBgretUrdJxzXM0SgXLZ3XfiaUuOj/IKQHuTXip+05Xn+uyEYdVg0kYDipTBcLrCVyUzAPz7QmArb0mmw==", + "dev": true }, "to-regex-range": { "version": "5.0.1", @@ -5104,22 +6596,12 @@ "punycode": "^2.3.1" } }, - "triple-beam": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/triple-beam/-/triple-beam-1.4.1.tgz", - "integrity": "sha512-aZbgViZrg1QNcG+LULa7nhZpJTZSLm/mXnHXnbAbjmN5aSa0y7V+wvv6+4WaBtpISJzThKy+PIPxc1Nq1EJ9mg==" - }, "tslib": { "version": "2.8.1", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", "dev": true }, - "type-fest": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", - "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==" - }, "type-is": { "version": "1.6.18", "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", @@ -5149,11 +6631,6 @@ "resolved": "https://registry.npmjs.org/url-template/-/url-template-2.0.8.tgz", "integrity": "sha512-XdVKMF4SJ0nP/O7XIPB0JwAEuT9lDIYnNsK8yGVe43y0AWoKeJNdv3ZNWh7ksJ6KqQFjOO6ox/VEitLnaVNufw==" }, - "util-deprecate": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", - "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==" - }, "utils-merge": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", @@ -5164,6 +6641,64 @@ "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==" }, + "vite": { + "version": "8.0.13", + "resolved": "https://registry.npmjs.org/vite/-/vite-8.0.13.tgz", + "integrity": "sha512-MFtjBYgzmSxmgA4RAfjIyXWpGe1oALnjgUTzzV7QLx/TKxCzjtMH6Fd9/eVK+5Fg1qNoz5VAwsmMs/NofrmJvw==", + "dev": true, + "requires": { + "fsevents": "~2.3.3", + "lightningcss": "^1.32.0", + "picomatch": "^4.0.4", + "postcss": "^8.5.14", + "rolldown": "1.0.1", + "tinyglobby": "^0.2.16" + }, + "dependencies": { + "picomatch": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", + "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", + "dev": true + } + } + }, + "vitest": { + "version": "4.1.6", + "resolved": "https://registry.npmjs.org/vitest/-/vitest-4.1.6.tgz", + "integrity": "sha512-6lvjbS3p9b4CrdCmguzbh2/4uoXhGE2q71R4OX5sqF9R1bo9Xd6fGrMAfvp5wnCzlBnFVdCOp6onuTQVbo8iUQ==", + "dev": true, + "requires": { + "@vitest/expect": "4.1.6", + "@vitest/mocker": "4.1.6", + "@vitest/pretty-format": "4.1.6", + "@vitest/runner": "4.1.6", + "@vitest/snapshot": "4.1.6", + "@vitest/spy": "4.1.6", + "@vitest/utils": "4.1.6", + "es-module-lexer": "^2.0.0", + "expect-type": "^1.3.0", + "magic-string": "^0.30.21", + "obug": "^2.1.1", + "pathe": "^2.0.3", + "picomatch": "^4.0.3", + "std-env": "^4.0.0-rc.1", + "tinybench": "^2.9.0", + "tinyexec": "^1.0.2", + "tinyglobby": "^0.2.15", + "tinyrainbow": "^3.1.0", + "vite": "^6.0.0 || ^7.0.0 || ^8.0.0", + "why-is-node-running": "^2.3.0" + }, + "dependencies": { + "picomatch": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", + "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", + "dev": true + } + } + }, "web-streams-polyfill": { "version": "3.3.3", "resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-3.3.3.tgz", @@ -5183,35 +6718,22 @@ "webidl-conversions": "^7.0.0" } }, - "winston": { - "version": "3.19.0", - "resolved": "https://registry.npmjs.org/winston/-/winston-3.19.0.tgz", - "integrity": "sha512-LZNJgPzfKR+/J3cHkxcpHKpKKvGfDZVPS4hfJCc4cCG0CgYzvlD6yE/S3CIL/Yt91ak327YCpiF/0MyeZHEHKA==", - "peer": true, - "requires": { - "@colors/colors": "^1.6.0", - "@dabh/diagnostics": "^2.0.8", - "async": "^3.2.3", - "is-stream": "^2.0.0", - "logform": "^2.7.0", - "one-time": "^1.0.0", - "readable-stream": "^3.4.0", - "safe-stable-stringify": "^2.3.1", - "stack-trace": "0.0.x", - "triple-beam": "^1.3.0", - "winston-transport": "^4.9.0" - } - }, - "winston-transport": { - "version": "4.9.0", - "resolved": "https://registry.npmjs.org/winston-transport/-/winston-transport-4.9.0.tgz", - "integrity": "sha512-8drMJ4rkgaPo1Me4zD/3WLfI/zPdA9o2IipKODunnGDcuqbHwjsbB79ylv04LCGGzU0xQ6vTznOMpQGaLhhm6A==", + "why-is-node-running": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/why-is-node-running/-/why-is-node-running-2.3.0.tgz", + "integrity": "sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==", + "dev": true, "requires": { - "logform": "^2.7.0", - "readable-stream": "^3.6.2", - "triple-beam": "^1.3.0" + "siginfo": "^2.0.0", + "stackback": "0.0.2" } }, + "wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "dev": true + }, "ws": { "version": "8.18.3", "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.3.tgz", diff --git a/server/package.json b/server/package.json index 2f856a1..45e1612 100644 --- a/server/package.json +++ b/server/package.json @@ -2,11 +2,13 @@ "name": "server", "version": "1.0.0", "description": "Discord Clone Backend", - "main": "src/index.js", + "main": "index.js", "type": "module", "scripts": { - "start": "node src/index.js", - "dev": "nodemon src/index.js", + "start": "node index.js", + "dev": "nodemon index.js", + "test": "vitest run", + "test:watch": "vitest", "test:auth:unit": "node scripts/run-auth-jwt-unit.mjs", "test:auth": "node scripts/run-auth-integration.mjs", "gmail:oauth-setup": "node scripts/gmail-oauth-setup.mjs" @@ -14,17 +16,12 @@ "author": "shunnu", "license": "ISC", "dependencies": { - "@logtail/node": "^0.5.8", - "@logtail/winston": "^0.5.8", "bcryptjs": "^3.0.3", - "compression": "^1.8.1", - "cors": "^2.8.5", + "cors": "^2.8.6", "dotenv": "^16.0.2", "express": "^4.18.1", - "express-rate-limit": "^8.5.2", "google-auth-library": "^10.5.0", "googleapis": "^171.4.0", - "helmet": "^8.1.0", "jsonwebtoken": "^9.0.3", "mongoose": "^9.3.1", "nanoid": "^5.1.6", @@ -32,10 +29,11 @@ "nodemon": "^3.1.14", "redis": "^5.11.0", "shortid": "^2.2.16", - "socket.io": "^4.5.4", - "winston": "^3.19.0" + "socket.io": "^4.5.4" }, "devDependencies": { - "mongodb-memory-server": "^11.1.0" + "mongodb-memory-server": "^11.1.0", + "supertest": "^7.2.2", + "vitest": "^4.1.6" } } diff --git a/server/src/routes/auth.js b/server/routes/auth.js similarity index 91% rename from server/src/routes/auth.js rename to server/routes/auth.js index dac1f9b..15f872c 100644 --- a/server/src/routes/auth.js +++ b/server/routes/auth.js @@ -1,12 +1,10 @@ -import config from "../config/index.js"; - import crypto from "crypto"; import express from "express"; import jwt from "jsonwebtoken"; import bcrypt from "bcryptjs"; import { buildAuthUserJwtPayload } from "../lib/authJwtPayload.js"; -import logger from "../lib/winston.js"; +import { OTP_TTL_MS } from "../config/constants.js"; import { authToken } from "../middleware/auth.js"; import User from "../models/User.js"; import { generateOTP, sendMail } from "../services/email.js"; @@ -16,8 +14,6 @@ import { updatingCreds, } from "../services/userService.js"; -import expressRateLimit from "../middleware/rateLimit.js"; - const router = express.Router(); function looksLikeBcryptHash(storedPassword) { @@ -54,11 +50,11 @@ function generateAvatar(username) { try { const seed = `${username || "user"}-${Date.now()}`; - return `${config.DICEBEAR_API}/${config.DICEBEAR_STYLE}/svg?seed=${encodeURIComponent(seed)}`; + return `${process.env.DICEBEAR_API}/${process.env.DICEBEAR_STYLE}/svg?seed=${encodeURIComponent(seed)}`; } catch (error) { - logger.error(`Avatar generation error: ${error.message}`); + console.error("Avatar generation error:", error.message); - return config.DEFAULT_PROFILE_PIC; + return process.env.DEFAULT_PROFILE_PIC; } } @@ -66,7 +62,7 @@ router.post("/verify_route", authToken, (req, res) => { res.status(201).json({ message: "authorized", status: 201 }); }); -router.post("/signup", expressRateLimit("auth"), async (req, res) => { +router.post("/signup", async (req, res) => { const { email, username, password, dob } = req.body; const authorized = false; @@ -182,7 +178,7 @@ router.post("/signup", expressRateLimit("auth"), async (req, res) => { } }); -router.post("/verify", expressRateLimit("otp"), async (req, res) => { +router.post("/verify", async (req, res) => { const { email } = req.body; const otpValue = String(req.body.otp_value || "").trim(); @@ -196,7 +192,7 @@ router.post("/verify", expressRateLimit("otp"), async (req, res) => { const username = user.username; const currentOtp = user.verification?.[0]?.code; - if (Date.now() - currentTimestamp < config.OTP_TTL_MS) { + if (Date.now() - currentTimestamp < OTP_TTL_MS) { if (otpValue === currentOtp) { await User.updateOne({ email }, { $set: { authorized: true } }); return res.status(201).json({ @@ -223,7 +219,7 @@ router.post("/verify", expressRateLimit("otp"), async (req, res) => { } }); -router.post("/resend_otp", expressRateLimit("otp"), async (req, res) => { +router.post("/resend_otp", async (req, res) => { const { email } = req.body; try { @@ -252,7 +248,7 @@ router.post("/resend_otp", expressRateLimit("otp"), async (req, res) => { } }); -router.post("/signin", expressRateLimit("auth"), async (req, res) => { +router.post("/signin", async (req, res) => { try { const email = req.body.email; const plainPassword = req.body.password; @@ -298,7 +294,7 @@ router.post("/signin", expressRateLimit("auth"), async (req, res) => { const token = jwt.sign( buildAuthUserJwtPayload(user), - config.ACCESS_TOKEN, + process.env.ACCESS_TOKEN, ); return res .status(201) diff --git a/server/src/routes/chat.js b/server/routes/chat.js similarity index 94% rename from server/src/routes/chat.js rename to server/routes/chat.js index 29ee586..3e3feaa 100644 --- a/server/src/routes/chat.js +++ b/server/routes/chat.js @@ -1,5 +1,3 @@ -import config from "../config/index.js"; - import express from "express"; import jwt from "jsonwebtoken"; @@ -7,13 +5,10 @@ import Chat from "../models/Chat.js"; import Server from "../models/Server.js"; import User from "../models/User.js"; import * as cache from "../lib/cache.js"; -import logger from "../lib/winston.js"; import { getChats } from "../services/chatService.js"; import { incrementServerUnread } from "../services/unreadService.js"; import { getIO } from "../socket/runtime.js"; -import expressRateLimit from "../middleware/rateLimit.js"; - const router = express.Router(); async function shouldSendNotification(userId, preferenceKey) { @@ -29,14 +24,14 @@ async function shouldSendNotification(userId, preferenceKey) { function getAuthorizedUser(req, res) { try { - return jwt.verify(req.headers["x-auth-token"], config.ACCESS_TOKEN); + return jwt.verify(req.headers["x-auth-token"], process.env.ACCESS_TOKEN); } catch (e) { res.status(401).json({ message: "Unauthorized", status: 401 }); return null; } } -router.post("/store_message", expressRateLimit("chat"), async (req, res) => { +router.post("/store_message", async (req, res) => { const { message, server_id, @@ -177,7 +172,7 @@ router.post("/get_messages", async (req, res) => { await cache.setJson(cacheKey, { chats }); return res.json({ chats }); } catch (error) { - logger.error(`Error retrieving chats: ${error.message}`); + console.error("Error retrieving chats:", error); res.status(500).json({ error: "Failed to retrieve chats." }); } }); @@ -233,7 +228,7 @@ router.post("/edit_server_message", async (req, res) => { res.status(200).json({ status: 200, message: "Message updated" }); } catch (error) { - logger.error(`Error editing message: ${error.message}`); + console.error("Error editing message:", error); res.status(500).json({ status: 500, message: "Failed to edit message" }); } }); @@ -291,7 +286,7 @@ router.post("/delete_server_message", async (req, res) => { res.status(200).json({ status: 200, message: "Message deleted" }); } catch (error) { - logger.error(`Error deleting message: ${error.message}`); + console.error("Error deleting message:", error); res.status(500).json({ status: 500, message: "Failed to delete message" }); } }); diff --git a/server/src/routes/directMessages.js b/server/routes/directMessages.js similarity index 98% rename from server/src/routes/directMessages.js rename to server/routes/directMessages.js index 47dd6cc..365b71e 100644 --- a/server/src/routes/directMessages.js +++ b/server/routes/directMessages.js @@ -1,5 +1,3 @@ -import config from "../config/index.js"; - import express from "express"; import jwt from "jsonwebtoken"; @@ -24,7 +22,7 @@ async function shouldSendNotification(userId, preferenceKey) { function getAuthorizedUser(req, res) { try { - return jwt.verify(req.headers["x-auth-token"], config.ACCESS_TOKEN); + return jwt.verify(req.headers["x-auth-token"], process.env.ACCESS_TOKEN); } catch (e) { res.status(401).json({ message: "Unauthorized", status: 401 }); return null; diff --git a/server/src/routes/friends.js b/server/routes/friends.js similarity index 95% rename from server/src/routes/friends.js rename to server/routes/friends.js index 4907be6..0aec1ee 100644 --- a/server/src/routes/friends.js +++ b/server/routes/friends.js @@ -1,9 +1,6 @@ -import config from "../config/index.js"; - import express from "express"; import jwt from "jsonwebtoken"; -import logger from "../lib/winston.js"; import User from "../models/User.js"; import { addFriend, @@ -27,7 +24,7 @@ router.post("/add_friend", async (req, res) => { try { user_id = jwt.verify( req.headers["x-auth-token"], - config.ACCESS_TOKEN + process.env.ACCESS_TOKEN ); } catch (e) { return res.status(401).json({ message: "Unauthorized", status: 401 }); @@ -146,7 +143,7 @@ router.get("/user_relations", async (req, res) => { try { const user_id = jwt.verify( req.headers["x-auth-token"], - config.ACCESS_TOKEN + process.env.ACCESS_TOKEN ); const result = await User.findOne({ _id: user_id.id }).lean(); if (!result) { @@ -159,7 +156,7 @@ router.get("/user_relations", async (req, res) => { servers: result.servers || [], }); } catch (err) { - logger.error(`Error fetching user relations: ${err.message}`); + console.error("Error fetching user relations:", err); res.status(500).json({ message: "Something went wrong", status: 500 }); } }); @@ -173,7 +170,7 @@ router.post("/process_req", async (req, res) => { try { user_id = jwt.verify( req.headers["x-auth-token"], - config.ACCESS_TOKEN + process.env.ACCESS_TOKEN ); } catch (e) { return res.status(401).json({ message: "Unauthorized", status: 401 }); diff --git a/server/src/routes/invites.js b/server/routes/invites.js similarity index 100% rename from server/src/routes/invites.js rename to server/routes/invites.js diff --git a/server/src/routes/notifications.js b/server/routes/notifications.js similarity index 82% rename from server/src/routes/notifications.js rename to server/routes/notifications.js index 6c44722..c49f12e 100644 --- a/server/src/routes/notifications.js +++ b/server/routes/notifications.js @@ -1,10 +1,7 @@ -import config from "../config/index.js"; - import express from "express"; import jwt from "jsonwebtoken"; import { authToken } from "../middleware/auth.js"; -import logger from "../lib/winston.js"; import User from "../models/User.js"; import { clearDmUnread, @@ -15,7 +12,7 @@ import { const router = express.Router(); function getAuthorizedUser(req) { - return jwt.verify(req.headers["x-auth-token"], config.ACCESS_TOKEN); + return jwt.verify(req.headers["x-auth-token"], process.env.ACCESS_TOKEN); } router.get("/unread_summary", authToken, async (req, res) => { @@ -27,7 +24,7 @@ router.get("/unread_summary", authToken, async (req, res) => { res.status(200).json({ status: 200, summary }); } catch (error) { - logger.error(`Unread summary error: ${error.message}`); + console.error("Unread summary error:", error); res.status(500).json({ status: 500, message: "Failed to load unread summary" }); } }); @@ -38,7 +35,7 @@ router.post("/mark_direct_messages_read", authToken, async (req, res) => { await clearDmUnread(user.id, req.body.friend_id); res.status(200).json({ status: 200 }); } catch (error) { - logger.error(`Mark DM read error: ${error.message}`); + console.error("Mark DM read error:", error); res.status(500).json({ status: 500, message: "Failed to clear DM unread" }); } }); @@ -50,7 +47,7 @@ router.post("/mark_channel_read", authToken, async (req, res) => { await clearServerChannelUnread(user.id, server_id, channel_id); res.status(200).json({ status: 200 }); } catch (error) { - logger.error(`Mark channel read error: ${error.message}`); + console.error("Mark channel read error:", error); res.status(500).json({ status: 500, message: "Failed to clear channel unread" }); } }); diff --git a/server/src/routes/profile.js b/server/routes/profile.js similarity index 96% rename from server/src/routes/profile.js rename to server/routes/profile.js index 47e4470..efa9849 100644 --- a/server/src/routes/profile.js +++ b/server/routes/profile.js @@ -1,5 +1,3 @@ -import config from "../config/index.js"; - import express from "express"; import jwt from "jsonwebtoken"; import bcrypt from "bcryptjs"; @@ -98,7 +96,7 @@ async function propagateUserIdentity({ userId, username, profile_pic }) { await Promise.all(arrayUpdates); } -router.patch("/", authToken, async (req, res) => { +router.patch("/profile", authToken, async (req, res) => { try { const userId = req.user?.id; if (!userId) { @@ -156,7 +154,7 @@ router.patch("/", authToken, async (req, res) => { const token = jwt.sign( buildAuthUserJwtPayload(updated), - config.ACCESS_TOKEN + process.env.ACCESS_TOKEN ); return res.status(200).json({ @@ -175,7 +173,7 @@ router.patch("/", authToken, async (req, res) => { } }); -router.patch("/password", authToken, async (req, res) => { +router.patch("/profile/password", authToken, async (req, res) => { try { const userId = req.user?.id; if (!userId) { @@ -223,7 +221,7 @@ router.patch("/password", authToken, async (req, res) => { } }); -router.patch("/notifications", authToken, async (req, res) => { +router.patch("/profile/notifications", authToken, async (req, res) => { try { const userId = req.user?.id; if (!userId) { @@ -254,7 +252,7 @@ router.patch("/notifications", authToken, async (req, res) => { const token = jwt.sign( buildAuthUserJwtPayload(updated), - config.ACCESS_TOKEN, + process.env.ACCESS_TOKEN, ); return res.status(200).json({ diff --git a/server/src/routes/servers.js b/server/routes/servers.js similarity index 97% rename from server/src/routes/servers.js rename to server/routes/servers.js index 3235cd2..56f5435 100644 --- a/server/src/routes/servers.js +++ b/server/routes/servers.js @@ -1,5 +1,3 @@ -import config from "../config/index.js"; - import express from "express"; import jwt from "jsonwebtoken"; import mongoose from "mongoose"; @@ -22,7 +20,7 @@ router.post("/create_server", async (req, res) => { try { user_id = jwt.verify( req.headers["x-auth-token"], - config.ACCESS_TOKEN + process.env.ACCESS_TOKEN ); } catch (e) { return res.status(401).json({ message: "Unauthorized", status: 401 }); @@ -64,7 +62,7 @@ router.post("/server_info", async (req, res) => { try { user_id = jwt.verify( req.headers["x-auth-token"], - config.ACCESS_TOKEN + process.env.ACCESS_TOKEN ); } catch (e) { return res.status(401).json({ message: "Unauthorized", status: 401 }); @@ -166,7 +164,7 @@ router.post("/leave_server", async (req, res) => { try { user_id = jwt.verify( req.headers["x-auth-token"], - config.ACCESS_TOKEN + process.env.ACCESS_TOKEN ); } catch (e) { return res.status(401).json({ message: "Unauthorized", status: 401 }); diff --git a/server/scripts/gmail-oauth-setup.mjs b/server/scripts/gmail-oauth-setup.mjs index c6b0be2..43ab2ac 100644 --- a/server/scripts/gmail-oauth-setup.mjs +++ b/server/scripts/gmail-oauth-setup.mjs @@ -4,19 +4,21 @@ * * Usage: cd server && npm run gmail:oauth-setup */ -import "dotenv/config"; - -import config from "../src/config/index.js"; - import { createServer } from "http"; import { OAuth2Client } from "google-auth-library"; -import { GMAIL_SEND_SCOPE } from "../src/lib/gmailOAuth.js"; +import dotenv from "dotenv"; +import path from "path"; +import { fileURLToPath } from "url"; +import { GMAIL_SEND_SCOPE } from "../lib/gmailOAuth.js"; + +const __dirname = path.dirname(fileURLToPath(import.meta.url)); +dotenv.config({ path: path.join(__dirname, "..", "..", ".env") }); const REDIRECT_URI = "http://localhost:53682/oauth2callback"; const clientId = - config.OAUTH_CLIENT_ID || ""; -const clientSecret = config.OAUTH_CLIENT_SECRET || ""; + process.env.OAUTH_CLIENT_ID || process.env.OAUTH_CLIENTID || ""; +const clientSecret = process.env.OAUTH_CLIENT_SECRET || ""; if (!clientId || !clientSecret) { console.error("Set OAUTH_CLIENT_ID and OAUTH_CLIENT_SECRET in .env first."); diff --git a/server/scripts/run-auth-integration.mjs b/server/scripts/run-auth-integration.mjs index b55ee47..7c4d7cd 100644 --- a/server/scripts/run-auth-integration.mjs +++ b/server/scripts/run-auth-integration.mjs @@ -1,13 +1,9 @@ /** * Auth integration checks (MongoDB + HTTP). * Run from repo: node server/scripts/run-auth-integration.mjs - * Uses environment variables loaded via dotenv/config. - * Falls back to a local MongoDB instance if MONGO_URI is unset. + * Uses MONGO_URI from the environment or PiperChat01/.env (via config/env.js). + * If MONGO_URI is still unset, defaults to mongodb://127.0.0.1:27017/piperchat_auth_itest. */ -import "dotenv/config"; - -import config from "../src/config/index.js"; - import express from "express"; import http from "http"; import jwt from "jsonwebtoken"; @@ -17,27 +13,28 @@ import mongoose from "mongoose"; const BCRYPT_RE = /^\$2[aby]\$\d{2}\$[./A-Za-z0-9]{53}$/; function ensureTestEnv() { - if (!config.MONGO_URI) { - config.MONGO_URI = + if (!process.env.MONGO_URI) { + process.env.MONGO_URI = "mongodb://127.0.0.1:27017/piperchat_auth_itest"; } - if (!config.ACCESS_TOKEN) { - config.ACCESS_TOKEN = + if (!process.env.ACCESS_TOKEN) { + process.env.ACCESS_TOKEN = "integration-test-jwt-secret-do-not-use-in-production-32chars-min"; } - if (!config.DEFAULT_PROFILE_PIC) { - config.DEFAULT_PROFILE_PIC = "https://example.com/default.png"; + if (!process.env.default_profile_pic) { + process.env.default_profile_pic = "https://example.com/default.png"; } } +await import("../config/env.js"); ensureTestEnv(); // Avoid live SMTP during automated auth tests (signup still persists user). -config.MAIL_TRANSPORT = "console"; +process.env.MAIL_TRANSPORT = "console"; -const { connectDatabase } = await import("../src/config/db.js"); -const User = (await import("../src/models/User.js")).default; -const authRoutes = (await import("../src/routes/auth.js")).default; -const profileRoutes = (await import("../src/routes/profile.js")).default; +const { connect } = await import("../config/db.js"); +const User = (await import("../models/User.js")).default; +const authRoutes = (await import("../routes/auth.js")).default; +const profileRoutes = (await import("../routes/profile.js")).default; function decodeJwtPayload(token) { const [, payload] = token.split("."); @@ -69,7 +66,7 @@ async function request(baseUrl, path, options = {}) { } async function main() { - await connectDatabase(); + await connect(); const emailPlain = `auth-itest-plain-${Date.now()}@example.com`; const emailNew = `auth-itest-new-${Date.now()}@example.com`; @@ -95,7 +92,7 @@ async function main() { const app = express(); app.use(express.json()); app.use("/", authRoutes); - app.use("/profile", profileRoutes); + app.use("/", profileRoutes); const server = http.createServer(app); const baseUrl = await new Promise((resolve, reject) => { diff --git a/server/scripts/run-auth-jwt-unit.mjs b/server/scripts/run-auth-jwt-unit.mjs index e953fba..0e18064 100644 --- a/server/scripts/run-auth-jwt-unit.mjs +++ b/server/scripts/run-auth-jwt-unit.mjs @@ -2,11 +2,9 @@ * Fast checks for JWT payload helper and bcrypt hash shape (no MongoDB). * Run: node server/scripts/run-auth-jwt-unit.mjs */ -import config from "../src/config/index.js"; - import jwt from "jsonwebtoken"; import bcrypt from "bcryptjs"; -import { buildAuthUserJwtPayload } from "../src/lib/authJwtPayload.js"; +import { buildAuthUserJwtPayload } from "../lib/authJwtPayload.js"; const BCRYPT_RE = /^\$2[aby]\$\d{2}\$[./A-Za-z0-9]{53}$/; @@ -15,7 +13,7 @@ function assert(c, m) { } const secret = - config.ACCESS_TOKEN || + process.env.ACCESS_TOKEN || "unit-test-jwt-secret-minimum-length-32-characters"; const mockUser = { diff --git a/server/src/services/chatService.js b/server/services/chatService.js similarity index 100% rename from server/src/services/chatService.js rename to server/services/chatService.js diff --git a/server/src/services/email.js b/server/services/email.js similarity index 75% rename from server/src/services/email.js rename to server/services/email.js index 24fc634..111a06c 100644 --- a/server/src/services/email.js +++ b/server/services/email.js @@ -1,12 +1,9 @@ -import config from "../config/index.js"; - import nodemailer from "nodemailer"; import { getMailUser, getOAuthClientId, hasGmailOAuthCredentials, } from "../lib/gmailOAuth.js"; -import logger from "../lib/winston.js"; import { sendOtpViaGmailApi, verifyGmailApi } from "./gmailApiMail.js"; const MAIL_MODES = { @@ -18,14 +15,14 @@ const MAIL_MODES = { }; function hasSmtpCredentials() { - const smtpHost = config.SMTP_HOST?.trim(); - const smtpUser = (config.SMTP_USER || getMailUser()).trim(); - const smtpPass = (config.SMTP_PASS || config.MAIL_PASS || "").trim(); + const smtpHost = process.env.SMTP_HOST?.trim(); + const smtpUser = (process.env.SMTP_USER || getMailUser()).trim(); + const smtpPass = (process.env.SMTP_PASS || process.env.MAIL_PASS || "").trim(); return Boolean(smtpHost && smtpUser && smtpPass); } function hasPasswordCredentials() { - return Boolean(getMailUser() && config.MAIL_PASS?.trim()); + return Boolean(getMailUser() && process.env.MAIL_PASS?.trim()); } /** @@ -33,7 +30,7 @@ function hasPasswordCredentials() { * Nodemailer still uses SMTP and may ETIMEDOUT on cloud hosts that block outbound SMTP. */ export function resolveMailMode() { - const requested = (config.MAIL_TRANSPORT || "auto").trim().toLowerCase(); + const requested = (process.env.MAIL_TRANSPORT || "auto").trim().toLowerCase(); if (requested === "console") return MAIL_MODES.CONSOLE; @@ -64,24 +61,24 @@ function createNodemailerTransporter(mode) { if (mode === MAIL_MODES.PASSWORD) { return nodemailer.createTransport({ service: "gmail", - auth: { user: getMailUser(), pass: config.MAIL_PASS.trim() }, + auth: { user: getMailUser(), pass: process.env.MAIL_PASS.trim() }, }); } if (mode === MAIL_MODES.SMTP) { - const smtpPort = config.SMTP_PORT - ? Number(config.SMTP_PORT) + const smtpPort = process.env.SMTP_PORT + ? Number(process.env.SMTP_PORT) : undefined; return nodemailer.createTransport({ - host: config.SMTP_HOST.trim(), + host: process.env.SMTP_HOST.trim(), port: smtpPort || 587, secure: - config.SMTP_SECURE === "true" || - config.SMTP_SECURE === "1" || + process.env.SMTP_SECURE === "true" || + process.env.SMTP_SECURE === "1" || smtpPort === 465, auth: { - user: (config.SMTP_USER || getMailUser()).trim(), - pass: (config.SMTP_PASS || config.MAIL_PASS).trim(), + user: (process.env.SMTP_USER || getMailUser()).trim(), + pass: (process.env.SMTP_PASS || process.env.MAIL_PASS).trim(), }, }); } @@ -105,12 +102,12 @@ export async function verifyMailTransport() { const mode = resolveMailMode(); if (mode === MAIL_MODES.CONSOLE) { - logger.info("[email] MAIL_TRANSPORT=console — OTP emails are logged only."); + console.log("[email] MAIL_TRANSPORT=console — OTP emails are logged only."); return { ok: true, mode }; } if (mode === MAIL_MODES.NONE) { - logger.warn( + console.warn( "[email] Not configured. Set MAIL_TRANSPORT=gmail_api and Gmail OAuth env vars." ); return { ok: false, mode, reason: "mail_not_configured" }; @@ -119,23 +116,23 @@ export async function verifyMailTransport() { if (mode === MAIL_MODES.GMAIL_API) { const result = await verifyGmailApi(); if (result.ok) { - logger.info(`[email] Gmail API ready as ${result.address}`); + console.log(`[email] Gmail API ready as ${result.address}`); return { ok: true, mode }; } - logger.error(`[email] Gmail API verification failed: ${result.reason}`); - if (result.hint) logger.error(`[email] ${result.hint}`); + console.error(`[email] Gmail API verification failed: ${result.reason}`); + if (result.hint) console.error(`[email] ${result.hint}`); return { ok: false, mode, ...result }; } const transporter = createNodemailerTransporter(mode); try { await transporter.verify(); - logger.info(`[email] SMTP ready (${mode}) as ${getMailUser()}`); + console.log(`[email] SMTP ready (${mode}) as ${getMailUser()}`); return { ok: true, mode }; } catch (error) { - logger.error(`[email] SMTP verify failed (${mode}): ${error.message}`); + console.error(`[email] SMTP verify failed (${mode}):`, error.message); if (error.code === "ETIMEDOUT") { - logger.error( + console.error( "[email] SMTP is blocked on this host. Use MAIL_TRANSPORT=gmail_api with OAuth2." ); } @@ -147,7 +144,7 @@ async function sendMail(otp, mailValue, nameValue) { const mode = resolveMailMode(); if (mode === MAIL_MODES.CONSOLE) { - logger.info(`[email:console] OTP for ${mailValue}: ${otp}`); + console.log(`[email:console] OTP for ${mailValue}: ${otp}`); return { ok: true, mode, simulated: true }; } @@ -157,7 +154,7 @@ async function sendMail(otp, mailValue, nameValue) { const transporter = createNodemailerTransporter(mode); if (!transporter) { - logger.warn("[email] Mail not configured."); + console.warn("[email] Mail not configured."); return { ok: false, reason: "mail_not_configured", mode }; } @@ -167,9 +164,9 @@ async function sendMail(otp, mailValue, nameValue) { ); return { ok: true, mode, messageId: info.messageId }; } catch (error) { - logger.error(`[email] Send failed (${mode}): ${error.message}`); + console.error(`[email] Send failed (${mode}):`, error.message); if (error.code === "ETIMEDOUT") { - logger.error( + console.error( "[email] Use MAIL_TRANSPORT=gmail_api (SMTP ports are often blocked in production)." ); } diff --git a/server/src/services/friendService.js b/server/services/friendService.js similarity index 100% rename from server/src/services/friendService.js rename to server/services/friendService.js diff --git a/server/src/services/gmailApiMail.js b/server/services/gmailApiMail.js similarity index 96% rename from server/src/services/gmailApiMail.js rename to server/services/gmailApiMail.js index ec7b26e..c532556 100644 --- a/server/src/services/gmailApiMail.js +++ b/server/services/gmailApiMail.js @@ -4,7 +4,6 @@ import { getMailUser, hasGmailOAuthCredentials, } from "../lib/gmailOAuth.js"; -import logger from "../lib/winston.js"; function buildRawMessage({ from, to, subject, text, html }) { const boundary = `piperchat_${Date.now()}`; @@ -111,7 +110,7 @@ export async function sendOtpViaGmailApi(otp, to, name) { }); return { ok: true, mode: "gmail_api", messageId: result.data.id }; } catch (error) { - logger.error(`[email:gmail_api] Send failed: ${error.message}`); + console.error("[email:gmail_api] Send failed:", error.message); return { ok: false, reason: "send_failed", mode: "gmail_api", error: error.message }; } } diff --git a/server/src/services/inviteService.js b/server/services/inviteService.js similarity index 100% rename from server/src/services/inviteService.js rename to server/services/inviteService.js diff --git a/server/src/services/serverService.js b/server/services/serverService.js similarity index 100% rename from server/src/services/serverService.js rename to server/services/serverService.js diff --git a/server/src/services/unreadService.js b/server/services/unreadService.js similarity index 90% rename from server/src/services/unreadService.js rename to server/services/unreadService.js index b80f7fe..de82767 100644 --- a/server/src/services/unreadService.js +++ b/server/services/unreadService.js @@ -1,6 +1,3 @@ -import config from "../config/index.js"; -import logger from "../lib/winston.js"; - let createClient = null; try { @@ -13,13 +10,13 @@ const memoryStore = new Map(); let redisClientPromise = null; function getRedisUrl() { - if (config.REDIS_URL) { - return config.REDIS_URL; + if (process.env.REDIS_URL) { + return process.env.REDIS_URL; } - if (config.REDIS_HOST) { - const port = config.REDIS_PORT || "6379"; - return `redis://${config.REDIS_HOST}:${port}`; + if (process.env.REDIS_HOST) { + const port = process.env.REDIS_PORT || "6379"; + return `redis://${process.env.REDIS_HOST}:${port}`; } return null; @@ -40,12 +37,12 @@ async function getRedisClient() { try { const client = createClient({ url }); client.on("error", (error) => { - logger.error(`Redis error: ${error.message}`); + console.error("Redis error:", error.message); }); await client.connect(); return client; } catch (error) { - logger.error(`Redis connection failed: ${error.message}`); + console.error("Redis connection failed:", error.message); return null; } })(); diff --git a/server/src/services/userService.js b/server/services/userService.js similarity index 93% rename from server/src/services/userService.js rename to server/services/userService.js index 9130994..d0ebd47 100644 --- a/server/src/services/userService.js +++ b/server/services/userService.js @@ -1,6 +1,5 @@ -import config from "../config/index.js"; - import User from "../models/User.js"; +import { OTP_TTL_MS } from "../config/constants.js"; import { sendMail } from "./email.js"; export async function isUsernameAvailable(username) { @@ -39,13 +38,13 @@ export function signup(email, username, password, dob) { const currentTimestamp = data[0].verification?.[0]?.timestamp ?? 0; const currentOtp = data[0].verification?.[0]?.code; - if (data[0].username !== username && Date.now() - currentTimestamp < config.OTP_TTL_MS) { + if (data[0].username !== username && Date.now() - currentTimestamp < OTP_TTL_MS) { return { message: "not_TLE", otp: currentOtp }; } - if (data[0].username === username && Date.now() - currentTimestamp < config.OTP_TTL_MS) { + if (data[0].username === username && Date.now() - currentTimestamp < OTP_TTL_MS) { return { message: "not_TLE_2", otp: currentOtp, tag: data[0].tag }; } - if (data[0].username === username && Date.now() - currentTimestamp > config.OTP_TTL_MS) { + if (data[0].username === username && Date.now() - currentTimestamp > OTP_TTL_MS) { return { message: "TLE", tag: data[0].tag }; } return { message: "TLE_2" }; diff --git a/server/src/socket/index.js b/server/socket/index.js similarity index 100% rename from server/src/socket/index.js rename to server/socket/index.js diff --git a/server/src/socket/runtime.js b/server/socket/runtime.js similarity index 100% rename from server/src/socket/runtime.js rename to server/socket/runtime.js diff --git a/server/src/config/cors.js b/server/src/config/cors.js deleted file mode 100644 index 42d4cde..0000000 --- a/server/src/config/cors.js +++ /dev/null @@ -1,20 +0,0 @@ -import config from "./index.js"; - -const corsOptions = { - credentials: true, - // Custom origin validation function - origin: (requestOrigin, callback) => { - if (requestOrigin && config.CORS_WHITELIST.includes(requestOrigin)) { - return callback(null, true); - } else { - // In development allow all origins; other, block with an error - if (config.NODE_ENV === "development") { - return callback(null, true); - } - - return callback(new Error("Not allowed by CORS")); - } - }, -}; - -export default corsOptions; diff --git a/server/src/config/db.js b/server/src/config/db.js deleted file mode 100644 index a9c4739..0000000 --- a/server/src/config/db.js +++ /dev/null @@ -1,55 +0,0 @@ -import mongoose from "mongoose"; - -import config from "./index.js"; -import logger from "../lib/winston.js"; - -const connectionOptions = { - serverSelectionTimeoutMS: 8000, - - serverApi: { - version: "1", - strict: true, - deprecationErrors: true, - }, -}; - -mongoose.set("strictQuery", true); - -const connectDatabase = async (options = {}) => { - if (!config.MONGO_URI) { - throw new Error("MONGO_URI is not set"); - } - - try { - await mongoose.connect(config.MONGO_URI, { - ...connectionOptions, - ...options, - }); - - logger.info("Database connected successfully"); - } catch (error) { - if (error.message?.includes("auth")) { - logger.error( - "[MongoDB] Authentication failed. Check if special characters in the password are URL-encoded.", - ); - } - - logger.error(`Failed to connect database: ${error.message}`); - - throw error; - } -}; - -const disconnectDatabase = async () => { - try { - await mongoose.disconnect(); - - logger.info("Database disconnected successfully"); - } catch (error) { - logger.error(`Error during database disconnection: ${error.message}`); - - throw error; - } -}; - -export { connectDatabase, disconnectDatabase }; diff --git a/server/src/config/index.js b/server/src/config/index.js deleted file mode 100644 index 4128495..0000000 --- a/server/src/config/index.js +++ /dev/null @@ -1,46 +0,0 @@ -/** - * Constants - */ -const OTP_TTL_MS = Number(process.env.OTP_TTL_MS) || 10 * 60 * 1000; // 10 minutes - -const config = { - PORT: process.env.PORT || 2000, - NODE_ENV: process.env.NODE_ENV || "development", - MONGO_URI: process.env.MONGO_URI, - ACCESS_TOKEN: process.env.ACCESS_TOKEN, - DICEBEAR_API: process.env.DICEBEAR_API, - DICEBEAR_STYLE: process.env.DICEBEAR_STYLE, - DEFAULT_PROFILE_PIC: process.env.DEFAULT_PROFILE_PIC, - EMAIL_USER: process.env.MAIL_USER, - MAIL_USER: process.env.MAIL_USER, - MAIL_TRANSPORT: process.env.MAIL_TRANSPORT, - MAIL_PASS: process.env.MAIL_PASS, - SMTP_HOST: process.env.SMTP_HOST, - SMTP_USER: process.env.SMTP_USER, - SMTP_PASS: process.env.SMTP_PASS, - SMTP_PORT: process.env.SMTP_PORT, - SMTP_SECURE: process.env.SMTP_SECURE, - OAUTH_CLIENT_ID: process.env.OAUTH_CLIENT_ID, - OAUTH_CLIENT_SECRET: process.env.OAUTH_CLIENT_SECRET, - OAUTH_REFRESH_TOKEN: process.env.OAUTH_REFRESH_TOKEN, - REDIS_URL: process.env.REDIS_URL, - REDIS_CACHE_TTL_SECONDS: Number(process.env.REDIS_CACHE_TTL_SECONDS) || 30, - REDIS_HOST: process.env.REDIS_HOST, - REDIS_PORT: process.env.REDIS_PORT, - CORS_WHITELIST: ( - process.env.FRONTEND_ORIGINS || - "http://localhost:3000,http://localhost:5173" - ) - .split(",") - .map((origin) => origin.trim()) - .filter(Boolean), - OTP_TTL_MS, - UPSTASH_REDIS_URL: process.env.UPSTASH_REDIS_URL, - UPSTASH_REDIS_TLS_URL: process.env.UPSTASH_REDIS_TLS_URL, - RATE_LIMIT_WINDOW_MS: - Number(process.env.RATE_LIMIT_WINDOW_MS) || 15 * 60 * 1000, - LOGTAIL_SOURCE_TOKEN: process.env.LOGTAIL_SOURCE_TOKEN, - LOGTAIL_INGESTING_HOST: process.env.LOGTAIL_INGESTING_HOST, -}; - -export default config; diff --git a/server/src/index.js b/server/src/index.js deleted file mode 100644 index b5e7c5d..0000000 --- a/server/src/index.js +++ /dev/null @@ -1,74 +0,0 @@ -import "dotenv/config"; -import config from "./config/index.js"; - -import app from "./server.js"; -import { Server as SocketIOServer } from "socket.io"; -import { connectDatabase, disconnectDatabase } from "./config/db.js"; -import { attachSocketHandlers } from "./socket/index.js"; -import { setIO } from "./socket/runtime.js"; -import { verifyMailTransport } from "./services/email.js"; - -import logger, { logtail } from "./lib/winston.js"; - -let server; - -(async function startServer() { - try { - await connectDatabase(); - await verifyMailTransport(); - - server = app.listen(config.PORT, () => { - logger.info(`Server running on port ${config.PORT}`); - }); - - const io = new SocketIOServer(server, { - pingTimeout: 20000, - cors: { - origin: config.CORS_WHITELIST, - }, - }); - setIO(io); - attachSocketHandlers(io); - } catch (error) { - logger.error(`Failed to start server: ${error.message}`); - if (config.NODE_ENV === "production") process.exit(1); - } -})(); - -// Handle graceful shutdown on termination signals -const serverTermination = async (signal) => { - try { - // Log a warning indicating the server is shutting down - logger.warn(`${signal} received. Shutting down gracefully...`); - - // Disconnect from the database - await disconnectDatabase(); - - // Close http server - if (server) { - await new Promise((resolve, reject) => { - server.close((err) => { - if (err) return reject(err); - - logger.info("HTTP server closed"); - resolve(); - }); - }); - } - - // Flush any remaining log to Logtail before exiting - if (logtail) { - await logtail.flush(); - } - - // Exit the process cleanly - process.exit(0); - } catch (error) { - logger.error(`Error during server shutdown: ${error.message}`); - process.exit(1); - } -}; - -// Listen for the termination signals and trigger graceful shutdown -process.on("SIGTERM", serverTermination); -process.on("SIGINT", serverTermination); diff --git a/server/src/lib/winston.js b/server/src/lib/winston.js deleted file mode 100644 index 6d5ce2a..0000000 --- a/server/src/lib/winston.js +++ /dev/null @@ -1,50 +0,0 @@ -import { createLogger, format, transports } from "winston"; -import { Logtail } from "@logtail/node"; -import { LogtailTransport } from "@logtail/winston"; - -import config from "../config/index.js"; - -const loggerTransports = []; - -const { combine, colorize, timestamp, printf } = format; - -loggerTransports.push( - new transports.Console({ - handleExceptions: true, - - format: combine( - colorize({ all: true }), - - timestamp({ - format: "DD MMMM hh:mm:ss A", - }), - - printf(({ level, message, timestamp }) => { - return `${timestamp} [${level}]: ${message}`; - }), - ), - }), -); - -let logtail; - -if (config.NODE_ENV === "production") { - if (!config.LOGTAIL_SOURCE_TOKEN || !config.LOGTAIL_INGESTING_HOST) { - throw new Error("Logtail source token or ingesting host is missing"); - } - - logtail = new Logtail(config.LOGTAIL_SOURCE_TOKEN, { - endpoint: config.LOGTAIL_INGESTING_HOST, - }); - - loggerTransports.push(new LogtailTransport(logtail)); -} - -const logger = createLogger({ - level: config.NODE_ENV === "development" ? "debug" : "info", - - transports: loggerTransports, -}); - -export default logger; -export { logtail }; diff --git a/server/src/middleware/rateLimit.js b/server/src/middleware/rateLimit.js deleted file mode 100644 index 6d3be93..0000000 --- a/server/src/middleware/rateLimit.js +++ /dev/null @@ -1,59 +0,0 @@ -import config from "../config/index.js"; - -import { rateLimit } from "express-rate-limit"; - -const rateLimitHandler = (req, res, next, options) => { - return res.status(options.statusCode).json({ - status: options.statusCode, - message: options.message, - }); -}; - -const defaultLimitOpt = { - windowMs: config.RATE_LIMIT_WINDOW_MS, - legacyHeaders: false, - standardHeaders: true, - handler: rateLimitHandler, -}; - -const rateLimitOpt = new Map([ - [ - "basic", - { - ...defaultLimitOpt, - limit: 100, - message: "Too many requests. Please try again later.", - }, - ], - [ - "auth", - { - ...defaultLimitOpt, - limit: 10, - message: "Too many authentication attempts. Please try again later.", - }, - ], - [ - "otp", - { - ...defaultLimitOpt, - limit: 3, - message: "Too many OTP requests. Please wait before trying again.", - }, - ], - [ - "chat", - { - ...defaultLimitOpt, - windowMs: 60 * 1000, - limit: 30, - message: "You are sending messages too quickly.", - }, - ], -]); - -const expressRateLimit = (type = "basic") => { - return rateLimit(rateLimitOpt.get(type) || rateLimitOpt.get("basic")); -}; - -export default expressRateLimit; diff --git a/server/src/routes/index.js b/server/src/routes/index.js deleted file mode 100644 index 1cab1de..0000000 --- a/server/src/routes/index.js +++ /dev/null @@ -1,23 +0,0 @@ -import express from "express"; - -import authRoutes from "./auth.js"; -import chatRoutes from "./chat.js"; -import directMessageRoutes from "./directMessages.js"; -import friendsRoutes from "./friends.js"; -import invitesRoutes from "./invites.js"; -import notificationRoutes from "./notifications.js"; -import profileRoutes from "./profile.js"; -import serversRoutes from "./servers.js"; - -const router = express.Router(); - -router.use("/auth", authRoutes); -router.use("/chat", chatRoutes); -router.use("/direct-messages", directMessageRoutes); -router.use("/friends", friendsRoutes); -router.use("/invites", invitesRoutes); -router.use("/notifications", notificationRoutes); -router.use("/profile", profileRoutes); -router.use("/servers", serversRoutes); - -export default router; diff --git a/server/src/server.js b/server/src/server.js deleted file mode 100644 index fed8bbb..0000000 --- a/server/src/server.js +++ /dev/null @@ -1,28 +0,0 @@ -import express from "express"; -import cors from "cors"; -import helmet from "helmet"; -import compression from "compression"; - -import corsOptions from "./config/cors.js"; - -import routes from "./routes/index.js"; - -const app = express(); - -app.use(cors(corsOptions)); -app.use(helmet()); -app.use(compression()); -app.use(express.json({ limit: "10kb" })); -app.use(express.urlencoded({ extended: true, limit: "10kb" })); - -app.get("/", (req, res) => { - res.status(200).json({ - success: true, - message: "Server is up and running!", - status: "ok", - }); -}); - -app.use("/api/v1", routes); - -export default app; diff --git a/server/tests/auth.test.js b/server/tests/auth.test.js new file mode 100644 index 0000000..8c9ef13 --- /dev/null +++ b/server/tests/auth.test.js @@ -0,0 +1,126 @@ +import "./mocks.js"; + +import request from "supertest"; +import { describe, expect, test } from "vitest"; + +import User from "../models/User.js"; +import app from "../app.js"; + +async function createAndLoginUser(userData) { + + await request(app).post("/signup").send(userData); + + await request(app).post("/verify").send({ + email: userData.email, + otp_value: "123456", + }); + + const signinRes = await request(app).post("/signin").send({ + email: userData.email, + password: userData.password, + }); + + return signinRes.body.token; +} + +describe("Friend Request Flow", () => { + + test("User should send friend request", async () => { + + const tokenA = await createAndLoginUser({ + email: "a@test.com", + username: "userA", + password: "1234567", + dob: "2005-01-01", + }); + + await createAndLoginUser({ + email: "b@test.com", + username: "userB", + password: "1234567", + dob: "2005-01-01", + }); + + const userB = await User.findOne({ + email: "b@test.com", + }); + + const friendTag = `${userB.username}#${userB.tag}`; + + const res = await request(app) + .post("/add_friend") + .set("x-auth-token", tokenA) + .send({ + friend: friendTag, + }); + + expect(res.statusCode).toBe(203); + + expect(res.body.message) + .toBe("Request sent successfully"); + + }); + + test("User should accept friend request", async () => { + + const tokenA = await createAndLoginUser({ + email: "a@test.com", + username: "userA", + password: "1234567", + dob: "2005-01-01", + }); + + const tokenB = await createAndLoginUser({ + email: "b@test.com", + username: "userB", + password: "1234567", + dob: "2005-01-01", + }); + + const userA = await User.findOne({ + email: "a@test.com", + }); + + const userB = await User.findOne({ + email: "b@test.com", + }); + + await request(app) + .post("/add_friend") + .set("x-auth-token", tokenA) + .send({ + friend: `${userB.username}#${userB.tag}`, + }); + + const res = await request(app) + .post("/process_req") + .set("x-auth-token", tokenB) + .send({ + message: "Accept", + friend_data: { + id: String(userA._id), + username: userA.username, + tag: userA.tag, + profile_pic: userA.profile_pic, + }, + }); + + expect(res.statusCode).toBe(200); + + const updatedA = await User.findById(userA._id); + + const updatedB = await User.findById(userB._id); + + expect(updatedA.friends.length).toBe(1); + + expect(updatedB.friends.length).toBe(1); + + expect(updatedA.friends[0].id) + .toBe(String(userB._id)); + + expect(updatedB.friends[0].id) + .toBe(String(userA._id)); + + }); + +}); \ No newline at end of file diff --git a/server/tests/friend.test.js b/server/tests/friend.test.js new file mode 100644 index 0000000..d96e9ce --- /dev/null +++ b/server/tests/friend.test.js @@ -0,0 +1,109 @@ +import "./mocks.js"; + +import request from "supertest"; +import { describe, expect, test } from "vitest"; + +import User from "../models/User.js"; +import app from "../app.js"; + +async function createAndLoginUser(userData) { + await request(app).post("/signup").send(userData); + + await request(app).post("/verify").send({ + email: userData.email, + otp_value: "123456", + }); + + const signinRes = await request(app).post("/signin").send({ + email: userData.email, + password: userData.password, + }); + + return signinRes.body.token; +} + +describe("Friend Request Flow", () => { + test("User should send friend request", async () => { + const tokenA = await createAndLoginUser({ + email: "a@test.com", + username: "userA", + password: "1234567", + dob: "2005-01-01", + }); + + await createAndLoginUser({ + email: "b@test.com", + username: "userB", + password: "1234567", + dob: "2005-01-01", + }); + + const userB = await User.findOne({ + email: "b@test.com", + }); + + const friendTag = `${userB.username}#${userB.tag}`; + + const res = await request(app) + .post("/add_friend") + .set("x-auth-token", tokenA) + .send({ + friend: friendTag, + }); + + expect(res.statusCode).toBe(203); + + expect(res.body.message).toBe("Request sent successfully"); + }); + + test("User should ignore friend request", async () => { + const tokenA = await createAndLoginUser({ + email: "a@test.com", + username: "userA", + password: "1234567", + dob: "2005-01-01", + }); + + const tokenB = await createAndLoginUser({ + email: "b@test.com", + username: "userB", + password: "1234567", + dob: "2005-01-01", + }); + + const userA = await User.findOne({ + email: "a@test.com", + }); + + const userB = await User.findOne({ + email: "b@test.com", + }); + + await request(app) + .post("/add_friend") + .set("x-auth-token", tokenA) + .send({ + friend: `${userB.username}#${userB.tag}`, + }); + + const res = await request(app) + .post("/process_req") + .set("x-auth-token", tokenB) + .send({ + message: "Ignore", + friend_data: { + id: String(userA._id), + }, + }); + + expect(res.statusCode).toBe(200); + + const updatedA = await User.findById(userA._id); + + const updatedB = await User.findById(userB._id); + + expect(updatedA.friends.length).toBe(0); + + expect(updatedB.friends.length).toBe(0); + }); +}); diff --git a/server/tests/mocks.js b/server/tests/mocks.js new file mode 100644 index 0000000..b5bc80d --- /dev/null +++ b/server/tests/mocks.js @@ -0,0 +1,13 @@ +import { vi } from "vitest"; + +vi.mock("../services/email.js", () => { + return { + generateOTP: vi.fn(() => "123456"), + + sendMail: vi.fn(async () => ({ + ok: true, + })), + + verifyMailTransport: vi.fn(async () => true), + }; +}); \ No newline at end of file diff --git a/server/tests/setup.js b/server/tests/setup.js new file mode 100644 index 0000000..15f3d1b --- /dev/null +++ b/server/tests/setup.js @@ -0,0 +1,32 @@ +process.env.ACCESS_TOKEN = "testsecret"; +process.env.DICEBEAR_API = "https://api.dicebear.com/7.x"; +process.env.DICEBEAR_STYLE = "bottts"; +process.env.DEFAULT_PROFILE_PIC = "test.png"; + +import mongoose from "mongoose"; +import { MongoMemoryServer } from "mongodb-memory-server"; + +let mongoServer; + +beforeAll(async () => { + mongoServer = await MongoMemoryServer.create(); + + const uri = mongoServer.getUri(); + + await mongoose.connect(uri); +}); + +afterEach(async () => { + const collections = mongoose.connection.collections; + + for (const key in collections) { + await collections[key].deleteMany({}); + } +}); + +afterAll(async () => { + await mongoose.connection.dropDatabase(); + await mongoose.connection.close(); + + await mongoServer.stop(); +}); \ No newline at end of file diff --git a/server/vitest.config.js b/server/vitest.config.js new file mode 100644 index 0000000..38a6cd9 --- /dev/null +++ b/server/vitest.config.js @@ -0,0 +1,10 @@ +import { defineConfig } from "vitest/config"; + +export default defineConfig({ + test: { + globals: true, + environment: "node", + setupFiles: "./tests/setup.js", + testTimeout: 30000 + }, +}); \ No newline at end of file