From c2eb44785c2b17e9f7daa8c9f3b379ed53058f4f Mon Sep 17 00:00:00 2001 From: Itodo-S Date: Tue, 23 Jun 2026 23:39:30 +0100 Subject: [PATCH 1/2] fix(trade): prune dropped WebSocket connections to stop memory leak (#158) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The TradeGateway broadcast to clients but never tracked or tore them down, so dropped/ghost sockets and their listeners were retained on the heap — the source of the gradual OOM under connection churn. Changes: - Track every connection in an explicit pool (Map keyed by socket id) via OnGatewayConnection/OnGatewayDisconnect; the entry (and all references to the socket) is deleted on disconnect. - Add a per-socket heartbeat: a 30s interval that forcibly disconnects sockets that are no longer connected or stop proving liveness. Liveness is refreshed from engine-level pongs (no client changes needed) and an optional app-level 'heartbeat:pong'. - Enable engine.io ping/pong (pingInterval 25s / pingTimeout 20s) so dead transports are terminated by the protocol itself. - On disconnect, clear the per-socket interval with clearInterval and detach every listener that was attached (packet/heartbeat:pong/error); clear all timers on module destroy. - Expose getConnectionCount() for health checks and leak assertions. - Fix a latent bad import: OnModuleInit comes from @nestjs/common, not @nestjs/websockets. Verification: - src/trade/trade.gateway.spec.ts: 9 tests covering connect/disconnect cleanup, heartbeat ping, ghost/dead-transport termination, listener detachment, timer clearing on destroy, and a 500-client connect/disconnect churn that leaves the pool empty (no leaked references). - scripts/ws-load-test.js: churns 500 clients x N rounds against a running server and prints rss/heapUsed each round (with --expose-gc) to confirm memory plateaus rather than growing. --- scripts/ws-load-test.js | 98 +++++++++++++++++ src/trade/trade.gateway.spec.ts | 183 ++++++++++++++++++++++++++++++++ src/trade/trade.gateway.ts | 142 ++++++++++++++++++++++++- 3 files changed, 418 insertions(+), 5 deletions(-) create mode 100644 scripts/ws-load-test.js create mode 100644 src/trade/trade.gateway.spec.ts diff --git a/scripts/ws-load-test.js b/scripts/ws-load-test.js new file mode 100644 index 0000000..139a2cd --- /dev/null +++ b/scripts/ws-load-test.js @@ -0,0 +1,98 @@ +/** + * WebSocket connection-churn load test (issue #158). + * + * Simulates many clients repeatedly connecting and disconnecting to verify that + * the TradeGateway connection pool stays flat (no memory leak) under flaky-network + * conditions. Use it together with heap snapshots to confirm dropped sockets are + * garbage collected. + * + * Prerequisites: + * - The API running locally: npm run start + * - socket.io-client installed: npm i -D socket.io-client + * + * Usage: + * node scripts/ws-load-test.js + * URL=http://localhost:3000 CLIENTS=500 ROUNDS=20 HOLD_MS=500 node scripts/ws-load-test.js + * + * Capture heap snapshots around the run to confirm stability: + * node --expose-gc scripts/ws-load-test.js # forces GC between rounds + * # then compare process RSS / heapUsed printed each round; it should plateau. + */ + +const URL = process.env.URL || 'http://localhost:3000'; +const CLIENTS = Number(process.env.CLIENTS || 500); +const ROUNDS = Number(process.env.ROUNDS || 20); +const HOLD_MS = Number(process.env.HOLD_MS || 500); + +let io; +try { + io = require('socket.io-client'); +} catch { + console.error( + 'socket.io-client is required to run this load test.\n' + + 'Install it with: npm i -D socket.io-client', + ); + process.exit(1); +} + +const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms)); + +function mb(bytes) { + return (bytes / 1024 / 1024).toFixed(1); +} + +function logMemory(label) { + const m = process.memoryUsage(); + console.log( + `[${label}] rss=${mb(m.rss)}MB heapUsed=${mb(m.heapUsed)}MB heapTotal=${mb(m.heapTotal)}MB`, + ); +} + +function connectOnce() { + return new Promise((resolve) => { + const socket = io(URL, { + transports: ['websocket'], + reconnection: false, + timeout: 5000, + }); + const done = () => resolve(socket); + socket.on('connect', done); + socket.on('connect_error', done); + }); +} + +async function round(n) { + const sockets = await Promise.all( + Array.from({ length: CLIENTS }, () => connectOnce()), + ); + await sleep(HOLD_MS); + // Abruptly drop every connection — mimics flaky clients vanishing. + sockets.forEach((s) => s.disconnect()); + await sleep(HOLD_MS); + if (global.gc) { + global.gc(); + } + logMemory(`round ${n + 1}/${ROUNDS}`); +} + +async function main() { + console.log( + `Churning ${CLIENTS} clients x ${ROUNDS} rounds against ${URL} ` + + `(${global.gc ? 'gc enabled' : 'run with --expose-gc for GC between rounds'})`, + ); + logMemory('baseline'); + for (let i = 0; i < ROUNDS; i++) { + await round(i); + } + logMemory('final'); + console.log( + 'If heapUsed/rss plateau across rounds rather than growing monotonically, ' + + 'dropped connections are being pruned correctly.', + ); + process.exit(0); +} + +main().catch((err) => { + console.error('Load test failed:', err); + process.exit(1); +}); diff --git a/src/trade/trade.gateway.spec.ts b/src/trade/trade.gateway.spec.ts new file mode 100644 index 0000000..a33e9a8 --- /dev/null +++ b/src/trade/trade.gateway.spec.ts @@ -0,0 +1,183 @@ +import { Test, TestingModule } from '@nestjs/testing'; +import { TradeGateway } from './trade.gateway'; +import { RedisService } from '../common/redis/redis.service'; + +/** + * Unit tests for the TradeGateway connection pool (issue #158). + * + * Verifies that connections are tracked on connect, fully removed on disconnect + * (no leaked references or timers), that the heartbeat terminates ghost/dead + * sockets, and that churning 500 clients leaves the pool empty. + */ +const HEARTBEAT_INTERVAL_MS = 30_000; + +interface MockSocket { + id: string; + connected: boolean; + conn: { on: jest.Mock; off: jest.Mock }; + on: jest.Mock; + off: jest.Mock; + emit: jest.Mock; + disconnect: jest.Mock; + trigger: (event: string, ...args: unknown[]) => void; + triggerConn: (event: string, ...args: unknown[]) => void; +} + +function createMockSocket(id: string): MockSocket { + const handlers: Record void> = {}; + const connHandlers: Record void> = {}; + const socket: MockSocket = { + id, + connected: true, + conn: { + on: jest.fn((event: string, handler: (...args: unknown[]) => void) => { + connHandlers[event] = handler; + }), + off: jest.fn(), + }, + on: jest.fn((event: string, handler: (...args: unknown[]) => void) => { + handlers[event] = handler; + }), + off: jest.fn(), + emit: jest.fn(), + disconnect: jest.fn(function (this: MockSocket) { + this.connected = false; + }), + trigger: (event, ...args) => handlers[event]?.(...args), + triggerConn: (event, ...args) => connHandlers[event]?.(...args), + }; + return socket; +} + +describe('TradeGateway (connection pool)', () => { + let gateway: TradeGateway; + + beforeEach(async () => { + const module: TestingModule = await Test.createTestingModule({ + providers: [ + TradeGateway, + { provide: RedisService, useValue: { subscribe: jest.fn() } }, + ], + }).compile(); + + gateway = module.get(TradeGateway); + + // Use jest's fake timers for the whole suite so the per-socket heartbeat is + // deterministic and timer globals are consistently available. Enabled after + // the async module compile so it doesn't interfere with promise resolution. + jest.useFakeTimers(); + }); + + afterEach(() => { + gateway.onModuleDestroy(); + jest.clearAllTimers(); + jest.useRealTimers(); + }); + + it('should be defined', () => { + expect(gateway).toBeDefined(); + expect(gateway.getConnectionCount()).toBe(0); + }); + + it('tracks a connection on connect and releases it on disconnect', () => { + const socket = createMockSocket('a'); + + gateway.handleConnection(socket as never); + expect(gateway.getConnectionCount()).toBe(1); + + gateway.handleDisconnect(socket as never); + expect(gateway.getConnectionCount()).toBe(0); + }); + + it('clears the per-socket heartbeat on disconnect (no lingering interval)', () => { + jest.useFakeTimers(); + const clearSpy = jest.spyOn(global, 'clearInterval'); + const socket = createMockSocket('a'); + + gateway.handleConnection(socket as never); + gateway.handleDisconnect(socket as never); + + expect(clearSpy).toHaveBeenCalled(); + + // After disconnect, advancing time must not emit further heartbeats. + socket.emit.mockClear(); + jest.advanceTimersByTime(HEARTBEAT_INTERVAL_MS * 3); + expect(socket.emit).not.toHaveBeenCalled(); + }); + + it('detaches every listener it attached on disconnect', () => { + const socket = createMockSocket('a'); + + gateway.handleConnection(socket as never); + gateway.handleDisconnect(socket as never); + + expect(socket.off).toHaveBeenCalledWith('heartbeat:pong', expect.any(Function)); + expect(socket.off).toHaveBeenCalledWith('error', expect.any(Function)); + expect(socket.conn.off).toHaveBeenCalledWith('packet', expect.any(Function)); + }); + + it('sends a heartbeat ping and keeps a responsive client alive', () => { + jest.useFakeTimers(); + const socket = createMockSocket('a'); + gateway.handleConnection(socket as never); + + // First probe: emits an app-level ping and marks the client unproven. + jest.advanceTimersByTime(HEARTBEAT_INTERVAL_MS); + expect(socket.emit).toHaveBeenCalledWith('heartbeat:ping'); + + // Client proves liveness (engine pong packet). + socket.triggerConn('packet', { type: 'pong' }); + + // Next probe: still alive, so it is NOT disconnected. + jest.advanceTimersByTime(HEARTBEAT_INTERVAL_MS); + expect(socket.disconnect).not.toHaveBeenCalled(); + expect(gateway.getConnectionCount()).toBe(1); + }); + + it('terminates an unresponsive (ghost) client', () => { + jest.useFakeTimers(); + const socket = createMockSocket('a'); + gateway.handleConnection(socket as never); + + // Two probes with no pong in between → second probe terminates it. + jest.advanceTimersByTime(HEARTBEAT_INTERVAL_MS); // marks unproven, pings + jest.advanceTimersByTime(HEARTBEAT_INTERVAL_MS); // still unproven → disconnect + expect(socket.disconnect).toHaveBeenCalledWith(true); + }); + + it('terminates a client whose transport already dropped', () => { + jest.useFakeTimers(); + const socket = createMockSocket('a'); + gateway.handleConnection(socket as never); + + socket.connected = false; // transport gone but still in the pool + jest.advanceTimersByTime(HEARTBEAT_INTERVAL_MS); + expect(socket.disconnect).toHaveBeenCalledWith(true); + }); + + it('does not leak references when 500 clients connect and disconnect', () => { + const sockets = Array.from({ length: 500 }, (_, i) => + createMockSocket(`client-${i}`), + ); + + sockets.forEach((s) => gateway.handleConnection(s as never)); + expect(gateway.getConnectionCount()).toBe(500); + + sockets.forEach((s) => gateway.handleDisconnect(s as never)); + expect(gateway.getConnectionCount()).toBe(0); + }); + + it('clears all timers on module destroy', () => { + jest.useFakeTimers(); + const clearSpy = jest.spyOn(global, 'clearInterval'); + [createMockSocket('a'), createMockSocket('b')].forEach((s) => + gateway.handleConnection(s as never), + ); + expect(gateway.getConnectionCount()).toBe(2); + + gateway.onModuleDestroy(); + + expect(gateway.getConnectionCount()).toBe(0); + expect(clearSpy).toHaveBeenCalledTimes(2); + }); +}); diff --git a/src/trade/trade.gateway.ts b/src/trade/trade.gateway.ts index 902b7f5..d4bdcfb 100644 --- a/src/trade/trade.gateway.ts +++ b/src/trade/trade.gateway.ts @@ -1,23 +1,68 @@ -import { WebSocketGateway, WebSocketServer, OnModuleInit } from '@nestjs/websockets'; -import { Server } from 'socket.io'; +import { + WebSocketGateway, + WebSocketServer, + OnGatewayConnection, + OnGatewayDisconnect, +} from '@nestjs/websockets'; +import { Logger, OnModuleInit, OnModuleDestroy } from '@nestjs/common'; +import { Server, Socket } from 'socket.io'; import { RedisService } from '../common/redis/redis.service'; -import { Logger } from '@nestjs/common'; + +/** How often each socket is probed for liveness (issue #158: ~30s heartbeat). */ +const HEARTBEAT_INTERVAL_MS = 30_000; +/** Engine.io ping cadence — kept below the app heartbeat so a live client always refreshes liveness first. */ +const PING_INTERVAL_MS = 25_000; +/** Engine.io waits this long for the client's pong before dropping the socket. */ +const PING_TIMEOUT_MS = 20_000; + +/** + * Per-connection bookkeeping. Held in the {@link TradeGateway.clients} pool and + * deleted on disconnect so a dropped socket (and everything it closes over) can + * be garbage collected. + */ +interface ClientState { + /** Refreshed whenever the client proves it is alive (engine pong or app pong). */ + isAlive: boolean; + /** Per-socket heartbeat timer — MUST be cleared on disconnect to avoid a leak. */ + heartbeat: ReturnType; + /** Listeners we attached, kept so they can be detached on cleanup. */ + onPacket: (packet: { type: string }) => void; + onPong: () => void; + onError: (err: Error) => void; +} /** * WebSocket Gateway for real-time trade updates. - * Subscribes to Redis 'live_trades' channel and broadcasts messages to connected clients. + * + * Subscribes to the Redis `live_trades` channel and broadcasts to connected + * clients. Maintains an explicit connection pool with a heartbeat so that dead + * or dropped sockets (issue #158) are pruned from memory rather than leaking: + * + * - Every socket is tracked in {@link clients} on connect and removed on + * disconnect, dropping all references to the socket instance. + * - Engine.io ping/pong (`pingInterval`/`pingTimeout`) terminates connections + * whose underlying transport has gone away. + * - An additional per-socket heartbeat forcibly disconnects ghost connections + * that stop responding, and its `setInterval` is always cleared on disconnect. */ @WebSocketGateway({ cors: { origin: '*', }, + pingInterval: PING_INTERVAL_MS, + pingTimeout: PING_TIMEOUT_MS, }) -export class TradeGateway implements OnModuleInit { +export class TradeGateway + implements OnModuleInit, OnGatewayConnection, OnGatewayDisconnect, OnModuleDestroy +{ @WebSocketServer() server: Server; private readonly logger = new Logger(TradeGateway.name); + /** Active connection pool, keyed by socket id. */ + private readonly clients = new Map(); + constructor(private readonly redisService: RedisService) {} /** @@ -29,4 +74,91 @@ export class TradeGateway implements OnModuleInit { this.server.emit('trade', JSON.parse(message)); }); } + + /** + * Registers a new connection in the pool and wires up its heartbeat. + */ + handleConnection(client: Socket) { + const markAlive = () => { + const state = this.clients.get(client.id); + if (state) { + state.isAlive = true; + } + }; + + // Treat any inbound engine pong (sent automatically by socket.io clients in + // response to the server's ping) as proof of life — no client-side code + // required. App-level 'heartbeat:pong' is also honored for custom clients. + const onPacket = (packet: { type: string }) => { + if (packet.type === 'pong') { + markAlive(); + } + }; + const onPong = () => markAlive(); + const onError = (err: Error) => { + // Socket.IO emits 'disconnect' after a fatal 'error'; log for visibility. + this.logger.error(`Socket error for ${client.id}: ${err?.message ?? err}`); + }; + + client.conn?.on('packet', onPacket); + client.on('heartbeat:pong', onPong); + client.on('error', onError); + + const heartbeat = setInterval(() => { + const state = this.clients.get(client.id); + if (!state) { + return; + } + // A socket that is no longer connected, or hasn't proven liveness since + // the last probe, is a ghost — force it closed so it gets cleaned up. + if (!client.connected || !state.isAlive) { + this.logger.warn(`Terminating unresponsive client: ${client.id}`); + client.disconnect(true); // triggers handleDisconnect -> cleanup + return; + } + state.isAlive = false; + client.emit('heartbeat:ping'); + }, HEARTBEAT_INTERVAL_MS); + + this.clients.set(client.id, { isAlive: true, heartbeat, onPacket, onPong, onError }); + this.logger.log( + `Client connected: ${client.id} (active connections: ${this.clients.size})`, + ); + } + + /** + * Tears down a connection: clears its heartbeat, detaches listeners, and drops + * it from the pool so the socket instance is no longer referenced. + */ + handleDisconnect(client: Socket) { + const state = this.clients.get(client.id); + if (state) { + clearInterval(state.heartbeat); + client.conn?.off('packet', state.onPacket); + client.off('heartbeat:pong', state.onPong); + client.off('error', state.onError); + this.clients.delete(client.id); + } + this.logger.log( + `Client disconnected: ${client.id} (active connections: ${this.clients.size})`, + ); + } + + /** + * Clears every per-socket timer on shutdown so no intervals outlive the module. + */ + onModuleDestroy() { + for (const state of this.clients.values()) { + clearInterval(state.heartbeat); + } + this.clients.clear(); + } + + /** + * Number of connections currently tracked in the pool. Exposed for health + * checks and leak assertions in tests. + */ + getConnectionCount(): number { + return this.clients.size; + } } From 915d8e1d6193606c69d9494d64e1e89cdb258866 Mon Sep 17 00:00:00 2001 From: Itodo-S Date: Wed, 24 Jun 2026 20:56:24 +0100 Subject: [PATCH 2/2] fix(ci): make build and tests pass (#158) CI failed at `npm ci` and the build/test steps could not run. Resolve the pre-existing breakages that block the pipeline: - Align @nestjs/websockets and @nestjs/platform-socket.io to ^10.4.22 (matching the rest of @nestjs). The pinned ^10.0.0 peers reflect-metadata@^0.1.12, which conflicts with the project's reflect-metadata@^0.2.2 and made `npm ci` fail with ERESOLVE. Regenerated package-lock.json so the install resolves cleanly. - Fix InvoicesModule (src/dynamic/dynamic.module.ts): it imported ./invoices.controller and ./pdf.service, which do not exist. Point it at the classes that do (NetworkController, PdfService in ./dynamic.serivice). - Type CustomLogger.formatMessage's logLevel param as LogLevel (not string) so the override matches ConsoleLogger and compiles. - Fix the pools apy-history integration test: the endpoint returns the ApyHistoryPoint[] array directly (per its return type / Swagger isArray), and there is no global response-envelope interceptor, so assert the array shape instead of a {status,data} envelope. After this: `npm ci`, `npm run build`, and `npm test` (17/17) all pass. --- package-lock.json | 977 ++++++++++++++++--------- package.json | 4 +- src/common/logger/custom.logger.ts | 4 +- src/dynamic/dynamic.module.ts | 7 +- src/pools/pools.controller.int.spec.ts | 12 +- 5 files changed, 652 insertions(+), 352 deletions(-) diff --git a/package-lock.json b/package-lock.json index 50bc4d8..5d02286 100644 --- a/package-lock.json +++ b/package-lock.json @@ -13,11 +13,11 @@ "@nestjs/config": "^3.2.3", "@nestjs/core": "^10.4.22", "@nestjs/platform-express": "^10.4.22", - "@nestjs/platform-socket.io": "^10.0.0", + "@nestjs/platform-socket.io": "^10.4.22", "@nestjs/swagger": "^7.1.1", "@nestjs/throttler": "^6.5.0", "@nestjs/typeorm": "^10.0.2", - "@nestjs/websockets": "^10.0.0", + "@nestjs/websockets": "^10.4.22", "@prisma/client": "^5.19.1", "@stellar/stellar-sdk": "^11.0.0", "@types/jsonwebtoken": "^9.0.10", @@ -679,7 +679,7 @@ }, "node_modules/@cspotcode/source-map-support": { "version": "0.8.1", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "@jridgewell/trace-mapping": "0.3.9" @@ -690,7 +690,7 @@ }, "node_modules/@cspotcode/source-map-support/node_modules/@jridgewell/trace-mapping": { "version": "0.3.9", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "@jridgewell/resolve-uri": "^3.0.3", @@ -1567,7 +1567,7 @@ }, "node_modules/@jridgewell/resolve-uri": { "version": "3.1.2", - "dev": true, + "devOptional": true, "license": "MIT", "engines": { "node": ">=6.0.0" @@ -1584,7 +1584,7 @@ }, "node_modules/@jridgewell/sourcemap-codec": { "version": "1.5.5", - "dev": true, + "devOptional": true, "license": "MIT" }, "node_modules/@jridgewell/trace-mapping": { @@ -1912,13 +1912,13 @@ } }, "node_modules/@nestjs/platform-socket.io": { - "version": "10.0.0", - "resolved": "https://registry.npmjs.org/@nestjs/platform-socket.io/-/platform-socket.io-10.0.0.tgz", - "integrity": "sha512-sYW72flbnlfw7VcBuEamUp45nNr/OUGzBvqfnmraYJ9aJDqcd5vM0w2zn/S6YvLu2ZEgGrnyx8KEIXYf5iMfuQ==", + "version": "10.4.22", + "resolved": "https://registry.npmjs.org/@nestjs/platform-socket.io/-/platform-socket.io-10.4.22.tgz", + "integrity": "sha512-xxGw3R0Ihr51/Omq23z3//bKmCXyVKaikxbH0/pkwqMsQrxkUv9NabNUZ22b4Jnlwwi02X+zlwo8GRa9u8oV9g==", "license": "MIT", "dependencies": { - "socket.io": "4.6.2", - "tslib": "2.5.3" + "socket.io": "4.8.1", + "tslib": "2.8.1" }, "funding": { "type": "opencollective", @@ -1930,190 +1930,6 @@ "rxjs": "^7.1.0" } }, - "node_modules/@nestjs/platform-socket.io/node_modules/cookie": { - "version": "0.4.2", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.2.tgz", - "integrity": "sha512-aSWTXFzaKWkvHO1Ny/s+ePFpvKsPnjc551iI41v3ny/ow6tBG5Vd+FuqGNhh1LxOmVzOlGUriIlOaokOvhaStA==", - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/@nestjs/platform-socket.io/node_modules/debug": { - "version": "4.3.7", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz", - "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==", - "license": "MIT", - "dependencies": { - "ms": "^2.1.3" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/@nestjs/platform-socket.io/node_modules/engine.io": { - "version": "6.4.2", - "resolved": "https://registry.npmjs.org/engine.io/-/engine.io-6.4.2.tgz", - "integrity": "sha512-FKn/3oMiJjrOEOeUub2WCox6JhxBXq/Zn3fZOMCBxKnNYtsdKjxhl7yR3fZhM9PV+rdE75SU5SYMc+2PGzo+Tg==", - "license": "MIT", - "dependencies": { - "@types/cookie": "^0.4.1", - "@types/cors": "^2.8.12", - "@types/node": ">=10.0.0", - "accepts": "~1.3.4", - "base64id": "2.0.0", - "cookie": "~0.4.1", - "cors": "~2.8.5", - "debug": "~4.3.1", - "engine.io-parser": "~5.0.3", - "ws": "~8.11.0" - }, - "engines": { - "node": ">=10.0.0" - } - }, - "node_modules/@nestjs/platform-socket.io/node_modules/engine.io-parser": { - "version": "5.0.7", - "resolved": "https://registry.npmjs.org/engine.io-parser/-/engine.io-parser-5.0.7.tgz", - "integrity": "sha512-P+jDFbvK6lE3n1OL+q9KuzdOFWkkZ/cMV9gol/SbVfpyqfvrfrFTOFJ6fQm2VC3PZHlU3QPhVwmbsCnauHF2MQ==", - "license": "MIT", - "engines": { - "node": ">=10.0.0" - } - }, - "node_modules/@nestjs/platform-socket.io/node_modules/ms": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", - "license": "MIT" - }, - "node_modules/@nestjs/platform-socket.io/node_modules/socket.io": { - "version": "4.6.2", - "resolved": "https://registry.npmjs.org/socket.io/-/socket.io-4.6.2.tgz", - "integrity": "sha512-Vp+lSks5k0dewYTfwgPT9UeGGd+ht7sCpB7p0e83VgO4X/AHYWhXITMrNk/pg8syY2bpx23ptClCQuHhqi2BgQ==", - "license": "MIT", - "dependencies": { - "accepts": "~1.3.4", - "base64id": "~2.0.0", - "debug": "~4.3.2", - "engine.io": "~6.4.2", - "socket.io-adapter": "~2.5.2", - "socket.io-parser": "~4.2.4" - }, - "engines": { - "node": ">=10.0.0" - } - }, - "node_modules/@nestjs/platform-socket.io/node_modules/socket.io-adapter": { - "version": "2.5.6", - "resolved": "https://registry.npmjs.org/socket.io-adapter/-/socket.io-adapter-2.5.6.tgz", - "integrity": "sha512-DkkO/dz7MGln0dHn5bmN3pPy+JmywNICWrJqVWiVOyvXjWQFIv9c2h24JrQLLFJ2aQVQf/Cvl1vblnd4r2apLQ==", - "license": "MIT", - "dependencies": { - "debug": "~4.4.1", - "ws": "~8.18.3" - } - }, - "node_modules/@nestjs/platform-socket.io/node_modules/socket.io-adapter/node_modules/debug": { - "version": "4.4.3", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", - "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", - "license": "MIT", - "dependencies": { - "ms": "^2.1.3" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/@nestjs/platform-socket.io/node_modules/socket.io-adapter/node_modules/ws": { - "version": "8.18.3", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.3.tgz", - "integrity": "sha512-PEIGCY5tSlUt50cqyMXfCzX+oOPqN0vuGqWzbcJ2xvnkzkq46oOpz7dQaTDBdfICb4N14+GARUDw2XV2N4tvzg==", - "license": "MIT", - "engines": { - "node": ">=10.0.0" - }, - "peerDependencies": { - "bufferutil": "^4.0.1", - "utf-8-validate": ">=5.0.2" - }, - "peerDependenciesMeta": { - "bufferutil": { - "optional": true - }, - "utf-8-validate": { - "optional": true - } - } - }, - "node_modules/@nestjs/platform-socket.io/node_modules/socket.io-parser": { - "version": "4.2.6", - "resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-4.2.6.tgz", - "integrity": "sha512-asJqbVBDsBCJx0pTqw3WfesSY0iRX+2xzWEWzrpcH7L6fLzrhyF8WPI8UaeM4YCuDfpwA/cgsdugMsmtz8EJeg==", - "license": "MIT", - "dependencies": { - "@socket.io/component-emitter": "~3.1.0", - "debug": "~4.4.1" - }, - "engines": { - "node": ">=10.0.0" - } - }, - "node_modules/@nestjs/platform-socket.io/node_modules/socket.io-parser/node_modules/debug": { - "version": "4.4.3", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", - "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", - "license": "MIT", - "dependencies": { - "ms": "^2.1.3" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/@nestjs/platform-socket.io/node_modules/tslib": { - "version": "2.5.3", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.3.tgz", - "integrity": "sha512-mSxlJJwl3BMEQCUNnxXBU9jP4JBktcEGhURcPR6VQVlnP0FdDEsIaz0C35dXNGLyRfrATNofF0F5p2KPxQgB+w==", - "license": "0BSD" - }, - "node_modules/@nestjs/platform-socket.io/node_modules/ws": { - "version": "8.11.0", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.11.0.tgz", - "integrity": "sha512-HPG3wQd9sNQoT9xHyNCXoDUa+Xw/VevmY9FoHyQ+g+rrMn4j6FB4np7Z0OhdTgjx6MgQLK7jwSy1YecU1+4Asg==", - "license": "MIT", - "engines": { - "node": ">=10.0.0" - }, - "peerDependencies": { - "bufferutil": "^4.0.1", - "utf-8-validate": "^5.0.2" - }, - "peerDependenciesMeta": { - "bufferutil": { - "optional": true - }, - "utf-8-validate": { - "optional": true - } - } - }, "node_modules/@nestjs/schematics": { "version": "11.0.9", "dev": true, @@ -2266,20 +2082,20 @@ } }, "node_modules/@nestjs/websockets": { - "version": "10.0.0", - "resolved": "https://registry.npmjs.org/@nestjs/websockets/-/websockets-10.0.0.tgz", - "integrity": "sha512-Qv7KKdJ7bNnR2jaA8rXhhqTuwbFoAV29TKA1EZIUWGL6OHb2do/JB14mJ9yDQfi1fbfb0xjxWno7iPgaO+LHUw==", + "version": "10.4.22", + "resolved": "https://registry.npmjs.org/@nestjs/websockets/-/websockets-10.4.22.tgz", + "integrity": "sha512-OLd4i0Faq7vgdtB5vVUrJ54hWEtcXy9poJ6n7kbbh/5ms+KffUl+wwGsbe7uSXLrkoyI8xXU6fZPkFArI+XiRg==", "license": "MIT", "dependencies": { "iterare": "1.2.1", "object-hash": "3.0.0", - "tslib": "2.5.3" + "tslib": "2.8.1" }, "peerDependencies": { "@nestjs/common": "^10.0.0", "@nestjs/core": "^10.0.0", "@nestjs/platform-socket.io": "^10.0.0", - "reflect-metadata": "^0.1.12", + "reflect-metadata": "^0.1.12 || ^0.2.0", "rxjs": "^7.1.0" }, "peerDependenciesMeta": { @@ -2288,12 +2104,6 @@ } } }, - "node_modules/@nestjs/websockets/node_modules/tslib": { - "version": "2.5.3", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.3.tgz", - "integrity": "sha512-mSxlJJwl3BMEQCUNnxXBU9jP4JBktcEGhURcPR6VQVlnP0FdDEsIaz0C35dXNGLyRfrATNofF0F5p2KPxQgB+w==", - "license": "0BSD" - }, "node_modules/@noble/hashes": { "version": "1.8.0", "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.8.0.tgz", @@ -2519,22 +2329,22 @@ }, "node_modules/@tsconfig/node10": { "version": "1.0.12", - "dev": true, + "devOptional": true, "license": "MIT" }, "node_modules/@tsconfig/node12": { "version": "1.0.11", - "dev": true, + "devOptional": true, "license": "MIT" }, "node_modules/@tsconfig/node14": { "version": "1.0.3", - "dev": true, + "devOptional": true, "license": "MIT" }, "node_modules/@tsconfig/node16": { "version": "1.0.4", - "dev": true, + "devOptional": true, "license": "MIT" }, "node_modules/@tybys/wasm-util": { @@ -2592,12 +2402,6 @@ "@types/node": "*" } }, - "node_modules/@types/component-emitter": { - "version": "1.2.14", - "resolved": "https://registry.npmjs.org/@types/component-emitter/-/component-emitter-1.2.14.tgz", - "integrity": "sha512-lmPil1g82wwWg/qHSxMWkSKyJGQOK+ejXeMAAWyxNtVUD0/Ycj2maL63RAqpxVfdtvTfZkRnqzB0A9ft59y69g==", - "license": "MIT" - }, "node_modules/@types/compression": { "version": "1.7.5", "resolved": "https://registry.npmjs.org/@types/compression/-/compression-1.7.5.tgz", @@ -2616,12 +2420,6 @@ "@types/node": "*" } }, - "node_modules/@types/cookie": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/@types/cookie/-/cookie-0.4.1.tgz", - "integrity": "sha512-XW/Aa8APYr6jSVVA1y/DEIZX0/GMKLEVekNG727R8cs56ahETkRAy/3DR7+fJyh7oUgGwNQaRfXCun0+KbWY7Q==", - "license": "MIT" - }, "node_modules/@types/cookiejar": { "version": "2.1.5", "resolved": "https://registry.npmjs.org/@types/cookiejar/-/cookiejar-2.1.5.tgz", @@ -2833,6 +2631,15 @@ "resolved": "https://registry.npmjs.org/@types/validator/-/validator-13.15.10.tgz", "integrity": "sha512-T8L6i7wCuyoK8A/ZeLYt1+q0ty3Zb9+qbSSvrIVitzT3YjZqkTZ40IbRsPanlB4h1QB3JVL1SYCdR6ngtFYcuA==" }, + "node_modules/@types/ws": { + "version": "8.18.1", + "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.18.1.tgz", + "integrity": "sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg==", + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, "node_modules/@types/yargs": { "version": "17.0.35", "dev": true, @@ -3019,7 +2826,7 @@ }, "node_modules/acorn": { "version": "8.16.0", - "dev": true, + "devOptional": true, "license": "MIT", "bin": { "acorn": "bin/acorn" @@ -3041,7 +2848,7 @@ }, "node_modules/acorn-walk": { "version": "8.3.5", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "acorn": "^8.11.0" @@ -3177,7 +2984,7 @@ }, "node_modules/arg": { "version": "4.1.3", - "dev": true, + "devOptional": true, "license": "MIT" }, "node_modules/argparse": { @@ -3365,14 +3172,6 @@ "node": ">=0.12.0" } }, - "node_modules/base64-arraybuffer": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/base64-arraybuffer/-/base64-arraybuffer-0.1.4.tgz", - "integrity": "sha512-a1eIFi4R9ySrbiMuyTGx5e92uRH5tQY6kArNcFaKBUleIoLjdjBg7Zxm3Mqm3Kmkf27HLR/1fnxX9q8GQ7Iavg==", - "engines": { - "node": ">= 0.6.0" - } - }, "node_modules/base64-js": { "version": "1.5.1", "funding": [ @@ -3901,6 +3700,7 @@ "version": "1.3.1", "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.3.1.tgz", "integrity": "sha512-T0+barUSQRTUQASh8bx02dl+DhF54GtIDY13Y3m9oWTklKbb3Wv974meRpeZ3lp1JpLVECWWNHC4vaG2XHXouQ==", + "dev": true, "license": "MIT", "funding": { "url": "https://github.com/sponsors/sindresorhus" @@ -3973,6 +3773,20 @@ "version": "2.15.3", "license": "MIT" }, + "node_modules/content-disposition": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-1.1.0.tgz", + "integrity": "sha512-5jRCH9Z/+DRP7rkvY83B+yGIGX96OYdJmzngqnw2SBSxqCFPd0w2km3s5iawpGX8krnwSGmF0FW5Nhr0Hfai3g==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, "node_modules/content-type": { "version": "1.0.5", "license": "MIT", @@ -3996,7 +3810,6 @@ "version": "1.2.2", "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.2.2.tgz", "integrity": "sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg==", - "dev": true, "license": "MIT", "engines": { "node": ">=6.6.0" @@ -4052,7 +3865,7 @@ }, "node_modules/create-require": { "version": "1.1.1", - "dev": true, + "devOptional": true, "license": "MIT" }, "node_modules/cross-spawn": { @@ -4176,7 +3989,7 @@ }, "node_modules/diff": { "version": "4.0.4", - "dev": true, + "devOptional": true, "license": "BSD-3-Clause", "engines": { "node": ">=0.3.1" @@ -4254,48 +4067,39 @@ } }, "node_modules/engine.io": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/engine.io/-/engine.io-5.0.0.tgz", - "integrity": "sha512-BATIdDV3H1SrE9/u2BAotvsmjJg0t1P4+vGedImSs1lkFAtQdvk4Ev1y4LDiPF7BPWgXWEG+NDY+nLvW3UrMWw==", + "version": "6.6.9", + "resolved": "https://registry.npmjs.org/engine.io/-/engine.io-6.6.9.tgz", + "integrity": "sha512-clKkw4C7nJ22mGgoVcCg6V/W/TxdNyIOTr89k2ONZu81qqkddPFDF0LXcbAwhzPD8DjkiRCjzuiO6Y+fkpD4vg==", "license": "MIT", "dependencies": { + "@types/cors": "^2.8.12", + "@types/node": ">=10.0.0", + "@types/ws": "^8.5.12", "accepts": "~1.3.4", "base64id": "2.0.0", - "cookie": "~0.4.1", + "cookie": "~0.7.2", "cors": "~2.8.5", - "debug": "~4.3.1", - "engine.io-parser": "~4.0.0", - "ws": "~7.4.2" + "debug": "~4.4.1", + "engine.io-parser": "~5.2.1", + "ws": "~8.21.0" }, "engines": { - "node": ">=10.0.0" + "node": ">=10.2.0" } }, "node_modules/engine.io-parser": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/engine.io-parser/-/engine.io-parser-4.0.3.tgz", - "integrity": "sha512-xEAAY0msNnESNPc00e19y5heTPX4y/TJ36gr8t1voOaNmTojP9b3oK3BbJLFufW2XFPQaaijpFewm2g2Um3uqA==", - "license": "MIT", - "dependencies": { - "base64-arraybuffer": "0.1.4" - }, - "engines": { - "node": ">=8.0.0" - } - }, - "node_modules/engine.io/node_modules/cookie": { - "version": "0.4.2", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.2.tgz", - "integrity": "sha512-aSWTXFzaKWkvHO1Ny/s+ePFpvKsPnjc551iI41v3ny/ow6tBG5Vd+FuqGNhh1LxOmVzOlGUriIlOaokOvhaStA==", + "version": "5.2.3", + "resolved": "https://registry.npmjs.org/engine.io-parser/-/engine.io-parser-5.2.3.tgz", + "integrity": "sha512-HqD3yTBfnBxIrbnM1DoD6Pcq8NECnh8d4As1Qgh0z5Gg3jRRIqijury0CL3ghu/edArpUYiYqQiDUQBIs4np3Q==", "license": "MIT", "engines": { - "node": ">= 0.6" + "node": ">=10.0.0" } }, "node_modules/engine.io/node_modules/debug": { - "version": "4.3.7", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz", - "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==", + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", "license": "MIT", "dependencies": { "ms": "^2.1.3" @@ -4462,62 +4266,305 @@ "node": ">=0.8.x" } }, - "node_modules/eventsource": { - "version": "2.0.2", + "node_modules/eventsource": { + "version": "2.0.2", + "license": "MIT", + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/execa": { + "version": "5.1.1", + "dev": true, + "license": "MIT", + "dependencies": { + "cross-spawn": "^7.0.3", + "get-stream": "^6.0.0", + "human-signals": "^2.1.0", + "is-stream": "^2.0.0", + "merge-stream": "^2.0.0", + "npm-run-path": "^4.0.1", + "onetime": "^5.1.2", + "signal-exit": "^3.0.3", + "strip-final-newline": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sindresorhus/execa?sponsor=1" + } + }, + "node_modules/execa/node_modules/signal-exit": { + "version": "3.0.7", + "dev": true, + "license": "ISC" + }, + "node_modules/exit-x": { + "version": "0.2.2", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/expect": { + "version": "30.3.0", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/expect-utils": "30.3.0", + "@jest/get-type": "30.1.0", + "jest-matcher-utils": "30.3.0", + "jest-message-util": "30.3.0", + "jest-mock": "30.3.0", + "jest-util": "30.3.0" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/express": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/express/-/express-5.2.1.tgz", + "integrity": "sha512-hIS4idWWai69NezIdRt2xFVofaF4j+6INOpJlVOLDO8zXGpUVEVzIYk12UUi2JzjEzWL3IOAxcTubgz9Po0yXw==", + "license": "MIT", + "peer": true, + "dependencies": { + "accepts": "^2.0.0", + "body-parser": "^2.2.1", + "content-disposition": "^1.0.0", + "content-type": "^1.0.5", + "cookie": "^0.7.1", + "cookie-signature": "^1.2.1", + "debug": "^4.4.0", + "depd": "^2.0.0", + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "etag": "^1.8.1", + "finalhandler": "^2.1.0", + "fresh": "^2.0.0", + "http-errors": "^2.0.0", + "merge-descriptors": "^2.0.0", + "mime-types": "^3.0.0", + "on-finished": "^2.4.1", + "once": "^1.4.0", + "parseurl": "^1.3.3", + "proxy-addr": "^2.0.7", + "qs": "^6.14.0", + "range-parser": "^1.2.1", + "router": "^2.2.0", + "send": "^1.1.0", + "serve-static": "^2.2.0", + "statuses": "^2.0.1", + "type-is": "^2.0.1", + "vary": "^1.1.2" + }, + "engines": { + "node": ">= 18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/express-rate-limit": { + "version": "8.5.2", + "resolved": "https://registry.npmjs.org/express-rate-limit/-/express-rate-limit-8.5.2.tgz", + "integrity": "sha512-5Kb34ipNX694DH48vN9irak1Qx30nb0PLYHXfJgw4YEjiC3ZEmZJhwOp+VfiCYwFzvFTdB9QkArYS5kXa2cx2A==", + "license": "MIT", + "peer": true, + "dependencies": { + "ip-address": "^10.2.0" + }, + "engines": { + "node": ">= 16" + }, + "funding": { + "url": "https://github.com/sponsors/express-rate-limit" + }, + "peerDependencies": { + "express": ">= 4.11" + } + }, + "node_modules/express/node_modules/accepts": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-2.0.0.tgz", + "integrity": "sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==", + "license": "MIT", + "peer": true, + "dependencies": { + "mime-types": "^3.0.0", + "negotiator": "^1.0.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/express/node_modules/body-parser": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.3.0.tgz", + "integrity": "sha512-2cGmJupaNgg+QUwVLAucDuWuoMZ6EX9iHDRswZ5lsNYEmwPaRknMPCLZz07yTzVq/83p4o/wzbDZbBrTvGGTIw==", + "license": "MIT", + "peer": true, + "dependencies": { + "bytes": "^3.1.2", + "content-type": "^2.0.0", + "debug": "^4.4.3", + "http-errors": "^2.0.1", + "iconv-lite": "^0.7.2", + "on-finished": "^2.4.1", + "qs": "^6.15.2", + "raw-body": "^3.0.2", + "type-is": "^2.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/express/node_modules/body-parser/node_modules/content-type": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-2.0.0.tgz", + "integrity": "sha512-j/O/d7GcZCyNl7/hwZAb606rzqkyvaDctLmckbxLzHvFBzTJHuGEdodATcP3yIRoDrLHkIATJuvzbFlp/ki2cQ==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/express/node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "license": "MIT", + "peer": true, + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/express/node_modules/media-typer": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-1.1.0.tgz", + "integrity": "sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/express/node_modules/mime-types": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.2.tgz", + "integrity": "sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A==", + "license": "MIT", + "peer": true, + "dependencies": { + "mime-db": "^1.54.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/express/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT", + "peer": true + }, + "node_modules/express/node_modules/negotiator": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-1.0.0.tgz", + "integrity": "sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/express/node_modules/qs": { + "version": "6.15.2", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.15.2.tgz", + "integrity": "sha512-Rzq0KEyX/w/tEybncDgdkZrJgVUsUMk3xjh3t5bv3S1HTAtg+uOYt72+ZfwiQwKdysThkTBdL/rTi6HDmX9Ddw==", + "license": "BSD-3-Clause", + "peer": true, + "dependencies": { + "side-channel": "^1.1.0" + }, + "engines": { + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/express/node_modules/raw-body": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-3.0.2.tgz", + "integrity": "sha512-K5zQjDllxWkf7Z5xJdV0/B0WTNqx6vxG70zJE4N0kBs4LovmEYWJzQGxC9bS9RAKu3bgM40lrd5zoLJ12MQ5BA==", "license": "MIT", + "peer": true, + "dependencies": { + "bytes": "~3.1.2", + "http-errors": "~2.0.1", + "iconv-lite": "~0.7.0", + "unpipe": "~1.0.0" + }, "engines": { - "node": ">=12.0.0" + "node": ">= 0.10" } }, - "node_modules/execa": { - "version": "5.1.1", - "dev": true, + "node_modules/express/node_modules/type-is": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-2.1.0.tgz", + "integrity": "sha512-faYHw0anBbc/kWF3zFTEnxSFOAGUX9GFbOBthvDdLsIlEoWOFOtS0zgCiQYwIskL9iGXZL3kAXD8OoZ4GmMATA==", "license": "MIT", + "peer": true, "dependencies": { - "cross-spawn": "^7.0.3", - "get-stream": "^6.0.0", - "human-signals": "^2.1.0", - "is-stream": "^2.0.0", - "merge-stream": "^2.0.0", - "npm-run-path": "^4.0.1", - "onetime": "^5.1.2", - "signal-exit": "^3.0.3", - "strip-final-newline": "^2.0.0" + "content-type": "^2.0.0", + "media-typer": "^1.1.0", + "mime-types": "^3.0.0" }, "engines": { - "node": ">=10" + "node": ">= 18" }, "funding": { - "url": "https://github.com/sindresorhus/execa?sponsor=1" + "type": "opencollective", + "url": "https://opencollective.com/express" } }, - "node_modules/execa/node_modules/signal-exit": { - "version": "3.0.7", - "dev": true, - "license": "ISC" - }, - "node_modules/exit-x": { - "version": "0.2.2", - "dev": true, + "node_modules/express/node_modules/type-is/node_modules/content-type": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-2.0.0.tgz", + "integrity": "sha512-j/O/d7GcZCyNl7/hwZAb606rzqkyvaDctLmckbxLzHvFBzTJHuGEdodATcP3yIRoDrLHkIATJuvzbFlp/ki2cQ==", "license": "MIT", + "peer": true, "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/expect": { - "version": "30.3.0", - "dev": true, - "license": "MIT", - "dependencies": { - "@jest/expect-utils": "30.3.0", - "@jest/get-type": "30.1.0", - "jest-matcher-utils": "30.3.0", - "jest-message-util": "30.3.0", - "jest-mock": "30.3.0", - "jest-util": "30.3.0" + "node": ">=18" }, - "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" } }, "node_modules/fast-deep-equal": { @@ -4577,6 +4624,53 @@ "url": "https://github.com/sindresorhus/file-type?sponsor=1" } }, + "node_modules/finalhandler": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-2.1.1.tgz", + "integrity": "sha512-S8KoZgRZN+a5rNwqTxlZZePjT/4cnm0ROV70LedRHZ0p8u9fRID0hJUZQpkKLzro8LfmC8sx23bY6tVNxv8pQA==", + "license": "MIT", + "peer": true, + "dependencies": { + "debug": "^4.4.0", + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "on-finished": "^2.4.1", + "parseurl": "^1.3.3", + "statuses": "^2.0.1" + }, + "engines": { + "node": ">= 18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/finalhandler/node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "license": "MIT", + "peer": true, + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/finalhandler/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT", + "peer": true + }, "node_modules/find-up": { "version": "4.1.0", "dev": true, @@ -4699,6 +4793,16 @@ "node": ">= 0.6" } }, + "node_modules/fresh": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-2.0.0.tgz", + "integrity": "sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">= 0.8" + } + }, "node_modules/fs-extra": { "version": "10.1.0", "dev": true, @@ -4990,7 +5094,6 @@ }, "node_modules/iconv-lite": { "version": "0.7.2", - "dev": true, "license": "MIT", "dependencies": { "safer-buffer": ">= 2.1.2 < 3.0.0" @@ -5122,6 +5225,16 @@ "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", "license": "MIT" }, + "node_modules/ip-address": { + "version": "10.2.0", + "resolved": "https://registry.npmjs.org/ip-address/-/ip-address-10.2.0.tgz", + "integrity": "sha512-/+S6j4E9AHvW9SWMSEY9Xfy66O5PWvVEJ08O0y5JGyEKQpojb0K0GKpz/v5HJ/G0vi3D2sjGK78119oXZeE0qA==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">= 12" + } + }, "node_modules/ipaddr.js": { "version": "1.9.1", "license": "MIT", @@ -5167,6 +5280,13 @@ "node": ">=8" } }, + "node_modules/is-promise": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-4.0.0.tgz", + "integrity": "sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==", + "license": "MIT", + "peer": true + }, "node_modules/is-stream": { "version": "2.0.1", "dev": true, @@ -6230,7 +6350,7 @@ }, "node_modules/make-error": { "version": "1.3.6", - "dev": true, + "devOptional": true, "license": "ISC" }, "node_modules/makeerror": { @@ -6266,6 +6386,19 @@ "node": ">= 4.0.0" } }, + "node_modules/merge-descriptors": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-2.0.0.tgz", + "integrity": "sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/merge-stream": { "version": "2.0.0", "dev": true, @@ -6562,7 +6695,6 @@ }, "node_modules/once": { "version": "1.4.0", - "dev": true, "license": "ISC", "dependencies": { "wrappy": "1" @@ -7177,6 +7309,59 @@ "dev": true, "license": "ISC" }, + "node_modules/router": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/router/-/router-2.2.0.tgz", + "integrity": "sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ==", + "license": "MIT", + "peer": true, + "dependencies": { + "debug": "^4.4.0", + "depd": "^2.0.0", + "is-promise": "^4.0.0", + "parseurl": "^1.3.3", + "path-to-regexp": "^8.0.0" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/router/node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "license": "MIT", + "peer": true, + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/router/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT", + "peer": true + }, + "node_modules/router/node_modules/path-to-regexp": { + "version": "8.4.2", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-8.4.2.tgz", + "integrity": "sha512-qRcuIdP69NPm4qbACK+aDogI5CBDMi1jKe0ry5rSQJz8JVLsC7jV8XpiJjGRLLol3N+R5ihGYcrPLTno6pAdBA==", + "license": "MIT", + "peer": true, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, "node_modules/rxjs": { "version": "7.8.2", "license": "Apache-2.0", @@ -7261,6 +7446,95 @@ "node": ">=10" } }, + "node_modules/send": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/send/-/send-1.2.1.tgz", + "integrity": "sha512-1gnZf7DFcoIcajTjTwjwuDjzuz4PPcY2StKPlsGAQ1+YH20IRVrBaXSWmdjowTJ6u8Rc01PoYOGHXfP1mYcZNQ==", + "license": "MIT", + "peer": true, + "dependencies": { + "debug": "^4.4.3", + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "etag": "^1.8.1", + "fresh": "^2.0.0", + "http-errors": "^2.0.1", + "mime-types": "^3.0.2", + "ms": "^2.1.3", + "on-finished": "^2.4.1", + "range-parser": "^1.2.1", + "statuses": "^2.0.2" + }, + "engines": { + "node": ">= 18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/send/node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "license": "MIT", + "peer": true, + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/send/node_modules/mime-types": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.2.tgz", + "integrity": "sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A==", + "license": "MIT", + "peer": true, + "dependencies": { + "mime-db": "^1.54.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/send/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT", + "peer": true + }, + "node_modules/serve-static": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-2.2.1.tgz", + "integrity": "sha512-xRXBn0pPqQTVQiC8wyQrKs2MOlX24zQ0POGaj0kultvoOCstBQM5yvOhAVSUwOMjQtTvsPWoNCHfPGwaaQJhTw==", + "license": "MIT", + "peer": true, + "dependencies": { + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "parseurl": "^1.3.3", + "send": "^1.2.0" + }, + "engines": { + "node": ">= 18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, "node_modules/set-function-length": { "version": "1.2.2", "license": "MIT", @@ -7398,49 +7672,73 @@ } }, "node_modules/socket.io": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/socket.io/-/socket.io-4.0.0.tgz", - "integrity": "sha512-/c1riZMV/4yz7KEpaMhDQbwhJDIoO55whXaRKgyEBQrLU9zUHXo9rzeTMvTOqwL9mbKfHKdrXcMoCeQ/1YtMsg==", + "version": "4.8.1", + "resolved": "https://registry.npmjs.org/socket.io/-/socket.io-4.8.1.tgz", + "integrity": "sha512-oZ7iUCxph8WYRHHcjBEc9unw3adt5CmSNlppj/5Q4k2RIrhl8Z5yY2Xr4j9zj0+wzVZ0bxmYoGSzKJnRl6A4yg==", "license": "MIT", "dependencies": { - "@types/cookie": "^0.4.0", - "@types/cors": "^2.8.8", - "@types/node": ">=10.0.0", "accepts": "~1.3.4", "base64id": "~2.0.0", - "debug": "~4.3.1", - "engine.io": "~5.0.0", - "socket.io-adapter": "~2.2.0", - "socket.io-parser": "~4.0.3" + "cors": "~2.8.5", + "debug": "~4.3.2", + "engine.io": "~6.6.0", + "socket.io-adapter": "~2.5.2", + "socket.io-parser": "~4.2.4" }, "engines": { - "node": ">=10.0.0" + "node": ">=10.2.0" } }, "node_modules/socket.io-adapter": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/socket.io-adapter/-/socket.io-adapter-2.2.0.tgz", - "integrity": "sha512-rG49L+FwaVEwuAdeBRq49M97YI3ElVabJPzvHT9S6a2CWhDKnjSFasvwAwSYPRhQzfn4NtDIbCaGYgOCOU/rlg==", + "version": "2.5.8", + "resolved": "https://registry.npmjs.org/socket.io-adapter/-/socket.io-adapter-2.5.8.tgz", + "integrity": "sha512-6Oy52pbg+kvdCVvjcN+FnY7BvxZ7cIHNScbvztT/It5d0vbwoJoVZmF2gjJmnV0/4WlXRfG15zc45ySk9Ah8bw==", + "license": "MIT", + "dependencies": { + "debug": "~4.4.1", + "ws": "~8.21.0" + } + }, + "node_modules/socket.io-adapter/node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/socket.io-adapter/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", "license": "MIT" }, "node_modules/socket.io-parser": { - "version": "4.0.5", - "resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-4.0.5.tgz", - "integrity": "sha512-sNjbT9dX63nqUFIOv95tTVm6elyIU4RvB1m8dOeZt+IgWwcWklFDOdmGcfo3zSiRsnR/3pJkjY5lfoGqEe4Eig==", + "version": "4.2.6", + "resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-4.2.6.tgz", + "integrity": "sha512-asJqbVBDsBCJx0pTqw3WfesSY0iRX+2xzWEWzrpcH7L6fLzrhyF8WPI8UaeM4YCuDfpwA/cgsdugMsmtz8EJeg==", "license": "MIT", "dependencies": { - "@types/component-emitter": "^1.2.10", - "component-emitter": "~1.3.0", - "debug": "~4.3.1" + "@socket.io/component-emitter": "~3.1.0", + "debug": "~4.4.1" }, "engines": { "node": ">=10.0.0" } }, "node_modules/socket.io-parser/node_modules/debug": { - "version": "4.3.7", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz", - "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==", + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", "license": "MIT", "dependencies": { "ms": "^2.1.3" @@ -8089,7 +8387,7 @@ }, "node_modules/ts-node": { "version": "10.9.2", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "@cspotcode/source-map-support": "^0.8.0", @@ -8417,7 +8715,7 @@ }, "node_modules/typescript": { "version": "5.9.3", - "dev": true, + "devOptional": true, "license": "Apache-2.0", "bin": { "tsc": "bin/tsc", @@ -8813,7 +9111,7 @@ }, "node_modules/v8-compile-cache-lib": { "version": "3.0.1", - "dev": true, + "devOptional": true, "license": "MIT" }, "node_modules/v8-to-istanbul": { @@ -9049,7 +9347,6 @@ }, "node_modules/wrappy": { "version": "1.0.2", - "dev": true, "license": "ISC" }, "node_modules/write-file-atomic": { @@ -9065,16 +9362,16 @@ } }, "node_modules/ws": { - "version": "7.4.6", - "resolved": "https://registry.npmjs.org/ws/-/ws-7.4.6.tgz", - "integrity": "sha512-YmhHDO4MzaDLB+M9ym/mDA5z0naX8j7SIlT8f8z+I0VtzsRbekxEutHSme7NPS2qE8StCYQNUnfWdXta/Yu85A==", + "version": "8.21.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.21.0.tgz", + "integrity": "sha512-Vsp28b7DRcimFQvrqu2Wek3z1iYxDCWqHYB8Qsnk/S4RfaCQzPGPyBNuVjJV3cd6UiKtUtp6sNM77gWvzcCH+g==", "license": "MIT", "engines": { - "node": ">=8.3.0" + "node": ">=10.0.0" }, "peerDependencies": { "bufferutil": "^4.0.1", - "utf-8-validate": "^5.0.2" + "utf-8-validate": ">=5.0.2" }, "peerDependenciesMeta": { "bufferutil": { @@ -9131,7 +9428,7 @@ }, "node_modules/yn": { "version": "3.1.1", - "dev": true, + "devOptional": true, "license": "MIT", "engines": { "node": ">=6" diff --git a/package.json b/package.json index 2c3f9fa..d10ef0b 100644 --- a/package.json +++ b/package.json @@ -35,11 +35,11 @@ "@nestjs/config": "^3.2.3", "@nestjs/core": "^10.4.22", "@nestjs/platform-express": "^10.4.22", - "@nestjs/platform-socket.io": "^10.0.0", + "@nestjs/platform-socket.io": "^10.4.22", "@nestjs/swagger": "^7.1.1", "@nestjs/throttler": "^6.5.0", "@nestjs/typeorm": "^10.0.2", - "@nestjs/websockets": "^10.0.0", + "@nestjs/websockets": "^10.4.22", "@prisma/client": "^5.19.1", "@stellar/stellar-sdk": "^11.0.0", "@types/jsonwebtoken": "^9.0.10", diff --git a/src/common/logger/custom.logger.ts b/src/common/logger/custom.logger.ts index 14108fe..bdbd685 100644 --- a/src/common/logger/custom.logger.ts +++ b/src/common/logger/custom.logger.ts @@ -1,4 +1,4 @@ -import { ConsoleLogger, Injectable, Scope } from '@nestjs/common'; +import { ConsoleLogger, Injectable, LogLevel, Scope } from '@nestjs/common'; import { requestContextStorage } from '../storage/request-context.storage'; /** @@ -19,7 +19,7 @@ export class CustomLogger extends ConsoleLogger { * @returns The fully formatted log string. */ formatMessage( - logLevel: string, + logLevel: LogLevel, message: unknown, pidMessage: string, formattedLogLevel: string, diff --git a/src/dynamic/dynamic.module.ts b/src/dynamic/dynamic.module.ts index 057f800..a447712 100644 --- a/src/dynamic/dynamic.module.ts +++ b/src/dynamic/dynamic.module.ts @@ -1,9 +1,10 @@ import { Module } from '@nestjs/common'; -import { InvoicesController } from './invoices.controller'; -import { PdfService } from './pdf.service'; +import { NetworkController } from './dynamic.controller'; +import { PdfService } from './dynamic.serivice'; @Module({ - controllers: [InvoicesController], + controllers: [NetworkController], providers: [PdfService], + exports: [PdfService], }) export class InvoicesModule {} diff --git a/src/pools/pools.controller.int.spec.ts b/src/pools/pools.controller.int.spec.ts index 2418a40..4a21cb4 100644 --- a/src/pools/pools.controller.int.spec.ts +++ b/src/pools/pools.controller.int.spec.ts @@ -20,17 +20,19 @@ describe('PoolsController (integration)', () => { await app.close(); }); - it('GET /api/v1/pools/:poolId/apy-history returns 200 and success envelope', async () => { + it('GET /api/v1/pools/:poolId/apy-history returns 200 and a 7-point history array', async () => { const res = await request(app.getHttpServer()) .get('/api/v1/pools/pool-123/apy-history') .expect(200); + // The endpoint returns the APY history array directly (see the controller's + // `ApyHistoryPoint[]` return type / Swagger `isArray: true`); this app has no + // global response-envelope interceptor. expect(res.body).toBeDefined(); - expect(res.body.status).toBe('success'); - expect(Array.isArray(res.body.data)).toBe(true); - expect(res.body.data).toHaveLength(7); + expect(Array.isArray(res.body)).toBe(true); + expect(res.body).toHaveLength(7); - for (const item of res.body.data) { + for (const item of res.body) { expect(item).toHaveProperty('date'); expect(item).toHaveProperty('apyPercentage'); expect(typeof item.date).toBe('string');