From 2111cde15ce39ac2075bd0e6fb9f46e3094486d7 Mon Sep 17 00:00:00 2001 From: peaceshallom37-rgb Date: Wed, 29 Jul 2026 21:21:55 +0000 Subject: [PATCH 1/2] fix(api): resolve api dockerfile workspace dependency failures The api Docker build was failing because the build context (api/) could not resolve file: workspace dependencies (`@xstreamroll/types`, `@xstreamroll/contract-tests`, and the root `xstreamroll` package). Changes: - Rewrite api/Dockerfile to use repo root as build context, copying workspace packages (packages/types, tests/contracts, root) and database/migrations into the builder stage - Add `rxjs` as a direct dependency in api/package.json (imported by source code but previously only available transitively via NestJS) - Regenerate api/pnpm-lock.yaml (was out of sync with package.json, causing ERR_PNPM_OUTDATED_LOCKFILE) - Create root .dockerignore to exclude node_modules, dist, .next, etc. from the Docker build context - Update docker-compose.yml api service: context changed from ./api to ., with dockerfile: api/Dockerfile - Update security-scan.yml, release.yml, docker-scan.yml: api matrix entries now use context: . with dockerfile: api/Dockerfile Closes the api Docker build failure in the security-scan workflow. Co-Authored-By: Freebuff --- .dockerignore | 12 + .github/workflows/docker-scan.yml | 3 +- .github/workflows/release.yml | 3 +- .github/workflows/security-scan.yml | 3 +- api/Dockerfile | 31 +- api/package.json | 1 + api/pnpm-lock.yaml | 643 ++++++++++++++++------- docker-compose.yml | 3 +- k8s/README.md | 141 +++++ monitoring/grafana-dashboard.json | 773 ++++++++++++++++++++++++++++ monitoring/prometheus-rules.yaml | 211 ++++++++ 11 files changed, 1636 insertions(+), 188 deletions(-) create mode 100644 .dockerignore create mode 100644 monitoring/grafana-dashboard.json create mode 100644 monitoring/prometheus-rules.yaml diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..f0d40ae --- /dev/null +++ b/.dockerignore @@ -0,0 +1,12 @@ +# Root .dockerignore — used when building Docker images with the repo +# root as build context (e.g., api/Dockerfile needs workspace packages). +node_modules +dist +coverage +.git +.env +.env.* +!.env.example +npm-debug.log* +.next +*.log diff --git a/.github/workflows/docker-scan.yml b/.github/workflows/docker-scan.yml index 1125a7c..5884dd3 100644 --- a/.github/workflows/docker-scan.yml +++ b/.github/workflows/docker-scan.yml @@ -61,7 +61,8 @@ jobs: fail-fast: false matrix: include: - - context: api + - context: . + dockerfile: api/Dockerfile image: xstreamroll-api - context: app image: xstreamroll-app diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 5e72d9a..cd2896e 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -41,7 +41,8 @@ jobs: strategy: matrix: include: - - context: api + - context: . + dockerfile: api/Dockerfile image: xstreamroll-api - context: app image: xstreamroll-app diff --git a/.github/workflows/security-scan.yml b/.github/workflows/security-scan.yml index c7c91d6..6441f14 100644 --- a/.github/workflows/security-scan.yml +++ b/.github/workflows/security-scan.yml @@ -123,7 +123,8 @@ jobs: matrix: include: - image: api - context: api + context: . + dockerfile: api/Dockerfile - image: app context: app - image: processing diff --git a/api/Dockerfile b/api/Dockerfile index 43bb629..5879969 100644 --- a/api/Dockerfile +++ b/api/Dockerfile @@ -1,19 +1,34 @@ +# Build context: repo root (not api/) — required so pnpm can resolve file: +# workspace dependencies (@xstreamroll/types, @xstreamroll/contract-tests). FROM node:20-alpine AS builder WORKDIR /app -COPY package.json pnpm-lock.yaml ./ + +# ── Workspace package manifests (needed for pnpm file: resolution) ──────────── +COPY package.json ./ +COPY packages/types/package.json packages/types/tsconfig.json ./packages/types/ +COPY packages/types/src/ ./packages/types/src/ +COPY tests/contracts/package.json tests/contracts/tsconfig.json ./tests/contracts/ +# Issue #340: include SQL migration files so the k8s init container can run +# `node-pg-migrate up -d /app/database/migrations -m sql` without a separate +# ConfigMap mount. Keeps the runner, schema, and runtime image in lockstep +# from a single published artifact. +COPY database/migrations/ ./database/migrations/ + +# ── API package manifests and install ───────────────────────────────────────── +COPY api/package.json api/pnpm-lock.yaml api/pnpm-workspace.yaml ./api/ +WORKDIR /app/api RUN npm install -g pnpm && pnpm install --frozen-lockfile -COPY . . + +# ── Copy remaining API source and build ─────────────────────────────────────── +COPY api/ ./ RUN pnpm run build +# ── Production image ────────────────────────────────────────────────────────── FROM node:20-alpine WORKDIR /app RUN addgroup -S appgroup && adduser -S appuser -G appgroup -COPY --from=builder --chown=appuser:appgroup /app/dist ./dist -COPY --from=builder --chown=appuser:appgroup /app/node_modules ./node_modules -# Issue #340: include SQL migration files so the k8s init container can run -# `node-pg-migrate up -d /app/database/migrations -m sql` without a separate -# ConfigMap mount. Keeps the runner, schema, and runtime image in lockstep -# from a single published artifact. +COPY --from=builder --chown=appuser:appgroup /app/api/dist ./dist +COPY --from=builder --chown=appuser:appgroup /app/api/node_modules ./node_modules COPY --from=builder --chown=appuser:appgroup /app/database/migrations ./database/migrations USER appuser EXPOSE 3001 diff --git a/api/package.json b/api/package.json index 747215e..2f7bf06 100644 --- a/api/package.json +++ b/api/package.json @@ -45,6 +45,7 @@ "prom-client": "^15.1.3", "cookie-parser": "^1.4.6", "reflect-metadata": "^0.1.13", + "rxjs": "^7.8.2", "sanitize-html": "^2.17.4", "socket.io": "^4.8.3", "swagger-ui-express": "^5.0.1", diff --git a/api/pnpm-lock.yaml b/api/pnpm-lock.yaml index 6fb1259..1c6c8a5 100644 --- a/api/pnpm-lock.yaml +++ b/api/pnpm-lock.yaml @@ -10,19 +10,19 @@ importers: dependencies: '@nestjs/cache-manager': specifier: ^2.3.0 - version: 2.3.0(@nestjs/common@10.4.22(class-transformer@0.5.1)(class-validator@0.15.1)(reflect-metadata@0.1.14)(rxjs@7.8.2)(supports-color@8.1.1))(@nestjs/core@10.4.22)(cache-manager@5.7.6)(rxjs@7.8.2) + version: 2.3.0(@nestjs/common@10.4.22(class-transformer@0.5.1)(class-validator@0.15.1)(reflect-metadata@0.1.14)(rxjs@7.8.2))(@nestjs/core@10.4.22)(cache-manager@5.7.6)(rxjs@7.8.2) '@nestjs/common': specifier: ^10.3.0 - version: 10.4.22(class-transformer@0.5.1)(class-validator@0.15.1)(reflect-metadata@0.1.14)(rxjs@7.8.2)(supports-color@8.1.1) + version: 10.4.22(class-transformer@0.5.1)(class-validator@0.15.1)(reflect-metadata@0.1.14)(rxjs@7.8.2) '@nestjs/core': specifier: ^10.3.0 - version: 10.4.22(@nestjs/common@10.4.22(class-transformer@0.5.1)(class-validator@0.15.1)(reflect-metadata@0.1.14)(rxjs@7.8.2)(supports-color@8.1.1))(@nestjs/platform-express@10.4.22)(@nestjs/websockets@10.4.22)(reflect-metadata@0.1.14)(rxjs@7.8.2) + version: 10.4.22(@nestjs/common@10.4.22(class-transformer@0.5.1)(class-validator@0.15.1)(reflect-metadata@0.1.14)(rxjs@7.8.2))(@nestjs/platform-express@10.4.22)(@nestjs/websockets@10.4.22)(reflect-metadata@0.1.14)(rxjs@7.8.2) '@nestjs/jwt': specifier: ^10.2.0 - version: 10.2.0(@nestjs/common@10.4.22(class-transformer@0.5.1)(class-validator@0.15.1)(reflect-metadata@0.1.14)(rxjs@7.8.2)(supports-color@8.1.1)) + version: 10.2.0(@nestjs/common@10.4.22(class-transformer@0.5.1)(class-validator@0.15.1)(reflect-metadata@0.1.14)(rxjs@7.8.2)) '@nestjs/platform-express': specifier: ^10.3.0 - version: 10.4.22(@nestjs/common@10.4.22(class-transformer@0.5.1)(class-validator@0.15.1)(reflect-metadata@0.1.14)(rxjs@7.8.2)(supports-color@8.1.1))(@nestjs/core@10.4.22)(supports-color@8.1.1) + version: 10.4.22(@nestjs/common@10.4.22(class-transformer@0.5.1)(class-validator@0.15.1)(reflect-metadata@0.1.14)(rxjs@7.8.2))(@nestjs/core@10.4.22) '@nestjs/platform-socket.io': specifier: ^10.4.22 version: 10.4.22(@nestjs/common@10.4.22(class-transformer@0.5.1)(class-validator@0.15.1)(reflect-metadata@0.1.14)(rxjs@7.8.2))(@nestjs/websockets@10.4.22)(rxjs@7.8.2) @@ -31,13 +31,13 @@ importers: version: 4.1.2(@nestjs/common@10.4.22(class-transformer@0.5.1)(class-validator@0.15.1)(reflect-metadata@0.1.14)(rxjs@7.8.2))(@nestjs/core@10.4.22) '@nestjs/swagger': specifier: ^7.4.2 - version: 7.4.2(@nestjs/common@10.4.22(class-transformer@0.5.1)(class-validator@0.15.1)(reflect-metadata@0.1.14)(rxjs@7.8.2)(supports-color@8.1.1))(@nestjs/core@10.4.22)(class-transformer@0.5.1)(class-validator@0.15.1)(reflect-metadata@0.1.14) + version: 7.4.2(@nestjs/common@10.4.22(class-transformer@0.5.1)(class-validator@0.15.1)(reflect-metadata@0.1.14)(rxjs@7.8.2))(@nestjs/core@10.4.22)(class-transformer@0.5.1)(class-validator@0.15.1)(reflect-metadata@0.1.14) '@nestjs/terminus': specifier: ^10.0.0 version: 10.3.0(@grpc/grpc-js@1.14.4)(@grpc/proto-loader@0.8.1)(@nestjs/common@10.4.22(class-transformer@0.5.1)(class-validator@0.15.1)(reflect-metadata@0.1.14)(rxjs@7.8.2))(@nestjs/core@10.4.22)(reflect-metadata@0.1.14)(rxjs@7.8.2) '@nestjs/throttler': specifier: ^6.5.0 - version: 6.5.0(@nestjs/common@10.4.22(class-transformer@0.5.1)(class-validator@0.15.1)(reflect-metadata@0.1.14)(rxjs@7.8.2)(supports-color@8.1.1))(@nestjs/core@10.4.22)(reflect-metadata@0.1.14) + version: 6.5.0(@nestjs/common@10.4.22(class-transformer@0.5.1)(class-validator@0.15.1)(reflect-metadata@0.1.14)(rxjs@7.8.2))(@nestjs/core@10.4.22)(reflect-metadata@0.1.14) '@nestjs/websockets': specifier: ^10.4.22 version: 10.4.22(@nestjs/common@10.4.22(class-transformer@0.5.1)(class-validator@0.15.1)(reflect-metadata@0.1.14)(rxjs@7.8.2))(@nestjs/core@10.4.22)(@nestjs/platform-socket.io@10.4.22)(reflect-metadata@0.1.14)(rxjs@7.8.2) @@ -48,11 +48,14 @@ importers: specifier: ^0.54.0 version: 0.54.0(@opentelemetry/api@1.9.1) '@opentelemetry/exporter-trace-otlp-http': - specifier: ^0.57.0 + specifier: ^0.57.2 version: 0.57.2(@opentelemetry/api@1.9.1) '@opentelemetry/sdk-node': - specifier: ^0.57.0 + specifier: ^0.57.2 version: 0.57.2(@opentelemetry/api@1.9.1) + '@xstreamroll/types': + specifier: file:../packages/types + version: file:../packages/types bcrypt: specifier: ^6.0.0 version: 6.0.0 @@ -74,6 +77,9 @@ importers: cookie-parser: specifier: ^1.4.6 version: 1.4.7 + fast-check: + specifier: ^4.9.0 + version: 4.9.0 helmet: specifier: 8.1.0 version: 8.1.0 @@ -90,17 +96,17 @@ importers: specifier: ^0.1.13 version: 0.1.14 rxjs: - specifier: ^7.8.1 + specifier: ^7.8.2 version: 7.8.2 sanitize-html: specifier: ^2.17.4 version: 2.17.4 socket.io: specifier: ^4.8.3 - version: 4.8.3(supports-color@8.1.1) + version: 4.8.3 swagger-ui-express: specifier: ^5.0.1 - version: 5.0.1(express@4.22.1(supports-color@8.1.1)) + version: 5.0.1(express@4.22.1) xstreamroll: specifier: file:.. version: file:..(react@19.2.7) @@ -110,10 +116,10 @@ importers: devDependencies: '@nestjs/cli': specifier: ^10.3.0 - version: 10.4.9(uglify-js@3.19.3) + version: 10.4.9 '@nestjs/testing': specifier: ^10.4.22 - version: 10.4.22(@nestjs/common@10.4.22(class-transformer@0.5.1)(class-validator@0.15.1)(reflect-metadata@0.1.14)(rxjs@7.8.2)(supports-color@8.1.1))(@nestjs/core@10.4.22)(@nestjs/platform-express@10.4.22) + version: 10.4.22(@nestjs/common@10.4.22(class-transformer@0.5.1)(class-validator@0.15.1)(reflect-metadata@0.1.14)(rxjs@7.8.2))(@nestjs/core@10.4.22)(@nestjs/platform-express@10.4.22) '@types/bcrypt': specifier: ^6.0.0 version: 6.0.0 @@ -129,18 +135,33 @@ importers: '@types/node': specifier: ^20.10.0 version: 20.19.30 + '@types/nodemailer': + specifier: ^8.0.1 + version: 8.0.1 '@types/pg': specifier: ^8.20.0 version: 8.20.0 '@types/sanitize-html': specifier: ^2.16.1 version: 2.16.1 + '@types/supertest': + specifier: ^7.2.0 + version: 7.2.1 + '@xstreamroll/contract-tests': + specifier: file:../tests/contracts + version: file:../tests/contracts jest: specifier: ^29.7.0 - version: 29.7.0(@types/node@20.19.30)(supports-color@8.1.1) + version: 29.7.0(@types/node@20.19.30) + node-pg-migrate: + specifier: ^7.6.1 + version: 7.9.1(@types/pg@8.20.0)(pg@8.21.0) + supertest: + specifier: ^7.2.2 + version: 7.2.2 ts-jest: specifier: ^29.4.11 - version: 29.4.11(@babel/core@7.29.7(supports-color@8.1.1))(@jest/transform@29.7.0(supports-color@8.1.1))(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(jest-util@29.7.0)(jest@29.7.0(@types/node@20.19.30)(supports-color@8.1.1))(typescript@5.9.3) + version: 29.4.11(@babel/core@7.29.7)(@jest/transform@29.7.0)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@7.29.7))(jest-util@29.7.0)(jest@29.7.0(@types/node@20.19.30))(typescript@5.9.3) typescript: specifier: ^5.3.0 version: 5.9.3 @@ -165,10 +186,6 @@ packages: resolution: {integrity: sha512-I5wviiIqiFwar9Pdk30Lujk8FczEEc18i22A5c6Z9lbmhPQdTroDnEQdsfXjy404wPe8H62s0I15o4pmMGfTYQ==} engines: {node: ^18.13.0 || >=20.9.0, npm: ^6.11.0 || ^7.5.6 || >=8.0.0, yarn: '>= 1.13.0'} - '@babel/code-frame@7.28.6': - resolution: {integrity: sha512-JYgintcMjRiCvS8mMECzaEn+m3PfoQiyqukOMCCVQtoJGYJw8j/8LBJEiqkHLkfwCcs74E3pbAUFNg7d9VNJ+Q==} - engines: {node: '>=6.9.0'} - '@babel/code-frame@7.29.7': resolution: {integrity: sha512-Aup7aUOfpbAUg2ROOJN6Iw5f9DMBlzu0mIkm/malLQFN/YQgO48wCj0Kxa3sEHJvPVFg7siR+qRInwXd2qhQKw==} engines: {node: '>=6.9.0'} @@ -211,10 +228,6 @@ packages: resolution: {integrity: sha512-Pb5ijPrZ89GDH8223L4UP8i6QApWxs04RbPQJTeWDV0/keR2E36MeKnyr6LYmUUvqRRI+Iv87SuF1W6ErINzYw==} engines: {node: '>=6.9.0'} - '@babel/helper-validator-identifier@7.28.5': - resolution: {integrity: sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==} - engines: {node: '>=6.9.0'} - '@babel/helper-validator-identifier@7.29.7': resolution: {integrity: sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg==} engines: {node: '>=6.9.0'} @@ -363,6 +376,10 @@ packages: resolution: {integrity: sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==} engines: {node: '>=12'} + '@isaacs/cliui@9.0.0': + resolution: {integrity: sha512-AokJm4tuBHillT+FpMtxQ60n8ObyXBatq7jD2/JA9dxbDDokKQm8KMht5ibGzLVU9IJDIKK4TPKgMHEYMn3lMg==} + engines: {node: '>=18'} + '@istanbuljs/load-nyc-config@1.1.0': resolution: {integrity: sha512-VjeHSlIzpv/NyD3N0YuHfXOPDIixcA1q2ZV98wsMqcYlPmv2n3Yb2lYP9XMElnaFVXg5A7YLTeLu6V84uQDjmQ==} engines: {node: '>=8'} @@ -660,6 +677,10 @@ packages: '@nestjs/platform-socket.io': optional: true + '@noble/hashes@1.8.0': + resolution: {integrity: sha512-jCs9ldd7NwzpgXDIf6P3+NrHh9/sD6CQdxHyjQI+h/6rDNo88ypBxxz45UDuZHz9r3tNz7N/VInSVoVdtXEI4A==} + engines: {node: ^14.21.3 || >=16} + '@nuxtjs/opencollective@0.3.2': resolution: {integrity: sha512-um0xL3fO7Mf4fDxcqx9KryrB7zgRM5JSlvGN5AGkP6JLM5XEKyjeAiPbNxdXVXQ16isuAhYpvP88NgL2BGd6aA==} engines: {node: '>=8.0.0', npm: '>=5.0.0'} @@ -1252,6 +1273,9 @@ packages: peerDependencies: '@opentelemetry/api': ^1.1.0 + '@paralleldrive/cuid2@2.3.1': + resolution: {integrity: sha512-XO7cAxhnTZl0Yggq6jOgjiOHhbgcO4NqFqwSmQpjK3b6TEE6Uj/jfSk6wzYyemh3+I0sHirKSetjQwn5cZktFw==} + '@pkgjs/parseargs@0.11.0': resolution: {integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==} engines: {node: '>=14'} @@ -1330,6 +1354,14 @@ packages: '@standard-schema/utils@0.3.0': resolution: {integrity: sha512-e7Mew686owMaPJVNNLs55PUvgz371nKgwsc4vxE49zsODpJEnxgxRo2y/OKrqueavXgZNMDVj3DdHFlaSAeU8g==} + '@tanstack/query-core@5.101.4': + resolution: {integrity: sha512-gNwcvOJcRbLWPOLG/2OBm+zM+Yv+MKsXKEOWC57USuZDEsI71hEErQsiEGx5wX9rzWWkfwM0fVSPoiIFSsxfiw==} + + '@tanstack/react-query@5.101.4': + resolution: {integrity: sha512-yRg2pfOCxIs4ZJW3XYYHU/WgtD04FHSnfHlpRT7h7pR77hwkdRG4wxbKe4aq6P0RvXUTBSQpQeadS1SUYUe+KA==} + peerDependencies: + react: ^18 || ^19 + '@tokenizer/inflate@0.2.7': resolution: {integrity: sha512-MADQgmZT1eKjp06jpI2yozxaU9uVs4GzzgSL+uEq7bVcJ9V1ZXQkeGNql1fsSI0gMy1vhvNTNbUqrx+pZfJVmg==} engines: {node: '>=18'} @@ -1370,6 +1402,9 @@ packages: '@types/connect@3.4.38': resolution: {integrity: sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug==} + '@types/cookiejar@2.1.5': + resolution: {integrity: sha512-he+DHOWReW0nghN24E1WUqM0efK4kI9oTqDm6XmK8ZPe2djZ90BSNdGnIyCLzCPw7/pogPlGbzI2wHGGmi4O/Q==} + '@types/cors@2.8.19': resolution: {integrity: sha512-mFNylyeyqN93lfe/9CSxOGREz8cpzAhH+E93xJ4xWQf62V8sQ/24reV2nyzUWM6H6Xji+GGHpkbLe7pVoUEskg==} @@ -1418,12 +1453,18 @@ packages: '@types/memcached@2.2.10': resolution: {integrity: sha512-AM9smvZN55Gzs2wRrqeMHVP7KE8KWgCJO/XL5yCly2xF6EKa4YlbpK+cLSAH4NG/Ah64HrlegmGqW8kYws7Vxg==} + '@types/methods@1.1.4': + resolution: {integrity: sha512-ymXWVrDiCxTBE3+RIrrP533E70eA+9qu7zdWoHuOmGujkYtzf4HQF96b8nwHLqhuf4ykX61IGRIB38CC6/sImQ==} + '@types/mysql@2.15.26': resolution: {integrity: sha512-DSLCOXhkvfS5WNNPbfn2KdICAmk8lLc+/PNvnPnF7gOdMZCxopXduqv0OQ13y/yA/zXTSikZZqVgybUxOEg6YQ==} '@types/node@20.19.30': resolution: {integrity: sha512-WJtwWJu7UdlvzEAUm484QNg5eAoq5QR08KDNx7g45Usrs2NtOPiX8ugDqmKdXkyL03rBqU5dYNYVQetEpBHq2g==} + '@types/nodemailer@8.0.1': + resolution: {integrity: sha512-PxpaInm8V1JQDd4j0ds5HfvWQk8JupS1C0Picb96QJsrrRDjBH+DlK7L4ZdNSqNULhiZRQHc40nLVShaGxXAMw==} + '@types/pg-pool@2.0.6': resolution: {integrity: sha512-TaAUE5rq2VQYxab5Ts7WZhKNmuN78Q6PiFonTDdpbx8a1H0M1vhy3rhiMjl+e2iHmogyMw7jZF4FrE6eJUy5HQ==} @@ -1454,6 +1495,12 @@ packages: '@types/stack-utils@2.0.3': resolution: {integrity: sha512-9aEbYZ3TbYMznPdcdr3SmIrLXwC/AKZXQeCf9Pgao5CKb8CyHuEX5jzWPTkvregvhRJHcpRO6BFoGW9ycaOkYw==} + '@types/superagent@8.1.11': + resolution: {integrity: sha512-KA7srSW/HENDtOw9DOqaFLgWuMqN9WgjEw62lh9dpvRaZDkhdOkazASd7X7i2eMUYLHa1U37ZttnePsH5zTDHw==} + + '@types/supertest@7.2.1': + resolution: {integrity: sha512-4CbBvoYVLHL7+yhbYrZET0vsvuyXTC05aRe7dNQkwMzm56auceoy6Yu3K50uZmwfHna1os3CMSgM/3QVkUtPTw==} + '@types/tedious@4.0.14': resolution: {integrity: sha512-KHPsfX/FoVbUGbyYvk1q9MMQHLPeRZhRJZdO45Q4YjvFkv4hMNghCWTvy7rdKessBsmtz4euWCWAB6/tVpI1Iw==} @@ -1514,6 +1561,12 @@ packages: '@webassemblyjs/wast-printer@1.14.1': resolution: {integrity: sha512-kPSSXE6De1XOR820C90RIo2ogvZG+c3KiHzqUoO/F34Y2shGzesfqv7o57xrxovZJH/MetF5UjroJ/R/3isoiw==} + '@xstreamroll/contract-tests@file:../tests/contracts': + resolution: {directory: ../tests/contracts, type: directory} + + '@xstreamroll/types@file:../packages/types': + resolution: {directory: ../packages/types, type: directory} + '@xtuc/ieee754@1.2.0': resolution: {integrity: sha512-DX8nKgqcGwsc0eJSqYt5lwP4DH5FlHnmuWWBRy7X0NcaGR0ZtuyeESgMwTYVEtxmsNGY+qit4QYT/MIYTOTPeA==} @@ -1615,6 +1668,12 @@ packages: array-timsort@1.0.3: resolution: {integrity: sha512-/+3GRL7dDAGEfM6TseQk/U+mi18TU2Ms9I3UlLdUMhz2hbvGNTKdj9xniwXfUqgYhHxRx0+8UnKkvlNwVU+cWQ==} + asap@2.0.6: + resolution: {integrity: sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA==} + + asynckit@0.4.0: + resolution: {integrity: sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==} + babel-jest@29.7.0: resolution: {integrity: sha512-BrvGY3xZSwEcCzKvKsCi2GgHqDqsYkOP4/by5xCgIwGXQxIEh+8ew3gmrE1y7XRR6LHZIj6yLYnUi/mm2KXKBg==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} @@ -1643,6 +1702,10 @@ packages: balanced-match@1.0.2: resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} + balanced-match@4.0.4: + resolution: {integrity: sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==} + engines: {node: 18 || 20 || >=22} + base64-js@1.5.1: resolution: {integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==} @@ -1685,6 +1748,10 @@ packages: brace-expansion@2.0.2: resolution: {integrity: sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==} + brace-expansion@5.0.8: + resolution: {integrity: sha512-JZyDyq3D4AUifKTPOB7DELf6XsB3WdPuNxCtob1vFXPsSXhdAiHBWJ/tJ8HAc9aH84BK+5JFZLNkJKx3G9kzQg==} + engines: {node: 20 || >=22} + braces@3.0.3: resolution: {integrity: sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==} engines: {node: '>=8'} @@ -1843,6 +1910,10 @@ packages: color-name@1.1.4: resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} + combined-stream@1.0.8: + resolution: {integrity: sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==} + engines: {node: '>= 0.8'} + commander@2.20.3: resolution: {integrity: sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==} @@ -1854,6 +1925,9 @@ packages: resolution: {integrity: sha512-bKw/r35jR3HGt5PEPm1ljsQQGyCrR8sFGNiN5L+ykDHdpO8Smxkrkla9Yi6NkQyUrb8V54PGhfMs6NrIwtxtdw==} engines: {node: '>= 6'} + component-emitter@1.3.1: + resolution: {integrity: sha512-T0+barUSQRTUQASh8bx02dl+DhF54GtIDY13Y3m9oWTklKbb3Wv974meRpeZ3lp1JpLVECWWNHC4vaG2XHXouQ==} + compressible@2.0.18: resolution: {integrity: sha512-AF3r7P5dWxL8MxyITRMlORQNaOA2IkAFaTr4k7BUumjPtRpGDTZpl0Pb1XCO6JeDCBdp126Cgs9sMxqSjgYyRg==} engines: {node: '>= 0.6'} @@ -1893,10 +1967,17 @@ packages: cookie-signature@1.0.7: resolution: {integrity: sha512-NXdYc3dLr47pBkpUCHtKSwIOQXLVn8dZEuywboCOJY/osA0wFSLlSawr3KN8qXJEyX66FcONTH8EIlVuK0yyFA==} + cookie-signature@1.2.2: + resolution: {integrity: sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg==} + engines: {node: '>=6.6.0'} + cookie@0.7.2: resolution: {integrity: sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==} engines: {node: '>= 0.6'} + cookiejar@2.1.4: + resolution: {integrity: sha512-LDx6oHrK+PhzLKJU9j5S7/Y3jM/mUHvD/DeI1WQmJn652iPC5Y4TBzC9l+5OMOXlyTTA+SmVUPm0HQUwpD5Jqw==} + core-util-is@1.0.3: resolution: {integrity: sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==} @@ -1973,6 +2054,10 @@ packages: resolution: {integrity: sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==} engines: {node: '>= 0.4'} + delayed-stream@1.0.0: + resolution: {integrity: sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==} + engines: {node: '>=0.4.0'} + depd@2.0.0: resolution: {integrity: sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==} engines: {node: '>= 0.8'} @@ -1985,6 +2070,9 @@ packages: resolution: {integrity: sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA==} engines: {node: '>=8'} + dezalgo@1.0.4: + resolution: {integrity: sha512-rXSP0bf+5n0Qonsb+SVVfNfIsimO4HEtmnIpPHY8Q1UCzKlQrDMfdobr8nJOOsRgWCyMRqeSBQzmWUMq7zvVig==} + diff-sequences@29.6.3: resolution: {integrity: sha512-EjePK1srD3P08o2j4f0ExnylqRs5B9tJjcp9t1krH2qRi8CCdsYfwe9JgSLurFBWwq4uOlipzfk5fHNvwFKr8Q==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} @@ -2070,6 +2158,10 @@ packages: resolution: {integrity: sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==} engines: {node: '>= 0.4'} + es-set-tostringtag@2.1.0: + resolution: {integrity: sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==} + engines: {node: '>= 0.4'} + escalade@3.2.0: resolution: {integrity: sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==} engines: {node: '>=6'} @@ -2144,6 +2236,10 @@ packages: resolution: {integrity: sha512-hMQ4CX1p1izmuLYyZqLMO/qGNw10wSv9QDCPfzXfyFrOaCSSoRfqE1Kf1s5an66J5JZC62NewG+mK49jOCtQew==} engines: {node: '>=4'} + fast-check@4.9.0: + resolution: {integrity: sha512-7ms6T7SybUev/PQITciI0yLM2pOSFy5zpG8Ty7tQofcVaQUvrMXp6CBwqF6fThLCLOrfBtuHAtwq6Yu4XPCllg==} + engines: {node: '>=12.17.0'} + fast-deep-equal@3.1.3: resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==} @@ -2193,6 +2289,14 @@ packages: typescript: '>3.6.0' webpack: ^5.11.0 + form-data@4.0.6: + resolution: {integrity: sha512-vKatAh4SlVfgbv+YtmhiRjhEMJsYpsG1Y2rMQtR+SVSbytsSD1YGzDIcrAJmdFec88u/+VoGmxnl+80gL1tRCQ==} + engines: {node: '>= 6'} + + formidable@3.5.4: + resolution: {integrity: sha512-YikH+7CUTOtP44ZTnUhR7Ic2UASBPOqmaRkRKxRbywPTe5VxF7RRCck4af9wutiZ/QKM5nME9Bie2fFaPz5Gug==} + engines: {node: '>=14.0.0'} + forwarded-parse@2.1.2: resolution: {integrity: sha512-alTFZZQDKMporBH77856pXgzhEzaUVmLCDk+egLgIgHst3Tpndzz8MnKe+GzRJRfvVdn69HhpW7cmXzvtLvJAw==} @@ -2270,6 +2374,12 @@ packages: deprecated: Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me hasBin: true + glob@11.0.3: + resolution: {integrity: sha512-2Nim7dha1KVkaiF4q6Dj+ngPPMdfvLJEOpZk/jKiUAkqKebpGAWQXAq9z1xu9HKu5lWfqw/FASuccEjyznjPaA==} + engines: {node: 20 || >=22} + deprecated: Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me + hasBin: true + glob@7.2.3: resolution: {integrity: sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==} deprecated: Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me @@ -2305,6 +2415,10 @@ packages: resolution: {integrity: sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==} engines: {node: '>= 0.4'} + has-tostringtag@1.0.2: + resolution: {integrity: sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==} + engines: {node: '>= 0.4'} + hasown@2.0.4: resolution: {integrity: sha512-T2UbfbBEF32wiepXIsMlTW9+dDYC6wMh/t/vYA4tuOMKqWz/n3vr1NFSxQiyP+zk2mXsoMA/i/7qV6LKut1t1A==} engines: {node: '>= 0.4'} @@ -2454,6 +2568,10 @@ packages: jackspeak@3.4.3: resolution: {integrity: sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==} + jackspeak@4.2.3: + resolution: {integrity: sha512-ykkVRwrYvFm1nb2AJfKKYPr0emF6IiXDYUaFx4Zn9ZuIH7MrzEZ3sD5RlqGXNRpHtvUHJyOnCEFxOlNDtGo7wg==} + engines: {node: 20 || >=22} + jest-changed-files@29.7.0: resolution: {integrity: sha512-fEArFiwf1BpQ+4bXSprcDc3/x4HSzL4al2tozwVpDFpsxALjLYdyiIK4e5Vz66GQJIbXJ82+35PtysofptNX2w==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} @@ -2714,6 +2832,10 @@ packages: lru-cache@10.4.3: resolution: {integrity: sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==} + lru-cache@11.5.2: + resolution: {integrity: sha512-4pfM1Ff0x50o0tQwb5ucw/RzNyD0/YJME6IVcStalZuMWxdt3sR3huStTtxz4PUmvZfRguvDejasvQ2kifR11g==} + engines: {node: 20 || >=22} + lru-cache@5.1.1: resolution: {integrity: sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==} @@ -2774,10 +2896,19 @@ packages: engines: {node: '>=4'} hasBin: true + mime@2.6.0: + resolution: {integrity: sha512-USPkMeET31rOMiarsBNIHZKLGgvKc/LrjofAnBlOttf5ajRvqiRA8QsenbcooctK6d6Ts6aqZXBA+XbkKthiQg==} + engines: {node: '>=4.0.0'} + hasBin: true + mimic-fn@2.1.0: resolution: {integrity: sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==} engines: {node: '>=6'} + minimatch@10.2.6: + resolution: {integrity: sha512-vpLQEs+VLCr1nU0BXS07maYoFwlDAH0gngQuuttxIwutDFEMHq2blX+8vpgxDdK3J1PwjCJiep77OitTZ4Ll1A==} + engines: {node: 18 || 20 || >=22} + minimatch@3.1.2: resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==} @@ -2861,6 +2992,17 @@ packages: node-int64@0.4.0: resolution: {integrity: sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw==} + node-pg-migrate@7.9.1: + resolution: {integrity: sha512-6z4OSN27ye8aYdX9ZU7NN2PTI5pOp34hTr+22Ej12djIYECq++gT7LPLZVOQXEeVCBOZQLqf87kC3Y36G434OQ==} + engines: {node: '>=18.19.0'} + hasBin: true + peerDependencies: + '@types/pg': '>=6.0.0 <9.0.0' + pg: '>=4.3.0 <9.0.0' + peerDependenciesMeta: + '@types/pg': + optional: true + node-releases@2.0.27: resolution: {integrity: sha512-nmh3lCkYZ3grZvqcCH+fjmQ7X+H0OeZgP40OierEaAptX4XofMh5kwNbWh7lBduUzCcV/8kZ+NDLCwm2iorIlA==} @@ -2964,6 +3106,10 @@ packages: resolution: {integrity: sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==} engines: {node: '>=16 || 14 >=14.18'} + path-scurry@2.0.2: + resolution: {integrity: sha512-3O/iVVsJAPsOnpwWIeD+d6z/7PmqApyQePUtCndjatj/9I5LylHvt5qluFaBT3I5h3r1ejfR056c+FCv+NnNXg==} + engines: {node: 18 || 20 || >=22} + path-to-regexp@0.1.12: resolution: {integrity: sha512-RA1GjUVMnvYFxuqovrEqZoxxW5NUZqbwKtYz/Tt7nXerk0LbLblQmrsgdeOxV5SFHf0UDggjS/bSeOZwt1pmEQ==} @@ -3082,6 +3228,9 @@ packages: pure-rand@6.1.0: resolution: {integrity: sha512-bVWawvoZoBYpp6yIoQtQXHZjmz35RSVHnUOTefl8Vcjr8snTPY1wnpSPMWekcFwbxI6gtmT7rSYPFvz71ldiOA==} + pure-rand@8.4.2: + resolution: {integrity: sha512-vvuOGgcuPJAirlHvuQw1TrOiw7ptaIXXmIbNuiNOY6lNGJJH49PQ1Kj4nd783nPdQhQdicgOjVI2yI/9BD6/Ng==} + qs@6.14.1: resolution: {integrity: sha512-4EK3+xJl8Ts67nLYNwqw/dsFVnCf+qR7RgXSK9jEEm9unao3njwMDdmsdvoKBKHzxd7tCYz5e5M+SnMjdtXGQQ==} engines: {node: '>=0.6'} @@ -3200,11 +3349,6 @@ packages: resolution: {integrity: sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==} hasBin: true - semver@7.7.3: - resolution: {integrity: sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==} - engines: {node: '>=10'} - hasBin: true - semver@7.8.4: resolution: {integrity: sha512-rUCObTnP32Q08R2uuIrt7r9PlEonuTmtuXYcW6s5kjdlj3xbnwe+21yXptAUYcMAABLkYYTtnmzb3w3EDZfueA==} engines: {node: '>=10'} @@ -3364,6 +3508,14 @@ packages: resolution: {integrity: sha512-KIy5nylvC5le1OdaaoCJ07L+8iQzJHGH6pWDuzS+d07Cu7n1MZ2x26P8ZKIWfbK02+XIL8Mp4RkWeqdUCrDMfg==} engines: {node: '>=18'} + superagent@10.3.0: + resolution: {integrity: sha512-B+4Ik7ROgVKrQsXTV0Jwp2u+PXYLSlqtDAhYnkkD+zn3yg8s/zjA2MeGayPoY/KICrbitwneDHrjSotxKL+0XQ==} + engines: {node: '>=14.18.0'} + + supertest@7.2.2: + resolution: {integrity: sha512-oK8WG9diS3DlhdUkcFn4tkNIiIbBx9lI2ClF8K+b2/m8Eyv47LSawxUzZQSNKUrVb2KsqeTDCcjAAVPYaSLVTA==} + engines: {node: '>=14.18.0'} + supports-color@7.2.0: resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==} engines: {node: '>=8'} @@ -3731,12 +3883,6 @@ snapshots: transitivePeerDependencies: - chokidar - '@babel/code-frame@7.28.6': - dependencies: - '@babel/helper-validator-identifier': 7.28.5 - js-tokens: 4.0.0 - picocolors: 1.1.1 - '@babel/code-frame@7.29.7': dependencies: '@babel/helper-validator-identifier': 7.29.7 @@ -3803,8 +3949,6 @@ snapshots: '@babel/helper-string-parser@7.29.7': {} - '@babel/helper-validator-identifier@7.28.5': {} - '@babel/helper-validator-identifier@7.29.7': {} '@babel/helper-validator-option@7.29.7': {} @@ -3959,6 +4103,8 @@ snapshots: wrap-ansi: 8.1.0 wrap-ansi-cjs: wrap-ansi@7.0.0 + '@isaacs/cliui@9.0.0': {} + '@istanbuljs/load-nyc-config@1.1.0': dependencies: camelcase: 5.3.1 @@ -4338,6 +4484,8 @@ snapshots: optionalDependencies: '@nestjs/platform-socket.io': 10.4.22(@nestjs/common@10.4.22(class-transformer@0.5.1)(class-validator@0.15.1)(reflect-metadata@0.1.14)(rxjs@7.8.2))(@nestjs/websockets@10.4.22)(rxjs@7.8.2) + '@noble/hashes@1.8.0': {} + '@nuxtjs/opencollective@0.3.2': dependencies: chalk: 4.1.2 @@ -5203,6 +5351,10 @@ snapshots: '@opentelemetry/api': 1.9.1 '@opentelemetry/core': 1.30.1(@opentelemetry/api@1.9.1) + '@paralleldrive/cuid2@2.3.1': + dependencies: + '@noble/hashes': 1.8.0 + '@pkgjs/parseargs@0.11.0': optional: true @@ -5268,9 +5420,16 @@ snapshots: '@standard-schema/utils@0.3.0': {} - '@tokenizer/inflate@0.2.7(supports-color@8.1.1)': + '@tanstack/query-core@5.101.4': {} + + '@tanstack/react-query@5.101.4(react@19.2.7)': dependencies: - debug: 4.4.3(supports-color@8.1.1) + '@tanstack/query-core': 5.101.4 + react: 19.2.7 + + '@tokenizer/inflate@0.2.7': + dependencies: + debug: 4.4.3 fflate: 0.8.2 token-types: 6.1.2 transitivePeerDependencies: @@ -5327,6 +5486,8 @@ snapshots: dependencies: '@types/node': 20.19.30 + '@types/cookiejar@2.1.5': {} + '@types/cors@2.8.19': dependencies: '@types/node': 20.19.30 @@ -5389,6 +5550,8 @@ snapshots: dependencies: '@types/node': 20.19.30 + '@types/methods@1.1.4': {} + '@types/mysql@2.15.26': dependencies: '@types/node': 20.19.30 @@ -5397,6 +5560,10 @@ snapshots: dependencies: undici-types: 6.21.0 + '@types/nodemailer@8.0.1': + dependencies: + '@types/node': 20.19.30 + '@types/pg-pool@2.0.6': dependencies: '@types/pg': 8.20.0 @@ -5434,6 +5601,18 @@ snapshots: '@types/stack-utils@2.0.3': {} + '@types/superagent@8.1.11': + dependencies: + '@types/cookiejar': 2.1.5 + '@types/methods': 1.1.4 + '@types/node': 20.19.30 + form-data: 4.0.6 + + '@types/supertest@7.2.1': + dependencies: + '@types/methods': 1.1.4 + '@types/superagent': 8.1.11 + '@types/tedious@4.0.14': dependencies: '@types/node': 20.19.30 @@ -5526,6 +5705,13 @@ snapshots: '@webassemblyjs/ast': 1.14.1 '@xtuc/long': 4.2.2 + '@xstreamroll/contract-tests@file:../tests/contracts': + dependencies: + '@xstreamroll/types': file:../packages/types + zod: 3.25.76 + + '@xstreamroll/types@file:../packages/types': {} + '@xtuc/ieee754@1.2.0': {} '@xtuc/long@4.2.2': {} @@ -5620,25 +5806,29 @@ snapshots: array-timsort@1.0.3: {} - babel-jest@29.7.0(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1): + asap@2.0.6: {} + + asynckit@0.4.0: {} + + babel-jest@29.7.0(@babel/core@7.29.7): dependencies: - '@babel/core': 7.29.7(supports-color@8.1.1) - '@jest/transform': 29.7.0(supports-color@8.1.1) + '@babel/core': 7.29.7 + '@jest/transform': 29.7.0 '@types/babel__core': 7.20.5 - babel-plugin-istanbul: 6.1.1(supports-color@8.1.1) - babel-preset-jest: 29.6.3(@babel/core@7.29.7(supports-color@8.1.1)) + babel-plugin-istanbul: 6.1.1 + babel-preset-jest: 29.6.3(@babel/core@7.29.7) chalk: 4.1.2 graceful-fs: 4.2.11 slash: 3.0.0 transitivePeerDependencies: - supports-color - babel-plugin-istanbul@6.1.1(supports-color@8.1.1): + babel-plugin-istanbul@6.1.1: dependencies: '@babel/helper-plugin-utils': 7.29.7 '@istanbuljs/load-nyc-config': 1.1.0 '@istanbuljs/schema': 0.1.6 - istanbul-lib-instrument: 5.2.1(supports-color@8.1.1) + istanbul-lib-instrument: 5.2.1 test-exclude: 6.0.0 transitivePeerDependencies: - supports-color @@ -5650,33 +5840,35 @@ snapshots: '@types/babel__core': 7.20.5 '@types/babel__traverse': 7.28.0 - babel-preset-current-node-syntax@1.2.0(@babel/core@7.29.7(supports-color@8.1.1)): - dependencies: - '@babel/core': 7.29.7(supports-color@8.1.1) - '@babel/plugin-syntax-async-generators': 7.8.4(@babel/core@7.29.7(supports-color@8.1.1)) - '@babel/plugin-syntax-bigint': 7.8.3(@babel/core@7.29.7(supports-color@8.1.1)) - '@babel/plugin-syntax-class-properties': 7.12.13(@babel/core@7.29.7(supports-color@8.1.1)) - '@babel/plugin-syntax-class-static-block': 7.14.5(@babel/core@7.29.7(supports-color@8.1.1)) - '@babel/plugin-syntax-import-attributes': 7.29.7(@babel/core@7.29.7(supports-color@8.1.1)) - '@babel/plugin-syntax-import-meta': 7.10.4(@babel/core@7.29.7(supports-color@8.1.1)) - '@babel/plugin-syntax-json-strings': 7.8.3(@babel/core@7.29.7(supports-color@8.1.1)) - '@babel/plugin-syntax-logical-assignment-operators': 7.10.4(@babel/core@7.29.7(supports-color@8.1.1)) - '@babel/plugin-syntax-nullish-coalescing-operator': 7.8.3(@babel/core@7.29.7(supports-color@8.1.1)) - '@babel/plugin-syntax-numeric-separator': 7.10.4(@babel/core@7.29.7(supports-color@8.1.1)) - '@babel/plugin-syntax-object-rest-spread': 7.8.3(@babel/core@7.29.7(supports-color@8.1.1)) - '@babel/plugin-syntax-optional-catch-binding': 7.8.3(@babel/core@7.29.7(supports-color@8.1.1)) - '@babel/plugin-syntax-optional-chaining': 7.8.3(@babel/core@7.29.7(supports-color@8.1.1)) - '@babel/plugin-syntax-private-property-in-object': 7.14.5(@babel/core@7.29.7(supports-color@8.1.1)) - '@babel/plugin-syntax-top-level-await': 7.14.5(@babel/core@7.29.7(supports-color@8.1.1)) - - babel-preset-jest@29.6.3(@babel/core@7.29.7(supports-color@8.1.1)): - dependencies: - '@babel/core': 7.29.7(supports-color@8.1.1) + babel-preset-current-node-syntax@1.2.0(@babel/core@7.29.7): + dependencies: + '@babel/core': 7.29.7 + '@babel/plugin-syntax-async-generators': 7.8.4(@babel/core@7.29.7) + '@babel/plugin-syntax-bigint': 7.8.3(@babel/core@7.29.7) + '@babel/plugin-syntax-class-properties': 7.12.13(@babel/core@7.29.7) + '@babel/plugin-syntax-class-static-block': 7.14.5(@babel/core@7.29.7) + '@babel/plugin-syntax-import-attributes': 7.29.7(@babel/core@7.29.7) + '@babel/plugin-syntax-import-meta': 7.10.4(@babel/core@7.29.7) + '@babel/plugin-syntax-json-strings': 7.8.3(@babel/core@7.29.7) + '@babel/plugin-syntax-logical-assignment-operators': 7.10.4(@babel/core@7.29.7) + '@babel/plugin-syntax-nullish-coalescing-operator': 7.8.3(@babel/core@7.29.7) + '@babel/plugin-syntax-numeric-separator': 7.10.4(@babel/core@7.29.7) + '@babel/plugin-syntax-object-rest-spread': 7.8.3(@babel/core@7.29.7) + '@babel/plugin-syntax-optional-catch-binding': 7.8.3(@babel/core@7.29.7) + '@babel/plugin-syntax-optional-chaining': 7.8.3(@babel/core@7.29.7) + '@babel/plugin-syntax-private-property-in-object': 7.14.5(@babel/core@7.29.7) + '@babel/plugin-syntax-top-level-await': 7.14.5(@babel/core@7.29.7) + + babel-preset-jest@29.6.3(@babel/core@7.29.7): + dependencies: + '@babel/core': 7.29.7 babel-plugin-jest-hoist: 29.6.3 - babel-preset-current-node-syntax: 1.2.0(@babel/core@7.29.7(supports-color@8.1.1)) + babel-preset-current-node-syntax: 1.2.0(@babel/core@7.29.7) balanced-match@1.0.2: {} + balanced-match@4.0.4: {} + base64-js@1.5.1: {} base64id@2.0.0: {} @@ -5700,11 +5892,11 @@ snapshots: inherits: 2.0.4 readable-stream: 3.6.2 - body-parser@1.20.4(supports-color@8.1.1): + body-parser@1.20.4: dependencies: bytes: 3.1.2 content-type: 1.0.5 - debug: 2.6.9(supports-color@8.1.1) + debug: 2.6.9 depd: 2.0.0 destroy: 1.2.0 http-errors: 2.0.1 @@ -5737,6 +5929,10 @@ snapshots: dependencies: balanced-match: 1.0.2 + brace-expansion@5.0.8: + dependencies: + balanced-match: 4.0.4 + braces@3.0.3: dependencies: fill-range: 7.1.1 @@ -5885,6 +6081,10 @@ snapshots: color-name@1.1.4: {} + combined-stream@1.0.8: + dependencies: + delayed-stream: 1.0.0 + commander@2.20.3: {} commander@4.1.1: {} @@ -5897,15 +6097,17 @@ snapshots: has-own-prop: 2.0.0 repeat-string: 1.6.1 + component-emitter@1.3.1: {} + compressible@2.0.18: dependencies: mime-db: 1.52.0 - compression@1.8.1(supports-color@8.1.1): + compression@1.8.1: dependencies: bytes: 3.1.2 compressible: 2.0.18 - debug: 2.6.9(supports-color@8.1.1) + debug: 2.6.9 negotiator: 0.6.4 on-headers: 1.1.0 safe-buffer: 5.2.1 @@ -5941,8 +6143,12 @@ snapshots: cookie-signature@1.0.7: {} + cookie-signature@1.2.2: {} + cookie@0.7.2: {} + cookiejar@2.1.4: {} + core-util-is@1.0.3: {} cors@2.8.5: @@ -5959,13 +6165,13 @@ snapshots: optionalDependencies: typescript: 5.7.2 - create-jest@29.7.0(@types/node@20.19.30)(supports-color@8.1.1): + create-jest@29.7.0(@types/node@20.19.30): dependencies: '@jest/types': 29.6.3 chalk: 4.1.2 exit: 0.1.2 graceful-fs: 4.2.11 - jest-config: 29.7.0(@types/node@20.19.30)(supports-color@8.1.1) + jest-config: 29.7.0(@types/node@20.19.30) jest-util: 29.7.0 prompts: 2.4.2 transitivePeerDependencies: @@ -5987,23 +6193,17 @@ snapshots: dayjs@1.11.20: {} - debug@2.6.9(supports-color@8.1.1): + debug@2.6.9: dependencies: ms: 2.0.0 - optionalDependencies: - supports-color: 8.1.1 - debug@4.3.7(supports-color@8.1.1): + debug@4.3.7: dependencies: ms: 2.1.3 - optionalDependencies: - supports-color: 8.1.1 - debug@4.4.3(supports-color@8.1.1): + debug@4.4.3: dependencies: ms: 2.1.3 - optionalDependencies: - supports-color: 8.1.1 dedent@1.7.2: {} @@ -6019,12 +6219,19 @@ snapshots: es-errors: 1.3.0 gopd: 1.2.0 + delayed-stream@1.0.0: {} + depd@2.0.0: {} destroy@1.2.0: {} detect-newline@3.1.0: {} + dezalgo@1.0.4: + dependencies: + asap: 2.0.6 + wrappy: 1.0.2 + diff-sequences@29.6.3: {} dom-serializer@2.0.0: @@ -6071,7 +6278,7 @@ snapshots: engine.io-parser@5.2.3: {} - engine.io@6.6.7(supports-color@8.1.1): + engine.io@6.6.7: dependencies: '@types/cors': 2.8.19 '@types/node': 20.19.30 @@ -6080,7 +6287,7 @@ snapshots: base64id: 2.0.0 cookie: 0.7.2 cors: 2.8.5 - debug: 4.4.3(supports-color@8.1.1) + debug: 4.4.3 engine.io-parser: 5.2.3 ws: 8.18.3 transitivePeerDependencies: @@ -6111,6 +6318,13 @@ snapshots: dependencies: es-errors: 1.3.0 + es-set-tostringtag@2.1.0: + dependencies: + es-errors: 1.3.0 + get-intrinsic: 1.3.0 + has-tostringtag: 1.0.2 + hasown: 2.0.4 + escalade@3.2.0: {} escape-html@1.0.3: {} @@ -6164,21 +6378,21 @@ snapshots: jest-message-util: 29.7.0 jest-util: 29.7.0 - express@4.22.1(supports-color@8.1.1): + express@4.22.1: dependencies: accepts: 1.3.8 array-flatten: 1.1.1 - body-parser: 1.20.4(supports-color@8.1.1) + body-parser: 1.20.4 content-disposition: 0.5.4 content-type: 1.0.5 cookie: 0.7.2 cookie-signature: 1.0.7 - debug: 2.6.9(supports-color@8.1.1) + debug: 2.6.9 depd: 2.0.0 encodeurl: 2.0.0 escape-html: 1.0.3 etag: 1.8.1 - finalhandler: 1.3.2(supports-color@8.1.1) + finalhandler: 1.3.2 fresh: 0.5.2 http-errors: 2.0.1 merge-descriptors: 1.0.3 @@ -6190,8 +6404,8 @@ snapshots: qs: 6.14.1 range-parser: 1.2.1 safe-buffer: 5.2.1 - send: 0.19.2(supports-color@8.1.1) - serve-static: 1.16.3(supports-color@8.1.1) + send: 0.19.2 + serve-static: 1.16.3 setprototypeof: 1.2.0 statuses: 2.0.2 type-is: 1.6.18 @@ -6208,6 +6422,10 @@ snapshots: iconv-lite: 0.4.24 tmp: 0.0.33 + fast-check@4.9.0: + dependencies: + pure-rand: 8.4.2 + fast-deep-equal@3.1.3: {} fast-json-stable-stringify@2.1.0: {} @@ -6226,9 +6444,9 @@ snapshots: dependencies: escape-string-regexp: 1.0.5 - file-type@20.4.1(supports-color@8.1.1): + file-type@20.4.1: dependencies: - '@tokenizer/inflate': 0.2.7(supports-color@8.1.1) + '@tokenizer/inflate': 0.2.7 strtok3: 10.3.4 token-types: 6.1.2 uint8array-extras: 1.5.0 @@ -6239,9 +6457,9 @@ snapshots: dependencies: to-regex-range: 5.0.1 - finalhandler@1.3.2(supports-color@8.1.1): + finalhandler@1.3.2: dependencies: - debug: 2.6.9(supports-color@8.1.1) + debug: 2.6.9 encodeurl: 2.0.0 escape-html: 1.0.3 on-finished: 2.4.1 @@ -6261,9 +6479,9 @@ snapshots: cross-spawn: 7.0.6 signal-exit: 4.1.0 - fork-ts-checker-webpack-plugin@9.0.2(typescript@5.7.2)(webpack@5.97.1(uglify-js@3.19.3)): + fork-ts-checker-webpack-plugin@9.0.2(typescript@5.7.2)(webpack@5.97.1): dependencies: - '@babel/code-frame': 7.28.6 + '@babel/code-frame': 7.29.7 chalk: 4.1.2 chokidar: 3.6.0 cosmiconfig: 8.3.6(typescript@5.7.2) @@ -6273,10 +6491,24 @@ snapshots: minimatch: 3.1.2 node-abort-controller: 3.1.1 schema-utils: 3.3.0 - semver: 7.7.3 + semver: 7.8.4 tapable: 2.3.0 typescript: 5.7.2 - webpack: 5.97.1(uglify-js@3.19.3) + webpack: 5.97.1 + + form-data@4.0.6: + dependencies: + asynckit: 0.4.0 + combined-stream: 1.0.8 + es-set-tostringtag: 2.1.0 + hasown: 2.0.4 + mime-types: 2.1.35 + + formidable@3.5.4: + dependencies: + '@paralleldrive/cuid2': 2.3.1 + dezalgo: 1.0.4 + once: 1.4.0 forwarded-parse@2.1.2: {} @@ -6362,6 +6594,15 @@ snapshots: package-json-from-dist: 1.0.1 path-scurry: 1.11.1 + glob@11.0.3: + dependencies: + foreground-child: 3.3.1 + jackspeak: 4.2.3 + minimatch: 10.2.6 + minipass: 7.1.2 + package-json-from-dist: 1.0.1 + path-scurry: 2.0.2 + glob@7.2.3: dependencies: fs.realpath: 1.0.0 @@ -6396,6 +6637,10 @@ snapshots: has-symbols@1.1.0: {} + has-tostringtag@1.0.2: + dependencies: + has-symbols: 1.1.0 + hasown@2.0.4: dependencies: function-bind: 1.1.2 @@ -6532,9 +6777,9 @@ snapshots: istanbul-lib-coverage@3.2.2: {} - istanbul-lib-instrument@5.2.1(supports-color@8.1.1): + istanbul-lib-instrument@5.2.1: dependencies: - '@babel/core': 7.29.7(supports-color@8.1.1) + '@babel/core': 7.29.7 '@babel/parser': 7.29.7 '@istanbuljs/schema': 0.1.6 istanbul-lib-coverage: 3.2.2 @@ -6542,13 +6787,13 @@ snapshots: transitivePeerDependencies: - supports-color - istanbul-lib-instrument@6.0.3(supports-color@8.1.1): + istanbul-lib-instrument@6.0.3: dependencies: - '@babel/core': 7.29.7(supports-color@8.1.1) + '@babel/core': 7.29.7 '@babel/parser': 7.29.7 '@istanbuljs/schema': 0.1.6 istanbul-lib-coverage: 3.2.2 - semver: 7.7.3 + semver: 7.8.4 transitivePeerDependencies: - supports-color @@ -6558,9 +6803,9 @@ snapshots: make-dir: 4.0.0 supports-color: 7.2.0 - istanbul-lib-source-maps@4.0.1(supports-color@8.1.1): + istanbul-lib-source-maps@4.0.1: dependencies: - debug: 4.4.3(supports-color@8.1.1) + debug: 4.4.3 istanbul-lib-coverage: 3.2.2 source-map: 0.6.1 transitivePeerDependencies: @@ -6579,16 +6824,20 @@ snapshots: optionalDependencies: '@pkgjs/parseargs': 0.11.0 + jackspeak@4.2.3: + dependencies: + '@isaacs/cliui': 9.0.0 + jest-changed-files@29.7.0: dependencies: execa: 5.1.1 jest-util: 29.7.0 p-limit: 3.1.0 - jest-circus@29.7.0(supports-color@8.1.1): + jest-circus@29.7.0: dependencies: '@jest/environment': 29.7.0 - '@jest/expect': 29.7.0(supports-color@8.1.1) + '@jest/expect': 29.7.0 '@jest/test-result': 29.7.0 '@jest/types': 29.6.3 '@types/node': 20.19.30 @@ -6599,8 +6848,8 @@ snapshots: jest-each: 29.7.0 jest-matcher-utils: 29.7.0 jest-message-util: 29.7.0 - jest-runtime: 29.7.0(supports-color@8.1.1) - jest-snapshot: 29.7.0(supports-color@8.1.1) + jest-runtime: 29.7.0 + jest-snapshot: 29.7.0 jest-util: 29.7.0 p-limit: 3.1.0 pretty-format: 29.7.0 @@ -6611,16 +6860,16 @@ snapshots: - babel-plugin-macros - supports-color - jest-cli@29.7.0(@types/node@20.19.30)(supports-color@8.1.1): + jest-cli@29.7.0(@types/node@20.19.30): dependencies: - '@jest/core': 29.7.0(supports-color@8.1.1) + '@jest/core': 29.7.0 '@jest/test-result': 29.7.0 '@jest/types': 29.6.3 chalk: 4.1.2 - create-jest: 29.7.0(@types/node@20.19.30)(supports-color@8.1.1) + create-jest: 29.7.0(@types/node@20.19.30) exit: 0.1.2 import-local: 3.2.0 - jest-config: 29.7.0(@types/node@20.19.30)(supports-color@8.1.1) + jest-config: 29.7.0(@types/node@20.19.30) jest-util: 29.7.0 jest-validate: 29.7.0 yargs: 17.7.2 @@ -6630,23 +6879,23 @@ snapshots: - supports-color - ts-node - jest-config@29.7.0(@types/node@20.19.30)(supports-color@8.1.1): + jest-config@29.7.0(@types/node@20.19.30): dependencies: - '@babel/core': 7.29.7(supports-color@8.1.1) + '@babel/core': 7.29.7 '@jest/test-sequencer': 29.7.0 '@jest/types': 29.6.3 - babel-jest: 29.7.0(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1) + babel-jest: 29.7.0(@babel/core@7.29.7) chalk: 4.1.2 ci-info: 3.9.0 deepmerge: 4.3.1 glob: 7.2.3 graceful-fs: 4.2.11 - jest-circus: 29.7.0(supports-color@8.1.1) + jest-circus: 29.7.0 jest-environment-node: 29.7.0 jest-get-type: 29.6.3 jest-regex-util: 29.6.3 jest-resolve: 29.7.0 - jest-runner: 29.7.0(supports-color@8.1.1) + jest-runner: 29.7.0 jest-util: 29.7.0 jest-validate: 29.7.0 micromatch: 4.0.8 @@ -6720,7 +6969,7 @@ snapshots: jest-message-util@29.7.0: dependencies: - '@babel/code-frame': 7.28.6 + '@babel/code-frame': 7.29.7 '@jest/types': 29.6.3 '@types/stack-utils': 2.0.3 chalk: 4.1.2 @@ -6742,10 +6991,10 @@ snapshots: jest-regex-util@29.6.3: {} - jest-resolve-dependencies@29.7.0(supports-color@8.1.1): + jest-resolve-dependencies@29.7.0: dependencies: jest-regex-util: 29.6.3 - jest-snapshot: 29.7.0(supports-color@8.1.1) + jest-snapshot: 29.7.0 transitivePeerDependencies: - supports-color @@ -6761,12 +7010,12 @@ snapshots: resolve.exports: 2.0.3 slash: 3.0.0 - jest-runner@29.7.0(supports-color@8.1.1): + jest-runner@29.7.0: dependencies: '@jest/console': 29.7.0 '@jest/environment': 29.7.0 '@jest/test-result': 29.7.0 - '@jest/transform': 29.7.0(supports-color@8.1.1) + '@jest/transform': 29.7.0 '@jest/types': 29.6.3 '@types/node': 20.19.30 chalk: 4.1.2 @@ -6778,7 +7027,7 @@ snapshots: jest-leak-detector: 29.7.0 jest-message-util: 29.7.0 jest-resolve: 29.7.0 - jest-runtime: 29.7.0(supports-color@8.1.1) + jest-runtime: 29.7.0 jest-util: 29.7.0 jest-watcher: 29.7.0 jest-worker: 29.7.0 @@ -6787,14 +7036,14 @@ snapshots: transitivePeerDependencies: - supports-color - jest-runtime@29.7.0(supports-color@8.1.1): + jest-runtime@29.7.0: dependencies: '@jest/environment': 29.7.0 '@jest/fake-timers': 29.7.0 - '@jest/globals': 29.7.0(supports-color@8.1.1) + '@jest/globals': 29.7.0 '@jest/source-map': 29.6.3 '@jest/test-result': 29.7.0 - '@jest/transform': 29.7.0(supports-color@8.1.1) + '@jest/transform': 29.7.0 '@jest/types': 29.6.3 '@types/node': 20.19.30 chalk: 4.1.2 @@ -6807,24 +7056,24 @@ snapshots: jest-mock: 29.7.0 jest-regex-util: 29.6.3 jest-resolve: 29.7.0 - jest-snapshot: 29.7.0(supports-color@8.1.1) + jest-snapshot: 29.7.0 jest-util: 29.7.0 slash: 3.0.0 strip-bom: 4.0.0 transitivePeerDependencies: - supports-color - jest-snapshot@29.7.0(supports-color@8.1.1): + jest-snapshot@29.7.0: dependencies: - '@babel/core': 7.29.7(supports-color@8.1.1) + '@babel/core': 7.29.7 '@babel/generator': 7.29.7 - '@babel/plugin-syntax-jsx': 7.29.7(@babel/core@7.29.7(supports-color@8.1.1)) - '@babel/plugin-syntax-typescript': 7.29.7(@babel/core@7.29.7(supports-color@8.1.1)) + '@babel/plugin-syntax-jsx': 7.29.7(@babel/core@7.29.7) + '@babel/plugin-syntax-typescript': 7.29.7(@babel/core@7.29.7) '@babel/types': 7.29.7 '@jest/expect-utils': 29.7.0 - '@jest/transform': 29.7.0(supports-color@8.1.1) + '@jest/transform': 29.7.0 '@jest/types': 29.6.3 - babel-preset-current-node-syntax: 1.2.0(@babel/core@7.29.7(supports-color@8.1.1)) + babel-preset-current-node-syntax: 1.2.0(@babel/core@7.29.7) chalk: 4.1.2 expect: 29.7.0 graceful-fs: 4.2.11 @@ -6835,7 +7084,7 @@ snapshots: jest-util: 29.7.0 natural-compare: 1.4.0 pretty-format: 29.7.0 - semver: 7.7.3 + semver: 7.8.4 transitivePeerDependencies: - supports-color @@ -6881,12 +7130,12 @@ snapshots: merge-stream: 2.0.0 supports-color: 8.1.1 - jest@29.7.0(@types/node@20.19.30)(supports-color@8.1.1): + jest@29.7.0(@types/node@20.19.30): dependencies: - '@jest/core': 29.7.0(supports-color@8.1.1) + '@jest/core': 29.7.0 '@jest/types': 29.6.3 import-local: 3.2.0 - jest-cli: 29.7.0(@types/node@20.19.30)(supports-color@8.1.1) + jest-cli: 29.7.0(@types/node@20.19.30) transitivePeerDependencies: - '@types/node' - babel-plugin-macros @@ -6945,7 +7194,7 @@ snapshots: lodash.isstring: 4.0.1 lodash.once: 4.1.1 ms: 2.1.3 - semver: 7.7.3 + semver: 7.8.4 jwa@1.4.2: dependencies: @@ -7007,6 +7256,8 @@ snapshots: lru-cache@10.4.3: {} + lru-cache@11.5.2: {} + lru-cache@5.1.1: dependencies: yallist: 3.1.1 @@ -7019,7 +7270,7 @@ snapshots: make-dir@4.0.0: dependencies: - semver: 7.7.3 + semver: 7.8.4 make-error@1.3.6: {} @@ -7054,8 +7305,14 @@ snapshots: mime@1.6.0: {} + mime@2.6.0: {} + mimic-fn@2.1.0: {} + minimatch@10.2.6: + dependencies: + brace-expansion: 5.0.8 + minimatch@3.1.2: dependencies: brace-expansion: 1.1.12 @@ -7118,6 +7375,14 @@ snapshots: node-int64@0.4.0: {} + node-pg-migrate@7.9.1(@types/pg@8.20.0)(pg@8.21.0): + dependencies: + glob: 11.0.3 + pg: 8.21.0 + yargs: 17.7.2 + optionalDependencies: + '@types/pg': 8.20.0 + node-releases@2.0.27: {} nodemailer@6.10.1: {} @@ -7184,7 +7449,7 @@ snapshots: parse-json@5.2.0: dependencies: - '@babel/code-frame': 7.28.6 + '@babel/code-frame': 7.29.7 error-ex: 1.3.4 json-parse-even-better-errors: 2.3.1 lines-and-columns: 1.2.4 @@ -7206,6 +7471,11 @@ snapshots: lru-cache: 10.4.3 minipass: 7.1.2 + path-scurry@2.0.2: + dependencies: + lru-cache: 11.5.2 + minipass: 7.1.2 + path-to-regexp@0.1.12: {} path-to-regexp@3.3.0: {} @@ -7318,6 +7588,8 @@ snapshots: pure-rand@6.1.0: {} + pure-rand@8.4.2: {} + qs@6.14.1: dependencies: side-channel: 1.1.0 @@ -7441,13 +7713,11 @@ snapshots: semver@6.3.1: {} - semver@7.7.3: {} - semver@7.8.4: {} - send@0.19.2(supports-color@8.1.1): + send@0.19.2: dependencies: - debug: 2.6.9(supports-color@8.1.1) + debug: 2.6.9 depd: 2.0.0 destroy: 1.2.0 encodeurl: 2.0.0 @@ -7467,12 +7737,12 @@ snapshots: dependencies: randombytes: 2.1.0 - serve-static@1.16.3(supports-color@8.1.1): + serve-static@1.16.3: dependencies: encodeurl: 2.0.0 escape-html: 1.0.3 parseurl: 1.3.3 - send: 0.19.2(supports-color@8.1.1) + send: 0.19.2 transitivePeerDependencies: - supports-color @@ -7531,45 +7801,45 @@ snapshots: slash@3.0.0: {} - socket.io-adapter@2.5.6(supports-color@8.1.1): + socket.io-adapter@2.5.6: dependencies: - debug: 4.4.3(supports-color@8.1.1) + debug: 4.4.3 ws: 8.18.3 transitivePeerDependencies: - bufferutil - supports-color - utf-8-validate - socket.io-parser@4.2.6(supports-color@8.1.1): + socket.io-parser@4.2.6: dependencies: '@socket.io/component-emitter': 3.1.2 - debug: 4.4.3(supports-color@8.1.1) + debug: 4.4.3 transitivePeerDependencies: - supports-color - socket.io@4.8.1(supports-color@8.1.1): + socket.io@4.8.1: dependencies: accepts: 1.3.8 base64id: 2.0.0 cors: 2.8.5 - debug: 4.3.7(supports-color@8.1.1) - engine.io: 6.6.7(supports-color@8.1.1) - socket.io-adapter: 2.5.6(supports-color@8.1.1) - socket.io-parser: 4.2.6(supports-color@8.1.1) + debug: 4.3.7 + engine.io: 6.6.7 + socket.io-adapter: 2.5.6 + socket.io-parser: 4.2.6 transitivePeerDependencies: - bufferutil - supports-color - utf-8-validate - socket.io@4.8.3(supports-color@8.1.1): + socket.io@4.8.3: dependencies: accepts: 1.3.8 base64id: 2.0.0 cors: 2.8.5 - debug: 4.4.3(supports-color@8.1.1) - engine.io: 6.6.7(supports-color@8.1.1) - socket.io-adapter: 2.5.6(supports-color@8.1.1) - socket.io-parser: 4.2.6(supports-color@8.1.1) + debug: 4.4.3 + engine.io: 6.6.7 + socket.io-adapter: 2.5.6 + socket.io-parser: 4.2.6 transitivePeerDependencies: - bufferutil - supports-color @@ -7644,6 +7914,28 @@ snapshots: dependencies: '@tokenizer/token': 0.3.0 + superagent@10.3.0: + dependencies: + component-emitter: 1.3.1 + cookiejar: 2.1.4 + debug: 4.4.3 + fast-safe-stringify: 2.1.1 + form-data: 4.0.6 + formidable: 3.5.4 + methods: 1.1.2 + mime: 2.6.0 + qs: 6.14.1 + transitivePeerDependencies: + - supports-color + + supertest@7.2.2: + dependencies: + cookie-signature: 1.2.2 + methods: 1.1.2 + superagent: 10.3.0 + transitivePeerDependencies: + - supports-color + supports-color@7.2.0: dependencies: has-flag: 4.0.0 @@ -7660,9 +7952,9 @@ snapshots: dependencies: '@scarf/scarf': 1.4.0 - swagger-ui-express@5.0.1(express@4.22.1(supports-color@8.1.1)): + swagger-ui-express@5.0.1(express@4.22.1): dependencies: - express: 4.22.1(supports-color@8.1.1) + express: 4.22.1 swagger-ui-dist: 5.32.6 symbol-observable@4.0.0: {} @@ -7673,16 +7965,14 @@ snapshots: dependencies: bintrees: 1.0.2 - terser-webpack-plugin@5.3.16(uglify-js@3.19.3)(webpack@5.97.1(uglify-js@3.19.3)): + terser-webpack-plugin@5.3.16(webpack@5.97.1): dependencies: '@jridgewell/trace-mapping': 0.3.31 jest-worker: 27.5.1 schema-utils: 4.3.3 serialize-javascript: 6.0.2 terser: 5.46.0 - webpack: 5.97.1(uglify-js@3.19.3) - optionalDependencies: - uglify-js: 3.19.3 + webpack: 5.97.1 terser@5.46.0: dependencies: @@ -7721,12 +8011,12 @@ snapshots: tree-kill@1.2.2: {} - ts-jest@29.4.11(@babel/core@7.29.7(supports-color@8.1.1))(@jest/transform@29.7.0(supports-color@8.1.1))(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(jest-util@29.7.0)(jest@29.7.0(@types/node@20.19.30)(supports-color@8.1.1))(typescript@5.9.3): + ts-jest@29.4.11(@babel/core@7.29.7)(@jest/transform@29.7.0)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@7.29.7))(jest-util@29.7.0)(jest@29.7.0(@types/node@20.19.30))(typescript@5.9.3): dependencies: bs-logger: 0.2.6 fast-json-stable-stringify: 2.1.0 handlebars: 4.7.9 - jest: 29.7.0(@types/node@20.19.30)(supports-color@8.1.1) + jest: 29.7.0(@types/node@20.19.30) json5: 2.2.3 lodash.memoize: 4.1.2 make-error: 1.3.6 @@ -7735,10 +8025,10 @@ snapshots: typescript: 5.9.3 yargs-parser: 21.1.1 optionalDependencies: - '@babel/core': 7.29.7(supports-color@8.1.1) - '@jest/transform': 29.7.0(supports-color@8.1.1) + '@babel/core': 7.29.7 + '@jest/transform': 29.7.0 '@jest/types': 29.6.3 - babel-jest: 29.7.0(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1) + babel-jest: 29.7.0(@babel/core@7.29.7) jest-util: 29.7.0 tsconfig-paths-webpack-plugin@4.2.0: @@ -7837,7 +8127,7 @@ snapshots: webpack-sources@3.3.3: {} - webpack@5.97.1(uglify-js@3.19.3): + webpack@5.97.1: dependencies: '@types/eslint-scope': 3.7.7 '@types/estree': 1.0.8 @@ -7859,7 +8149,7 @@ snapshots: neo-async: 2.6.2 schema-utils: 3.3.0 tapable: 2.3.0 - terser-webpack-plugin: 5.3.16(uglify-js@3.19.3)(webpack@5.97.1(uglify-js@3.19.3)) + terser-webpack-plugin: 5.3.16(webpack@5.97.1) watchpack: 2.5.1 webpack-sources: 3.3.3 transitivePeerDependencies: @@ -7912,6 +8202,7 @@ snapshots: xstreamroll@file:..(react@19.2.7): dependencies: '@hookform/resolvers': 5.4.0(react-hook-form@7.81.0(react@19.2.7)) + '@tanstack/react-query': 5.101.4(react@19.2.7) js-cookie: 3.0.8 react-hook-form: 7.81.0(react@19.2.7) zod: 4.4.3 diff --git a/docker-compose.yml b/docker-compose.yml index d36c4db..f2e8440 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -43,7 +43,8 @@ services: api: build: - context: ./api + context: . + dockerfile: api/Dockerfile ports: - "3001:3001" environment: diff --git a/k8s/README.md b/k8s/README.md index 8bfbb97..03fc1b8 100644 --- a/k8s/README.md +++ b/k8s/README.md @@ -363,6 +363,147 @@ kubectl -n xstreamroll create configmap postgres-init \ --dry-run=client -o yaml | kubectl apply -f - ``` +## Monitoring (issue #349) + +Prometheus alerting rules and a Grafana dashboard ship with the project +in the `monitoring/` directory at the repo root. Operators deploying the +kube-prometheus-stack (or any Prometheus+Grafana setup) can import these +definitions to get production-ready observability out of the box. + +### Prometheus alerting rules + +`monitoring/prometheus-rules.yaml` defines alert rules for: + +| Alert | Severity | Trigger | +| -------------------------- | -------- | ---------------------------------------------------- | +| `HighErrorRate` | critical | 5xx error rate > 5 % for 5 min | +| `HighLatency` | warning | API p99 latency > 1 s for 5 min | +| `WebSocketConnectionsZero` | warning | No active WebSocket connections for 5 min | +| `DatabasePoolSaturation` | warning | Waiting requests > idle connections for 2 min | +| `WorkerRestarts` | warning | Worker uptime counter reset (crashed) | +| `HighQueueDepth` | warning | Worker queue > 200 for 10 min | +| `CriticalQueueDepth` | critical | Worker queue > 500 for 5 min (events being dropped) | +| `WorkerHighErrorRate` | warning | Worker errors > 10/s for 5 min | +| `LockRenewalFailures` | warning | Lock renewals failing (duplicate processing risk) | + +#### Validating the rules + +```bash +# Requires promtool (ships with Prometheus). +promtool check rules monitoring/prometheus-rules.yaml +# Expected: SUCCESS: ... rules found, 0 invalid +``` + +This validation runs in CI (`validate-monitoring-rules` job in +`.github/workflows/ci.yml`) so every PR keeps the rules in-sync. + +#### Installing the rules + +**kube-prometheus-stack (recommended):** + +```bash +# Create the PrometheusRule resource in the monitoring namespace. +kubectl -n monitoring apply -f monitoring/prometheus-rules.yaml +``` + +**Standalone Prometheus:** + +Add the rules file to your Prometheus config's `rule_files:` section and +reload: + +```yaml +rule_files: + - /etc/prometheus/rules/xstreamroll/*.yaml +``` + +```bash +curl -X POST http://localhost:9090/-/reload +``` + +### Grafana dashboard + +`monitoring/grafana-dashboard.json` is a pre-built dashboard covering: + +| Section | Panels | +| -------------------- | ----------------------------------------------------------- | +| API Overview | Request rate by path, latency percentiles (p50/p90/p99) | +| | Error rate gauge, active WebSocket connections, DB pool | +| Processing Worker | Queue depth, throughput (messages vs errors), worker status | +| | Lock activity (acquired/denied), active locks | +| Database | Connection pool (active / idle / waiting) | + +#### Importing the dashboard + +> **Note on data source UID:** The dashboard assumes the Prometheus data +> source UID is `prometheus`. If your Grafana instance uses a different +> UID (check under **Connections → Data Sources**), update all +> `"datasource": { "type": "prometheus", "uid": "..." }` references in +> the JSON before importing, or use Grafana's **Dashboards → Import → +> Change UID** feature. + +**Via Grafana UI:** + +1. Navigate to **Dashboards → New → Import**. +2. Upload `monitoring/grafana-dashboard.json` or paste its contents. +3. Select the Prometheus data source. +4. Click **Import**. + +**Via ConfigMap (GitOps):** + +```bash +kubectl -n monitoring create configmap grafana-dashboard-xstreamroll \ + --from-file=monitoring/grafana-dashboard.json \ + -o yaml --dry-run=client | kubectl apply -f - + +# Label so the Grafana sidecar picks it up. +kubectl -n monitoring label configmap grafana-dashboard-xstreamroll \ + grafana_dashboard=1 --overwrite +``` + +### Prometheus scrape configuration + +The API exposes metrics at `:3001/metrics` and the processing worker at +`:3002/metrics`. Add the following scrape targets to your Prometheus +configuration: + +```yaml +scrape_configs: + - job_name: xstreamroll-api + kubernetes_sd_configs: + - role: pod + namespaces: + names: + - xstreamroll + relabel_configs: + - source_labels: [__meta_kubernetes_pod_label_app] + action: keep + regex: api + - source_labels: [__meta_kubernetes_pod_container_port_number] + action: keep + regex: "3001" + + - job_name: xstreamroll-processing + kubernetes_sd_configs: + - role: pod + namespaces: + names: + - xstreamroll + relabel_configs: + - source_labels: [__meta_kubernetes_pod_label_app] + action: keep + regex: processing + - source_labels: [__meta_kubernetes_pod_container_port_number] + action: keep + regex: "3002" +``` + +> **NetworkPolicy note:** The processing worker metrics port (`3002`) +> is reachable from any pod in the `xstreamroll` namespace per +> `k8s/70-network-policies.yaml`. Prometheus deployments outside the +> `xstreamroll` namespace (e.g., in a separate `monitoring` namespace) +> need an additional ingress rule — see the [Known +> limitations](#known-limitations) section above. + ## Verifying the rollout ```bash diff --git a/monitoring/grafana-dashboard.json b/monitoring/grafana-dashboard.json new file mode 100644 index 0000000..75ecf30 --- /dev/null +++ b/monitoring/grafana-dashboard.json @@ -0,0 +1,773 @@ +{ + "annotations": { + "list": [ + { + "builtIn": 1, + "datasource": { "type": "datasource", "uid": "grafana" }, + "enable": true, + "hide": true, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations & Alerts", + "type": "dashboard" + } + ] + }, + "editable": true, + "fiscalYearStartMonth": 0, + "graphTooltip": 1, + "id": null, + "links": [], + "panels": [ + { + "collapsed": false, + "gridPos": { "h": 1, "w": 24, "x": 0, "y": 0 }, + "id": 100, + "panels": [], + "title": "API Overview", + "type": "row" + }, + { + "datasource": { "type": "prometheus", "uid": "prometheus" }, + "fieldConfig": { + "defaults": { + "color": { "mode": "palette-classic" }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 10, + "gradientMode": "none", + "hideFrom": { "legend": false, "tooltip": false, "viz": false }, + "insertNulls": false, + "lineInterpolation": "smooth", + "lineWidth": 2, + "pointSize": 5, + "scaleDistribution": { "type": "linear" }, + "showPoints": "never", + "spanNulls": false, + "stacking": { "group": "A", "mode": "none" }, + "thresholdsStyle": { "mode": "off" } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { "color": "green", "value": null }, + { "color": "red", "value": 80 } + ] + }, + "unit": "reqps" + }, + "overrides": [] + }, + "gridPos": { "h": 9, "w": 12, "x": 0, "y": 1 }, + "id": 1, + "options": { + "legend": { + "calcs": ["mean", "max", "lastNotNull"], + "displayMode": "table", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { "mode": "multi", "sort": "desc" } + }, + "targets": [ + { + "datasource": { "type": "prometheus", "uid": "prometheus" }, + "editorMode": "code", + "expr": "sum(rate(http_requests_total[5m])) by (path)", + "legendFormat": "{{ path }}", + "range": true, + "refId": "A" + } + ], + "title": "API Request Rate (by path)", + "type": "timeseries" + }, + { + "datasource": { "type": "prometheus", "uid": "prometheus" }, + "fieldConfig": { + "defaults": { + "color": { "mode": "palette-classic" }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 10, + "gradientMode": "none", + "hideFrom": { "legend": false, "tooltip": false, "viz": false }, + "insertNulls": false, + "lineInterpolation": "smooth", + "lineWidth": 2, + "pointSize": 5, + "scaleDistribution": { "type": "linear" }, + "showPoints": "never", + "spanNulls": false, + "stacking": { "group": "A", "mode": "none" }, + "thresholdsStyle": { "mode": "off" } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { "color": "green", "value": null }, + { "color": "red", "value": 80 } + ] + }, + "unit": "s" + }, + "overrides": [] + }, + "gridPos": { "h": 9, "w": 12, "x": 12, "y": 1 }, + "id": 2, + "options": { + "legend": { + "calcs": ["mean", "max", "lastNotNull"], + "displayMode": "table", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { "mode": "multi", "sort": "desc" } + }, + "targets": [ + { + "datasource": { "type": "prometheus", "uid": "prometheus" }, + "editorMode": "code", + "expr": "job:http_request_duration_seconds:p50", + "legendFormat": "p50", + "range": true, + "refId": "A" + }, + { + "datasource": { "type": "prometheus", "uid": "prometheus" }, + "editorMode": "code", + "expr": "job:http_request_duration_seconds:p90", + "legendFormat": "p90", + "range": true, + "refId": "B" + }, + { + "datasource": { "type": "prometheus", "uid": "prometheus" }, + "editorMode": "code", + "expr": "job:http_request_duration_seconds:p99", + "legendFormat": "p99", + "range": true, + "refId": "C" + } + ], + "title": "API Latency Percentiles", + "type": "timeseries" + }, + { + "datasource": { "type": "prometheus", "uid": "prometheus" }, + "fieldConfig": { + "defaults": { + "color": { "mode": "thresholds" }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { "color": "green", "value": null }, + { "color": "red", "value": 0.05 } + ] + }, + "unit": "percentunit" + }, + "overrides": [] + }, + "gridPos": { "h": 7, "w": 8, "x": 0, "y": 10 }, + "id": 3, + "options": { + "orientation": "auto", + "reduceOptions": { + "calcs": ["lastNotNull"], + "values": false + }, + "showThresholdLabels": false, + "showThresholdMarkers": true, + "text": {} + }, + "pluginVersion": "10.4.0", + "targets": [ + { + "datasource": { "type": "prometheus", "uid": "prometheus" }, + "editorMode": "code", + "expr": "sum(rate(http_requests_total{status_code=~\"5..\"}[5m])) / sum(rate(http_requests_total[5m]))", + "legendFormat": "5xx Error Rate", + "range": true, + "refId": "A" + } + ], + "title": "API Error Rate (5xx)", + "type": "gauge" + }, + { + "datasource": { "type": "prometheus", "uid": "prometheus" }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { "color": "red", "value": null }, + { "color": "green", "value": 1 } + ] + }, + "unit": "short" + }, + "overrides": [] + }, + "gridPos": { "h": 7, "w": 8, "x": 8, "y": 10 }, + "id": 4, + "options": { + "colorMode": "value", + "graphMode": "area", + "justifyMode": "auto", + "orientation": "auto", + "percentChangeColorMode": "standard", + "reduceOptions": { + "calcs": ["lastNotNull"], + "values": false + }, + "showPercentChange": false, + "text": {}, + "textMode": "auto", + "wideLayout": true + }, + "pluginVersion": "10.4.0", + "targets": [ + { + "datasource": { "type": "prometheus", "uid": "prometheus" }, + "editorMode": "code", + "expr": "websocket_active_connections", + "legendFormat": "Active WebSocket Connections", + "range": true, + "refId": "A" + } + ], + "title": "Active WebSocket Connections", + "type": "stat" + }, + { + "datasource": { "type": "prometheus", "uid": "prometheus" }, + "fieldConfig": { + "defaults": { + "color": { "mode": "thresholds" }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { "color": "green", "value": null }, + { "color": "orange", "value": 5 }, + { "color": "red", "value": 10 } + ] + }, + "unit": "short" + }, + "overrides": [] + }, + "gridPos": { "h": 7, "w": 8, "x": 16, "y": 10 }, + "id": 5, + "options": { + "colorMode": "value", + "graphMode": "area", + "justifyMode": "auto", + "orientation": "auto", + "percentChangeColorMode": "standard", + "reduceOptions": { + "calcs": ["lastNotNull"], + "values": false + }, + "showPercentChange": false, + "text": {}, + "textMode": "auto", + "wideLayout": true + }, + "pluginVersion": "10.4.0", + "targets": [ + { + "datasource": { "type": "prometheus", "uid": "prometheus" }, + "editorMode": "code", + "expr": "db_pool_waiting_requests", + "legendFormat": "Waiting DB Requests", + "range": true, + "refId": "A" + } + ], + "title": "DB Pool Waiting Requests", + "type": "stat" + }, + { + "collapsed": false, + "gridPos": { "h": 1, "w": 24, "x": 0, "y": 17 }, + "id": 200, + "panels": [], + "title": "Processing Worker", + "type": "row" + }, + { + "datasource": { "type": "prometheus", "uid": "prometheus" }, + "fieldConfig": { + "defaults": { + "color": { "mode": "palette-classic" }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 10, + "gradientMode": "none", + "hideFrom": { "legend": false, "tooltip": false, "viz": false }, + "insertNulls": false, + "lineInterpolation": "smooth", + "lineWidth": 2, + "pointSize": 5, + "scaleDistribution": { "type": "linear" }, + "showPoints": "never", + "spanNulls": false, + "stacking": { "group": "A", "mode": "none" }, + "thresholdsStyle": { "mode": "off" } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { "color": "green", "value": null }, + { "color": "red", "value": 80 } + ] + }, + "unit": "none" + }, + "overrides": [] + }, + "gridPos": { "h": 9, "w": 12, "x": 0, "y": 18 }, + "id": 6, + "options": { + "legend": { + "calcs": ["mean", "max", "lastNotNull"], + "displayMode": "table", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { "mode": "multi", "sort": "desc" } + }, + "targets": [ + { + "datasource": { "type": "prometheus", "uid": "prometheus" }, + "editorMode": "code", + "expr": "xstreamroll_queue_depth", + "legendFormat": "Queue Depth", + "range": true, + "refId": "A" + } + ], + "title": "Worker Queue Depth", + "type": "timeseries" + }, + { + "datasource": { "type": "prometheus", "uid": "prometheus" }, + "fieldConfig": { + "defaults": { + "color": { "mode": "palette-classic" }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 10, + "gradientMode": "none", + "hideFrom": { "legend": false, "tooltip": false, "viz": false }, + "insertNulls": false, + "lineInterpolation": "smooth", + "lineWidth": 2, + "pointSize": 5, + "scaleDistribution": { "type": "linear" }, + "showPoints": "never", + "spanNulls": false, + "stacking": { "group": "A", "mode": "none" }, + "thresholdsStyle": { "mode": "off" } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { "color": "green", "value": null }, + { "color": "red", "value": 80 } + ] + }, + "unit": "none" + }, + "overrides": [] + }, + "gridPos": { "h": 9, "w": 12, "x": 12, "y": 18 }, + "id": 7, + "options": { + "legend": { + "calcs": ["mean", "max", "lastNotNull"], + "displayMode": "table", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { "mode": "multi", "sort": "desc" } + }, + "targets": [ + { + "datasource": { "type": "prometheus", "uid": "prometheus" }, + "editorMode": "code", + "expr": "rate(xstreamroll_messages_processed_total[5m])", + "legendFormat": "Processed", + "range": true, + "refId": "A" + }, + { + "datasource": { "type": "prometheus", "uid": "prometheus" }, + "editorMode": "code", + "expr": "rate(xstreamroll_errors_total[5m])", + "legendFormat": "Errors", + "range": true, + "refId": "B" + } + ], + "title": "Worker Throughput (messages vs errors)", + "type": "timeseries" + }, + { + "datasource": { "type": "prometheus", "uid": "prometheus" }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [ + { + "options": { + "0": { "color": "red", "index": 1, "text": "Stopped" }, + "1": { "color": "green", "index": 0, "text": "Running" } + }, + "type": "value" + } + ], + "thresholds": { + "mode": "absolute", + "steps": [ + { "color": "green", "value": null }, + { "color": "red", "value": 0.5 } + ] + }, + "unit": "none" + }, + "overrides": [] + }, + "gridPos": { "h": 6, "w": 8, "x": 0, "y": 27 }, + "id": 8, + "options": { + "colorMode": "background", + "graphMode": "none", + "justifyMode": "auto", + "orientation": "auto", + "percentChangeColorMode": "standard", + "reduceOptions": { + "calcs": ["lastNotNull"], + "values": false + }, + "showPercentChange": false, + "text": {}, + "textMode": "auto", + "wideLayout": true + }, + "pluginVersion": "10.4.0", + "targets": [ + { + "datasource": { "type": "prometheus", "uid": "prometheus" }, + "editorMode": "code", + "expr": "xstreamroll_uptime_seconds > 0", + "legendFormat": "Worker Running", + "range": true, + "refId": "A" + } + ], + "title": "Worker Status", + "type": "stat" + }, + { + "datasource": { "type": "prometheus", "uid": "prometheus" }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 30, + "gradientMode": "none", + "hideFrom": { "legend": false, "tooltip": false, "viz": false }, + "insertNulls": false, + "lineInterpolation": "smooth", + "lineWidth": 2, + "pointSize": 5, + "scaleDistribution": { "type": "linear" }, + "showPoints": "never", + "spanNulls": false, + "stacking": { "group": "A", "mode": "normal" }, + "thresholdsStyle": { "mode": "off" } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { "color": "green", "value": null } + ] + }, + "unit": "none" + }, + "overrides": [ + { + "matcher": { "id": "byName", "options": "Held" }, + "properties": [ + { "id": "color", "value": { "fixedColor": "green", "mode": "fixed" } } + ] + }, + { + "matcher": { "id": "byName", "options": "Denied" }, + "properties": [ + { "id": "color", "value": { "fixedColor": "orange", "mode": "fixed" } } + ] + }, + { + "matcher": { "id": "byName", "options": "Failed" }, + "properties": [ + { "id": "color", "value": { "fixedColor": "red", "mode": "fixed" } } + ] + } + ] + }, + "gridPos": { "h": 6, "w": 8, "x": 8, "y": 27 }, + "id": 9, + "options": { + "legend": { + "calcs": ["sum", "lastNotNull"], + "displayMode": "table", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { "mode": "multi", "sort": "desc" } + }, + "targets": [ + { + "datasource": { "type": "prometheus", "uid": "prometheus" }, + "editorMode": "code", + "expr": "rate(xstreamroll_lock_acquisitions_total[5m])", + "legendFormat": "Acquired", + "range": true, + "refId": "A" + }, + { + "datasource": { "type": "prometheus", "uid": "prometheus" }, + "editorMode": "code", + "expr": "rate(xstreamroll_lock_acquisitions_denied[5m])", + "legendFormat": "Denied", + "range": true, + "refId": "B" + } + ], + "title": "Worker Lock Activity", + "type": "timeseries" + }, + { + "datasource": { "type": "prometheus", "uid": "prometheus" }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { "color": "green", "value": null }, + { "color": "yellow", "value": 5 }, + { "color": "red", "value": 20 } + ] + }, + "unit": "none" + }, + "overrides": [] + }, + "gridPos": { "h": 6, "w": 8, "x": 16, "y": 27 }, + "id": 10, + "options": { + "colorMode": "value", + "graphMode": "area", + "justifyMode": "auto", + "orientation": "auto", + "percentChangeColorMode": "standard", + "reduceOptions": { + "calcs": ["lastNotNull"], + "values": false + }, + "showPercentChange": false, + "text": {}, + "textMode": "auto", + "wideLayout": true + }, + "pluginVersion": "10.4.0", + "targets": [ + { + "datasource": { "type": "prometheus", "uid": "prometheus" }, + "editorMode": "code", + "expr": "xstreamroll_active_locks", + "legendFormat": "Active Locks", + "range": true, + "refId": "A" + } + ], + "title": "Active Worker Locks", + "type": "stat" + }, + { + "collapsed": false, + "gridPos": { "h": 1, "w": 24, "x": 0, "y": 33 }, + "id": 300, + "panels": [], + "title": "Database", + "type": "row" + }, + { + "datasource": { "type": "prometheus", "uid": "prometheus" }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 30, + "gradientMode": "none", + "hideFrom": { "legend": false, "tooltip": false, "viz": false }, + "insertNulls": false, + "lineInterpolation": "smooth", + "lineWidth": 2, + "pointSize": 5, + "scaleDistribution": { "type": "linear" }, + "showPoints": "never", + "spanNulls": false, + "stacking": { "group": "A", "mode": "normal" }, + "thresholdsStyle": { "mode": "off" } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { "color": "green", "value": null } + ] + }, + "unit": "short" + }, + "overrides": [ + { + "matcher": { "id": "byName", "options": "Active" }, + "properties": [ + { "id": "color", "value": { "fixedColor": "blue", "mode": "fixed" } } + ] + }, + { + "matcher": { "id": "byName", "options": "Idle" }, + "properties": [ + { "id": "color", "value": { "fixedColor": "green", "mode": "fixed" } } + ] + }, + { + "matcher": { "id": "byName", "options": "Waiting" }, + "properties": [ + { "id": "color", "value": { "fixedColor": "orange", "mode": "fixed" } } + ] + } + ] + }, + "gridPos": { "h": 8, "w": 24, "x": 0, "y": 34 }, + "id": 11, + "options": { + "legend": { + "calcs": ["lastNotNull"], + "displayMode": "table", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { "mode": "multi", "sort": "desc" } + }, + "targets": [ + { + "datasource": { "type": "prometheus", "uid": "prometheus" }, + "editorMode": "code", + "expr": "db_pool_active_connections", + "legendFormat": "Active", + "range": true, + "refId": "A" + }, + { + "datasource": { "type": "prometheus", "uid": "prometheus" }, + "editorMode": "code", + "expr": "db_pool_idle_connections", + "legendFormat": "Idle", + "range": true, + "refId": "B" + }, + { + "datasource": { "type": "prometheus", "uid": "prometheus" }, + "editorMode": "code", + "expr": "db_pool_waiting_requests", + "legendFormat": "Waiting", + "range": true, + "refId": "C" + } + ], + "title": "Database Connection Pool", + "type": "timeseries" + } + ], + "refresh": "30s", + "schemaVersion": 39, + "tags": ["xstreamroll", "api", "worker"], + "templating": { + "list": [] + }, + "time": { + "from": "now-6h", + "to": "now" + }, + "timepicker": {}, + "timezone": "browser", + "title": "XStreamRoll", + "uid": "xstreamroll", + "version": 1, + "weekStart": "" +} diff --git a/monitoring/prometheus-rules.yaml b/monitoring/prometheus-rules.yaml new file mode 100644 index 0000000..7faf858 --- /dev/null +++ b/monitoring/prometheus-rules.yaml @@ -0,0 +1,211 @@ +# XStreamRoll Prometheus Alerting Rules +# Issue #349 — Observability: alerting rules for production operators. +# +# Validated with: promtool check rules monitoring/prometheus-rules.yaml +# +# Every alert includes: +# • `severity` label — `critical` (page someone now), `warning` (page during +# business hours), or `info` (audit only). +# • `runbook_url` annotation — points into the project docs so the on-call +# engineer can follow step-by-step debugging instructions without hunting +# through READMEs. +# • Conservative `for` windows so alerts fire on sustained conditions, not +# transient spikes. + +groups: + # ── API alerts ──────────────────────────────────────────────────── + - name: xstreamroll-api + rules: + # High 5xx error rate. 5 % of requests returning server-side errors + # over a 5-minute window means the application is failing to handle + # a significant fraction of traffic. + - alert: HighErrorRate + expr: | + ( + sum(rate(http_requests_total{status_code=~"5.."}[5m])) + / + sum(rate(http_requests_total[5m])) + ) > 0.05 + for: 5m + labels: + severity: critical + annotations: + summary: "High API error rate ({{ $value | humanizePercentage }})" + description: > + The 5xx error rate is {{ $value | humanizePercentage }} over the + last 5 minutes. Check API logs (`kubectl logs -l app=api`) and + database connectivity (`/health` readiness probe). + runbook_url: "https://github.com/XStreamRollz/XStreamRoll/blob/main/docs/runbooks/api-high-error-rate.md" + + # P99 latency > 1 s. The 99th-percentile request duration exceeding + # 1 second indicates degraded user experience. + - alert: HighLatency + expr: | + histogram_quantile( + 0.99, + sum(rate(http_request_duration_seconds_bucket[5m])) by (le) + ) > 1 + for: 5m + labels: + severity: warning + annotations: + summary: "API p99 latency > 1 s ({{ $value }}s)" + description: > + The 99th-percentile HTTP request duration is {{ $value }} seconds. + Check for slow database queries, upstream dependency timeouts, or + CPU throttling on the API pods. + runbook_url: "https://github.com/XStreamRollz/XStreamRoll/blob/main/docs/runbooks/api-high-latency.md" + + # WebSocket connections drop to zero. Zero active WebSocket + # connections for 5 minutes means the real-time pipeline is broken. + - alert: WebSocketConnectionsZero + expr: websocket_active_connections == 0 + for: 5m + labels: + severity: warning + annotations: + summary: "No active WebSocket connections" + description: > + The WebSocket connection gauge has been zero for 5 minutes. + Verify that clients can reach the API and that the gateway + is proxying WebSocket upgrades correctly. + runbook_url: "https://github.com/XStreamRollz/XStreamRoll/blob/main/docs/runbooks/websocket-zero-connections.md" + + # Database connection pool saturation. When the number of waiting + # requests exceeds the number of idle connections, clients are + # queuing for database access and will experience latency. + - alert: DatabasePoolSaturation + expr: db_pool_waiting_requests > db_pool_idle_connections + for: 2m + labels: + severity: warning + annotations: + summary: "Database connection pool saturated" + description: > + {{ $value }} requests are waiting for a database connection + while only {{ with query "db_pool_idle_connections" }}{{ . | first + | value }}{{ end }} connections are idle. Consider increasing + the pool size or scaling the database. + runbook_url: "https://github.com/XStreamRollz/XStreamRoll/blob/main/docs/runbooks/db-pool-saturation.md" + + # ── Processing worker alerts ────────────────────────────────────── + - name: xstreamroll-processing + rules: + # Worker restarts. The `xstreamroll_uptime_seconds` gauge resets to + # zero on every restart. A drop signals a crash or OOM kill. + - alert: WorkerRestarts + expr: | + ( + xstreamroll_uptime_seconds + - + xstreamroll_uptime_seconds offset 2m + ) < 0 + for: 1m + labels: + severity: warning + annotations: + summary: "Processing worker restarted (instance {{ $labels.instance }})" + description: > + The processing worker uptime dropped in the last 2 minutes, + indicating a restart. Check the worker logs for crash details: + `kubectl logs -l app=processing --previous`. + runbook_url: "https://github.com/XStreamRollz/XStreamRoll/blob/main/docs/runbooks/worker-restarts.md" + + # High queue depth. Sustained queue depth above 80 % of the + # configured MAX_QUEUE_DEPTH means the worker is falling behind + # on event processing. + # + # TUNING: The 200 / 500 thresholds assume the default + # MAX_QUEUE_DEPTH and HIGH_WATERMARK values. Operators who + # change these via environment variables should adjust the + # alert thresholds to match. + - alert: HighQueueDepth + expr: xstreamroll_queue_depth > 200 + for: 10m + labels: + severity: warning + annotations: + summary: "Processing worker queue depth elevated ({{ $value }})" + description: > + The worker queue depth is {{ $value }} — above the warning + threshold of 200 events. If sustained, consider scaling out + the processing worker or checking for slow session processing. + runbook_url: "https://github.com/XStreamRollz/XStreamRoll/blob/main/docs/runbooks/worker-high-queue.md" + + # Critical queue depth (imminent back-pressure). When queue depth + # exceeds 500 events the worker will begin dropping events at the + # high-watermark. + # + # TUNING: As with HighQueueDepth above, adjust this threshold to + # match your deployment's MAX_QUEUE_DEPTH configuration. + - alert: CriticalQueueDepth + expr: xstreamroll_queue_depth > 500 + for: 5m + labels: + severity: critical + annotations: + summary: "Processing worker queue depth critical ({{ $value }})" + description: > + The worker queue depth is {{ $value }} — approaching the + high-watermark where events will be dropped. Scale out + immediately or investigate the processing pipeline for + bottlenecks. + runbook_url: "https://github.com/XStreamRollz/XStreamRoll/blob/main/docs/runbooks/worker-high-queue.md" + + # High error rate on the processing worker. Worker errors include + # API polling failures, lock acquisition denials, and session + # processing exceptions. + - alert: WorkerHighErrorRate + expr: | + rate(xstreamroll_errors_total[5m]) > 10 + for: 5m + labels: + severity: warning + annotations: + summary: "Processing worker error rate elevated ({{ $value }} errors/s)" + description: > + The worker is logging {{ $value }} errors per second over the + last 5 minutes. Check worker logs for recurring failures and + verify the API is healthy. + runbook_url: "https://github.com/XStreamRollz/XStreamRoll/blob/main/docs/runbooks/worker-high-error-rate.md" + + # Lock renewal failures (lock loss). The worker is losing locks + # it previously held, which can cause duplicate session processing + # and wasted work. + - alert: LockRenewalFailures + expr: rate(xstreamroll_lock_renewals_failed[5m]) > 0 + for: 5m + labels: + severity: warning + annotations: + summary: "Worker lock renewals failing ({{ $value }} failures/s)" + description: > + Lock renewals are failing at {{ $value }} per second. This + suggests a connectivity issue between the worker and the lock + backend (Postgres/Redis). Failed renewals cause re-processing + of events. + runbook_url: "https://github.com/XStreamRollz/XStreamRoll/blob/main/docs/runbooks/worker-lock-failures.md" + + # ── Recording rules (pre-computed aggregates for dashboards) ────── + - name: xstreamroll-recording + rules: + # Per-path request rate for the dashboard timeseries panels. + - record: job:http_requests_per_second:rate5m + expr: rate(http_requests_total[5m]) + + # Per-path error rate for the dashboard error panel. + - record: job:http_errors_per_second:rate5m + expr: rate(http_requests_total{status_code=~"5.."}[5m]) + + # P50 / P90 / P99 latency aggregates. + - record: job:http_request_duration_seconds:p50 + expr: histogram_quantile(0.50, + sum(rate(http_request_duration_seconds_bucket[5m])) by (le, path)) + + - record: job:http_request_duration_seconds:p90 + expr: histogram_quantile(0.90, + sum(rate(http_request_duration_seconds_bucket[5m])) by (le, path)) + + - record: job:http_request_duration_seconds:p99 + expr: histogram_quantile(0.99, + sum(rate(http_request_duration_seconds_bucket[5m])) by (le, path)) From 521c328fabffa4987c349e2a3d946636ffb06f4a Mon Sep 17 00:00:00 2001 From: beulah7717108-eng Date: Thu, 30 Jul 2026 00:36:02 +0000 Subject: [PATCH 2/2] fix(app): resolve app dockerfile workspace dependency failures The app Docker build was failing because the build context (app/) could not resolve the file: workspace dependency @xstreamroll/types (file:../packages/types). Changes: - Rewrite app/Dockerfile to use repo root as build context, copying packages/types into the builder stage so npm can resolve the file: dependency. Merge deps+builder stages to avoid cross-stage symlink breakage. - Add RUN mkdir -p public to ensure the optional Next.js public/ directory exists for the runner stage. - Update docker-compose.yml app service: context changed from ./app to ., with dockerfile: app/Dockerfile - Update security-scan.yml, release.yml, docker-scan.yml: app matrix entries now use context: . with dockerfile: app/Dockerfile Closes the app Docker build failure in the security-scan workflow. --- .github/workflows/docker-scan.yml | 3 ++- .github/workflows/release.yml | 3 ++- .github/workflows/security-scan.yml | 3 ++- app/Dockerfile | 25 ++++++++++++++++--------- docker-compose.yml | 3 ++- 5 files changed, 24 insertions(+), 13 deletions(-) diff --git a/.github/workflows/docker-scan.yml b/.github/workflows/docker-scan.yml index 5884dd3..9d4c26d 100644 --- a/.github/workflows/docker-scan.yml +++ b/.github/workflows/docker-scan.yml @@ -64,7 +64,8 @@ jobs: - context: . dockerfile: api/Dockerfile image: xstreamroll-api - - context: app + - context: . + dockerfile: app/Dockerfile image: xstreamroll-app - context: xstreamroll-processing image: xstreamroll-processing diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index cd2896e..1417814 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -44,7 +44,8 @@ jobs: - context: . dockerfile: api/Dockerfile image: xstreamroll-api - - context: app + - context: . + dockerfile: app/Dockerfile image: xstreamroll-app - context: xstreamroll-processing image: xstreamroll-processing diff --git a/.github/workflows/security-scan.yml b/.github/workflows/security-scan.yml index 6441f14..188ee2d 100644 --- a/.github/workflows/security-scan.yml +++ b/.github/workflows/security-scan.yml @@ -126,7 +126,8 @@ jobs: context: . dockerfile: api/Dockerfile - image: app - context: app + context: . + dockerfile: app/Dockerfile - image: processing context: xstreamroll-processing diff --git a/app/Dockerfile b/app/Dockerfile index 3251aa6..fa04b4a 100644 --- a/app/Dockerfile +++ b/app/Dockerfile @@ -1,16 +1,23 @@ -# ── Deps ────────────────────────────────────────────────────────────────────── -FROM node:20-alpine AS deps +# Build context: repo root (not app/) — required so npm can resolve the +# file: workspace dependency @xstreamroll/types (file:../packages/types). +FROM node:20-alpine AS builder WORKDIR /app -COPY package.json ./ +# Copy app package manifest +COPY app/package.json ./ + +# Copy workspace dependency so npm can resolve file:../packages/types +COPY packages/types/package.json packages/types/tsconfig.json /packages/types/ +COPY packages/types/src/ /packages/types/src/ + RUN npm install -# ── Builder ─────────────────────────────────────────────────────────────────── -FROM node:20-alpine AS builder -WORKDIR /app +# Copy app source +COPY app/ ./ -COPY --from=deps /app/node_modules ./node_modules -COPY . . +# Ensure public directory exists (optional in Next.js, but the runner stage +# expects it for static asset serving). +RUN mkdir -p public ENV NEXT_TELEMETRY_DISABLED=1 @@ -42,4 +49,4 @@ USER appuser EXPOSE 3000 -CMD ["node", "server.js"] \ No newline at end of file +CMD ["node", "server.js"] diff --git a/docker-compose.yml b/docker-compose.yml index f2e8440..1e629d7 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -72,7 +72,8 @@ services: app: build: - context: ./app + context: . + dockerfile: app/Dockerfile args: NEXT_PUBLIC_API_URL: http://localhost:3001 NEXT_PUBLIC_VIEWER_URL: http://localhost:3000