feat: implement game API with TRPC and Redis transport

- Added GameApiConfig interface and configuration resolver from environment variables.
- Created GameApiContext for managing database and transport dependencies.
- Implemented InMemoryTurnDaemonTransport for testing purposes.
- Developed RedisTurnDaemonTransport for command and status handling via Redis streams.
- Defined TurnDaemonStreamKeys for namespacing Redis streams by profile.
- Established TRPC router with endpoints for health check, world state retrieval, and turn daemon commands (run, pause, resume, status).
- Integrated Fastify server with TRPC and Redis transport.
- Added unit tests for router functionality and stream key generation.
- Configured Vitest for testing environment.
This commit is contained in:
2025-12-30 01:04:52 +00:00
parent d6c4a4ae25
commit 1688ca0d23
16 changed files with 1036 additions and 3 deletions
+11 -2
View File
@@ -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"
}
}
+42
View File
@@ -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'
),
};
};
+41
View File
@@ -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<WorldStateRow | null>;
};
}
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,
};
};
@@ -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<string> {
const requestId = command.type === 'getStatus' ? command.requestId : randomUUID();
this.commands.push({
requestId,
sentAt: new Date().toISOString(),
command,
});
return requestId;
}
async requestStatus(): Promise<TurnDaemonStatus> {
return this.status;
}
setStatus(status: TurnDaemonStatus): void {
this.status = status;
}
}
+110
View File
@@ -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<string, string> }>;
}>;
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<TurnDaemonEventEnvelope>;
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<string> {
const envelope = buildCommandEnvelope(command);
await this.client.xAdd(this.keys.commandStream, '*', {
payload: JSON.stringify(envelope),
});
return envelope.requestId;
}
async requestStatus(timeoutMs?: number): Promise<TurnDaemonStatus | null> {
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;
}
}
+11
View File
@@ -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`,
};
};
+6
View File
@@ -0,0 +1,6 @@
import type { TurnDaemonCommand, TurnDaemonStatus } from './types.js';
export interface TurnDaemonTransport {
sendCommand(command: TurnDaemonCommand): Promise<string>;
requestStatus(timeoutMs?: number): Promise<TurnDaemonStatus | null>;
}
+64
View File
@@ -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;
}
+28 -1
View File
@@ -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;
});
}
+94
View File
@@ -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;
+78
View File
@@ -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<void> => {
const { app, config } = await createGameApiServer();
await app.listen({
host: config.host,
port: config.port,
});
};
+8
View File
@@ -0,0 +1,8 @@
import { initTRPC } from '@trpc/server';
import type { GameApiContext } from './context.js';
const t = initTRPC.context<GameApiContext>().create();
export const router = t.router;
export const procedure = t.procedure;
+75
View File
@@ -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);
});
});
+11
View File
@@ -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');
});
});
+17
View File
@@ -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'],
},
});
+396
View File
@@ -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