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..9d4c26d 100644 --- a/.github/workflows/docker-scan.yml +++ b/.github/workflows/docker-scan.yml @@ -61,9 +61,11 @@ jobs: fail-fast: false matrix: include: - - context: api + - 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 5e72d9a..1417814 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -41,9 +41,11 @@ jobs: strategy: matrix: include: - - context: api + - 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 c7c91d6..188ee2d 100644 --- a/.github/workflows/security-scan.yml +++ b/.github/workflows/security-scan.yml @@ -123,9 +123,11 @@ jobs: matrix: include: - image: api - context: api + context: . + dockerfile: api/Dockerfile - image: app - context: app + context: . + dockerfile: app/Dockerfile - image: processing context: xstreamroll-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/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 d36c4db..1e629d7 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: @@ -71,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 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))