diff --git a/.github/workflows/test_next_server.yml b/.github/workflows/test_next_server.yml new file mode 100644 index 00000000..892566d6 --- /dev/null +++ b/.github/workflows/test_next_server.yml @@ -0,0 +1,45 @@ +name: Test Next Server + +on: + pull_request: + paths: + - "www/**" + push: + branches: + - main + paths: + - "www/**" + +jobs: + test-next-server: + runs-on: ubuntu-latest + + defaults: + run: + working-directory: ./www + + steps: + - uses: actions/checkout@v4 + + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: '20' + + - name: Install pnpm + uses: pnpm/action-setup@v4 + with: + version: 8 + + - name: Setup Node.js cache + uses: actions/setup-node@v4 + with: + node-version: '20' + cache: 'pnpm' + cache-dependency-path: './www/pnpm-lock.yaml' + + - name: Install dependencies + run: pnpm install + + - name: Run tests + run: pnpm test \ No newline at end of file diff --git a/.gitignore b/.gitignore index 29d56f25..f3249991 100644 --- a/.gitignore +++ b/.gitignore @@ -17,3 +17,4 @@ server/test.sqlite CLAUDE.local.md www/.env.development www/.env.production +.playwright-mcp diff --git a/CHANGELOG.md b/CHANGELOG.md index 433691e9..40174fc4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,37 @@ # Changelog +## [0.10.0](https://github.com/Monadical-SAS/reflector/compare/v0.9.0...v0.10.0) (2025-09-11) + + +### Features + +* replace nextjs-config with environment variables ([#632](https://github.com/Monadical-SAS/reflector/issues/632)) ([369ecdf](https://github.com/Monadical-SAS/reflector/commit/369ecdff13f3862d926a9c0b87df52c9d94c4dde)) + + +### Bug Fixes + +* anonymous users transcript permissions ([#621](https://github.com/Monadical-SAS/reflector/issues/621)) ([f81fe99](https://github.com/Monadical-SAS/reflector/commit/f81fe9948a9237b3e0001b2d8ca84f54d76878f9)) +* auth post ([#624](https://github.com/Monadical-SAS/reflector/issues/624)) ([cde99ca](https://github.com/Monadical-SAS/reflector/commit/cde99ca2716f84ba26798f289047732f0448742e)) +* auth post ([#626](https://github.com/Monadical-SAS/reflector/issues/626)) ([3b85ff3](https://github.com/Monadical-SAS/reflector/commit/3b85ff3bdf4fb053b103070646811bc990c0e70a)) +* auth post ([#627](https://github.com/Monadical-SAS/reflector/issues/627)) ([962038e](https://github.com/Monadical-SAS/reflector/commit/962038ee3f2a555dc3c03856be0e4409456e0996)) +* missing follow_redirects=True on modal endpoint ([#630](https://github.com/Monadical-SAS/reflector/issues/630)) ([fc363bd](https://github.com/Monadical-SAS/reflector/commit/fc363bd49b17b075e64f9186e5e0185abc325ea7)) +* sync backend and frontend token refresh logic ([#614](https://github.com/Monadical-SAS/reflector/issues/614)) ([5a5b323](https://github.com/Monadical-SAS/reflector/commit/5a5b3233820df9536da75e87ce6184a983d4713a)) + +## [0.9.0](https://github.com/Monadical-SAS/reflector/compare/v0.8.2...v0.9.0) (2025-09-06) + + +### Features + +* frontend openapi react query ([#606](https://github.com/Monadical-SAS/reflector/issues/606)) ([c4d2825](https://github.com/Monadical-SAS/reflector/commit/c4d2825c81f81ad8835629fbf6ea8c7383f8c31b)) + + +### Bug Fixes + +* align whisper transcriber api with parakeet ([#602](https://github.com/Monadical-SAS/reflector/issues/602)) ([0663700](https://github.com/Monadical-SAS/reflector/commit/0663700a615a4af69a03c96c410f049e23ec9443)) +* kv use tls explicit ([#610](https://github.com/Monadical-SAS/reflector/issues/610)) ([08d88ec](https://github.com/Monadical-SAS/reflector/commit/08d88ec349f38b0d13e0fa4cb73486c8dfd31836)) +* source kind for file processing ([#601](https://github.com/Monadical-SAS/reflector/issues/601)) ([dc82f8b](https://github.com/Monadical-SAS/reflector/commit/dc82f8bb3bdf3ab3d4088e592a30fd63907319e1)) +* token refresh locking ([#613](https://github.com/Monadical-SAS/reflector/issues/613)) ([7f5a4c9](https://github.com/Monadical-SAS/reflector/commit/7f5a4c9ddc7fd098860c8bdda2ca3b57f63ded2f)) + ## [0.8.2](https://github.com/Monadical-SAS/reflector/compare/v0.8.1...v0.8.2) (2025-08-29) diff --git a/CLAUDE.md b/CLAUDE.md index 14c58e42..22a99171 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -66,7 +66,6 @@ pnpm install # Copy configuration templates cp .env_template .env -cp config-template.ts config.ts ``` **Development:** diff --git a/README.md b/README.md index 497dd5b5..ebb91fcb 100644 --- a/README.md +++ b/README.md @@ -99,11 +99,10 @@ Start with `cd www`. ```bash pnpm install -cp .env_template .env -cp config-template.ts config.ts +cp .env.example .env ``` -Then, fill in the environment variables in `.env` and the configuration in `config.ts` as needed. If you are unsure on how to proceed, ask in Zulip. +Then, fill in the environment variables in `.env` as needed. If you are unsure on how to proceed, ask in Zulip. **Run in development mode** @@ -168,3 +167,34 @@ You can manually process an audio file by calling the process tool: ```bash uv run python -m reflector.tools.process path/to/audio.wav ``` + + +## Feature Flags + +Reflector uses environment variable-based feature flags to control application functionality. These flags allow you to enable or disable features without code changes. + +### Available Feature Flags + +| Feature Flag | Environment Variable | +|-------------|---------------------| +| `requireLogin` | `NEXT_PUBLIC_FEATURE_REQUIRE_LOGIN` | +| `privacy` | `NEXT_PUBLIC_FEATURE_PRIVACY` | +| `browse` | `NEXT_PUBLIC_FEATURE_BROWSE` | +| `sendToZulip` | `NEXT_PUBLIC_FEATURE_SEND_TO_ZULIP` | +| `rooms` | `NEXT_PUBLIC_FEATURE_ROOMS` | + +### Setting Feature Flags + +Feature flags are controlled via environment variables using the pattern `NEXT_PUBLIC_FEATURE_{FEATURE_NAME}` where `{FEATURE_NAME}` is the SCREAMING_SNAKE_CASE version of the feature name. + +**Examples:** +```bash +# Enable user authentication requirement +NEXT_PUBLIC_FEATURE_REQUIRE_LOGIN=true + +# Disable browse functionality +NEXT_PUBLIC_FEATURE_BROWSE=false + +# Enable Zulip integration +NEXT_PUBLIC_FEATURE_SEND_TO_ZULIP=true +``` diff --git a/compose.yml b/compose.yml index 492c7b8c..acbfd3b5 100644 --- a/compose.yml +++ b/compose.yml @@ -6,6 +6,7 @@ services: - 1250:1250 volumes: - ./server/:/app/ + - /app/.venv env_file: - ./server/.env environment: @@ -16,6 +17,7 @@ services: context: server volumes: - ./server/:/app/ + - /app/.venv env_file: - ./server/.env environment: @@ -26,6 +28,7 @@ services: context: server volumes: - ./server/:/app/ + - /app/.venv env_file: - ./server/.env environment: diff --git a/server/docs/gpu/api-transcription.md b/server/docs/gpu/api-transcription.md new file mode 100644 index 00000000..7a15d793 --- /dev/null +++ b/server/docs/gpu/api-transcription.md @@ -0,0 +1,194 @@ +## Reflector GPU Transcription API (Specification) + +This document defines the Reflector GPU transcription API that all implementations must adhere to. Current implementations include NVIDIA Parakeet (NeMo) and Whisper (faster-whisper), both deployed on Modal.com. The API surface and response shapes are OpenAI/Whisper-compatible, so clients can switch implementations by changing only the base URL. + +### Base URL and Authentication + +- Example base URLs (Modal web endpoints): + + - Parakeet: `https://--reflector-transcriber-parakeet-web.modal.run` + - Whisper: `https://--reflector-transcriber-web.modal.run` + +- All endpoints are served under `/v1` and require a Bearer token: + +``` +Authorization: Bearer +``` + +Note: To switch implementations, deploy the desired variant and point `TRANSCRIPT_URL` to its base URL. The API is identical. + +### Supported file types + +`mp3, mp4, mpeg, mpga, m4a, wav, webm` + +### Models and languages + +- Parakeet (NVIDIA NeMo): default `nvidia/parakeet-tdt-0.6b-v2` + - Language support: only `en`. Other languages return HTTP 400. +- Whisper (faster-whisper): default `large-v2` (or deployment-specific) + - Language support: multilingual (per Whisper model capabilities). + +Note: The `model` parameter is accepted by all implementations for interface parity. Some backends may treat it as informational. + +### Endpoints + +#### POST /v1/audio/transcriptions + +Transcribe one or more uploaded audio files. + +Request: multipart/form-data + +- `file` (File) — optional. Single file to transcribe. +- `files` (File[]) — optional. One or more files to transcribe. +- `model` (string) — optional. Defaults to the implementation-specific model (see above). +- `language` (string) — optional, defaults to `en`. + - Parakeet: only `en` is accepted; other values return HTTP 400 + - Whisper: model-dependent; typically multilingual +- `batch` (boolean) — optional, defaults to `false`. + +Notes: + +- Provide either `file` or `files`, not both. If neither is provided, HTTP 400. +- `batch` requires `files`; using `batch=true` without `files` returns HTTP 400. +- Response shape for multiple files is the same regardless of `batch`. +- Files sent to this endpoint are processed in a single pass (no VAD/chunking). This is intended for short clips (roughly ≤ 30s; depends on GPU memory/model). For longer audio, prefer `/v1/audio/transcriptions-from-url` which supports VAD-based chunking. + +Responses + +Single file response: + +```json +{ + "text": "transcribed text", + "words": [ + { "word": "hello", "start": 0.0, "end": 0.5 }, + { "word": "world", "start": 0.5, "end": 1.0 } + ], + "filename": "audio.mp3" +} +``` + +Multiple files response: + +```json +{ + "results": [ + {"filename": "a1.mp3", "text": "...", "words": [...]}, + {"filename": "a2.mp3", "text": "...", "words": [...]}] +} +``` + +Notes: + +- Word objects always include keys: `word`, `start`, `end`. +- Some implementations may include a trailing space in `word` to match Whisper tokenization behavior; clients should trim if needed. + +Example curl (single file): + +```bash +curl -X POST \ + -H "Authorization: Bearer $REFLECTOR_GPU_APIKEY" \ + -F "file=@/path/to/audio.mp3" \ + -F "language=en" \ + "$BASE_URL/v1/audio/transcriptions" +``` + +Example curl (multiple files, batch): + +```bash +curl -X POST \ + -H "Authorization: Bearer $REFLECTOR_GPU_APIKEY" \ + -F "files=@/path/a1.mp3" -F "files=@/path/a2.mp3" \ + -F "batch=true" -F "language=en" \ + "$BASE_URL/v1/audio/transcriptions" +``` + +#### POST /v1/audio/transcriptions-from-url + +Transcribe a single remote audio file by URL. + +Request: application/json + +Body parameters: + +- `audio_file_url` (string) — required. URL of the audio file to transcribe. +- `model` (string) — optional. Defaults to the implementation-specific model (see above). +- `language` (string) — optional, defaults to `en`. Parakeet only accepts `en`. +- `timestamp_offset` (number) — optional, defaults to `0.0`. Added to each word's `start`/`end` in the response. + +```json +{ + "audio_file_url": "https://example.com/audio.mp3", + "model": "nvidia/parakeet-tdt-0.6b-v2", + "language": "en", + "timestamp_offset": 0.0 +} +``` + +Response: + +```json +{ + "text": "transcribed text", + "words": [ + { "word": "hello", "start": 10.0, "end": 10.5 }, + { "word": "world", "start": 10.5, "end": 11.0 } + ] +} +``` + +Notes: + +- `timestamp_offset` is added to each word’s `start`/`end` in the response. +- Implementations may perform VAD-based chunking and batching for long-form audio; word timings are adjusted accordingly. + +Example curl: + +```bash +curl -X POST \ + -H "Authorization: Bearer $REFLECTOR_GPU_APIKEY" \ + -H "Content-Type: application/json" \ + -d '{ + "audio_file_url": "https://example.com/audio.mp3", + "language": "en", + "timestamp_offset": 0 + }' \ + "$BASE_URL/v1/audio/transcriptions-from-url" +``` + +### Error handling + +- 400 Bad Request + - Parakeet: `language` other than `en` + - Missing required parameters (`file`/`files` for upload; `audio_file_url` for URL endpoint) + - Unsupported file extension +- 401 Unauthorized + - Missing or invalid Bearer token +- 404 Not Found + - `audio_file_url` does not exist + +### Implementation details + +- GPUs: A10G for small-file/live, L40S for large-file URL transcription (subject to deployment) +- VAD chunking and segment batching; word timings adjusted and overlapping ends constrained +- Pads very short segments (< 0.5s) to avoid model crashes on some backends + +### Server configuration (Reflector API) + +Set the Reflector server to use the Modal backend and point `TRANSCRIPT_URL` to your chosen deployment: + +``` +TRANSCRIPT_BACKEND=modal +TRANSCRIPT_URL=https://--reflector-transcriber-parakeet-web.modal.run +TRANSCRIPT_MODAL_API_KEY= +``` + +### Conformance tests + +Use the pytest-based conformance tests to validate any new implementation (including self-hosted) against this spec: + +``` +TRANSCRIPT_URL=https:// \ +TRANSCRIPT_MODAL_API_KEY=your-api-key \ +uv run -m pytest -m gpu_modal --no-cov server/tests/test_gpu_modal_transcript.py +``` diff --git a/server/gpu/modal_deployments/reflector_transcriber.py b/server/gpu/modal_deployments/reflector_transcriber.py index 4bbbe512..3be25542 100644 --- a/server/gpu/modal_deployments/reflector_transcriber.py +++ b/server/gpu/modal_deployments/reflector_transcriber.py @@ -1,41 +1,78 @@ import os -import tempfile +import sys import threading +import uuid +from typing import Generator, Mapping, NamedTuple, NewType, TypedDict +from urllib.parse import urlparse import modal -from pydantic import BaseModel - -MODELS_DIR = "/models" MODEL_NAME = "large-v2" MODEL_COMPUTE_TYPE: str = "float16" MODEL_NUM_WORKERS: int = 1 - MINUTES = 60 # seconds +SAMPLERATE = 16000 +UPLOADS_PATH = "/uploads" +CACHE_PATH = "/models" +SUPPORTED_FILE_EXTENSIONS = ["mp3", "mp4", "mpeg", "mpga", "m4a", "wav", "webm"] +VAD_CONFIG = { + "batch_max_duration": 30.0, + "silence_padding": 0.5, + "window_size": 512, +} -volume = modal.Volume.from_name("models", create_if_missing=True) + +WhisperUniqFilename = NewType("WhisperUniqFilename", str) +AudioFileExtension = NewType("AudioFileExtension", str) app = modal.App("reflector-transcriber") +model_cache = modal.Volume.from_name("models", create_if_missing=True) +upload_volume = modal.Volume.from_name("whisper-uploads", create_if_missing=True) + + +class TimeSegment(NamedTuple): + """Represents a time segment with start and end times.""" + + start: float + end: float + + +class AudioSegment(NamedTuple): + """Represents an audio segment with timing and audio data.""" + + start: float + end: float + audio: any + + +class TranscriptResult(NamedTuple): + """Represents a transcription result with text and word timings.""" + + text: str + words: list["WordTiming"] + + +class WordTiming(TypedDict): + """Represents a word with its timing information.""" + + word: str + start: float + end: float + def download_model(): from faster_whisper import download_model - volume.reload() + model_cache.reload() - download_model(MODEL_NAME, cache_dir=MODELS_DIR) + download_model(MODEL_NAME, cache_dir=CACHE_PATH) - volume.commit() + model_cache.commit() image = ( modal.Image.debian_slim(python_version="3.12") - .pip_install( - "huggingface_hub==0.27.1", - "hf-transfer==0.1.9", - "torch==2.5.1", - "faster-whisper==1.1.1", - ) .env( { "HF_HUB_ENABLE_HF_TRANSFER": "1", @@ -45,19 +82,98 @@ image = ( ), } ) - .run_function(download_model, volumes={MODELS_DIR: volume}) + .apt_install("ffmpeg") + .pip_install( + "huggingface_hub==0.27.1", + "hf-transfer==0.1.9", + "torch==2.5.1", + "faster-whisper==1.1.1", + "fastapi==0.115.12", + "requests", + "librosa==0.10.1", + "numpy<2", + "silero-vad==5.1.0", + ) + .run_function(download_model, volumes={CACHE_PATH: model_cache}) ) +def detect_audio_format(url: str, headers: Mapping[str, str]) -> AudioFileExtension: + parsed_url = urlparse(url) + url_path = parsed_url.path + + for ext in SUPPORTED_FILE_EXTENSIONS: + if url_path.lower().endswith(f".{ext}"): + return AudioFileExtension(ext) + + content_type = headers.get("content-type", "").lower() + if "audio/mpeg" in content_type or "audio/mp3" in content_type: + return AudioFileExtension("mp3") + if "audio/wav" in content_type: + return AudioFileExtension("wav") + if "audio/mp4" in content_type: + return AudioFileExtension("mp4") + + raise ValueError( + f"Unsupported audio format for URL: {url}. " + f"Supported extensions: {', '.join(SUPPORTED_FILE_EXTENSIONS)}" + ) + + +def download_audio_to_volume( + audio_file_url: str, +) -> tuple[WhisperUniqFilename, AudioFileExtension]: + import requests + from fastapi import HTTPException + + response = requests.head(audio_file_url, allow_redirects=True) + if response.status_code == 404: + raise HTTPException(status_code=404, detail="Audio file not found") + + response = requests.get(audio_file_url, allow_redirects=True) + response.raise_for_status() + + audio_suffix = detect_audio_format(audio_file_url, response.headers) + unique_filename = WhisperUniqFilename(f"{uuid.uuid4()}.{audio_suffix}") + file_path = f"{UPLOADS_PATH}/{unique_filename}" + + with open(file_path, "wb") as f: + f.write(response.content) + + upload_volume.commit() + return unique_filename, audio_suffix + + +def pad_audio(audio_array, sample_rate: int = SAMPLERATE): + """Add 0.5s of silence if audio is shorter than the silence_padding window. + + Whisper does not require this strictly, but aligning behavior with Parakeet + avoids edge-case crashes on extremely short inputs and makes comparisons easier. + """ + import numpy as np + + audio_duration = len(audio_array) / sample_rate + if audio_duration < VAD_CONFIG["silence_padding"]: + silence_samples = int(sample_rate * VAD_CONFIG["silence_padding"]) + silence = np.zeros(silence_samples, dtype=np.float32) + return np.concatenate([audio_array, silence]) + return audio_array + + @app.cls( gpu="A10G", timeout=5 * MINUTES, scaledown_window=5 * MINUTES, - allow_concurrent_inputs=6, image=image, - volumes={MODELS_DIR: volume}, + volumes={CACHE_PATH: model_cache, UPLOADS_PATH: upload_volume}, ) -class Transcriber: +@modal.concurrent(max_inputs=10) +class TranscriberWhisperLive: + """Live transcriber class for small audio segments (A10G). + + Mirrors the Parakeet live class API but uses Faster-Whisper under the hood. + """ + @modal.enter() def enter(self): import faster_whisper @@ -71,23 +187,200 @@ class Transcriber: device=self.device, compute_type=MODEL_COMPUTE_TYPE, num_workers=MODEL_NUM_WORKERS, - download_root=MODELS_DIR, + download_root=CACHE_PATH, local_files_only=True, ) + print(f"Model is on device: {self.device}") @modal.method() def transcribe_segment( self, - audio_data: str, - audio_suffix: str, - language: str, + filename: str, + language: str = "en", ): - with tempfile.NamedTemporaryFile("wb+", suffix=f".{audio_suffix}") as fp: - fp.write(audio_data) + """Transcribe a single uploaded audio file by filename.""" + upload_volume.reload() + + file_path = f"{UPLOADS_PATH}/{filename}" + if not os.path.exists(file_path): + raise FileNotFoundError(f"File not found: {file_path}") + + with self.lock: + with NoStdStreams(): + segments, _ = self.model.transcribe( + file_path, + language=language, + beam_size=5, + word_timestamps=True, + vad_filter=True, + vad_parameters={"min_silence_duration_ms": 500}, + ) + + segments = list(segments) + text = "".join(segment.text for segment in segments).strip() + words = [ + { + "word": word.word, + "start": round(float(word.start), 2), + "end": round(float(word.end), 2), + } + for segment in segments + for word in segment.words + ] + + return {"text": text, "words": words} + + @modal.method() + def transcribe_batch( + self, + filenames: list[str], + language: str = "en", + ): + """Transcribe multiple uploaded audio files and return per-file results.""" + upload_volume.reload() + + results = [] + for filename in filenames: + file_path = f"{UPLOADS_PATH}/{filename}" + if not os.path.exists(file_path): + raise FileNotFoundError(f"Batch file not found: {file_path}") + + with self.lock: + with NoStdStreams(): + segments, _ = self.model.transcribe( + file_path, + language=language, + beam_size=5, + word_timestamps=True, + vad_filter=True, + vad_parameters={"min_silence_duration_ms": 500}, + ) + + segments = list(segments) + text = "".join(seg.text for seg in segments).strip() + words = [ + { + "word": w.word, + "start": round(float(w.start), 2), + "end": round(float(w.end), 2), + } + for seg in segments + for w in seg.words + ] + + results.append( + { + "filename": filename, + "text": text, + "words": words, + } + ) + + return results + + +@app.cls( + gpu="L40S", + timeout=15 * MINUTES, + image=image, + volumes={CACHE_PATH: model_cache, UPLOADS_PATH: upload_volume}, +) +class TranscriberWhisperFile: + """File transcriber for larger/longer audio, using VAD-driven batching (L40S).""" + + @modal.enter() + def enter(self): + import faster_whisper + import torch + from silero_vad import load_silero_vad + + self.lock = threading.Lock() + self.use_gpu = torch.cuda.is_available() + self.device = "cuda" if self.use_gpu else "cpu" + self.model = faster_whisper.WhisperModel( + MODEL_NAME, + device=self.device, + compute_type=MODEL_COMPUTE_TYPE, + num_workers=MODEL_NUM_WORKERS, + download_root=CACHE_PATH, + local_files_only=True, + ) + self.vad_model = load_silero_vad(onnx=False) + + @modal.method() + def transcribe_segment( + self, filename: str, timestamp_offset: float = 0.0, language: str = "en" + ): + import librosa + import numpy as np + from silero_vad import VADIterator + + def vad_segments( + audio_array, + sample_rate: int = SAMPLERATE, + window_size: int = VAD_CONFIG["window_size"], + ) -> Generator[TimeSegment, None, None]: + """Generate speech segments as TimeSegment using Silero VAD.""" + iterator = VADIterator(self.vad_model, sampling_rate=sample_rate) + start = None + for i in range(0, len(audio_array), window_size): + chunk = audio_array[i : i + window_size] + if len(chunk) < window_size: + chunk = np.pad( + chunk, (0, window_size - len(chunk)), mode="constant" + ) + speech = iterator(chunk) + if not speech: + continue + if "start" in speech: + start = speech["start"] + continue + if "end" in speech and start is not None: + end = speech["end"] + yield TimeSegment( + start / float(SAMPLERATE), end / float(SAMPLERATE) + ) + start = None + iterator.reset_states() + + upload_volume.reload() + file_path = f"{UPLOADS_PATH}/{filename}" + if not os.path.exists(file_path): + raise FileNotFoundError(f"File not found: {file_path}") + + audio_array, _sr = librosa.load(file_path, sr=SAMPLERATE, mono=True) + + # Batch segments up to ~30s windows by merging contiguous VAD segments + merged_batches: list[TimeSegment] = [] + batch_start = None + batch_end = None + max_duration = VAD_CONFIG["batch_max_duration"] + for segment in vad_segments(audio_array): + seg_start, seg_end = segment.start, segment.end + if batch_start is None: + batch_start, batch_end = seg_start, seg_end + continue + if seg_end - batch_start <= max_duration: + batch_end = seg_end + else: + merged_batches.append(TimeSegment(batch_start, batch_end)) + batch_start, batch_end = seg_start, seg_end + if batch_start is not None and batch_end is not None: + merged_batches.append(TimeSegment(batch_start, batch_end)) + + all_text = [] + all_words = [] + + for segment in merged_batches: + start_time, end_time = segment.start, segment.end + s_idx = int(start_time * SAMPLERATE) + e_idx = int(end_time * SAMPLERATE) + segment = audio_array[s_idx:e_idx] + segment = pad_audio(segment, SAMPLERATE) with self.lock: segments, _ = self.model.transcribe( - fp.name, + segment, language=language, beam_size=5, word_timestamps=True, @@ -96,66 +389,220 @@ class Transcriber: ) segments = list(segments) - text = "".join(segment.text for segment in segments) + text = "".join(seg.text for seg in segments).strip() words = [ - {"word": word.word, "start": word.start, "end": word.end} - for segment in segments - for word in segment.words + { + "word": w.word, + "start": round(float(w.start) + start_time + timestamp_offset, 2), + "end": round(float(w.end) + start_time + timestamp_offset, 2), + } + for seg in segments + for w in seg.words ] + if text: + all_text.append(text) + all_words.extend(words) - return {"text": text, "words": words} + return {"text": " ".join(all_text), "words": all_words} + + +def detect_audio_format(url: str, headers: dict) -> str: + from urllib.parse import urlparse + + from fastapi import HTTPException + + url_path = urlparse(url).path + for ext in SUPPORTED_FILE_EXTENSIONS: + if url_path.lower().endswith(f".{ext}"): + return ext + + content_type = headers.get("content-type", "").lower() + if "audio/mpeg" in content_type or "audio/mp3" in content_type: + return "mp3" + if "audio/wav" in content_type: + return "wav" + if "audio/mp4" in content_type: + return "mp4" + + raise HTTPException( + status_code=400, + detail=( + f"Unsupported audio format for URL. Supported extensions: {', '.join(SUPPORTED_FILE_EXTENSIONS)}" + ), + ) + + +def download_audio_to_volume(audio_file_url: str) -> tuple[str, str]: + import requests + from fastapi import HTTPException + + response = requests.head(audio_file_url, allow_redirects=True) + if response.status_code == 404: + raise HTTPException(status_code=404, detail="Audio file not found") + + response = requests.get(audio_file_url, allow_redirects=True) + response.raise_for_status() + + audio_suffix = detect_audio_format(audio_file_url, response.headers) + unique_filename = f"{uuid.uuid4()}.{audio_suffix}" + file_path = f"{UPLOADS_PATH}/{unique_filename}" + + with open(file_path, "wb") as f: + f.write(response.content) + + upload_volume.commit() + return unique_filename, audio_suffix @app.function( scaledown_window=60, - timeout=60, - allow_concurrent_inputs=40, + timeout=600, secrets=[ modal.Secret.from_name("reflector-gpu"), ], - volumes={MODELS_DIR: volume}, + volumes={CACHE_PATH: model_cache, UPLOADS_PATH: upload_volume}, + image=image, ) +@modal.concurrent(max_inputs=40) @modal.asgi_app() def web(): - from fastapi import Body, Depends, FastAPI, HTTPException, UploadFile, status + from fastapi import ( + Body, + Depends, + FastAPI, + Form, + HTTPException, + UploadFile, + status, + ) from fastapi.security import OAuth2PasswordBearer - from typing_extensions import Annotated - transcriber = Transcriber() + transcriber_live = TranscriberWhisperLive() + transcriber_file = TranscriberWhisperFile() app = FastAPI() oauth2_scheme = OAuth2PasswordBearer(tokenUrl="token") - supported_file_types = ["mp3", "mp4", "mpeg", "mpga", "m4a", "wav", "webm"] - def apikey_auth(apikey: str = Depends(oauth2_scheme)): - if apikey != os.environ["REFLECTOR_GPU_APIKEY"]: - raise HTTPException( - status_code=status.HTTP_401_UNAUTHORIZED, - detail="Invalid API key", - headers={"WWW-Authenticate": "Bearer"}, - ) + if apikey == os.environ["REFLECTOR_GPU_APIKEY"]: + return + raise HTTPException( + status_code=status.HTTP_401_UNAUTHORIZED, + detail="Invalid API key", + headers={"WWW-Authenticate": "Bearer"}, + ) - class TranscriptResponse(BaseModel): - result: dict + class TranscriptResponse(dict): + pass @app.post("/v1/audio/transcriptions", dependencies=[Depends(apikey_auth)]) def transcribe( - file: UploadFile, - model: str = "whisper-1", - language: Annotated[str, Body(...)] = "en", - ) -> TranscriptResponse: - audio_data = file.file.read() - audio_suffix = file.filename.split(".")[-1] - assert audio_suffix in supported_file_types + file: UploadFile = None, + files: list[UploadFile] | None = None, + model: str = Form(MODEL_NAME), + language: str = Form("en"), + batch: bool = Form(False), + ): + if not file and not files: + raise HTTPException( + status_code=400, detail="Either 'file' or 'files' parameter is required" + ) + if batch and not files: + raise HTTPException( + status_code=400, detail="Batch transcription requires 'files'" + ) - func = transcriber.transcribe_segment.spawn( - audio_data=audio_data, - audio_suffix=audio_suffix, - language=language, - ) - result = func.get() - return result + upload_files = [file] if file else files + + uploaded_filenames: list[str] = [] + for upload_file in upload_files: + audio_suffix = upload_file.filename.split(".")[-1] + if audio_suffix not in SUPPORTED_FILE_EXTENSIONS: + raise HTTPException( + status_code=400, + detail=( + f"Unsupported audio format. Supported extensions: {', '.join(SUPPORTED_FILE_EXTENSIONS)}" + ), + ) + + unique_filename = f"{uuid.uuid4()}.{audio_suffix}" + file_path = f"{UPLOADS_PATH}/{unique_filename}" + with open(file_path, "wb") as f: + content = upload_file.file.read() + f.write(content) + uploaded_filenames.append(unique_filename) + + upload_volume.commit() + + try: + if batch and len(upload_files) > 1: + func = transcriber_live.transcribe_batch.spawn( + filenames=uploaded_filenames, + language=language, + ) + results = func.get() + return {"results": results} + + results = [] + for filename in uploaded_filenames: + func = transcriber_live.transcribe_segment.spawn( + filename=filename, + language=language, + ) + result = func.get() + result["filename"] = filename + results.append(result) + + return {"results": results} if len(results) > 1 else results[0] + finally: + for filename in uploaded_filenames: + try: + file_path = f"{UPLOADS_PATH}/{filename}" + os.remove(file_path) + except Exception: + pass + upload_volume.commit() + + @app.post("/v1/audio/transcriptions-from-url", dependencies=[Depends(apikey_auth)]) + def transcribe_from_url( + audio_file_url: str = Body( + ..., description="URL of the audio file to transcribe" + ), + model: str = Body(MODEL_NAME), + language: str = Body("en"), + timestamp_offset: float = Body(0.0), + ): + unique_filename, _audio_suffix = download_audio_to_volume(audio_file_url) + try: + func = transcriber_file.transcribe_segment.spawn( + filename=unique_filename, + timestamp_offset=timestamp_offset, + language=language, + ) + result = func.get() + return result + finally: + try: + file_path = f"{UPLOADS_PATH}/{unique_filename}" + os.remove(file_path) + upload_volume.commit() + except Exception: + pass return app + + +class NoStdStreams: + def __init__(self): + self.devnull = open(os.devnull, "w") + + def __enter__(self): + self._stdout, self._stderr = sys.stdout, sys.stderr + self._stdout.flush() + self._stderr.flush() + sys.stdout, sys.stderr = self.devnull, self.devnull + + def __exit__(self, exc_type, exc_value, traceback): + sys.stdout, sys.stderr = self._stdout, self._stderr + self.devnull.close() diff --git a/server/migrations/versions/0ce521cda2ee_remove_user_id_from_meeting_table.py b/server/migrations/versions/0ce521cda2ee_remove_user_id_from_meeting_table.py new file mode 100644 index 00000000..2e76e8a6 --- /dev/null +++ b/server/migrations/versions/0ce521cda2ee_remove_user_id_from_meeting_table.py @@ -0,0 +1,36 @@ +"""remove user_id from meeting table + +Revision ID: 0ce521cda2ee +Revises: 6dec9fb5b46c +Create Date: 2025-09-10 12:40:55.688899 + +""" + +from typing import Sequence, Union + +import sqlalchemy as sa +from alembic import op + +# revision identifiers, used by Alembic. +revision: str = "0ce521cda2ee" +down_revision: Union[str, None] = "6dec9fb5b46c" +branch_labels: Union[str, Sequence[str], None] = None +depends_on: Union[str, Sequence[str], None] = None + + +def upgrade() -> None: + # ### commands auto generated by Alembic - please adjust! ### + with op.batch_alter_table("meeting", schema=None) as batch_op: + batch_op.drop_column("user_id") + + # ### end Alembic commands ### + + +def downgrade() -> None: + # ### commands auto generated by Alembic - please adjust! ### + with op.batch_alter_table("meeting", schema=None) as batch_op: + batch_op.add_column( + sa.Column("user_id", sa.VARCHAR(), autoincrement=False, nullable=True) + ) + + # ### end Alembic commands ### diff --git a/server/migrations/versions/2ae3db106d4e_clean_up_orphaned_room_id_references_in_.py b/server/migrations/versions/2ae3db106d4e_clean_up_orphaned_room_id_references_in_.py new file mode 100644 index 00000000..c091ab49 --- /dev/null +++ b/server/migrations/versions/2ae3db106d4e_clean_up_orphaned_room_id_references_in_.py @@ -0,0 +1,32 @@ +"""clean up orphaned room_id references in meeting table + +Revision ID: 2ae3db106d4e +Revises: def1b5867d4c +Create Date: 2025-09-11 10:35:15.759967 + +""" + +from typing import Sequence, Union + +from alembic import op + +# revision identifiers, used by Alembic. +revision: str = "2ae3db106d4e" +down_revision: Union[str, None] = "def1b5867d4c" +branch_labels: Union[str, Sequence[str], None] = None +depends_on: Union[str, Sequence[str], None] = None + + +def upgrade() -> None: + # Set room_id to NULL for meetings that reference non-existent rooms + op.execute(""" + UPDATE meeting + SET room_id = NULL + WHERE room_id IS NOT NULL + AND room_id NOT IN (SELECT id FROM room WHERE id IS NOT NULL) + """) + + +def downgrade() -> None: + # Cannot restore orphaned references - no operation needed + pass diff --git a/server/migrations/versions/6dec9fb5b46c_make_meeting_room_id_required_and_add_.py b/server/migrations/versions/6dec9fb5b46c_make_meeting_room_id_required_and_add_.py new file mode 100644 index 00000000..20828c65 --- /dev/null +++ b/server/migrations/versions/6dec9fb5b46c_make_meeting_room_id_required_and_add_.py @@ -0,0 +1,38 @@ +"""make meeting room_id required and add foreign key + +Revision ID: 6dec9fb5b46c +Revises: 61882a919591 +Create Date: 2025-09-10 10:47:06.006819 + +""" + +from typing import Sequence, Union + +import sqlalchemy as sa +from alembic import op + +# revision identifiers, used by Alembic. +revision: str = "6dec9fb5b46c" +down_revision: Union[str, None] = "61882a919591" +branch_labels: Union[str, Sequence[str], None] = None +depends_on: Union[str, Sequence[str], None] = None + + +def upgrade() -> None: + # ### commands auto generated by Alembic - please adjust! ### + with op.batch_alter_table("meeting", schema=None) as batch_op: + batch_op.alter_column("room_id", existing_type=sa.VARCHAR(), nullable=False) + batch_op.create_foreign_key( + None, "room", ["room_id"], ["id"], ondelete="CASCADE" + ) + + # ### end Alembic commands ### + + +def downgrade() -> None: + # ### commands auto generated by Alembic - please adjust! ### + with op.batch_alter_table("meeting", schema=None) as batch_op: + batch_op.drop_constraint("meeting_room_id_fkey", type_="foreignkey") + batch_op.alter_column("room_id", existing_type=sa.VARCHAR(), nullable=True) + + # ### end Alembic commands ### diff --git a/server/migrations/versions/def1b5867d4c_make_meeting_room_id_nullable_but_keep_.py b/server/migrations/versions/def1b5867d4c_make_meeting_room_id_nullable_but_keep_.py new file mode 100644 index 00000000..982bea27 --- /dev/null +++ b/server/migrations/versions/def1b5867d4c_make_meeting_room_id_nullable_but_keep_.py @@ -0,0 +1,34 @@ +"""make meeting room_id nullable but keep foreign key + +Revision ID: def1b5867d4c +Revises: 0ce521cda2ee +Create Date: 2025-09-11 09:42:18.697264 + +""" + +from typing import Sequence, Union + +import sqlalchemy as sa +from alembic import op + +# revision identifiers, used by Alembic. +revision: str = "def1b5867d4c" +down_revision: Union[str, None] = "0ce521cda2ee" +branch_labels: Union[str, Sequence[str], None] = None +depends_on: Union[str, Sequence[str], None] = None + + +def upgrade() -> None: + # ### commands auto generated by Alembic - please adjust! ### + with op.batch_alter_table("meeting", schema=None) as batch_op: + batch_op.alter_column("room_id", existing_type=sa.VARCHAR(), nullable=True) + + # ### end Alembic commands ### + + +def downgrade() -> None: + # ### commands auto generated by Alembic - please adjust! ### + with op.batch_alter_table("meeting", schema=None) as batch_op: + batch_op.alter_column("room_id", existing_type=sa.VARCHAR(), nullable=False) + + # ### end Alembic commands ### diff --git a/server/reflector/db/meetings.py b/server/reflector/db/meetings.py index ed4f6169..85cfd6b2 100644 --- a/server/reflector/db/meetings.py +++ b/server/reflector/db/meetings.py @@ -2,7 +2,6 @@ from datetime import datetime, timezone from typing import Any, Dict, List, Literal import sqlalchemy as sa -from fastapi import HTTPException from pydantic import BaseModel, Field from reflector.db import get_database, metadata @@ -18,8 +17,12 @@ meetings = sa.Table( sa.Column("host_room_url", sa.String), sa.Column("start_date", sa.DateTime(timezone=True)), sa.Column("end_date", sa.DateTime(timezone=True)), - sa.Column("user_id", sa.String), - sa.Column("room_id", sa.String), + sa.Column( + "room_id", + sa.String, + sa.ForeignKey("room.id", ondelete="CASCADE"), + nullable=True, + ), sa.Column("is_locked", sa.Boolean, nullable=False, server_default=sa.false()), sa.Column("room_mode", sa.String, nullable=False, server_default="normal"), sa.Column("recording_type", sa.String, nullable=False, server_default="cloud"), @@ -83,8 +86,7 @@ class Meeting(BaseModel): host_room_url: str start_date: datetime end_date: datetime - user_id: str | None = None - room_id: str | None = None + room_id: str | None is_locked: bool = False room_mode: Literal["normal", "group"] = "normal" recording_type: Literal["none", "local", "cloud"] = "cloud" @@ -105,12 +107,8 @@ class MeetingController: host_room_url: str, start_date: datetime, end_date: datetime, - user_id: str, room: Room, ): - """ - Create a new meeting - """ meeting = Meeting( id=id, room_name=room_name, @@ -118,7 +116,6 @@ class MeetingController: host_room_url=host_room_url, start_date=start_date, end_date=end_date, - user_id=user_id, room_id=room.id, is_locked=room.is_locked, room_mode=room.room_mode, @@ -131,19 +128,13 @@ class MeetingController: return meeting async def get_all_active(self) -> list[Meeting]: - """ - Get active meetings. - """ query = meetings.select().where(meetings.c.is_active) return await get_database().fetch_all(query) async def get_by_room_name( self, room_name: str, - ) -> Meeting: - """ - Get a meeting by room name. - """ + ) -> Meeting | None: query = meetings.select().where(meetings.c.room_name == room_name) result = await get_database().fetch_one(query) if not result: @@ -151,10 +142,7 @@ class MeetingController: return Meeting(**result) - async def get_active(self, room: Room, current_time: datetime) -> Meeting: - """ - Get latest active meeting for a room. - """ + async def get_active(self, room: Room, current_time: datetime) -> Meeting | None: end_date = getattr(meetings.c, "end_date") query = ( meetings.select() @@ -174,32 +162,12 @@ class MeetingController: return Meeting(**result) async def get_by_id(self, meeting_id: str, **kwargs) -> Meeting | None: - """ - Get a meeting by id - """ query = meetings.select().where(meetings.c.id == meeting_id) result = await get_database().fetch_one(query) if not result: return None return Meeting(**result) - async def get_by_id_for_http(self, meeting_id: str, user_id: str | None) -> Meeting: - """ - Get a meeting by ID for HTTP request. - - If not found, it will raise a 404 error. - """ - query = meetings.select().where(meetings.c.id == meeting_id) - result = await get_database().fetch_one(query) - if not result: - raise HTTPException(status_code=404, detail="Meeting not found") - - meeting = Meeting(**result) - if result["user_id"] != user_id: - meeting.host_room_url = "" - - return meeting - async def update_meeting(self, meeting_id: str, **kwargs): query = meetings.update().where(meetings.c.id == meeting_id).values(**kwargs) await get_database().execute(query) @@ -286,7 +254,7 @@ class MeetingConsentController: result = await get_database().fetch_one(query) if result is None: return None - return MeetingConsent(**result) if result else None + return MeetingConsent(**result) async def upsert(self, consent: MeetingConsent) -> MeetingConsent: """Create new consent or update existing one for authenticated users""" diff --git a/server/reflector/db/search.py b/server/reflector/db/search.py index 66a25ccf..caa21c65 100644 --- a/server/reflector/db/search.py +++ b/server/reflector/db/search.py @@ -23,7 +23,7 @@ from pydantic import ( from reflector.db import get_database from reflector.db.rooms import rooms -from reflector.db.transcripts import SourceKind, transcripts +from reflector.db.transcripts import SourceKind, TranscriptStatus, transcripts from reflector.db.utils import is_postgresql from reflector.logger import logger from reflector.utils.string import NonEmptyString, try_parse_non_empty_string @@ -161,7 +161,7 @@ class SearchResult(BaseModel): room_name: str | None = None source_kind: SourceKind created_at: datetime - status: str = Field(..., min_length=1) + status: TranscriptStatus = Field(..., min_length=1) rank: float = Field(..., ge=0, le=1) duration: NonNegativeFloat | None = Field(..., description="Duration in seconds") search_snippets: list[str] = Field( diff --git a/server/reflector/pipelines/main_file_pipeline.py b/server/reflector/pipelines/main_file_pipeline.py index 5c57dddb..ce9d000e 100644 --- a/server/reflector/pipelines/main_file_pipeline.py +++ b/server/reflector/pipelines/main_file_pipeline.py @@ -12,7 +12,7 @@ from pathlib import Path import av import structlog -from celery import shared_task +from celery import chain, shared_task from reflector.asynctask import asynctask from reflector.db.rooms import rooms_controller @@ -26,6 +26,8 @@ from reflector.logger import logger from reflector.pipelines.main_live_pipeline import ( PipelineMainBase, broadcast_to_sockets, + task_cleanup_consent, + task_pipeline_post_to_zulip, ) from reflector.processors import ( AudioFileWriterProcessor, @@ -379,6 +381,28 @@ class PipelineMainFile(PipelineMainBase): await processor.flush() +@shared_task +@asynctask +async def task_send_webhook_if_needed(*, transcript_id: str): + """Send webhook if this is a room recording with webhook configured""" + transcript = await transcripts_controller.get_by_id(transcript_id) + if not transcript: + return + + if transcript.source_kind == SourceKind.ROOM and transcript.room_id: + room = await rooms_controller.get_by_id(transcript.room_id) + if room and room.webhook_url: + logger.info( + "Dispatching webhook", + transcript_id=transcript_id, + room_id=room.id, + webhook_url=room.webhook_url, + ) + send_transcript_webhook.delay( + transcript_id, room.id, event_id=uuid.uuid4().hex + ) + + @shared_task @asynctask async def task_pipeline_file_process(*, transcript_id: str): @@ -406,16 +430,10 @@ async def task_pipeline_file_process(*, transcript_id: str): await pipeline.set_status(transcript_id, "error") raise - # Trigger webhook if this is a room recording with webhook configured - if transcript.source_kind == SourceKind.ROOM and transcript.room_id: - room = await rooms_controller.get_by_id(transcript.room_id) - if room and room.webhook_url: - logger.info( - "Dispatching webhook task", - transcript_id=transcript_id, - room_id=room.id, - webhook_url=room.webhook_url, - ) - send_transcript_webhook.delay( - transcript_id, room.id, event_id=uuid.uuid4().hex - ) + # Run post-processing chain: consent cleanup -> zulip -> webhook + post_chain = chain( + task_cleanup_consent.si(transcript_id=transcript_id), + task_pipeline_post_to_zulip.si(transcript_id=transcript_id), + task_send_webhook_if_needed.si(transcript_id=transcript_id), + ) + post_chain.delay() diff --git a/server/reflector/processors/file_diarization_modal.py b/server/reflector/processors/file_diarization_modal.py index 518f444e..8865063d 100644 --- a/server/reflector/processors/file_diarization_modal.py +++ b/server/reflector/processors/file_diarization_modal.py @@ -47,6 +47,7 @@ class FileDiarizationModalProcessor(FileDiarizationProcessor): "audio_file_url": data.audio_url, "timestamp": 0, }, + follow_redirects=True, ) response.raise_for_status() diarization_data = response.json()["diarization"] diff --git a/server/reflector/processors/file_transcript_modal.py b/server/reflector/processors/file_transcript_modal.py index b99cf806..82250b6c 100644 --- a/server/reflector/processors/file_transcript_modal.py +++ b/server/reflector/processors/file_transcript_modal.py @@ -54,6 +54,7 @@ class FileTranscriptModalProcessor(FileTranscriptProcessor): "language": data.language, "batch": True, }, + follow_redirects=True, ) response.raise_for_status() result = response.json() diff --git a/server/reflector/settings.py b/server/reflector/settings.py index 1d837692..fcd9f1cf 100644 --- a/server/reflector/settings.py +++ b/server/reflector/settings.py @@ -1,6 +1,8 @@ from pydantic.types import PositiveInt from pydantic_settings import BaseSettings, SettingsConfigDict +from reflector.utils.string import NonEmptyString + class Settings(BaseSettings): model_config = SettingsConfigDict( @@ -120,7 +122,7 @@ class Settings(BaseSettings): # Whereby integration WHEREBY_API_URL: str = "https://api.whereby.dev/v1" - WHEREBY_API_KEY: str | None = None + WHEREBY_API_KEY: NonEmptyString | None = None WHEREBY_WEBHOOK_SECRET: str | None = None AWS_WHEREBY_ACCESS_KEY_ID: str | None = None AWS_WHEREBY_ACCESS_KEY_SECRET: str | None = None diff --git a/server/reflector/utils/string.py b/server/reflector/utils/string.py index 08a9de78..05f40e30 100644 --- a/server/reflector/utils/string.py +++ b/server/reflector/utils/string.py @@ -10,8 +10,11 @@ NonEmptyString = Annotated[ non_empty_string_adapter = TypeAdapter(NonEmptyString) -def parse_non_empty_string(s: str) -> NonEmptyString: - return non_empty_string_adapter.validate_python(s) +def parse_non_empty_string(s: str, error: str | None = None) -> NonEmptyString: + try: + return non_empty_string_adapter.validate_python(s) + except Exception as e: + raise ValueError(f"{e}: {error}" if error else e) from e def try_parse_non_empty_string(s: str) -> NonEmptyString | None: diff --git a/server/reflector/views/rooms.py b/server/reflector/views/rooms.py index 20a644b3..720f22eb 100644 --- a/server/reflector/views/rooms.py +++ b/server/reflector/views/rooms.py @@ -241,7 +241,9 @@ async def rooms_create_meeting( ) if meeting is None: logger.error( - "Meeting disappeared after race condition for room %s", room.name + "Meeting disappeared after race condition for room %s", + room.name, + exc_info=True, ) raise HTTPException( status_code=503, detail="Unable to join meeting - please try again" diff --git a/server/reflector/views/transcripts.py b/server/reflector/views/transcripts.py index b64ecf11..ed2445ae 100644 --- a/server/reflector/views/transcripts.py +++ b/server/reflector/views/transcripts.py @@ -27,6 +27,7 @@ from reflector.db.search import ( from reflector.db.transcripts import ( SourceKind, TranscriptParticipant, + TranscriptStatus, TranscriptTopic, transcripts_controller, ) @@ -63,7 +64,7 @@ class GetTranscriptMinimal(BaseModel): id: str user_id: str | None name: str - status: str + status: TranscriptStatus locked: bool duration: float title: str | None @@ -96,6 +97,7 @@ class CreateTranscript(BaseModel): name: str source_language: str = Field("en") target_language: str = Field("en") + source_kind: SourceKind | None = None class UpdateTranscript(BaseModel): @@ -213,7 +215,7 @@ async def transcripts_create( user_id = user["sub"] if user else None return await transcripts_controller.add( info.name, - source_kind=SourceKind.LIVE, + source_kind=info.source_kind or SourceKind.LIVE, source_language=info.source_language, target_language=info.target_language, user_id=user_id, @@ -348,8 +350,6 @@ async def transcript_update( transcript = await transcripts_controller.get_by_id_for_http( transcript_id, user_id=user_id ) - if not transcript: - raise HTTPException(status_code=404, detail="Transcript not found") values = info.dict(exclude_unset=True) updated_transcript = await transcripts_controller.update(transcript, values) return updated_transcript diff --git a/server/reflector/views/transcripts_process.py b/server/reflector/views/transcripts_process.py index 0200e7f8..f9295765 100644 --- a/server/reflector/views/transcripts_process.py +++ b/server/reflector/views/transcripts_process.py @@ -34,7 +34,7 @@ async def transcript_process( ) if task_is_scheduled_or_active( - "reflector.pipelines.main_live_pipeline.task_pipeline_process", + "reflector.pipelines.main_file_pipeline.task_pipeline_file_process", transcript_id=transcript_id, ): return ProcessStatus(status="already running") diff --git a/server/reflector/whereby.py b/server/reflector/whereby.py index deaa5274..8b5c18fd 100644 --- a/server/reflector/whereby.py +++ b/server/reflector/whereby.py @@ -1,18 +1,60 @@ +import logging from datetime import datetime import httpx from reflector.db.rooms import Room from reflector.settings import settings +from reflector.utils.string import parse_non_empty_string + +logger = logging.getLogger(__name__) + + +def _get_headers(): + api_key = parse_non_empty_string( + settings.WHEREBY_API_KEY, "WHEREBY_API_KEY value is required." + ) + return { + "Content-Type": "application/json; charset=utf-8", + "Authorization": f"Bearer {api_key}", + } + -HEADERS = { - "Content-Type": "application/json; charset=utf-8", - "Authorization": f"Bearer {settings.WHEREBY_API_KEY}", -} TIMEOUT = 10 # seconds +def _get_whereby_s3_auth(): + errors = [] + try: + bucket_name = parse_non_empty_string( + settings.RECORDING_STORAGE_AWS_BUCKET_NAME, + "RECORDING_STORAGE_AWS_BUCKET_NAME value is required.", + ) + except Exception as e: + errors.append(e) + try: + key_id = parse_non_empty_string( + settings.AWS_WHEREBY_ACCESS_KEY_ID, + "AWS_WHEREBY_ACCESS_KEY_ID value is required.", + ) + except Exception as e: + errors.append(e) + try: + key_secret = parse_non_empty_string( + settings.AWS_WHEREBY_ACCESS_KEY_SECRET, + "AWS_WHEREBY_ACCESS_KEY_SECRET value is required.", + ) + except Exception as e: + errors.append(e) + if len(errors) > 0: + raise Exception( + f"Failed to get Whereby auth settings: {', '.join(str(e) for e in errors)}" + ) + return bucket_name, key_id, key_secret + + async def create_meeting(room_name_prefix: str, end_date: datetime, room: Room): + s3_bucket_name, s3_key_id, s3_key_secret = _get_whereby_s3_auth() data = { "isLocked": room.is_locked, "roomNamePrefix": room_name_prefix, @@ -23,23 +65,26 @@ async def create_meeting(room_name_prefix: str, end_date: datetime, room: Room): "type": room.recording_type, "destination": { "provider": "s3", - "bucket": settings.RECORDING_STORAGE_AWS_BUCKET_NAME, - "accessKeyId": settings.AWS_WHEREBY_ACCESS_KEY_ID, - "accessKeySecret": settings.AWS_WHEREBY_ACCESS_KEY_SECRET, + "bucket": s3_bucket_name, + "accessKeyId": s3_key_id, + "accessKeySecret": s3_key_secret, "fileFormat": "mp4", }, "startTrigger": room.recording_trigger, }, "fields": ["hostRoomUrl"], } - async with httpx.AsyncClient() as client: response = await client.post( f"{settings.WHEREBY_API_URL}/meetings", - headers=HEADERS, + headers=_get_headers(), json=data, timeout=TIMEOUT, ) + if response.status_code == 403: + logger.warning( + f"Failed to create meeting: access denied on Whereby: {response.text}" + ) response.raise_for_status() return response.json() @@ -48,7 +93,7 @@ async def get_room_sessions(room_name: str): async with httpx.AsyncClient() as client: response = await client.get( f"{settings.WHEREBY_API_URL}/insights/room-sessions?roomName={room_name}", - headers=HEADERS, + headers=_get_headers(), timeout=TIMEOUT, ) response.raise_for_status() diff --git a/server/runserver.sh b/server/runserver.sh index a4fb6869..9cccaacb 100755 --- a/server/runserver.sh +++ b/server/runserver.sh @@ -2,7 +2,7 @@ if [ "${ENTRYPOINT}" = "server" ]; then uv run alembic upgrade head - uv run -m reflector.app + uv run uvicorn reflector.app:app --host 0.0.0.0 --port 1250 elif [ "${ENTRYPOINT}" = "worker" ]; then uv run celery -A reflector.worker.app worker --loglevel=info elif [ "${ENTRYPOINT}" = "beat" ]; then diff --git a/server/tests/test_cleanup.py b/server/tests/test_cleanup.py index 3c5149ae..2cb8614c 100644 --- a/server/tests/test_cleanup.py +++ b/server/tests/test_cleanup.py @@ -105,7 +105,6 @@ async def test_cleanup_deletes_associated_meeting_and_recording(): host_room_url="https://example.com/meeting-host", start_date=old_date, end_date=old_date + timedelta(hours=1), - user_id=None, room_id=None, ) ) @@ -241,7 +240,6 @@ async def test_meeting_consent_cascade_delete(): host_room_url="https://example.com/cascade-test-host", start_date=datetime.now(timezone.utc), end_date=datetime.now(timezone.utc) + timedelta(hours=1), - user_id="test-user", room_id=None, ) ) diff --git a/server/tests/test_gpu_modal_transcript.py b/server/tests/test_gpu_modal_transcript.py index 9b37fbe6..9a152185 100644 --- a/server/tests/test_gpu_modal_transcript.py +++ b/server/tests/test_gpu_modal_transcript.py @@ -272,6 +272,9 @@ class TestGPUModalTranscript: for f in temp_files: Path(f).unlink(missing_ok=True) + @pytest.mark.skipif( + not "parakeet" in get_model_name(), reason="Parakeet only supports English" + ) def test_transcriptions_error_handling(self): """Test error handling for invalid requests.""" url = get_modal_transcript_url() diff --git a/server/tests/test_search.py b/server/tests/test_search.py index 0f5c8923..82890080 100644 --- a/server/tests/test_search.py +++ b/server/tests/test_search.py @@ -58,7 +58,7 @@ async def test_empty_transcript_title_only_match(): "id": test_id, "name": "Empty Transcript", "title": "Empty Meeting", - "status": "completed", + "status": "ended", "locked": False, "duration": 0.0, "created_at": datetime.now(timezone.utc), @@ -109,7 +109,7 @@ async def test_search_with_long_summary(): "id": test_id, "name": "Test Long Summary", "title": "Regular Meeting", - "status": "completed", + "status": "ended", "locked": False, "duration": 1800.0, "created_at": datetime.now(timezone.utc), @@ -165,7 +165,7 @@ async def test_postgresql_search_with_data(): "id": test_id, "name": "Test Search Transcript", "title": "Engineering Planning Meeting Q4 2024", - "status": "completed", + "status": "ended", "locked": False, "duration": 1800.0, "created_at": datetime.now(timezone.utc), @@ -221,7 +221,7 @@ We need to implement PostgreSQL tsvector for better performance.""", test_result = next((r for r in results if r.id == test_id), None) if test_result: assert test_result.title == "Engineering Planning Meeting Q4 2024" - assert test_result.status == "completed" + assert test_result.status == "ended" assert test_result.duration == 1800.0 assert 0 <= test_result.rank <= 1, "Rank should be normalized to 0-1" @@ -268,7 +268,7 @@ def mock_db_result(): "title": "Test Transcript", "created_at": datetime(2024, 6, 15, tzinfo=timezone.utc), "duration": 3600.0, - "status": "completed", + "status": "ended", "user_id": "test-user", "room_id": "room1", "source_kind": SourceKind.LIVE, @@ -433,7 +433,7 @@ class TestSearchResultModel: room_id="room-456", source_kind=SourceKind.ROOM, created_at=datetime(2024, 6, 15, tzinfo=timezone.utc), - status="completed", + status="ended", rank=0.85, duration=1800.5, search_snippets=["snippet 1", "snippet 2"], @@ -443,7 +443,7 @@ class TestSearchResultModel: assert result.title == "Test Title" assert result.user_id == "user-123" assert result.room_id == "room-456" - assert result.status == "completed" + assert result.status == "ended" assert result.rank == 0.85 assert result.duration == 1800.5 assert len(result.search_snippets) == 2 @@ -474,7 +474,7 @@ class TestSearchResultModel: id="test-id", source_kind=SourceKind.LIVE, created_at=datetime(2024, 6, 15, 12, 30, 45, tzinfo=timezone.utc), - status="completed", + status="ended", rank=0.9, duration=None, search_snippets=[], diff --git a/server/tests/test_search_long_summary.py b/server/tests/test_search_long_summary.py index 8857778b..3f911a99 100644 --- a/server/tests/test_search_long_summary.py +++ b/server/tests/test_search_long_summary.py @@ -25,7 +25,7 @@ async def test_long_summary_snippet_prioritization(): "id": test_id, "name": "Test Snippet Priority", "title": "Meeting About Projects", - "status": "completed", + "status": "ended", "locked": False, "duration": 1800.0, "created_at": datetime.now(timezone.utc), @@ -106,7 +106,7 @@ async def test_long_summary_only_search(): "id": test_id, "name": "Test Long Only", "title": "Standard Meeting", - "status": "completed", + "status": "ended", "locked": False, "duration": 1800.0, "created_at": datetime.now(timezone.utc), diff --git a/www/.env.example b/www/.env.example new file mode 100644 index 00000000..77017d91 --- /dev/null +++ b/www/.env.example @@ -0,0 +1,34 @@ +# Environment +ENVIRONMENT=development +NEXT_PUBLIC_ENV=development + +# Site Configuration +NEXT_PUBLIC_SITE_URL=http://localhost:3000 + +# Nextauth envs +# not used in app code but in lib code +NEXTAUTH_URL=http://localhost:3000 +NEXTAUTH_SECRET=your-nextauth-secret-here +# / Nextauth envs + +# Authentication (Authentik OAuth/OIDC) +AUTHENTIK_ISSUER=https://authentik.example.com/application/o/reflector +AUTHENTIK_REFRESH_TOKEN_URL=https://authentik.example.com/application/o/token/ +AUTHENTIK_CLIENT_ID=your-client-id-here +AUTHENTIK_CLIENT_SECRET=your-client-secret-here + +# Feature Flags +# NEXT_PUBLIC_FEATURE_REQUIRE_LOGIN=true +# NEXT_PUBLIC_FEATURE_PRIVACY=false +# NEXT_PUBLIC_FEATURE_BROWSE=true +# NEXT_PUBLIC_FEATURE_SEND_TO_ZULIP=true +# NEXT_PUBLIC_FEATURE_ROOMS=true + +# API URLs +NEXT_PUBLIC_API_URL=http://127.0.0.1:1250 +NEXT_PUBLIC_WEBSOCKET_URL=ws://127.0.0.1:1250 +NEXT_PUBLIC_AUTH_CALLBACK_URL=http://localhost:3000/auth-callback + +# Sentry +# SENTRY_DSN=https://your-dsn@sentry.io/project-id +# SENTRY_IGNORE_API_RESOLUTION_ERROR=1 \ No newline at end of file diff --git a/www/.gitignore b/www/.gitignore index c0ad8c1e..9acefbb2 100644 --- a/www/.gitignore +++ b/www/.gitignore @@ -40,7 +40,6 @@ next-env.d.ts # Sentry Auth Token .sentryclirc -config.ts # openapi logs openapi-ts-error-*.log diff --git a/www/app/(app)/AuthWrapper.tsx b/www/app/(app)/AuthWrapper.tsx new file mode 100644 index 00000000..57038b7b --- /dev/null +++ b/www/app/(app)/AuthWrapper.tsx @@ -0,0 +1,30 @@ +"use client"; + +import { Flex, Spinner } from "@chakra-ui/react"; +import { useAuth } from "../lib/AuthProvider"; +import { useLoginRequiredPages } from "../lib/useLoginRequiredPages"; + +export default function AuthWrapper({ + children, +}: { + children: React.ReactNode; +}) { + const auth = useAuth(); + const redirectPath = useLoginRequiredPages(); + const redirectHappens = !!redirectPath; + + if (auth.status === "loading" || redirectHappens) { + return ( + + + + ); + } + + return <>{children}; +} diff --git a/www/app/(app)/browse/_components/FilterSidebar.tsx b/www/app/(app)/browse/_components/FilterSidebar.tsx index b2abe481..6eef61b8 100644 --- a/www/app/(app)/browse/_components/FilterSidebar.tsx +++ b/www/app/(app)/browse/_components/FilterSidebar.tsx @@ -1,7 +1,10 @@ import React from "react"; import { Box, Stack, Link, Heading } from "@chakra-ui/react"; import NextLink from "next/link"; -import { Room, SourceKind } from "../../../api"; +import type { components } from "../../../reflector-api"; + +type Room = components["schemas"]["Room"]; +type SourceKind = components["schemas"]["SourceKind"]; interface FilterSidebarProps { rooms: Room[]; @@ -72,7 +75,7 @@ export default function FilterSidebar({ key={room.id} as={NextLink} href="#" - onClick={() => onFilterChange("room", room.id)} + onClick={() => onFilterChange("room" as SourceKind, room.id)} color={ selectedSourceKind === "room" && selectedRoomId === room.id ? "blue.500" diff --git a/www/app/(app)/browse/_components/TranscriptCards.tsx b/www/app/(app)/browse/_components/TranscriptCards.tsx index b67e71e7..8dbc3568 100644 --- a/www/app/(app)/browse/_components/TranscriptCards.tsx +++ b/www/app/(app)/browse/_components/TranscriptCards.tsx @@ -18,7 +18,10 @@ import { highlightMatches, generateTextFragment, } from "../../../lib/textHighlight"; -import { SearchResult } from "../../../api"; +import type { components } from "../../../reflector-api"; + +type SearchResult = components["schemas"]["SearchResult"]; +type SourceKind = components["schemas"]["SourceKind"]; interface TranscriptCardsProps { results: SearchResult[]; @@ -120,7 +123,7 @@ function TranscriptCard({ : "N/A"; const formattedDate = formatLocalDate(result.created_at); const source = - result.source_kind === "room" + result.source_kind === ("room" as SourceKind) ? result.room_name || result.room_id : result.source_kind; diff --git a/www/app/(app)/browse/_components/TranscriptStatusIcon.tsx b/www/app/(app)/browse/_components/TranscriptStatusIcon.tsx index 0eebadc8..20164993 100644 --- a/www/app/(app)/browse/_components/TranscriptStatusIcon.tsx +++ b/www/app/(app)/browse/_components/TranscriptStatusIcon.tsx @@ -7,9 +7,10 @@ import { FaMicrophone, FaGear, } from "react-icons/fa6"; +import { TranscriptStatus } from "../../../lib/transcript"; interface TranscriptStatusIconProps { - status: string; + status: TranscriptStatus; } export default function TranscriptStatusIcon({ diff --git a/www/app/(app)/browse/page.tsx b/www/app/(app)/browse/page.tsx index e7522e14..8523650e 100644 --- a/www/app/(app)/browse/page.tsx +++ b/www/app/(app)/browse/page.tsx @@ -19,37 +19,33 @@ import { parseAsStringLiteral, } from "nuqs"; import { LuX } from "react-icons/lu"; -import { useSearchTranscripts } from "../transcripts/useSearchTranscripts"; -import useSessionUser from "../../lib/useSessionUser"; -import { Room, SourceKind, SearchResult, $SourceKind } from "../../api"; -import useApi from "../../lib/useApi"; -import { useError } from "../../(errors)/errorContext"; +import type { components } from "../../reflector-api"; + +type Room = components["schemas"]["Room"]; +type SourceKind = components["schemas"]["SourceKind"]; +type SearchResult = components["schemas"]["SearchResult"]; +import { + useRoomsList, + useTranscriptsSearch, + useTranscriptDelete, + useTranscriptProcess, +} from "../../lib/apiHooks"; import FilterSidebar from "./_components/FilterSidebar"; import Pagination, { FIRST_PAGE, PaginationPage, parsePaginationPage, totalPages as getTotalPages, + paginationPageTo0Based, } from "./_components/Pagination"; import TranscriptCards from "./_components/TranscriptCards"; import DeleteTranscriptDialog from "./_components/DeleteTranscriptDialog"; import { formatLocalDate } from "../../lib/time"; import { RECORD_A_MEETING_URL } from "../../api/urls"; +import { useUserName } from "../../lib/useUserName"; const SEARCH_FORM_QUERY_INPUT_NAME = "query" as const; -const usePrefetchRooms = (setRooms: (rooms: Room[]) => void): void => { - const { setError } = useError(); - const api = useApi(); - useEffect(() => { - if (!api) return; - api - .v1RoomsList({ page: 1 }) - .then((rooms) => setRooms(rooms.items)) - .catch((err) => setError(err, "There was an error fetching the rooms")); - }, [api, setError]); -}; - const SearchForm: React.FC<{ setPage: (page: PaginationPage) => void; sourceKind: SourceKind | null; @@ -69,7 +65,6 @@ const SearchForm: React.FC<{ searchQuery, setSearchQuery, }) => { - // to keep the search input controllable + more fine grained control (urlSearchQuery is updated on submits) const [searchInputValue, setSearchInputValue] = useState(searchQuery || ""); const handleSearchQuerySubmit = async (d: FormData) => { await setSearchQuery((d.get(SEARCH_FORM_QUERY_INPUT_NAME) as string) || ""); @@ -163,7 +158,6 @@ const UnderSearchFormFilterIndicators: React.FC<{ p="1px" onClick={() => { setSourceKind(null); - // TODO questionable setRoomId(null); }} _hover={{ bg: "blue.200" }} @@ -209,7 +203,11 @@ export default function TranscriptBrowser() { const [urlSourceKind, setUrlSourceKind] = useQueryState( "source", - parseAsStringLiteral($SourceKind.enum).withOptions({ + parseAsStringLiteral([ + "room", + "live", + "file", + ] as const satisfies SourceKind[]).withOptions({ shallow: false, }), ); @@ -229,46 +227,40 @@ export default function TranscriptBrowser() { useEffect(() => { const maybePage = parsePaginationPage(urlPage); if ("error" in maybePage) { - setPage(FIRST_PAGE).then(() => { - /*may be called n times we dont care*/ - }); + setPage(FIRST_PAGE).then(() => {}); return; } _setSafePage(maybePage.value); }, [urlPage]); - const [rooms, setRooms] = useState([]); - const pageSize = 20; + const { - results, - totalCount: totalResults, - isLoading, - reload, - } = useSearchTranscripts( - urlSearchQuery, - { - roomIds: urlRoomId ? [urlRoomId] : null, - sourceKind: urlSourceKind, - }, - { - pageSize, - page, - }, - ); + data: searchData, + isLoading: searchLoading, + refetch: reloadSearch, + } = useTranscriptsSearch(urlSearchQuery, { + limit: pageSize, + offset: paginationPageTo0Based(page) * pageSize, + room_id: urlRoomId || undefined, + source_kind: urlSourceKind || undefined, + }); + + const results = searchData?.results || []; + const totalResults = searchData?.total || 0; + + // Fetch rooms + const { data: roomsData } = useRoomsList(1); + const rooms = roomsData?.items || []; const totalPages = getTotalPages(totalResults, pageSize); - const userName = useSessionUser().name; + const userName = useUserName(); const [deletionLoading, setDeletionLoading] = useState(false); - const api = useApi(); - const { setError } = useError(); const cancelRef = React.useRef(null); const [transcriptToDeleteId, setTranscriptToDeleteId] = React.useState(); - usePrefetchRooms(setRooms); - const handleFilterTranscripts = ( sourceKind: SourceKind | null, roomId: string, @@ -280,44 +272,37 @@ export default function TranscriptBrowser() { const onCloseDeletion = () => setTranscriptToDeleteId(undefined); + const deleteTranscript = useTranscriptDelete(); + const processTranscript = useTranscriptProcess(); + const confirmDeleteTranscript = (transcriptId: string) => { - if (!api || deletionLoading) return; + if (deletionLoading) return; setDeletionLoading(true); - api - .v1TranscriptDelete({ transcriptId }) - .then(() => { - setDeletionLoading(false); - onCloseDeletion(); - reload(); - }) - .catch((err) => { - setDeletionLoading(false); - setError(err, "There was an error deleting the transcript"); - }); + deleteTranscript.mutate( + { + params: { + path: { transcript_id: transcriptId }, + }, + }, + { + onSuccess: () => { + setDeletionLoading(false); + onCloseDeletion(); + reloadSearch(); + }, + onError: () => { + setDeletionLoading(false); + }, + }, + ); }; const handleProcessTranscript = (transcriptId: string) => { - if (!api) { - console.error("API not available on handleProcessTranscript"); - return; - } - api - .v1TranscriptProcess({ transcriptId }) - .then((result) => { - const status = - result && typeof result === "object" && "status" in result - ? (result as { status: string }).status - : undefined; - if (status === "already running") { - setError( - new Error("Processing is already running, please wait"), - "Processing is already running, please wait", - ); - } - }) - .catch((err) => { - setError(err, "There was an error processing the transcript"); - }); + processTranscript.mutate({ + params: { + path: { transcript_id: transcriptId }, + }, + }); }; const transcriptToDelete = results?.find( @@ -332,7 +317,7 @@ export default function TranscriptBrowser() { ? transcriptToDelete.room_name || transcriptToDelete.room_id : transcriptToDelete?.source_kind; - if (isLoading && results.length === 0) { + if (searchLoading && results.length === 0) { return ( {userName ? `${userName}'s Transcriptions` : "Your Transcriptions"}{" "} - {(isLoading || deletionLoading) && } + {(searchLoading || deletionLoading) && } @@ -403,12 +388,12 @@ export default function TranscriptBrowser() { - {!isLoading && results.length === 0 && ( + {!searchLoading && results.length === 0 && ( )} diff --git a/www/app/(app)/layout.tsx b/www/app/(app)/layout.tsx index 5760e19d..8bca1df6 100644 --- a/www/app/(app)/layout.tsx +++ b/www/app/(app)/layout.tsx @@ -1,10 +1,9 @@ import { Container, Flex, Link } from "@chakra-ui/react"; -import { getConfig } from "../lib/edgeConfig"; +import { featureEnabled } from "../lib/features"; import NextLink from "next/link"; import Image from "next/image"; -import About from "../(aboutAndPrivacy)/about"; -import Privacy from "../(aboutAndPrivacy)/privacy"; import UserInfo from "../(auth)/userInfo"; +import AuthWrapper from "./AuthWrapper"; import { RECORD_A_MEETING_URL } from "../api/urls"; export default async function AppLayout({ @@ -12,8 +11,6 @@ export default async function AppLayout({ }: { children: React.ReactNode; }) { - const config = await getConfig(); - const { requireLogin, privacy, browse, rooms } = config.features; return ( Create - {browse ? ( + {featureEnabled("browse") ? ( <>  ·  @@ -69,7 +66,7 @@ export default async function AppLayout({ ) : ( <> )} - {rooms ? ( + {featureEnabled("rooms") ? ( <>  ·  @@ -79,7 +76,7 @@ export default async function AppLayout({ ) : ( <> )} - {requireLogin ? ( + {featureEnabled("requireLogin") ? ( <>  ·  @@ -90,7 +87,7 @@ export default async function AppLayout({ - {children} + {children} ); } diff --git a/www/app/(app)/rooms/_components/RoomCards.tsx b/www/app/(app)/rooms/_components/RoomCards.tsx index a917a0c8..3a89a3e1 100644 --- a/www/app/(app)/rooms/_components/RoomCards.tsx +++ b/www/app/(app)/rooms/_components/RoomCards.tsx @@ -13,7 +13,9 @@ import { Badge, } from "@chakra-ui/react"; import { LuLink } from "react-icons/lu"; -import { RoomDetails } from "../../../api"; +import type { components } from "../../../reflector-api"; + +type Room = components["schemas"]["Room"]; import { RoomActionsMenu } from "./RoomActionsMenu"; import { getPlatformDisplayName, @@ -21,7 +23,7 @@ import { } from "../../../lib/videoPlatforms"; interface RoomCardsProps { - rooms: RoomDetails[]; + rooms: Room[]; linkCopied: string; onCopyUrl: (roomName: string) => void; onEdit: (roomId: string, roomData: any) => void; diff --git a/www/app/(app)/rooms/_components/RoomList.tsx b/www/app/(app)/rooms/_components/RoomList.tsx index 73fe8a5c..218c890c 100644 --- a/www/app/(app)/rooms/_components/RoomList.tsx +++ b/www/app/(app)/rooms/_components/RoomList.tsx @@ -1,11 +1,13 @@ import { Box, Heading, Text, VStack } from "@chakra-ui/react"; -import { RoomDetails } from "../../../api"; +import type { components } from "../../../reflector-api"; + +type Room = components["schemas"]["Room"]; import { RoomTable } from "./RoomTable"; import { RoomCards } from "./RoomCards"; interface RoomListProps { title: string; - rooms: RoomDetails[]; + rooms: Room[]; linkCopied: string; onCopyUrl: (roomName: string) => void; onEdit: (roomId: string, roomData: any) => void; diff --git a/www/app/(app)/rooms/_components/RoomTable.tsx b/www/app/(app)/rooms/_components/RoomTable.tsx index 80329cd1..4339eecc 100644 --- a/www/app/(app)/rooms/_components/RoomTable.tsx +++ b/www/app/(app)/rooms/_components/RoomTable.tsx @@ -10,7 +10,9 @@ import { Badge, } from "@chakra-ui/react"; import { LuLink } from "react-icons/lu"; -import { RoomDetails } from "../../../api"; +import type { components } from "../../../reflector-api"; + +type Room = components["schemas"]["Room"]; import { RoomActionsMenu } from "./RoomActionsMenu"; import { getPlatformDisplayName, @@ -18,7 +20,7 @@ import { } from "../../../lib/videoPlatforms"; interface RoomTableProps { - rooms: RoomDetails[]; + rooms: Room[]; linkCopied: string; onCopyUrl: (roomName: string) => void; onEdit: (roomId: string, roomData: any) => void; diff --git a/www/app/(app)/rooms/page.tsx b/www/app/(app)/rooms/page.tsx index 33cfa6b3..8b1378df 100644 --- a/www/app/(app)/rooms/page.tsx +++ b/www/app/(app)/rooms/page.tsx @@ -15,13 +15,24 @@ import { createListCollection, useDisclosure, } from "@chakra-ui/react"; -import { useEffect, useState } from "react"; +import { useEffect, useMemo, useState } from "react"; import { LuEye, LuEyeOff } from "react-icons/lu"; -import useApi from "../../lib/useApi"; import useRoomList from "./useRoomList"; -import { ApiError, RoomDetails } from "../../api"; +import type { components } from "../../reflector-api"; +import { + useRoomCreate, + useRoomUpdate, + useRoomDelete, + useZulipStreams, + useZulipTopics, + useRoomGet, + useRoomTestWebhook, +} from "../../lib/apiHooks"; import { RoomList } from "./_components/RoomList"; import { PaginationPage } from "../browse/_components/Pagination"; +import { assertExists } from "../../lib/utils"; + +type Room = components["schemas"]["Room"]; interface SelectOption { label: string; @@ -76,66 +87,77 @@ export default function RoomsList() { const recordingTypeCollection = createListCollection({ items: recordingTypeOptions, }); - const [room, setRoom] = useState(roomInitialState); + const [roomInput, setRoomInput] = useState( + null, + ); const [isEditing, setIsEditing] = useState(false); - const [editRoomId, setEditRoomId] = useState(""); - const api = useApi(); - // TODO seems to be no setPage calls - const [page, setPage] = useState(1); - const { loading, response, refetch } = useRoomList(PaginationPage(page)); - const [streams, setStreams] = useState([]); - const [topics, setTopics] = useState([]); + const [editRoomId, setEditRoomId] = useState(null); + const { + loading, + response, + refetch, + error: roomListError, + } = useRoomList(PaginationPage(1)); const [nameError, setNameError] = useState(""); const [linkCopied, setLinkCopied] = useState(""); + const [selectedStreamId, setSelectedStreamId] = useState(null); const [testingWebhook, setTestingWebhook] = useState(false); const [webhookTestResult, setWebhookTestResult] = useState( null, ); const [showWebhookSecret, setShowWebhookSecret] = useState(false); - interface Stream { - stream_id: number; - name: string; - } - interface Topic { - name: string; - } + const createRoomMutation = useRoomCreate(); + const updateRoomMutation = useRoomUpdate(); + const deleteRoomMutation = useRoomDelete(); + const { data: streams = [] } = useZulipStreams(); + const { data: topics = [] } = useZulipTopics(selectedStreamId); + const { + data: detailedEditedRoom, + isLoading: isDetailedEditedRoomLoading, + error: detailedEditedRoomError, + } = useRoomGet(editRoomId); + + const error = roomListError || detailedEditedRoomError; + + // room being edited, as fetched from the server + const editedRoom: typeof roomInitialState | null = useMemo( + () => + detailedEditedRoom + ? { + name: detailedEditedRoom.name, + zulipAutoPost: detailedEditedRoom.zulip_auto_post, + zulipStream: detailedEditedRoom.zulip_stream, + zulipTopic: detailedEditedRoom.zulip_topic, + isLocked: detailedEditedRoom.is_locked, + roomMode: detailedEditedRoom.room_mode, + recordingType: detailedEditedRoom.recording_type, + recordingTrigger: detailedEditedRoom.recording_trigger, + isShared: detailedEditedRoom.is_shared, + webhookUrl: detailedEditedRoom.webhook_url || "", + webhookSecret: detailedEditedRoom.webhook_secret || "", + } + : null, + [detailedEditedRoom], + ); + + // a room input value or a last api room state + const room = roomInput || editedRoom || roomInitialState; + + const roomTestWebhookMutation = useRoomTestWebhook(); + + // Update selected stream ID when zulip stream changes useEffect(() => { - const fetchZulipStreams = async () => { - if (!api) return; - - try { - const response = await api.v1ZulipGetStreams(); - setStreams(response); - } catch (error) { - console.error("Error fetching Zulip streams:", error); + if (room.zulipStream && streams.length > 0) { + const selectedStream = streams.find((s) => s.name === room.zulipStream); + if (selectedStream !== undefined) { + setSelectedStreamId(selectedStream.stream_id); } - }; - - if (room.zulipAutoPost) { - fetchZulipStreams(); + } else { + setSelectedStreamId(null); } - }, [room.zulipAutoPost, !api]); - - useEffect(() => { - const fetchZulipTopics = async () => { - if (!api || !room.zulipStream) return; - try { - const selectedStream = streams.find((s) => s.name === room.zulipStream); - if (selectedStream) { - const response = await api.v1ZulipGetTopics({ - streamId: selectedStream.stream_id, - }); - setTopics(response); - } - } catch (error) { - console.error("Error fetching Zulip topics:", error); - } - }; - - fetchZulipTopics(); - }, [room.zulipStream, streams, api]); + }, [room.zulipStream, streams]); const streamOptions: SelectOption[] = streams.map((stream) => { return { label: stream.name, value: stream.name }; @@ -167,35 +189,42 @@ export default function RoomsList() { const handleCloseDialog = () => { setShowWebhookSecret(false); setWebhookTestResult(null); + setEditRoomId(null); onClose(); }; const handleTestWebhook = async () => { - if (!room.webhookUrl || !editRoomId) { + if (!room.webhookUrl) { setWebhookTestResult("Please enter a webhook URL first"); return; } + if (!editRoomId) { + console.error("No room ID to test webhook"); + return; + } setTestingWebhook(true); setWebhookTestResult(null); try { - const response = await api?.v1RoomsTestWebhook({ - roomId: editRoomId, + const response = await roomTestWebhookMutation.mutateAsync({ + params: { + path: { + room_id: editRoomId, + }, + }, }); - if (response?.success) { + if (response.success) { setWebhookTestResult( `✅ Webhook test successful! Status: ${response.status_code}`, ); } else { let errorMsg = `❌ Webhook test failed`; - if (response?.status_code) { - errorMsg += ` (Status: ${response.status_code})`; - } - if (response?.error) { + errorMsg += ` (Status: ${response.status_code})`; + if (response.error) { errorMsg += `: ${response.error}`; - } else if (response?.response_preview) { + } else if (response.response_preview) { // Try to parse and extract meaningful error from response // Specific to N8N at the moment, as there is no specification for that // We could just display as is, but decided here to dig a little bit more. @@ -249,27 +278,29 @@ export default function RoomsList() { }; if (isEditing) { - await api?.v1RoomsUpdate({ - roomId: editRoomId, - requestBody: roomData, + await updateRoomMutation.mutateAsync({ + params: { + path: { room_id: assertExists(editRoomId) }, + }, + body: roomData, }); } else { - await api?.v1RoomsCreate({ - requestBody: roomData, + await createRoomMutation.mutateAsync({ + body: roomData, }); } - setRoom(roomInitialState); + setRoomInput(null); setIsEditing(false); setEditRoomId(""); setNameError(""); refetch(); + onClose(); handleCloseDialog(); - } catch (err) { + } catch (err: any) { if ( - err instanceof ApiError && - err.status === 400 && - (err.body as any).detail == "Room name is not unique" + err?.status === 400 && + err?.body?.detail == "Room name is not unique" ) { setNameError( "This room name is already taken. Please choose a different name.", @@ -280,46 +311,11 @@ export default function RoomsList() { } }; - const handleEditRoom = async (roomId, roomData) => { + const handleEditRoom = async (roomId: string, roomData) => { // Reset states setShowWebhookSecret(false); setWebhookTestResult(null); - // Fetch full room details to get webhook fields - try { - const detailedRoom = await api?.v1RoomsGet({ roomId }); - if (detailedRoom) { - setRoom({ - name: detailedRoom.name, - zulipAutoPost: detailedRoom.zulip_auto_post, - zulipStream: detailedRoom.zulip_stream, - zulipTopic: detailedRoom.zulip_topic, - isLocked: detailedRoom.is_locked, - roomMode: detailedRoom.room_mode, - recordingType: detailedRoom.recording_type, - recordingTrigger: detailedRoom.recording_trigger, - isShared: detailedRoom.is_shared, - webhookUrl: detailedRoom.webhook_url || "", - webhookSecret: detailedRoom.webhook_secret || "", - }); - } - } catch (error) { - console.error("Failed to fetch room details, using list data:", error); - // Fallback to using the data from the list - setRoom({ - name: roomData.name, - zulipAutoPost: roomData.zulip_auto_post, - zulipStream: roomData.zulip_stream, - zulipTopic: roomData.zulip_topic, - isLocked: roomData.is_locked, - roomMode: roomData.room_mode, - recordingType: roomData.recording_type, - recordingTrigger: roomData.recording_trigger, - isShared: roomData.is_shared, - webhookUrl: roomData.webhook_url || "", - webhookSecret: roomData.webhook_secret || "", - }); - } setEditRoomId(roomId); setIsEditing(true); setNameError(""); @@ -328,8 +324,10 @@ export default function RoomsList() { const handleDeleteRoom = async (roomId: string) => { try { - await api?.v1RoomsDelete({ - roomId, + await deleteRoomMutation.mutateAsync({ + params: { + path: { room_id: roomId }, + }, }); refetch(); } catch (err) { @@ -346,15 +344,15 @@ export default function RoomsList() { .toLowerCase(); setNameError(""); } - setRoom({ + setRoomInput({ ...room, [name]: type === "checkbox" ? checked : value, }); }; - const myRooms: RoomDetails[] = + const myRooms: Room[] = response?.items.filter((roomData) => !roomData.is_shared) || []; - const sharedRooms: RoomDetails[] = + const sharedRooms: Room[] = response?.items.filter((roomData) => roomData.is_shared) || []; if (loading && !response) @@ -369,6 +367,9 @@ export default function RoomsList() { ); + if (roomListError) + return
{`${roomListError.name}: ${roomListError.message}`}
; + return ( { setIsEditing(false); - setRoom(roomInitialState); + setRoomInput(null); setNameError(""); setShowWebhookSecret(false); setWebhookTestResult(null); @@ -456,7 +457,7 @@ export default function RoomsList() { - setRoom({ ...room, roomMode: e.value[0] }) + setRoomInput({ ...room, roomMode: e.value[0] }) } collection={roomModeCollection} > @@ -486,7 +487,7 @@ export default function RoomsList() { - setRoom({ + setRoomInput({ ...room, recordingType: e.value[0], recordingTrigger: @@ -521,7 +522,7 @@ export default function RoomsList() { - setRoom({ ...room, recordingTrigger: e.value[0] }) + setRoomInput({ ...room, recordingTrigger: e.value[0] }) } collection={recordingTriggerCollection} disabled={room.recordingType !== "cloud"} @@ -576,7 +577,7 @@ export default function RoomsList() { - setRoom({ + setRoomInput({ ...room, zulipStream: e.value[0], zulipTopic: "", @@ -611,7 +612,7 @@ export default function RoomsList() { - setRoom({ ...room, zulipTopic: e.value[0] }) + setRoomInput({ ...room, zulipTopic: e.value[0] }) } collection={topicCollection} disabled={!room.zulipAutoPost} diff --git a/www/app/(app)/rooms/useRoomList.tsx b/www/app/(app)/rooms/useRoomList.tsx index c1021ade..e8d11250 100644 --- a/www/app/(app)/rooms/useRoomList.tsx +++ b/www/app/(app)/rooms/useRoomList.tsx @@ -1,48 +1,27 @@ -import { useEffect, useState } from "react"; -import { useError } from "../../(errors)/errorContext"; -import useApi from "../../lib/useApi"; -import { Page_RoomDetails_ } from "../../api"; +import { useRoomsList } from "../../lib/apiHooks"; +import type { components } from "../../reflector-api"; + +type Page_Room_ = components["schemas"]["Page_RoomDetails_"]; import { PaginationPage } from "../browse/_components/Pagination"; type RoomList = { - response: Page_RoomDetails_ | null; + response: Page_Room_ | null; loading: boolean; error: Error | null; refetch: () => void; }; -//always protected +// Wrapper to maintain backward compatibility const useRoomList = (page: PaginationPage): RoomList => { - const [response, setResponse] = useState(null); - const [loading, setLoading] = useState(true); - const [error, setErrorState] = useState(null); - const { setError } = useError(); - const api = useApi(); - const [refetchCount, setRefetchCount] = useState(0); - - const refetch = () => { - setLoading(true); - setRefetchCount(refetchCount + 1); + const { data, isLoading, error, refetch } = useRoomsList(page); + return { + response: data || null, + loading: isLoading, + error: error + ? new Error(error.detail ? JSON.stringify(error.detail) : undefined) + : null, + refetch, }; - - useEffect(() => { - if (!api) return; - setLoading(true); - api - .v1RoomsList({ page }) - .then((response) => { - setResponse(response); - setLoading(false); - }) - .catch((err) => { - setResponse(null); - setLoading(false); - setError(err); - setErrorState(err); - }); - }, [!api, page, refetchCount]); - - return { response, loading, error, refetch }; }; export default useRoomList; diff --git a/www/app/(app)/transcripts/[transcriptId]/_components/TopicList.tsx b/www/app/(app)/transcripts/[transcriptId]/_components/TopicList.tsx index 1f5d1588..fdf3db41 100644 --- a/www/app/(app)/transcripts/[transcriptId]/_components/TopicList.tsx +++ b/www/app/(app)/transcripts/[transcriptId]/_components/TopicList.tsx @@ -3,8 +3,10 @@ import ScrollToBottom from "../../scrollToBottom"; import { Topic } from "../../webSocketTypes"; import useParticipants from "../../useParticipants"; import { Box, Flex, Text, Accordion } from "@chakra-ui/react"; -import { featureEnabled } from "../../../../domainContext"; import { TopicItem } from "./TopicItem"; +import { TranscriptStatus } from "../../../../lib/transcript"; + +import { featureEnabled } from "../../../../lib/features"; type TopicListProps = { topics: Topic[]; @@ -14,7 +16,7 @@ type TopicListProps = { ]; autoscroll: boolean; transcriptId: string; - status: string; + status: TranscriptStatus | null; currentTranscriptText: any; }; diff --git a/www/app/(app)/transcripts/[transcriptId]/correct/page.tsx b/www/app/(app)/transcripts/[transcriptId]/correct/page.tsx index 9eff7b60..c4d5a9fc 100644 --- a/www/app/(app)/transcripts/[transcriptId]/correct/page.tsx +++ b/www/app/(app)/transcripts/[transcriptId]/correct/page.tsx @@ -1,30 +1,35 @@ "use client"; -import { useState } from "react"; +import { useState, use } from "react"; import TopicHeader from "./topicHeader"; import TopicWords from "./topicWords"; import TopicPlayer from "./topicPlayer"; import useParticipants from "../../useParticipants"; import useTopicWithWords from "../../useTopicWithWords"; import ParticipantList from "./participantList"; -import { GetTranscriptTopic } from "../../../../api"; +import type { components } from "../../../../reflector-api"; +type GetTranscriptTopic = components["schemas"]["GetTranscriptTopic"]; import { SelectedText, selectedTextIsTimeSlice } from "./types"; -import useApi from "../../../../lib/useApi"; -import useTranscript from "../../useTranscript"; +import { + useTranscriptGet, + useTranscriptUpdate, +} from "../../../../lib/apiHooks"; import { useError } from "../../../../(errors)/errorContext"; import { useRouter } from "next/navigation"; import { Box, Grid } from "@chakra-ui/react"; export type TranscriptCorrect = { - params: { + params: Promise<{ transcriptId: string; - }; + }>; }; -export default function TranscriptCorrect({ - params: { transcriptId }, -}: TranscriptCorrect) { - const api = useApi(); - const transcript = useTranscript(transcriptId); +export default function TranscriptCorrect(props: TranscriptCorrect) { + const params = use(props.params); + + const { transcriptId } = params; + + const updateTranscriptMutation = useTranscriptUpdate(); + const transcript = useTranscriptGet(transcriptId); const stateCurrentTopic = useState(); const [currentTopic, _sct] = stateCurrentTopic; const stateSelectedText = useState(); @@ -34,16 +39,21 @@ export default function TranscriptCorrect({ const { setError } = useError(); const router = useRouter(); - const markAsDone = () => { - if (transcript.response && !transcript.response.reviewed) { - api - ?.v1TranscriptUpdate({ transcriptId, requestBody: { reviewed: true } }) - .then(() => { - router.push(`/transcripts/${transcriptId}`); - }) - .catch((e) => { - setError(e, "Error marking as done"); + const markAsDone = async () => { + if (transcript.data && !transcript.data.reviewed) { + try { + await updateTranscriptMutation.mutateAsync({ + params: { + path: { + transcript_id: transcriptId, + }, + }, + body: { reviewed: true }, }); + router.push(`/transcripts/${transcriptId}`); + } catch (e) { + setError(e as Error, "Error marking as done"); + } } }; @@ -108,7 +118,7 @@ export default function TranscriptCorrect({ }} /> - {transcript.response && !transcript.response?.reviewed && ( + {transcript.data && !transcript.data?.reviewed && (
- + )} {!isEditMode && ( diff --git a/www/app/(app)/transcripts/[transcriptId]/page.tsx b/www/app/(app)/transcripts/[transcriptId]/page.tsx index 0a2dba47..f06e8935 100644 --- a/www/app/(app)/transcripts/[transcriptId]/page.tsx +++ b/www/app/(app)/transcripts/[transcriptId]/page.tsx @@ -1,32 +1,38 @@ "use client"; import Modal from "../modal"; -import useTranscript from "../useTranscript"; import useTopics from "../useTopics"; import useWaveform from "../useWaveform"; import useMp3 from "../useMp3"; import { TopicList } from "./_components/TopicList"; import { Topic } from "../webSocketTypes"; -import React, { useEffect, useState } from "react"; +import React, { useEffect, useState, use } from "react"; import FinalSummary from "./finalSummary"; import TranscriptTitle from "../transcriptTitle"; import Player from "../player"; import { useRouter } from "next/navigation"; import { Box, Flex, Grid, GridItem, Skeleton, Text } from "@chakra-ui/react"; +import { useTranscriptGet } from "../../../lib/apiHooks"; +import { TranscriptStatus } from "../../../lib/transcript"; type TranscriptDetails = { - params: { + params: Promise<{ transcriptId: string; - }; + }>; }; export default function TranscriptDetails(details: TranscriptDetails) { - const transcriptId = details.params.transcriptId; + const params = use(details.params); + const transcriptId = params.transcriptId; const router = useRouter(); - const statusToRedirect = ["idle", "recording", "processing"]; + const statusToRedirect = [ + "idle", + "recording", + "processing", + ] satisfies TranscriptStatus[] as TranscriptStatus[]; - const transcript = useTranscript(transcriptId); - const transcriptStatus = transcript.response?.status; - const waiting = statusToRedirect.includes(transcriptStatus || ""); + const transcript = useTranscriptGet(transcriptId); + const waiting = + transcript.data && statusToRedirect.includes(transcript.data.status); const mp3 = useMp3(transcriptId, waiting); const topics = useTopics(transcriptId); @@ -38,7 +44,7 @@ export default function TranscriptDetails(details: TranscriptDetails) { useEffect(() => { if (waiting) { - const newUrl = "/transcripts/" + details.params.transcriptId + "/record"; + const newUrl = "/transcripts/" + params.transcriptId + "/record"; // Shallow redirection does not work on NextJS 13 // https://github.com/vercel/next.js/discussions/48110 // https://github.com/vercel/next.js/discussions/49540 @@ -56,7 +62,7 @@ export default function TranscriptDetails(details: TranscriptDetails) { ); } - if (transcript?.loading || topics?.loading) { + if (transcript?.isLoading || topics?.loading) { return ; } @@ -86,7 +92,7 @@ export default function TranscriptDetails(details: TranscriptDetails) { useActiveTopic={useActiveTopic} waveform={waveform.waveform} media={mp3.media} - mediaDuration={transcript.response.duration} + mediaDuration={transcript.data?.duration || null} /> ) : !mp3.loading && (waveform.error || mp3.error) ? ( @@ -116,10 +122,10 @@ export default function TranscriptDetails(details: TranscriptDetails) { { - transcript.reload(); + transcript.refetch().then(() => {}); }} /> @@ -136,23 +142,23 @@ export default function TranscriptDetails(details: TranscriptDetails) { useActiveTopic={useActiveTopic} autoscroll={false} transcriptId={transcriptId} - status={transcript.response?.status} + status={transcript.data?.status || null} currentTranscriptText="" /> - {transcript.response && topics.topics ? ( + {transcript.data && topics.topics ? ( <> { - transcript.reload(); + onUpdate={() => { + transcript.refetch(); }} /> ) : (
- {transcript.response.status == "processing" ? ( + {transcript?.data?.status == "processing" ? ( Loading Transcript ) : ( diff --git a/www/app/(app)/transcripts/[transcriptId]/record/page.tsx b/www/app/(app)/transcripts/[transcriptId]/record/page.tsx index 8f6634b0..d93b34b6 100644 --- a/www/app/(app)/transcripts/[transcriptId]/record/page.tsx +++ b/www/app/(app)/transcripts/[transcriptId]/record/page.tsx @@ -1,8 +1,7 @@ "use client"; -import { useEffect, useState } from "react"; +import { useEffect, useState, use } from "react"; import Recorder from "../../recorder"; import { TopicList } from "../_components/TopicList"; -import useTranscript from "../../useTranscript"; import { useWebSockets } from "../../useWebSockets"; import { Topic } from "../../webSocketTypes"; import { lockWakeState, releaseWakeState } from "../../../../lib/wakeLock"; @@ -11,26 +10,29 @@ import useMp3 from "../../useMp3"; import WaveformLoading from "../../waveformLoading"; import { Box, Text, Grid, Heading, VStack, Flex } from "@chakra-ui/react"; import LiveTrancription from "../../liveTranscription"; +import { useTranscriptGet } from "../../../../lib/apiHooks"; +import { TranscriptStatus } from "../../../../lib/transcript"; type TranscriptDetails = { - params: { + params: Promise<{ transcriptId: string; - }; + }>; }; const TranscriptRecord = (details: TranscriptDetails) => { - const transcript = useTranscript(details.params.transcriptId); + const params = use(details.params); + const transcript = useTranscriptGet(params.transcriptId); const [transcriptStarted, setTranscriptStarted] = useState(false); const useActiveTopic = useState(null); - const webSockets = useWebSockets(details.params.transcriptId); + const webSockets = useWebSockets(params.transcriptId); - const mp3 = useMp3(details.params.transcriptId, true); + const mp3 = useMp3(params.transcriptId, true); const router = useRouter(); - const [status, setStatus] = useState( - webSockets.status.value || transcript.response?.status || "idle", + const [status, setStatus] = useState( + webSockets.status?.value || transcript.data?.status || "idle", ); useEffect(() => { @@ -41,15 +43,15 @@ const TranscriptRecord = (details: TranscriptDetails) => { useEffect(() => { //TODO HANDLE ERROR STATUS BETTER const newStatus = - webSockets.status.value || transcript.response?.status || "idle"; + webSockets.status?.value || transcript.data?.status || "idle"; setStatus(newStatus); if (newStatus && (newStatus == "ended" || newStatus == "error")) { console.log(newStatus, "redirecting"); - const newUrl = "/transcripts/" + details.params.transcriptId; + const newUrl = "/transcripts/" + params.transcriptId; router.replace(newUrl); } - }, [webSockets.status.value, transcript.response?.status]); + }, [webSockets.status?.value, transcript.data?.status]); useEffect(() => { if (webSockets.waveform && webSockets.waveform) mp3.getNow(); @@ -74,7 +76,7 @@ const TranscriptRecord = (details: TranscriptDetails) => { ) : ( // todo: only start recording animation when you get "recorded" status - + )} { topics={webSockets.topics} useActiveTopic={useActiveTopic} autoscroll={true} - transcriptId={details.params.transcriptId} + transcriptId={params.transcriptId} status={status} currentTranscriptText={webSockets.accumulatedText} /> diff --git a/www/app/(app)/transcripts/[transcriptId]/upload/page.tsx b/www/app/(app)/transcripts/[transcriptId]/upload/page.tsx index 3a13052e..b4bc25cc 100644 --- a/www/app/(app)/transcripts/[transcriptId]/upload/page.tsx +++ b/www/app/(app)/transcripts/[transcriptId]/upload/page.tsx @@ -1,33 +1,40 @@ "use client"; -import { useEffect, useState } from "react"; -import useTranscript from "../../useTranscript"; +import { useEffect, useState, use } from "react"; import { useWebSockets } from "../../useWebSockets"; import { lockWakeState, releaseWakeState } from "../../../../lib/wakeLock"; import { useRouter } from "next/navigation"; import useMp3 from "../../useMp3"; import { Center, VStack, Text, Heading, Button } from "@chakra-ui/react"; import FileUploadButton from "../../fileUploadButton"; +import { useTranscriptGet } from "../../../../lib/apiHooks"; type TranscriptUpload = { - params: { + params: Promise<{ transcriptId: string; - }; + }>; }; const TranscriptUpload = (details: TranscriptUpload) => { - const transcript = useTranscript(details.params.transcriptId); + const params = use(details.params); + const transcript = useTranscriptGet(params.transcriptId); const [transcriptStarted, setTranscriptStarted] = useState(false); - const webSockets = useWebSockets(details.params.transcriptId); + const webSockets = useWebSockets(params.transcriptId); - const mp3 = useMp3(details.params.transcriptId, true); + const mp3 = useMp3(params.transcriptId, true); const router = useRouter(); - const [status, setStatus] = useState( - webSockets.status.value || transcript.response?.status || "idle", + const [status_, setStatus] = useState( + webSockets.status?.value || transcript.data?.status || "idle", ); + // status is obviously done if we have transcript + const status = + !transcript.isLoading && transcript.data?.status === "ended" + ? transcript.data?.status + : status_; + useEffect(() => { if (!transcriptStarted && webSockets.transcriptTextLive.length !== 0) setTranscriptStarted(true); @@ -35,16 +42,19 @@ const TranscriptUpload = (details: TranscriptUpload) => { useEffect(() => { //TODO HANDLE ERROR STATUS BETTER + // TODO deprecate webSockets.status.value / depend on transcript.response?.status from query lib const newStatus = - webSockets.status.value || transcript.response?.status || "idle"; + transcript.data?.status === "ended" + ? "ended" + : webSockets.status?.value || transcript.data?.status || "idle"; setStatus(newStatus); if (newStatus && (newStatus == "ended" || newStatus == "error")) { console.log(newStatus, "redirecting"); - const newUrl = "/transcripts/" + details.params.transcriptId; + const newUrl = "/transcripts/" + params.transcriptId; router.replace(newUrl); } - }, [webSockets.status.value, transcript.response?.status]); + }, [webSockets.status?.value, transcript.data?.status]); useEffect(() => { if (webSockets.waveform && webSockets.waveform) mp3.getNow(); @@ -75,7 +85,7 @@ const TranscriptUpload = (details: TranscriptUpload) => { Please select the file, supported formats: .mp3, m4a, .wav, .mp4, .mov or .webm - + )} {status && status == "uploaded" && ( diff --git a/www/app/(app)/transcripts/createTranscript.ts b/www/app/(app)/transcripts/createTranscript.ts index 015c82de..8a235161 100644 --- a/www/app/(app)/transcripts/createTranscript.ts +++ b/www/app/(app)/transcripts/createTranscript.ts @@ -1,45 +1,33 @@ -import { useEffect, useState } from "react"; +import type { components } from "../../reflector-api"; +import { useTranscriptCreate } from "../../lib/apiHooks"; -import { useError } from "../../(errors)/errorContext"; -import { CreateTranscript, GetTranscript } from "../../api"; -import useApi from "../../lib/useApi"; +type CreateTranscript = components["schemas"]["CreateTranscript"]; +type GetTranscript = components["schemas"]["GetTranscript"]; type UseCreateTranscript = { transcript: GetTranscript | null; loading: boolean; error: Error | null; - create: (transcriptCreationDetails: CreateTranscript) => void; + create: (transcriptCreationDetails: CreateTranscript) => Promise; }; const useCreateTranscript = (): UseCreateTranscript => { - const [transcript, setTranscript] = useState(null); - const [loading, setLoading] = useState(false); - const [error, setErrorState] = useState(null); - const { setError } = useError(); - const api = useApi(); + const createMutation = useTranscriptCreate(); - const create = (transcriptCreationDetails: CreateTranscript) => { - if (loading || !api) return; + const create = async (transcriptCreationDetails: CreateTranscript) => { + if (createMutation.isPending) return; - setLoading(true); - - api - .v1TranscriptsCreate({ requestBody: transcriptCreationDetails }) - .then((transcript) => { - setTranscript(transcript); - setLoading(false); - }) - .catch((err) => { - setError( - err, - "There was an issue creating a transcript, please try again.", - ); - setErrorState(err); - setLoading(false); - }); + await createMutation.mutateAsync({ + body: transcriptCreationDetails, + }); }; - return { transcript, loading, error, create }; + return { + transcript: createMutation.data || null, + loading: createMutation.isPending, + error: createMutation.error as Error | null, + create, + }; }; export default useCreateTranscript; diff --git a/www/app/(app)/transcripts/fileUploadButton.tsx b/www/app/(app)/transcripts/fileUploadButton.tsx index 1b4101e8..1f5d72eb 100644 --- a/www/app/(app)/transcripts/fileUploadButton.tsx +++ b/www/app/(app)/transcripts/fileUploadButton.tsx @@ -1,6 +1,7 @@ import React, { useState } from "react"; -import useApi from "../../lib/useApi"; +import { useTranscriptUploadAudio } from "../../lib/apiHooks"; import { Button, Spinner } from "@chakra-ui/react"; +import { useError } from "../../(errors)/errorContext"; type FileUploadButton = { transcriptId: string; @@ -8,13 +9,16 @@ type FileUploadButton = { export default function FileUploadButton(props: FileUploadButton) { const fileInputRef = React.useRef(null); - const api = useApi(); + const uploadMutation = useTranscriptUploadAudio(); + const { setError } = useError(); const [progress, setProgress] = useState(0); const triggerFileUpload = () => { fileInputRef.current?.click(); }; - const handleFileUpload = (event: React.ChangeEvent) => { + const handleFileUpload = async ( + event: React.ChangeEvent, + ) => { const file = event.target.files?.[0]; if (file) { @@ -24,37 +28,45 @@ export default function FileUploadButton(props: FileUploadButton) { let start = 0; let uploadedSize = 0; - api?.httpRequest.config.interceptors.request.use((request) => { - request.onUploadProgress = (progressEvent) => { - const currentProgress = Math.floor( - ((uploadedSize + progressEvent.loaded) / file.size) * 100, - ); - setProgress(currentProgress); - }; - return request; - }); - const uploadNextChunk = async () => { - if (chunkNumber == totalChunks) return; + if (chunkNumber == totalChunks) { + setProgress(0); + return; + } const chunkSize = Math.min(maxChunkSize, file.size - start); const end = start + chunkSize; const chunk = file.slice(start, end); - await api?.v1TranscriptRecordUpload({ - transcriptId: props.transcriptId, - formData: { - chunk, - }, - chunkNumber, - totalChunks, - }); + try { + const formData = new FormData(); + formData.append("chunk", chunk); - uploadedSize += chunkSize; - chunkNumber++; - start = end; + await uploadMutation.mutateAsync({ + params: { + path: { + transcript_id: props.transcriptId, + }, + query: { + chunk_number: chunkNumber, + total_chunks: totalChunks, + }, + }, + body: formData as any, + }); - uploadNextChunk(); + uploadedSize += chunkSize; + const currentProgress = Math.floor((uploadedSize / file.size) * 100); + setProgress(currentProgress); + + chunkNumber++; + start = end; + + await uploadNextChunk(); + } catch (error) { + setError(error as Error, "Failed to upload file"); + setProgress(0); + } }; uploadNextChunk(); diff --git a/www/app/(app)/transcripts/new/page.tsx b/www/app/(app)/transcripts/new/page.tsx index 698ac47b..8953e994 100644 --- a/www/app/(app)/transcripts/new/page.tsx +++ b/www/app/(app)/transcripts/new/page.tsx @@ -9,33 +9,25 @@ import { useRouter } from "next/navigation"; import useCreateTranscript from "../createTranscript"; import SelectSearch from "react-select-search"; import { supportedLanguages } from "../../../supportedLanguages"; -import useSessionStatus from "../../../lib/useSessionStatus"; -import { featureEnabled } from "../../../domainContext"; -import { signIn } from "next-auth/react"; import { Flex, Box, Spinner, Heading, Button, - Card, Center, - Link, - CardBody, - Stack, Text, - Icon, - Grid, - IconButton, Spacer, - Menu, - Tooltip, - Input, } from "@chakra-ui/react"; +import { useAuth } from "../../../lib/AuthProvider"; +import { featureEnabled } from "../../../lib/features"; + const TranscriptCreate = () => { - const isClient = typeof window !== "undefined"; const router = useRouter(); - const { isLoading, isAuthenticated } = useSessionStatus(); + const auth = useAuth(); + const isAuthenticated = auth.status === "authenticated"; + const isAuthRefreshing = auth.status === "refreshing"; + const isLoading = auth.status === "loading"; const requireLogin = featureEnabled("requireLogin"); const [name, setName] = useState(""); @@ -54,20 +46,32 @@ const TranscriptCreate = () => { const [loadingUpload, setLoadingUpload] = useState(false); const getTargetLanguage = () => { - if (targetLanguage === "NOTRANSLATION") return; + if (targetLanguage === "NOTRANSLATION") return undefined; return targetLanguage; }; const send = () => { if (loadingRecord || createTranscript.loading || permissionDenied) return; setLoadingRecord(true); - createTranscript.create({ name, target_language: getTargetLanguage() }); + const targetLang = getTargetLanguage(); + createTranscript.create({ + name, + source_language: "en", + target_language: targetLang || "en", + source_kind: "live", + }); }; const uploadFile = () => { if (loadingUpload || createTranscript.loading || permissionDenied) return; setLoadingUpload(true); - createTranscript.create({ name, target_language: getTargetLanguage() }); + const targetLang = getTargetLanguage(); + createTranscript.create({ + name, + source_language: "en", + target_language: targetLang || "en", + source_kind: "file", + }); }; useEffect(() => { @@ -132,8 +136,8 @@ const TranscriptCreate = () => {
{isLoading ? ( - ) : requireLogin && !isAuthenticated ? ( - + ) : requireLogin && !isAuthenticated && !isAuthRefreshing ? ( + ) : ( { placeholder="Choose your language" /> - {isClient && !loading ? ( + {!loading ? ( permissionOk ? ( ) : permissionDenied ? ( diff --git a/www/app/(app)/transcripts/player.tsx b/www/app/(app)/transcripts/player.tsx index c8163ecb..10b02aa1 100644 --- a/www/app/(app)/transcripts/player.tsx +++ b/www/app/(app)/transcripts/player.tsx @@ -5,7 +5,9 @@ import RegionsPlugin from "wavesurfer.js/dist/plugins/regions.esm.js"; import { formatTime, formatTimeMs } from "../../lib/time"; import { Topic } from "./webSocketTypes"; -import { AudioWaveform } from "../../api"; +import type { components } from "../../reflector-api"; + +type AudioWaveform = components["schemas"]["AudioWaveform"]; import { waveSurferStyles } from "../../styles/recorder"; import { Box, Flex, IconButton } from "@chakra-ui/react"; import { LuPause, LuPlay } from "react-icons/lu"; @@ -18,7 +20,7 @@ type PlayerProps = { ]; waveform: AudioWaveform; media: HTMLMediaElement; - mediaDuration: number; + mediaDuration: number | null; }; export default function Player(props: PlayerProps) { @@ -50,7 +52,9 @@ export default function Player(props: PlayerProps) { container: waveformRef.current, peaks: [props.waveform.data], height: "auto", - duration: Math.floor(props.mediaDuration / 1000), + duration: props.mediaDuration + ? Math.floor(props.mediaDuration / 1000) + : undefined, media: props.media, ...waveSurferStyles.playerSettings, diff --git a/www/app/(app)/transcripts/recorder.tsx b/www/app/(app)/transcripts/recorder.tsx index f57540d4..1cf68c39 100644 --- a/www/app/(app)/transcripts/recorder.tsx +++ b/www/app/(app)/transcripts/recorder.tsx @@ -6,16 +6,16 @@ import RecordPlugin from "../../lib/custom-plugins/record"; import { formatTime, formatTimeMs } from "../../lib/time"; import { waveSurferStyles } from "../../styles/recorder"; import { useError } from "../../(errors)/errorContext"; -import FileUploadButton from "./fileUploadButton"; import useWebRTC from "./useWebRTC"; import useAudioDevice from "./useAudioDevice"; import { Box, Flex, IconButton, Menu, RadioGroup } from "@chakra-ui/react"; import { LuScreenShare, LuMic, LuPlay, LuCircleStop } from "react-icons/lu"; import { RECORD_A_MEETING_URL } from "../../api/urls"; +import { TranscriptStatus } from "../../lib/transcript"; type RecorderProps = { transcriptId: string; - status: string; + status: TranscriptStatus; }; export default function Recorder(props: RecorderProps) { diff --git a/www/app/(app)/transcripts/shareAndPrivacy.tsx b/www/app/(app)/transcripts/shareAndPrivacy.tsx index 609793d3..8580015d 100644 --- a/www/app/(app)/transcripts/shareAndPrivacy.tsx +++ b/www/app/(app)/transcripts/shareAndPrivacy.tsx @@ -1,8 +1,10 @@ import { useEffect, useState } from "react"; -import { featureEnabled } from "../../domainContext"; import { ShareMode, toShareMode } from "../../lib/shareMode"; -import { GetTranscript, GetTranscriptTopic, UpdateTranscript } from "../../api"; +import type { components } from "../../reflector-api"; +type GetTranscript = components["schemas"]["GetTranscript"]; +type GetTranscriptTopic = components["schemas"]["GetTranscriptTopic"]; +type UpdateTranscript = components["schemas"]["UpdateTranscript"]; import { Box, Flex, @@ -15,12 +17,13 @@ import { createListCollection, } from "@chakra-ui/react"; import { LuShare2 } from "react-icons/lu"; -import useApi from "../../lib/useApi"; -import useSessionUser from "../../lib/useSessionUser"; -import { CustomSession } from "../../lib/types"; +import { useTranscriptUpdate } from "../../lib/apiHooks"; import ShareLink from "./shareLink"; import ShareCopy from "./shareCopy"; import ShareZulip from "./shareZulip"; +import { useAuth } from "../../lib/AuthProvider"; + +import { featureEnabled } from "../../lib/features"; type ShareAndPrivacyProps = { finalSummaryRef: any; @@ -50,12 +53,9 @@ export default function ShareAndPrivacy(props: ShareAndPrivacyProps) { ); const [shareLoading, setShareLoading] = useState(false); const requireLogin = featureEnabled("requireLogin"); - const api = useApi(); + const updateTranscriptMutation = useTranscriptUpdate(); const updateShareMode = async (selectedValue: string) => { - if (!api) - throw new Error("ShareLink's API should always be ready at this point"); - const selectedOption = shareOptionsData.find( (option) => option.value === selectedValue, ); @@ -67,19 +67,27 @@ export default function ShareAndPrivacy(props: ShareAndPrivacyProps) { share_mode: selectedValue as "public" | "semi-private" | "private", }; - const updatedTranscript = await api.v1TranscriptUpdate({ - transcriptId: props.transcriptResponse.id, - requestBody, - }); - setShareMode( - shareOptionsData.find( - (option) => option.value === updatedTranscript.share_mode, - ) || shareOptionsData[0], - ); - setShareLoading(false); + try { + const updatedTranscript = await updateTranscriptMutation.mutateAsync({ + params: { + path: { transcript_id: props.transcriptResponse.id }, + }, + body: requestBody, + }); + setShareMode( + shareOptionsData.find( + (option) => option.value === updatedTranscript.share_mode, + ) || shareOptionsData[0], + ); + } catch (err) { + console.error("Failed to update share mode:", err); + } finally { + setShareLoading(false); + } }; - const userId = useSessionUser().id; + const auth = useAuth(); + const userId = auth.status === "authenticated" ? auth.user?.id : null; useEffect(() => { setIsOwner(!!(requireLogin && userId === props.transcriptResponse.user_id)); @@ -124,7 +132,7 @@ export default function ShareAndPrivacy(props: ShareAndPrivacyProps) { "This transcript is public. Everyone can access it."} - {isOwner && api && ( + {isOwner && ( (undefined); + const [selectedStreamId, setSelectedStreamId] = useState(null); const [topic, setTopic] = useState(undefined); const [includeTopics, setIncludeTopics] = useState(false); - const [isLoading, setIsLoading] = useState(true); - const [streams, setStreams] = useState([]); - const [topics, setTopics] = useState([]); - const api = useApi(); - const { contains } = useFilter({ sensitivity: "base" }); - const { - collection: streamItemsCollection, - filter: streamItemsFilter, - set: streamItemsSet, - } = useListCollection({ - initialItems: [] as { label: string; value: string }[], - filter: contains, - }); + const { data: streams = [], isLoading: isLoadingStreams } = useZulipStreams(); + const { data: topics = [] } = useZulipTopics(selectedStreamId); + const postToZulipMutation = useTranscriptPostToZulip(); - const { - collection: topicItemsCollection, - filter: topicItemsFilter, - set: topicItemsSet, - } = useListCollection({ - initialItems: [] as { label: string; value: string }[], - filter: contains, - }); + const streamItems = useMemo(() => { + return streams.map((stream: Stream) => ({ + label: stream.name, + value: stream.name, + })); + }, [streams]); + const topicItems = useMemo(() => { + return topics.map(({ name }) => ({ + label: name, + value: name, + })); + }, [topics]); + + const streamCollection = useMemo( + () => + createListCollection({ + items: streamItems, + }), + [streamItems], + ); + + const topicCollection = useMemo( + () => + createListCollection({ + items: topicItems, + }), + [topicItems], + ); + + // Update selected stream ID when stream changes useEffect(() => { - const fetchZulipStreams = async () => { - if (!api) return; - - try { - const response = await api.v1ZulipGetStreams(); - setStreams(response); - - streamItemsSet( - response.map((stream) => ({ - label: stream.name, - value: stream.name, - })), - ); - - setIsLoading(false); - } catch (error) { - console.error("Error fetching Zulip streams:", error); - } - }; - - fetchZulipStreams(); - }, [!api]); - - useEffect(() => { - const fetchZulipTopics = async () => { - if (!api || !stream) return; - try { - const selectedStream = streams.find((s) => s.name === stream); - if (selectedStream) { - const response = await api.v1ZulipGetTopics({ - streamId: selectedStream.stream_id, - }); - setTopics(response); - topicItemsSet( - response.map((topic) => ({ - label: topic.name, - value: topic.name, - })), - ); - } else { - topicItemsSet([]); - } - } catch (error) { - console.error("Error fetching Zulip topics:", error); - } - }; - - fetchZulipTopics(); - }, [stream, streams, api]); + if (stream && streams) { + const selectedStream = streams.find((s: Stream) => s.name === stream); + setSelectedStreamId(selectedStream ? selectedStream.stream_id : null); + } else { + setSelectedStreamId(null); + } + }, [stream, streams]); const handleSendToZulip = async () => { - if (!api || !props.transcriptResponse) return; + if (!props.transcriptResponse) return; if (stream && topic) { try { - await api.v1TranscriptPostToZulip({ - transcriptId: props.transcriptResponse.id, - stream, - topic, - includeTopics, + await postToZulipMutation.mutateAsync({ + params: { + path: { + transcript_id: props.transcriptResponse.id, + }, + query: { + stream, + topic, + include_topics: includeTopics, + }, + }, }); setShowModal(false); } catch (error) { - console.log(error); + console.error("Error posting to Zulip:", error); } } }; @@ -155,7 +134,7 @@ export default function ShareZulip(props: ShareZulipProps & BoxProps) { - {isLoading ? ( + {isLoadingStreams ? ( @@ -178,15 +157,12 @@ export default function ShareZulip(props: ShareZulipProps & BoxProps) { # { setTopic(undefined); setStream(e.value[0]); }} - onInputValueChange={(e) => - streamItemsFilter(e.inputValue) - } openOnClick={true} positioning={{ strategy: "fixed", @@ -203,7 +179,7 @@ export default function ShareZulip(props: ShareZulipProps & BoxProps) { No streams found - {streamItemsCollection.items.map((item) => ( + {streamItems.map((item) => ( {item.label} @@ -219,12 +195,9 @@ export default function ShareZulip(props: ShareZulipProps & BoxProps) { # setTopic(e.value[0])} - onInputValueChange={(e) => - topicItemsFilter(e.inputValue) - } openOnClick selectionBehavior="replace" skipAnimationOnMount={true} @@ -244,7 +217,7 @@ export default function ShareZulip(props: ShareZulipProps & BoxProps) { No topics found - {topicItemsCollection.items.map((item) => ( + {topicItems.map((item) => ( {item.label} diff --git a/www/app/(app)/transcripts/transcriptTitle.tsx b/www/app/(app)/transcripts/transcriptTitle.tsx index 4678818f..72421f48 100644 --- a/www/app/(app)/transcripts/transcriptTitle.tsx +++ b/www/app/(app)/transcripts/transcriptTitle.tsx @@ -1,6 +1,8 @@ import { useState } from "react"; -import { UpdateTranscript } from "../../api"; -import useApi from "../../lib/useApi"; +import type { components } from "../../reflector-api"; + +type UpdateTranscript = components["schemas"]["UpdateTranscript"]; +import { useTranscriptUpdate } from "../../lib/apiHooks"; import { Heading, IconButton, Input, Flex, Spacer } from "@chakra-ui/react"; import { LuPen } from "react-icons/lu"; @@ -14,24 +16,27 @@ const TranscriptTitle = (props: TranscriptTitle) => { const [displayedTitle, setDisplayedTitle] = useState(props.title); const [preEditTitle, setPreEditTitle] = useState(props.title); const [isEditing, setIsEditing] = useState(false); - const api = useApi(); + const updateTranscriptMutation = useTranscriptUpdate(); const updateTitle = async (newTitle: string, transcriptId: string) => { - if (!api) return; try { const requestBody: UpdateTranscript = { title: newTitle, }; - const updatedTranscript = await api?.v1TranscriptUpdate({ - transcriptId, - requestBody, + await updateTranscriptMutation.mutateAsync({ + params: { + path: { transcript_id: transcriptId }, + }, + body: requestBody, }); if (props.onUpdate) { props.onUpdate(newTitle); } - console.log("Updated transcript:", updatedTranscript); + console.log("Updated transcript title:", newTitle); } catch (err) { console.error("Failed to update transcript:", err); + // Revert title on error + setDisplayedTitle(preEditTitle); } }; diff --git a/www/app/(app)/transcripts/useMp3.ts b/www/app/(app)/transcripts/useMp3.ts index 3e8344ad..cc0635ec 100644 --- a/www/app/(app)/transcripts/useMp3.ts +++ b/www/app/(app)/transcripts/useMp3.ts @@ -1,6 +1,7 @@ -import { useContext, useEffect, useState } from "react"; -import { DomainContext } from "../../domainContext"; -import getApi from "../../lib/useApi"; +import { useEffect, useState } from "react"; +import { useTranscriptGet } from "../../lib/apiHooks"; +import { useAuth } from "../../lib/AuthProvider"; +import { API_URL } from "../../lib/apiClient"; export type Mp3Response = { media: HTMLMediaElement | null; @@ -17,14 +18,16 @@ const useMp3 = (transcriptId: string, waiting?: boolean): Mp3Response => { const [audioLoadingError, setAudioLoadingError] = useState( null, ); - const [transcriptMetadataLoading, setTranscriptMetadataLoading] = - useState(true); - const [transcriptMetadataLoadingError, setTranscriptMetadataLoadingError] = - useState(null); const [audioDeleted, setAudioDeleted] = useState(null); - const api = getApi(); - const { api_url } = useContext(DomainContext); - const accessTokenInfo = api?.httpRequest?.config?.TOKEN; + const auth = useAuth(); + const accessTokenInfo = + auth.status === "authenticated" ? auth.accessToken : null; + + const { + data: transcript, + isLoading: transcriptMetadataLoading, + error: transcriptError, + } = useTranscriptGet(later ? null : transcriptId); const [serviceWorker, setServiceWorker] = useState(null); @@ -52,72 +55,50 @@ const useMp3 = (transcriptId: string, waiting?: boolean): Mp3Response => { }, [navigator.serviceWorker, !serviceWorker, accessTokenInfo]); useEffect(() => { - if (!transcriptId || !api || later) return; + if (!transcriptId || later || !transcript) return; let stopped = false; let audioElement: HTMLAudioElement | null = null; let handleCanPlay: (() => void) | null = null; let handleError: (() => void) | null = null; - setTranscriptMetadataLoading(true); setAudioLoading(true); - // First fetch transcript info to check if audio is deleted - api - .v1TranscriptGet({ transcriptId }) - .then((transcript) => { - if (stopped) { - return; - } + const deleted = transcript.audio_deleted || false; + setAudioDeleted(deleted); - const deleted = transcript.audio_deleted || false; - setAudioDeleted(deleted); - setTranscriptMetadataLoadingError(null); + if (deleted) { + // Audio is deleted, don't attempt to load it + setMedia(null); + setAudioLoadingError(null); + setAudioLoading(false); + return; + } - if (deleted) { - // Audio is deleted, don't attempt to load it - setMedia(null); - setAudioLoadingError(null); - setAudioLoading(false); - return; - } + // Audio is not deleted, proceed to load it + audioElement = document.createElement("audio"); + audioElement.src = `${API_URL}/v1/transcripts/${transcriptId}/audio/mp3`; + audioElement.crossOrigin = "anonymous"; + audioElement.preload = "auto"; - // Audio is not deleted, proceed to load it - audioElement = document.createElement("audio"); - audioElement.src = `${api_url}/v1/transcripts/${transcriptId}/audio/mp3`; - audioElement.crossOrigin = "anonymous"; - audioElement.preload = "auto"; + handleCanPlay = () => { + if (stopped) return; + setAudioLoading(false); + setAudioLoadingError(null); + }; - handleCanPlay = () => { - if (stopped) return; - setAudioLoading(false); - setAudioLoadingError(null); - }; + handleError = () => { + if (stopped) return; + setAudioLoading(false); + setAudioLoadingError("Failed to load audio"); + }; - handleError = () => { - if (stopped) return; - setAudioLoading(false); - setAudioLoadingError("Failed to load audio"); - }; + audioElement.addEventListener("canplay", handleCanPlay); + audioElement.addEventListener("error", handleError); - audioElement.addEventListener("canplay", handleCanPlay); - audioElement.addEventListener("error", handleError); - - if (!stopped) { - setMedia(audioElement); - } - }) - .catch((error) => { - if (stopped) return; - console.error("Failed to fetch transcript:", error); - setAudioDeleted(null); - setTranscriptMetadataLoadingError(error.message); - setAudioLoading(false); - }) - .finally(() => { - if (stopped) return; - setTranscriptMetadataLoading(false); - }); + if (!stopped) { + setMedia(audioElement); + } return () => { stopped = true; @@ -128,14 +109,18 @@ const useMp3 = (transcriptId: string, waiting?: boolean): Mp3Response => { if (handleError) audioElement.removeEventListener("error", handleError); } }; - }, [transcriptId, api, later, api_url]); + }, [transcriptId, transcript, later]); const getNow = () => { setLater(false); }; const loading = audioLoading || transcriptMetadataLoading; - const error = audioLoadingError || transcriptMetadataLoadingError; + const error = + audioLoadingError || + (transcriptError + ? (transcriptError as any).message || String(transcriptError) + : null); return { media, loading, error, getNow, audioDeleted }; }; diff --git a/www/app/(app)/transcripts/useParticipants.ts b/www/app/(app)/transcripts/useParticipants.ts index 38f5aa35..a3674597 100644 --- a/www/app/(app)/transcripts/useParticipants.ts +++ b/www/app/(app)/transcripts/useParticipants.ts @@ -1,8 +1,6 @@ -import { useEffect, useState } from "react"; -import { Participant } from "../../api"; -import { useError } from "../../(errors)/errorContext"; -import useApi from "../../lib/useApi"; -import { shouldShowError } from "../../lib/errorUtils"; +import type { components } from "../../reflector-api"; +type Participant = components["schemas"]["Participant"]; +import { useTranscriptParticipants } from "../../lib/apiHooks"; type ErrorParticipants = { error: Error; @@ -29,46 +27,38 @@ export type UseParticipants = ( ) & { refetch: () => void }; const useParticipants = (transcriptId: string): UseParticipants => { - const [response, setResponse] = useState(null); - const [loading, setLoading] = useState(true); - const [error, setErrorState] = useState(null); - const { setError } = useError(); - const api = useApi(); - const [count, setCount] = useState(0); + const { + data: response, + isLoading: loading, + error, + refetch, + } = useTranscriptParticipants(transcriptId || null); - const refetch = () => { - if (!loading) { - setCount(count + 1); - setLoading(true); - setErrorState(null); - } - }; + // Type-safe return based on state + if (error) { + return { + error: error as Error, + loading: false, + response: null, + refetch, + } satisfies ErrorParticipants & { refetch: () => void }; + } - useEffect(() => { - if (!transcriptId || !api) return; + if (loading || !response) { + return { + response: response || null, + loading: true, + error: null, + refetch, + } satisfies LoadingParticipants & { refetch: () => void }; + } - setLoading(true); - api - .v1TranscriptGetParticipants({ transcriptId }) - .then((result) => { - setResponse(result); - setLoading(false); - console.debug("Participants Loaded:", result); - }) - .catch((error) => { - const shouldShowHuman = shouldShowError(error); - if (shouldShowHuman) { - setError(error, "There was an error loading the participants"); - } else { - setError(error); - } - setErrorState(error); - setResponse(null); - setLoading(false); - }); - }, [transcriptId, !api, count]); - - return { response, loading, error, refetch } as UseParticipants; + return { + response, + loading: false, + error: null, + refetch, + } satisfies SuccessParticipants & { refetch: () => void }; }; export default useParticipants; diff --git a/www/app/(app)/transcripts/useSearchTranscripts.ts b/www/app/(app)/transcripts/useSearchTranscripts.ts deleted file mode 100644 index 2e6a7311..00000000 --- a/www/app/(app)/transcripts/useSearchTranscripts.ts +++ /dev/null @@ -1,123 +0,0 @@ -// this hook is not great, we want to substitute it with a proper state management solution that is also not re-invention - -import { useEffect, useRef, useState } from "react"; -import { SearchResult, SourceKind } from "../../api"; -import useApi from "../../lib/useApi"; -import { - PaginationPage, - paginationPageTo0Based, -} from "../browse/_components/Pagination"; - -interface SearchFilters { - roomIds: readonly string[] | null; - sourceKind: SourceKind | null; -} - -const EMPTY_SEARCH_FILTERS: SearchFilters = { - roomIds: null, - sourceKind: null, -}; - -type UseSearchTranscriptsOptions = { - pageSize: number; - page: PaginationPage; -}; - -interface UseSearchTranscriptsReturn { - results: SearchResult[]; - totalCount: number; - isLoading: boolean; - error: unknown; - reload: () => void; -} - -function hashEffectFilters(filters: SearchFilters): string { - return JSON.stringify(filters); -} - -export function useSearchTranscripts( - query: string = "", - filters: SearchFilters = EMPTY_SEARCH_FILTERS, - options: UseSearchTranscriptsOptions = { - pageSize: 20, - page: PaginationPage(1), - }, -): UseSearchTranscriptsReturn { - const { pageSize, page } = options; - - const [reloadCount, setReloadCount] = useState(0); - - const api = useApi(); - const abortControllerRef = useRef(); - - const [data, setData] = useState<{ results: SearchResult[]; total: number }>({ - results: [], - total: 0, - }); - const [error, setError] = useState(); - const [isLoading, setIsLoading] = useState(false); - - const filterHash = hashEffectFilters(filters); - - useEffect(() => { - if (!api) { - setData({ results: [], total: 0 }); - setError(undefined); - setIsLoading(false); - return; - } - - if (abortControllerRef.current) { - abortControllerRef.current.abort(); - } - - const abortController = new AbortController(); - abortControllerRef.current = abortController; - - const performSearch = async () => { - setIsLoading(true); - - try { - const response = await api.v1TranscriptsSearch({ - q: query || "", - limit: pageSize, - offset: paginationPageTo0Based(page) * pageSize, - roomId: filters.roomIds?.[0], - sourceKind: filters.sourceKind || undefined, - }); - - if (abortController.signal.aborted) return; - setData(response); - setError(undefined); - } catch (err: unknown) { - if ((err as Error).name === "AbortError") { - return; - } - if (abortController.signal.aborted) { - console.error("Aborted search but error", err); - return; - } - - setError(err); - } finally { - if (!abortController.signal.aborted) { - setIsLoading(false); - } - } - }; - - performSearch().then(() => {}); - - return () => { - abortController.abort(); - }; - }, [api, query, page, filterHash, pageSize, reloadCount]); - - return { - results: data.results, - totalCount: data.total, - isLoading, - error, - reload: () => setReloadCount(reloadCount + 1), - }; -} diff --git a/www/app/(app)/transcripts/useTopicWithWords.ts b/www/app/(app)/transcripts/useTopicWithWords.ts index 29d0b982..31e184cc 100644 --- a/www/app/(app)/transcripts/useTopicWithWords.ts +++ b/www/app/(app)/transcripts/useTopicWithWords.ts @@ -1,9 +1,8 @@ -import { useEffect, useState } from "react"; +import type { components } from "../../reflector-api"; +import { useTranscriptTopicsWithWordsPerSpeaker } from "../../lib/apiHooks"; -import { GetTranscriptTopicWithWordsPerSpeaker } from "../../api"; -import { useError } from "../../(errors)/errorContext"; -import useApi from "../../lib/useApi"; -import { shouldShowError } from "../../lib/errorUtils"; +type GetTranscriptTopicWithWordsPerSpeaker = + components["schemas"]["GetTranscriptTopicWithWordsPerSpeaker"]; type ErrorTopicWithWords = { error: Error; @@ -33,47 +32,40 @@ const useTopicWithWords = ( topicId: string | undefined, transcriptId: string, ): UseTopicWithWords => { - const [response, setResponse] = - useState(null); - const [loading, setLoading] = useState(false); - const [error, setErrorState] = useState(null); - const { setError } = useError(); - const api = useApi(); + const { + data: response, + isLoading: loading, + error, + refetch, + } = useTranscriptTopicsWithWordsPerSpeaker( + transcriptId || null, + topicId || null, + ); - const [count, setCount] = useState(0); + if (error) { + return { + error: error as Error, + loading: false, + response: null, + refetch, + } satisfies ErrorTopicWithWords & { refetch: () => void }; + } - const refetch = () => { - if (!loading) { - setCount(count + 1); - setLoading(true); - setErrorState(null); - } - }; + if (loading || !response) { + return { + response: response || null, + loading: true, + error: false, + refetch, + } satisfies LoadingTopicWithWords & { refetch: () => void }; + } - useEffect(() => { - if (!transcriptId || !topicId || !api) return; - - setLoading(true); - - api - .v1TranscriptGetTopicsWithWordsPerSpeaker({ transcriptId, topicId }) - .then((result) => { - setResponse(result); - setLoading(false); - console.debug("Topics with words Loaded:", result); - }) - .catch((error) => { - const shouldShowHuman = shouldShowError(error); - if (shouldShowHuman) { - setError(error, "There was an error loading the topics with words"); - } else { - setError(error); - } - setErrorState(error); - }); - }, [transcriptId, !api, topicId, count]); - - return { response, loading, error, refetch } as UseTopicWithWords; + return { + response, + loading: false, + error: null, + refetch, + } satisfies SuccessTopicWithWords & { refetch: () => void }; }; export default useTopicWithWords; diff --git a/www/app/(app)/transcripts/useTopics.ts b/www/app/(app)/transcripts/useTopics.ts index ff17beaf..7f337582 100644 --- a/www/app/(app)/transcripts/useTopics.ts +++ b/www/app/(app)/transcripts/useTopics.ts @@ -1,10 +1,7 @@ -import { useEffect, useState } from "react"; +import { useTranscriptTopics } from "../../lib/apiHooks"; +import type { components } from "../../reflector-api"; -import { useError } from "../../(errors)/errorContext"; -import { Topic } from "./webSocketTypes"; -import useApi from "../../lib/useApi"; -import { shouldShowError } from "../../lib/errorUtils"; -import { GetTranscriptTopic } from "../../api"; +type GetTranscriptTopic = components["schemas"]["GetTranscriptTopic"]; type TranscriptTopics = { topics: GetTranscriptTopic[] | null; @@ -13,34 +10,13 @@ type TranscriptTopics = { }; const useTopics = (id: string): TranscriptTopics => { - const [topics, setTopics] = useState(null); - const [loading, setLoading] = useState(true); - const [error, setErrorState] = useState(null); - const { setError } = useError(); - const api = useApi(); - useEffect(() => { - if (!id || !api) return; + const { data: topics, isLoading: loading, error } = useTranscriptTopics(id); - setLoading(true); - api - .v1TranscriptGetTopics({ transcriptId: id }) - .then((result) => { - setTopics(result); - setLoading(false); - console.debug("Transcript topics loaded:", result); - }) - .catch((err) => { - setErrorState(err); - const shouldShowHuman = shouldShowError(err); - if (shouldShowHuman) { - setError(err, "There was an error loading the topics"); - } else { - setError(err); - } - }); - }, [id, !api]); - - return { topics, loading, error }; + return { + topics: topics || null, + loading, + error: error as Error | null, + }; }; export default useTopics; diff --git a/www/app/(app)/transcripts/useTranscript.ts b/www/app/(app)/transcripts/useTranscript.ts deleted file mode 100644 index 49d257f0..00000000 --- a/www/app/(app)/transcripts/useTranscript.ts +++ /dev/null @@ -1,70 +0,0 @@ -import { useEffect, useState } from "react"; -import { GetTranscript } from "../../api"; -import { useError } from "../../(errors)/errorContext"; -import { shouldShowError } from "../../lib/errorUtils"; -import useApi from "../../lib/useApi"; - -type ErrorTranscript = { - error: Error; - loading: false; - response: null; - reload: () => void; -}; - -type LoadingTranscript = { - response: null; - loading: true; - error: false; - reload: () => void; -}; - -type SuccessTranscript = { - response: GetTranscript; - loading: false; - error: null; - reload: () => void; -}; - -const useTranscript = ( - id: string | null, -): ErrorTranscript | LoadingTranscript | SuccessTranscript => { - const [response, setResponse] = useState(null); - const [loading, setLoading] = useState(true); - const [error, setErrorState] = useState(null); - const [reload, setReload] = useState(0); - const { setError } = useError(); - const api = useApi(); - const reloadHandler = () => setReload((prev) => prev + 1); - - useEffect(() => { - if (!id || !api) return; - - if (!response) { - setLoading(true); - } - - api - .v1TranscriptGet({ transcriptId: id }) - .then((result) => { - setResponse(result); - setLoading(false); - console.debug("Transcript Loaded:", result); - }) - .catch((error) => { - const shouldShowHuman = shouldShowError(error); - if (shouldShowHuman) { - setError(error, "There was an error loading the transcript"); - } else { - setError(error); - } - setErrorState(error); - }); - }, [id, !api, reload]); - - return { response, loading, error, reload: reloadHandler } as - | ErrorTranscript - | LoadingTranscript - | SuccessTranscript; -}; - -export default useTranscript; diff --git a/www/app/(app)/transcripts/useWaveform.ts b/www/app/(app)/transcripts/useWaveform.ts index 19b2a265..8bb8c4c9 100644 --- a/www/app/(app)/transcripts/useWaveform.ts +++ b/www/app/(app)/transcripts/useWaveform.ts @@ -1,8 +1,7 @@ -import { useEffect, useState } from "react"; -import { AudioWaveform } from "../../api"; -import { useError } from "../../(errors)/errorContext"; -import useApi from "../../lib/useApi"; -import { shouldShowError } from "../../lib/errorUtils"; +import type { components } from "../../reflector-api"; +import { useTranscriptWaveform } from "../../lib/apiHooks"; + +type AudioWaveform = components["schemas"]["AudioWaveform"]; type AudioWaveFormResponse = { waveform: AudioWaveform | null; @@ -11,35 +10,17 @@ type AudioWaveFormResponse = { }; const useWaveform = (id: string, skip: boolean): AudioWaveFormResponse => { - const [waveform, setWaveform] = useState(null); - const [loading, setLoading] = useState(false); - const [error, setErrorState] = useState(null); - const { setError } = useError(); - const api = useApi(); + const { + data: waveform, + isLoading: loading, + error, + } = useTranscriptWaveform(skip ? null : id); - useEffect(() => { - if (!id || !api || skip) { - setLoading(false); - setErrorState(null); - setWaveform(null); - return; - } - setLoading(true); - setErrorState(null); - api - .v1TranscriptGetAudioWaveform({ transcriptId: id }) - .then((result) => { - setWaveform(result); - setLoading(false); - console.debug("Transcript waveform loaded:", result); - }) - .catch((err) => { - setErrorState(err); - setLoading(false); - }); - }, [id, api, skip]); - - return { waveform, loading, error }; + return { + waveform: waveform || null, + loading, + error: error as Error | null, + }; }; export default useWaveform; diff --git a/www/app/(app)/transcripts/useWebRTC.ts b/www/app/(app)/transcripts/useWebRTC.ts index c8370aa4..89a2a946 100644 --- a/www/app/(app)/transcripts/useWebRTC.ts +++ b/www/app/(app)/transcripts/useWebRTC.ts @@ -1,8 +1,9 @@ import { useEffect, useState } from "react"; import Peer from "simple-peer"; import { useError } from "../../(errors)/errorContext"; -import useApi from "../../lib/useApi"; -import { RtcOffer } from "../../api"; +import { useTranscriptWebRTC } from "../../lib/apiHooks"; +import type { components } from "../../reflector-api"; +type RtcOffer = components["schemas"]["RtcOffer"]; const useWebRTC = ( stream: MediaStream | null, @@ -10,10 +11,10 @@ const useWebRTC = ( ): Peer => { const [peer, setPeer] = useState(null); const { setError } = useError(); - const api = useApi(); + const { mutateAsync: mutateWebRtcTranscriptAsync } = useTranscriptWebRTC(); useEffect(() => { - if (!stream || !transcriptId || !api) { + if (!stream || !transcriptId) { return; } @@ -24,7 +25,7 @@ const useWebRTC = ( try { p = new Peer({ initiator: true, stream: stream }); } catch (error) { - setError(error, "Error creating WebRTC"); + setError(error as Error, "Error creating WebRTC"); return; } @@ -32,26 +33,31 @@ const useWebRTC = ( setError(new Error(`WebRTC error: ${err}`)); }); - p.on("signal", (data: any) => { - if (!api) return; + p.on("signal", async (data: any) => { if ("sdp" in data) { const rtcOffer: RtcOffer = { sdp: data.sdp, type: data.type, }; - api - .v1TranscriptRecordWebrtc({ transcriptId, requestBody: rtcOffer }) - .then((answer) => { - try { - p.signal(answer); - } catch (error) { - setError(error); - } - }) - .catch((error) => { - setError(error, "Error loading WebRTCOffer"); + try { + const answer = await mutateWebRtcTranscriptAsync({ + params: { + path: { + transcript_id: transcriptId, + }, + }, + body: rtcOffer, }); + + try { + p.signal(answer); + } catch (error) { + setError(error as Error); + } + } catch (error) { + setError(error as Error, "Error loading WebRTCOffer"); + } } }); @@ -63,7 +69,7 @@ const useWebRTC = ( return () => { p.destroy(); }; - }, [stream, transcriptId, !api]); + }, [stream, transcriptId, mutateWebRtcTranscriptAsync]); return peer; }; diff --git a/www/app/(app)/transcripts/useWebSockets.ts b/www/app/(app)/transcripts/useWebSockets.ts index 6fa5edc7..09426061 100644 --- a/www/app/(app)/transcripts/useWebSockets.ts +++ b/www/app/(app)/transcripts/useWebSockets.ts @@ -1,9 +1,12 @@ -import { useContext, useEffect, useState } from "react"; +import { useEffect, useState } from "react"; import { Topic, FinalSummary, Status } from "./webSocketTypes"; import { useError } from "../../(errors)/errorContext"; -import { DomainContext } from "../../domainContext"; -import { AudioWaveform, GetTranscriptSegmentTopic } from "../../api"; -import useApi from "../../lib/useApi"; +import type { components } from "../../reflector-api"; +type AudioWaveform = components["schemas"]["AudioWaveform"]; +type GetTranscriptSegmentTopic = + components["schemas"]["GetTranscriptSegmentTopic"]; +import { useQueryClient } from "@tanstack/react-query"; +import { $api, WEBSOCKET_URL } from "../../lib/apiClient"; export type UseWebSockets = { transcriptTextLive: string; @@ -12,7 +15,7 @@ export type UseWebSockets = { title: string; topics: Topic[]; finalSummary: FinalSummary; - status: Status; + status: Status | null; waveform: AudioWaveform | null; duration: number | null; }; @@ -30,11 +33,10 @@ export const useWebSockets = (transcriptId: string | null): UseWebSockets => { const [finalSummary, setFinalSummary] = useState({ summary: "", }); - const [status, setStatus] = useState({ value: "" }); + const [status, setStatus] = useState(null); const { setError } = useError(); - const { websocket_url } = useContext(DomainContext); - const api = useApi(); + const queryClient = useQueryClient(); const [accumulatedText, setAccumulatedText] = useState(""); @@ -105,6 +107,13 @@ export const useWebSockets = (transcriptId: string | null): UseWebSockets => { title: "Topic 1: Introduction to Quantum Mechanics", transcript: "A brief overview of quantum mechanics and its principles.", + segments: [ + { + speaker: 1, + start: 0, + text: "This is the transcription of an example title", + }, + ], }, { id: "2", @@ -315,11 +324,9 @@ export const useWebSockets = (transcriptId: string | null): UseWebSockets => { } }; - if (!transcriptId || !api) return; + if (!transcriptId) return; - api?.v1TranscriptGetWebsocketEvents({ transcriptId }).then((result) => {}); - - const url = `${websocket_url}/v1/transcripts/${transcriptId}/events`; + const url = `${WEBSOCKET_URL}/v1/transcripts/${transcriptId}/events`; let ws = new WebSocket(url); ws.onopen = () => { @@ -361,6 +368,16 @@ export const useWebSockets = (transcriptId: string | null): UseWebSockets => { return [...prevTopics, topic]; }); console.debug("TOPIC event:", message.data); + // Invalidate topics query to sync with WebSocket data + queryClient.invalidateQueries({ + queryKey: $api.queryOptions( + "get", + "/v1/transcripts/{transcript_id}/topics", + { + params: { path: { transcript_id: transcriptId } }, + }, + ).queryKey, + }); break; case "FINAL_SHORT_SUMMARY": @@ -370,6 +387,16 @@ export const useWebSockets = (transcriptId: string | null): UseWebSockets => { case "FINAL_LONG_SUMMARY": if (message.data) { setFinalSummary(message.data); + // Invalidate transcript query to sync summary + queryClient.invalidateQueries({ + queryKey: $api.queryOptions( + "get", + "/v1/transcripts/{transcript_id}", + { + params: { path: { transcript_id: transcriptId } }, + }, + ).queryKey, + }); } break; @@ -377,6 +404,16 @@ export const useWebSockets = (transcriptId: string | null): UseWebSockets => { console.debug("FINAL_TITLE event:", message.data); if (message.data) { setTitle(message.data.title); + // Invalidate transcript query to sync title + queryClient.invalidateQueries({ + queryKey: $api.queryOptions( + "get", + "/v1/transcripts/{transcript_id}", + { + params: { path: { transcript_id: transcriptId } }, + }, + ).queryKey, + }); } break; @@ -434,6 +471,11 @@ export const useWebSockets = (transcriptId: string | null): UseWebSockets => { break; case 1001: // Navigate away break; + case 1006: // Closed by client Chrome + console.warn( + "WebSocket closed by client, likely duplicated connection in react dev mode", + ); + break; default: setError( new Error(`WebSocket closed unexpectedly with code: ${event.code}`), @@ -450,7 +492,7 @@ export const useWebSockets = (transcriptId: string | null): UseWebSockets => { return () => { ws.close(); }; - }, [transcriptId, !api]); + }, [transcriptId]); return { transcriptTextLive, diff --git a/www/app/(app)/transcripts/webSocketTypes.ts b/www/app/(app)/transcripts/webSocketTypes.ts index edd35eb6..5422cc24 100644 --- a/www/app/(app)/transcripts/webSocketTypes.ts +++ b/www/app/(app)/transcripts/webSocketTypes.ts @@ -1,4 +1,7 @@ -import { GetTranscriptTopic } from "../../api"; +import type { components } from "../../reflector-api"; +import type { TranscriptStatus } from "../../lib/transcript"; + +type GetTranscriptTopic = components["schemas"]["GetTranscriptTopic"]; export type Topic = GetTranscriptTopic; @@ -11,7 +14,7 @@ export type FinalSummary = { }; export type Status = { - value: string; + value: TranscriptStatus; }; export type TranslatedTopic = { diff --git a/www/app/(auth)/userInfo.tsx b/www/app/(auth)/userInfo.tsx index ffb286b3..bf6a5b62 100644 --- a/www/app/(auth)/userInfo.tsx +++ b/www/app/(auth)/userInfo.tsx @@ -1,18 +1,21 @@ "use client"; -import { signOut, signIn } from "next-auth/react"; -import useSessionStatus from "../lib/useSessionStatus"; + import { Spinner, Link } from "@chakra-ui/react"; +import { useAuth } from "../lib/AuthProvider"; export default function UserInfo() { - const { isLoading, isAuthenticated } = useSessionStatus(); - + const auth = useAuth(); + const status = auth.status; + const isLoading = status === "loading"; + const isAuthenticated = status === "authenticated"; + const isRefreshing = status === "refreshing"; return isLoading ? ( - ) : !isAuthenticated ? ( + ) : !isAuthenticated && !isRefreshing ? ( signIn("authentik")} + onClick={() => auth.signIn("authentik")} > Log in @@ -20,7 +23,7 @@ export default function UserInfo() { signOut({ callbackUrl: "/" })} + onClick={() => auth.signOut({ callbackUrl: "/" })} > Log out diff --git a/www/app/[roomName]/page.tsx b/www/app/[roomName]/page.tsx index b9bb9a86..bd65c535 100644 --- a/www/app/[roomName]/page.tsx +++ b/www/app/[roomName]/page.tsx @@ -1,25 +1,28 @@ "use client"; -import { useCallback, useEffect, useState } from "react"; +import { useCallback, useEffect, useState, use } from "react"; import { Box, Spinner } from "@chakra-ui/react"; import useRoomMeeting from "./useRoomMeeting"; import { useRouter } from "next/navigation"; import { notFound } from "next/navigation"; -import useSessionStatus from "../lib/useSessionStatus"; +import { useAuth } from "../lib/AuthProvider"; import VideoPlatformEmbed from "../lib/videoPlatforms/VideoPlatformEmbed"; export type RoomDetails = { - params: { + params: Promise<{ roomName: string; - }; + }>; }; export default function Room(details: RoomDetails) { const [platformReady, setPlatformReady] = useState(false); - const roomName = details.params.roomName; + const params = use(details.params); + const roomName = params.roomName; const meeting = useRoomMeeting(roomName); const router = useRouter(); - const { isLoading, isAuthenticated } = useSessionStatus(); + const status = useAuth().status; + const isAuthenticated = status === "authenticated"; + const isLoading = status === "loading" || meeting.loading; const handleLeave = useCallback(() => { router.push("/browse"); diff --git a/www/app/[roomName]/useRoomMeeting.tsx b/www/app/[roomName]/useRoomMeeting.tsx index 98c2f1f2..93491a05 100644 --- a/www/app/[roomName]/useRoomMeeting.tsx +++ b/www/app/[roomName]/useRoomMeeting.tsx @@ -1,8 +1,10 @@ import { useEffect, useState } from "react"; import { useError } from "../(errors)/errorContext"; -import { Meeting } from "../api"; +import type { components } from "../reflector-api"; import { shouldShowError } from "../lib/errorUtils"; -import useApi from "../lib/useApi"; + +type Meeting = components["schemas"]["Meeting"]; +import { useRoomsCreateMeeting } from "../lib/apiHooks"; import { notFound } from "next/navigation"; type ErrorMeeting = { @@ -30,27 +32,25 @@ const useRoomMeeting = ( roomName: string | null | undefined, ): ErrorMeeting | LoadingMeeting | SuccessMeeting => { const [response, setResponse] = useState(null); - const [loading, setLoading] = useState(true); - const [error, setErrorState] = useState(null); const [reload, setReload] = useState(0); const { setError } = useError(); - const api = useApi(); + const createMeetingMutation = useRoomsCreateMeeting(); const reloadHandler = () => setReload((prev) => prev + 1); useEffect(() => { - if (!roomName || !api) return; + if (!roomName) return; - if (!response) { - setLoading(true); - } - - api - .v1RoomsCreateMeeting({ roomName }) - .then((result) => { + const createMeeting = async () => { + try { + const result = await createMeetingMutation.mutateAsync({ + params: { + path: { + room_name: roomName, + }, + }, + }); setResponse(result); - setLoading(false); - }) - .catch((error) => { + } catch (error: any) { const shouldShowHuman = shouldShowError(error); if (shouldShowHuman && error.status !== 404) { setError( @@ -60,9 +60,14 @@ const useRoomMeeting = ( } else { setError(error); } - setErrorState(error); - }); - }, [roomName, !api, reload]); + } + }; + + createMeeting(); + }, [roomName, reload]); + + const loading = createMeetingMutation.isPending && !response; + const error = createMeetingMutation.error as Error | null; return { response, loading, error, reload: reloadHandler } as | ErrorMeeting diff --git a/www/app/api/OpenApi.ts b/www/app/api/OpenApi.ts deleted file mode 100644 index 23cc35f3..00000000 --- a/www/app/api/OpenApi.ts +++ /dev/null @@ -1,37 +0,0 @@ -import type { BaseHttpRequest } from "./core/BaseHttpRequest"; -import type { OpenAPIConfig } from "./core/OpenAPI"; -import { Interceptors } from "./core/OpenAPI"; -import { AxiosHttpRequest } from "./core/AxiosHttpRequest"; - -import { DefaultService } from "./services.gen"; - -type HttpRequestConstructor = new (config: OpenAPIConfig) => BaseHttpRequest; - -export class OpenApi { - public readonly default: DefaultService; - - public readonly request: BaseHttpRequest; - - constructor( - config?: Partial, - HttpRequest: HttpRequestConstructor = AxiosHttpRequest, - ) { - this.request = new HttpRequest({ - BASE: config?.BASE ?? "", - VERSION: config?.VERSION ?? "0.1.0", - WITH_CREDENTIALS: config?.WITH_CREDENTIALS ?? false, - CREDENTIALS: config?.CREDENTIALS ?? "include", - TOKEN: config?.TOKEN, - USERNAME: config?.USERNAME, - PASSWORD: config?.PASSWORD, - HEADERS: config?.HEADERS, - ENCODE_PATH: config?.ENCODE_PATH, - interceptors: { - request: config?.interceptors?.request ?? new Interceptors(), - response: config?.interceptors?.response ?? new Interceptors(), - }, - }); - - this.default = new DefaultService(this.request); - } -} diff --git a/www/app/api/auth/[...nextauth]/route.ts b/www/app/api/auth/[...nextauth]/route.ts index 915ed04d..250e9e34 100644 --- a/www/app/api/auth/[...nextauth]/route.ts +++ b/www/app/api/auth/[...nextauth]/route.ts @@ -1,9 +1,6 @@ -// NextAuth route handler for Authentik -// Refresh rotation has been taken from https://next-auth.js.org/v3/tutorials/refresh-token-rotation even if we are using 4.x - import NextAuth from "next-auth"; -import { authOptions } from "../../../lib/auth"; +import { authOptions } from "../../../lib/authBackend"; -const handler = NextAuth(authOptions); +const handler = NextAuth(authOptions()); export { handler as GET, handler as POST }; diff --git a/www/app/api/core/ApiError.ts b/www/app/api/core/ApiError.ts deleted file mode 100644 index 1d07bb31..00000000 --- a/www/app/api/core/ApiError.ts +++ /dev/null @@ -1,25 +0,0 @@ -import type { ApiRequestOptions } from "./ApiRequestOptions"; -import type { ApiResult } from "./ApiResult"; - -export class ApiError extends Error { - public readonly url: string; - public readonly status: number; - public readonly statusText: string; - public readonly body: unknown; - public readonly request: ApiRequestOptions; - - constructor( - request: ApiRequestOptions, - response: ApiResult, - message: string, - ) { - super(message); - - this.name = "ApiError"; - this.url = response.url; - this.status = response.status; - this.statusText = response.statusText; - this.body = response.body; - this.request = request; - } -} diff --git a/www/app/api/core/ApiRequestOptions.ts b/www/app/api/core/ApiRequestOptions.ts deleted file mode 100644 index 57fbb095..00000000 --- a/www/app/api/core/ApiRequestOptions.ts +++ /dev/null @@ -1,21 +0,0 @@ -export type ApiRequestOptions = { - readonly method: - | "GET" - | "PUT" - | "POST" - | "DELETE" - | "OPTIONS" - | "HEAD" - | "PATCH"; - readonly url: string; - readonly path?: Record; - readonly cookies?: Record; - readonly headers?: Record; - readonly query?: Record; - readonly formData?: Record; - readonly body?: any; - readonly mediaType?: string; - readonly responseHeader?: string; - readonly responseTransformer?: (data: unknown) => Promise; - readonly errors?: Record; -}; diff --git a/www/app/api/core/ApiResult.ts b/www/app/api/core/ApiResult.ts deleted file mode 100644 index 05040ba8..00000000 --- a/www/app/api/core/ApiResult.ts +++ /dev/null @@ -1,7 +0,0 @@ -export type ApiResult = { - readonly body: TData; - readonly ok: boolean; - readonly status: number; - readonly statusText: string; - readonly url: string; -}; diff --git a/www/app/api/core/AxiosHttpRequest.ts b/www/app/api/core/AxiosHttpRequest.ts deleted file mode 100644 index aba5096e..00000000 --- a/www/app/api/core/AxiosHttpRequest.ts +++ /dev/null @@ -1,23 +0,0 @@ -import type { ApiRequestOptions } from "./ApiRequestOptions"; -import { BaseHttpRequest } from "./BaseHttpRequest"; -import type { CancelablePromise } from "./CancelablePromise"; -import type { OpenAPIConfig } from "./OpenAPI"; -import { request as __request } from "./request"; - -export class AxiosHttpRequest extends BaseHttpRequest { - constructor(config: OpenAPIConfig) { - super(config); - } - - /** - * Request method - * @param options The request options from the service - * @returns CancelablePromise - * @throws ApiError - */ - public override request( - options: ApiRequestOptions, - ): CancelablePromise { - return __request(this.config, options); - } -} diff --git a/www/app/api/core/BaseHttpRequest.ts b/www/app/api/core/BaseHttpRequest.ts deleted file mode 100644 index 3f89861c..00000000 --- a/www/app/api/core/BaseHttpRequest.ts +++ /dev/null @@ -1,11 +0,0 @@ -import type { ApiRequestOptions } from "./ApiRequestOptions"; -import type { CancelablePromise } from "./CancelablePromise"; -import type { OpenAPIConfig } from "./OpenAPI"; - -export abstract class BaseHttpRequest { - constructor(public readonly config: OpenAPIConfig) {} - - public abstract request( - options: ApiRequestOptions, - ): CancelablePromise; -} diff --git a/www/app/api/core/CancelablePromise.ts b/www/app/api/core/CancelablePromise.ts deleted file mode 100644 index 0640e989..00000000 --- a/www/app/api/core/CancelablePromise.ts +++ /dev/null @@ -1,126 +0,0 @@ -export class CancelError extends Error { - constructor(message: string) { - super(message); - this.name = "CancelError"; - } - - public get isCancelled(): boolean { - return true; - } -} - -export interface OnCancel { - readonly isResolved: boolean; - readonly isRejected: boolean; - readonly isCancelled: boolean; - - (cancelHandler: () => void): void; -} - -export class CancelablePromise implements Promise { - private _isResolved: boolean; - private _isRejected: boolean; - private _isCancelled: boolean; - readonly cancelHandlers: (() => void)[]; - readonly promise: Promise; - private _resolve?: (value: T | PromiseLike) => void; - private _reject?: (reason?: unknown) => void; - - constructor( - executor: ( - resolve: (value: T | PromiseLike) => void, - reject: (reason?: unknown) => void, - onCancel: OnCancel, - ) => void, - ) { - this._isResolved = false; - this._isRejected = false; - this._isCancelled = false; - this.cancelHandlers = []; - this.promise = new Promise((resolve, reject) => { - this._resolve = resolve; - this._reject = reject; - - const onResolve = (value: T | PromiseLike): void => { - if (this._isResolved || this._isRejected || this._isCancelled) { - return; - } - this._isResolved = true; - if (this._resolve) this._resolve(value); - }; - - const onReject = (reason?: unknown): void => { - if (this._isResolved || this._isRejected || this._isCancelled) { - return; - } - this._isRejected = true; - if (this._reject) this._reject(reason); - }; - - const onCancel = (cancelHandler: () => void): void => { - if (this._isResolved || this._isRejected || this._isCancelled) { - return; - } - this.cancelHandlers.push(cancelHandler); - }; - - Object.defineProperty(onCancel, "isResolved", { - get: (): boolean => this._isResolved, - }); - - Object.defineProperty(onCancel, "isRejected", { - get: (): boolean => this._isRejected, - }); - - Object.defineProperty(onCancel, "isCancelled", { - get: (): boolean => this._isCancelled, - }); - - return executor(onResolve, onReject, onCancel as OnCancel); - }); - } - - get [Symbol.toStringTag]() { - return "Cancellable Promise"; - } - - public then( - onFulfilled?: ((value: T) => TResult1 | PromiseLike) | null, - onRejected?: ((reason: unknown) => TResult2 | PromiseLike) | null, - ): Promise { - return this.promise.then(onFulfilled, onRejected); - } - - public catch( - onRejected?: ((reason: unknown) => TResult | PromiseLike) | null, - ): Promise { - return this.promise.catch(onRejected); - } - - public finally(onFinally?: (() => void) | null): Promise { - return this.promise.finally(onFinally); - } - - public cancel(): void { - if (this._isResolved || this._isRejected || this._isCancelled) { - return; - } - this._isCancelled = true; - if (this.cancelHandlers.length) { - try { - for (const cancelHandler of this.cancelHandlers) { - cancelHandler(); - } - } catch (error) { - console.warn("Cancellation threw an error", error); - return; - } - } - this.cancelHandlers.length = 0; - if (this._reject) this._reject(new CancelError("Request aborted")); - } - - public get isCancelled(): boolean { - return this._isCancelled; - } -} diff --git a/www/app/api/core/OpenAPI.ts b/www/app/api/core/OpenAPI.ts deleted file mode 100644 index 20ea0ed9..00000000 --- a/www/app/api/core/OpenAPI.ts +++ /dev/null @@ -1,57 +0,0 @@ -import type { AxiosRequestConfig, AxiosResponse } from "axios"; -import type { ApiRequestOptions } from "./ApiRequestOptions"; - -type Headers = Record; -type Middleware = (value: T) => T | Promise; -type Resolver = (options: ApiRequestOptions) => Promise; - -export class Interceptors { - _fns: Middleware[]; - - constructor() { - this._fns = []; - } - - eject(fn: Middleware): void { - const index = this._fns.indexOf(fn); - if (index !== -1) { - this._fns = [...this._fns.slice(0, index), ...this._fns.slice(index + 1)]; - } - } - - use(fn: Middleware): void { - this._fns = [...this._fns, fn]; - } -} - -export type OpenAPIConfig = { - BASE: string; - CREDENTIALS: "include" | "omit" | "same-origin"; - ENCODE_PATH?: ((path: string) => string) | undefined; - HEADERS?: Headers | Resolver | undefined; - PASSWORD?: string | Resolver | undefined; - TOKEN?: string | Resolver | undefined; - USERNAME?: string | Resolver | undefined; - VERSION: string; - WITH_CREDENTIALS: boolean; - interceptors: { - request: Interceptors; - response: Interceptors; - }; -}; - -export const OpenAPI: OpenAPIConfig = { - BASE: "", - CREDENTIALS: "include", - ENCODE_PATH: undefined, - HEADERS: undefined, - PASSWORD: undefined, - TOKEN: undefined, - USERNAME: undefined, - VERSION: "0.1.0", - WITH_CREDENTIALS: false, - interceptors: { - request: new Interceptors(), - response: new Interceptors(), - }, -}; diff --git a/www/app/api/core/request.ts b/www/app/api/core/request.ts deleted file mode 100644 index b576207e..00000000 --- a/www/app/api/core/request.ts +++ /dev/null @@ -1,387 +0,0 @@ -import axios from "axios"; -import type { - AxiosError, - AxiosRequestConfig, - AxiosResponse, - AxiosInstance, -} from "axios"; - -import { ApiError } from "./ApiError"; -import type { ApiRequestOptions } from "./ApiRequestOptions"; -import type { ApiResult } from "./ApiResult"; -import { CancelablePromise } from "./CancelablePromise"; -import type { OnCancel } from "./CancelablePromise"; -import type { OpenAPIConfig } from "./OpenAPI"; - -export const isString = (value: unknown): value is string => { - return typeof value === "string"; -}; - -export const isStringWithValue = (value: unknown): value is string => { - return isString(value) && value !== ""; -}; - -export const isBlob = (value: any): value is Blob => { - return value instanceof Blob; -}; - -export const isFormData = (value: unknown): value is FormData => { - return value instanceof FormData; -}; - -export const isSuccess = (status: number): boolean => { - return status >= 200 && status < 300; -}; - -export const base64 = (str: string): string => { - try { - return btoa(str); - } catch (err) { - // @ts-ignore - return Buffer.from(str).toString("base64"); - } -}; - -export const getQueryString = (params: Record): string => { - const qs: string[] = []; - - const append = (key: string, value: unknown) => { - qs.push(`${encodeURIComponent(key)}=${encodeURIComponent(String(value))}`); - }; - - const encodePair = (key: string, value: unknown) => { - if (value === undefined || value === null) { - return; - } - - if (value instanceof Date) { - append(key, value.toISOString()); - } else if (Array.isArray(value)) { - value.forEach((v) => encodePair(key, v)); - } else if (typeof value === "object") { - Object.entries(value).forEach(([k, v]) => encodePair(`${key}[${k}]`, v)); - } else { - append(key, value); - } - }; - - Object.entries(params).forEach(([key, value]) => encodePair(key, value)); - - return qs.length ? `?${qs.join("&")}` : ""; -}; - -const getUrl = (config: OpenAPIConfig, options: ApiRequestOptions): string => { - const encoder = config.ENCODE_PATH || encodeURI; - - const path = options.url - .replace("{api-version}", config.VERSION) - .replace(/{(.*?)}/g, (substring: string, group: string) => { - if (options.path?.hasOwnProperty(group)) { - return encoder(String(options.path[group])); - } - return substring; - }); - - const url = config.BASE + path; - return options.query ? url + getQueryString(options.query) : url; -}; - -export const getFormData = ( - options: ApiRequestOptions, -): FormData | undefined => { - if (options.formData) { - const formData = new FormData(); - - const process = (key: string, value: unknown) => { - if (isString(value) || isBlob(value)) { - formData.append(key, value); - } else { - formData.append(key, JSON.stringify(value)); - } - }; - - Object.entries(options.formData) - .filter(([, value]) => value !== undefined && value !== null) - .forEach(([key, value]) => { - if (Array.isArray(value)) { - value.forEach((v) => process(key, v)); - } else { - process(key, value); - } - }); - - return formData; - } - return undefined; -}; - -type Resolver = (options: ApiRequestOptions) => Promise; - -export const resolve = async ( - options: ApiRequestOptions, - resolver?: T | Resolver, -): Promise => { - if (typeof resolver === "function") { - return (resolver as Resolver)(options); - } - return resolver; -}; - -export const getHeaders = async ( - config: OpenAPIConfig, - options: ApiRequestOptions, -): Promise> => { - const [token, username, password, additionalHeaders] = await Promise.all([ - // @ts-ignore - resolve(options, config.TOKEN), - // @ts-ignore - resolve(options, config.USERNAME), - // @ts-ignore - resolve(options, config.PASSWORD), - // @ts-ignore - resolve(options, config.HEADERS), - ]); - - const headers = Object.entries({ - Accept: "application/json", - ...additionalHeaders, - ...options.headers, - }) - .filter(([, value]) => value !== undefined && value !== null) - .reduce( - (headers, [key, value]) => ({ - ...headers, - [key]: String(value), - }), - {} as Record, - ); - - if (isStringWithValue(token)) { - headers["Authorization"] = `Bearer ${token}`; - } - - if (isStringWithValue(username) && isStringWithValue(password)) { - const credentials = base64(`${username}:${password}`); - headers["Authorization"] = `Basic ${credentials}`; - } - - if (options.body !== undefined) { - if (options.mediaType) { - headers["Content-Type"] = options.mediaType; - } else if (isBlob(options.body)) { - headers["Content-Type"] = options.body.type || "application/octet-stream"; - } else if (isString(options.body)) { - headers["Content-Type"] = "text/plain"; - } else if (!isFormData(options.body)) { - headers["Content-Type"] = "application/json"; - } - } else if (options.formData !== undefined) { - if (options.mediaType) { - headers["Content-Type"] = options.mediaType; - } - } - - return headers; -}; - -export const getRequestBody = (options: ApiRequestOptions): unknown => { - if (options.body) { - return options.body; - } - return undefined; -}; - -export const sendRequest = async ( - config: OpenAPIConfig, - options: ApiRequestOptions, - url: string, - body: unknown, - formData: FormData | undefined, - headers: Record, - onCancel: OnCancel, - axiosClient: AxiosInstance, -): Promise> => { - const controller = new AbortController(); - - let requestConfig: AxiosRequestConfig = { - data: body ?? formData, - headers, - method: options.method, - signal: controller.signal, - url, - withCredentials: config.WITH_CREDENTIALS, - }; - - onCancel(() => controller.abort()); - - for (const fn of config.interceptors.request._fns) { - requestConfig = await fn(requestConfig); - } - - try { - return await axiosClient.request(requestConfig); - } catch (error) { - const axiosError = error as AxiosError; - if (axiosError.response) { - return axiosError.response; - } - throw error; - } -}; - -export const getResponseHeader = ( - response: AxiosResponse, - responseHeader?: string, -): string | undefined => { - if (responseHeader) { - const content = response.headers[responseHeader]; - if (isString(content)) { - return content; - } - } - return undefined; -}; - -export const getResponseBody = (response: AxiosResponse): unknown => { - if (response.status !== 204) { - return response.data; - } - return undefined; -}; - -export const catchErrorCodes = ( - options: ApiRequestOptions, - result: ApiResult, -): void => { - const errors: Record = { - 400: "Bad Request", - 401: "Unauthorized", - 402: "Payment Required", - 403: "Forbidden", - 404: "Not Found", - 405: "Method Not Allowed", - 406: "Not Acceptable", - 407: "Proxy Authentication Required", - 408: "Request Timeout", - 409: "Conflict", - 410: "Gone", - 411: "Length Required", - 412: "Precondition Failed", - 413: "Payload Too Large", - 414: "URI Too Long", - 415: "Unsupported Media Type", - 416: "Range Not Satisfiable", - 417: "Expectation Failed", - 418: "Im a teapot", - 421: "Misdirected Request", - 422: "Unprocessable Content", - 423: "Locked", - 424: "Failed Dependency", - 425: "Too Early", - 426: "Upgrade Required", - 428: "Precondition Required", - 429: "Too Many Requests", - 431: "Request Header Fields Too Large", - 451: "Unavailable For Legal Reasons", - 500: "Internal Server Error", - 501: "Not Implemented", - 502: "Bad Gateway", - 503: "Service Unavailable", - 504: "Gateway Timeout", - 505: "HTTP Version Not Supported", - 506: "Variant Also Negotiates", - 507: "Insufficient Storage", - 508: "Loop Detected", - 510: "Not Extended", - 511: "Network Authentication Required", - ...options.errors, - }; - - const error = errors[result.status]; - if (error) { - throw new ApiError(options, result, error); - } - - if (!result.ok) { - const errorStatus = result.status ?? "unknown"; - const errorStatusText = result.statusText ?? "unknown"; - const errorBody = (() => { - try { - return JSON.stringify(result.body, null, 2); - } catch (e) { - return undefined; - } - })(); - - throw new ApiError( - options, - result, - `Generic Error: status: ${errorStatus}; status text: ${errorStatusText}; body: ${errorBody}`, - ); - } -}; - -/** - * Request method - * @param config The OpenAPI configuration object - * @param options The request options from the service - * @param axiosClient The axios client instance to use - * @returns CancelablePromise - * @throws ApiError - */ -export const request = ( - config: OpenAPIConfig, - options: ApiRequestOptions, - axiosClient: AxiosInstance = axios, -): CancelablePromise => { - return new CancelablePromise(async (resolve, reject, onCancel) => { - try { - const url = getUrl(config, options); - const formData = getFormData(options); - const body = getRequestBody(options); - const headers = await getHeaders(config, options); - - if (!onCancel.isCancelled) { - let response = await sendRequest( - config, - options, - url, - body, - formData, - headers, - onCancel, - axiosClient, - ); - - for (const fn of config.interceptors.response._fns) { - response = await fn(response); - } - - const responseBody = getResponseBody(response); - const responseHeader = getResponseHeader( - response, - options.responseHeader, - ); - - let transformedBody = responseBody; - if (options.responseTransformer && isSuccess(response.status)) { - transformedBody = await options.responseTransformer(responseBody); - } - - const result: ApiResult = { - url, - ok: isSuccess(response.status), - status: response.status, - statusText: response.statusText, - body: responseHeader ?? transformedBody, - }; - - catchErrorCodes(options, result); - - resolve(result.body); - } - } catch (error) { - reject(error); - } - }); -}; diff --git a/www/app/api/index.ts b/www/app/api/index.ts deleted file mode 100644 index 27fbb57d..00000000 --- a/www/app/api/index.ts +++ /dev/null @@ -1,9 +0,0 @@ -// This file is auto-generated by @hey-api/openapi-ts -export { OpenApi } from "./OpenApi"; -export { ApiError } from "./core/ApiError"; -export { BaseHttpRequest } from "./core/BaseHttpRequest"; -export { CancelablePromise, CancelError } from "./core/CancelablePromise"; -export { OpenAPI, type OpenAPIConfig } from "./core/OpenAPI"; -export * from "./schemas.gen"; -export * from "./services.gen"; -export * from "./types.gen"; diff --git a/www/app/api/schemas.gen.ts b/www/app/api/schemas.gen.ts index 17f18f2d..e69de29b 100644 --- a/www/app/api/schemas.gen.ts +++ b/www/app/api/schemas.gen.ts @@ -1,1840 +0,0 @@ -// This file is auto-generated by @hey-api/openapi-ts - -export const $AudioWaveform = { - properties: { - data: { - items: { - type: "number", - }, - type: "array", - title: "Data", - }, - }, - type: "object", - required: ["data"], - title: "AudioWaveform", -} as const; - -export const $Body_transcript_record_upload_v1_transcripts__transcript_id__record_upload_post = - { - properties: { - chunk: { - type: "string", - format: "binary", - title: "Chunk", - }, - }, - type: "object", - required: ["chunk"], - title: - "Body_transcript_record_upload_v1_transcripts__transcript_id__record_upload_post", - } as const; - -export const $CreateParticipant = { - properties: { - speaker: { - anyOf: [ - { - type: "integer", - }, - { - type: "null", - }, - ], - title: "Speaker", - }, - name: { - type: "string", - title: "Name", - }, - }, - type: "object", - required: ["name"], - title: "CreateParticipant", -} as const; - -export const $CreateRoom = { - properties: { - name: { - type: "string", - title: "Name", - }, - zulip_auto_post: { - type: "boolean", - title: "Zulip Auto Post", - }, - zulip_stream: { - type: "string", - title: "Zulip Stream", - }, - zulip_topic: { - type: "string", - title: "Zulip Topic", - }, - is_locked: { - type: "boolean", - title: "Is Locked", - }, - room_mode: { - type: "string", - title: "Room Mode", - }, - recording_type: { - type: "string", - title: "Recording Type", - }, - recording_trigger: { - type: "string", - title: "Recording Trigger", - }, - is_shared: { - type: "boolean", - title: "Is Shared", - }, - webhook_url: { - type: "string", - title: "Webhook Url", - }, - webhook_secret: { - type: "string", - title: "Webhook Secret", - }, - platform: { - $ref: "#/components/schemas/VideoPlatform", - }, - }, - type: "object", - required: [ - "name", - "zulip_auto_post", - "zulip_stream", - "zulip_topic", - "is_locked", - "room_mode", - "recording_type", - "recording_trigger", - "is_shared", - "webhook_url", - "webhook_secret", - "platform", - ], - title: "CreateRoom", -} as const; - -export const $CreateTranscript = { - properties: { - name: { - type: "string", - title: "Name", - }, - source_language: { - type: "string", - title: "Source Language", - default: "en", - }, - target_language: { - type: "string", - title: "Target Language", - default: "en", - }, - }, - type: "object", - required: ["name"], - title: "CreateTranscript", -} as const; - -export const $DeletionStatus = { - properties: { - status: { - type: "string", - title: "Status", - }, - }, - type: "object", - required: ["status"], - title: "DeletionStatus", -} as const; - -export const $GetTranscript = { - properties: { - id: { - type: "string", - title: "Id", - }, - user_id: { - anyOf: [ - { - type: "string", - }, - { - type: "null", - }, - ], - title: "User Id", - }, - name: { - type: "string", - title: "Name", - }, - status: { - type: "string", - title: "Status", - }, - locked: { - type: "boolean", - title: "Locked", - }, - duration: { - type: "number", - title: "Duration", - }, - title: { - anyOf: [ - { - type: "string", - }, - { - type: "null", - }, - ], - title: "Title", - }, - short_summary: { - anyOf: [ - { - type: "string", - }, - { - type: "null", - }, - ], - title: "Short Summary", - }, - long_summary: { - anyOf: [ - { - type: "string", - }, - { - type: "null", - }, - ], - title: "Long Summary", - }, - created_at: { - type: "string", - title: "Created At", - }, - share_mode: { - type: "string", - title: "Share Mode", - default: "private", - }, - source_language: { - anyOf: [ - { - type: "string", - }, - { - type: "null", - }, - ], - title: "Source Language", - }, - target_language: { - anyOf: [ - { - type: "string", - }, - { - type: "null", - }, - ], - title: "Target Language", - }, - reviewed: { - type: "boolean", - title: "Reviewed", - }, - meeting_id: { - anyOf: [ - { - type: "string", - }, - { - type: "null", - }, - ], - title: "Meeting Id", - }, - source_kind: { - $ref: "#/components/schemas/SourceKind", - }, - room_id: { - anyOf: [ - { - type: "string", - }, - { - type: "null", - }, - ], - title: "Room Id", - }, - room_name: { - anyOf: [ - { - type: "string", - }, - { - type: "null", - }, - ], - title: "Room Name", - }, - audio_deleted: { - anyOf: [ - { - type: "boolean", - }, - { - type: "null", - }, - ], - title: "Audio Deleted", - }, - participants: { - anyOf: [ - { - items: { - $ref: "#/components/schemas/TranscriptParticipant", - }, - type: "array", - }, - { - type: "null", - }, - ], - title: "Participants", - }, - }, - type: "object", - required: [ - "id", - "user_id", - "name", - "status", - "locked", - "duration", - "title", - "short_summary", - "long_summary", - "created_at", - "source_language", - "target_language", - "reviewed", - "meeting_id", - "source_kind", - "participants", - ], - title: "GetTranscript", -} as const; - -export const $GetTranscriptMinimal = { - properties: { - id: { - type: "string", - title: "Id", - }, - user_id: { - anyOf: [ - { - type: "string", - }, - { - type: "null", - }, - ], - title: "User Id", - }, - name: { - type: "string", - title: "Name", - }, - status: { - type: "string", - title: "Status", - }, - locked: { - type: "boolean", - title: "Locked", - }, - duration: { - type: "number", - title: "Duration", - }, - title: { - anyOf: [ - { - type: "string", - }, - { - type: "null", - }, - ], - title: "Title", - }, - short_summary: { - anyOf: [ - { - type: "string", - }, - { - type: "null", - }, - ], - title: "Short Summary", - }, - long_summary: { - anyOf: [ - { - type: "string", - }, - { - type: "null", - }, - ], - title: "Long Summary", - }, - created_at: { - type: "string", - title: "Created At", - }, - share_mode: { - type: "string", - title: "Share Mode", - default: "private", - }, - source_language: { - anyOf: [ - { - type: "string", - }, - { - type: "null", - }, - ], - title: "Source Language", - }, - target_language: { - anyOf: [ - { - type: "string", - }, - { - type: "null", - }, - ], - title: "Target Language", - }, - reviewed: { - type: "boolean", - title: "Reviewed", - }, - meeting_id: { - anyOf: [ - { - type: "string", - }, - { - type: "null", - }, - ], - title: "Meeting Id", - }, - source_kind: { - $ref: "#/components/schemas/SourceKind", - }, - room_id: { - anyOf: [ - { - type: "string", - }, - { - type: "null", - }, - ], - title: "Room Id", - }, - room_name: { - anyOf: [ - { - type: "string", - }, - { - type: "null", - }, - ], - title: "Room Name", - }, - audio_deleted: { - anyOf: [ - { - type: "boolean", - }, - { - type: "null", - }, - ], - title: "Audio Deleted", - }, - }, - type: "object", - required: [ - "id", - "user_id", - "name", - "status", - "locked", - "duration", - "title", - "short_summary", - "long_summary", - "created_at", - "source_language", - "target_language", - "reviewed", - "meeting_id", - "source_kind", - ], - title: "GetTranscriptMinimal", -} as const; - -export const $GetTranscriptSegmentTopic = { - properties: { - text: { - type: "string", - title: "Text", - }, - start: { - type: "number", - title: "Start", - }, - speaker: { - type: "integer", - title: "Speaker", - }, - }, - type: "object", - required: ["text", "start", "speaker"], - title: "GetTranscriptSegmentTopic", -} as const; - -export const $GetTranscriptTopic = { - properties: { - id: { - type: "string", - title: "Id", - }, - title: { - type: "string", - title: "Title", - }, - summary: { - type: "string", - title: "Summary", - }, - timestamp: { - type: "number", - title: "Timestamp", - }, - duration: { - anyOf: [ - { - type: "number", - }, - { - type: "null", - }, - ], - title: "Duration", - }, - transcript: { - type: "string", - title: "Transcript", - }, - segments: { - items: { - $ref: "#/components/schemas/GetTranscriptSegmentTopic", - }, - type: "array", - title: "Segments", - default: [], - }, - }, - type: "object", - required: ["id", "title", "summary", "timestamp", "duration", "transcript"], - title: "GetTranscriptTopic", -} as const; - -export const $GetTranscriptTopicWithWords = { - properties: { - id: { - type: "string", - title: "Id", - }, - title: { - type: "string", - title: "Title", - }, - summary: { - type: "string", - title: "Summary", - }, - timestamp: { - type: "number", - title: "Timestamp", - }, - duration: { - anyOf: [ - { - type: "number", - }, - { - type: "null", - }, - ], - title: "Duration", - }, - transcript: { - type: "string", - title: "Transcript", - }, - segments: { - items: { - $ref: "#/components/schemas/GetTranscriptSegmentTopic", - }, - type: "array", - title: "Segments", - default: [], - }, - words: { - items: { - $ref: "#/components/schemas/Word", - }, - type: "array", - title: "Words", - default: [], - }, - }, - type: "object", - required: ["id", "title", "summary", "timestamp", "duration", "transcript"], - title: "GetTranscriptTopicWithWords", -} as const; - -export const $GetTranscriptTopicWithWordsPerSpeaker = { - properties: { - id: { - type: "string", - title: "Id", - }, - title: { - type: "string", - title: "Title", - }, - summary: { - type: "string", - title: "Summary", - }, - timestamp: { - type: "number", - title: "Timestamp", - }, - duration: { - anyOf: [ - { - type: "number", - }, - { - type: "null", - }, - ], - title: "Duration", - }, - transcript: { - type: "string", - title: "Transcript", - }, - segments: { - items: { - $ref: "#/components/schemas/GetTranscriptSegmentTopic", - }, - type: "array", - title: "Segments", - default: [], - }, - words_per_speaker: { - items: { - $ref: "#/components/schemas/SpeakerWords", - }, - type: "array", - title: "Words Per Speaker", - default: [], - }, - }, - type: "object", - required: ["id", "title", "summary", "timestamp", "duration", "transcript"], - title: "GetTranscriptTopicWithWordsPerSpeaker", -} as const; - -export const $HTTPValidationError = { - properties: { - detail: { - items: { - $ref: "#/components/schemas/ValidationError", - }, - type: "array", - title: "Detail", - }, - }, - type: "object", - title: "HTTPValidationError", -} as const; - -export const $JibriRecordingEvent = { - properties: { - room_name: { - type: "string", - title: "Room Name", - }, - recording_file: { - type: "string", - title: "Recording File", - }, - recording_status: { - type: "string", - title: "Recording Status", - }, - timestamp: { - type: "string", - format: "date-time", - title: "Timestamp", - }, - }, - type: "object", - required: ["room_name", "recording_file", "recording_status", "timestamp"], - title: "JibriRecordingEvent", -} as const; - -export const $JitsiWebhookEvent = { - properties: { - event: { - type: "string", - title: "Event", - }, - room: { - type: "string", - title: "Room", - }, - timestamp: { - type: "string", - format: "date-time", - title: "Timestamp", - }, - data: { - additionalProperties: true, - type: "object", - title: "Data", - default: {}, - }, - }, - type: "object", - required: ["event", "room", "timestamp"], - title: "JitsiWebhookEvent", -} as const; - -export const $Meeting = { - properties: { - id: { - type: "string", - title: "Id", - }, - room_name: { - type: "string", - title: "Room Name", - }, - room_url: { - type: "string", - title: "Room Url", - }, - host_room_url: { - type: "string", - title: "Host Room Url", - }, - start_date: { - type: "string", - format: "date-time", - title: "Start Date", - }, - end_date: { - type: "string", - format: "date-time", - title: "End Date", - }, - recording_type: { - type: "string", - enum: ["none", "local", "cloud"], - title: "Recording Type", - default: "cloud", - }, - }, - type: "object", - required: [ - "id", - "room_name", - "room_url", - "host_room_url", - "start_date", - "end_date", - ], - title: "Meeting", -} as const; - -export const $MeetingConsentRequest = { - properties: { - consent_given: { - type: "boolean", - title: "Consent Given", - }, - }, - type: "object", - required: ["consent_given"], - title: "MeetingConsentRequest", -} as const; - -export const $Page_GetTranscriptMinimal_ = { - properties: { - items: { - items: { - $ref: "#/components/schemas/GetTranscriptMinimal", - }, - type: "array", - title: "Items", - }, - total: { - anyOf: [ - { - type: "integer", - minimum: 0, - }, - { - type: "null", - }, - ], - title: "Total", - }, - page: { - anyOf: [ - { - type: "integer", - minimum: 1, - }, - { - type: "null", - }, - ], - title: "Page", - }, - size: { - anyOf: [ - { - type: "integer", - minimum: 1, - }, - { - type: "null", - }, - ], - title: "Size", - }, - pages: { - anyOf: [ - { - type: "integer", - minimum: 0, - }, - { - type: "null", - }, - ], - title: "Pages", - }, - }, - type: "object", - required: ["items", "page", "size"], - title: "Page[GetTranscriptMinimal]", -} as const; - -export const $Page_RoomDetails_ = { - properties: { - items: { - items: { - $ref: "#/components/schemas/RoomDetails", - }, - type: "array", - title: "Items", - }, - total: { - anyOf: [ - { - type: "integer", - minimum: 0, - }, - { - type: "null", - }, - ], - title: "Total", - }, - page: { - anyOf: [ - { - type: "integer", - minimum: 1, - }, - { - type: "null", - }, - ], - title: "Page", - }, - size: { - anyOf: [ - { - type: "integer", - minimum: 1, - }, - { - type: "null", - }, - ], - title: "Size", - }, - pages: { - anyOf: [ - { - type: "integer", - minimum: 0, - }, - { - type: "null", - }, - ], - title: "Pages", - }, - }, - type: "object", - required: ["items", "page", "size"], - title: "Page[RoomDetails]", -} as const; - -export const $Participant = { - properties: { - id: { - type: "string", - title: "Id", - }, - speaker: { - anyOf: [ - { - type: "integer", - }, - { - type: "null", - }, - ], - title: "Speaker", - }, - name: { - type: "string", - title: "Name", - }, - }, - type: "object", - required: ["id", "speaker", "name"], - title: "Participant", -} as const; - -export const $Room = { - properties: { - id: { - type: "string", - title: "Id", - }, - name: { - type: "string", - title: "Name", - }, - user_id: { - type: "string", - title: "User Id", - }, - created_at: { - type: "string", - format: "date-time", - title: "Created At", - }, - zulip_auto_post: { - type: "boolean", - title: "Zulip Auto Post", - }, - zulip_stream: { - type: "string", - title: "Zulip Stream", - }, - zulip_topic: { - type: "string", - title: "Zulip Topic", - }, - is_locked: { - type: "boolean", - title: "Is Locked", - }, - room_mode: { - type: "string", - title: "Room Mode", - }, - recording_type: { - type: "string", - title: "Recording Type", - }, - recording_trigger: { - type: "string", - title: "Recording Trigger", - }, - is_shared: { - type: "boolean", - title: "Is Shared", - }, - platform: { - $ref: "#/components/schemas/VideoPlatform", - default: "whereby", - }, - }, - type: "object", - required: [ - "id", - "name", - "user_id", - "created_at", - "zulip_auto_post", - "zulip_stream", - "zulip_topic", - "is_locked", - "room_mode", - "recording_type", - "recording_trigger", - "is_shared", - ], - title: "Room", -} as const; - -export const $RoomDetails = { - properties: { - id: { - type: "string", - title: "Id", - }, - name: { - type: "string", - title: "Name", - }, - user_id: { - type: "string", - title: "User Id", - }, - created_at: { - type: "string", - format: "date-time", - title: "Created At", - }, - zulip_auto_post: { - type: "boolean", - title: "Zulip Auto Post", - }, - zulip_stream: { - type: "string", - title: "Zulip Stream", - }, - zulip_topic: { - type: "string", - title: "Zulip Topic", - }, - is_locked: { - type: "boolean", - title: "Is Locked", - }, - room_mode: { - type: "string", - title: "Room Mode", - }, - recording_type: { - type: "string", - title: "Recording Type", - }, - recording_trigger: { - type: "string", - title: "Recording Trigger", - }, - is_shared: { - type: "boolean", - title: "Is Shared", - }, - platform: { - $ref: "#/components/schemas/VideoPlatform", - default: "whereby", - }, - webhook_url: { - anyOf: [ - { - type: "string", - }, - { - type: "null", - }, - ], - title: "Webhook Url", - }, - webhook_secret: { - anyOf: [ - { - type: "string", - }, - { - type: "null", - }, - ], - title: "Webhook Secret", - }, - }, - type: "object", - required: [ - "id", - "name", - "user_id", - "created_at", - "zulip_auto_post", - "zulip_stream", - "zulip_topic", - "is_locked", - "room_mode", - "recording_type", - "recording_trigger", - "is_shared", - "webhook_url", - "webhook_secret", - ], - title: "RoomDetails", -} as const; - -export const $RtcOffer = { - properties: { - sdp: { - type: "string", - title: "Sdp", - }, - type: { - type: "string", - title: "Type", - }, - }, - type: "object", - required: ["sdp", "type"], - title: "RtcOffer", -} as const; - -export const $SearchResponse = { - properties: { - results: { - items: { - $ref: "#/components/schemas/SearchResult", - }, - type: "array", - title: "Results", - }, - total: { - type: "integer", - minimum: 0, - title: "Total", - description: "Total number of search results", - }, - query: { - anyOf: [ - { - type: "string", - minLength: 1, - description: "Search query text", - }, - { - type: "null", - }, - ], - title: "Query", - }, - limit: { - type: "integer", - maximum: 100, - minimum: 1, - title: "Limit", - description: "Results per page", - }, - offset: { - type: "integer", - minimum: 0, - title: "Offset", - description: "Number of results to skip", - }, - }, - type: "object", - required: ["results", "total", "limit", "offset"], - title: "SearchResponse", -} as const; - -export const $SearchResult = { - properties: { - id: { - type: "string", - minLength: 1, - title: "Id", - }, - title: { - anyOf: [ - { - type: "string", - }, - { - type: "null", - }, - ], - title: "Title", - }, - user_id: { - anyOf: [ - { - type: "string", - }, - { - type: "null", - }, - ], - title: "User Id", - }, - room_id: { - anyOf: [ - { - type: "string", - }, - { - type: "null", - }, - ], - title: "Room Id", - }, - room_name: { - anyOf: [ - { - type: "string", - }, - { - type: "null", - }, - ], - title: "Room Name", - }, - source_kind: { - $ref: "#/components/schemas/SourceKind", - }, - created_at: { - type: "string", - title: "Created At", - }, - status: { - type: "string", - minLength: 1, - title: "Status", - }, - rank: { - type: "number", - maximum: 1, - minimum: 0, - title: "Rank", - }, - duration: { - anyOf: [ - { - type: "number", - minimum: 0, - }, - { - type: "null", - }, - ], - title: "Duration", - description: "Duration in seconds", - }, - search_snippets: { - items: { - type: "string", - }, - type: "array", - title: "Search Snippets", - description: "Text snippets around search matches", - }, - total_match_count: { - type: "integer", - minimum: 0, - title: "Total Match Count", - description: "Total number of matches found in the transcript", - default: 0, - }, - }, - type: "object", - required: [ - "id", - "source_kind", - "created_at", - "status", - "rank", - "duration", - "search_snippets", - ], - title: "SearchResult", - description: "Public search result model with computed fields.", -} as const; - -export const $SourceKind = { - type: "string", - enum: ["room", "live", "file"], - title: "SourceKind", -} as const; - -export const $SpeakerAssignment = { - properties: { - speaker: { - anyOf: [ - { - type: "integer", - minimum: 0, - }, - { - type: "null", - }, - ], - title: "Speaker", - }, - participant: { - anyOf: [ - { - type: "string", - }, - { - type: "null", - }, - ], - title: "Participant", - }, - timestamp_from: { - type: "number", - title: "Timestamp From", - }, - timestamp_to: { - type: "number", - title: "Timestamp To", - }, - }, - type: "object", - required: ["timestamp_from", "timestamp_to"], - title: "SpeakerAssignment", -} as const; - -export const $SpeakerAssignmentStatus = { - properties: { - status: { - type: "string", - title: "Status", - }, - }, - type: "object", - required: ["status"], - title: "SpeakerAssignmentStatus", -} as const; - -export const $SpeakerMerge = { - properties: { - speaker_from: { - type: "integer", - title: "Speaker From", - }, - speaker_to: { - type: "integer", - title: "Speaker To", - }, - }, - type: "object", - required: ["speaker_from", "speaker_to"], - title: "SpeakerMerge", -} as const; - -export const $SpeakerWords = { - properties: { - speaker: { - type: "integer", - title: "Speaker", - }, - words: { - items: { - $ref: "#/components/schemas/Word", - }, - type: "array", - title: "Words", - }, - }, - type: "object", - required: ["speaker", "words"], - title: "SpeakerWords", -} as const; - -export const $Stream = { - properties: { - stream_id: { - type: "integer", - title: "Stream Id", - }, - name: { - type: "string", - title: "Name", - }, - }, - type: "object", - required: ["stream_id", "name"], - title: "Stream", -} as const; - -export const $Topic = { - properties: { - name: { - type: "string", - title: "Name", - }, - }, - type: "object", - required: ["name"], - title: "Topic", -} as const; - -export const $TranscriptParticipant = { - properties: { - id: { - type: "string", - title: "Id", - }, - speaker: { - anyOf: [ - { - type: "integer", - }, - { - type: "null", - }, - ], - title: "Speaker", - }, - name: { - type: "string", - title: "Name", - }, - }, - type: "object", - required: ["speaker", "name"], - title: "TranscriptParticipant", -} as const; - -export const $UpdateParticipant = { - properties: { - speaker: { - anyOf: [ - { - type: "integer", - }, - { - type: "null", - }, - ], - title: "Speaker", - }, - name: { - anyOf: [ - { - type: "string", - }, - { - type: "null", - }, - ], - title: "Name", - }, - }, - type: "object", - title: "UpdateParticipant", -} as const; - -export const $UpdateRoom = { - properties: { - name: { - type: "string", - title: "Name", - }, - zulip_auto_post: { - type: "boolean", - title: "Zulip Auto Post", - }, - zulip_stream: { - type: "string", - title: "Zulip Stream", - }, - zulip_topic: { - type: "string", - title: "Zulip Topic", - }, - is_locked: { - type: "boolean", - title: "Is Locked", - }, - room_mode: { - type: "string", - title: "Room Mode", - }, - recording_type: { - type: "string", - title: "Recording Type", - }, - recording_trigger: { - type: "string", - title: "Recording Trigger", - }, - is_shared: { - type: "boolean", - title: "Is Shared", - }, - webhook_url: { - type: "string", - title: "Webhook Url", - }, - webhook_secret: { - type: "string", - title: "Webhook Secret", - }, - platform: { - $ref: "#/components/schemas/VideoPlatform", - }, - }, - type: "object", - required: [ - "name", - "zulip_auto_post", - "zulip_stream", - "zulip_topic", - "is_locked", - "room_mode", - "recording_type", - "recording_trigger", - "is_shared", - "webhook_url", - "webhook_secret", - "platform", - ], - title: "UpdateRoom", -} as const; - -export const $UpdateTranscript = { - properties: { - name: { - anyOf: [ - { - type: "string", - }, - { - type: "null", - }, - ], - title: "Name", - }, - locked: { - anyOf: [ - { - type: "boolean", - }, - { - type: "null", - }, - ], - title: "Locked", - }, - title: { - anyOf: [ - { - type: "string", - }, - { - type: "null", - }, - ], - title: "Title", - }, - short_summary: { - anyOf: [ - { - type: "string", - }, - { - type: "null", - }, - ], - title: "Short Summary", - }, - long_summary: { - anyOf: [ - { - type: "string", - }, - { - type: "null", - }, - ], - title: "Long Summary", - }, - share_mode: { - anyOf: [ - { - type: "string", - enum: ["public", "semi-private", "private"], - }, - { - type: "null", - }, - ], - title: "Share Mode", - }, - participants: { - anyOf: [ - { - items: { - $ref: "#/components/schemas/TranscriptParticipant", - }, - type: "array", - }, - { - type: "null", - }, - ], - title: "Participants", - }, - reviewed: { - anyOf: [ - { - type: "boolean", - }, - { - type: "null", - }, - ], - title: "Reviewed", - }, - audio_deleted: { - anyOf: [ - { - type: "boolean", - }, - { - type: "null", - }, - ], - title: "Audio Deleted", - }, - }, - type: "object", - title: "UpdateTranscript", -} as const; - -export const $UserInfo = { - properties: { - sub: { - type: "string", - title: "Sub", - }, - email: { - anyOf: [ - { - type: "string", - }, - { - type: "null", - }, - ], - title: "Email", - }, - email_verified: { - anyOf: [ - { - type: "boolean", - }, - { - type: "null", - }, - ], - title: "Email Verified", - }, - }, - type: "object", - required: ["sub", "email", "email_verified"], - title: "UserInfo", -} as const; - -export const $ValidationError = { - properties: { - loc: { - items: { - anyOf: [ - { - type: "string", - }, - { - type: "integer", - }, - ], - }, - type: "array", - title: "Location", - }, - msg: { - type: "string", - title: "Message", - }, - type: { - type: "string", - title: "Error Type", - }, - }, - type: "object", - required: ["loc", "msg", "type"], - title: "ValidationError", -} as const; - -export const $VideoPlatform = { - type: "string", - enum: ["whereby", "jitsi"], - title: "VideoPlatform", -} as const; - -export const $WebhookTestResult = { - properties: { - success: { - type: "boolean", - title: "Success", - }, - message: { - type: "string", - title: "Message", - default: "", - }, - error: { - type: "string", - title: "Error", - default: "", - }, - status_code: { - anyOf: [ - { - type: "integer", - }, - { - type: "null", - }, - ], - title: "Status Code", - }, - response_preview: { - anyOf: [ - { - type: "string", - }, - { - type: "null", - }, - ], - title: "Response Preview", - }, - }, - type: "object", - required: ["success"], - title: "WebhookTestResult", -} as const; - -export const $WherebyWebhookEvent = { - properties: { - apiVersion: { - type: "string", - title: "Apiversion", - }, - id: { - type: "string", - title: "Id", - }, - createdAt: { - type: "string", - format: "date-time", - title: "Createdat", - }, - type: { - type: "string", - title: "Type", - }, - data: { - additionalProperties: true, - type: "object", - title: "Data", - }, - }, - type: "object", - required: ["apiVersion", "id", "createdAt", "type", "data"], - title: "WherebyWebhookEvent", -} as const; - -export const $Word = { - properties: { - text: { - type: "string", - title: "Text", - }, - start: { - type: "number", - minimum: 0, - title: "Start", - description: "Time in seconds with float part", - }, - end: { - type: "number", - minimum: 0, - title: "End", - description: "Time in seconds with float part", - }, - speaker: { - type: "integer", - title: "Speaker", - default: 0, - }, - }, - type: "object", - required: ["text", "start", "end"], - title: "Word", -} as const; diff --git a/www/app/api/services.gen.ts b/www/app/api/services.gen.ts index d8cbae61..e69de29b 100644 --- a/www/app/api/services.gen.ts +++ b/www/app/api/services.gen.ts @@ -1,1012 +0,0 @@ -// This file is auto-generated by @hey-api/openapi-ts - -import type { CancelablePromise } from "./core/CancelablePromise"; -import type { BaseHttpRequest } from "./core/BaseHttpRequest"; -import type { - MetricsResponse, - V1MeetingAudioConsentData, - V1MeetingAudioConsentResponse, - V1RoomsListData, - V1RoomsListResponse, - V1RoomsCreateData, - V1RoomsCreateResponse, - V1RoomsGetData, - V1RoomsGetResponse, - V1RoomsUpdateData, - V1RoomsUpdateResponse, - V1RoomsDeleteData, - V1RoomsDeleteResponse, - V1RoomsCreateMeetingData, - V1RoomsCreateMeetingResponse, - V1RoomsTestWebhookData, - V1RoomsTestWebhookResponse, - V1TranscriptsListData, - V1TranscriptsListResponse, - V1TranscriptsCreateData, - V1TranscriptsCreateResponse, - V1TranscriptsSearchData, - V1TranscriptsSearchResponse, - V1TranscriptGetData, - V1TranscriptGetResponse, - V1TranscriptUpdateData, - V1TranscriptUpdateResponse, - V1TranscriptDeleteData, - V1TranscriptDeleteResponse, - V1TranscriptGetTopicsData, - V1TranscriptGetTopicsResponse, - V1TranscriptGetTopicsWithWordsData, - V1TranscriptGetTopicsWithWordsResponse, - V1TranscriptGetTopicsWithWordsPerSpeakerData, - V1TranscriptGetTopicsWithWordsPerSpeakerResponse, - V1TranscriptPostToZulipData, - V1TranscriptPostToZulipResponse, - V1TranscriptHeadAudioMp3Data, - V1TranscriptHeadAudioMp3Response, - V1TranscriptGetAudioMp3Data, - V1TranscriptGetAudioMp3Response, - V1TranscriptGetAudioWaveformData, - V1TranscriptGetAudioWaveformResponse, - V1TranscriptGetParticipantsData, - V1TranscriptGetParticipantsResponse, - V1TranscriptAddParticipantData, - V1TranscriptAddParticipantResponse, - V1TranscriptGetParticipantData, - V1TranscriptGetParticipantResponse, - V1TranscriptUpdateParticipantData, - V1TranscriptUpdateParticipantResponse, - V1TranscriptDeleteParticipantData, - V1TranscriptDeleteParticipantResponse, - V1TranscriptAssignSpeakerData, - V1TranscriptAssignSpeakerResponse, - V1TranscriptMergeSpeakerData, - V1TranscriptMergeSpeakerResponse, - V1TranscriptRecordUploadData, - V1TranscriptRecordUploadResponse, - V1TranscriptGetWebsocketEventsData, - V1TranscriptGetWebsocketEventsResponse, - V1TranscriptRecordWebrtcData, - V1TranscriptRecordWebrtcResponse, - V1TranscriptProcessData, - V1TranscriptProcessResponse, - V1UserMeResponse, - V1ZulipGetStreamsResponse, - V1ZulipGetTopicsData, - V1ZulipGetTopicsResponse, - V1WherebyWebhookData, - V1WherebyWebhookResponse, - V1JitsiEventsWebhookData, - V1JitsiEventsWebhookResponse, - V1JibriRecordingCompleteData, - V1JibriRecordingCompleteResponse, - V1JitsiHealthCheckResponse, -} from "./types.gen"; - -export class DefaultService { - constructor(public readonly httpRequest: BaseHttpRequest) {} - - /** - * Metrics - * Endpoint that serves Prometheus metrics. - * @returns unknown Successful Response - * @throws ApiError - */ - public metrics(): CancelablePromise { - return this.httpRequest.request({ - method: "GET", - url: "/metrics", - }); - } - - /** - * Meeting Audio Consent - * @param data The data for the request. - * @param data.meetingId - * @param data.requestBody - * @returns unknown Successful Response - * @throws ApiError - */ - public v1MeetingAudioConsent( - data: V1MeetingAudioConsentData, - ): CancelablePromise { - return this.httpRequest.request({ - method: "POST", - url: "/v1/meetings/{meeting_id}/consent", - path: { - meeting_id: data.meetingId, - }, - body: data.requestBody, - mediaType: "application/json", - errors: { - 422: "Validation Error", - }, - }); - } - - /** - * Rooms List - * @param data The data for the request. - * @param data.page Page number - * @param data.size Page size - * @returns Page_RoomDetails_ Successful Response - * @throws ApiError - */ - public v1RoomsList( - data: V1RoomsListData = {}, - ): CancelablePromise { - return this.httpRequest.request({ - method: "GET", - url: "/v1/rooms", - query: { - page: data.page, - size: data.size, - }, - errors: { - 422: "Validation Error", - }, - }); - } - - /** - * Rooms Create - * @param data The data for the request. - * @param data.requestBody - * @returns Room Successful Response - * @throws ApiError - */ - public v1RoomsCreate( - data: V1RoomsCreateData, - ): CancelablePromise { - return this.httpRequest.request({ - method: "POST", - url: "/v1/rooms", - body: data.requestBody, - mediaType: "application/json", - errors: { - 422: "Validation Error", - }, - }); - } - - /** - * Rooms Get - * @param data The data for the request. - * @param data.roomId - * @returns RoomDetails Successful Response - * @throws ApiError - */ - public v1RoomsGet( - data: V1RoomsGetData, - ): CancelablePromise { - return this.httpRequest.request({ - method: "GET", - url: "/v1/rooms/{room_id}", - path: { - room_id: data.roomId, - }, - errors: { - 422: "Validation Error", - }, - }); - } - - /** - * Rooms Update - * @param data The data for the request. - * @param data.roomId - * @param data.requestBody - * @returns RoomDetails Successful Response - * @throws ApiError - */ - public v1RoomsUpdate( - data: V1RoomsUpdateData, - ): CancelablePromise { - return this.httpRequest.request({ - method: "PATCH", - url: "/v1/rooms/{room_id}", - path: { - room_id: data.roomId, - }, - body: data.requestBody, - mediaType: "application/json", - errors: { - 422: "Validation Error", - }, - }); - } - - /** - * Rooms Delete - * @param data The data for the request. - * @param data.roomId - * @returns DeletionStatus Successful Response - * @throws ApiError - */ - public v1RoomsDelete( - data: V1RoomsDeleteData, - ): CancelablePromise { - return this.httpRequest.request({ - method: "DELETE", - url: "/v1/rooms/{room_id}", - path: { - room_id: data.roomId, - }, - errors: { - 422: "Validation Error", - }, - }); - } - - /** - * Rooms Create Meeting - * @param data The data for the request. - * @param data.roomName - * @returns Meeting Successful Response - * @throws ApiError - */ - public v1RoomsCreateMeeting( - data: V1RoomsCreateMeetingData, - ): CancelablePromise { - return this.httpRequest.request({ - method: "POST", - url: "/v1/rooms/{room_name}/meeting", - path: { - room_name: data.roomName, - }, - errors: { - 422: "Validation Error", - }, - }); - } - - /** - * Rooms Test Webhook - * @param data The data for the request. - * @param data.roomId - * @returns WebhookTestResult Successful Response - * @throws ApiError - */ - public v1RoomsTestWebhook( - data: V1RoomsTestWebhookData, - ): CancelablePromise { - return this.httpRequest.request({ - method: "POST", - url: "/v1/rooms/{room_id}/webhook/test", - path: { - room_id: data.roomId, - }, - errors: { - 422: "Validation Error", - }, - }); - } - - /** - * Transcripts List - * @param data The data for the request. - * @param data.sourceKind - * @param data.roomId - * @param data.searchTerm - * @param data.page Page number - * @param data.size Page size - * @returns Page_GetTranscriptMinimal_ Successful Response - * @throws ApiError - */ - public v1TranscriptsList( - data: V1TranscriptsListData = {}, - ): CancelablePromise { - return this.httpRequest.request({ - method: "GET", - url: "/v1/transcripts", - query: { - source_kind: data.sourceKind, - room_id: data.roomId, - search_term: data.searchTerm, - page: data.page, - size: data.size, - }, - errors: { - 422: "Validation Error", - }, - }); - } - - /** - * Transcripts Create - * @param data The data for the request. - * @param data.requestBody - * @returns GetTranscript Successful Response - * @throws ApiError - */ - public v1TranscriptsCreate( - data: V1TranscriptsCreateData, - ): CancelablePromise { - return this.httpRequest.request({ - method: "POST", - url: "/v1/transcripts", - body: data.requestBody, - mediaType: "application/json", - errors: { - 422: "Validation Error", - }, - }); - } - - /** - * Transcripts Search - * Full-text search across transcript titles and content. - * @param data The data for the request. - * @param data.q Search query text - * @param data.limit Results per page - * @param data.offset Number of results to skip - * @param data.roomId - * @param data.sourceKind - * @returns SearchResponse Successful Response - * @throws ApiError - */ - public v1TranscriptsSearch( - data: V1TranscriptsSearchData, - ): CancelablePromise { - return this.httpRequest.request({ - method: "GET", - url: "/v1/transcripts/search", - query: { - q: data.q, - limit: data.limit, - offset: data.offset, - room_id: data.roomId, - source_kind: data.sourceKind, - }, - errors: { - 422: "Validation Error", - }, - }); - } - - /** - * Transcript Get - * @param data The data for the request. - * @param data.transcriptId - * @returns GetTranscript Successful Response - * @throws ApiError - */ - public v1TranscriptGet( - data: V1TranscriptGetData, - ): CancelablePromise { - return this.httpRequest.request({ - method: "GET", - url: "/v1/transcripts/{transcript_id}", - path: { - transcript_id: data.transcriptId, - }, - errors: { - 422: "Validation Error", - }, - }); - } - - /** - * Transcript Update - * @param data The data for the request. - * @param data.transcriptId - * @param data.requestBody - * @returns GetTranscript Successful Response - * @throws ApiError - */ - public v1TranscriptUpdate( - data: V1TranscriptUpdateData, - ): CancelablePromise { - return this.httpRequest.request({ - method: "PATCH", - url: "/v1/transcripts/{transcript_id}", - path: { - transcript_id: data.transcriptId, - }, - body: data.requestBody, - mediaType: "application/json", - errors: { - 422: "Validation Error", - }, - }); - } - - /** - * Transcript Delete - * @param data The data for the request. - * @param data.transcriptId - * @returns DeletionStatus Successful Response - * @throws ApiError - */ - public v1TranscriptDelete( - data: V1TranscriptDeleteData, - ): CancelablePromise { - return this.httpRequest.request({ - method: "DELETE", - url: "/v1/transcripts/{transcript_id}", - path: { - transcript_id: data.transcriptId, - }, - errors: { - 422: "Validation Error", - }, - }); - } - - /** - * Transcript Get Topics - * @param data The data for the request. - * @param data.transcriptId - * @returns GetTranscriptTopic Successful Response - * @throws ApiError - */ - public v1TranscriptGetTopics( - data: V1TranscriptGetTopicsData, - ): CancelablePromise { - return this.httpRequest.request({ - method: "GET", - url: "/v1/transcripts/{transcript_id}/topics", - path: { - transcript_id: data.transcriptId, - }, - errors: { - 422: "Validation Error", - }, - }); - } - - /** - * Transcript Get Topics With Words - * @param data The data for the request. - * @param data.transcriptId - * @returns GetTranscriptTopicWithWords Successful Response - * @throws ApiError - */ - public v1TranscriptGetTopicsWithWords( - data: V1TranscriptGetTopicsWithWordsData, - ): CancelablePromise { - return this.httpRequest.request({ - method: "GET", - url: "/v1/transcripts/{transcript_id}/topics/with-words", - path: { - transcript_id: data.transcriptId, - }, - errors: { - 422: "Validation Error", - }, - }); - } - - /** - * Transcript Get Topics With Words Per Speaker - * @param data The data for the request. - * @param data.transcriptId - * @param data.topicId - * @returns GetTranscriptTopicWithWordsPerSpeaker Successful Response - * @throws ApiError - */ - public v1TranscriptGetTopicsWithWordsPerSpeaker( - data: V1TranscriptGetTopicsWithWordsPerSpeakerData, - ): CancelablePromise { - return this.httpRequest.request({ - method: "GET", - url: "/v1/transcripts/{transcript_id}/topics/{topic_id}/words-per-speaker", - path: { - transcript_id: data.transcriptId, - topic_id: data.topicId, - }, - errors: { - 422: "Validation Error", - }, - }); - } - - /** - * Transcript Post To Zulip - * @param data The data for the request. - * @param data.transcriptId - * @param data.stream - * @param data.topic - * @param data.includeTopics - * @returns unknown Successful Response - * @throws ApiError - */ - public v1TranscriptPostToZulip( - data: V1TranscriptPostToZulipData, - ): CancelablePromise { - return this.httpRequest.request({ - method: "POST", - url: "/v1/transcripts/{transcript_id}/zulip", - path: { - transcript_id: data.transcriptId, - }, - query: { - stream: data.stream, - topic: data.topic, - include_topics: data.includeTopics, - }, - errors: { - 422: "Validation Error", - }, - }); - } - - /** - * Transcript Get Audio Mp3 - * @param data The data for the request. - * @param data.transcriptId - * @param data.token - * @returns unknown Successful Response - * @throws ApiError - */ - public v1TranscriptHeadAudioMp3( - data: V1TranscriptHeadAudioMp3Data, - ): CancelablePromise { - return this.httpRequest.request({ - method: "HEAD", - url: "/v1/transcripts/{transcript_id}/audio/mp3", - path: { - transcript_id: data.transcriptId, - }, - query: { - token: data.token, - }, - errors: { - 422: "Validation Error", - }, - }); - } - - /** - * Transcript Get Audio Mp3 - * @param data The data for the request. - * @param data.transcriptId - * @param data.token - * @returns unknown Successful Response - * @throws ApiError - */ - public v1TranscriptGetAudioMp3( - data: V1TranscriptGetAudioMp3Data, - ): CancelablePromise { - return this.httpRequest.request({ - method: "GET", - url: "/v1/transcripts/{transcript_id}/audio/mp3", - path: { - transcript_id: data.transcriptId, - }, - query: { - token: data.token, - }, - errors: { - 422: "Validation Error", - }, - }); - } - - /** - * Transcript Get Audio Waveform - * @param data The data for the request. - * @param data.transcriptId - * @returns AudioWaveform Successful Response - * @throws ApiError - */ - public v1TranscriptGetAudioWaveform( - data: V1TranscriptGetAudioWaveformData, - ): CancelablePromise { - return this.httpRequest.request({ - method: "GET", - url: "/v1/transcripts/{transcript_id}/audio/waveform", - path: { - transcript_id: data.transcriptId, - }, - errors: { - 422: "Validation Error", - }, - }); - } - - /** - * Transcript Get Participants - * @param data The data for the request. - * @param data.transcriptId - * @returns Participant Successful Response - * @throws ApiError - */ - public v1TranscriptGetParticipants( - data: V1TranscriptGetParticipantsData, - ): CancelablePromise { - return this.httpRequest.request({ - method: "GET", - url: "/v1/transcripts/{transcript_id}/participants", - path: { - transcript_id: data.transcriptId, - }, - errors: { - 422: "Validation Error", - }, - }); - } - - /** - * Transcript Add Participant - * @param data The data for the request. - * @param data.transcriptId - * @param data.requestBody - * @returns Participant Successful Response - * @throws ApiError - */ - public v1TranscriptAddParticipant( - data: V1TranscriptAddParticipantData, - ): CancelablePromise { - return this.httpRequest.request({ - method: "POST", - url: "/v1/transcripts/{transcript_id}/participants", - path: { - transcript_id: data.transcriptId, - }, - body: data.requestBody, - mediaType: "application/json", - errors: { - 422: "Validation Error", - }, - }); - } - - /** - * Transcript Get Participant - * @param data The data for the request. - * @param data.transcriptId - * @param data.participantId - * @returns Participant Successful Response - * @throws ApiError - */ - public v1TranscriptGetParticipant( - data: V1TranscriptGetParticipantData, - ): CancelablePromise { - return this.httpRequest.request({ - method: "GET", - url: "/v1/transcripts/{transcript_id}/participants/{participant_id}", - path: { - transcript_id: data.transcriptId, - participant_id: data.participantId, - }, - errors: { - 422: "Validation Error", - }, - }); - } - - /** - * Transcript Update Participant - * @param data The data for the request. - * @param data.transcriptId - * @param data.participantId - * @param data.requestBody - * @returns Participant Successful Response - * @throws ApiError - */ - public v1TranscriptUpdateParticipant( - data: V1TranscriptUpdateParticipantData, - ): CancelablePromise { - return this.httpRequest.request({ - method: "PATCH", - url: "/v1/transcripts/{transcript_id}/participants/{participant_id}", - path: { - transcript_id: data.transcriptId, - participant_id: data.participantId, - }, - body: data.requestBody, - mediaType: "application/json", - errors: { - 422: "Validation Error", - }, - }); - } - - /** - * Transcript Delete Participant - * @param data The data for the request. - * @param data.transcriptId - * @param data.participantId - * @returns DeletionStatus Successful Response - * @throws ApiError - */ - public v1TranscriptDeleteParticipant( - data: V1TranscriptDeleteParticipantData, - ): CancelablePromise { - return this.httpRequest.request({ - method: "DELETE", - url: "/v1/transcripts/{transcript_id}/participants/{participant_id}", - path: { - transcript_id: data.transcriptId, - participant_id: data.participantId, - }, - errors: { - 422: "Validation Error", - }, - }); - } - - /** - * Transcript Assign Speaker - * @param data The data for the request. - * @param data.transcriptId - * @param data.requestBody - * @returns SpeakerAssignmentStatus Successful Response - * @throws ApiError - */ - public v1TranscriptAssignSpeaker( - data: V1TranscriptAssignSpeakerData, - ): CancelablePromise { - return this.httpRequest.request({ - method: "PATCH", - url: "/v1/transcripts/{transcript_id}/speaker/assign", - path: { - transcript_id: data.transcriptId, - }, - body: data.requestBody, - mediaType: "application/json", - errors: { - 422: "Validation Error", - }, - }); - } - - /** - * Transcript Merge Speaker - * @param data The data for the request. - * @param data.transcriptId - * @param data.requestBody - * @returns SpeakerAssignmentStatus Successful Response - * @throws ApiError - */ - public v1TranscriptMergeSpeaker( - data: V1TranscriptMergeSpeakerData, - ): CancelablePromise { - return this.httpRequest.request({ - method: "PATCH", - url: "/v1/transcripts/{transcript_id}/speaker/merge", - path: { - transcript_id: data.transcriptId, - }, - body: data.requestBody, - mediaType: "application/json", - errors: { - 422: "Validation Error", - }, - }); - } - - /** - * Transcript Record Upload - * @param data The data for the request. - * @param data.transcriptId - * @param data.chunkNumber - * @param data.totalChunks - * @param data.formData - * @returns unknown Successful Response - * @throws ApiError - */ - public v1TranscriptRecordUpload( - data: V1TranscriptRecordUploadData, - ): CancelablePromise { - return this.httpRequest.request({ - method: "POST", - url: "/v1/transcripts/{transcript_id}/record/upload", - path: { - transcript_id: data.transcriptId, - }, - query: { - chunk_number: data.chunkNumber, - total_chunks: data.totalChunks, - }, - formData: data.formData, - mediaType: "multipart/form-data", - errors: { - 422: "Validation Error", - }, - }); - } - - /** - * Transcript Get Websocket Events - * @param data The data for the request. - * @param data.transcriptId - * @returns unknown Successful Response - * @throws ApiError - */ - public v1TranscriptGetWebsocketEvents( - data: V1TranscriptGetWebsocketEventsData, - ): CancelablePromise { - return this.httpRequest.request({ - method: "GET", - url: "/v1/transcripts/{transcript_id}/events", - path: { - transcript_id: data.transcriptId, - }, - errors: { - 422: "Validation Error", - }, - }); - } - - /** - * Transcript Record Webrtc - * @param data The data for the request. - * @param data.transcriptId - * @param data.requestBody - * @returns unknown Successful Response - * @throws ApiError - */ - public v1TranscriptRecordWebrtc( - data: V1TranscriptRecordWebrtcData, - ): CancelablePromise { - return this.httpRequest.request({ - method: "POST", - url: "/v1/transcripts/{transcript_id}/record/webrtc", - path: { - transcript_id: data.transcriptId, - }, - body: data.requestBody, - mediaType: "application/json", - errors: { - 422: "Validation Error", - }, - }); - } - - /** - * Transcript Process - * @param data The data for the request. - * @param data.transcriptId - * @returns unknown Successful Response - * @throws ApiError - */ - public v1TranscriptProcess( - data: V1TranscriptProcessData, - ): CancelablePromise { - return this.httpRequest.request({ - method: "POST", - url: "/v1/transcripts/{transcript_id}/process", - path: { - transcript_id: data.transcriptId, - }, - errors: { - 422: "Validation Error", - }, - }); - } - - /** - * User Me - * @returns unknown Successful Response - * @throws ApiError - */ - public v1UserMe(): CancelablePromise { - return this.httpRequest.request({ - method: "GET", - url: "/v1/me", - }); - } - - /** - * Zulip Get Streams - * Get all Zulip streams. - * @returns Stream Successful Response - * @throws ApiError - */ - public v1ZulipGetStreams(): CancelablePromise { - return this.httpRequest.request({ - method: "GET", - url: "/v1/zulip/streams", - }); - } - - /** - * Zulip Get Topics - * Get all topics for a specific Zulip stream. - * @param data The data for the request. - * @param data.streamId - * @returns Topic Successful Response - * @throws ApiError - */ - public v1ZulipGetTopics( - data: V1ZulipGetTopicsData, - ): CancelablePromise { - return this.httpRequest.request({ - method: "GET", - url: "/v1/zulip/streams/{stream_id}/topics", - path: { - stream_id: data.streamId, - }, - errors: { - 422: "Validation Error", - }, - }); - } - - /** - * Whereby Webhook - * @param data The data for the request. - * @param data.requestBody - * @returns unknown Successful Response - * @throws ApiError - */ - public v1WherebyWebhook( - data: V1WherebyWebhookData, - ): CancelablePromise { - return this.httpRequest.request({ - method: "POST", - url: "/v1/whereby", - body: data.requestBody, - mediaType: "application/json", - errors: { - 422: "Validation Error", - }, - }); - } - - /** - * Jitsi Events Webhook - * Handle Prosody event-sync webhooks from Jitsi Meet. - * - * Expected event types: - * - muc-occupant-joined: participant joined the room - * - muc-occupant-left: participant left the room - * - jibri-recording-on: recording started - * - jibri-recording-off: recording stopped - * @param data The data for the request. - * @param data.requestBody - * @returns unknown Successful Response - * @throws ApiError - */ - public v1JitsiEventsWebhook( - data: V1JitsiEventsWebhookData, - ): CancelablePromise { - return this.httpRequest.request({ - method: "POST", - url: "/v1/jitsi/events", - body: data.requestBody, - mediaType: "application/json", - errors: { - 422: "Validation Error", - }, - }); - } - - /** - * Jibri Recording Complete - * Handle Jibri recording completion webhook. - * - * This endpoint is called by the Jibri finalize script when a recording - * is completed and uploaded to storage. - * @param data The data for the request. - * @param data.requestBody - * @returns unknown Successful Response - * @throws ApiError - */ - public v1JibriRecordingComplete( - data: V1JibriRecordingCompleteData, - ): CancelablePromise { - return this.httpRequest.request({ - method: "POST", - url: "/v1/jibri/recording-complete", - body: data.requestBody, - mediaType: "application/json", - errors: { - 422: "Validation Error", - }, - }); - } - - /** - * Jitsi Health Check - * Simple health check endpoint for Jitsi webhook configuration. - * @returns unknown Successful Response - * @throws ApiError - */ - public v1JitsiHealthCheck(): CancelablePromise { - return this.httpRequest.request({ - method: "GET", - url: "/v1/jitsi/health", - }); - } -} diff --git a/www/app/api/types.gen.ts b/www/app/api/types.gen.ts index 62e07e69..e69de29b 100644 --- a/www/app/api/types.gen.ts +++ b/www/app/api/types.gen.ts @@ -1,1218 +0,0 @@ -// This file is auto-generated by @hey-api/openapi-ts - -export type AudioWaveform = { - data: Array; -}; - -export type Body_transcript_record_upload_v1_transcripts__transcript_id__record_upload_post = - { - chunk: Blob | File; - }; - -export type CreateParticipant = { - speaker?: number | null; - name: string; -}; - -export type CreateRoom = { - name: string; - zulip_auto_post: boolean; - zulip_stream: string; - zulip_topic: string; - is_locked: boolean; - room_mode: string; - recording_type: string; - recording_trigger: string; - is_shared: boolean; - webhook_url: string; - webhook_secret: string; - platform: VideoPlatform; -}; - -export type CreateTranscript = { - name: string; - source_language?: string; - target_language?: string; -}; - -export type DeletionStatus = { - status: string; -}; - -export type GetTranscript = { - id: string; - user_id: string | null; - name: string; - status: string; - locked: boolean; - duration: number; - title: string | null; - short_summary: string | null; - long_summary: string | null; - created_at: string; - share_mode?: string; - source_language: string | null; - target_language: string | null; - reviewed: boolean; - meeting_id: string | null; - source_kind: SourceKind; - room_id?: string | null; - room_name?: string | null; - audio_deleted?: boolean | null; - participants: Array | null; -}; - -export type GetTranscriptMinimal = { - id: string; - user_id: string | null; - name: string; - status: string; - locked: boolean; - duration: number; - title: string | null; - short_summary: string | null; - long_summary: string | null; - created_at: string; - share_mode?: string; - source_language: string | null; - target_language: string | null; - reviewed: boolean; - meeting_id: string | null; - source_kind: SourceKind; - room_id?: string | null; - room_name?: string | null; - audio_deleted?: boolean | null; -}; - -export type GetTranscriptSegmentTopic = { - text: string; - start: number; - speaker: number; -}; - -export type GetTranscriptTopic = { - id: string; - title: string; - summary: string; - timestamp: number; - duration: number | null; - transcript: string; - segments?: Array; -}; - -export type GetTranscriptTopicWithWords = { - id: string; - title: string; - summary: string; - timestamp: number; - duration: number | null; - transcript: string; - segments?: Array; - words?: Array; -}; - -export type GetTranscriptTopicWithWordsPerSpeaker = { - id: string; - title: string; - summary: string; - timestamp: number; - duration: number | null; - transcript: string; - segments?: Array; - words_per_speaker?: Array; -}; - -export type HTTPValidationError = { - detail?: Array; -}; - -export type JibriRecordingEvent = { - room_name: string; - recording_file: string; - recording_status: string; - timestamp: string; -}; - -export type JitsiWebhookEvent = { - event: string; - room: string; - timestamp: string; - data?: { - [key: string]: unknown; - }; -}; - -export type Meeting = { - id: string; - room_name: string; - room_url: string; - host_room_url: string; - start_date: string; - end_date: string; - recording_type?: "none" | "local" | "cloud"; -}; - -export type recording_type = "none" | "local" | "cloud"; - -export type MeetingConsentRequest = { - consent_given: boolean; -}; - -export type Page_GetTranscriptMinimal_ = { - items: Array; - total?: number | null; - page: number | null; - size: number | null; - pages?: number | null; -}; - -export type Page_RoomDetails_ = { - items: Array; - total?: number | null; - page: number | null; - size: number | null; - pages?: number | null; -}; - -export type Participant = { - id: string; - speaker: number | null; - name: string; -}; - -export type Room = { - id: string; - name: string; - user_id: string; - created_at: string; - zulip_auto_post: boolean; - zulip_stream: string; - zulip_topic: string; - is_locked: boolean; - room_mode: string; - recording_type: string; - recording_trigger: string; - is_shared: boolean; - platform?: VideoPlatform; -}; - -export type RoomDetails = { - id: string; - name: string; - user_id: string; - created_at: string; - zulip_auto_post: boolean; - zulip_stream: string; - zulip_topic: string; - is_locked: boolean; - room_mode: string; - recording_type: string; - recording_trigger: string; - is_shared: boolean; - platform?: VideoPlatform; - webhook_url: string | null; - webhook_secret: string | null; -}; - -export type RtcOffer = { - sdp: string; - type: string; -}; - -export type SearchResponse = { - results: Array; - /** - * Total number of search results - */ - total: number; - query?: string | null; - /** - * Results per page - */ - limit: number; - /** - * Number of results to skip - */ - offset: number; -}; - -/** - * Public search result model with computed fields. - */ -export type SearchResult = { - id: string; - title?: string | null; - user_id?: string | null; - room_id?: string | null; - room_name?: string | null; - source_kind: SourceKind; - created_at: string; - status: string; - rank: number; - /** - * Duration in seconds - */ - duration: number | null; - /** - * Text snippets around search matches - */ - search_snippets: Array; - /** - * Total number of matches found in the transcript - */ - total_match_count?: number; -}; - -export type SourceKind = "room" | "live" | "file"; - -export type SpeakerAssignment = { - speaker?: number | null; - participant?: string | null; - timestamp_from: number; - timestamp_to: number; -}; - -export type SpeakerAssignmentStatus = { - status: string; -}; - -export type SpeakerMerge = { - speaker_from: number; - speaker_to: number; -}; - -export type SpeakerWords = { - speaker: number; - words: Array; -}; - -export type Stream = { - stream_id: number; - name: string; -}; - -export type Topic = { - name: string; -}; - -export type TranscriptParticipant = { - id?: string; - speaker: number | null; - name: string; -}; - -export type UpdateParticipant = { - speaker?: number | null; - name?: string | null; -}; - -export type UpdateRoom = { - name: string; - zulip_auto_post: boolean; - zulip_stream: string; - zulip_topic: string; - is_locked: boolean; - room_mode: string; - recording_type: string; - recording_trigger: string; - is_shared: boolean; - webhook_url: string; - webhook_secret: string; - platform: VideoPlatform; -}; - -export type UpdateTranscript = { - name?: string | null; - locked?: boolean | null; - title?: string | null; - short_summary?: string | null; - long_summary?: string | null; - share_mode?: "public" | "semi-private" | "private" | null; - participants?: Array | null; - reviewed?: boolean | null; - audio_deleted?: boolean | null; -}; - -export type UserInfo = { - sub: string; - email: string | null; - email_verified: boolean | null; -}; - -export type ValidationError = { - loc: Array; - msg: string; - type: string; -}; - -export type VideoPlatform = "whereby" | "jitsi"; - -export type WebhookTestResult = { - success: boolean; - message?: string; - error?: string; - status_code?: number | null; - response_preview?: string | null; -}; - -export type WherebyWebhookEvent = { - apiVersion: string; - id: string; - createdAt: string; - type: string; - data: { - [key: string]: unknown; - }; -}; - -export type Word = { - text: string; - /** - * Time in seconds with float part - */ - start: number; - /** - * Time in seconds with float part - */ - end: number; - speaker?: number; -}; - -export type MetricsResponse = unknown; - -export type V1MeetingAudioConsentData = { - meetingId: string; - requestBody: MeetingConsentRequest; -}; - -export type V1MeetingAudioConsentResponse = unknown; - -export type V1RoomsListData = { - /** - * Page number - */ - page?: number; - /** - * Page size - */ - size?: number; -}; - -export type V1RoomsListResponse = Page_RoomDetails_; - -export type V1RoomsCreateData = { - requestBody: CreateRoom; -}; - -export type V1RoomsCreateResponse = Room; - -export type V1RoomsGetData = { - roomId: string; -}; - -export type V1RoomsGetResponse = RoomDetails; - -export type V1RoomsUpdateData = { - requestBody: UpdateRoom; - roomId: string; -}; - -export type V1RoomsUpdateResponse = RoomDetails; - -export type V1RoomsDeleteData = { - roomId: string; -}; - -export type V1RoomsDeleteResponse = DeletionStatus; - -export type V1RoomsCreateMeetingData = { - roomName: string; -}; - -export type V1RoomsCreateMeetingResponse = Meeting; - -export type V1RoomsTestWebhookData = { - roomId: string; -}; - -export type V1RoomsTestWebhookResponse = WebhookTestResult; - -export type V1TranscriptsListData = { - /** - * Page number - */ - page?: number; - roomId?: string | null; - searchTerm?: string | null; - /** - * Page size - */ - size?: number; - sourceKind?: SourceKind | null; -}; - -export type V1TranscriptsListResponse = Page_GetTranscriptMinimal_; - -export type V1TranscriptsCreateData = { - requestBody: CreateTranscript; -}; - -export type V1TranscriptsCreateResponse = GetTranscript; - -export type V1TranscriptsSearchData = { - /** - * Results per page - */ - limit?: number; - /** - * Number of results to skip - */ - offset?: number; - /** - * Search query text - */ - q: string; - roomId?: string | null; - sourceKind?: SourceKind | null; -}; - -export type V1TranscriptsSearchResponse = SearchResponse; - -export type V1TranscriptGetData = { - transcriptId: string; -}; - -export type V1TranscriptGetResponse = GetTranscript; - -export type V1TranscriptUpdateData = { - requestBody: UpdateTranscript; - transcriptId: string; -}; - -export type V1TranscriptUpdateResponse = GetTranscript; - -export type V1TranscriptDeleteData = { - transcriptId: string; -}; - -export type V1TranscriptDeleteResponse = DeletionStatus; - -export type V1TranscriptGetTopicsData = { - transcriptId: string; -}; - -export type V1TranscriptGetTopicsResponse = Array; - -export type V1TranscriptGetTopicsWithWordsData = { - transcriptId: string; -}; - -export type V1TranscriptGetTopicsWithWordsResponse = - Array; - -export type V1TranscriptGetTopicsWithWordsPerSpeakerData = { - topicId: string; - transcriptId: string; -}; - -export type V1TranscriptGetTopicsWithWordsPerSpeakerResponse = - GetTranscriptTopicWithWordsPerSpeaker; - -export type V1TranscriptPostToZulipData = { - includeTopics: boolean; - stream: string; - topic: string; - transcriptId: string; -}; - -export type V1TranscriptPostToZulipResponse = unknown; - -export type V1TranscriptHeadAudioMp3Data = { - token?: string | null; - transcriptId: string; -}; - -export type V1TranscriptHeadAudioMp3Response = unknown; - -export type V1TranscriptGetAudioMp3Data = { - token?: string | null; - transcriptId: string; -}; - -export type V1TranscriptGetAudioMp3Response = unknown; - -export type V1TranscriptGetAudioWaveformData = { - transcriptId: string; -}; - -export type V1TranscriptGetAudioWaveformResponse = AudioWaveform; - -export type V1TranscriptGetParticipantsData = { - transcriptId: string; -}; - -export type V1TranscriptGetParticipantsResponse = Array; - -export type V1TranscriptAddParticipantData = { - requestBody: CreateParticipant; - transcriptId: string; -}; - -export type V1TranscriptAddParticipantResponse = Participant; - -export type V1TranscriptGetParticipantData = { - participantId: string; - transcriptId: string; -}; - -export type V1TranscriptGetParticipantResponse = Participant; - -export type V1TranscriptUpdateParticipantData = { - participantId: string; - requestBody: UpdateParticipant; - transcriptId: string; -}; - -export type V1TranscriptUpdateParticipantResponse = Participant; - -export type V1TranscriptDeleteParticipantData = { - participantId: string; - transcriptId: string; -}; - -export type V1TranscriptDeleteParticipantResponse = DeletionStatus; - -export type V1TranscriptAssignSpeakerData = { - requestBody: SpeakerAssignment; - transcriptId: string; -}; - -export type V1TranscriptAssignSpeakerResponse = SpeakerAssignmentStatus; - -export type V1TranscriptMergeSpeakerData = { - requestBody: SpeakerMerge; - transcriptId: string; -}; - -export type V1TranscriptMergeSpeakerResponse = SpeakerAssignmentStatus; - -export type V1TranscriptRecordUploadData = { - chunkNumber: number; - formData: Body_transcript_record_upload_v1_transcripts__transcript_id__record_upload_post; - totalChunks: number; - transcriptId: string; -}; - -export type V1TranscriptRecordUploadResponse = unknown; - -export type V1TranscriptGetWebsocketEventsData = { - transcriptId: string; -}; - -export type V1TranscriptGetWebsocketEventsResponse = unknown; - -export type V1TranscriptRecordWebrtcData = { - requestBody: RtcOffer; - transcriptId: string; -}; - -export type V1TranscriptRecordWebrtcResponse = unknown; - -export type V1TranscriptProcessData = { - transcriptId: string; -}; - -export type V1TranscriptProcessResponse = unknown; - -export type V1UserMeResponse = UserInfo | null; - -export type V1ZulipGetStreamsResponse = Array; - -export type V1ZulipGetTopicsData = { - streamId: number; -}; - -export type V1ZulipGetTopicsResponse = Array; - -export type V1WherebyWebhookData = { - requestBody: WherebyWebhookEvent; -}; - -export type V1WherebyWebhookResponse = unknown; - -export type V1JitsiEventsWebhookData = { - requestBody: JitsiWebhookEvent; -}; - -export type V1JitsiEventsWebhookResponse = unknown; - -export type V1JibriRecordingCompleteData = { - requestBody: JibriRecordingEvent; -}; - -export type V1JibriRecordingCompleteResponse = unknown; - -export type V1JitsiHealthCheckResponse = unknown; - -export type $OpenApiTs = { - "/metrics": { - get: { - res: { - /** - * Successful Response - */ - 200: unknown; - }; - }; - }; - "/v1/meetings/{meeting_id}/consent": { - post: { - req: V1MeetingAudioConsentData; - res: { - /** - * Successful Response - */ - 200: unknown; - /** - * Validation Error - */ - 422: HTTPValidationError; - }; - }; - }; - "/v1/rooms": { - get: { - req: V1RoomsListData; - res: { - /** - * Successful Response - */ - 200: Page_RoomDetails_; - /** - * Validation Error - */ - 422: HTTPValidationError; - }; - }; - post: { - req: V1RoomsCreateData; - res: { - /** - * Successful Response - */ - 200: Room; - /** - * Validation Error - */ - 422: HTTPValidationError; - }; - }; - }; - "/v1/rooms/{room_id}": { - get: { - req: V1RoomsGetData; - res: { - /** - * Successful Response - */ - 200: RoomDetails; - /** - * Validation Error - */ - 422: HTTPValidationError; - }; - }; - patch: { - req: V1RoomsUpdateData; - res: { - /** - * Successful Response - */ - 200: RoomDetails; - /** - * Validation Error - */ - 422: HTTPValidationError; - }; - }; - delete: { - req: V1RoomsDeleteData; - res: { - /** - * Successful Response - */ - 200: DeletionStatus; - /** - * Validation Error - */ - 422: HTTPValidationError; - }; - }; - }; - "/v1/rooms/{room_name}/meeting": { - post: { - req: V1RoomsCreateMeetingData; - res: { - /** - * Successful Response - */ - 200: Meeting; - /** - * Validation Error - */ - 422: HTTPValidationError; - }; - }; - }; - "/v1/rooms/{room_id}/webhook/test": { - post: { - req: V1RoomsTestWebhookData; - res: { - /** - * Successful Response - */ - 200: WebhookTestResult; - /** - * Validation Error - */ - 422: HTTPValidationError; - }; - }; - }; - "/v1/transcripts": { - get: { - req: V1TranscriptsListData; - res: { - /** - * Successful Response - */ - 200: Page_GetTranscriptMinimal_; - /** - * Validation Error - */ - 422: HTTPValidationError; - }; - }; - post: { - req: V1TranscriptsCreateData; - res: { - /** - * Successful Response - */ - 200: GetTranscript; - /** - * Validation Error - */ - 422: HTTPValidationError; - }; - }; - }; - "/v1/transcripts/search": { - get: { - req: V1TranscriptsSearchData; - res: { - /** - * Successful Response - */ - 200: SearchResponse; - /** - * Validation Error - */ - 422: HTTPValidationError; - }; - }; - }; - "/v1/transcripts/{transcript_id}": { - get: { - req: V1TranscriptGetData; - res: { - /** - * Successful Response - */ - 200: GetTranscript; - /** - * Validation Error - */ - 422: HTTPValidationError; - }; - }; - patch: { - req: V1TranscriptUpdateData; - res: { - /** - * Successful Response - */ - 200: GetTranscript; - /** - * Validation Error - */ - 422: HTTPValidationError; - }; - }; - delete: { - req: V1TranscriptDeleteData; - res: { - /** - * Successful Response - */ - 200: DeletionStatus; - /** - * Validation Error - */ - 422: HTTPValidationError; - }; - }; - }; - "/v1/transcripts/{transcript_id}/topics": { - get: { - req: V1TranscriptGetTopicsData; - res: { - /** - * Successful Response - */ - 200: Array; - /** - * Validation Error - */ - 422: HTTPValidationError; - }; - }; - }; - "/v1/transcripts/{transcript_id}/topics/with-words": { - get: { - req: V1TranscriptGetTopicsWithWordsData; - res: { - /** - * Successful Response - */ - 200: Array; - /** - * Validation Error - */ - 422: HTTPValidationError; - }; - }; - }; - "/v1/transcripts/{transcript_id}/topics/{topic_id}/words-per-speaker": { - get: { - req: V1TranscriptGetTopicsWithWordsPerSpeakerData; - res: { - /** - * Successful Response - */ - 200: GetTranscriptTopicWithWordsPerSpeaker; - /** - * Validation Error - */ - 422: HTTPValidationError; - }; - }; - }; - "/v1/transcripts/{transcript_id}/zulip": { - post: { - req: V1TranscriptPostToZulipData; - res: { - /** - * Successful Response - */ - 200: unknown; - /** - * Validation Error - */ - 422: HTTPValidationError; - }; - }; - }; - "/v1/transcripts/{transcript_id}/audio/mp3": { - head: { - req: V1TranscriptHeadAudioMp3Data; - res: { - /** - * Successful Response - */ - 200: unknown; - /** - * Validation Error - */ - 422: HTTPValidationError; - }; - }; - get: { - req: V1TranscriptGetAudioMp3Data; - res: { - /** - * Successful Response - */ - 200: unknown; - /** - * Validation Error - */ - 422: HTTPValidationError; - }; - }; - }; - "/v1/transcripts/{transcript_id}/audio/waveform": { - get: { - req: V1TranscriptGetAudioWaveformData; - res: { - /** - * Successful Response - */ - 200: AudioWaveform; - /** - * Validation Error - */ - 422: HTTPValidationError; - }; - }; - }; - "/v1/transcripts/{transcript_id}/participants": { - get: { - req: V1TranscriptGetParticipantsData; - res: { - /** - * Successful Response - */ - 200: Array; - /** - * Validation Error - */ - 422: HTTPValidationError; - }; - }; - post: { - req: V1TranscriptAddParticipantData; - res: { - /** - * Successful Response - */ - 200: Participant; - /** - * Validation Error - */ - 422: HTTPValidationError; - }; - }; - }; - "/v1/transcripts/{transcript_id}/participants/{participant_id}": { - get: { - req: V1TranscriptGetParticipantData; - res: { - /** - * Successful Response - */ - 200: Participant; - /** - * Validation Error - */ - 422: HTTPValidationError; - }; - }; - patch: { - req: V1TranscriptUpdateParticipantData; - res: { - /** - * Successful Response - */ - 200: Participant; - /** - * Validation Error - */ - 422: HTTPValidationError; - }; - }; - delete: { - req: V1TranscriptDeleteParticipantData; - res: { - /** - * Successful Response - */ - 200: DeletionStatus; - /** - * Validation Error - */ - 422: HTTPValidationError; - }; - }; - }; - "/v1/transcripts/{transcript_id}/speaker/assign": { - patch: { - req: V1TranscriptAssignSpeakerData; - res: { - /** - * Successful Response - */ - 200: SpeakerAssignmentStatus; - /** - * Validation Error - */ - 422: HTTPValidationError; - }; - }; - }; - "/v1/transcripts/{transcript_id}/speaker/merge": { - patch: { - req: V1TranscriptMergeSpeakerData; - res: { - /** - * Successful Response - */ - 200: SpeakerAssignmentStatus; - /** - * Validation Error - */ - 422: HTTPValidationError; - }; - }; - }; - "/v1/transcripts/{transcript_id}/record/upload": { - post: { - req: V1TranscriptRecordUploadData; - res: { - /** - * Successful Response - */ - 200: unknown; - /** - * Validation Error - */ - 422: HTTPValidationError; - }; - }; - }; - "/v1/transcripts/{transcript_id}/events": { - get: { - req: V1TranscriptGetWebsocketEventsData; - res: { - /** - * Successful Response - */ - 200: unknown; - /** - * Validation Error - */ - 422: HTTPValidationError; - }; - }; - }; - "/v1/transcripts/{transcript_id}/record/webrtc": { - post: { - req: V1TranscriptRecordWebrtcData; - res: { - /** - * Successful Response - */ - 200: unknown; - /** - * Validation Error - */ - 422: HTTPValidationError; - }; - }; - }; - "/v1/transcripts/{transcript_id}/process": { - post: { - req: V1TranscriptProcessData; - res: { - /** - * Successful Response - */ - 200: unknown; - /** - * Validation Error - */ - 422: HTTPValidationError; - }; - }; - }; - "/v1/me": { - get: { - res: { - /** - * Successful Response - */ - 200: UserInfo | null; - }; - }; - }; - "/v1/zulip/streams": { - get: { - res: { - /** - * Successful Response - */ - 200: Array; - }; - }; - }; - "/v1/zulip/streams/{stream_id}/topics": { - get: { - req: V1ZulipGetTopicsData; - res: { - /** - * Successful Response - */ - 200: Array; - /** - * Validation Error - */ - 422: HTTPValidationError; - }; - }; - }; - "/v1/whereby": { - post: { - req: V1WherebyWebhookData; - res: { - /** - * Successful Response - */ - 200: unknown; - /** - * Validation Error - */ - 422: HTTPValidationError; - }; - }; - }; - "/v1/jitsi/events": { - post: { - req: V1JitsiEventsWebhookData; - res: { - /** - * Successful Response - */ - 200: unknown; - /** - * Validation Error - */ - 422: HTTPValidationError; - }; - }; - }; - "/v1/jibri/recording-complete": { - post: { - req: V1JibriRecordingCompleteData; - res: { - /** - * Successful Response - */ - 200: unknown; - /** - * Validation Error - */ - 422: HTTPValidationError; - }; - }; - }; - "/v1/jitsi/health": { - get: { - res: { - /** - * Successful Response - */ - 200: unknown; - }; - }; - }; -}; diff --git a/www/app/api/urls.ts b/www/app/api/urls.ts index bd0a910c..89ce5af8 100644 --- a/www/app/api/urls.ts +++ b/www/app/api/urls.ts @@ -1,2 +1 @@ -// TODO better connection with generated schema; it's duplication export const RECORD_A_MEETING_URL = "/transcripts/new" as const; diff --git a/www/app/domainContext.tsx b/www/app/domainContext.tsx deleted file mode 100644 index 7e415f1c..00000000 --- a/www/app/domainContext.tsx +++ /dev/null @@ -1,49 +0,0 @@ -"use client"; -import { createContext, useContext, useEffect, useState } from "react"; -import { DomainConfig } from "./lib/edgeConfig"; - -type DomainContextType = Omit; - -export const DomainContext = createContext({ - features: { - requireLogin: false, - privacy: true, - browse: false, - sendToZulip: false, - }, - api_url: "", - websocket_url: "", -}); - -export const DomainContextProvider = ({ - config, - children, -}: { - config: DomainConfig; - children: any; -}) => { - const [context, setContext] = useState(); - - useEffect(() => { - if (!config) return; - const { auth_callback_url, ...others } = config; - setContext(others); - }, [config]); - - if (!context) return; - - return ( - {children} - ); -}; - -// Get feature config client-side with -export const featureEnabled = ( - featureName: "requireLogin" | "privacy" | "browse" | "sendToZulip", -) => { - const context = useContext(DomainContext); - - return context.features[featureName] as boolean | undefined; -}; - -// Get config server-side (out of react) : see lib/edgeConfig. diff --git a/www/app/layout.tsx b/www/app/layout.tsx index f73b8813..175b7cbc 100644 --- a/www/app/layout.tsx +++ b/www/app/layout.tsx @@ -1,14 +1,12 @@ import "./styles/globals.scss"; import { Metadata, Viewport } from "next"; import { Poppins } from "next/font/google"; -import SessionProvider from "./lib/SessionProvider"; import { ErrorProvider } from "./(errors)/errorContext"; import ErrorMessage from "./(errors)/errorMessage"; -import { DomainContextProvider } from "./domainContext"; import { RecordingConsentProvider } from "./recordingConsentContext"; -import { getConfig } from "./lib/edgeConfig"; import { ErrorBoundary } from "@sentry/nextjs"; import { Providers } from "./providers"; +import { assertExistsAndNonEmptyString } from "./lib/utils"; const poppins = Poppins({ subsets: ["latin"], @@ -23,8 +21,13 @@ export const viewport: Viewport = { maximumScale: 1, }; +const NEXT_PUBLIC_SITE_URL = assertExistsAndNonEmptyString( + process.env.NEXT_PUBLIC_SITE_URL, + "NEXT_PUBLIC_SITE_URL required", +); + export const metadata: Metadata = { - metadataBase: new URL(process.env.NEXT_PUBLIC_SITE_URL!), + metadataBase: new URL(NEXT_PUBLIC_SITE_URL), title: { template: "%s – Reflector", default: "Reflector - AI-Powered Meeting Transcriptions by Monadical", @@ -69,23 +72,17 @@ export default async function RootLayout({ }: { children: React.ReactNode; }) { - const config = await getConfig(); - return ( - - - - "something went really wrong"

}> - - - {children} - -
-
-
-
+ + "something went really wrong"

}> + + + {children} + +
+
); diff --git a/www/app/lib/AuthProvider.tsx b/www/app/lib/AuthProvider.tsx new file mode 100644 index 00000000..e1eabf99 --- /dev/null +++ b/www/app/lib/AuthProvider.tsx @@ -0,0 +1,132 @@ +"use client"; + +import { createContext, useContext } from "react"; +import { useSession as useNextAuthSession } from "next-auth/react"; +import { signOut, signIn } from "next-auth/react"; +import { configureApiAuth } from "./apiClient"; +import { assertCustomSession, CustomSession } from "./types"; +import { Session } from "next-auth"; +import { SessionAutoRefresh } from "./SessionAutoRefresh"; +import { REFRESH_ACCESS_TOKEN_ERROR } from "./auth"; +import { assertExists } from "./utils"; +import { featureEnabled } from "./features"; + +type AuthContextType = ( + | { status: "loading" } + | { status: "refreshing"; user: CustomSession["user"] } + | { status: "unauthenticated"; error?: string } + | { + status: "authenticated"; + accessToken: string; + accessTokenExpires: number; + user: CustomSession["user"]; + } +) & { + update: () => Promise; + signIn: typeof signIn; + signOut: typeof signOut; +}; + +const AuthContext = createContext(undefined); +const isAuthEnabled = featureEnabled("requireLogin"); + +const noopAuthContext: AuthContextType = { + status: "unauthenticated", + update: async () => { + return null; + }, + signIn: async () => { + throw new Error("signIn not supposed to be called"); + }, + signOut: async () => { + throw new Error("signOut not supposed to be called"); + }, +}; + +export function AuthProvider({ children }: { children: React.ReactNode }) { + const { data: session, status, update } = useNextAuthSession(); + + const contextValue: AuthContextType = isAuthEnabled + ? { + ...(() => { + switch (status) { + case "loading": { + const sessionIsHere = !!session; + // actually exists sometimes; nextAuth types are something else + switch (sessionIsHere as boolean) { + case false: { + return { status }; + } + case true: { + return { + status: "refreshing" as const, + user: assertCustomSession( + assertExists(session as unknown as Session), + ).user, + }; + } + default: { + throw new Error("unreachable"); + } + } + } + case "authenticated": { + const customSession = assertCustomSession(session); + if (customSession?.error === REFRESH_ACCESS_TOKEN_ERROR) { + // token had expired but next auth still returns "authenticated" so show user unauthenticated state + return { + status: "unauthenticated" as const, + }; + } else if (customSession?.accessToken) { + return { + status, + accessToken: customSession.accessToken, + accessTokenExpires: customSession.accessTokenExpires, + user: customSession.user, + }; + } else { + console.warn( + "illegal state: authenticated but have no session/or access token. ignoring", + ); + return { status: "unauthenticated" as const }; + } + } + case "unauthenticated": { + return { status: "unauthenticated" as const }; + } + default: { + const _: never = status; + throw new Error("unreachable"); + } + } + })(), + update, + signIn, + signOut, + } + : noopAuthContext; + + // not useEffect, we need it ASAP + // apparently, still no guarantee this code runs before mutations are fired + configureApiAuth( + contextValue.status === "authenticated" + ? contextValue.accessToken + : contextValue.status === "loading" + ? undefined + : null, + ); + + return ( + + {children} + + ); +} + +export function useAuth() { + const context = useContext(AuthContext); + if (context === undefined) { + throw new Error("useAuth must be used within an AuthProvider"); + } + return context; +} diff --git a/www/app/lib/SessionAutoRefresh.tsx b/www/app/lib/SessionAutoRefresh.tsx index 1e230d6c..6b26077d 100644 --- a/www/app/lib/SessionAutoRefresh.tsx +++ b/www/app/lib/SessionAutoRefresh.tsx @@ -1,5 +1,5 @@ /** - * This is a custom hook that automatically refreshes the session when the access token is about to expire. + * This is a custom provider that automatically refreshes the session when the access token is about to expire. * When communicating with the reflector API, we need to ensure that the access token is always valid. * * We could have implemented that as an interceptor on the API client, but not everything is using the @@ -7,30 +7,35 @@ */ "use client"; -import { useSession } from "next-auth/react"; import { useEffect } from "react"; -import { CustomSession } from "./types"; +import { useAuth } from "./AuthProvider"; +import { shouldRefreshToken } from "./auth"; -export function SessionAutoRefresh({ - children, - refreshInterval = 20 /* seconds */, -}) { - const { data: session, update } = useSession(); - const customSession = session as CustomSession; - const accessTokenExpires = customSession?.accessTokenExpires; +export function SessionAutoRefresh({ children }) { + const auth = useAuth(); + + const accessTokenExpires = + auth.status === "authenticated" ? auth.accessTokenExpires : null; useEffect(() => { + // technical value for how often the setInterval will be polling news - not too fast (no spam in case of errors) + // and not too slow (debuggable) + const INTERVAL_REFRESH_MS = 5000; const interval = setInterval(() => { - if (accessTokenExpires) { - const timeLeft = accessTokenExpires - Date.now(); - if (timeLeft < refreshInterval * 1000) { - update(); - } + if (accessTokenExpires === null) return; + if (shouldRefreshToken(accessTokenExpires)) { + auth + .update() + .then(() => {}) + .catch((e) => { + // note: 401 won't be considered error here + console.error("error refreshing auth token", e); + }); } - }, refreshInterval * 1000); + }, INTERVAL_REFRESH_MS); return () => clearInterval(interval); - }, [accessTokenExpires, refreshInterval, update]); + }, [accessTokenExpires, auth.update]); return children; } diff --git a/www/app/lib/SessionProvider.tsx b/www/app/lib/SessionProvider.tsx deleted file mode 100644 index 9c95fbc8..00000000 --- a/www/app/lib/SessionProvider.tsx +++ /dev/null @@ -1,11 +0,0 @@ -"use client"; -import { SessionProvider as SessionProviderNextAuth } from "next-auth/react"; -import { SessionAutoRefresh } from "./SessionAutoRefresh"; - -export default function SessionProvider({ children }) { - return ( - - {children} - - ); -} diff --git a/www/app/lib/__tests__/redisTokenCache.test.ts b/www/app/lib/__tests__/redisTokenCache.test.ts new file mode 100644 index 00000000..8ca8e8a1 --- /dev/null +++ b/www/app/lib/__tests__/redisTokenCache.test.ts @@ -0,0 +1,85 @@ +import { + getTokenCache, + setTokenCache, + deleteTokenCache, + TokenCacheEntry, + KV, +} from "../redisTokenCache"; + +const mockKV: KV & { + clear: () => void; +} = (() => { + const data = new Map(); + return { + async get(key: string): Promise { + return data.get(key) || null; + }, + + async setex(key: string, seconds_: number, value: string): Promise<"OK"> { + data.set(key, value); + return "OK"; + }, + + async del(key: string): Promise { + const existed = data.has(key); + data.delete(key); + return existed ? 1 : 0; + }, + + clear() { + data.clear(); + }, + }; +})(); + +describe("Redis Token Cache", () => { + beforeEach(() => { + mockKV.clear(); + }); + + test("basic write/read - value written equals value read", async () => { + const testKey = "token:test-user-123"; + const testValue: TokenCacheEntry = { + token: { + sub: "test-user-123", + name: "Test User", + email: "test@example.com", + accessToken: "access-token-123", + accessTokenExpires: Date.now() + 3600000, // 1 hour from now + refreshToken: "refresh-token-456", + }, + timestamp: Date.now(), + }; + + await setTokenCache(mockKV, testKey, testValue); + const retrievedValue = await getTokenCache(mockKV, testKey); + + expect(retrievedValue).not.toBeNull(); + expect(retrievedValue).toEqual(testValue); + expect(retrievedValue?.token.accessToken).toBe(testValue.token.accessToken); + expect(retrievedValue?.token.sub).toBe(testValue.token.sub); + expect(retrievedValue?.timestamp).toBe(testValue.timestamp); + }); + + test("get returns null for non-existent key", async () => { + const result = await getTokenCache(mockKV, "non-existent-key"); + expect(result).toBeNull(); + }); + + test("delete removes token from cache", async () => { + const testKey = "token:delete-test"; + const testValue: TokenCacheEntry = { + token: { + accessToken: "test-token", + accessTokenExpires: Date.now() + 3600000, + }, + timestamp: Date.now(), + }; + + await setTokenCache(mockKV, testKey, testValue); + await deleteTokenCache(mockKV, testKey); + + const result = await getTokenCache(mockKV, testKey); + expect(result).toBeNull(); + }); +}); diff --git a/www/app/lib/apiClient.tsx b/www/app/lib/apiClient.tsx new file mode 100644 index 00000000..a5cec06b --- /dev/null +++ b/www/app/lib/apiClient.tsx @@ -0,0 +1,72 @@ +"use client"; + +import createClient from "openapi-fetch"; +import type { paths } from "../reflector-api"; +import createFetchClient from "openapi-react-query"; +import { assertExistsAndNonEmptyString, parseNonEmptyString } from "./utils"; +import { isBuildPhase } from "./next"; +import { getSession } from "next-auth/react"; +import { assertExtendedToken } from "./types"; + +export const API_URL = !isBuildPhase + ? assertExistsAndNonEmptyString( + process.env.NEXT_PUBLIC_API_URL, + "NEXT_PUBLIC_API_URL required", + ) + : "http://localhost"; + +// TODO decide strict validation or not +export const WEBSOCKET_URL = + process.env.NEXT_PUBLIC_WEBSOCKET_URL || "ws://127.0.0.1:1250"; + +export const client = createClient({ + baseUrl: API_URL, +}); + +// will assert presence/absence of login initially +const initialSessionPromise = getSession(); + +const waitForAuthTokenDefinitivePresenceOrAbsence = async () => { + const initialSession = await initialSessionPromise; + if (currentAuthToken === undefined) { + currentAuthToken = + initialSession === null + ? null + : assertExtendedToken(initialSession).accessToken; + } + // otherwise already overwritten by external forces + return currentAuthToken; +}; + +client.use({ + async onRequest({ request }) { + const token = await waitForAuthTokenDefinitivePresenceOrAbsence(); + if (token !== null) { + request.headers.set( + "Authorization", + `Bearer ${parseNonEmptyString(token)}`, + ); + } + // XXX Only set Content-Type if not already set (FormData will set its own boundary) + // This is a work around for uploading file, we're passing a formdata + // but the content type was still application/json + if ( + !request.headers.has("Content-Type") && + !(request.body instanceof FormData) + ) { + request.headers.set("Content-Type", "application/json"); + } + return request; + }, +}); + +export const $api = createFetchClient(client); + +let currentAuthToken: string | null | undefined = undefined; + +// the function contract: lightweight, idempotent +export const configureApiAuth = (token: string | null | undefined) => { + // watch only for the initial loading; "reloading" state assumes token presence/absence + if (token === undefined && currentAuthToken !== undefined) return; + currentAuthToken = token; +}; diff --git a/www/app/lib/apiHooks.ts b/www/app/lib/apiHooks.ts new file mode 100644 index 00000000..3b5eed2b --- /dev/null +++ b/www/app/lib/apiHooks.ts @@ -0,0 +1,612 @@ +"use client"; + +import { $api } from "./apiClient"; +import { useError } from "../(errors)/errorContext"; +import { useQueryClient } from "@tanstack/react-query"; +import type { components } from "../reflector-api"; +import { useAuth } from "./AuthProvider"; + +/* + * XXX error types returned from the hooks are not always correct; declared types are ValidationError but real type could be string or any other + * this is either a limitation or incorrect usage of Python json schema generator + * or, limitation or incorrect usage of .d type generator from json schema + * */ + +const useAuthReady = () => { + const auth = useAuth(); + + return { + isAuthenticated: auth.status === "authenticated", + isLoading: auth.status === "loading", + }; +}; + +export function useRoomsList(page: number = 1) { + const { isAuthenticated } = useAuthReady(); + + return $api.useQuery( + "get", + "/v1/rooms", + { + params: { + query: { page }, + }, + }, + { + enabled: isAuthenticated, + }, + ); +} + +type SourceKind = components["schemas"]["SourceKind"]; + +export function useTranscriptsSearch( + q: string = "", + options: { + limit?: number; + offset?: number; + room_id?: string; + source_kind?: SourceKind; + } = {}, +) { + return $api.useQuery( + "get", + "/v1/transcripts/search", + { + params: { + query: { + q, + limit: options.limit, + offset: options.offset, + room_id: options.room_id, + source_kind: options.source_kind, + }, + }, + }, + { + enabled: true, + }, + ); +} + +export function useTranscriptDelete() { + const { setError } = useError(); + const queryClient = useQueryClient(); + + return $api.useMutation("delete", "/v1/transcripts/{transcript_id}", { + onSuccess: () => { + queryClient.invalidateQueries({ + queryKey: ["get", "/v1/transcripts/search"], + }); + }, + onError: (error) => { + setError(error as Error, "There was an error deleting the transcript"); + }, + }); +} + +export function useTranscriptProcess() { + const { setError } = useError(); + + return $api.useMutation("post", "/v1/transcripts/{transcript_id}/process", { + onError: (error) => { + setError(error as Error, "There was an error processing the transcript"); + }, + }); +} + +export function useTranscriptGet(transcriptId: string | null) { + return $api.useQuery( + "get", + "/v1/transcripts/{transcript_id}", + { + params: { + path: { + transcript_id: transcriptId || "", + }, + }, + }, + { + enabled: !!transcriptId, + }, + ); +} + +export function useRoomGet(roomId: string | null) { + const { isAuthenticated } = useAuthReady(); + + return $api.useQuery( + "get", + "/v1/rooms/{room_id}", + { + params: { + path: { room_id: roomId || "" }, + }, + }, + { + enabled: !!roomId && isAuthenticated, + }, + ); +} + +export function useRoomTestWebhook() { + const { setError } = useError(); + + return $api.useMutation("post", "/v1/rooms/{room_id}/webhook/test", { + onError: (error) => { + setError(error as Error, "There was an error testing the webhook"); + }, + }); +} + +export function useRoomCreate() { + const { setError } = useError(); + const queryClient = useQueryClient(); + + return $api.useMutation("post", "/v1/rooms", { + onSuccess: () => { + queryClient.invalidateQueries({ + queryKey: $api.queryOptions("get", "/v1/rooms").queryKey, + }); + }, + onError: (error) => { + setError(error as Error, "There was an error creating the room"); + }, + }); +} + +export function useRoomUpdate() { + const { setError } = useError(); + const queryClient = useQueryClient(); + + return $api.useMutation("patch", "/v1/rooms/{room_id}", { + onSuccess: async (room) => { + await Promise.all([ + queryClient.invalidateQueries({ + queryKey: $api.queryOptions("get", "/v1/rooms").queryKey, + }), + queryClient.invalidateQueries({ + queryKey: $api.queryOptions("get", "/v1/rooms/{room_id}", { + params: { + path: { + room_id: room.id, + }, + }, + }).queryKey, + }), + ]); + }, + onError: (error) => { + setError(error as Error, "There was an error updating the room"); + }, + }); +} + +export function useRoomDelete() { + const { setError } = useError(); + const queryClient = useQueryClient(); + + return $api.useMutation("delete", "/v1/rooms/{room_id}", { + onSuccess: () => { + queryClient.invalidateQueries({ + queryKey: $api.queryOptions("get", "/v1/rooms").queryKey, + }); + }, + onError: (error) => { + setError(error as Error, "There was an error deleting the room"); + }, + }); +} + +export function useZulipStreams() { + const { isAuthenticated } = useAuthReady(); + + return $api.useQuery( + "get", + "/v1/zulip/streams", + {}, + { + enabled: isAuthenticated, + }, + ); +} + +export function useZulipTopics(streamId: number | null) { + const { isAuthenticated } = useAuthReady(); + const enabled = !!streamId && isAuthenticated; + return $api.useQuery( + "get", + "/v1/zulip/streams/{stream_id}/topics", + { + params: { + path: { + stream_id: enabled ? streamId : 0, + }, + }, + }, + { + enabled, + }, + ); +} + +export function useTranscriptUpdate() { + const { setError } = useError(); + const queryClient = useQueryClient(); + + return $api.useMutation("patch", "/v1/transcripts/{transcript_id}", { + onSuccess: (data, variables) => { + queryClient.invalidateQueries({ + queryKey: $api.queryOptions("get", "/v1/transcripts/{transcript_id}", { + params: { + path: { transcript_id: variables.params.path.transcript_id }, + }, + }).queryKey, + }); + }, + onError: (error) => { + setError(error as Error, "There was an error updating the transcript"); + }, + }); +} + +export function useTranscriptPostToZulip() { + const { setError } = useError(); + + // @ts-ignore - Zulip endpoint not in OpenAPI spec + return $api.useMutation("post", "/v1/transcripts/{transcript_id}/zulip", { + onError: (error) => { + setError(error as Error, "There was an error posting to Zulip"); + }, + }); +} + +export function useTranscriptUploadAudio() { + const { setError } = useError(); + const queryClient = useQueryClient(); + + return $api.useMutation( + "post", + "/v1/transcripts/{transcript_id}/record/upload", + { + onSuccess: (data, variables) => { + queryClient.invalidateQueries({ + queryKey: $api.queryOptions( + "get", + "/v1/transcripts/{transcript_id}", + { + params: { + path: { transcript_id: variables.params.path.transcript_id }, + }, + }, + ).queryKey, + }); + }, + onError: (error) => { + setError(error as Error, "There was an error uploading the audio file"); + }, + }, + ); +} + +export function useTranscriptWaveform(transcriptId: string | null) { + return $api.useQuery( + "get", + "/v1/transcripts/{transcript_id}/audio/waveform", + { + params: { + path: { transcript_id: transcriptId! }, + }, + }, + { + enabled: !!transcriptId, + }, + ); +} + +export function useTranscriptMP3(transcriptId: string | null) { + const { isAuthenticated } = useAuthReady(); + + return $api.useQuery( + "get", + "/v1/transcripts/{transcript_id}/audio/mp3", + { + params: { + path: { transcript_id: transcriptId! }, + }, + }, + { + enabled: !!transcriptId && isAuthenticated, + }, + ); +} + +export function useTranscriptTopics(transcriptId: string | null) { + return $api.useQuery( + "get", + "/v1/transcripts/{transcript_id}/topics", + { + params: { + path: { transcript_id: transcriptId || "" }, + }, + }, + { + enabled: !!transcriptId, + }, + ); +} + +export function useTranscriptTopicsWithWords(transcriptId: string | null) { + const { isAuthenticated } = useAuthReady(); + + return $api.useQuery( + "get", + "/v1/transcripts/{transcript_id}/topics/with-words", + { + params: { + path: { transcript_id: transcriptId || "" }, + }, + }, + { + enabled: !!transcriptId && isAuthenticated, + }, + ); +} + +export function useTranscriptTopicsWithWordsPerSpeaker( + transcriptId: string | null, + topicId: string | null, +) { + const { isAuthenticated } = useAuthReady(); + + return $api.useQuery( + "get", + "/v1/transcripts/{transcript_id}/topics/{topic_id}/words-per-speaker", + { + params: { + path: { + transcript_id: transcriptId || "", + topic_id: topicId || "", + }, + }, + }, + { + enabled: !!transcriptId && !!topicId && isAuthenticated, + }, + ); +} + +export function useTranscriptParticipants(transcriptId: string | null) { + const { isAuthenticated } = useAuthReady(); + + return $api.useQuery( + "get", + "/v1/transcripts/{transcript_id}/participants", + { + params: { + path: { transcript_id: transcriptId || "" }, + }, + }, + { + enabled: !!transcriptId && isAuthenticated, + }, + ); +} + +export function useTranscriptParticipantUpdate() { + const { setError } = useError(); + const queryClient = useQueryClient(); + + return $api.useMutation( + "patch", + "/v1/transcripts/{transcript_id}/participants/{participant_id}", + { + onSuccess: (data, variables) => { + queryClient.invalidateQueries({ + queryKey: $api.queryOptions( + "get", + "/v1/transcripts/{transcript_id}/participants", + { + params: { + path: { transcript_id: variables.params.path.transcript_id }, + }, + }, + ).queryKey, + }); + }, + onError: (error) => { + setError(error as Error, "There was an error updating the participant"); + }, + }, + ); +} + +export function useTranscriptParticipantCreate() { + const { setError } = useError(); + const queryClient = useQueryClient(); + + return $api.useMutation( + "post", + "/v1/transcripts/{transcript_id}/participants", + { + onSuccess: (data, variables) => { + queryClient.invalidateQueries({ + queryKey: $api.queryOptions( + "get", + "/v1/transcripts/{transcript_id}/participants", + { + params: { + path: { transcript_id: variables.params.path.transcript_id }, + }, + }, + ).queryKey, + }); + }, + onError: (error) => { + setError(error as Error, "There was an error creating the participant"); + }, + }, + ); +} + +export function useTranscriptParticipantDelete() { + const { setError } = useError(); + const queryClient = useQueryClient(); + + return $api.useMutation( + "delete", + "/v1/transcripts/{transcript_id}/participants/{participant_id}", + { + onSuccess: (data, variables) => { + queryClient.invalidateQueries({ + queryKey: $api.queryOptions( + "get", + "/v1/transcripts/{transcript_id}/participants", + { + params: { + path: { transcript_id: variables.params.path.transcript_id }, + }, + }, + ).queryKey, + }); + }, + onError: (error) => { + setError(error as Error, "There was an error deleting the participant"); + }, + }, + ); +} + +export function useTranscriptSpeakerAssign() { + const { setError } = useError(); + const queryClient = useQueryClient(); + + return $api.useMutation( + "patch", + "/v1/transcripts/{transcript_id}/speaker/assign", + { + onSuccess: (data, variables) => { + queryClient.invalidateQueries({ + queryKey: $api.queryOptions( + "get", + "/v1/transcripts/{transcript_id}", + { + params: { + path: { transcript_id: variables.params.path.transcript_id }, + }, + }, + ).queryKey, + }); + queryClient.invalidateQueries({ + queryKey: $api.queryOptions( + "get", + "/v1/transcripts/{transcript_id}/participants", + { + params: { + path: { transcript_id: variables.params.path.transcript_id }, + }, + }, + ).queryKey, + }); + }, + onError: (error) => { + setError(error as Error, "There was an error assigning the speaker"); + }, + }, + ); +} + +export function useTranscriptSpeakerMerge() { + const { setError } = useError(); + const queryClient = useQueryClient(); + + return $api.useMutation( + "patch", + "/v1/transcripts/{transcript_id}/speaker/merge", + { + onSuccess: (data, variables) => { + queryClient.invalidateQueries({ + queryKey: $api.queryOptions( + "get", + "/v1/transcripts/{transcript_id}", + { + params: { + path: { transcript_id: variables.params.path.transcript_id }, + }, + }, + ).queryKey, + }); + queryClient.invalidateQueries({ + queryKey: $api.queryOptions( + "get", + "/v1/transcripts/{transcript_id}/participants", + { + params: { + path: { transcript_id: variables.params.path.transcript_id }, + }, + }, + ).queryKey, + }); + }, + onError: (error) => { + setError(error as Error, "There was an error merging speakers"); + }, + }, + ); +} + +export function useMeetingAudioConsent() { + const { setError } = useError(); + + return $api.useMutation("post", "/v1/meetings/{meeting_id}/consent", { + onError: (error) => { + setError(error as Error, "There was an error recording consent"); + }, + }); +} + +export function useTranscriptWebRTC() { + const { setError } = useError(); + + return $api.useMutation( + "post", + "/v1/transcripts/{transcript_id}/record/webrtc", + { + onError: (error) => { + setError(error as Error, "There was an error with WebRTC connection"); + }, + }, + ); +} + +export function useTranscriptCreate() { + const { setError } = useError(); + const queryClient = useQueryClient(); + + return $api.useMutation("post", "/v1/transcripts", { + onSuccess: () => { + queryClient.invalidateQueries({ + queryKey: ["get", "/v1/transcripts/search"], + }); + }, + onError: (error) => { + setError(error as Error, "There was an error creating the transcript"); + }, + }); +} + +export function useRoomsCreateMeeting() { + const { setError } = useError(); + const queryClient = useQueryClient(); + + return $api.useMutation("post", "/v1/rooms/{room_name}/meeting", { + onSuccess: () => { + queryClient.invalidateQueries({ + queryKey: $api.queryOptions("get", "/v1/rooms").queryKey, + }); + }, + onError: (error) => { + setError(error as Error, "There was an error creating the meeting"); + }, + }); +} diff --git a/www/app/lib/array.ts b/www/app/lib/array.ts new file mode 100644 index 00000000..f47aaa42 --- /dev/null +++ b/www/app/lib/array.ts @@ -0,0 +1,12 @@ +export type NonEmptyArray = [T, ...T[]]; +export const isNonEmptyArray = (arr: T[]): arr is NonEmptyArray => + arr.length > 0; +export const assertNonEmptyArray = ( + arr: T[], + err?: string, +): NonEmptyArray => { + if (isNonEmptyArray(arr)) { + return arr; + } + throw new Error(err ?? "Expected non-empty array"); +}; diff --git a/www/app/lib/auth.ts b/www/app/lib/auth.ts index 9169c694..e562eaed 100644 --- a/www/app/lib/auth.ts +++ b/www/app/lib/auth.ts @@ -1,157 +1,20 @@ -// import { kv } from "@vercel/kv"; -import Redlock, { ResourceLockedError } from "redlock"; -import { AuthOptions } from "next-auth"; -import AuthentikProvider from "next-auth/providers/authentik"; -import { JWT } from "next-auth/jwt"; -import { JWTWithAccessToken, CustomSession } from "./types"; -import Redis from "ioredis"; +import { assertExistsAndNonEmptyString } from "./utils"; -const PRETIMEOUT = 60; // seconds before token expires to refresh it -const DEFAULT_REDIS_KEY_TIMEOUT = 60 * 60 * 24 * 30; // 30 days (refresh token expires in 30 days) -const kv = new Redis(process.env.KV_URL || "", { - tls: {}, -}); -const redlock = new Redlock([kv], {}); +export const REFRESH_ACCESS_TOKEN_ERROR = "RefreshAccessTokenError" as const; +// 4 min is 1 min less than default authentic value. here we assume that authentic won't be set to access tokens < 4 min +export const REFRESH_ACCESS_TOKEN_BEFORE = 4 * 60 * 1000; -redlock.on("error", (error) => { - if (error instanceof ResourceLockedError) { - return; - } - - // Log all other errors. - console.error(error); -}); - -export const authOptions: AuthOptions = { - providers: [ - AuthentikProvider({ - clientId: process.env.AUTHENTIK_CLIENT_ID as string, - clientSecret: process.env.AUTHENTIK_CLIENT_SECRET as string, - issuer: process.env.AUTHENTIK_ISSUER, - authorization: { - params: { - scope: "openid email profile offline_access", - }, - }, - }), - ], - session: { - strategy: "jwt", - }, - callbacks: { - async jwt({ token, account, user }) { - const extendedToken = token as JWTWithAccessToken; - if (account && user) { - // called only on first login - // XXX account.expires_in used in example is not defined for authentik backend, but expires_at is - const expiresAt = (account.expires_at as number) - PRETIMEOUT; - const jwtToken = { - ...extendedToken, - accessToken: account.access_token, - accessTokenExpires: expiresAt * 1000, - refreshToken: account.refresh_token, - }; - kv.set( - `token:${jwtToken.sub}`, - JSON.stringify(jwtToken), - "EX", - DEFAULT_REDIS_KEY_TIMEOUT, - ); - return jwtToken; - } - - if (Date.now() < extendedToken.accessTokenExpires) { - return token; - } - - // access token has expired, try to update it - return await redisLockedrefreshAccessToken(token); - }, - async session({ session, token }) { - const extendedToken = token as JWTWithAccessToken; - const customSession = session as CustomSession; - customSession.accessToken = extendedToken.accessToken; - customSession.accessTokenExpires = extendedToken.accessTokenExpires; - customSession.error = extendedToken.error; - customSession.user = { - id: extendedToken.sub, - name: extendedToken.name, - email: extendedToken.email, - }; - return customSession; - }, - }, +export const shouldRefreshToken = (accessTokenExpires: number): boolean => { + const timeLeft = accessTokenExpires - Date.now(); + return timeLeft < REFRESH_ACCESS_TOKEN_BEFORE; }; -async function redisLockedrefreshAccessToken(token: JWT) { - return await redlock.using( - [token.sub as string, "jwt-refresh"], - 5000, - async () => { - const redisToken = await kv.get(`token:${token.sub}`); - const currentToken = JSON.parse( - redisToken as string, - ) as JWTWithAccessToken; +export const LOGIN_REQUIRED_PAGES = [ + "/transcripts/[!new]", + "/browse(.*)", + "/rooms(.*)", +]; - // if there is multiple requests for the same token, it may already have been refreshed - if (Date.now() < currentToken.accessTokenExpires) { - return currentToken; - } - - // now really do the request - const newToken = await refreshAccessToken(currentToken); - await kv.set( - `token:${currentToken.sub}`, - JSON.stringify(newToken), - "EX", - DEFAULT_REDIS_KEY_TIMEOUT, - ); - return newToken; - }, - ); -} - -async function refreshAccessToken(token: JWT): Promise { - try { - const url = `${process.env.AUTHENTIK_REFRESH_TOKEN_URL}`; - - const options = { - headers: { - "Content-Type": "application/x-www-form-urlencoded", - }, - body: new URLSearchParams({ - client_id: process.env.AUTHENTIK_CLIENT_ID as string, - client_secret: process.env.AUTHENTIK_CLIENT_SECRET as string, - grant_type: "refresh_token", - refresh_token: token.refreshToken as string, - }).toString(), - method: "POST", - }; - - const response = await fetch(url, options); - if (!response.ok) { - console.error( - new Date().toISOString(), - "Failed to refresh access token. Response status:", - response.status, - ); - const responseBody = await response.text(); - console.error(new Date().toISOString(), "Response body:", responseBody); - throw new Error(`Failed to refresh access token: ${response.statusText}`); - } - const refreshedTokens = await response.json(); - return { - ...token, - accessToken: refreshedTokens.access_token, - accessTokenExpires: - Date.now() + (refreshedTokens.expires_in - PRETIMEOUT) * 1000, - refreshToken: refreshedTokens.refresh_token, - }; - } catch (error) { - console.error("Error refreshing access token", error); - return { - ...token, - error: "RefreshAccessTokenError", - } as JWTWithAccessToken; - } -} +export const PROTECTED_PAGES = new RegExp( + LOGIN_REQUIRED_PAGES.map((page) => `^${page}$`).join("|"), +); diff --git a/www/app/lib/authBackend.ts b/www/app/lib/authBackend.ts new file mode 100644 index 00000000..5e9767c9 --- /dev/null +++ b/www/app/lib/authBackend.ts @@ -0,0 +1,245 @@ +import { AuthOptions } from "next-auth"; +import AuthentikProvider from "next-auth/providers/authentik"; +import type { JWT } from "next-auth/jwt"; +import { JWTWithAccessToken, CustomSession } from "./types"; +import { + assertExists, + assertExistsAndNonEmptyString, + assertNotExists, +} from "./utils"; +import { + REFRESH_ACCESS_TOKEN_BEFORE, + REFRESH_ACCESS_TOKEN_ERROR, + shouldRefreshToken, +} from "./auth"; +import { + getTokenCache, + setTokenCache, + deleteTokenCache, +} from "./redisTokenCache"; +import { tokenCacheRedis, redlock } from "./redisClient"; +import { isBuildPhase } from "./next"; +import { sequenceThrows } from "./errorUtils"; +import { featureEnabled } from "./features"; + +const TOKEN_CACHE_TTL = REFRESH_ACCESS_TOKEN_BEFORE; +const getAuthentikClientId = () => + assertExistsAndNonEmptyString( + process.env.AUTHENTIK_CLIENT_ID, + "AUTHENTIK_CLIENT_ID required", + ); +const getAuthentikClientSecret = () => + assertExistsAndNonEmptyString( + process.env.AUTHENTIK_CLIENT_SECRET, + "AUTHENTIK_CLIENT_SECRET required", + ); +const getAuthentikRefreshTokenUrl = () => + assertExistsAndNonEmptyString( + process.env.AUTHENTIK_REFRESH_TOKEN_URL, + "AUTHENTIK_REFRESH_TOKEN_URL required", + ); + +export const authOptions = (): AuthOptions => + featureEnabled("requireLogin") + ? { + providers: [ + AuthentikProvider({ + ...(() => { + const [clientId, clientSecret] = sequenceThrows( + getAuthentikClientId, + getAuthentikClientSecret, + ); + return { + clientId, + clientSecret, + }; + })(), + issuer: process.env.AUTHENTIK_ISSUER, + authorization: { + params: { + scope: "openid email profile offline_access", + }, + }, + }), + ], + session: { + strategy: "jwt", + }, + callbacks: { + async jwt({ token, account, user }) { + if (account && !account.access_token) { + await deleteTokenCache(tokenCacheRedis, `token:${token.sub}`); + } + + if (account && user) { + // called only on first login + // XXX account.expires_in used in example is not defined for authentik backend, but expires_at is + if (account.access_token) { + const expiresAtS = assertExists(account.expires_at); + const expiresAtMs = expiresAtS * 1000; + const jwtToken: JWTWithAccessToken = { + ...token, + accessToken: account.access_token, + accessTokenExpires: expiresAtMs, + refreshToken: account.refresh_token, + }; + if (jwtToken.error) { + await deleteTokenCache(tokenCacheRedis, `token:${token.sub}`); + } else { + assertNotExists( + jwtToken.error, + `panic! trying to cache token with error in jwt: ${jwtToken.error}`, + ); + await setTokenCache(tokenCacheRedis, `token:${token.sub}`, { + token: jwtToken, + timestamp: Date.now(), + }); + return jwtToken; + } + } + } + + const currentToken = await getTokenCache( + tokenCacheRedis, + `token:${token.sub}`, + ); + console.debug( + "currentToken from cache", + JSON.stringify(currentToken, null, 2), + "will be returned?", + currentToken && + !shouldRefreshToken(currentToken.token.accessTokenExpires), + ); + if ( + currentToken && + !shouldRefreshToken(currentToken.token.accessTokenExpires) + ) { + return currentToken.token; + } + + // access token has expired, try to update it + return await lockedRefreshAccessToken(token); + }, + async session({ session, token }) { + const extendedToken = token as JWTWithAccessToken; + return { + ...session, + accessToken: extendedToken.accessToken, + accessTokenExpires: extendedToken.accessTokenExpires, + error: extendedToken.error, + user: { + id: assertExists(extendedToken.sub), + name: extendedToken.name, + email: extendedToken.email, + }, + } satisfies CustomSession; + }, + }, + } + : { + providers: [], + }; + +async function lockedRefreshAccessToken( + token: JWT, +): Promise { + const lockKey = `${token.sub}-lock`; + + return redlock + .using([lockKey], 10000, async () => { + const cached = await getTokenCache(tokenCacheRedis, `token:${token.sub}`); + if (cached) + console.debug( + "received cached token. to delete?", + Date.now() - cached.timestamp > TOKEN_CACHE_TTL, + ); + else console.debug("no cached token received"); + if (cached) { + if (Date.now() - cached.timestamp > TOKEN_CACHE_TTL) { + await deleteTokenCache(tokenCacheRedis, `token:${token.sub}`); + } else if (!shouldRefreshToken(cached.token.accessTokenExpires)) { + console.debug("returning cached token", cached.token); + return cached.token; + } + } + + const currentToken = cached?.token || (token as JWTWithAccessToken); + const newToken = await refreshAccessToken(currentToken); + + console.debug("current token during refresh", currentToken); + console.debug("new token during refresh", newToken); + + if (newToken.error) { + await deleteTokenCache(tokenCacheRedis, `token:${token.sub}`); + return newToken; + } + + assertNotExists( + newToken.error, + `panic! trying to cache token with error during refresh: ${newToken.error}`, + ); + await setTokenCache(tokenCacheRedis, `token:${token.sub}`, { + token: newToken, + timestamp: Date.now(), + }); + + return newToken; + }) + .catch((e) => { + console.error("error refreshing token", e); + deleteTokenCache(tokenCacheRedis, `token:${token.sub}`).catch((e) => { + console.error("error deleting errored token", e); + }); + return { + ...token, + error: REFRESH_ACCESS_TOKEN_ERROR, + } as JWTWithAccessToken; + }); +} + +async function refreshAccessToken(token: JWT): Promise { + const [url, clientId, clientSecret] = sequenceThrows( + getAuthentikRefreshTokenUrl, + getAuthentikClientId, + getAuthentikClientSecret, + ); + try { + const options = { + headers: { + "Content-Type": "application/x-www-form-urlencoded", + }, + body: new URLSearchParams({ + client_id: clientId, + client_secret: clientSecret, + grant_type: "refresh_token", + refresh_token: token.refreshToken as string, + }).toString(), + method: "POST", + }; + + const response = await fetch(url, options); + if (!response.ok) { + console.error( + new Date().toISOString(), + "Failed to refresh access token. Response status:", + response.status, + ); + const responseBody = await response.text(); + console.error(new Date().toISOString(), "Response body:", responseBody); + throw new Error(`Failed to refresh access token: ${response.statusText}`); + } + const refreshedTokens = await response.json(); + return { + ...token, + accessToken: refreshedTokens.access_token, + accessTokenExpires: Date.now() + refreshedTokens.expires_in * 1000, + refreshToken: refreshedTokens.refresh_token, + }; + } catch (error) { + console.error("Error refreshing access token", error); + return { + ...token, + error: REFRESH_ACCESS_TOKEN_ERROR, + } as JWTWithAccessToken; + } +} diff --git a/www/app/lib/edgeConfig.ts b/www/app/lib/edgeConfig.ts deleted file mode 100644 index 2e31e146..00000000 --- a/www/app/lib/edgeConfig.ts +++ /dev/null @@ -1,48 +0,0 @@ -import { get } from "@vercel/edge-config"; -import { isDevelopment } from "./utils"; - -type EdgeConfig = { - [domainWithDash: string]: { - features: { - [featureName in - | "requireLogin" - | "privacy" - | "browse" - | "sendToZulip"]: boolean; - }; - auth_callback_url: string; - websocket_url: string; - api_url: string; - }; -}; - -export type DomainConfig = EdgeConfig["domainWithDash"]; - -// Edge config main keys can only be alphanumeric and _ or - -export function edgeKeyToDomain(key: string) { - return key.replaceAll("_", "."); -} - -export function edgeDomainToKey(domain: string) { - return domain.replaceAll(".", "_"); -} - -// get edge config server-side (prefer DomainContext when available), domain is the hostname -export async function getConfig() { - const domain = new URL(process.env.NEXT_PUBLIC_SITE_URL!).hostname; - - if (process.env.NEXT_PUBLIC_ENV === "development") { - return require("../../config").localConfig; - } - - let config = await get(edgeDomainToKey(domain)); - - if (typeof config !== "object") { - console.warn("No config for this domain, falling back to default"); - config = await get(edgeDomainToKey("default")); - } - - if (typeof config !== "object") throw Error("Error fetching config"); - - return config as DomainConfig; -} diff --git a/www/app/lib/errorUtils.ts b/www/app/lib/errorUtils.ts index e9e5300d..1512230c 100644 --- a/www/app/lib/errorUtils.ts +++ b/www/app/lib/errorUtils.ts @@ -1,4 +1,6 @@ -function shouldShowError(error: Error | null | undefined) { +import { isNonEmptyArray, NonEmptyArray } from "./array"; + +export function shouldShowError(error: Error | null | undefined) { if ( error?.name == "ResponseError" && (error["response"].status == 404 || error["response"].status == 403) @@ -8,4 +10,40 @@ function shouldShowError(error: Error | null | undefined) { return true; } -export { shouldShowError }; +const defaultMergeErrors = (ex: NonEmptyArray): unknown => { + try { + return new Error( + ex + .map((e) => + e ? (e.toString ? e.toString() : JSON.stringify(e)) : `${e}`, + ) + .join("\n"), + ); + } catch (e) { + console.error("Error merging errors:", e); + return ex[0]; + } +}; + +type ReturnTypes any)[]> = { + [K in keyof T]: T[K] extends () => infer R ? R : never; +}; + +// sequence semantic for "throws" +// calls functions passed and collects its thrown values +export function sequenceThrows any)[]>( + ...fs: Fns +): ReturnTypes { + const results: unknown[] = []; + const errors: unknown[] = []; + + for (const f of fs) { + try { + results.push(f()); + } catch (e) { + errors.push(e); + } + } + if (errors.length) throw defaultMergeErrors(errors as NonEmptyArray); + return results as ReturnTypes; +} diff --git a/www/app/lib/features.ts b/www/app/lib/features.ts new file mode 100644 index 00000000..7684c8e0 --- /dev/null +++ b/www/app/lib/features.ts @@ -0,0 +1,55 @@ +export const FEATURES = [ + "requireLogin", + "privacy", + "browse", + "sendToZulip", + "rooms", +] as const; + +export type FeatureName = (typeof FEATURES)[number]; + +export type Features = Readonly>; + +export const DEFAULT_FEATURES: Features = { + requireLogin: true, + privacy: true, + browse: true, + sendToZulip: true, + rooms: true, +} as const; + +function parseBooleanEnv( + value: string | undefined, + defaultValue: boolean = false, +): boolean { + if (!value) return defaultValue; + return value.toLowerCase() === "true"; +} + +// WARNING: keep process.env.* as-is, next.js won't see them if you generate dynamically +const features: Features = { + requireLogin: parseBooleanEnv( + process.env.NEXT_PUBLIC_FEATURE_REQUIRE_LOGIN, + DEFAULT_FEATURES.requireLogin, + ), + privacy: parseBooleanEnv( + process.env.NEXT_PUBLIC_FEATURE_PRIVACY, + DEFAULT_FEATURES.privacy, + ), + browse: parseBooleanEnv( + process.env.NEXT_PUBLIC_FEATURE_BROWSE, + DEFAULT_FEATURES.browse, + ), + sendToZulip: parseBooleanEnv( + process.env.NEXT_PUBLIC_FEATURE_SEND_TO_ZULIP, + DEFAULT_FEATURES.sendToZulip, + ), + rooms: parseBooleanEnv( + process.env.NEXT_PUBLIC_FEATURE_ROOMS, + DEFAULT_FEATURES.rooms, + ), +}; + +export const featureEnabled = (featureName: FeatureName): boolean => { + return features[featureName]; +}; diff --git a/www/app/lib/next.ts b/www/app/lib/next.ts new file mode 100644 index 00000000..91d88bd2 --- /dev/null +++ b/www/app/lib/next.ts @@ -0,0 +1,2 @@ +// next.js tries to run all the lib code during build phase; we don't always want it when e.g. we have connections initialized we don't want to have +export const isBuildPhase = process.env.NEXT_PHASE?.includes("build"); diff --git a/www/app/lib/queryClient.tsx b/www/app/lib/queryClient.tsx new file mode 100644 index 00000000..bd5946e0 --- /dev/null +++ b/www/app/lib/queryClient.tsx @@ -0,0 +1,17 @@ +"use client"; + +import { QueryClient } from "@tanstack/react-query"; + +export const queryClient = new QueryClient({ + defaultOptions: { + queries: { + staleTime: 60 * 1000, // 1 minute + gcTime: 5 * 60 * 1000, // 5 minutes (formerly cacheTime) + retry: 1, + refetchOnWindowFocus: false, + }, + mutations: { + retry: 0, + }, + }, +}); diff --git a/www/app/lib/redisClient.ts b/www/app/lib/redisClient.ts new file mode 100644 index 00000000..aeb3595b --- /dev/null +++ b/www/app/lib/redisClient.ts @@ -0,0 +1,78 @@ +import Redis from "ioredis"; +import { isBuildPhase } from "./next"; +import Redlock, { ResourceLockedError } from "redlock"; + +export type RedisClient = Pick; +export type RedlockClient = { + using: ( + keys: string | string[], + ttl: number, + cb: () => Promise, + ) => Promise; +}; +const KV_USE_TLS = process.env.KV_USE_TLS + ? process.env.KV_USE_TLS === "true" + : undefined; + +let redisClient: Redis | null = null; + +const getRedisClient = (): RedisClient => { + if (redisClient) return redisClient; + const redisUrl = process.env.KV_URL; + if (!redisUrl) { + throw new Error("KV_URL environment variable is required"); + } + redisClient = new Redis(redisUrl, { + maxRetriesPerRequest: 3, + ...(KV_USE_TLS === true + ? { + tls: {}, + } + : {}), + }); + + redisClient.on("error", (error) => { + console.error("Redis error:", error); + }); + + return redisClient; +}; + +// next.js buildtime usage - we want to isolate next.js "build" time concepts here +const noopClient: RedisClient = (() => { + const noopSetex: Redis["setex"] = async () => { + return "OK" as const; + }; + const noopDel: Redis["del"] = async () => { + return 0; + }; + return { + get: async () => { + return null; + }, + setex: noopSetex, + del: noopDel, + }; +})(); + +const noopRedlock: RedlockClient = { + using: (resource: string | string[], ttl: number, cb: () => Promise) => + cb(), +}; + +export const redlock: RedlockClient = isBuildPhase + ? noopRedlock + : (() => { + const r = new Redlock([getRedisClient()], {}); + r.on("error", (error) => { + if (error instanceof ResourceLockedError) { + return; + } + + // Log all other errors. + console.error(error); + }); + return r; + })(); + +export const tokenCacheRedis = isBuildPhase ? noopClient : getRedisClient(); diff --git a/www/app/lib/redisTokenCache.ts b/www/app/lib/redisTokenCache.ts new file mode 100644 index 00000000..a8b720ef --- /dev/null +++ b/www/app/lib/redisTokenCache.ts @@ -0,0 +1,61 @@ +import { z } from "zod"; +import { REFRESH_ACCESS_TOKEN_BEFORE } from "./auth"; + +const TokenCacheEntrySchema = z.object({ + token: z.object({ + sub: z.string().optional(), + name: z.string().nullish(), + email: z.string().nullish(), + accessToken: z.string(), + accessTokenExpires: z.number(), + refreshToken: z.string().optional(), + }), + timestamp: z.number(), +}); + +const TokenCacheEntryCodec = z.codec(z.string(), TokenCacheEntrySchema, { + decode: (jsonString) => { + const parsed = JSON.parse(jsonString); + return TokenCacheEntrySchema.parse(parsed); + }, + encode: (value) => JSON.stringify(value), +}); + +export type TokenCacheEntry = z.infer; + +export type KV = { + get(key: string): Promise; + setex(key: string, seconds: number, value: string): Promise<"OK">; + del(key: string): Promise; +}; + +export async function getTokenCache( + redis: KV, + key: string, +): Promise { + const data = await redis.get(key); + if (!data) return null; + + try { + return TokenCacheEntryCodec.decode(data); + } catch (error) { + console.error("Invalid token cache data:", error); + await redis.del(key); + return null; + } +} + +const TTL_SECONDS = 30 * 24 * 60 * 60; + +export async function setTokenCache( + redis: KV, + key: string, + value: TokenCacheEntry, +): Promise { + const encodedValue = TokenCacheEntryCodec.encode(value); + await redis.setex(key, TTL_SECONDS, encodedValue); +} + +export async function deleteTokenCache(redis: KV, key: string): Promise { + await redis.del(key); +} diff --git a/www/app/lib/transcript.ts b/www/app/lib/transcript.ts new file mode 100644 index 00000000..d1fd8b3d --- /dev/null +++ b/www/app/lib/transcript.ts @@ -0,0 +1,5 @@ +import { components } from "../reflector-api"; + +type ApiTranscriptStatus = components["schemas"]["GetTranscript"]["status"]; + +export type TranscriptStatus = ApiTranscriptStatus; diff --git a/www/app/lib/types.ts b/www/app/lib/types.ts index 851ee5be..7bcb522b 100644 --- a/www/app/lib/types.ts +++ b/www/app/lib/types.ts @@ -1,10 +1,11 @@ -import { Session } from "next-auth"; -import { JWT } from "next-auth/jwt"; +import type { Session } from "next-auth"; +import type { JWT } from "next-auth/jwt"; +import { parseMaybeNonEmptyString } from "./utils"; export interface JWTWithAccessToken extends JWT { accessToken: string; accessTokenExpires: number; - refreshToken: string; + refreshToken?: string; error?: string; } @@ -12,9 +13,68 @@ export interface CustomSession extends Session { accessToken: string; accessTokenExpires: number; error?: string; - user: { - id?: string; - name?: string | null; - email?: string | null; + user: Session["user"] & { + id: string; }; } + +// assumption that JWT is JWTWithAccessToken - we set it in jwt callback of auth; typing isn't strong around there +// but the assumption is crucial to auth working +export const assertExtendedToken = ( + t: Exclude, +): T & { + accessTokenExpires: number; + accessToken: string; +} => { + if ( + typeof (t as { accessTokenExpires: any }).accessTokenExpires === "number" && + !isNaN((t as { accessTokenExpires: any }).accessTokenExpires) && + typeof ( + t as { + accessToken: any; + } + ).accessToken === "string" && + parseMaybeNonEmptyString((t as { accessToken: any }).accessToken) !== null + ) { + return t as T & { + accessTokenExpires: number; + accessToken: string; + }; + } + throw new Error("Token is not extended with access token"); +}; + +export const assertExtendedTokenAndUserId = ( + t: Exclude, +): T & { + accessTokenExpires: number; + accessToken: string; + user: U & { + id: string; + }; +} => { + const extendedToken = assertExtendedToken(t); + if (typeof (extendedToken.user as any)?.id === "string") { + return t as Exclude & { + accessTokenExpires: number; + accessToken: string; + user: U & { + id: string; + }; + }; + } + throw new Error("Token is not extended with user id"); +}; + +// best attempt to check the session is valid +export const assertCustomSession = ( + s: Exclude, +): CustomSession => { + const r = assertExtendedTokenAndUserId(s); + // no other checks for now + return r as CustomSession; +}; + +export type Mutable = { + -readonly [P in keyof T]: T[P]; +}; diff --git a/www/app/lib/useApi.ts b/www/app/lib/useApi.ts deleted file mode 100644 index 837ef84f..00000000 --- a/www/app/lib/useApi.ts +++ /dev/null @@ -1,37 +0,0 @@ -import { useSession, signOut } from "next-auth/react"; -import { useContext, useEffect, useState } from "react"; -import { DomainContext, featureEnabled } from "../domainContext"; -import { OpenApi, DefaultService } from "../api"; -import { CustomSession } from "./types"; -import useSessionStatus from "./useSessionStatus"; -import useSessionAccessToken from "./useSessionAccessToken"; - -export default function useApi(): DefaultService | null { - const api_url = useContext(DomainContext).api_url; - const [api, setApi] = useState(null); - const { isLoading, isAuthenticated } = useSessionStatus(); - const { accessToken, error } = useSessionAccessToken(); - - if (!api_url) throw new Error("no API URL"); - - useEffect(() => { - if (error === "RefreshAccessTokenError") { - signOut(); - } - }, [error]); - - useEffect(() => { - if (isLoading || (isAuthenticated && !accessToken)) { - return; - } - - const openApi = new OpenApi({ - BASE: api_url, - TOKEN: accessToken || undefined, - }); - - setApi(openApi); - }, [isLoading, isAuthenticated, accessToken]); - - return api?.default ?? null; -} diff --git a/www/app/lib/useLoginRequiredPages.ts b/www/app/lib/useLoginRequiredPages.ts new file mode 100644 index 00000000..37ee96b1 --- /dev/null +++ b/www/app/lib/useLoginRequiredPages.ts @@ -0,0 +1,26 @@ +// for paths that are not supposed to be public +import { PROTECTED_PAGES } from "./auth"; +import { usePathname } from "next/navigation"; +import { useAuth } from "./AuthProvider"; +import { useEffect } from "react"; + +const HOME = "/" as const; + +export const useLoginRequiredPages = () => { + const pathname = usePathname(); + const isProtected = PROTECTED_PAGES.test(pathname); + const auth = useAuth(); + const isNotLoggedIn = auth.status === "unauthenticated"; + // safety + const isLastDestination = pathname === HOME; + const shouldRedirect = isNotLoggedIn && isProtected && !isLastDestination; + useEffect(() => { + if (!shouldRedirect) return; + // on the backend, the redirect goes straight to the auth provider, but we don't have it because it's hidden inside next-auth middleware + // so we just "softly" lead the user to the main page + // warning: if HOME redirects somewhere else, we won't be protected by isLastDestination + window.location.href = HOME; + }, [shouldRedirect]); + // optionally save from blink, since window.location.href takes a bit of time + return shouldRedirect ? HOME : null; +}; diff --git a/www/app/lib/useSessionAccessToken.ts b/www/app/lib/useSessionAccessToken.ts deleted file mode 100644 index fc28c076..00000000 --- a/www/app/lib/useSessionAccessToken.ts +++ /dev/null @@ -1,42 +0,0 @@ -"use client"; - -import { useState, useEffect } from "react"; -import { useSession as useNextAuthSession } from "next-auth/react"; -import { CustomSession } from "./types"; - -export default function useSessionAccessToken() { - const { data: session } = useNextAuthSession(); - const customSession = session as CustomSession; - const naAccessToken = customSession?.accessToken; - const naAccessTokenExpires = customSession?.accessTokenExpires; - const naError = customSession?.error; - const [accessToken, setAccessToken] = useState(null); - const [accessTokenExpires, setAccessTokenExpires] = useState( - null, - ); - const [error, setError] = useState(); - - useEffect(() => { - if (naAccessToken !== accessToken) { - setAccessToken(naAccessToken); - } - }, [naAccessToken]); - - useEffect(() => { - if (naAccessTokenExpires !== accessTokenExpires) { - setAccessTokenExpires(naAccessTokenExpires); - } - }, [naAccessTokenExpires]); - - useEffect(() => { - if (naError !== error) { - setError(naError); - } - }, [naError]); - - return { - accessToken, - accessTokenExpires, - error, - }; -} diff --git a/www/app/lib/useSessionStatus.ts b/www/app/lib/useSessionStatus.ts deleted file mode 100644 index 5629c025..00000000 --- a/www/app/lib/useSessionStatus.ts +++ /dev/null @@ -1,22 +0,0 @@ -"use client"; - -import { useState, useEffect } from "react"; -import { useSession as useNextAuthSession } from "next-auth/react"; -import { Session } from "next-auth"; - -export default function useSessionStatus() { - const { status: naStatus } = useNextAuthSession(); - const [status, setStatus] = useState("loading"); - - useEffect(() => { - if (naStatus !== "loading" && naStatus !== status) { - setStatus(naStatus); - } - }, [naStatus]); - - return { - status, - isLoading: status === "loading", - isAuthenticated: status === "authenticated", - }; -} diff --git a/www/app/lib/useSessionUser.ts b/www/app/lib/useSessionUser.ts deleted file mode 100644 index 2da299f5..00000000 --- a/www/app/lib/useSessionUser.ts +++ /dev/null @@ -1,33 +0,0 @@ -"use client"; - -import { useState, useEffect } from "react"; -import { useSession as useNextAuthSession } from "next-auth/react"; -import { Session } from "next-auth"; - -// user type with id, name, email -export interface User { - id?: string | null; - name?: string | null; - email?: string | null; -} - -export default function useSessionUser() { - const { data: session } = useNextAuthSession(); - const [user, setUser] = useState(null); - - useEffect(() => { - if (!session?.user) { - setUser(null); - return; - } - if (JSON.stringify(session.user) !== JSON.stringify(user)) { - setUser(session.user); - } - }, [session]); - - return { - id: user?.id, - name: user?.name, - email: user?.email, - }; -} diff --git a/www/app/lib/useUserName.ts b/www/app/lib/useUserName.ts new file mode 100644 index 00000000..46850176 --- /dev/null +++ b/www/app/lib/useUserName.ts @@ -0,0 +1,8 @@ +import { useAuth } from "./AuthProvider"; + +export const useUserName = (): string | null | undefined => { + const auth = useAuth(); + if (auth.status !== "authenticated" && auth.status !== "refreshing") + return undefined; + return auth.user?.name || null; +}; diff --git a/www/app/lib/utils.ts b/www/app/lib/utils.ts index 80d0d91b..11939cdb 100644 --- a/www/app/lib/utils.ts +++ b/www/app/lib/utils.ts @@ -137,9 +137,40 @@ export function extractDomain(url) { } } -export function assertExists(value: T | null | undefined, err?: string): T { +export type NonEmptyString = string & { __brand: "NonEmptyString" }; +export const parseMaybeNonEmptyString = ( + s: string, + trim = true, +): NonEmptyString | null => { + s = trim ? s.trim() : s; + return s.length > 0 ? (s as NonEmptyString) : null; +}; +export const parseNonEmptyString = (s: string, trim = true): NonEmptyString => + assertExists(parseMaybeNonEmptyString(s, trim), "Expected non-empty string"); + +export const assertExists = ( + value: T | null | undefined, + err?: string, +): T => { if (value === null || value === undefined) { throw new Error(`Assertion failed: ${err ?? "value is null or undefined"}`); } return value; -} +}; + +export const assertNotExists = ( + value: T | null | undefined, + err?: string, +): void => { + if (value !== null && value !== undefined) { + throw new Error( + `Assertion failed: ${err ?? "value is not null or undefined"}`, + ); + } +}; + +export const assertExistsAndNonEmptyString = ( + value: string | null | undefined, + err?: string, +): NonEmptyString => + parseNonEmptyString(assertExists(value, err || "Expected non-empty string")); diff --git a/www/app/providers.tsx b/www/app/providers.tsx index f0f1ea52..020112ac 100644 --- a/www/app/providers.tsx +++ b/www/app/providers.tsx @@ -2,20 +2,38 @@ import { ChakraProvider } from "@chakra-ui/react"; import system from "./styles/theme"; +import dynamic from "next/dynamic"; -import { WherebyProvider } from "@whereby.com/browser-sdk/react"; import { Toaster } from "./components/ui/toaster"; import { NuqsAdapter } from "nuqs/adapters/next/app"; +import { QueryClientProvider } from "@tanstack/react-query"; +import { queryClient } from "./lib/queryClient"; +import { AuthProvider } from "./lib/AuthProvider"; +import { SessionProvider as SessionProviderNextAuth } from "next-auth/react"; + +const WherebyProvider = dynamic( + () => + import("@whereby.com/browser-sdk/react").then((mod) => ({ + default: mod.WherebyProvider, + })), + { ssr: false }, +); export function Providers({ children }: { children: React.ReactNode }) { return ( - - - {children} - - - + + + + + + {children} + + + + + + ); } diff --git a/www/app/reflector-api.d.ts b/www/app/reflector-api.d.ts new file mode 100644 index 00000000..a6d524d5 --- /dev/null +++ b/www/app/reflector-api.d.ts @@ -0,0 +1,2535 @@ +/** + * This file was auto-generated by openapi-typescript. + * Do not make direct changes to the file. + */ + +export interface paths { + "/metrics": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * Metrics + * @description Endpoint that serves Prometheus metrics. + */ + get: operations["metrics"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/v1/meetings/{meeting_id}/consent": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** Meeting Audio Consent */ + post: operations["v1_meeting_audio_consent"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/v1/rooms": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** Rooms List */ + get: operations["v1_rooms_list"]; + put?: never; + /** Rooms Create */ + post: operations["v1_rooms_create"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/v1/rooms/{room_id}": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** Rooms Get */ + get: operations["v1_rooms_get"]; + put?: never; + post?: never; + /** Rooms Delete */ + delete: operations["v1_rooms_delete"]; + options?: never; + head?: never; + /** Rooms Update */ + patch: operations["v1_rooms_update"]; + trace?: never; + }; + "/v1/rooms/{room_name}/meeting": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** Rooms Create Meeting */ + post: operations["v1_rooms_create_meeting"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/v1/rooms/{room_id}/webhook/test": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** Rooms Test Webhook */ + post: operations["v1_rooms_test_webhook"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/v1/transcripts": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** Transcripts List */ + get: operations["v1_transcripts_list"]; + put?: never; + /** Transcripts Create */ + post: operations["v1_transcripts_create"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/v1/transcripts/search": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * Transcripts Search + * @description Full-text search across transcript titles and content. + */ + get: operations["v1_transcripts_search"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/v1/transcripts/{transcript_id}": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** Transcript Get */ + get: operations["v1_transcript_get"]; + put?: never; + post?: never; + /** Transcript Delete */ + delete: operations["v1_transcript_delete"]; + options?: never; + head?: never; + /** Transcript Update */ + patch: operations["v1_transcript_update"]; + trace?: never; + }; + "/v1/transcripts/{transcript_id}/topics": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** Transcript Get Topics */ + get: operations["v1_transcript_get_topics"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/v1/transcripts/{transcript_id}/topics/with-words": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** Transcript Get Topics With Words */ + get: operations["v1_transcript_get_topics_with_words"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/v1/transcripts/{transcript_id}/topics/{topic_id}/words-per-speaker": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** Transcript Get Topics With Words Per Speaker */ + get: operations["v1_transcript_get_topics_with_words_per_speaker"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/v1/transcripts/{transcript_id}/zulip": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** Transcript Post To Zulip */ + post: operations["v1_transcript_post_to_zulip"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/v1/transcripts/{transcript_id}/audio/mp3": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** Transcript Get Audio Mp3 */ + get: operations["v1_transcript_get_audio_mp3"]; + put?: never; + post?: never; + delete?: never; + options?: never; + /** Transcript Get Audio Mp3 */ + head: operations["v1_transcript_head_audio_mp3"]; + patch?: never; + trace?: never; + }; + "/v1/transcripts/{transcript_id}/audio/waveform": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** Transcript Get Audio Waveform */ + get: operations["v1_transcript_get_audio_waveform"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/v1/transcripts/{transcript_id}/participants": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** Transcript Get Participants */ + get: operations["v1_transcript_get_participants"]; + put?: never; + /** Transcript Add Participant */ + post: operations["v1_transcript_add_participant"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/v1/transcripts/{transcript_id}/participants/{participant_id}": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** Transcript Get Participant */ + get: operations["v1_transcript_get_participant"]; + put?: never; + post?: never; + /** Transcript Delete Participant */ + delete: operations["v1_transcript_delete_participant"]; + options?: never; + head?: never; + /** Transcript Update Participant */ + patch: operations["v1_transcript_update_participant"]; + trace?: never; + }; + "/v1/transcripts/{transcript_id}/speaker/assign": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + /** Transcript Assign Speaker */ + patch: operations["v1_transcript_assign_speaker"]; + trace?: never; + }; + "/v1/transcripts/{transcript_id}/speaker/merge": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + /** Transcript Merge Speaker */ + patch: operations["v1_transcript_merge_speaker"]; + trace?: never; + }; + "/v1/transcripts/{transcript_id}/record/upload": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** Transcript Record Upload */ + post: operations["v1_transcript_record_upload"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/v1/transcripts/{transcript_id}/events": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** Transcript Get Websocket Events */ + get: operations["v1_transcript_get_websocket_events"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/v1/transcripts/{transcript_id}/record/webrtc": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** Transcript Record Webrtc */ + post: operations["v1_transcript_record_webrtc"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/v1/transcripts/{transcript_id}/process": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** Transcript Process */ + post: operations["v1_transcript_process"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/v1/me": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** User Me */ + get: operations["v1_user_me"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/v1/zulip/streams": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * Zulip Get Streams + * @description Get all Zulip streams. + */ + get: operations["v1_zulip_get_streams"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/v1/zulip/streams/{stream_id}/topics": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * Zulip Get Topics + * @description Get all topics for a specific Zulip stream. + */ + get: operations["v1_zulip_get_topics"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/v1/whereby": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** Whereby Webhook */ + post: operations["v1_whereby_webhook"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/v1/jitsi/events": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** + * Jitsi Events Webhook + * @description Handle Prosody event-sync webhooks from Jitsi Meet. + * + * Expected event types: + * - muc-occupant-joined: participant joined the room + * - muc-occupant-left: participant left the room + * - jibri-recording-on: recording started + * - jibri-recording-off: recording stopped + */ + post: operations["v1_jitsi_events_webhook"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/v1/jibri/recording-complete": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** + * Jibri Recording Complete + * @description Handle Jibri recording completion webhook. + * + * This endpoint is called by the Jibri finalize script when a recording + * is completed and uploaded to storage. + */ + post: operations["v1_jibri_recording_complete"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/v1/jitsi/health": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * Jitsi Health Check + * @description Simple health check endpoint for Jitsi webhook configuration. + */ + get: operations["v1_jitsi_health_check"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; +} +export type webhooks = Record; +export interface components { + schemas: { + /** AudioWaveform */ + AudioWaveform: { + /** Data */ + data: number[]; + }; + /** Body_transcript_record_upload_v1_transcripts__transcript_id__record_upload_post */ + Body_transcript_record_upload_v1_transcripts__transcript_id__record_upload_post: { + /** + * Chunk + * Format: binary + */ + chunk: string; + }; + /** CreateParticipant */ + CreateParticipant: { + /** Speaker */ + speaker?: number | null; + /** Name */ + name: string; + }; + /** CreateRoom */ + CreateRoom: { + /** Name */ + name: string; + /** Zulip Auto Post */ + zulip_auto_post: boolean; + /** Zulip Stream */ + zulip_stream: string; + /** Zulip Topic */ + zulip_topic: string; + /** Is Locked */ + is_locked: boolean; + /** Room Mode */ + room_mode: string; + /** Recording Type */ + recording_type: string; + /** Recording Trigger */ + recording_trigger: string; + /** Is Shared */ + is_shared: boolean; + /** Webhook Url */ + webhook_url: string; + /** Webhook Secret */ + webhook_secret: string; + platform: components["schemas"]["VideoPlatform"]; + }; + /** CreateTranscript */ + CreateTranscript: { + /** Name */ + name: string; + /** + * Source Language + * @default en + */ + source_language: string; + /** + * Target Language + * @default en + */ + target_language: string; + source_kind?: components["schemas"]["SourceKind"] | null; + }; + /** DeletionStatus */ + DeletionStatus: { + /** Status */ + status: string; + }; + /** GetTranscript */ + GetTranscript: { + /** Id */ + id: string; + /** User Id */ + user_id: string | null; + /** Name */ + name: string; + /** + * Status + * @enum {string} + */ + status: + | "idle" + | "uploaded" + | "recording" + | "processing" + | "error" + | "ended"; + /** Locked */ + locked: boolean; + /** Duration */ + duration: number; + /** Title */ + title: string | null; + /** Short Summary */ + short_summary: string | null; + /** Long Summary */ + long_summary: string | null; + /** Created At */ + created_at: string; + /** + * Share Mode + * @default private + */ + share_mode: string; + /** Source Language */ + source_language: string | null; + /** Target Language */ + target_language: string | null; + /** Reviewed */ + reviewed: boolean; + /** Meeting Id */ + meeting_id: string | null; + source_kind: components["schemas"]["SourceKind"]; + /** Room Id */ + room_id?: string | null; + /** Room Name */ + room_name?: string | null; + /** Audio Deleted */ + audio_deleted?: boolean | null; + /** Participants */ + participants: components["schemas"]["TranscriptParticipant"][] | null; + }; + /** GetTranscriptMinimal */ + GetTranscriptMinimal: { + /** Id */ + id: string; + /** User Id */ + user_id: string | null; + /** Name */ + name: string; + /** + * Status + * @enum {string} + */ + status: + | "idle" + | "uploaded" + | "recording" + | "processing" + | "error" + | "ended"; + /** Locked */ + locked: boolean; + /** Duration */ + duration: number; + /** Title */ + title: string | null; + /** Short Summary */ + short_summary: string | null; + /** Long Summary */ + long_summary: string | null; + /** Created At */ + created_at: string; + /** + * Share Mode + * @default private + */ + share_mode: string; + /** Source Language */ + source_language: string | null; + /** Target Language */ + target_language: string | null; + /** Reviewed */ + reviewed: boolean; + /** Meeting Id */ + meeting_id: string | null; + source_kind: components["schemas"]["SourceKind"]; + /** Room Id */ + room_id?: string | null; + /** Room Name */ + room_name?: string | null; + /** Audio Deleted */ + audio_deleted?: boolean | null; + }; + /** GetTranscriptSegmentTopic */ + GetTranscriptSegmentTopic: { + /** Text */ + text: string; + /** Start */ + start: number; + /** Speaker */ + speaker: number; + }; + /** GetTranscriptTopic */ + GetTranscriptTopic: { + /** Id */ + id: string; + /** Title */ + title: string; + /** Summary */ + summary: string; + /** Timestamp */ + timestamp: number; + /** Duration */ + duration: number | null; + /** Transcript */ + transcript: string; + /** + * Segments + * @default [] + */ + segments: components["schemas"]["GetTranscriptSegmentTopic"][]; + }; + /** GetTranscriptTopicWithWords */ + GetTranscriptTopicWithWords: { + /** Id */ + id: string; + /** Title */ + title: string; + /** Summary */ + summary: string; + /** Timestamp */ + timestamp: number; + /** Duration */ + duration: number | null; + /** Transcript */ + transcript: string; + /** + * Segments + * @default [] + */ + segments: components["schemas"]["GetTranscriptSegmentTopic"][]; + /** + * Words + * @default [] + */ + words: components["schemas"]["Word"][]; + }; + /** GetTranscriptTopicWithWordsPerSpeaker */ + GetTranscriptTopicWithWordsPerSpeaker: { + /** Id */ + id: string; + /** Title */ + title: string; + /** Summary */ + summary: string; + /** Timestamp */ + timestamp: number; + /** Duration */ + duration: number | null; + /** Transcript */ + transcript: string; + /** + * Segments + * @default [] + */ + segments: components["schemas"]["GetTranscriptSegmentTopic"][]; + /** + * Words Per Speaker + * @default [] + */ + words_per_speaker: components["schemas"]["SpeakerWords"][]; + }; + /** HTTPValidationError */ + HTTPValidationError: { + /** Detail */ + detail?: components["schemas"]["ValidationError"][]; + }; + /** JibriRecordingEvent */ + JibriRecordingEvent: { + /** Room Name */ + room_name: string; + /** Recording File */ + recording_file: string; + /** Recording Status */ + recording_status: string; + /** + * Timestamp + * Format: date-time + */ + timestamp: string; + }; + /** JitsiWebhookEvent */ + JitsiWebhookEvent: { + /** Event */ + event: string; + /** Room */ + room: string; + /** + * Timestamp + * Format: date-time + */ + timestamp: string; + /** + * Data + * @default {} + */ + data: { + [key: string]: unknown; + }; + }; + /** Meeting */ + Meeting: { + /** Id */ + id: string; + /** Room Name */ + room_name: string; + /** Room Url */ + room_url: string; + /** Host Room Url */ + host_room_url: string; + /** + * Start Date + * Format: date-time + */ + start_date: string; + /** + * End Date + * Format: date-time + */ + end_date: string; + /** + * Recording Type + * @default cloud + * @enum {string} + */ + recording_type: "none" | "local" | "cloud"; + }; + /** MeetingConsentRequest */ + MeetingConsentRequest: { + /** Consent Given */ + consent_given: boolean; + }; + /** Page[GetTranscriptMinimal] */ + Page_GetTranscriptMinimal_: { + /** Items */ + items: components["schemas"]["GetTranscriptMinimal"][]; + /** Total */ + total?: number | null; + /** Page */ + page: number | null; + /** Size */ + size: number | null; + /** Pages */ + pages?: number | null; + }; + /** Page[RoomDetails] */ + Page_RoomDetails_: { + /** Items */ + items: components["schemas"]["RoomDetails"][]; + /** Total */ + total?: number | null; + /** Page */ + page: number | null; + /** Size */ + size: number | null; + /** Pages */ + pages?: number | null; + }; + /** Participant */ + Participant: { + /** Id */ + id: string; + /** Speaker */ + speaker: number | null; + /** Name */ + name: string; + }; + /** Room */ + Room: { + /** Id */ + id: string; + /** Name */ + name: string; + /** User Id */ + user_id: string; + /** + * Created At + * Format: date-time + */ + created_at: string; + /** Zulip Auto Post */ + zulip_auto_post: boolean; + /** Zulip Stream */ + zulip_stream: string; + /** Zulip Topic */ + zulip_topic: string; + /** Is Locked */ + is_locked: boolean; + /** Room Mode */ + room_mode: string; + /** Recording Type */ + recording_type: string; + /** Recording Trigger */ + recording_trigger: string; + /** Is Shared */ + is_shared: boolean; + /** @default whereby */ + platform: components["schemas"]["VideoPlatform"]; + }; + /** RoomDetails */ + RoomDetails: { + /** Id */ + id: string; + /** Name */ + name: string; + /** User Id */ + user_id: string; + /** + * Created At + * Format: date-time + */ + created_at: string; + /** Zulip Auto Post */ + zulip_auto_post: boolean; + /** Zulip Stream */ + zulip_stream: string; + /** Zulip Topic */ + zulip_topic: string; + /** Is Locked */ + is_locked: boolean; + /** Room Mode */ + room_mode: string; + /** Recording Type */ + recording_type: string; + /** Recording Trigger */ + recording_trigger: string; + /** Is Shared */ + is_shared: boolean; + /** @default whereby */ + platform: components["schemas"]["VideoPlatform"]; + /** Webhook Url */ + webhook_url: string | null; + /** Webhook Secret */ + webhook_secret: string | null; + }; + /** RtcOffer */ + RtcOffer: { + /** Sdp */ + sdp: string; + /** Type */ + type: string; + }; + /** SearchResponse */ + SearchResponse: { + /** Results */ + results: components["schemas"]["SearchResult"][]; + /** + * Total + * @description Total number of search results + */ + total: number; + /** Query */ + query?: string | null; + /** + * Limit + * @description Results per page + */ + limit: number; + /** + * Offset + * @description Number of results to skip + */ + offset: number; + }; + /** + * SearchResult + * @description Public search result model with computed fields. + */ + SearchResult: { + /** Id */ + id: string; + /** Title */ + title?: string | null; + /** User Id */ + user_id?: string | null; + /** Room Id */ + room_id?: string | null; + /** Room Name */ + room_name?: string | null; + source_kind: components["schemas"]["SourceKind"]; + /** Created At */ + created_at: string; + /** + * Status + * @enum {string} + */ + status: + | "idle" + | "uploaded" + | "recording" + | "processing" + | "error" + | "ended"; + /** Rank */ + rank: number; + /** + * Duration + * @description Duration in seconds + */ + duration: number | null; + /** + * Search Snippets + * @description Text snippets around search matches + */ + search_snippets: string[]; + /** + * Total Match Count + * @description Total number of matches found in the transcript + * @default 0 + */ + total_match_count: number; + }; + /** + * SourceKind + * @enum {string} + */ + SourceKind: "room" | "live" | "file"; + /** SpeakerAssignment */ + SpeakerAssignment: { + /** Speaker */ + speaker?: number | null; + /** Participant */ + participant?: string | null; + /** Timestamp From */ + timestamp_from: number; + /** Timestamp To */ + timestamp_to: number; + }; + /** SpeakerAssignmentStatus */ + SpeakerAssignmentStatus: { + /** Status */ + status: string; + }; + /** SpeakerMerge */ + SpeakerMerge: { + /** Speaker From */ + speaker_from: number; + /** Speaker To */ + speaker_to: number; + }; + /** SpeakerWords */ + SpeakerWords: { + /** Speaker */ + speaker: number; + /** Words */ + words: components["schemas"]["Word"][]; + }; + /** Stream */ + Stream: { + /** Stream Id */ + stream_id: number; + /** Name */ + name: string; + }; + /** Topic */ + Topic: { + /** Name */ + name: string; + }; + /** TranscriptParticipant */ + TranscriptParticipant: { + /** Id */ + id?: string; + /** Speaker */ + speaker: number | null; + /** Name */ + name: string; + }; + /** UpdateParticipant */ + UpdateParticipant: { + /** Speaker */ + speaker?: number | null; + /** Name */ + name?: string | null; + }; + /** UpdateRoom */ + UpdateRoom: { + /** Name */ + name: string; + /** Zulip Auto Post */ + zulip_auto_post: boolean; + /** Zulip Stream */ + zulip_stream: string; + /** Zulip Topic */ + zulip_topic: string; + /** Is Locked */ + is_locked: boolean; + /** Room Mode */ + room_mode: string; + /** Recording Type */ + recording_type: string; + /** Recording Trigger */ + recording_trigger: string; + /** Is Shared */ + is_shared: boolean; + /** Webhook Url */ + webhook_url: string; + /** Webhook Secret */ + webhook_secret: string; + platform: components["schemas"]["VideoPlatform"]; + }; + /** UpdateTranscript */ + UpdateTranscript: { + /** Name */ + name?: string | null; + /** Locked */ + locked?: boolean | null; + /** Title */ + title?: string | null; + /** Short Summary */ + short_summary?: string | null; + /** Long Summary */ + long_summary?: string | null; + /** Share Mode */ + share_mode?: ("public" | "semi-private" | "private") | null; + /** Participants */ + participants?: components["schemas"]["TranscriptParticipant"][] | null; + /** Reviewed */ + reviewed?: boolean | null; + /** Audio Deleted */ + audio_deleted?: boolean | null; + }; + /** UserInfo */ + UserInfo: { + /** Sub */ + sub: string; + /** Email */ + email: string | null; + /** Email Verified */ + email_verified: boolean | null; + }; + /** ValidationError */ + ValidationError: { + /** Location */ + loc: (string | number)[]; + /** Message */ + msg: string; + /** Error Type */ + type: string; + }; + /** + * VideoPlatform + * @enum {string} + */ + VideoPlatform: "whereby" | "jitsi"; + /** WebhookTestResult */ + WebhookTestResult: { + /** Success */ + success: boolean; + /** + * Message + * @default + */ + message: string; + /** + * Error + * @default + */ + error: string; + /** Status Code */ + status_code?: number | null; + /** Response Preview */ + response_preview?: string | null; + }; + /** WherebyWebhookEvent */ + WherebyWebhookEvent: { + /** Apiversion */ + apiVersion: string; + /** Id */ + id: string; + /** + * Createdat + * Format: date-time + */ + createdAt: string; + /** Type */ + type: string; + /** Data */ + data: { + [key: string]: unknown; + }; + }; + /** Word */ + Word: { + /** Text */ + text: string; + /** + * Start + * @description Time in seconds with float part + */ + start: number; + /** + * End + * @description Time in seconds with float part + */ + end: number; + /** + * Speaker + * @default 0 + */ + speaker: number; + }; + }; + responses: never; + parameters: never; + requestBodies: never; + headers: never; + pathItems: never; +} +export type $defs = Record; +export interface operations { + metrics: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + }; + }; + v1_meeting_audio_consent: { + parameters: { + query?: never; + header?: never; + path: { + meeting_id: string; + }; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["MeetingConsentRequest"]; + }; + }; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + v1_rooms_list: { + parameters: { + query?: { + /** @description Page number */ + page?: number; + /** @description Page size */ + size?: number; + }; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Page_RoomDetails_"]; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + v1_rooms_create: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["CreateRoom"]; + }; + }; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Room"]; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + v1_rooms_get: { + parameters: { + query?: never; + header?: never; + path: { + room_id: string; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["RoomDetails"]; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + v1_rooms_delete: { + parameters: { + query?: never; + header?: never; + path: { + room_id: string; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["DeletionStatus"]; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + v1_rooms_update: { + parameters: { + query?: never; + header?: never; + path: { + room_id: string; + }; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["UpdateRoom"]; + }; + }; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["RoomDetails"]; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + v1_rooms_create_meeting: { + parameters: { + query?: never; + header?: never; + path: { + room_name: string; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Meeting"]; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + v1_rooms_test_webhook: { + parameters: { + query?: never; + header?: never; + path: { + room_id: string; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["WebhookTestResult"]; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + v1_transcripts_list: { + parameters: { + query?: { + source_kind?: components["schemas"]["SourceKind"] | null; + room_id?: string | null; + search_term?: string | null; + /** @description Page number */ + page?: number; + /** @description Page size */ + size?: number; + }; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Page_GetTranscriptMinimal_"]; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + v1_transcripts_create: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["CreateTranscript"]; + }; + }; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["GetTranscript"]; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + v1_transcripts_search: { + parameters: { + query: { + /** @description Search query text */ + q: string; + /** @description Results per page */ + limit?: number; + /** @description Number of results to skip */ + offset?: number; + room_id?: string | null; + source_kind?: components["schemas"]["SourceKind"] | null; + }; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["SearchResponse"]; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + v1_transcript_get: { + parameters: { + query?: never; + header?: never; + path: { + transcript_id: string; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["GetTranscript"]; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + v1_transcript_delete: { + parameters: { + query?: never; + header?: never; + path: { + transcript_id: string; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["DeletionStatus"]; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + v1_transcript_update: { + parameters: { + query?: never; + header?: never; + path: { + transcript_id: string; + }; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["UpdateTranscript"]; + }; + }; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["GetTranscript"]; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + v1_transcript_get_topics: { + parameters: { + query?: never; + header?: never; + path: { + transcript_id: string; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["GetTranscriptTopic"][]; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + v1_transcript_get_topics_with_words: { + parameters: { + query?: never; + header?: never; + path: { + transcript_id: string; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["GetTranscriptTopicWithWords"][]; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + v1_transcript_get_topics_with_words_per_speaker: { + parameters: { + query?: never; + header?: never; + path: { + transcript_id: string; + topic_id: string; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["GetTranscriptTopicWithWordsPerSpeaker"]; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + v1_transcript_post_to_zulip: { + parameters: { + query: { + stream: string; + topic: string; + include_topics: boolean; + }; + header?: never; + path: { + transcript_id: string; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + v1_transcript_get_audio_mp3: { + parameters: { + query?: { + token?: string | null; + }; + header?: never; + path: { + transcript_id: string; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + v1_transcript_head_audio_mp3: { + parameters: { + query?: { + token?: string | null; + }; + header?: never; + path: { + transcript_id: string; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + v1_transcript_get_audio_waveform: { + parameters: { + query?: never; + header?: never; + path: { + transcript_id: string; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["AudioWaveform"]; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + v1_transcript_get_participants: { + parameters: { + query?: never; + header?: never; + path: { + transcript_id: string; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Participant"][]; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + v1_transcript_add_participant: { + parameters: { + query?: never; + header?: never; + path: { + transcript_id: string; + }; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["CreateParticipant"]; + }; + }; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Participant"]; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + v1_transcript_get_participant: { + parameters: { + query?: never; + header?: never; + path: { + transcript_id: string; + participant_id: string; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Participant"]; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + v1_transcript_delete_participant: { + parameters: { + query?: never; + header?: never; + path: { + transcript_id: string; + participant_id: string; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["DeletionStatus"]; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + v1_transcript_update_participant: { + parameters: { + query?: never; + header?: never; + path: { + transcript_id: string; + participant_id: string; + }; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["UpdateParticipant"]; + }; + }; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Participant"]; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + v1_transcript_assign_speaker: { + parameters: { + query?: never; + header?: never; + path: { + transcript_id: string; + }; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["SpeakerAssignment"]; + }; + }; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["SpeakerAssignmentStatus"]; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + v1_transcript_merge_speaker: { + parameters: { + query?: never; + header?: never; + path: { + transcript_id: string; + }; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["SpeakerMerge"]; + }; + }; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["SpeakerAssignmentStatus"]; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + v1_transcript_record_upload: { + parameters: { + query: { + chunk_number: number; + total_chunks: number; + }; + header?: never; + path: { + transcript_id: string; + }; + cookie?: never; + }; + requestBody: { + content: { + "multipart/form-data": components["schemas"]["Body_transcript_record_upload_v1_transcripts__transcript_id__record_upload_post"]; + }; + }; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + v1_transcript_get_websocket_events: { + parameters: { + query?: never; + header?: never; + path: { + transcript_id: string; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + v1_transcript_record_webrtc: { + parameters: { + query?: never; + header?: never; + path: { + transcript_id: string; + }; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["RtcOffer"]; + }; + }; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + v1_transcript_process: { + parameters: { + query?: never; + header?: never; + path: { + transcript_id: string; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + v1_user_me: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["UserInfo"] | null; + }; + }; + }; + }; + v1_zulip_get_streams: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Stream"][]; + }; + }; + }; + }; + v1_zulip_get_topics: { + parameters: { + query?: never; + header?: never; + path: { + stream_id: number; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Topic"][]; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + v1_whereby_webhook: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["WherebyWebhookEvent"]; + }; + }; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + v1_jitsi_events_webhook: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["JitsiWebhookEvent"]; + }; + }; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + v1_jibri_recording_complete: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["JibriRecordingEvent"]; + }; + }; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + v1_jitsi_health_check: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + }; + }; +} diff --git a/www/app/webinars/[title]/page.tsx b/www/app/webinars/[title]/page.tsx index ab873a6b..51583a2a 100644 --- a/www/app/webinars/[title]/page.tsx +++ b/www/app/webinars/[title]/page.tsx @@ -1,5 +1,5 @@ "use client"; -import { useEffect, useState } from "react"; +import { useEffect, useState, use } from "react"; import Link from "next/link"; import Image from "next/image"; import { notFound } from "next/navigation"; @@ -30,9 +30,9 @@ const FORM_FIELDS = { }; export type WebinarDetails = { - params: { + params: Promise<{ title: string; - }; + }>; }; export type Webinar = { @@ -63,7 +63,8 @@ const WEBINARS: Webinar[] = [ ]; export default function WebinarPage(details: WebinarDetails) { - const title = details.params.title; + const params = use(details.params); + const title = params.title; const webinar = WEBINARS.find((webinar) => webinar.title === title); if (!webinar) { return notFound(); diff --git a/www/config-template.ts b/www/config-template.ts deleted file mode 100644 index 667b2b64..00000000 --- a/www/config-template.ts +++ /dev/null @@ -1,18 +0,0 @@ -export const localConfig = { - features: { - requireLogin: true, - privacy: true, - browse: true, - sendToZulip: true, - rooms: true, - }, - api_url: "http://127.0.0.1:1250", - websocket_url: "ws://127.0.0.1:1250", - auth_callback_url: "http://localhost:3000/auth-callback", - zulip_streams: "", // Find the value on zulip - // Video platform configuration - set via NEXT_PUBLIC_VIDEO_PLATFORM env variable - // Options: "whereby" | "jitsi" - video_platform: - (process.env.NEXT_PUBLIC_VIDEO_PLATFORM as "whereby" | "jitsi") || - "whereby", -}; diff --git a/www/sentry.client.config.ts b/www/instrumentation-client.ts similarity index 91% rename from www/sentry.client.config.ts rename to www/instrumentation-client.ts index aff65bbd..5ea5e2e9 100644 --- a/www/sentry.client.config.ts +++ b/www/instrumentation-client.ts @@ -23,3 +23,5 @@ if (SENTRY_DSN) { replaysSessionSampleRate: 0.0, }); } + +export const onRouterTransitionStart = Sentry.captureRouterTransitionStart; diff --git a/www/instrumentation.ts b/www/instrumentation.ts new file mode 100644 index 00000000..f8a929ba --- /dev/null +++ b/www/instrumentation.ts @@ -0,0 +1,9 @@ +export async function register() { + if (process.env.NEXT_RUNTIME === "nodejs") { + await import("./sentry.server.config"); + } + + if (process.env.NEXT_RUNTIME === "edge") { + await import("./sentry.edge.config"); + } +} diff --git a/www/jest.config.js b/www/jest.config.js new file mode 100644 index 00000000..d2f3247b --- /dev/null +++ b/www/jest.config.js @@ -0,0 +1,8 @@ +module.exports = { + preset: "ts-jest", + testEnvironment: "node", + roots: ["/app"], + testMatch: ["**/__tests__/**/*.test.ts"], + collectCoverage: true, + collectCoverageFrom: ["app/**/*.ts", "!app/**/*.d.ts"], +}; diff --git a/www/middleware.ts b/www/middleware.ts index 39145220..7f487cd2 100644 --- a/www/middleware.ts +++ b/www/middleware.ts @@ -1,16 +1,7 @@ import { withAuth } from "next-auth/middleware"; -import { getConfig } from "./app/lib/edgeConfig"; +import { featureEnabled } from "./app/lib/features"; import { NextResponse } from "next/server"; - -const LOGIN_REQUIRED_PAGES = [ - "/transcripts/[!new]", - "/browse(.*)", - "/rooms(.*)", -]; - -const PROTECTED_PAGES = new RegExp( - LOGIN_REQUIRED_PAGES.map((page) => `^${page}$`).join("|"), -); +import { PROTECTED_PAGES } from "./app/lib/auth"; export const config = { matcher: [ @@ -28,13 +19,12 @@ export const config = { export default withAuth( async function middleware(request) { - const config = await getConfig(); const pathname = request.nextUrl.pathname; // feature-flags protected paths if ( - (!config.features.browse && pathname.startsWith("/browse")) || - (!config.features.rooms && pathname.startsWith("/rooms")) + (!featureEnabled("browse") && pathname.startsWith("/browse")) || + (!featureEnabled("rooms") && pathname.startsWith("/rooms")) ) { return NextResponse.redirect(request.nextUrl.origin); } @@ -42,10 +32,8 @@ export default withAuth( { callbacks: { async authorized({ req, token }) { - const config = await getConfig(); - if ( - config.features.requireLogin && + featureEnabled("requireLogin") && PROTECTED_PAGES.test(req.nextUrl.pathname) ) { return !!token; diff --git a/www/next.config.js b/www/next.config.js index e37d5402..eedbac7f 100644 --- a/www/next.config.js +++ b/www/next.config.js @@ -1,7 +1,9 @@ /** @type {import('next').NextConfig} */ const nextConfig = { output: "standalone", - experimental: { esmExternals: "loose" }, + env: { + IS_CI: process.env.IS_CI, + }, }; module.exports = nextConfig; diff --git a/www/openapi-ts.config.ts b/www/openapi-ts.config.ts deleted file mode 100644 index 9304b8f7..00000000 --- a/www/openapi-ts.config.ts +++ /dev/null @@ -1,14 +0,0 @@ -import { defineConfig } from "@hey-api/openapi-ts"; - -export default defineConfig({ - client: "axios", - name: "OpenApi", - input: "http://127.0.0.1:1250/openapi.json", - output: { - path: "./app/api", - format: "prettier", - }, - services: { - asClass: true, - }, -}); diff --git a/www/package.json b/www/package.json index 482a29f6..d53c1536 100644 --- a/www/package.json +++ b/www/package.json @@ -8,7 +8,8 @@ "start": "next start", "lint": "next lint", "format": "prettier --write .", - "openapi": "openapi-ts" + "openapi": "openapi-typescript http://127.0.0.1:1250/openapi.json -o ./app/reflector-api.d.ts", + "test": "jest" }, "dependencies": { "@chakra-ui/react": "^3.24.2", @@ -16,22 +17,24 @@ "@fortawesome/fontawesome-svg-core": "^6.4.0", "@fortawesome/free-solid-svg-icons": "^6.4.0", "@fortawesome/react-fontawesome": "^0.2.0", - "@sentry/nextjs": "^7.77.0", - "@vercel/edge-config": "^0.4.1", - "@vercel/kv": "^2.0.0", + "@sentry/nextjs": "^10.11.0", + "@tanstack/react-query": "^5.85.9", + "@types/ioredis": "^5.0.0", "@whereby.com/browser-sdk": "^3.3.4", "autoprefixer": "10.4.20", "axios": "^1.8.2", "eslint": "^9.33.0", - "eslint-config-next": "^14.2.31", + "eslint-config-next": "^15.5.3", "fontawesome": "^5.6.3", - "ioredis": "^5.4.1", + "ioredis": "^5.7.0", "jest-worker": "^29.6.2", "lucide-react": "^0.525.0", - "next": "^14.2.30", + "next": "^15.5.3", "next-auth": "^4.24.7", "next-themes": "^0.4.6", "nuqs": "^2.4.3", + "openapi-fetch": "^0.14.0", + "openapi-react-query": "^0.5.0", "postcss": "8.4.31", "prop-types": "^15.8.1", "react": "^18.2.0", @@ -41,22 +44,25 @@ "react-markdown": "^9.0.0", "react-qr-code": "^2.0.12", "react-select-search": "^4.1.7", - "redlock": "^5.0.0-beta.2", + "redlock": "5.0.0-beta.2", "sass": "^1.63.6", "simple-peer": "^9.11.1", "tailwindcss": "^3.3.2", "typescript": "^5.1.6", - "wavesurfer.js": "^7.4.2" + "wavesurfer.js": "^7.4.2", + "zod": "^4.1.5" }, "main": "index.js", "repository": "https://github.com/Monadical-SAS/reflector-ui.git", "author": "Andreas ", "license": "All Rights Reserved", "devDependencies": { - "@hey-api/openapi-ts": "^0.48.0", + "@types/jest": "^30.0.0", "@types/react": "18.2.20", + "jest": "^30.1.3", + "openapi-typescript": "^7.9.1", "prettier": "^3.0.0", - "vercel": "^37.3.0" + "ts-jest": "^29.4.1" }, "packageManager": "pnpm@10.14.0+sha512.ad27a79641b49c3e481a16a805baa71817a04bbe06a38d17e60e2eaee83f6a146c6a688125f5792e48dd5ba30e7da52a5cda4c3992b9ccf333f9ce223af84748" } diff --git a/www/pnpm-lock.yaml b/www/pnpm-lock.yaml index 55aef9c8..a4e78972 100644 --- a/www/pnpm-lock.yaml +++ b/www/pnpm-lock.yaml @@ -23,14 +23,14 @@ importers: specifier: ^0.2.0 version: 0.2.3(@fortawesome/fontawesome-svg-core@6.7.2)(react@18.3.1) "@sentry/nextjs": - specifier: ^7.77.0 - version: 7.120.4(next@14.2.31(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.90.0))(react@18.3.1) - "@vercel/edge-config": - specifier: ^0.4.1 - version: 0.4.1 - "@vercel/kv": - specifier: ^2.0.0 - version: 2.0.0 + specifier: ^10.11.0 + version: 10.11.0(@opentelemetry/context-async-hooks@2.1.0(@opentelemetry/api@1.9.0))(@opentelemetry/core@2.1.0(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@2.1.0(@opentelemetry/api@1.9.0))(next@15.5.3(@babel/core@7.28.3)(@opentelemetry/api@1.9.0)(babel-plugin-macros@3.1.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.90.0))(react@18.3.1)(webpack@5.101.3) + "@tanstack/react-query": + specifier: ^5.85.9 + version: 5.85.9(react@18.3.1) + "@types/ioredis": + specifier: ^5.0.0 + version: 5.0.0 "@whereby.com/browser-sdk": specifier: ^3.3.4 version: 3.13.1(@types/react@18.2.20)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) @@ -44,13 +44,13 @@ importers: specifier: ^9.33.0 version: 9.33.0(jiti@1.21.7) eslint-config-next: - specifier: ^14.2.31 - version: 14.2.31(eslint@9.33.0(jiti@1.21.7))(typescript@5.9.2) + specifier: ^15.5.3 + version: 15.5.3(eslint@9.33.0(jiti@1.21.7))(typescript@5.9.2) fontawesome: specifier: ^5.6.3 version: 5.6.3 ioredis: - specifier: ^5.4.1 + specifier: ^5.7.0 version: 5.7.0 jest-worker: specifier: ^29.6.2 @@ -59,17 +59,23 @@ importers: specifier: ^0.525.0 version: 0.525.0(react@18.3.1) next: - specifier: ^14.2.30 - version: 14.2.31(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.90.0) + specifier: ^15.5.3 + version: 15.5.3(@babel/core@7.28.3)(@opentelemetry/api@1.9.0)(babel-plugin-macros@3.1.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.90.0) next-auth: specifier: ^4.24.7 - version: 4.24.11(next@14.2.31(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.90.0))(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + version: 4.24.11(next@15.5.3(@babel/core@7.28.3)(@opentelemetry/api@1.9.0)(babel-plugin-macros@3.1.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.90.0))(react-dom@18.3.1(react@18.3.1))(react@18.3.1) next-themes: specifier: ^0.4.6 version: 0.4.6(react-dom@18.3.1(react@18.3.1))(react@18.3.1) nuqs: specifier: ^2.4.3 - version: 2.4.3(next@14.2.31(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.90.0))(react@18.3.1) + version: 2.4.3(next@15.5.3(@babel/core@7.28.3)(@opentelemetry/api@1.9.0)(babel-plugin-macros@3.1.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.90.0))(react@18.3.1) + openapi-fetch: + specifier: ^0.14.0 + version: 0.14.0 + openapi-react-query: + specifier: ^0.5.0 + version: 0.5.0(@tanstack/react-query@5.85.9(react@18.3.1))(openapi-fetch@0.14.0) postcss: specifier: 8.4.31 version: 8.4.31 @@ -98,7 +104,7 @@ importers: specifier: ^4.1.7 version: 4.1.8(prop-types@15.8.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) redlock: - specifier: ^5.0.0-beta.2 + specifier: 5.0.0-beta.2 version: 5.0.0-beta.2 sass: specifier: ^1.63.6 @@ -108,26 +114,35 @@ importers: version: 9.11.1 tailwindcss: specifier: ^3.3.2 - version: 3.4.17(ts-node@10.9.1(@types/node@16.18.11)(typescript@5.9.2)) + version: 3.4.17(ts-node@10.9.1(@types/node@24.2.1)(typescript@5.9.2)) typescript: specifier: ^5.1.6 version: 5.9.2 wavesurfer.js: specifier: ^7.4.2 version: 7.10.1 + zod: + specifier: ^4.1.5 + version: 4.1.5 devDependencies: - "@hey-api/openapi-ts": - specifier: ^0.48.0 - version: 0.48.3(typescript@5.9.2) + "@types/jest": + specifier: ^30.0.0 + version: 30.0.0 "@types/react": specifier: 18.2.20 version: 18.2.20 + jest: + specifier: ^30.1.3 + version: 30.1.3(@types/node@24.2.1)(babel-plugin-macros@3.1.0)(ts-node@10.9.1(@types/node@24.2.1)(typescript@5.9.2)) + openapi-typescript: + specifier: ^7.9.1 + version: 7.9.1(typescript@5.9.2) prettier: specifier: ^3.0.0 version: 3.6.2 - vercel: - specifier: ^37.3.0 - version: 37.14.0 + ts-jest: + specifier: ^29.4.1 + version: 29.4.1(@babel/core@7.28.3)(@jest/transform@30.1.2)(@jest/types@30.0.5)(babel-jest@30.1.2(@babel/core@7.28.3))(jest-util@30.0.5)(jest@30.1.3(@types/node@24.2.1)(babel-plugin-macros@3.1.0)(ts-node@10.9.1(@types/node@24.2.1)(typescript@5.9.2)))(typescript@5.9.2) packages: "@alloc/quick-lru@5.2.0": @@ -137,12 +152,12 @@ packages: } engines: { node: ">=10" } - "@apidevtools/json-schema-ref-parser@11.6.4": + "@ampproject/remapping@2.3.0": resolution: { - integrity: sha512-9K6xOqeevacvweLGik6LnZCb1fBtCOSIWQs8d096XGeqoLKC33UVMGz9+77Gw44KvbH4pKcQPWo4ZpxkXYj05w==, + integrity: sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==, } - engines: { node: ">= 16" } + engines: { node: ">=6.0.0" } "@ark-ui/react@5.18.2": resolution: @@ -160,6 +175,20 @@ packages: } engines: { node: ">=6.9.0" } + "@babel/compat-data@7.28.0": + resolution: + { + integrity: sha512-60X7qkglvrap8mn1lh2ebxXdZYtUcpd7gsmy9kLaBJ4i/WdY8PqTSdxyA8qraikqKQK5C1KRBKXqznrVapyNaw==, + } + engines: { node: ">=6.9.0" } + + "@babel/core@7.28.3": + resolution: + { + integrity: sha512-yDBHV9kQNcr2/sUr9jghVyz9C3Y5G2zUM2H2lo+9mKv4sFgbA8s8Z9t8D1jiTkGoO/NoIfKMyKWr4s6CN23ZwQ==, + } + engines: { node: ">=6.9.0" } + "@babel/generator@7.28.0": resolution: { @@ -167,6 +196,20 @@ packages: } engines: { node: ">=6.9.0" } + "@babel/generator@7.28.3": + resolution: + { + integrity: sha512-3lSpxGgvnmZznmBkCRnVREPUFJv2wrv9iAoFDvADJc0ypmdOxdUtcLeBgBJ6zE0PMeTKnxeQzyk0xTBq4Ep7zw==, + } + engines: { node: ">=6.9.0" } + + "@babel/helper-compilation-targets@7.27.2": + resolution: + { + integrity: sha512-2+1thGUUWWjLTYTHZWK1n8Yga0ijBz1XAhUXcKy81rd5g6yh7hGqMp45v7cadSbEHc9G3OTv45SyneRN3ps4DQ==, + } + engines: { node: ">=6.9.0" } + "@babel/helper-globals@7.28.0": resolution: { @@ -181,6 +224,22 @@ packages: } engines: { node: ">=6.9.0" } + "@babel/helper-module-transforms@7.28.3": + resolution: + { + integrity: sha512-gytXUbs8k2sXS9PnQptz5o0QnpLL51SwASIORY6XaBKF88nsOT0Zw9szLqlSGQDP/4TljBAD5y98p2U1fqkdsw==, + } + engines: { node: ">=6.9.0" } + peerDependencies: + "@babel/core": ^7.0.0 + + "@babel/helper-plugin-utils@7.27.1": + resolution: + { + integrity: sha512-1gn1Up5YXka3YYAHGKpbideQ5Yjf1tDa9qYcgysz+cNCXukyLl6DjPXhD3VRwSb8c0J9tA4b2+rHEZtc6R0tlw==, + } + engines: { node: ">=6.9.0" } + "@babel/helper-string-parser@7.27.1": resolution: { @@ -195,6 +254,20 @@ packages: } engines: { node: ">=6.9.0" } + "@babel/helper-validator-option@7.27.1": + resolution: + { + integrity: sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==, + } + engines: { node: ">=6.9.0" } + + "@babel/helpers@7.28.3": + resolution: + { + integrity: sha512-PTNtvUQihsAsDHMOP5pfobP8C6CM4JWXmP8DrEIt46c3r2bf87Ua1zoqevsMo9g+tWDwgWrFP5EIxuBx5RudAw==, + } + engines: { node: ">=6.9.0" } + "@babel/parser@7.28.0": resolution: { @@ -203,6 +276,156 @@ packages: engines: { node: ">=6.0.0" } hasBin: true + "@babel/parser@7.28.3": + resolution: + { + integrity: sha512-7+Ey1mAgYqFAx2h0RuoxcQT5+MlG3GTV0TQrgr7/ZliKsm/MNDxVVutlWaziMq7wJNAz8MTqz55XLpWvva6StA==, + } + engines: { node: ">=6.0.0" } + hasBin: true + + "@babel/plugin-syntax-async-generators@7.8.4": + resolution: + { + integrity: sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw==, + } + peerDependencies: + "@babel/core": ^7.0.0-0 + + "@babel/plugin-syntax-bigint@7.8.3": + resolution: + { + integrity: sha512-wnTnFlG+YxQm3vDxpGE57Pj0srRU4sHE/mDkt1qv2YJJSeUAec2ma4WLUnUPeKjyrfntVwe/N6dCXpU+zL3Npg==, + } + peerDependencies: + "@babel/core": ^7.0.0-0 + + "@babel/plugin-syntax-class-properties@7.12.13": + resolution: + { + integrity: sha512-fm4idjKla0YahUNgFNLCB0qySdsoPiZP3iQE3rky0mBUtMZ23yDJ9SJdg6dXTSDnulOVqiF3Hgr9nbXvXTQZYA==, + } + peerDependencies: + "@babel/core": ^7.0.0-0 + + "@babel/plugin-syntax-class-static-block@7.14.5": + resolution: + { + integrity: sha512-b+YyPmr6ldyNnM6sqYeMWE+bgJcJpO6yS4QD7ymxgH34GBPNDM/THBh8iunyvKIZztiwLH4CJZ0RxTk9emgpjw==, + } + engines: { node: ">=6.9.0" } + peerDependencies: + "@babel/core": ^7.0.0-0 + + "@babel/plugin-syntax-import-attributes@7.27.1": + resolution: + { + integrity: sha512-oFT0FrKHgF53f4vOsZGi2Hh3I35PfSmVs4IBFLFj4dnafP+hIWDLg3VyKmUHfLoLHlyxY4C7DGtmHuJgn+IGww==, + } + engines: { node: ">=6.9.0" } + peerDependencies: + "@babel/core": ^7.0.0-0 + + "@babel/plugin-syntax-import-meta@7.10.4": + resolution: + { + integrity: sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g==, + } + peerDependencies: + "@babel/core": ^7.0.0-0 + + "@babel/plugin-syntax-json-strings@7.8.3": + resolution: + { + integrity: sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA==, + } + peerDependencies: + "@babel/core": ^7.0.0-0 + + "@babel/plugin-syntax-jsx@7.27.1": + resolution: + { + integrity: sha512-y8YTNIeKoyhGd9O0Jiyzyyqk8gdjnumGTQPsz0xOZOQ2RmkVJeZ1vmmfIvFEKqucBG6axJGBZDE/7iI5suUI/w==, + } + engines: { node: ">=6.9.0" } + peerDependencies: + "@babel/core": ^7.0.0-0 + + "@babel/plugin-syntax-logical-assignment-operators@7.10.4": + resolution: + { + integrity: sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig==, + } + peerDependencies: + "@babel/core": ^7.0.0-0 + + "@babel/plugin-syntax-nullish-coalescing-operator@7.8.3": + resolution: + { + integrity: sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ==, + } + peerDependencies: + "@babel/core": ^7.0.0-0 + + "@babel/plugin-syntax-numeric-separator@7.10.4": + resolution: + { + integrity: sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug==, + } + peerDependencies: + "@babel/core": ^7.0.0-0 + + "@babel/plugin-syntax-object-rest-spread@7.8.3": + resolution: + { + integrity: sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA==, + } + peerDependencies: + "@babel/core": ^7.0.0-0 + + "@babel/plugin-syntax-optional-catch-binding@7.8.3": + resolution: + { + integrity: sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q==, + } + peerDependencies: + "@babel/core": ^7.0.0-0 + + "@babel/plugin-syntax-optional-chaining@7.8.3": + resolution: + { + integrity: sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg==, + } + peerDependencies: + "@babel/core": ^7.0.0-0 + + "@babel/plugin-syntax-private-property-in-object@7.14.5": + resolution: + { + integrity: sha512-0wVnp9dxJ72ZUJDV27ZfbSj6iHLoytYZmh3rFcxNnvsJF3ktkzLDZPy/mA17HGsaQT3/DQsWYX1f1QGWkCoVUg==, + } + engines: { node: ">=6.9.0" } + peerDependencies: + "@babel/core": ^7.0.0-0 + + "@babel/plugin-syntax-top-level-await@7.14.5": + resolution: + { + integrity: sha512-hx++upLv5U1rgYfwe1xBQUhRmU41NEvpUvrp8jkrSCdvGSnM5/qdRMtylJ6PG5OFkBaHkbTAKTnd3/YyESRHFw==, + } + engines: { node: ">=6.9.0" } + peerDependencies: + "@babel/core": ^7.0.0-0 + + "@babel/plugin-syntax-typescript@7.27.1": + resolution: + { + integrity: sha512-xfYCBMxveHrRMnAWl1ZlPXOZjzkN82THFvLhQhFXFt81Z5HnN+EtUkZhv/zcKpmT3fzmWZB0ywiBrbC3vogbwQ==, + } + engines: { node: ">=6.9.0" } + peerDependencies: + "@babel/core": ^7.0.0-0 + "@babel/runtime@7.28.2": resolution: { @@ -224,6 +447,13 @@ packages: } engines: { node: ">=6.9.0" } + "@babel/traverse@7.28.3": + resolution: + { + integrity: sha512-7w4kZYHneL3A6NP2nxzHvT3HCZ7puDZZjFMqDpBPECub79sTtSO5CGXDkKrTQq8ksAwfD/XI2MRFX23njdDaIQ==, + } + engines: { node: ">=6.9.0" } + "@babel/types@7.28.2": resolution: { @@ -231,6 +461,12 @@ packages: } engines: { node: ">=6.9.0" } + "@bcoe/v8-coverage@0.2.3": + resolution: + { + integrity: sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==, + } + "@chakra-ui/react@3.24.2": resolution: { @@ -248,41 +484,6 @@ packages: } engines: { node: ">=12" } - "@edge-runtime/format@2.2.1": - resolution: - { - integrity: sha512-JQTRVuiusQLNNLe2W9tnzBlV/GvSVcozLl4XZHk5swnRZ/v6jp8TqR8P7sqmJsQqblDZ3EztcWmLDbhRje/+8g==, - } - engines: { node: ">=16" } - - "@edge-runtime/node-utils@2.3.0": - resolution: - { - integrity: sha512-uUtx8BFoO1hNxtHjp3eqVPC/mWImGb2exOfGjMLUoipuWgjej+f4o/VP4bUI8U40gu7Teogd5VTeZUkGvJSPOQ==, - } - engines: { node: ">=16" } - - "@edge-runtime/ponyfill@2.4.2": - resolution: - { - integrity: sha512-oN17GjFr69chu6sDLvXxdhg0Qe8EZviGSuqzR9qOiKh4MhFYGdBBcqRNzdmYeAdeRzOW2mM9yil4RftUQ7sUOA==, - } - engines: { node: ">=16" } - - "@edge-runtime/primitives@4.1.0": - resolution: - { - integrity: sha512-Vw0lbJ2lvRUqc7/soqygUX216Xb8T3WBZ987oywz6aJqRxcwSVWwr9e+Nqo2m9bxobA9mdbWNNoRY6S9eko1EQ==, - } - engines: { node: ">=16" } - - "@edge-runtime/vm@3.2.0": - resolution: - { - integrity: sha512-0dEVyRLM/lG4gp1R/Ik5bfPl/1wX00xFwd5KcNH602tzBa09oF7pbTKETEhR1GjZ75K6OJnYFu8II2dyMhONMw==, - } - engines: { node: ">=16" } - "@emnapi/core@1.4.5": resolution: { @@ -446,13 +647,6 @@ packages: } engines: { node: ^18.18.0 || ^20.9.0 || >=21.1.0 } - "@fastify/busboy@2.1.1": - resolution: - { - integrity: sha512-vBZP4NlzfOlerQTnba4aqZoMhE/a9HY7HRqoOPaETQcSQuWEIyZMHGfVu6w9wGtGK5fED5qRs2DteVCjOH60sA==, - } - engines: { node: ">=14" } - "@floating-ui/core@1.7.3": resolution: { @@ -516,16 +710,6 @@ packages: "@fortawesome/fontawesome-svg-core": ~1 || ~6 || ~7 react: ^16.3 || ^17.0.0 || ^18.0.0 || ^19.0.0 - "@hey-api/openapi-ts@0.48.3": - resolution: - { - integrity: sha512-R53Nr4Gicz77icS+RiH0fwHa9A0uFPtzsjC8SBaGwtOel5ZyxeBbayWE6HhE789hp3dok9pegwWncwwOrr4WFA==, - } - engines: { node: ^18.0.0 || >=20.0.0 } - hasBin: true - peerDependencies: - typescript: ^5.x - "@humanfs/core@0.19.1": resolution: { @@ -561,6 +745,194 @@ packages: } engines: { node: ">=18.18" } + "@img/sharp-darwin-arm64@0.34.3": + resolution: + { + integrity: sha512-ryFMfvxxpQRsgZJqBd4wsttYQbCxsJksrv9Lw/v798JcQ8+w84mBWuXwl+TT0WJ/WrYOLaYpwQXi3sA9nTIaIg==, + } + engines: { node: ^18.17.0 || ^20.3.0 || >=21.0.0 } + cpu: [arm64] + os: [darwin] + + "@img/sharp-darwin-x64@0.34.3": + resolution: + { + integrity: sha512-yHpJYynROAj12TA6qil58hmPmAwxKKC7reUqtGLzsOHfP7/rniNGTL8tjWX6L3CTV4+5P4ypcS7Pp+7OB+8ihA==, + } + engines: { node: ^18.17.0 || ^20.3.0 || >=21.0.0 } + cpu: [x64] + os: [darwin] + + "@img/sharp-libvips-darwin-arm64@1.2.0": + resolution: + { + integrity: sha512-sBZmpwmxqwlqG9ueWFXtockhsxefaV6O84BMOrhtg/YqbTaRdqDE7hxraVE3y6gVM4eExmfzW4a8el9ArLeEiQ==, + } + cpu: [arm64] + os: [darwin] + + "@img/sharp-libvips-darwin-x64@1.2.0": + resolution: + { + integrity: sha512-M64XVuL94OgiNHa5/m2YvEQI5q2cl9d/wk0qFTDVXcYzi43lxuiFTftMR1tOnFQovVXNZJ5TURSDK2pNe9Yzqg==, + } + cpu: [x64] + os: [darwin] + + "@img/sharp-libvips-linux-arm64@1.2.0": + resolution: + { + integrity: sha512-RXwd0CgG+uPRX5YYrkzKyalt2OJYRiJQ8ED/fi1tq9WQW2jsQIn0tqrlR5l5dr/rjqq6AHAxURhj2DVjyQWSOA==, + } + cpu: [arm64] + os: [linux] + + "@img/sharp-libvips-linux-arm@1.2.0": + resolution: + { + integrity: sha512-mWd2uWvDtL/nvIzThLq3fr2nnGfyr/XMXlq8ZJ9WMR6PXijHlC3ksp0IpuhK6bougvQrchUAfzRLnbsen0Cqvw==, + } + cpu: [arm] + os: [linux] + + "@img/sharp-libvips-linux-ppc64@1.2.0": + resolution: + { + integrity: sha512-Xod/7KaDDHkYu2phxxfeEPXfVXFKx70EAFZ0qyUdOjCcxbjqyJOEUpDe6RIyaunGxT34Anf9ue/wuWOqBW2WcQ==, + } + cpu: [ppc64] + os: [linux] + + "@img/sharp-libvips-linux-s390x@1.2.0": + resolution: + { + integrity: sha512-eMKfzDxLGT8mnmPJTNMcjfO33fLiTDsrMlUVcp6b96ETbnJmd4uvZxVJSKPQfS+odwfVaGifhsB07J1LynFehw==, + } + cpu: [s390x] + os: [linux] + + "@img/sharp-libvips-linux-x64@1.2.0": + resolution: + { + integrity: sha512-ZW3FPWIc7K1sH9E3nxIGB3y3dZkpJlMnkk7z5tu1nSkBoCgw2nSRTFHI5pB/3CQaJM0pdzMF3paf9ckKMSE9Tg==, + } + cpu: [x64] + os: [linux] + + "@img/sharp-libvips-linuxmusl-arm64@1.2.0": + resolution: + { + integrity: sha512-UG+LqQJbf5VJ8NWJ5Z3tdIe/HXjuIdo4JeVNADXBFuG7z9zjoegpzzGIyV5zQKi4zaJjnAd2+g2nna8TZvuW9Q==, + } + cpu: [arm64] + os: [linux] + + "@img/sharp-libvips-linuxmusl-x64@1.2.0": + resolution: + { + integrity: sha512-SRYOLR7CXPgNze8akZwjoGBoN1ThNZoqpOgfnOxmWsklTGVfJiGJoC/Lod7aNMGA1jSsKWM1+HRX43OP6p9+6Q==, + } + cpu: [x64] + os: [linux] + + "@img/sharp-linux-arm64@0.34.3": + resolution: + { + integrity: sha512-QdrKe3EvQrqwkDrtuTIjI0bu6YEJHTgEeqdzI3uWJOH6G1O8Nl1iEeVYRGdj1h5I21CqxSvQp1Yv7xeU3ZewbA==, + } + engines: { node: ^18.17.0 || ^20.3.0 || >=21.0.0 } + cpu: [arm64] + os: [linux] + + "@img/sharp-linux-arm@0.34.3": + resolution: + { + integrity: sha512-oBK9l+h6KBN0i3dC8rYntLiVfW8D8wH+NPNT3O/WBHeW0OQWCjfWksLUaPidsrDKpJgXp3G3/hkmhptAW0I3+A==, + } + engines: { node: ^18.17.0 || ^20.3.0 || >=21.0.0 } + cpu: [arm] + os: [linux] + + "@img/sharp-linux-ppc64@0.34.3": + resolution: + { + integrity: sha512-GLtbLQMCNC5nxuImPR2+RgrviwKwVql28FWZIW1zWruy6zLgA5/x2ZXk3mxj58X/tszVF69KK0Is83V8YgWhLA==, + } + engines: { node: ^18.17.0 || ^20.3.0 || >=21.0.0 } + cpu: [ppc64] + os: [linux] + + "@img/sharp-linux-s390x@0.34.3": + resolution: + { + integrity: sha512-3gahT+A6c4cdc2edhsLHmIOXMb17ltffJlxR0aC2VPZfwKoTGZec6u5GrFgdR7ciJSsHT27BD3TIuGcuRT0KmQ==, + } + engines: { node: ^18.17.0 || ^20.3.0 || >=21.0.0 } + cpu: [s390x] + os: [linux] + + "@img/sharp-linux-x64@0.34.3": + resolution: + { + integrity: sha512-8kYso8d806ypnSq3/Ly0QEw90V5ZoHh10yH0HnrzOCr6DKAPI6QVHvwleqMkVQ0m+fc7EH8ah0BB0QPuWY6zJQ==, + } + engines: { node: ^18.17.0 || ^20.3.0 || >=21.0.0 } + cpu: [x64] + os: [linux] + + "@img/sharp-linuxmusl-arm64@0.34.3": + resolution: + { + integrity: sha512-vAjbHDlr4izEiXM1OTggpCcPg9tn4YriK5vAjowJsHwdBIdx0fYRsURkxLG2RLm9gyBq66gwtWI8Gx0/ov+JKQ==, + } + engines: { node: ^18.17.0 || ^20.3.0 || >=21.0.0 } + cpu: [arm64] + os: [linux] + + "@img/sharp-linuxmusl-x64@0.34.3": + resolution: + { + integrity: sha512-gCWUn9547K5bwvOn9l5XGAEjVTTRji4aPTqLzGXHvIr6bIDZKNTA34seMPgM0WmSf+RYBH411VavCejp3PkOeQ==, + } + engines: { node: ^18.17.0 || ^20.3.0 || >=21.0.0 } + cpu: [x64] + os: [linux] + + "@img/sharp-wasm32@0.34.3": + resolution: + { + integrity: sha512-+CyRcpagHMGteySaWos8IbnXcHgfDn7pO2fiC2slJxvNq9gDipYBN42/RagzctVRKgxATmfqOSulgZv5e1RdMg==, + } + engines: { node: ^18.17.0 || ^20.3.0 || >=21.0.0 } + cpu: [wasm32] + + "@img/sharp-win32-arm64@0.34.3": + resolution: + { + integrity: sha512-MjnHPnbqMXNC2UgeLJtX4XqoVHHlZNd+nPt1kRPmj63wURegwBhZlApELdtxM2OIZDRv/DFtLcNhVbd1z8GYXQ==, + } + engines: { node: ^18.17.0 || ^20.3.0 || >=21.0.0 } + cpu: [arm64] + os: [win32] + + "@img/sharp-win32-ia32@0.34.3": + resolution: + { + integrity: sha512-xuCdhH44WxuXgOM714hn4amodJMZl3OEvf0GVTm0BEyMeA2to+8HEdRPShH0SLYptJY1uBw+SCFP9WVQi1Q/cw==, + } + engines: { node: ^18.17.0 || ^20.3.0 || >=21.0.0 } + cpu: [ia32] + os: [win32] + + "@img/sharp-win32-x64@0.34.3": + resolution: + { + integrity: sha512-OWwz05d++TxzLEv4VnsTz5CmZ6mI6S05sfQGEMrNrQcOEERbX46332IvE7pO/EUiw7jUrrS40z/M7kPyjfl04g==, + } + engines: { node: ^18.17.0 || ^20.3.0 || >=21.0.0 } + cpu: [x64] + os: [win32] + "@internationalized/date@3.8.2": resolution: { @@ -573,10 +945,10 @@ packages: integrity: sha512-p+Zh1sb6EfrfVaS86jlHGQ9HA66fJhV9x5LiE5vCbZtXEHAuhcmUZUdZ4WrFpUBfNalr2OkAJI5AcKEQF+Lebw==, } - "@ioredis/commands@1.3.0": + "@ioredis/commands@1.3.1": resolution: { - integrity: sha512-M/T6Zewn7sDaBQEqIZ8Rb+i9y8qfGmq+5SDFSf9sA2lUZTmdDLVdOiQaeDp+Q4wElZ9HG1GAX5KhDaidp6LQsQ==, + integrity: sha512-bYtU8avhGIcje3IhvF9aSjsa5URMZBHnwKtOvXsT4sfYy9gppW11gLPT/9oNqlJZD47yPKveQFTAFWpHjKvUoQ==, } "@isaacs/cliui@8.0.2": @@ -586,6 +958,107 @@ packages: } engines: { node: ">=12" } + "@istanbuljs/load-nyc-config@1.1.0": + resolution: + { + integrity: sha512-VjeHSlIzpv/NyD3N0YuHfXOPDIixcA1q2ZV98wsMqcYlPmv2n3Yb2lYP9XMElnaFVXg5A7YLTeLu6V84uQDjmQ==, + } + engines: { node: ">=8" } + + "@istanbuljs/schema@0.1.3": + resolution: + { + integrity: sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==, + } + engines: { node: ">=8" } + + "@jest/console@30.1.2": + resolution: + { + integrity: sha512-BGMAxj8VRmoD0MoA/jo9alMXSRoqW8KPeqOfEo1ncxnRLatTBCpRoOwlwlEMdudp68Q6WSGwYrrLtTGOh8fLzw==, + } + engines: { node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0 } + + "@jest/core@30.1.3": + resolution: + { + integrity: sha512-LIQz7NEDDO1+eyOA2ZmkiAyYvZuo6s1UxD/e2IHldR6D7UYogVq3arTmli07MkENLq6/3JEQjp0mA8rrHHJ8KQ==, + } + engines: { node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0 } + peerDependencies: + node-notifier: ^8.0.1 || ^9.0.0 || ^10.0.0 + peerDependenciesMeta: + node-notifier: + optional: true + + "@jest/diff-sequences@30.0.1": + resolution: + { + integrity: sha512-n5H8QLDJ47QqbCNn5SuFjCRDrOLEZ0h8vAHCK5RL9Ls7Xa8AQLa/YxAc9UjFqoEDM48muwtBGjtMY5cr0PLDCw==, + } + engines: { node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0 } + + "@jest/environment@30.1.2": + resolution: + { + integrity: sha512-N8t1Ytw4/mr9uN28OnVf0SYE2dGhaIxOVYcwsf9IInBKjvofAjbFRvedvBBlyTYk2knbJTiEjEJ2PyyDIBnd9w==, + } + engines: { node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0 } + + "@jest/expect-utils@30.1.2": + resolution: + { + integrity: sha512-HXy1qT/bfdjCv7iC336ExbqqYtZvljrV8odNdso7dWK9bSeHtLlvwWWC3YSybSPL03Gg5rug6WLCZAZFH72m0A==, + } + engines: { node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0 } + + "@jest/expect@30.1.2": + resolution: + { + integrity: sha512-tyaIExOwQRCxPCGNC05lIjWJztDwk2gPDNSDGg1zitXJJ8dC3++G/CRjE5mb2wQsf89+lsgAgqxxNpDLiCViTA==, + } + engines: { node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0 } + + "@jest/fake-timers@30.1.2": + resolution: + { + integrity: sha512-Beljfv9AYkr9K+ETX9tvV61rJTY706BhBUtiaepQHeEGfe0DbpvUA5Z3fomwc5Xkhns6NWrcFDZn+72fLieUnA==, + } + engines: { node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0 } + + "@jest/get-type@30.1.0": + resolution: + { + integrity: sha512-eMbZE2hUnx1WV0pmURZY9XoXPkUYjpc55mb0CrhtdWLtzMQPFvu/rZkTLZFTsdaVQa+Tr4eWAteqcUzoawq/uA==, + } + engines: { node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0 } + + "@jest/globals@30.1.2": + resolution: + { + integrity: sha512-teNTPZ8yZe3ahbYnvnVRDeOjr+3pu2uiAtNtrEsiMjVPPj+cXd5E/fr8BL7v/T7F31vYdEHrI5cC/2OoO/vM9A==, + } + engines: { node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0 } + + "@jest/pattern@30.0.1": + resolution: + { + integrity: sha512-gWp7NfQW27LaBQz3TITS8L7ZCQ0TLvtmI//4OwlQRx4rnWxcPNIYjxZpDcN4+UlGxgm3jS5QPz8IPTCkb59wZA==, + } + engines: { node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0 } + + "@jest/reporters@30.1.3": + resolution: + { + integrity: sha512-VWEQmJWfXMOrzdFEOyGjUEOuVXllgZsoPtEHZzfdNz18RmzJ5nlR6kp8hDdY8dDS1yGOXAY7DHT+AOHIPSBV0w==, + } + engines: { node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0 } + peerDependencies: + node-notifier: ^8.0.1 || ^9.0.0 || ^10.0.0 + peerDependenciesMeta: + node-notifier: + optional: true + "@jest/schemas@29.6.3": resolution: { @@ -593,6 +1066,48 @@ packages: } engines: { node: ^14.15.0 || ^16.10.0 || >=18.0.0 } + "@jest/schemas@30.0.5": + resolution: + { + integrity: sha512-DmdYgtezMkh3cpU8/1uyXakv3tJRcmcXxBOcO0tbaozPwpmh4YMsnWrQm9ZmZMfa5ocbxzbFk6O4bDPEc/iAnA==, + } + engines: { node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0 } + + "@jest/snapshot-utils@30.1.2": + resolution: + { + integrity: sha512-vHoMTpimcPSR7OxS2S0V1Cpg8eKDRxucHjoWl5u4RQcnxqQrV3avETiFpl8etn4dqxEGarBeHbIBety/f8mLXw==, + } + engines: { node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0 } + + "@jest/source-map@30.0.1": + resolution: + { + integrity: sha512-MIRWMUUR3sdbP36oyNyhbThLHyJ2eEDClPCiHVbrYAe5g3CHRArIVpBw7cdSB5fr+ofSfIb2Tnsw8iEHL0PYQg==, + } + engines: { node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0 } + + "@jest/test-result@30.1.3": + resolution: + { + integrity: sha512-P9IV8T24D43cNRANPPokn7tZh0FAFnYS2HIfi5vK18CjRkTDR9Y3e1BoEcAJnl4ghZZF4Ecda4M/k41QkvurEQ==, + } + engines: { node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0 } + + "@jest/test-sequencer@30.1.3": + resolution: + { + integrity: sha512-82J+hzC0qeQIiiZDThh+YUadvshdBswi5nuyXlEmXzrhw5ZQSRHeQ5LpVMD/xc8B3wPePvs6VMzHnntxL+4E3w==, + } + engines: { node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0 } + + "@jest/transform@30.1.2": + resolution: + { + integrity: sha512-UYYFGifSgfjujf1Cbd3iU/IQoSd6uwsj8XHj5DSDf5ERDcWMdJOPTkHWXj4U+Z/uMagyOQZ6Vne8C4nRIrCxqA==, + } + engines: { node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0 } + "@jest/types@29.6.3": resolution: { @@ -600,6 +1115,13 @@ packages: } engines: { node: ^14.15.0 || ^16.10.0 || >=18.0.0 } + "@jest/types@30.0.5": + resolution: + { + integrity: sha512-aREYa3aku9SSnea4aX6bhKn4bgv3AXkgijoQgbYV3yvbiGt6z+MQ85+6mIhx9DsKW2BuB/cLR/A+tcMThx+KLQ==, + } + engines: { node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0 } + "@jridgewell/gen-mapping@0.3.13": resolution: { @@ -613,6 +1135,12 @@ packages: } engines: { node: ">=6.0.0" } + "@jridgewell/source-map@0.3.11": + resolution: + { + integrity: sha512-ZMp1V8ZFcPG5dIWnQLr3NSI1MiCU7UETdS/A0G8V/XWHvJv3ZsFqutJn1Y5RPmAPX6F3BiE397OqveU/9NCuIA==, + } + "@jridgewell/sourcemap-codec@1.5.5": resolution: { @@ -625,119 +1153,103 @@ packages: integrity: sha512-GQ7Nw5G2lTu/BtHTKfXhKHok2WGetd4XYcVKGx00SjAk8GMwgJM3zr6zORiPGuOE+/vkc90KtTosSSvaCjKb2Q==, } + "@jridgewell/trace-mapping@0.3.31": + resolution: + { + integrity: sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==, + } + "@jridgewell/trace-mapping@0.3.9": resolution: { integrity: sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==, } - "@jsdevtools/ono@7.1.3": - resolution: - { - integrity: sha512-4JQNk+3mVzK3xh2rqd6RB4J46qUR19azEHBneZyTZM+c456qOrbbM/5xcR8huNCCcbVt7+UmizG6GuUvPvKUYg==, - } - - "@mapbox/node-pre-gyp@1.0.11": - resolution: - { - integrity: sha512-Yhlar6v9WQgUp/He7BdgzOz8lqMQ8sU+jkCq7Wx8Myc5YFJLbEe7lgui/V7G1qB1DJykHSGwreceSaD60Y0PUQ==, - } - hasBin: true - "@napi-rs/wasm-runtime@0.2.12": resolution: { integrity: sha512-ZVWUcfwY4E/yPitQJl481FjFo3K22D6qF0DuFH6Y/nbnE11GY5uguDxZMGXPQ8WQ0128MXQD7TnfHyK4oWoIJQ==, } - "@next/env@14.2.31": + "@next/env@15.5.3": resolution: { - integrity: sha512-X8VxxYL6VuezrG82h0pUA1V+DuTSJp7Nv15bxq3ivrFqZLjx81rfeHMWOE9T0jm1n3DtHGv8gdn6B0T0kr0D3Q==, + integrity: sha512-RSEDTRqyihYXygx/OJXwvVupfr9m04+0vH8vyy0HfZ7keRto6VX9BbEk0J2PUk0VGy6YhklJUSrgForov5F9pw==, } - "@next/eslint-plugin-next@14.2.31": + "@next/eslint-plugin-next@15.5.3": resolution: { - integrity: sha512-ouaB+l8Cr/uzGxoGHUvd01OnfFTM8qM81Crw1AG0xoWDRN0DKLXyTWVe0FdAOHVBpGuXB87aufdRmrwzZDArIw==, + integrity: sha512-SdhaKdko6dpsSr0DldkESItVrnPYB1NS2NpShCSX5lc7SSQmLZt5Mug6t2xbiuVWEVDLZSuIAoQyYVBYp0dR5g==, } - "@next/swc-darwin-arm64@14.2.31": + "@next/swc-darwin-arm64@15.5.3": resolution: { - integrity: sha512-dTHKfaFO/xMJ3kzhXYgf64VtV6MMwDs2viedDOdP+ezd0zWMOQZkxcwOfdcQeQCpouTr9b+xOqMCUXxgLizl8Q==, + integrity: sha512-nzbHQo69+au9wJkGKTU9lP7PXv0d1J5ljFpvb+LnEomLtSbJkbZyEs6sbF3plQmiOB2l9OBtN2tNSvCH1nQ9Jg==, } engines: { node: ">= 10" } cpu: [arm64] os: [darwin] - "@next/swc-darwin-x64@14.2.31": + "@next/swc-darwin-x64@15.5.3": resolution: { - integrity: sha512-iSavebQgeMukUAfjfW8Fi2Iz01t95yxRl2w2wCzjD91h5In9la99QIDKcKSYPfqLjCgwz3JpIWxLG6LM/sxL4g==, + integrity: sha512-w83w4SkOOhekJOcA5HBvHyGzgV1W/XvOfpkrxIse4uPWhYTTRwtGEM4v/jiXwNSJvfRvah0H8/uTLBKRXlef8g==, } engines: { node: ">= 10" } cpu: [x64] os: [darwin] - "@next/swc-linux-arm64-gnu@14.2.31": + "@next/swc-linux-arm64-gnu@15.5.3": resolution: { - integrity: sha512-XJb3/LURg1u1SdQoopG6jDL2otxGKChH2UYnUTcby4izjM0il7ylBY5TIA7myhvHj9lG5pn9F2nR2s3i8X9awQ==, + integrity: sha512-+m7pfIs0/yvgVu26ieaKrifV8C8yiLe7jVp9SpcIzg7XmyyNE7toC1fy5IOQozmr6kWl/JONC51osih2RyoXRw==, } engines: { node: ">= 10" } cpu: [arm64] os: [linux] - "@next/swc-linux-arm64-musl@14.2.31": + "@next/swc-linux-arm64-musl@15.5.3": resolution: { - integrity: sha512-IInDAcchNCu3BzocdqdCv1bKCmUVO/bKJHnBFTeq3svfaWpOPewaLJ2Lu3GL4yV76c/86ZvpBbG/JJ1lVIs5MA==, + integrity: sha512-u3PEIzuguSenoZviZJahNLgCexGFhso5mxWCrrIMdvpZn6lkME5vc/ADZG8UUk5K1uWRy4hqSFECrON6UKQBbQ==, } engines: { node: ">= 10" } cpu: [arm64] os: [linux] - "@next/swc-linux-x64-gnu@14.2.31": + "@next/swc-linux-x64-gnu@15.5.3": resolution: { - integrity: sha512-YTChJL5/9e4NXPKW+OJzsQa42RiWUNbE+k+ReHvA+lwXk+bvzTsVQboNcezWOuCD+p/J+ntxKOB/81o0MenBhw==, + integrity: sha512-lDtOOScYDZxI2BENN9m0pfVPJDSuUkAD1YXSvlJF0DKwZt0WlA7T7o3wrcEr4Q+iHYGzEaVuZcsIbCps4K27sA==, } engines: { node: ">= 10" } cpu: [x64] os: [linux] - "@next/swc-linux-x64-musl@14.2.31": + "@next/swc-linux-x64-musl@15.5.3": resolution: { - integrity: sha512-A0JmD1y4q/9ufOGEAhoa60Sof++X10PEoiWOH0gZ2isufWZeV03NnyRlRmJpRQWGIbRkJUmBo9I3Qz5C10vx4w==, + integrity: sha512-9vWVUnsx9PrY2NwdVRJ4dUURAQ8Su0sLRPqcCCxtX5zIQUBES12eRVHq6b70bbfaVaxIDGJN2afHui0eDm+cLg==, } engines: { node: ">= 10" } cpu: [x64] os: [linux] - "@next/swc-win32-arm64-msvc@14.2.31": + "@next/swc-win32-arm64-msvc@15.5.3": resolution: { - integrity: sha512-nowJ5GbMeDOMzbTm29YqrdrD6lTM8qn2wnZfGpYMY7SZODYYpaJHH1FJXE1l1zWICHR+WfIMytlTDBHu10jb8A==, + integrity: sha512-1CU20FZzY9LFQigRi6jM45oJMU3KziA5/sSG+dXeVaTm661snQP6xu3ykGxxwU5sLG3sh14teO/IOEPVsQMRfA==, } engines: { node: ">= 10" } cpu: [arm64] os: [win32] - "@next/swc-win32-ia32-msvc@14.2.31": + "@next/swc-win32-x64-msvc@15.5.3": resolution: { - integrity: sha512-pk9Bu4K0015anTS1OS9d/SpS0UtRObC+xe93fwnm7Gvqbv/W1ZbzhK4nvc96RURIQOux3P/bBH316xz8wjGSsA==, - } - engines: { node: ">= 10" } - cpu: [ia32] - os: [win32] - - "@next/swc-win32-x64-msvc@14.2.31": - resolution: - { - integrity: sha512-LwFZd4JFnMHGceItR9+jtlMm8lGLU/IPkgjBBgYmdYSfalbHCiDpjMYtgDQ2wtwiAOSJOCyFI4m8PikrsDyA6Q==, + integrity: sha512-JMoLAq3n3y5tKXPQwCK5c+6tmwkuFDa2XAxz8Wm4+IVthdBZdZGh+lmiLUHg9f9IDwIQpUjp+ysd6OkYTyZRZw==, } engines: { node: ">= 10" } cpu: [x64] @@ -771,6 +1283,327 @@ packages: } engines: { node: ">=12.4.0" } + "@opentelemetry/api-logs@0.203.0": + resolution: + { + integrity: sha512-9B9RU0H7Ya1Dx/Rkyc4stuBZSGVQF27WigitInx2QQoj6KUpEFYPKoWjdFTunJYxmXmh17HeBvbMa1EhGyPmqQ==, + } + engines: { node: ">=8.0.0" } + + "@opentelemetry/api-logs@0.204.0": + resolution: + { + integrity: sha512-DqxY8yoAaiBPivoJD4UtgrMS8gEmzZ5lnaxzPojzLVHBGqPxgWm4zcuvcUHZiqQ6kRX2Klel2r9y8cA2HAtqpw==, + } + engines: { node: ">=8.0.0" } + + "@opentelemetry/api-logs@0.57.2": + resolution: + { + integrity: sha512-uIX52NnTM0iBh84MShlpouI7UKqkZ7MrUszTmaypHBu4r7NofznSnQRfJ+uUeDtQDj6w8eFGg5KBLDAwAPz1+A==, + } + engines: { node: ">=14" } + + "@opentelemetry/api@1.9.0": + resolution: + { + integrity: sha512-3giAOQvZiH5F9bMlMiv8+GSPMeqg0dbaeo58/0SlA9sxSqZhnUtxzX9/2FzyhS9sWQf5S0GJE0AKBrFqjpeYcg==, + } + engines: { node: ">=8.0.0" } + + "@opentelemetry/context-async-hooks@2.1.0": + resolution: + { + integrity: sha512-zOyetmZppnwTyPrt4S7jMfXiSX9yyfF0hxlA8B5oo2TtKl+/RGCy7fi4DrBfIf3lCPrkKsRBWZZD7RFojK7FDg==, + } + engines: { node: ^18.19.0 || >=20.6.0 } + peerDependencies: + "@opentelemetry/api": ">=1.0.0 <1.10.0" + + "@opentelemetry/core@2.0.1": + resolution: + { + integrity: sha512-MaZk9SJIDgo1peKevlbhP6+IwIiNPNmswNL4AF0WaQJLbHXjr9SrZMgS12+iqr9ToV4ZVosCcc0f8Rg67LXjxw==, + } + engines: { node: ^18.19.0 || >=20.6.0 } + peerDependencies: + "@opentelemetry/api": ">=1.0.0 <1.10.0" + + "@opentelemetry/core@2.1.0": + resolution: + { + integrity: sha512-RMEtHsxJs/GiHHxYT58IY57UXAQTuUnZVco6ymDEqTNlJKTimM4qPUPVe8InNFyBjhHBEAx4k3Q8LtNayBsbUQ==, + } + engines: { node: ^18.19.0 || >=20.6.0 } + peerDependencies: + "@opentelemetry/api": ">=1.0.0 <1.10.0" + + "@opentelemetry/instrumentation-amqplib@0.50.0": + resolution: + { + integrity: sha512-kwNs/itehHG/qaQBcVrLNcvXVPW0I4FCOVtw3LHMLdYIqD7GJ6Yv2nX+a4YHjzbzIeRYj8iyMp0Bl7tlkidq5w==, + } + engines: { node: ^18.19.0 || >=20.6.0 } + peerDependencies: + "@opentelemetry/api": ^1.3.0 + + "@opentelemetry/instrumentation-connect@0.47.0": + resolution: + { + integrity: sha512-pjenvjR6+PMRb6/4X85L4OtkQCootgb/Jzh/l/Utu3SJHBid1F+gk9sTGU2FWuhhEfV6P7MZ7BmCdHXQjgJ42g==, + } + engines: { node: ^18.19.0 || >=20.6.0 } + peerDependencies: + "@opentelemetry/api": ^1.3.0 + + "@opentelemetry/instrumentation-dataloader@0.21.1": + resolution: + { + integrity: sha512-hNAm/bwGawLM8VDjKR0ZUDJ/D/qKR3s6lA5NV+btNaPVm2acqhPcT47l2uCVi+70lng2mywfQncor9v8/ykuyw==, + } + engines: { node: ^18.19.0 || >=20.6.0 } + peerDependencies: + "@opentelemetry/api": ^1.3.0 + + "@opentelemetry/instrumentation-express@0.52.0": + resolution: + { + integrity: sha512-W7pizN0Wh1/cbNhhTf7C62NpyYw7VfCFTYg0DYieSTrtPBT1vmoSZei19wfKLnrMsz3sHayCg0HxCVL2c+cz5w==, + } + engines: { node: ^18.19.0 || >=20.6.0 } + peerDependencies: + "@opentelemetry/api": ^1.3.0 + + "@opentelemetry/instrumentation-fs@0.23.0": + resolution: + { + integrity: sha512-Puan+QopWHA/KNYvDfOZN6M/JtF6buXEyD934vrb8WhsX1/FuM7OtoMlQyIqAadnE8FqqDL4KDPiEfCQH6pQcQ==, + } + engines: { node: ^18.19.0 || >=20.6.0 } + peerDependencies: + "@opentelemetry/api": ^1.3.0 + + "@opentelemetry/instrumentation-generic-pool@0.47.0": + resolution: + { + integrity: sha512-UfHqf3zYK+CwDwEtTjaD12uUqGGTswZ7ofLBEdQ4sEJp9GHSSJMQ2hT3pgBxyKADzUdoxQAv/7NqvL42ZI+Qbw==, + } + engines: { node: ^18.19.0 || >=20.6.0 } + peerDependencies: + "@opentelemetry/api": ^1.3.0 + + "@opentelemetry/instrumentation-graphql@0.51.0": + resolution: + { + integrity: sha512-LchkOu9X5DrXAnPI1+Z06h/EH/zC7D6sA86hhPrk3evLlsJTz0grPrkL/yUJM9Ty0CL/y2HSvmWQCjbJEz/ADg==, + } + engines: { node: ^18.19.0 || >=20.6.0 } + peerDependencies: + "@opentelemetry/api": ^1.3.0 + + "@opentelemetry/instrumentation-hapi@0.50.0": + resolution: + { + integrity: sha512-5xGusXOFQXKacrZmDbpHQzqYD1gIkrMWuwvlrEPkYOsjUqGUjl1HbxCsn5Y9bUXOCgP1Lj6A4PcKt1UiJ2MujA==, + } + engines: { node: ^18.19.0 || >=20.6.0 } + peerDependencies: + "@opentelemetry/api": ^1.3.0 + + "@opentelemetry/instrumentation-http@0.203.0": + resolution: + { + integrity: sha512-y3uQAcCOAwnO6vEuNVocmpVzG3PER6/YZqbPbbffDdJ9te5NkHEkfSMNzlC3+v7KlE+WinPGc3N7MR30G1HY2g==, + } + engines: { node: ^18.19.0 || >=20.6.0 } + peerDependencies: + "@opentelemetry/api": ^1.3.0 + + "@opentelemetry/instrumentation-ioredis@0.52.0": + resolution: + { + integrity: sha512-rUvlyZwI90HRQPYicxpDGhT8setMrlHKokCtBtZgYxQWRF5RBbG4q0pGtbZvd7kyseuHbFpA3I/5z7M8b/5ywg==, + } + engines: { node: ^18.19.0 || >=20.6.0 } + peerDependencies: + "@opentelemetry/api": ^1.3.0 + + "@opentelemetry/instrumentation-kafkajs@0.13.0": + resolution: + { + integrity: sha512-FPQyJsREOaGH64hcxlzTsIEQC4DYANgTwHjiB7z9lldmvua1LRMVn3/FfBlzXoqF179B0VGYviz6rn75E9wsDw==, + } + engines: { node: ^18.19.0 || >=20.6.0 } + peerDependencies: + "@opentelemetry/api": ^1.3.0 + + "@opentelemetry/instrumentation-knex@0.48.0": + resolution: + { + integrity: sha512-V5wuaBPv/lwGxuHjC6Na2JFRjtPgstw19jTFl1B1b6zvaX8zVDYUDaR5hL7glnQtUSCMktPttQsgK4dhXpddcA==, + } + engines: { node: ^18.19.0 || >=20.6.0 } + peerDependencies: + "@opentelemetry/api": ^1.3.0 + + "@opentelemetry/instrumentation-koa@0.51.0": + resolution: + { + integrity: sha512-XNLWeMTMG1/EkQBbgPYzCeBD0cwOrfnn8ao4hWgLv0fNCFQu1kCsJYygz2cvKuCs340RlnG4i321hX7R8gj3Rg==, + } + engines: { node: ^18.19.0 || >=20.6.0 } + peerDependencies: + "@opentelemetry/api": ^1.3.0 + + "@opentelemetry/instrumentation-lru-memoizer@0.48.0": + resolution: + { + integrity: sha512-KUW29wfMlTPX1wFz+NNrmE7IzN7NWZDrmFWHM/VJcmFEuQGnnBuTIdsP55CnBDxKgQ/qqYFp4udQFNtjeFosPw==, + } + engines: { node: ^18.19.0 || >=20.6.0 } + peerDependencies: + "@opentelemetry/api": ^1.3.0 + + "@opentelemetry/instrumentation-mongodb@0.56.0": + resolution: + { + integrity: sha512-YG5IXUUmxX3Md2buVMvxm9NWlKADrnavI36hbJsihqqvBGsWnIfguf0rUP5Srr0pfPqhQjUP+agLMsvu0GmUpA==, + } + engines: { node: ^18.19.0 || >=20.6.0 } + peerDependencies: + "@opentelemetry/api": ^1.3.0 + + "@opentelemetry/instrumentation-mongoose@0.50.0": + resolution: + { + integrity: sha512-Am8pk1Ct951r4qCiqkBcGmPIgGhoDiFcRtqPSLbJrUZqEPUsigjtMjoWDRLG1Ki1NHgOF7D0H7d+suWz1AAizw==, + } + engines: { node: ^18.19.0 || >=20.6.0 } + peerDependencies: + "@opentelemetry/api": ^1.3.0 + + "@opentelemetry/instrumentation-mysql2@0.50.0": + resolution: + { + integrity: sha512-PoOMpmq73rOIE3nlTNLf3B1SyNYGsp7QXHYKmeTZZnJ2Ou7/fdURuOhWOI0e6QZ5gSem18IR1sJi6GOULBQJ9g==, + } + engines: { node: ^18.19.0 || >=20.6.0 } + peerDependencies: + "@opentelemetry/api": ^1.3.0 + + "@opentelemetry/instrumentation-mysql@0.49.0": + resolution: + { + integrity: sha512-QU9IUNqNsrlfE3dJkZnFHqLjlndiU39ll/YAAEvWE40sGOCi9AtOF6rmEGzJ1IswoZ3oyePV7q2MP8SrhJfVAA==, + } + engines: { node: ^18.19.0 || >=20.6.0 } + peerDependencies: + "@opentelemetry/api": ^1.3.0 + + "@opentelemetry/instrumentation-pg@0.55.0": + resolution: + { + integrity: sha512-yfJ5bYE7CnkW/uNsnrwouG/FR7nmg09zdk2MSs7k0ZOMkDDAE3WBGpVFFApGgNu2U+gtzLgEzOQG4I/X+60hXw==, + } + engines: { node: ^18.19.0 || >=20.6.0 } + peerDependencies: + "@opentelemetry/api": ^1.3.0 + + "@opentelemetry/instrumentation-redis@0.51.0": + resolution: + { + integrity: sha512-uL/GtBA0u72YPPehwOvthAe+Wf8k3T+XQPBssJmTYl6fzuZjNq8zTfxVFhl9nRFjFVEe+CtiYNT0Q3AyqW1Z0A==, + } + engines: { node: ^18.19.0 || >=20.6.0 } + peerDependencies: + "@opentelemetry/api": ^1.3.0 + + "@opentelemetry/instrumentation-tedious@0.22.0": + resolution: + { + integrity: sha512-XrrNSUCyEjH1ax9t+Uo6lv0S2FCCykcF7hSxBMxKf7Xn0bPRxD3KyFUZy25aQXzbbbUHhtdxj3r2h88SfEM3aA==, + } + engines: { node: ^18.19.0 || >=20.6.0 } + peerDependencies: + "@opentelemetry/api": ^1.3.0 + + "@opentelemetry/instrumentation-undici@0.14.0": + resolution: + { + integrity: sha512-2HN+7ztxAReXuxzrtA3WboAKlfP5OsPA57KQn2AdYZbJ3zeRPcLXyW4uO/jpLE6PLm0QRtmeGCmfYpqRlwgSwg==, + } + engines: { node: ^18.19.0 || >=20.6.0 } + peerDependencies: + "@opentelemetry/api": ^1.7.0 + + "@opentelemetry/instrumentation@0.203.0": + resolution: + { + integrity: sha512-ke1qyM+3AK2zPuBPb6Hk/GCsc5ewbLvPNkEuELx/JmANeEp6ZjnZ+wypPAJSucTw0wvCGrUaibDSdcrGFoWxKQ==, + } + engines: { node: ^18.19.0 || >=20.6.0 } + peerDependencies: + "@opentelemetry/api": ^1.3.0 + + "@opentelemetry/instrumentation@0.204.0": + resolution: + { + integrity: sha512-vV5+WSxktzoMP8JoYWKeopChy6G3HKk4UQ2hESCRDUUTZqQ3+nM3u8noVG0LmNfRWwcFBnbZ71GKC7vaYYdJ1g==, + } + engines: { node: ^18.19.0 || >=20.6.0 } + peerDependencies: + "@opentelemetry/api": ^1.3.0 + + "@opentelemetry/instrumentation@0.57.2": + resolution: + { + integrity: sha512-BdBGhQBh8IjZ2oIIX6F2/Q3LKm/FDDKi6ccYKcBTeilh6SNdNKveDOLk73BkSJjQLJk6qe4Yh+hHw1UPhCDdrg==, + } + engines: { node: ">=14" } + peerDependencies: + "@opentelemetry/api": ^1.3.0 + + "@opentelemetry/redis-common@0.38.0": + resolution: + { + integrity: sha512-4Wc0AWURII2cfXVVoZ6vDqK+s5n4K5IssdrlVrvGsx6OEOKdghKtJZqXAHWFiZv4nTDLH2/2fldjIHY8clMOjQ==, + } + engines: { node: ^18.19.0 || >=20.6.0 } + + "@opentelemetry/resources@2.1.0": + resolution: + { + integrity: sha512-1CJjf3LCvoefUOgegxi8h6r4B/wLSzInyhGP2UmIBYNlo4Qk5CZ73e1eEyWmfXvFtm1ybkmfb2DqWvspsYLrWw==, + } + engines: { node: ^18.19.0 || >=20.6.0 } + peerDependencies: + "@opentelemetry/api": ">=1.3.0 <1.10.0" + + "@opentelemetry/sdk-trace-base@2.1.0": + resolution: + { + integrity: sha512-uTX9FBlVQm4S2gVQO1sb5qyBLq/FPjbp+tmGoxu4tIgtYGmBYB44+KX/725RFDe30yBSaA9Ml9fqphe1hbUyLQ==, + } + engines: { node: ^18.19.0 || >=20.6.0 } + peerDependencies: + "@opentelemetry/api": ">=1.3.0 <1.10.0" + + "@opentelemetry/semantic-conventions@1.37.0": + resolution: + { + integrity: sha512-JD6DerIKdJGmRp4jQyX5FlrQjA4tjOw1cvfsPAZXfOOEErMUHjPcPSICS+6WnM0nB0efSFARh0KAZss+bvExOA==, + } + engines: { node: ">=14" } + + "@opentelemetry/sql-common@0.41.0": + resolution: + { + integrity: sha512-pmzXctVbEERbqSfiAgdes9Y63xjoOyXcD7B6IXBkVb+vbM7M9U98mn33nGXxPf4dfYR0M+vhcKRZmbSJ7HfqFA==, + } + engines: { node: ^18.19.0 || >=20.6.0 } + peerDependencies: + "@opentelemetry/api": ^1.1.0 + "@pandacss/is-valid-prop@0.54.0": resolution: { @@ -914,6 +1747,21 @@ packages: } engines: { node: ">=14" } + "@pkgr/core@0.2.9": + resolution: + { + integrity: sha512-QNqXyfVS2wm9hweSYD2O7F0G06uurj9kZ96TRQE5Y9hU7+tgdZwIkbAKc5Ocy1HxEY2kuDQa6cQ1WRs/O5LFKA==, + } + engines: { node: ^12.20.0 || ^14.18.0 || >=16.0.0 } + + "@prisma/instrumentation@6.14.0": + resolution: + { + integrity: sha512-Po/Hry5bAeunRDq0yAQueKookW3glpP+qjjvvyOfm6dI2KG5/Y6Bgg3ahyWd7B0u2E+Wf9xRk2rtdda7ySgK1A==, + } + peerDependencies: + "@opentelemetry/api": ^1.8 + "@radix-ui/primitive@1.1.3": resolution: { @@ -1198,6 +2046,25 @@ packages: integrity: sha512-HPwpGIzkl28mWyZqG52jiqDJ12waP11Pa1lGoiyUkIEuMLBP0oeK/C89esbXrxsky5we7dfd8U58nm0SgAWpVw==, } + "@redocly/ajv@8.11.3": + resolution: + { + integrity: sha512-4P3iZse91TkBiY+Dx5DUgxQ9GXkVJf++cmI0MOyLDxV9b5MUBI4II6ES8zA5JCbO72nKAJxWrw4PUPW+YP3ZDQ==, + } + + "@redocly/config@0.22.2": + resolution: + { + integrity: sha512-roRDai8/zr2S9YfmzUfNhKjOF0NdcOIqF7bhf4MVC5UxpjIysDjyudvlAiVbpPHp3eDRWbdzUgtkK1a7YiDNyQ==, + } + + "@redocly/openapi-core@1.34.5": + resolution: + { + integrity: sha512-0EbE8LRbkogtcCXU7liAyC00n9uNG9hJ+eMyHFdUsy9lB/WGqnEBgwjA9q2cyzAVcdTkQqTBBU1XePNnN3OijA==, + } + engines: { node: ">=18.17.0", npm: ">=9.5.0" } + "@reduxjs/toolkit@2.8.2": resolution: { @@ -1212,25 +2079,18 @@ packages: react-redux: optional: true - "@rollup/plugin-commonjs@24.0.0": + "@rollup/plugin-commonjs@28.0.1": resolution: { - integrity: sha512-0w0wyykzdyRRPHOb0cQt14mIBLujfAv6GgP6g8nvg/iBxEm112t3YPPq+Buqe2+imvElTka+bjNlJ/gB56TD8g==, + integrity: sha512-+tNWdlWKbpB3WgBN7ijjYkq9X5uhjmcvyjEght4NmH5fAU++zfQzAJ6wumLS+dNcvwEZhKx2Z+skY8m7v0wGSA==, } - engines: { node: ">=14.0.0" } + engines: { node: ">=16.0.0 || 14 >= 14.17" } peerDependencies: - rollup: ^2.68.0||^3.0.0 + rollup: ^2.68.0||^3.0.0||^4.0.0 peerDependenciesMeta: rollup: optional: true - "@rollup/pluginutils@4.2.1": - resolution: - { - integrity: sha512-iKnFXr7NkdZAIHiIWE+BX5ULi/ucVFYWD6TbAV+rZctiRTY2PL6tsIKhoIOaoskiWAkgu+VsbXgUVDNLHf+InQ==, - } - engines: { node: ">= 8.0.0" } - "@rollup/pluginutils@5.2.0": resolution: { @@ -1243,6 +2103,174 @@ packages: rollup: optional: true + "@rollup/rollup-android-arm-eabi@4.50.1": + resolution: + { + integrity: sha512-HJXwzoZN4eYTdD8bVV22DN8gsPCAj3V20NHKOs8ezfXanGpmVPR7kalUHd+Y31IJp9stdB87VKPFbsGY3H/2ag==, + } + cpu: [arm] + os: [android] + + "@rollup/rollup-android-arm64@4.50.1": + resolution: + { + integrity: sha512-PZlsJVcjHfcH53mOImyt3bc97Ep3FJDXRpk9sMdGX0qgLmY0EIWxCag6EigerGhLVuL8lDVYNnSo8qnTElO4xw==, + } + cpu: [arm64] + os: [android] + + "@rollup/rollup-darwin-arm64@4.50.1": + resolution: + { + integrity: sha512-xc6i2AuWh++oGi4ylOFPmzJOEeAa2lJeGUGb4MudOtgfyyjr4UPNK+eEWTPLvmPJIY/pgw6ssFIox23SyrkkJw==, + } + cpu: [arm64] + os: [darwin] + + "@rollup/rollup-darwin-x64@4.50.1": + resolution: + { + integrity: sha512-2ofU89lEpDYhdLAbRdeyz/kX3Y2lpYc6ShRnDjY35bZhd2ipuDMDi6ZTQ9NIag94K28nFMofdnKeHR7BT0CATw==, + } + cpu: [x64] + os: [darwin] + + "@rollup/rollup-freebsd-arm64@4.50.1": + resolution: + { + integrity: sha512-wOsE6H2u6PxsHY/BeFHA4VGQN3KUJFZp7QJBmDYI983fgxq5Th8FDkVuERb2l9vDMs1D5XhOrhBrnqcEY6l8ZA==, + } + cpu: [arm64] + os: [freebsd] + + "@rollup/rollup-freebsd-x64@4.50.1": + resolution: + { + integrity: sha512-A/xeqaHTlKbQggxCqispFAcNjycpUEHP52mwMQZUNqDUJFFYtPHCXS1VAG29uMlDzIVr+i00tSFWFLivMcoIBQ==, + } + cpu: [x64] + os: [freebsd] + + "@rollup/rollup-linux-arm-gnueabihf@4.50.1": + resolution: + { + integrity: sha512-54v4okehwl5TaSIkpp97rAHGp7t3ghinRd/vyC1iXqXMfjYUTm7TfYmCzXDoHUPTTf36L8pr0E7YsD3CfB3ZDg==, + } + cpu: [arm] + os: [linux] + + "@rollup/rollup-linux-arm-musleabihf@4.50.1": + resolution: + { + integrity: sha512-p/LaFyajPN/0PUHjv8TNyxLiA7RwmDoVY3flXHPSzqrGcIp/c2FjwPPP5++u87DGHtw+5kSH5bCJz0mvXngYxw==, + } + cpu: [arm] + os: [linux] + + "@rollup/rollup-linux-arm64-gnu@4.50.1": + resolution: + { + integrity: sha512-2AbMhFFkTo6Ptna1zO7kAXXDLi7H9fGTbVaIq2AAYO7yzcAsuTNWPHhb2aTA6GPiP+JXh85Y8CiS54iZoj4opw==, + } + cpu: [arm64] + os: [linux] + + "@rollup/rollup-linux-arm64-musl@4.50.1": + resolution: + { + integrity: sha512-Cgef+5aZwuvesQNw9eX7g19FfKX5/pQRIyhoXLCiBOrWopjo7ycfB292TX9MDcDijiuIJlx1IzJz3IoCPfqs9w==, + } + cpu: [arm64] + os: [linux] + + "@rollup/rollup-linux-loongarch64-gnu@4.50.1": + resolution: + { + integrity: sha512-RPhTwWMzpYYrHrJAS7CmpdtHNKtt2Ueo+BlLBjfZEhYBhK00OsEqM08/7f+eohiF6poe0YRDDd8nAvwtE/Y62Q==, + } + cpu: [loong64] + os: [linux] + + "@rollup/rollup-linux-ppc64-gnu@4.50.1": + resolution: + { + integrity: sha512-eSGMVQw9iekut62O7eBdbiccRguuDgiPMsw++BVUg+1K7WjZXHOg/YOT9SWMzPZA+w98G+Fa1VqJgHZOHHnY0Q==, + } + cpu: [ppc64] + os: [linux] + + "@rollup/rollup-linux-riscv64-gnu@4.50.1": + resolution: + { + integrity: sha512-S208ojx8a4ciIPrLgazF6AgdcNJzQE4+S9rsmOmDJkusvctii+ZvEuIC4v/xFqzbuP8yDjn73oBlNDgF6YGSXQ==, + } + cpu: [riscv64] + os: [linux] + + "@rollup/rollup-linux-riscv64-musl@4.50.1": + resolution: + { + integrity: sha512-3Ag8Ls1ggqkGUvSZWYcdgFwriy2lWo+0QlYgEFra/5JGtAd6C5Hw59oojx1DeqcA2Wds2ayRgvJ4qxVTzCHgzg==, + } + cpu: [riscv64] + os: [linux] + + "@rollup/rollup-linux-s390x-gnu@4.50.1": + resolution: + { + integrity: sha512-t9YrKfaxCYe7l7ldFERE1BRg/4TATxIg+YieHQ966jwvo7ddHJxPj9cNFWLAzhkVsbBvNA4qTbPVNsZKBO4NSg==, + } + cpu: [s390x] + os: [linux] + + "@rollup/rollup-linux-x64-gnu@4.50.1": + resolution: + { + integrity: sha512-MCgtFB2+SVNuQmmjHf+wfI4CMxy3Tk8XjA5Z//A0AKD7QXUYFMQcns91K6dEHBvZPCnhJSyDWLApk40Iq/H3tA==, + } + cpu: [x64] + os: [linux] + + "@rollup/rollup-linux-x64-musl@4.50.1": + resolution: + { + integrity: sha512-nEvqG+0jeRmqaUMuwzlfMKwcIVffy/9KGbAGyoa26iu6eSngAYQ512bMXuqqPrlTyfqdlB9FVINs93j534UJrg==, + } + cpu: [x64] + os: [linux] + + "@rollup/rollup-openharmony-arm64@4.50.1": + resolution: + { + integrity: sha512-RDsLm+phmT3MJd9SNxA9MNuEAO/J2fhW8GXk62G/B4G7sLVumNFbRwDL6v5NrESb48k+QMqdGbHgEtfU0LCpbA==, + } + cpu: [arm64] + os: [openharmony] + + "@rollup/rollup-win32-arm64-msvc@4.50.1": + resolution: + { + integrity: sha512-hpZB/TImk2FlAFAIsoElM3tLzq57uxnGYwplg6WDyAxbYczSi8O2eQ+H2Lx74504rwKtZ3N2g4bCUkiamzS6TQ==, + } + cpu: [arm64] + os: [win32] + + "@rollup/rollup-win32-ia32-msvc@4.50.1": + resolution: + { + integrity: sha512-SXjv8JlbzKM0fTJidX4eVsH+Wmnp0/WcD8gJxIZyR6Gay5Qcsmdbi9zVtnbkGPG8v2vMR1AD06lGWy5FLMcG7A==, + } + cpu: [ia32] + os: [win32] + + "@rollup/rollup-win32-x64-msvc@4.50.1": + resolution: + { + integrity: sha512-StxAO/8ts62KZVRAm4JZYq9+NqNsV7RvimNK+YM7ry//zebEH6meuugqW/P5OFUCjyQgui+9fUxT6d5NShvMvA==, + } + cpu: [x64] + os: [win32] + "@rtsao/scc@1.1.0": resolution: { @@ -1255,126 +2283,209 @@ packages: integrity: sha512-5EwMtOqvJMMa3HbmxLlF74e+3/HhwBTMcvt3nqVJgGCozO6hzIPOBlwm8mGVNR9SN2IJpxSnlxczyDjcn7qIyw==, } - "@sentry-internal/feedback@7.120.4": + "@sentry-internal/browser-utils@10.11.0": resolution: { - integrity: sha512-eSwgvTdrh03zYYaI6UVOjI9p4VmKg6+c2+CBQfRZX++6wwnCVsNv7XF7WUIpVGBAkJ0N2oapjQmCzJKGKBRWQg==, + integrity: sha512-fnMlz5ntap6x4vRsLOHwPqXh7t82StgAiRt+EaqcMX0t9l8C0w0df8qwrONKXvE5GdHWTNFJj5qR15FERSkg3Q==, } - engines: { node: ">=12" } + engines: { node: ">=18" } - "@sentry-internal/replay-canvas@7.120.4": + "@sentry-internal/feedback@10.11.0": resolution: { - integrity: sha512-2+W4CgUL1VzrPjArbTid4WhKh7HH21vREVilZdvffQPVwOEpgNTPAb69loQuTlhJVveh9hWTj2nE5UXLbLP+AA==, + integrity: sha512-ADey51IIaa29kepb8B7aSgSGSrcyT7QZdRsN1rhitefzrruHzpSUci5c2EPIvmWfKJq8Wnvukm9BHXZXAAIOzA==, } - engines: { node: ">=12" } + engines: { node: ">=18" } - "@sentry-internal/tracing@7.120.4": + "@sentry-internal/replay-canvas@10.11.0": resolution: { - integrity: sha512-Fz5+4XCg3akeoFK+K7g+d7HqGMjmnLoY2eJlpONJmaeT9pXY7yfUyXKZMmMajdE2LxxKJgQ2YKvSCaGVamTjHw==, + integrity: sha512-brWQ90IYQyZr44IpTprlmvbtz4l2ABzLdpP94Egh12Onf/q6n4CjLKaA25N5kX0uggHqX1Rs7dNaG0mP3ETHhA==, } - engines: { node: ">=8" } + engines: { node: ">=18" } - "@sentry/browser@7.120.4": + "@sentry-internal/replay@10.11.0": resolution: { - integrity: sha512-ymlNtIPG6HAKzM/JXpWVGCzCNufZNADfy+O/olZuVJW5Be1DtOFyRnBvz0LeKbmxJbXb2lX/XMhuen6PXPdoQw==, + integrity: sha512-t4M2bxMp2rKGK/l7bkVWjN+xVw9H9V12jAeXmO/Fskz2RcG1ZNLQnKSx/W/zCRMk8k7xOQFsfiApq+zDN+ziKA==, } - engines: { node: ">=8" } + engines: { node: ">=18" } - "@sentry/cli@1.77.3": + "@sentry/babel-plugin-component-annotate@4.3.0": resolution: { - integrity: sha512-c3eDqcDRmy4TFz2bFU5Y6QatlpoBPPa8cxBooaS4aMQpnIdLYPF1xhyyiW0LQlDUNc3rRjNF7oN5qKoaRoMTQQ==, + integrity: sha512-OuxqBprXRyhe8Pkfyz/4yHQJc5c3lm+TmYWSSx8u48g5yKewSQDOxkiLU5pAk3WnbLPy8XwU/PN+2BG0YFU9Nw==, } - engines: { node: ">= 8" } + engines: { node: ">= 14" } + + "@sentry/browser@10.11.0": + resolution: + { + integrity: sha512-qemaKCJKJHHCyGBpdLq23xL5u9Xvir20XN7YFTnHcEq4Jvj0GoWsslxKi5cQB2JvpYn62WxTiDgVLeQlleZhSg==, + } + engines: { node: ">=18" } + + "@sentry/bundler-plugin-core@4.3.0": + resolution: + { + integrity: sha512-dmR4DJhJ4jqVWGWppuTL2blNFqOZZnt4aLkewbD1myFG3KVfUx8CrMQWEmGjkgPOtj5TO6xH9PyTJjXC6o5tnA==, + } + engines: { node: ">= 14" } + + "@sentry/cli-darwin@2.53.0": + resolution: + { + integrity: sha512-NNPfpILMwKgpHiyJubHHuauMKltkrgLQ5tvMdxNpxY60jBNdo5VJtpESp4XmXlnidzV4j1z61V4ozU6ttDgt5Q==, + } + engines: { node: ">=10" } + os: [darwin] + + "@sentry/cli-linux-arm64@2.53.0": + resolution: + { + integrity: sha512-xY/CZ1dVazsSCvTXzKpAgXaRqfljVfdrFaYZRUaRPf1ZJRGa3dcrivoOhSIeG/p5NdYtMvslMPY9Gm2MT0M83A==, + } + engines: { node: ">=10" } + cpu: [arm64] + os: [linux, freebsd, android] + + "@sentry/cli-linux-arm@2.53.0": + resolution: + { + integrity: sha512-NdRzQ15Ht83qG0/Lyu11ciy/Hu/oXbbtJUgwzACc7bWvHQA8xEwTsehWexqn1529Kfc5EjuZ0Wmj3MHmp+jOWw==, + } + engines: { node: ">=10" } + cpu: [arm] + os: [linux, freebsd, android] + + "@sentry/cli-linux-i686@2.53.0": + resolution: + { + integrity: sha512-0REmBibGAB4jtqt9S6JEsFF4QybzcXHPcHtJjgMi5T0ueh952uG9wLzjSxQErCsxTKF+fL8oG0Oz5yKBuCwCCQ==, + } + engines: { node: ">=10" } + cpu: [x86, ia32] + os: [linux, freebsd, android] + + "@sentry/cli-linux-x64@2.53.0": + resolution: + { + integrity: sha512-9UGJL+Vy5N/YL1EWPZ/dyXLkShlNaDNrzxx4G7mTS9ywjg+BIuemo6rnN7w43K1NOjObTVO6zY0FwumJ1pCyLg==, + } + engines: { node: ">=10" } + cpu: [x64] + os: [linux, freebsd, android] + + "@sentry/cli-win32-arm64@2.53.0": + resolution: + { + integrity: sha512-G1kjOjrjMBY20rQcJV2GA8KQE74ufmROCDb2GXYRfjvb1fKAsm4Oh8N5+Tqi7xEHdjQoLPkE4CNW0aH68JSUDQ==, + } + engines: { node: ">=10" } + cpu: [arm64] + os: [win32] + + "@sentry/cli-win32-i686@2.53.0": + resolution: + { + integrity: sha512-qbGTZUzesuUaPtY9rPXdNfwLqOZKXrJRC1zUFn52hdo6B+Dmv0m/AHwRVFHZP53Tg1NCa8bDei2K/uzRN0dUZw==, + } + engines: { node: ">=10" } + cpu: [x86, ia32] + os: [win32] + + "@sentry/cli-win32-x64@2.53.0": + resolution: + { + integrity: sha512-1TXYxYHtwgUq5KAJt3erRzzUtPqg7BlH9T7MdSPHjJatkrr/kwZqnVe2H6Arr/5NH891vOlIeSPHBdgJUAD69g==, + } + engines: { node: ">=10" } + cpu: [x64] + os: [win32] + + "@sentry/cli@2.53.0": + resolution: + { + integrity: sha512-n2ZNb+5Z6AZKQSI0SusQ7ZzFL637mfw3Xh4C3PEyVSn9LiF683fX0TTq8OeGmNZQS4maYfS95IFD+XpydU0dEA==, + } + engines: { node: ">= 10" } hasBin: true - "@sentry/core@7.120.4": + "@sentry/core@10.11.0": resolution: { - integrity: sha512-TXu3Q5kKiq8db9OXGkWyXUbIxMMuttB5vJ031yolOl5T/B69JRyAoKuojLBjRv1XX583gS1rSSoX8YXX7ATFGA==, + integrity: sha512-39Rxn8cDXConx3+SKOCAhW+/hklM7UDaz+U1OFzFMDlT59vXSpfI6bcXtNiFDrbOxlQ2hX8yAqx8YRltgSftoA==, } - engines: { node: ">=8" } + engines: { node: ">=18" } - "@sentry/integrations@7.120.4": + "@sentry/nextjs@10.11.0": resolution: { - integrity: sha512-kkBTLk053XlhDCg7OkBQTIMF4puqFibeRO3E3YiVc4PGLnocXMaVpOSCkMqAc1k1kZ09UgGi8DxfQhnFEjUkpA==, + integrity: sha512-oMRmRW982H6kNlUHNij5QAro8Kbi43r3VrcrKtrx7LgjHOUTFUvZmJeynC+T+PcMgLhQNvCC3JgzOhfSqxOChg==, } - engines: { node: ">=8" } - - "@sentry/nextjs@7.120.4": - resolution: - { - integrity: sha512-1wtyDP1uiVvYqaJyCgXfP69eqyDgJrd6lERAVd4WqXNVEIs4vBT8oxfPQz6gxG2SJJUiTyQRjubMxuEc7dPoGQ==, - } - engines: { node: ">=8" } + engines: { node: ">=18" } peerDependencies: - next: ^10.0.8 || ^11.0 || ^12.0 || ^13.0 || ^14.0 - react: 16.x || 17.x || 18.x - webpack: ">= 4.0.0" - peerDependenciesMeta: - webpack: - optional: true + next: ^13.2.0 || ^14.0 || ^15.0.0-rc.0 - "@sentry/node@7.120.4": + "@sentry/node-core@10.11.0": resolution: { - integrity: sha512-qq3wZAXXj2SRWhqErnGCSJKUhPSlZ+RGnCZjhfjHpP49KNpcd9YdPTIUsFMgeyjdh6Ew6aVCv23g1hTP0CHpYw==, + integrity: sha512-dkVZ06F+W5W0CsD47ATTTOTTocmccT/ezrF9idspQq+HVOcjoKSU60WpWo22NjtVNdSYKLnom0q1LKRoaRA/Ww==, } - engines: { node: ">=8" } - - "@sentry/react@7.120.4": - resolution: - { - integrity: sha512-Pj1MSezEncE+5riuwsk8peMncuz5HR72Yr1/RdZhMZvUxoxAR/tkwD3aPcK6ddQJTagd2TGwhdr9SHuDLtONew==, - } - engines: { node: ">=8" } + engines: { node: ">=18" } peerDependencies: - react: 15.x || 16.x || 17.x || 18.x + "@opentelemetry/api": ^1.9.0 + "@opentelemetry/context-async-hooks": ^1.30.1 || ^2.0.0 + "@opentelemetry/core": ^1.30.1 || ^2.0.0 + "@opentelemetry/instrumentation": ">=0.57.1 <1" + "@opentelemetry/resources": ^1.30.1 || ^2.0.0 + "@opentelemetry/sdk-trace-base": ^1.30.1 || ^2.0.0 + "@opentelemetry/semantic-conventions": ^1.34.0 - "@sentry/replay@7.120.4": + "@sentry/node@10.11.0": resolution: { - integrity: sha512-FW8sPenNFfnO/K7sncsSTX4rIVak9j7VUiLIagJrcqZIC7d1dInFNjy8CdVJUlyz3Y3TOgIl3L3+ZpjfyMnaZg==, + integrity: sha512-Tbcjr3iQAEjYi7/QIpdS8afv/LU1TwDTiy5x87MSpVEoeFcZ7f2iFC4GV0fhB3p4qDuFdL2JGVsIIrzapp8Y4A==, } - engines: { node: ">=12" } + engines: { node: ">=18" } - "@sentry/types@7.120.4": + "@sentry/opentelemetry@10.11.0": resolution: { - integrity: sha512-cUq2hSSe6/qrU6oZsEP4InMI5VVdD86aypE+ENrQ6eZEVLTCYm1w6XhW1NvIu3UuWh7gZec4a9J7AFpYxki88Q==, + integrity: sha512-BY2SsVlRKICzNUO9atUy064BZqYnhV5A/O+JjEx0kj7ylq+oZd++zmGkks00rSwaJE220cVcVhpwqxcFUpc2hw==, } - engines: { node: ">=8" } + engines: { node: ">=18" } + peerDependencies: + "@opentelemetry/api": ^1.9.0 + "@opentelemetry/context-async-hooks": ^1.30.1 || ^2.0.0 + "@opentelemetry/core": ^1.30.1 || ^2.0.0 + "@opentelemetry/sdk-trace-base": ^1.30.1 || ^2.0.0 + "@opentelemetry/semantic-conventions": ^1.34.0 - "@sentry/utils@7.120.4": + "@sentry/react@10.11.0": resolution: { - integrity: sha512-zCKpyDIWKHwtervNK2ZlaK8mMV7gVUijAgFeJStH+CU/imcdquizV3pFLlSQYRswG+Lbyd6CT/LGRh3IbtkCFw==, + integrity: sha512-bE4lJ5Ni/n9JUdLWGG99yucY0/zOUXjKl9gfSTkvUvOiAIX/bY0Y4WgOqeWySvbMz679ZdOwF34k8RA/gI7a8g==, } - engines: { node: ">=8" } + engines: { node: ">=18" } + peerDependencies: + react: ^16.14.0 || 17.x || 18.x || 19.x - "@sentry/vercel-edge@7.120.4": + "@sentry/vercel-edge@10.11.0": resolution: { - integrity: sha512-wZMnF7Rt2IBfStQTVDhjShEtLcsH1WNc7YVgzoibuIeRDrEmyx/MFIsru2BkhWnz7m0TRnWXxA40cH+6VZsf5w==, + integrity: sha512-jAsJ8RbbF2JWj2wnXfd6BwWxCR6GBITMtlaoWc7pG22HknEtoH15dKsQC3Ew5r/KRcofr2e+ywdnBn5CPr1Pbg==, } - engines: { node: ">=8" } + engines: { node: ">=18" } - "@sentry/webpack-plugin@1.21.0": + "@sentry/webpack-plugin@4.3.0": resolution: { - integrity: sha512-x0PYIMWcsTauqxgl7vWUY6sANl+XGKtx7DCVnnY7aOIIlIna0jChTAPANTfA2QrK+VK+4I/4JxatCEZBnXh3Og==, - } - engines: { node: ">= 8" } - - "@sinclair/typebox@0.25.24": - resolution: - { - integrity: sha512-XJfwUVUKDHF5ugKwIcxEgc9k8b7HbznCp6eUfWgu710hMPNIO4aw4/zB5RogDQz8nd6gyCDpU9O/m6qYEWY6yQ==, + integrity: sha512-K4nU1SheK/tvyakBws2zfd+MN6hzmpW+wPTbSbDWn1+WL9+g9hsPh8hjFFiVe47AhhUoUZ3YgiH2HyeHXjHflA==, } + engines: { node: ">= 14" } + peerDependencies: + webpack: ">=4.40.0" "@sinclair/typebox@0.27.8": resolution: @@ -1382,6 +2493,24 @@ packages: integrity: sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA==, } + "@sinclair/typebox@0.34.41": + resolution: + { + integrity: sha512-6gS8pZzSXdyRHTIqoqSVknxolr1kzfy4/CeDnrzsVz8TTIWUbOBr6gnzOmTYJ3eXQNh4IYHIGi5aIL7sOZ2G/g==, + } + + "@sinonjs/commons@3.0.1": + resolution: + { + integrity: sha512-K3mCHKQ9sVh8o1C9cxkwxaOmXoAMlDxC1mYyHrjqOWEcBjYr76t96zL2zlj5dUGZ3HSw240X1qgH3Mjf1yJWpQ==, + } + + "@sinonjs/fake-timers@13.0.5": + resolution: + { + integrity: sha512-36/hTbH2uaWuGVERyC6da9YwGWnzUZXuPro/F2LfsdOsLnCojz/iSH8MxUt/FD2S5XBSVPhmArFUXcpCQ2Hkiw==, + } + "@socket.io/component-emitter@3.1.2": resolution: { @@ -1400,10 +2529,10 @@ packages: integrity: sha512-e7Mew686owMaPJVNNLs55PUvgz371nKgwsc4vxE49zsODpJEnxgxRo2y/OKrqueavXgZNMDVj3DdHFlaSAeU8g==, } - "@swc/counter@0.1.3": + "@swc/helpers@0.5.15": resolution: { - integrity: sha512-e2BR4lsJkkRlKZ/qCHPw9ZaSxc0MVUd7gtbtaB7aMvHeJVYe8sOB8DBZkP2DtISHGSku9sCK6T6cnY0CtXrOCQ==, + integrity: sha512-JQ5TuMi45Owi4/BIMAJBoSQoOJu12oOk/gADqlcUL9JEdHB8vyjUSsxqeNXnmXHjYKMi2WcYtezGEEhqUI/E2g==, } "@swc/helpers@0.5.17": @@ -1412,24 +2541,19 @@ packages: integrity: sha512-5IKx/Y13RsYd+sauPb2x+U/xZikHjolzfuDgTAl/Tdf3Q8rslRvC19NKDLgAJQ6wsqADk10ntlv08nPFw/gO/A==, } - "@swc/helpers@0.5.5": + "@tanstack/query-core@5.85.9": resolution: { - integrity: sha512-KGYxvIOXcceOAbEk4bi/dVLEK9z8sZ0uBB3Il5b1rhfClSpcX0yfRO0KmTkqR2cnQDymwLB+25ZyMzICg/cm/A==, + integrity: sha512-5fxb9vwyftYE6KFLhhhDyLr8NO75+Wpu7pmTo+TkwKmMX2oxZDoLwcqGP8ItKSpUMwk3urWgQDZfyWr5Jm9LsQ==, } - "@tootallnate/once@2.0.0": + "@tanstack/react-query@5.85.9": resolution: { - integrity: sha512-XCuKFP5PS55gnMVu3dty8KPatLqUoy/ZYzDzAGCQ8JNFCkLXzmI7vNHCR+XpbZaMWQK/vQubr7PkYq8g470J/A==, - } - engines: { node: ">= 10" } - - "@ts-morph/common@0.11.1": - resolution: - { - integrity: sha512-7hWZS0NRpEsNV8vWJzg7FEz6V8MaLNeJOmwmghqUXTpzk16V1LLZhdo+4QvE/+zv4cVci0OviuJFnqhEfoV3+g==, + integrity: sha512-2T5zgSpcOZXGkH/UObIbIkGmUPQqZqn7esVQFXLOze622h4spgWf5jmvrqAo9dnI13/hyMcNsF1jsoDcb59nJQ==, } + peerDependencies: + react: ^18 || ^19 "@tsconfig/node10@1.0.11": resolution: @@ -1461,12 +2585,54 @@ packages: integrity: sha512-VyyPYFlOMNylG45GoAe0xDoLwWuowvf92F9kySqzYh8vmYm7D2u4iUJKa1tOUpS70Ku13ASrOkS4ScXFsTaCNQ==, } + "@types/babel__core@7.20.5": + resolution: + { + integrity: sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==, + } + + "@types/babel__generator@7.27.0": + resolution: + { + integrity: sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg==, + } + + "@types/babel__template@7.4.4": + resolution: + { + integrity: sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==, + } + + "@types/babel__traverse@7.28.0": + resolution: + { + integrity: sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q==, + } + + "@types/connect@3.4.38": + resolution: + { + integrity: sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug==, + } + "@types/debug@4.1.12": resolution: { integrity: sha512-vIChWdVG3LG1SMxEvI/AK+FWJthlrqlTu7fbrlywTkkaONwk/UAGaULXRlf8vkzFBLVm0zkMdCquhL5aOjhXPQ==, } + "@types/eslint-scope@3.7.7": + resolution: + { + integrity: sha512-MzMFlSLBqNF2gcHWO0G1vP/YQyfvrxZ0bF+u7mzUdZ1/xK4A4sru+nraZz5i3iEIk1l1uyicaDVTB4QbbEkAYg==, + } + + "@types/eslint@9.6.1": + resolution: + { + integrity: sha512-FXx2pKgId/WyYo2jXw63kk7/+TY7u7AziEJxJAnSFzHlqTAS3Ync6SvgYAN/k4/PQpnnVuzoMuVnByKK2qp0ag==, + } + "@types/estree-jsx@1.0.5": resolution: { @@ -1491,6 +2657,13 @@ packages: integrity: sha512-WPs+bbQw5aCj+x6laNGWLH3wviHtoCv/P3+otBhbOhJgG8qtpdAMlTCxLtsTWA7LH1Oh/bFCHsBn0TPS5m30EQ==, } + "@types/ioredis@5.0.0": + resolution: + { + integrity: sha512-zJbJ3FVE17CNl5KXzdeSPtdltc4tMT3TzC6fxQS0sQngkbFZ6h+0uTafsRqu+eSLIugf6Yb0Ea0SUuRr42Nk9g==, + } + deprecated: This is a stub types definition. ioredis provides its own type definitions, so you do not need this installed. + "@types/istanbul-lib-coverage@2.0.6": resolution: { @@ -1509,6 +2682,12 @@ packages: integrity: sha512-pk2B1NWalF9toCRu6gjBzR69syFjP4Od8WRAX+0mmf9lAjCRicLOWc+ZrxZHx/0XRjotgkF9t6iaMJ+aXcOdZQ==, } + "@types/jest@30.0.0": + resolution: + { + integrity: sha512-XTYugzhuwqWjws0CVz8QpM36+T+Dz5mTEBKhNs/esGLnCIlGdRy+Dq78NRjd7ls7r8BC8ZRMOrKlkO1hU0JOwA==, + } + "@types/json-schema@7.0.15": resolution: { @@ -1533,30 +2712,48 @@ packages: integrity: sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA==, } + "@types/mysql@2.15.27": + resolution: + { + integrity: sha512-YfWiV16IY0OeBfBCk8+hXKmdTKrKlwKN1MNKAPBu5JYxLwBEZl7QzeEpGnlZb3VMGJrrGmB84gXiH+ofs/TezA==, + } + "@types/node-fetch@2.6.13": resolution: { integrity: sha512-QGpRVpzSaUs30JBSGPjOg4Uveu384erbHBoT1zeONvyCfwQxIkUshLAOqN/k9EjGviPRmWTTe6aH2qySWKTVSw==, } - "@types/node@16.18.11": - resolution: - { - integrity: sha512-3oJbGBUWuS6ahSnEq1eN2XrCyf4YsWI8OyCvo7c64zQJNplk3mO84t53o8lfTk+2ji59g5ycfc6qQ3fdHliHuA==, - } - "@types/node@24.2.1": resolution: { integrity: sha512-DRh5K+ka5eJic8CjH7td8QpYEV6Zo10gfRkjHCO3weqZHWDtAaSTFtl4+VMqOJ4N5jcuhZ9/l+yy8rVgw7BQeQ==, } + "@types/node@24.3.1": + resolution: + { + integrity: sha512-3vXmQDXy+woz+gnrTvuvNrPzekOi+Ds0ReMxw0LzBiK3a+1k0kQn9f2NWk+lgD4rJehFUmYy2gMhJ2ZI+7YP9g==, + } + "@types/parse-json@4.0.2": resolution: { integrity: sha512-dISoDXWWQwUquiKsyZ4Ng+HX2KsPL7LyHKHQwgGFEA3IaKac4Obd+h2a/a6waisAoepJlBcx9paWqjA8/HVjCw==, } + "@types/pg-pool@2.0.6": + resolution: + { + integrity: sha512-TaAUE5rq2VQYxab5Ts7WZhKNmuN78Q6PiFonTDdpbx8a1H0M1vhy3rhiMjl+e2iHmogyMw7jZF4FrE6eJUy5HQ==, + } + + "@types/pg@8.15.4": + resolution: + { + integrity: sha512-I6UNVBAoYbvuWkkU3oosC8yxqH21f4/Jc4DK71JLG3dT2mdlGe1z+ep/LQGXaKaOgcvUrsQoPRqfgtMcvZiJhg==, + } + "@types/prop-types@15.7.15": resolution: { @@ -1575,6 +2772,24 @@ packages: integrity: sha512-WFHp9YUJQ6CKshqoC37iOlHnQSmxNc795UhB26CyBBttrN9svdIrUjl/NjnNmfcwtncN0h/0PPAFWv9ovP8mLA==, } + "@types/shimmer@1.2.0": + resolution: + { + integrity: sha512-UE7oxhQLLd9gub6JKIAhDq06T0F6FnztwMNRvYgjeQSBeMc1ZG/tA47EwfduvkuQS8apbkM/lpLpWsaCeYsXVg==, + } + + "@types/stack-utils@2.0.3": + resolution: + { + integrity: sha512-9aEbYZ3TbYMznPdcdr3SmIrLXwC/AKZXQeCf9Pgao5CKb8CyHuEX5jzWPTkvregvhRJHcpRO6BFoGW9ycaOkYw==, + } + + "@types/tedious@4.0.14": + resolution: + { + integrity: sha512-KHPsfX/FoVbUGbyYvk1q9MMQHLPeRZhRJZdO45Q4YjvFkv4hMNghCWTvy7rdKessBsmtz4euWCWAB6/tVpI1Iw==, + } + "@types/ua-parser-js@0.7.39": resolution: { @@ -1888,135 +3103,94 @@ packages: cpu: [x64] os: [win32] - "@upstash/redis@1.35.3": + "@webassemblyjs/ast@1.14.1": resolution: { - integrity: sha512-hSjv66NOuahW3MisRGlSgoszU2uONAY2l5Qo3Sae8OT3/Tng9K+2/cBRuyPBX8egwEGcNNCF9+r0V6grNnhL+w==, + integrity: sha512-nuBEDgQfm1ccRp/8bCQrx1frohyufl4JlbMMZ4P1wpeOfDhF6FQkxZJ1b/e+PLwr6X1Nhw6OLme5usuBWYBvuQ==, } - "@vercel/build-utils@8.4.12": + "@webassemblyjs/floating-point-hex-parser@1.13.2": resolution: { - integrity: sha512-pIH0b965wJhd1otROVPndfZenPKFVoYSaRjtSKVOT/oNBT13ifq86UVjb5ZjoVfqUI2TtSTP+68kBqLPeoq30g==, + integrity: sha512-6oXyTOzbKxGH4steLbLNOu71Oj+C8Lg34n6CqRvqfS2O71BxY6ByfMDRhBytzknj9yGUPVJ1qIKhRlAwO1AovA==, } - "@vercel/edge-config-fs@0.1.0": + "@webassemblyjs/helper-api-error@1.13.2": resolution: { - integrity: sha512-NRIBwfcS0bUoUbRWlNGetqjvLSwgYH/BqKqDN7vK1g32p7dN96k0712COgaz6VFizAm9b0g6IG6hR6+hc0KCPg==, + integrity: sha512-U56GMYxy4ZQCbDZd6JuvvNV/WFildOjsaWD3Tzzvmw/mas3cXzRJPMjP83JqEsgSbyrmaGjBfDtV7KDXV9UzFQ==, } - "@vercel/edge-config@0.4.1": + "@webassemblyjs/helper-buffer@1.14.1": resolution: { - integrity: sha512-4Mc3H7lE+x4RrL17nY8CWeEorvJHbkNbQTy9p8H1tO7y11WeKj5xeZSr07wNgfWInKXDUwj5FZ3qd/jIzjPxug==, - } - engines: { node: ">=14.6" } - - "@vercel/error-utils@2.0.2": - resolution: - { - integrity: sha512-Sj0LFafGpYr6pfCqrQ82X6ukRl5qpmVrHM/191kNYFqkkB9YkjlMAj6QcEsvCG259x4QZ7Tya++0AB85NDPbKQ==, + integrity: sha512-jyH7wtcHiKssDtFPRB+iQdxlDf96m0E39yb0k5uJVhFGleZFoNw1c4aeIcVUPPbXUVJ94wwnMOAqUHyzoEPVMA==, } - "@vercel/fun@1.1.0": + "@webassemblyjs/helper-numbers@1.13.2": resolution: { - integrity: sha512-SpuPAo+MlAYMtcMcC0plx7Tv4Mp7SQhJJj1iIENlOnABL24kxHpL09XLQMGzZIzIW7upR8c3edwgfpRtp+dhVw==, - } - engines: { node: ">= 10" } - - "@vercel/gatsby-plugin-vercel-analytics@1.0.11": - resolution: - { - integrity: sha512-iTEA0vY6RBPuEzkwUTVzSHDATo1aF6bdLLspI68mQ/BTbi5UQEGjpjyzdKOVcSYApDtFU6M6vypZ1t4vIEnHvw==, + integrity: sha512-FE8aCmS5Q6eQYcV3gI35O4J789wlQA+7JrqTTpJqn5emA4U2hvwJmvFRC0HODS+3Ye6WioDklgd6scJ3+PLnEA==, } - "@vercel/gatsby-plugin-vercel-builder@2.0.56": + "@webassemblyjs/helper-wasm-bytecode@1.13.2": resolution: { - integrity: sha512-SZM8k/YcOcfk2p1cSZOuSK37CDBJtF/WiEr8CemDI/MBbXM4aC2StfzDd0F0cK/2rExpSA9lTAE9ia3w+cDS9w==, + integrity: sha512-3QbLKy93F0EAIXLh0ogEVR6rOubA9AoZ+WRYhNbFyuB70j3dRdwH9g+qXhLAO0kiYGlg3TxDV+I4rQTr/YNXkA==, } - "@vercel/go@3.2.0": + "@webassemblyjs/helper-wasm-section@1.14.1": resolution: { - integrity: sha512-zUCBoh57x1OEtw+TKdRhSQciqERrpDxLlPeBOYawUCC5uKjsBjhdq0U21+NGz2LcRUaYyYYGMw6BzqVaig9u1g==, + integrity: sha512-ds5mXEqTJ6oxRoqjhWDU83OgzAYjwsCV8Lo/N+oRsNDmx/ZDpqalmrtgOMkHwxsG0iI//3BwWAErYRHtgn0dZw==, } - "@vercel/hydrogen@1.0.9": + "@webassemblyjs/ieee754@1.13.2": resolution: { - integrity: sha512-IPAVaALuGAzt2apvTtBs5tB+8zZRzn/yG3AGp8dFyCsw/v5YOuk0Q5s8Z3fayLvJbFpjrKtqRNDZzVJBBU3MrQ==, + integrity: sha512-4LtOzh58S/5lX4ITKxnAK2USuNEvpdVV9AlgGQb8rJDHaLeHciwG4zlGr0j/SNWlr7x3vO1lDEsuePvtcDNCkw==, } - "@vercel/kv@2.0.0": + "@webassemblyjs/leb128@1.13.2": resolution: { - integrity: sha512-zdVrhbzZBYo5d1Hfn4bKtqCeKf0FuzW8rSHauzQVMUgv1+1JOwof2mWcBuI+YMJy8s0G0oqAUfQ7HgUDzb8EbA==, - } - engines: { node: ">=14.6" } - - "@vercel/next@4.3.18": - resolution: - { - integrity: sha512-ih6++AA7/NCcLkMpdsDhr/folMlAKsU1sYUoyOjq4rYE9sSapELtgxls0CArv4ehE2Tt4YwoxBISnKPZKK5SSA==, + integrity: sha512-Lde1oNoIdzVzdkNEAWZ1dZ5orIbff80YPdHx20mrHwHrVNNTjNr8E3xz9BdpcGqRQbAEa+fkrCb+fRFTl/6sQw==, } - "@vercel/nft@0.27.3": + "@webassemblyjs/utf8@1.13.2": resolution: { - integrity: sha512-oySTdDSzUAFDXpsSLk9Q943o+/Yu/+TCFxnehpFQEf/3khi2stMpTHPVNwFdvZq/Z4Ky93lE+MGHpXCRpMkSCA==, - } - engines: { node: ">=16" } - hasBin: true - - "@vercel/node@3.2.24": - resolution: - { - integrity: sha512-KEm50YBmcfRNOw5NfdcqMI4BkP4+5TD9kRwAByHHlIZXLj1NTTknvMF+69sHBYzwpK/SUZIkeo7jTrtcl4g+RQ==, + integrity: sha512-3NQWGjKTASY1xV5m7Hr0iPeXD9+RDobLll3T9d2AO+g3my8xy5peVyjSag4I50mR1bBSN/Ct12lo+R9tJk0NZQ==, } - "@vercel/python@4.3.1": + "@webassemblyjs/wasm-edit@1.14.1": resolution: { - integrity: sha512-pWRApBwUsAQJS8oZ7eKMiaBGbYJO71qw2CZqDFvkTj34FNBZtNIUcWSmqGfJJY5m2pU/9wt8z1CnKIyT9dstog==, + integrity: sha512-RNJUIQH/J8iA/1NzlE4N7KtyZNHi3w7at7hDjvRNm5rcUXa00z1vRz3glZoULfJ5mpvYhLybmVcwcjGrC1pRrQ==, } - "@vercel/redwood@2.1.8": + "@webassemblyjs/wasm-gen@1.14.1": resolution: { - integrity: sha512-qBUBqIDxPEYnxRh3tsvTaPMtBkyK/D2tt9tBugNPe0OeYnMCMXVj9SJYbxiDI2GzAEFUZn4Poh63CZtXMDb9Tg==, + integrity: sha512-AmomSIjP8ZbfGQhumkNvgC33AY7qtMCXnN6bL2u2Js4gVCg8fp735aEiMSBbDR7UQIj90n4wKAFUSEd0QN2Ukg==, } - "@vercel/remix-builder@2.2.13": + "@webassemblyjs/wasm-opt@1.14.1": resolution: { - integrity: sha512-TenVtvfERodSwUjm0rzjz3v00Drd0FUXLWnwdwnv7VLgqmX2FW/2+1byhmPhJicMp3Eybl52GvF2/KbBkNo95w==, + integrity: sha512-PTcKLUNvBqnY2U6E5bdOQcSM+oVP/PmrDY9NzowJjislEjwP/C4an2303MCVS2Mg9d3AJpIGdUFIQQWbPds0Sw==, } - "@vercel/routing-utils@3.1.0": + "@webassemblyjs/wasm-parser@1.14.1": resolution: { - integrity: sha512-Ci5xTjVTJY/JLZXpCXpLehMft97i9fH34nu9PGav6DtwkVUF6TOPX86U0W0niQjMZ5n6/ZP0BwcJK2LOozKaGw==, + integrity: sha512-JLBl+KZ0R5qB7mCnud/yyX08jWFw5MsoalJ1pQ4EdFlgj9VdXKGuENGsiCIjegI1W7p91rUlcB/LB5yRJKNTcQ==, } - "@vercel/ruby@2.1.0": + "@webassemblyjs/wast-printer@1.14.1": resolution: { - integrity: sha512-UZYwlSEEfVnfzTmgkD+kxex9/gkZGt7unOWNyWFN7V/ZnZSsGBUgv6hXLnwejdRi3EztgRQEBd1kUKlXdIeC0Q==, - } - - "@vercel/static-build@2.5.34": - resolution: - { - integrity: sha512-4RL60ghhBufs/45j6J9zQzMpt8JmUhp/4+xE8RxO80n6qTlc/oERKrWxzeXLEGF32whSHsB+ROJt0Ytytoz2Tw==, - } - - "@vercel/static-config@3.0.0": - resolution: - { - integrity: sha512-2qtvcBJ1bGY0dYGYh3iM7yGKkk971FujLEDXzuW5wcZsPr1GSEjO/w2iSr3qve6nDDtBImsGoDEnus5FI4+fIw==, + integrity: sha512-kPSSXE6De1XOR820C90RIo2ogvZG+c3KiHzqUoO/F34Y2shGzesfqv7o57xrxovZJH/MetF5UjroJ/R/3isoiw==, } "@whereby.com/browser-sdk@3.13.1": @@ -2041,6 +3215,18 @@ packages: } engines: { node: ">=16.0.0" } + "@xtuc/ieee754@1.2.0": + resolution: + { + integrity: sha512-DX8nKgqcGwsc0eJSqYt5lwP4DH5FlHnmuWWBRy7X0NcaGR0ZtuyeESgMwTYVEtxmsNGY+qit4QYT/MIYTOTPeA==, + } + + "@xtuc/long@4.2.2": + resolution: + { + integrity: sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ==, + } + "@zag-js/accordion@1.21.0": resolution: { @@ -2458,12 +3644,6 @@ packages: integrity: sha512-yI/CZizbk387TdkDCy9Uc4l53uaeQuWAIJESrmAwwq6yMNbHZ2dm5+1NHdZr/guES5TgyJa/BYJsNJeCsCfesg==, } - abbrev@1.1.1: - resolution: - { - integrity: sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==, - } - acorn-import-attributes@1.9.5: resolution: { @@ -2472,6 +3652,15 @@ packages: peerDependencies: acorn: ^8 + acorn-import-phases@1.0.4: + resolution: + { + integrity: sha512-wKmbr/DDiIXzEOiWrTTUcDm24kQ2vGfZQvM2fwg2vXqR5uW6aapr7ObPtj1th32b9u90/Pf4AItvdTh42fBmVQ==, + } + engines: { node: ">=10.13.0" } + peerDependencies: + acorn: ^8.14.0 + acorn-jsx@5.3.2: resolution: { @@ -2502,18 +3691,58 @@ packages: } engines: { node: ">= 6.0.0" } + agent-base@7.1.4: + resolution: + { + integrity: sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==, + } + engines: { node: ">= 14" } + + ajv-formats@2.1.1: + resolution: + { + integrity: sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA==, + } + peerDependencies: + ajv: ^8.0.0 + peerDependenciesMeta: + ajv: + optional: true + + ajv-keywords@5.1.0: + resolution: + { + integrity: sha512-YCS/JNFAUyr5vAuhk1DWm1CBxRHW9LbJ2ozWeemrIqpbsqKjHVxYPyi5GC0rjZIT5JxJ3virVTS8wk4i/Z+krw==, + } + peerDependencies: + ajv: ^8.8.2 + ajv@6.12.6: resolution: { integrity: sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==, } - ajv@8.6.3: + ajv@8.17.1: resolution: { - integrity: sha512-SMJOdDP6LqTkD0Uq8qLi+gMwSt0imXLSV080qFVwJCpH9U6Mb+SUGHAXM0KNbcBPguytWyvFxcHgMLe2D2XSpw==, + integrity: sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==, } + ansi-colors@4.1.3: + resolution: + { + integrity: sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw==, + } + engines: { node: ">=6" } + + ansi-escapes@4.3.2: + resolution: + { + integrity: sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==, + } + engines: { node: ">=8" } + ansi-regex@5.0.1: resolution: { @@ -2535,6 +3764,13 @@ packages: } engines: { node: ">=8" } + ansi-styles@5.2.0: + resolution: + { + integrity: sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==, + } + engines: { node: ">=10" } + ansi-styles@6.2.1: resolution: { @@ -2555,26 +3791,6 @@ packages: } engines: { node: ">= 8" } - aproba@2.1.0: - resolution: - { - integrity: sha512-tLIEcj5GuR2RSTnxNKdkK0dJ/GrC7P38sUkiDmDuHfsHmbagTFAxDVIBltoklXEVIQ/f14IL8IMJ5pn9Hez1Ew==, - } - - are-we-there-yet@2.0.0: - resolution: - { - integrity: sha512-Ci/qENmwHnsYo9xKIcUJN5LeDKdJ6R1Z1j9V/J5wyq8nh/mYPEpIKJbBZXtZjG04HiK7zV/p6Vs9952MrMeUIw==, - } - engines: { node: ">=10" } - deprecated: This package is no longer supported. - - arg@4.1.0: - resolution: - { - integrity: sha512-ZWc51jO3qegGkVh8Hwpv636EkbesNV5ZNQPCtRa+0qytRYPEs9IYT9qITY9buezqUH5uqyzlWLcufrzU2rffdg==, - } - arg@4.1.3: resolution: { @@ -2587,6 +3803,12 @@ packages: integrity: sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==, } + argparse@1.0.10: + resolution: + { + integrity: sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==, + } + argparse@2.0.1: resolution: { @@ -2676,32 +3898,6 @@ packages: } engines: { node: ">= 0.4" } - async-listen@1.2.0: - resolution: - { - integrity: sha512-CcEtRh/oc9Jc4uWeUwdpG/+Mb2YUHKmdaTf0gUr7Wa+bfp4xx70HOb3RuSTJMvqKNB1TkdTfjLdrcz2X4rkkZA==, - } - - async-listen@3.0.0: - resolution: - { - integrity: sha512-V+SsTpDqkrWTimiotsyl33ePSjA5/KrithwupuvJ6ztsqPvGv6ge4OredFhPffVXiLN/QUWvE0XcqJaYgt6fOg==, - } - engines: { node: ">= 14" } - - async-listen@3.0.1: - resolution: - { - integrity: sha512-cWMaNwUJnf37C/S5TfCkk/15MwbPRwVYALA2jtjkbHjCmAPiDXyNJy2q3p1KAZzDLHAWyarUWSujUoHR4pEgrA==, - } - engines: { node: ">= 14" } - - async-sema@3.1.1: - resolution: - { - integrity: sha512-tLRNUXati5MFePdAk8dw7Qt7DpxPB60ofAgn8WRhW6a2rcimZnYBP9oxHiv0OHy+Wz7kPMG+t4LGdt31+4EmGg==, - } - asynckit@0.4.0: resolution: { @@ -2758,6 +3954,29 @@ packages: } engines: { node: ">= 0.4" } + babel-jest@30.1.2: + resolution: + { + integrity: sha512-IQCus1rt9kaSh7PQxLYRY5NmkNrNlU2TpabzwV7T2jljnpdHOcmnYYv8QmE04Li4S3a2Lj8/yXyET5pBarPr6g==, + } + engines: { node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0 } + peerDependencies: + "@babel/core": ^7.11.0 + + babel-plugin-istanbul@7.0.0: + resolution: + { + integrity: sha512-C5OzENSx/A+gt7t4VH1I2XsflxyPUmXRFPKBxt33xncdOmq7oROVM3bZv9Ysjjkv8OJYDMa+tKuKMvqU/H3xdw==, + } + engines: { node: ">=12" } + + babel-plugin-jest-hoist@30.0.1: + resolution: + { + integrity: sha512-zTPME3pI50NsFW8ZBaVIOeAxzEY7XHlmWeXXu9srI+9kNfzCUTy8MFan46xOGZY8NZThMqq+e3qZUKsvXbasnQ==, + } + engines: { node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0 } + babel-plugin-macros@3.1.0: resolution: { @@ -2765,6 +3984,23 @@ packages: } engines: { node: ">=10", npm: ">=6" } + babel-preset-current-node-syntax@1.2.0: + resolution: + { + integrity: sha512-E/VlAEzRrsLEb2+dv8yp3bo4scof3l9nR4lrld+Iy5NyVqgVYUJnDAmunkhPMisRI32Qc4iRiz425d8vM++2fg==, + } + peerDependencies: + "@babel/core": ^7.0.0 || ^8.0.0-0 + + babel-preset-jest@30.0.1: + resolution: + { + integrity: sha512-+YHejD5iTWI46cZmcc/YtX4gaKBtdqCHCVfuVinizVpbmyjO3zYmeuyFdfA8duRqQZfgCAMlsfmkVbJ+e2MAJw==, + } + engines: { node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0 } + peerDependencies: + "@babel/core": ^7.11.0 + bail@2.0.2: resolution: { @@ -2790,12 +4026,6 @@ packages: } engines: { node: ">=8" } - bindings@1.5.0: - resolution: - { - integrity: sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==, - } - brace-expansion@1.1.12: resolution: { @@ -2823,6 +4053,19 @@ packages: engines: { node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7 } hasBin: true + bs-logger@0.2.6: + resolution: + { + integrity: sha512-pd8DCoxmbgc7hyPKOvxtqNcjYoOsABPQdcCUjGp3d42VR2CX1ORhk2A87oqqu5R1kk+76nsxZupkmyd+MVtCog==, + } + engines: { node: ">= 6" } + + bser@2.1.1: + resolution: + { + integrity: sha512-gQxTNE/GAfIIrmHLUE3oJyp5FO6HRBfhjnw4/wMmA63ZGDJnWBmgY/lyQBpnDUkGmAhbSe39tx2d/iTOAfglwQ==, + } + btoa@1.2.1: resolution: { @@ -2831,10 +4074,10 @@ packages: engines: { node: ">= 0.4.0" } hasBin: true - buffer-crc32@0.2.13: + buffer-from@1.1.2: resolution: { - integrity: sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ==, + integrity: sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==, } buffer@6.0.3: @@ -2843,31 +4086,6 @@ packages: integrity: sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==, } - busboy@1.6.0: - resolution: - { - integrity: sha512-8SFQbg/0hQ9xy3UNTB0YEnsNBbWfhf7RtnzpL7TkBiTBRfrQ9Fxcnz7VJsleJpyp6rVLvXiuORqjlHi5q+PYuA==, - } - engines: { node: ">=10.16.0" } - - bytes@3.1.0: - resolution: - { - integrity: sha512-zauLjrfCG+xvoyaqLoV8bLVXXNGC4JqlxFCutSDWA6fJrTo2ZuvLYTqZ7aHBLZSMOopbzwv8f+wZcVzfVTI2Dg==, - } - engines: { node: ">= 0.8" } - - c12@1.11.1: - resolution: - { - integrity: sha512-KDU0TvSvVdaYcQKQ6iPHATGz/7p/KiVjPg4vQrB6Jg/wX9R0yl5RZxWm9IoZqaIHD2+6PZd81+KMGwRr/lRIUg==, - } - peerDependencies: - magicast: ^0.3.4 - peerDependenciesMeta: - magicast: - optional: true - call-bind-apply-helpers@1.0.2: resolution: { @@ -2903,12 +4121,19 @@ packages: } engines: { node: ">= 6" } - camelcase@8.0.0: + camelcase@5.3.1: resolution: { - integrity: sha512-8WB3Jcas3swSvjIeA2yvCJ+Miyz5l1ZmB6HFb9R1317dt9LCQoswg/BGrmAmkWVEszSrrg4RwmO46qIm2OEnSA==, + integrity: sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==, } - engines: { node: ">=16" } + engines: { node: ">=6" } + + camelcase@6.3.0: + resolution: + { + integrity: sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==, + } + engines: { node: ">=10" } caniuse-lite@1.0.30001734: resolution: @@ -2936,6 +4161,19 @@ packages: } engines: { node: ">=10" } + change-case@5.4.4: + resolution: + { + integrity: sha512-HRQyTk2/YPEkt9TnUPbOpr64Uw3KOicFWPVBb+xiHvd6eBx/qPr9xqfBFDT8P2vWsvvz4jbEkfDe71W3VyNu2w==, + } + + char-regex@1.0.2: + resolution: + { + integrity: sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw==, + } + engines: { node: ">=10" } + character-entities-html4@2.1.0: resolution: { @@ -2966,13 +4204,6 @@ packages: integrity: sha512-LuLBA6r4aS/4B7pvOqmT4Bi+GKnNNC/V18K0zDTRFjAxNeUzGsr0wmsOfFhFH7fGjwdx6GX6wyIQBkUhFox2Pw==, } - chokidar@3.3.1: - resolution: - { - integrity: sha512-4QYCEWOcK3OJrxwvyyAOxFuhpvOVCYkr33LPfFNBjAD/w3sEzWsp2BUOkI4l9bHvWioAd0rc6NlHUOEaWkTeqg==, - } - engines: { node: ">= 8.10.0" } - chokidar@3.6.0: resolution: { @@ -2987,18 +4218,12 @@ packages: } engines: { node: ">= 14.16.0" } - chownr@1.1.4: + chrome-trace-event@1.0.4: resolution: { - integrity: sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==, + integrity: sha512-rNjApaLzuwaOTjCiT8lSDdGN1APCiqkChLMJxJPWLunPAt5fy8xgU9/jNOchV84wfIxrA0lRQB7oCT8jrn/wrQ==, } - - chownr@2.0.0: - resolution: - { - integrity: sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==, - } - engines: { node: ">=10" } + engines: { node: ">=6.0" } ci-info@3.9.0: resolution: @@ -3007,16 +4232,23 @@ packages: } engines: { node: ">=8" } - citty@0.1.6: + ci-info@4.3.0: resolution: { - integrity: sha512-tskPPKEs8D2KPafUypv2gxwJP8h/OaJmC82QQGGDQcHvXX43xF2VDACcJVmZ0EuSxkpO9Kc4MlrA3q0+FG58AQ==, + integrity: sha512-l+2bNRMiQgcfILUi33labAZYIWlH1kWDp+ecNo5iisRKrbm0xcRyCww71/YU0Fkw0mAFpz9bJayXPjey6vkmaQ==, + } + engines: { node: ">=8" } + + cjs-module-lexer@1.4.3: + resolution: + { + integrity: sha512-9z8TZaGM1pfswYeXrUpzPrkx8UnWYdhJclsiYMm6x/w5+nN+8Tf/LnAgfLGQCm59qAOxU8WwHEq2vNwF6i4j+Q==, } - cjs-module-lexer@1.2.3: + cjs-module-lexer@2.1.0: resolution: { - integrity: sha512-0TNiGstbQmCFwt4akjjBg5pLRTSyj/PkWQ1ZoO2zntmg9yLqSRxwEa4iCfQLGjqhiqBfOJa7W/E8wfGrTDmlZQ==, + integrity: sha512-UX0OwmYRYQQetfrLEZeewIFFI+wSTofC+pMBLNuH3RUuu/xzG1oz84UCEDOSoQlN3fZ4+AzmV50ZYvGqkMh9yA==, } classnames@2.5.1: @@ -3031,6 +4263,13 @@ packages: integrity: sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA==, } + cliui@8.0.1: + resolution: + { + integrity: sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==, + } + engines: { node: ">=12" } + clsx@2.1.1: resolution: { @@ -3045,10 +4284,17 @@ packages: } engines: { node: ">=0.10.0" } - code-block-writer@10.1.1: + co@4.6.0: resolution: { - integrity: sha512-67ueh2IRGst/51p0n6FvPrnRjAGHY5F8xdjkgrYE7DDzpJe6qA07RYQ9VcoUeo5ATOjSOiWpSL3SWBRRbempMw==, + integrity: sha512-QVb0dM5HvG+uaxitm8wONl7jltx8dqhfU33DcqtOZcLSVIKSDDLDi7+0LbAKiyI8hD9u42m2YxXSkMGWThaecQ==, + } + engines: { iojs: ">= 1.0.0", node: ">= 0.12.0" } + + collect-v8-coverage@1.0.2: + resolution: + { + integrity: sha512-lHl4d5/ONEbLlJvaJNtsF/Lz+WvB07u2ycqTYbdrq7UypDXailES4valYb2eWiJFxZlVmpGekfqoxQhzyFdT4Q==, } color-convert@2.0.1: @@ -3064,12 +4310,24 @@ packages: integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==, } - color-support@1.1.3: + color-string@1.9.1: resolution: { - integrity: sha512-qiBjkpbMLO/HL68y+lh4q0/O1MZFj2RX6X/KmMa3+gJD3z+WwI1ZzDHysvqHGS3mP6mznPckpXmw1nI9cJjyRg==, + integrity: sha512-shrVawQFojnZv6xM40anx4CkoDP+fZsw/ZerEMsW/pyzsRbElpsL/DBVW7q3ExxwusdNXI3lXpuhEZkzs8p5Eg==, + } + + color@4.2.3: + resolution: + { + integrity: sha512-1rXeuUUiGGrykh+CeBdu5Ie7OJwinCgQY0bc7GCRxy5xVHy+moaqkpL/jqQq0MtQOeYcrqEz4abc5f0KtU7W4A==, + } + engines: { node: ">=12.5.0" } + + colorette@1.4.0: + resolution: + { + integrity: sha512-Y2oEozpomLn7Q3HFP7dpww7AtMJplbM9lGZP6RDfHqmbeRjiwRg4n6VM6j4KLmRke85uWEI7JqF17f3pqdRA0g==, } - hasBin: true combined-stream@1.0.8: resolution: @@ -3084,12 +4342,11 @@ packages: integrity: sha512-Fu4hJdvzeylCfQPp9SGWidpzrMs7tTrlu6Vb8XGaRGck8QSNZJJp538Wrb60Lax4fPwR64ViY468OIUTbRlGZg==, } - commander@12.1.0: + commander@2.20.3: resolution: { - integrity: sha512-Vw8qHK3bZM9y/P10u3Vib8o/DdkvA2OtPtZvD871QKjy74Wj1WSKFILMPRPSdUSx5RFK1arlJzEtA4PkFgnbuA==, + integrity: sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==, } - engines: { node: ">=18" } commander@4.1.1: resolution: @@ -3110,45 +4367,18 @@ packages: integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==, } - confbox@0.1.8: - resolution: - { - integrity: sha512-RMtmw0iFkeR4YV+fUOSucriAQNb9g8zFR52MWCtl+cCZOFRNL6zeB395vPzFhEjjn4fMxXudmELnl/KF/WrK6w==, - } - - consola@3.4.2: - resolution: - { - integrity: sha512-5IKcdX0nnYavi6G7TtOhwkYzyjfJlatbjMjuLSfE2kYT5pMDOilZ4OvMhi637CcDICTmz3wARPoyhqyX1Y+XvA==, - } - engines: { node: ^14.18.0 || >=16.10.0 } - - console-control-strings@1.1.0: - resolution: - { - integrity: sha512-ty/fTekppD2fIwRvnZAVdeOiGd1c7YXEixbgJTNzqcxJWKQnjJ/V1bNEEE6hygpM3WjwHFUVK6HTjWSzV4a8sQ==, - } - - content-type@1.0.4: - resolution: - { - integrity: sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA==, - } - engines: { node: ">= 0.6" } - - convert-hrtime@3.0.0: - resolution: - { - integrity: sha512-7V+KqSvMiHp8yWDuwfww06XleMWVVB9b9tURBx+G7UTADuo5hYPuowKloz4OzOqbPezxgo+fdQ1522WzPG4OeA==, - } - engines: { node: ">=8" } - convert-source-map@1.9.0: resolution: { integrity: sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==, } + convert-source-map@2.0.0: + resolution: + { + integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==, + } + cookie@0.7.2: resolution: { @@ -3228,18 +4458,6 @@ packages: supports-color: optional: true - debug@4.1.1: - resolution: - { - integrity: sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==, - } - deprecated: Debug versions >=3.2.0 <3.2.7 || >=4 <4.3.1 have a low-severity ReDos regression when used in a Node.js environment. It is recommended you upgrade to 3.2.7 or 4.3.1. (https://github.com/visionmedia/debug/issues/797) - peerDependencies: - supports-color: "*" - peerDependenciesMeta: - supports-color: - optional: true - debug@4.3.7: resolution: { @@ -3270,12 +4488,30 @@ packages: integrity: sha512-c6fcElNV6ShtZXmsgNgFFV5tVX2PaV4g+MOAkb8eXHvn6sryJBrZa9r0zV6+dtTyoCKxtDy5tyQ5ZwQuidtd+Q==, } + dedent@1.7.0: + resolution: + { + integrity: sha512-HGFtf8yhuhGhqO07SV79tRp+br4MnbdjeVxotpn1QBl30pcLLCQjX5b2295ll0fv8RKDKsmWYrl05usHM9CewQ==, + } + peerDependencies: + babel-plugin-macros: ^3.1.0 + peerDependenciesMeta: + babel-plugin-macros: + optional: true + deep-is@0.1.4: resolution: { integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==, } + deepmerge@4.3.1: + resolution: + { + integrity: sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==, + } + engines: { node: ">=0.10.0" } + define-data-property@1.1.4: resolution: { @@ -3290,12 +4526,6 @@ packages: } engines: { node: ">= 0.4" } - defu@6.1.4: - resolution: - { - integrity: sha512-mEQCMmwJu317oSz8CwdIOdwf3xMif1ttiM8LTufzc3g6kR+9Pe236twL8j3IYT1F7GfRgGcW6MWxzZjLIkuHIg==, - } - delayed-stream@1.0.0: resolution: { @@ -3303,12 +4533,6 @@ packages: } engines: { node: ">=0.4.0" } - delegates@1.0.0: - resolution: - { - integrity: sha512-bd2L678uiWATM6m5Z1VzNCErI3jiGzt6HGY8OVICs40JQq/HALfbyNJmp0UDakEY4pMMaN0Ly5om/B1VI/+xfQ==, - } - denque@2.1.0: resolution: { @@ -3316,13 +4540,6 @@ packages: } engines: { node: ">=0.10" } - depd@1.1.2: - resolution: - { - integrity: sha512-7emPTl6Dpo6JRXOXjLRxck+FlLRX5847cLKEn00PLAgc3g2hTZZgr+e4c2v6QpSmLeFP3n5yUo7ft6avBK/5jQ==, - } - engines: { node: ">= 0.6" } - dequal@2.0.3: resolution: { @@ -3330,12 +4547,6 @@ packages: } engines: { node: ">=6" } - destr@2.0.5: - resolution: - { - integrity: sha512-ugFTXCtDZunbzasqBxrK93Ik/DRYsO6S/fedkWEMKqt04xZ4csmnmwGDBAb07QWNaGMAmnTIemsYZCksjATwsA==, - } - detect-europe-js@0.1.2: resolution: { @@ -3357,6 +4568,13 @@ packages: } engines: { node: ">=8" } + detect-newline@3.1.0: + resolution: + { + integrity: sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA==, + } + engines: { node: ">=8" } + detect-node-es@1.1.0: resolution: { @@ -3433,20 +4651,19 @@ packages: integrity: sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==, } - edge-runtime@2.5.9: - resolution: - { - integrity: sha512-pk+k0oK0PVXdlT4oRp4lwh+unuKB7Ng4iZ2HB+EZ7QCEQizX360Rp/F4aRpgpRgdP2ufB35N+1KppHmYjqIGSg==, - } - engines: { node: ">=16" } - hasBin: true - electron-to-chromium@1.5.200: resolution: { integrity: sha512-rFCxROw7aOe4uPTfIAx+rXv9cEcGx+buAF4npnhtTqCJk5KDFRnh3+KYj7rdVh6lsFt5/aPs+Irj9rZ33WMA7w==, } + emittery@0.13.1: + resolution: + { + integrity: sha512-DeWwawk6r5yR9jFgnDKYt4sLS0LmHJJi3ZOnb5/JdbYwj3nW+FxQnHIjhBKz8YLC7oRNPVM9NQ47I3CVx34eqQ==, + } + engines: { node: ">=12" } + emoji-regex@8.0.0: resolution: { @@ -3459,18 +4676,6 @@ packages: integrity: sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==, } - end-of-stream@1.1.0: - resolution: - { - integrity: sha512-EoulkdKF/1xa92q25PbjuDcgJ9RDHYU2Rs3SCIvs2/dSQ3BpmxneNHmA/M7fe60M3PrV7nNGTTNbkK62l6vXiQ==, - } - - end-of-stream@1.4.5: - resolution: - { - integrity: sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg==, - } - engine.io-client@6.5.4: resolution: { @@ -3484,6 +4689,13 @@ packages: } engines: { node: ">=10.0.0" } + enhanced-resolve@5.18.3: + resolution: + { + integrity: sha512-d4lC8xfavMeBjzGr2vECC3fsGXziXZQyJxD868h2M/mBI3PwAuODxAkLkq5HYuvrPYcUtiLzsTo8U3PgX3Ocww==, + } + engines: { node: ">=10.13.0" } + err-code@3.0.1: resolution: { @@ -3524,10 +4736,10 @@ packages: } engines: { node: ">= 0.4" } - es-module-lexer@1.4.1: + es-module-lexer@1.7.0: resolution: { - integrity: sha512-cXLGjP0c4T3flZJKQSuziYoq7MlT+rnvfZjfp7h+I7K9BNX54kP9nyWvdbwjQ4u1iWbOL4u96fgeZLToQlZC7w==, + integrity: sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA==, } es-object-atoms@1.1.1: @@ -3558,194 +4770,6 @@ packages: } engines: { node: ">= 0.4" } - esbuild-android-64@0.14.47: - resolution: - { - integrity: sha512-R13Bd9+tqLVFndncMHssZrPWe6/0Kpv2/dt4aA69soX4PRxlzsVpCvoJeFE8sOEoeVEiBkI0myjlkDodXlHa0g==, - } - engines: { node: ">=12" } - cpu: [x64] - os: [android] - - esbuild-android-arm64@0.14.47: - resolution: - { - integrity: sha512-OkwOjj7ts4lBp/TL6hdd8HftIzOy/pdtbrNA4+0oVWgGG64HrdVzAF5gxtJufAPOsEjkyh1oIYvKAUinKKQRSQ==, - } - engines: { node: ">=12" } - cpu: [arm64] - os: [android] - - esbuild-darwin-64@0.14.47: - resolution: - { - integrity: sha512-R6oaW0y5/u6Eccti/TS6c/2c1xYTb1izwK3gajJwi4vIfNs1s8B1dQzI1UiC9T61YovOQVuePDcfqHLT3mUZJA==, - } - engines: { node: ">=12" } - cpu: [x64] - os: [darwin] - - esbuild-darwin-arm64@0.14.47: - resolution: - { - integrity: sha512-seCmearlQyvdvM/noz1L9+qblC5vcBrhUaOoLEDDoLInF/VQ9IkobGiLlyTPYP5dW1YD4LXhtBgOyevoIHGGnw==, - } - engines: { node: ">=12" } - cpu: [arm64] - os: [darwin] - - esbuild-freebsd-64@0.14.47: - resolution: - { - integrity: sha512-ZH8K2Q8/Ux5kXXvQMDsJcxvkIwut69KVrYQhza/ptkW50DC089bCVrJZZ3sKzIoOx+YPTrmsZvqeZERjyYrlvQ==, - } - engines: { node: ">=12" } - cpu: [x64] - os: [freebsd] - - esbuild-freebsd-arm64@0.14.47: - resolution: - { - integrity: sha512-ZJMQAJQsIOhn3XTm7MPQfCzEu5b9STNC+s90zMWe2afy9EwnHV7Ov7ohEMv2lyWlc2pjqLW8QJnz2r0KZmeAEQ==, - } - engines: { node: ">=12" } - cpu: [arm64] - os: [freebsd] - - esbuild-linux-32@0.14.47: - resolution: - { - integrity: sha512-FxZOCKoEDPRYvq300lsWCTv1kcHgiiZfNrPtEhFAiqD7QZaXrad8LxyJ8fXGcWzIFzRiYZVtB3ttvITBvAFhKw==, - } - engines: { node: ">=12" } - cpu: [ia32] - os: [linux] - - esbuild-linux-64@0.14.47: - resolution: - { - integrity: sha512-nFNOk9vWVfvWYF9YNYksZptgQAdstnDCMtR6m42l5Wfugbzu11VpMCY9XrD4yFxvPo9zmzcoUL/88y0lfJZJJw==, - } - engines: { node: ">=12" } - cpu: [x64] - os: [linux] - - esbuild-linux-arm64@0.14.47: - resolution: - { - integrity: sha512-ywfme6HVrhWcevzmsufjd4iT3PxTfCX9HOdxA7Hd+/ZM23Y9nXeb+vG6AyA6jgq/JovkcqRHcL9XwRNpWG6XRw==, - } - engines: { node: ">=12" } - cpu: [arm64] - os: [linux] - - esbuild-linux-arm@0.14.47: - resolution: - { - integrity: sha512-ZGE1Bqg/gPRXrBpgpvH81tQHpiaGxa8c9Rx/XOylkIl2ypLuOcawXEAo8ls+5DFCcRGt/o3sV+PzpAFZobOsmA==, - } - engines: { node: ">=12" } - cpu: [arm] - os: [linux] - - esbuild-linux-mips64le@0.14.47: - resolution: - { - integrity: sha512-mg3D8YndZ1LvUiEdDYR3OsmeyAew4MA/dvaEJxvyygahWmpv1SlEEnhEZlhPokjsUMfRagzsEF/d/2XF+kTQGg==, - } - engines: { node: ">=12" } - cpu: [mips64el] - os: [linux] - - esbuild-linux-ppc64le@0.14.47: - resolution: - { - integrity: sha512-WER+f3+szmnZiWoK6AsrTKGoJoErG2LlauSmk73LEZFQ/iWC+KhhDsOkn1xBUpzXWsxN9THmQFltLoaFEH8F8w==, - } - engines: { node: ">=12" } - cpu: [ppc64] - os: [linux] - - esbuild-linux-riscv64@0.14.47: - resolution: - { - integrity: sha512-1fI6bP3A3rvI9BsaaXbMoaOjLE3lVkJtLxsgLHqlBhLlBVY7UqffWBvkrX/9zfPhhVMd9ZRFiaqXnB1T7BsL2g==, - } - engines: { node: ">=12" } - cpu: [riscv64] - os: [linux] - - esbuild-linux-s390x@0.14.47: - resolution: - { - integrity: sha512-eZrWzy0xFAhki1CWRGnhsHVz7IlSKX6yT2tj2Eg8lhAwlRE5E96Hsb0M1mPSE1dHGpt1QVwwVivXIAacF/G6mw==, - } - engines: { node: ">=12" } - cpu: [s390x] - os: [linux] - - esbuild-netbsd-64@0.14.47: - resolution: - { - integrity: sha512-Qjdjr+KQQVH5Q2Q1r6HBYswFTToPpss3gqCiSw2Fpq/ua8+eXSQyAMG+UvULPqXceOwpnPo4smyZyHdlkcPppQ==, - } - engines: { node: ">=12" } - cpu: [x64] - os: [netbsd] - - esbuild-openbsd-64@0.14.47: - resolution: - { - integrity: sha512-QpgN8ofL7B9z8g5zZqJE+eFvD1LehRlxr25PBkjyyasakm4599iroUpaj96rdqRlO2ShuyqwJdr+oNqWwTUmQw==, - } - engines: { node: ">=12" } - cpu: [x64] - os: [openbsd] - - esbuild-sunos-64@0.14.47: - resolution: - { - integrity: sha512-uOeSgLUwukLioAJOiGYm3kNl+1wJjgJA8R671GYgcPgCx7QR73zfvYqXFFcIO93/nBdIbt5hd8RItqbbf3HtAQ==, - } - engines: { node: ">=12" } - cpu: [x64] - os: [sunos] - - esbuild-windows-32@0.14.47: - resolution: - { - integrity: sha512-H0fWsLTp2WBfKLBgwYT4OTfFly4Im/8B5f3ojDv1Kx//kiubVY0IQunP2Koc/fr/0wI7hj3IiBDbSrmKlrNgLQ==, - } - engines: { node: ">=12" } - cpu: [ia32] - os: [win32] - - esbuild-windows-64@0.14.47: - resolution: - { - integrity: sha512-/Pk5jIEH34T68r8PweKRi77W49KwanZ8X6lr3vDAtOlH5EumPE4pBHqkCUdELanvsT14yMXLQ/C/8XPi1pAtkQ==, - } - engines: { node: ">=12" } - cpu: [x64] - os: [win32] - - esbuild-windows-arm64@0.14.47: - resolution: - { - integrity: sha512-HFSW2lnp62fl86/qPQlqw6asIwCnEsEoNIL1h2uVMgakddf+vUuMcCbtUY1i8sst7KkgHrVKCJQB33YhhOweCQ==, - } - engines: { node: ">=12" } - cpu: [arm64] - os: [win32] - - esbuild@0.14.47: - resolution: - { - integrity: sha512-wI4ZiIfFxpkuxB8ju4MHrGwGLyp1+awEHAHVpx6w7a+1pmYIq8T9FGEVVwFo0iFierDoMj++Xq69GXWYn2EiwA==, - } - engines: { node: ">=12" } - hasBin: true - escalade@3.2.0: resolution: { @@ -3753,6 +4777,13 @@ packages: } engines: { node: ">=6" } + escape-string-regexp@2.0.0: + resolution: + { + integrity: sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==, + } + engines: { node: ">=8" } + escape-string-regexp@4.0.0: resolution: { @@ -3760,13 +4791,13 @@ packages: } engines: { node: ">=10" } - eslint-config-next@14.2.31: + eslint-config-next@15.5.3: resolution: { - integrity: sha512-sT32j4678je7SWstBM6l0kE2L+LSgAARDAxw8iloNhI4/8xwkdDesbrGCPaGWzQv+dD6f6adhB+eRSThpGkBdg==, + integrity: sha512-e6j+QhQFOr5pfsc8VJbuTD9xTXJaRvMHYjEeLPA2pFkheNlgPLCkxdvhxhfuM4KGcqSZj2qEnpHisdTVs3BxuQ==, } peerDependencies: - eslint: ^7.23.0 || ^8.0.0 + eslint: ^7.23.0 || ^8.0.0 || ^9.0.0 typescript: ">=3.3.1" peerDependenciesMeta: typescript: @@ -3840,14 +4871,14 @@ packages: peerDependencies: eslint: ^3 || ^4 || ^5 || ^6 || ^7 || ^8 || ^9 - eslint-plugin-react-hooks@5.0.0-canary-7118f5dd7-20230705: + eslint-plugin-react-hooks@5.2.0: resolution: { - integrity: sha512-AZYbMo/NW9chdL7vk6HQzQhT+PvTAEVqWk9ziruUoW2kAOcN5qNyelv70e0F1VNQAbvutOC9oc+xfWycI9FxDw==, + integrity: sha512-+f15FfK64YQwZdJNELETdn5ibXEUQmW1DZL6KXhNnc2heoy/sg9VJJeT7n8TlMWouzWqSWavFkIhHyIbIAEapg==, } engines: { node: ">=10" } peerDependencies: - eslint: ^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0 + eslint: ^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0 || ^9.0.0 eslint-plugin-react@7.37.5: resolution: @@ -3858,6 +4889,13 @@ packages: peerDependencies: eslint: ^3 || ^4 || ^5 || ^6 || ^7 || ^8 || ^9.7 + eslint-scope@5.1.1: + resolution: + { + integrity: sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==, + } + engines: { node: ">=8.0.0" } + eslint-scope@8.4.0: resolution: { @@ -3899,6 +4937,14 @@ packages: } engines: { node: ^18.18.0 || ^20.9.0 || >=21.1.0 } + esprima@4.0.1: + resolution: + { + integrity: sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==, + } + engines: { node: ">=4" } + hasBin: true + esquery@1.6.0: resolution: { @@ -3913,6 +4959,13 @@ packages: } engines: { node: ">=4.0" } + estraverse@4.3.0: + resolution: + { + integrity: sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==, + } + engines: { node: ">=4.0" } + estraverse@5.3.0: resolution: { @@ -3939,13 +4992,6 @@ packages: } engines: { node: ">=0.10.0" } - etag@1.8.1: - resolution: - { - integrity: sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==, - } - engines: { node: ">= 0.6" } - event-target-shim@6.0.2: resolution: { @@ -3953,12 +4999,6 @@ packages: } engines: { node: ">=10.13.0" } - events-intercept@2.0.0: - resolution: - { - integrity: sha512-blk1va0zol9QOrdZt0rFXo5KMkNPVSp92Eju/Qz8THwKWKRKeE0T8Br/1aW6+Edkyq9xHYgYxn2QtOnUKPUp+Q==, - } - events@3.3.0: resolution: { @@ -3966,12 +5006,26 @@ packages: } engines: { node: ">=0.8.x" } - execa@3.2.0: + execa@5.1.1: resolution: { - integrity: sha512-kJJfVbI/lZE1PZYDI5VPxp8zXPO9rtxOkhpZ0jMKha56AI9y2gGVC6bkukStQf0ka5Rh15BA5m7cCCH4jmHqkw==, + integrity: sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==, } - engines: { node: ^8.12.0 || >=9.7.0 } + engines: { node: ">=10" } + + exit-x@0.2.2: + resolution: + { + integrity: sha512-+I6B/IkJc1o/2tiURyz/ivu/O0nKNEArIUB5O7zBrlDVJr22SCLH3xTeEry428LvFhRzIA1g8izguxJ/gbNcVQ==, + } + engines: { node: ">= 0.8.0" } + + expect@30.1.2: + resolution: + { + integrity: sha512-xvHszRavo28ejws8FpemjhwswGj4w/BetHIL8cU49u4sGyXDw2+p3YbeDbj6xzlxi6kWTjIRSTJ+9sNXPnF0Zg==, + } + engines: { node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0 } extend@3.0.2: resolution: @@ -3991,6 +5045,13 @@ packages: integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==, } + fast-glob@3.3.1: + resolution: + { + integrity: sha512-kNFPyjhh5cKjrUltxs+wFx+ZkbRaxxmZ+X0ZU31SOsxCEtP9VPgtq2teZw1DebupL5GmDaNQ6yKMMVcM41iqDg==, + } + engines: { node: ">=8.6.0" } + fast-glob@3.3.3: resolution: { @@ -4016,16 +5077,22 @@ packages: integrity: sha512-W+KJc2dmILlPplD/H4K9l9LcAHAfPtP6BY84uVLXQ6Evcz9Lcg33Y2z1IVblT6xdY54PXYVHEv+0Wpq8Io6zkA==, } + fast-uri@3.1.0: + resolution: + { + integrity: sha512-iPeeDKJSWf4IEOasVVrknXpaBV0IApz/gp7S2bb7Z4Lljbl2MGJRqInZiUrQwV16cpzw/D3S5j5Julj/gT52AA==, + } + fastq@1.19.1: resolution: { integrity: sha512-GwLTyxkCXjXbxqIhTsMI2Nui8huMPtnxg7krajPJAjnEG/iiOS7i+zCtWGZR9G0NBKbXKh6X9m9UIsYX/N6vvQ==, } - fd-slicer@1.1.0: + fb-watchman@2.0.2: resolution: { - integrity: sha512-cE1qsB/VwyQozZ+q1dGxR8LBYNZeofhEdUNGSMbQD3Gw2lAzX9Zb3uIU6Ebc/Fmyjo9AWWfnn0AUCHqtevs/8g==, + integrity: sha512-p5161BqbuCaSnB8jIbzQHOlpgsPmK5rJVDfDKO91Axs5NC1uu3HRQm6wt9cd9/+GtQQIO53JdGXXoyDpTAsgYA==, } fdir@6.4.6: @@ -4046,12 +5113,6 @@ packages: } engines: { node: ">=16.0.0" } - file-uri-to-path@1.0.0: - resolution: - { - integrity: sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==, - } - fill-range@7.1.1: resolution: { @@ -4065,6 +5126,13 @@ packages: integrity: sha512-NKfW6bec6GfKc0SGx1e07QZY9PE99u0Bft/0rzSD5k3sO/vwkVUpDUKVm5Gpp5Ue3YfShPFTX2070tDs5kB9Ng==, } + find-up@4.1.0: + resolution: + { + integrity: sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==, + } + engines: { node: ">=8" } + find-up@5.0.0: resolution: { @@ -4124,53 +5192,24 @@ packages: } engines: { node: ">= 6" } + forwarded-parse@2.1.2: + resolution: + { + integrity: sha512-alTFZZQDKMporBH77856pXgzhEzaUVmLCDk+egLgIgHst3Tpndzz8MnKe+GzRJRfvVdn69HhpW7cmXzvtLvJAw==, + } + fraction.js@4.3.7: resolution: { integrity: sha512-ZsDfxO51wGAXREY55a7la9LScWpwv9RxIrYABrlvOFBlH/ShPnrtsXeuUIfXKKOVicNxQ+o8JTbJvjS4M89yew==, } - fs-extra@11.1.0: - resolution: - { - integrity: sha512-0rcTq621PD5jM/e0a3EJoGC/1TC5ZBCERW82LQuwfGnCa1V8w7dpYH1yNu+SLb6E5dkeCBzKEyLGlFrnr+dUyw==, - } - engines: { node: ">=14.14" } - - fs-extra@8.1.0: - resolution: - { - integrity: sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==, - } - engines: { node: ">=6 <7 || >=8" } - - fs-minipass@1.2.7: - resolution: - { - integrity: sha512-GWSSJGFy4e9GUeCcbIkED+bgAoFyj7XF1mV8rma3QW4NIqX9Kyx79N/PF61H5udOV3aY1IaMLs6pGbH71nlCTA==, - } - - fs-minipass@2.1.0: - resolution: - { - integrity: sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg==, - } - engines: { node: ">= 8" } - fs.realpath@1.0.0: resolution: { integrity: sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==, } - fsevents@2.1.3: - resolution: - { - integrity: sha512-Auw9a4AxqWpa9GUfj370BMPzzyncfBABW8Mab7BGWBYDj4Isgq+cDKtx0i6u9jcX9pQDnswsaaOTgTmA5pEjuQ==, - } - engines: { node: ^8.16.0 || ^10.6.0 || >=11.0.0 } - os: [darwin] - fsevents@2.3.3: resolution: { @@ -4198,20 +5237,12 @@ packages: integrity: sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==, } - gauge@3.0.2: + gensync@1.0.0-beta.2: resolution: { - integrity: sha512-+5J6MS/5XksCuXq++uFRsnUd7Ovu1XenbeuIuNRJxYWjgQbPuFhT14lAvsWfqfAmnwluf1OwMjz39HjfLPci0Q==, + integrity: sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==, } - engines: { node: ">=10" } - deprecated: This package is no longer supported. - - generic-pool@3.4.2: - resolution: - { - integrity: sha512-H7cUpwCQSiJmAHM4c/aFu6fUfrhWXW1ncyh8ftxEPMu6AiYkHw9K8br720TGPZJbk5eOH2bynjZD1yPvdDAmag==, - } - engines: { node: ">= 4" } + engines: { node: ">=6.9.0" } get-browser-rtc@1.1.0: resolution: @@ -4219,6 +5250,13 @@ packages: integrity: sha512-MghbMJ61EJrRsDe7w1Bvqt3ZsBuqhce5nrn/XAwgwOXhcsz53/ltdxOse1h/8eKXj5slzxdsz56g5rzOFSGwfQ==, } + get-caller-file@2.0.5: + resolution: + { + integrity: sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==, + } + engines: { node: 6.* || 8.* || >= 10.* } + get-intrinsic@1.3.0: resolution: { @@ -4233,6 +5271,13 @@ packages: } engines: { node: ">=6" } + get-package-type@0.1.0: + resolution: + { + integrity: sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q==, + } + engines: { node: ">=8.0.0" } + get-proto@1.0.1: resolution: { @@ -4240,12 +5285,12 @@ packages: } engines: { node: ">= 0.4" } - get-stream@5.2.0: + get-stream@6.0.1: resolution: { - integrity: sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==, + integrity: sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==, } - engines: { node: ">=8" } + engines: { node: ">=10" } get-symbol-description@1.1.0: resolution: @@ -4260,13 +5305,6 @@ packages: integrity: sha512-auHyJ4AgMz7vgS8Hp3N6HXSmlMdUyhSUrfBF16w153rxtLIEOE+HGqaBppczZvnHLqQJfiHotCYpNhl0lUROFQ==, } - giget@1.2.5: - resolution: - { - integrity: sha512-r1ekGw/Bgpi3HLV3h1MRBIlSAdHoIMklpaQ3OQLFcRw9PwAj2rqigvIbg+dBUI51OxVI2jsEtDywDBjSiuf7Ug==, - } - hasBin: true - glob-parent@5.1.2: resolution: { @@ -4281,13 +5319,11 @@ packages: } engines: { node: ">=10.13.0" } - glob@10.3.10: + glob-to-regexp@0.4.1: resolution: { - integrity: sha512-fa46+tv1Ak0UPK1TOy/pZrIybNNt4HCv7SDzwyfiOZkvZLEbjsZkJBPtDHVshZjbecAoAGSC20MjLDG/qr679g==, + integrity: sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw==, } - engines: { node: ">=16 || 14 >=14.17" } - hasBin: true glob@10.4.5: resolution: @@ -4303,13 +5339,12 @@ packages: } deprecated: Glob versions prior to v9 are no longer supported - glob@8.1.0: + glob@9.3.5: resolution: { - integrity: sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ==, + integrity: sha512-e1LleDykUz2Iu+MTYdkSsuWX8lvAjAcs0Xef0lNIu0S2wOAzuTxCJtcd9S3cijlwYF18EsU3rzb8jPVobxDh9Q==, } - engines: { node: ">=12" } - deprecated: Glob versions prior to v9 are no longer supported + engines: { node: ">=16 || 14 >=14.17" } globals@14.0.0: resolution: @@ -4400,12 +5435,6 @@ packages: } engines: { node: ">= 0.4" } - has-unicode@2.0.1: - resolution: - { - integrity: sha512-8Rf9Y83NBReMnx0gFzA8JImQACstCYWUplepDa9xprwwtmgEZUF0h/i5xSA625zB/I37EtrswSST6OXxwaaIJQ==, - } - hasown@2.0.2: resolution: { @@ -4437,26 +5466,18 @@ packages: integrity: sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==, } + html-escaper@2.0.2: + resolution: + { + integrity: sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==, + } + html-url-attributes@3.0.1: resolution: { integrity: sha512-ol6UPyBWqsrO6EJySPz2O7ZSr856WDrEzM5zMqp+FJJLGMW35cLYmmZnl0vztAZxRUoNZJFTCohfjuIJ8I4QBQ==, } - http-errors@1.4.0: - resolution: - { - integrity: sha512-oLjPqve1tuOl5aRhv8GK5eHpqP1C9fb+Ol+XTLjKfLltE44zdDbEdjPSbU7Ch5rSNsVFqZn97SrMmZLdu1/YMw==, - } - engines: { node: ">= 0.6" } - - http-errors@1.7.3: - resolution: - { - integrity: sha512-ZTTX0MWrsQ2ZAhA1cejAwDLycFsd7I7nVtnkT3Ol0aqodaKW+0CTZDQ1uBv5whptCnc8e8HeRRJxRs0kmm/Qfw==, - } - engines: { node: ">= 0.6" } - https-proxy-agent@5.0.1: resolution: { @@ -4464,12 +5485,19 @@ packages: } engines: { node: ">= 6" } - human-signals@1.1.1: + https-proxy-agent@7.0.6: resolution: { - integrity: sha512-SEQu7vl8KjNL2eoGBLF3+wAjpsNfA9XMlXAYj/3EdaNfAlxKthD1xjEQfGOUhllCGGJVNY34bRr6lPINhNjyZw==, + integrity: sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==, } - engines: { node: ">=8.12.0" } + engines: { node: ">= 14" } + + human-signals@2.1.0: + resolution: + { + integrity: sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==, + } + engines: { node: ">=10.17.0" } hyperhtml-style@0.1.3: resolution: @@ -4477,13 +5505,6 @@ packages: integrity: sha512-IvLy8MzHTSJ0fDpSzrb8rcdnla6yROEmNBSxInEMyIFu2DQkbmpadTf6B4fHvnytN6iHL2gGwpe5/jHL3wMi+A==, } - iconv-lite@0.4.24: - resolution: - { - integrity: sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==, - } - engines: { node: ">=0.10.0" } - ieee754@1.2.1: resolution: { @@ -4504,12 +5525,6 @@ packages: } engines: { node: ">= 4" } - immediate@3.0.6: - resolution: - { - integrity: sha512-XXOFtyqDjNDAQxVfYxuF7g9Il/IbWmmlQg2MYKOH8ExIT1qg6xc4zyS3HaEEATgs1btfzxq15ciUiY7gjSXRGQ==, - } - immer@10.1.1: resolution: { @@ -4529,6 +5544,20 @@ packages: } engines: { node: ">=6" } + import-in-the-middle@1.14.2: + resolution: + { + integrity: sha512-5tCuY9BV8ujfOpwtAGgsTx9CGUapcFMEEyByLv1B+v2+6DhAcw+Zr0nhQT7uwaZ7DiourxFEscghOR8e1aPLQw==, + } + + import-local@3.2.0: + resolution: + { + integrity: sha512-2SPlun1JUPWoM6t3F0dw0FkCF/jWY8kttcY4f599GLTSjh2OCuuhdTkJQsEcZzBqbXZGKMK2OqW1oZsjtf/gQA==, + } + engines: { node: ">=8" } + hasBin: true + imurmurhash@0.1.4: resolution: { @@ -4536,6 +5565,13 @@ packages: } engines: { node: ">=0.8.19" } + index-to-position@1.1.0: + resolution: + { + integrity: sha512-XPdx9Dq4t9Qk1mTMbWONJqU7boCoumEH7fRET37HX5+khDUl3J2W6PdALxhILYlIYx2amlwYcRPp28p0tSiojg==, + } + engines: { node: ">=18" } + inflight@1.0.6: resolution: { @@ -4543,12 +5579,6 @@ packages: } deprecated: This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful. - inherits@2.0.1: - resolution: - { - integrity: sha512-8nWq2nLTAwd02jTqJExUYFSD/fKq6VH9Y/oG2accc/kdI0V98Bag8d5a4gi3XHz73rDWa2PvTtvcWYquKqSENA==, - } - inherits@2.0.4: resolution: { @@ -4620,6 +5650,12 @@ packages: integrity: sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==, } + is-arrayish@0.3.2: + resolution: + { + integrity: sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ==, + } + is-async-function@2.1.1: resolution: { @@ -4709,6 +5745,13 @@ packages: } engines: { node: ">=8" } + is-generator-fn@2.1.0: + resolution: + { + integrity: sha512-cTIB4yPYL/Grw0EaSzASzg6bBy9gqCofvWN8okThAYIxKJZC+udlRAmGbM0XLeniEJSs8uEgHPGuHSe1XsOLSQ==, + } + engines: { node: ">=6" } + is-generator-function@1.1.0: resolution: { @@ -4846,12 +5889,6 @@ packages: } engines: { node: ">= 0.4" } - isarray@0.0.1: - resolution: - { - integrity: sha512-D2S+3GLxWH+uhrNEcoh/fnmYeP8E8/zHl644d/jdA0g2uyXvy3sb0qxotE+ne0LtccHknQzWwZEzhak7oJ0COQ==, - } - isarray@2.0.5: resolution: { @@ -4864,6 +5901,41 @@ packages: integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==, } + istanbul-lib-coverage@3.2.2: + resolution: + { + integrity: sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg==, + } + engines: { node: ">=8" } + + istanbul-lib-instrument@6.0.3: + resolution: + { + integrity: sha512-Vtgk7L/R2JHyyGW07spoFlB8/lpjiOLTjMdms6AFMraYt3BaJauod/NGrfnVG/y4Ix1JEuMRPDPEj2ua+zz1/Q==, + } + engines: { node: ">=10" } + + istanbul-lib-report@3.0.1: + resolution: + { + integrity: sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw==, + } + engines: { node: ">=10" } + + istanbul-lib-source-maps@5.0.6: + resolution: + { + integrity: sha512-yg2d+Em4KizZC5niWhQaIomgf5WlL4vOOjZ5xGCmF8SnPE/mDWWXgvRExdcpCgh9lLRRa1/fSYp2ymmbJ1pI+A==, + } + engines: { node: ">=10" } + + istanbul-reports@3.2.0: + resolution: + { + integrity: sha512-HGYWWS/ehqTV3xN10i23tkPkpH46MLCIMFNCaaKNavAXTF1RkqxawEPtnjnGZ6XKSInBKkiOA5BKS+aZiY3AvA==, + } + engines: { node: ">=8" } + iterator.prototype@1.1.5: resolution: { @@ -4871,19 +5943,174 @@ packages: } engines: { node: ">= 0.4" } - jackspeak@2.3.6: - resolution: - { - integrity: sha512-N3yCS/NegsOBokc8GAdM8UcmfsKiSS8cipheD/nivzr700H+nsMOxJjQnvwOcRYVuFkdH0wGUvW2WbXGmrZGbQ==, - } - engines: { node: ">=14" } - jackspeak@3.4.3: resolution: { integrity: sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==, } + jest-changed-files@30.0.5: + resolution: + { + integrity: sha512-bGl2Ntdx0eAwXuGpdLdVYVr5YQHnSZlQ0y9HVDu565lCUAe9sj6JOtBbMmBBikGIegne9piDDIOeiLVoqTkz4A==, + } + engines: { node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0 } + + jest-circus@30.1.3: + resolution: + { + integrity: sha512-Yf3dnhRON2GJT4RYzM89t/EXIWNxKTpWTL9BfF3+geFetWP4XSvJjiU1vrWplOiUkmq8cHLiwuhz+XuUp9DscA==, + } + engines: { node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0 } + + jest-cli@30.1.3: + resolution: + { + integrity: sha512-G8E2Ol3OKch1DEeIBl41NP7OiC6LBhfg25Btv+idcusmoUSpqUkbrneMqbW9lVpI/rCKb/uETidb7DNteheuAQ==, + } + engines: { node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0 } + hasBin: true + peerDependencies: + node-notifier: ^8.0.1 || ^9.0.0 || ^10.0.0 + peerDependenciesMeta: + node-notifier: + optional: true + + jest-config@30.1.3: + resolution: + { + integrity: sha512-M/f7gqdQEPgZNA181Myz+GXCe8jXcJsGjCMXUzRj22FIXsZOyHNte84e0exntOvdPaeh9tA0w+B8qlP2fAezfw==, + } + engines: { node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0 } + peerDependencies: + "@types/node": "*" + esbuild-register: ">=3.4.0" + ts-node: ">=9.0.0" + peerDependenciesMeta: + "@types/node": + optional: true + esbuild-register: + optional: true + ts-node: + optional: true + + jest-diff@30.1.2: + resolution: + { + integrity: sha512-4+prq+9J61mOVXCa4Qp8ZjavdxzrWQXrI80GNxP8f4tkI2syPuPrJgdRPZRrfUTRvIoUwcmNLbqEJy9W800+NQ==, + } + engines: { node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0 } + + jest-docblock@30.0.1: + resolution: + { + integrity: sha512-/vF78qn3DYphAaIc3jy4gA7XSAz167n9Bm/wn/1XhTLW7tTBIzXtCJpb/vcmc73NIIeeohCbdL94JasyXUZsGA==, + } + engines: { node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0 } + + jest-each@30.1.0: + resolution: + { + integrity: sha512-A+9FKzxPluqogNahpCv04UJvcZ9B3HamqpDNWNKDjtxVRYB8xbZLFuCr8JAJFpNp83CA0anGQFlpQna9Me+/tQ==, + } + engines: { node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0 } + + jest-environment-node@30.1.2: + resolution: + { + integrity: sha512-w8qBiXtqGWJ9xpJIA98M0EIoq079GOQRQUyse5qg1plShUCQ0Ek1VTTcczqKrn3f24TFAgFtT+4q3aOXvjbsuA==, + } + engines: { node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0 } + + jest-haste-map@30.1.0: + resolution: + { + integrity: sha512-JLeM84kNjpRkggcGpQLsV7B8W4LNUWz7oDNVnY1Vjj22b5/fAb3kk3htiD+4Na8bmJmjJR7rBtS2Rmq/NEcADg==, + } + engines: { node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0 } + + jest-leak-detector@30.1.0: + resolution: + { + integrity: sha512-AoFvJzwxK+4KohH60vRuHaqXfWmeBATFZpzpmzNmYTtmRMiyGPVhkXpBqxUQunw+dQB48bDf4NpUs6ivVbRv1g==, + } + engines: { node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0 } + + jest-matcher-utils@30.1.2: + resolution: + { + integrity: sha512-7ai16hy4rSbDjvPTuUhuV8nyPBd6EX34HkBsBcBX2lENCuAQ0qKCPb/+lt8OSWUa9WWmGYLy41PrEzkwRwoGZQ==, + } + engines: { node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0 } + + jest-message-util@30.1.0: + resolution: + { + integrity: sha512-HizKDGG98cYkWmaLUHChq4iN+oCENohQLb7Z5guBPumYs+/etonmNFlg1Ps6yN9LTPyZn+M+b/9BbnHx3WTMDg==, + } + engines: { node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0 } + + jest-mock@30.0.5: + resolution: + { + integrity: sha512-Od7TyasAAQX/6S+QCbN6vZoWOMwlTtzzGuxJku1GhGanAjz9y+QsQkpScDmETvdc9aSXyJ/Op4rhpMYBWW91wQ==, + } + engines: { node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0 } + + jest-pnp-resolver@1.2.3: + resolution: + { + integrity: sha512-+3NpwQEnRoIBtx4fyhblQDPgJI0H1IEIkX7ShLUjPGA7TtUTvI1oiKi3SR4oBR0hQhQR80l4WAe5RrXBwWMA8w==, + } + engines: { node: ">=6" } + peerDependencies: + jest-resolve: "*" + peerDependenciesMeta: + jest-resolve: + optional: true + + jest-regex-util@30.0.1: + resolution: + { + integrity: sha512-jHEQgBXAgc+Gh4g0p3bCevgRCVRkB4VB70zhoAE48gxeSr1hfUOsM/C2WoJgVL7Eyg//hudYENbm3Ne+/dRVVA==, + } + engines: { node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0 } + + jest-resolve-dependencies@30.1.3: + resolution: + { + integrity: sha512-DNfq3WGmuRyHRHfEet+Zm3QOmVFtIarUOQHHryKPc0YL9ROfgWZxl4+aZq/VAzok2SS3gZdniP+dO4zgo59hBg==, + } + engines: { node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0 } + + jest-resolve@30.1.3: + resolution: + { + integrity: sha512-DI4PtTqzw9GwELFS41sdMK32Ajp3XZQ8iygeDMWkxlRhm7uUTOFSZFVZABFuxr0jvspn8MAYy54NxZCsuCTSOw==, + } + engines: { node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0 } + + jest-runner@30.1.3: + resolution: + { + integrity: sha512-dd1ORcxQraW44Uz029TtXj85W11yvLpDuIzNOlofrC8GN+SgDlgY4BvyxJiVeuabA1t6idjNbX59jLd2oplOGQ==, + } + engines: { node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0 } + + jest-runtime@30.1.3: + resolution: + { + integrity: sha512-WS8xgjuNSphdIGnleQcJ3AKE4tBKOVP+tKhCD0u+Tb2sBmsU8DxfbBpZX7//+XOz81zVs4eFpJQwBNji2Y07DA==, + } + engines: { node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0 } + + jest-snapshot@30.1.2: + resolution: + { + integrity: sha512-4q4+6+1c8B6Cy5pGgFvjDy/Pa6VYRiGu0yQafKkJ9u6wQx4G5PqI2QR6nxTl43yy7IWsINwz6oT4o6tD12a8Dg==, + } + engines: { node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0 } + jest-util@29.7.0: resolution: { @@ -4891,6 +6118,34 @@ packages: } engines: { node: ^14.15.0 || ^16.10.0 || >=18.0.0 } + jest-util@30.0.5: + resolution: + { + integrity: sha512-pvyPWssDZR0FlfMxCBoc0tvM8iUEskaRFALUtGQYzVEAqisAztmy+R8LnU14KT4XA0H/a5HMVTXat1jLne010g==, + } + engines: { node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0 } + + jest-validate@30.1.0: + resolution: + { + integrity: sha512-7P3ZlCFW/vhfQ8pE7zW6Oi4EzvuB4sgR72Q1INfW9m0FGo0GADYlPwIkf4CyPq7wq85g+kPMtPOHNAdWHeBOaA==, + } + engines: { node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0 } + + jest-watcher@30.1.3: + resolution: + { + integrity: sha512-6jQUZCP1BTL2gvG9E4YF06Ytq4yMb4If6YoQGRR6PpjtqOXSP3sKe2kqwB6SQ+H9DezOfZaSLnmka1NtGm3fCQ==, + } + engines: { node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0 } + + jest-worker@27.5.1: + resolution: + { + integrity: sha512-7vuh85V5cdDofPyxn58nrPjBktZo0u9x1g8WtjQol+jZDaE+fhN+cIvTj11GndBnMnyfrUOG1sZQxCdjKh+DKg==, + } + engines: { node: ">= 10.13.0" } + jest-worker@29.7.0: resolution: { @@ -4898,6 +6153,26 @@ packages: } engines: { node: ^14.15.0 || ^16.10.0 || >=18.0.0 } + jest-worker@30.1.0: + resolution: + { + integrity: sha512-uvWcSjlwAAgIu133Tt77A05H7RIk3Ho8tZL50bQM2AkvLdluw9NG48lRCl3Dt+MOH719n/0nnb5YxUwcuJiKRA==, + } + engines: { node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0 } + + jest@30.1.3: + resolution: + { + integrity: sha512-Ry+p2+NLk6u8Agh5yVqELfUJvRfV51hhVBRIB5yZPY7mU0DGBmOuFG5GebZbMbm86cdQNK0fhJuDX8/1YorISQ==, + } + engines: { node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0 } + hasBin: true + peerDependencies: + node-notifier: ^8.0.1 || ^9.0.0 || ^10.0.0 + peerDependenciesMeta: + node-notifier: + optional: true + jiti@1.21.7: resolution: { @@ -4911,12 +6186,26 @@ packages: integrity: sha512-1vUQX+IdDMVPj4k8kOxgUqlcK518yluMuGZwqlr44FS1ppZB/5GWh4rZG89erpOBOJjU/OBsnCVFfapsRz6nEA==, } + js-levenshtein@1.1.6: + resolution: + { + integrity: sha512-X2BB11YZtrRqY4EnQcLX5Rh373zbK4alC1FW7D7MBhL2gtcC17cTnr6DmfHZeS0s2rTHjUTMMHfG7gO8SSdw+g==, + } + engines: { node: ">=0.10.0" } + js-tokens@4.0.0: resolution: { integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==, } + js-yaml@3.14.1: + resolution: + { + integrity: sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==, + } + hasBin: true + js-yaml@4.1.0: resolution: { @@ -4950,12 +6239,6 @@ packages: integrity: sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==, } - json-schema-to-ts@1.6.4: - resolution: - { - integrity: sha512-pR4yQ9DHz6itqswtHCm26mw45FSNfQ9rEQjosaZErhn5J3J2sIViQiz8rDaezjKAhFGpmsoczYVBgGHzFw/stA==, - } - json-schema-traverse@0.4.1: resolution: { @@ -4981,17 +6264,13 @@ packages: } hasBin: true - jsonfile@4.0.0: + json5@2.2.3: resolution: { - integrity: sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg==, - } - - jsonfile@6.2.0: - resolution: - { - integrity: sha512-FGuPw30AdOIUTRMC2OMRtQV+jkVj2cfPqSeWXv1NEAJ1qZ5zb1X6z1mFhbfOB/iy3ssJCD+3KuZ8r8C3uVFlAg==, + integrity: sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==, } + engines: { node: ">=6" } + hasBin: true jsx-ast-utils@3.3.5: resolution: @@ -5019,6 +6298,13 @@ packages: } engines: { node: ">=0.10" } + leven@3.1.0: + resolution: + { + integrity: sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==, + } + engines: { node: ">=6" } + levn@0.4.1: resolution: { @@ -5026,12 +6312,6 @@ packages: } engines: { node: ">= 0.8.0" } - lie@3.1.1: - resolution: - { - integrity: sha512-RiNhHysUjhrDQntfYSfY4MU24coXXdEOgw9WGcKHNeEwffDYbF//u87M1EWaMGzuFoSbqW0C9C6lEEhDOAswfw==, - } - lighterhtml@4.2.0: resolution: { @@ -5051,11 +6331,19 @@ packages: integrity: sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==, } - localforage@1.10.0: + loader-runner@4.3.0: resolution: { - integrity: sha512-14/H1aX7hzBBmmh7sGPd+AOMkkIrHM3Z1PAyGgZigA1H1p5O5ANnMyWzvpAETtG68/dC4pC0ncy3+PPGzXZHPg==, + integrity: sha512-3R/1M+yS3j5ou80Me59j7F9IMs4PXs3VqRrm0TU3AbKPxlmpoY1TNscJV/oGJXo8qCatFGTfDbY6W6ipGOYXfg==, } + engines: { node: ">=6.11.5" } + + locate-path@5.0.0: + resolution: + { + integrity: sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==, + } + engines: { node: ">=8" } locate-path@6.0.0: resolution: @@ -5076,6 +6364,12 @@ packages: integrity: sha512-chi4NHZlZqZD18a0imDHnZPrDeBbTtVN7GXMwuGdRH9qotxAjYs3aVLKc7zNOG9eddR5Ksd8rvFEBc9SsggPpg==, } + lodash.memoize@4.1.2: + resolution: + { + integrity: sha512-t7j+NzmgnQzTAYXcsHYLgimltOV1MXHtlOWf6GjL9Kj8GK5FInw5JotxvbOs+IvV1/Dzo04/fCGfLVs7aXb4Ag==, + } + lodash.merge@4.6.2: resolution: { @@ -5101,6 +6395,12 @@ packages: integrity: sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==, } + lru-cache@5.1.1: + resolution: + { + integrity: sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==, + } + lru-cache@6.0.0: resolution: { @@ -5116,19 +6416,25 @@ packages: peerDependencies: react: ^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0 - magic-string@0.27.0: + magic-string@0.30.19: resolution: { - integrity: sha512-8UnnX2PeRAPZuN12svgR9j7M1uWMovg/CEnIwIG0LFkXSJJe4PdfUGiTGl8V9bsBHFUtfVINcSyYxd7q+kx9fA==, + integrity: sha512-2N21sPY9Ws53PZvsEpVtNuSW+ScYbQdp4b9qUaL+9QkHUrGFKo56Lg9Emg5s9V/qrtNBmiR01sYhUOwu3H+VOw==, + } + + magic-string@0.30.8: + resolution: + { + integrity: sha512-ISQTe55T2ao7XtlAStud6qwYPZjE4GK1S/BeVPus4jrq6JuOnQ00YKQC581RWhR122W7msZV263KzVeLoqidyQ==, } engines: { node: ">=12" } - make-dir@3.1.0: + make-dir@4.0.0: resolution: { - integrity: sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==, + integrity: sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==, } - engines: { node: ">=8" } + engines: { node: ">=10" } make-error@1.3.6: resolution: @@ -5136,6 +6442,12 @@ packages: integrity: sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==, } + makeerror@1.0.12: + resolution: + { + integrity: sha512-JmqCvUhmt43madlpFzG4BQzG2Z3m6tvQDNKdClZnO3VbIudJYmxsT0FNJMeiB2+JTSlTQTSbU8QdesVmwJcmLg==, + } + math-intrinsics@1.1.0: resolution: { @@ -5211,14 +6523,6 @@ packages: } engines: { node: ">= 8" } - micro@9.3.5-canary.3: - resolution: - { - integrity: sha512-viYIo9PefV+w9dvoIBh1gI44Mvx1BOk67B4BpC2QK77qdY0xZF0Q+vWLt/BII6cLkIc8rLmSIcJaB/OrXXKe1g==, - } - engines: { node: ">= 8.0.0" } - hasBin: true - micromark-core-commonmark@2.0.3: resolution: { @@ -5386,6 +6690,13 @@ packages: } engines: { node: ">=10" } + minimatch@8.0.4: + resolution: + { + integrity: sha512-W0Wvr9HyFXZRGIDgCicunpQ299OKXs9RgZfaukz4qAW/pJhcpUfupc9c+OObPOFueNy8VSrZgEmDtk6Kh4WzDA==, + } + engines: { node: ">=16 || 14 >=14.17" } + minimatch@9.0.5: resolution: { @@ -5399,23 +6710,10 @@ packages: integrity: sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==, } - minipass@2.9.0: + minipass@4.2.8: resolution: { - integrity: sha512-wxfUjg9WebH+CUDX/CdbRlh5SmfZiy/hpkxaRI16Y9W56Pa75sWgd/rvFilSgrauD9NyFymP/+JFV3KwzIsJeg==, - } - - minipass@3.3.6: - resolution: - { - integrity: sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==, - } - engines: { node: ">=8" } - - minipass@5.0.0: - resolution: - { - integrity: sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ==, + integrity: sha512-fNzuVyifolSLFL4NzpF+wEF4qrgqaaKX0haXPQEdQ7NKAN+WecoKMHV09YcuL/DHxrUsYQOK3MiuDf7Ip2OXfQ==, } engines: { node: ">=8" } @@ -5426,57 +6724,16 @@ packages: } engines: { node: ">=16 || 14 >=14.17" } - minizlib@1.3.3: - resolution: - { - integrity: sha512-6ZYMOEnmVsdCeTJVE0W9ZD+pVnE8h9Hma/iOwwRDsdQoePpoX56/8B6z3P9VNwppJuBKNRuFDRNRqRWexT9G9Q==, - } - - minizlib@2.1.2: - resolution: - { - integrity: sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==, - } - engines: { node: ">= 8" } - mitt@3.0.1: resolution: { integrity: sha512-vKivATfr97l2/QBCYAkXYDbrIWPM2IIKEl7YPhjCvKlG3kE2gm+uBo6nEXK3M5/Ffh/FLpKExzOQ3JJoJGFKBw==, } - mkdirp@0.5.6: + module-details-from-path@1.0.4: resolution: { - integrity: sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==, - } - hasBin: true - - mkdirp@1.0.4: - resolution: - { - integrity: sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==, - } - engines: { node: ">=10" } - hasBin: true - - mlly@1.7.4: - resolution: - { - integrity: sha512-qmdSIPC4bDJXgZTCR7XosJiNKySV7O215tsPtDN9iEO/7q/76b/ijtgRu/+epFXSJhijtTCCGp3DWS549P3xKw==, - } - - mri@1.2.0: - resolution: - { - integrity: sha512-tzzskb3bG8LvYGFF/mDTpq3jpI6Q9wc3LEmBaghu+DdCssd1FakN7Bc0hVNmEyGq1bq3RgfkCb3cmQLpNPOroA==, - } - engines: { node: ">=4" } - - ms@2.1.1: - resolution: - { - integrity: sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==, + integrity: sha512-EGWKgxALGMgzvxYF1UyGTy0HXX/2vHLkw6+NvDKW2jypWbHpjQuj4UMcqQWXHERJhVGKikolT06G3bcKe4fi7w==, } ms@2.1.3: @@ -5545,24 +6802,27 @@ packages: react: ^16.8 || ^17 || ^18 || ^19 || ^19.0.0-rc react-dom: ^16.8 || ^17 || ^18 || ^19 || ^19.0.0-rc - next@14.2.31: + next@15.5.3: resolution: { - integrity: sha512-Wyw1m4t8PhqG+or5a1U/Deb888YApC4rAez9bGhHkTsfwAy4SWKVro0GhEx4sox1856IbLhvhce2hAA6o8vkog==, + integrity: sha512-r/liNAx16SQj4D+XH/oI1dlpv9tdKJ6cONYPwwcCC46f2NjpaRWY+EKCzULfgQYV6YKXjHBchff2IZBSlZmJNw==, } - engines: { node: ">=18.17.0" } + engines: { node: ^18.18.0 || ^19.8.0 || >= 20.0.0 } hasBin: true peerDependencies: "@opentelemetry/api": ^1.1.0 - "@playwright/test": ^1.41.2 - react: ^18.2.0 - react-dom: ^18.2.0 + "@playwright/test": ^1.51.1 + babel-plugin-react-compiler: "*" + react: ^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0 + react-dom: ^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0 sass: ^1.3.0 peerDependenciesMeta: "@opentelemetry/api": optional: true "@playwright/test": optional: true + babel-plugin-react-compiler: + optional: true sass: optional: true @@ -5578,36 +6838,6 @@ packages: integrity: sha512-5m3bsyrjFWE1xf7nz7YXdN4udnVtXK6/Yfgn5qnahL6bCkf2yKt4k3nuTKAtT4r3IG8JNR2ncsIMdZuAzJjHQQ==, } - node-fetch-native@1.6.7: - resolution: - { - integrity: sha512-g9yhqoedzIUm0nTnTqAQvueMPVOuIY16bqgAJJC8XOOubYFNwz6IER9qs0Gq2Xd0+CecCKFjtdDTMA4u4xG06Q==, - } - - node-fetch@2.6.7: - resolution: - { - integrity: sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ==, - } - engines: { node: 4.x || >=6.0.0 } - peerDependencies: - encoding: ^0.1.0 - peerDependenciesMeta: - encoding: - optional: true - - node-fetch@2.6.9: - resolution: - { - integrity: sha512-DJm/CJkZkRjKKj4Zi4BsKVZh3ValV5IR5s7LVZnW+6YMh0W1BfNA8XSs6DLMGYlId5F3KnA70uu2qepcR08Qqg==, - } - engines: { node: 4.x || >=6.0.0 } - peerDependencies: - encoding: ^0.1.0 - peerDependenciesMeta: - encoding: - optional: true - node-fetch@2.7.0: resolution: { @@ -5620,12 +6850,11 @@ packages: encoding: optional: true - node-gyp-build@4.8.4: + node-int64@0.4.0: resolution: { - integrity: sha512-LA4ZjwlnUblHVgq0oBF3Jl/6h/Nvs5fzBLwdEF4nuxnFdsfajde4WfxtJr3CaiH+F6ewcIB/q4jQ4UzPyid+CQ==, + integrity: sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw==, } - hasBin: true node-releases@2.0.19: resolution: @@ -5633,14 +6862,6 @@ packages: integrity: sha512-xxOWJsBKtzAq7DY0J+DTzuz58K8e7sJbdgwkbMWQe8UYB6ekmsQ45q0M/tJDsGaZmbC+l7n57UV8Hl5tHxO9uw==, } - nopt@5.0.0: - resolution: - { - integrity: sha512-Tbj67rffqceeLpcRXrT7vKAN8CwfPeIBgM7E6iBkmKLV7bEMwpGgYLGv0jACUsECaa/vuxP0IjEont6umdMgtQ==, - } - engines: { node: ">=6" } - hasBin: true - normalize-path@3.0.0: resolution: { @@ -5662,13 +6883,6 @@ packages: } engines: { node: ">=8" } - npmlog@5.0.1: - resolution: - { - integrity: sha512-AqZtDUWOMKs1G/8lwylVjrdYgqA4d9nu8hc+0gzRxlDb1I10+FHBGMXs6aiQHFdCUUlqH99MUMuLfzWDNDtfxw==, - } - deprecated: This package is no longer supported. - nuqs@2.4.3: resolution: { @@ -5690,14 +6904,6 @@ packages: react-router-dom: optional: true - nypm@0.5.4: - resolution: - { - integrity: sha512-X0SNNrZiGU8/e/zAB7sCTtdxWTMSIO73q+xuKgglm2Yvzwlo8UoC5FNySQFCvl84uPaeADkqHUZUkWy4aH4xOA==, - } - engines: { node: ^14.16.0 || >=16.10.0 } - hasBin: true - oauth@0.9.15: resolution: { @@ -5774,12 +6980,6 @@ packages: } engines: { node: ">= 0.4" } - ohash@1.1.6: - resolution: - { - integrity: sha512-TBu7PtV8YkAZn0tSxobKY2n2aAQva936lhRrj6957aDaCf9IEtqsKbgMzXE/F/sjqYOwmrukeORHNLe5glk7Cg==, - } - oidc-token-hash@5.1.1: resolution: { @@ -5787,12 +6987,6 @@ packages: } engines: { node: ^10.13.0 || >=12.0.0 } - once@1.3.3: - resolution: - { - integrity: sha512-6vaNInhu+CHxtONf3zw3vq4SP2DOQhjBvIa3rNcG0+P7eKWlYH6Peu7rHizSloRU2EwMz6GraLieis9Ac9+p1w==, - } - once@1.4.0: resolution: { @@ -5806,6 +7000,36 @@ packages: } engines: { node: ">=6" } + openapi-fetch@0.14.0: + resolution: + { + integrity: sha512-PshIdm1NgdLvb05zp8LqRQMNSKzIlPkyMxYFxwyHR+UlKD4t2nUjkDhNxeRbhRSEd3x5EUNh2w5sJYwkhOH4fg==, + } + + openapi-react-query@0.5.0: + resolution: + { + integrity: sha512-VtyqiamsbWsdSWtXmj/fAR+m9nNxztsof6h8ZIsjRj8c8UR/x9AIwHwd60IqwgymmFwo7qfSJQ1ZzMJrtqjQVg==, + } + peerDependencies: + "@tanstack/react-query": ^5.25.0 + openapi-fetch: ^0.14.0 + + openapi-typescript-helpers@0.0.15: + resolution: + { + integrity: sha512-opyTPaunsklCBpTK8JGef6mfPhLSnyy5a0IN9vKtx3+4aExf+KxEqYwIy3hqkedXIB97u357uLMJsOnm3GVjsw==, + } + + openapi-typescript@7.9.1: + resolution: + { + integrity: sha512-9gJtoY04mk6iPMbToPjPxEAtfXZ0dTsMZtsgUI8YZta0btPPig9DJFP4jlerQD/7QOwYgb0tl+zLUpDf7vb7VA==, + } + hasBin: true + peerDependencies: + typescript: ^5.x + openid-client@5.7.1: resolution: { @@ -5819,13 +7043,6 @@ packages: } engines: { node: ">= 0.8.0" } - os-paths@4.4.0: - resolution: - { - integrity: sha512-wrAwOeXp1RRMFfQY8Sy7VaGVmPocaLwSFOYCGKSyo8qmJ+/yaafCl5BCA1IQZWqFSRBrKDYFeR9d/VyQzfH/jg==, - } - engines: { node: ">= 6.0" } - own-keys@1.0.1: resolution: { @@ -5833,12 +7050,12 @@ packages: } engines: { node: ">= 0.4" } - p-finally@2.0.1: + p-limit@2.3.0: resolution: { - integrity: sha512-vpm09aKwq6H9phqRQzecoDpD8TmVyGw70qmWlyq5onxY7tqyTTFVvxMykxQSQKILBSFlbXpypIw2T1Ml7+DDtw==, + integrity: sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==, } - engines: { node: ">=8" } + engines: { node: ">=6" } p-limit@3.1.0: resolution: @@ -5847,6 +7064,13 @@ packages: } engines: { node: ">=10" } + p-locate@4.1.0: + resolution: + { + integrity: sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==, + } + engines: { node: ">=8" } + p-locate@5.0.0: resolution: { @@ -5854,6 +7078,13 @@ packages: } engines: { node: ">=10" } + p-try@2.2.0: + resolution: + { + integrity: sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==, + } + engines: { node: ">=6" } + package-json-from-dist@1.0.1: resolution: { @@ -5880,18 +7111,12 @@ packages: } engines: { node: ">=8" } - parse-ms@2.1.0: + parse-json@8.3.0: resolution: { - integrity: sha512-kHt7kzLoS9VBZfUsiKjv43mr91ea+U05EyKkEtqp7vNbHxmaVuEqN7XxeEVnGrMtYOAxGrDElSi96K7EgO1zCA==, - } - engines: { node: ">=6" } - - path-browserify@1.0.1: - resolution: - { - integrity: sha512-b7uo2UCUOYZcnF/3ID0lulOJi/bafxa1xPe7ZPsammBSpjSWQkjNxlt635YGS2MiR9GjvuXCtz2emr3jbsz98g==, + integrity: sha512-ybiGyvspI+fAoRQbIPRddCcSTV9/LsJbf0e/S85VLowVGzRmokfneg2kwVW/KU5rOXrPSbF1qAKPMgNTqqROQQ==, } + engines: { node: ">=18" } path-exists@4.0.0: resolution: @@ -5914,13 +7139,6 @@ packages: } engines: { node: ">=8" } - path-match@1.2.4: - resolution: - { - integrity: sha512-UWlehEdqu36jmh4h5CWJ7tARp1OEVKGHKm6+dg9qMq5RKUTV5WJrGgaZ3dN2m7WFAXDbjlHzvJvL/IUpy84Ktw==, - } - deprecated: This package is archived and no longer maintained. For support, visit https://github.com/expressjs/express/discussions - path-parse@1.0.7: resolution: { @@ -5934,24 +7152,6 @@ packages: } engines: { node: ">=16 || 14 >=14.18" } - path-to-regexp@1.9.0: - resolution: - { - integrity: sha512-xIp7/apCFJuUHdDLWe8O1HIkb0kQrOMb/0u6FXQjemHn/ii5LrIzU6bdECnsiTF/GjZkMEKg1xdiZwNqDYlZ6g==, - } - - path-to-regexp@6.1.0: - resolution: - { - integrity: sha512-h9DqehX3zZZDCEm+xbfU0ZmwCGFCAAraPJWMXJ4+v32NjZJilVg3k1TcKsRgIb8IQ/izZSaydDc1OhJCZvs2Dw==, - } - - path-to-regexp@6.2.1: - resolution: - { - integrity: sha512-JLyh7xT1kizaEvcaXOQwOc2/Yhw6KZOvPf1S8401UyLk86CU79LN3vl7ztXGm/pZ+YjoyAJ4rxmHwbkBXJX+yw==, - } - path-type@4.0.0: resolution: { @@ -5959,41 +7159,31 @@ packages: } engines: { node: ">=8" } - pathe@1.1.2: - resolution: - { - integrity: sha512-whLdWMYL2TwI08hn8/ZqAbrVemu0LNaNNJZX73O6qaIdCTfXutsLhMkjdENX0qhsQ9uIimo4/aQOmXkoon2nDQ==, - } - - pathe@2.0.3: - resolution: - { - integrity: sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==, - } - - pend@1.2.0: - resolution: - { - integrity: sha512-F3asv42UuXchdzt+xXqfW1OGlVBe+mxa2mqI0pg5yAHZPvFmY3Y6drSf/GQ1A86WgWEN9Kzh/WrgKa6iGcHXLg==, - } - - perfect-debounce@1.0.0: - resolution: - { - integrity: sha512-xCy9V055GLEqoFaHoC1SoLIaLmWctgCUaBaWxDZ7/Zx4CTyX7cJQLJOok/orfjZAh9kEYpjJa4d0KcJmCbctZA==, - } - perfect-freehand@1.2.2: resolution: { integrity: sha512-eh31l019WICQ03pkF3FSzHxB8n07ItqIQ++G5UV8JX0zVOXzgTGCqnRR0jJ2h9U8/2uW4W4mtGJELt9kEV0CFQ==, } - picocolors@1.0.0: + pg-int8@1.0.1: resolution: { - integrity: sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==, + integrity: sha512-WCtabS6t3c8SkpDBUlb1kjOs7l66xsGdKpIPZsg4wR+B3+u9UAum2odSsF9tnvxg80h4ZxLWMy4pRjOsFIqQpw==, } + engines: { node: ">=4.0.0" } + + pg-protocol@1.10.3: + resolution: + { + integrity: sha512-6DIBgBQaTKDJyxnXaLiLR8wBpQQcGWuAESkRBX/t6OwA8YsqP+iVSiond2EDy6Y/dsGk8rh/jtax3js5NeV7JQ==, + } + + pg-types@2.2.0: + resolution: + { + integrity: sha512-qTAAlrEsl8s4OiEQY69wDvcMIdQN6wdz5ojQiOy6YRMuynxenON0O5oCpJI6lshc6scgAY8qvJ2On/p+CXY0GA==, + } + engines: { node: ">=4" } picocolors@1.1.1: resolution: @@ -6029,11 +7219,19 @@ packages: } engines: { node: ">= 6" } - pkg-types@1.3.1: + pkg-dir@4.2.0: resolution: { - integrity: sha512-/Jm5M4RvtBFVkKWRu2BLUTNP8/M2a+UwuAX+ae4770q1qVGtfjG+WTCupoZixokjmHiry8uI+dlY8KXYV5HVVQ==, + integrity: sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==, } + engines: { node: ">=8" } + + pluralize@8.0.0: + resolution: + { + integrity: sha512-Nc3IT5yHzflTfbjgqWcCPpo7DaKy4FnpB0l/zCAW0Tc7jxAiuqSxHasntB3D7887LSrA93kDJ9IXovxJYxyLCA==, + } + engines: { node: ">=4" } possible-typed-array-names@1.1.0: resolution: @@ -6111,6 +7309,34 @@ packages: } engines: { node: ^10 || ^12 || >=14 } + postgres-array@2.0.0: + resolution: + { + integrity: sha512-VpZrUqU5A69eQyW2c5CA1jtLecCsN2U/bD6VilrFDWq5+5UIEVO7nazS3TEcHf1zuPYO/sqGvUvW62g86RXZuA==, + } + engines: { node: ">=4" } + + postgres-bytea@1.0.0: + resolution: + { + integrity: sha512-xy3pmLuQqRBZBXDULy7KbaitYqLcmxigw14Q5sj8QBVLqEwXfeybIKVWiqAXTlcvdvb0+xkOtDbfQMOf4lST1w==, + } + engines: { node: ">=0.10.0" } + + postgres-date@1.0.7: + resolution: + { + integrity: sha512-suDmjLVQg78nMK2UZ454hAG+OAW+HQPZ6n++TNDUX+L0+uUlLywnoxJKDou51Zm+zTCjrCl0Nq6J9C5hP9vK/Q==, + } + engines: { node: ">=0.10.0" } + + postgres-interval@1.2.0: + resolution: + { + integrity: sha512-9ZhXKM/rw350N1ovuWHbGxnGh/SNJ4cnxHiM0rxE4VN41wsg8P8zWn9hv/buK00RP4WvlOyr/RBDiptyxVbkZQ==, + } + engines: { node: ">=0.10.0" } + preact-render-to-string@5.2.6: resolution: { @@ -6146,12 +7372,12 @@ packages: integrity: sha512-WuxUnVtlWL1OfZFQFuqvnvs6MiAGk9UNsBostyBOB0Is9wb5uRESevA6rnl/rkksXaGX3GzZhPup5d6Vp1nFew==, } - pretty-ms@7.0.1: + pretty-format@30.0.5: resolution: { - integrity: sha512-973driJZvxiGOQ5ONsFhOF/DtzPMOMtgC11kCpUrPGMTgqp2q/1gwzCquocrN33is0VZ5GFHXZYMM9l6h67v2Q==, + integrity: sha512-D1tKtYvByrBkFLe2wHJl2bwMJIiT8rW+XA+TiataH79/FszLQMrpGEvzUVkzPau7OCO0Qnrhpe87PqtOAIB8Yw==, } - engines: { node: ">=10" } + engines: { node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0 } progress@2.0.3: resolution: @@ -6160,12 +7386,6 @@ packages: } engines: { node: ">=0.4.0" } - promisepipe@3.0.0: - resolution: - { - integrity: sha512-V6TbZDJ/ZswevgkDNpGt/YqNCiZP9ASfgU+p83uJE6NrGtvSGoOcHLiDCqkMs2+yg7F5qHdLV8d0aS8O26G/KA==, - } - prop-types@15.8.1: resolution: { @@ -6196,12 +7416,6 @@ packages: integrity: sha512-VDdG/VYtOgdGkWJx7y0o7p+zArSf2383Isci8C+BP3YXgMYDoPd3cCBjw0JdWb6YBb9sFiOPbAADDVTPJnh+9g==, } - pump@3.0.3: - resolution: - { - integrity: sha512-todwxLMY7/heScKmntwQG8CXVkWUOdYxIvY2s0VWAAMh/nd8SoYiRaKjlr7+iCs984f2P8zvrfWcDDYVb73NfA==, - } - punycode@2.3.1: resolution: { @@ -6209,6 +7423,12 @@ packages: } engines: { node: ">=6" } + pure-rand@7.0.1: + resolution: + { + integrity: sha512-oTUZM/NAZS8p7ANR3SHh30kXB+zK2r2BPcEn/awJIbOvq82WoMN4p62AWWp3Hhw50G0xMsw1mhIBLqHw64EcNQ==, + } + qr.js@0.0.0: resolution: { @@ -6227,19 +7447,6 @@ packages: integrity: sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==, } - raw-body@2.4.1: - resolution: - { - integrity: sha512-9WmIKF6mkvA0SLmA2Knm9+qj89e+j1zqgyn8aXGd7+nAduPoqgI9lO57SAZNn/Byzo5P7JhXTyg9PzaJbH73bA==, - } - engines: { node: ">= 0.8" } - - rc9@2.1.2: - resolution: - { - integrity: sha512-btXCnMmRIBINM2LDZoEmOogIZU7Qe7zn4BpomSKZ/ykbLObuBdvG+mFq11DL6fjH1DRwHhrlgtYWG96bJiC7Cg==, - } - react-dom@18.3.1: resolution: { @@ -6271,6 +7478,12 @@ packages: integrity: sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==, } + react-is@18.3.1: + resolution: + { + integrity: sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==, + } + react-markdown@9.1.0: resolution: { @@ -6357,13 +7570,6 @@ packages: } engines: { node: ">= 6" } - readdirp@3.3.0: - resolution: - { - integrity: sha512-zz0pAkSPOXXm1viEwygWIPSPkcBYjW1xU5j/JBh5t9bGCJwa6f9+BJa6VaB2g+b55yVrmXzqkyLf4xaWYM0IkQ==, - } - engines: { node: ">=8.10.0" } - readdirp@3.6.0: resolution: { @@ -6439,6 +7645,13 @@ packages: integrity: sha512-Dh7l57ianaEoIpzbp0PC9UKAdCSVklD8E5Rpw7ETfbTl3FqcOOgq5q2LVDhgGCkaBv7p24JXikPdvhhmHvKMsw==, } + require-directory@2.1.1: + resolution: + { + integrity: sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==, + } + engines: { node: ">=0.10.0" } + require-from-string@2.0.2: resolution: { @@ -6446,6 +7659,13 @@ packages: } engines: { node: ">=0.10.0" } + require-in-the-middle@7.5.2: + resolution: + { + integrity: sha512-gAZ+kLqBdHarXB64XpAe2VCjB7rIRv+mU8tfRWziHRJ5umKsIHN2tLLv6EtMw7WCdP19S0ERVMldNvxYCHnhSQ==, + } + engines: { node: ">=8.6.0" } + reraf@1.1.1: resolution: { @@ -6458,6 +7678,13 @@ packages: integrity: sha512-K/BG6eIky/SBpzfHZv/dd+9JBFiS4SWV7FIujVyJRux6e45+73RaUHXLmIR1f7WOMaQ0U1km6qwklRQxpJJY0w==, } + resolve-cwd@3.0.0: + resolution: + { + integrity: sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg==, + } + engines: { node: ">=8" } + resolve-from@4.0.0: resolution: { @@ -6507,20 +7734,12 @@ packages: } engines: { iojs: ">=1.0.0", node: ">=0.10.0" } - rimraf@3.0.2: + rollup@4.50.1: resolution: { - integrity: sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==, + integrity: sha512-78E9voJHwnXQMiQdiqswVLZwJIzdBKJ1GdI5Zx6XwoFKUIk09/sSrr+05QFzvYb8q6Y9pPV45zzDuYa3907TZA==, } - deprecated: Rimraf versions prior to v4 are no longer supported - hasBin: true - - rollup@2.79.2: - resolution: - { - integrity: sha512-fS6iqSPZDs3dr/y7Od6y5nha8dW1YnbgtsyotCVvoFGKbERG++CVRFv1meyGDE1SNItQA8BrnCw7ScdAhRJ3XQ==, - } - engines: { node: ">=10.0.0" } + engines: { node: ">=18.0.0", npm: ">=8.0.0" } hasBin: true rtcstats@https://codeload.github.com/whereby/rtcstats/tar.gz/63bcb6420d76d34161b39e494524ae73aa6dd70d: @@ -6570,12 +7789,6 @@ packages: } engines: { node: ">= 0.4" } - safer-buffer@2.1.2: - resolution: - { - integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==, - } - sass@1.90.0: resolution: { @@ -6590,6 +7803,13 @@ packages: integrity: sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==, } + schema-utils@4.3.2: + resolution: + { + integrity: sha512-Gn/JaSk/Mt9gYubxTtSn/QCV4em9mpAPiR1rqy/Ocu19u/G9J5WWdNoUT4SiV6mFC3y6cxyFcFwdzPM3FgxGAQ==, + } + engines: { node: ">= 10.13.0" } + sdp-transform@2.15.0: resolution: { @@ -6610,14 +7830,6 @@ packages: } hasBin: true - semver@7.3.5: - resolution: - { - integrity: sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ==, - } - engines: { node: ">=10" } - hasBin: true - semver@7.7.2: resolution: { @@ -6626,10 +7838,10 @@ packages: engines: { node: ">=10" } hasBin: true - set-blocking@2.0.0: + serialize-javascript@6.0.2: resolution: { - integrity: sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==, + integrity: sha512-Saa1xPByTTq2gdeFZYLLo+RFE35NHZkAbqZeWNd3BpzppeVisAqpDjcp8dyf6uIvEqJRd46jemmyA4iFIeVk8g==, } set-function-length@1.2.2: @@ -6653,11 +7865,12 @@ packages: } engines: { node: ">= 0.4" } - setprototypeof@1.1.1: + sharp@0.34.3: resolution: { - integrity: sha512-JvdAWfbXeIGaZ9cILp38HntZSFSo3mWg6xGcJJsd+d4aRMOqauag1C63dJfDw7OaMYwEbHMOxEZ1lqVRYP2OAw==, + integrity: sha512-eX2IQ6nFohW4DbvHIOLRB3MHFpYqaqvXd3Tp5e/T/dSH83fxaNJQRvDMhASmkNTsNTVF2/OOopzRCt7xokgPfg==, } + engines: { node: ^18.17.0 || ^20.3.0 || >=21.0.0 } shebang-command@2.0.0: resolution: @@ -6673,6 +7886,12 @@ packages: } engines: { node: ">=8" } + shimmer@1.2.1: + resolution: + { + integrity: sha512-sQTKC1Re/rM6XyFM6fIAGHRPVGvyXfgzIDvzoq608vM+jeyVD0Tu1E6Np0Kc2zAIFWIj963V2800iF/9LPieQw==, + } + side-channel-list@1.0.0: resolution: { @@ -6707,13 +7926,6 @@ packages: integrity: sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==, } - signal-exit@4.0.2: - resolution: - { - integrity: sha512-MY2/qGx4enyjprQnFaZsHib3Yadh3IXyV2C321GY0pjGfVBu4un0uDJkwgdxqO+Rdx8JMT8IfJIRwbYVz3Ob3Q==, - } - engines: { node: ">=14" } - signal-exit@4.1.0: resolution: { @@ -6727,6 +7939,19 @@ packages: integrity: sha512-D1SaWpOW8afq1CZGWB8xTfrT3FekjQmPValrqncJMX7QFl8YwhrPTZvMCANLtgBwwdS+7zURyqxDDEmY558tTw==, } + simple-swizzle@0.2.2: + resolution: + { + integrity: sha512-JA//kQgZtbuY83m+xT+tXJkmJncGMTFT+C+g2h2R9uxkYIrE2yy9sgmcLhCnw57/WSD+Eh3J97FPEDFnbXnDUg==, + } + + slash@3.0.0: + resolution: + { + integrity: sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==, + } + engines: { node: ">=8" } + socket.io-client@4.7.2: resolution: { @@ -6748,6 +7973,18 @@ packages: } engines: { node: ">=0.10.0" } + source-map-support@0.5.13: + resolution: + { + integrity: sha512-SHSKFHadjVA5oR4PPqhtAVdcBWwRYVd6g6cAXnIbRiIwc2EhPrTuKUBdSLvlEKyIP3GCf89fltvcZiP9MMFA1w==, + } + + source-map-support@0.5.21: + resolution: + { + integrity: sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==, + } + source-map@0.5.7: resolution: { @@ -6768,6 +8005,12 @@ packages: integrity: sha512-PEGlAwrG8yXGXRjW32fGbg66JAlOAwbObuqVoJpv/mRgoWDQfgH1wDPvtzWyUSNAXBGSk8h755YDbbcEy3SH2Q==, } + sprintf-js@1.0.3: + resolution: + { + integrity: sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==, + } + sprintf-js@1.1.3: resolution: { @@ -6780,6 +8023,13 @@ packages: integrity: sha512-+L3ccpzibovGXFK+Ap/f8LOS0ahMrHTf3xu7mMLSpEGU0EO9ucaysSylKo9eRDFNhWve/y275iPmIZ4z39a9iA==, } + stack-utils@2.0.6: + resolution: + { + integrity: sha512-XlkWvfIm6RmsWtNJx+uqtKLS8eqFbxUg0ZzLXqY0caEy9l7hruX8IpiDnjsLavoBgqCCR71TqWO8MaXYheJ3RQ==, + } + engines: { node: ">=10" } + stacktrace-parser@0.1.11: resolution: { @@ -6793,19 +8043,6 @@ packages: integrity: sha512-qoRRSyROncaz1z0mvYqIE4lCd9p2R90i6GxW3uZv5ucSu8tU7B5HXUP1gG8pVZsYNVaXjk8ClXHPttLyxAL48A==, } - stat-mode@0.3.0: - resolution: - { - integrity: sha512-QjMLR0A3WwFY2aZdV0okfFEJB5TRjkggXZjxP3A1RsWsNHNu3YPv8btmtc6iCFZ0Rul3FE93OYogvhOUClU+ng==, - } - - statuses@1.5.0: - resolution: - { - integrity: sha512-OpZ3zP+jT1PI7I8nemJX4AKmAX070ZkYPVWV/AaKTJl+tXCTGyVdC1a4SL8RUQYEwk/f34ZX8UTykN68FwrqAA==, - } - engines: { node: ">= 0.6" } - stop-iteration-iterator@1.1.0: resolution: { @@ -6813,24 +8050,12 @@ packages: } engines: { node: ">= 0.4" } - stream-to-array@2.3.0: + string-length@4.0.2: resolution: { - integrity: sha512-UsZtOYEn4tWU2RGLOXr/o/xjRBftZRlG3dEWoaHr8j4GuypJ3isitGbVyjQKAuMu+xbiop8q224TjiZWc4XTZA==, + integrity: sha512-+l6rNN5fYHNhZZy41RXsYptCjA2Igmq4EG7kZAYFQI1E1VTXarr6ZPXBg6eq7Y6eK4FEhY6AJlyuFIb/v/S0VQ==, } - - stream-to-promise@2.2.0: - resolution: - { - integrity: sha512-HAGUASw8NT0k8JvIVutB2Y/9iBk7gpgEyAudXwNJmZERdMITGdajOa4VJfD/kNiA3TppQpTP4J+CtcHwdzKBAw==, - } - - streamsearch@1.1.0: - resolution: - { - integrity: sha512-Mcc5wHehp9aXz1ax6bZUyY5afg9u2rv5cqQI3mRrYkGC8rW2hM02jWuwjtL++LS5qinSyhj2QfLyNsuc+VsExg==, - } - engines: { node: ">=10.0.0" } + engines: { node: ">=10" } string-width@4.2.3: resolution: @@ -6920,6 +8145,13 @@ packages: } engines: { node: ">=4" } + strip-bom@4.0.0: + resolution: + { + integrity: sha512-3xurFv5tEgii33Zi8Jtp55wEIILR9eh34FAW00PZf+JnSsTmV/ioewSgQl97JHvgjoRGwPShsWm+IdrxB35d0w==, + } + engines: { node: ">=8" } + strip-final-newline@2.0.0: resolution: { @@ -6946,16 +8178,16 @@ packages: integrity: sha512-G4qppLgKu/k6FwRpHiGiKPaPTFcG3g4wNVX/Qsfu+RqQM30E7Tyu/TEgxcL9PNLF5pdRLwQdE3YKKf+KF2Dzlw==, } - styled-jsx@5.1.1: + styled-jsx@5.1.6: resolution: { - integrity: sha512-pW7uC1l4mBZ8ugbiZrcIsiIvVx1UmTfw7UkC3Um2tmfUq9Bhk8IiyEIPl6F8agHgjzku6j0xQEZbfA5uSgSaCw==, + integrity: sha512-qSVyDTeMotdvQYoHWLNGwRFJHC+i+ZvdBRYosOFgC+Wg1vx4frN2/RG/NA7SYqqvKNLf39P2LSRA2pu6n0XYZA==, } engines: { node: ">= 12.0.0" } peerDependencies: "@babel/core": "*" babel-plugin-macros: "*" - react: ">= 16.8.0 || 17.x.x || ^18.0.0-0" + react: ">= 16.8.0 || 17.x.x || ^18.0.0-0 || ^19.0.0-0" peerDependenciesMeta: "@babel/core": optional: true @@ -6976,6 +8208,13 @@ packages: engines: { node: ">=16 || 14 >=14.17" } hasBin: true + supports-color@10.2.0: + resolution: + { + integrity: sha512-5eG9FQjEjDbAlI5+kdpdyPIBMRH4GfTVDGREVupaZHmVoppknhM29b/S9BkQz7cathp85BVgRi/As3Siln7e0Q==, + } + engines: { node: ">=18" } + supports-color@7.2.0: resolution: { @@ -7004,6 +8243,13 @@ packages: } engines: { node: ">= 0.4" } + synckit@0.11.11: + resolution: + { + integrity: sha512-MeQTA1r0litLUf0Rp/iisCaL8761lKAZHaimlbGK4j0HysC4PLfqygQj9srcs0m2RdtDYnF8UuYyKpbjHYp7Jw==, + } + engines: { node: ^14.18.0 || >=16.0.0 } + tailwindcss@3.4.17: resolution: { @@ -7012,19 +8258,46 @@ packages: engines: { node: ">=14.0.0" } hasBin: true - tar@4.4.18: + tapable@2.2.3: resolution: { - integrity: sha512-ZuOtqqmkV9RE1+4odd+MhBpibmCxNP6PJhH/h2OqNuotTX7/XHPZQJv2pKvWMplFH9SIZZhitehh6vBH6LO8Pg==, + integrity: sha512-ZL6DDuAlRlLGghwcfmSn9sK3Hr6ArtyudlSAiCqQ6IfE+b+HHbydbYDIG15IfS5do+7XQQBdBiubF/cV2dnDzg==, } - engines: { node: ">=4.5" } + engines: { node: ">=6" } - tar@6.2.1: + terser-webpack-plugin@5.3.14: resolution: { - integrity: sha512-DZ4yORTwrbTj/7MZYq2w+/ZFdI6OZ/f9SFHR+71gIVUZhOQPHzVCLpvRnPgyaMpfWxxk/4ONva3GQSyNIKRv6A==, + integrity: sha512-vkZjpUjb6OMS7dhV+tILUW6BhpDR7P2L/aQSAv+Uwk+m8KATX9EccViHTJR2qDtACKPIYndLGCyl3FMo+r2LMw==, + } + engines: { node: ">= 10.13.0" } + peerDependencies: + "@swc/core": "*" + esbuild: "*" + uglify-js: "*" + webpack: ^5.1.0 + peerDependenciesMeta: + "@swc/core": + optional: true + esbuild: + optional: true + uglify-js: + optional: true + + terser@5.44.0: + resolution: + { + integrity: sha512-nIVck8DK+GM/0Frwd+nIhZ84pR/BX7rmXMfYwyg+Sri5oGVE99/E3KvXqpC2xHFxyqXyGHTKBSioxxplrO4I4w==, } engines: { node: ">=10" } + hasBin: true + + test-exclude@6.0.0: + resolution: + { + integrity: sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w==, + } + engines: { node: ">=8" } thenify-all@1.6.0: resolution: @@ -7039,19 +8312,6 @@ packages: integrity: sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==, } - time-span@4.0.0: - resolution: - { - integrity: sha512-MyqZCTGLDZ77u4k+jqg4UlrzPTPZ49NDlaekU6uuFaJLzPIN1woaRXCbGeqOfxwc3Y37ZROGAJ614Rdv7Olt+g==, - } - engines: { node: ">=10" } - - tinyexec@0.3.2: - resolution: - { - integrity: sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA==, - } - tinyglobby@0.2.14: resolution: { @@ -7059,6 +8319,12 @@ packages: } engines: { node: ">=12.0.0" } + tmpl@1.0.5: + resolution: + { + integrity: sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw==, + } + to-regex-range@5.0.1: resolution: { @@ -7066,26 +8332,12 @@ packages: } engines: { node: ">=8.0" } - toidentifier@1.0.0: - resolution: - { - integrity: sha512-yaOH/Pk/VEhBWWTlhI+qXxDFXlejDGcQipMlyxda9nthulaxLZUNcUqFxokp0vcYnvteJln5FNQDRrxj3YcbVw==, - } - engines: { node: ">=0.6" } - tr46@0.0.3: resolution: { integrity: sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==, } - tree-kill@1.2.2: - resolution: - { - integrity: sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==, - } - hasBin: true - trim-lines@3.0.1: resolution: { @@ -7113,11 +8365,35 @@ packages: integrity: sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==, } - ts-morph@12.0.0: + ts-jest@29.4.1: resolution: { - integrity: sha512-VHC8XgU2fFW7yO1f/b3mxKDje1vmyzFXHWzOYmKEkCEwcLjDtbdLgBQviqj4ZwP4MJkQtRo6Ha2I29lq/B+VxA==, + integrity: sha512-SaeUtjfpg9Uqu8IbeDKtdaS0g8lS6FT6OzM3ezrDfErPJPHNDo/Ey+VFGP1bQIDfagYDLyRpd7O15XpG1Es2Uw==, } + engines: { node: ^14.15.0 || ^16.10.0 || ^18.0.0 || >=20.0.0 } + hasBin: true + peerDependencies: + "@babel/core": ">=7.0.0-beta.0 <8" + "@jest/transform": ^29.0.0 || ^30.0.0 + "@jest/types": ^29.0.0 || ^30.0.0 + babel-jest: ^29.0.0 || ^30.0.0 + esbuild: "*" + jest: ^29.0.0 || ^30.0.0 + jest-util: ^29.0.0 || ^30.0.0 + typescript: ">=4.3 <6" + peerDependenciesMeta: + "@babel/core": + optional: true + "@jest/transform": + optional: true + "@jest/types": + optional: true + babel-jest: + optional: true + esbuild: + optional: true + jest-util: + optional: true ts-node@10.9.1: resolution: @@ -7136,12 +8412,6 @@ packages: "@swc/wasm": optional: true - ts-toolbelt@6.15.5: - resolution: - { - integrity: sha512-FZIXf1ksVyLcfr7M317jbB67XFJhOO1YqdTcuGaq9q5jLUoTikukZ+98TPjKiP2jC5CgmYdWWYs0s2nLSU0/1A==, - } - tsconfig-paths@3.15.0: resolution: { @@ -7161,6 +8431,20 @@ packages: } engines: { node: ">= 0.8.0" } + type-detect@4.0.8: + resolution: + { + integrity: sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==, + } + engines: { node: ">=4" } + + type-fest@0.21.3: + resolution: + { + integrity: sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==, + } + engines: { node: ">=10" } + type-fest@0.7.1: resolution: { @@ -7168,6 +8452,13 @@ packages: } engines: { node: ">=8" } + type-fest@4.41.0: + resolution: + { + integrity: sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA==, + } + engines: { node: ">=16" } + typed-array-buffer@1.0.3: resolution: { @@ -7196,14 +8487,6 @@ packages: } engines: { node: ">= 0.4" } - typescript@4.9.5: - resolution: - { - integrity: sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g==, - } - engines: { node: ">=4.2.0" } - hasBin: true - typescript@5.9.2: resolution: { @@ -7244,12 +8527,6 @@ packages: integrity: sha512-v+Z8Jal+GtmKGtJ34GIQlCJAxrDt9kbjpNsNvYoAXFyr4gNfWlD4uJJuoNNu/0UTVaKvQwHaSU095YDl71lKPw==, } - ufo@1.6.1: - resolution: - { - integrity: sha512-9a4/uxlTWJ4+a5i0ooc1rU7C7YOw3wT+UGqdeNNHWnOF9qcMBgLRS+4IYUqbczewFx4mLEig6gawh7X6mFlEkA==, - } - uglify-js@3.19.3: resolution: { @@ -7270,12 +8547,6 @@ packages: integrity: sha512-o0QVGuFg24FK765Qdd5kk0zU/U4dEsCtN/GSiwNI9i8xsSVtjIAOdTaVhLwZ1nrbWxFVMxNDDl+9fednsOMsBw==, } - uid-promise@1.0.0: - resolution: - { - integrity: sha512-R8375j0qwXyIu/7R0tjdF06/sElHqbmdmWC9M2qQHpEVbvE4I5+38KJI7LUUmQMp7NVq4tKHiBMkT0NFM453Ig==, - } - umap@1.0.2: resolution: { @@ -7289,25 +8560,12 @@ packages: } engines: { node: ">= 0.4" } - uncrypto@0.1.3: - resolution: - { - integrity: sha512-Ql87qFHB3s/De2ClA9e0gsnS6zXG27SkTiSJwjCc9MebbfapQfuPzumMIUMi38ezPZVNFcHI9sUIepeQfw8J8Q==, - } - undici-types@7.10.0: resolution: { integrity: sha512-t5Fy/nfn+14LuOc2KNYg75vZqClpAiqscVvMygNnlsHBFpSXdJaYtXMcdNLpl/Qvc3P2cB3s6lOV51nqsFq4ag==, } - undici@5.28.4: - resolution: - { - integrity: sha512-72RFADWFqKmUb2hmmvNODKL3p9hcB6Gt2DOQMis1SEBaV6a4MH8soBvzg+95CYhCKPFedut2JY9bMfrDl9D23g==, - } - engines: { node: ">=14.0" } - unified@11.0.5: resolution: { @@ -7344,26 +8602,11 @@ packages: integrity: sha512-MR04uvD+07cwl/yhVuVWAtw+3GOR/knlL55Nd/wAdblk27GCVt3lqpTivy/tkJcZoNPzTwS1Y+KMojlLDhoTzg==, } - universalify@0.1.2: + unplugin@1.0.1: resolution: { - integrity: sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==, + integrity: sha512-aqrHaVBWW1JVKBHmGo33T5TxeL0qWzfvjWokObHA9bYmN7eNDkwOxmLjhioHl9878qDFMAaT51XNroRyuz7WxA==, } - engines: { node: ">= 4.0.0" } - - universalify@2.0.1: - resolution: - { - integrity: sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==, - } - engines: { node: ">= 10.0.0" } - - unpipe@1.0.0: - resolution: - { - integrity: sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==, - } - engines: { node: ">= 0.8" } unrs-resolver@1.11.1: resolution: @@ -7386,6 +8629,12 @@ packages: integrity: sha512-MJu7ypHq6QasgF5YRTjqscSzQp/W11zoUk6kvmlH+fmWEs63Y0Eib13hYFwAzagRJcVY8WVnlV+eBDUGMJ5IbA==, } + uri-js-replace@1.0.1: + resolution: + { + integrity: sha512-W+C9NWNLFOoBI2QWDp4UT9pv65r2w5Cx+3sTYFvtMdDBxkKt1syCqsUdSFAChbEe1uK5TfS04wt/nGwmaeIQ0g==, + } + uri-js@4.4.1: resolution: { @@ -7430,14 +8679,6 @@ packages: integrity: sha512-Fykw5U4eZESbq739BeLvEBFRuJODfrlmjx5eJux7W817LjRaq4b7/i4t2zxQmhcX+fAj4nMfRdTzO4tmwLKn0w==, } - uuid@3.3.2: - resolution: - { - integrity: sha512-yXJmeNaw3DnnKAOKJE51sL/ZaYfWJRl1pK9dr19YFCu0ObS231AB1/LbqTKRAQ5kw8A90rA6fr4riOUpTZvQZA==, - } - deprecated: Please upgrade to version 7 or higher. Older versions may use Math.random() in certain circumstances, which is known to be problematic. See https://v8.dev/blog/math-random for details. - hasBin: true - uuid@8.3.2: resolution: { @@ -7464,13 +8705,12 @@ packages: integrity: sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==, } - vercel@37.14.0: + v8-to-istanbul@9.3.0: resolution: { - integrity: sha512-ZSEvhARyJBn4YnEVZULsvti8/OHd5txRCgJqEhNIyo/XXSvBJSvlCjA+SE1zraqn0rqyEOG3+56N3kh1Enk8Tg==, + integrity: sha512-kiGUalWN+rgBJ/1OHZsBtU4rXZOfj/7rKQxULKlIzwzQSvMJUUNgPwJEEh7gU6xEVxC0ahoOBvN2YI8GH6FNgA==, } - engines: { node: ">= 16" } - hasBin: true + engines: { node: ">=10.12.0" } vfile-message@4.0.3: resolution: @@ -7484,18 +8724,25 @@ packages: integrity: sha512-KzIbH/9tXat2u30jf+smMwFCsno4wHVdNmzFyL+T/L3UGqqk6JKfVqOFOZEpZSHADH1k40ab6NUIXZq422ov3Q==, } + walker@1.0.8: + resolution: + { + integrity: sha512-ts/8E8l5b7kY0vlWLewOkDXMmPdLcVV4GmOQLyxuSswIJsweeFZtAsMF7k1Nszz+TYBQrlYRmzOnr398y1JemQ==, + } + + watchpack@2.4.4: + resolution: + { + integrity: sha512-c5EGNOiyxxV5qmTtAB7rbiXxi1ooX1pQKMLX/MIabJjRA0SJBQOjKF+KSVfHkr9U1cADPon0mRiVe/riyaiDUA==, + } + engines: { node: ">=10.13.0" } + wavesurfer.js@7.10.1: resolution: { integrity: sha512-tF1ptFCAi8SAqKbM1e7705zouLC3z4ulXCg15kSP5dQ7VDV30Q3x/xFRcuVIYTT5+jB/PdkhiBRCfsMshZG1Ug==, } - web-vitals@0.2.4: - resolution: - { - integrity: sha512-6BjspCO9VriYy12z356nL6JBS0GYeEcA457YyRzD+dD6XYCQ75NKhcOHUMHentOE7OcVCIXXDvOm0jKFfQG2Gg==, - } - webidl-conversions@3.0.1: resolution: { @@ -7509,6 +8756,25 @@ packages: } engines: { node: ">=10.13.0" } + webpack-virtual-modules@0.5.0: + resolution: + { + integrity: sha512-kyDivFZ7ZM0BVOUteVbDFhlRt7Ah/CSPwJdi8hBpkK7QLumUqdLtVfm/PX/hkcnrvr0i77fO5+TjZ94Pe+C9iw==, + } + + webpack@5.101.3: + resolution: + { + integrity: sha512-7b0dTKR3Ed//AD/6kkx/o7duS8H3f1a4w3BYpIriX4BzIhjkn4teo05cptsxvLesHFKK5KObnadmCHBwGc+51A==, + } + engines: { node: ">=10.13.0" } + hasBin: true + peerDependencies: + webpack-cli: "*" + peerDependenciesMeta: + webpack-cli: + optional: true + webrtc-adapter@9.0.3: resolution: { @@ -7558,12 +8824,6 @@ packages: engines: { node: ">= 8" } hasBin: true - wide-align@1.1.5: - resolution: - { - integrity: sha512-eDMORYaPNZ4sQIuuYPDHdQvf4gyCF9rEEV/yPxGfwPkRodwEgiMUUXTx/dex+Me0wxx53S+NgUHaP7y3MGlDmg==, - } - word-wrap@1.2.5: resolution: { @@ -7597,6 +8857,13 @@ packages: integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==, } + write-file-atomic@5.0.1: + resolution: + { + integrity: sha512-+QU2zd6OTD8XWIJCbffaiQeH9U73qIqafo1x6V1snCWYGJf6cVE0cDR4D8xRzcEnfI21IFrUPzPGtcPf8AC+Rw==, + } + engines: { node: ^14.17.0 || ^16.13.0 || >=18.0.0 } + ws@8.17.1: resolution: { @@ -7612,20 +8879,6 @@ packages: utf-8-validate: optional: true - xdg-app-paths@5.1.0: - resolution: - { - integrity: sha512-RAQ3WkPf4KTU1A8RtFx3gWywzVKe00tfOPFfl2NDGqbIFENQO4kqAJp7mhQjNj/33W5x5hiWWUdyfPq/5SU3QA==, - } - engines: { node: ">=6" } - - xdg-portable@7.3.0: - resolution: - { - integrity: sha512-sqMMuL1rc0FmMBOzCpd0yuy9trqF2yTTVe+E9ogwCSWQCdDEtQUwrZPT6AxqtsFGRNxycgncbP/xmOOSPw5ZUw==, - } - engines: { node: ">= 6.0" } - xmlhttprequest-ssl@2.0.0: resolution: { @@ -7633,6 +8886,20 @@ packages: } engines: { node: ">=0.4.0" } + xtend@4.0.2: + resolution: + { + integrity: sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==, + } + engines: { node: ">=0.4" } + + y18n@5.0.8: + resolution: + { + integrity: sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==, + } + engines: { node: ">=10" } + yallist@3.1.1: resolution: { @@ -7645,6 +8912,12 @@ packages: integrity: sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==, } + yaml-ast-parser@0.0.43: + resolution: + { + integrity: sha512-2PTINUwsRqSd+s8XxKaJWQlUuEMHJQyEuh2edBbW8KNJz0SJPwUSD2zRWqezFEdN7IzAgeuYHFUCF7o8zRdZ0A==, + } + yaml@1.10.2: resolution: { @@ -7660,25 +8933,19 @@ packages: engines: { node: ">= 14.6" } hasBin: true - yauzl-clone@1.0.4: + yargs-parser@21.1.1: resolution: { - integrity: sha512-igM2RRCf3k8TvZoxR2oguuw4z1xasOnA31joCqHIyLkeWrvAc2Jgay5ISQ2ZplinkoGaJ6orCz56Ey456c5ESA==, + integrity: sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==, } - engines: { node: ">=6" } + engines: { node: ">=12" } - yauzl-promise@2.1.3: + yargs@17.7.2: resolution: { - integrity: sha512-A1pf6fzh6eYkK0L4Qp7g9jzJSDrM6nN0bOn5T0IbY4Yo3w+YkWlHFkJP7mzknMXjqusHFHlKsK2N+4OLsK2MRA==, - } - engines: { node: ">=6" } - - yauzl@2.10.0: - resolution: - { - integrity: sha512-p4a9I6X6nu6IhoGmBqAcbJy1mlC4j27vEPZX9F4L4/vZT3Lyq1VkFHw/V/PUcB9Buo+DG3iHkT0x3Qya58zc3g==, + integrity: sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==, } + engines: { node: ">=12" } yn@3.1.1: resolution: @@ -7694,6 +8961,12 @@ packages: } engines: { node: ">=10" } + zod@4.1.5: + resolution: + { + integrity: sha512-rcUUZqlLJgBC33IT3PNMgsCq6TzLQEG/Ei/KTCU0PedSWRMAXoOUN+4t/0H+Q8bdnLPdqUYnvboJT0bn/229qg==, + } + zwitch@2.0.4: resolution: { @@ -7703,11 +8976,10 @@ packages: snapshots: "@alloc/quick-lru@5.2.0": {} - "@apidevtools/json-schema-ref-parser@11.6.4": + "@ampproject/remapping@2.3.0": dependencies: - "@jsdevtools/ono": 7.1.3 - "@types/json-schema": 7.0.15 - js-yaml: 4.1.0 + "@jridgewell/gen-mapping": 0.3.13 + "@jridgewell/trace-mapping": 0.3.30 "@ark-ui/react@5.18.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1)": dependencies: @@ -7779,6 +9051,28 @@ snapshots: js-tokens: 4.0.0 picocolors: 1.1.1 + "@babel/compat-data@7.28.0": {} + + "@babel/core@7.28.3": + dependencies: + "@ampproject/remapping": 2.3.0 + "@babel/code-frame": 7.27.1 + "@babel/generator": 7.28.3 + "@babel/helper-compilation-targets": 7.27.2 + "@babel/helper-module-transforms": 7.28.3(@babel/core@7.28.3) + "@babel/helpers": 7.28.3 + "@babel/parser": 7.28.3 + "@babel/template": 7.27.2 + "@babel/traverse": 7.28.3 + "@babel/types": 7.28.2 + convert-source-map: 2.0.0 + debug: 4.4.1(supports-color@9.4.0) + gensync: 1.0.0-beta.2 + json5: 2.2.3 + semver: 6.3.1 + transitivePeerDependencies: + - supports-color + "@babel/generator@7.28.0": dependencies: "@babel/parser": 7.28.0 @@ -7787,6 +9081,22 @@ snapshots: "@jridgewell/trace-mapping": 0.3.30 jsesc: 3.1.0 + "@babel/generator@7.28.3": + dependencies: + "@babel/parser": 7.28.3 + "@babel/types": 7.28.2 + "@jridgewell/gen-mapping": 0.3.13 + "@jridgewell/trace-mapping": 0.3.30 + jsesc: 3.1.0 + + "@babel/helper-compilation-targets@7.27.2": + dependencies: + "@babel/compat-data": 7.28.0 + "@babel/helper-validator-option": 7.27.1 + browserslist: 4.25.2 + lru-cache: 5.1.1 + semver: 6.3.1 + "@babel/helper-globals@7.28.0": {} "@babel/helper-module-imports@7.27.1": @@ -7796,14 +9106,121 @@ snapshots: transitivePeerDependencies: - supports-color + "@babel/helper-module-transforms@7.28.3(@babel/core@7.28.3)": + dependencies: + "@babel/core": 7.28.3 + "@babel/helper-module-imports": 7.27.1 + "@babel/helper-validator-identifier": 7.27.1 + "@babel/traverse": 7.28.3 + transitivePeerDependencies: + - supports-color + + "@babel/helper-plugin-utils@7.27.1": {} + "@babel/helper-string-parser@7.27.1": {} "@babel/helper-validator-identifier@7.27.1": {} + "@babel/helper-validator-option@7.27.1": {} + + "@babel/helpers@7.28.3": + dependencies: + "@babel/template": 7.27.2 + "@babel/types": 7.28.2 + "@babel/parser@7.28.0": dependencies: "@babel/types": 7.28.2 + "@babel/parser@7.28.3": + dependencies: + "@babel/types": 7.28.2 + + "@babel/plugin-syntax-async-generators@7.8.4(@babel/core@7.28.3)": + dependencies: + "@babel/core": 7.28.3 + "@babel/helper-plugin-utils": 7.27.1 + + "@babel/plugin-syntax-bigint@7.8.3(@babel/core@7.28.3)": + dependencies: + "@babel/core": 7.28.3 + "@babel/helper-plugin-utils": 7.27.1 + + "@babel/plugin-syntax-class-properties@7.12.13(@babel/core@7.28.3)": + dependencies: + "@babel/core": 7.28.3 + "@babel/helper-plugin-utils": 7.27.1 + + "@babel/plugin-syntax-class-static-block@7.14.5(@babel/core@7.28.3)": + dependencies: + "@babel/core": 7.28.3 + "@babel/helper-plugin-utils": 7.27.1 + + "@babel/plugin-syntax-import-attributes@7.27.1(@babel/core@7.28.3)": + dependencies: + "@babel/core": 7.28.3 + "@babel/helper-plugin-utils": 7.27.1 + + "@babel/plugin-syntax-import-meta@7.10.4(@babel/core@7.28.3)": + dependencies: + "@babel/core": 7.28.3 + "@babel/helper-plugin-utils": 7.27.1 + + "@babel/plugin-syntax-json-strings@7.8.3(@babel/core@7.28.3)": + dependencies: + "@babel/core": 7.28.3 + "@babel/helper-plugin-utils": 7.27.1 + + "@babel/plugin-syntax-jsx@7.27.1(@babel/core@7.28.3)": + dependencies: + "@babel/core": 7.28.3 + "@babel/helper-plugin-utils": 7.27.1 + + "@babel/plugin-syntax-logical-assignment-operators@7.10.4(@babel/core@7.28.3)": + dependencies: + "@babel/core": 7.28.3 + "@babel/helper-plugin-utils": 7.27.1 + + "@babel/plugin-syntax-nullish-coalescing-operator@7.8.3(@babel/core@7.28.3)": + dependencies: + "@babel/core": 7.28.3 + "@babel/helper-plugin-utils": 7.27.1 + + "@babel/plugin-syntax-numeric-separator@7.10.4(@babel/core@7.28.3)": + dependencies: + "@babel/core": 7.28.3 + "@babel/helper-plugin-utils": 7.27.1 + + "@babel/plugin-syntax-object-rest-spread@7.8.3(@babel/core@7.28.3)": + dependencies: + "@babel/core": 7.28.3 + "@babel/helper-plugin-utils": 7.27.1 + + "@babel/plugin-syntax-optional-catch-binding@7.8.3(@babel/core@7.28.3)": + dependencies: + "@babel/core": 7.28.3 + "@babel/helper-plugin-utils": 7.27.1 + + "@babel/plugin-syntax-optional-chaining@7.8.3(@babel/core@7.28.3)": + dependencies: + "@babel/core": 7.28.3 + "@babel/helper-plugin-utils": 7.27.1 + + "@babel/plugin-syntax-private-property-in-object@7.14.5(@babel/core@7.28.3)": + dependencies: + "@babel/core": 7.28.3 + "@babel/helper-plugin-utils": 7.27.1 + + "@babel/plugin-syntax-top-level-await@7.14.5(@babel/core@7.28.3)": + dependencies: + "@babel/core": 7.28.3 + "@babel/helper-plugin-utils": 7.27.1 + + "@babel/plugin-syntax-typescript@7.27.1(@babel/core@7.28.3)": + dependencies: + "@babel/core": 7.28.3 + "@babel/helper-plugin-utils": 7.27.1 + "@babel/runtime@7.28.2": {} "@babel/template@7.27.2": @@ -7824,11 +9241,25 @@ snapshots: transitivePeerDependencies: - supports-color + "@babel/traverse@7.28.3": + dependencies: + "@babel/code-frame": 7.27.1 + "@babel/generator": 7.28.3 + "@babel/helper-globals": 7.28.0 + "@babel/parser": 7.28.3 + "@babel/template": 7.27.2 + "@babel/types": 7.28.2 + debug: 4.4.1(supports-color@9.4.0) + transitivePeerDependencies: + - supports-color + "@babel/types@7.28.2": dependencies: "@babel/helper-string-parser": 7.27.1 "@babel/helper-validator-identifier": 7.27.1 + "@bcoe/v8-coverage@0.2.3": {} + "@chakra-ui/react@3.24.2(@emotion/react@11.14.0(@types/react@18.2.20)(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)": dependencies: "@ark-ui/react": 5.18.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1) @@ -7846,18 +9277,7 @@ snapshots: "@cspotcode/source-map-support@0.8.1": dependencies: "@jridgewell/trace-mapping": 0.3.9 - - "@edge-runtime/format@2.2.1": {} - - "@edge-runtime/node-utils@2.3.0": {} - - "@edge-runtime/ponyfill@2.4.2": {} - - "@edge-runtime/primitives@4.1.0": {} - - "@edge-runtime/vm@3.2.0": - dependencies: - "@edge-runtime/primitives": 4.1.0 + optional: true "@emnapi/core@1.4.5": dependencies: @@ -7987,8 +9407,6 @@ snapshots: "@eslint/core": 0.15.2 levn: 0.4.1 - "@fastify/busboy@2.1.1": {} - "@floating-ui/core@1.7.3": dependencies: "@floating-ui/utils": 0.2.10 @@ -8027,17 +9445,6 @@ snapshots: prop-types: 15.8.1 react: 18.3.1 - "@hey-api/openapi-ts@0.48.3(typescript@5.9.2)": - dependencies: - "@apidevtools/json-schema-ref-parser": 11.6.4 - c12: 1.11.1 - camelcase: 8.0.0 - commander: 12.1.0 - handlebars: 4.7.8 - typescript: 5.9.2 - transitivePeerDependencies: - - magicast - "@humanfs/core@0.19.1": {} "@humanfs/node@0.16.6": @@ -8051,6 +9458,92 @@ snapshots: "@humanwhocodes/retry@0.4.3": {} + "@img/sharp-darwin-arm64@0.34.3": + optionalDependencies: + "@img/sharp-libvips-darwin-arm64": 1.2.0 + optional: true + + "@img/sharp-darwin-x64@0.34.3": + optionalDependencies: + "@img/sharp-libvips-darwin-x64": 1.2.0 + optional: true + + "@img/sharp-libvips-darwin-arm64@1.2.0": + optional: true + + "@img/sharp-libvips-darwin-x64@1.2.0": + optional: true + + "@img/sharp-libvips-linux-arm64@1.2.0": + optional: true + + "@img/sharp-libvips-linux-arm@1.2.0": + optional: true + + "@img/sharp-libvips-linux-ppc64@1.2.0": + optional: true + + "@img/sharp-libvips-linux-s390x@1.2.0": + optional: true + + "@img/sharp-libvips-linux-x64@1.2.0": + optional: true + + "@img/sharp-libvips-linuxmusl-arm64@1.2.0": + optional: true + + "@img/sharp-libvips-linuxmusl-x64@1.2.0": + optional: true + + "@img/sharp-linux-arm64@0.34.3": + optionalDependencies: + "@img/sharp-libvips-linux-arm64": 1.2.0 + optional: true + + "@img/sharp-linux-arm@0.34.3": + optionalDependencies: + "@img/sharp-libvips-linux-arm": 1.2.0 + optional: true + + "@img/sharp-linux-ppc64@0.34.3": + optionalDependencies: + "@img/sharp-libvips-linux-ppc64": 1.2.0 + optional: true + + "@img/sharp-linux-s390x@0.34.3": + optionalDependencies: + "@img/sharp-libvips-linux-s390x": 1.2.0 + optional: true + + "@img/sharp-linux-x64@0.34.3": + optionalDependencies: + "@img/sharp-libvips-linux-x64": 1.2.0 + optional: true + + "@img/sharp-linuxmusl-arm64@0.34.3": + optionalDependencies: + "@img/sharp-libvips-linuxmusl-arm64": 1.2.0 + optional: true + + "@img/sharp-linuxmusl-x64@0.34.3": + optionalDependencies: + "@img/sharp-libvips-linuxmusl-x64": 1.2.0 + optional: true + + "@img/sharp-wasm32@0.34.3": + dependencies: + "@emnapi/runtime": 1.4.5 + optional: true + + "@img/sharp-win32-arm64@0.34.3": + optional: true + + "@img/sharp-win32-ia32@0.34.3": + optional: true + + "@img/sharp-win32-x64@0.34.3": + optional: true + "@internationalized/date@3.8.2": dependencies: "@swc/helpers": 0.5.17 @@ -8059,7 +9552,7 @@ snapshots: dependencies: "@swc/helpers": 0.5.17 - "@ioredis/commands@1.3.0": {} + "@ioredis/commands@1.3.1": {} "@isaacs/cliui@8.0.2": dependencies: @@ -8070,10 +9563,189 @@ snapshots: wrap-ansi: 8.1.0 wrap-ansi-cjs: wrap-ansi@7.0.0 + "@istanbuljs/load-nyc-config@1.1.0": + dependencies: + camelcase: 5.3.1 + find-up: 4.1.0 + get-package-type: 0.1.0 + js-yaml: 3.14.1 + resolve-from: 5.0.0 + + "@istanbuljs/schema@0.1.3": {} + + "@jest/console@30.1.2": + dependencies: + "@jest/types": 30.0.5 + "@types/node": 24.2.1 + chalk: 4.1.2 + jest-message-util: 30.1.0 + jest-util: 30.0.5 + slash: 3.0.0 + + "@jest/core@30.1.3(babel-plugin-macros@3.1.0)(ts-node@10.9.1(@types/node@24.2.1)(typescript@5.9.2))": + dependencies: + "@jest/console": 30.1.2 + "@jest/pattern": 30.0.1 + "@jest/reporters": 30.1.3 + "@jest/test-result": 30.1.3 + "@jest/transform": 30.1.2 + "@jest/types": 30.0.5 + "@types/node": 24.2.1 + ansi-escapes: 4.3.2 + chalk: 4.1.2 + ci-info: 4.3.0 + exit-x: 0.2.2 + graceful-fs: 4.2.11 + jest-changed-files: 30.0.5 + jest-config: 30.1.3(@types/node@24.2.1)(babel-plugin-macros@3.1.0)(ts-node@10.9.1(@types/node@24.2.1)(typescript@5.9.2)) + jest-haste-map: 30.1.0 + jest-message-util: 30.1.0 + jest-regex-util: 30.0.1 + jest-resolve: 30.1.3 + jest-resolve-dependencies: 30.1.3 + jest-runner: 30.1.3 + jest-runtime: 30.1.3 + jest-snapshot: 30.1.2 + jest-util: 30.0.5 + jest-validate: 30.1.0 + jest-watcher: 30.1.3 + micromatch: 4.0.8 + pretty-format: 30.0.5 + slash: 3.0.0 + transitivePeerDependencies: + - babel-plugin-macros + - esbuild-register + - supports-color + - ts-node + + "@jest/diff-sequences@30.0.1": {} + + "@jest/environment@30.1.2": + dependencies: + "@jest/fake-timers": 30.1.2 + "@jest/types": 30.0.5 + "@types/node": 24.2.1 + jest-mock: 30.0.5 + + "@jest/expect-utils@30.1.2": + dependencies: + "@jest/get-type": 30.1.0 + + "@jest/expect@30.1.2": + dependencies: + expect: 30.1.2 + jest-snapshot: 30.1.2 + transitivePeerDependencies: + - supports-color + + "@jest/fake-timers@30.1.2": + dependencies: + "@jest/types": 30.0.5 + "@sinonjs/fake-timers": 13.0.5 + "@types/node": 24.2.1 + jest-message-util: 30.1.0 + jest-mock: 30.0.5 + jest-util: 30.0.5 + + "@jest/get-type@30.1.0": {} + + "@jest/globals@30.1.2": + dependencies: + "@jest/environment": 30.1.2 + "@jest/expect": 30.1.2 + "@jest/types": 30.0.5 + jest-mock: 30.0.5 + transitivePeerDependencies: + - supports-color + + "@jest/pattern@30.0.1": + dependencies: + "@types/node": 24.2.1 + jest-regex-util: 30.0.1 + + "@jest/reporters@30.1.3": + dependencies: + "@bcoe/v8-coverage": 0.2.3 + "@jest/console": 30.1.2 + "@jest/test-result": 30.1.3 + "@jest/transform": 30.1.2 + "@jest/types": 30.0.5 + "@jridgewell/trace-mapping": 0.3.30 + "@types/node": 24.2.1 + chalk: 4.1.2 + collect-v8-coverage: 1.0.2 + exit-x: 0.2.2 + glob: 10.4.5 + graceful-fs: 4.2.11 + istanbul-lib-coverage: 3.2.2 + istanbul-lib-instrument: 6.0.3 + istanbul-lib-report: 3.0.1 + istanbul-lib-source-maps: 5.0.6 + istanbul-reports: 3.2.0 + jest-message-util: 30.1.0 + jest-util: 30.0.5 + jest-worker: 30.1.0 + slash: 3.0.0 + string-length: 4.0.2 + v8-to-istanbul: 9.3.0 + transitivePeerDependencies: + - supports-color + "@jest/schemas@29.6.3": dependencies: "@sinclair/typebox": 0.27.8 + "@jest/schemas@30.0.5": + dependencies: + "@sinclair/typebox": 0.34.41 + + "@jest/snapshot-utils@30.1.2": + dependencies: + "@jest/types": 30.0.5 + chalk: 4.1.2 + graceful-fs: 4.2.11 + natural-compare: 1.4.0 + + "@jest/source-map@30.0.1": + dependencies: + "@jridgewell/trace-mapping": 0.3.30 + callsites: 3.1.0 + graceful-fs: 4.2.11 + + "@jest/test-result@30.1.3": + dependencies: + "@jest/console": 30.1.2 + "@jest/types": 30.0.5 + "@types/istanbul-lib-coverage": 2.0.6 + collect-v8-coverage: 1.0.2 + + "@jest/test-sequencer@30.1.3": + dependencies: + "@jest/test-result": 30.1.3 + graceful-fs: 4.2.11 + jest-haste-map: 30.1.0 + slash: 3.0.0 + + "@jest/transform@30.1.2": + dependencies: + "@babel/core": 7.28.3 + "@jest/types": 30.0.5 + "@jridgewell/trace-mapping": 0.3.30 + babel-plugin-istanbul: 7.0.0 + chalk: 4.1.2 + convert-source-map: 2.0.0 + fast-json-stable-stringify: 2.1.0 + graceful-fs: 4.2.11 + jest-haste-map: 30.1.0 + jest-regex-util: 30.0.1 + jest-util: 30.0.5 + micromatch: 4.0.8 + pirates: 4.0.7 + slash: 3.0.0 + write-file-atomic: 5.0.1 + transitivePeerDependencies: + - supports-color + "@jest/types@29.6.3": dependencies: "@jest/schemas": 29.6.3 @@ -8083,6 +9755,16 @@ snapshots: "@types/yargs": 17.0.33 chalk: 4.1.2 + "@jest/types@30.0.5": + dependencies: + "@jest/pattern": 30.0.1 + "@jest/schemas": 30.0.5 + "@types/istanbul-lib-coverage": 2.0.6 + "@types/istanbul-reports": 3.0.4 + "@types/node": 24.2.1 + "@types/yargs": 17.0.33 + chalk: 4.1.2 + "@jridgewell/gen-mapping@0.3.13": dependencies: "@jridgewell/sourcemap-codec": 1.5.5 @@ -8090,6 +9772,11 @@ snapshots: "@jridgewell/resolve-uri@3.1.2": {} + "@jridgewell/source-map@0.3.11": + dependencies: + "@jridgewell/gen-mapping": 0.3.13 + "@jridgewell/trace-mapping": 0.3.31 + "@jridgewell/sourcemap-codec@1.5.5": {} "@jridgewell/trace-mapping@0.3.30": @@ -8097,27 +9784,16 @@ snapshots: "@jridgewell/resolve-uri": 3.1.2 "@jridgewell/sourcemap-codec": 1.5.5 + "@jridgewell/trace-mapping@0.3.31": + dependencies: + "@jridgewell/resolve-uri": 3.1.2 + "@jridgewell/sourcemap-codec": 1.5.5 + "@jridgewell/trace-mapping@0.3.9": dependencies: "@jridgewell/resolve-uri": 3.1.2 "@jridgewell/sourcemap-codec": 1.5.5 - - "@jsdevtools/ono@7.1.3": {} - - "@mapbox/node-pre-gyp@1.0.11": - dependencies: - detect-libc: 2.0.4 - https-proxy-agent: 5.0.1 - make-dir: 3.1.0 - node-fetch: 2.7.0 - nopt: 5.0.0 - npmlog: 5.0.1 - rimraf: 3.0.2 - semver: 7.7.2 - tar: 6.2.1 - transitivePeerDependencies: - - encoding - - supports-color + optional: true "@napi-rs/wasm-runtime@0.2.12": dependencies: @@ -8126,37 +9802,34 @@ snapshots: "@tybys/wasm-util": 0.10.0 optional: true - "@next/env@14.2.31": {} + "@next/env@15.5.3": {} - "@next/eslint-plugin-next@14.2.31": + "@next/eslint-plugin-next@15.5.3": dependencies: - glob: 10.3.10 + fast-glob: 3.3.1 - "@next/swc-darwin-arm64@14.2.31": + "@next/swc-darwin-arm64@15.5.3": optional: true - "@next/swc-darwin-x64@14.2.31": + "@next/swc-darwin-x64@15.5.3": optional: true - "@next/swc-linux-arm64-gnu@14.2.31": + "@next/swc-linux-arm64-gnu@15.5.3": optional: true - "@next/swc-linux-arm64-musl@14.2.31": + "@next/swc-linux-arm64-musl@15.5.3": optional: true - "@next/swc-linux-x64-gnu@14.2.31": + "@next/swc-linux-x64-gnu@15.5.3": optional: true - "@next/swc-linux-x64-musl@14.2.31": + "@next/swc-linux-x64-musl@15.5.3": optional: true - "@next/swc-win32-arm64-msvc@14.2.31": + "@next/swc-win32-arm64-msvc@15.5.3": optional: true - "@next/swc-win32-ia32-msvc@14.2.31": - optional: true - - "@next/swc-win32-x64-msvc@14.2.31": + "@next/swc-win32-x64-msvc@15.5.3": optional: true "@nodelib/fs.scandir@2.1.5": @@ -8173,6 +9846,276 @@ snapshots: "@nolyfill/is-core-module@1.0.39": {} + "@opentelemetry/api-logs@0.203.0": + dependencies: + "@opentelemetry/api": 1.9.0 + + "@opentelemetry/api-logs@0.204.0": + dependencies: + "@opentelemetry/api": 1.9.0 + + "@opentelemetry/api-logs@0.57.2": + dependencies: + "@opentelemetry/api": 1.9.0 + + "@opentelemetry/api@1.9.0": {} + + "@opentelemetry/context-async-hooks@2.1.0(@opentelemetry/api@1.9.0)": + dependencies: + "@opentelemetry/api": 1.9.0 + + "@opentelemetry/core@2.0.1(@opentelemetry/api@1.9.0)": + dependencies: + "@opentelemetry/api": 1.9.0 + "@opentelemetry/semantic-conventions": 1.37.0 + + "@opentelemetry/core@2.1.0(@opentelemetry/api@1.9.0)": + dependencies: + "@opentelemetry/api": 1.9.0 + "@opentelemetry/semantic-conventions": 1.37.0 + + "@opentelemetry/instrumentation-amqplib@0.50.0(@opentelemetry/api@1.9.0)": + dependencies: + "@opentelemetry/api": 1.9.0 + "@opentelemetry/core": 2.1.0(@opentelemetry/api@1.9.0) + "@opentelemetry/instrumentation": 0.203.0(@opentelemetry/api@1.9.0) + "@opentelemetry/semantic-conventions": 1.37.0 + transitivePeerDependencies: + - supports-color + + "@opentelemetry/instrumentation-connect@0.47.0(@opentelemetry/api@1.9.0)": + dependencies: + "@opentelemetry/api": 1.9.0 + "@opentelemetry/core": 2.1.0(@opentelemetry/api@1.9.0) + "@opentelemetry/instrumentation": 0.203.0(@opentelemetry/api@1.9.0) + "@opentelemetry/semantic-conventions": 1.37.0 + "@types/connect": 3.4.38 + transitivePeerDependencies: + - supports-color + + "@opentelemetry/instrumentation-dataloader@0.21.1(@opentelemetry/api@1.9.0)": + dependencies: + "@opentelemetry/api": 1.9.0 + "@opentelemetry/instrumentation": 0.203.0(@opentelemetry/api@1.9.0) + transitivePeerDependencies: + - supports-color + + "@opentelemetry/instrumentation-express@0.52.0(@opentelemetry/api@1.9.0)": + dependencies: + "@opentelemetry/api": 1.9.0 + "@opentelemetry/core": 2.1.0(@opentelemetry/api@1.9.0) + "@opentelemetry/instrumentation": 0.203.0(@opentelemetry/api@1.9.0) + "@opentelemetry/semantic-conventions": 1.37.0 + transitivePeerDependencies: + - supports-color + + "@opentelemetry/instrumentation-fs@0.23.0(@opentelemetry/api@1.9.0)": + dependencies: + "@opentelemetry/api": 1.9.0 + "@opentelemetry/core": 2.1.0(@opentelemetry/api@1.9.0) + "@opentelemetry/instrumentation": 0.203.0(@opentelemetry/api@1.9.0) + transitivePeerDependencies: + - supports-color + + "@opentelemetry/instrumentation-generic-pool@0.47.0(@opentelemetry/api@1.9.0)": + dependencies: + "@opentelemetry/api": 1.9.0 + "@opentelemetry/instrumentation": 0.203.0(@opentelemetry/api@1.9.0) + transitivePeerDependencies: + - supports-color + + "@opentelemetry/instrumentation-graphql@0.51.0(@opentelemetry/api@1.9.0)": + dependencies: + "@opentelemetry/api": 1.9.0 + "@opentelemetry/instrumentation": 0.203.0(@opentelemetry/api@1.9.0) + transitivePeerDependencies: + - supports-color + + "@opentelemetry/instrumentation-hapi@0.50.0(@opentelemetry/api@1.9.0)": + dependencies: + "@opentelemetry/api": 1.9.0 + "@opentelemetry/core": 2.1.0(@opentelemetry/api@1.9.0) + "@opentelemetry/instrumentation": 0.203.0(@opentelemetry/api@1.9.0) + "@opentelemetry/semantic-conventions": 1.37.0 + transitivePeerDependencies: + - supports-color + + "@opentelemetry/instrumentation-http@0.203.0(@opentelemetry/api@1.9.0)": + dependencies: + "@opentelemetry/api": 1.9.0 + "@opentelemetry/core": 2.0.1(@opentelemetry/api@1.9.0) + "@opentelemetry/instrumentation": 0.203.0(@opentelemetry/api@1.9.0) + "@opentelemetry/semantic-conventions": 1.37.0 + forwarded-parse: 2.1.2 + transitivePeerDependencies: + - supports-color + + "@opentelemetry/instrumentation-ioredis@0.52.0(@opentelemetry/api@1.9.0)": + dependencies: + "@opentelemetry/api": 1.9.0 + "@opentelemetry/instrumentation": 0.204.0(@opentelemetry/api@1.9.0) + "@opentelemetry/redis-common": 0.38.0 + "@opentelemetry/semantic-conventions": 1.37.0 + transitivePeerDependencies: + - supports-color + + "@opentelemetry/instrumentation-kafkajs@0.13.0(@opentelemetry/api@1.9.0)": + dependencies: + "@opentelemetry/api": 1.9.0 + "@opentelemetry/instrumentation": 0.203.0(@opentelemetry/api@1.9.0) + "@opentelemetry/semantic-conventions": 1.37.0 + transitivePeerDependencies: + - supports-color + + "@opentelemetry/instrumentation-knex@0.48.0(@opentelemetry/api@1.9.0)": + dependencies: + "@opentelemetry/api": 1.9.0 + "@opentelemetry/instrumentation": 0.203.0(@opentelemetry/api@1.9.0) + "@opentelemetry/semantic-conventions": 1.37.0 + transitivePeerDependencies: + - supports-color + + "@opentelemetry/instrumentation-koa@0.51.0(@opentelemetry/api@1.9.0)": + dependencies: + "@opentelemetry/api": 1.9.0 + "@opentelemetry/core": 2.1.0(@opentelemetry/api@1.9.0) + "@opentelemetry/instrumentation": 0.203.0(@opentelemetry/api@1.9.0) + "@opentelemetry/semantic-conventions": 1.37.0 + transitivePeerDependencies: + - supports-color + + "@opentelemetry/instrumentation-lru-memoizer@0.48.0(@opentelemetry/api@1.9.0)": + dependencies: + "@opentelemetry/api": 1.9.0 + "@opentelemetry/instrumentation": 0.203.0(@opentelemetry/api@1.9.0) + transitivePeerDependencies: + - supports-color + + "@opentelemetry/instrumentation-mongodb@0.56.0(@opentelemetry/api@1.9.0)": + dependencies: + "@opentelemetry/api": 1.9.0 + "@opentelemetry/instrumentation": 0.203.0(@opentelemetry/api@1.9.0) + "@opentelemetry/semantic-conventions": 1.37.0 + transitivePeerDependencies: + - supports-color + + "@opentelemetry/instrumentation-mongoose@0.50.0(@opentelemetry/api@1.9.0)": + dependencies: + "@opentelemetry/api": 1.9.0 + "@opentelemetry/core": 2.1.0(@opentelemetry/api@1.9.0) + "@opentelemetry/instrumentation": 0.203.0(@opentelemetry/api@1.9.0) + "@opentelemetry/semantic-conventions": 1.37.0 + transitivePeerDependencies: + - supports-color + + "@opentelemetry/instrumentation-mysql2@0.50.0(@opentelemetry/api@1.9.0)": + dependencies: + "@opentelemetry/api": 1.9.0 + "@opentelemetry/instrumentation": 0.203.0(@opentelemetry/api@1.9.0) + "@opentelemetry/semantic-conventions": 1.37.0 + "@opentelemetry/sql-common": 0.41.0(@opentelemetry/api@1.9.0) + transitivePeerDependencies: + - supports-color + + "@opentelemetry/instrumentation-mysql@0.49.0(@opentelemetry/api@1.9.0)": + dependencies: + "@opentelemetry/api": 1.9.0 + "@opentelemetry/instrumentation": 0.203.0(@opentelemetry/api@1.9.0) + "@opentelemetry/semantic-conventions": 1.37.0 + "@types/mysql": 2.15.27 + transitivePeerDependencies: + - supports-color + + "@opentelemetry/instrumentation-pg@0.55.0(@opentelemetry/api@1.9.0)": + dependencies: + "@opentelemetry/api": 1.9.0 + "@opentelemetry/core": 2.1.0(@opentelemetry/api@1.9.0) + "@opentelemetry/instrumentation": 0.203.0(@opentelemetry/api@1.9.0) + "@opentelemetry/semantic-conventions": 1.37.0 + "@opentelemetry/sql-common": 0.41.0(@opentelemetry/api@1.9.0) + "@types/pg": 8.15.4 + "@types/pg-pool": 2.0.6 + transitivePeerDependencies: + - supports-color + + "@opentelemetry/instrumentation-redis@0.51.0(@opentelemetry/api@1.9.0)": + dependencies: + "@opentelemetry/api": 1.9.0 + "@opentelemetry/instrumentation": 0.203.0(@opentelemetry/api@1.9.0) + "@opentelemetry/redis-common": 0.38.0 + "@opentelemetry/semantic-conventions": 1.37.0 + transitivePeerDependencies: + - supports-color + + "@opentelemetry/instrumentation-tedious@0.22.0(@opentelemetry/api@1.9.0)": + dependencies: + "@opentelemetry/api": 1.9.0 + "@opentelemetry/instrumentation": 0.203.0(@opentelemetry/api@1.9.0) + "@opentelemetry/semantic-conventions": 1.37.0 + "@types/tedious": 4.0.14 + transitivePeerDependencies: + - supports-color + + "@opentelemetry/instrumentation-undici@0.14.0(@opentelemetry/api@1.9.0)": + dependencies: + "@opentelemetry/api": 1.9.0 + "@opentelemetry/core": 2.1.0(@opentelemetry/api@1.9.0) + "@opentelemetry/instrumentation": 0.203.0(@opentelemetry/api@1.9.0) + transitivePeerDependencies: + - supports-color + + "@opentelemetry/instrumentation@0.203.0(@opentelemetry/api@1.9.0)": + dependencies: + "@opentelemetry/api": 1.9.0 + "@opentelemetry/api-logs": 0.203.0 + import-in-the-middle: 1.14.2 + require-in-the-middle: 7.5.2 + transitivePeerDependencies: + - supports-color + + "@opentelemetry/instrumentation@0.204.0(@opentelemetry/api@1.9.0)": + dependencies: + "@opentelemetry/api": 1.9.0 + "@opentelemetry/api-logs": 0.204.0 + import-in-the-middle: 1.14.2 + require-in-the-middle: 7.5.2 + transitivePeerDependencies: + - supports-color + + "@opentelemetry/instrumentation@0.57.2(@opentelemetry/api@1.9.0)": + dependencies: + "@opentelemetry/api": 1.9.0 + "@opentelemetry/api-logs": 0.57.2 + "@types/shimmer": 1.2.0 + import-in-the-middle: 1.14.2 + require-in-the-middle: 7.5.2 + semver: 7.7.2 + shimmer: 1.2.1 + transitivePeerDependencies: + - supports-color + + "@opentelemetry/redis-common@0.38.0": {} + + "@opentelemetry/resources@2.1.0(@opentelemetry/api@1.9.0)": + dependencies: + "@opentelemetry/api": 1.9.0 + "@opentelemetry/core": 2.1.0(@opentelemetry/api@1.9.0) + "@opentelemetry/semantic-conventions": 1.37.0 + + "@opentelemetry/sdk-trace-base@2.1.0(@opentelemetry/api@1.9.0)": + dependencies: + "@opentelemetry/api": 1.9.0 + "@opentelemetry/core": 2.1.0(@opentelemetry/api@1.9.0) + "@opentelemetry/resources": 2.1.0(@opentelemetry/api@1.9.0) + "@opentelemetry/semantic-conventions": 1.37.0 + + "@opentelemetry/semantic-conventions@1.37.0": {} + + "@opentelemetry/sql-common@0.41.0(@opentelemetry/api@1.9.0)": + dependencies: + "@opentelemetry/api": 1.9.0 + "@opentelemetry/core": 2.1.0(@opentelemetry/api@1.9.0) + "@pandacss/is-valid-prop@0.54.0": {} "@panva/hkdf@1.2.1": {} @@ -8241,6 +10184,15 @@ snapshots: "@pkgjs/parseargs@0.11.0": optional: true + "@pkgr/core@0.2.9": {} + + "@prisma/instrumentation@6.14.0(@opentelemetry/api@1.9.0)": + dependencies: + "@opentelemetry/api": 1.9.0 + "@opentelemetry/instrumentation": 0.57.2(@opentelemetry/api@1.9.0) + transitivePeerDependencies: + - supports-color + "@radix-ui/primitive@1.1.3": {} "@radix-ui/react-arrow@1.1.7(@types/react@18.2.20)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)": @@ -8420,6 +10372,29 @@ snapshots: "@radix-ui/rect@1.1.1": {} + "@redocly/ajv@8.11.3": + dependencies: + fast-deep-equal: 3.1.3 + json-schema-traverse: 1.0.0 + require-from-string: 2.0.2 + uri-js-replace: 1.0.1 + + "@redocly/config@0.22.2": {} + + "@redocly/openapi-core@1.34.5(supports-color@10.2.0)": + dependencies: + "@redocly/ajv": 8.11.3 + "@redocly/config": 0.22.2 + colorette: 1.4.0 + https-proxy-agent: 7.0.6(supports-color@10.2.0) + js-levenshtein: 1.1.6 + js-yaml: 4.1.0 + minimatch: 5.1.6 + pluralize: 8.0.0 + yaml-ast-parser: 0.0.43 + transitivePeerDependencies: + - supports-color + "@reduxjs/toolkit@2.8.2(react@18.3.1)": dependencies: "@standard-schema/spec": 1.0.0 @@ -8431,202 +10406,382 @@ snapshots: optionalDependencies: react: 18.3.1 - "@rollup/plugin-commonjs@24.0.0(rollup@2.79.2)": + "@rollup/plugin-commonjs@28.0.1(rollup@4.50.1)": dependencies: - "@rollup/pluginutils": 5.2.0(rollup@2.79.2) + "@rollup/pluginutils": 5.2.0(rollup@4.50.1) commondir: 1.0.1 estree-walker: 2.0.2 - glob: 8.1.0 + fdir: 6.4.6(picomatch@4.0.3) is-reference: 1.2.1 - magic-string: 0.27.0 + magic-string: 0.30.19 + picomatch: 4.0.3 optionalDependencies: - rollup: 2.79.2 + rollup: 4.50.1 - "@rollup/pluginutils@4.2.1": - dependencies: - estree-walker: 2.0.2 - picomatch: 2.3.1 - - "@rollup/pluginutils@5.2.0(rollup@2.79.2)": + "@rollup/pluginutils@5.2.0(rollup@4.50.1)": dependencies: "@types/estree": 1.0.8 estree-walker: 2.0.2 picomatch: 4.0.3 optionalDependencies: - rollup: 2.79.2 + rollup: 4.50.1 + + "@rollup/rollup-android-arm-eabi@4.50.1": + optional: true + + "@rollup/rollup-android-arm64@4.50.1": + optional: true + + "@rollup/rollup-darwin-arm64@4.50.1": + optional: true + + "@rollup/rollup-darwin-x64@4.50.1": + optional: true + + "@rollup/rollup-freebsd-arm64@4.50.1": + optional: true + + "@rollup/rollup-freebsd-x64@4.50.1": + optional: true + + "@rollup/rollup-linux-arm-gnueabihf@4.50.1": + optional: true + + "@rollup/rollup-linux-arm-musleabihf@4.50.1": + optional: true + + "@rollup/rollup-linux-arm64-gnu@4.50.1": + optional: true + + "@rollup/rollup-linux-arm64-musl@4.50.1": + optional: true + + "@rollup/rollup-linux-loongarch64-gnu@4.50.1": + optional: true + + "@rollup/rollup-linux-ppc64-gnu@4.50.1": + optional: true + + "@rollup/rollup-linux-riscv64-gnu@4.50.1": + optional: true + + "@rollup/rollup-linux-riscv64-musl@4.50.1": + optional: true + + "@rollup/rollup-linux-s390x-gnu@4.50.1": + optional: true + + "@rollup/rollup-linux-x64-gnu@4.50.1": + optional: true + + "@rollup/rollup-linux-x64-musl@4.50.1": + optional: true + + "@rollup/rollup-openharmony-arm64@4.50.1": + optional: true + + "@rollup/rollup-win32-arm64-msvc@4.50.1": + optional: true + + "@rollup/rollup-win32-ia32-msvc@4.50.1": + optional: true + + "@rollup/rollup-win32-x64-msvc@4.50.1": + optional: true "@rtsao/scc@1.1.0": {} "@rushstack/eslint-patch@1.12.0": {} - "@sentry-internal/feedback@7.120.4": + "@sentry-internal/browser-utils@10.11.0": dependencies: - "@sentry/core": 7.120.4 - "@sentry/types": 7.120.4 - "@sentry/utils": 7.120.4 + "@sentry/core": 10.11.0 - "@sentry-internal/replay-canvas@7.120.4": + "@sentry-internal/feedback@10.11.0": dependencies: - "@sentry/core": 7.120.4 - "@sentry/replay": 7.120.4 - "@sentry/types": 7.120.4 - "@sentry/utils": 7.120.4 + "@sentry/core": 10.11.0 - "@sentry-internal/tracing@7.120.4": + "@sentry-internal/replay-canvas@10.11.0": dependencies: - "@sentry/core": 7.120.4 - "@sentry/types": 7.120.4 - "@sentry/utils": 7.120.4 + "@sentry-internal/replay": 10.11.0 + "@sentry/core": 10.11.0 - "@sentry/browser@7.120.4": + "@sentry-internal/replay@10.11.0": dependencies: - "@sentry-internal/feedback": 7.120.4 - "@sentry-internal/replay-canvas": 7.120.4 - "@sentry-internal/tracing": 7.120.4 - "@sentry/core": 7.120.4 - "@sentry/integrations": 7.120.4 - "@sentry/replay": 7.120.4 - "@sentry/types": 7.120.4 - "@sentry/utils": 7.120.4 + "@sentry-internal/browser-utils": 10.11.0 + "@sentry/core": 10.11.0 - "@sentry/cli@1.77.3": + "@sentry/babel-plugin-component-annotate@4.3.0": {} + + "@sentry/browser@10.11.0": + dependencies: + "@sentry-internal/browser-utils": 10.11.0 + "@sentry-internal/feedback": 10.11.0 + "@sentry-internal/replay": 10.11.0 + "@sentry-internal/replay-canvas": 10.11.0 + "@sentry/core": 10.11.0 + + "@sentry/bundler-plugin-core@4.3.0": + dependencies: + "@babel/core": 7.28.3 + "@sentry/babel-plugin-component-annotate": 4.3.0 + "@sentry/cli": 2.53.0 + dotenv: 16.6.1 + find-up: 5.0.0 + glob: 9.3.5 + magic-string: 0.30.8 + unplugin: 1.0.1 + transitivePeerDependencies: + - encoding + - supports-color + + "@sentry/cli-darwin@2.53.0": + optional: true + + "@sentry/cli-linux-arm64@2.53.0": + optional: true + + "@sentry/cli-linux-arm@2.53.0": + optional: true + + "@sentry/cli-linux-i686@2.53.0": + optional: true + + "@sentry/cli-linux-x64@2.53.0": + optional: true + + "@sentry/cli-win32-arm64@2.53.0": + optional: true + + "@sentry/cli-win32-i686@2.53.0": + optional: true + + "@sentry/cli-win32-x64@2.53.0": + optional: true + + "@sentry/cli@2.53.0": dependencies: https-proxy-agent: 5.0.1 - mkdirp: 0.5.6 node-fetch: 2.7.0 progress: 2.0.3 proxy-from-env: 1.1.0 which: 2.0.2 + optionalDependencies: + "@sentry/cli-darwin": 2.53.0 + "@sentry/cli-linux-arm": 2.53.0 + "@sentry/cli-linux-arm64": 2.53.0 + "@sentry/cli-linux-i686": 2.53.0 + "@sentry/cli-linux-x64": 2.53.0 + "@sentry/cli-win32-arm64": 2.53.0 + "@sentry/cli-win32-i686": 2.53.0 + "@sentry/cli-win32-x64": 2.53.0 transitivePeerDependencies: - encoding - supports-color - "@sentry/core@7.120.4": - dependencies: - "@sentry/types": 7.120.4 - "@sentry/utils": 7.120.4 + "@sentry/core@10.11.0": {} - "@sentry/integrations@7.120.4": + "@sentry/nextjs@10.11.0(@opentelemetry/context-async-hooks@2.1.0(@opentelemetry/api@1.9.0))(@opentelemetry/core@2.1.0(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@2.1.0(@opentelemetry/api@1.9.0))(next@15.5.3(@babel/core@7.28.3)(@opentelemetry/api@1.9.0)(babel-plugin-macros@3.1.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.90.0))(react@18.3.1)(webpack@5.101.3)": dependencies: - "@sentry/core": 7.120.4 - "@sentry/types": 7.120.4 - "@sentry/utils": 7.120.4 - localforage: 1.10.0 - - "@sentry/nextjs@7.120.4(next@14.2.31(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.90.0))(react@18.3.1)": - dependencies: - "@rollup/plugin-commonjs": 24.0.0(rollup@2.79.2) - "@sentry/core": 7.120.4 - "@sentry/integrations": 7.120.4 - "@sentry/node": 7.120.4 - "@sentry/react": 7.120.4(react@18.3.1) - "@sentry/types": 7.120.4 - "@sentry/utils": 7.120.4 - "@sentry/vercel-edge": 7.120.4 - "@sentry/webpack-plugin": 1.21.0 + "@opentelemetry/api": 1.9.0 + "@opentelemetry/semantic-conventions": 1.37.0 + "@rollup/plugin-commonjs": 28.0.1(rollup@4.50.1) + "@sentry-internal/browser-utils": 10.11.0 + "@sentry/bundler-plugin-core": 4.3.0 + "@sentry/core": 10.11.0 + "@sentry/node": 10.11.0 + "@sentry/opentelemetry": 10.11.0(@opentelemetry/api@1.9.0)(@opentelemetry/context-async-hooks@2.1.0(@opentelemetry/api@1.9.0))(@opentelemetry/core@2.1.0(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@2.1.0(@opentelemetry/api@1.9.0))(@opentelemetry/semantic-conventions@1.37.0) + "@sentry/react": 10.11.0(react@18.3.1) + "@sentry/vercel-edge": 10.11.0 + "@sentry/webpack-plugin": 4.3.0(webpack@5.101.3) chalk: 3.0.0 - next: 14.2.31(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.90.0) - react: 18.3.1 + next: 15.5.3(@babel/core@7.28.3)(@opentelemetry/api@1.9.0)(babel-plugin-macros@3.1.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.90.0) resolve: 1.22.8 - rollup: 2.79.2 + rollup: 4.50.1 stacktrace-parser: 0.1.11 transitivePeerDependencies: + - "@opentelemetry/context-async-hooks" + - "@opentelemetry/core" + - "@opentelemetry/sdk-trace-base" - encoding + - react + - supports-color + - webpack + + "@sentry/node-core@10.11.0(@opentelemetry/api@1.9.0)(@opentelemetry/context-async-hooks@2.1.0(@opentelemetry/api@1.9.0))(@opentelemetry/core@2.1.0(@opentelemetry/api@1.9.0))(@opentelemetry/instrumentation@0.203.0(@opentelemetry/api@1.9.0))(@opentelemetry/resources@2.1.0(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@2.1.0(@opentelemetry/api@1.9.0))(@opentelemetry/semantic-conventions@1.37.0)": + dependencies: + "@opentelemetry/api": 1.9.0 + "@opentelemetry/context-async-hooks": 2.1.0(@opentelemetry/api@1.9.0) + "@opentelemetry/core": 2.1.0(@opentelemetry/api@1.9.0) + "@opentelemetry/instrumentation": 0.203.0(@opentelemetry/api@1.9.0) + "@opentelemetry/resources": 2.1.0(@opentelemetry/api@1.9.0) + "@opentelemetry/sdk-trace-base": 2.1.0(@opentelemetry/api@1.9.0) + "@opentelemetry/semantic-conventions": 1.37.0 + "@sentry/core": 10.11.0 + "@sentry/opentelemetry": 10.11.0(@opentelemetry/api@1.9.0)(@opentelemetry/context-async-hooks@2.1.0(@opentelemetry/api@1.9.0))(@opentelemetry/core@2.1.0(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@2.1.0(@opentelemetry/api@1.9.0))(@opentelemetry/semantic-conventions@1.37.0) + import-in-the-middle: 1.14.2 + + "@sentry/node@10.11.0": + dependencies: + "@opentelemetry/api": 1.9.0 + "@opentelemetry/context-async-hooks": 2.1.0(@opentelemetry/api@1.9.0) + "@opentelemetry/core": 2.1.0(@opentelemetry/api@1.9.0) + "@opentelemetry/instrumentation": 0.203.0(@opentelemetry/api@1.9.0) + "@opentelemetry/instrumentation-amqplib": 0.50.0(@opentelemetry/api@1.9.0) + "@opentelemetry/instrumentation-connect": 0.47.0(@opentelemetry/api@1.9.0) + "@opentelemetry/instrumentation-dataloader": 0.21.1(@opentelemetry/api@1.9.0) + "@opentelemetry/instrumentation-express": 0.52.0(@opentelemetry/api@1.9.0) + "@opentelemetry/instrumentation-fs": 0.23.0(@opentelemetry/api@1.9.0) + "@opentelemetry/instrumentation-generic-pool": 0.47.0(@opentelemetry/api@1.9.0) + "@opentelemetry/instrumentation-graphql": 0.51.0(@opentelemetry/api@1.9.0) + "@opentelemetry/instrumentation-hapi": 0.50.0(@opentelemetry/api@1.9.0) + "@opentelemetry/instrumentation-http": 0.203.0(@opentelemetry/api@1.9.0) + "@opentelemetry/instrumentation-ioredis": 0.52.0(@opentelemetry/api@1.9.0) + "@opentelemetry/instrumentation-kafkajs": 0.13.0(@opentelemetry/api@1.9.0) + "@opentelemetry/instrumentation-knex": 0.48.0(@opentelemetry/api@1.9.0) + "@opentelemetry/instrumentation-koa": 0.51.0(@opentelemetry/api@1.9.0) + "@opentelemetry/instrumentation-lru-memoizer": 0.48.0(@opentelemetry/api@1.9.0) + "@opentelemetry/instrumentation-mongodb": 0.56.0(@opentelemetry/api@1.9.0) + "@opentelemetry/instrumentation-mongoose": 0.50.0(@opentelemetry/api@1.9.0) + "@opentelemetry/instrumentation-mysql": 0.49.0(@opentelemetry/api@1.9.0) + "@opentelemetry/instrumentation-mysql2": 0.50.0(@opentelemetry/api@1.9.0) + "@opentelemetry/instrumentation-pg": 0.55.0(@opentelemetry/api@1.9.0) + "@opentelemetry/instrumentation-redis": 0.51.0(@opentelemetry/api@1.9.0) + "@opentelemetry/instrumentation-tedious": 0.22.0(@opentelemetry/api@1.9.0) + "@opentelemetry/instrumentation-undici": 0.14.0(@opentelemetry/api@1.9.0) + "@opentelemetry/resources": 2.1.0(@opentelemetry/api@1.9.0) + "@opentelemetry/sdk-trace-base": 2.1.0(@opentelemetry/api@1.9.0) + "@opentelemetry/semantic-conventions": 1.37.0 + "@prisma/instrumentation": 6.14.0(@opentelemetry/api@1.9.0) + "@sentry/core": 10.11.0 + "@sentry/node-core": 10.11.0(@opentelemetry/api@1.9.0)(@opentelemetry/context-async-hooks@2.1.0(@opentelemetry/api@1.9.0))(@opentelemetry/core@2.1.0(@opentelemetry/api@1.9.0))(@opentelemetry/instrumentation@0.203.0(@opentelemetry/api@1.9.0))(@opentelemetry/resources@2.1.0(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@2.1.0(@opentelemetry/api@1.9.0))(@opentelemetry/semantic-conventions@1.37.0) + "@sentry/opentelemetry": 10.11.0(@opentelemetry/api@1.9.0)(@opentelemetry/context-async-hooks@2.1.0(@opentelemetry/api@1.9.0))(@opentelemetry/core@2.1.0(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@2.1.0(@opentelemetry/api@1.9.0))(@opentelemetry/semantic-conventions@1.37.0) + import-in-the-middle: 1.14.2 + minimatch: 9.0.5 + transitivePeerDependencies: - supports-color - "@sentry/node@7.120.4": + "@sentry/opentelemetry@10.11.0(@opentelemetry/api@1.9.0)(@opentelemetry/context-async-hooks@2.1.0(@opentelemetry/api@1.9.0))(@opentelemetry/core@2.1.0(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@2.1.0(@opentelemetry/api@1.9.0))(@opentelemetry/semantic-conventions@1.37.0)": dependencies: - "@sentry-internal/tracing": 7.120.4 - "@sentry/core": 7.120.4 - "@sentry/integrations": 7.120.4 - "@sentry/types": 7.120.4 - "@sentry/utils": 7.120.4 + "@opentelemetry/api": 1.9.0 + "@opentelemetry/context-async-hooks": 2.1.0(@opentelemetry/api@1.9.0) + "@opentelemetry/core": 2.1.0(@opentelemetry/api@1.9.0) + "@opentelemetry/sdk-trace-base": 2.1.0(@opentelemetry/api@1.9.0) + "@opentelemetry/semantic-conventions": 1.37.0 + "@sentry/core": 10.11.0 - "@sentry/react@7.120.4(react@18.3.1)": + "@sentry/react@10.11.0(react@18.3.1)": dependencies: - "@sentry/browser": 7.120.4 - "@sentry/core": 7.120.4 - "@sentry/types": 7.120.4 - "@sentry/utils": 7.120.4 + "@sentry/browser": 10.11.0 + "@sentry/core": 10.11.0 hoist-non-react-statics: 3.3.2 react: 18.3.1 - "@sentry/replay@7.120.4": + "@sentry/vercel-edge@10.11.0": dependencies: - "@sentry-internal/tracing": 7.120.4 - "@sentry/core": 7.120.4 - "@sentry/types": 7.120.4 - "@sentry/utils": 7.120.4 + "@opentelemetry/api": 1.9.0 + "@opentelemetry/resources": 2.1.0(@opentelemetry/api@1.9.0) + "@sentry/core": 10.11.0 - "@sentry/types@7.120.4": {} - - "@sentry/utils@7.120.4": + "@sentry/webpack-plugin@4.3.0(webpack@5.101.3)": dependencies: - "@sentry/types": 7.120.4 - - "@sentry/vercel-edge@7.120.4": - dependencies: - "@sentry-internal/tracing": 7.120.4 - "@sentry/core": 7.120.4 - "@sentry/integrations": 7.120.4 - "@sentry/types": 7.120.4 - "@sentry/utils": 7.120.4 - - "@sentry/webpack-plugin@1.21.0": - dependencies: - "@sentry/cli": 1.77.3 - webpack-sources: 3.3.3 + "@sentry/bundler-plugin-core": 4.3.0 + unplugin: 1.0.1 + uuid: 9.0.1 + webpack: 5.101.3 transitivePeerDependencies: - encoding - supports-color - "@sinclair/typebox@0.25.24": {} - "@sinclair/typebox@0.27.8": {} + "@sinclair/typebox@0.34.41": {} + + "@sinonjs/commons@3.0.1": + dependencies: + type-detect: 4.0.8 + + "@sinonjs/fake-timers@13.0.5": + dependencies: + "@sinonjs/commons": 3.0.1 + "@socket.io/component-emitter@3.1.2": {} "@standard-schema/spec@1.0.0": {} "@standard-schema/utils@0.3.0": {} - "@swc/counter@0.1.3": {} + "@swc/helpers@0.5.15": + dependencies: + tslib: 2.8.1 "@swc/helpers@0.5.17": dependencies: tslib: 2.8.1 - "@swc/helpers@0.5.5": + "@tanstack/query-core@5.85.9": {} + + "@tanstack/react-query@5.85.9(react@18.3.1)": dependencies: - "@swc/counter": 0.1.3 - tslib: 2.8.1 + "@tanstack/query-core": 5.85.9 + react: 18.3.1 - "@tootallnate/once@2.0.0": {} + "@tsconfig/node10@1.0.11": + optional: true - "@ts-morph/common@0.11.1": - dependencies: - fast-glob: 3.3.3 - minimatch: 3.1.2 - mkdirp: 1.0.4 - path-browserify: 1.0.1 + "@tsconfig/node12@1.0.11": + optional: true - "@tsconfig/node10@1.0.11": {} + "@tsconfig/node14@1.0.3": + optional: true - "@tsconfig/node12@1.0.11": {} - - "@tsconfig/node14@1.0.3": {} - - "@tsconfig/node16@1.0.4": {} + "@tsconfig/node16@1.0.4": + optional: true "@tybys/wasm-util@0.10.0": dependencies: tslib: 2.8.1 optional: true + "@types/babel__core@7.20.5": + dependencies: + "@babel/parser": 7.28.0 + "@babel/types": 7.28.2 + "@types/babel__generator": 7.27.0 + "@types/babel__template": 7.4.4 + "@types/babel__traverse": 7.28.0 + + "@types/babel__generator@7.27.0": + dependencies: + "@babel/types": 7.28.2 + + "@types/babel__template@7.4.4": + dependencies: + "@babel/parser": 7.28.0 + "@babel/types": 7.28.2 + + "@types/babel__traverse@7.28.0": + dependencies: + "@babel/types": 7.28.2 + + "@types/connect@3.4.38": + dependencies: + "@types/node": 24.2.1 + "@types/debug@4.1.12": dependencies: "@types/ms": 2.1.0 + "@types/eslint-scope@3.7.7": + dependencies: + "@types/eslint": 9.6.1 + "@types/estree": 1.0.8 + + "@types/eslint@9.6.1": + dependencies: + "@types/estree": 1.0.8 + "@types/json-schema": 7.0.15 + "@types/estree-jsx@1.0.5": dependencies: "@types/estree": 1.0.8 @@ -8639,6 +10794,12 @@ snapshots: dependencies: "@types/unist": 3.0.3 + "@types/ioredis@5.0.0": + dependencies: + ioredis: 5.7.0 + transitivePeerDependencies: + - supports-color + "@types/istanbul-lib-coverage@2.0.6": {} "@types/istanbul-lib-report@3.0.3": @@ -8649,6 +10810,11 @@ snapshots: dependencies: "@types/istanbul-lib-report": 3.0.3 + "@types/jest@30.0.0": + dependencies: + expect: 30.1.2 + pretty-format: 30.0.5 + "@types/json-schema@7.0.15": {} "@types/json5@0.0.29": {} @@ -8659,19 +10825,35 @@ snapshots: "@types/ms@2.1.0": {} + "@types/mysql@2.15.27": + dependencies: + "@types/node": 24.2.1 + "@types/node-fetch@2.6.13": dependencies: "@types/node": 24.2.1 form-data: 4.0.4 - "@types/node@16.18.11": {} - "@types/node@24.2.1": dependencies: undici-types: 7.10.0 + "@types/node@24.3.1": + dependencies: + undici-types: 7.10.0 + "@types/parse-json@4.0.2": {} + "@types/pg-pool@2.0.6": + dependencies: + "@types/pg": 8.15.4 + + "@types/pg@8.15.4": + dependencies: + "@types/node": 24.2.1 + pg-protocol: 1.10.3 + pg-types: 2.2.0 + "@types/prop-types@15.7.15": {} "@types/react@18.2.20": @@ -8682,6 +10864,14 @@ snapshots: "@types/scheduler@0.26.0": {} + "@types/shimmer@1.2.0": {} + + "@types/stack-utils@2.0.3": {} + + "@types/tedious@4.0.14": + dependencies: + "@types/node": 24.2.1 + "@types/ua-parser-js@0.7.39": {} "@types/unist@2.0.11": {} @@ -8860,166 +11050,81 @@ snapshots: "@unrs/resolver-binding-win32-x64-msvc@1.11.1": optional: true - "@upstash/redis@1.35.3": + "@webassemblyjs/ast@1.14.1": dependencies: - uncrypto: 0.1.3 + "@webassemblyjs/helper-numbers": 1.13.2 + "@webassemblyjs/helper-wasm-bytecode": 1.13.2 - "@vercel/build-utils@8.4.12": {} + "@webassemblyjs/floating-point-hex-parser@1.13.2": {} - "@vercel/edge-config-fs@0.1.0": {} + "@webassemblyjs/helper-api-error@1.13.2": {} - "@vercel/edge-config@0.4.1": + "@webassemblyjs/helper-buffer@1.14.1": {} + + "@webassemblyjs/helper-numbers@1.13.2": dependencies: - "@vercel/edge-config-fs": 0.1.0 + "@webassemblyjs/floating-point-hex-parser": 1.13.2 + "@webassemblyjs/helper-api-error": 1.13.2 + "@xtuc/long": 4.2.2 - "@vercel/error-utils@2.0.2": {} + "@webassemblyjs/helper-wasm-bytecode@1.13.2": {} - "@vercel/fun@1.1.0": + "@webassemblyjs/helper-wasm-section@1.14.1": dependencies: - "@tootallnate/once": 2.0.0 - async-listen: 1.2.0 - debug: 4.1.1 - execa: 3.2.0 - fs-extra: 8.1.0 - generic-pool: 3.4.2 - micro: 9.3.5-canary.3 - ms: 2.1.1 - node-fetch: 2.6.7 - path-match: 1.2.4 - promisepipe: 3.0.0 - semver: 7.3.5 - stat-mode: 0.3.0 - stream-to-promise: 2.2.0 - tar: 4.4.18 - tree-kill: 1.2.2 - uid-promise: 1.0.0 - uuid: 3.3.2 - xdg-app-paths: 5.1.0 - yauzl-promise: 2.1.3 - transitivePeerDependencies: - - encoding - - supports-color + "@webassemblyjs/ast": 1.14.1 + "@webassemblyjs/helper-buffer": 1.14.1 + "@webassemblyjs/helper-wasm-bytecode": 1.13.2 + "@webassemblyjs/wasm-gen": 1.14.1 - "@vercel/gatsby-plugin-vercel-analytics@1.0.11": + "@webassemblyjs/ieee754@1.13.2": dependencies: - web-vitals: 0.2.4 + "@xtuc/ieee754": 1.2.0 - "@vercel/gatsby-plugin-vercel-builder@2.0.56": + "@webassemblyjs/leb128@1.13.2": dependencies: - "@sinclair/typebox": 0.25.24 - "@vercel/build-utils": 8.4.12 - "@vercel/routing-utils": 3.1.0 - esbuild: 0.14.47 - etag: 1.8.1 - fs-extra: 11.1.0 + "@xtuc/long": 4.2.2 - "@vercel/go@3.2.0": {} + "@webassemblyjs/utf8@1.13.2": {} - "@vercel/hydrogen@1.0.9": + "@webassemblyjs/wasm-edit@1.14.1": dependencies: - "@vercel/static-config": 3.0.0 - ts-morph: 12.0.0 + "@webassemblyjs/ast": 1.14.1 + "@webassemblyjs/helper-buffer": 1.14.1 + "@webassemblyjs/helper-wasm-bytecode": 1.13.2 + "@webassemblyjs/helper-wasm-section": 1.14.1 + "@webassemblyjs/wasm-gen": 1.14.1 + "@webassemblyjs/wasm-opt": 1.14.1 + "@webassemblyjs/wasm-parser": 1.14.1 + "@webassemblyjs/wast-printer": 1.14.1 - "@vercel/kv@2.0.0": + "@webassemblyjs/wasm-gen@1.14.1": dependencies: - "@upstash/redis": 1.35.3 + "@webassemblyjs/ast": 1.14.1 + "@webassemblyjs/helper-wasm-bytecode": 1.13.2 + "@webassemblyjs/ieee754": 1.13.2 + "@webassemblyjs/leb128": 1.13.2 + "@webassemblyjs/utf8": 1.13.2 - "@vercel/next@4.3.18": + "@webassemblyjs/wasm-opt@1.14.1": dependencies: - "@vercel/nft": 0.27.3 - transitivePeerDependencies: - - encoding - - supports-color + "@webassemblyjs/ast": 1.14.1 + "@webassemblyjs/helper-buffer": 1.14.1 + "@webassemblyjs/wasm-gen": 1.14.1 + "@webassemblyjs/wasm-parser": 1.14.1 - "@vercel/nft@0.27.3": + "@webassemblyjs/wasm-parser@1.14.1": dependencies: - "@mapbox/node-pre-gyp": 1.0.11 - "@rollup/pluginutils": 4.2.1 - acorn: 8.15.0 - acorn-import-attributes: 1.9.5(acorn@8.15.0) - async-sema: 3.1.1 - bindings: 1.5.0 - estree-walker: 2.0.2 - glob: 7.2.3 - graceful-fs: 4.2.11 - micromatch: 4.0.8 - node-gyp-build: 4.8.4 - resolve-from: 5.0.0 - transitivePeerDependencies: - - encoding - - supports-color + "@webassemblyjs/ast": 1.14.1 + "@webassemblyjs/helper-api-error": 1.13.2 + "@webassemblyjs/helper-wasm-bytecode": 1.13.2 + "@webassemblyjs/ieee754": 1.13.2 + "@webassemblyjs/leb128": 1.13.2 + "@webassemblyjs/utf8": 1.13.2 - "@vercel/node@3.2.24": + "@webassemblyjs/wast-printer@1.14.1": dependencies: - "@edge-runtime/node-utils": 2.3.0 - "@edge-runtime/primitives": 4.1.0 - "@edge-runtime/vm": 3.2.0 - "@types/node": 16.18.11 - "@vercel/build-utils": 8.4.12 - "@vercel/error-utils": 2.0.2 - "@vercel/nft": 0.27.3 - "@vercel/static-config": 3.0.0 - async-listen: 3.0.0 - cjs-module-lexer: 1.2.3 - edge-runtime: 2.5.9 - es-module-lexer: 1.4.1 - esbuild: 0.14.47 - etag: 1.8.1 - node-fetch: 2.6.9 - path-to-regexp: 6.2.1 - ts-morph: 12.0.0 - ts-node: 10.9.1(@types/node@16.18.11)(typescript@4.9.5) - typescript: 4.9.5 - undici: 5.28.4 - transitivePeerDependencies: - - "@swc/core" - - "@swc/wasm" - - encoding - - supports-color - - "@vercel/python@4.3.1": {} - - "@vercel/redwood@2.1.8": - dependencies: - "@vercel/nft": 0.27.3 - "@vercel/routing-utils": 3.1.0 - "@vercel/static-config": 3.0.0 - semver: 6.3.1 - ts-morph: 12.0.0 - transitivePeerDependencies: - - encoding - - supports-color - - "@vercel/remix-builder@2.2.13": - dependencies: - "@vercel/error-utils": 2.0.2 - "@vercel/nft": 0.27.3 - "@vercel/static-config": 3.0.0 - ts-morph: 12.0.0 - transitivePeerDependencies: - - encoding - - supports-color - - "@vercel/routing-utils@3.1.0": - dependencies: - path-to-regexp: 6.1.0 - optionalDependencies: - ajv: 6.12.6 - - "@vercel/ruby@2.1.0": {} - - "@vercel/static-build@2.5.34": - dependencies: - "@vercel/gatsby-plugin-vercel-analytics": 1.0.11 - "@vercel/gatsby-plugin-vercel-builder": 2.0.56 - "@vercel/static-config": 3.0.0 - ts-morph: 12.0.0 - - "@vercel/static-config@3.0.0": - dependencies: - ajv: 8.6.3 - json-schema-to-ts: 1.6.4 - ts-morph: 12.0.0 + "@webassemblyjs/ast": 1.14.1 + "@xtuc/long": 4.2.2 "@whereby.com/browser-sdk@3.13.1(@types/react@18.2.20)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)": dependencies: @@ -9078,6 +11183,10 @@ snapshots: - supports-color - utf-8-validate + "@xtuc/ieee754@1.2.0": {} + + "@xtuc/long@4.2.2": {} + "@zag-js/accordion@1.21.0": dependencies: "@zag-js/anatomy": 1.21.0 @@ -9579,12 +11688,14 @@ snapshots: "@zag-js/utils@1.21.0": {} - abbrev@1.1.1: {} - acorn-import-attributes@1.9.5(acorn@8.15.0): dependencies: acorn: 8.15.0 + acorn-import-phases@1.0.4(acorn@8.15.0): + dependencies: + acorn: 8.15.0 + acorn-jsx@5.3.2(acorn@8.15.0): dependencies: acorn: 8.15.0 @@ -9592,6 +11703,7 @@ snapshots: acorn-walk@8.3.4: dependencies: acorn: 8.15.0 + optional: true acorn@8.15.0: {} @@ -9601,6 +11713,17 @@ snapshots: transitivePeerDependencies: - supports-color + agent-base@7.1.4: {} + + ajv-formats@2.1.1(ajv@8.17.1): + optionalDependencies: + ajv: 8.17.1 + + ajv-keywords@5.1.0(ajv@8.17.1): + dependencies: + ajv: 8.17.1 + fast-deep-equal: 3.1.3 + ajv@6.12.6: dependencies: fast-deep-equal: 3.1.3 @@ -9608,12 +11731,18 @@ snapshots: json-schema-traverse: 0.4.1 uri-js: 4.4.1 - ajv@8.6.3: + ajv@8.17.1: dependencies: fast-deep-equal: 3.1.3 + fast-uri: 3.1.0 json-schema-traverse: 1.0.0 require-from-string: 2.0.2 - uri-js: 4.4.1 + + ansi-colors@4.1.3: {} + + ansi-escapes@4.3.2: + dependencies: + type-fest: 0.21.3 ansi-regex@5.0.1: {} @@ -9623,6 +11752,8 @@ snapshots: dependencies: color-convert: 2.0.1 + ansi-styles@5.2.0: {} + ansi-styles@6.2.1: {} any-promise@1.3.0: {} @@ -9632,19 +11763,15 @@ snapshots: normalize-path: 3.0.0 picomatch: 2.3.1 - aproba@2.1.0: {} - - are-we-there-yet@2.0.0: - dependencies: - delegates: 1.0.0 - readable-stream: 3.6.2 - - arg@4.1.0: {} - - arg@4.1.3: {} + arg@4.1.3: + optional: true arg@5.0.2: {} + argparse@1.0.10: + dependencies: + sprintf-js: 1.0.3 + argparse@2.0.1: {} aria-hidden@1.2.6: @@ -9724,14 +11851,6 @@ snapshots: async-function@1.0.0: {} - async-listen@1.2.0: {} - - async-listen@3.0.0: {} - - async-listen@3.0.1: {} - - async-sema@3.1.1: {} - asynckit@0.4.0: {} augmentor@2.2.0: @@ -9771,12 +11890,66 @@ snapshots: axobject-query@4.1.0: {} + babel-jest@30.1.2(@babel/core@7.28.3): + dependencies: + "@babel/core": 7.28.3 + "@jest/transform": 30.1.2 + "@types/babel__core": 7.20.5 + babel-plugin-istanbul: 7.0.0 + babel-preset-jest: 30.0.1(@babel/core@7.28.3) + chalk: 4.1.2 + graceful-fs: 4.2.11 + slash: 3.0.0 + transitivePeerDependencies: + - supports-color + + babel-plugin-istanbul@7.0.0: + dependencies: + "@babel/helper-plugin-utils": 7.27.1 + "@istanbuljs/load-nyc-config": 1.1.0 + "@istanbuljs/schema": 0.1.3 + istanbul-lib-instrument: 6.0.3 + test-exclude: 6.0.0 + transitivePeerDependencies: + - supports-color + + babel-plugin-jest-hoist@30.0.1: + dependencies: + "@babel/template": 7.27.2 + "@babel/types": 7.28.2 + "@types/babel__core": 7.20.5 + babel-plugin-macros@3.1.0: dependencies: "@babel/runtime": 7.28.2 cosmiconfig: 7.1.0 resolve: 1.22.10 + babel-preset-current-node-syntax@1.2.0(@babel/core@7.28.3): + dependencies: + "@babel/core": 7.28.3 + "@babel/plugin-syntax-async-generators": 7.8.4(@babel/core@7.28.3) + "@babel/plugin-syntax-bigint": 7.8.3(@babel/core@7.28.3) + "@babel/plugin-syntax-class-properties": 7.12.13(@babel/core@7.28.3) + "@babel/plugin-syntax-class-static-block": 7.14.5(@babel/core@7.28.3) + "@babel/plugin-syntax-import-attributes": 7.27.1(@babel/core@7.28.3) + "@babel/plugin-syntax-import-meta": 7.10.4(@babel/core@7.28.3) + "@babel/plugin-syntax-json-strings": 7.8.3(@babel/core@7.28.3) + "@babel/plugin-syntax-logical-assignment-operators": 7.10.4(@babel/core@7.28.3) + "@babel/plugin-syntax-nullish-coalescing-operator": 7.8.3(@babel/core@7.28.3) + "@babel/plugin-syntax-numeric-separator": 7.10.4(@babel/core@7.28.3) + "@babel/plugin-syntax-object-rest-spread": 7.8.3(@babel/core@7.28.3) + "@babel/plugin-syntax-optional-catch-binding": 7.8.3(@babel/core@7.28.3) + "@babel/plugin-syntax-optional-chaining": 7.8.3(@babel/core@7.28.3) + "@babel/plugin-syntax-private-property-in-object": 7.14.5(@babel/core@7.28.3) + "@babel/plugin-syntax-top-level-await": 7.14.5(@babel/core@7.28.3) + + babel-preset-jest@30.0.1(@babel/core@7.28.3): + dependencies: + "@babel/core": 7.28.3 + babel-plugin-jest-hoist: 30.0.1 + babel-preset-current-node-syntax: 1.2.0(@babel/core@7.28.3) + bail@2.0.2: {} balanced-match@1.0.2: {} @@ -9785,10 +11958,6 @@ snapshots: binary-extensions@2.3.0: {} - bindings@1.5.0: - dependencies: - file-uri-to-path: 1.0.0 - brace-expansion@1.1.12: dependencies: balanced-match: 1.0.2 @@ -9809,36 +11978,23 @@ snapshots: node-releases: 2.0.19 update-browserslist-db: 1.1.3(browserslist@4.25.2) + bs-logger@0.2.6: + dependencies: + fast-json-stable-stringify: 2.1.0 + + bser@2.1.1: + dependencies: + node-int64: 0.4.0 + btoa@1.2.1: {} - buffer-crc32@0.2.13: {} + buffer-from@1.1.2: {} buffer@6.0.3: dependencies: base64-js: 1.5.1 ieee754: 1.2.1 - busboy@1.6.0: - dependencies: - streamsearch: 1.1.0 - - bytes@3.1.0: {} - - c12@1.11.1: - dependencies: - chokidar: 3.6.0 - confbox: 0.1.8 - defu: 6.1.4 - dotenv: 16.6.1 - giget: 1.2.5 - jiti: 1.21.7 - mlly: 1.7.4 - ohash: 1.1.6 - pathe: 1.1.2 - perfect-debounce: 1.0.0 - pkg-types: 1.3.1 - rc9: 2.1.2 - call-bind-apply-helpers@1.0.2: dependencies: es-errors: 1.3.0 @@ -9860,7 +12016,9 @@ snapshots: camelcase-css@2.0.1: {} - camelcase@8.0.0: {} + camelcase@5.3.1: {} + + camelcase@6.3.0: {} caniuse-lite@1.0.30001734: {} @@ -9876,6 +12034,10 @@ snapshots: ansi-styles: 4.3.0 supports-color: 7.2.0 + change-case@5.4.4: {} + + char-regex@1.0.2: {} + character-entities-html4@2.1.0: {} character-entities-legacy@3.0.0: {} @@ -9888,18 +12050,6 @@ snapshots: dependencies: ip-range-check: 0.0.2 - chokidar@3.3.1: - dependencies: - anymatch: 3.1.3 - braces: 3.0.3 - glob-parent: 5.1.2 - is-binary-path: 2.1.0 - is-glob: 4.0.3 - normalize-path: 3.0.0 - readdirp: 3.3.0 - optionalDependencies: - fsevents: 2.1.3 - chokidar@3.6.0: dependencies: anymatch: 3.1.3 @@ -9916,27 +12066,33 @@ snapshots: dependencies: readdirp: 4.1.2 - chownr@1.1.4: {} - - chownr@2.0.0: {} + chrome-trace-event@1.0.4: {} ci-info@3.9.0: {} - citty@0.1.6: - dependencies: - consola: 3.4.2 + ci-info@4.3.0: {} - cjs-module-lexer@1.2.3: {} + cjs-module-lexer@1.4.3: {} + + cjs-module-lexer@2.1.0: {} classnames@2.5.1: {} client-only@0.0.1: {} + cliui@8.0.1: + dependencies: + string-width: 4.2.3 + strip-ansi: 6.0.1 + wrap-ansi: 7.0.0 + clsx@2.1.1: {} cluster-key-slot@1.1.2: {} - code-block-writer@10.1.1: {} + co@4.6.0: {} + + collect-v8-coverage@1.0.2: {} color-convert@2.0.1: dependencies: @@ -9944,7 +12100,19 @@ snapshots: color-name@1.1.4: {} - color-support@1.1.3: {} + color-string@1.9.1: + dependencies: + color-name: 1.1.4 + simple-swizzle: 0.2.2 + optional: true + + color@4.2.3: + dependencies: + color-convert: 2.0.1 + color-string: 1.9.1 + optional: true + + colorette@1.4.0: {} combined-stream@1.0.8: dependencies: @@ -9952,7 +12120,7 @@ snapshots: comma-separated-tokens@2.0.3: {} - commander@12.1.0: {} + commander@2.20.3: {} commander@4.1.1: {} @@ -9960,18 +12128,10 @@ snapshots: concat-map@0.0.1: {} - confbox@0.1.8: {} - - consola@3.4.2: {} - - console-control-strings@1.1.0: {} - - content-type@1.0.4: {} - - convert-hrtime@3.0.0: {} - convert-source-map@1.9.0: {} + convert-source-map@2.0.0: {} + cookie@0.7.2: {} cosmiconfig@7.1.0: @@ -9982,7 +12142,8 @@ snapshots: path-type: 4.0.0 yaml: 1.10.2 - create-require@1.1.1: {} + create-require@1.1.1: + optional: true cross-spawn@7.0.6: dependencies: @@ -10018,14 +12179,16 @@ snapshots: dependencies: ms: 2.1.3 - debug@4.1.1: - dependencies: - ms: 2.1.1 - debug@4.3.7: dependencies: ms: 2.1.3 + debug@4.4.1(supports-color@10.2.0): + dependencies: + ms: 2.1.3 + optionalDependencies: + supports-color: 10.2.0 + debug@4.4.1(supports-color@9.4.0): dependencies: ms: 2.1.3 @@ -10036,8 +12199,14 @@ snapshots: dependencies: character-entities: 2.0.2 + dedent@1.7.0(babel-plugin-macros@3.1.0): + optionalDependencies: + babel-plugin-macros: 3.1.0 + deep-is@0.1.4: {} + deepmerge@4.3.1: {} + define-data-property@1.1.4: dependencies: es-define-property: 1.0.1 @@ -10050,26 +12219,21 @@ snapshots: has-property-descriptors: 1.0.2 object-keys: 1.1.1 - defu@6.1.4: {} - delayed-stream@1.0.0: {} - delegates@1.0.0: {} - denque@2.1.0: {} - depd@1.1.2: {} - dequal@2.0.3: {} - destr@2.0.5: {} - detect-europe-js@0.1.2: {} detect-libc@1.0.3: optional: true - detect-libc@2.0.4: {} + detect-libc@2.0.4: + optional: true + + detect-newline@3.1.0: {} detect-node-es@1.1.0: {} @@ -10079,7 +12243,8 @@ snapshots: didyoumean@1.2.2: {} - diff@4.0.2: {} + diff@4.0.2: + optional: true dlv@1.1.3: {} @@ -10113,32 +12278,14 @@ snapshots: eastasianwidth@0.2.0: {} - edge-runtime@2.5.9: - dependencies: - "@edge-runtime/format": 2.2.1 - "@edge-runtime/ponyfill": 2.4.2 - "@edge-runtime/vm": 3.2.0 - async-listen: 3.0.1 - mri: 1.2.0 - picocolors: 1.0.0 - pretty-ms: 7.0.1 - signal-exit: 4.0.2 - time-span: 4.0.0 - electron-to-chromium@1.5.200: {} + emittery@0.13.1: {} + emoji-regex@8.0.0: {} emoji-regex@9.2.2: {} - end-of-stream@1.1.0: - dependencies: - once: 1.3.3 - - end-of-stream@1.4.5: - dependencies: - once: 1.4.0 - engine.io-client@6.5.4: dependencies: "@socket.io/component-emitter": 3.1.2 @@ -10153,6 +12300,11 @@ snapshots: engine.io-parser@5.2.3: {} + enhanced-resolve@5.18.3: + dependencies: + graceful-fs: 4.2.11 + tapable: 2.2.3 + err-code@3.0.1: {} error-ex@1.3.2: @@ -10239,7 +12391,7 @@ snapshots: iterator.prototype: 1.1.5 safe-array-concat: 1.1.3 - es-module-lexer@1.4.1: {} + es-module-lexer@1.7.0: {} es-object-atoms@1.1.1: dependencies: @@ -10262,96 +12414,15 @@ snapshots: is-date-object: 1.1.0 is-symbol: 1.1.1 - esbuild-android-64@0.14.47: - optional: true - - esbuild-android-arm64@0.14.47: - optional: true - - esbuild-darwin-64@0.14.47: - optional: true - - esbuild-darwin-arm64@0.14.47: - optional: true - - esbuild-freebsd-64@0.14.47: - optional: true - - esbuild-freebsd-arm64@0.14.47: - optional: true - - esbuild-linux-32@0.14.47: - optional: true - - esbuild-linux-64@0.14.47: - optional: true - - esbuild-linux-arm64@0.14.47: - optional: true - - esbuild-linux-arm@0.14.47: - optional: true - - esbuild-linux-mips64le@0.14.47: - optional: true - - esbuild-linux-ppc64le@0.14.47: - optional: true - - esbuild-linux-riscv64@0.14.47: - optional: true - - esbuild-linux-s390x@0.14.47: - optional: true - - esbuild-netbsd-64@0.14.47: - optional: true - - esbuild-openbsd-64@0.14.47: - optional: true - - esbuild-sunos-64@0.14.47: - optional: true - - esbuild-windows-32@0.14.47: - optional: true - - esbuild-windows-64@0.14.47: - optional: true - - esbuild-windows-arm64@0.14.47: - optional: true - - esbuild@0.14.47: - optionalDependencies: - esbuild-android-64: 0.14.47 - esbuild-android-arm64: 0.14.47 - esbuild-darwin-64: 0.14.47 - esbuild-darwin-arm64: 0.14.47 - esbuild-freebsd-64: 0.14.47 - esbuild-freebsd-arm64: 0.14.47 - esbuild-linux-32: 0.14.47 - esbuild-linux-64: 0.14.47 - esbuild-linux-arm: 0.14.47 - esbuild-linux-arm64: 0.14.47 - esbuild-linux-mips64le: 0.14.47 - esbuild-linux-ppc64le: 0.14.47 - esbuild-linux-riscv64: 0.14.47 - esbuild-linux-s390x: 0.14.47 - esbuild-netbsd-64: 0.14.47 - esbuild-openbsd-64: 0.14.47 - esbuild-sunos-64: 0.14.47 - esbuild-windows-32: 0.14.47 - esbuild-windows-64: 0.14.47 - esbuild-windows-arm64: 0.14.47 - escalade@3.2.0: {} + escape-string-regexp@2.0.0: {} + escape-string-regexp@4.0.0: {} - eslint-config-next@14.2.31(eslint@9.33.0(jiti@1.21.7))(typescript@5.9.2): + eslint-config-next@15.5.3(eslint@9.33.0(jiti@1.21.7))(typescript@5.9.2): dependencies: - "@next/eslint-plugin-next": 14.2.31 + "@next/eslint-plugin-next": 15.5.3 "@rushstack/eslint-patch": 1.12.0 "@typescript-eslint/eslint-plugin": 8.39.1(@typescript-eslint/parser@8.39.1(eslint@9.33.0(jiti@1.21.7))(typescript@5.9.2))(eslint@9.33.0(jiti@1.21.7))(typescript@5.9.2) "@typescript-eslint/parser": 8.39.1(eslint@9.33.0(jiti@1.21.7))(typescript@5.9.2) @@ -10361,7 +12432,7 @@ snapshots: eslint-plugin-import: 2.32.0(@typescript-eslint/parser@8.39.1(eslint@9.33.0(jiti@1.21.7))(typescript@5.9.2))(eslint-import-resolver-typescript@3.10.1)(eslint@9.33.0(jiti@1.21.7)) eslint-plugin-jsx-a11y: 6.10.2(eslint@9.33.0(jiti@1.21.7)) eslint-plugin-react: 7.37.5(eslint@9.33.0(jiti@1.21.7)) - eslint-plugin-react-hooks: 5.0.0-canary-7118f5dd7-20230705(eslint@9.33.0(jiti@1.21.7)) + eslint-plugin-react-hooks: 5.2.0(eslint@9.33.0(jiti@1.21.7)) optionalDependencies: typescript: 5.9.2 transitivePeerDependencies: @@ -10451,7 +12522,7 @@ snapshots: safe-regex-test: 1.1.0 string.prototype.includes: 2.0.1 - eslint-plugin-react-hooks@5.0.0-canary-7118f5dd7-20230705(eslint@9.33.0(jiti@1.21.7)): + eslint-plugin-react-hooks@5.2.0(eslint@9.33.0(jiti@1.21.7)): dependencies: eslint: 9.33.0(jiti@1.21.7) @@ -10477,6 +12548,11 @@ snapshots: string.prototype.matchall: 4.0.12 string.prototype.repeat: 1.0.0 + eslint-scope@5.1.1: + dependencies: + esrecurse: 4.3.0 + estraverse: 4.3.0 + eslint-scope@8.4.0: dependencies: esrecurse: 4.3.0 @@ -10534,6 +12610,8 @@ snapshots: acorn-jsx: 5.3.2(acorn@8.15.0) eslint-visitor-keys: 4.2.1 + esprima@4.0.1: {} + esquery@1.6.0: dependencies: estraverse: 5.3.0 @@ -10542,6 +12620,8 @@ snapshots: dependencies: estraverse: 5.3.0 + estraverse@4.3.0: {} + estraverse@5.3.0: {} estree-util-is-identifier-name@3.0.0: {} @@ -10550,27 +12630,33 @@ snapshots: esutils@2.0.3: {} - etag@1.8.1: {} - event-target-shim@6.0.2: {} - events-intercept@2.0.0: {} - events@3.3.0: {} - execa@3.2.0: + execa@5.1.1: dependencies: cross-spawn: 7.0.6 - get-stream: 5.2.0 - human-signals: 1.1.1 + get-stream: 6.0.1 + human-signals: 2.1.0 is-stream: 2.0.1 merge-stream: 2.0.0 npm-run-path: 4.0.1 onetime: 5.1.2 - p-finally: 2.0.1 signal-exit: 3.0.7 strip-final-newline: 2.0.0 + exit-x@0.2.2: {} + + expect@30.1.2: + dependencies: + "@jest/expect-utils": 30.1.2 + "@jest/get-type": 30.1.0 + jest-matcher-utils: 30.1.2 + jest-message-util: 30.1.0 + jest-mock: 30.0.5 + jest-util: 30.0.5 + extend@3.0.2: {} fake-mediastreamtrack@1.2.0: @@ -10580,6 +12666,14 @@ snapshots: fast-deep-equal@3.1.3: {} + fast-glob@3.3.1: + dependencies: + "@nodelib/fs.stat": 2.0.5 + "@nodelib/fs.walk": 1.2.8 + glob-parent: 5.1.2 + merge2: 1.4.1 + micromatch: 4.0.8 + fast-glob@3.3.3: dependencies: "@nodelib/fs.stat": 2.0.5 @@ -10594,13 +12688,15 @@ snapshots: fast-safe-stringify@2.1.1: {} + fast-uri@3.1.0: {} + fastq@1.19.1: dependencies: reusify: 1.1.0 - fd-slicer@1.1.0: + fb-watchman@2.0.2: dependencies: - pend: 1.2.0 + bser: 2.1.1 fdir@6.4.6(picomatch@4.0.3): optionalDependencies: @@ -10610,14 +12706,17 @@ snapshots: dependencies: flat-cache: 4.0.1 - file-uri-to-path@1.0.0: {} - fill-range@7.1.1: dependencies: to-regex-range: 5.0.1 find-root@1.1.0: {} + find-up@4.1.0: + dependencies: + locate-path: 5.0.0 + path-exists: 4.0.0 + find-up@5.0.0: dependencies: locate-path: 6.0.0 @@ -10651,33 +12750,12 @@ snapshots: hasown: 2.0.2 mime-types: 2.1.35 + forwarded-parse@2.1.2: {} + fraction.js@4.3.7: {} - fs-extra@11.1.0: - dependencies: - graceful-fs: 4.2.11 - jsonfile: 6.2.0 - universalify: 2.0.1 - - fs-extra@8.1.0: - dependencies: - graceful-fs: 4.2.11 - jsonfile: 4.0.0 - universalify: 0.1.2 - - fs-minipass@1.2.7: - dependencies: - minipass: 2.9.0 - - fs-minipass@2.1.0: - dependencies: - minipass: 3.3.6 - fs.realpath@1.0.0: {} - fsevents@2.1.3: - optional: true - fsevents@2.3.3: optional: true @@ -10694,22 +12772,12 @@ snapshots: functions-have-names@1.2.3: {} - gauge@3.0.2: - dependencies: - aproba: 2.1.0 - color-support: 1.1.3 - console-control-strings: 1.1.0 - has-unicode: 2.0.1 - object-assign: 4.1.1 - signal-exit: 3.0.7 - string-width: 4.2.3 - strip-ansi: 6.0.1 - wide-align: 1.1.5 - - generic-pool@3.4.2: {} + gensync@1.0.0-beta.2: {} get-browser-rtc@1.1.0: {} + get-caller-file@2.0.5: {} + get-intrinsic@1.3.0: dependencies: call-bind-apply-helpers: 1.0.2 @@ -10725,14 +12793,14 @@ snapshots: get-nonce@1.0.1: {} + get-package-type@0.1.0: {} + get-proto@1.0.1: dependencies: dunder-proto: 1.0.1 es-object-atoms: 1.1.1 - get-stream@5.2.0: - dependencies: - pump: 3.0.3 + get-stream@6.0.1: {} get-symbol-description@1.1.0: dependencies: @@ -10744,16 +12812,6 @@ snapshots: dependencies: resolve-pkg-maps: 1.0.0 - giget@1.2.5: - dependencies: - citty: 0.1.6 - consola: 3.4.2 - defu: 6.1.4 - node-fetch-native: 1.6.7 - nypm: 0.5.4 - pathe: 2.0.3 - tar: 6.2.1 - glob-parent@5.1.2: dependencies: is-glob: 4.0.3 @@ -10762,13 +12820,7 @@ snapshots: dependencies: is-glob: 4.0.3 - glob@10.3.10: - dependencies: - foreground-child: 3.3.1 - jackspeak: 2.3.6 - minimatch: 9.0.5 - minipass: 7.1.2 - path-scurry: 1.11.1 + glob-to-regexp@0.4.1: {} glob@10.4.5: dependencies: @@ -10788,13 +12840,12 @@ snapshots: once: 1.4.0 path-is-absolute: 1.0.1 - glob@8.1.0: + glob@9.3.5: dependencies: fs.realpath: 1.0.0 - inflight: 1.0.6 - inherits: 2.0.4 - minimatch: 5.1.6 - once: 1.4.0 + minimatch: 8.0.4 + minipass: 4.2.8 + path-scurry: 1.11.1 globals@14.0.0: {} @@ -10842,8 +12893,6 @@ snapshots: dependencies: has-symbols: 1.1.0 - has-unicode@2.0.1: {} - hasown@2.0.2: dependencies: function-bind: 1.1.2 @@ -10885,21 +12934,10 @@ snapshots: dependencies: react-is: 16.13.1 + html-escaper@2.0.2: {} + html-url-attributes@3.0.1: {} - http-errors@1.4.0: - dependencies: - inherits: 2.0.1 - statuses: 1.5.0 - - http-errors@1.7.3: - dependencies: - depd: 1.1.2 - inherits: 2.0.4 - setprototypeof: 1.1.1 - statuses: 1.5.0 - toidentifier: 1.0.0 - https-proxy-agent@5.0.1: dependencies: agent-base: 6.0.2 @@ -10907,22 +12945,23 @@ snapshots: transitivePeerDependencies: - supports-color - human-signals@1.1.1: {} + https-proxy-agent@7.0.6(supports-color@10.2.0): + dependencies: + agent-base: 7.1.4 + debug: 4.4.1(supports-color@10.2.0) + transitivePeerDependencies: + - supports-color + + human-signals@2.1.0: {} hyperhtml-style@0.1.3: {} - iconv-lite@0.4.24: - dependencies: - safer-buffer: 2.1.2 - ieee754@1.2.1: {} ignore@5.3.2: {} ignore@7.0.5: {} - immediate@3.0.6: {} - immer@10.1.1: {} immutable@5.1.3: {} @@ -10932,15 +12971,27 @@ snapshots: parent-module: 1.0.1 resolve-from: 4.0.0 + import-in-the-middle@1.14.2: + dependencies: + acorn: 8.15.0 + acorn-import-attributes: 1.9.5(acorn@8.15.0) + cjs-module-lexer: 1.4.3 + module-details-from-path: 1.0.4 + + import-local@3.2.0: + dependencies: + pkg-dir: 4.2.0 + resolve-cwd: 3.0.0 + imurmurhash@0.1.4: {} + index-to-position@1.1.0: {} + inflight@1.0.6: dependencies: once: 1.4.0 wrappy: 1.0.2 - inherits@2.0.1: {} - inherits@2.0.4: {} inline-style-parser@0.2.4: {} @@ -10953,7 +13004,7 @@ snapshots: ioredis@5.7.0: dependencies: - "@ioredis/commands": 1.3.0 + "@ioredis/commands": 1.3.1 cluster-key-slot: 1.1.2 debug: 4.4.1(supports-color@9.4.0) denque: 2.1.0 @@ -10991,6 +13042,9 @@ snapshots: is-arrayish@0.2.1: {} + is-arrayish@0.3.2: + optional: true + is-async-function@2.1.1: dependencies: async-function: 1.0.0 @@ -11043,6 +13097,8 @@ snapshots: is-fullwidth-code-point@3.0.0: {} + is-generator-fn@2.1.0: {} + is-generator-function@1.1.0: dependencies: call-bound: 1.0.4 @@ -11116,12 +13172,41 @@ snapshots: call-bound: 1.0.4 get-intrinsic: 1.3.0 - isarray@0.0.1: {} - isarray@2.0.5: {} isexe@2.0.0: {} + istanbul-lib-coverage@3.2.2: {} + + istanbul-lib-instrument@6.0.3: + dependencies: + "@babel/core": 7.28.3 + "@babel/parser": 7.28.0 + "@istanbuljs/schema": 0.1.3 + istanbul-lib-coverage: 3.2.2 + semver: 7.7.2 + transitivePeerDependencies: + - supports-color + + istanbul-lib-report@3.0.1: + dependencies: + istanbul-lib-coverage: 3.2.2 + make-dir: 4.0.0 + supports-color: 7.2.0 + + istanbul-lib-source-maps@5.0.6: + dependencies: + "@jridgewell/trace-mapping": 0.3.30 + debug: 4.4.1(supports-color@9.4.0) + istanbul-lib-coverage: 3.2.2 + transitivePeerDependencies: + - supports-color + + istanbul-reports@3.2.0: + dependencies: + html-escaper: 2.0.2 + istanbul-lib-report: 3.0.1 + iterator.prototype@1.1.5: dependencies: define-data-property: 1.1.4 @@ -11131,18 +13216,274 @@ snapshots: has-symbols: 1.1.0 set-function-name: 2.0.2 - jackspeak@2.3.6: - dependencies: - "@isaacs/cliui": 8.0.2 - optionalDependencies: - "@pkgjs/parseargs": 0.11.0 - jackspeak@3.4.3: dependencies: "@isaacs/cliui": 8.0.2 optionalDependencies: "@pkgjs/parseargs": 0.11.0 + jest-changed-files@30.0.5: + dependencies: + execa: 5.1.1 + jest-util: 30.0.5 + p-limit: 3.1.0 + + jest-circus@30.1.3(babel-plugin-macros@3.1.0): + dependencies: + "@jest/environment": 30.1.2 + "@jest/expect": 30.1.2 + "@jest/test-result": 30.1.3 + "@jest/types": 30.0.5 + "@types/node": 24.2.1 + chalk: 4.1.2 + co: 4.6.0 + dedent: 1.7.0(babel-plugin-macros@3.1.0) + is-generator-fn: 2.1.0 + jest-each: 30.1.0 + jest-matcher-utils: 30.1.2 + jest-message-util: 30.1.0 + jest-runtime: 30.1.3 + jest-snapshot: 30.1.2 + jest-util: 30.0.5 + p-limit: 3.1.0 + pretty-format: 30.0.5 + pure-rand: 7.0.1 + slash: 3.0.0 + stack-utils: 2.0.6 + transitivePeerDependencies: + - babel-plugin-macros + - supports-color + + jest-cli@30.1.3(@types/node@24.2.1)(babel-plugin-macros@3.1.0)(ts-node@10.9.1(@types/node@24.2.1)(typescript@5.9.2)): + dependencies: + "@jest/core": 30.1.3(babel-plugin-macros@3.1.0)(ts-node@10.9.1(@types/node@24.2.1)(typescript@5.9.2)) + "@jest/test-result": 30.1.3 + "@jest/types": 30.0.5 + chalk: 4.1.2 + exit-x: 0.2.2 + import-local: 3.2.0 + jest-config: 30.1.3(@types/node@24.2.1)(babel-plugin-macros@3.1.0)(ts-node@10.9.1(@types/node@24.2.1)(typescript@5.9.2)) + jest-util: 30.0.5 + jest-validate: 30.1.0 + yargs: 17.7.2 + transitivePeerDependencies: + - "@types/node" + - babel-plugin-macros + - esbuild-register + - supports-color + - ts-node + + jest-config@30.1.3(@types/node@24.2.1)(babel-plugin-macros@3.1.0)(ts-node@10.9.1(@types/node@24.2.1)(typescript@5.9.2)): + dependencies: + "@babel/core": 7.28.3 + "@jest/get-type": 30.1.0 + "@jest/pattern": 30.0.1 + "@jest/test-sequencer": 30.1.3 + "@jest/types": 30.0.5 + babel-jest: 30.1.2(@babel/core@7.28.3) + chalk: 4.1.2 + ci-info: 4.3.0 + deepmerge: 4.3.1 + glob: 10.4.5 + graceful-fs: 4.2.11 + jest-circus: 30.1.3(babel-plugin-macros@3.1.0) + jest-docblock: 30.0.1 + jest-environment-node: 30.1.2 + jest-regex-util: 30.0.1 + jest-resolve: 30.1.3 + jest-runner: 30.1.3 + jest-util: 30.0.5 + jest-validate: 30.1.0 + micromatch: 4.0.8 + parse-json: 5.2.0 + pretty-format: 30.0.5 + slash: 3.0.0 + strip-json-comments: 3.1.1 + optionalDependencies: + "@types/node": 24.2.1 + ts-node: 10.9.1(@types/node@24.2.1)(typescript@5.9.2) + transitivePeerDependencies: + - babel-plugin-macros + - supports-color + + jest-diff@30.1.2: + dependencies: + "@jest/diff-sequences": 30.0.1 + "@jest/get-type": 30.1.0 + chalk: 4.1.2 + pretty-format: 30.0.5 + + jest-docblock@30.0.1: + dependencies: + detect-newline: 3.1.0 + + jest-each@30.1.0: + dependencies: + "@jest/get-type": 30.1.0 + "@jest/types": 30.0.5 + chalk: 4.1.2 + jest-util: 30.0.5 + pretty-format: 30.0.5 + + jest-environment-node@30.1.2: + dependencies: + "@jest/environment": 30.1.2 + "@jest/fake-timers": 30.1.2 + "@jest/types": 30.0.5 + "@types/node": 24.2.1 + jest-mock: 30.0.5 + jest-util: 30.0.5 + jest-validate: 30.1.0 + + jest-haste-map@30.1.0: + dependencies: + "@jest/types": 30.0.5 + "@types/node": 24.2.1 + anymatch: 3.1.3 + fb-watchman: 2.0.2 + graceful-fs: 4.2.11 + jest-regex-util: 30.0.1 + jest-util: 30.0.5 + jest-worker: 30.1.0 + micromatch: 4.0.8 + walker: 1.0.8 + optionalDependencies: + fsevents: 2.3.3 + + jest-leak-detector@30.1.0: + dependencies: + "@jest/get-type": 30.1.0 + pretty-format: 30.0.5 + + jest-matcher-utils@30.1.2: + dependencies: + "@jest/get-type": 30.1.0 + chalk: 4.1.2 + jest-diff: 30.1.2 + pretty-format: 30.0.5 + + jest-message-util@30.1.0: + dependencies: + "@babel/code-frame": 7.27.1 + "@jest/types": 30.0.5 + "@types/stack-utils": 2.0.3 + chalk: 4.1.2 + graceful-fs: 4.2.11 + micromatch: 4.0.8 + pretty-format: 30.0.5 + slash: 3.0.0 + stack-utils: 2.0.6 + + jest-mock@30.0.5: + dependencies: + "@jest/types": 30.0.5 + "@types/node": 24.2.1 + jest-util: 30.0.5 + + jest-pnp-resolver@1.2.3(jest-resolve@30.1.3): + optionalDependencies: + jest-resolve: 30.1.3 + + jest-regex-util@30.0.1: {} + + jest-resolve-dependencies@30.1.3: + dependencies: + jest-regex-util: 30.0.1 + jest-snapshot: 30.1.2 + transitivePeerDependencies: + - supports-color + + jest-resolve@30.1.3: + dependencies: + chalk: 4.1.2 + graceful-fs: 4.2.11 + jest-haste-map: 30.1.0 + jest-pnp-resolver: 1.2.3(jest-resolve@30.1.3) + jest-util: 30.0.5 + jest-validate: 30.1.0 + slash: 3.0.0 + unrs-resolver: 1.11.1 + + jest-runner@30.1.3: + dependencies: + "@jest/console": 30.1.2 + "@jest/environment": 30.1.2 + "@jest/test-result": 30.1.3 + "@jest/transform": 30.1.2 + "@jest/types": 30.0.5 + "@types/node": 24.2.1 + chalk: 4.1.2 + emittery: 0.13.1 + exit-x: 0.2.2 + graceful-fs: 4.2.11 + jest-docblock: 30.0.1 + jest-environment-node: 30.1.2 + jest-haste-map: 30.1.0 + jest-leak-detector: 30.1.0 + jest-message-util: 30.1.0 + jest-resolve: 30.1.3 + jest-runtime: 30.1.3 + jest-util: 30.0.5 + jest-watcher: 30.1.3 + jest-worker: 30.1.0 + p-limit: 3.1.0 + source-map-support: 0.5.13 + transitivePeerDependencies: + - supports-color + + jest-runtime@30.1.3: + dependencies: + "@jest/environment": 30.1.2 + "@jest/fake-timers": 30.1.2 + "@jest/globals": 30.1.2 + "@jest/source-map": 30.0.1 + "@jest/test-result": 30.1.3 + "@jest/transform": 30.1.2 + "@jest/types": 30.0.5 + "@types/node": 24.2.1 + chalk: 4.1.2 + cjs-module-lexer: 2.1.0 + collect-v8-coverage: 1.0.2 + glob: 10.4.5 + graceful-fs: 4.2.11 + jest-haste-map: 30.1.0 + jest-message-util: 30.1.0 + jest-mock: 30.0.5 + jest-regex-util: 30.0.1 + jest-resolve: 30.1.3 + jest-snapshot: 30.1.2 + jest-util: 30.0.5 + slash: 3.0.0 + strip-bom: 4.0.0 + transitivePeerDependencies: + - supports-color + + jest-snapshot@30.1.2: + dependencies: + "@babel/core": 7.28.3 + "@babel/generator": 7.28.0 + "@babel/plugin-syntax-jsx": 7.27.1(@babel/core@7.28.3) + "@babel/plugin-syntax-typescript": 7.27.1(@babel/core@7.28.3) + "@babel/types": 7.28.2 + "@jest/expect-utils": 30.1.2 + "@jest/get-type": 30.1.0 + "@jest/snapshot-utils": 30.1.2 + "@jest/transform": 30.1.2 + "@jest/types": 30.0.5 + babel-preset-current-node-syntax: 1.2.0(@babel/core@7.28.3) + chalk: 4.1.2 + expect: 30.1.2 + graceful-fs: 4.2.11 + jest-diff: 30.1.2 + jest-matcher-utils: 30.1.2 + jest-message-util: 30.1.0 + jest-util: 30.0.5 + pretty-format: 30.0.5 + semver: 7.7.2 + synckit: 0.11.11 + transitivePeerDependencies: + - supports-color + jest-util@29.7.0: dependencies: "@jest/types": 29.6.3 @@ -11152,6 +13493,41 @@ snapshots: graceful-fs: 4.2.11 picomatch: 2.3.1 + jest-util@30.0.5: + dependencies: + "@jest/types": 30.0.5 + "@types/node": 24.2.1 + chalk: 4.1.2 + ci-info: 4.3.0 + graceful-fs: 4.2.11 + picomatch: 4.0.3 + + jest-validate@30.1.0: + dependencies: + "@jest/get-type": 30.1.0 + "@jest/types": 30.0.5 + camelcase: 6.3.0 + chalk: 4.1.2 + leven: 3.1.0 + pretty-format: 30.0.5 + + jest-watcher@30.1.3: + dependencies: + "@jest/test-result": 30.1.3 + "@jest/types": 30.0.5 + "@types/node": 24.2.1 + ansi-escapes: 4.3.2 + chalk: 4.1.2 + emittery: 0.13.1 + jest-util: 30.0.5 + string-length: 4.0.2 + + jest-worker@27.5.1: + dependencies: + "@types/node": 24.3.1 + merge-stream: 2.0.0 + supports-color: 8.1.1 + jest-worker@29.7.0: dependencies: "@types/node": 24.2.1 @@ -11159,12 +13535,40 @@ snapshots: merge-stream: 2.0.0 supports-color: 8.1.1 + jest-worker@30.1.0: + dependencies: + "@types/node": 24.2.1 + "@ungap/structured-clone": 1.3.0 + jest-util: 30.0.5 + merge-stream: 2.0.0 + supports-color: 8.1.1 + + jest@30.1.3(@types/node@24.2.1)(babel-plugin-macros@3.1.0)(ts-node@10.9.1(@types/node@24.2.1)(typescript@5.9.2)): + dependencies: + "@jest/core": 30.1.3(babel-plugin-macros@3.1.0)(ts-node@10.9.1(@types/node@24.2.1)(typescript@5.9.2)) + "@jest/types": 30.0.5 + import-local: 3.2.0 + jest-cli: 30.1.3(@types/node@24.2.1)(babel-plugin-macros@3.1.0)(ts-node@10.9.1(@types/node@24.2.1)(typescript@5.9.2)) + transitivePeerDependencies: + - "@types/node" + - babel-plugin-macros + - esbuild-register + - supports-color + - ts-node + jiti@1.21.7: {} jose@4.15.9: {} + js-levenshtein@1.1.6: {} + js-tokens@4.0.0: {} + js-yaml@3.14.1: + dependencies: + argparse: 1.0.10 + esprima: 4.0.1 + js-yaml@4.1.0: dependencies: argparse: 2.0.1 @@ -11177,11 +13581,6 @@ snapshots: json-parse-even-better-errors@2.3.1: {} - json-schema-to-ts@1.6.4: - dependencies: - "@types/json-schema": 7.0.15 - ts-toolbelt: 6.15.5 - json-schema-traverse@0.4.1: {} json-schema-traverse@1.0.0: {} @@ -11192,15 +13591,7 @@ snapshots: dependencies: minimist: 1.2.8 - jsonfile@4.0.0: - optionalDependencies: - graceful-fs: 4.2.11 - - jsonfile@6.2.0: - dependencies: - universalify: 2.0.1 - optionalDependencies: - graceful-fs: 4.2.11 + json5@2.2.3: {} jsx-ast-utils@3.3.5: dependencies: @@ -11219,15 +13610,13 @@ snapshots: dependencies: language-subtag-registry: 0.3.23 + leven@3.1.0: {} + levn@0.4.1: dependencies: prelude-ls: 1.2.1 type-check: 0.4.0 - lie@3.1.1: - dependencies: - immediate: 3.0.6 - lighterhtml@4.2.0: dependencies: "@ungap/create-content": 0.2.0 @@ -11244,9 +13633,11 @@ snapshots: lines-and-columns@1.2.4: {} - localforage@1.10.0: + loader-runner@4.3.0: {} + + locate-path@5.0.0: dependencies: - lie: 3.1.1 + p-locate: 4.1.0 locate-path@6.0.0: dependencies: @@ -11256,6 +13647,8 @@ snapshots: lodash.isarguments@3.1.0: {} + lodash.memoize@4.1.2: {} + lodash.merge@4.6.2: {} longest-streak@3.1.0: {} @@ -11266,6 +13659,10 @@ snapshots: lru-cache@10.4.3: {} + lru-cache@5.1.1: + dependencies: + yallist: 3.1.1 + lru-cache@6.0.0: dependencies: yallist: 4.0.0 @@ -11274,16 +13671,24 @@ snapshots: dependencies: react: 18.3.1 - magic-string@0.27.0: + magic-string@0.30.19: dependencies: "@jridgewell/sourcemap-codec": 1.5.5 - make-dir@3.1.0: + magic-string@0.30.8: dependencies: - semver: 6.3.1 + "@jridgewell/sourcemap-codec": 1.5.5 + + make-dir@4.0.0: + dependencies: + semver: 7.7.2 make-error@1.3.6: {} + makeerror@1.0.12: + dependencies: + tmpl: 1.0.5 + math-intrinsics@1.1.0: {} mdast-util-from-markdown@2.0.2: @@ -11395,12 +13800,6 @@ snapshots: merge2@1.4.1: {} - micro@9.3.5-canary.3: - dependencies: - arg: 4.1.0 - content-type: 1.0.4 - raw-body: 2.4.1 - micromark-core-commonmark@2.0.3: dependencies: decode-named-character-reference: 1.2.0 @@ -11555,52 +13954,23 @@ snapshots: dependencies: brace-expansion: 2.0.2 + minimatch@8.0.4: + dependencies: + brace-expansion: 2.0.2 + minimatch@9.0.5: dependencies: brace-expansion: 2.0.2 minimist@1.2.8: {} - minipass@2.9.0: - dependencies: - safe-buffer: 5.2.1 - yallist: 3.1.1 - - minipass@3.3.6: - dependencies: - yallist: 4.0.0 - - minipass@5.0.0: {} + minipass@4.2.8: {} minipass@7.1.2: {} - minizlib@1.3.3: - dependencies: - minipass: 2.9.0 - - minizlib@2.1.2: - dependencies: - minipass: 3.3.6 - yallist: 4.0.0 - mitt@3.0.1: {} - mkdirp@0.5.6: - dependencies: - minimist: 1.2.8 - - mkdirp@1.0.4: {} - - mlly@1.7.4: - dependencies: - acorn: 8.15.0 - pathe: 2.0.3 - pkg-types: 1.3.1 - ufo: 1.6.1 - - mri@1.2.0: {} - - ms@2.1.1: {} + module-details-from-path@1.0.4: {} ms@2.1.3: {} @@ -11618,13 +13988,13 @@ snapshots: neo-async@2.6.2: {} - next-auth@4.24.11(next@14.2.31(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.90.0))(react-dom@18.3.1(react@18.3.1))(react@18.3.1): + next-auth@4.24.11(next@15.5.3(@babel/core@7.28.3)(@opentelemetry/api@1.9.0)(babel-plugin-macros@3.1.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.90.0))(react-dom@18.3.1(react@18.3.1))(react@18.3.1): dependencies: "@babel/runtime": 7.28.2 "@panva/hkdf": 1.2.1 cookie: 0.7.2 jose: 4.15.9 - next: 14.2.31(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.90.0) + next: 15.5.3(@babel/core@7.28.3)(@opentelemetry/api@1.9.0)(babel-plugin-macros@3.1.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.90.0) oauth: 0.9.15 openid-client: 5.7.1 preact: 10.27.0 @@ -11638,28 +14008,27 @@ snapshots: react: 18.3.1 react-dom: 18.3.1(react@18.3.1) - next@14.2.31(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.90.0): + next@15.5.3(@babel/core@7.28.3)(@opentelemetry/api@1.9.0)(babel-plugin-macros@3.1.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.90.0): dependencies: - "@next/env": 14.2.31 - "@swc/helpers": 0.5.5 - busboy: 1.6.0 + "@next/env": 15.5.3 + "@swc/helpers": 0.5.15 caniuse-lite: 1.0.30001734 - graceful-fs: 4.2.11 postcss: 8.4.31 react: 18.3.1 react-dom: 18.3.1(react@18.3.1) - styled-jsx: 5.1.1(react@18.3.1) + styled-jsx: 5.1.6(@babel/core@7.28.3)(babel-plugin-macros@3.1.0)(react@18.3.1) optionalDependencies: - "@next/swc-darwin-arm64": 14.2.31 - "@next/swc-darwin-x64": 14.2.31 - "@next/swc-linux-arm64-gnu": 14.2.31 - "@next/swc-linux-arm64-musl": 14.2.31 - "@next/swc-linux-x64-gnu": 14.2.31 - "@next/swc-linux-x64-musl": 14.2.31 - "@next/swc-win32-arm64-msvc": 14.2.31 - "@next/swc-win32-ia32-msvc": 14.2.31 - "@next/swc-win32-x64-msvc": 14.2.31 + "@next/swc-darwin-arm64": 15.5.3 + "@next/swc-darwin-x64": 15.5.3 + "@next/swc-linux-arm64-gnu": 15.5.3 + "@next/swc-linux-arm64-musl": 15.5.3 + "@next/swc-linux-x64-gnu": 15.5.3 + "@next/swc-linux-x64-musl": 15.5.3 + "@next/swc-win32-arm64-msvc": 15.5.3 + "@next/swc-win32-x64-msvc": 15.5.3 + "@opentelemetry/api": 1.9.0 sass: 1.90.0 + sharp: 0.34.3 transitivePeerDependencies: - "@babel/core" - babel-plugin-macros @@ -11669,28 +14038,14 @@ snapshots: node-addon-api@7.1.1: optional: true - node-fetch-native@1.6.7: {} - - node-fetch@2.6.7: - dependencies: - whatwg-url: 5.0.0 - - node-fetch@2.6.9: - dependencies: - whatwg-url: 5.0.0 - node-fetch@2.7.0: dependencies: whatwg-url: 5.0.0 - node-gyp-build@4.8.4: {} + node-int64@0.4.0: {} node-releases@2.0.19: {} - nopt@5.0.0: - dependencies: - abbrev: 1.1.1 - normalize-path@3.0.0: {} normalize-range@0.1.2: {} @@ -11699,28 +14054,12 @@ snapshots: dependencies: path-key: 3.1.1 - npmlog@5.0.1: - dependencies: - are-we-there-yet: 2.0.0 - console-control-strings: 1.1.0 - gauge: 3.0.2 - set-blocking: 2.0.0 - - nuqs@2.4.3(next@14.2.31(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.90.0))(react@18.3.1): + nuqs@2.4.3(next@15.5.3(@babel/core@7.28.3)(@opentelemetry/api@1.9.0)(babel-plugin-macros@3.1.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.90.0))(react@18.3.1): dependencies: mitt: 3.0.1 react: 18.3.1 optionalDependencies: - next: 14.2.31(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.90.0) - - nypm@0.5.4: - dependencies: - citty: 0.1.6 - consola: 3.4.2 - pathe: 2.0.3 - pkg-types: 1.3.1 - tinyexec: 0.3.2 - ufo: 1.6.1 + next: 15.5.3(@babel/core@7.28.3)(@opentelemetry/api@1.9.0)(babel-plugin-macros@3.1.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.90.0) oauth@0.9.15: {} @@ -11770,14 +14109,8 @@ snapshots: define-properties: 1.2.1 es-object-atoms: 1.1.1 - ohash@1.1.6: {} - oidc-token-hash@5.1.1: {} - once@1.3.3: - dependencies: - wrappy: 1.0.2 - once@1.4.0: dependencies: wrappy: 1.0.2 @@ -11786,6 +14119,28 @@ snapshots: dependencies: mimic-fn: 2.1.0 + openapi-fetch@0.14.0: + dependencies: + openapi-typescript-helpers: 0.0.15 + + openapi-react-query@0.5.0(@tanstack/react-query@5.85.9(react@18.3.1))(openapi-fetch@0.14.0): + dependencies: + "@tanstack/react-query": 5.85.9(react@18.3.1) + openapi-fetch: 0.14.0 + openapi-typescript-helpers: 0.0.15 + + openapi-typescript-helpers@0.0.15: {} + + openapi-typescript@7.9.1(typescript@5.9.2): + dependencies: + "@redocly/openapi-core": 1.34.5(supports-color@10.2.0) + ansi-colors: 4.1.3 + change-case: 5.4.4 + parse-json: 8.3.0 + supports-color: 10.2.0 + typescript: 5.9.2 + yargs-parser: 21.1.1 + openid-client@5.7.1: dependencies: jose: 4.15.9 @@ -11802,24 +14157,30 @@ snapshots: type-check: 0.4.0 word-wrap: 1.2.5 - os-paths@4.4.0: {} - own-keys@1.0.1: dependencies: get-intrinsic: 1.3.0 object-keys: 1.1.1 safe-push-apply: 1.0.0 - p-finally@2.0.1: {} + p-limit@2.3.0: + dependencies: + p-try: 2.2.0 p-limit@3.1.0: dependencies: yocto-queue: 0.1.0 + p-locate@4.1.0: + dependencies: + p-limit: 2.3.0 + p-locate@5.0.0: dependencies: p-limit: 3.1.0 + p-try@2.2.0: {} + package-json-from-dist@1.0.1: {} parent-module@1.0.1: @@ -11843,9 +14204,11 @@ snapshots: json-parse-even-better-errors: 2.3.1 lines-and-columns: 1.2.4 - parse-ms@2.1.0: {} - - path-browserify@1.0.1: {} + parse-json@8.3.0: + dependencies: + "@babel/code-frame": 7.27.1 + index-to-position: 1.1.0 + type-fest: 4.41.0 path-exists@4.0.0: {} @@ -11853,11 +14216,6 @@ snapshots: path-key@3.1.1: {} - path-match@1.2.4: - dependencies: - http-errors: 1.4.0 - path-to-regexp: 1.9.0 - path-parse@1.0.7: {} path-scurry@1.11.1: @@ -11865,27 +14223,21 @@ snapshots: lru-cache: 10.4.3 minipass: 7.1.2 - path-to-regexp@1.9.0: - dependencies: - isarray: 0.0.1 - - path-to-regexp@6.1.0: {} - - path-to-regexp@6.2.1: {} - path-type@4.0.0: {} - pathe@1.1.2: {} - - pathe@2.0.3: {} - - pend@1.2.0: {} - - perfect-debounce@1.0.0: {} - perfect-freehand@1.2.2: {} - picocolors@1.0.0: {} + pg-int8@1.0.1: {} + + pg-protocol@1.10.3: {} + + pg-types@2.2.0: + dependencies: + pg-int8: 1.0.1 + postgres-array: 2.0.0 + postgres-bytea: 1.0.0 + postgres-date: 1.0.7 + postgres-interval: 1.2.0 picocolors@1.1.1: {} @@ -11897,11 +14249,11 @@ snapshots: pirates@4.0.7: {} - pkg-types@1.3.1: + pkg-dir@4.2.0: dependencies: - confbox: 0.1.8 - mlly: 1.7.4 - pathe: 2.0.3 + find-up: 4.1.0 + + pluralize@8.0.0: {} possible-typed-array-names@1.1.0: {} @@ -11917,13 +14269,13 @@ snapshots: camelcase-css: 2.0.1 postcss: 8.5.6 - postcss-load-config@4.0.2(postcss@8.5.6)(ts-node@10.9.1(@types/node@16.18.11)(typescript@5.9.2)): + postcss-load-config@4.0.2(postcss@8.5.6)(ts-node@10.9.1(@types/node@24.2.1)(typescript@5.9.2)): dependencies: lilconfig: 3.1.3 yaml: 2.8.1 optionalDependencies: postcss: 8.5.6 - ts-node: 10.9.1(@types/node@16.18.11)(typescript@5.9.2) + ts-node: 10.9.1(@types/node@24.2.1)(typescript@5.9.2) postcss-nested@6.2.0(postcss@8.5.6): dependencies: @@ -11949,6 +14301,16 @@ snapshots: picocolors: 1.1.1 source-map-js: 1.2.1 + postgres-array@2.0.0: {} + + postgres-bytea@1.0.0: {} + + postgres-date@1.0.7: {} + + postgres-interval@1.2.0: + dependencies: + xtend: 4.0.2 + preact-render-to-string@5.2.6(preact@10.27.0): dependencies: preact: 10.27.0 @@ -11962,14 +14324,14 @@ snapshots: pretty-format@3.8.0: {} - pretty-ms@7.0.1: + pretty-format@30.0.5: dependencies: - parse-ms: 2.1.0 + "@jest/schemas": 30.0.5 + ansi-styles: 5.2.0 + react-is: 18.3.1 progress@2.0.3: {} - promisepipe@3.0.0: {} - prop-types@15.8.1: dependencies: loose-envify: 1.4.0 @@ -11986,13 +14348,10 @@ snapshots: dependencies: proxy-compare: 3.0.1 - pump@3.0.3: - dependencies: - end-of-stream: 1.4.5 - once: 1.4.0 - punycode@2.3.1: {} + pure-rand@7.0.1: {} + qr.js@0.0.0: {} queue-microtask@1.2.3: {} @@ -12001,18 +14360,6 @@ snapshots: dependencies: safe-buffer: 5.2.1 - raw-body@2.4.1: - dependencies: - bytes: 3.1.0 - http-errors: 1.7.3 - iconv-lite: 0.4.24 - unpipe: 1.0.0 - - rc9@2.1.2: - dependencies: - defu: 6.1.4 - destr: 2.0.5 - react-dom@18.3.1(react@18.3.1): dependencies: loose-envify: 1.4.0 @@ -12031,6 +14378,8 @@ snapshots: react-is@16.13.1: {} + react-is@18.3.1: {} + react-markdown@9.1.0(@types/react@18.2.20)(react@18.3.1): dependencies: "@types/hast": 3.0.4 @@ -12102,10 +14451,6 @@ snapshots: string_decoder: 1.3.0 util-deprecate: 1.0.2 - readdirp@3.3.0: - dependencies: - picomatch: 2.3.1 - readdirp@3.6.0: dependencies: picomatch: 2.3.1 @@ -12165,12 +14510,26 @@ snapshots: unified: 11.0.5 vfile: 6.0.3 + require-directory@2.1.1: {} + require-from-string@2.0.2: {} + require-in-the-middle@7.5.2: + dependencies: + debug: 4.4.1(supports-color@9.4.0) + module-details-from-path: 1.0.4 + resolve: 1.22.10 + transitivePeerDependencies: + - supports-color + reraf@1.1.1: {} reselect@5.1.1: {} + resolve-cwd@3.0.0: + dependencies: + resolve-from: 5.0.0 + resolve-from@4.0.0: {} resolve-from@5.0.0: {} @@ -12197,12 +14556,31 @@ snapshots: reusify@1.1.0: {} - rimraf@3.0.2: + rollup@4.50.1: dependencies: - glob: 7.2.3 - - rollup@2.79.2: + "@types/estree": 1.0.8 optionalDependencies: + "@rollup/rollup-android-arm-eabi": 4.50.1 + "@rollup/rollup-android-arm64": 4.50.1 + "@rollup/rollup-darwin-arm64": 4.50.1 + "@rollup/rollup-darwin-x64": 4.50.1 + "@rollup/rollup-freebsd-arm64": 4.50.1 + "@rollup/rollup-freebsd-x64": 4.50.1 + "@rollup/rollup-linux-arm-gnueabihf": 4.50.1 + "@rollup/rollup-linux-arm-musleabihf": 4.50.1 + "@rollup/rollup-linux-arm64-gnu": 4.50.1 + "@rollup/rollup-linux-arm64-musl": 4.50.1 + "@rollup/rollup-linux-loongarch64-gnu": 4.50.1 + "@rollup/rollup-linux-ppc64-gnu": 4.50.1 + "@rollup/rollup-linux-riscv64-gnu": 4.50.1 + "@rollup/rollup-linux-riscv64-musl": 4.50.1 + "@rollup/rollup-linux-s390x-gnu": 4.50.1 + "@rollup/rollup-linux-x64-gnu": 4.50.1 + "@rollup/rollup-linux-x64-musl": 4.50.1 + "@rollup/rollup-openharmony-arm64": 4.50.1 + "@rollup/rollup-win32-arm64-msvc": 4.50.1 + "@rollup/rollup-win32-ia32-msvc": 4.50.1 + "@rollup/rollup-win32-x64-msvc": 4.50.1 fsevents: 2.3.3 rtcstats@https://codeload.github.com/whereby/rtcstats/tar.gz/63bcb6420d76d34161b39e494524ae73aa6dd70d: @@ -12235,8 +14613,6 @@ snapshots: es-errors: 1.3.0 is-regex: 1.2.1 - safer-buffer@2.1.2: {} - sass@1.90.0: dependencies: chokidar: 4.0.3 @@ -12249,19 +14625,24 @@ snapshots: dependencies: loose-envify: 1.4.0 + schema-utils@4.3.2: + dependencies: + "@types/json-schema": 7.0.15 + ajv: 8.17.1 + ajv-formats: 2.1.1(ajv@8.17.1) + ajv-keywords: 5.1.0(ajv@8.17.1) + sdp-transform@2.15.0: {} sdp@3.2.1: {} semver@6.3.1: {} - semver@7.3.5: - dependencies: - lru-cache: 6.0.0 - semver@7.7.2: {} - set-blocking@2.0.0: {} + serialize-javascript@6.0.2: + dependencies: + randombytes: 2.1.0 set-function-length@1.2.2: dependencies: @@ -12285,7 +14666,35 @@ snapshots: es-errors: 1.3.0 es-object-atoms: 1.1.1 - setprototypeof@1.1.1: {} + sharp@0.34.3: + dependencies: + color: 4.2.3 + detect-libc: 2.0.4 + semver: 7.7.2 + optionalDependencies: + "@img/sharp-darwin-arm64": 0.34.3 + "@img/sharp-darwin-x64": 0.34.3 + "@img/sharp-libvips-darwin-arm64": 1.2.0 + "@img/sharp-libvips-darwin-x64": 1.2.0 + "@img/sharp-libvips-linux-arm": 1.2.0 + "@img/sharp-libvips-linux-arm64": 1.2.0 + "@img/sharp-libvips-linux-ppc64": 1.2.0 + "@img/sharp-libvips-linux-s390x": 1.2.0 + "@img/sharp-libvips-linux-x64": 1.2.0 + "@img/sharp-libvips-linuxmusl-arm64": 1.2.0 + "@img/sharp-libvips-linuxmusl-x64": 1.2.0 + "@img/sharp-linux-arm": 0.34.3 + "@img/sharp-linux-arm64": 0.34.3 + "@img/sharp-linux-ppc64": 0.34.3 + "@img/sharp-linux-s390x": 0.34.3 + "@img/sharp-linux-x64": 0.34.3 + "@img/sharp-linuxmusl-arm64": 0.34.3 + "@img/sharp-linuxmusl-x64": 0.34.3 + "@img/sharp-wasm32": 0.34.3 + "@img/sharp-win32-arm64": 0.34.3 + "@img/sharp-win32-ia32": 0.34.3 + "@img/sharp-win32-x64": 0.34.3 + optional: true shebang-command@2.0.0: dependencies: @@ -12293,6 +14702,8 @@ snapshots: shebang-regex@3.0.0: {} + shimmer@1.2.1: {} + side-channel-list@1.0.0: dependencies: es-errors: 1.3.0 @@ -12323,8 +14734,6 @@ snapshots: signal-exit@3.0.7: {} - signal-exit@4.0.2: {} - signal-exit@4.1.0: {} simple-peer@9.11.1: @@ -12339,6 +14748,13 @@ snapshots: transitivePeerDependencies: - supports-color + simple-swizzle@0.2.2: + dependencies: + is-arrayish: 0.3.2 + optional: true + + slash@3.0.0: {} + socket.io-client@4.7.2: dependencies: "@socket.io/component-emitter": 3.1.2 @@ -12359,42 +14775,47 @@ snapshots: source-map-js@1.2.1: {} + source-map-support@0.5.13: + dependencies: + buffer-from: 1.1.2 + source-map: 0.6.1 + + source-map-support@0.5.21: + dependencies: + buffer-from: 1.1.2 + source-map: 0.6.1 + source-map@0.5.7: {} source-map@0.6.1: {} space-separated-tokens@2.0.2: {} + sprintf-js@1.0.3: {} + sprintf-js@1.1.3: {} stable-hash@0.0.5: {} + stack-utils@2.0.6: + dependencies: + escape-string-regexp: 2.0.0 + stacktrace-parser@0.1.11: dependencies: type-fest: 0.7.1 standard-as-callback@2.1.0: {} - stat-mode@0.3.0: {} - - statuses@1.5.0: {} - stop-iteration-iterator@1.1.0: dependencies: es-errors: 1.3.0 internal-slot: 1.1.0 - stream-to-array@2.3.0: + string-length@4.0.2: dependencies: - any-promise: 1.3.0 - - stream-to-promise@2.2.0: - dependencies: - any-promise: 1.3.0 - end-of-stream: 1.1.0 - stream-to-array: 2.3.0 - - streamsearch@1.1.0: {} + char-regex: 1.0.2 + strip-ansi: 6.0.1 string-width@4.2.3: dependencies: @@ -12477,6 +14898,8 @@ snapshots: strip-bom@3.0.0: {} + strip-bom@4.0.0: {} + strip-final-newline@2.0.0: {} strip-json-comments@3.1.1: {} @@ -12489,10 +14912,13 @@ snapshots: dependencies: inline-style-parser: 0.2.4 - styled-jsx@5.1.1(react@18.3.1): + styled-jsx@5.1.6(@babel/core@7.28.3)(babel-plugin-macros@3.1.0)(react@18.3.1): dependencies: client-only: 0.0.1 react: 18.3.1 + optionalDependencies: + "@babel/core": 7.28.3 + babel-plugin-macros: 3.1.0 stylis@4.2.0: {} @@ -12506,6 +14932,8 @@ snapshots: pirates: 4.0.7 ts-interface-checker: 0.1.13 + supports-color@10.2.0: {} + supports-color@7.2.0: dependencies: has-flag: 4.0.0 @@ -12518,7 +14946,11 @@ snapshots: supports-preserve-symlinks-flag@1.0.0: {} - tailwindcss@3.4.17(ts-node@10.9.1(@types/node@16.18.11)(typescript@5.9.2)): + synckit@0.11.11: + dependencies: + "@pkgr/core": 0.2.9 + + tailwindcss@3.4.17(ts-node@10.9.1(@types/node@24.2.1)(typescript@5.9.2)): dependencies: "@alloc/quick-lru": 5.2.0 arg: 5.0.2 @@ -12537,7 +14969,7 @@ snapshots: postcss: 8.5.6 postcss-import: 15.1.0(postcss@8.5.6) postcss-js: 4.0.1(postcss@8.5.6) - postcss-load-config: 4.0.2(postcss@8.5.6)(ts-node@10.9.1(@types/node@16.18.11)(typescript@5.9.2)) + postcss-load-config: 4.0.2(postcss@8.5.6)(ts-node@10.9.1(@types/node@24.2.1)(typescript@5.9.2)) postcss-nested: 6.2.0(postcss@8.5.6) postcss-selector-parser: 6.1.2 resolve: 1.22.10 @@ -12545,24 +14977,29 @@ snapshots: transitivePeerDependencies: - ts-node - tar@4.4.18: - dependencies: - chownr: 1.1.4 - fs-minipass: 1.2.7 - minipass: 2.9.0 - minizlib: 1.3.3 - mkdirp: 0.5.6 - safe-buffer: 5.2.1 - yallist: 3.1.1 + tapable@2.2.3: {} - tar@6.2.1: + terser-webpack-plugin@5.3.14(webpack@5.101.3): dependencies: - chownr: 2.0.0 - fs-minipass: 2.1.0 - minipass: 5.0.0 - minizlib: 2.1.2 - mkdirp: 1.0.4 - yallist: 4.0.0 + "@jridgewell/trace-mapping": 0.3.31 + jest-worker: 27.5.1 + schema-utils: 4.3.2 + serialize-javascript: 6.0.2 + terser: 5.44.0 + webpack: 5.101.3 + + terser@5.44.0: + dependencies: + "@jridgewell/source-map": 0.3.11 + acorn: 8.15.0 + commander: 2.20.3 + source-map-support: 0.5.21 + + test-exclude@6.0.0: + dependencies: + "@istanbuljs/schema": 0.1.3 + glob: 7.2.3 + minimatch: 3.1.2 thenify-all@1.6.0: dependencies: @@ -12572,27 +15009,19 @@ snapshots: dependencies: any-promise: 1.3.0 - time-span@4.0.0: - dependencies: - convert-hrtime: 3.0.0 - - tinyexec@0.3.2: {} - tinyglobby@0.2.14: dependencies: fdir: 6.4.6(picomatch@4.0.3) picomatch: 4.0.3 + tmpl@1.0.5: {} + to-regex-range@5.0.1: dependencies: is-number: 7.0.0 - toidentifier@1.0.0: {} - tr46@0.0.3: {} - tree-kill@1.2.2: {} - trim-lines@3.0.1: {} trough@2.2.0: {} @@ -12603,37 +15032,34 @@ snapshots: ts-interface-checker@0.1.13: {} - ts-morph@12.0.0: + ts-jest@29.4.1(@babel/core@7.28.3)(@jest/transform@30.1.2)(@jest/types@30.0.5)(babel-jest@30.1.2(@babel/core@7.28.3))(jest-util@30.0.5)(jest@30.1.3(@types/node@24.2.1)(babel-plugin-macros@3.1.0)(ts-node@10.9.1(@types/node@24.2.1)(typescript@5.9.2)))(typescript@5.9.2): dependencies: - "@ts-morph/common": 0.11.1 - code-block-writer: 10.1.1 - - ts-node@10.9.1(@types/node@16.18.11)(typescript@4.9.5): - dependencies: - "@cspotcode/source-map-support": 0.8.1 - "@tsconfig/node10": 1.0.11 - "@tsconfig/node12": 1.0.11 - "@tsconfig/node14": 1.0.3 - "@tsconfig/node16": 1.0.4 - "@types/node": 16.18.11 - acorn: 8.15.0 - acorn-walk: 8.3.4 - arg: 4.1.3 - create-require: 1.1.1 - diff: 4.0.2 + bs-logger: 0.2.6 + fast-json-stable-stringify: 2.1.0 + handlebars: 4.7.8 + jest: 30.1.3(@types/node@24.2.1)(babel-plugin-macros@3.1.0)(ts-node@10.9.1(@types/node@24.2.1)(typescript@5.9.2)) + json5: 2.2.3 + lodash.memoize: 4.1.2 make-error: 1.3.6 - typescript: 4.9.5 - v8-compile-cache-lib: 3.0.1 - yn: 3.1.1 + semver: 7.7.2 + type-fest: 4.41.0 + typescript: 5.9.2 + yargs-parser: 21.1.1 + optionalDependencies: + "@babel/core": 7.28.3 + "@jest/transform": 30.1.2 + "@jest/types": 30.0.5 + babel-jest: 30.1.2(@babel/core@7.28.3) + jest-util: 30.0.5 - ts-node@10.9.1(@types/node@16.18.11)(typescript@5.9.2): + ts-node@10.9.1(@types/node@24.2.1)(typescript@5.9.2): dependencies: "@cspotcode/source-map-support": 0.8.1 "@tsconfig/node10": 1.0.11 "@tsconfig/node12": 1.0.11 "@tsconfig/node14": 1.0.3 "@tsconfig/node16": 1.0.4 - "@types/node": 16.18.11 + "@types/node": 24.2.1 acorn: 8.15.0 acorn-walk: 8.3.4 arg: 4.1.3 @@ -12645,8 +15071,6 @@ snapshots: yn: 3.1.1 optional: true - ts-toolbelt@6.15.5: {} - tsconfig-paths@3.15.0: dependencies: "@types/json5": 0.0.29 @@ -12660,8 +15084,14 @@ snapshots: dependencies: prelude-ls: 1.2.1 + type-detect@4.0.8: {} + + type-fest@0.21.3: {} + type-fest@0.7.1: {} + type-fest@4.41.0: {} + typed-array-buffer@1.0.3: dependencies: call-bound: 1.0.4 @@ -12695,8 +15125,6 @@ snapshots: possible-typed-array-names: 1.1.0 reflect.getprototypeof: 1.0.10 - typescript@4.9.5: {} - typescript@5.9.2: {} ua-is-frozen@0.1.2: {} @@ -12717,8 +15145,6 @@ snapshots: udomdiff@1.1.2: {} - ufo@1.6.1: {} - uglify-js@3.19.3: optional: true @@ -12728,8 +15154,6 @@ snapshots: uhyphen@0.1.0: {} - uid-promise@1.0.0: {} - umap@1.0.2: {} unbox-primitive@1.1.0: @@ -12739,14 +15163,8 @@ snapshots: has-symbols: 1.1.0 which-boxed-primitive: 1.1.1 - uncrypto@0.1.3: {} - undici-types@7.10.0: {} - undici@5.28.4: - dependencies: - "@fastify/busboy": 2.1.1 - unified@11.0.5: dependencies: "@types/unist": 3.0.3 @@ -12780,11 +15198,12 @@ snapshots: unist-util-is: 6.0.0 unist-util-visit-parents: 6.0.1 - universalify@0.1.2: {} - - universalify@2.0.1: {} - - unpipe@1.0.0: {} + unplugin@1.0.1: + dependencies: + acorn: 8.15.0 + chokidar: 3.6.0 + webpack-sources: 3.3.3 + webpack-virtual-modules: 0.5.0 unrs-resolver@1.11.1: dependencies: @@ -12818,6 +15237,8 @@ snapshots: uqr@0.1.2: {} + uri-js-replace@1.0.1: {} + uri-js@4.4.1: dependencies: punycode: 2.3.1 @@ -12841,8 +15262,6 @@ snapshots: uuid-validate@0.0.3: {} - uuid@3.3.2: {} - uuid@8.3.2: {} uuid@9.0.1: {} @@ -12851,27 +15270,14 @@ snapshots: dependencies: uarray: 1.0.0 - v8-compile-cache-lib@3.0.1: {} + v8-compile-cache-lib@3.0.1: + optional: true - vercel@37.14.0: + v8-to-istanbul@9.3.0: dependencies: - "@vercel/build-utils": 8.4.12 - "@vercel/fun": 1.1.0 - "@vercel/go": 3.2.0 - "@vercel/hydrogen": 1.0.9 - "@vercel/next": 4.3.18 - "@vercel/node": 3.2.24 - "@vercel/python": 4.3.1 - "@vercel/redwood": 2.1.8 - "@vercel/remix-builder": 2.2.13 - "@vercel/ruby": 2.1.0 - "@vercel/static-build": 2.5.34 - chokidar: 3.3.1 - transitivePeerDependencies: - - "@swc/core" - - "@swc/wasm" - - encoding - - supports-color + "@jridgewell/trace-mapping": 0.3.30 + "@types/istanbul-lib-coverage": 2.0.6 + convert-source-map: 2.0.0 vfile-message@4.0.3: dependencies: @@ -12883,14 +15289,55 @@ snapshots: "@types/unist": 3.0.3 vfile-message: 4.0.3 - wavesurfer.js@7.10.1: {} + walker@1.0.8: + dependencies: + makeerror: 1.0.12 - web-vitals@0.2.4: {} + watchpack@2.4.4: + dependencies: + glob-to-regexp: 0.4.1 + graceful-fs: 4.2.11 + + wavesurfer.js@7.10.1: {} webidl-conversions@3.0.1: {} webpack-sources@3.3.3: {} + webpack-virtual-modules@0.5.0: {} + + webpack@5.101.3: + dependencies: + "@types/eslint-scope": 3.7.7 + "@types/estree": 1.0.8 + "@types/json-schema": 7.0.15 + "@webassemblyjs/ast": 1.14.1 + "@webassemblyjs/wasm-edit": 1.14.1 + "@webassemblyjs/wasm-parser": 1.14.1 + acorn: 8.15.0 + acorn-import-phases: 1.0.4(acorn@8.15.0) + browserslist: 4.25.2 + chrome-trace-event: 1.0.4 + enhanced-resolve: 5.18.3 + es-module-lexer: 1.7.0 + eslint-scope: 5.1.1 + events: 3.3.0 + glob-to-regexp: 0.4.1 + graceful-fs: 4.2.11 + json-parse-even-better-errors: 2.3.1 + loader-runner: 4.3.0 + mime-types: 2.1.35 + neo-async: 2.6.2 + schema-utils: 4.3.2 + tapable: 2.2.3 + terser-webpack-plugin: 5.3.14(webpack@5.101.3) + watchpack: 2.4.4 + webpack-sources: 3.3.3 + transitivePeerDependencies: + - "@swc/core" + - esbuild + - uglify-js + webrtc-adapter@9.0.3: dependencies: sdp: 3.2.1 @@ -12945,10 +15392,6 @@ snapshots: dependencies: isexe: 2.0.0 - wide-align@1.1.5: - dependencies: - string-width: 4.2.3 - word-wrap@1.2.5: {} wordwrap@1.0.0: {} @@ -12967,42 +15410,46 @@ snapshots: wrappy@1.0.2: {} + write-file-atomic@5.0.1: + dependencies: + imurmurhash: 0.1.4 + signal-exit: 4.1.0 + ws@8.17.1: {} - xdg-app-paths@5.1.0: - dependencies: - xdg-portable: 7.3.0 - - xdg-portable@7.3.0: - dependencies: - os-paths: 4.4.0 - xmlhttprequest-ssl@2.0.0: {} + xtend@4.0.2: {} + + y18n@5.0.8: {} + yallist@3.1.1: {} yallist@4.0.0: {} + yaml-ast-parser@0.0.43: {} + yaml@1.10.2: {} yaml@2.8.1: {} - yauzl-clone@1.0.4: - dependencies: - events-intercept: 2.0.0 + yargs-parser@21.1.1: {} - yauzl-promise@2.1.3: + yargs@17.7.2: dependencies: - yauzl: 2.10.0 - yauzl-clone: 1.0.4 + cliui: 8.0.1 + escalade: 3.2.0 + get-caller-file: 2.0.5 + require-directory: 2.1.1 + string-width: 4.2.3 + y18n: 5.0.8 + yargs-parser: 21.1.1 - yauzl@2.10.0: - dependencies: - buffer-crc32: 0.2.13 - fd-slicer: 1.1.0 - - yn@3.1.1: {} + yn@3.1.1: + optional: true yocto-queue@0.1.0: {} + zod@4.1.5: {} + zwitch@2.0.4: {} diff --git a/www/public/service-worker.js b/www/public/service-worker.js index 109561d5..e798e369 100644 --- a/www/public/service-worker.js +++ b/www/public/service-worker.js @@ -1,4 +1,4 @@ -let authToken = ""; // Variable to store the token +let authToken = null; self.addEventListener("message", (event) => { if (event.data && event.data.type === "SET_AUTH_TOKEN") {