diff --git a/app/game-api/package.json b/app/game-api/package.json index be6ef19..ee882da 100644 --- a/app/game-api/package.json +++ b/app/game-api/package.json @@ -7,10 +7,19 @@ "build": "tsdown -c ../../tsdown.config.ts -F @sammo-ts/game-api", "dev": "tsdown -c ../../tsdown.config.ts -F @sammo-ts/game-api --watch", "lint": "node -e \"console.log('lint not configured')\"", - "test": "node -e \"console.log('test not configured')\"", + "test": "vitest run --config vitest.config.ts", "typecheck": "tsc --noEmit" }, "devDependencies": { - "tsdown": "^0.18.3" + "tsdown": "^0.18.3", + "vite-tsconfig-paths": "^6.0.0", + "vitest": "^4.0.16" + }, + "dependencies": { + "@fastify/cors": "^10.0.1", + "@sammo-ts/infra": "workspace:*", + "@trpc/server": "^11.4.4", + "fastify": "^5.3.3", + "zod": "^4.2.1" } } diff --git a/app/game-api/src/config.ts b/app/game-api/src/config.ts new file mode 100644 index 0000000..81158ca --- /dev/null +++ b/app/game-api/src/config.ts @@ -0,0 +1,42 @@ +export interface GameApiConfig { + host: string; + port: number; + trpcPath: string; + profile: string; + scenario: string; + profileName: string; + daemonRequestTimeoutMs: number; +} + +const parseNumber = (value: string | undefined, fallback: number, label: string): number => { + if (!value) { + return fallback; + } + const parsed = Number(value); + if (Number.isNaN(parsed)) { + throw new Error(`${label} must be a number.`); + } + return parsed; +}; + +export const resolveGameApiConfigFromEnv = ( + env: NodeJS.ProcessEnv = process.env +): GameApiConfig => { + const profile = env.PROFILE ?? env.SERVER_PROFILE ?? 'che'; + const scenario = env.SCENARIO ?? 'default'; + const profileName = `${profile}:${scenario}`; + + return { + host: env.GAME_API_HOST ?? '0.0.0.0', + port: parseNumber(env.GAME_API_PORT, 14000, 'GAME_API_PORT'), + trpcPath: env.TRPC_PATH ?? '/trpc', + profile, + scenario, + profileName, + daemonRequestTimeoutMs: parseNumber( + env.DAEMON_REQUEST_TIMEOUT_MS, + 5000, + 'DAEMON_REQUEST_TIMEOUT_MS' + ), + }; +}; diff --git a/app/game-api/src/context.ts b/app/game-api/src/context.ts new file mode 100644 index 0000000..48af66c --- /dev/null +++ b/app/game-api/src/context.ts @@ -0,0 +1,41 @@ +import type { TurnDaemonTransport } from './daemon/transport.js'; + +export interface GameProfile { + id: string; + scenario: string; + name: string; +} + +export interface WorldStateRow { + scenarioCode: string; + currentYear: number; + currentMonth: number; + tickSeconds: number; + config: unknown; + meta: unknown; + updatedAt: Date; +} + +export interface DatabaseClient { + worldState: { + findFirst(args?: unknown): Promise; + }; +} + +export interface GameApiContext { + db: DatabaseClient; + turnDaemon: TurnDaemonTransport; + profile: GameProfile; +} + +export const createGameApiContext = (options: { + db: DatabaseClient; + turnDaemon: TurnDaemonTransport; + profile: GameProfile; +}): GameApiContext => { + return { + db: options.db, + turnDaemon: options.turnDaemon, + profile: options.profile, + }; +}; diff --git a/app/game-api/src/daemon/inMemoryTransport.ts b/app/game-api/src/daemon/inMemoryTransport.ts new file mode 100644 index 0000000..425ecd5 --- /dev/null +++ b/app/game-api/src/daemon/inMemoryTransport.ts @@ -0,0 +1,44 @@ +import { randomUUID } from 'node:crypto'; + +import type { + TurnDaemonCommand, + TurnDaemonCommandEnvelope, + TurnDaemonStatus, +} from './types.js'; +import type { TurnDaemonTransport } from './transport.js'; + +const buildDefaultStatus = (): TurnDaemonStatus => ({ + state: 'idle', + running: false, + paused: false, + queueDepth: 0, +}); + +// 턴 데몬 통신을 메모리 큐로 흉내 내는 테스트용 전송기. +export class InMemoryTurnDaemonTransport implements TurnDaemonTransport { + public readonly commands: TurnDaemonCommandEnvelope[] = []; + private status: TurnDaemonStatus; + + constructor(initialStatus: TurnDaemonStatus = buildDefaultStatus()) { + this.status = initialStatus; + } + + // 테스트용: 메모리 큐에 명령을 저장하고 requestId를 반환한다. + async sendCommand(command: TurnDaemonCommand): Promise { + const requestId = command.type === 'getStatus' ? command.requestId : randomUUID(); + this.commands.push({ + requestId, + sentAt: new Date().toISOString(), + command, + }); + return requestId; + } + + async requestStatus(): Promise { + return this.status; + } + + setStatus(status: TurnDaemonStatus): void { + this.status = status; + } +} diff --git a/app/game-api/src/daemon/redisTransport.ts b/app/game-api/src/daemon/redisTransport.ts new file mode 100644 index 0000000..593b960 --- /dev/null +++ b/app/game-api/src/daemon/redisTransport.ts @@ -0,0 +1,110 @@ +import { randomUUID } from 'node:crypto'; + +import type { RedisClientType } from 'redis'; + +import type { TurnDaemonStreamKeys } from './streamKeys.js'; +import type { TurnDaemonTransport } from './transport.js'; +import type { + TurnDaemonCommand, + TurnDaemonCommandEnvelope, + TurnDaemonEventEnvelope, + TurnDaemonStatus, +} from './types.js'; + +interface RedisTurnDaemonTransportOptions { + keys: TurnDaemonStreamKeys; + requestTimeoutMs: number; +} + +type RedisStreamReadResponse = Array<{ + name: string; + messages: Array<{ id: string; message: Record }>; +}>; + +const buildCommandEnvelope = (command: TurnDaemonCommand): TurnDaemonCommandEnvelope => { + const requestId = command.type === 'getStatus' ? command.requestId : randomUUID(); + return { + requestId, + sentAt: new Date().toISOString(), + command, + }; +}; + +const parseEventEnvelope = (raw: string): TurnDaemonEventEnvelope | null => { + try { + const parsed = JSON.parse(raw) as Partial; + if (!parsed || typeof parsed !== 'object') { + return null; + } + if (!parsed.event || typeof parsed.event !== 'object') { + return null; + } + if (typeof parsed.sentAt !== 'string') { + return null; + } + return parsed as TurnDaemonEventEnvelope; + } catch { + return null; + } +}; + +// 턴 데몬 제어 스트림을 Redis로 구현한 전송기. +export class RedisTurnDaemonTransport implements TurnDaemonTransport { + private readonly client: RedisClientType; + private readonly keys: TurnDaemonStreamKeys; + private readonly requestTimeoutMs: number; + + constructor(client: RedisClientType, options: RedisTurnDaemonTransportOptions) { + this.client = client; + this.keys = options.keys; + this.requestTimeoutMs = options.requestTimeoutMs; + } + + // Redis 스트림에 명령을 기록해서 턴 데몬에게 전달한다. + async sendCommand(command: TurnDaemonCommand): Promise { + const envelope = buildCommandEnvelope(command); + await this.client.xAdd(this.keys.commandStream, '*', { + payload: JSON.stringify(envelope), + }); + return envelope.requestId; + } + + async requestStatus(timeoutMs?: number): Promise { + const requestId = randomUUID(); + await this.sendCommand({ type: 'getStatus', requestId }); + + const deadline = Date.now() + (timeoutMs ?? this.requestTimeoutMs); + let lastId = '$'; + + while (Date.now() < deadline) { + const remaining = Math.max(1, deadline - Date.now()); + const response = (await this.client.xRead( + { key: this.keys.eventStream, id: lastId }, + { BLOCK: remaining, COUNT: 10 } + )) as RedisStreamReadResponse | null; + + if (!response) { + return null; + } + + for (const stream of response) { + for (const message of stream.messages) { + lastId = message.id; + const payload = message.message.payload; + if (!payload) { + continue; + } + const envelope = parseEventEnvelope(payload); + if (!envelope) { + continue; + } + if (envelope.event.type === 'status' && envelope.requestId === requestId) { + return envelope.event.status; + } + } + } + } + + return null; + } +} diff --git a/app/game-api/src/daemon/streamKeys.ts b/app/game-api/src/daemon/streamKeys.ts new file mode 100644 index 0000000..fedc067 --- /dev/null +++ b/app/game-api/src/daemon/streamKeys.ts @@ -0,0 +1,11 @@ +export interface TurnDaemonStreamKeys { + commandStream: string; + eventStream: string; +} + +export const buildTurnDaemonStreamKeys = (profileName: string): TurnDaemonStreamKeys => { + return { + commandStream: `sammo:${profileName}:turn-daemon:commands`, + eventStream: `sammo:${profileName}:turn-daemon:events`, + }; +}; diff --git a/app/game-api/src/daemon/transport.ts b/app/game-api/src/daemon/transport.ts new file mode 100644 index 0000000..d05e6fd --- /dev/null +++ b/app/game-api/src/daemon/transport.ts @@ -0,0 +1,6 @@ +import type { TurnDaemonCommand, TurnDaemonStatus } from './types.js'; + +export interface TurnDaemonTransport { + sendCommand(command: TurnDaemonCommand): Promise; + requestStatus(timeoutMs?: number): Promise; +} diff --git a/app/game-api/src/daemon/types.ts b/app/game-api/src/daemon/types.ts new file mode 100644 index 0000000..389fc9e --- /dev/null +++ b/app/game-api/src/daemon/types.ts @@ -0,0 +1,64 @@ +export type TurnDaemonState = 'idle' | 'running' | 'flushing' | 'paused' | 'stopping'; + +export type RunReason = 'schedule' | 'manual' | 'poke'; + +export interface TurnRunBudget { + budgetMs: number; + maxGenerals: number; + catchUpCap: number; +} + +export interface TurnCheckpoint { + turnTime: string; + generalId?: number; + year: number; + month: number; +} + +export interface TurnRunResult { + lastTurnTime: string; + processedGenerals: number; + processedTurns: number; + durationMs: number; + partial: boolean; + checkpoint?: TurnCheckpoint; +} + +export interface TurnDaemonStatus { + state: TurnDaemonState; + running: boolean; + paused: boolean; + lastRunAt?: string; + lastDurationMs?: number; + lastTurnTime?: string; + nextTurnTime?: string; + pendingReason?: RunReason; + queueDepth: number; + checkpoint?: TurnCheckpoint; +} + +// 턴 데몬 제어 요청은 Redis 스트림으로 전달한다. +export type TurnDaemonCommand = + | { type: 'run'; reason: RunReason; targetTime?: string; budget?: TurnRunBudget } + | { type: 'pause'; reason?: string } + | { type: 'resume'; reason?: string } + | { type: 'getStatus'; requestId: string }; + +// 턴 데몬 이벤트는 상태/실행 결과를 API 서버에 알려준다. +export type TurnDaemonEvent = + | { type: 'status'; requestId?: string; status: TurnDaemonStatus } + | { type: 'runStarted'; at: string; reason: RunReason } + | { type: 'runCompleted'; at: string; result: TurnRunResult } + | { type: 'runFailed'; at: string; error: string }; + +export interface TurnDaemonCommandEnvelope { + requestId: string; + sentAt: string; + command: TurnDaemonCommand; +} + +export interface TurnDaemonEventEnvelope { + requestId?: string; + sentAt: string; + event: TurnDaemonEvent; +} diff --git a/app/game-api/src/index.ts b/app/game-api/src/index.ts index cb0ff5c..7b671ac 100644 --- a/app/game-api/src/index.ts +++ b/app/game-api/src/index.ts @@ -1 +1,28 @@ -export {}; +import path from 'node:path'; +import { fileURLToPath } from 'node:url'; + +import { runGameApiServer } from './server.js'; + +export * from './config.js'; +export * from './context.js'; +export * from './router.js'; +export * from './server.js'; +export * from './daemon/types.js'; +export * from './daemon/streamKeys.js'; +export * from './daemon/transport.js'; +export * from './daemon/inMemoryTransport.js'; +export * from './daemon/redisTransport.js'; + +const isMain = (): boolean => { + if (!process.argv[1]) { + return false; + } + return fileURLToPath(import.meta.url) === path.resolve(process.argv[1]); +}; + +if (isMain()) { + runGameApiServer().catch((error) => { + console.error('[game-api] failed to start', error); + process.exitCode = 1; + }); +} diff --git a/app/game-api/src/router.ts b/app/game-api/src/router.ts new file mode 100644 index 0000000..cbd0d63 --- /dev/null +++ b/app/game-api/src/router.ts @@ -0,0 +1,94 @@ +import { z } from 'zod'; + +import type { WorldStateRow } from './context.js'; +import { procedure, router } from './trpc.js'; + +const zRunReason = z.enum(['schedule', 'manual', 'poke']); + +const zTurnRunBudget = z.object({ + budgetMs: z.number().int().positive(), + maxGenerals: z.number().int().positive(), + catchUpCap: z.number().int().positive(), +}); + +const toWorldStateSnapshot = (row: WorldStateRow) => ({ + scenarioCode: row.scenarioCode, + currentYear: row.currentYear, + currentMonth: row.currentMonth, + tickSeconds: row.tickSeconds, + config: row.config, + meta: row.meta, + updatedAt: row.updatedAt.toISOString(), +}); + +export const appRouter = router({ + health: router({ + ping: procedure.query(({ ctx }) => ({ + ok: true, + profile: ctx.profile.name, + now: new Date().toISOString(), + })), + }), + world: router({ + getState: procedure.query(async ({ ctx }) => { + const state = await ctx.db.worldState.findFirst(); + return state ? toWorldStateSnapshot(state) : null; + }), + }), + turnDaemon: router({ + run: procedure + .input( + z.object({ + reason: zRunReason, + targetTime: z.string().min(1).optional(), + budget: zTurnRunBudget.optional(), + }) + ) + .mutation(async ({ ctx, input }) => { + const requestId = await ctx.turnDaemon.sendCommand({ + type: 'run', + reason: input.reason, + targetTime: input.targetTime, + budget: input.budget, + }); + return { accepted: true, requestId }; + }), + pause: procedure + .input( + z.object({ + reason: z.string().min(1).optional(), + }).optional() + ) + .mutation(async ({ ctx, input }) => { + const requestId = await ctx.turnDaemon.sendCommand({ + type: 'pause', + reason: input?.reason, + }); + return { accepted: true, requestId }; + }), + resume: procedure + .input( + z.object({ + reason: z.string().min(1).optional(), + }).optional() + ) + .mutation(async ({ ctx, input }) => { + const requestId = await ctx.turnDaemon.sendCommand({ + type: 'resume', + reason: input?.reason, + }); + return { accepted: true, requestId }; + }), + status: procedure + .input( + z.object({ + timeoutMs: z.number().int().positive().optional(), + }).optional() + ) + .query(async ({ ctx, input }) => { + return ctx.turnDaemon.requestStatus(input?.timeoutMs); + }), + }), +}); + +export type AppRouter = typeof appRouter; diff --git a/app/game-api/src/server.ts b/app/game-api/src/server.ts new file mode 100644 index 0000000..58a3c76 --- /dev/null +++ b/app/game-api/src/server.ts @@ -0,0 +1,78 @@ +import fastify from 'fastify'; +import cors from '@fastify/cors'; +import { fastifyTRPCPlugin } from '@trpc/server/adapters/fastify'; +import { + createPostgresConnector, + createRedisConnector, + resolvePostgresConfigFromEnv, + resolveRedisConfigFromEnv, +} from '@sammo-ts/infra'; + +import { resolveGameApiConfigFromEnv } from './config.js'; +import { createGameApiContext } from './context.js'; +import { buildTurnDaemonStreamKeys } from './daemon/streamKeys.js'; +import { RedisTurnDaemonTransport } from './daemon/redisTransport.js'; +import { appRouter } from './router.js'; + +export const createGameApiServer = async () => { + const config = resolveGameApiConfigFromEnv(); + const postgres = createPostgresConnector(resolvePostgresConfigFromEnv()); + const redis = createRedisConnector(resolveRedisConfigFromEnv()); + + await postgres.connect(); + await redis.connect(); + + const turnDaemon = new RedisTurnDaemonTransport(redis.client, { + keys: buildTurnDaemonStreamKeys(config.profileName), + requestTimeoutMs: config.daemonRequestTimeoutMs, + }); + + const app = fastify({ + logger: true, + }); + + await app.register(cors, { + origin: true, + credentials: true, + }); + + await app.register(fastifyTRPCPlugin, { + prefix: config.trpcPath, + trpcOptions: { + router: appRouter, + createContext: () => + createGameApiContext({ + db: postgres.prisma, + turnDaemon, + profile: { + id: config.profile, + scenario: config.scenario, + name: config.profileName, + }, + }), + }, + }); + + app.get('/healthz', async () => ({ + ok: true, + profile: config.profileName, + })); + + app.addHook('onClose', async () => { + await redis.disconnect(); + await postgres.disconnect(); + }); + + return { + app, + config, + }; +}; + +export const runGameApiServer = async (): Promise => { + const { app, config } = await createGameApiServer(); + await app.listen({ + host: config.host, + port: config.port, + }); +}; diff --git a/app/game-api/src/trpc.ts b/app/game-api/src/trpc.ts new file mode 100644 index 0000000..7e91431 --- /dev/null +++ b/app/game-api/src/trpc.ts @@ -0,0 +1,8 @@ +import { initTRPC } from '@trpc/server'; + +import type { GameApiContext } from './context.js'; + +const t = initTRPC.context().create(); + +export const router = t.router; +export const procedure = t.procedure; diff --git a/app/game-api/test/router.test.ts b/app/game-api/test/router.test.ts new file mode 100644 index 0000000..01fa14d --- /dev/null +++ b/app/game-api/test/router.test.ts @@ -0,0 +1,75 @@ +import { describe, expect, it } from 'vitest'; + +import type { GameApiContext, GameProfile, WorldStateRow } from '../src/context.js'; +import { InMemoryTurnDaemonTransport } from '../src/daemon/inMemoryTransport.js'; +import { appRouter } from '../src/router.js'; + +const profile: GameProfile = { + id: 'che', + scenario: 'default', + name: 'che:default', +}; + +const buildContext = (options?: { + state?: WorldStateRow | null; + transport?: InMemoryTurnDaemonTransport; +}): GameApiContext => { + const transport = options?.transport ?? new InMemoryTurnDaemonTransport(); + const db = { + worldState: { + findFirst: async () => options?.state ?? null, + }, + }; + return { + db, + turnDaemon: transport, + profile, + }; +}; + +describe('appRouter', () => { + it('queues turn daemon run commands', async () => { + const transport = new InMemoryTurnDaemonTransport(); + const caller = appRouter.createCaller(buildContext({ transport })); + const response = await caller.turnDaemon.run({ reason: 'manual' }); + + expect(response.accepted).toBe(true); + expect(transport.commands).toHaveLength(1); + expect(transport.commands[0]?.command.type).toBe('run'); + expect(transport.commands[0]?.requestId).toBe(response.requestId); + }); + + it('returns world state snapshots', async () => { + const state: WorldStateRow = { + scenarioCode: 'default', + currentYear: 1, + currentMonth: 2, + tickSeconds: 600, + config: { seed: 123 }, + meta: { label: 'sample' }, + updatedAt: new Date('2026-01-01T00:00:00Z'), + }; + + const caller = appRouter.createCaller(buildContext({ state })); + const response = await caller.world.getState(); + + expect(response?.scenarioCode).toBe('default'); + expect(response?.currentYear).toBe(1); + expect(response?.updatedAt).toBe('2026-01-01T00:00:00.000Z'); + }); + + it('returns status from transport', async () => { + const transport = new InMemoryTurnDaemonTransport({ + state: 'paused', + running: false, + paused: true, + queueDepth: 2, + }); + + const caller = appRouter.createCaller(buildContext({ transport })); + const response = await caller.turnDaemon.status(); + + expect(response?.state).toBe('paused'); + expect(response?.queueDepth).toBe(2); + }); +}); diff --git a/app/game-api/test/streamKeys.test.ts b/app/game-api/test/streamKeys.test.ts new file mode 100644 index 0000000..d15b420 --- /dev/null +++ b/app/game-api/test/streamKeys.test.ts @@ -0,0 +1,11 @@ +import { describe, expect, it } from 'vitest'; + +import { buildTurnDaemonStreamKeys } from '../src/daemon/streamKeys.js'; + +describe('buildTurnDaemonStreamKeys', () => { + it('namespaces streams by profile', () => { + const keys = buildTurnDaemonStreamKeys('che:default'); + expect(keys.commandStream).toBe('sammo:che:default:turn-daemon:commands'); + expect(keys.eventStream).toBe('sammo:che:default:turn-daemon:events'); + }); +}); diff --git a/app/game-api/vitest.config.ts b/app/game-api/vitest.config.ts new file mode 100644 index 0000000..400c273 --- /dev/null +++ b/app/game-api/vitest.config.ts @@ -0,0 +1,17 @@ +import path from 'node:path'; + +import { defineConfig } from 'vitest/config'; +import tsconfigPaths from 'vite-tsconfig-paths'; + +export default defineConfig({ + plugins: [ + tsconfigPaths({ + projects: [path.resolve(__dirname, '../../tsconfig.paths.json')], + }), + ], + test: { + environment: 'node', + globals: true, + include: ['test/**/*.test.ts'], + }, +}); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 94e9aae..2219e19 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -19,10 +19,32 @@ importers: version: 5.9.3 app/game-api: + dependencies: + '@fastify/cors': + specifier: ^10.0.1 + version: 10.1.0 + '@sammo-ts/infra': + specifier: workspace:* + version: link:../../packages/infra + '@trpc/server': + specifier: ^11.4.4 + version: 11.8.1(typescript@5.9.3) + fastify: + specifier: ^5.3.3 + version: 5.6.2 + zod: + specifier: ^4.2.1 + version: 4.2.1 devDependencies: tsdown: specifier: ^0.18.3 version: 0.18.3(typescript@5.9.3) + vite-tsconfig-paths: + specifier: ^6.0.0 + version: 6.0.3(typescript@5.9.3)(vite@7.3.0(@types/node@20.19.27)(jiti@2.6.1)) + vitest: + specifier: ^4.0.16 + version: 4.0.16(@types/node@20.19.27)(jiti@2.6.1) app/game-engine: dependencies: @@ -338,6 +360,27 @@ packages: cpu: [x64] os: [win32] + '@fastify/ajv-compiler@4.0.5': + resolution: {integrity: sha512-KoWKW+MhvfTRWL4qrhUwAAZoaChluo0m0vbiJlGMt2GXvL4LVPQEjt8kSpHI3IBq5Rez8fg+XeH3cneztq+C7A==} + + '@fastify/cors@10.1.0': + resolution: {integrity: sha512-MZyBCBJtII60CU9Xme/iE4aEy8G7QpzGR8zkdXZkDFt7ElEMachbE61tfhAG/bvSaULlqlf0huMT12T7iqEmdQ==} + + '@fastify/error@4.2.0': + resolution: {integrity: sha512-RSo3sVDXfHskiBZKBPRgnQTtIqpi/7zhJOEmAxCiBcM7d0uwdGdxLlsCaLzGs8v8NnxIRlfG0N51p5yFaOentQ==} + + '@fastify/fast-json-stringify-compiler@5.0.3': + resolution: {integrity: sha512-uik7yYHkLr6fxd8hJSZ8c+xF4WafPK+XzneQDPU+D10r5X19GW8lJcom2YijX2+qtFF1ENJlHXKFM9ouXNJYgQ==} + + '@fastify/forwarded@3.0.1': + resolution: {integrity: sha512-JqDochHFqXs3C3Ml3gOY58zM7OqO9ENqPo0UqAjAjH8L01fRZqwX9iLeX34//kiJubF7r2ZQHtBRU36vONbLlw==} + + '@fastify/merge-json-schemas@0.2.1': + resolution: {integrity: sha512-OA3KGBCy6KtIvLf8DINC5880o5iBlDX4SxzLQS8HorJAbqluzLRn80UXU0bxZn7UOFhFgpRJDasfwn9nG4FG4A==} + + '@fastify/proxy-addr@5.1.0': + resolution: {integrity: sha512-INS+6gh91cLUjB+PVHfu1UqcB76Sqtpyp7bnL+FYojhjygvOPA9ctiD/JDKsyD9Xgu4hUhCSJBPig/w7duNajw==} + '@hono/node-server@1.19.6': resolution: {integrity: sha512-Shz/KjlIeAhfiuE93NDKVdZ7HdBVLQAfdbaXEaoAVO3ic9ibRSLGIQGkcBbFyuLr+7/1D5ZCINM8B+6IvXeMtw==} engines: {node: '>=18.14.1'} @@ -367,6 +410,9 @@ packages: '@oxc-project/types@0.103.0': resolution: {integrity: sha512-bkiYX5kaXWwUessFRSoXFkGIQTmc6dLGdxuRTrC+h8PSnIdZyuXHHlLAeTmOue5Br/a0/a7dHH0Gca6eXn9MKg==} + '@pinojs/redact@0.4.0': + resolution: {integrity: sha512-k2ENnmBugE/rzQfEcdWHcCY+/FM3VLzH9cYEsbdsoqrvzAKRhUZeRNhAZvB8OitQJ1TBed3yqWtdjzS6wJKBwg==} + '@prisma/adapter-pg@7.2.0': resolution: {integrity: sha512-euIdQ13cRB2wZ3jPsnDnFhINquo1PYFPCg6yVL8b2rp3EdinQHsX9EDdCtRr489D5uhphcRk463OdQAFlsCr0w==} @@ -650,6 +696,11 @@ packages: '@standard-schema/spec@1.1.0': resolution: {integrity: sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==} + '@trpc/server@11.8.1': + resolution: {integrity: sha512-P4rzZRpEL7zDFgjxK65IdyH0e41FMFfTkQkuq0BA5tKcr7E6v9/v38DEklCpoDN6sPiB1Sigy/PUEzHENhswDA==} + peerDependencies: + typescript: '>=5.7.2' + '@tybys/wasm-util@0.10.1': resolution: {integrity: sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg==} @@ -700,6 +751,20 @@ packages: '@vitest/utils@4.0.16': resolution: {integrity: sha512-h8z9yYhV3e1LEfaQ3zdypIrnAg/9hguReGZoS7Gl0aBG5xgA410zBqECqmaF/+RkTggRsfnzc1XaAHA6bmUufA==} + abstract-logging@2.0.1: + resolution: {integrity: sha512-2BjRTZxTPvheOvGbBslFSYOUkr+SjPtOnrLP33f+VIWLzezQpZcqVg7ja3L4dBXmzzgwT+a029jRx5PCi3JuiA==} + + ajv-formats@3.0.1: + resolution: {integrity: sha512-8iUql50EUR+uUcdRQ3HDqa6EVyo3docL8g5WJ3FNcWmu62IbkGUue/pEyLBW8VGKKucTPgqeks4fIU1DA4yowQ==} + peerDependencies: + ajv: ^8.0.0 + peerDependenciesMeta: + ajv: + optional: true + + ajv@8.17.1: + resolution: {integrity: sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==} + ansis@4.2.0: resolution: {integrity: sha512-HqZ5rWlFjGiV0tDm3UxxgNRqsOTniqoKZu0pIAfh7TZQMGuZK+hH0drySty0si0QXj1ieop4+SkSfPZBPPkHig==} engines: {node: '>=14'} @@ -712,6 +777,13 @@ packages: resolution: {integrity: sha512-m1Q/RaVOnTp9JxPX+F+Zn7IcLYMzM8kZofDImfsKZd8MbR+ikdOzTeztStWqfrqIxZnYWryyI9ePm3NGjnZgGw==} engines: {node: '>=20.19.0'} + atomic-sleep@1.0.0: + resolution: {integrity: sha512-kNOjDqAh7px0XWNI+4QbzoiR/nTkHAWNud2uvnJquD1/x5a7EQZMJT0AczqK0Qn67oY/TTQ1LbUKajZpp3I9tQ==} + engines: {node: '>=8.0.0'} + + avvio@9.1.0: + resolution: {integrity: sha512-fYASnYi600CsH/j9EQov7lECAniYiBFiiAtBNuZYLA2leLe9qOvZzqYHFjtIj6gD2VMoMLP14834LFWvr4IfDw==} + aws-ssl-profiles@1.1.2: resolution: {integrity: sha512-NZKeq9AfyQvEeNlN0zSYAaWrmBffJh3IELMZfRpJVWgrpEbtEpnjvzqBPf+mxoI287JohRDoa+/nsfqqiZmF6g==} engines: {node: '>= 6.0.0'} @@ -756,6 +828,10 @@ packages: resolution: {integrity: sha512-5IKcdX0nnYavi6G7TtOhwkYzyjfJlatbjMjuLSfE2kYT5pMDOilZ4OvMhi637CcDICTmz3wARPoyhqyX1Y+XvA==} engines: {node: ^14.18.0 || >=16.10.0} + cookie@1.1.1: + resolution: {integrity: sha512-ei8Aos7ja0weRpFzJnEA9UHJ/7XQmqglbRwnf2ATjcB9Wq874VKH9kfjjirM6UhU2/E5fFYadylyhFldcqSidQ==} + engines: {node: '>=18'} + cross-spawn@7.0.6: resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==} engines: {node: '>= 8'} @@ -783,6 +859,10 @@ packages: resolution: {integrity: sha512-HVQE3AAb/pxF8fQAoiqpvg9i3evqug3hoiwakOyZAwJm+6vZehbkYXZ0l4JxS+I3QxM97v5aaRNhj8v5oBhekw==} engines: {node: '>=0.10'} + dequal@2.0.3: + resolution: {integrity: sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==} + engines: {node: '>=6'} + destr@2.0.5: resolution: {integrity: sha512-ugFTXCtDZunbzasqBxrK93Ik/DRYsO6S/fedkWEMKqt04xZ4csmnmwGDBAb07QWNaGMAmnTIemsYZCksjATwsA==} @@ -832,6 +912,30 @@ packages: resolution: {integrity: sha512-h5+1OzzfCC3Ef7VbtKdcv7zsstUQwUDlYpUTvjeUsJAssPgLn7QzbboPtL5ro04Mq0rPOsMzl7q5hIbRs2wD1A==} engines: {node: '>=8.0.0'} + fast-decode-uri-component@1.0.1: + resolution: {integrity: sha512-WKgKWg5eUxvRZGwW8FvfbaH7AXSh2cL+3j5fMGzUMCxWBJ3dV3a7Wz8y2f/uQ0e3B6WmodD3oS54jTQ9HVTIIg==} + + fast-deep-equal@3.1.3: + resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==} + + fast-json-stringify@6.1.1: + resolution: {integrity: sha512-DbgptncYEXZqDUOEl4krff4mUiVrTZZVI7BBrQR/T3BqMj/eM1flTC1Uk2uUoLcWCxjT95xKulV/Lc6hhOZsBQ==} + + fast-querystring@1.1.2: + resolution: {integrity: sha512-g6KuKWmFXc0fID8WWH0jit4g0AGBoJhCkJMb1RmbsSEUNvQ+ZC8D6CUZ+GtF8nMzSPXnhiePyyqqipzNNEnHjg==} + + fast-uri@3.1.0: + resolution: {integrity: sha512-iPeeDKJSWf4IEOasVVrknXpaBV0IApz/gp7S2bb7Z4Lljbl2MGJRqInZiUrQwV16cpzw/D3S5j5Julj/gT52AA==} + + fastify-plugin@5.1.0: + resolution: {integrity: sha512-FAIDA8eovSt5qcDgcBvDuX/v0Cjz0ohGhENZ/wpc3y+oZCY2afZ9Baqql3g/lC+OHRnciQol4ww7tuthOb9idw==} + + fastify@5.6.2: + resolution: {integrity: sha512-dPugdGnsvYkBlENLhCgX8yhyGCsCPrpA8lFWbTNU428l+YOnLgYHR69hzV8HWPC79n536EqzqQtvhtdaCE0dKg==} + + fastq@1.20.1: + resolution: {integrity: sha512-GGToxJ/w1x32s/D2EKND7kTil4n8OVk/9mycTc4VDza13lOvpUZTGX3mFSCtV9ksdGBVzvsyAVLM6mHFThxXxw==} + fdir@6.5.0: resolution: {integrity: sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==} engines: {node: '>=12.0.0'} @@ -841,6 +945,10 @@ packages: picomatch: optional: true + find-my-way@9.3.0: + resolution: {integrity: sha512-eRoFWQw+Yv2tuYlK2pjFS2jGXSxSppAs3hSQjfxVKxM5amECzIgYYc1FEI8ZmhSh/Ig+FrKEz43NLRKJjYCZVg==} + engines: {node: '>=20'} + foreground-child@3.3.1: resolution: {integrity: sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==} engines: {node: '>=14'} @@ -894,6 +1002,10 @@ packages: resolution: {integrity: sha512-B6Lc2s6yApwnD2/pMzFh/d5AVjdsDXjgkeJ766FmFuJELIGHNycKRj+l3A39yZPM4CchqNCB4RITEAYB1KUM6A==} engines: {node: '>=20.19.0'} + ipaddr.js@2.3.0: + resolution: {integrity: sha512-Zv/pA+ciVFbCSBBjGfaKUya/CcGmUHzTydLMaTwrUUEM2DIEO3iZvueGxmacvmN50fGpGVKeTXpb2LcYQxeVdg==} + engines: {node: '>= 10'} + is-property@1.0.2: resolution: {integrity: sha512-Ks/IoX00TtClbGQr4TWXemAnktAQvYB7HzcCxDGqEZU6oCmb2INHuOoKxbtR+HFkmYWBKv/dOZtGRiAjDhj92g==} @@ -912,6 +1024,15 @@ packages: engines: {node: '>=6'} hasBin: true + json-schema-ref-resolver@3.0.0: + resolution: {integrity: sha512-hOrZIVL5jyYFjzk7+y7n5JDzGlU8rfWDuYyHwGa2WA8/pcmMHezp2xsVwxrebD/Q9t8Nc5DboieySDpCp4WG4A==} + + json-schema-traverse@1.0.0: + resolution: {integrity: sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==} + + light-my-request@6.6.0: + resolution: {integrity: sha512-CHYbu8RtboSIoVsHZ6Ye4cj4Aw/yg2oAFimlF7mNvfDV192LR7nDiKtSIfCuLT7KokPSTn/9kfVLm5OGN0A28A==} + lilconfig@2.1.0: resolution: {integrity: sha512-utWOt/GHzuUxnLKxB6dk81RoOeoNeHgbrXiuGk4yyF5qlRz+iIVWu56E2fqGHFrXz0QNUhLB/8nKqvRH66JKGQ==} engines: {node: '>=10'} @@ -929,6 +1050,9 @@ packages: magic-string@0.30.21: resolution: {integrity: sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==} + mnemonist@0.40.0: + resolution: {integrity: sha512-kdd8AFNig2AD5Rkih7EPCXhu/iMvwevQFX/uEiGhZyPZi7fHqOoF4V4kHLpCfysxXMgQ4B52kdPMCwARshKvEg==} + ms@2.1.3: resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} @@ -953,12 +1077,19 @@ packages: engines: {node: ^14.16.0 || >=16.10.0} hasBin: true + obliterator@2.0.5: + resolution: {integrity: sha512-42CPE9AhahZRsMNslczq0ctAEtqk8Eka26QofnqC346BZdHDySk3LWka23LI7ULIw11NmltpiLagIq8gBozxTw==} + obug@2.1.1: resolution: {integrity: sha512-uTqF9MuPraAQ+IsnPf366RG4cP9RtUi7MLO1N3KEc+wb0a6yKpeL0lmk2IB1jY5KHPAlTc6T/JRdC/YqxHNwkQ==} ohash@2.0.11: resolution: {integrity: sha512-RdR9FQrFwNBNXAr4GixM8YaRZRJ5PUWbKYbE5eOsrwAjJW0q2REGcf79oYPsLyskQCZG1PLN+S/K1V00joZAoQ==} + on-exit-leak-free@2.1.2: + resolution: {integrity: sha512-0eJJY6hXLGf1udHwfNftBqH+g73EU4B504nZeKpz1sYRKafAghwxEJunB2O7rDZkL4PGfsMVnTXZ2EjibbqcsA==} + engines: {node: '>=14.0.0'} + path-key@3.1.1: resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==} engines: {node: '>=8'} @@ -1010,6 +1141,16 @@ packages: resolution: {integrity: sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==} engines: {node: '>=12'} + pino-abstract-transport@2.0.0: + resolution: {integrity: sha512-F63x5tizV6WCh4R6RHyi2Ml+M70DNRXt/+HANowMflpgGFMAym/VKm6G7ZOQRjqN7XbGxK1Lg9t6ZrtzOaivMw==} + + pino-std-serializers@7.0.0: + resolution: {integrity: sha512-e906FRY0+tV27iq4juKzSYPbUj2do2X2JX4EzSca1631EB2QJQUqGbDuERal7LCtOpxl6x3+nvo9NPZcmjkiFA==} + + pino@10.1.0: + resolution: {integrity: sha512-0zZC2ygfdqvqK8zJIr1e+wT1T/L+LF6qvqvbzEQ6tiMAoTqEVK9a1K3YRu8HEUvGEvNqZyPJTtb2sNIoTkB83w==} + hasBin: true + pkg-types@2.3.0: resolution: {integrity: sha512-SIqCzDRg0s9npO5XQ3tNZioRY1uK06lA41ynBC1YmFTmnY6FjUjVt6s4LoADmwoig1qqD0oK8h1p/8mlMx8Oig==} @@ -1054,6 +1195,12 @@ packages: typescript: optional: true + process-warning@4.0.1: + resolution: {integrity: sha512-3c2LzQ3rY9d0hc1emcsHhfT9Jwz0cChib/QN89oME2R451w5fy3f0afAhERFZAwrbDU43wk12d0ORBpDVME50Q==} + + process-warning@5.0.0: + resolution: {integrity: sha512-a39t9ApHNx2L4+HBnQKqxxHNs1r7KF+Intd8Q/g1bUh6q0WIp9voPXJ/x0j+ZL45KF1pJd9+q2jLIRMfvEshkA==} + proper-lockfile@4.1.2: resolution: {integrity: sha512-TjNPblN4BwAWMXU8s9AEz4JmQxnD1NNL7bNOY/AKUzyamc379FWASUhc/K1pL2noVb+XmZKLL68cjzLsiOAMaA==} @@ -1063,6 +1210,9 @@ packages: quansync@1.0.0: resolution: {integrity: sha512-5xZacEEufv3HSTPQuchrvV6soaiACMFnq1H8wkVioctoH3TRha9Sz66lOxRwPK/qZj7HPiSveih9yAyh98gvqA==} + quick-format-unescaped@4.0.4: + resolution: {integrity: sha512-tYC1Q1hgyRuHgloV/YXs2w15unPVh8qfu/qCTfhTYamaw7fyhumKa2yGpdSo87vY32rIclj+4fWYQXUMs9EHvg==} + rc9@2.1.2: resolution: {integrity: sha512-btXCnMmRIBINM2LDZoEmOogIZU7Qe7zn4BpomSKZ/ykbLObuBdvG+mFq11DL6fjH1DRwHhrlgtYWG96bJiC7Cg==} @@ -1079,6 +1229,10 @@ packages: resolution: {integrity: sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==} engines: {node: '>= 14.18.0'} + real-require@0.2.0: + resolution: {integrity: sha512-57frrGM/OCTLqLOAh0mhVA9VBMHd+9U7Zb2THMGdBUoZVOtGbJzjxsYGDJ3A9AYYCP4hn6y1TVbaOfzWtm5GFg==} + engines: {node: '>= 12.13.0'} + redis@4.7.1: resolution: {integrity: sha512-S1bJDnqLftzHXHP8JsT5II/CtHWQrASX5K96REjWjlmWKrviSOLWmM7QnRLstAWsu1VBBV1ffV6DzCvxNP0UJQ==} @@ -1088,13 +1242,28 @@ packages: remeda@2.21.3: resolution: {integrity: sha512-XXrZdLA10oEOQhLLzEJEiFFSKi21REGAkHdImIb4rt/XXy8ORGXh5HCcpUOsElfPNDb+X6TA/+wkh+p2KffYmg==} + require-from-string@2.0.2: + resolution: {integrity: sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==} + engines: {node: '>=0.10.0'} + resolve-pkg-maps@1.0.0: resolution: {integrity: sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==} + ret@0.5.0: + resolution: {integrity: sha512-I1XxrZSQ+oErkRR4jYbAyEEu2I0avBvvMM5JN+6EBprOGRCs63ENqZ3vjavq8fBw2+62G5LF5XelKwuJpcvcxw==} + engines: {node: '>=10'} + retry@0.12.0: resolution: {integrity: sha512-9LkiTwjUh6rT555DtE9rTX+BKByPfrMzEAtnlEtdEwr3Nkffwiihqe2bWADg+OQRjt9gl6ICdmB/ZFDCGAtSow==} engines: {node: '>= 4'} + reusify@1.1.0: + resolution: {integrity: sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==} + engines: {iojs: '>=1.0.0', node: '>=0.10.0'} + + rfdc@1.4.1: + resolution: {integrity: sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA==} + rolldown-plugin-dts@0.20.0: resolution: {integrity: sha512-cLAY1kN2ilTYMfZcFlGWbXnu6Nb+8uwUBsi+Mjbh4uIx7IN8uMOmJ7RxrrRgPsO4H7eSz3E+JwGoL1gyugiyUA==} engines: {node: '>=20.19.0'} @@ -1124,12 +1293,22 @@ packages: engines: {node: '>=18.0.0', npm: '>=8.0.0'} hasBin: true + safe-regex2@5.0.0: + resolution: {integrity: sha512-YwJwe5a51WlK7KbOJREPdjNrpViQBI3p4T50lfwPuDhZnE3XGVTlGvi+aolc5+RvxDD6bnUmjVsU9n1eboLUYw==} + + safe-stable-stringify@2.5.0: + resolution: {integrity: sha512-b3rppTKm9T+PsVCBEOUR46GWI7fdOs00VKZ1+9c1EWDaDMvjQc6tUwuFyIprgGgTcWoVHSKrU8H31ZHA2e0RHA==} + engines: {node: '>=10'} + safer-buffer@2.1.2: resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==} scheduler@0.27.0: resolution: {integrity: sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==} + secure-json-parse@4.1.0: + resolution: {integrity: sha512-l4KnYfEyqYJxDwlNVyRfO2E4NTHfMKAWdUuA8J0yve2Dz/E/PdBepY03RvyJpssIpRFwJoCD55wA+mEDs6ByWA==} + semver@7.7.3: resolution: {integrity: sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==} engines: {node: '>=10'} @@ -1138,6 +1317,9 @@ packages: seq-queue@0.0.5: resolution: {integrity: sha512-hr3Wtp/GZIc/6DAGPDcV4/9WoZhjrkXsi5B/07QgX8tsdc6ilr7BFM6PM6rbdAX1kFSDYeZGLipIZZKyQP0O5Q==} + set-cookie-parser@2.7.2: + resolution: {integrity: sha512-oeM1lpU/UvhTxw+g3cIfxXHyJRc/uidd3yK1P242gzHds0udQBYzs3y8j4gCCW+ZJ7ad0yctld8RYO+bdurlvw==} + shebang-command@2.0.0: resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==} engines: {node: '>=8'} @@ -1156,6 +1338,9 @@ packages: resolution: {integrity: sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==} engines: {node: '>=14'} + sonic-boom@4.2.0: + resolution: {integrity: sha512-INb7TM37/mAcsGmc9hyyI6+QR3rR1zVRu36B0NeGXKnOOLiZOfER5SA+N7X7k3yUYRzLWafduTDvJAfDswwEww==} + source-map-js@1.2.1: resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==} engines: {node: '>=0.10.0'} @@ -1177,6 +1362,9 @@ packages: std-env@3.9.0: resolution: {integrity: sha512-UGvjygr6F6tpH7o2qyqR6QYpwraIjKSdtzyBdyytFOHmPZY917kwdwLG0RbOjWOnKmnm3PeHjaoLLMie7kPLQw==} + thread-stream@3.1.0: + resolution: {integrity: sha512-OqyPZ9u96VohAyMfJykzmivOrY2wfMSf3C5TtFJVgN+Hm6aj+voFhlK+kZEIv2FBh1X6Xp3DlnCOfEQ3B2J86A==} + tinybench@2.9.0: resolution: {integrity: sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==} @@ -1192,6 +1380,10 @@ packages: resolution: {integrity: sha512-PSkbLUoxOFRzJYjjxHJt9xro7D+iilgMX/C9lawzVuYiIdcihh9DXmVibBe8lmcFrRi/VzlPjBxbN7rH24q8/Q==} engines: {node: '>=14.0.0'} + toad-cache@3.7.0: + resolution: {integrity: sha512-/m8M+2BJUpoJdgAHoG+baCwBT+tf2VraSfkBgl0Y00qIWt41DJ8R5B8nsEw0I58YwF5IZH6z24/2TobDKnqSWw==} + engines: {node: '>=12'} + tree-kill@1.2.2: resolution: {integrity: sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==} hasBin: true @@ -1275,6 +1467,14 @@ packages: vite: optional: true + vite-tsconfig-paths@6.0.3: + resolution: {integrity: sha512-7bL7FPX/DSviaZGYUKowWF1AiDVWjMjxNbE8lyaVGDezkedWqfGhlnQ4BZXre0ZN5P4kAgIJfAlgFDVyjrCIyg==} + peerDependencies: + vite: '*' + peerDependenciesMeta: + vite: + optional: true + vite@7.3.0: resolution: {integrity: sha512-dZwN5L1VlUBewiP6H9s2+B3e3Jg96D0vzN+Ry73sOefebhYr9f94wwkMNN/9ouoU8pV1BqA1d1zGk8928cx0rg==} engines: {node: ^20.19.0 || >=22.12.0} @@ -1514,6 +1714,34 @@ snapshots: '@esbuild/win32-x64@0.27.2': optional: true + '@fastify/ajv-compiler@4.0.5': + dependencies: + ajv: 8.17.1 + ajv-formats: 3.0.1(ajv@8.17.1) + fast-uri: 3.1.0 + + '@fastify/cors@10.1.0': + dependencies: + fastify-plugin: 5.1.0 + mnemonist: 0.40.0 + + '@fastify/error@4.2.0': {} + + '@fastify/fast-json-stringify-compiler@5.0.3': + dependencies: + fast-json-stringify: 6.1.1 + + '@fastify/forwarded@3.0.1': {} + + '@fastify/merge-json-schemas@0.2.1': + dependencies: + dequal: 2.0.3 + + '@fastify/proxy-addr@5.1.0': + dependencies: + '@fastify/forwarded': 3.0.1 + ipaddr.js: 2.3.0 + '@hono/node-server@1.19.6(hono@4.10.6)': dependencies: hono: 4.10.6 @@ -1546,6 +1774,8 @@ snapshots: '@oxc-project/types@0.103.0': {} + '@pinojs/redact@0.4.0': {} + '@prisma/adapter-pg@7.2.0': dependencies: '@prisma/driver-adapter-utils': 7.2.0 @@ -1774,6 +2004,10 @@ snapshots: '@standard-schema/spec@1.1.0': {} + '@trpc/server@11.8.1(typescript@5.9.3)': + dependencies: + typescript: 5.9.3 + '@tybys/wasm-util@0.10.1': dependencies: tslib: 2.8.1 @@ -1841,6 +2075,19 @@ snapshots: '@vitest/pretty-format': 4.0.16 tinyrainbow: 3.0.3 + abstract-logging@2.0.1: {} + + ajv-formats@3.0.1(ajv@8.17.1): + optionalDependencies: + ajv: 8.17.1 + + ajv@8.17.1: + dependencies: + fast-deep-equal: 3.1.3 + fast-uri: 3.1.0 + json-schema-traverse: 1.0.0 + require-from-string: 2.0.2 + ansis@4.2.0: {} assertion-error@2.0.1: {} @@ -1850,6 +2097,13 @@ snapshots: '@babel/parser': 7.28.5 pathe: 2.0.3 + atomic-sleep@1.0.0: {} + + avvio@9.1.0: + dependencies: + '@fastify/error': 4.2.0 + fastq: 1.20.1 + aws-ssl-profiles@1.1.2: {} birpc@4.0.0: {} @@ -1896,6 +2150,8 @@ snapshots: consola@3.4.2: {} + cookie@1.1.1: {} + cross-spawn@7.0.6: dependencies: path-key: 3.1.1 @@ -1914,6 +2170,8 @@ snapshots: denque@2.1.0: {} + dequal@2.0.3: {} + destr@2.0.5: {} dotenv@16.6.1: {} @@ -1972,10 +2230,59 @@ snapshots: dependencies: pure-rand: 6.1.0 + fast-decode-uri-component@1.0.1: {} + + fast-deep-equal@3.1.3: {} + + fast-json-stringify@6.1.1: + dependencies: + '@fastify/merge-json-schemas': 0.2.1 + ajv: 8.17.1 + ajv-formats: 3.0.1(ajv@8.17.1) + fast-uri: 3.1.0 + json-schema-ref-resolver: 3.0.0 + rfdc: 1.4.1 + + fast-querystring@1.1.2: + dependencies: + fast-decode-uri-component: 1.0.1 + + fast-uri@3.1.0: {} + + fastify-plugin@5.1.0: {} + + fastify@5.6.2: + dependencies: + '@fastify/ajv-compiler': 4.0.5 + '@fastify/error': 4.2.0 + '@fastify/fast-json-stringify-compiler': 5.0.3 + '@fastify/proxy-addr': 5.1.0 + abstract-logging: 2.0.1 + avvio: 9.1.0 + fast-json-stringify: 6.1.1 + find-my-way: 9.3.0 + light-my-request: 6.6.0 + pino: 10.1.0 + process-warning: 5.0.0 + rfdc: 1.4.1 + secure-json-parse: 4.1.0 + semver: 7.7.3 + toad-cache: 3.7.0 + + fastq@1.20.1: + dependencies: + reusify: 1.1.0 + fdir@6.5.0(picomatch@4.0.3): optionalDependencies: picomatch: 4.0.3 + find-my-way@9.3.0: + dependencies: + fast-deep-equal: 3.1.3 + fast-querystring: 1.1.2 + safe-regex2: 5.0.0 + foreground-child@3.3.1: dependencies: cross-spawn: 7.0.6 @@ -2023,6 +2330,8 @@ snapshots: import-without-cache@0.2.5: {} + ipaddr.js@2.3.0: {} + is-property@1.0.2: {} isexe@2.0.0: {} @@ -2033,6 +2342,18 @@ snapshots: jsesc@3.1.0: {} + json-schema-ref-resolver@3.0.0: + dependencies: + dequal: 2.0.3 + + json-schema-traverse@1.0.0: {} + + light-my-request@6.6.0: + dependencies: + cookie: 1.1.1 + process-warning: 4.0.1 + set-cookie-parser: 2.7.2 + lilconfig@2.1.0: {} lodash@4.17.21: {} @@ -2045,6 +2366,10 @@ snapshots: dependencies: '@jridgewell/sourcemap-codec': 1.5.5 + mnemonist@0.40.0: + dependencies: + obliterator: 2.0.5 + ms@2.1.3: {} mysql2@3.15.3: @@ -2075,10 +2400,14 @@ snapshots: pkg-types: 2.3.0 tinyexec: 1.0.2 + obliterator@2.0.5: {} + obug@2.1.1: {} ohash@2.0.11: {} + on-exit-leak-free@2.1.2: {} + path-key@3.1.1: {} pathe@2.0.3: {} @@ -2124,6 +2453,26 @@ snapshots: picomatch@4.0.3: {} + pino-abstract-transport@2.0.0: + dependencies: + split2: 4.2.0 + + pino-std-serializers@7.0.0: {} + + pino@10.1.0: + dependencies: + '@pinojs/redact': 0.4.0 + atomic-sleep: 1.0.0 + on-exit-leak-free: 2.1.2 + pino-abstract-transport: 2.0.0 + pino-std-serializers: 7.0.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.0 + thread-stream: 3.1.0 + pkg-types@2.3.0: dependencies: confbox: 0.2.2 @@ -2166,6 +2515,10 @@ snapshots: - react - react-dom + process-warning@4.0.1: {} + + process-warning@5.0.0: {} + proper-lockfile@4.1.2: dependencies: graceful-fs: 4.2.11 @@ -2176,6 +2529,8 @@ snapshots: quansync@1.0.0: {} + quick-format-unescaped@4.0.4: {} + rc9@2.1.2: dependencies: defu: 6.1.4 @@ -2190,6 +2545,8 @@ snapshots: readdirp@4.1.2: {} + real-require@0.2.0: {} + redis@4.7.1: dependencies: '@redis/bloom': 1.2.0(@redis/client@1.6.1) @@ -2205,10 +2562,18 @@ snapshots: dependencies: type-fest: 4.41.0 + require-from-string@2.0.2: {} + resolve-pkg-maps@1.0.0: {} + ret@0.5.0: {} + retry@0.12.0: {} + reusify@1.1.0: {} + + rfdc@1.4.1: {} + rolldown-plugin-dts@0.20.0(rolldown@1.0.0-beta.57)(typescript@5.9.3): dependencies: '@babel/generator': 7.28.5 @@ -2272,14 +2637,24 @@ snapshots: '@rollup/rollup-win32-x64-msvc': 4.54.0 fsevents: 2.3.3 + safe-regex2@5.0.0: + dependencies: + ret: 0.5.0 + + safe-stable-stringify@2.5.0: {} + safer-buffer@2.1.2: {} scheduler@0.27.0: {} + secure-json-parse@4.1.0: {} + semver@7.7.3: {} seq-queue@0.0.5: {} + set-cookie-parser@2.7.2: {} + shebang-command@2.0.0: dependencies: shebang-regex: 3.0.0 @@ -2292,6 +2667,10 @@ snapshots: signal-exit@4.1.0: {} + sonic-boom@4.2.0: + dependencies: + atomic-sleep: 1.0.0 + source-map-js@1.2.1: {} split2@4.2.0: {} @@ -2304,6 +2683,10 @@ snapshots: std-env@3.9.0: {} + thread-stream@3.1.0: + dependencies: + real-require: 0.2.0 + tinybench@2.9.0: {} tinyexec@1.0.2: {} @@ -2315,6 +2698,8 @@ snapshots: tinyrainbow@3.0.3: {} + toad-cache@3.7.0: {} + tree-kill@1.2.2: {} tsconfck@3.1.6(typescript@5.9.3): @@ -2381,6 +2766,17 @@ snapshots: - supports-color - typescript + vite-tsconfig-paths@6.0.3(typescript@5.9.3)(vite@7.3.0(@types/node@20.19.27)(jiti@2.6.1)): + dependencies: + debug: 4.4.3 + globrex: 0.1.2 + tsconfck: 3.1.6(typescript@5.9.3) + optionalDependencies: + vite: 7.3.0(@types/node@20.19.27)(jiti@2.6.1) + transitivePeerDependencies: + - supports-color + - typescript + vite@7.3.0(@types/node@20.19.27)(jiti@2.6.1): dependencies: esbuild: 0.27.2