diff --git a/.claude/settings.json b/.claude/settings.json index 200bba3..a4794c5 100644 --- a/.claude/settings.json +++ b/.claude/settings.json @@ -3,6 +3,8 @@ "allow": [ "Bash(node -e *)", "Bash(python3 *)", + "Bash(curl *)", + "Bash(node *)", "Bash(node --input-type=module)", "Bash(2>&1)", "Bash(pnpm *)" diff --git a/.env b/.env new file mode 100644 index 0000000..aab6418 --- /dev/null +++ b/.env @@ -0,0 +1,5 @@ +DATABASE_URL="postgresql://fog:fog_dev@172.20.0.3:5432/fog_expedition" +REDIS_HOST="172.20.0.2" +REDIS_PORT="6379" +TWITCH_EXTENSION_SECRET="dev_secret_placeholder" +DEV_AUTH_BYPASS="true" diff --git a/CLAUDE.md b/CLAUDE.md index 983510b..5fa0f54 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -38,11 +38,14 @@ This is a Twitch Video Overlay extension implementing an autonomous tick-based Z ## Hard rules +- Don’t assume. Don’t hide confusion. Surface tradeoffs. +- Minimum code that solves the problem. Nothing speculative. +- Touch only what you must. Clean up only your own mess. +- Define success criteria. Loop until verified. - Do not use `Math.random()` anywhere in game logic. - Do not skip `nextTickAt` jitter — synchronised global ticks will thunder-herd Postgres. - Do not assume Twitch PubSub message delivery; treat as a hint to refresh, reconcile from REST. - Do not add inline scripts or inline event handlers to the overlay HTML — Twitch CSP forbids them. -- Do not reproduce Dead by Daylight assets, character names, or trademarked terms in encounter content. The aesthetic is *inspired by* the genre, not a clone. ## Twitch extension specifics diff --git a/apps/api/prisma/migrations/20260508150353_add_mission_duration_minutes/migration.sql b/apps/api/prisma/migrations/20260508150353_add_mission_duration_minutes/migration.sql new file mode 100644 index 0000000..c24940a --- /dev/null +++ b/apps/api/prisma/migrations/20260508150353_add_mission_duration_minutes/migration.sql @@ -0,0 +1,2 @@ +-- AlterTable +ALTER TABLE "missions" ADD COLUMN "duration_minutes" SMALLINT NOT NULL DEFAULT 20; diff --git a/apps/api/prisma/migrations/migration_lock.toml b/apps/api/prisma/migrations/migration_lock.toml new file mode 100644 index 0000000..fbffa92 --- /dev/null +++ b/apps/api/prisma/migrations/migration_lock.toml @@ -0,0 +1,3 @@ +# Please do not edit this file manually +# It should be added in your version-control system (i.e. Git) +provider = "postgresql" \ No newline at end of file diff --git a/apps/api/prisma/schema.prisma b/apps/api/prisma/schema.prisma index edbaeb1..7442efb 100644 --- a/apps/api/prisma/schema.prisma +++ b/apps/api/prisma/schema.prisma @@ -37,6 +37,7 @@ model Mission { groupId String? @map("group_id") @db.Uuid channelId String @map("channel_id") difficulty Int @db.SmallInt + durationMinutes Int @default(20) @map("duration_minutes") @db.SmallInt status String @default("active") encounterLibraryVersion String @map("encounter_library_version") startedAt DateTime @default(now()) @map("started_at") diff --git a/apps/api/src/app/app.module.ts b/apps/api/src/app/app.module.ts index 6b5d887..acacce2 100644 --- a/apps/api/src/app/app.module.ts +++ b/apps/api/src/app/app.module.ts @@ -2,22 +2,22 @@ import { Module } from '@nestjs/common'; import { APP_GUARD } from '@nestjs/core'; import { ScheduleModule } from '@nestjs/schedule'; import { ThrottlerGuard, ThrottlerModule } from '@nestjs/throttler'; +import { AppLoggerModule } from './logger/logger.module'; import { MissionsModule } from './missions/missions.module'; import { PrismaModule } from './prisma/prisma.module'; import { TickEngineModule } from './tick-engine/tick-engine.module'; @Module({ imports: [ + AppLoggerModule, ScheduleModule.forRoot(), ThrottlerModule.forRoot({ - throttlers: [{ ttl: 10000, limit: 30 }], + throttlers: [{ ttl: 1000, limit: 300 }], }), PrismaModule, MissionsModule, TickEngineModule, ], - providers: [ - { provide: APP_GUARD, useClass: ThrottlerGuard }, - ], + providers: [{ provide: APP_GUARD, useClass: ThrottlerGuard }], }) export class AppModule {} diff --git a/apps/api/src/app/auth/twitch-jwt.guard.ts b/apps/api/src/app/auth/twitch-jwt.guard.ts index d9c0b63..3755e12 100644 --- a/apps/api/src/app/auth/twitch-jwt.guard.ts +++ b/apps/api/src/app/auth/twitch-jwt.guard.ts @@ -6,6 +6,7 @@ import { UnauthorizedException, } from '@nestjs/common'; import { createHmac } from 'crypto'; +import { PinoLogger } from 'nestjs-pino'; export interface TwitchJwtPayload { opaque_user_id: string; @@ -17,6 +18,7 @@ export interface TwitchJwtPayload { interface HttpRequest { headers: Record; twitchClaims?: TwitchJwtPayload; + log?: { bindings: () => Record; child: (b: Record) => unknown }; } export const TwitchClaims = createParamDecorator( @@ -29,6 +31,10 @@ export const TwitchClaims = createParamDecorator( @Injectable() export class TwitchJwtGuard implements CanActivate { + constructor(private readonly logger: PinoLogger) { + this.logger.setContext(TwitchJwtGuard.name); + } + canActivate(ctx: ExecutionContext): boolean { const req = ctx.switchToHttp().getRequest(); const authHeader = req.headers['authorization']; @@ -36,11 +42,52 @@ export class TwitchJwtGuard implements CanActivate { if (!auth?.startsWith('Bearer ')) throw new UnauthorizedException(); const token = auth.slice(7); + const devClaims = tryDevBypass(token); + if (devClaims) { + req.twitchClaims = devClaims; + this.logger.warn({ msg: 'dev auth bypass active', opaqueUserId: devClaims.opaque_user_id }); + this.logger.assign({ channelId: devClaims.channel_id, opaqueUserId: devClaims.opaque_user_id }); + return true; + } + req.twitchClaims = verifyAndDecode(token); + + // Bind Twitch identity into this request's pino logger so every subsequent + // log line in the request lifecycle carries channelId and opaqueUserId. + this.logger.assign({ + channelId: req.twitchClaims.channel_id, + opaqueUserId: req.twitchClaims.opaque_user_id, + }); + return true; } } +/** + * Accepts the fake dev JWT minted by TwitchAuthService.buildDevAuth() when + * NODE_ENV=development. Tokens are identified by their `.dev` signature and + * a UDEV-prefixed opaque_user_id. Fails loudly if called outside dev mode — + * this guard must NEVER run in production. + */ +function tryDevBypass(token: string): TwitchJwtPayload | null { + if (process.env['NODE_ENV'] !== 'development') return null; + + const parts = token.split('.'); + if (parts.length !== 3 || parts[2] !== 'dev') return null; + + try { + const raw = Buffer.from( + parts[1].replace(/-/g, '+').replace(/_/g, '/'), + 'base64' + ).toString('utf8'); + const payload = JSON.parse(raw) as TwitchJwtPayload; + if (!payload.opaque_user_id?.startsWith('UDEV')) return null; + return payload; + } catch { + return null; + } +} + function verifyAndDecode(token: string): TwitchJwtPayload { const parts = token.split('.'); if (parts.length !== 3) throw new UnauthorizedException('Malformed JWT'); diff --git a/apps/api/src/app/logger/logger.module.ts b/apps/api/src/app/logger/logger.module.ts new file mode 100644 index 0000000..e025f72 --- /dev/null +++ b/apps/api/src/app/logger/logger.module.ts @@ -0,0 +1,27 @@ +import { Module } from '@nestjs/common'; +import { LoggerModule } from 'nestjs-pino'; + +const isDev = process.env['NODE_ENV'] !== 'production'; + +@Module({ + imports: [ + LoggerModule.forRoot({ + pinoHttp: { + level: process.env['LOG_LEVEL'] ?? (isDev ? 'debug' : 'info'), + // pino-pretty in dev; raw JSON in prod (consumed by log aggregators). + transport: isDev + ? { target: 'pino-pretty', options: { colorize: true, singleLine: false } } + : undefined, + // Suppress /health probes from cluttering the log stream. + autoLogging: { + ignore: (req) => req.url?.includes('/health') ?? false, + }, + serializers: { + req: (req) => ({ method: req.method, url: req.url }), + res: (res) => ({ statusCode: res.statusCode }), + }, + }, + }), + ], +}) +export class AppLoggerModule {} diff --git a/apps/api/src/app/missions/encounter.service.ts b/apps/api/src/app/missions/encounter.service.ts index 5c631be..62670ed 100644 --- a/apps/api/src/app/missions/encounter.service.ts +++ b/apps/api/src/app/missions/encounter.service.ts @@ -1,4 +1,5 @@ -import { Injectable, Logger } from '@nestjs/common'; +import { Injectable } from '@nestjs/common'; +import { PinoLogger } from 'nestjs-pino'; import type { EncounterResult, Mission, @@ -21,16 +22,18 @@ const RECENT_LOG_MAX = 20; @Injectable() export class EncounterService { - private readonly logger = new Logger(EncounterService.name); - - constructor(private readonly groupSynergy: GroupSynergyService) {} + constructor( + private readonly logger: PinoLogger, + private readonly groupSynergy: GroupSynergyService, + ) {} /** * Resolves one tick for the given mission state. - * Returns the updated state, or null if the mission ended. + * channelId is threaded in from the TickService for structured log correlation. */ processTick( - current: NonNullable + current: NonNullable, + channelId: string | null, ): NonNullable { const { mission, survivors } = current; const tickIndex = mission.tickIndex + 1; @@ -56,7 +59,7 @@ export class EncounterService { const augmentedPerks = this.groupSynergy.buildAugmentedPerks( survivor, - groupModifiers + groupModifiers, ); const result = resolveEncounter({ @@ -81,21 +84,22 @@ export class EncounterService { newLog.push({ ...result, logText: flavor }); - this.logger.log({ - message: 'tick resolved', + this.logger.info({ + msg: 'tick resolved', missionId: mission.id, - channelId: 'unknown', + channelId, tickIndex, survivorId: survivor.id, encounterKey: encounter.key, success: result.success, seed: result.seed, + modifiersApplied: result.modifiersApplied.length, }); const stateChange = result.survivorStateChange; if (stateChange) { updatedSurvivors = updatedSurvivors.map((s) => - s.id === survivor.id ? { ...s, state: stateChange.to } : s + s.id === survivor.id ? { ...s, state: stateChange.to } : s, ); updatedParticipants = updatedParticipants.map((p) => { if (p.survivorId !== survivor.id) return p; @@ -111,13 +115,13 @@ export class EncounterService { const updatedMission = buildUpdatedMission( mission, updatedParticipants, - tickIndex + tickIndex, ); - const recentLog = [ - ...newLog, - ...current.recentLog, - ].slice(0, RECENT_LOG_MAX); + const recentLog = [...newLog, ...current.recentLog].slice( + 0, + RECENT_LOG_MAX, + ); return { mission: updatedMission, @@ -134,14 +138,9 @@ function buildSeed(missionId: string, tickIndex: number): string { function buildUpdatedMission( mission: Mission, participants: MissionParticipant[], - tickIndex: number + tickIndex: number, ): Mission { - const allSacrificed = participants.every( - (p) => p.state === 'sacrificed' - ); - const allEscaped = participants.every( - (p) => p.state === 'active' || p.state === 'idle' - ); + const allSacrificed = participants.every((p) => p.state === 'sacrificed'); let status = mission.status; let endedAt = mission.endedAt; @@ -149,15 +148,14 @@ function buildUpdatedMission( if (allSacrificed && mission.status === 'active') { status = 'sacrifice'; endedAt = new Date().toISOString(); - } else if (allEscaped && tickIndex >= 10 && mission.status === 'active') { - // Success after at least 10 ticks with all survivors still active + } else if (!allSacrificed && tickIndex >= mission.durationMinutes && mission.status === 'active') { status = 'success'; endedAt = new Date().toISOString(); } const jitter = Math.floor(Math.random() * TICK_JITTER_MS); const nextTickAt = new Date( - Date.now() + TICK_BASE_INTERVAL_MS + jitter + Date.now() + TICK_BASE_INTERVAL_MS + jitter, ).toISOString(); return { diff --git a/apps/api/src/app/missions/missions.controller.ts b/apps/api/src/app/missions/missions.controller.ts index 08645d5..8b9018f 100644 --- a/apps/api/src/app/missions/missions.controller.ts +++ b/apps/api/src/app/missions/missions.controller.ts @@ -14,7 +14,11 @@ import { MissionStateResponseSchema, StartMissionRequestSchema, } from '@fog-explorer/api-interfaces'; -import { TwitchClaims, TwitchJwtGuard, TwitchJwtPayload } from '../auth/twitch-jwt.guard'; +import { + TwitchClaims, + TwitchJwtGuard, + TwitchJwtPayload, +} from '../auth/twitch-jwt.guard'; import { MissionStoreService } from './mission-store.service'; import { MissionsService } from './missions.service'; @@ -23,28 +27,39 @@ import { MissionsService } from './missions.service'; export class MissionsController { constructor( private readonly store: MissionStoreService, - private readonly missions: MissionsService + private readonly missions: MissionsService, ) {} @Get('state') - @Throttle({ default: { limit: 10, ttl: 10000 } }) + @Throttle({ default: { limit: 100, ttl: 1000 } }) async getState( - @TwitchClaims() claims: TwitchJwtPayload + @TwitchClaims() claims: TwitchJwtPayload, ): Promise { const state = await this.store.getStateForChannel(claims.channel_id); return MissionStateResponseSchema.parse(state); } + @Post('abandon') + @HttpCode(HttpStatus.NO_CONTENT) + async abandonMission( + @TwitchClaims() claims: TwitchJwtPayload, + ): Promise { + if (!claims.opaque_user_id.startsWith('U')) { + throw new ForbiddenException('Anonymous viewers cannot abandon missions'); + } + await this.missions.abandonMission(claims); + } + @Post('start') @HttpCode(HttpStatus.CREATED) async startMission( @TwitchClaims() claims: TwitchJwtPayload, - @Body() body: unknown + @Body() body: unknown, ): Promise> { if (!claims.opaque_user_id.startsWith('U')) { throw new ForbiddenException('Anonymous viewers cannot start missions'); } - const { difficulty } = StartMissionRequestSchema.parse(body); - return this.missions.startMission(claims, difficulty); + const { difficulty, durationMinutes, characterName } = StartMissionRequestSchema.parse(body); + return this.missions.startMission(claims, difficulty, durationMinutes, characterName); } } diff --git a/apps/api/src/app/missions/missions.module.ts b/apps/api/src/app/missions/missions.module.ts index 48e75ba..284323a 100644 --- a/apps/api/src/app/missions/missions.module.ts +++ b/apps/api/src/app/missions/missions.module.ts @@ -1,4 +1,6 @@ import { Module } from '@nestjs/common'; +import { LoggerModule } from 'nestjs-pino'; +import { TwitchJwtGuard } from '../auth/twitch-jwt.guard'; import { RedisModule } from '../redis/redis.module'; import { EncounterService } from './encounter.service'; import { GroupSynergyService } from './group-synergy.service'; @@ -7,9 +9,10 @@ import { MissionsController } from './missions.controller'; import { MissionsService } from './missions.service'; @Module({ - imports: [RedisModule], + imports: [RedisModule, LoggerModule], controllers: [MissionsController], providers: [ + TwitchJwtGuard, MissionStoreService, MissionsService, EncounterService, diff --git a/apps/api/src/app/missions/missions.service.ts b/apps/api/src/app/missions/missions.service.ts index d91dd68..bbb75e0 100644 --- a/apps/api/src/app/missions/missions.service.ts +++ b/apps/api/src/app/missions/missions.service.ts @@ -1,6 +1,11 @@ -import { Injectable } from '@nestjs/common'; +import { + ForbiddenException, + Injectable, + NotFoundException, +} from '@nestjs/common'; import type { Mission, + MissionDurationMinutes, MissionStateResponse, Survivor, SurvivorStats, @@ -22,7 +27,9 @@ export class MissionsService { async startMission( claims: TwitchJwtPayload, - difficulty: number + difficulty: number, + durationMinutes: MissionDurationMinutes, + characterName?: string, ): Promise> { const missionId = crypto.randomUUID(); const survivorId = crypto.randomUUID(); @@ -30,6 +37,7 @@ export class MissionsService { const jitter = Math.floor(Math.random() * TICK_JITTER_MS); const nextTickAt = new Date(now.getTime() + TICK_BASE_INTERVAL_MS + jitter); const stats: SurvivorStats = { objectives: 5, survival: 5, altruism: 5 }; + const survivorName = characterName ?? defaultName(claims.opaque_user_id); // Upsert user and create survivor + mission in one transaction. await this.prisma.$transaction(async (tx) => { @@ -44,7 +52,7 @@ export class MissionsService { id: survivorId, userId: user.id, channelId: claims.channel_id, - name: defaultName(claims.opaque_user_id), + name: survivorName, state: 'active', stats, perkSlots: [], @@ -56,6 +64,7 @@ export class MissionsService { id: missionId, channelId: claims.channel_id, difficulty, + durationMinutes, status: 'active', encounterLibraryVersion: getLibraryVersion(), nextTickAt, @@ -75,7 +84,7 @@ export class MissionsService { id: survivorId, opaqueUserId: claims.opaque_user_id, channelId: claims.channel_id, - name: defaultName(claims.opaque_user_id), + name: survivorName, state: 'active', stats, perkSlots: [], @@ -87,6 +96,7 @@ export class MissionsService { groupId: null, participants: [{ survivorId, state: 'active', hookCount: 0 }], difficulty, + durationMinutes, status: 'active', encounterLibraryVersion: getLibraryVersion(), nextTickAt: nextTickAt.toISOString(), @@ -107,6 +117,38 @@ export class MissionsService { return state; } + + async abandonMission(claims: TwitchJwtPayload): Promise { + const state = await this.store.getStateForChannel(claims.channel_id); + + if (!state) { + throw new NotFoundException('No active mission'); + } + if (state.mission.status !== 'active') { + throw new ForbiddenException('Mission is not active'); + } + + const isParticipant = state.survivors.some( + (s) => s.opaqueUserId === claims.opaque_user_id, + ); + if (!isParticipant) { + throw new ForbiddenException('Not a participant in this mission'); + } + + const now = new Date().toISOString(); + const updated: NonNullable = { + ...state, + mission: { ...state.mission, status: 'abandoned', endedAt: now }, + }; + + await this.prisma.mission.update({ + where: { id: state.mission.id }, + data: { status: 'abandoned', endedAt: new Date(now) }, + }); + + await this.store.setActiveMission(updated); + await this.store.removeMissionFromQueue(state.mission.id); + } } function defaultName(opaqueUserId: string): string { diff --git a/apps/api/src/app/tick-engine/tick-engine.module.ts b/apps/api/src/app/tick-engine/tick-engine.module.ts index 8cc8128..4fdf9e4 100644 --- a/apps/api/src/app/tick-engine/tick-engine.module.ts +++ b/apps/api/src/app/tick-engine/tick-engine.module.ts @@ -1,10 +1,11 @@ import { Module } from '@nestjs/common'; +import { LoggerModule } from 'nestjs-pino'; import { MissionsModule } from '../missions/missions.module'; import { TickService } from './tick.service'; import { TwitchPubSubService } from './twitch-pubsub.service'; @Module({ - imports: [MissionsModule], + imports: [MissionsModule, LoggerModule], providers: [TickService, TwitchPubSubService], }) export class TickEngineModule {} diff --git a/apps/api/src/app/tick-engine/tick.service.ts b/apps/api/src/app/tick-engine/tick.service.ts index 59b1b9d..3922a04 100644 --- a/apps/api/src/app/tick-engine/tick.service.ts +++ b/apps/api/src/app/tick-engine/tick.service.ts @@ -1,5 +1,6 @@ -import { Injectable, Logger } from '@nestjs/common'; +import { Injectable } from '@nestjs/common'; import { Cron, CronExpression } from '@nestjs/schedule'; +import { PinoLogger } from 'nestjs-pino'; import { PrismaService } from '../prisma/prisma.service'; import { EncounterService } from '../missions/encounter.service'; import { MissionStoreService } from '../missions/mission-store.service'; @@ -7,13 +8,12 @@ import { TwitchPubSubService } from './twitch-pubsub.service'; @Injectable() export class TickService { - private readonly logger = new Logger(TickService.name); - constructor( + private readonly logger: PinoLogger, private readonly store: MissionStoreService, private readonly encounters: EncounterService, private readonly prisma: PrismaService, - private readonly pubsub: TwitchPubSubService + private readonly pubsub: TwitchPubSubService, ) {} @Cron(CronExpression.EVERY_10_SECONDS) @@ -22,7 +22,7 @@ export class TickService { const dueMissionIds = await this.store.getDueMissionIds(now); await Promise.allSettled( - dueMissionIds.map((id) => this.processMission(id)) + dueMissionIds.map((id) => this.processMission(id)), ); } @@ -30,6 +30,10 @@ export class TickService { const token = await this.store.acquireLock(missionId); if (!token) return; + // Fetch channelId up-front so every log line in this mission carries it. + const channelId = await this.getChannelId(missionId); + const log = this.logger.logger.child({ missionId, channelId }); + try { const state = await this.store.getActiveMission(missionId); if (!state || state.mission.status !== 'active') { @@ -37,10 +41,10 @@ export class TickService { return; } - const updated = this.encounters.processTick(state); + const updated = this.encounters.processTick(state, channelId); const newLogs = updated.recentLog.slice( 0, - updated.recentLog.length - state.recentLog.length + updated.recentLog.length - state.recentLog.length, ); // Write to Postgres in a transaction. @@ -51,18 +55,23 @@ export class TickService { tickIndex: updated.mission.tickIndex, nextTickAt: new Date(updated.mission.nextTickAt), status: updated.mission.status, - endedAt: updated.mission.endedAt ? new Date(updated.mission.endedAt) : null, + endedAt: updated.mission.endedAt + ? new Date(updated.mission.endedAt) + : null, }, }); for (const survivor of updated.survivors) { const participant = updated.mission.participants.find( - (p) => p.survivorId === survivor.id + (p) => p.survivorId === survivor.id, ); if (!participant) continue; await tx.missionParticipant.updateMany({ where: { missionId, survivorId: survivor.id }, - data: { state: participant.state, hookCount: participant.hookCount }, + data: { + state: participant.state, + hookCount: participant.hookCount, + }, }); await tx.survivor.update({ where: { id: survivor.id }, @@ -72,14 +81,14 @@ export class TickService { if (newLogs.length > 0) { await tx.missionLog.createMany({ - data: newLogs.map((log) => ({ + data: newLogs.map((entry) => ({ id: crypto.randomUUID(), missionId, - tickIndex: log.tickIndex, - encounterKey: log.encounterKey, - renderedText: log.logText, - seed: log.seed, - modifiersApplied: log.modifiersApplied, + tickIndex: entry.tickIndex, + encounterKey: entry.encounterKey, + renderedText: entry.logText, + seed: entry.seed, + modifiersApplied: entry.modifiersApplied, })), }); } @@ -93,19 +102,16 @@ export class TickService { await this.store.scheduleTick(missionId, nextMs); } else { await this.store.removeMissionFromQueue(missionId); - this.logger.log({ - message: 'mission ended', - missionId, + log.info({ + msg: 'mission ended', status: updated.mission.status, tickIndex: updated.mission.tickIndex, }); } - await this.pubsub.broadcast(state.mission.participants[0] - ? await this.getChannelId(missionId) - : null, updated); + await this.pubsub.broadcast(channelId, updated); } catch (err) { - this.logger.error({ message: 'tick failed', missionId, err }); + log.error({ msg: 'tick failed', err }); } finally { await this.store.releaseLock(missionId, token); } diff --git a/apps/api/src/assets/.gitkeep b/apps/api/src/assets/.gitkeep index e69de29..3cf20d5 100644 --- a/apps/api/src/assets/.gitkeep +++ b/apps/api/src/assets/.gitkeep @@ -0,0 +1 @@ +- \ No newline at end of file diff --git a/apps/api/src/main.ts b/apps/api/src/main.ts index 2ffd74b..174cbf2 100644 --- a/apps/api/src/main.ts +++ b/apps/api/src/main.ts @@ -1,20 +1,33 @@ -/** - * This is not a production server yet! - * This is only a minimal backend to get started. - */ +// OTel must be initialised before any other requires — do not move this line. +import './tracing'; -import { Logger } from '@nestjs/common'; +import { Logger } from 'nestjs-pino'; import { NestFactory } from '@nestjs/core'; import { AppModule } from './app/app.module'; async function bootstrap() { - const app = await NestFactory.create(AppModule); + if (process.env['NODE_ENV'] === 'production' && process.env['DEV_AUTH_BYPASS']) { + throw new Error('DEV_AUTH_BYPASS must not be set in production'); + } + + const app = await NestFactory.create(AppModule, { bufferLogs: true }); + app.useLogger(app.get(Logger)); + + const allowedOrigins = + process.env['NODE_ENV'] === 'development' + ? ['http://localhost:4200', /https:\/\/.*\.ext-twitch\.tv$/] + : [/https:\/\/.*\.ext-twitch\.tv$/]; + app.enableCors({ origin: allowedOrigins, credentials: true }); + const globalPrefix = 'api'; app.setGlobalPrefix(globalPrefix); - const port = process.env.PORT || 3000; + + const port = process.env['PORT'] ?? 3000; await app.listen(port, '0.0.0.0'); - Logger.log( - `🚀 Application is running on: http://localhost:${port}/${globalPrefix}`, + + app.get(Logger).log( + `Application running on http://localhost:${port}/${globalPrefix}`, + 'bootstrap' ); } diff --git a/apps/api/src/tracing.ts b/apps/api/src/tracing.ts new file mode 100644 index 0000000..b6e3099 --- /dev/null +++ b/apps/api/src/tracing.ts @@ -0,0 +1,30 @@ +/** + * OpenTelemetry bootstrap — must be imported before any other app module. + * main.ts imports this as its first line. + */ +import { NodeSDK } from '@opentelemetry/sdk-node'; +import { getNodeAutoInstrumentations } from '@opentelemetry/auto-instrumentations-node'; +import { OTLPTraceExporter } from '@opentelemetry/exporter-trace-otlp-http'; +import { ConsoleSpanExporter } from '@opentelemetry/sdk-trace-node'; +import { resourceFromAttributes } from '@opentelemetry/resources'; + +const exporter = process.env['OTEL_EXPORTER_OTLP_ENDPOINT'] + ? new OTLPTraceExporter() // reads OTEL_EXPORTER_OTLP_ENDPOINT from env + : new ConsoleSpanExporter(); + +const sdk = new NodeSDK({ + resource: resourceFromAttributes({ 'service.name': 'fog-expedition-api' }), + traceExporter: exporter, + instrumentations: [ + getNodeAutoInstrumentations({ + // fs instrumentation is too noisy for a server with many module loads. + '@opentelemetry/instrumentation-fs': { enabled: false }, + }), + ], +}); + +sdk.start(); + +process.on('SIGTERM', () => { + sdk.shutdown().finally(() => process.exit(0)); +}); diff --git a/apps/overlay/project.json b/apps/overlay/project.json index 6bfafc4..ff1eb17 100644 --- a/apps/overlay/project.json +++ b/apps/overlay/project.json @@ -23,6 +23,12 @@ }, "configurations": { "production": { + "fileReplacements": [ + { + "replace": "apps/overlay/src/environments/environment.ts", + "with": "apps/overlay/src/environments/environment.prod.ts" + } + ], "budgets": [ { "type": "initial", diff --git a/apps/overlay/public/avatars/IconItems_chineseFirecracker.webp b/apps/overlay/public/avatars/IconItems_chineseFirecracker.webp new file mode 100755 index 0000000..dda13f6 Binary files /dev/null and b/apps/overlay/public/avatars/IconItems_chineseFirecracker.webp differ diff --git a/apps/overlay/public/avatars/IconItems_flashlight.webp b/apps/overlay/public/avatars/IconItems_flashlight.webp new file mode 100755 index 0000000..35ad400 Binary files /dev/null and b/apps/overlay/public/avatars/IconItems_flashlight.webp differ diff --git a/apps/overlay/public/avatars/IconItems_key.webp b/apps/overlay/public/avatars/IconItems_key.webp new file mode 100755 index 0000000..2ad2c58 Binary files /dev/null and b/apps/overlay/public/avatars/IconItems_key.webp differ diff --git a/apps/overlay/public/avatars/IconItems_map.webp b/apps/overlay/public/avatars/IconItems_map.webp new file mode 100755 index 0000000..b29865c Binary files /dev/null and b/apps/overlay/public/avatars/IconItems_map.webp differ diff --git a/apps/overlay/public/avatars/IconItems_medkit.webp b/apps/overlay/public/avatars/IconItems_medkit.webp new file mode 100755 index 0000000..21ebc97 Binary files /dev/null and b/apps/overlay/public/avatars/IconItems_medkit.webp differ diff --git a/apps/overlay/public/avatars/IconItems_toolbox.webp b/apps/overlay/public/avatars/IconItems_toolbox.webp new file mode 100755 index 0000000..d517ae3 Binary files /dev/null and b/apps/overlay/public/avatars/IconItems_toolbox.webp differ diff --git a/apps/overlay/public/avatars/S01_DwightFairfield_Portrait.webp b/apps/overlay/public/avatars/S01_DwightFairfield_Portrait.webp new file mode 100755 index 0000000..95d794d Binary files /dev/null and b/apps/overlay/public/avatars/S01_DwightFairfield_Portrait.webp differ diff --git a/apps/overlay/public/avatars/S02_MegThomas_Portrait.webp b/apps/overlay/public/avatars/S02_MegThomas_Portrait.webp new file mode 100755 index 0000000..cdc8aa6 Binary files /dev/null and b/apps/overlay/public/avatars/S02_MegThomas_Portrait.webp differ diff --git a/apps/overlay/public/avatars/S03_ClaudetteMorel_Portrait.webp b/apps/overlay/public/avatars/S03_ClaudetteMorel_Portrait.webp new file mode 100755 index 0000000..9ce15ce Binary files /dev/null and b/apps/overlay/public/avatars/S03_ClaudetteMorel_Portrait.webp differ diff --git a/apps/overlay/public/avatars/S04_JakePark_Portrait.webp b/apps/overlay/public/avatars/S04_JakePark_Portrait.webp new file mode 100755 index 0000000..e68b782 Binary files /dev/null and b/apps/overlay/public/avatars/S04_JakePark_Portrait.webp differ diff --git a/apps/overlay/public/avatars/S05_NeaKarlsson_Portrait.webp b/apps/overlay/public/avatars/S05_NeaKarlsson_Portrait.webp new file mode 100755 index 0000000..1e36222 Binary files /dev/null and b/apps/overlay/public/avatars/S05_NeaKarlsson_Portrait.webp differ diff --git a/apps/overlay/public/avatars/S06_LaurieStrode_Portrait.webp b/apps/overlay/public/avatars/S06_LaurieStrode_Portrait.webp new file mode 100755 index 0000000..c09dac3 Binary files /dev/null and b/apps/overlay/public/avatars/S06_LaurieStrode_Portrait.webp differ diff --git a/apps/overlay/public/avatars/S07_AceVisconti_Portrait.webp b/apps/overlay/public/avatars/S07_AceVisconti_Portrait.webp new file mode 100755 index 0000000..8ca890b Binary files /dev/null and b/apps/overlay/public/avatars/S07_AceVisconti_Portrait.webp differ diff --git a/apps/overlay/public/avatars/S08_BillOverbeck_Portrait.webp b/apps/overlay/public/avatars/S08_BillOverbeck_Portrait.webp new file mode 100755 index 0000000..b2c47f7 Binary files /dev/null and b/apps/overlay/public/avatars/S08_BillOverbeck_Portrait.webp differ diff --git a/apps/overlay/public/avatars/S09_FengMin_Portrait.webp b/apps/overlay/public/avatars/S09_FengMin_Portrait.webp new file mode 100755 index 0000000..04ae1d7 Binary files /dev/null and b/apps/overlay/public/avatars/S09_FengMin_Portrait.webp differ diff --git a/apps/overlay/public/avatars/S10_DavidKing_Portrait.webp b/apps/overlay/public/avatars/S10_DavidKing_Portrait.webp new file mode 100755 index 0000000..644511d Binary files /dev/null and b/apps/overlay/public/avatars/S10_DavidKing_Portrait.webp differ diff --git a/apps/overlay/public/avatars/S11_QuentinSmith_Portrait.webp b/apps/overlay/public/avatars/S11_QuentinSmith_Portrait.webp new file mode 100755 index 0000000..d3dfae2 Binary files /dev/null and b/apps/overlay/public/avatars/S11_QuentinSmith_Portrait.webp differ diff --git a/apps/overlay/public/avatars/S12_DavidTapp_Portrait.webp b/apps/overlay/public/avatars/S12_DavidTapp_Portrait.webp new file mode 100755 index 0000000..ad5891f Binary files /dev/null and b/apps/overlay/public/avatars/S12_DavidTapp_Portrait.webp differ diff --git a/apps/overlay/public/avatars/S13_KateDenson_Portrait.webp b/apps/overlay/public/avatars/S13_KateDenson_Portrait.webp new file mode 100755 index 0000000..c446f4d Binary files /dev/null and b/apps/overlay/public/avatars/S13_KateDenson_Portrait.webp differ diff --git a/apps/overlay/public/avatars/S14_AdamFrancis_Portrait.webp b/apps/overlay/public/avatars/S14_AdamFrancis_Portrait.webp new file mode 100755 index 0000000..340555e Binary files /dev/null and b/apps/overlay/public/avatars/S14_AdamFrancis_Portrait.webp differ diff --git a/apps/overlay/public/avatars/S15_JeffJohansen_Portrait.webp b/apps/overlay/public/avatars/S15_JeffJohansen_Portrait.webp new file mode 100755 index 0000000..a381fe2 Binary files /dev/null and b/apps/overlay/public/avatars/S15_JeffJohansen_Portrait.webp differ diff --git a/apps/overlay/public/avatars/S16_JaneRomero_Portrait.webp b/apps/overlay/public/avatars/S16_JaneRomero_Portrait.webp new file mode 100755 index 0000000..3cbefca Binary files /dev/null and b/apps/overlay/public/avatars/S16_JaneRomero_Portrait.webp differ diff --git a/apps/overlay/public/avatars/S17_AshWilliams_Portrait.webp b/apps/overlay/public/avatars/S17_AshWilliams_Portrait.webp new file mode 100755 index 0000000..6c759ce Binary files /dev/null and b/apps/overlay/public/avatars/S17_AshWilliams_Portrait.webp differ diff --git a/apps/overlay/public/avatars/S18_NancyWheeler_Portrait.webp b/apps/overlay/public/avatars/S18_NancyWheeler_Portrait.webp new file mode 100755 index 0000000..62c1eca Binary files /dev/null and b/apps/overlay/public/avatars/S18_NancyWheeler_Portrait.webp differ diff --git a/apps/overlay/public/avatars/S19_SteveHarrington_Portrait.webp b/apps/overlay/public/avatars/S19_SteveHarrington_Portrait.webp new file mode 100755 index 0000000..5b07b48 Binary files /dev/null and b/apps/overlay/public/avatars/S19_SteveHarrington_Portrait.webp differ diff --git a/apps/overlay/public/avatars/S20_YuiKimura_Portrait.webp b/apps/overlay/public/avatars/S20_YuiKimura_Portrait.webp new file mode 100755 index 0000000..bf3c67c Binary files /dev/null and b/apps/overlay/public/avatars/S20_YuiKimura_Portrait.webp differ diff --git a/apps/overlay/public/avatars/S21_ZarinaKassir_Portrait.webp b/apps/overlay/public/avatars/S21_ZarinaKassir_Portrait.webp new file mode 100755 index 0000000..776741f Binary files /dev/null and b/apps/overlay/public/avatars/S21_ZarinaKassir_Portrait.webp differ diff --git a/apps/overlay/public/avatars/S22_CherylMason_Portrait.webp b/apps/overlay/public/avatars/S22_CherylMason_Portrait.webp new file mode 100755 index 0000000..d70623d Binary files /dev/null and b/apps/overlay/public/avatars/S22_CherylMason_Portrait.webp differ diff --git a/apps/overlay/public/avatars/S23_FelixRichter_Portrait.webp b/apps/overlay/public/avatars/S23_FelixRichter_Portrait.webp new file mode 100755 index 0000000..e9bfac8 Binary files /dev/null and b/apps/overlay/public/avatars/S23_FelixRichter_Portrait.webp differ diff --git a/apps/overlay/public/avatars/S24_ElodieRakoto_Portrait.webp b/apps/overlay/public/avatars/S24_ElodieRakoto_Portrait.webp new file mode 100755 index 0000000..8ddf7d4 Binary files /dev/null and b/apps/overlay/public/avatars/S24_ElodieRakoto_Portrait.webp differ diff --git a/apps/overlay/public/avatars/S25_Yun-JinLee_Portrait.webp b/apps/overlay/public/avatars/S25_Yun-JinLee_Portrait.webp new file mode 100755 index 0000000..6211e56 Binary files /dev/null and b/apps/overlay/public/avatars/S25_Yun-JinLee_Portrait.webp differ diff --git a/apps/overlay/public/avatars/S26_JillValentine_Portrait.webp b/apps/overlay/public/avatars/S26_JillValentine_Portrait.webp new file mode 100755 index 0000000..7ea3e40 Binary files /dev/null and b/apps/overlay/public/avatars/S26_JillValentine_Portrait.webp differ diff --git a/apps/overlay/public/avatars/S27_LeonScottKennedy_Portrait.webp b/apps/overlay/public/avatars/S27_LeonScottKennedy_Portrait.webp new file mode 100755 index 0000000..ea27b83 Binary files /dev/null and b/apps/overlay/public/avatars/S27_LeonScottKennedy_Portrait.webp differ diff --git a/apps/overlay/public/avatars/S28_MikaelaReid_Portrait.webp b/apps/overlay/public/avatars/S28_MikaelaReid_Portrait.webp new file mode 100755 index 0000000..b0c4fd6 Binary files /dev/null and b/apps/overlay/public/avatars/S28_MikaelaReid_Portrait.webp differ diff --git a/apps/overlay/public/avatars/S29_JonahVasquez_Portrait.webp b/apps/overlay/public/avatars/S29_JonahVasquez_Portrait.webp new file mode 100755 index 0000000..943c440 Binary files /dev/null and b/apps/overlay/public/avatars/S29_JonahVasquez_Portrait.webp differ diff --git a/apps/overlay/public/avatars/S30_YoichiAsakawa_Portrait.webp b/apps/overlay/public/avatars/S30_YoichiAsakawa_Portrait.webp new file mode 100755 index 0000000..cfa7562 Binary files /dev/null and b/apps/overlay/public/avatars/S30_YoichiAsakawa_Portrait.webp differ diff --git a/apps/overlay/public/avatars/S31_HaddieKaur_Portrait.webp b/apps/overlay/public/avatars/S31_HaddieKaur_Portrait.webp new file mode 100755 index 0000000..5867018 Binary files /dev/null and b/apps/overlay/public/avatars/S31_HaddieKaur_Portrait.webp differ diff --git a/apps/overlay/public/avatars/S32_AdaWong_Portrait.webp b/apps/overlay/public/avatars/S32_AdaWong_Portrait.webp new file mode 100755 index 0000000..c3c3532 Binary files /dev/null and b/apps/overlay/public/avatars/S32_AdaWong_Portrait.webp differ diff --git a/apps/overlay/public/avatars/S33_RebeccaChambers_Portrait.webp b/apps/overlay/public/avatars/S33_RebeccaChambers_Portrait.webp new file mode 100755 index 0000000..ab9189c Binary files /dev/null and b/apps/overlay/public/avatars/S33_RebeccaChambers_Portrait.webp differ diff --git a/apps/overlay/public/avatars/S34_VittorioToscano_Portrait.webp b/apps/overlay/public/avatars/S34_VittorioToscano_Portrait.webp new file mode 100755 index 0000000..f8e6c07 Binary files /dev/null and b/apps/overlay/public/avatars/S34_VittorioToscano_Portrait.webp differ diff --git a/apps/overlay/public/avatars/S35_ThalitaLyra_Portrait.webp b/apps/overlay/public/avatars/S35_ThalitaLyra_Portrait.webp new file mode 100755 index 0000000..d0d4453 Binary files /dev/null and b/apps/overlay/public/avatars/S35_ThalitaLyra_Portrait.webp differ diff --git a/apps/overlay/public/avatars/S36_RenatoLyra_Portrait.webp b/apps/overlay/public/avatars/S36_RenatoLyra_Portrait.webp new file mode 100755 index 0000000..72e2abd Binary files /dev/null and b/apps/overlay/public/avatars/S36_RenatoLyra_Portrait.webp differ diff --git a/apps/overlay/public/avatars/S37_GabrielSoma_Portrait.webp b/apps/overlay/public/avatars/S37_GabrielSoma_Portrait.webp new file mode 100755 index 0000000..674c6e7 Binary files /dev/null and b/apps/overlay/public/avatars/S37_GabrielSoma_Portrait.webp differ diff --git a/apps/overlay/public/avatars/S38_NicolasCage_Portrait.webp b/apps/overlay/public/avatars/S38_NicolasCage_Portrait.webp new file mode 100755 index 0000000..2b7dbb9 Binary files /dev/null and b/apps/overlay/public/avatars/S38_NicolasCage_Portrait.webp differ diff --git a/apps/overlay/public/avatars/S39_EllenRipley_Portrait.webp b/apps/overlay/public/avatars/S39_EllenRipley_Portrait.webp new file mode 100755 index 0000000..9256022 Binary files /dev/null and b/apps/overlay/public/avatars/S39_EllenRipley_Portrait.webp differ diff --git a/apps/overlay/public/avatars/S40_AlanWake_Portrait.webp b/apps/overlay/public/avatars/S40_AlanWake_Portrait.webp new file mode 100755 index 0000000..1833971 Binary files /dev/null and b/apps/overlay/public/avatars/S40_AlanWake_Portrait.webp differ diff --git a/apps/overlay/public/avatars/S41_SableWard_Portrait.webp b/apps/overlay/public/avatars/S41_SableWard_Portrait.webp new file mode 100755 index 0000000..f9cbce0 Binary files /dev/null and b/apps/overlay/public/avatars/S41_SableWard_Portrait.webp differ diff --git a/apps/overlay/public/avatars/S42_TheTroupe_Portrait.webp b/apps/overlay/public/avatars/S42_TheTroupe_Portrait.webp new file mode 100755 index 0000000..c47d561 Binary files /dev/null and b/apps/overlay/public/avatars/S42_TheTroupe_Portrait.webp differ diff --git a/apps/overlay/public/avatars/S43_LaraCroft_Portrait.webp b/apps/overlay/public/avatars/S43_LaraCroft_Portrait.webp new file mode 100755 index 0000000..2831c8f Binary files /dev/null and b/apps/overlay/public/avatars/S43_LaraCroft_Portrait.webp differ diff --git a/apps/overlay/public/avatars/S44_TrevorBelmont_Portrait.webp b/apps/overlay/public/avatars/S44_TrevorBelmont_Portrait.webp new file mode 100755 index 0000000..cb4fb61 Binary files /dev/null and b/apps/overlay/public/avatars/S44_TrevorBelmont_Portrait.webp differ diff --git a/apps/overlay/public/avatars/S45_TaurieCain_Portrait.webp b/apps/overlay/public/avatars/S45_TaurieCain_Portrait.webp new file mode 100755 index 0000000..18ce1b3 Binary files /dev/null and b/apps/overlay/public/avatars/S45_TaurieCain_Portrait.webp differ diff --git a/apps/overlay/public/avatars/S46_RickGrimes_Portrait.webp b/apps/overlay/public/avatars/S46_RickGrimes_Portrait.webp new file mode 100755 index 0000000..6eb84d6 Binary files /dev/null and b/apps/overlay/public/avatars/S46_RickGrimes_Portrait.webp differ diff --git a/apps/overlay/public/avatars/S47_MichonneGrimes_Portrait.webp b/apps/overlay/public/avatars/S47_MichonneGrimes_Portrait.webp new file mode 100755 index 0000000..1c99163 Binary files /dev/null and b/apps/overlay/public/avatars/S47_MichonneGrimes_Portrait.webp differ diff --git a/apps/overlay/public/avatars/S48_VeeBoonyasak_Portrait.webp b/apps/overlay/public/avatars/S48_VeeBoonyasak_Portrait.webp new file mode 100755 index 0000000..ef854dd Binary files /dev/null and b/apps/overlay/public/avatars/S48_VeeBoonyasak_Portrait.webp differ diff --git a/apps/overlay/src/app/app.config.ts b/apps/overlay/src/app/app.config.ts index 66c3ccc..a4bc73a 100644 --- a/apps/overlay/src/app/app.config.ts +++ b/apps/overlay/src/app/app.config.ts @@ -4,10 +4,13 @@ import { } from '@angular/core'; import { provideHttpClient, withInterceptors } from '@angular/common/http'; import { authInterceptor } from './ebs/auth.interceptor'; +import { EBS_BASE_URL } from './ebs/ebs-api.service'; +import { environment } from '../environments/environment'; export const appConfig: ApplicationConfig = { providers: [ provideBrowserGlobalErrorListeners(), provideHttpClient(withInterceptors([authInterceptor])), + { provide: EBS_BASE_URL, useValue: environment.ebsBaseUrl }, ], }; diff --git a/apps/overlay/src/app/ebs/ebs-api.service.spec.ts b/apps/overlay/src/app/ebs/ebs-api.service.spec.ts index ad80e2a..ea52c9a 100644 --- a/apps/overlay/src/app/ebs/ebs-api.service.spec.ts +++ b/apps/overlay/src/app/ebs/ebs-api.service.spec.ts @@ -45,17 +45,17 @@ describe('EbsApiService', () => { }); describe('startMission', () => { - it('POSTs to /missions/start with difficulty', () => { - service.startMission({ difficulty: 2 }).subscribe({ error: () => undefined }); + it('POSTs to /missions/start with difficulty and durationMinutes', () => { + service.startMission({ difficulty: 2, durationMinutes: 20 }).subscribe({ error: () => undefined }); const req = controller.expectOne(`${BASE}/missions/start`); expect(req.request.method).toBe('POST'); - expect(req.request.body).toEqual({ difficulty: 2 }); + expect(req.request.body).toEqual({ difficulty: 2, durationMinutes: 20 }); req.flush({ bad: 'shape' }); }); - it('throws ZodError for invalid difficulty before sending request', () => { - expect(() => service.startMission({ difficulty: 99 })).toThrow(); + it('throws ZodError for invalid request before sending', () => { + expect(() => service.startMission({ difficulty: 99, durationMinutes: 20 })).toThrow(); controller.expectNone(`${BASE}/missions/start`); }); }); diff --git a/apps/overlay/src/app/ebs/ebs-api.service.ts b/apps/overlay/src/app/ebs/ebs-api.service.ts index dbb0f53..b65d4a6 100644 --- a/apps/overlay/src/app/ebs/ebs-api.service.ts +++ b/apps/overlay/src/app/ebs/ebs-api.service.ts @@ -5,7 +5,6 @@ import { ChoosePerkRequestSchema, MissionStateResponse, MissionStateResponseSchema, - MissionSchema, StartMissionRequest, StartMissionRequestSchema, SurvivorSchema, @@ -25,11 +24,21 @@ export class EbsApiService { .pipe(map((body) => MissionStateResponseSchema.parse(body))); } - startMission(req: StartMissionRequest) { + startMission(req: StartMissionRequest): Observable> { StartMissionRequestSchema.parse(req); return this.http .post(`${this.baseUrl}/missions/start`, req) - .pipe(map((body) => MissionSchema.parse(body))); + .pipe( + map((body) => { + const parsed = MissionStateResponseSchema.parse(body); + if (!parsed) throw new Error('Unexpected null from /missions/start'); + return parsed; + }), + ); + } + + abandonMission(): Observable { + return this.http.post(`${this.baseUrl}/missions/abandon`, {}); } choosePerk(req: ChoosePerkRequest) { diff --git a/apps/overlay/src/app/mission/mission-state.store.ts b/apps/overlay/src/app/mission/mission-state.store.ts index 48b9c8d..65ebef4 100644 --- a/apps/overlay/src/app/mission/mission-state.store.ts +++ b/apps/overlay/src/app/mission/mission-state.store.ts @@ -1,8 +1,10 @@ -import { inject, Injectable, DestroyRef, signal } from '@angular/core'; +import { effect, inject, Injectable, DestroyRef, signal, untracked } from '@angular/core'; import { MissionStateResponse } from '@fog-explorer/api-interfaces'; import { EbsApiService } from '../ebs/ebs-api.service'; import { TwitchAuthService } from '../twitch/twitch-auth.service'; +const POLL_INTERVAL_MS = 20_000; + @Injectable({ providedIn: 'root' }) export class MissionStateStore { private readonly ebs = inject(EbsApiService); @@ -11,11 +13,30 @@ export class MissionStateStore { private readonly _state = signal(null); private readonly _loading = signal(false); + private readonly _starting = signal(false); private readonly _error = signal(null); + private readonly _initializing = signal(true); readonly state = this._state.asReadonly(); readonly loading = this._loading.asReadonly(); + readonly starting = this._starting.asReadonly(); readonly error = this._error.asReadonly(); + readonly initializing = this._initializing.asReadonly(); + + private pollTimer: ReturnType | null = null; + + constructor() { + // Poll while the mission is active and the overlay is visible. + // PubSub is the primary update path; polling reconciles missed messages. + effect(() => { + const shouldPoll = + this._state()?.mission.status === 'active' && + this.authService.isVisible(); + untracked(() => (shouldPoll ? this.startPolling() : this.stopPolling())); + }); + + this.destroyRef.onDestroy(() => this.stopPolling()); + } init(): void { this.fetchState(); @@ -27,6 +48,31 @@ export class MissionStateStore { this.fetchState(); } + abandonMission(): void { + if (this._starting()) return; + this._error.set(null); + this.ebs.abandonMission().subscribe({ + next: () => this._state.set(null), + error: (err) => this._error.set(err), + }); + } + + startMission(difficulty: 1 | 2 | 3, durationMinutes: 10 | 20 | 30, characterName?: string): void { + if (this._starting()) return; + this._starting.set(true); + this._error.set(null); + this.ebs.startMission({ difficulty, durationMinutes, characterName }).subscribe({ + next: (state) => { + this._state.set(state); + this._starting.set(false); + }, + error: (err) => { + this._starting.set(false); + this._error.set(err); + }, + }); + } + private fetchState(): void { if (!this.authService.auth()) return; @@ -35,15 +81,28 @@ export class MissionStateStore { next: (state) => { this._state.set(state); this._loading.set(false); + this._initializing.set(false); this._error.set(null); }, error: (err) => { this._loading.set(false); + this._initializing.set(false); this._error.set(err); }, }); } + private startPolling(): void { + if (this.pollTimer !== null) return; + this.pollTimer = setInterval(() => this.fetchState(), POLL_INTERVAL_MS); + } + + private stopPolling(): void { + if (this.pollTimer === null) return; + clearInterval(this.pollTimer); + this.pollTimer = null; + } + private subscribePubSub(): void { if (!window.Twitch?.ext) return; diff --git a/apps/overlay/src/app/panel/ambient-event.component.css b/apps/overlay/src/app/panel/ambient-event.component.css new file mode 100644 index 0000000..17a19ee --- /dev/null +++ b/apps/overlay/src/app/panel/ambient-event.component.css @@ -0,0 +1,54 @@ +:host { display: block; } + +.panel { + width: 290px; + min-height: 72px; + background: var(--fog-bg); + padding: 12px 16px; + display: flex; + align-items: center; + gap: 14px; + cursor: pointer; + border-top: 1px solid var(--fog-border); + animation: slide-up 0.22s ease-out; +} + +@keyframes slide-up { + from { transform: translateY(8px); opacity: 0; } + to { transform: translateY(0); opacity: 1; } +} + +.glyph { + font-family: var(--font-mono); + font-size: 22px; + flex-shrink: 0; + line-height: 1; + color: var(--fog-amber); +} + +.glyph.injury { color: var(--fog-amber-dim); } +.glyph.sacrifice { color: var(--fog-red); } + +.text { + flex: 1; + overflow: hidden; +} + +.event-label { + font-family: var(--font-mono); + font-size: 9px; + text-transform: uppercase; + letter-spacing: 0.12em; + color: var(--fog-text-dim); +} + +.event-desc { + font-family: var(--font-mono); + font-size: 12px; + color: var(--fog-text); + line-height: 1.4; + margin-top: 2px; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +} diff --git a/apps/overlay/src/app/panel/ambient-event.component.html b/apps/overlay/src/app/panel/ambient-event.component.html new file mode 100644 index 0000000..8748ece --- /dev/null +++ b/apps/overlay/src/app/panel/ambient-event.component.html @@ -0,0 +1,8 @@ +
+ {{ meta().glyph }} +
+
{{ meta().label }}
+
{{ event().description }}
+
+
diff --git a/apps/overlay/src/app/panel/ambient-event.component.ts b/apps/overlay/src/app/panel/ambient-event.component.ts index 01a0ab0..58630c9 100644 --- a/apps/overlay/src/app/panel/ambient-event.component.ts +++ b/apps/overlay/src/app/panel/ambient-event.component.ts @@ -1,52 +1,27 @@ -import { ChangeDetectionStrategy, Component, input, output } from '@angular/core'; +import { ChangeDetectionStrategy, Component, computed, input, output } from '@angular/core'; export interface AmbientEventData { type: 'injury' | 'sacrifice' | 'mission-complete' | 'perk-acquired'; description: string; } +const EVENT_META: Record = { + 'injury': { glyph: '◈', label: 'Injured' }, + 'sacrifice': { glyph: '✕', label: 'Sacrificed' }, + 'mission-complete':{ glyph: '◆', label: 'Mission complete' }, + 'perk-acquired': { glyph: '⬡', label: 'Perk acquired' }, +}; + @Component({ selector: 'app-ambient-event', standalone: true, changeDetection: ChangeDetectionStrategy.OnPush, - styles: [` - :host { display: block; } - .panel { - width: 290px; - height: 92px; - background: rgba(15, 18, 22, 0.88); - padding: 12px 16px; - box-sizing: border-box; - display: flex; - flex-direction: column; - justify-content: center; - gap: 4px; - cursor: pointer; - } - .event-type { - font-family: sans-serif; - font-size: 10px; - text-transform: uppercase; - letter-spacing: 0.08em; - color: #6a7080; - } - .event-type.injury, .event-type.downed { color: #B8842E; } - .event-type.sacrifice { color: #C03A3A; } - .event-type.mission-complete { color: #E8A547; } - .event-desc { - font-family: 'JetBrains Mono', 'IBM Plex Mono', monospace; - font-size: 13px; - color: #c8ccd0; - } - `], - template: ` -
- {{ event().type }} - {{ event().description }} -
- `, + templateUrl: './ambient-event.component.html', + styleUrl: './ambient-event.component.css', }) export class AmbientEventComponent { event = input.required(); dismissed = output(); + + protected meta = computed(() => EVENT_META[this.event().type]); } diff --git a/apps/overlay/src/app/panel/expanded-panel.component.css b/apps/overlay/src/app/panel/expanded-panel.component.css new file mode 100644 index 0000000..aa47b55 --- /dev/null +++ b/apps/overlay/src/app/panel/expanded-panel.component.css @@ -0,0 +1,260 @@ +:host { display: block; } + +.panel { + position: relative; + width: 320px; + max-height: 440px; + background: var(--fog-bg-deep); + display: flex; + flex-direction: column; + overflow: hidden; +} + +/* ── Header ── */ +.header { + display: flex; + align-items: flex-start; + gap: 12px; + padding: 14px 40px 12px 16px; + border-bottom: 1px solid var(--fog-border); +} + +.avatar { + flex-shrink: 0; + width: 48px; + height: 48px; + border-radius: 50%; + background: #0e1116; + border: 2px solid var(--fog-amber); + display: flex; + align-items: center; + justify-content: center; + transition: border-color 0.3s ease; +} + +.avatar-img { + width: 100%; + height: 100%; + border-radius: 50%; + object-fit: cover; + object-position: top center; +} + +.avatar .avatar-initials { + font-family: var(--font-mono); + font-size: 15px; + font-weight: 600; + letter-spacing: 0.06em; + color: #8a9ab8; + line-height: 1; +} + +.header-info { + flex: 1; + min-width: 0; +} + +.survivor-name { + font-family: var(--font-serif); + font-size: 20px; + font-weight: 700; + color: #e8eaed; + letter-spacing: 0.015em; + line-height: 1.1; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +} + +.survivor-state { + font-family: var(--font-mono); + font-size: 10px; + text-transform: uppercase; + letter-spacing: 0.12em; + color: var(--fog-amber); + margin-top: 2px; +} +.survivor-state.injured { color: var(--fog-amber-dim); } +.survivor-state.downed { color: var(--fog-red); } +.survivor-state.sacrificed { color: var(--fog-red); } + +.close-btn { + position: absolute; + top: 10px; + right: 10px; + width: 28px; + height: 28px; + background: none; + border: 1px solid var(--fog-border); + color: var(--fog-text-dim); + font-size: 14px; + line-height: 1; + cursor: pointer; + display: flex; + align-items: center; + justify-content: center; + transition: color 0.15s, border-color 0.15s; +} +.close-btn:hover { + color: var(--fog-text); + border-color: rgba(255,255,255,0.15); +} +.close-btn:focus-visible { + outline: 2px solid var(--fog-amber); + outline-offset: 2px; +} + +/* ── Perks ── */ +.perk-slots { + display: flex; + flex-wrap: wrap; + gap: 4px; + margin-top: 8px; +} + +.perk-chip { + font-family: var(--font-mono); + font-size: 9.5px; + background: rgba(232, 165, 71, 0.10); + color: var(--fog-amber); + border: 1px solid rgba(232, 165, 71, 0.20); + padding: 2px 7px; + letter-spacing: 0.04em; +} + +.perk-empty { + font-family: var(--font-mono); + font-size: 10px; + color: var(--fog-text-faint); + font-style: italic; +} + +/* ── Mission strip ── */ +.mission-strip { + display: flex; + align-items: center; + gap: 8px; + padding: 8px 16px; + border-bottom: 1px solid var(--fog-border); +} + +.difficulty-pips { + display: flex; + gap: 3px; +} + +.pip { + width: 6px; + height: 6px; + background: var(--fog-amber); + clip-path: polygon(50% 0%, 100% 50%, 50% 100%, 0% 50%); +} + +.pip.dim { background: var(--fog-text-faint); } + +.mission-label { + font-family: var(--font-mono); + font-size: 9.5px; + color: var(--fog-text-dim); + letter-spacing: 0.06em; + text-transform: uppercase; +} + +.tick-counter { + font-family: var(--font-mono); + font-size: 10px; + color: var(--fog-text-dim); + margin-left: auto; +} + +/* ── Log ── */ +.log { + flex: 1; + overflow: hidden; + padding: 10px 16px 12px; + display: flex; + flex-direction: column; + gap: 5px; +} + +.log-entry { + font-family: var(--font-mono); + font-size: 10.5px; + line-height: 1.5; + color: var(--fog-text); + transition: opacity 0.2s; +} + +.log-glyph { + margin-right: 5px; + font-size: 9px; +} + +.log-entry.success .log-glyph { color: var(--fog-green); } +.log-entry.failure .log-glyph { color: var(--fog-red); } + +/* ── Abandon row ── */ +.abandon-row { + display: flex; + align-items: center; + gap: 6px; + padding: 8px 16px; + border-bottom: 1px solid var(--fog-border); +} + +.abandon-btn { + background: none; + border: 1px solid rgba(180, 60, 60, 0.3); + color: rgba(180, 60, 60, 0.6); + font-family: var(--font-mono); + font-size: 9.5px; + text-transform: uppercase; + letter-spacing: 0.06em; + padding: 4px 10px; + cursor: pointer; + transition: border-color 0.15s, color 0.15s; +} +.abandon-btn:hover, .abandon-btn:focus-visible { + border-color: var(--fog-red); + color: var(--fog-red); + outline: none; +} + +.abandon-confirm-label { + font-family: var(--font-mono); + font-size: 9.5px; + color: var(--fog-red); + flex: 1; + font-style: italic; +} + +.abandon-confirm-btn { + background: rgba(180, 60, 60, 0.15); + border: 1px solid var(--fog-red); + color: var(--fog-red); + font-family: var(--font-mono); + font-size: 9.5px; + text-transform: uppercase; + letter-spacing: 0.06em; + padding: 4px 10px; + cursor: pointer; +} +.abandon-confirm-btn:focus-visible { outline: 2px solid var(--fog-red); } + +.abandon-cancel-btn { + background: none; + border: 1px solid var(--fog-border); + color: var(--fog-text-dim); + font-family: var(--font-mono); + font-size: 9.5px; + text-transform: uppercase; + letter-spacing: 0.06em; + padding: 4px 10px; + cursor: pointer; + transition: border-color 0.15s, color 0.15s; +} +.abandon-cancel-btn:hover, .abandon-cancel-btn:focus-visible { + border-color: rgba(255,255,255,0.15); + color: var(--fog-text); + outline: none; +} diff --git a/apps/overlay/src/app/panel/expanded-panel.component.html b/apps/overlay/src/app/panel/expanded-panel.component.html new file mode 100644 index 0000000..c07df4d --- /dev/null +++ b/apps/overlay/src/app/panel/expanded-panel.component.html @@ -0,0 +1,72 @@ +
+ + + @if (survivor(); as s) { +
+ +
+
{{ s.name }}
+
{{ s.state }}
+
+ @for (perk of s.perkSlots; track perk.id) { + {{ perk.name }} + } @empty { + No perks equipped + } +
+
+
+ } + + @if (mission(); as m) { +
+
+ @for (pip of difficultyPips(); track $index) { +
+ } +
+ Mission + T+{{ m.tickIndex }} +
+ } + + @if (missionState().mission.status === 'active') { +
+ @if (!confirmingAbandon()) { + + } @else { + Leave the fog? + + + } +
+ } + +
+ @for (entry of logEntries(); track entry.tickIndex; let i = $index) { +
+ {{ entry.success ? '▶' : '▷' }}{{ entry.logText }} +
+ } +
+
diff --git a/apps/overlay/src/app/panel/expanded-panel.component.ts b/apps/overlay/src/app/panel/expanded-panel.component.ts index 591431c..8f081e3 100644 --- a/apps/overlay/src/app/panel/expanded-panel.component.ts +++ b/apps/overlay/src/app/panel/expanded-panel.component.ts @@ -4,126 +4,65 @@ import { computed, input, output, + signal, } from '@angular/core'; import { MissionStateResponse } from '@fog-explorer/api-interfaces'; +import { avatarUrl, survivorInitials } from './survivor-initials'; + +/** Most-recent entry is index 0; older entries fade toward 0 opacity. */ +const LOG_OPACITY_STEP = 0.18; +const LOG_MAX_VISIBLE = 8; @Component({ selector: 'app-expanded-panel', standalone: true, changeDetection: ChangeDetectionStrategy.OnPush, - styles: [` - :host { display: block; } - .panel { - width: 320px; - height: 440px; - background: rgba(15, 18, 22, 0.95); - box-sizing: border-box; - display: flex; - flex-direction: column; - padding: 16px; - gap: 12px; - } - .close-btn { - position: absolute; - top: 12px; - right: 12px; - background: none; - border: none; - color: #6a7080; - font-size: 18px; - cursor: pointer; - line-height: 1; - } - .survivor-name { - font-family: 'Cormorant', serif; - font-size: 20px; - font-weight: 700; - color: #e0e4e8; - letter-spacing: 0.02em; - } - .survivor-state { - font-size: 11px; - text-transform: uppercase; - letter-spacing: 0.08em; - color: #E8A547; - } - .survivor-state.injured, .survivor-state.downed { color: #B8842E; } - .survivor-state.sacrificed { color: #C03A3A; } - .mission-strip { - display: flex; - align-items: center; - gap: 8px; - padding: 8px 0; - border-top: 1px solid rgba(255,255,255,0.06); - border-bottom: 1px solid rgba(255,255,255,0.06); - } - .difficulty { color: #E8A547; letter-spacing: 0.1em; font-size: 12px; } - .tick { font-family: monospace; font-size: 11px; color: #6a7080; margin-left: auto; } - .log { - flex: 1; - overflow: hidden; - display: flex; - flex-direction: column; - gap: 4px; - } - .log-entry { - font-family: 'JetBrains Mono', 'IBM Plex Mono', monospace; - font-size: 11px; - color: #c8ccd0; - line-height: 1.5; - } - .perk-slots { - display: flex; - gap: 6px; - flex-wrap: wrap; - } - .perk { - font-size: 10px; - background: rgba(232, 165, 71, 0.12); - color: #E8A547; - padding: 2px 6px; - border-radius: 2px; - } - .perk-empty { font-size: 11px; color: #6a7080; font-style: italic; } - `], - template: ` -
- - @if (survivor(); as s) { -
-
{{ s.name }}
-
{{ s.state }}
-
- @for (perk of s.perkSlots; track perk.id) { - {{ perk.name }} - } @empty { - No perks equipped - } -
-
- } - @if (mission(); as m) { -
- {{ difficultyGlyphs() }} - T+{{ m.tickIndex }} -
- } -
- @for (entry of recentLog(); track entry.tickIndex) { -
{{ entry.logText }}
- } -
-
- `, + templateUrl: './expanded-panel.component.html', + styleUrl: './expanded-panel.component.css', }) export class ExpandedPanelComponent { missionState = input.required>(); - close = output(); + panelClose = output(); + abandonRequest = output(); + + protected readonly confirmingAbandon = signal(false); protected mission = computed(() => this.missionState().mission); protected survivor = computed(() => this.missionState().survivors[0] ?? null); - protected recentLog = computed(() => [...this.missionState().recentLog].reverse()); - protected difficultyGlyphs = computed(() => - '◆'.repeat(this.missionState().mission.difficulty) + protected initials = computed(() => { + const name = this.missionState().survivors[0]?.name ?? ''; + return name ? survivorInitials(name) : '?'; + }); + + protected avatarSrc = computed(() => { + const name = this.missionState().survivors[0]?.name ?? ''; + return avatarUrl(name); + }); + + protected readonly imgError = signal(false); + + protected survivorStateColor = computed(() => { + const state = this.missionState().survivors[0]?.state ?? 'idle'; + const map: Record = { + active: 'var(--fog-amber)', + injured: 'var(--fog-amber-dim)', + downed: 'var(--fog-red)', + sacrificed:'var(--fog-red)', + idle: 'var(--fog-text-dim)', + }; + return map[state] ?? 'var(--fog-text-dim)'; + }); + + protected logEntries = computed(() => + this.missionState().recentLog.slice(0, LOG_MAX_VISIBLE) ); + + protected difficultyPips = computed(() => { + const d = this.missionState().mission.difficulty; + return [1, 2, 3].map((n) => n <= d); + }); + + protected entryOpacity(index: number): number { + return Math.max(0.15, 1 - index * LOG_OPACITY_STEP); + } } diff --git a/apps/overlay/src/app/panel/minimised-panel.component.css b/apps/overlay/src/app/panel/minimised-panel.component.css new file mode 100644 index 0000000..7fef949 --- /dev/null +++ b/apps/overlay/src/app/panel/minimised-panel.component.css @@ -0,0 +1,93 @@ +:host { display: block; } + +.panel { + display: flex; + align-items: center; + gap: 10px; + width: 290px; + height: 56px; + background: var(--fog-bg); + padding: 0 12px 0 8px; +} + +.lantern-wrap { + flex-shrink: 0; + position: relative; + width: 44px; + height: 44px; +} + +.lantern { + width: 100%; + height: 100%; + border-radius: 50%; + background: #12151a; + border: 2.5px solid var(--fog-amber); + cursor: pointer; + transition: border-color 0.3s ease; + display: flex; + align-items: center; + justify-content: center; +} + +.lantern:focus-visible { + outline: 2px solid var(--fog-amber); + outline-offset: 3px; +} + +.avatar-img { + width: 100%; + height: 100%; + border-radius: 50%; + object-fit: cover; + object-position: top center; + pointer-events: none; +} + +.avatar-initials { + font-family: var(--font-mono); + font-size: 13px; + font-weight: 600; + letter-spacing: 0.06em; + color: #8a9ab8; + pointer-events: none; + line-height: 1; +} + +.state-badge { + position: absolute; + bottom: -3px; + right: -3px; + width: 15px; + height: 15px; + border-radius: 50%; + background: rgba(15, 18, 22, 0.95); + display: flex; + align-items: center; + justify-content: center; + font-family: var(--font-mono); + font-size: 8px; + pointer-events: none; +} + +.ticker { + flex: 1; + overflow: hidden; +} + +.log-line { + display: block; + font-family: var(--font-mono); + font-size: 10.5px; + font-weight: 400; + color: var(--fog-text); + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + line-height: 1.4; +} + +.log-line.idle { + color: var(--fog-text-dim); + font-style: italic; +} diff --git a/apps/overlay/src/app/panel/minimised-panel.component.html b/apps/overlay/src/app/panel/minimised-panel.component.html new file mode 100644 index 0000000..2353db4 --- /dev/null +++ b/apps/overlay/src/app/panel/minimised-panel.component.html @@ -0,0 +1,33 @@ +
+
+ + +
+
+ @if (latestLogLine(); as line) { + {{ line }} + } @else { + The fog stirs… + } +
+
diff --git a/apps/overlay/src/app/panel/minimised-panel.component.ts b/apps/overlay/src/app/panel/minimised-panel.component.ts index 5f01720..149a10e 100644 --- a/apps/overlay/src/app/panel/minimised-panel.component.ts +++ b/apps/overlay/src/app/panel/minimised-panel.component.ts @@ -4,75 +4,53 @@ import { computed, input, output, + signal, } from '@angular/core'; import { MissionStateResponse } from '@fog-explorer/api-interfaces'; +import { avatarUrl, survivorInitials } from './survivor-initials'; + +/** + * State → ring colour + shape indicator. + * Colour alone is never the sole differentiator (accessibility requirement). + */ +const STATE_META: Record = { + active: { color: 'var(--fog-amber)', label: '◆' }, + injured: { color: 'var(--fog-amber-dim)', label: '◈' }, + downed: { color: 'var(--fog-red)', label: '◇' }, + sacrificed:{ color: 'var(--fog-red)', label: '✕' }, + idle: { color: 'var(--fog-text-dim)', label: '○' }, +}; @Component({ selector: 'app-minimised-panel', standalone: true, changeDetection: ChangeDetectionStrategy.OnPush, - styles: [` - :host { display: block; } - .panel { - display: flex; - align-items: center; - gap: 8px; - width: 290px; - height: 56px; - background: rgba(15, 18, 22, 0.88); - padding: 0 12px; - box-sizing: border-box; - } - .lantern { - flex-shrink: 0; - width: 48px; - height: 48px; - border-radius: 50%; - background: #1a1e24; - border: 3px solid #E8A547; - cursor: pointer; - box-sizing: border-box; - } - .lantern.injured { border-color: #B8842E; } - .lantern.downed, .lantern.sacrificed { border-color: #C03A3A; } - .ticker { - flex: 1; - overflow: hidden; - } - .log-line { - display: block; - font-family: 'JetBrains Mono', 'IBM Plex Mono', monospace; - font-size: 11px; - color: #c8ccd0; - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; - } - .idle { color: #6a7080; font-style: italic; } - `], - template: ` -
-
-
- @if (latestLogLine(); as line) { - {{ line }} - } @else { - The fog stirs… - } -
-
- `, + templateUrl: './minimised-panel.component.html', + styleUrl: './minimised-panel.component.css', }) export class MinimisedPanelComponent { missionState = input.required>(); lanternClick = output(); - protected survivorState = computed( - () => this.missionState().survivors[0]?.state ?? 'idle' - ); + protected stateMeta = computed(() => { + const state = this.missionState().survivors[0]?.state ?? 'idle'; + return STATE_META[state] ?? STATE_META['idle']; + }); + + protected initials = computed(() => { + const name = this.missionState().survivors[0]?.name ?? ''; + return name ? survivorInitials(name) : '?'; + }); + + protected avatarSrc = computed(() => { + const name = this.missionState().survivors[0]?.name ?? ''; + return avatarUrl(name); + }); + + protected readonly imgError = signal(false); protected latestLogLine = computed(() => { const log = this.missionState().recentLog; - return log.length ? log[log.length - 1].logText : null; + return log.length ? log[0].logText : null; }); } diff --git a/apps/overlay/src/app/panel/panel-shell.component.css b/apps/overlay/src/app/panel/panel-shell.component.css new file mode 100644 index 0000000..50c3d60 --- /dev/null +++ b/apps/overlay/src/app/panel/panel-shell.component.css @@ -0,0 +1,244 @@ +:host { + display: block; + position: fixed; + bottom: 16px; + left: 16px; + z-index: 9999; +} + +/* ── Anonymous viewer: desaturated lantern, no ticker ── */ +.anon-panel { + width: 290px; + height: 56px; + background: var(--fog-bg); + display: flex; + align-items: center; + padding: 0 12px 0 8px; +} +.lantern-desaturated { + width: 44px; + height: 44px; + border-radius: 50%; + background: #1a1c20; + border: 2.5px solid #3a3e48; + flex-shrink: 0; +} +.anon-label { + font-family: var(--font-mono); + font-size: 10px; + color: var(--fog-text-faint); + margin-left: 10px; + font-style: italic; +} + +/* ── First-time / onboarding panel ── */ +.onboarding-panel { + width: 290px; + min-height: 56px; + background: var(--fog-bg); + display: flex; + flex-direction: column; + align-items: flex-start; + padding: 12px 16px; + gap: 8px; + border-left: 2px solid var(--fog-amber); +} +.onboarding-glyph { + font-family: var(--font-mono); + font-size: 18px; + color: var(--fog-amber); + opacity: 0.6; + flex-shrink: 0; +} +.onboarding-text { + font-family: var(--font-mono); + font-size: 11px; + color: var(--fog-text-dim); + font-style: italic; + line-height: 1.4; + margin: 0; +} +.onboarding-outcome { + font-family: var(--font-mono); + font-size: 10px; + color: var(--fog-text-faint); + font-style: italic; + margin: 0 0 4px; +} +.onboarding-error { + font-family: var(--font-mono); + font-size: 10px; + color: var(--fog-red); + margin: 4px 0 0; +} +.onboarding-glyph--pulse { + animation: pulse 1.4s ease-in-out infinite; +} +@keyframes pulse { + 0%, 100% { opacity: 0.3; } + 50% { opacity: 1; } +} +.difficulty-row { + display: flex; + gap: 6px; + margin-top: 10px; +} +.diff-btn { + flex: 1; + background: transparent; + border: 1px solid var(--fog-amber-dim); + color: var(--fog-text-dim); + font-family: var(--font-mono); + font-size: 10px; + padding: 6px 4px; + cursor: pointer; + display: flex; + flex-direction: column; + align-items: center; + gap: 3px; + transition: border-color 0.15s, color 0.15s; +} +.diff-btn:hover, .diff-btn:focus-visible { + border-color: var(--fog-amber); + color: var(--fog-amber); + outline: none; +} +.diff-pips { + font-size: 9px; + letter-spacing: 1px; + color: var(--fog-amber); + opacity: 0.7; +} +.diff-btn:hover .diff-pips, .diff-btn:focus-visible .diff-pips { + opacity: 1; +} +.diff-label { + font-size: 9px; + text-transform: uppercase; + letter-spacing: 0.05em; +} + +.back-btn { + background: none; + border: none; + color: var(--fog-text-faint); + font-family: var(--font-mono); + font-size: 9px; + text-transform: uppercase; + letter-spacing: 0.05em; + cursor: pointer; + padding: 4px 0 0; + align-self: flex-start; + transition: color 0.15s; +} +.back-btn:hover, .back-btn:focus-visible { + color: var(--fog-text-dim); + outline: none; +} +.onboarding-panel--loading { + flex-direction: row; + align-items: center; +} + +/* ── Character picker ── */ +.character-search-row { + width: 100%; +} +.character-filter { + width: 100%; + box-sizing: border-box; + background: transparent; + border: 1px solid var(--fog-amber-dim); + color: var(--fog-text-dim); + font-family: var(--font-mono); + font-size: 10px; + padding: 5px 8px; + outline: none; + transition: border-color 0.15s; +} +.character-filter::placeholder { + color: var(--fog-text-faint); + font-style: italic; +} +.character-filter:focus { + border-color: var(--fog-amber); +} +.character-list { + width: 100%; + max-height: 180px; + overflow-y: auto; + display: flex; + flex-direction: column; + gap: 2px; + scrollbar-width: thin; + scrollbar-color: var(--fog-amber-dim) transparent; +} +.character-btn { + width: 100%; + background: transparent; + border: none; + border-left: 2px solid transparent; + color: var(--fog-text-faint); + font-family: var(--font-mono); + font-size: 10px; + padding: 3px 8px; + text-align: left; + cursor: pointer; + transition: color 0.12s, border-color 0.12s; + display: flex; + align-items: center; + gap: 7px; +} + +.character-thumb { + flex-shrink: 0; + width: 22px; + height: 22px; + border-radius: 50%; + object-fit: cover; + object-position: top center; + opacity: 0.7; + transition: opacity 0.12s; +} + +.character-btn:hover .character-thumb, +.character-btn:focus-visible .character-thumb { + opacity: 1; +} +.character-btn:hover, +.character-btn:focus-visible { + color: var(--fog-text-dim); + border-left-color: var(--fog-amber-dim); + outline: none; +} +.character-btn--random { + color: var(--fog-amber); + opacity: 0.75; + font-style: italic; + border-bottom: 1px solid var(--fog-amber-dim); + margin-bottom: 4px; + padding-bottom: 6px; +} +.character-btn--random:hover, +.character-btn--random:focus-visible { + opacity: 1; + border-left-color: transparent; +} +.character-chosen { + color: var(--fog-amber); +} + +/* ── Backdrop dim when expanded ── */ +.backdrop { + position: fixed; + inset: 0; + background: rgba(0, 0, 0, 0.45); + z-index: -1; + animation: fade-in 0.15s ease; + border: none; + cursor: default; +} +@keyframes fade-in { + from { opacity: 0; } + to { opacity: 1; } +} diff --git a/apps/overlay/src/app/panel/panel-shell.component.html b/apps/overlay/src/app/panel/panel-shell.component.html new file mode 100644 index 0000000..d71e30f --- /dev/null +++ b/apps/overlay/src/app/panel/panel-shell.component.html @@ -0,0 +1,132 @@ +@if (authService.auth()) { + @if (!authService.isLoggedIn()) { +
+
+ Sign in to join the expedition +
+ } @else if (store.initializing()) { +
+ +

Reading the fog…

+
+ } @else if (showSummary()) { + + } @else if (!store.state() || !missionIsActive()) { + @if (store.starting()) { +
+ +

Entering the fog…

+
+ } @else { +
+ @if (terminalOutcome()) { +

{{ terminalOutcome() }}

+ } + @if (store.error()) { +

Something went wrong. Try again.

+ } + @if (!selectedCharacter()) { +

Who enters the fog?

+
+ +
+
+ @if (!characterFilter()) { + + } + @for (name of filteredSurvivors(); track name) { + + } +
+ } @else if (!selectedDifficulty()) { +

+ {{ selectedCharacter() }} + enters the fog. +

+

Choose your path.

+
+ + + +
+ + } @else { +

How long will you endure?

+
+ + + +
+ + } +
+ } + } @else { + @if (view() === 'expanded') { + + } + @switch (view()) { + @case ('minimised') { + + } + @case ('ambient') { + + } + @case ('expanded') { + + } + } + } +} diff --git a/apps/overlay/src/app/panel/panel-shell.component.spec.ts b/apps/overlay/src/app/panel/panel-shell.component.spec.ts index cec833e..dc1de6a 100644 --- a/apps/overlay/src/app/panel/panel-shell.component.spec.ts +++ b/apps/overlay/src/app/panel/panel-shell.component.spec.ts @@ -28,7 +28,7 @@ class AmbientEventStub { @Component({ selector: 'app-expanded-panel', standalone: true, template: '' }) class ExpandedPanelStub { @Input() missionState!: NonNullable; - @Output() close = new EventEmitter(); + @Output() panelClose = new EventEmitter(); } const BASE = 'https://test.local'; @@ -193,7 +193,7 @@ describe('PanelShellComponent', () => { // Emit close from the expanded stub fixture.debugElement.query(By.directive(ExpandedPanelStub)) - .componentInstance.close.emit(); + .componentInstance.panelClose.emit(); fixture.detectChanges(); expect(fixture.nativeElement.querySelector('app-minimised-panel')).not.toBeNull(); }); diff --git a/apps/overlay/src/app/panel/panel-shell.component.ts b/apps/overlay/src/app/panel/panel-shell.component.ts index 7181d70..4b18d5a 100644 --- a/apps/overlay/src/app/panel/panel-shell.component.ts +++ b/apps/overlay/src/app/panel/panel-shell.component.ts @@ -1,6 +1,7 @@ import { ChangeDetectionStrategy, Component, + computed, DestroyRef, effect, inject, @@ -8,15 +9,18 @@ import { signal, untracked, } from '@angular/core'; +import { FormsModule } from '@angular/forms'; import { takeUntilDestroyed, toObservable } from '@angular/core/rxjs-interop'; import { MissionStateResponse } from '@fog-explorer/api-interfaces'; +import { SURVIVOR_NAMES } from '@fog-explorer/encounter-library'; +import { avatarUrl } from './survivor-initials'; import { distinctUntilChanged, filter, skip } from 'rxjs'; -import { EbsApiService } from '../ebs/ebs-api.service'; import { MissionStateStore } from '../mission/mission-state.store'; import { TwitchAuthService } from '../twitch/twitch-auth.service'; import { AmbientEventComponent, AmbientEventData } from './ambient-event.component'; import { ExpandedPanelComponent } from './expanded-panel.component'; import { MinimisedPanelComponent } from './minimised-panel.component'; +import { SummaryPanelComponent } from './summary-panel.component'; type OverlayView = 'minimised' | 'ambient' | 'expanded'; @@ -45,76 +49,44 @@ function detectAmbientEvent( selector: 'app-panel-shell', standalone: true, changeDetection: ChangeDetectionStrategy.OnPush, - imports: [MinimisedPanelComponent, AmbientEventComponent, ExpandedPanelComponent], - template: ` - @if (authService.auth()) { - @if (!authService.isLoggedIn()) { -
-
-
- } @else if (!store.state()) { -
-

The fog stirs. Awaiting a survivor.

-
- } @else { - @switch (view()) { - @case ('minimised') { - - } - @case ('ambient') { - - } - @case ('expanded') { - - } - } - } - } - `, - styles: [` - :host { display: block; position: fixed; bottom: 16px; left: 16px; z-index: 9999; } - .anon-panel, .onboarding-panel { - width: 290px; - min-height: 56px; - background: rgba(15, 18, 22, 0.88); - padding: 12px 16px; - box-sizing: border-box; - } - .lantern-desaturated { - width: 48px; - height: 48px; - border-radius: 50%; - background: #2a2e34; - border: 3px solid #4a4e54; - } - .onboarding-text { - font-family: 'JetBrains Mono', 'IBM Plex Mono', monospace; - font-size: 12px; - color: #6a7080; - margin: 0; - font-style: italic; - } - `], + imports: [FormsModule, MinimisedPanelComponent, AmbientEventComponent, ExpandedPanelComponent, SummaryPanelComponent], + templateUrl: './panel-shell.component.html', + styleUrl: './panel-shell.component.css', }) export class PanelShellComponent implements OnInit { protected readonly authService = inject(TwitchAuthService); protected readonly store = inject(MissionStateStore); - // EbsApiService eagerly resolved to ensure EBS_BASE_URL token is - // available before any child component triggers a request. - private readonly _ebs = inject(EbsApiService); private readonly destroyRef = inject(DestroyRef); protected readonly view = signal('minimised'); protected readonly pendingEvent = signal(null); + protected readonly selectedCharacter = signal(null); + protected readonly selectedDifficulty = signal<1 | 2 | 3 | null>(null); + protected readonly characterFilter = signal(''); + + protected readonly survivorNames = SURVIVOR_NAMES; + protected readonly avatarUrl = avatarUrl; + protected readonly filteredSurvivors = computed(() => { + const q = this.characterFilter().toLowerCase().trim(); + return q ? SURVIVOR_NAMES.filter((n) => n.toLowerCase().includes(q)) : SURVIVOR_NAMES; + }); + + protected readonly missionIsActive = computed( + () => this.store.state()?.mission.status === 'active', + ); + protected readonly terminalOutcome = computed(() => { + const status = this.store.state()?.mission.status; + if (status === 'success') return 'The expedition is complete.'; + if (status === 'sacrifice') return 'Taken by the fog.'; + if (status === 'abandoned') return 'The expedition was abandoned.'; + return null; + }); + + protected readonly summaryDismissed = signal(false); + protected readonly showSummary = computed(() => { + const status = this.store.state()?.mission.status; + return (status === 'success' || status === 'sacrifice') && !this.summaryDismissed(); + }); private prevState: MissionStateResponse = null; private ambientTimer: ReturnType | null = null; @@ -134,6 +106,13 @@ export class PanelShellComponent implements OnInit { const current = this.store.state(); untracked(() => this.handleStateChange(current)); }); + + // Reset summary gate when a fresh active mission arrives. + effect(() => { + if (this.store.state()?.mission.status === 'active') { + untracked(() => this.summaryDismissed.set(false)); + } + }); } ngOnInit(): void { @@ -145,6 +124,49 @@ export class PanelShellComponent implements OnInit { this.view.update((v) => (v === 'expanded' ? 'minimised' : 'expanded')); } + protected onSelectCharacter(name: string): void { + this.selectedCharacter.set(name); + this.characterFilter.set(''); + } + + protected onSelectRandomCharacter(): void { + const names = SURVIVOR_NAMES; + const picked = names[Math.floor(Math.random() * names.length)]; + this.selectedCharacter.set(picked); + this.characterFilter.set(''); + } + + protected onBackToCharacter(): void { + this.selectedCharacter.set(null); + this.selectedDifficulty.set(null); + this.characterFilter.set(''); + } + + protected onSelectDifficulty(difficulty: 1 | 2 | 3): void { + this.selectedDifficulty.set(difficulty); + } + + protected onSelectDuration(durationMinutes: 10 | 20 | 30): void { + const difficulty = this.selectedDifficulty(); + if (!difficulty) return; + this.store.startMission(difficulty, durationMinutes, this.selectedCharacter() ?? undefined); + } + + protected onBackToDifficulty(): void { + this.selectedDifficulty.set(null); + } + + protected onAbandon(): void { + this.store.abandonMission(); + this.view.set('minimised'); + } + + protected onContinueAfterSummary(): void { + this.summaryDismissed.set(true); + this.selectedCharacter.set(null); + this.selectedDifficulty.set(null); + } + protected onAmbientDismiss(): void { this.clearAmbientTimer(); this.view.set('minimised'); diff --git a/apps/overlay/src/app/panel/summary-panel.component.css b/apps/overlay/src/app/panel/summary-panel.component.css new file mode 100644 index 0000000..57f1410 --- /dev/null +++ b/apps/overlay/src/app/panel/summary-panel.component.css @@ -0,0 +1,147 @@ +:host { display: block; } + +.panel { + width: 290px; + max-height: 480px; + background: var(--fog-bg); + border-left: 2px solid var(--fog-amber); + display: flex; + flex-direction: column; + overflow: hidden; +} + +/* ── Outcome header ── */ +.outcome-header { + display: flex; + align-items: center; + gap: 10px; + padding: 14px 16px 12px; + border-bottom: 1px solid var(--fog-border); + flex-shrink: 0; +} + +.outcome-glyph { + font-family: var(--font-mono); + font-size: 18px; + line-height: 1; + color: var(--fog-amber); + flex-shrink: 0; +} +.outcome-glyph.sacrifice { color: var(--fog-red); } + +.outcome-heading { + font-family: var(--font-mono); + font-size: 11px; + text-transform: uppercase; + letter-spacing: 0.1em; + color: var(--fog-text); +} + +/* ── Stats ── */ +.stats-row { + display: flex; + justify-content: space-between; + align-items: baseline; + padding: 8px 16px; + border-bottom: 1px solid var(--fog-border); + flex-shrink: 0; +} + +.stat-label { + font-family: var(--font-mono); + font-size: 9.5px; + text-transform: uppercase; + letter-spacing: 0.08em; + color: var(--fog-text-dim); +} + +.stat-value { + font-family: var(--font-mono); + font-size: 11px; + color: var(--fog-text); +} + +/* ── Survivors ── */ +.survivors { + display: flex; + flex-direction: column; + padding: 6px 0; + border-bottom: 1px solid var(--fog-border); + flex-shrink: 0; +} + +.survivor-row { + display: flex; + justify-content: space-between; + align-items: center; + padding: 4px 16px; +} + +.survivor-name { + font-family: var(--font-serif); + font-size: 13px; + color: var(--fog-text); +} + +.survivor-state { + font-family: var(--font-mono); + font-size: 9px; + text-transform: uppercase; + letter-spacing: 0.1em; + color: var(--fog-amber); +} +.survivor-state.injured { color: var(--fog-amber-dim); } +.survivor-state.downed { color: var(--fog-red); } +.survivor-state.sacrificed { color: var(--fog-red); } + +/* ── Log ── */ +.log { + flex: 1; + overflow-y: auto; + padding: 8px 16px; + display: flex; + flex-direction: column; + gap: 4px; + border-bottom: 1px solid var(--fog-border); + min-height: 0; +} + +.log::-webkit-scrollbar { width: 3px; } +.log::-webkit-scrollbar-track { background: transparent; } +.log::-webkit-scrollbar-thumb { background: var(--fog-border); } + +.log-entry { + font-family: var(--font-mono); + font-size: 10px; + line-height: 1.5; + color: var(--fog-text-dim); +} + +.log-glyph { + margin-right: 5px; + font-size: 8px; +} + +.log-entry.success .log-glyph { color: var(--fog-green); } +.log-entry.failure .log-glyph { color: var(--fog-red); opacity: 0.5; } + +/* ── Continue button ── */ +.continue-btn { + margin: 12px 16px; + padding: 8px 0; + background: transparent; + border: 1px solid var(--fog-amber-dim); + color: var(--fog-text-dim); + font-family: var(--font-mono); + font-size: 10px; + text-transform: uppercase; + letter-spacing: 0.08em; + cursor: pointer; + transition: border-color 0.15s, color 0.15s; + flex-shrink: 0; +} +.continue-btn:hover, .continue-btn:focus-visible { + border-color: var(--fog-amber); + color: var(--fog-amber); + outline: none; +} diff --git a/apps/overlay/src/app/panel/summary-panel.component.html b/apps/overlay/src/app/panel/summary-panel.component.html new file mode 100644 index 0000000..c4e1b5e --- /dev/null +++ b/apps/overlay/src/app/panel/summary-panel.component.html @@ -0,0 +1,34 @@ +
+
+ {{ outcome().glyph }} + {{ outcome().heading }} +
+ +
+ Survived + {{ ticks() }} tick{{ ticks() === 1 ? '' : 's' }} +
+ +
+ @for (survivor of missionState().survivors; track survivor.id) { +
+ {{ survivor.name }} + {{ survivor.state }} +
+ } +
+ + @if (missionState().recentLog.length) { +
+ @for (entry of missionState().recentLog; track entry.tickIndex) { +
+ {{ entry.success ? '▶' : '▷' }}{{ entry.logText }} +
+ } +
+ } + + +
diff --git a/apps/overlay/src/app/panel/summary-panel.component.ts b/apps/overlay/src/app/panel/summary-panel.component.ts new file mode 100644 index 0000000..f01bad3 --- /dev/null +++ b/apps/overlay/src/app/panel/summary-panel.component.ts @@ -0,0 +1,32 @@ +import { + ChangeDetectionStrategy, + Component, + computed, + input, + output, +} from '@angular/core'; +import { MissionStateResponse } from '@fog-explorer/api-interfaces'; + +const OUTCOME: Record = { + success: { glyph: '◆', heading: 'Expedition complete', glyphClass: 'success' }, + sacrifice: { glyph: '✕', heading: 'Taken by the fog', glyphClass: 'sacrifice' }, +}; + +@Component({ + selector: 'app-summary-panel', + standalone: true, + changeDetection: ChangeDetectionStrategy.OnPush, + templateUrl: './summary-panel.component.html', + styleUrl: './summary-panel.component.css', +}) +export class SummaryPanelComponent { + missionState = input.required>(); + continueExpedition = output(); + + protected outcome = computed(() => { + const status = this.missionState().mission.status; + return OUTCOME[status] ?? { glyph: '○', heading: 'Expedition ended', glyphClass: '' }; + }); + + protected ticks = computed(() => this.missionState().mission.tickIndex); +} diff --git a/apps/overlay/src/app/panel/survivor-initials.ts b/apps/overlay/src/app/panel/survivor-initials.ts new file mode 100644 index 0000000..c6531fa --- /dev/null +++ b/apps/overlay/src/app/panel/survivor-initials.ts @@ -0,0 +1,62 @@ +export function survivorInitials(name: string): string { + const parts = name.trim().split(/\s+/).filter((p) => /[A-Za-zÀ-ÿ]/.test(p[0])); + if (parts.length === 0) return '?'; + if (parts.length === 1) return parts[0][0].toUpperCase(); + return (parts[0][0] + parts[parts.length - 1][0]).toUpperCase(); +} + +const SURVIVOR_AVATAR: Record = { + 'Dwight Fairfield': 'S01_DwightFairfield_Portrait.webp', + 'Meg Thomas': 'S02_MegThomas_Portrait.webp', + 'Claudette Morel': 'S03_ClaudetteMorel_Portrait.webp', + 'Jake Park': 'S04_JakePark_Portrait.webp', + 'Nea Karlsson': 'S05_NeaKarlsson_Portrait.webp', + 'Laurie Strode': 'S06_LaurieStrode_Portrait.webp', + 'Ace Visconti': 'S07_AceVisconti_Portrait.webp', + 'Bill Overbeck': 'S08_BillOverbeck_Portrait.webp', + 'Feng Min': 'S09_FengMin_Portrait.webp', + 'David King': 'S10_DavidKing_Portrait.webp', + 'Quentin Smith': 'S11_QuentinSmith_Portrait.webp', + 'David Tapp': 'S12_DavidTapp_Portrait.webp', + 'Kate Denson': 'S13_KateDenson_Portrait.webp', + 'Adam Francis': 'S14_AdamFrancis_Portrait.webp', + 'Jeff Johansen': 'S15_JeffJohansen_Portrait.webp', + 'Jane Romero': 'S16_JaneRomero_Portrait.webp', + 'Ash Williams': 'S17_AshWilliams_Portrait.webp', + 'Nancy Wheeler': 'S18_NancyWheeler_Portrait.webp', + 'Steve Harrington': 'S19_SteveHarrington_Portrait.webp', + 'Yui Kimura': 'S20_YuiKimura_Portrait.webp', + 'Zarina Kassir': 'S21_ZarinaKassir_Portrait.webp', + 'Cheryl Mason': 'S22_CherylMason_Portrait.webp', + 'Felix Richter': 'S23_FelixRichter_Portrait.webp', + 'Élodie Rakoto': 'S24_ElodieRakoto_Portrait.webp', + 'Yun-Jin Lee': 'S25_Yun-JinLee_Portrait.webp', + 'Jill Valentine': 'S26_JillValentine_Portrait.webp', + 'Leon S. Kennedy': 'S27_LeonScottKennedy_Portrait.webp', + 'Mikaela Reid': 'S28_MikaelaReid_Portrait.webp', + 'Jonah Vasquez': 'S29_JonahVasquez_Portrait.webp', + 'Yoichi Asakawa': 'S30_YoichiAsakawa_Portrait.webp', + 'Haddie Kaur': 'S31_HaddieKaur_Portrait.webp', + 'Ada Wong': 'S32_AdaWong_Portrait.webp', + 'Rebecca Chambers': 'S33_RebeccaChambers_Portrait.webp', + 'Vittorio Toscano': 'S34_VittorioToscano_Portrait.webp', + 'Thalita Lyra': 'S35_ThalitaLyra_Portrait.webp', + 'Renato Lyra': 'S36_RenatoLyra_Portrait.webp', + 'Gabriel Soma': 'S37_GabrielSoma_Portrait.webp', + 'Nicolas Cage': 'S38_NicolasCage_Portrait.webp', + 'Ellen Ripley': 'S39_EllenRipley_Portrait.webp', + 'Alan Wake': 'S40_AlanWake_Portrait.webp', + 'Sable Ward': 'S41_SableWard_Portrait.webp', + 'Aestri Yazar': 'S42_TheTroupe_Portrait.webp', + 'Lara Croft': 'S43_LaraCroft_Portrait.webp', + 'Trevor Belmont': 'S44_TrevorBelmont_Portrait.webp', + 'Taurie Cain': 'S45_TaurieCain_Portrait.webp', + 'Rick Grimes': 'S46_RickGrimes_Portrait.webp', + 'Michonne Grimes': 'S47_MichonneGrimes_Portrait.webp', + 'Vee Boonyasak': 'S48_VeeBoonyasak_Portrait.webp', +}; + +export function avatarUrl(name: string): string | null { + const file = SURVIVOR_AVATAR[name]; + return file ? `avatars/${file}` : null; +} diff --git a/apps/overlay/src/environments/environment.prod.ts b/apps/overlay/src/environments/environment.prod.ts new file mode 100644 index 0000000..db5fa32 --- /dev/null +++ b/apps/overlay/src/environments/environment.prod.ts @@ -0,0 +1,5 @@ +export const environment = { + production: true, + // Production EBS API base URL for deployed overlay builds. + ebsBaseUrl: 'https://api.fog-expedition.example.com/api', +}; diff --git a/apps/overlay/src/environments/environment.ts b/apps/overlay/src/environments/environment.ts new file mode 100644 index 0000000..d9a903f --- /dev/null +++ b/apps/overlay/src/environments/environment.ts @@ -0,0 +1,6 @@ +export const environment = { + production: false, + // HTTP (not HTTPS) for local dev — no Caddy/TLS needed when the Twitch rig + // is not in the loop and the browser is not enforcing mixed-content rules. + ebsBaseUrl: 'http://localhost:3000/api', +}; diff --git a/apps/overlay/src/index.html b/apps/overlay/src/index.html index 6363753..66ddf55 100644 --- a/apps/overlay/src/index.html +++ b/apps/overlay/src/index.html @@ -2,10 +2,17 @@ - overlay + Fog Expedition + + + + diff --git a/apps/overlay/src/styles.css b/apps/overlay/src/styles.css index 90d4ee0..fa79156 100644 --- a/apps/overlay/src/styles.css +++ b/apps/overlay/src/styles.css @@ -1 +1,43 @@ -/* You can add global styles to this file, and also import other style files */ +/* + * Global reset and design tokens. + * Component styles live in their own files — only truly global rules here. + */ + +*, *::before, *::after { + box-sizing: border-box; + margin: 0; + padding: 0; +} + +:root { + /* Palette */ + --fog-bg: rgba(15, 18, 22, 0.88); + --fog-bg-deep: rgba(15, 18, 22, 0.95); + --fog-surface: rgba(255, 255, 255, 0.04); + --fog-border: rgba(255, 255, 255, 0.06); + + --fog-text: #c8ccd0; + --fog-text-dim: #6a7080; + --fog-text-faint:#3a3e48; + + --fog-amber: #E8A547; /* active state, primary accent */ + --fog-amber-dim: #B8842E; /* injured state */ + --fog-red: #C03A3A; /* downed / sacrifice */ + --fog-green: #4a8c6f; /* success outcome */ + + /* Typography */ + --font-serif: 'Cormorant', Georgia, serif; + --font-mono: 'JetBrains Mono', 'IBM Plex Mono', 'Courier New', monospace; + --font-sans: system-ui, -apple-system, sans-serif; +} + +body { + font-family: var(--font-sans); + background: transparent; + color: var(--fog-text); + -webkit-font-smoothing: antialiased; +} + +button { + font-family: inherit; +} diff --git a/libs/api-interfaces/src/lib/mission-state.ts b/libs/api-interfaces/src/lib/mission-state.ts index 6b5815d..8c4b9b0 100644 --- a/libs/api-interfaces/src/lib/mission-state.ts +++ b/libs/api-interfaces/src/lib/mission-state.ts @@ -14,6 +14,8 @@ export type MissionStateResponse = z.infer; export const StartMissionRequestSchema = z.object({ difficulty: z.number().int().min(1).max(3), + durationMinutes: z.union([z.literal(10), z.literal(20), z.literal(30)]), + characterName: z.string().min(1).max(32).optional(), }); export type StartMissionRequest = z.infer; diff --git a/libs/api-interfaces/src/lib/mission.ts b/libs/api-interfaces/src/lib/mission.ts index df6cf4f..56b4d94 100644 --- a/libs/api-interfaces/src/lib/mission.ts +++ b/libs/api-interfaces/src/lib/mission.ts @@ -17,11 +17,19 @@ export const MissionParticipantSchema = z.object({ }); export type MissionParticipant = z.infer; +export const MissionDurationMinutesSchema = z.union([ + z.literal(10), + z.literal(20), + z.literal(30), +]); +export type MissionDurationMinutes = z.infer; + export const MissionSchema = z.object({ id: z.uuid(), groupId: z.uuid().nullable(), participants: z.array(MissionParticipantSchema).min(1).max(4), difficulty: z.number().int().min(1).max(3), + durationMinutes: MissionDurationMinutesSchema.default(20), status: MissionStateSchema, encounterLibraryVersion: z.string().min(1), nextTickAt: z.iso.datetime(), diff --git a/libs/encounter-library/src/index.ts b/libs/encounter-library/src/index.ts index b945e8f..8b0c578 100644 --- a/libs/encounter-library/src/index.ts +++ b/libs/encounter-library/src/index.ts @@ -1 +1,2 @@ export * from './lib/encounter-library'; +export { SURVIVOR_NAMES } from './lib/survivors'; diff --git a/libs/encounter-library/src/lib/encounter-library.ts b/libs/encounter-library/src/lib/encounter-library.ts index cbb4e5a..9a8f013 100644 --- a/libs/encounter-library/src/lib/encounter-library.ts +++ b/libs/encounter-library/src/lib/encounter-library.ts @@ -1,4 +1,6 @@ import type { EncounterDefinition } from '@fog-explorer/api-interfaces'; +import { ALLY_FIRST_NAMES } from './survivors'; +import encountersData from './encounters.json'; interface RawEncounter { key: string; @@ -13,9 +15,6 @@ interface EncountersFile { encounters: RawEncounter[]; } -// eslint-disable-next-line @typescript-eslint/no-require-imports -const encountersData = require('./encounters.json') as EncountersFile; - export interface LibraryEncounter extends EncounterDefinition { tier: 1 | 2 | 3; flavorSuccess: string[]; @@ -66,5 +65,12 @@ export function pickFlavor( rng: () => number ): string { const pool = ctx.success ? encounter.flavorSuccess : encounter.flavorFailure; - return pool[Math.floor(rng() * pool.length)]; + const raw = pool[Math.floor(rng() * pool.length)]; + return expandAlly(raw, rng); +} + +function expandAlly(text: string, rng: () => number): string { + return text.replace(/\{\{ally\}\}/g, () => { + return ALLY_FIRST_NAMES[Math.floor(rng() * ALLY_FIRST_NAMES.length)]; + }); } diff --git a/libs/encounter-library/src/lib/encounters.json b/libs/encounter-library/src/lib/encounters.json index 5a500d3..f969ccf 100644 --- a/libs/encounter-library/src/lib/encounters.json +++ b/libs/encounter-library/src/lib/encounters.json @@ -1,5 +1,5 @@ { - "version": "1.0.0", + "version": "1.2.0", "encounters": [ { "key": "generator_repair", @@ -7,28 +7,28 @@ "tags": ["generator", "objectives"], "tier": 1, "flavorSuccess": [ - "The generator sputters to life. Light floods the area.", - "Sparks fly, then hum — the generator catches.", - "Wires connected. The machine breathes again.", - "A deep mechanical thrum. Progress.", - "The gauge climbs. Another generator done.", - "Hands steady, heart racing. The engine turns over.", - "The generator shudders, then roars. One less in the dark.", - "Circuit reconnected. The hum is its own small victory.", - "It takes longer than expected, but the light comes.", - "The generator protests, then yields." + "It catches. I step back before I let myself feel anything.", + "Sparks, then hum. I keep moving.", + "The last wire clicks. One down.", + "It's running. I don't stop to celebrate.", + "My hands stop shaking when the engine turns over.", + "{{ally}} holds the wire steady. It catches.", + "The gauge climbs. I'm already looking for the next one.", + "Takes too long. But it works.", + "Done. The light helps more than I expected.", + "{{ally}} calls out. I make the connection. Done." ], "flavorFailure": [ - "The generator kicks back. Too many watchers in the dark.", - "The mechanism jams. Footsteps echo nearby.", - "A noise betrays the position. The generator goes cold.", - "Something shifts in the fog. The work abandoned.", - "The wiring resists every attempt. No progress tonight.", - "A shadow crosses the generator. Not now.", - "The tool slips. The noise is too loud.", - "The generator sparks wrong. An alarm of sorts.", - "Patience runs out before the generator does.", - "A nearby sound. The repair will have to wait." + "Something moves in the fog. I pull back.", + "A sound behind me. I leave the gen and run.", + "Too loud. I made it too loud.", + "The wiring's wrong. I can't fix it from here.", + "I miscalculate. The whole thing sparks wrong.", + "{{ally}} had to break away. I lose the progress.", + "My hand slips. The noise is too much.", + "A shadow on the gen. I'm already gone.", + "Not tonight. Not this one.", + "{{ally}} pulls back — something's close. I follow." ] }, { @@ -37,28 +37,28 @@ "tags": ["totem", "altruistic", "objectives"], "tier": 1, "flavorSuccess": [ - "The totem crumbles. Its curse lifts from the fog.", - "Bones scatter. The hex dissolves into smoke.", - "The ritual unravels. Something distant screams.", - "The totem falls apart like it was never solid.", - "Ash and splinter. The hex breaks.", - "A soft crack — the bones give way. Something changes.", - "The skull shatters. Whatever watched through it is gone.", - "The cleansing takes. The fog feels thinner.", - "The totem offers no resistance. It was waiting to fall.", - "One less curse in the trial." + "It breaks. Something screams — not here, but somewhere.", + "The bones scatter. I feel the curse lift.", + "One hex gone. The fog feels slightly less wrong.", + "It comes apart like it was never solid.", + "I don't understand what I just broke. I'm glad I broke it.", + "{{ally}} spots it first. I make short work of it.", + "The skull gives. Whatever watched through it is gone.", + "{{ally}} watches my back. The cleanse is fast.", + "It was waiting to fall. I let it.", + "One less curse." ], "flavorFailure": [ - "The totem resists. Its pull is stronger than expected.", - "A presence drives the survivor back before the cleanse completes.", - "The hex holds. Dread thickens the air.", - "Fingers close on the totem — then something warns away.", - "The totem hums with malice. Not today.", - "The ritual is interrupted. The hex burns brighter.", - "The bones refuse to break. Wrong approach.", - "The totem feels wrong. Leave it for now.", - "Whatever keeps the totem standing isn't done yet.", - "The cleanse stalls halfway. The hex endures." + "It pulls back. I can't finish this.", + "Something drives me away before it's done.", + "The hex holds. The air gets thicker.", + "I reach for it and stop. Something warns me off.", + "The totem hums. I'm not strong enough. Not yet.", + "{{ally}} calls wrong. The totem burns brighter.", + "The bones won't break. Wrong approach.", + "{{ally}} needs me. I leave the totem standing.", + "Halfway through. Then something changes. I run.", + "The hex endures. I'll find another way." ] }, { @@ -67,28 +67,28 @@ "tags": ["chest", "item"], "tier": 1, "flavorSuccess": [ - "The chest yields a worn medkit. Small mercies.", - "A flashlight, still charged. The fog recedes slightly.", - "A useful tool among the debris.", - "Something worth having. The chest wasn't empty after all.", - "Buried under detritus — a working toolbox.", - "A medkit, half-depleted but still viable.", - "The chest creaks open. Something useful inside.", - "Luck, this time.", - "Not everything in the fog is hostile. The chest provides.", - "A find. The supplies won't last, but they'll help." + "A medkit. Worn, but real.", + "A flashlight, still charged. I pocket it.", + "Something useful. I don't ask how it got here.", + "Not empty. That's not nothing.", + "A toolbox, buried under rot. I'll take it.", + "Half a medkit. Better than nothing.", + "The chest creaks open. I take what's there.", + "Lucky.", + "The fog gives sometimes. Not often.", + "Supplies. They won't last, but they'll help." ], "flavorFailure": [ - "The chest is empty. Only rust and regret.", - "The lid splinters — nothing useful inside.", - "A noise nearby cuts the search short.", - "Someone got here first.", - "The chest is locked. No time to force it.", - "Debris, nothing more.", + "Empty. Just rust and old regret.", + "Nothing worth taking.", + "A sound cuts the search short.", + "Someone else was here first.", + "Locked. I don't have time to force it.", + "Debris. Nothing more.", "Whatever was here is long gone.", - "The chest yields nothing. Time wasted.", - "Empty. The trial takes without giving.", - "Nothing. Move on." + "Nothing. Time I don't have, wasted.", + "The trial takes without giving.", + "Move on." ] }, { @@ -97,28 +97,28 @@ "tags": ["hook", "survival"], "tier": 2, "flavorSuccess": [ - "With grim determination, the survivor slips free.", - "Arms aching, the hook releases. Freedom, for now.", - "A desperate push — the chains give way.", - "The hook yields to persistence.", + "I don't know how. I don't care. I'm off.", + "Arms burning. The hook releases. Move.", + "Every instinct says give up. I don't.", + "It gives. I fall. I run.", "Blood and effort. The barb comes free.", - "The survivor drops to the ground, gasping.", - "Will wins where strength falls short. Free.", - "The hook's grip loosens. A precious second of clarity.", - "Every instinct says stop. The survivor doesn't.", - "The chains snap. Unlikely. Necessary." + "{{ally}}'s voice from out there somewhere. I fight harder. I'm free.", + "Will wins. I'm off the hook.", + "A fraction of movement — and it releases.", + "I stop thinking and just push. It works.", + "{{ally}} is coming. I hold on long enough. Then I'm free." ], "flavorFailure": [ - "The struggle exhausts. The hook holds.", - "Every movement drives the barb deeper. Stay still.", - "The fog presses in. The hook remains.", - "The attempt fails. The chains don't care.", - "No leverage. No escape. Not yet.", - "The energy spent is wasted. Still trapped.", - "The hook was designed for this. It holds.", - "Patience isn't a luxury here — but neither is panic.", + "The struggle costs everything. The hook doesn't move.", + "Every movement makes it worse. I go still.", + "The fog presses in. I'm still up here.", + "I try. The chains don't.", + "No leverage. Not this time.", + "{{ally}} can't get to me. Still stuck.", + "It's designed for exactly this. It holds.", + "Panic and pain in equal measure. Neither helps.", "The barb shifts but doesn't release.", - "The struggle ends. The hook wins this round." + "I lose this one." ] }, { @@ -127,28 +127,28 @@ "tags": ["exit", "objectives"], "tier": 2, "flavorSuccess": [ - "The gate grinds open. Cold air rushes in.", - "Generators humming, the lock gives. Almost there.", - "The exit yields. Light from outside cuts the fog.", - "The switch holds down. The gate obeys.", - "A long, agonising second — then the gate opens.", - "The exit panel responds. The way out is clear.", - "The gate was always going to open. Tonight it does.", - "The outside world, briefly visible through iron.", - "The switch trips. The gate moves.", - "One last obstacle overcome." + "The gate grinds open. Cold air. Real air.", + "The lock gives. I keep pressure on the switch.", + "Light from outside cuts through the fog.", + "The gate obeys. I don't let go until it does.", + "A long second. Then the mechanism gives.", + "The panel responds. I'm almost out.", + "It opens. It actually opens.", + "Through the iron, I can see outside.", + "The switch trips. The gate moves. I move.", + "One more thing behind me." ], "flavorFailure": [ - "The gate mechanism is jammed. Precious seconds lost.", - "A shadow falls across the panel. The attempt abandoned.", - "The switch is stuck. The gate stays shut.", - "The gate won't cooperate. Not now.", - "The panel sparks and resets. Try again.", - "Something holds the gate. Not mechanical — worse.", - "The generator noise covers the gate's resistance.", - "The lock mechanism fails. The gate holds.", - "Too slow. The gate doesn't open in time.", - "Pressure on the switch, nothing. Wrong timing." + "The gate mechanism jams. I burn seconds I don't have.", + "A shadow at the panel. I abandon it.", + "The switch won't budge.", + "The gate refuses. Not now.", + "The panel sparks and resets. I try again.", + "Something holds it. Nothing mechanical.", + "The gate resists. I can't tell why.", + "The lock fails. The gate stays shut.", + "Too slow. The gate doesn't care.", + "Nothing. Wrong timing." ] }, { @@ -157,28 +157,28 @@ "tags": ["stealth", "survival"], "tier": 1, "flavorSuccess": [ - "Still as stone. The threat passes without noticing.", - "A breath held long — then silence. Safe.", - "The fog swallows the survivor whole. Unseen.", - "The patrol route is predictable. Predictable is avoidable.", - "Close. Too close. But not close enough to matter.", - "The survivor becomes scenery. The patrol sees nothing.", - "A narrow gap in the patrol. Taken.", - "The killer's attention is elsewhere. Move.", - "Footsteps near, then receding. Safe.", - "The locker is cold and cramped and perfect." + "I don't breathe. I don't think. It passes.", + "A long silence — then the footsteps recede.", + "I melt into the fog. Unseen.", + "Predictable. Predictable is avoidable.", + "Close. Too close. But not enough.", + "{{ally}} draws the patrol the other way. I slip through.", + "A gap in the route. I take it.", + "The killer looks elsewhere. I don't waste it.", + "Footsteps near. Then far. Then gone.", + "The locker is cold and tight and it works." ], "flavorFailure": [ - "A twig snaps. Eye contact — then the chase begins.", - "The survivor misjudges the angle. Spotted.", - "Heartbeat too loud. Presence too close.", - "The patrol's route changed. A fatal assumption.", - "No cover. The survivor is seen.", - "A shadow where there should be none. Detected.", - "The instinct to run is wrong. It happens anyway.", - "The fog offers no protection here.", - "Spotted. The trial shifts.", - "The killer turns at the wrong moment." + "A wrong step. Eye contact. The chase starts.", + "I misjudge the angle. Spotted.", + "My heartbeat is too loud. The presence is too close.", + "The route changed. I didn't know.", + "No cover. Nowhere to go.", + "{{ally}} breaks stealth nearby. We're both compromised.", + "Every instinct says run. That instinct is wrong. I run anyway.", + "The fog doesn't protect me here.", + "Spotted. Everything changes.", + "{{ally}}'s noise pulls the killer my way." ] }, { @@ -187,28 +187,28 @@ "tags": ["healing", "altruistic", "survival"], "tier": 1, "flavorSuccess": [ - "Bandages tight, the wound closes. Pain recedes.", - "The medkit does its job. The survivor steadies.", - "A few tense minutes — injuries tended.", - "Not good as new, but functional. Good enough.", - "The bleeding stops. The fog feels slightly less hostile.", - "Clinical precision in a place with no clinics.", - "The medkit is emptied wisely. The injury yields.", - "Hands that shake still heal.", - "The wound is dressed. One less liability.", - "Recovery, however temporary, is still recovery." + "The bandages hold. The pain pulls back.", + "The medkit does what it's supposed to.", + "Not ideal. But the wound closes.", + "Not fixed. Functional. That's enough.", + "The bleeding stops. I breathe.", + "{{ally}}'s hands are steadier than mine. It works.", + "I use the supplies right. The wound yields.", + "{{ally}} moves fast. The treatment holds.", + "One less liability.", + "Better than nothing. Better than before." ], "flavorFailure": [ - "Supplies exhausted before the job is done.", - "Shaking hands fumble the medkit. Time runs out.", - "The wound is worse than it looked. Supplies fall short.", - "The medkit can't do what it wasn't built for.", - "Partial healing is still an open wound.", - "The medkit empties wrong. Wasted.", + "The supplies run out before the job is done.", + "My hands shake too much. Time runs out.", + "Worse than I thought. Supplies fall short.", + "The medkit can't do this.", + "Half-healed is still injured.", + "{{ally}} isn't here. I can't manage it alone.", "Not enough supplies. Not enough time.", - "A noise ends the attempt prematurely.", - "The injury resists treatment.", - "The medkit fails the survivor. Move anyway." + "A sound. I have to stop.", + "The wound doesn't cooperate.", + "The medkit fails me. I move anyway." ] }, { @@ -217,28 +217,28 @@ "tags": ["survival", "escape"], "tier": 2, "flavorSuccess": [ - "The pallet crashes down. A moment bought.", - "Timber splinters between them. Distance gained.", - "The drop lands true. The chase falters.", - "A calculated throw. The pallet does its job.", - "The killer staggers. A few precious seconds.", - "The pallet drops at the right instant.", - "An obstacle placed. The chase interrupted.", - "The wood holds. That's all it needs to do.", - "A roar of frustration behind the pallet. Run.", - "The drop lands. The gap widens." + "It crashes down between us. A moment bought.", + "Wood and impact. I gain distance.", + "The drop lands right. The chase falters.", + "Timed right. The pallet works.", + "They stagger. I use every second.", + "The drop lands at the right instant.", + "A barrier placed. The chase interrupted.", + "It holds. That's all it needs to do.", + "A roar behind the pallet. I'm already running.", + "The gap widens. Use it." ], "flavorFailure": [ - "The pallet drops wide. No gap created.", - "Too slow — the obstacle proves useless.", - "The throw miscalculated. The pursuit continues.", - "The pallet falls too early. Used for nothing.", - "The killer vaults before the drop lands.", - "The angle was wrong. The pallet wasted.", - "The swing comes before the pallet hits.", - "Not enough distance to matter.", - "The throw panics. The pallet means nothing.", - "A wasted pallet. The chase doesn't stop." + "The pallet drops wide. No gap.", + "Too slow. It means nothing.", + "I miscalculate. The chase continues.", + "Too early. Wasted.", + "They vault before the wood lands.", + "Wrong angle. Wrong moment.", + "The swing comes first.", + "Not enough distance. Never was.", + "I panic. The pallet follows.", + "Wasted. The chase doesn't stop." ] }, { @@ -247,28 +247,28 @@ "tags": ["chest", "item", "high-risk"], "tier": 3, "flavorSuccess": [ - "The basement yields rare supplies. Worth the risk.", - "A pristine toolbox — almost worth dying for.", - "The gamble paid off. The survivor emerges with something valuable.", - "The basement gives up its best. A full medkit.", - "Down there and back. The best supplies in the trial.", - "The risk calculates out. Everything needed, found.", + "Down there and back. Worth it.", + "A pristine toolbox. Almost worth dying for.", + "The gamble paid. I have what I need.", + "A full medkit in the basement. I don't question it.", + "The best supplies in the trial. Found.", + "I run the numbers. The risk pays out.", "The stairs back up feel like a victory.", - "The basement held back something good tonight.", + "The basement gives up something good.", "A rare find. The fog occasionally provides.", - "Against instinct, into the basement. Worth it." + "I go against every instinct. It works." ], "flavorFailure": [ - "The basement was a trap. Retreat costs dearly.", - "The stairwell offers no escape. A mistake made clear.", - "The risk was not worth the reward found — nothing.", - "The basement is empty. And now time is lost.", - "Going down is easy. Coming back is not.", - "The killer knows about the basement.", + "The basement was a trap. I pay for it.", + "The stairs offer nothing. Getting back costs more.", + "The risk wasn't worth it. Nothing down here.", + "Empty. And now I'm out of time.", + "Going down is easy. Coming back is the problem.", + "They know about the basement.", "Nothing down here but dread.", - "The search finds nothing. The stairs up feel longer.", + "I find nothing. The stairs back feel endless.", "An empty chest in a dangerous room.", - "The basement takes more than it gives tonight." + "The basement takes more than it gives." ] }, { @@ -277,28 +277,28 @@ "tags": ["exit", "survival", "high-risk"], "tier": 3, "flavorSuccess": [ - "The hatch sighs open. One last mercy from the fog.", - "A sound — familiar, haunting. The hatch, just ahead.", - "Against all odds, the escape route reveals itself.", - "The hatch was always here. It waited.", + "The hatch opens. One last way out.", + "That sound — I know that sound. I run toward it.", + "Against everything. The escape route is there.", + "It was here all along. Waiting.", "The fog breaks once. That's enough.", - "The survivor finds it before the killer does.", - "One last door. It opens.", - "The hatch glows faintly. Run.", - "The sound of it — an invitation.", - "Found. The trial ends here." + "I find it before they do.", + "One door left. It opens.", + "The hatch glows. I don't wait.", + "I hear it. I go.", + "Found. This ends here." ], "flavorFailure": [ - "The hatch is nowhere. Only fog and silence.", + "Nothing. Only fog.", "Close — so close. Then it closes.", - "The sound was something else entirely.", - "The hatch stays hidden.", - "Searching costs time that isn't there.", - "The killer finds the hatch first.", + "That sound was something else.", + "It stays hidden.", + "Searching burns time I don't have.", + "They find the hatch first.", "The fog gives nothing freely.", - "The sound led nowhere. A trick of the trial.", - "No hatch. No escape. Not this way.", - "The trial isn't done with the survivor yet." + "The sound led nowhere.", + "No hatch. No escape this way.", + "The trial isn't finished with me yet." ] }, { @@ -307,27 +307,27 @@ "tags": ["escape", "survival"], "tier": 1, "flavorSuccess": [ - "The window frame holds. Through and clear.", - "A clean vault. Momentum kept.", - "The opening is tight but usable.", - "Through the gap before the killer closes.", - "The vault buys distance. Use it.", - "A practiced move. The window yields.", - "Landing clean on the other side.", - "The window is a door when you need it.", - "Fast enough. The gap widens.", + "Clean vault. The frame holds.", + "Through and clear. Momentum kept.", + "Tight, but usable.", + "I make it through before they close the gap.", + "The vault buys distance. I use it.", + "Practiced. The window yields.", + "I land clean on the other side.", + "The window is a door when I need it.", + "Fast enough. The gap opens.", "Through. The chase resets." ], "flavorFailure": [ - "The window is boarded shut.", - "The vault is too slow. Caught.", - "The frame splinters. Not a clean exit.", - "Misjudged the height. A costly stumble.", - "The window closes the gap but not the killer.", - "Too narrow. No good.", - "The vault fails at the worst moment.", - "The opening was an illusion. Dead end.", - "A bad angle kills the attempt.", + "Boarded. No exit here.", + "Too slow. I'm caught mid-vault.", + "The frame splinters wrong.", + "I misjudge the height. I pay for it.", + "The window doesn't solve the problem.", + "Too narrow.", + "The worst possible moment for this to fail.", + "The opening was an illusion.", + "Bad angle. Bad outcome.", "The window doesn't help tonight." ] }, @@ -337,28 +337,28 @@ "tags": ["stealth", "survival"], "tier": 1, "flavorSuccess": [ - "The locker closes. Footsteps pass. Breathe.", - "Darkness and rust — perfect cover.", - "The killer walks past the locker without a glance.", + "The door closes. Footsteps pass. Breathe.", + "Rust and darkness. It works.", + "They walk past without a glance.", "Still. Quiet. Safe.", - "The locker is old and cold and hiding works.", - "In and out before the threat turns around.", - "The locker holds its occupant safe.", - "A long minute of nothing. Then clear.", - "The hiding spot works. The patrol moves on.", - "The locker door stays closed. The killer moves on." + "Cold metal. Silence. It holds.", + "{{ally}} draws them off. The locker stays shut.", + "I hold my breath. The footsteps don't stop here.", + "One long minute. Then nothing. Clear.", + "The hiding spot works. They move on.", + "They don't check it. I don't breathe until they're gone." ], "flavorFailure": [ - "The locker door rattles. Discovered.", - "The killer yanks it open. No hiding here.", - "The locker was already checked.", - "The sound of breathing gives the position away.", - "The killer knows these lockers too well.", - "A squeak of the hinge. Fatal.", - "The hiding spot chosen is the wrong one.", - "The killer's instinct is correct tonight.", - "Spotted entering. No time.", - "The locker offers nothing. Pulled out." + "The door rattles. I'm found.", + "It yanks open. No hiding here.", + "Already checked. Bad call.", + "My breathing gives me away.", + "They know these lockers too well.", + "{{ally}} makes noise nearby. They look harder. They find me.", + "The wrong spot. I know it as soon as I'm inside.", + "Their instinct is right tonight.", + "Spotted going in. No time.", + "Pulled out." ] }, { @@ -367,28 +367,28 @@ "tags": ["objectives", "altruistic"], "tier": 2, "flavorSuccess": [ - "The trap clicks disarmed. One less hazard.", - "Patient work. The mechanism yields.", - "The snap of metal, controlled this time.", - "Disarmed without triggering. Clean.", - "The trap comes apart safely.", - "A moment of focus. The trap poses no more threat.", + "It clicks safe. One less hazard.", + "Patience. The mechanism yields.", + "The snap of metal — controlled this time.", + "Clean disarm. No mistakes.", + "It comes apart safely. I exhale.", + "One moment of focus. The trap is inert.", "The floor is safer now.", - "The mechanism was elegant. Now it's inert.", - "One trap gone. Onwards.", - "The disarm holds. The path is clear." + "Elegant mechanism. Now it's nothing.", + "One trap down. Keep moving.", + "Disarmed. Path clear." ], "flavorFailure": [ - "The trap snaps. A brutal reminder.", + "It snaps. A brutal reminder of what this place is.", "One wrong move. The trap wins.", - "The mechanism was subtler than expected.", - "The disarm attempt triggers it.", - "Fingers too slow. The trap bites.", - "The trap was set with someone capable in mind.", - "Miscalculation. The mechanism triggers.", - "The trap holds and catches.", - "The attempt fails. Pain follows.", - "The trap wasn't meant to be disarmed easily." + "Subtler than I expected.", + "My attempt triggers it.", + "Too slow. The trap bites.", + "Whoever set this knew what they were doing.", + "I miscalculate. The mechanism fires.", + "It catches me.", + "I fail. Pain follows.", + "It wasn't meant to be easy." ] }, { @@ -397,26 +397,26 @@ "tags": ["altruistic", "hook"], "tier": 2, "flavorSuccess": [ - "Off the hook. Both survivors run.", - "The unhook is clean. No one watching.", - "A teammate freed. The trial continues.", - "Quick hands, lucky timing. The rescue holds.", - "The hook releases. Time to move.", - "Pulled free before the killer returns.", - "The rescue works. Move fast.", - "A window of opportunity, taken.", - "The teammate drops free. The trial has two again.", - "Freed. Both running now." + "{{ally}} gets there first. Both of us run.", + "Clean unhook. Nobody watching.", + "I get them down. The trial continues.", + "Fast hands, right timing.", + "They're off the hook. We move.", + "{{ally}} pulls them free before the killer returns.", + "The rescue works. Don't stop moving.", + "The window was there. I took it.", + "{{ally}} gets them clear. Two of us running again.", + "Free. Both running now." ], "flavorFailure": [ - "The killer turns back too soon. Retreat.", - "The rescue fails. Both caught.", - "The timing was wrong.", - "The unhook attempted — the killer was watching.", - "No safe window. The rescue abandoned.", - "The hook held on.", - "The approach was spotted.", - "The teammate is hooked again. Worse now.", + "{{ally}} pulls back. The killer turns too soon.", + "The rescue fails. Both of us in danger.", + "Wrong timing.", + "{{ally}} reaches for the hook — the killer is already watching.", + "No safe window. I leave.", + "The hook holds.", + "{{ally}}'s approach is spotted.", + "They're hooked again. Worse now.", "The rescue was a trap.", "The killer was closer than it looked." ] @@ -427,26 +427,26 @@ "tags": ["totem", "objectives", "altruistic"], "tier": 2, "flavorSuccess": [ - "The ruin totem falls. Generator speed returns to normal.", + "It falls. Generator speed comes back.", "The hex breaks. The pressure lifts.", - "Ruin destroyed. The generators are finally cooperating.", - "The totem crumbles. Something screams.", - "The ruin is gone. Work can continue properly.", + "Ruin is gone. Finally.", + "{{ally}} calls out the location. I put it down.", + "One less advantage for the fog.", "The corrupting influence ends here.", - "Ruin undone. A meaningful contribution.", - "The hex shattered. Every generator benefits.", - "Ruin falls. The trial shifts.", - "The cleansing removes one of the fog's advantages." + "Ruin undone. That mattered.", + "{{ally}} watches while I cleanse. The hex shatters.", + "Ruin falls. The whole trial shifts.", + "One of the fog's edges, removed." ], "flavorFailure": [ - "Ruin holds. The generators resist every effort.", - "The totem is guarded.", - "Ruin endures. Progress is a trickle.", - "The hex is stronger than expected.", - "The cleanse fails. Ruin stays.", - "Guarded or cursed — the totem won't fall.", - "The approach to ruin is cut off.", - "Ruin watches everything. No safe approach.", + "Ruin holds. The generators push back at every touch.", + "It's guarded.", + "Ruin endures. Progress is slower than it should be.", + "The hex is stronger than I am right now.", + "I fail. Ruin stays.", + "{{ally}} couldn't hold them back. Ruin stands.", + "The path to it is cut off.", + "Ruin sees everything. I can't get close.", "The hex survives the attempt.", "Ruin stands. The generators punish every mistake." ] @@ -457,26 +457,26 @@ "tags": ["stealth", "survival", "objectives"], "tier": 1, "flavorSuccess": [ - "The area is clear. Work continues.", - "A careful sweep — nothing here.", - "All quiet. Safe to proceed.", - "The check takes seconds. Worth every one.", - "Nothing threatening nearby. Focus returned.", - "The perimeter is clean. Move on.", - "A slow look around — all clear.", - "The survivor's instincts were right. Safe.", - "No danger. The noise was nothing.", - "The area is empty. Continue." + "All clear. Back to work.", + "Nothing here. I was right to check.", + "Quiet. Safe to continue.", + "Seconds spent checking. Worth it.", + "Nothing threatening nearby.", + "{{ally}} signals clear. I continue.", + "Slow sweep. Nothing. Move.", + "My instincts were right. Safe.", + "The noise was nothing.", + "Empty. Continue." ], "flavorFailure": [ - "The check reveals what was feared.", - "Not clear. Danger is close.", - "The area isn't empty.", - "The sound was not nothing.", - "A presence, sensed too late.", - "The check confirms the worst.", - "Not safe. Move now.", - "The area is watched.", + "The check confirms what I feared.", + "Not clear. Move now.", + "Not empty.", + "That sound was not nothing.", + "{{ally}}'s signal cuts out. Something is here.", + "Confirmed. Worse than I thought.", + "Get out.", + "I'm being watched.", "The noise was a warning.", "Something is here." ] @@ -487,26 +487,26 @@ "tags": ["item", "objectives"], "tier": 1, "flavorSuccess": [ - "A map of the trial grounds. The generators are marked.", - "Routes, exits — everything useful on one piece of paper.", - "The map provides context. Decisions improve.", + "A map of the grounds. The generators are marked.", + "Routes, exits — all of it on one piece of paper.", + "The map helps. Decisions get clearer.", "A schematic of the realm. Useful.", - "The map shows what couldn't be known otherwise.", - "A rare find. The trial becomes navigable.", - "Totem locations, hooks — the map tells all.", - "The map is accurate. That's not guaranteed here.", - "A clearer picture of the trial grounds.", - "The map is old but correct. It will help." + "The map tells me things I couldn't know otherwise.", + "A rare find. I know where I'm going now.", + "Totem locations, hooks — the map knows.", + "Accurate. I didn't expect that.", + "A clearer picture.", + "Old, but correct. I'll take it." ], "flavorFailure": [ - "Nothing useful found.", - "The map, if it existed, is gone.", - "The search yields no information.", - "Fog and confusion. No map here.", + "Nothing useful.", + "No map. If there was one, it's gone.", + "The search gives me nothing.", + "Fog and no information.", "The grounds give nothing away.", "The trial keeps its layout to itself.", "No orientation gained.", - "Blind as before.", + "Still blind.", "The realm offers no guide tonight.", "No map. Trust instinct." ] @@ -517,28 +517,28 @@ "tags": ["stealth", "altruistic"], "tier": 1, "flavorSuccess": [ - "The trail read correctly. The survivor is found.", - "Scratch marks, followed. Another survivor located.", - "The signs in the fog tell a story.", - "The trail is fresh. Someone was here recently.", + "I read the trail right. Found.", + "{{ally}}'s marks, followed. They're close.", + "The fog leaves clues if you know how to look.", + "{{ally}} was just here. The trail is fresh.", "The marks lead somewhere useful.", "Reading the ground pays off.", "The scratch marks don't lie.", - "A teammate's path, followed to its source.", - "The signs are legible. The survivor is found.", - "The trail ends at something worth finding." + "I follow {{ally}}'s path. It ends well.", + "Legible. I find what I was looking for.", + "The trail ends somewhere worth being." ], "flavorFailure": [ "The trail goes cold.", - "The marks lead nowhere useful.", - "Misread. The path was wrong.", - "The trail circles back. Disorienting.", - "The signs in the fog are misleading.", - "Too old. The trail is gone.", - "The scratch marks weren't recent.", - "The trail led somewhere dangerous.", - "The signs can't be read clearly.", - "The trail ends without resolution." + "{{ally}}'s marks lead nowhere useful.", + "I read it wrong. Wrong direction.", + "The trail circles. I lose time.", + "The fog misleads. The signs are wrong.", + "Too old. {{ally}}'s trail is gone.", + "The marks weren't recent.", + "The trail leads somewhere I don't want to be.", + "I can't make sense of them.", + "No resolution." ] }, { @@ -547,26 +547,26 @@ "tags": ["high-risk", "survival"], "tier": 3, "flavorSuccess": [ - "The presence recedes. The survivor is not chosen.", - "Whatever stalks the fog passes over.", - "The terror subsides. Not tonight.", - "The entity considers — then moves on.", - "The survivor is overlooked. Lucky.", - "The cold lifts. The entity chose elsewhere.", - "The fog's attention moves away.", - "Stillness. The presence is gone.", + "The presence passes over me. Not chosen. Not tonight.", + "Whatever stalks the fog — it moves on.", + "The terror recedes. I'm still here.", + "It considers. Then looks elsewhere.", + "I'm overlooked. I don't question it.", + "The cold lifts. It chose someone else.", + "The fog's attention slides away.", + "Stillness. It's gone.", "Not taken. Not yet.", - "The chill passes. The trial continues." + "The chill passes. I continue." ], "flavorFailure": [ - "The fog thickens. Something approaches.", - "The presence locks on. There is no outrunning this.", - "The entity's attention does not waver.", - "Noticed. The trial changes.", - "The chill deepens. The entity is here.", - "No escape from something this deliberate.", - "The fog moves with purpose.", - "The survivor is seen by something that doesn't forget.", + "The fog thickens around me. Something is coming.", + "It locks on. There's no running from this.", + "The attention doesn't move.", + "Noticed. Everything changes.", + "The chill deepens. It's here.", + "Nothing escapes something this deliberate.", + "The fog moves with intent.", + "Something sees me. Something that doesn't forget.", "The presence descends.", "Chosen. The hardest part begins." ] @@ -577,28 +577,28 @@ "tags": ["high-risk", "stealth"], "tier": 3, "flavorSuccess": [ - "The instinct passes harmlessly. Not found.", - "The killer's sense of where to look is wrong tonight.", - "The prediction fails. Safe.", - "The killer checks elsewhere.", - "Instinct misfires. The survivor is still hidden.", - "Wrong corner checked. The survivor breathes.", - "The killer's certainty was misplaced.", + "The instinct misfires. I'm not found.", + "They look in the wrong place.", + "The prediction fails. I'm safe.", + "They check elsewhere.", + "Wrong corner. I breathe.", + "They're certain. They're wrong.", + "The read was off. I move.", "Not there. Not tonight.", - "The instinct was wrong. Move quickly.", - "The killer's focus breaks elsewhere." + "The instinct fails them.", + "Their focus breaks. I use it." ], "flavorFailure": [ - "The killer knows exactly where to look.", - "Instinct and experience. The survivor is found.", - "No hiding from something this certain.", - "The killer's read was correct.", - "The position is given away before the search begins.", - "Exactly where expected. No luck tonight.", - "The killer's certainty was earned.", - "Found before the search starts.", + "They know exactly where I am.", + "Instinct and experience. I'm found.", + "There's no hiding from something this certain.", + "The read was right.", + "My position is given away before the search starts.", + "Exactly where expected.", + "Their certainty was earned.", + "Found before the search begins.", "The instinct is right.", - "The killer walks directly to the survivor." + "They walk directly to me." ] } ] diff --git a/libs/encounter-library/src/lib/survivors.ts b/libs/encounter-library/src/lib/survivors.ts new file mode 100644 index 0000000..303deba --- /dev/null +++ b/libs/encounter-library/src/lib/survivors.ts @@ -0,0 +1,63 @@ +export const SURVIVOR_NAMES: readonly string[] = [ + 'Dwight Fairfield', + 'Meg Thomas', + 'Claudette Morel', + 'Jake Park', + 'Nea Karlsson', + 'Laurie Strode', + 'Ace Visconti', + 'Bill Overbeck', + 'Feng Min', + 'David King', + 'Quentin Smith', + 'David Tapp', + 'Kate Denson', + 'Adam Francis', + 'Jeff Johansen', + 'Jane Romero', + 'Ash Williams', + 'Nancy Wheeler', + 'Steve Harrington', + 'Yui Kimura', + 'Zarina Kassir', + 'Cheryl Mason', + 'Felix Richter', + 'Élodie Rakoto', + 'Yun-Jin Lee', + 'Jill Valentine', + 'Leon S. Kennedy', + 'Mikaela Reid', + 'Jonah Vasquez', + 'Yoichi Asakawa', + 'Haddie Kaur', + 'Ada Wong', + 'Rebecca Chambers', + 'Vittorio Toscano', + 'Thalita Lyra', + 'Renato Lyra', + 'Gabriel Soma', + 'Nicolas Cage', + 'Ellen Ripley', + 'Alan Wake', + 'Sable Ward', + 'Aestri Yazar', + 'Lara Croft', + 'Trevor Belmont', + 'Taurie Cain', + 'Rick Grimes', + 'Michonne Grimes', + 'Vee Boonyasak', +]; + +export const ALLY_FIRST_NAMES: readonly string[] = [ + 'Dwight', 'Meg', 'Claudette', 'Jake', 'Nea', + 'Laurie', 'Ace', 'Bill', 'Feng', 'David', + 'Quentin', 'Tapp', 'Kate', 'Adam', 'Jeff', + 'Jane', 'Ash', 'Nancy', 'Steve', 'Yui', + 'Zarina', 'Cheryl', 'Felix', 'Élodie', 'Yun-Jin', + 'Jill', 'Leon', 'Mikaela', 'Jonah', 'Yoichi', + 'Haddie', 'Ada', 'Rebecca', 'Vittorio', 'Thalita', + 'Renato', 'Gabriel', 'Nicolas', 'Ellen', 'Alan', + 'Sable', 'Aestri', 'Lara', 'Trevor', + 'Taurie', 'Rick', 'Michonne', 'Vee', +]; diff --git a/package.json b/package.json index 880d928..a41ddd9 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,13 @@ "name": "@fog-expedition/source", "version": "0.0.0", "license": "MIT", - "scripts": {}, + "scripts": { + "dev": "pnpm run --parallel dev:api dev:overlay", + "dev:api": "NODE_ENV=development pnpm nx serve api", + "dev:overlay": "pnpm nx serve overlay", + "infra:up": "docker compose up -d postgres redis", + "infra:down": "docker compose down" + }, "private": true, "devDependencies": { "@angular-devkit/core": "~21.2.0", @@ -51,6 +57,7 @@ "jsdom": "^27.1.0", "jsonc-eslint-parser": "^2.1.0", "nx": "22.7.1", + "pino-pretty": "^13.1.3", "prettier": "~3.6.2", "prisma": "^5.22.0", "ts-jest": "^29.4.0", @@ -74,9 +81,17 @@ "@nestjs/platform-express": "^11.0.0", "@nestjs/schedule": "^6.1.3", "@nestjs/throttler": "^6.5.0", + "@opentelemetry/auto-instrumentations-node": "^0.75.0", + "@opentelemetry/exporter-trace-otlp-http": "^0.217.0", + "@opentelemetry/resources": "^2.7.1", + "@opentelemetry/sdk-node": "^0.217.0", + "@opentelemetry/sdk-trace-node": "^2.7.1", + "@opentelemetry/semantic-conventions": "^1.40.0", "@prisma/client": "^5.22.0", "axios": "^1.6.0", "ioredis": "^5.10.1", + "nestjs-pino": "^4.6.1", + "pino-http": "^11.0.0", "reflect-metadata": "^0.1.13", "rxjs": "^7.8.0", "seedrandom": "^3.0.5", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index ed5b7a4..9c54f31 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -41,6 +41,24 @@ importers: '@nestjs/throttler': specifier: ^6.5.0 version: 6.5.0(@nestjs/common@11.1.19(reflect-metadata@0.1.14)(rxjs@7.8.2))(@nestjs/core@11.1.19)(reflect-metadata@0.1.14) + '@opentelemetry/auto-instrumentations-node': + specifier: ^0.75.0 + version: 0.75.0(@opentelemetry/api@1.9.1)(@opentelemetry/core@2.7.1(@opentelemetry/api@1.9.1)) + '@opentelemetry/exporter-trace-otlp-http': + specifier: ^0.217.0 + version: 0.217.0(@opentelemetry/api@1.9.1) + '@opentelemetry/resources': + specifier: ^2.7.1 + version: 2.7.1(@opentelemetry/api@1.9.1) + '@opentelemetry/sdk-node': + specifier: ^0.217.0 + version: 0.217.0(@opentelemetry/api@1.9.1) + '@opentelemetry/sdk-trace-node': + specifier: ^2.7.1 + version: 2.7.1(@opentelemetry/api@1.9.1) + '@opentelemetry/semantic-conventions': + specifier: ^1.40.0 + version: 1.40.0 '@prisma/client': specifier: ^5.22.0 version: 5.22.0(prisma@5.22.0) @@ -50,6 +68,12 @@ importers: ioredis: specifier: ^5.10.1 version: 5.10.1 + nestjs-pino: + specifier: ^4.6.1 + version: 4.6.1(@nestjs/common@11.1.19(reflect-metadata@0.1.14)(rxjs@7.8.2))(pino-http@11.0.0)(pino@10.3.1)(rxjs@7.8.2) + pino-http: + specifier: ^11.0.0 + version: 11.0.0 reflect-metadata: specifier: ^0.1.13 version: 0.1.14 @@ -201,6 +225,9 @@ importers: nx: specifier: 22.7.1 version: 22.7.1(@swc-node/register@1.11.1(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0)(@swc/core@1.15.33(@swc/helpers@0.5.21))(@swc/types@0.1.26)(typescript@5.9.3))(@swc/core@1.15.33(@swc/helpers@0.5.21)) + pino-pretty: + specifier: ^13.1.3 + version: 13.1.3 prettier: specifier: ~3.6.2 version: 3.6.2 @@ -227,7 +254,7 @@ importers: version: 8.0.10(@types/node@20.19.9)(esbuild@0.27.3)(jiti@2.7.0)(less@4.5.1)(sass-embedded@1.99.0)(sass@1.99.0)(terser@5.46.2)(yaml@2.8.0) vitest: specifier: ^4.0.8 - version: 4.1.5(@types/node@20.19.9)(@vitest/coverage-v8@4.1.5)(happy-dom@20.9.0)(jsdom@27.4.0)(vite@8.0.10(@types/node@20.19.9)(esbuild@0.27.3)(jiti@2.7.0)(less@4.5.1)(sass-embedded@1.99.0)(sass@1.99.0)(terser@5.46.2)(yaml@2.8.0)) + version: 4.1.5(@opentelemetry/api@1.9.1)(@types/node@20.19.9)(@vitest/coverage-v8@4.1.5)(happy-dom@20.9.0)(jsdom@27.4.0)(vite@8.0.10(@types/node@20.19.9)(esbuild@0.27.3)(jiti@2.7.0)(less@4.5.1)(sass-embedded@1.99.0)(sass@1.99.0)(terser@5.46.2)(yaml@2.8.0)) webpack-cli: specifier: ^5.1.4 version: 5.1.4(webpack@5.106.2) @@ -1405,6 +1432,15 @@ packages: resolution: {integrity: sha512-GmzA9ckNokPypTg10pgpeHNQe7ph+iIKKmhKu3Ob9ANkswreCx7R3cKmY781K8QK3AqVL3xVh9A42JvIAbkkSA==} engines: {node: ^20.17.0 || >=22.9.0} + '@grpc/grpc-js@1.14.3': + resolution: {integrity: sha512-Iq8QQQ/7X3Sac15oB6p0FmUg/klxQvXLeileoqrTRGJYLV+/9tubbr9ipz0GKHjmXVsgFPo/+W+2cA8eNcR+XA==} + engines: {node: '>=12.10.0'} + + '@grpc/proto-loader@0.8.0': + resolution: {integrity: sha512-rc1hOQtjIWGxcxpb9aHAfLpIctjEnsDehj0DAiVfBlmT84uvR0uUtN2hEi/ecvWVjXUGf5qPF4qEgiLOx1YIMQ==} + engines: {node: '>=6'} + hasBin: true + '@harperfast/extended-iterable@1.0.3': resolution: {integrity: sha512-sSAYhQca3rDWtQUHSAPeO7axFIUJOI6hn1gjRC5APVE1a90tuyT8f5WIgRsFhhWA7htNkju2veB9eWL6YHi/Lw==} @@ -1695,6 +1731,9 @@ packages: '@jridgewell/trace-mapping@0.3.9': resolution: {integrity: sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==} + '@js-sdsl/ordered-map@4.4.2': + resolution: {integrity: sha512-iUKgm52T8HOE/makSxjqoWhe95ZJA1/G1sYsGev2JDKUSS14KAgg1LHb+Ba+IPow0xflbnSkOsZcO08C7w1gYw==} + '@jsonjoy.com/base64@1.1.2': resolution: {integrity: sha512-q6XAnWQDIMA3+FTiOYajoYqySkO+JSat0ytXGSuRdq9uXE7o92gzuQwQM14xaCRlBLGq3v5miDGC4vkVTn54xA==} engines: {node: '>=10.0'} @@ -2430,6 +2469,461 @@ packages: '@nx/workspace@22.7.1': resolution: {integrity: sha512-wnBMgeogdGaRdxDDzZspSt1U87PMeYJhz1ygr42L9Lot9E5nCf17E85iyHl3YxcMSNslHxnHyTkq7MyQ8hHjdQ==} + '@opentelemetry/api-logs@0.217.0': + resolution: {integrity: sha512-Cdq0jW2lknrNfrAm92MyEAvpe2cRsKjdnQLHUL6xRA4IVUnsWx6P65E7NcUO0Y+L4w1Aee5iV8FvjSwd+lrs9A==} + engines: {node: '>=8.0.0'} + + '@opentelemetry/api@1.9.1': + resolution: {integrity: sha512-gLyJlPHPZYdAk1JENA9LeHejZe1Ti77/pTeFm/nMXmQH/HFZlcS/O2XJB+L8fkbrNSqhdtlvjBVjxwUYanNH5Q==} + engines: {node: '>=8.0.0'} + + '@opentelemetry/auto-instrumentations-node@0.75.0': + resolution: {integrity: sha512-gu//UwgQo8DexE/g1+wDUHhHV5gvRku5rbA8EsubHSDXWPGDcdNy4wktDQ9wyX0EDdP80BRiRaSYLIPFKo2DQw==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': ^1.4.1 + '@opentelemetry/core': ^2.0.0 + + '@opentelemetry/configuration@0.217.0': + resolution: {integrity: sha512-xCtrYOhBqdy6ZOMfe0Oa73ZKF+2LMhoOv4L5vmwAHVvOXUg+V3fvKuEIr9ZyD0Ow+vxllEjWO6PV1wd0DOtyvw==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': ^1.9.0 + + '@opentelemetry/context-async-hooks@2.7.1': + resolution: {integrity: sha512-OPFBYuXEn1E4ja3Y6eeA7O+ZnLBNcXTV5Cgsn1VaqBZ6hC5FnpZPLBNme1LJY8ZtF4aOujPKFoeWN4ik487KuQ==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': '>=1.0.0 <1.10.0' + + '@opentelemetry/core@2.7.1': + resolution: {integrity: sha512-QAqIj32AtK6+pEVNG7EOVxHdE06RP+FM5qpiEJ4RtDcFIqKUZHYhl7/7UY5efhwmwNAg7j8QbJVBLxMerc0+gw==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': '>=1.0.0 <1.10.0' + + '@opentelemetry/exporter-logs-otlp-grpc@0.217.0': + resolution: {integrity: sha512-vC5S0Dc+noxD86CVtNu1+awCHPA5Kewi1Sg23ps+9lh4YifwsKXh3pe4XTNEKtUJiAcjpJ5dqStGakLbrSE+YQ==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': ^1.3.0 + + '@opentelemetry/exporter-logs-otlp-http@0.217.0': + resolution: {integrity: sha512-KfLAdt1uilVE+3FxbgVnp2ZrzqbIawzcesnRoi+Kh9ckB5Ld5D8btUgoBvwTbdmuNx1j6b132Wsh72azq+pPNQ==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': ^1.3.0 + + '@opentelemetry/exporter-logs-otlp-proto@0.217.0': + resolution: {integrity: sha512-Se0GG/ZO24mQTlQj7zprR4pNI0nKe4lPDPBsuJmi6508b9TlZEuUd3EfyuHk6oJxzL7fGyDFYAbxNigQvRP2ZQ==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': ^1.3.0 + + '@opentelemetry/exporter-metrics-otlp-grpc@0.217.0': + resolution: {integrity: sha512-0GpJKnCoVaVA1rKBMVPHziznfOQlXgH72S9ktjBAF1AnAVPzX7vVEBGrhwiSxxHDAiefXk+J8znApsMb/K6Z3w==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': ^1.3.0 + + '@opentelemetry/exporter-metrics-otlp-http@0.217.0': + resolution: {integrity: sha512-1zkMzzhiNJdVmLxuwkltqWGw4fOOam47bqRxmuQNjyKJe/9NmY5cIrZ4kiQV7sVGxoOgT0ZvGUfLcjvtpC/b9Q==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': ^1.3.0 + + '@opentelemetry/exporter-metrics-otlp-proto@0.217.0': + resolution: {integrity: sha512-nfxt/KxVGFkjkO/M+58y1ugHu/dwPtxG4eYq0KApcQ7xk5CHzhdn+IuLZfDSvNDrJ3Uy5q++Fj/wbK7i8yryfQ==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': ^1.3.0 + + '@opentelemetry/exporter-prometheus@0.217.0': + resolution: {integrity: sha512-U9MCXxJu0sBCh5aEkylYRR4xVIL8D1CW6dGwvYXbfFr0qveSorfD0XJchCAWoW6QfAAIcY/yxjf4Dj8OgkHBPw==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': ^1.3.0 + + '@opentelemetry/exporter-trace-otlp-grpc@0.217.0': + resolution: {integrity: sha512-fPZs2fw7veLH3pEKu8vSepUa2fQpAE2P7al6qU10aH9GrEJJ8YaPgsd5xON7by5rbcEVS71FOU2aWyK6nzB7VQ==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': ^1.3.0 + + '@opentelemetry/exporter-trace-otlp-http@0.217.0': + resolution: {integrity: sha512-38YQoqtYjglz2GV94LGUN/djLvxtvGIQO68o6qAFPVshjmwSdX1F2i0c7vn3lEl1L5B/YqjB/bgKXaVx7KO+RQ==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': ^1.3.0 + + '@opentelemetry/exporter-trace-otlp-proto@0.217.0': + resolution: {integrity: sha512-nPV8gKHUiSuTZpQcnZU3/pBlK7crSyEGpZuh5MtWySB0vv6NNG0QvvfKitQt+Fc2Mc6qfyU54KlZcurwoTbrVg==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': ^1.3.0 + + '@opentelemetry/exporter-zipkin@2.7.1': + resolution: {integrity: sha512-mfsD9bKAxcKrh5+y08TPodvClBO0CznBE3p79YAGnO81WI4LrdsGA65T53e4iTSbCalW4WaUpkbeJcbpyIUHfg==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': ^1.0.0 + + '@opentelemetry/instrumentation-amqplib@0.64.0': + resolution: {integrity: sha512-yiTkjF7bSGp25UmhIYqKkbxyxDDD8w3fFf0KHkpc0m+8DsEXqmuu5a5buERSoeoT7wGgUSCuFcsm+BwlKetK4w==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': ^1.3.0 + + '@opentelemetry/instrumentation-aws-lambda@0.69.0': + resolution: {integrity: sha512-LhICbZcZZFXSIaSb1xztg6vU4oaSl1e87oc7NfWGzcsUx4EBqeteMXWRQNRvWMVu0p2HGl085oBoPOX93RJl1Q==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': ^1.3.0 + + '@opentelemetry/instrumentation-aws-sdk@0.72.0': + resolution: {integrity: sha512-E274IgU5FWknUEu4KYh1pF5jZTRHyrZcoRE8z2BMQZ1ywgE0cldi8hIGcmSbS+0yKynHCxigpr4iYowaJFWs0w==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': ^1.3.0 + + '@opentelemetry/instrumentation-bunyan@0.62.0': + resolution: {integrity: sha512-4PjSfgyzKzhnnczUkbh8ogDclQqInBsSK0J3BQX4wRueK7cCGUyQghLQYqPW1TbzrXUfM0kNtrnu1D5Xbk3LEA==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': ^1.3.0 + + '@opentelemetry/instrumentation-cassandra-driver@0.62.0': + resolution: {integrity: sha512-IdnVAJs4pJhJl5/fOey6hM7KTBjUBKRMtljdbSYTVmVkxQ35y1kTfq4EygxakUPYjcbG7JM6YV/Qy8NlX5vSCA==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': ^1.3.0 + + '@opentelemetry/instrumentation-connect@0.60.0': + resolution: {integrity: sha512-55YFP5ae2tuivf2EENGVN+woYHmGZdmcU1BVCyMZQ0BRJCNLJ35+ouJSUKlMmF/zTv1gvdFXkTuC7c3/E3HRoQ==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': ^1.3.0 + + '@opentelemetry/instrumentation-cucumber@0.33.0': + resolution: {integrity: sha512-9J1kBYXK6VZC0GDVCU8yGfWVM89WuYQCNORWI6JxuKcynGRSXHufHZ5NV2xpWlK5MsscAl9Ww5uTFRS37wNJew==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': ^1.0.0 + + '@opentelemetry/instrumentation-dataloader@0.34.0': + resolution: {integrity: sha512-Pq49BecX5HE5betSxutz/wtWIIZTgrPO32ZSxEk6nYJ1H/LWkQAsR/fSd2LZ2fdw2kLbkqCnw2rjOB5T9yESNw==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': ^1.3.0 + + '@opentelemetry/instrumentation-dns@0.60.0': + resolution: {integrity: sha512-UQ8ocJMLHWtZ6u+Y5JusHrYkFJnm2/S7+de1nbhrMyDyz8TV+HMBQ0cTWt6Wl9UkR5ad8mMql98xke0ysVVBRg==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': ^1.3.0 + + '@opentelemetry/instrumentation-express@0.65.0': + resolution: {integrity: sha512-Z22sVuMYUKGnEFO1ovlC4iKQubSKv8AlP8NxvYHM3hlD023GaC6YV5WW4fapGyvIDUnN++Cll3ZjGT689T3YZQ==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': ^1.3.0 + + '@opentelemetry/instrumentation-fs@0.36.0': + resolution: {integrity: sha512-W7MhFtkZR4bnPi3GYivsH6RqwdtzTqkhZEJZ3YKuadncFhvNgEz6F/ZGkaNwuP/QbdcMhu9/lTDa+uJqzu4Mjg==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': ^1.3.0 + + '@opentelemetry/instrumentation-generic-pool@0.60.0': + resolution: {integrity: sha512-W7NXzadxYr73yUuXQC7V/rfrzWGRhopeWiVXaNc6fZzq/ocTE4D+MZqunyC9vlbmY6GABofIuxxziesNtebMJQ==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': ^1.3.0 + + '@opentelemetry/instrumentation-graphql@0.65.0': + resolution: {integrity: sha512-Qj1R0+ltveYtXpUTI606O6mEKWqWPYv+9v5j6G8ypFf2sZHW9L4Z8NEuCxPufhIJhW6HsKGeEzP0B3QB8ZZntQ==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': ^1.3.0 + + '@opentelemetry/instrumentation-grpc@0.217.0': + resolution: {integrity: sha512-2kpvT5IueYstqiS5UsPu/fpRI6ae8166KdUbwLbZh8LN5YxWY3oWOy4ekjDPdJ/iqHQ9KFGY+NUjFMm78ODFNg==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': ^1.3.0 + + '@opentelemetry/instrumentation-hapi@0.63.0': + resolution: {integrity: sha512-8WlkrIkwtMP77PS7WXSwrPJY3yv1FUgDvNwje0KoZH8Dx7jxu5mEwn36fGnLCYkiUmJeMYhKydpShjY0XGs/8g==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': ^1.3.0 + + '@opentelemetry/instrumentation-http@0.217.0': + resolution: {integrity: sha512-B88Y7k5A9a60pHUboFoeJlgVwXq2T0rsZKj6dTwzSMKSOsNXR4Jz5ovwprVn3kHLAZrkyLEjQtBJ34DYHs1U4Q==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': ^1.3.0 + + '@opentelemetry/instrumentation-ioredis@0.65.0': + resolution: {integrity: sha512-ZOy2m2JRTAsxA3Y02j0QsDm976PF8wA5LdjfbIrSWgACekJEt28uKFXoAc/ufqyVGvgsGACN/A8V92usset2qA==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': ^1.3.0 + + '@opentelemetry/instrumentation-kafkajs@0.26.0': + resolution: {integrity: sha512-tukLaQ1bHYc3exhjEydkOdy2n4nklTOYN+vhJT2GgL19FZtz3Z9sdnslTsaoiBq5mXQII6nP9O+0wLIEimIK/g==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': ^1.3.0 + + '@opentelemetry/instrumentation-knex@0.61.0': + resolution: {integrity: sha512-gpSLnEhkKS65okAmne1NKiwXXQ/7Zh8AhMP5eNIVJjacyso73fEnYFT6yOlRxUPF0eZbJAAGIVPGMTGjaPCF+A==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': ^1.3.0 + + '@opentelemetry/instrumentation-koa@0.65.0': + resolution: {integrity: sha512-3GgDktABVRmu25LLtIiCE62gZ5qKAo8d2z4WoBS+roQK/SlbJw4i5IlGWuzeCz/y6XgSmKR3yguPCY8c5nvI7A==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': ^1.9.0 + + '@opentelemetry/instrumentation-lru-memoizer@0.61.0': + resolution: {integrity: sha512-p2oA8PDPTUE0wxRpeGmkr7B8M0aL+zkcX3jv3Ce+Z9zjYaEyWfOxpPiXQjZIGn0WTIo1ZPvyQi+3ZYui+Nmzsw==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': ^1.3.0 + + '@opentelemetry/instrumentation-memcached@0.60.0': + resolution: {integrity: sha512-CkaiOa+/PHip+GEqV6FVgWi9MSYh27piPdIdfPUi9qmierax++ZB+r+YqLnozx3xUz1SUVuIKPzKMkW3W4P6MA==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': ^1.3.0 + + '@opentelemetry/instrumentation-mongodb@0.70.0': + resolution: {integrity: sha512-9DeBe6SiOkguL5uRyj20ma0I1lXgW0EYc9mUnABlMzbxYC3b7sczUFSj37eWaLzZNzTVQNr+U8UZZP2LvlZ+vw==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': ^1.3.0 + + '@opentelemetry/instrumentation-mongoose@0.63.0': + resolution: {integrity: sha512-UsttKQ02fp+FlEw+fQLUVqXLzoxSBpqdzOpKM4DVY8Jy4/53skDnVVDODk9CyUAzD3WSmlglf3Cnl7T5awwV+A==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': ^1.3.0 + + '@opentelemetry/instrumentation-mysql2@0.63.0': + resolution: {integrity: sha512-tQ8K4PbDUNKoCmCXcUtugidGXGqefG60S6sYH7BcOMvHZ40e3+zzMxI1GbkIEypCYNrtYJqxcQZlMO9guxJcdw==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': ^1.3.0 + + '@opentelemetry/instrumentation-mysql@0.63.0': + resolution: {integrity: sha512-IFzZiZtLgQEzK74OHT921n1ARRUyRWWe/5oa5nezuwQF4XzLEXIqTP+vthrZ4QJc1qIkRLgPROVDcjz20gEKew==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': ^1.3.0 + + '@opentelemetry/instrumentation-nestjs-core@0.63.0': + resolution: {integrity: sha512-uw27m1wSx4AjC+9qfKHlY4IZDQxB9+YR42jJoS5wZcAzhTD95vW8JKIt8oqfcCMBlzTk7LBlaO0J7PTdAVTWkA==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': ^1.3.0 + + '@opentelemetry/instrumentation-net@0.61.0': + resolution: {integrity: sha512-F1wtphTiaHCuwyvX1j5iLi8CJf64mWdCfpUvVkZuOrJ1Z5xf52s/akBaV2Kt8xc1h5WGUlqWw0gglE0R3bt8Yg==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': ^1.3.0 + + '@opentelemetry/instrumentation-openai@0.15.0': + resolution: {integrity: sha512-K9hcKD+9bYhEHJr7jz7OywoQtFkEN4mztP+tAKeoNjoT+ksSbBBOOXg4kaHlX6CR8uzdaoOFPqX6OWRvulxejA==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': ^1.3.0 + + '@opentelemetry/instrumentation-oracledb@0.42.0': + resolution: {integrity: sha512-xM+tRZjw/XucOJO3ZuEedzcsEr/cAGMEH8KendrGJxtYOrsarhHlfi1K2p+qGjD7Zvox0JSoRdmpDa5AAG6eyQ==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': ^1.3.0 + + '@opentelemetry/instrumentation-pg@0.69.0': + resolution: {integrity: sha512-5tHz2AOyuYaWJePxybooIHPlV8v1EPBwrgFlfuuGXBV/EFnnQME+KEPJ9u9e3oe2aKd0gi8CEkTzZ61VwuWo5g==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': ^1.3.0 + + '@opentelemetry/instrumentation-pino@0.63.0': + resolution: {integrity: sha512-3OIHBN/bGKXgqeqNYnXb+eXJJx+MhXLzolNiFZR0eR7HUXv+rqj6P3J5LFFhrQzrTTD/huePTYidhJl87vLdow==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': ^1.3.0 + + '@opentelemetry/instrumentation-redis@0.65.0': + resolution: {integrity: sha512-NwEhtBXwIKvcPSEGyYqjWaUZB1vfda1aVbEOTjhtBSbNDsxSXnGF1/xlwBB2yYZtLC4oYzlc9FRS7wX//X4LTg==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': ^1.3.0 + + '@opentelemetry/instrumentation-restify@0.62.0': + resolution: {integrity: sha512-xovdX+JIByMII9T4kC1mLJ7Fi74ziN+A7fHtubVbrDNAlc05ViM1isHHoBe14vr3sOLYtME65FxIFdiLIOlfJg==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': ^1.3.0 + + '@opentelemetry/instrumentation-router@0.61.0': + resolution: {integrity: sha512-eHDJ+tIponMkzTMcQSZenQME+U1Iq7BpIoV0a4jJhJYchDJ5ALHuA5SOJCMs7HXGI2GiF4DtFAOfuEFzt0lXVQ==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': ^1.3.0 + + '@opentelemetry/instrumentation-runtime-node@0.30.0': + resolution: {integrity: sha512-5vq3SB9Nwoqbzv6QAFhzStAeR2ejFmH5fMS9KLzBR5Dqu8sMMPbhEU3fnJwFJONio4edx5BoU+QpMQ/UjiP2tg==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': ^1.3.0 + + '@opentelemetry/instrumentation-socket.io@0.64.0': + resolution: {integrity: sha512-BnCjj+WaV92kx/PJay4EwlYxqzynXH/7Bh+dnaVrchaAs9mWZKyrOKZSlVi6+1AMEqJ436n1JQMFu/teFRbe+g==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': ^1.3.0 + + '@opentelemetry/instrumentation-tedious@0.36.0': + resolution: {integrity: sha512-CP4el0m8YFGUAfzcAnzT2+kvlvs3EKeMFdhF43c4cgMPvZrKHl/9G9H81sgXciZZrw1avBQDQ/dlqLN1vFK6HQ==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': ^1.3.0 + + '@opentelemetry/instrumentation-undici@0.27.0': + resolution: {integrity: sha512-W69Vjtj9ts16An94KrKO6OOxrEkEXOolhVjHK8qibtDhxtWrmaB/qnhVdk1qrSZ9p63cnabC8XSc3FsHxJd3Jw==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': ^1.7.0 + + '@opentelemetry/instrumentation-winston@0.61.0': + resolution: {integrity: sha512-H600rWjFPFXK0UDGmWucEcbylXKI9i+7i2g8LGSkYqU44PCw09xFhKwv4VPTvyGmcEsCIlckbIKj/GvYy0UeEw==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': ^1.3.0 + + '@opentelemetry/instrumentation@0.217.0': + resolution: {integrity: sha512-24ucQMjz7Y34Kw3trbxL2ZrssbtgWnR+Clpaa+YdeWuuyH3Cvk23Q03PcQvqiZrDvt8AmQmjgg9v6Y9PHoxG7w==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': ^1.3.0 + + '@opentelemetry/otlp-exporter-base@0.217.0': + resolution: {integrity: sha512-eYfqnB3UhKu/5frhd1R6+FprKygbhkomuaceMXDyzxbfXB9tKgZOVmjaJ02CkLA6Tdzumxl+e2H+vo2a8jiMPQ==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': ^1.3.0 + + '@opentelemetry/otlp-grpc-exporter-base@0.217.0': + resolution: {integrity: sha512-7RTAdZuOsCDnsyqTCG4+bDzrfnsWdzkRs7z0AVi/V3tEQx0oKeyc+OuRWYxnRsmaJXgxcmB8vb/lfxn58Dj6Ag==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': ^1.3.0 + + '@opentelemetry/otlp-transformer@0.217.0': + resolution: {integrity: sha512-MKK8UHKFUOGAvbZRWh90MhwHG+Fxm6OROBdjKPCF+HQobjuJ/Kuf8Chs8CR45X1aqotxrMj7OxTdsXe8sXuGVA==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': ^1.3.0 + + '@opentelemetry/propagator-b3@2.7.1': + resolution: {integrity: sha512-RJid6E2CKyeGfKBzXKF21ejabGMHypFkPAh3qZ+NvI+SGjuIye79t3PmiqcDgtRzdKH6ynXzbfslQ8DfpRUg2A==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': '>=1.0.0 <1.10.0' + + '@opentelemetry/propagator-jaeger@2.7.1': + resolution: {integrity: sha512-KMjVBHzP4N60bOzxja76M1F1hZZ43lGPga5ix+mkv9+kk1nx9SbkxSvJsMbuVUxdPQmsPTqGShmhN8ulrMOg6Q==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': '>=1.0.0 <1.10.0' + + '@opentelemetry/redis-common@0.38.3': + resolution: {integrity: sha512-VCghU1JYs/4gP6Gqf/xro9MEsZ7LrMv2uONVsaESKL38ZOB9BqnI98FfS23wjMnHlpuE+TTaWSoAVNpTwYXzjw==} + engines: {node: ^18.19.0 || >=20.6.0} + + '@opentelemetry/resource-detector-alibaba-cloud@0.33.7': + resolution: {integrity: sha512-rLTLRDg2uehLfrhxmSWRYJMRFRVxmsUAbV/Qhw4PWD2LvW/XMRW5sfGzU3ZFyOXND/1HkYqGv3vtl5z009uhFQ==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': ^1.0.0 + + '@opentelemetry/resource-detector-aws@2.17.0': + resolution: {integrity: sha512-IpeUtIdKW5PQsO2Ylfvxqr5ZawoObKigzQtEbT96NxSs7gej8Fp4LQZuooGAGu5H+qAI8/wjCZWLlJk2FT1Uzg==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': ^1.0.0 + + '@opentelemetry/resource-detector-azure@0.25.0': + resolution: {integrity: sha512-e/NRDC3W2NYfxXJIto38wF1qgEyWpelaKp6QPIHGn320EknobnHCI7gUuoOAB6J2EJaW9ewNTIoDWDhO3AbdKA==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': ^1.0.0 + + '@opentelemetry/resource-detector-container@0.8.8': + resolution: {integrity: sha512-8FG0la3ffB/bktmtGiKePmV9+yAQbj3LqdBHYoDkdq41X3kig/7MH0nN91J1Wn56fos8ifga/yiN3ZhDzXTnAA==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': ^1.0.0 + + '@opentelemetry/resource-detector-gcp@0.52.0': + resolution: {integrity: sha512-6rOAZoUDqgrqHdR+JhIT5eBtLe5XkDoivQytDl0KvCSxF9Pq63ZdGfX90y9TJE1IlcoKF+pS1FQiO/0DCQGyBw==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': ^1.0.0 + + '@opentelemetry/resources@2.7.1': + resolution: {integrity: sha512-DeT6KKolmC4e/dRQvMQ/RwlnzhaqeiFOXY5ngoOPJ07GgVVKxZOg9EcrNZb5aTzUn+iCrJldAgOfQm1O/QfPAQ==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': '>=1.3.0 <1.10.0' + + '@opentelemetry/sdk-logs@0.217.0': + resolution: {integrity: sha512-BB+PcHItcZDL63dPMW+mJvwN9rk37wuIDjRxbVlg6pPDvDR/7GL7UJHbGsllgoggOoTimsKgENaWPoGch/oE1A==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': '>=1.4.0 <1.10.0' + + '@opentelemetry/sdk-metrics@2.7.1': + resolution: {integrity: sha512-MpDJdkiFDs3Pm1RHO3KByuZbuBdJEXEAkiC0+yJdsZGVCdf1RpHR6n+LHDcS7ffmfrt5kVCzJSCfm4z2C7v0uQ==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': '>=1.9.0 <1.10.0' + + '@opentelemetry/sdk-node@0.217.0': + resolution: {integrity: sha512-K/60pSv42+NQiZKy1pAH18nYDkxltsDV4O3SJ233J0E9raU1ksyL9gsKuS8p30bYBb4AMPCfDuutHQaHYpcv0Q==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': '>=1.3.0 <1.10.0' + + '@opentelemetry/sdk-trace-base@2.7.1': + resolution: {integrity: sha512-NAYIlsF8MPUsKqJMiDQJTMPOmlbawC1Iz/omMLygZ1C9am8fTKYjTaI+OZM+WTY3t3Glo0wnOg/6/pac6RGPPw==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': '>=1.3.0 <1.10.0' + + '@opentelemetry/sdk-trace-node@2.7.1': + resolution: {integrity: sha512-pCpQxU68lV+I9s9svqMyVu5iHdDDUnqUpSxqwyCU8A9ejEsSnMPCbearwsUO4yk08ZJzAIUCFuReMdVQvHrdvg==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': '>=1.0.0 <1.10.0' + + '@opentelemetry/semantic-conventions@1.40.0': + resolution: {integrity: sha512-cifvXDhcqMwwTlTK04GBNeIe7yyo28Mfby85QXFe1Yk8nmi36Ab/5UQwptOx84SsoGNRg+EVSjwzfSZMy6pmlw==} + engines: {node: '>=14'} + + '@opentelemetry/sql-common@0.41.2': + resolution: {integrity: sha512-4mhWm3Z8z+i508zQJ7r6Xi7y4mmoJpdvH0fZPFRkWrdp5fq7hhZ2HhYokEOLkfqSMgPR4Z9EyB3DBkbKGOqZiQ==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': ^1.1.0 + '@oxc-project/runtime@0.115.0': resolution: {integrity: sha512-Rg8Wlt5dCbXhQnsXPrkOjL1DTSvXLgb2R/KYfnf1/K+R0k6UMLEmbQXPM+kwrWqSmWA2t0B1EtHy2/3zikQpvQ==} engines: {node: ^20.19.0 || >=22.12.0} @@ -2664,6 +3158,9 @@ packages: peerDependencies: typescript: ^3 || ^4 || ^5 + '@pinojs/redact@0.4.0': + resolution: {integrity: sha512-k2ENnmBugE/rzQfEcdWHcCY+/FM3VLzH9cYEsbdsoqrvzAKRhUZeRNhAZvB8OitQJ1TBed3yqWtdjzS6wJKBwg==} + '@pkgjs/parseargs@0.11.0': resolution: {integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==} engines: {node: '>=14'} @@ -2701,6 +3198,36 @@ packages: '@prisma/get-platform@5.22.0': resolution: {integrity: sha512-pHhpQdr1UPFpt+zFfnPazhulaZYCUqeIcPpJViYoq9R+D/yw4fjE+CtnsnKzPYm0ddUbeXUzjGVGIRVgPDCk4Q==} + '@protobufjs/aspromise@1.1.2': + resolution: {integrity: sha512-j+gKExEuLmKwvz3OgROXtrJ2UG2x8Ch2YZUxahh+s1F2HZ+wAceUNLkvy6zKCPVRkU++ZWQrdxsUeQXmcg4uoQ==} + + '@protobufjs/base64@1.1.2': + resolution: {integrity: sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg==} + + '@protobufjs/codegen@2.0.5': + resolution: {integrity: sha512-zgXFLzW3Ap33e6d0Wlj4MGIm6Ce8O89n/apUaGNB/jx+hw+ruWEp7EwGUshdLKVRCxZW12fp9r40E1mQrf/34g==} + + '@protobufjs/eventemitter@1.1.0': + resolution: {integrity: sha512-j9ednRT81vYJ9OfVuXG6ERSTdEL1xVsNgqpkxMsbIabzSo3goCjDIveeGv5d03om39ML71RdmrGNjG5SReBP/Q==} + + '@protobufjs/fetch@1.1.0': + resolution: {integrity: sha512-lljVXpqXebpsijW71PZaCYeIcE5on1w5DlQy5WH6GLbFryLUrBD4932W/E2BSpfRJWseIL4v/KPgBFxDOIdKpQ==} + + '@protobufjs/float@1.0.2': + resolution: {integrity: sha512-Ddb+kVXlXst9d+R9PfTIxh1EdNkgoRe5tOX6t01f1lYWOvJnSPDBlG241QLzcyPdoNTsblLUdujGSE4RzrTZGQ==} + + '@protobufjs/inquire@1.1.1': + resolution: {integrity: sha512-mnzgDV26ueAvk7rsbt9L7bE0SuAoqyuys/sMMrmVcN5x9VsxpcG3rqAUSgDyLp0UZlmNfIbQ4fHfCtreVBk8Ew==} + + '@protobufjs/path@1.1.2': + resolution: {integrity: sha512-6JOcJ5Tm08dOHAbdR3GrvP+yUUfkjG5ePsHYczMFLq3ZmMkAD98cDgcT2iA1lJ9NVwFd4tH/iSSoe44YWkltEA==} + + '@protobufjs/pool@1.1.0': + resolution: {integrity: sha512-0kELaGSIDBKvcgS4zkjz1PeddatrjYcmMWOlAuAPwAeccUrPHdUqo/J6LiymHHEiJT5NrF1UVwxY14f+fy4WQw==} + + '@protobufjs/utf8@1.1.1': + resolution: {integrity: sha512-oOAWABowe8EAbMyWKM0tYDKi8Yaox52D+HWZhAIJqQXbqe0xI/GV7FhLWqlEKreMkfDjshR5FKgi3mnle0h6Eg==} + '@rolldown/binding-android-arm64@1.0.0-rc.17': resolution: {integrity: sha512-s70pVGhw4zqGeFnXWvAzJDlvxhlRollagdCCKRgOsgUOH3N1l0LIxf83AtGzmb5SiVM4Hjl5HyarMRfdfj3DaQ==} engines: {node: ^20.19.0 || >=22.12.0} @@ -3256,6 +3783,9 @@ packages: '@tybys/wasm-util@0.9.0': resolution: {integrity: sha512-6+7nlbMVX/PVDCwaIQ8nTOPveOcFLSt8GcXdx8hD0bt39uWxYT88uXzqTd4fTvqta7oeUJqudepapKNt2DYJFw==} + '@types/aws-lambda@8.10.161': + resolution: {integrity: sha512-rUYdp+MQwSFocxIOcSsYSF3YYYC/uUpMbCY/mbO21vGqfrEYvNSoPyKYDj6RhXXpPfS0KstW9RwG3qXh9sL7FQ==} + '@types/babel__core@7.20.5': resolution: {integrity: sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==} @@ -3274,6 +3804,9 @@ packages: '@types/bonjour@3.5.13': resolution: {integrity: sha512-z9fJ5Im06zvUL548KvYNecEVlA7cVDkGUi6kZusb04mpyEFKCIZJvloCcmpmLaIahDpOQGHaHmG6imtPMmPXGQ==} + '@types/bunyan@1.8.11': + resolution: {integrity: sha512-758fRH7umIMk5qt5ELmRMff4mLDlN+xyYzC+dkPTdKwbSkJFvz6xwyScrytPU0QIBbRRwbiE8/BIg8bpajerNQ==} + '@types/chai@5.2.3': resolution: {integrity: sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA==} @@ -3331,18 +3864,33 @@ packages: '@types/luxon@3.7.1': resolution: {integrity: sha512-H3iskjFIAn5SlJU7OuxUmTEpebK6TKB8rxZShDslBMZJ5u9S//KM1sbdAisiSrqwLQncVjnpi2OK2J51h+4lsg==} + '@types/memcached@2.2.10': + resolution: {integrity: sha512-AM9smvZN55Gzs2wRrqeMHVP7KE8KWgCJO/XL5yCly2xF6EKa4YlbpK+cLSAH4NG/Ah64HrlegmGqW8kYws7Vxg==} + '@types/mime@1.3.5': resolution: {integrity: sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w==} + '@types/mysql@2.15.27': + resolution: {integrity: sha512-YfWiV16IY0OeBfBCk8+hXKmdTKrKlwKN1MNKAPBu5JYxLwBEZl7QzeEpGnlZb3VMGJrrGmB84gXiH+ofs/TezA==} + '@types/node-forge@1.3.14': resolution: {integrity: sha512-mhVF2BnD4BO+jtOp7z1CdzaK4mbuK0LLQYAvdOLqHTavxFNq4zA1EmYkpnFjP8HOUzedfQkRnp0E2ulSAYSzAw==} '@types/node@20.19.9': resolution: {integrity: sha512-cuVNgarYWZqxRJDQHEB58GEONhOK79QVR/qYx4S7kcUObQvUwvFnYxJuuHUKm2aieN9X3yZB4LZsuYNU1Qphsw==} + '@types/oracledb@6.5.2': + resolution: {integrity: sha512-kK1eBS/Adeyis+3OlBDMeQQuasIDLUYXsi2T15ccNJ0iyUpQ4xDF7svFu3+bGVrI0CMBUclPciz+lsQR3JX3TQ==} + '@types/parse-json@4.0.2': resolution: {integrity: sha512-dISoDXWWQwUquiKsyZ4Ng+HX2KsPL7LyHKHQwgGFEA3IaKac4Obd+h2a/a6waisAoepJlBcx9paWqjA8/HVjCw==} + '@types/pg-pool@2.0.7': + resolution: {integrity: sha512-U4CwmGVQcbEuqpyju8/ptOKg6gEC+Tqsvj2xS9o1g71bUh8twxnC6ZL5rZKCsGN0iyH0CwgUyc9VR5owNQF9Ng==} + + '@types/pg@8.15.6': + resolution: {integrity: sha512-NoaMtzhxOrubeL/7UZuNTrejB4MPAJ0RpxZqXQf2qXuVlTPuG6Y8p4u9dKRaue4yjmC7ZhzVO2/Yyyn25znrPQ==} + '@types/qs@6.15.0': resolution: {integrity: sha512-JawvT8iBVWpzTrz3EGw9BTQFg3BQNmwERdKE22vlTxawwtbyUSlMppvZYKLZzB5zgACXdXxbD3m1bXaMqP/9ow==} @@ -3376,6 +3924,9 @@ packages: '@types/stack-utils@2.0.3': resolution: {integrity: sha512-9aEbYZ3TbYMznPdcdr3SmIrLXwC/AKZXQeCf9Pgao5CKb8CyHuEX5jzWPTkvregvhRJHcpRO6BFoGW9ycaOkYw==} + '@types/tedious@4.0.14': + resolution: {integrity: sha512-KHPsfX/FoVbUGbyYvk1q9MMQHLPeRZhRJZdO45Q4YjvFkv4hMNghCWTvy7rdKessBsmtz4euWCWAB6/tVpI1Iw==} + '@types/whatwg-mimetype@3.0.2': resolution: {integrity: sha512-c2AKvDT8ToxLIOUlN51gTiHXflsfIFisS4pO7pDPoKouJCESkhZnEy623gwP9laCy5lnLDAw1vAzu2vM2YLOrA==} @@ -3684,6 +4235,11 @@ packages: resolution: {integrity: sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==} engines: {node: '>= 0.6'} + acorn-import-attributes@1.9.5: + resolution: {integrity: sha512-n02Vykv5uA3eHGM/Z2dQrcD56kL8TyDb2p1+0P83PClMnC/nc+anbQRhIOWnSq4Ke/KvDPrY3C9hDtC/A3eHnQ==} + peerDependencies: + acorn: ^8 + acorn-import-phases@1.0.4: resolution: {integrity: sha512-wKmbr/DDiIXzEOiWrTTUcDm24kQ2vGfZQvM2fwg2vXqR5uW6aapr7ObPtj1th32b9u90/Pf4AItvdTh42fBmVQ==} engines: {node: '>=10.13.0'} @@ -3843,6 +4399,10 @@ packages: asynckit@0.4.0: resolution: {integrity: sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==} + atomic-sleep@1.0.0: + resolution: {integrity: sha512-kNOjDqAh7px0XWNI+4QbzoiR/nTkHAWNud2uvnJquD1/x5a7EQZMJT0AczqK0Qn67oY/TTQ1LbUKajZpp3I9tQ==} + engines: {node: '>=8.0.0'} + autoprefixer@10.5.0: resolution: {integrity: sha512-FMhOoZV4+qR6aTUALKX2rEqGG+oyATvwBt9IIzVR5rMa2HRWPkxf+P+PAJLD1I/H5/II+HuZcBJYEFBpq39ong==} engines: {node: ^10 || ^12 || >=14} @@ -3959,6 +4519,9 @@ packages: big.js@5.2.2: resolution: {integrity: sha512-vyL2OymJxmarO8gxMr0mhChsO9QGwhynfuu4+MHTAW6czfq9humCB7rKpUjDd9YUiDPU4mzpyupFSvOClAwbmQ==} + bignumber.js@9.3.1: + resolution: {integrity: sha512-Ko0uX15oIUS7wJ3Rb30Fs6SkVbLmPBAKdlm7q9+ak9bbIeFf0MwuBsQV6z7+X768/cHsfg+WlysDWJcmthjsjQ==} + binary-extensions@2.3.0: resolution: {integrity: sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==} engines: {node: '>=8'} @@ -4405,10 +4968,17 @@ packages: resolution: {integrity: sha512-7D2EPVltRrsTkhpQmksIu+LxeWAIEk6wRDMJ1qljlv+CKHJM+cJLlfhWIzNA44eAsHXSNe3+vO6DW1yCYx8SuQ==} engines: {node: '>=20'} + data-uri-to-buffer@4.0.1: + resolution: {integrity: sha512-0R9ikRb668HB7QDxT1vkpuUBtqc53YyAwMwGeUFKRojY/NWKvdZ+9UYtRfGmhqNbRkTSVpMbmyhXipFFv2cb/A==} + engines: {node: '>= 12'} + data-urls@6.0.1: resolution: {integrity: sha512-euIQENZg6x8mj3fO6o9+fOW8MimUI4PpD/fZBhJfeioZVy9TUpM4UY7KjQNVZFlqwJ0UdzRDzkycB997HEq1BQ==} engines: {node: '>=20'} + dateformat@4.6.3: + resolution: {integrity: sha512-2P0p0pFGzHS5EMnhdxQi7aJN+iMheud0UhG4dlE1DLAlvL8JHjJJTX/CSm4JXwV0Ka5nGk3zC5mcb5bUQUxxMA==} + debug@2.6.9: resolution: {integrity: sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==} peerDependencies: @@ -4820,10 +5390,16 @@ packages: resolution: {integrity: sha512-hIS4idWWai69NezIdRt2xFVofaF4j+6INOpJlVOLDO8zXGpUVEVzIYk12UUi2JzjEzWL3IOAxcTubgz9Po0yXw==} engines: {node: '>= 18'} + extend@3.0.2: + resolution: {integrity: sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==} + fast-check@4.7.0: resolution: {integrity: sha512-NsZRtqvSSoCP0HbNjUD+r1JH8zqZalyp6gLY9e7OYs7NK9b6AHOs2baBFeBG7bVNsuoukh89x2Yg3rPsul8ziQ==} engines: {node: '>=12.17.0'} + fast-copy@4.0.3: + resolution: {integrity: sha512-58apWr0GUiDFM8+3afrO6eYwJBn9ZAhDOzG3L+/9llab/haCARS2UIfffmOurYLwbgDRs8n0rfr6qAAPEAuAQw==} + fast-deep-equal@3.1.3: resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==} @@ -4859,6 +5435,10 @@ packages: picomatch: optional: true + fetch-blob@3.2.0: + resolution: {integrity: sha512-7yAQpD2UMJzLi1Dqv7qFYnPbaPx7ZfFK6PiIxQ4PfkGPyNyl2Ugx+a/umUonmKqjhM4DnfbMvdX6otXq83soQQ==} + engines: {node: ^12.20 || >= 14.13} + figures@3.2.0: resolution: {integrity: sha512-yaduQFRKLXYOGgEn6AZau90j3ggSOyiqXU0F9JZfeXYhNa+Jk4X+s45A2zg5jns87GAFa34BBm2kXw4XpNcbdg==} engines: {node: '>=8'} @@ -4942,6 +5522,13 @@ packages: resolution: {integrity: sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w==} engines: {node: '>= 6'} + formdata-polyfill@4.0.10: + resolution: {integrity: sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g==} + engines: {node: '>=12.20.0'} + + forwarded-parse@2.1.2: + resolution: {integrity: sha512-alTFZZQDKMporBH77856pXgzhEzaUVmLCDk+egLgIgHst3Tpndzz8MnKe+GzRJRfvVdn69HhpW7cmXzvtLvJAw==} + forwarded@0.2.0: resolution: {integrity: sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==} engines: {node: '>= 0.6'} @@ -4987,6 +5574,14 @@ packages: function-bind@1.1.2: resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==} + gaxios@7.1.4: + resolution: {integrity: sha512-bTIgTsM2bWn3XklZISBTQX7ZSddGW+IO3bMdGaemHZ3tbqExMENHLx6kKZ/KlejgrMtj8q7wBItt51yegqalrA==} + engines: {node: '>=18'} + + gcp-metadata@8.1.2: + resolution: {integrity: sha512-zV/5HKTfCeKWnxG0Dmrw51hEWFGfcF2xiXqcA3+J90WDuP0SvoiSO5ORvcBsifmx/FoIjgQN3oNOGaQ5PhLFkg==} + engines: {node: '>=18'} + gensync@1.0.0-beta.2: resolution: {integrity: sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==} engines: {node: '>=6.9.0'} @@ -5068,6 +5663,10 @@ packages: resolution: {integrity: sha512-7ACyT3wmyp3I61S4fG682L0VA2RGD9otkqGJIwNUMF1SWUombIIk+af1unuDYgMm082aHYwD+mzJvv9Iu8dsgg==} engines: {node: '>=18'} + google-logging-utils@1.1.3: + resolution: {integrity: sha512-eAmLkjDjAFCVXg7A1unxHsLf961m6y17QFqXqAXGj/gVkKFrEICfStRfwUlGNfeCEjNRa32JEWOUTlYXPyyKvA==} + engines: {node: '>=14'} + gopd@1.2.0: resolution: {integrity: sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==} engines: {node: '>= 0.4'} @@ -5114,6 +5713,9 @@ packages: resolution: {integrity: sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==} hasBin: true + help-me@5.0.0: + resolution: {integrity: sha512-7xgomUX6ADmcYzFik0HzAxh/73YlKR9bmFzf51CZwR+b6YtzU2m0u49hQCqV6SvlqIqsaxovfwdvbnsw3b/zpg==} + homedir-polyfill@1.0.3: resolution: {integrity: sha512-eSmmWE5bZTK2Nou4g0AI3zZ9rswp7GRKoKXS1BLUkvPviOqs4YTN1djQIqrXy9k5gEtdLPy86JjRwsNM9tnDcA==} engines: {node: '>=0.10.0'} @@ -5247,6 +5849,10 @@ packages: resolution: {integrity: sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==} engines: {node: '>=6'} + import-in-the-middle@3.0.1: + resolution: {integrity: sha512-pYkiyXVL2Mf3pozdlDGV6NAObxQx13Ae8knZk1UJRJ6uRW/ZRmTGHlQYtrsSl7ubuE5F8CD1z+s1n4RHNuTtuA==} + engines: {node: '>=18'} + import-local@3.2.0: resolution: {integrity: sha512-2SPlun1JUPWoM6t3F0dw0FkCF/jWY8kttcY4f599GLTSjh2OCuuhdTkJQsEcZzBqbXZGKMK2OqW1oZsjtf/gQA==} engines: {node: '>=8'} @@ -5597,6 +6203,10 @@ packages: jose@6.2.3: resolution: {integrity: sha512-YYVDInQKFJfR/xa3ojUTl8c2KoTwiL1R5Wg9YCydwH0x0B9grbzlg5HC7mMjCtUJjbQ/YnGEZIhI5tCgfTb4Hw==} + joycon@3.1.1: + resolution: {integrity: sha512-34wB/Y7MW7bzjKRjUKTa46I2Z7eV62Rkhva+KkopW7Qvv/OSWBqvkSY7vusOPrNuZcUG3tApvdVgNB8POj3SPw==} + engines: {node: '>=10'} + js-tokens@10.0.0: resolution: {integrity: sha512-lM/UBzQmfJRo9ABXbPWemivdCW8V2G8FHaHdypQaIy523snUjog0W71ayWXTjiR+ixeMyVHN2XcpnTd/liPg/Q==} @@ -5625,6 +6235,9 @@ packages: engines: {node: '>=6'} hasBin: true + json-bigint@1.0.0: + resolution: {integrity: sha512-SiPv/8VpZuWbvLSMtTDU8hEfrZWg/mH/nV/b4o0CYbSxu1UIQPLdwKOCIyLQX+VIPO5vrLX3i8qtqFyhdPSUSQ==} + json-buffer@3.0.1: resolution: {integrity: sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==} @@ -5830,6 +6443,9 @@ packages: resolution: {integrity: sha512-gvVijfZvn7R+2qyPX8mAuKcFGDf6Nc61GdvGafQsHL0sBIxfKzA+usWn4GFC/bk+QdwPUD4kWFJLhElipq+0VA==} engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + lodash.camelcase@4.3.0: + resolution: {integrity: sha512-TwuEnCnxbc3rAvhf/LbG7tJUDzhqXyFnv3dtzLOPgCG/hODL7WFnsbwktkD7yUV0RrreP/l1PALq/YSg6VvjlA==} + lodash.debounce@4.0.8: resolution: {integrity: sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==} @@ -5863,6 +6479,9 @@ packages: long-timeout@0.1.1: resolution: {integrity: sha512-BFRuQUqc7x2NWxfJBCyUrN8iYUYznzL9JROmRz1gZ6KlOIgmoD+njPVbb+VNn2nGMKggMsK79iUNErillsrx7w==} + long@5.3.2: + resolution: {integrity: sha512-mNAgZ1GmyNhD7AuqnTG3/VQ26o760+ZYBPKjPvugO8+nLbYfX6TVpJPseBvopbdY+qpZ/lKUnmEc1LeZYS3QAA==} + lru-cache@10.4.3: resolution: {integrity: sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==} @@ -6033,6 +6652,9 @@ packages: resolution: {integrity: sha512-KZxYo1BUkWD2TVFLr0MQoM8vUUigWD3LlD83a/75BqC+4qE0Hb1Vo5v1FgcfaNXvfXzr+5EhQ6ing/CaBijTlw==} engines: {node: '>= 18'} + module-details-from-path@1.0.4: + resolution: {integrity: sha512-EGWKgxALGMgzvxYF1UyGTy0HXX/2vHLkw6+NvDKW2jypWbHpjQuj4UMcqQWXHERJhVGKikolT06G3bcKe4fi7w==} + mrmime@2.0.1: resolution: {integrity: sha512-Y3wQdFg2Va6etvQ5I82yUhGdsKrcYox6p7FfL1LbK2J4V01F9TGlepTIhnK24t7koZibmg82KGglhA1XK5IsLQ==} engines: {node: '>=10'} @@ -6098,6 +6720,15 @@ packages: neo-async@2.6.2: resolution: {integrity: sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==} + nestjs-pino@4.6.1: + resolution: {integrity: sha512-nuARXa0xpdJ1lY2+fgycIQr6H3g0VgqAWNK3xMYjOFcj2DoPETNXj0lV3Y86nRuI7BUfQp5PGiVoZvT4dTWbpQ==} + engines: {node: '>= 14'} + peerDependencies: + '@nestjs/common': ^8.0.0 || ^9.0.0 || ^10.0.0 || ^11.0.0 + pino: ^7.5.0 || ^8.0.0 || ^9.0.0 || ^10.0.0 + pino-http: ^6.4.0 || ^7.0.0 || ^8.0.0 || ^9.0.0 || ^10.0.0 || ^11.0.0 + rxjs: ^7.1.0 + node-abort-controller@3.1.1: resolution: {integrity: sha512-AGK2yQKIjRuqnc6VkX2Xj5d+QW8xZ87pa1UK6yA6ouUyuxfHuMP6umE5QK7UmTeOAymo+Zx1Fxiuw9rVx8taHQ==} @@ -6107,6 +6738,11 @@ packages: node-addon-api@7.1.1: resolution: {integrity: sha512-5m3bsyrjFWE1xf7nz7YXdN4udnVtXK6/Yfgn5qnahL6bCkf2yKt4k3nuTKAtT4r3IG8JNR2ncsIMdZuAzJjHQQ==} + node-domexception@1.0.0: + resolution: {integrity: sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==} + engines: {node: '>=10.5.0'} + deprecated: Use your platform's native DOMException instead + node-fetch@2.7.0: resolution: {integrity: sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==} engines: {node: 4.x || >=6.0.0} @@ -6116,6 +6752,10 @@ packages: encoding: optional: true + node-fetch@3.3.2: + resolution: {integrity: sha512-dRB78srN/l6gqWulah9SrxeYnxeddIG30+GOqK/9OlLVyLg3HPnr6SqOWTWOXKRwC2eGYCkZ59NNuSgvSrpgOA==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + node-forge@1.4.0: resolution: {integrity: sha512-LarFH0+6VfriEhqMMcLX2F7SwSXeWwnEAJEsYm5QKWchiVYVvJyV9v7UDvUv+w5HO23ZpQTXDv/GxdDdMyOuoQ==} engines: {node: '>= 6.13.0'} @@ -6209,6 +6849,10 @@ packages: obug@2.1.1: resolution: {integrity: sha512-uTqF9MuPraAQ+IsnPf366RG4cP9RtUi7MLO1N3KEc+wb0a6yKpeL0lmk2IB1jY5KHPAlTc6T/JRdC/YqxHNwkQ==} + on-exit-leak-free@2.1.2: + resolution: {integrity: sha512-0eJJY6hXLGf1udHwfNftBqH+g73EU4B504nZeKpz1sYRKafAghwxEJunB2O7rDZkL4PGfsMVnTXZ2EjibbqcsA==} + engines: {node: '>=14.0.0'} + on-finished@2.4.1: resolution: {integrity: sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==} engines: {node: '>= 0.8'} @@ -6378,6 +7022,17 @@ packages: pathe@2.0.3: resolution: {integrity: sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==} + pg-int8@1.0.1: + resolution: {integrity: sha512-WCtabS6t3c8SkpDBUlb1kjOs7l66xsGdKpIPZsg4wR+B3+u9UAum2odSsF9tnvxg80h4ZxLWMy4pRjOsFIqQpw==} + engines: {node: '>=4.0.0'} + + pg-protocol@1.13.0: + resolution: {integrity: sha512-zzdvXfS6v89r6v7OcFCHfHlyG/wvry1ALxZo4LqgUoy7W9xhBDMaqOuMiF3qEV45VqsN6rdlcehHrfDtlCPc8w==} + + pg-types@2.2.0: + resolution: {integrity: sha512-qTAAlrEsl8s4OiEQY69wDvcMIdQN6wdz5ojQiOy6YRMuynxenON0O5oCpJI6lshc6scgAY8qvJ2On/p+CXY0GA==} + engines: {node: '>=4'} + picocolors@1.1.1: resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==} @@ -6397,6 +7052,23 @@ packages: resolution: {integrity: sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==} engines: {node: '>=6'} + pino-abstract-transport@3.0.0: + resolution: {integrity: sha512-wlfUczU+n7Hy/Ha5j9a/gZNy7We5+cXp8YL+X+PG8S0KXxw7n/JXA3c46Y0zQznIJ83URJiwy7Lh56WLokNuxg==} + + pino-http@11.0.0: + resolution: {integrity: sha512-wqg5XIAGRRIWtTk8qPGxkbrfiwEWz1lgedVLvhLALudKXvg1/L2lTFgTGPJ4Z2e3qcRmxoFxDuSdMdMGNM6I1g==} + + pino-pretty@13.1.3: + resolution: {integrity: sha512-ttXRkkOz6WWC95KeY9+xxWL6AtImwbyMHrL1mSwqwW9u+vLp/WIElvHvCSDg0xO/Dzrggz1zv3rN5ovTRVowKg==} + hasBin: true + + pino-std-serializers@7.1.0: + resolution: {integrity: sha512-BndPH67/JxGExRgiX1dX0w1FvZck5Wa4aal9198SrRhZjH3GxKQUKIBnYJTdj2HDN3UQAS06HlfcSbQj2OHmaw==} + + pino@10.3.1: + resolution: {integrity: sha512-r34yH/GlQpKZbU1BvFFqOjhISRo1MNx1tWYsYvmj6KIRHSPMT2+yHOEb1SG6NMvRoHRF0a07kCOox/9yakl1vg==} + hasBin: true + pirates@4.0.7: resolution: {integrity: sha512-TfySrs/5nm8fQJDcBDuUng3VOUKsd7S+zqvbOTiGXHfxX4wK31ard+hoNuvkicM/2YFzlpDgABOevKSsB4G/FA==} engines: {node: '>= 6'} @@ -6664,6 +7336,22 @@ packages: resolution: {integrity: sha512-SoSL4+OSEtR99LHFZQiJLkT59C5B1amGO1NzTwj7TT1qCUgUO6hxOvzkOYxD+vMrXBM3XJIKzokoERdqQq/Zmg==} engines: {node: ^10 || ^12 || >=14} + postgres-array@2.0.0: + resolution: {integrity: sha512-VpZrUqU5A69eQyW2c5CA1jtLecCsN2U/bD6VilrFDWq5+5UIEVO7nazS3TEcHf1zuPYO/sqGvUvW62g86RXZuA==} + engines: {node: '>=4'} + + postgres-bytea@1.0.1: + resolution: {integrity: sha512-5+5HqXnsZPE65IJZSMkZtURARZelel2oXUEO8rH83VS/hxH5vv1uHquPg5wZs8yMAfdv971IU+kcPUczi7NVBQ==} + engines: {node: '>=0.10.0'} + + postgres-date@1.0.7: + resolution: {integrity: sha512-suDmjLVQg78nMK2UZ454hAG+OAW+HQPZ6n++TNDUX+L0+uUlLywnoxJKDou51Zm+zTCjrCl0Nq6J9C5hP9vK/Q==} + engines: {node: '>=0.10.0'} + + postgres-interval@1.2.0: + resolution: {integrity: sha512-9ZhXKM/rw350N1ovuWHbGxnGh/SNJ4cnxHiM0rxE4VN41wsg8P8zWn9hv/buK00RP4WvlOyr/RBDiptyxVbkZQ==} + engines: {node: '>=0.10.0'} + prelude-ls@1.2.1: resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==} engines: {node: '>= 0.8.0'} @@ -6689,10 +7377,21 @@ packages: process-nextick-args@2.0.1: resolution: {integrity: sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==} + process-warning@5.0.0: + resolution: {integrity: sha512-a39t9ApHNx2L4+HBnQKqxxHNs1r7KF+Intd8Q/g1bUh6q0WIp9voPXJ/x0j+ZL45KF1pJd9+q2jLIRMfvEshkA==} + promise-retry@2.0.1: resolution: {integrity: sha512-y+WKFlBR8BGXnsNlIHFGPZmyDf3DFMoLhaflAnyZgV6rG6xu+JwesTo2Q9R6XwYmtmwAFCkAk3e35jEdoeh/3g==} engines: {node: '>=10'} + protobufjs@7.5.6: + resolution: {integrity: sha512-M71sTMB146U3u0di3yup8iM+zv8yPRNQVr1KK4tyBitl3qFvEGucq/rGDRShD2rsJhtN02RJaJ7j5X5hmy8SJg==} + engines: {node: '>=12.0.0'} + + protobufjs@8.0.1: + resolution: {integrity: sha512-NWWCCscLjs+cOKF/s/XVNFRW7Yih0fdH+9brffR5NZCy8k42yRdl5KlWKMVXuI1vfCoy4o1z80XR/W/QUb3V3w==} + engines: {node: '>=12.0.0'} + proxy-addr@2.0.7: resolution: {integrity: sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==} engines: {node: '>= 0.10'} @@ -6704,6 +7403,9 @@ packages: prr@1.0.1: resolution: {integrity: sha512-yPw4Sng1gWghHQWj0B3ZggWUm4qVbPwPFcRG8KyxiU7J2OHFSoEHKS+EZ3fv5l1t9CyCiop6l/ZYeWbrgoQejw==} + pump@3.0.4: + resolution: {integrity: sha512-VS7sjc6KR7e1ukRFhQSY5LM2uBWAUPiOPa/A3mkKmiMwSmRFUITt0xuj+/lesgnCv+dPIEYlkzrcyXgquIHMcA==} + punycode@2.3.1: resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==} engines: {node: '>=6'} @@ -6729,6 +7431,9 @@ packages: resolution: {integrity: sha512-6YHEFRL9mfgcAvql/XhwTvf5jKcOiiupt2FiJxHkiX1z4j7WL8J/jRHYLluORvc1XxB5rV20KoeK00gVJamspg==} engines: {node: '>=0.6'} + quick-format-unescaped@4.0.4: + resolution: {integrity: sha512-tYC1Q1hgyRuHgloV/YXs2w15unPVh8qfu/qCTfhTYamaw7fyhumKa2yGpdSo87vY32rIclj+4fWYQXUMs9EHvg==} + range-parser@1.2.1: resolution: {integrity: sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==} engines: {node: '>= 0.6'} @@ -6770,6 +7475,10 @@ packages: resolution: {integrity: sha512-9u/XQ1pvrQtYyMpZe7DXKv2p5CNvyVwzUB6uhLAnQwHMSgKMBR62lc7AHljaeteeHXn11XTAaLLUVZYVZyuRBQ==} engines: {node: '>= 20.19.0'} + real-require@0.2.0: + resolution: {integrity: sha512-57frrGM/OCTLqLOAh0mhVA9VBMHd+9U7Zb2THMGdBUoZVOtGbJzjxsYGDJ3A9AYYCP4hn6y1TVbaOfzWtm5GFg==} + engines: {node: '>= 12.13.0'} + rechoir@0.8.0: resolution: {integrity: sha512-/vxpCXddiX8NGfGO/mTafwjq4aFa/71pvamip0++IQk3zG8cbCj0fifNPrjjF1XMXUne91jL9OoxmdykoEtifQ==} engines: {node: '>= 10.13.0'} @@ -6814,6 +7523,10 @@ packages: resolution: {integrity: sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==} engines: {node: '>=0.10.0'} + require-in-the-middle@8.0.1: + resolution: {integrity: sha512-QT7FVMXfWOYFbeRBF6nu+I6tr2Tf3u0q8RIEjNob/heKY/nh7drD/k7eeMFmSQgnTtCzLDcCu/XEnpW2wk4xCQ==} + engines: {node: '>=9.3.0 || >=8.10.0 <9.0.0'} + requires-port@1.0.0: resolution: {integrity: sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==} @@ -6900,6 +7613,10 @@ packages: safe-buffer@5.2.1: resolution: {integrity: sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==} + safe-stable-stringify@2.5.0: + resolution: {integrity: sha512-b3rppTKm9T+PsVCBEOUR46GWI7fdOs00VKZ1+9c1EWDaDMvjQc6tUwuFyIprgGgTcWoVHSKrU8H31ZHA2e0RHA==} + engines: {node: '>=10'} + safer-buffer@2.1.2: resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==} @@ -7066,6 +7783,9 @@ packages: secure-compare@3.0.1: resolution: {integrity: sha512-AckIIV90rPDcBcglUwXPF3kg0P0qmPsPXAj6BBEENQE1p5yA1xfmDJzfi1Tappj37Pv2mVbKpL3Z1T+Nn7k1Qw==} + secure-json-parse@4.1.0: + resolution: {integrity: sha512-l4KnYfEyqYJxDwlNVyRfO2E4NTHfMKAWdUuA8J0yve2Dz/E/PdBepY03RvyJpssIpRFwJoCD55wA+mEDs6ByWA==} + seedrandom@3.0.5: resolution: {integrity: sha512-8OwmbklUNzwezjGInmZ+2clQmExQPvomqjL7LFqOYqtmuxRgQYqOD3mHaU+MvZn5FLUeVxVfQjwLZW/n/JFuqg==} @@ -7205,6 +7925,9 @@ packages: resolution: {integrity: sha512-NlGELfPrgX2f1TAAcz0WawlLn+0r3FyhhCRpFFK2CemXenPYvzMWWZINv3eDNo9ucdwme7oCHRY0Jnbs4aIkog==} engines: {node: '>= 10.0.0', npm: '>= 3.0.0'} + sonic-boom@4.2.1: + resolution: {integrity: sha512-w6AxtubXa2wTXAUsZMMWERrsIRAdrK0Sc+FUytWvYAhBJLyuI4llrMIC1DtlNSdI99EI86KZum2MMq3EAZlF9Q==} + sorted-array-functions@1.3.0: resolution: {integrity: sha512-2sqgzeFlid6N4Z2fUQ1cvFmTOLRi/sEDzSQ0OKYchqgoPmQBVyM3959qYx3fpS6Esef80KjmpgPeEr028dP3OA==} @@ -7255,6 +7978,10 @@ packages: resolution: {integrity: sha512-r46gZQZQV+Kl9oItvl1JZZqJKGr+oEkB08A6BzkiR7593/7IbtuncXHd2YoYeTsG4157ZssMu9KYvUHLcjcDoA==} engines: {node: '>=6.0.0'} + split2@4.2.0: + resolution: {integrity: sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==} + engines: {node: '>= 10.x'} + sprintf-js@1.0.3: resolution: {integrity: sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==} @@ -7344,6 +8071,10 @@ packages: resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==} engines: {node: '>=8'} + strip-json-comments@5.0.3: + resolution: {integrity: sha512-1tB5mhVo7U+ETBKNf92xT4hrQa3pm0MZ0PQvuDnWgAAGHDsfp4lPSpiS6psrSiet87wyGPh9ft6wmhOMQ0hDiw==} + engines: {node: '>=14.16'} + strtok3@10.3.5: resolution: {integrity: sha512-ki4hZQfh5rX0QDLLkOCj+h+CVNkqmp/CMf8v8kZpkNVK6jGQooMytqzLZYUVYIZcFZ6yDB70EfD8POcFXiF5oA==} engines: {node: '>=18'} @@ -7442,6 +8173,10 @@ packages: peerDependencies: tslib: ^2 + thread-stream@4.0.0: + resolution: {integrity: sha512-4iMVL6HAINXWf1ZKZjIPcz5wYaOdPhtO8ATvZ+Xqp3BTdaqtAwQkNmKORqcIo5YkQqGXq5cwfswDwMqqQNrpJA==} + engines: {node: '>=20'} + thunky@1.1.0: resolution: {integrity: sha512-eHY7nBftgThBqOyHGVN+l8gF0BucP09fMo0oO/Lb0w1OF80dJv+lDVpXG60WMQvkcxAkNybKsrEIE3ZtKGmPrA==} @@ -7891,6 +8626,10 @@ packages: weak-lru-cache@1.2.2: resolution: {integrity: sha512-DEAoo25RfSYMuTGc9vPJzZcZullwIqRDSI9LOy+fkCJPi6hykCnfKaXTuPBDuXAUcqHXyOgFtHNp/kB2FjYHbw==} + web-streams-polyfill@3.3.3: + resolution: {integrity: sha512-d2JWLCivmZYTSIoge9MsgFCZrt571BikcWGYkjC1khllbTeDlGqZ2D8vD8E/lJa8WGWbb7Plm8/XJYV7IJHZZw==} + engines: {node: '>= 8'} + webidl-conversions@3.0.1: resolution: {integrity: sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==} @@ -8088,6 +8827,10 @@ packages: xmlchars@2.2.0: resolution: {integrity: sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==} + xtend@4.0.2: + resolution: {integrity: sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==} + engines: {node: '>=0.4'} + y18n@5.0.8: resolution: {integrity: sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==} engines: {node: '>=10'} @@ -8400,7 +9143,7 @@ snapshots: less: 4.5.1 lmdb: 3.5.1 postcss: 8.5.14 - vitest: 4.1.5(@types/node@20.19.9)(@vitest/coverage-v8@4.1.5)(happy-dom@20.9.0)(jsdom@27.4.0)(vite@8.0.10(@types/node@20.19.9)(esbuild@0.27.3)(jiti@2.7.0)(less@4.5.1)(sass-embedded@1.99.0)(sass@1.99.0)(terser@5.46.2)(yaml@2.8.0)) + vitest: 4.1.5(@opentelemetry/api@1.9.1)(@types/node@20.19.9)(@vitest/coverage-v8@4.1.5)(happy-dom@20.9.0)(jsdom@27.4.0)(vite@8.0.10(@types/node@20.19.9)(esbuild@0.27.3)(jiti@2.7.0)(less@4.5.1)(sass-embedded@1.99.0)(sass@1.99.0)(terser@5.46.2)(yaml@2.8.0)) transitivePeerDependencies: - '@emnapi/core' - '@emnapi/runtime' @@ -9510,6 +10253,18 @@ snapshots: '@gar/promise-retry@1.0.3': {} + '@grpc/grpc-js@1.14.3': + dependencies: + '@grpc/proto-loader': 0.8.0 + '@js-sdsl/ordered-map': 4.4.2 + + '@grpc/proto-loader@0.8.0': + dependencies: + lodash.camelcase: 4.3.0 + long: 5.3.2 + protobufjs: 7.5.6 + yargs: 17.7.2 + '@harperfast/extended-iterable@1.0.3': optional: true @@ -9891,6 +10646,8 @@ snapshots: '@jridgewell/resolve-uri': 3.1.2 '@jridgewell/sourcemap-codec': 1.5.5 + '@js-sdsl/ordered-map@4.4.2': {} + '@jsonjoy.com/base64@1.1.2(tslib@2.8.1)': dependencies: tslib: 2.8.1 @@ -10927,7 +11684,7 @@ snapshots: tsconfig-paths: 4.2.0 tslib: 2.8.1 vite: 8.0.10(@types/node@20.19.9)(esbuild@0.27.3)(jiti@2.7.0)(less@4.5.1)(sass-embedded@1.99.0)(sass@1.99.0)(terser@5.46.2)(yaml@2.8.0) - vitest: 4.1.5(@types/node@20.19.9)(@vitest/coverage-v8@4.1.5)(happy-dom@20.9.0)(jsdom@27.4.0)(vite@8.0.10(@types/node@20.19.9)(esbuild@0.27.3)(jiti@2.7.0)(less@4.5.1)(sass-embedded@1.99.0)(sass@1.99.0)(terser@5.46.2)(yaml@2.8.0)) + vitest: 4.1.5(@opentelemetry/api@1.9.1)(@types/node@20.19.9)(@vitest/coverage-v8@4.1.5)(happy-dom@20.9.0)(jsdom@27.4.0)(vite@8.0.10(@types/node@20.19.9)(esbuild@0.27.3)(jiti@2.7.0)(less@4.5.1)(sass-embedded@1.99.0)(sass@1.99.0)(terser@5.46.2)(yaml@2.8.0)) transitivePeerDependencies: - '@babel/traverse' - '@nx/eslint' @@ -10949,7 +11706,7 @@ snapshots: optionalDependencies: '@nx/eslint': 22.7.1(aae4ffcd5669f069990aaa10ad143f8a) vite: 8.0.10(@types/node@20.19.9)(esbuild@0.27.3)(jiti@2.7.0)(less@4.5.1)(sass-embedded@1.99.0)(sass@1.99.0)(terser@5.46.2)(yaml@2.8.0) - vitest: 4.1.5(@types/node@20.19.9)(@vitest/coverage-v8@4.1.5)(happy-dom@20.9.0)(jsdom@27.4.0)(vite@8.0.10(@types/node@20.19.9)(esbuild@0.27.3)(jiti@2.7.0)(less@4.5.1)(sass-embedded@1.99.0)(sass@1.99.0)(terser@5.46.2)(yaml@2.8.0)) + vitest: 4.1.5(@opentelemetry/api@1.9.1)(@types/node@20.19.9)(@vitest/coverage-v8@4.1.5)(happy-dom@20.9.0)(jsdom@27.4.0)(vite@8.0.10(@types/node@20.19.9)(esbuild@0.27.3)(jiti@2.7.0)(less@4.5.1)(sass-embedded@1.99.0)(sass@1.99.0)(terser@5.46.2)(yaml@2.8.0)) transitivePeerDependencies: - '@babel/traverse' - '@swc-node/register' @@ -11060,6 +11817,681 @@ snapshots: - '@swc/core' - debug + '@opentelemetry/api-logs@0.217.0': + dependencies: + '@opentelemetry/api': 1.9.1 + + '@opentelemetry/api@1.9.1': {} + + '@opentelemetry/auto-instrumentations-node@0.75.0(@opentelemetry/api@1.9.1)(@opentelemetry/core@2.7.1(@opentelemetry/api@1.9.1))': + dependencies: + '@opentelemetry/api': 1.9.1 + '@opentelemetry/core': 2.7.1(@opentelemetry/api@1.9.1) + '@opentelemetry/instrumentation': 0.217.0(@opentelemetry/api@1.9.1) + '@opentelemetry/instrumentation-amqplib': 0.64.0(@opentelemetry/api@1.9.1) + '@opentelemetry/instrumentation-aws-lambda': 0.69.0(@opentelemetry/api@1.9.1) + '@opentelemetry/instrumentation-aws-sdk': 0.72.0(@opentelemetry/api@1.9.1) + '@opentelemetry/instrumentation-bunyan': 0.62.0(@opentelemetry/api@1.9.1) + '@opentelemetry/instrumentation-cassandra-driver': 0.62.0(@opentelemetry/api@1.9.1) + '@opentelemetry/instrumentation-connect': 0.60.0(@opentelemetry/api@1.9.1) + '@opentelemetry/instrumentation-cucumber': 0.33.0(@opentelemetry/api@1.9.1) + '@opentelemetry/instrumentation-dataloader': 0.34.0(@opentelemetry/api@1.9.1) + '@opentelemetry/instrumentation-dns': 0.60.0(@opentelemetry/api@1.9.1) + '@opentelemetry/instrumentation-express': 0.65.0(@opentelemetry/api@1.9.1) + '@opentelemetry/instrumentation-fs': 0.36.0(@opentelemetry/api@1.9.1) + '@opentelemetry/instrumentation-generic-pool': 0.60.0(@opentelemetry/api@1.9.1) + '@opentelemetry/instrumentation-graphql': 0.65.0(@opentelemetry/api@1.9.1) + '@opentelemetry/instrumentation-grpc': 0.217.0(@opentelemetry/api@1.9.1) + '@opentelemetry/instrumentation-hapi': 0.63.0(@opentelemetry/api@1.9.1) + '@opentelemetry/instrumentation-http': 0.217.0(@opentelemetry/api@1.9.1) + '@opentelemetry/instrumentation-ioredis': 0.65.0(@opentelemetry/api@1.9.1) + '@opentelemetry/instrumentation-kafkajs': 0.26.0(@opentelemetry/api@1.9.1) + '@opentelemetry/instrumentation-knex': 0.61.0(@opentelemetry/api@1.9.1) + '@opentelemetry/instrumentation-koa': 0.65.0(@opentelemetry/api@1.9.1) + '@opentelemetry/instrumentation-lru-memoizer': 0.61.0(@opentelemetry/api@1.9.1) + '@opentelemetry/instrumentation-memcached': 0.60.0(@opentelemetry/api@1.9.1) + '@opentelemetry/instrumentation-mongodb': 0.70.0(@opentelemetry/api@1.9.1) + '@opentelemetry/instrumentation-mongoose': 0.63.0(@opentelemetry/api@1.9.1) + '@opentelemetry/instrumentation-mysql': 0.63.0(@opentelemetry/api@1.9.1) + '@opentelemetry/instrumentation-mysql2': 0.63.0(@opentelemetry/api@1.9.1) + '@opentelemetry/instrumentation-nestjs-core': 0.63.0(@opentelemetry/api@1.9.1) + '@opentelemetry/instrumentation-net': 0.61.0(@opentelemetry/api@1.9.1) + '@opentelemetry/instrumentation-openai': 0.15.0(@opentelemetry/api@1.9.1) + '@opentelemetry/instrumentation-oracledb': 0.42.0(@opentelemetry/api@1.9.1) + '@opentelemetry/instrumentation-pg': 0.69.0(@opentelemetry/api@1.9.1) + '@opentelemetry/instrumentation-pino': 0.63.0(@opentelemetry/api@1.9.1) + '@opentelemetry/instrumentation-redis': 0.65.0(@opentelemetry/api@1.9.1) + '@opentelemetry/instrumentation-restify': 0.62.0(@opentelemetry/api@1.9.1) + '@opentelemetry/instrumentation-router': 0.61.0(@opentelemetry/api@1.9.1) + '@opentelemetry/instrumentation-runtime-node': 0.30.0(@opentelemetry/api@1.9.1) + '@opentelemetry/instrumentation-socket.io': 0.64.0(@opentelemetry/api@1.9.1) + '@opentelemetry/instrumentation-tedious': 0.36.0(@opentelemetry/api@1.9.1) + '@opentelemetry/instrumentation-undici': 0.27.0(@opentelemetry/api@1.9.1) + '@opentelemetry/instrumentation-winston': 0.61.0(@opentelemetry/api@1.9.1) + '@opentelemetry/resource-detector-alibaba-cloud': 0.33.7(@opentelemetry/api@1.9.1) + '@opentelemetry/resource-detector-aws': 2.17.0(@opentelemetry/api@1.9.1) + '@opentelemetry/resource-detector-azure': 0.25.0(@opentelemetry/api@1.9.1) + '@opentelemetry/resource-detector-container': 0.8.8(@opentelemetry/api@1.9.1) + '@opentelemetry/resource-detector-gcp': 0.52.0(@opentelemetry/api@1.9.1) + '@opentelemetry/resources': 2.7.1(@opentelemetry/api@1.9.1) + '@opentelemetry/sdk-node': 0.217.0(@opentelemetry/api@1.9.1) + transitivePeerDependencies: + - supports-color + + '@opentelemetry/configuration@0.217.0(@opentelemetry/api@1.9.1)': + dependencies: + '@opentelemetry/api': 1.9.1 + '@opentelemetry/core': 2.7.1(@opentelemetry/api@1.9.1) + yaml: 2.8.0 + + '@opentelemetry/context-async-hooks@2.7.1(@opentelemetry/api@1.9.1)': + dependencies: + '@opentelemetry/api': 1.9.1 + + '@opentelemetry/core@2.7.1(@opentelemetry/api@1.9.1)': + dependencies: + '@opentelemetry/api': 1.9.1 + '@opentelemetry/semantic-conventions': 1.40.0 + + '@opentelemetry/exporter-logs-otlp-grpc@0.217.0(@opentelemetry/api@1.9.1)': + dependencies: + '@grpc/grpc-js': 1.14.3 + '@opentelemetry/api': 1.9.1 + '@opentelemetry/core': 2.7.1(@opentelemetry/api@1.9.1) + '@opentelemetry/otlp-exporter-base': 0.217.0(@opentelemetry/api@1.9.1) + '@opentelemetry/otlp-grpc-exporter-base': 0.217.0(@opentelemetry/api@1.9.1) + '@opentelemetry/otlp-transformer': 0.217.0(@opentelemetry/api@1.9.1) + '@opentelemetry/sdk-logs': 0.217.0(@opentelemetry/api@1.9.1) + + '@opentelemetry/exporter-logs-otlp-http@0.217.0(@opentelemetry/api@1.9.1)': + dependencies: + '@opentelemetry/api': 1.9.1 + '@opentelemetry/api-logs': 0.217.0 + '@opentelemetry/core': 2.7.1(@opentelemetry/api@1.9.1) + '@opentelemetry/otlp-exporter-base': 0.217.0(@opentelemetry/api@1.9.1) + '@opentelemetry/otlp-transformer': 0.217.0(@opentelemetry/api@1.9.1) + '@opentelemetry/sdk-logs': 0.217.0(@opentelemetry/api@1.9.1) + + '@opentelemetry/exporter-logs-otlp-proto@0.217.0(@opentelemetry/api@1.9.1)': + dependencies: + '@opentelemetry/api': 1.9.1 + '@opentelemetry/api-logs': 0.217.0 + '@opentelemetry/core': 2.7.1(@opentelemetry/api@1.9.1) + '@opentelemetry/otlp-exporter-base': 0.217.0(@opentelemetry/api@1.9.1) + '@opentelemetry/otlp-transformer': 0.217.0(@opentelemetry/api@1.9.1) + '@opentelemetry/resources': 2.7.1(@opentelemetry/api@1.9.1) + '@opentelemetry/sdk-logs': 0.217.0(@opentelemetry/api@1.9.1) + '@opentelemetry/sdk-trace-base': 2.7.1(@opentelemetry/api@1.9.1) + + '@opentelemetry/exporter-metrics-otlp-grpc@0.217.0(@opentelemetry/api@1.9.1)': + dependencies: + '@grpc/grpc-js': 1.14.3 + '@opentelemetry/api': 1.9.1 + '@opentelemetry/core': 2.7.1(@opentelemetry/api@1.9.1) + '@opentelemetry/exporter-metrics-otlp-http': 0.217.0(@opentelemetry/api@1.9.1) + '@opentelemetry/otlp-exporter-base': 0.217.0(@opentelemetry/api@1.9.1) + '@opentelemetry/otlp-grpc-exporter-base': 0.217.0(@opentelemetry/api@1.9.1) + '@opentelemetry/otlp-transformer': 0.217.0(@opentelemetry/api@1.9.1) + '@opentelemetry/resources': 2.7.1(@opentelemetry/api@1.9.1) + '@opentelemetry/sdk-metrics': 2.7.1(@opentelemetry/api@1.9.1) + + '@opentelemetry/exporter-metrics-otlp-http@0.217.0(@opentelemetry/api@1.9.1)': + dependencies: + '@opentelemetry/api': 1.9.1 + '@opentelemetry/core': 2.7.1(@opentelemetry/api@1.9.1) + '@opentelemetry/otlp-exporter-base': 0.217.0(@opentelemetry/api@1.9.1) + '@opentelemetry/otlp-transformer': 0.217.0(@opentelemetry/api@1.9.1) + '@opentelemetry/resources': 2.7.1(@opentelemetry/api@1.9.1) + '@opentelemetry/sdk-metrics': 2.7.1(@opentelemetry/api@1.9.1) + + '@opentelemetry/exporter-metrics-otlp-proto@0.217.0(@opentelemetry/api@1.9.1)': + dependencies: + '@opentelemetry/api': 1.9.1 + '@opentelemetry/core': 2.7.1(@opentelemetry/api@1.9.1) + '@opentelemetry/exporter-metrics-otlp-http': 0.217.0(@opentelemetry/api@1.9.1) + '@opentelemetry/otlp-exporter-base': 0.217.0(@opentelemetry/api@1.9.1) + '@opentelemetry/otlp-transformer': 0.217.0(@opentelemetry/api@1.9.1) + '@opentelemetry/resources': 2.7.1(@opentelemetry/api@1.9.1) + '@opentelemetry/sdk-metrics': 2.7.1(@opentelemetry/api@1.9.1) + + '@opentelemetry/exporter-prometheus@0.217.0(@opentelemetry/api@1.9.1)': + dependencies: + '@opentelemetry/api': 1.9.1 + '@opentelemetry/core': 2.7.1(@opentelemetry/api@1.9.1) + '@opentelemetry/resources': 2.7.1(@opentelemetry/api@1.9.1) + '@opentelemetry/sdk-metrics': 2.7.1(@opentelemetry/api@1.9.1) + '@opentelemetry/semantic-conventions': 1.40.0 + + '@opentelemetry/exporter-trace-otlp-grpc@0.217.0(@opentelemetry/api@1.9.1)': + dependencies: + '@grpc/grpc-js': 1.14.3 + '@opentelemetry/api': 1.9.1 + '@opentelemetry/core': 2.7.1(@opentelemetry/api@1.9.1) + '@opentelemetry/otlp-exporter-base': 0.217.0(@opentelemetry/api@1.9.1) + '@opentelemetry/otlp-grpc-exporter-base': 0.217.0(@opentelemetry/api@1.9.1) + '@opentelemetry/otlp-transformer': 0.217.0(@opentelemetry/api@1.9.1) + '@opentelemetry/resources': 2.7.1(@opentelemetry/api@1.9.1) + '@opentelemetry/sdk-trace-base': 2.7.1(@opentelemetry/api@1.9.1) + + '@opentelemetry/exporter-trace-otlp-http@0.217.0(@opentelemetry/api@1.9.1)': + dependencies: + '@opentelemetry/api': 1.9.1 + '@opentelemetry/core': 2.7.1(@opentelemetry/api@1.9.1) + '@opentelemetry/otlp-exporter-base': 0.217.0(@opentelemetry/api@1.9.1) + '@opentelemetry/otlp-transformer': 0.217.0(@opentelemetry/api@1.9.1) + '@opentelemetry/resources': 2.7.1(@opentelemetry/api@1.9.1) + '@opentelemetry/sdk-trace-base': 2.7.1(@opentelemetry/api@1.9.1) + + '@opentelemetry/exporter-trace-otlp-proto@0.217.0(@opentelemetry/api@1.9.1)': + dependencies: + '@opentelemetry/api': 1.9.1 + '@opentelemetry/core': 2.7.1(@opentelemetry/api@1.9.1) + '@opentelemetry/otlp-exporter-base': 0.217.0(@opentelemetry/api@1.9.1) + '@opentelemetry/otlp-transformer': 0.217.0(@opentelemetry/api@1.9.1) + '@opentelemetry/resources': 2.7.1(@opentelemetry/api@1.9.1) + '@opentelemetry/sdk-trace-base': 2.7.1(@opentelemetry/api@1.9.1) + + '@opentelemetry/exporter-zipkin@2.7.1(@opentelemetry/api@1.9.1)': + dependencies: + '@opentelemetry/api': 1.9.1 + '@opentelemetry/core': 2.7.1(@opentelemetry/api@1.9.1) + '@opentelemetry/resources': 2.7.1(@opentelemetry/api@1.9.1) + '@opentelemetry/sdk-trace-base': 2.7.1(@opentelemetry/api@1.9.1) + '@opentelemetry/semantic-conventions': 1.40.0 + + '@opentelemetry/instrumentation-amqplib@0.64.0(@opentelemetry/api@1.9.1)': + dependencies: + '@opentelemetry/api': 1.9.1 + '@opentelemetry/core': 2.7.1(@opentelemetry/api@1.9.1) + '@opentelemetry/instrumentation': 0.217.0(@opentelemetry/api@1.9.1) + '@opentelemetry/semantic-conventions': 1.40.0 + transitivePeerDependencies: + - supports-color + + '@opentelemetry/instrumentation-aws-lambda@0.69.0(@opentelemetry/api@1.9.1)': + dependencies: + '@opentelemetry/api': 1.9.1 + '@opentelemetry/instrumentation': 0.217.0(@opentelemetry/api@1.9.1) + '@opentelemetry/semantic-conventions': 1.40.0 + '@types/aws-lambda': 8.10.161 + transitivePeerDependencies: + - supports-color + + '@opentelemetry/instrumentation-aws-sdk@0.72.0(@opentelemetry/api@1.9.1)': + dependencies: + '@opentelemetry/api': 1.9.1 + '@opentelemetry/core': 2.7.1(@opentelemetry/api@1.9.1) + '@opentelemetry/instrumentation': 0.217.0(@opentelemetry/api@1.9.1) + '@opentelemetry/semantic-conventions': 1.40.0 + transitivePeerDependencies: + - supports-color + + '@opentelemetry/instrumentation-bunyan@0.62.0(@opentelemetry/api@1.9.1)': + dependencies: + '@opentelemetry/api': 1.9.1 + '@opentelemetry/api-logs': 0.217.0 + '@opentelemetry/instrumentation': 0.217.0(@opentelemetry/api@1.9.1) + '@types/bunyan': 1.8.11 + transitivePeerDependencies: + - supports-color + + '@opentelemetry/instrumentation-cassandra-driver@0.62.0(@opentelemetry/api@1.9.1)': + dependencies: + '@opentelemetry/api': 1.9.1 + '@opentelemetry/instrumentation': 0.217.0(@opentelemetry/api@1.9.1) + '@opentelemetry/semantic-conventions': 1.40.0 + transitivePeerDependencies: + - supports-color + + '@opentelemetry/instrumentation-connect@0.60.0(@opentelemetry/api@1.9.1)': + dependencies: + '@opentelemetry/api': 1.9.1 + '@opentelemetry/core': 2.7.1(@opentelemetry/api@1.9.1) + '@opentelemetry/instrumentation': 0.217.0(@opentelemetry/api@1.9.1) + '@opentelemetry/semantic-conventions': 1.40.0 + '@types/connect': 3.4.38 + transitivePeerDependencies: + - supports-color + + '@opentelemetry/instrumentation-cucumber@0.33.0(@opentelemetry/api@1.9.1)': + dependencies: + '@opentelemetry/api': 1.9.1 + '@opentelemetry/instrumentation': 0.217.0(@opentelemetry/api@1.9.1) + '@opentelemetry/semantic-conventions': 1.40.0 + transitivePeerDependencies: + - supports-color + + '@opentelemetry/instrumentation-dataloader@0.34.0(@opentelemetry/api@1.9.1)': + dependencies: + '@opentelemetry/api': 1.9.1 + '@opentelemetry/instrumentation': 0.217.0(@opentelemetry/api@1.9.1) + transitivePeerDependencies: + - supports-color + + '@opentelemetry/instrumentation-dns@0.60.0(@opentelemetry/api@1.9.1)': + dependencies: + '@opentelemetry/api': 1.9.1 + '@opentelemetry/instrumentation': 0.217.0(@opentelemetry/api@1.9.1) + transitivePeerDependencies: + - supports-color + + '@opentelemetry/instrumentation-express@0.65.0(@opentelemetry/api@1.9.1)': + dependencies: + '@opentelemetry/api': 1.9.1 + '@opentelemetry/core': 2.7.1(@opentelemetry/api@1.9.1) + '@opentelemetry/instrumentation': 0.217.0(@opentelemetry/api@1.9.1) + '@opentelemetry/semantic-conventions': 1.40.0 + transitivePeerDependencies: + - supports-color + + '@opentelemetry/instrumentation-fs@0.36.0(@opentelemetry/api@1.9.1)': + dependencies: + '@opentelemetry/api': 1.9.1 + '@opentelemetry/core': 2.7.1(@opentelemetry/api@1.9.1) + '@opentelemetry/instrumentation': 0.217.0(@opentelemetry/api@1.9.1) + transitivePeerDependencies: + - supports-color + + '@opentelemetry/instrumentation-generic-pool@0.60.0(@opentelemetry/api@1.9.1)': + dependencies: + '@opentelemetry/api': 1.9.1 + '@opentelemetry/instrumentation': 0.217.0(@opentelemetry/api@1.9.1) + transitivePeerDependencies: + - supports-color + + '@opentelemetry/instrumentation-graphql@0.65.0(@opentelemetry/api@1.9.1)': + dependencies: + '@opentelemetry/api': 1.9.1 + '@opentelemetry/instrumentation': 0.217.0(@opentelemetry/api@1.9.1) + transitivePeerDependencies: + - supports-color + + '@opentelemetry/instrumentation-grpc@0.217.0(@opentelemetry/api@1.9.1)': + dependencies: + '@opentelemetry/api': 1.9.1 + '@opentelemetry/instrumentation': 0.217.0(@opentelemetry/api@1.9.1) + '@opentelemetry/semantic-conventions': 1.40.0 + transitivePeerDependencies: + - supports-color + + '@opentelemetry/instrumentation-hapi@0.63.0(@opentelemetry/api@1.9.1)': + dependencies: + '@opentelemetry/api': 1.9.1 + '@opentelemetry/core': 2.7.1(@opentelemetry/api@1.9.1) + '@opentelemetry/instrumentation': 0.217.0(@opentelemetry/api@1.9.1) + '@opentelemetry/semantic-conventions': 1.40.0 + transitivePeerDependencies: + - supports-color + + '@opentelemetry/instrumentation-http@0.217.0(@opentelemetry/api@1.9.1)': + dependencies: + '@opentelemetry/api': 1.9.1 + '@opentelemetry/core': 2.7.1(@opentelemetry/api@1.9.1) + '@opentelemetry/instrumentation': 0.217.0(@opentelemetry/api@1.9.1) + '@opentelemetry/semantic-conventions': 1.40.0 + forwarded-parse: 2.1.2 + transitivePeerDependencies: + - supports-color + + '@opentelemetry/instrumentation-ioredis@0.65.0(@opentelemetry/api@1.9.1)': + dependencies: + '@opentelemetry/api': 1.9.1 + '@opentelemetry/instrumentation': 0.217.0(@opentelemetry/api@1.9.1) + '@opentelemetry/redis-common': 0.38.3 + '@opentelemetry/semantic-conventions': 1.40.0 + transitivePeerDependencies: + - supports-color + + '@opentelemetry/instrumentation-kafkajs@0.26.0(@opentelemetry/api@1.9.1)': + dependencies: + '@opentelemetry/api': 1.9.1 + '@opentelemetry/instrumentation': 0.217.0(@opentelemetry/api@1.9.1) + '@opentelemetry/semantic-conventions': 1.40.0 + transitivePeerDependencies: + - supports-color + + '@opentelemetry/instrumentation-knex@0.61.0(@opentelemetry/api@1.9.1)': + dependencies: + '@opentelemetry/api': 1.9.1 + '@opentelemetry/instrumentation': 0.217.0(@opentelemetry/api@1.9.1) + '@opentelemetry/semantic-conventions': 1.40.0 + transitivePeerDependencies: + - supports-color + + '@opentelemetry/instrumentation-koa@0.65.0(@opentelemetry/api@1.9.1)': + dependencies: + '@opentelemetry/api': 1.9.1 + '@opentelemetry/core': 2.7.1(@opentelemetry/api@1.9.1) + '@opentelemetry/instrumentation': 0.217.0(@opentelemetry/api@1.9.1) + '@opentelemetry/semantic-conventions': 1.40.0 + transitivePeerDependencies: + - supports-color + + '@opentelemetry/instrumentation-lru-memoizer@0.61.0(@opentelemetry/api@1.9.1)': + dependencies: + '@opentelemetry/api': 1.9.1 + '@opentelemetry/instrumentation': 0.217.0(@opentelemetry/api@1.9.1) + transitivePeerDependencies: + - supports-color + + '@opentelemetry/instrumentation-memcached@0.60.0(@opentelemetry/api@1.9.1)': + dependencies: + '@opentelemetry/api': 1.9.1 + '@opentelemetry/instrumentation': 0.217.0(@opentelemetry/api@1.9.1) + '@opentelemetry/semantic-conventions': 1.40.0 + '@types/memcached': 2.2.10 + transitivePeerDependencies: + - supports-color + + '@opentelemetry/instrumentation-mongodb@0.70.0(@opentelemetry/api@1.9.1)': + dependencies: + '@opentelemetry/api': 1.9.1 + '@opentelemetry/instrumentation': 0.217.0(@opentelemetry/api@1.9.1) + '@opentelemetry/semantic-conventions': 1.40.0 + transitivePeerDependencies: + - supports-color + + '@opentelemetry/instrumentation-mongoose@0.63.0(@opentelemetry/api@1.9.1)': + dependencies: + '@opentelemetry/api': 1.9.1 + '@opentelemetry/core': 2.7.1(@opentelemetry/api@1.9.1) + '@opentelemetry/instrumentation': 0.217.0(@opentelemetry/api@1.9.1) + '@opentelemetry/semantic-conventions': 1.40.0 + transitivePeerDependencies: + - supports-color + + '@opentelemetry/instrumentation-mysql2@0.63.0(@opentelemetry/api@1.9.1)': + dependencies: + '@opentelemetry/api': 1.9.1 + '@opentelemetry/instrumentation': 0.217.0(@opentelemetry/api@1.9.1) + '@opentelemetry/semantic-conventions': 1.40.0 + '@opentelemetry/sql-common': 0.41.2(@opentelemetry/api@1.9.1) + transitivePeerDependencies: + - supports-color + + '@opentelemetry/instrumentation-mysql@0.63.0(@opentelemetry/api@1.9.1)': + dependencies: + '@opentelemetry/api': 1.9.1 + '@opentelemetry/instrumentation': 0.217.0(@opentelemetry/api@1.9.1) + '@opentelemetry/semantic-conventions': 1.40.0 + '@types/mysql': 2.15.27 + transitivePeerDependencies: + - supports-color + + '@opentelemetry/instrumentation-nestjs-core@0.63.0(@opentelemetry/api@1.9.1)': + dependencies: + '@opentelemetry/api': 1.9.1 + '@opentelemetry/instrumentation': 0.217.0(@opentelemetry/api@1.9.1) + '@opentelemetry/semantic-conventions': 1.40.0 + transitivePeerDependencies: + - supports-color + + '@opentelemetry/instrumentation-net@0.61.0(@opentelemetry/api@1.9.1)': + dependencies: + '@opentelemetry/api': 1.9.1 + '@opentelemetry/instrumentation': 0.217.0(@opentelemetry/api@1.9.1) + '@opentelemetry/semantic-conventions': 1.40.0 + transitivePeerDependencies: + - supports-color + + '@opentelemetry/instrumentation-openai@0.15.0(@opentelemetry/api@1.9.1)': + dependencies: + '@opentelemetry/api': 1.9.1 + '@opentelemetry/api-logs': 0.217.0 + '@opentelemetry/instrumentation': 0.217.0(@opentelemetry/api@1.9.1) + '@opentelemetry/semantic-conventions': 1.40.0 + transitivePeerDependencies: + - supports-color + + '@opentelemetry/instrumentation-oracledb@0.42.0(@opentelemetry/api@1.9.1)': + dependencies: + '@opentelemetry/api': 1.9.1 + '@opentelemetry/instrumentation': 0.217.0(@opentelemetry/api@1.9.1) + '@opentelemetry/semantic-conventions': 1.40.0 + '@types/oracledb': 6.5.2 + transitivePeerDependencies: + - supports-color + + '@opentelemetry/instrumentation-pg@0.69.0(@opentelemetry/api@1.9.1)': + dependencies: + '@opentelemetry/api': 1.9.1 + '@opentelemetry/core': 2.7.1(@opentelemetry/api@1.9.1) + '@opentelemetry/instrumentation': 0.217.0(@opentelemetry/api@1.9.1) + '@opentelemetry/semantic-conventions': 1.40.0 + '@opentelemetry/sql-common': 0.41.2(@opentelemetry/api@1.9.1) + '@types/pg': 8.15.6 + '@types/pg-pool': 2.0.7 + transitivePeerDependencies: + - supports-color + + '@opentelemetry/instrumentation-pino@0.63.0(@opentelemetry/api@1.9.1)': + dependencies: + '@opentelemetry/api': 1.9.1 + '@opentelemetry/api-logs': 0.217.0 + '@opentelemetry/core': 2.7.1(@opentelemetry/api@1.9.1) + '@opentelemetry/instrumentation': 0.217.0(@opentelemetry/api@1.9.1) + transitivePeerDependencies: + - supports-color + + '@opentelemetry/instrumentation-redis@0.65.0(@opentelemetry/api@1.9.1)': + dependencies: + '@opentelemetry/api': 1.9.1 + '@opentelemetry/instrumentation': 0.217.0(@opentelemetry/api@1.9.1) + '@opentelemetry/redis-common': 0.38.3 + '@opentelemetry/semantic-conventions': 1.40.0 + transitivePeerDependencies: + - supports-color + + '@opentelemetry/instrumentation-restify@0.62.0(@opentelemetry/api@1.9.1)': + dependencies: + '@opentelemetry/api': 1.9.1 + '@opentelemetry/core': 2.7.1(@opentelemetry/api@1.9.1) + '@opentelemetry/instrumentation': 0.217.0(@opentelemetry/api@1.9.1) + '@opentelemetry/semantic-conventions': 1.40.0 + transitivePeerDependencies: + - supports-color + + '@opentelemetry/instrumentation-router@0.61.0(@opentelemetry/api@1.9.1)': + dependencies: + '@opentelemetry/api': 1.9.1 + '@opentelemetry/instrumentation': 0.217.0(@opentelemetry/api@1.9.1) + '@opentelemetry/semantic-conventions': 1.40.0 + transitivePeerDependencies: + - supports-color + + '@opentelemetry/instrumentation-runtime-node@0.30.0(@opentelemetry/api@1.9.1)': + dependencies: + '@opentelemetry/api': 1.9.1 + '@opentelemetry/instrumentation': 0.217.0(@opentelemetry/api@1.9.1) + transitivePeerDependencies: + - supports-color + + '@opentelemetry/instrumentation-socket.io@0.64.0(@opentelemetry/api@1.9.1)': + dependencies: + '@opentelemetry/api': 1.9.1 + '@opentelemetry/instrumentation': 0.217.0(@opentelemetry/api@1.9.1) + transitivePeerDependencies: + - supports-color + + '@opentelemetry/instrumentation-tedious@0.36.0(@opentelemetry/api@1.9.1)': + dependencies: + '@opentelemetry/api': 1.9.1 + '@opentelemetry/instrumentation': 0.217.0(@opentelemetry/api@1.9.1) + '@opentelemetry/semantic-conventions': 1.40.0 + '@types/tedious': 4.0.14 + transitivePeerDependencies: + - supports-color + + '@opentelemetry/instrumentation-undici@0.27.0(@opentelemetry/api@1.9.1)': + dependencies: + '@opentelemetry/api': 1.9.1 + '@opentelemetry/core': 2.7.1(@opentelemetry/api@1.9.1) + '@opentelemetry/instrumentation': 0.217.0(@opentelemetry/api@1.9.1) + '@opentelemetry/semantic-conventions': 1.40.0 + transitivePeerDependencies: + - supports-color + + '@opentelemetry/instrumentation-winston@0.61.0(@opentelemetry/api@1.9.1)': + dependencies: + '@opentelemetry/api': 1.9.1 + '@opentelemetry/api-logs': 0.217.0 + '@opentelemetry/instrumentation': 0.217.0(@opentelemetry/api@1.9.1) + transitivePeerDependencies: + - supports-color + + '@opentelemetry/instrumentation@0.217.0(@opentelemetry/api@1.9.1)': + dependencies: + '@opentelemetry/api': 1.9.1 + '@opentelemetry/api-logs': 0.217.0 + import-in-the-middle: 3.0.1 + require-in-the-middle: 8.0.1 + transitivePeerDependencies: + - supports-color + + '@opentelemetry/otlp-exporter-base@0.217.0(@opentelemetry/api@1.9.1)': + dependencies: + '@opentelemetry/api': 1.9.1 + '@opentelemetry/core': 2.7.1(@opentelemetry/api@1.9.1) + '@opentelemetry/otlp-transformer': 0.217.0(@opentelemetry/api@1.9.1) + + '@opentelemetry/otlp-grpc-exporter-base@0.217.0(@opentelemetry/api@1.9.1)': + dependencies: + '@grpc/grpc-js': 1.14.3 + '@opentelemetry/api': 1.9.1 + '@opentelemetry/core': 2.7.1(@opentelemetry/api@1.9.1) + '@opentelemetry/otlp-exporter-base': 0.217.0(@opentelemetry/api@1.9.1) + '@opentelemetry/otlp-transformer': 0.217.0(@opentelemetry/api@1.9.1) + + '@opentelemetry/otlp-transformer@0.217.0(@opentelemetry/api@1.9.1)': + dependencies: + '@opentelemetry/api': 1.9.1 + '@opentelemetry/api-logs': 0.217.0 + '@opentelemetry/core': 2.7.1(@opentelemetry/api@1.9.1) + '@opentelemetry/resources': 2.7.1(@opentelemetry/api@1.9.1) + '@opentelemetry/sdk-logs': 0.217.0(@opentelemetry/api@1.9.1) + '@opentelemetry/sdk-metrics': 2.7.1(@opentelemetry/api@1.9.1) + '@opentelemetry/sdk-trace-base': 2.7.1(@opentelemetry/api@1.9.1) + protobufjs: 8.0.1 + + '@opentelemetry/propagator-b3@2.7.1(@opentelemetry/api@1.9.1)': + dependencies: + '@opentelemetry/api': 1.9.1 + '@opentelemetry/core': 2.7.1(@opentelemetry/api@1.9.1) + + '@opentelemetry/propagator-jaeger@2.7.1(@opentelemetry/api@1.9.1)': + dependencies: + '@opentelemetry/api': 1.9.1 + '@opentelemetry/core': 2.7.1(@opentelemetry/api@1.9.1) + + '@opentelemetry/redis-common@0.38.3': {} + + '@opentelemetry/resource-detector-alibaba-cloud@0.33.7(@opentelemetry/api@1.9.1)': + dependencies: + '@opentelemetry/api': 1.9.1 + '@opentelemetry/core': 2.7.1(@opentelemetry/api@1.9.1) + '@opentelemetry/resources': 2.7.1(@opentelemetry/api@1.9.1) + + '@opentelemetry/resource-detector-aws@2.17.0(@opentelemetry/api@1.9.1)': + dependencies: + '@opentelemetry/api': 1.9.1 + '@opentelemetry/core': 2.7.1(@opentelemetry/api@1.9.1) + '@opentelemetry/resources': 2.7.1(@opentelemetry/api@1.9.1) + '@opentelemetry/semantic-conventions': 1.40.0 + + '@opentelemetry/resource-detector-azure@0.25.0(@opentelemetry/api@1.9.1)': + dependencies: + '@opentelemetry/api': 1.9.1 + '@opentelemetry/core': 2.7.1(@opentelemetry/api@1.9.1) + '@opentelemetry/resources': 2.7.1(@opentelemetry/api@1.9.1) + '@opentelemetry/semantic-conventions': 1.40.0 + + '@opentelemetry/resource-detector-container@0.8.8(@opentelemetry/api@1.9.1)': + dependencies: + '@opentelemetry/api': 1.9.1 + '@opentelemetry/core': 2.7.1(@opentelemetry/api@1.9.1) + '@opentelemetry/resources': 2.7.1(@opentelemetry/api@1.9.1) + + '@opentelemetry/resource-detector-gcp@0.52.0(@opentelemetry/api@1.9.1)': + dependencies: + '@opentelemetry/api': 1.9.1 + '@opentelemetry/core': 2.7.1(@opentelemetry/api@1.9.1) + '@opentelemetry/resources': 2.7.1(@opentelemetry/api@1.9.1) + gcp-metadata: 8.1.2 + transitivePeerDependencies: + - supports-color + + '@opentelemetry/resources@2.7.1(@opentelemetry/api@1.9.1)': + dependencies: + '@opentelemetry/api': 1.9.1 + '@opentelemetry/core': 2.7.1(@opentelemetry/api@1.9.1) + '@opentelemetry/semantic-conventions': 1.40.0 + + '@opentelemetry/sdk-logs@0.217.0(@opentelemetry/api@1.9.1)': + dependencies: + '@opentelemetry/api': 1.9.1 + '@opentelemetry/api-logs': 0.217.0 + '@opentelemetry/core': 2.7.1(@opentelemetry/api@1.9.1) + '@opentelemetry/resources': 2.7.1(@opentelemetry/api@1.9.1) + '@opentelemetry/semantic-conventions': 1.40.0 + + '@opentelemetry/sdk-metrics@2.7.1(@opentelemetry/api@1.9.1)': + dependencies: + '@opentelemetry/api': 1.9.1 + '@opentelemetry/core': 2.7.1(@opentelemetry/api@1.9.1) + '@opentelemetry/resources': 2.7.1(@opentelemetry/api@1.9.1) + + '@opentelemetry/sdk-node@0.217.0(@opentelemetry/api@1.9.1)': + dependencies: + '@opentelemetry/api': 1.9.1 + '@opentelemetry/api-logs': 0.217.0 + '@opentelemetry/configuration': 0.217.0(@opentelemetry/api@1.9.1) + '@opentelemetry/context-async-hooks': 2.7.1(@opentelemetry/api@1.9.1) + '@opentelemetry/core': 2.7.1(@opentelemetry/api@1.9.1) + '@opentelemetry/exporter-logs-otlp-grpc': 0.217.0(@opentelemetry/api@1.9.1) + '@opentelemetry/exporter-logs-otlp-http': 0.217.0(@opentelemetry/api@1.9.1) + '@opentelemetry/exporter-logs-otlp-proto': 0.217.0(@opentelemetry/api@1.9.1) + '@opentelemetry/exporter-metrics-otlp-grpc': 0.217.0(@opentelemetry/api@1.9.1) + '@opentelemetry/exporter-metrics-otlp-http': 0.217.0(@opentelemetry/api@1.9.1) + '@opentelemetry/exporter-metrics-otlp-proto': 0.217.0(@opentelemetry/api@1.9.1) + '@opentelemetry/exporter-prometheus': 0.217.0(@opentelemetry/api@1.9.1) + '@opentelemetry/exporter-trace-otlp-grpc': 0.217.0(@opentelemetry/api@1.9.1) + '@opentelemetry/exporter-trace-otlp-http': 0.217.0(@opentelemetry/api@1.9.1) + '@opentelemetry/exporter-trace-otlp-proto': 0.217.0(@opentelemetry/api@1.9.1) + '@opentelemetry/exporter-zipkin': 2.7.1(@opentelemetry/api@1.9.1) + '@opentelemetry/instrumentation': 0.217.0(@opentelemetry/api@1.9.1) + '@opentelemetry/otlp-exporter-base': 0.217.0(@opentelemetry/api@1.9.1) + '@opentelemetry/propagator-b3': 2.7.1(@opentelemetry/api@1.9.1) + '@opentelemetry/propagator-jaeger': 2.7.1(@opentelemetry/api@1.9.1) + '@opentelemetry/resources': 2.7.1(@opentelemetry/api@1.9.1) + '@opentelemetry/sdk-logs': 0.217.0(@opentelemetry/api@1.9.1) + '@opentelemetry/sdk-metrics': 2.7.1(@opentelemetry/api@1.9.1) + '@opentelemetry/sdk-trace-base': 2.7.1(@opentelemetry/api@1.9.1) + '@opentelemetry/sdk-trace-node': 2.7.1(@opentelemetry/api@1.9.1) + '@opentelemetry/semantic-conventions': 1.40.0 + transitivePeerDependencies: + - supports-color + + '@opentelemetry/sdk-trace-base@2.7.1(@opentelemetry/api@1.9.1)': + dependencies: + '@opentelemetry/api': 1.9.1 + '@opentelemetry/core': 2.7.1(@opentelemetry/api@1.9.1) + '@opentelemetry/resources': 2.7.1(@opentelemetry/api@1.9.1) + '@opentelemetry/semantic-conventions': 1.40.0 + + '@opentelemetry/sdk-trace-node@2.7.1(@opentelemetry/api@1.9.1)': + dependencies: + '@opentelemetry/api': 1.9.1 + '@opentelemetry/context-async-hooks': 2.7.1(@opentelemetry/api@1.9.1) + '@opentelemetry/core': 2.7.1(@opentelemetry/api@1.9.1) + '@opentelemetry/sdk-trace-base': 2.7.1(@opentelemetry/api@1.9.1) + + '@opentelemetry/semantic-conventions@1.40.0': {} + + '@opentelemetry/sql-common@0.41.2(@opentelemetry/api@1.9.1)': + dependencies: + '@opentelemetry/api': 1.9.1 + '@opentelemetry/core': 2.7.1(@opentelemetry/api@1.9.1) + '@oxc-project/runtime@0.115.0': {} '@oxc-project/types@0.113.0': {} @@ -11292,6 +12724,8 @@ snapshots: esquery: 1.7.0 typescript: 5.9.3 + '@pinojs/redact@0.4.0': {} + '@pkgjs/parseargs@0.11.0': optional: true @@ -11326,6 +12760,29 @@ snapshots: dependencies: '@prisma/debug': 5.22.0 + '@protobufjs/aspromise@1.1.2': {} + + '@protobufjs/base64@1.1.2': {} + + '@protobufjs/codegen@2.0.5': {} + + '@protobufjs/eventemitter@1.1.0': {} + + '@protobufjs/fetch@1.1.0': + dependencies: + '@protobufjs/aspromise': 1.1.2 + '@protobufjs/inquire': 1.1.1 + + '@protobufjs/float@1.0.2': {} + + '@protobufjs/inquire@1.1.1': {} + + '@protobufjs/path@1.1.2': {} + + '@protobufjs/pool@1.1.0': {} + + '@protobufjs/utf8@1.1.1': {} + '@rolldown/binding-android-arm64@1.0.0-rc.17': optional: true @@ -11774,6 +13231,8 @@ snapshots: dependencies: tslib: 2.8.1 + '@types/aws-lambda@8.10.161': {} + '@types/babel__core@7.20.5': dependencies: '@babel/parser': 7.29.3 @@ -11804,6 +13263,10 @@ snapshots: dependencies: '@types/node': 20.19.9 + '@types/bunyan@1.8.11': + dependencies: + '@types/node': 20.19.9 + '@types/chai@5.2.3': dependencies: '@types/deep-eql': 4.0.2 @@ -11877,8 +13340,16 @@ snapshots: '@types/luxon@3.7.1': {} + '@types/memcached@2.2.10': + dependencies: + '@types/node': 20.19.9 + '@types/mime@1.3.5': {} + '@types/mysql@2.15.27': + dependencies: + '@types/node': 20.19.9 + '@types/node-forge@1.3.14': dependencies: '@types/node': 20.19.9 @@ -11887,8 +13358,22 @@ snapshots: dependencies: undici-types: 6.21.0 + '@types/oracledb@6.5.2': + dependencies: + '@types/node': 20.19.9 + '@types/parse-json@4.0.2': {} + '@types/pg-pool@2.0.7': + dependencies: + '@types/pg': 8.15.6 + + '@types/pg@8.15.6': + dependencies: + '@types/node': 20.19.9 + pg-protocol: 1.13.0 + pg-types: 2.2.0 + '@types/qs@6.15.0': {} '@types/range-parser@1.2.7': {} @@ -11924,6 +13409,10 @@ snapshots: '@types/stack-utils@2.0.3': {} + '@types/tedious@4.0.14': + dependencies: + '@types/node': 20.19.9 + '@types/whatwg-mimetype@3.0.2': {} '@types/ws@8.18.1': @@ -12104,7 +13593,7 @@ snapshots: obug: 2.1.1 std-env: 4.1.0 tinyrainbow: 3.1.0 - vitest: 4.1.5(@types/node@20.19.9)(@vitest/coverage-v8@4.1.5)(happy-dom@20.9.0)(jsdom@27.4.0)(vite@8.0.10(@types/node@20.19.9)(esbuild@0.27.3)(jiti@2.7.0)(less@4.5.1)(sass-embedded@1.99.0)(sass@1.99.0)(terser@5.46.2)(yaml@2.8.0)) + vitest: 4.1.5(@opentelemetry/api@1.9.1)(@types/node@20.19.9)(@vitest/coverage-v8@4.1.5)(happy-dom@20.9.0)(jsdom@27.4.0)(vite@8.0.10(@types/node@20.19.9)(esbuild@0.27.3)(jiti@2.7.0)(less@4.5.1)(sass-embedded@1.99.0)(sass@1.99.0)(terser@5.46.2)(yaml@2.8.0)) '@vitest/expect@4.1.5': dependencies: @@ -12260,6 +13749,10 @@ snapshots: mime-types: 3.0.2 negotiator: 1.0.0 + acorn-import-attributes@1.9.5(acorn@8.16.0): + dependencies: + acorn: 8.16.0 + acorn-import-phases@1.0.4(acorn@8.16.0): dependencies: acorn: 8.16.0 @@ -12421,6 +13914,8 @@ snapshots: asynckit@0.4.0: {} + atomic-sleep@1.0.0: {} + autoprefixer@10.5.0(postcss@8.5.14): dependencies: browserslist: 4.28.2 @@ -12585,6 +14080,8 @@ snapshots: big.js@5.2.2: {} + bignumber.js@9.3.1: {} + binary-extensions@2.3.0: {} bl@4.1.0: @@ -13075,11 +14572,15 @@ snapshots: css-tree: 3.2.1 lru-cache: 11.3.6 + data-uri-to-buffer@4.0.1: {} + data-urls@6.0.1: dependencies: whatwg-mimetype: 5.0.0 whatwg-url: 15.1.0 + dateformat@4.6.3: {} + debug@2.6.9: dependencies: ms: 2.0.0 @@ -13522,10 +15023,14 @@ snapshots: transitivePeerDependencies: - supports-color + extend@3.0.2: {} + fast-check@4.7.0: dependencies: pure-rand: 8.4.0 + fast-copy@4.0.3: {} + fast-deep-equal@3.1.3: {} fast-json-stable-stringify@2.1.0: {} @@ -13550,6 +15055,11 @@ snapshots: optionalDependencies: picomatch: 4.0.4 + fetch-blob@3.2.0: + dependencies: + node-domexception: 1.0.0 + web-streams-polyfill: 3.3.3 + figures@3.2.0: dependencies: escape-string-regexp: 1.0.5 @@ -13665,6 +15175,12 @@ snapshots: hasown: 2.0.2 mime-types: 2.1.35 + formdata-polyfill@4.0.10: + dependencies: + fetch-blob: 3.2.0 + + forwarded-parse@2.1.2: {} + forwarded@0.2.0: {} fraction.js@5.3.4: {} @@ -13697,6 +15213,22 @@ snapshots: function-bind@1.1.2: {} + gaxios@7.1.4: + dependencies: + extend: 3.0.2 + https-proxy-agent: 7.0.6 + node-fetch: 3.3.2 + transitivePeerDependencies: + - supports-color + + gcp-metadata@8.1.2: + dependencies: + gaxios: 7.1.4 + google-logging-utils: 1.1.3 + json-bigint: 1.0.0 + transitivePeerDependencies: + - supports-color + gensync@1.0.0-beta.2: {} get-caller-file@2.0.5: {} @@ -13787,6 +15319,8 @@ snapshots: globals@15.15.0: {} + google-logging-utils@1.1.3: {} + gopd@1.2.0: {} graceful-fs@4.2.11: {} @@ -13834,6 +15368,8 @@ snapshots: he@1.2.0: {} + help-me@5.0.0: {} + homedir-polyfill@1.0.3: dependencies: parse-passwd: 1.0.0 @@ -14000,6 +15536,13 @@ snapshots: parent-module: 1.0.1 resolve-from: 4.0.0 + import-in-the-middle@3.0.1: + dependencies: + acorn: 8.16.0 + acorn-import-attributes: 1.9.5(acorn@8.16.0) + cjs-module-lexer: 2.2.0 + module-details-from-path: 1.0.4 + import-local@3.2.0: dependencies: pkg-dir: 4.2.0 @@ -14494,6 +16037,8 @@ snapshots: jose@6.2.3: {} + joycon@3.1.1: {} + js-tokens@10.0.0: {} js-tokens@4.0.0: {} @@ -14537,6 +16082,10 @@ snapshots: jsesc@3.1.0: {} + json-bigint@1.0.0: + dependencies: + bignumber.js: 9.3.1 + json-buffer@3.0.1: {} json-parse-even-better-errors@2.3.1: {} @@ -14726,6 +16275,8 @@ snapshots: dependencies: p-locate: 6.0.0 + lodash.camelcase@4.3.0: {} + lodash.debounce@4.0.8: {} lodash.defaults@4.2.0: {} @@ -14758,6 +16309,8 @@ snapshots: long-timeout@0.1.1: {} + long@5.3.2: {} + lru-cache@10.4.3: {} lru-cache@11.3.6: {} @@ -14932,6 +16485,8 @@ snapshots: dependencies: minipass: 7.1.3 + module-details-from-path@1.0.4: {} + mrmime@2.0.1: {} ms@2.0.0: {} @@ -14991,6 +16546,13 @@ snapshots: neo-async@2.6.2: {} + nestjs-pino@4.6.1(@nestjs/common@11.1.19(reflect-metadata@0.1.14)(rxjs@7.8.2))(pino-http@11.0.0)(pino@10.3.1)(rxjs@7.8.2): + dependencies: + '@nestjs/common': 11.1.19(reflect-metadata@0.1.14)(rxjs@7.8.2) + pino: 10.3.1 + pino-http: 11.0.0 + rxjs: 7.8.2 + node-abort-controller@3.1.1: {} node-addon-api@6.1.0: @@ -14999,12 +16561,20 @@ snapshots: node-addon-api@7.1.1: optional: true + node-domexception@1.0.0: {} + node-fetch@2.7.0(encoding@0.1.13): dependencies: whatwg-url: 5.0.0 optionalDependencies: encoding: 0.1.13 + node-fetch@3.3.2: + dependencies: + data-uri-to-buffer: 4.0.1 + fetch-blob: 3.2.0 + formdata-polyfill: 4.0.10 + node-forge@1.4.0: {} node-gyp-build-optional-packages@5.2.2: @@ -15227,6 +16797,8 @@ snapshots: obug@2.1.1: {} + on-exit-leak-free@2.1.2: {} + on-finished@2.4.1: dependencies: ee-first: 1.1.1 @@ -15451,6 +17023,18 @@ snapshots: pathe@2.0.3: {} + pg-int8@1.0.1: {} + + pg-protocol@1.13.0: {} + + pg-types@2.2.0: + dependencies: + pg-int8: 1.0.1 + postgres-array: 2.0.0 + postgres-bytea: 1.0.1 + postgres-date: 1.0.7 + postgres-interval: 1.2.0 + picocolors@1.1.1: {} picomatch@2.3.2: {} @@ -15462,6 +17046,49 @@ snapshots: pify@4.0.1: optional: true + pino-abstract-transport@3.0.0: + dependencies: + split2: 4.2.0 + + pino-http@11.0.0: + dependencies: + get-caller-file: 2.0.5 + pino: 10.3.1 + pino-std-serializers: 7.1.0 + process-warning: 5.0.0 + + pino-pretty@13.1.3: + dependencies: + colorette: 2.0.20 + dateformat: 4.6.3 + fast-copy: 4.0.3 + fast-safe-stringify: 2.1.1 + help-me: 5.0.0 + joycon: 3.1.1 + minimist: 1.2.8 + on-exit-leak-free: 2.1.2 + pino-abstract-transport: 3.0.0 + pump: 3.0.4 + secure-json-parse: 4.1.0 + sonic-boom: 4.2.1 + strip-json-comments: 5.0.3 + + pino-std-serializers@7.1.0: {} + + pino@10.3.1: + dependencies: + '@pinojs/redact': 0.4.0 + atomic-sleep: 1.0.0 + on-exit-leak-free: 2.1.2 + pino-abstract-transport: 3.0.0 + pino-std-serializers: 7.1.0 + process-warning: 5.0.0 + quick-format-unescaped: 4.0.4 + real-require: 0.2.0 + safe-stable-stringify: 2.5.0 + sonic-boom: 4.2.1 + thread-stream: 4.0.0 + pirates@4.0.7: {} piscina@5.1.4: @@ -15714,6 +17341,16 @@ snapshots: picocolors: 1.1.1 source-map-js: 1.2.1 + postgres-array@2.0.0: {} + + postgres-bytea@1.0.1: {} + + postgres-date@1.0.7: {} + + postgres-interval@1.2.0: + dependencies: + xtend: 4.0.2 + prelude-ls@1.2.1: {} prettier@3.6.2: {} @@ -15734,11 +17371,43 @@ snapshots: process-nextick-args@2.0.1: {} + process-warning@5.0.0: {} + promise-retry@2.0.1: dependencies: err-code: 2.0.3 retry: 0.12.0 + protobufjs@7.5.6: + dependencies: + '@protobufjs/aspromise': 1.1.2 + '@protobufjs/base64': 1.1.2 + '@protobufjs/codegen': 2.0.5 + '@protobufjs/eventemitter': 1.1.0 + '@protobufjs/fetch': 1.1.0 + '@protobufjs/float': 1.0.2 + '@protobufjs/inquire': 1.1.1 + '@protobufjs/path': 1.1.2 + '@protobufjs/pool': 1.1.0 + '@protobufjs/utf8': 1.1.1 + '@types/node': 20.19.9 + long: 5.3.2 + + protobufjs@8.0.1: + dependencies: + '@protobufjs/aspromise': 1.1.2 + '@protobufjs/base64': 1.1.2 + '@protobufjs/codegen': 2.0.5 + '@protobufjs/eventemitter': 1.1.0 + '@protobufjs/fetch': 1.1.0 + '@protobufjs/float': 1.0.2 + '@protobufjs/inquire': 1.1.1 + '@protobufjs/path': 1.1.2 + '@protobufjs/pool': 1.1.0 + '@protobufjs/utf8': 1.1.1 + '@types/node': 20.19.9 + long: 5.3.2 + proxy-addr@2.0.7: dependencies: forwarded: 0.2.0 @@ -15749,6 +17418,11 @@ snapshots: prr@1.0.1: optional: true + pump@3.0.4: + dependencies: + end-of-stream: 1.4.5 + once: 1.4.0 + punycode@2.3.1: {} pure-rand@7.0.1: {} @@ -15769,6 +17443,8 @@ snapshots: dependencies: side-channel: 1.1.0 + quick-format-unescaped@4.0.4: {} + range-parser@1.2.1: {} raw-body@2.5.3: @@ -15817,6 +17493,8 @@ snapshots: readdirp@5.0.0: {} + real-require@0.2.0: {} + rechoir@0.8.0: dependencies: resolve: 1.22.12 @@ -15856,6 +17534,13 @@ snapshots: require-from-string@2.0.2: {} + require-in-the-middle@8.0.1: + dependencies: + debug: 4.4.3 + module-details-from-path: 1.0.4 + transitivePeerDependencies: + - supports-color + requires-port@1.0.0: {} resolve-cwd@3.0.0: @@ -16000,6 +17685,8 @@ snapshots: safe-buffer@5.2.1: {} + safe-stable-stringify@2.5.0: {} + safer-buffer@2.1.2: {} sass-embedded-all-unknown@1.99.0: @@ -16142,6 +17829,8 @@ snapshots: secure-compare@3.0.1: {} + secure-json-parse@4.1.0: {} + seedrandom@3.0.5: {} select-hose@2.0.0: {} @@ -16327,6 +18016,10 @@ snapshots: ip-address: 10.2.0 smart-buffer: 4.2.0 + sonic-boom@4.2.1: + dependencies: + atomic-sleep: 1.0.0 + sorted-array-functions@1.3.0: {} source-map-js@1.2.1: {} @@ -16388,6 +18081,8 @@ snapshots: transitivePeerDependencies: - supports-color + split2@4.2.0: {} + sprintf-js@1.0.3: {} ssri@13.0.1: @@ -16466,6 +18161,8 @@ snapshots: strip-json-comments@3.1.1: {} + strip-json-comments@5.0.3: {} + strtok3@10.3.5: dependencies: '@tokenizer/token': 0.3.0 @@ -16567,6 +18264,10 @@ snapshots: dependencies: tslib: 2.8.1 + thread-stream@4.0.0: + dependencies: + real-require: 0.2.0 + thunky@1.1.0: {} tinybench@2.9.0: {} @@ -16890,7 +18591,7 @@ snapshots: terser: 5.46.2 yaml: 2.8.0 - vitest@4.1.5(@types/node@20.19.9)(@vitest/coverage-v8@4.1.5)(happy-dom@20.9.0)(jsdom@27.4.0)(vite@8.0.10(@types/node@20.19.9)(esbuild@0.27.3)(jiti@2.7.0)(less@4.5.1)(sass-embedded@1.99.0)(sass@1.99.0)(terser@5.46.2)(yaml@2.8.0)): + vitest@4.1.5(@opentelemetry/api@1.9.1)(@types/node@20.19.9)(@vitest/coverage-v8@4.1.5)(happy-dom@20.9.0)(jsdom@27.4.0)(vite@8.0.10(@types/node@20.19.9)(esbuild@0.27.3)(jiti@2.7.0)(less@4.5.1)(sass-embedded@1.99.0)(sass@1.99.0)(terser@5.46.2)(yaml@2.8.0)): dependencies: '@vitest/expect': 4.1.5 '@vitest/mocker': 4.1.5(vite@8.0.10(@types/node@20.19.9)(esbuild@0.27.3)(jiti@2.7.0)(less@4.5.1)(sass-embedded@1.99.0)(sass@1.99.0)(terser@5.46.2)(yaml@2.8.0)) @@ -16913,6 +18614,7 @@ snapshots: vite: 8.0.10(@types/node@20.19.9)(esbuild@0.27.3)(jiti@2.7.0)(less@4.5.1)(sass-embedded@1.99.0)(sass@1.99.0)(terser@5.46.2)(yaml@2.8.0) why-is-node-running: 2.3.0 optionalDependencies: + '@opentelemetry/api': 1.9.1 '@types/node': 20.19.9 '@vitest/coverage-v8': 4.1.5(vitest@4.1.5) happy-dom: 20.9.0 @@ -16944,6 +18646,8 @@ snapshots: weak-lru-cache@1.2.2: optional: true + web-streams-polyfill@3.3.3: {} + webidl-conversions@3.0.1: {} webidl-conversions@8.0.1: {} @@ -17160,6 +18864,8 @@ snapshots: xmlchars@2.2.0: {} + xtend@4.0.2: {} + y18n@5.0.8: {} yallist@3.1.1: {} diff --git a/tsconfig.base.json b/tsconfig.base.json index a2701e5..0637de8 100644 --- a/tsconfig.base.json +++ b/tsconfig.base.json @@ -11,6 +11,7 @@ "target": "es2015", "module": "esnext", "lib": ["es2020", "dom"], + "resolveJsonModule": true, "skipLibCheck": true, "skipDefaultLibCheck": true, "paths": {