diff --git a/app/game-engine/package.json b/app/game-engine/package.json index 5e04628..44acc14 100644 --- a/app/game-engine/package.json +++ b/app/game-engine/package.json @@ -12,7 +12,9 @@ }, "dependencies": { "@sammo-ts/common": "workspace:*", - "@sammo-ts/logic": "workspace:*" + "@sammo-ts/infra": "workspace:*", + "@sammo-ts/logic": "workspace:*", + "@prisma/client": "^7.2.0" }, "devDependencies": { "tsdown": "^0.18.3", diff --git a/app/game-engine/src/index.ts b/app/game-engine/src/index.ts index 59316fa..cebf96f 100644 --- a/app/game-engine/src/index.ts +++ b/app/game-engine/src/index.ts @@ -4,3 +4,6 @@ export * from './lifecycle/inMemoryControlQueue.js'; export * from './lifecycle/turnDaemonLifecycle.js'; export * from './lifecycle/getNextTickTime.js'; export * from './scenario/scenarioLoader.js'; +export * from './scenario/databaseUrl.js'; +export * from './scenario/legacyMapLoader.js'; +export * from './scenario/scenarioSeeder.js'; diff --git a/app/game-engine/src/scenario/databaseUrl.ts b/app/game-engine/src/scenario/databaseUrl.ts new file mode 100644 index 0000000..2a7351b --- /dev/null +++ b/app/game-engine/src/scenario/databaseUrl.ts @@ -0,0 +1,70 @@ +import fs from 'node:fs/promises'; +import path from 'node:path'; +import { fileURLToPath } from 'node:url'; + +const __filename = fileURLToPath(import.meta.url); +const __dirname = path.dirname(__filename); +const DEFAULT_ENV_FILE = path.resolve(__dirname, '..', '..', '..', '..', '.env.ci'); + +type EnvMap = Record; + +export interface DatabaseUrlOptions { + envFile?: string; + env?: NodeJS.ProcessEnv; +} + +const parseEnvFile = (rawText: string): EnvMap => { + const env: EnvMap = {}; + const lines = rawText.split(/\r?\n/); + for (const line of lines) { + const trimmed = line.trim(); + if (!trimmed || trimmed.startsWith('#')) { + continue; + } + const index = trimmed.indexOf('='); + if (index < 0) { + continue; + } + const key = trimmed.slice(0, index).trim(); + let value = trimmed.slice(index + 1).trim(); + if ( + (value.startsWith('"') && value.endsWith('"')) || + (value.startsWith("'") && value.endsWith("'")) + ) { + value = value.slice(1, -1); + } + env[key] = value; + } + return env; +}; + +const loadEnvFile = async (envFile: string): Promise => { + try { + const text = await fs.readFile(envFile, 'utf8'); + return parseEnvFile(text); + } catch { + return {}; + } +}; + +export const resolveDatabaseUrl = async ( + options?: DatabaseUrlOptions +): Promise => { + const env = options?.env ?? process.env; + if (env.DATABASE_URL) { + return env.DATABASE_URL; + } + + const envFile = options?.envFile ?? DEFAULT_ENV_FILE; + const fileEnv = await loadEnvFile(envFile); + if (fileEnv.DATABASE_URL) { + return fileEnv.DATABASE_URL; + } + + const host = env.POSTGRES_HOST ?? fileEnv.POSTGRES_HOST ?? '127.0.0.1'; + const port = env.POSTGRES_PORT ?? fileEnv.POSTGRES_PORT ?? '15432'; + const user = env.POSTGRES_USER ?? fileEnv.POSTGRES_USER ?? 'sammo'; + const password = env.POSTGRES_PASSWORD ?? fileEnv.POSTGRES_PASSWORD ?? ''; + const dbName = env.POSTGRES_DB ?? fileEnv.POSTGRES_DB ?? 'sammo'; + return `postgresql://${user}:${password}@${host}:${port}/${dbName}?schema=public`; +}; diff --git a/app/game-engine/src/scenario/legacyMapLoader.ts b/app/game-engine/src/scenario/legacyMapLoader.ts new file mode 100644 index 0000000..c70a3f4 --- /dev/null +++ b/app/game-engine/src/scenario/legacyMapLoader.ts @@ -0,0 +1,427 @@ +import fs from 'node:fs/promises'; +import path from 'node:path'; +import { fileURLToPath } from 'node:url'; + +import type { + MapCityDefinition, + MapCityStats, + MapDefinition, +} from '@sammo-ts/logic'; + +const __filename = fileURLToPath(import.meta.url); +const __dirname = path.dirname(__filename); +const DEFAULT_LEGACY_MAP_ROOT = path.resolve( + __dirname, + '..', + '..', + '..', + '..', + 'legacy', + 'hwe', + 'scenario', + 'map' +); +const DEFAULT_LEGACY_BASE_FILE = path.resolve( + __dirname, + '..', + '..', + '..', + '..', + 'legacy', + 'hwe', + 'sammo', + 'CityConstBase.php' +); + +const LEVEL_MAP: Record = { + '수': 1, + '진': 2, + '관': 3, + '이': 4, + '소': 5, + '중': 6, + '대': 7, + '특': 8, +}; + +const LEVEL_LABELS: Record = Object.entries(LEVEL_MAP) + .reduce>((acc, [label, value]) => { + acc[value] = label; + return acc; + }, {}); + +const REGION_MAP: Record = { + '하북': 1, + '중원': 2, + '서북': 3, + '서촉': 4, + '남중': 5, + '초': 6, + '오월': 7, + '동이': 8, +}; + +const BUILD_INIT_COMMON = { + trust: 50, + trade: 100, +}; + +const BUILD_INIT: Record = { + '수': { + population: 5000, + agriculture: 100, + commerce: 100, + security: 100, + defence: 500, + wall: 500, + }, + '진': { + population: 5000, + agriculture: 100, + commerce: 100, + security: 100, + defence: 500, + wall: 500, + }, + '관': { + population: 10000, + agriculture: 100, + commerce: 100, + security: 100, + defence: 1000, + wall: 1000, + }, + '이': { + population: 50000, + agriculture: 1000, + commerce: 1000, + security: 1000, + defence: 1000, + wall: 1000, + }, + '소': { + population: 100000, + agriculture: 1000, + commerce: 1000, + security: 1000, + defence: 2000, + wall: 2000, + }, + '중': { + population: 100000, + agriculture: 1000, + commerce: 1000, + security: 1000, + defence: 3000, + wall: 3000, + }, + '대': { + population: 150000, + agriculture: 1000, + commerce: 1000, + security: 1000, + defence: 4000, + wall: 4000, + }, + '특': { + population: 150000, + agriculture: 1000, + commerce: 1000, + security: 1000, + defence: 5000, + wall: 5000, + }, +}; + +const DEFAULT_SUPPLY_STATE = 1; +const DEFAULT_FRONT_STATE = 0; + +interface LegacyCityRow { + id: number; + name: string; + level: string | number; + population: number; + agriculture: number; + commerce: number; + security: number; + defence: number; + wall: number; + region: string | number; + positionX: number; + positionY: number; + connectionNames: string[]; +} + +export interface LegacyMapLoaderOptions { + mapRoot?: string; + baseFilePath?: string; +} + +const readFileOrNull = async (filePath: string): Promise => { + try { + return await fs.readFile(filePath, 'utf8'); + } catch { + return null; + } +}; + +const extractPhpArray = (source: string, marker: string): string | null => { + const markerIndex = source.indexOf(marker); + if (markerIndex < 0) { + return null; + } + const start = source.indexOf('[', markerIndex); + if (start < 0) { + return null; + } + + let depth = 0; + let inString: '"' | "'" | null = null; + + for (let i = start; i < source.length; i += 1) { + const char = source[i]; + if (inString) { + if (char === '\\') { + i += 1; + continue; + } + if (char === inString) { + inString = null; + } + continue; + } + + if (char === '"' || char === "'") { + inString = char; + continue; + } + + if (char === '[') { + depth += 1; + continue; + } + if (char === ']') { + depth -= 1; + if (depth === 0) { + return source.slice(start, i + 1); + } + } + } + + return null; +}; + +const stripPhpComments = (source: string): string => + source + .replace(/\/\*[\s\S]*?\*\//g, '') + .replace(/\/\/.*$/gm, '') + .replace(/#.*$/gm, ''); + +const normalizePhpArray = (source: string): string => + stripPhpComments(source) + .replace(/\bNULL\b/gi, 'null') + .replace(/'/g, '"') + .replace(/,(\s*[\]\}])/g, '$1'); + +const parseLegacyCityRows = (value: unknown): LegacyCityRow[] => { + if (!Array.isArray(value)) { + throw new Error('Legacy map data is not an array.'); + } + + return value.map((row, index) => { + if (!Array.isArray(row)) { + throw new Error(`Legacy map row ${index} is not an array.`); + } + const [ + id, + name, + level, + population, + agriculture, + commerce, + security, + defence, + wall, + region, + positionX, + positionY, + connections, + ] = row; + + if (typeof id !== 'number' || typeof name !== 'string') { + throw new Error(`Legacy map row ${index} has invalid id/name.`); + } + if ( + typeof level !== 'string' && + typeof level !== 'number' + ) { + throw new Error(`Legacy map row ${index} has invalid level.`); + } + const stats = [ + population, + agriculture, + commerce, + security, + defence, + wall, + ]; + if (stats.some((value) => typeof value !== 'number')) { + throw new Error(`Legacy map row ${index} has invalid stats.`); + } + if ( + typeof region !== 'string' && + typeof region !== 'number' + ) { + throw new Error(`Legacy map row ${index} has invalid region.`); + } + if ( + typeof positionX !== 'number' || + typeof positionY !== 'number' + ) { + throw new Error(`Legacy map row ${index} has invalid position.`); + } + + const connectionNames = Array.isArray(connections) + ? connections.filter( + (value): value is string => typeof value === 'string' + ) + : []; + + return { + id, + name, + level, + population, + agriculture, + commerce, + security, + defence, + wall, + region, + positionX, + positionY, + connectionNames, + }; + }); +}; + +const resolveLevelLabel = (level: string | number): string => { + if (typeof level === 'string') { + return level; + } + const label = LEVEL_LABELS[level]; + if (!label) { + throw new Error(`Unknown level value: ${level}`); + } + return label; +}; + +const resolveLevelValue = (level: string | number): number => { + if (typeof level === 'number') { + return level; + } + const value = LEVEL_MAP[level]; + if (!value) { + throw new Error(`Unknown level label: ${level}`); + } + return value; +}; + +const resolveRegionValue = (region: string | number): number => { + if (typeof region === 'number') { + return region; + } + const value = REGION_MAP[region]; + if (!value) { + throw new Error(`Unknown region label: ${region}`); + } + return value; +}; + +const buildCityDefinition = ( + row: LegacyCityRow, + nameToId: Map +): MapCityDefinition => { + const levelLabel = resolveLevelLabel(row.level); + const initial = BUILD_INIT[levelLabel]; + if (!initial) { + throw new Error(`Missing build init for level ${levelLabel}.`); + } + + const connections = row.connectionNames + .map((name) => nameToId.get(name)) + .filter((value): value is number => typeof value === 'number'); + + return { + id: row.id, + name: row.name, + level: resolveLevelValue(row.level), + region: resolveRegionValue(row.region), + position: { + x: row.positionX, + y: row.positionY, + }, + connections, + max: { + population: row.population * 100, + agriculture: row.agriculture * 100, + commerce: row.commerce * 100, + security: row.security * 100, + defence: row.defence * 100, + wall: row.wall * 100, + }, + initial, + meta: { + source: 'legacy', + connectionNames: row.connectionNames, + }, + }; +}; + +export const loadLegacyMapDefinition = async ( + mapName: string, + options?: LegacyMapLoaderOptions +): Promise => { + const mapRoot = options?.mapRoot ?? DEFAULT_LEGACY_MAP_ROOT; + const baseFilePath = options?.baseFilePath ?? DEFAULT_LEGACY_BASE_FILE; + const mapFilePath = path.resolve(mapRoot, `${mapName}.php`); + + const [mapSource, baseSource] = await Promise.all([ + readFileOrNull(mapFilePath), + readFileOrNull(baseFilePath), + ]); + + if (!baseSource) { + throw new Error(`Legacy base map file is missing: ${baseFilePath}`); + } + + const mapInitCity = + (mapSource + ? extractPhpArray(mapSource, 'protected static $initCity') + : null) ?? + extractPhpArray(baseSource, 'protected static $initCity'); + + if (!mapInitCity) { + throw new Error(`Legacy map data not found for ${mapName}.`); + } + + const parsed = JSON.parse(normalizePhpArray(mapInitCity)) as unknown; + const rows = parseLegacyCityRows(parsed); + const nameToId = new Map(rows.map((row) => [row.name, row.id])); + + return { + id: mapName, + name: mapName, + cities: rows.map((row) => buildCityDefinition(row, nameToId)), + defaults: { + trust: BUILD_INIT_COMMON.trust, + trade: BUILD_INIT_COMMON.trade, + supplyState: DEFAULT_SUPPLY_STATE, + frontState: DEFAULT_FRONT_STATE, + }, + meta: { + source: 'legacy', + mapName, + }, + }; +}; diff --git a/app/game-engine/src/scenario/scenarioSeeder.ts b/app/game-engine/src/scenario/scenarioSeeder.ts new file mode 100644 index 0000000..a3fe112 --- /dev/null +++ b/app/game-engine/src/scenario/scenarioSeeder.ts @@ -0,0 +1,268 @@ +import type { Prisma } from '@prisma/client'; + +import { createPostgresConnector } from '@sammo-ts/infra'; +import { + buildScenarioBootstrap, + type ScenarioBootstrapWarning, + type WorldSeedPayload, +} from '@sammo-ts/logic'; + +import type { LegacyMapLoaderOptions } from './legacyMapLoader.js'; +import { loadLegacyMapDefinition } from './legacyMapLoader.js'; +import type { ScenarioLoaderOptions } from './scenarioLoader.js'; +import { loadScenarioDefinitionById } from './scenarioLoader.js'; + +const DEFAULT_TICK_SECONDS = 120 * 60; +const DEFAULT_GENERAL_GOLD = 1000; +const DEFAULT_GENERAL_RICE = 1000; + +export interface ScenarioSeedOptions { + scenarioId: number; + databaseUrl: string; + scenarioOptions?: ScenarioLoaderOptions; + mapOptions?: LegacyMapLoaderOptions; + resetTables?: boolean; + now?: Date; + tickSeconds?: number; + includeNeutralNationInSeed?: boolean; + defaultGeneralGold?: number; + defaultGeneralRice?: number; +} + +export interface ScenarioSeedResult { + seed: WorldSeedPayload; + warnings: ScenarioBootstrapWarning[]; +} + +const asJson = (value: unknown): Prisma.InputJsonValue => + value as Prisma.InputJsonValue; + +const resolveGeneralAge = ( + startYear: number | null, + birthYear: number +): number => { + if (startYear === null || birthYear <= 0) { + return 20; + } + return Math.max(startYear - birthYear, 0); +}; + +const buildEventRows = ( + rows: unknown[], + targetOverride?: string +): Prisma.EventCreateManyInput[] => { + const result: Prisma.EventCreateManyInput[] = []; + + for (const row of rows) { + if (!Array.isArray(row)) { + continue; + } + if (targetOverride) { + const [condition, ...actions] = row; + result.push({ + targetCode: targetOverride, + priority: 0, + condition: asJson(condition ?? null), + action: asJson(actions), + meta: asJson({ source: targetOverride }), + }); + continue; + } + + const [target, priority, condition, ...actions] = row; + if (typeof target !== 'string' || typeof priority !== 'number') { + continue; + } + result.push({ + targetCode: target, + priority, + condition: asJson(condition ?? null), + action: asJson(actions), + meta: asJson({ source: 'scenario' }), + }); + } + + return result; +}; + +// 시나리오 초기 데이터를 로드해 DB에 저장한다. +export const seedScenarioToDatabase = async ( + options: ScenarioSeedOptions +): Promise => { + const scenario = await loadScenarioDefinitionById( + options.scenarioId, + options.scenarioOptions + ); + const map = await loadLegacyMapDefinition( + scenario.config.environment.mapName, + options.mapOptions + ); + + const { seed, warnings } = buildScenarioBootstrap({ + scenario, + map, + options: { + includeNeutralNationInSeed: + options.includeNeutralNationInSeed ?? true, + }, + }); + + const connector = createPostgresConnector({ url: options.databaseUrl }); + const now = options.now ?? new Date(); + const tickSeconds = options.tickSeconds ?? DEFAULT_TICK_SECONDS; + const generalGold = options.defaultGeneralGold ?? DEFAULT_GENERAL_GOLD; + const generalRice = options.defaultGeneralRice ?? DEFAULT_GENERAL_RICE; + + await connector.connect(); + try { + const prisma = connector.prisma; + + if (options.resetTables ?? true) { + await prisma.event.deleteMany(); + await prisma.diplomacy.deleteMany(); + await prisma.general.deleteMany(); + await prisma.city.deleteMany(); + await prisma.nation.deleteMany(); + await prisma.worldState.deleteMany(); + } + + await prisma.worldState.create({ + data: { + scenarioCode: String(options.scenarioId), + currentYear: scenario.startYear ?? 0, + currentMonth: 1, + tickSeconds, + config: asJson(seed.scenarioConfig), + meta: asJson({ + scenarioId: options.scenarioId, + scenarioMeta: seed.scenarioMeta, + }), + }, + }); + + if (seed.nations.length > 0) { + await prisma.nation.createMany({ + data: seed.nations.map((nation) => ({ + id: nation.id, + name: nation.name, + color: nation.color, + capitalCityId: nation.capitalCityId ?? null, + gold: nation.gold, + rice: nation.rice, + tech: nation.tech, + level: nation.level, + typeCode: nation.typeCode, + meta: asJson({ + infoText: nation.infoText, + cityIds: nation.cityIds, + }), + })), + }); + } + + if (seed.cities.length > 0) { + await prisma.city.createMany({ + data: seed.cities.map((city) => ({ + id: city.id, + name: city.name, + level: city.level, + nationId: city.nationId, + supplyState: city.supplyState, + frontState: city.frontState, + population: city.population, + populationMax: city.populationMax, + agriculture: city.agriculture, + agricultureMax: city.agricultureMax, + commerce: city.commerce, + commerceMax: city.commerceMax, + security: city.security, + securityMax: city.securityMax, + trust: city.trust, + trade: city.trade, + defence: city.defence, + defenceMax: city.defenceMax, + wall: city.wall, + wallMax: city.wallMax, + region: city.region, + conflict: asJson({}), + meta: asJson({ + position: city.position, + connections: city.connections, + ...city.meta, + }), + })), + }); + } + + if (seed.generals.length > 0) { + await prisma.general.createMany({ + data: seed.generals.map((general) => ({ + id: general.id, + name: general.name, + nationId: general.nationId, + cityId: general.cityId, + npcState: general.npcType, + affinity: general.affinity, + bornYear: general.birthYear, + deadYear: general.deathYear, + picture: + general.picture === null + ? null + : String(general.picture), + leadership: general.stats.leadership, + strength: general.stats.strength, + intel: general.stats.intelligence, + officerLevel: general.officerLevel, + gold: generalGold, + rice: generalRice, + crewTypeId: general.crewTypeId, + horseCode: general.horse ?? 'None', + weaponCode: general.weapon ?? 'None', + bookCode: general.book ?? 'None', + itemCode: general.item ?? 'None', + turnTime: now, + age: resolveGeneralAge( + scenario.startYear ?? null, + general.birthYear + ), + personalCode: general.personality ?? 'None', + specialCode: general.special ?? 'None', + special2Code: general.specialWar ?? 'None', + lastTurn: asJson({}), + meta: asJson({ + npcType: general.npcType, + crewTypeId: general.crewTypeId, + ...general.meta, + }), + penalty: asJson({}), + })), + }); + } + + if (seed.diplomacy.length > 0) { + await prisma.diplomacy.createMany({ + data: seed.diplomacy.map((row) => ({ + srcNationId: row.fromNationId, + destNationId: row.toNationId, + stateCode: row.state, + term: row.durationMonths, + meta: asJson({}), + })), + }); + } + + const eventRows = [ + ...buildEventRows(seed.events), + ...buildEventRows(seed.initialEvents, 'initial'), + ]; + if (eventRows.length > 0) { + await prisma.event.createMany({ + data: eventRows, + }); + } + } finally { + await connector.disconnect(); + } + + return { seed, warnings }; +}; diff --git a/app/game-engine/test/scenarioSeeder.test.ts b/app/game-engine/test/scenarioSeeder.test.ts new file mode 100644 index 0000000..46535cd --- /dev/null +++ b/app/game-engine/test/scenarioSeeder.test.ts @@ -0,0 +1,71 @@ +import { createPostgresConnector } from '@sammo-ts/infra'; + +import { resolveDatabaseUrl } from '../src/scenario/databaseUrl.js'; +import { seedScenarioToDatabase } from '../src/scenario/scenarioSeeder.js'; + +const scenarioId = 1010; +const databaseUrl = await resolveDatabaseUrl(); + +const canConnectToDatabase = async (url: string): Promise => { + const connector = createPostgresConnector({ url }); + try { + await connector.connect(); + await connector.prisma.$queryRawUnsafe('SELECT 1'); + return true; + } catch { + return false; + } finally { + await connector.disconnect(); + } +}; + +const canRun = await canConnectToDatabase(databaseUrl); +const describeDb = describe.runIf(canRun); + +describeDb('scenario database seed', () => { + test('writes scenario data into tables', async () => { + const { seed } = await seedScenarioToDatabase({ + scenarioId, + databaseUrl, + }); + + const connector = createPostgresConnector({ url: databaseUrl }); + await connector.connect(); + try { + const prisma = connector.prisma; + const [ + nationCount, + cityCount, + generalCount, + diplomacyCount, + ] = await Promise.all([ + prisma.nation.count(), + prisma.city.count(), + prisma.general.count(), + prisma.diplomacy.count(), + ]); + + expect(nationCount).toBe(seed.nations.length); + expect(cityCount).toBe(seed.cities.length); + expect(generalCount).toBe(seed.generals.length); + expect(diplomacyCount).toBe(seed.diplomacy.length); + expect(generalCount).toBeGreaterThan(0); + + if (seed.diplomacy.length > 0) { + const sample = seed.diplomacy[0]; + const row = await prisma.diplomacy.findFirst({ + where: { + srcNationId: sample.fromNationId, + destNationId: sample.toNationId, + }, + }); + expect(row).not.toBeNull(); + if (row) { + expect(row.stateCode).toBe(sample.state); + } + } + } finally { + await connector.disconnect(); + } + }); +}); diff --git a/packages/infra/package.json b/packages/infra/package.json index 6f95dae..97047e7 100644 --- a/packages/infra/package.json +++ b/packages/infra/package.json @@ -13,11 +13,14 @@ "typecheck": "tsc --noEmit" }, "dependencies": { - "@prisma/client": "^5.18.0", + "@prisma/adapter-pg": "^7.2.0", + "@prisma/client": "^7.2.0", + "pg": "^8.16.3", "redis": "^4.7.0" }, "devDependencies": { - "prisma": "^5.18.0", + "dotenv": "^17.2.3", + "prisma": "^7.2.0", "tsdown": "^0.18.3" } } diff --git a/packages/infra/prisma.config.ts b/packages/infra/prisma.config.ts new file mode 100644 index 0000000..bf68499 --- /dev/null +++ b/packages/infra/prisma.config.ts @@ -0,0 +1,22 @@ +import 'dotenv/config'; + +import { defineConfig } from 'prisma/config'; + +const buildDatabaseUrlFromEnv = (): string => { + const host = process.env.POSTGRES_HOST ?? '127.0.0.1'; + const port = process.env.POSTGRES_PORT ?? '15432'; + const user = process.env.POSTGRES_USER ?? 'sammo'; + const password = process.env.POSTGRES_PASSWORD ?? ''; + const dbName = process.env.POSTGRES_DB ?? 'sammo'; + return `postgresql://${user}:${password}@${host}:${port}/${dbName}?schema=public`; +}; + +const databaseUrl = + process.env.DATABASE_URL ?? buildDatabaseUrlFromEnv(); + +export default defineConfig({ + schema: 'prisma/schema.prisma', + datasource: { + url: databaseUrl, + }, +}); diff --git a/packages/infra/prisma/schema.prisma b/packages/infra/prisma/schema.prisma index c3975a1..3df6bdd 100644 --- a/packages/infra/prisma/schema.prisma +++ b/packages/infra/prisma/schema.prisma @@ -4,7 +4,132 @@ generator client { datasource db { provider = "postgresql" - url = env("DATABASE_URL") } -// TODO: Translate docs/architecture/postgres-schema.md into Prisma models. +model WorldState { + id Int @id @default(autoincrement()) + scenarioCode String @map("scenario_code") + currentYear Int @map("current_year") + currentMonth Int @map("current_month") + tickSeconds Int @map("tick_seconds") + config Json @default(dbgenerated("'{}'::jsonb")) + meta Json @default(dbgenerated("'{}'::jsonb")) + updatedAt DateTime @updatedAt @map("updated_at") + + @@map("world_state") +} + +model Nation { + id Int @id + name String + color String + capitalCityId Int? @map("capital_city_id") + gold Int @default(0) + rice Int @default(0) + tech Float @default(0) + level Int @default(0) + typeCode String @default("che_중립") @map("type_code") + meta Json @default(dbgenerated("'{}'::jsonb")) + + @@map("nation") +} + +model City { + id Int @id + name String + level Int + nationId Int @default(0) @map("nation_id") + supplyState Int @default(1) @map("supply_state") + frontState Int @default(0) @map("front_state") + population Int @map("pop") + populationMax Int @map("pop_max") + agriculture Int @map("agri") + agricultureMax Int @map("agri_max") + commerce Int @map("comm") + commerceMax Int @map("comm_max") + security Int @map("secu") + securityMax Int @map("secu_max") + trust Int @default(0) + trade Int @default(100) + defence Int @map("def") + defenceMax Int @map("def_max") + wall Int @map("wall") + wallMax Int @map("wall_max") + region Int + conflict Json @default(dbgenerated("'{}'::jsonb")) + meta Json @default(dbgenerated("'{}'::jsonb")) + + @@map("city") +} + +model General { + id Int @id + name String + nationId Int @default(0) @map("nation_id") + cityId Int @default(0) @map("city_id") + troopId Int @default(0) @map("troop_id") + npcState Int @default(0) @map("npc_state") + affinity Int? + bornYear Int @default(180) @map("born_year") + deadYear Int @default(300) @map("dead_year") + picture String? + imageServer Int @default(0) @map("image_server") + leadership Int @default(50) + strength Int @default(50) + intel Int @default(50) + injury Int @default(0) + experience Int @default(0) + dedication Int @default(0) + officerLevel Int @default(0) @map("officer_level") + gold Int @default(1000) + rice Int @default(1000) + crew Int @default(0) + crewTypeId Int @default(0) @map("crew_type_id") + train Int @default(0) + atmos Int @default(0) + weaponCode String @default("None") @map("weapon_code") + bookCode String @default("None") @map("book_code") + horseCode String @default("None") @map("horse_code") + itemCode String @default("None") @map("item_code") + turnTime DateTime @map("turn_time") + recentWarTime DateTime? @map("recent_war_time") + age Int @default(20) + startAge Int @default(20) @map("start_age") + personalCode String @default("None") @map("personal_code") + specialCode String @default("None") @map("special_code") + special2Code String @default("None") @map("special2_code") + lastTurn Json @default(dbgenerated("'{}'::jsonb")) @map("last_turn") + meta Json @default(dbgenerated("'{}'::jsonb")) + penalty Json @default(dbgenerated("'{}'::jsonb")) + createdAt DateTime @default(now()) @map("created_at") + updatedAt DateTime @default(now()) @map("updated_at") + + @@map("general") +} + +model Diplomacy { + id Int @id @default(autoincrement()) + srcNationId Int @map("src_nation_id") + destNationId Int @map("dest_nation_id") + stateCode Int @map("state_code") + term Int @default(0) + isDead Boolean @default(false) @map("is_dead") + isShowing Boolean @default(true) @map("is_showing") + meta Json @default(dbgenerated("'{}'::jsonb")) + createdAt DateTime @default(now()) @map("created_at") + + @@unique([srcNationId, destNationId]) + @@map("diplomacy") +} + +model Event { + id Int @id @default(autoincrement()) + targetCode String @map("target_code") + priority Int @default(0) + condition Json @default(dbgenerated("'{}'::jsonb")) + action Json @default(dbgenerated("'{}'::jsonb")) + meta Json @default(dbgenerated("'{}'::jsonb")) + createdAt DateTime @default(now()) @map("created_at") + + @@map("event") +} diff --git a/packages/infra/src/postgres.ts b/packages/infra/src/postgres.ts index 8eee43a..d7e8c4e 100644 --- a/packages/infra/src/postgres.ts +++ b/packages/infra/src/postgres.ts @@ -1,4 +1,6 @@ import { Prisma, PrismaClient } from '@prisma/client'; +import { PrismaPg } from '@prisma/adapter-pg'; +import { Pool } from 'pg'; export interface PostgresConfig { url: string; @@ -11,10 +13,21 @@ export interface PostgresConnector { disconnect(): Promise; } +const buildDatabaseUrlFromEnv = ( + env: NodeJS.ProcessEnv +): string => { + const host = env.POSTGRES_HOST ?? '127.0.0.1'; + const port = env.POSTGRES_PORT ?? '15432'; + const user = env.POSTGRES_USER ?? 'sammo'; + const password = env.POSTGRES_PASSWORD ?? ''; + const dbName = env.POSTGRES_DB ?? 'sammo'; + return `postgresql://${user}:${password}@${host}:${port}/${dbName}?schema=public`; +}; + export const resolvePostgresConfigFromEnv = ( env: NodeJS.ProcessEnv = process.env ): PostgresConfig => { - const url = env.DATABASE_URL ?? ''; + const url = env.DATABASE_URL ?? buildDatabaseUrlFromEnv(env); if (!url) { throw new Error('DATABASE_URL is required to create a Postgres client.'); } @@ -25,18 +38,21 @@ export const resolvePostgresConfigFromEnv = ( export const createPostgresConnector = ( config: PostgresConfig ): PostgresConnector => { + const pool = new Pool({ + connectionString: config.url, + }); + const adapter = new PrismaPg(pool); const prisma = new PrismaClient({ - datasources: { - db: { - url: config.url, - }, - }, + adapter, log: config.log, }); return { prisma, connect: () => prisma.$connect(), - disconnect: () => prisma.$disconnect(), + disconnect: async () => { + await prisma.$disconnect(); + await pool.end(); + }, }; }; diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 8d88dc5..51ff699 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -26,9 +26,15 @@ importers: app/game-engine: dependencies: + '@prisma/client': + specifier: ^7.2.0 + version: 7.2.0(prisma@7.2.0(@types/react@19.2.7)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(typescript@5.9.3))(typescript@5.9.3) '@sammo-ts/common': specifier: workspace:* version: link:../../packages/common + '@sammo-ts/infra': + specifier: workspace:* + version: link:../../packages/infra '@sammo-ts/logic': specifier: workspace:* version: link:../../packages/logic @@ -38,10 +44,10 @@ importers: version: 0.18.3(typescript@5.9.3) vite-tsconfig-paths: specifier: ^4.3.2 - version: 4.3.2(typescript@5.9.3)(vite@7.3.0(@types/node@20.19.27)) + version: 4.3.2(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) + version: 4.0.16(@types/node@20.19.27)(jiti@2.6.1) app/game-frontend: {} @@ -64,20 +70,29 @@ importers: version: 0.18.3(typescript@5.9.3) vitest: specifier: ^4.0.16 - version: 4.0.16(@types/node@20.19.27) + version: 4.0.16(@types/node@20.19.27)(jiti@2.6.1) packages/infra: dependencies: + '@prisma/adapter-pg': + specifier: ^7.2.0 + version: 7.2.0 '@prisma/client': - specifier: ^5.18.0 - version: 5.22.0(prisma@5.22.0) + specifier: ^7.2.0 + version: 7.2.0(prisma@7.2.0(@types/react@19.2.7)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(typescript@5.9.3))(typescript@5.9.3) + pg: + specifier: ^8.16.3 + version: 8.16.3 redis: specifier: ^4.7.0 version: 4.7.1 devDependencies: + dotenv: + specifier: ^17.2.3 + version: 17.2.3 prisma: - specifier: ^5.18.0 - version: 5.22.0 + specifier: ^7.2.0 + version: 7.2.0(@types/react@19.2.7)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(typescript@5.9.3) tsdown: specifier: ^0.18.3 version: 0.18.3(typescript@5.9.3) @@ -96,10 +111,10 @@ importers: version: 0.18.3(typescript@5.9.3) vite-tsconfig-paths: specifier: ^4.3.2 - version: 4.3.2(typescript@5.9.3)(vite@7.3.0(@types/node@20.19.27)) + version: 4.3.2(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) + version: 4.0.16(@types/node@20.19.27)(jiti@2.6.1) tools/build-scripts: {} @@ -126,6 +141,32 @@ packages: resolution: {integrity: sha512-qQ5m48eI/MFLQ5PxQj4PFaprjyCTLI37ElWMmNs0K8Lk3dVeOdNpB3ks8jc7yM5CDmVC73eMVk/trk3fgmrUpA==} engines: {node: '>=6.9.0'} + '@chevrotain/cst-dts-gen@10.5.0': + resolution: {integrity: sha512-lhmC/FyqQ2o7pGK4Om+hzuDrm9rhFYIJ/AXoQBeongmn870Xeb0L6oGEiuR8nohFNL5sMaQEJWCxr1oIVIVXrw==} + + '@chevrotain/gast@10.5.0': + resolution: {integrity: sha512-pXdMJ9XeDAbgOWKuD1Fldz4ieCs6+nLNmyVhe2gZVqoO7v8HXuHYs5OV2EzUtbuai37TlOAQHrTDvxMnvMJz3A==} + + '@chevrotain/types@10.5.0': + resolution: {integrity: sha512-f1MAia0x/pAVPWH/T73BJVyO2XU5tI4/iE7cnxb7tqdNTNhQI3Uq3XkqcoteTmD4t1aM0LbHCJOhgIDn07kl2A==} + + '@chevrotain/utils@10.5.0': + resolution: {integrity: sha512-hBzuU5+JjB2cqNZyszkDHZgOSrUUT8V3dhgRl8Q9Gp6dAj/H5+KILGjbhDpc3Iy9qmqlm/akuOI2ut9VUtzJxQ==} + + '@electric-sql/pglite-socket@0.0.6': + resolution: {integrity: sha512-6RjmgzphIHIBA4NrMGJsjNWK4pu+bCWJlEWlwcxFTVY3WT86dFpKwbZaGWZV6C5Rd7sCk1Z0CI76QEfukLAUXw==} + hasBin: true + peerDependencies: + '@electric-sql/pglite': 0.3.2 + + '@electric-sql/pglite-tools@0.2.7': + resolution: {integrity: sha512-9dAccClqxx4cZB+Ar9B+FZ5WgxDc/Xvl9DPrTWv+dYTf0YNubLzi4wHHRGRGhrJv15XwnyKcGOZAP1VXSneSUg==} + peerDependencies: + '@electric-sql/pglite': 0.3.2 + + '@electric-sql/pglite@0.3.2': + resolution: {integrity: sha512-zfWWa+V2ViDCY/cmUfRqeWY1yLto+EpxjXnZzenB1TyxsTiXaTWeZFIZw6mac52BsuQm0RjCnisjBtdBaXOI6w==} + '@emnapi/core@1.7.1': resolution: {integrity: sha512-o1uhUASyo921r2XtHYOHy7gdkGLge8ghBEQHMWmyJFoXlpU58kIrhhN3w26lpQb6dspetweapMn2CSNwQ8I4wg==} @@ -291,6 +332,12 @@ packages: cpu: [x64] os: [win32] + '@hono/node-server@1.19.6': + resolution: {integrity: sha512-Shz/KjlIeAhfiuE93NDKVdZ7HdBVLQAfdbaXEaoAVO3ic9ibRSLGIQGkcBbFyuLr+7/1D5ZCINM8B+6IvXeMtw==} + engines: {node: '>=18.14.1'} + peerDependencies: + hono: ^4 + '@jridgewell/gen-mapping@0.3.13': resolution: {integrity: sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==} @@ -304,35 +351,73 @@ packages: '@jridgewell/trace-mapping@0.3.31': resolution: {integrity: sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==} + '@mrleebo/prisma-ast@0.12.1': + resolution: {integrity: sha512-JwqeCQ1U3fvccttHZq7Tk0m/TMC6WcFAQZdukypW3AzlJYKYTGNVd1ANU2GuhKnv4UQuOFj3oAl0LLG/gxFN1w==} + engines: {node: '>=16'} + '@napi-rs/wasm-runtime@1.1.0': resolution: {integrity: sha512-Fq6DJW+Bb5jaWE69/qOE0D1TUN9+6uWhCeZpdnSBk14pjLcCWR7Q8n49PTSPHazM37JqrsdpEthXy2xn6jWWiA==} '@oxc-project/types@0.103.0': resolution: {integrity: sha512-bkiYX5kaXWwUessFRSoXFkGIQTmc6dLGdxuRTrC+h8PSnIdZyuXHHlLAeTmOue5Br/a0/a7dHH0Gca6eXn9MKg==} - '@prisma/client@5.22.0': - resolution: {integrity: sha512-M0SVXfyHnQREBKxCgyo7sffrKttwE6R8PMq330MIUF0pTwjUhLbW84pFDlf06B27XyCR++VtjugEnIHdr07SVA==} - engines: {node: '>=16.13'} + '@prisma/adapter-pg@7.2.0': + resolution: {integrity: sha512-euIdQ13cRB2wZ3jPsnDnFhINquo1PYFPCg6yVL8b2rp3EdinQHsX9EDdCtRr489D5uhphcRk463OdQAFlsCr0w==} + + '@prisma/client-runtime-utils@7.2.0': + resolution: {integrity: sha512-dn7oB53v0tqkB0wBdMuTNFNPdEbfICEUe82Tn9FoKAhJCUkDH+fmyEp0ClciGh+9Hp2Tuu2K52kth2MTLstvmA==} + + '@prisma/client@7.2.0': + resolution: {integrity: sha512-JdLF8lWZ+LjKGKpBqyAlenxd/kXjd1Abf/xK+6vUA7R7L2Suo6AFTHFRpPSdAKCan9wzdFApsUpSa/F6+t1AtA==} + engines: {node: ^20.19 || ^22.12 || >=24.0} peerDependencies: prisma: '*' + typescript: '>=5.4.0' peerDependenciesMeta: prisma: optional: true + typescript: + optional: true - '@prisma/debug@5.22.0': - resolution: {integrity: sha512-AUt44v3YJeggO2ZU5BkXI7M4hu9BF2zzH2iF2V5pyXT/lRTyWiElZ7It+bRH1EshoMRxHgpYg4VB6rCM+mG5jQ==} + '@prisma/config@7.2.0': + resolution: {integrity: sha512-qmvSnfQ6l/srBW1S7RZGfjTQhc44Yl3ldvU6y3pgmuLM+83SBDs6UQVgMtQuMRe9J3gGqB0RF8wER6RlXEr6jQ==} - '@prisma/engines-version@5.22.0-44.605197351a3c8bdd595af2d2a9bc3025bca48ea2': - resolution: {integrity: sha512-2PTmxFR2yHW/eB3uqWtcgRcgAbG1rwG9ZriSvQw+nnb7c4uCr3RAcGMb6/zfE88SKlC1Nj2ziUvc96Z379mHgQ==} + '@prisma/debug@6.8.2': + resolution: {integrity: sha512-4muBSSUwJJ9BYth5N8tqts8JtiLT8QI/RSAzEogwEfpbYGFo9mYsInsVo8dqXdPO2+Rm5OG5q0qWDDE3nyUbVg==} - '@prisma/engines@5.22.0': - resolution: {integrity: sha512-UNjfslWhAt06kVL3CjkuYpHAWSO6L4kDCVPegV6itt7nD1kSJavd3vhgAEhjglLJJKEdJ7oIqDJ+yHk6qO8gPA==} + '@prisma/debug@7.2.0': + resolution: {integrity: sha512-YSGTiSlBAVJPzX4ONZmMotL+ozJwQjRmZweQNIq/ER0tQJKJynNkRB3kyvt37eOfsbMCXk3gnLF6J9OJ4QWftw==} - '@prisma/fetch-engine@5.22.0': - resolution: {integrity: sha512-bkrD/Mc2fSvkQBV5EpoFcZ87AvOgDxbG99488a5cexp5Ccny+UM6MAe/UFkUC0wLYD9+9befNOqGiIJhhq+HbA==} + '@prisma/dev@0.17.0': + resolution: {integrity: sha512-6sGebe5jxX+FEsQTpjHLzvOGPn6ypFQprcs3jcuIWv1Xp/5v6P/rjfdvAwTkP2iF6pDx2tCd8vGLNWcsWzImTA==} - '@prisma/get-platform@5.22.0': - resolution: {integrity: sha512-pHhpQdr1UPFpt+zFfnPazhulaZYCUqeIcPpJViYoq9R+D/yw4fjE+CtnsnKzPYm0ddUbeXUzjGVGIRVgPDCk4Q==} + '@prisma/driver-adapter-utils@7.2.0': + resolution: {integrity: sha512-gzrUcbI9VmHS24Uf+0+7DNzdIw7keglJsD5m/MHxQOU68OhGVzlphQRobLiDMn8CHNA2XN8uugwKjudVtnfMVQ==} + + '@prisma/engines-version@7.2.0-4.0c8ef2ce45c83248ab3df073180d5eda9e8be7a3': + resolution: {integrity: sha512-KezsjCZDsbjNR7SzIiVlUsn9PnLePI7r5uxABlwL+xoerurZTfgQVbIjvjF2sVr3Uc0ZcsnREw3F84HvbggGdA==} + + '@prisma/engines@7.2.0': + resolution: {integrity: sha512-HUeOI/SvCDsHrR9QZn24cxxZcujOjcS3w1oW/XVhnSATAli5SRMOfp/WkG3TtT5rCxDA4xOnlJkW7xkho4nURA==} + + '@prisma/fetch-engine@7.2.0': + resolution: {integrity: sha512-Z5XZztJ8Ap+wovpjPD2lQKnB8nWFGNouCrglaNFjxIWAGWz0oeHXwUJRiclIoSSXN/ptcs9/behptSk8d0Yy6w==} + + '@prisma/get-platform@6.8.2': + resolution: {integrity: sha512-vXSxyUgX3vm1Q70QwzwkjeYfRryIvKno1SXbIqwSptKwqKzskINnDUcx85oX+ys6ooN2ATGSD0xN2UTfg6Zcow==} + + '@prisma/get-platform@7.2.0': + resolution: {integrity: sha512-k1V0l0Td1732EHpAfi2eySTezyllok9dXb6UQanajkJQzPUGi3vO2z7jdkz67SypFTdmbnyGYxvEvYZdZsMAVA==} + + '@prisma/query-plan-executor@6.18.0': + resolution: {integrity: sha512-jZ8cfzFgL0jReE1R10gT8JLHtQxjWYLiQ//wHmVYZ2rVkFHoh0DT8IXsxcKcFlfKN7ak7k6j0XMNn2xVNyr5cA==} + + '@prisma/studio-core@0.9.0': + resolution: {integrity: sha512-xA2zoR/ADu/NCSQuriBKTh6Ps4XjU0bErkEcgMfnSGh346K1VI7iWKnoq1l2DoxUqiddPHIEWwtxJ6xCHG6W7g==} + peerDependencies: + '@types/react': ^18.0.0 || ^19.0.0 + react: ^18.0.0 || ^19.0.0 + react-dom: ^18.0.0 || ^19.0.0 '@quansync/fs@1.0.0': resolution: {integrity: sha512-4TJ3DFtlf1L5LDMaM6CanJ/0lckGNtJcMjQ1NAV6zDmA0tEHKZtxNKin8EgPaVX1YzljbxckyT2tJrpQKAtngQ==} @@ -574,6 +659,9 @@ packages: '@types/node@20.19.27': resolution: {integrity: sha512-N2clP5pJhB2YnZJ3PIHFk5RkygRX5WO/5f0WC08tp0wd+sv0rsJk3MqWn3CbNmT2J505a5336jaQj4ph1AdMug==} + '@types/react@19.2.7': + resolution: {integrity: sha512-MWtvHrGZLFttgeEj28VXHxpmwYbor/ATPYbBfSFZEIRK0ecCFLl2Qo55z52Hss+UV9CRN7trSeq1zbgx7YDWWg==} + '@vitest/expect@4.0.16': resolution: {integrity: sha512-eshqULT2It7McaJkQGLkPjPjNph+uevROGuIMJdG3V+0BSR2w9u6J9Lwu+E8cK5TETlfou8GRijhafIMhXsimA==} @@ -615,9 +703,21 @@ packages: resolution: {integrity: sha512-m1Q/RaVOnTp9JxPX+F+Zn7IcLYMzM8kZofDImfsKZd8MbR+ikdOzTeztStWqfrqIxZnYWryyI9ePm3NGjnZgGw==} engines: {node: '>=20.19.0'} + aws-ssl-profiles@1.1.2: + resolution: {integrity: sha512-NZKeq9AfyQvEeNlN0zSYAaWrmBffJh3IELMZfRpJVWgrpEbtEpnjvzqBPf+mxoI287JohRDoa+/nsfqqiZmF6g==} + engines: {node: '>= 6.0.0'} + birpc@4.0.0: resolution: {integrity: sha512-LShSxJP0KTmd101b6DRyGBj57LZxSDYWKitQNW/mi8GRMvZb078Uf9+pveax1DrVL89vm7mWe+TovdI/UDOuPw==} + c12@3.1.0: + resolution: {integrity: sha512-uWoS8OU1MEIsOv8p/5a82c3H31LsWVR5qiyXVfBNOzfffjUWtPnhAb4BYI2uG2HfGmZmFjCtui5XNWaps+iFuw==} + peerDependencies: + magicast: ^0.3.5 + peerDependenciesMeta: + magicast: + optional: true + cac@6.7.14: resolution: {integrity: sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==} engines: {node: '>=8'} @@ -626,10 +726,34 @@ packages: resolution: {integrity: sha512-NUPRluOfOiTKBKvWPtSD4PhFvWCqOi0BGStNWs57X9js7XGTprSmFoz5F0tWhR4WPjNeR9jXqdC7/UpSJTnlRg==} engines: {node: '>=18'} + chevrotain@10.5.0: + resolution: {integrity: sha512-Pkv5rBY3+CsHOYfV5g/Vs5JY9WTHHDEKOlohI2XeygaZhUeqhAlldZ8Hz9cRmxu709bvS08YzxHdTPHhffc13A==} + + chokidar@4.0.3: + resolution: {integrity: sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==} + engines: {node: '>= 14.16.0'} + + citty@0.1.6: + resolution: {integrity: sha512-tskPPKEs8D2KPafUypv2gxwJP8h/OaJmC82QQGGDQcHvXX43xF2VDACcJVmZ0EuSxkpO9Kc4MlrA3q0+FG58AQ==} + cluster-key-slot@1.1.2: resolution: {integrity: sha512-RMr0FhtfXemyinomL4hrWcYJxmX6deFdCxpJzhDttxgO1+bcCnkk+9drydLVDmAMG7NE6aN/fl4F7ucU/90gAA==} engines: {node: '>=0.10.0'} + confbox@0.2.2: + resolution: {integrity: sha512-1NB+BKqhtNipMsov4xI/NnhCKp9XG9NamYp5PVm9klAT0fsrNPjaFICsCFhNhwZJKNh7zB/3q8qXz0E9oaMNtQ==} + + consola@3.4.2: + resolution: {integrity: sha512-5IKcdX0nnYavi6G7TtOhwkYzyjfJlatbjMjuLSfE2kYT5pMDOilZ4OvMhi637CcDICTmz3wARPoyhqyX1Y+XvA==} + engines: {node: ^14.18.0 || >=16.10.0} + + cross-spawn@7.0.6: + resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==} + engines: {node: '>= 8'} + + csstype@3.2.3: + resolution: {integrity: sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==} + debug@4.4.3: resolution: {integrity: sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==} engines: {node: '>=6.0'} @@ -639,9 +763,28 @@ packages: supports-color: optional: true + deepmerge-ts@7.1.5: + resolution: {integrity: sha512-HOJkrhaYsweh+W+e74Yn7YStZOilkoPb6fycpwNLKzSPtruFs48nYis0zy5yJz1+ktUhHxoRDJ27RQAWLIJVJw==} + engines: {node: '>=16.0.0'} + defu@6.1.4: resolution: {integrity: sha512-mEQCMmwJu317oSz8CwdIOdwf3xMif1ttiM8LTufzc3g6kR+9Pe236twL8j3IYT1F7GfRgGcW6MWxzZjLIkuHIg==} + denque@2.1.0: + resolution: {integrity: sha512-HVQE3AAb/pxF8fQAoiqpvg9i3evqug3hoiwakOyZAwJm+6vZehbkYXZ0l4JxS+I3QxM97v5aaRNhj8v5oBhekw==} + engines: {node: '>=0.10'} + + destr@2.0.5: + resolution: {integrity: sha512-ugFTXCtDZunbzasqBxrK93Ik/DRYsO6S/fedkWEMKqt04xZ4csmnmwGDBAb07QWNaGMAmnTIemsYZCksjATwsA==} + + dotenv@16.6.1: + resolution: {integrity: sha512-uBq4egWHTcTt33a72vpSG0z3HnPuIl6NqYcTrKEg2azoEyl2hpW0zqlxysq2pK9HlDIHyHyakeYaYnSAwd8bow==} + engines: {node: '>=12'} + + dotenv@17.2.3: + resolution: {integrity: sha512-JVUnt+DUIzu87TABbhPmNfVdBDt18BLOWjMUFJMSi/Qqg7NTYtabbvSNJGOJ7afbRuv9D/lngizHtP7QyLQ+9w==} + engines: {node: '>=12'} + dts-resolver@2.1.3: resolution: {integrity: sha512-bihc7jPC90VrosXNzK0LTE2cuLP6jr0Ro8jk+kMugHReJVLIpHz/xadeq3MhuwyO4TD4OA3L1Q8pBBFRc08Tsw==} engines: {node: '>=20.19.0'} @@ -651,6 +794,9 @@ packages: oxc-resolver: optional: true + effect@3.18.4: + resolution: {integrity: sha512-b1LXQJLe9D11wfnOKAk3PKxuqYshQ0Heez+y5pnkd3jLj1yx9QhM72zZ9uUrOQyNvrs2GZZd/3maL0ZV18YuDA==} + empathic@2.0.0: resolution: {integrity: sha512-i6UzDscO/XfAcNYD75CfICkmfLedpyPDdozrLMmQc5ORaQcdMoc21OnlEylMIqI7U8eniKrPMxxtj8k0vhmJhA==} engines: {node: '>=14'} @@ -670,6 +816,13 @@ packages: resolution: {integrity: sha512-knvyeauYhqjOYvQ66MznSMs83wmHrCycNEN6Ao+2AeYEfxUIkuiVxdEa1qlGEPK+We3n0THiDciYSsCcgW/DoA==} engines: {node: '>=12.0.0'} + exsolve@1.0.8: + resolution: {integrity: sha512-LmDxfWXwcTArk8fUEnOfSZpHOJ6zOMUJKOtFLFqJLoKJetuQG874Uc7/Kki7zFLzYybmZhp1M7+98pfMqeX8yA==} + + fast-check@3.23.2: + resolution: {integrity: sha512-h5+1OzzfCC3Ef7VbtKdcv7zsstUQwUDlYpUTvjeUsJAssPgLn7QzbboPtL5ro04Mq0rPOsMzl7q5hIbRs2wD1A==} + engines: {node: '>=8.0.0'} + fdir@6.5.0: resolution: {integrity: sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==} engines: {node: '>=12.0.0'} @@ -679,28 +832,69 @@ packages: picomatch: optional: true + foreground-child@3.3.1: + resolution: {integrity: sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==} + engines: {node: '>=14'} + fsevents@2.3.3: resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} os: [darwin] + generate-function@2.3.1: + resolution: {integrity: sha512-eeB5GfMNeevm/GRYq20ShmsaGcmI81kIX2K9XQx5miC8KdHaC6Jm0qQ8ZNeGOi7wYB8OsdxKs+Y2oVuTFuVwKQ==} + generic-pool@3.9.0: resolution: {integrity: sha512-hymDOu5B53XvN4QT9dBmZxPX4CWhBPPLguTZ9MMFeFa/Kg0xWVfylOVNlJji/E7yTZWFd/q9GO5TxDLq156D7g==} engines: {node: '>= 4'} + get-port-please@3.1.2: + resolution: {integrity: sha512-Gxc29eLs1fbn6LQ4jSU4vXjlwyZhF5HsGuMAa7gqBP4Rw4yxxltyDUuF5MBclFzDTXO+ACchGQoeela4DSfzdQ==} + get-tsconfig@4.13.0: resolution: {integrity: sha512-1VKTZJCwBrvbd+Wn3AOgQP/2Av+TfTCOlE4AcRJE72W1ksZXbAx8PPBR9RzgTeSPzlPMHrbANMH3LbltH73wxQ==} + giget@2.0.0: + resolution: {integrity: sha512-L5bGsVkxJbJgdnwyuheIunkGatUF/zssUoxxjACCseZYAVbaqdh9Tsmmlkl8vYan09H7sbvKt4pS8GqKLBrEzA==} + hasBin: true + globrex@0.1.2: resolution: {integrity: sha512-uHJgbwAMwNFf5mLst7IWLNg14x1CkeqglJb/K3doi4dw6q2IvAAmM/Y81kevy83wP+Sst+nutFTYOGg3d1lsxg==} + graceful-fs@4.2.11: + resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==} + + grammex@3.1.12: + resolution: {integrity: sha512-6ufJOsSA7LcQehIJNCO7HIBykfM7DXQual0Ny780/DEcJIpBlHRvcqEBWGPYd7hrXL2GJ3oJI1MIhaXjWmLQOQ==} + + hono@4.10.6: + resolution: {integrity: sha512-BIdolzGpDO9MQ4nu3AUuDwHZZ+KViNm+EZ75Ae55eMXMqLVhDFqEMXxtUe9Qh8hjL+pIna/frs2j6Y2yD5Ua/g==} + engines: {node: '>=16.9.0'} + hookable@6.0.1: resolution: {integrity: sha512-uKGyY8BuzN/a5gvzvA+3FVWo0+wUjgtfSdnmjtrOVwQCZPHpHDH2WRO3VZSOeluYrHoDCiXFffZXs8Dj1ULWtw==} + http-status-codes@2.3.0: + resolution: {integrity: sha512-RJ8XvFvpPM/Dmc5SV+dC4y5PCeOhT3x1Hq0NU3rjGeg5a/CqlhZ7uudknPwZFz4aeAXDcbAyaeP7GAo9lvngtA==} + + iconv-lite@0.7.1: + resolution: {integrity: sha512-2Tth85cXwGFHfvRgZWszZSvdo+0Xsqmw8k8ZwxScfcBneNUraK+dxRxRm24nszx80Y0TVio8kKLt5sLE7ZCLlw==} + engines: {node: '>=0.10.0'} + import-without-cache@0.2.5: resolution: {integrity: sha512-B6Lc2s6yApwnD2/pMzFh/d5AVjdsDXjgkeJ766FmFuJELIGHNycKRj+l3A39yZPM4CchqNCB4RITEAYB1KUM6A==} engines: {node: '>=20.19.0'} + is-property@1.0.2: + resolution: {integrity: sha512-Ks/IoX00TtClbGQr4TWXemAnktAQvYB7HzcCxDGqEZU6oCmb2INHuOoKxbtR+HFkmYWBKv/dOZtGRiAjDhj92g==} + + isexe@2.0.0: + resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} + + jiti@2.6.1: + resolution: {integrity: sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ==} + hasBin: true + js-sha512@0.9.0: resolution: {integrity: sha512-mirki9WS/SUahm+1TbAPkqvbCiCfOAAsyXeHxK1UkullnJVVqoJG2pL9ObvT05CN+tM7fxhfYm0NbXn+1hWoZg==} @@ -709,23 +903,97 @@ packages: engines: {node: '>=6'} hasBin: true + lilconfig@2.1.0: + resolution: {integrity: sha512-utWOt/GHzuUxnLKxB6dk81RoOeoNeHgbrXiuGk4yyF5qlRz+iIVWu56E2fqGHFrXz0QNUhLB/8nKqvRH66JKGQ==} + engines: {node: '>=10'} + + lodash@4.17.21: + resolution: {integrity: sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==} + + long@5.3.2: + resolution: {integrity: sha512-mNAgZ1GmyNhD7AuqnTG3/VQ26o760+ZYBPKjPvugO8+nLbYfX6TVpJPseBvopbdY+qpZ/lKUnmEc1LeZYS3QAA==} + + lru.min@1.1.3: + resolution: {integrity: sha512-Lkk/vx6ak3rYkRR0Nhu4lFUT2VDnQSxBe8Hbl7f36358p6ow8Bnvr8lrLt98H8J1aGxfhbX4Fs5tYg2+FTwr5Q==} + engines: {bun: '>=1.0.0', deno: '>=1.30.0', node: '>=8.0.0'} + magic-string@0.30.21: resolution: {integrity: sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==} ms@2.1.3: resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} + mysql2@3.15.3: + resolution: {integrity: sha512-FBrGau0IXmuqg4haEZRBfHNWB5mUARw6hNwPDXXGg0XzVJ50mr/9hb267lvpVMnhZ1FON3qNd4Xfcez1rbFwSg==} + engines: {node: '>= 8.0'} + + named-placeholders@1.1.6: + resolution: {integrity: sha512-Tz09sEL2EEuv5fFowm419c1+a/jSMiBjI9gHxVLrVdbUkkNUUfjsVYs9pVZu5oCon/kmRh9TfLEObFtkVxmY0w==} + engines: {node: '>=8.0.0'} + nanoid@3.3.11: resolution: {integrity: sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==} engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} hasBin: true + node-fetch-native@1.6.7: + resolution: {integrity: sha512-g9yhqoedzIUm0nTnTqAQvueMPVOuIY16bqgAJJC8XOOubYFNwz6IER9qs0Gq2Xd0+CecCKFjtdDTMA4u4xG06Q==} + + nypm@0.6.2: + resolution: {integrity: sha512-7eM+hpOtrKrBDCh7Ypu2lJ9Z7PNZBdi/8AT3AX8xoCj43BBVHD0hPSTEvMtkMpfs8FCqBGhxB+uToIQimA111g==} + engines: {node: ^14.16.0 || >=16.10.0} + hasBin: true + obug@2.1.1: resolution: {integrity: sha512-uTqF9MuPraAQ+IsnPf366RG4cP9RtUi7MLO1N3KEc+wb0a6yKpeL0lmk2IB1jY5KHPAlTc6T/JRdC/YqxHNwkQ==} + ohash@2.0.11: + resolution: {integrity: sha512-RdR9FQrFwNBNXAr4GixM8YaRZRJ5PUWbKYbE5eOsrwAjJW0q2REGcf79oYPsLyskQCZG1PLN+S/K1V00joZAoQ==} + + path-key@3.1.1: + resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==} + engines: {node: '>=8'} + pathe@2.0.3: resolution: {integrity: sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==} + perfect-debounce@1.0.0: + resolution: {integrity: sha512-xCy9V055GLEqoFaHoC1SoLIaLmWctgCUaBaWxDZ7/Zx4CTyX7cJQLJOok/orfjZAh9kEYpjJa4d0KcJmCbctZA==} + + pg-cloudflare@1.2.7: + resolution: {integrity: sha512-YgCtzMH0ptvZJslLM1ffsY4EuGaU0cx4XSdXLRFae8bPP4dS5xL1tNB3k2o/N64cHJpwU7dxKli/nZ2lUa5fLg==} + + pg-connection-string@2.9.1: + resolution: {integrity: sha512-nkc6NpDcvPVpZXxrreI/FOtX3XemeLl8E0qFr6F2Lrm/I8WOnaWNhIPK2Z7OHpw7gh5XJThi6j6ppgNoaT1w4w==} + + pg-int8@1.0.1: + resolution: {integrity: sha512-WCtabS6t3c8SkpDBUlb1kjOs7l66xsGdKpIPZsg4wR+B3+u9UAum2odSsF9tnvxg80h4ZxLWMy4pRjOsFIqQpw==} + engines: {node: '>=4.0.0'} + + pg-pool@3.10.1: + resolution: {integrity: sha512-Tu8jMlcX+9d8+QVzKIvM/uJtp07PKr82IUOYEphaWcoBhIYkoHpLXN3qO59nAI11ripznDsEzEv8nUxBVWajGg==} + peerDependencies: + pg: '>=8.0' + + pg-protocol@1.10.3: + resolution: {integrity: sha512-6DIBgBQaTKDJyxnXaLiLR8wBpQQcGWuAESkRBX/t6OwA8YsqP+iVSiond2EDy6Y/dsGk8rh/jtax3js5NeV7JQ==} + + pg-types@2.2.0: + resolution: {integrity: sha512-qTAAlrEsl8s4OiEQY69wDvcMIdQN6wdz5ojQiOy6YRMuynxenON0O5oCpJI6lshc6scgAY8qvJ2On/p+CXY0GA==} + engines: {node: '>=4'} + + pg@8.16.3: + resolution: {integrity: sha512-enxc1h0jA/aq5oSDMvqyW3q89ra6XIIDZgCX9vkMrnz5DFTw/Ny3Li2lFQ+pt3L6MCgm/5o2o8HW9hiJji+xvw==} + engines: {node: '>= 16.0.0'} + peerDependencies: + pg-native: '>=3.0.1' + peerDependenciesMeta: + pg-native: + optional: true + + pgpass@1.0.5: + resolution: {integrity: sha512-FdW9r/jQZhSeohs1Z3sI1yxFQNFvMcnmfuj4WBMUTxOrAyLMaTcE1aAMBiTlbMNaXvBCQuVi0R7hd8udDSP7ug==} + picocolors@1.1.1: resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==} @@ -733,24 +1001,91 @@ packages: resolution: {integrity: sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==} engines: {node: '>=12'} + pkg-types@2.3.0: + resolution: {integrity: sha512-SIqCzDRg0s9npO5XQ3tNZioRY1uK06lA41ynBC1YmFTmnY6FjUjVt6s4LoADmwoig1qqD0oK8h1p/8mlMx8Oig==} + postcss@8.5.6: resolution: {integrity: sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==} engines: {node: ^10 || ^12 || >=14} - prisma@5.22.0: - resolution: {integrity: sha512-vtpjW3XuYCSnMsNVBjLMNkTj6OZbudcPPTPYHqX0CJfpcdWciI1dM8uHETwmDxxiqEwCIE6WvXucWUetJgfu/A==} - engines: {node: '>=16.13'} + postgres-array@2.0.0: + resolution: {integrity: sha512-VpZrUqU5A69eQyW2c5CA1jtLecCsN2U/bD6VilrFDWq5+5UIEVO7nazS3TEcHf1zuPYO/sqGvUvW62g86RXZuA==} + engines: {node: '>=4'} + + postgres-array@3.0.4: + resolution: {integrity: sha512-nAUSGfSDGOaOAEGwqsRY27GPOea7CNipJPOA7lPbdEpx5Kg3qzdP0AaWC5MlhTWV9s4hFX39nomVZ+C4tnGOJQ==} + engines: {node: '>=12'} + + 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'} + + postgres@3.4.7: + resolution: {integrity: sha512-Jtc2612XINuBjIl/QTWsV5UvE8UHuNblcO3vVADSrKsrc6RqGX6lOW1cEo3CM2v0XG4Nat8nI+YM7/f26VxXLw==} + engines: {node: '>=12'} + + prisma@7.2.0: + resolution: {integrity: sha512-jSdHWgWOgFF24+nRyyNRVBIgGDQEsMEF8KPHvhBBg3jWyR9fUAK0Nq9ThUmiGlNgq2FA7vSk/ZoCvefod+a8qg==} + engines: {node: ^20.19 || ^22.12 || >=24.0} hasBin: true + peerDependencies: + better-sqlite3: '>=9.0.0' + typescript: '>=5.4.0' + peerDependenciesMeta: + better-sqlite3: + optional: true + typescript: + optional: true + + proper-lockfile@4.1.2: + resolution: {integrity: sha512-TjNPblN4BwAWMXU8s9AEz4JmQxnD1NNL7bNOY/AKUzyamc379FWASUhc/K1pL2noVb+XmZKLL68cjzLsiOAMaA==} + + pure-rand@6.1.0: + resolution: {integrity: sha512-bVWawvoZoBYpp6yIoQtQXHZjmz35RSVHnUOTefl8Vcjr8snTPY1wnpSPMWekcFwbxI6gtmT7rSYPFvz71ldiOA==} quansync@1.0.0: resolution: {integrity: sha512-5xZacEEufv3HSTPQuchrvV6soaiACMFnq1H8wkVioctoH3TRha9Sz66lOxRwPK/qZj7HPiSveih9yAyh98gvqA==} + rc9@2.1.2: + resolution: {integrity: sha512-btXCnMmRIBINM2LDZoEmOogIZU7Qe7zn4BpomSKZ/ykbLObuBdvG+mFq11DL6fjH1DRwHhrlgtYWG96bJiC7Cg==} + + react-dom@19.2.3: + resolution: {integrity: sha512-yELu4WmLPw5Mr/lmeEpox5rw3RETacE++JgHqQzd2dg+YbJuat3jH4ingc+WPZhxaoFzdv9y33G+F7Nl5O0GBg==} + peerDependencies: + react: ^19.2.3 + + react@19.2.3: + resolution: {integrity: sha512-Ku/hhYbVjOQnXDZFv2+RibmLFGwFdeeKHFcOTlrt7xplBnya5OGn/hIRDsqDiSUcfORsDC7MPxwork8jBwsIWA==} + engines: {node: '>=0.10.0'} + + readdirp@4.1.2: + resolution: {integrity: sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==} + engines: {node: '>= 14.18.0'} + redis@4.7.1: resolution: {integrity: sha512-S1bJDnqLftzHXHP8JsT5II/CtHWQrASX5K96REjWjlmWKrviSOLWmM7QnRLstAWsu1VBBV1ffV6DzCvxNP0UJQ==} + regexp-to-ast@0.5.0: + resolution: {integrity: sha512-tlbJqcMHnPKI9zSrystikWKwHkBqu2a/Sgw01h3zFjvYrMxEDYHzzoMZnUrbIfpTFEsoRnnviOXNCzFiSc54Qw==} + + remeda@2.21.3: + resolution: {integrity: sha512-XXrZdLA10oEOQhLLzEJEiFFSKi21REGAkHdImIb4rt/XXy8ORGXh5HCcpUOsElfPNDb+X6TA/+wkh+p2KffYmg==} + resolve-pkg-maps@1.0.0: resolution: {integrity: sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==} + retry@0.12.0: + resolution: {integrity: sha512-9LkiTwjUh6rT555DtE9rTX+BKByPfrMzEAtnlEtdEwr3Nkffwiihqe2bWADg+OQRjt9gl6ICdmB/ZFDCGAtSow==} + engines: {node: '>= 4'} + rolldown-plugin-dts@0.20.0: resolution: {integrity: sha512-cLAY1kN2ilTYMfZcFlGWbXnu6Nb+8uwUBsi+Mjbh4uIx7IN8uMOmJ7RxrrRgPsO4H7eSz3E+JwGoL1gyugiyUA==} engines: {node: '>=20.19.0'} @@ -780,24 +1115,59 @@ packages: engines: {node: '>=18.0.0', npm: '>=8.0.0'} hasBin: true + safer-buffer@2.1.2: + resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==} + + scheduler@0.27.0: + resolution: {integrity: sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==} + semver@7.7.3: resolution: {integrity: sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==} engines: {node: '>=10'} hasBin: true + seq-queue@0.0.5: + resolution: {integrity: sha512-hr3Wtp/GZIc/6DAGPDcV4/9WoZhjrkXsi5B/07QgX8tsdc6ilr7BFM6PM6rbdAX1kFSDYeZGLipIZZKyQP0O5Q==} + + shebang-command@2.0.0: + resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==} + engines: {node: '>=8'} + + shebang-regex@3.0.0: + resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==} + engines: {node: '>=8'} + siginfo@2.0.0: resolution: {integrity: sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==} + signal-exit@3.0.7: + resolution: {integrity: sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==} + + signal-exit@4.1.0: + resolution: {integrity: sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==} + engines: {node: '>=14'} + source-map-js@1.2.1: resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==} engines: {node: '>=0.10.0'} + split2@4.2.0: + resolution: {integrity: sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==} + engines: {node: '>= 10.x'} + + sqlstring@2.3.3: + resolution: {integrity: sha512-qC9iz2FlN7DQl3+wjwn3802RTyjCx7sDvfQEXchwa6CWOx07/WVfh91gBmQ9fahw8snwGEWU3xGzOt4tFyHLxg==} + engines: {node: '>= 0.6'} + stackback@0.0.2: resolution: {integrity: sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==} std-env@3.10.0: resolution: {integrity: sha512-5GS12FdOZNliM5mAOxFRg7Ir0pWz8MdpYm6AY6VPkGpbA7ZzmbzNcBJQ0GPvvyWgcY7QAhCgf9Uy89I03faLkg==} + std-env@3.9.0: + resolution: {integrity: sha512-UGvjygr6F6tpH7o2qyqR6QYpwraIjKSdtzyBdyytFOHmPZY917kwdwLG0RbOjWOnKmnm3PeHjaoLLMie7kPLQw==} + tinybench@2.9.0: resolution: {integrity: sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==} @@ -855,6 +1225,10 @@ packages: tslib@2.8.1: resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==} + type-fest@4.41.0: + resolution: {integrity: sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA==} + engines: {node: '>=16'} + typescript@5.9.3: resolution: {integrity: sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==} engines: {node: '>=14.17'} @@ -876,6 +1250,14 @@ packages: synckit: optional: true + valibot@1.2.0: + resolution: {integrity: sha512-mm1rxUsmOxzrwnX5arGS+U4T25RdvpPjPN4yR0u9pUBov9+zGVtO84tif1eY4r6zWxVxu3KzIyknJy3rxfRZZg==} + peerDependencies: + typescript: '>=5' + peerDependenciesMeta: + typescript: + optional: true + vite-tsconfig-paths@4.3.2: resolution: {integrity: sha512-0Vd/a6po6Q+86rPlntHye7F31zA2URZMbH8M3saAZ/xR9QoGN/L21bxEGfXdWmFdNkqPpRdxFT7nmNe12e9/uA==} peerDependencies: @@ -958,14 +1340,26 @@ packages: jsdom: optional: true + which@2.0.2: + resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==} + engines: {node: '>= 8'} + hasBin: true + why-is-node-running@2.3.0: resolution: {integrity: sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==} engines: {node: '>=8'} hasBin: true + xtend@4.0.2: + resolution: {integrity: sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==} + engines: {node: '>=0.4'} + yallist@4.0.0: resolution: {integrity: sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==} + zeptomatch@2.0.2: + resolution: {integrity: sha512-H33jtSKf8Ijtb5BW6wua3G5DhnFjbFML36eFu+VdOoVY4HD9e7ggjqdM6639B+L87rjnR6Y+XeRzBXZdy52B/g==} + zod@4.2.1: resolution: {integrity: sha512-0wZ1IRqGGhMP76gLqz8EyfBXKk0J2qo2+H3fi4mcUP/KtTocoX08nmIAHl1Z2kJIZbZee8KOpBCSNPRgauucjw==} @@ -992,6 +1386,31 @@ snapshots: '@babel/helper-string-parser': 7.27.1 '@babel/helper-validator-identifier': 7.28.5 + '@chevrotain/cst-dts-gen@10.5.0': + dependencies: + '@chevrotain/gast': 10.5.0 + '@chevrotain/types': 10.5.0 + lodash: 4.17.21 + + '@chevrotain/gast@10.5.0': + dependencies: + '@chevrotain/types': 10.5.0 + lodash: 4.17.21 + + '@chevrotain/types@10.5.0': {} + + '@chevrotain/utils@10.5.0': {} + + '@electric-sql/pglite-socket@0.0.6(@electric-sql/pglite@0.3.2)': + dependencies: + '@electric-sql/pglite': 0.3.2 + + '@electric-sql/pglite-tools@0.2.7(@electric-sql/pglite@0.3.2)': + dependencies: + '@electric-sql/pglite': 0.3.2 + + '@electric-sql/pglite@0.3.2': {} + '@emnapi/core@1.7.1': dependencies: '@emnapi/wasi-threads': 1.1.0 @@ -1086,6 +1505,10 @@ snapshots: '@esbuild/win32-x64@0.27.2': optional: true + '@hono/node-server@1.19.6(hono@4.10.6)': + dependencies: + hono: 4.10.6 + '@jridgewell/gen-mapping@0.3.13': dependencies: '@jridgewell/sourcemap-codec': 1.5.5 @@ -1100,6 +1523,11 @@ snapshots: '@jridgewell/resolve-uri': 3.1.2 '@jridgewell/sourcemap-codec': 1.5.5 + '@mrleebo/prisma-ast@0.12.1': + dependencies: + chevrotain: 10.5.0 + lilconfig: 2.1.0 + '@napi-rs/wasm-runtime@1.1.0': dependencies: '@emnapi/core': 1.7.1 @@ -1109,30 +1537,92 @@ snapshots: '@oxc-project/types@0.103.0': {} - '@prisma/client@5.22.0(prisma@5.22.0)': + '@prisma/adapter-pg@7.2.0': + dependencies: + '@prisma/driver-adapter-utils': 7.2.0 + pg: 8.16.3 + postgres-array: 3.0.4 + transitivePeerDependencies: + - pg-native + + '@prisma/client-runtime-utils@7.2.0': {} + + '@prisma/client@7.2.0(prisma@7.2.0(@types/react@19.2.7)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(typescript@5.9.3))(typescript@5.9.3)': + dependencies: + '@prisma/client-runtime-utils': 7.2.0 optionalDependencies: - prisma: 5.22.0 + prisma: 7.2.0(@types/react@19.2.7)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(typescript@5.9.3) + typescript: 5.9.3 - '@prisma/debug@5.22.0': {} - - '@prisma/engines-version@5.22.0-44.605197351a3c8bdd595af2d2a9bc3025bca48ea2': {} - - '@prisma/engines@5.22.0': + '@prisma/config@7.2.0': dependencies: - '@prisma/debug': 5.22.0 - '@prisma/engines-version': 5.22.0-44.605197351a3c8bdd595af2d2a9bc3025bca48ea2 - '@prisma/fetch-engine': 5.22.0 - '@prisma/get-platform': 5.22.0 + c12: 3.1.0 + deepmerge-ts: 7.1.5 + effect: 3.18.4 + empathic: 2.0.0 + transitivePeerDependencies: + - magicast - '@prisma/fetch-engine@5.22.0': - dependencies: - '@prisma/debug': 5.22.0 - '@prisma/engines-version': 5.22.0-44.605197351a3c8bdd595af2d2a9bc3025bca48ea2 - '@prisma/get-platform': 5.22.0 + '@prisma/debug@6.8.2': {} - '@prisma/get-platform@5.22.0': + '@prisma/debug@7.2.0': {} + + '@prisma/dev@0.17.0(typescript@5.9.3)': dependencies: - '@prisma/debug': 5.22.0 + '@electric-sql/pglite': 0.3.2 + '@electric-sql/pglite-socket': 0.0.6(@electric-sql/pglite@0.3.2) + '@electric-sql/pglite-tools': 0.2.7(@electric-sql/pglite@0.3.2) + '@hono/node-server': 1.19.6(hono@4.10.6) + '@mrleebo/prisma-ast': 0.12.1 + '@prisma/get-platform': 6.8.2 + '@prisma/query-plan-executor': 6.18.0 + foreground-child: 3.3.1 + get-port-please: 3.1.2 + hono: 4.10.6 + http-status-codes: 2.3.0 + pathe: 2.0.3 + proper-lockfile: 4.1.2 + remeda: 2.21.3 + std-env: 3.9.0 + valibot: 1.2.0(typescript@5.9.3) + zeptomatch: 2.0.2 + transitivePeerDependencies: + - typescript + + '@prisma/driver-adapter-utils@7.2.0': + dependencies: + '@prisma/debug': 7.2.0 + + '@prisma/engines-version@7.2.0-4.0c8ef2ce45c83248ab3df073180d5eda9e8be7a3': {} + + '@prisma/engines@7.2.0': + dependencies: + '@prisma/debug': 7.2.0 + '@prisma/engines-version': 7.2.0-4.0c8ef2ce45c83248ab3df073180d5eda9e8be7a3 + '@prisma/fetch-engine': 7.2.0 + '@prisma/get-platform': 7.2.0 + + '@prisma/fetch-engine@7.2.0': + dependencies: + '@prisma/debug': 7.2.0 + '@prisma/engines-version': 7.2.0-4.0c8ef2ce45c83248ab3df073180d5eda9e8be7a3 + '@prisma/get-platform': 7.2.0 + + '@prisma/get-platform@6.8.2': + dependencies: + '@prisma/debug': 6.8.2 + + '@prisma/get-platform@7.2.0': + dependencies: + '@prisma/debug': 7.2.0 + + '@prisma/query-plan-executor@6.18.0': {} + + '@prisma/studio-core@0.9.0(@types/react@19.2.7)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)': + dependencies: + '@types/react': 19.2.7 + react: 19.2.3 + react-dom: 19.2.3(react@19.2.3) '@quansync/fs@1.0.0': dependencies: @@ -1293,6 +1783,10 @@ snapshots: dependencies: undici-types: 6.21.0 + '@types/react@19.2.7': + dependencies: + csstype: 3.2.3 + '@vitest/expect@4.0.16': dependencies: '@standard-schema/spec': 1.1.0 @@ -1302,13 +1796,13 @@ snapshots: chai: 6.2.2 tinyrainbow: 3.0.3 - '@vitest/mocker@4.0.16(vite@7.3.0(@types/node@20.19.27))': + '@vitest/mocker@4.0.16(vite@7.3.0(@types/node@20.19.27)(jiti@2.6.1))': dependencies: '@vitest/spy': 4.0.16 estree-walker: 3.0.3 magic-string: 0.30.21 optionalDependencies: - vite: 7.3.0(@types/node@20.19.27) + vite: 7.3.0(@types/node@20.19.27)(jiti@2.6.1) '@vitest/pretty-format@4.0.16': dependencies: @@ -1341,22 +1835,83 @@ snapshots: '@babel/parser': 7.28.5 pathe: 2.0.3 + aws-ssl-profiles@1.1.2: {} + birpc@4.0.0: {} + c12@3.1.0: + dependencies: + chokidar: 4.0.3 + confbox: 0.2.2 + defu: 6.1.4 + dotenv: 16.6.1 + exsolve: 1.0.8 + giget: 2.0.0 + jiti: 2.6.1 + ohash: 2.0.11 + pathe: 2.0.3 + perfect-debounce: 1.0.0 + pkg-types: 2.3.0 + rc9: 2.1.2 + cac@6.7.14: {} chai@6.2.2: {} + chevrotain@10.5.0: + dependencies: + '@chevrotain/cst-dts-gen': 10.5.0 + '@chevrotain/gast': 10.5.0 + '@chevrotain/types': 10.5.0 + '@chevrotain/utils': 10.5.0 + lodash: 4.17.21 + regexp-to-ast: 0.5.0 + + chokidar@4.0.3: + dependencies: + readdirp: 4.1.2 + + citty@0.1.6: + dependencies: + consola: 3.4.2 + cluster-key-slot@1.1.2: {} + confbox@0.2.2: {} + + consola@3.4.2: {} + + cross-spawn@7.0.6: + dependencies: + path-key: 3.1.1 + shebang-command: 2.0.0 + which: 2.0.2 + + csstype@3.2.3: {} + debug@4.4.3: dependencies: ms: 2.1.3 + deepmerge-ts@7.1.5: {} + defu@6.1.4: {} + denque@2.1.0: {} + + destr@2.0.5: {} + + dotenv@16.6.1: {} + + dotenv@17.2.3: {} + dts-resolver@2.1.3: {} + effect@3.18.4: + dependencies: + '@standard-schema/spec': 1.1.0 + fast-check: 3.23.2 + empathic@2.0.0: {} es-module-lexer@1.7.0: {} @@ -1396,59 +1951,230 @@ snapshots: expect-type@1.3.0: {} + exsolve@1.0.8: {} + + fast-check@3.23.2: + dependencies: + pure-rand: 6.1.0 + fdir@6.5.0(picomatch@4.0.3): optionalDependencies: picomatch: 4.0.3 + foreground-child@3.3.1: + dependencies: + cross-spawn: 7.0.6 + signal-exit: 4.1.0 + fsevents@2.3.3: optional: true + generate-function@2.3.1: + dependencies: + is-property: 1.0.2 + generic-pool@3.9.0: {} + get-port-please@3.1.2: {} + get-tsconfig@4.13.0: dependencies: resolve-pkg-maps: 1.0.0 + giget@2.0.0: + dependencies: + citty: 0.1.6 + consola: 3.4.2 + defu: 6.1.4 + node-fetch-native: 1.6.7 + nypm: 0.6.2 + pathe: 2.0.3 + globrex@0.1.2: {} + graceful-fs@4.2.11: {} + + grammex@3.1.12: {} + + hono@4.10.6: {} + hookable@6.0.1: {} + http-status-codes@2.3.0: {} + + iconv-lite@0.7.1: + dependencies: + safer-buffer: 2.1.2 + import-without-cache@0.2.5: {} + is-property@1.0.2: {} + + isexe@2.0.0: {} + + jiti@2.6.1: {} + js-sha512@0.9.0: {} jsesc@3.1.0: {} + lilconfig@2.1.0: {} + + lodash@4.17.21: {} + + long@5.3.2: {} + + lru.min@1.1.3: {} + magic-string@0.30.21: dependencies: '@jridgewell/sourcemap-codec': 1.5.5 ms@2.1.3: {} + mysql2@3.15.3: + dependencies: + aws-ssl-profiles: 1.1.2 + denque: 2.1.0 + generate-function: 2.3.1 + iconv-lite: 0.7.1 + long: 5.3.2 + lru.min: 1.1.3 + named-placeholders: 1.1.6 + seq-queue: 0.0.5 + sqlstring: 2.3.3 + + named-placeholders@1.1.6: + dependencies: + lru.min: 1.1.3 + nanoid@3.3.11: {} + node-fetch-native@1.6.7: {} + + nypm@0.6.2: + dependencies: + citty: 0.1.6 + consola: 3.4.2 + pathe: 2.0.3 + pkg-types: 2.3.0 + tinyexec: 1.0.2 + obug@2.1.1: {} + ohash@2.0.11: {} + + path-key@3.1.1: {} + pathe@2.0.3: {} + perfect-debounce@1.0.0: {} + + pg-cloudflare@1.2.7: + optional: true + + pg-connection-string@2.9.1: {} + + pg-int8@1.0.1: {} + + pg-pool@3.10.1(pg@8.16.3): + dependencies: + pg: 8.16.3 + + pg-protocol@1.10.3: {} + + 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 + + pg@8.16.3: + dependencies: + pg-connection-string: 2.9.1 + pg-pool: 3.10.1(pg@8.16.3) + pg-protocol: 1.10.3 + pg-types: 2.2.0 + pgpass: 1.0.5 + optionalDependencies: + pg-cloudflare: 1.2.7 + + pgpass@1.0.5: + dependencies: + split2: 4.2.0 + picocolors@1.1.1: {} picomatch@4.0.3: {} + pkg-types@2.3.0: + dependencies: + confbox: 0.2.2 + exsolve: 1.0.8 + pathe: 2.0.3 + postcss@8.5.6: dependencies: nanoid: 3.3.11 picocolors: 1.1.1 source-map-js: 1.2.1 - prisma@5.22.0: + postgres-array@2.0.0: {} + + postgres-array@3.0.4: {} + + postgres-bytea@1.0.1: {} + + postgres-date@1.0.7: {} + + postgres-interval@1.2.0: dependencies: - '@prisma/engines': 5.22.0 + xtend: 4.0.2 + + postgres@3.4.7: {} + + prisma@7.2.0(@types/react@19.2.7)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(typescript@5.9.3): + dependencies: + '@prisma/config': 7.2.0 + '@prisma/dev': 0.17.0(typescript@5.9.3) + '@prisma/engines': 7.2.0 + '@prisma/studio-core': 0.9.0(@types/react@19.2.7)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + mysql2: 3.15.3 + postgres: 3.4.7 optionalDependencies: - fsevents: 2.3.3 + typescript: 5.9.3 + transitivePeerDependencies: + - '@types/react' + - magicast + - react + - react-dom + + proper-lockfile@4.1.2: + dependencies: + graceful-fs: 4.2.11 + retry: 0.12.0 + signal-exit: 3.0.7 + + pure-rand@6.1.0: {} quansync@1.0.0: {} + rc9@2.1.2: + dependencies: + defu: 6.1.4 + destr: 2.0.5 + + react-dom@19.2.3(react@19.2.3): + dependencies: + react: 19.2.3 + scheduler: 0.27.0 + + react@19.2.3: {} + + readdirp@4.1.2: {} + redis@4.7.1: dependencies: '@redis/bloom': 1.2.0(@redis/client@1.6.1) @@ -1458,8 +2184,16 @@ snapshots: '@redis/search': 1.2.0(@redis/client@1.6.1) '@redis/time-series': 1.1.0(@redis/client@1.6.1) + regexp-to-ast@0.5.0: {} + + remeda@2.21.3: + dependencies: + type-fest: 4.41.0 + resolve-pkg-maps@1.0.0: {} + retry@0.12.0: {} + rolldown-plugin-dts@0.20.0(rolldown@1.0.0-beta.57)(typescript@5.9.3): dependencies: '@babel/generator': 7.28.5 @@ -1523,16 +2257,38 @@ snapshots: '@rollup/rollup-win32-x64-msvc': 4.54.0 fsevents: 2.3.3 + safer-buffer@2.1.2: {} + + scheduler@0.27.0: {} + semver@7.7.3: {} + seq-queue@0.0.5: {} + + shebang-command@2.0.0: + dependencies: + shebang-regex: 3.0.0 + + shebang-regex@3.0.0: {} + siginfo@2.0.0: {} + signal-exit@3.0.7: {} + + signal-exit@4.1.0: {} + source-map-js@1.2.1: {} + split2@4.2.0: {} + + sqlstring@2.3.3: {} + stackback@0.0.2: {} std-env@3.10.0: {} + std-env@3.9.0: {} + tinybench@2.9.0: {} tinyexec@1.0.2: {} @@ -1580,6 +2336,8 @@ snapshots: tslib@2.8.1: optional: true + type-fest@4.41.0: {} + typescript@5.9.3: {} unconfig-core@7.4.2: @@ -1593,18 +2351,22 @@ snapshots: dependencies: rolldown: 1.0.0-beta.57 - vite-tsconfig-paths@4.3.2(typescript@5.9.3)(vite@7.3.0(@types/node@20.19.27)): + valibot@1.2.0(typescript@5.9.3): + optionalDependencies: + typescript: 5.9.3 + + vite-tsconfig-paths@4.3.2(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) + 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): + vite@7.3.0(@types/node@20.19.27)(jiti@2.6.1): dependencies: esbuild: 0.27.2 fdir: 6.5.0(picomatch@4.0.3) @@ -1615,11 +2377,12 @@ snapshots: optionalDependencies: '@types/node': 20.19.27 fsevents: 2.3.3 + jiti: 2.6.1 - vitest@4.0.16(@types/node@20.19.27): + vitest@4.0.16(@types/node@20.19.27)(jiti@2.6.1): dependencies: '@vitest/expect': 4.0.16 - '@vitest/mocker': 4.0.16(vite@7.3.0(@types/node@20.19.27)) + '@vitest/mocker': 4.0.16(vite@7.3.0(@types/node@20.19.27)(jiti@2.6.1)) '@vitest/pretty-format': 4.0.16 '@vitest/runner': 4.0.16 '@vitest/snapshot': 4.0.16 @@ -1636,7 +2399,7 @@ snapshots: tinyexec: 1.0.2 tinyglobby: 0.2.15 tinyrainbow: 3.0.3 - vite: 7.3.0(@types/node@20.19.27) + vite: 7.3.0(@types/node@20.19.27)(jiti@2.6.1) why-is-node-running: 2.3.0 optionalDependencies: '@types/node': 20.19.27 @@ -1653,11 +2416,21 @@ snapshots: - tsx - yaml + which@2.0.2: + dependencies: + isexe: 2.0.0 + why-is-node-running@2.3.0: dependencies: siginfo: 2.0.0 stackback: 0.0.2 + xtend@4.0.2: {} + yallist@4.0.0: {} + zeptomatch@2.0.2: + dependencies: + grammex: 3.1.12 + zod@4.2.1: {} diff --git a/tools/test-dbms/check-connections.mts b/tools/test-dbms/check-connections.mts index 0b83bee..20a0889 100644 --- a/tools/test-dbms/check-connections.mts +++ b/tools/test-dbms/check-connections.mts @@ -7,17 +7,19 @@ const __filename = fileURLToPath(import.meta.url); const __dirname = path.dirname(__filename); const DEFAULT_ENV_FILE = path.resolve(__dirname, '..', '..', '.env.ci'); -const COMMON_PACKAGE_JSON = path.resolve( +const INFRA_PACKAGE_JSON = path.resolve( __dirname, '..', '..', 'packages', - 'common', + 'infra', 'package.json' ); -const commonRequire = createRequire(COMMON_PACKAGE_JSON); -const { PrismaClient } = commonRequire('@prisma/client'); -const { createClient } = commonRequire('redis'); +const infraRequire = createRequire(INFRA_PACKAGE_JSON); +const { PrismaClient } = infraRequire('@prisma/client'); +const { PrismaPg } = infraRequire('@prisma/adapter-pg'); +const { Pool } = infraRequire('pg'); +const { createClient } = infraRequire('redis'); type EnvMap = Record; @@ -84,17 +86,17 @@ const resolveRedisUrl = (env: EnvMap): string => { }; const testPostgres = async (databaseUrl: string): Promise => { - const prisma = new PrismaClient({ - datasources: { - db: { - url: databaseUrl, - }, - }, - }); + const pool = new Pool({ connectionString: databaseUrl }); + const adapter = new PrismaPg(pool); + const prisma = new PrismaClient({ adapter }); - await prisma.$connect(); - await prisma.$queryRawUnsafe('SELECT 1'); - await prisma.$disconnect(); + try { + await prisma.$connect(); + await prisma.$queryRawUnsafe('SELECT 1'); + } finally { + await prisma.$disconnect(); + await pool.end(); + } }; const testRedis = async (redisUrl: string): Promise => {