feat: add database schema support for gateway and game profiles

- Introduced `dbSchema` configuration option in GatewayApiConfig and GatewayOrchestratorConfig.
- Implemented schema resolution logic in environment configuration functions.
- Updated context and orchestrator factory to use GatewayPrismaClient instead of PrismaClient.
- Refactored orchestrator server and profile repository to accommodate new database schema handling.
- Created separate Prisma schemas for game and gateway in the infra package.
- Enhanced Postgres connector to support schema overrides in database URLs.
- Updated documentation to reflect changes in database schema handling.
- Added new Prisma generation and database push scripts for game and gateway schemas.
This commit is contained in:
2026-01-03 14:47:28 +00:00
parent 67d995c65f
commit a9609dcc19
33 changed files with 388 additions and 158 deletions
+12 -2
View File
@@ -9,6 +9,7 @@ export interface TurnDaemonCliOptions {
profileName?: string;
scenario?: string;
databaseUrl?: string;
gatewayDatabaseUrl?: string;
tickMinutes?: number;
schedule?: TurnSchedule;
budget?: Partial<TurnRunBudget>;
@@ -72,14 +73,22 @@ export const runTurnDaemonCli = async (
): Promise<void> => {
const env = options.env ?? process.env;
const profile =
options.profile ?? env.TURN_PROFILE ?? env.PROFILE ?? 'che';
options.profile ?? env.TURN_PROFILE ?? env.PROFILE ?? 'hwe';
const scenario = options.scenario ?? env.TURN_SCENARIO ?? env.SCENARIO;
const profileName =
options.profileName ??
env.TURN_PROFILE_NAME ??
(scenario ? `${profile}:${scenario}` : profile);
const databaseUrl =
options.databaseUrl ?? (await resolveDatabaseUrl({ env }));
options.databaseUrl ??
(await resolveDatabaseUrl({ env, schema: profile }));
const gatewayDatabaseUrl =
options.gatewayDatabaseUrl ??
env.GATEWAY_DATABASE_URL ??
(await resolveDatabaseUrl({
env,
schema: env.GATEWAY_DB_SCHEMA ?? 'public',
}));
const budget = buildBudgetOverride(env, options.budget);
const tickMinutes =
options.tickMinutes ?? parseNumber(env.TURN_TICK_MINUTES);
@@ -93,6 +102,7 @@ export const runTurnDaemonCli = async (
profile,
profileName,
databaseUrl,
gatewayDatabaseUrl,
defaultBudget: budget,
tickMinutes,
schedule: options.schedule,
+3 -3
View File
@@ -1,5 +1,5 @@
import {
createPostgresConnector,
createGamePostgresConnector,
type InputJsonValue,
type TurnEngineCityUpdateInput,
type TurnEngineDatabaseClient,
@@ -236,9 +236,9 @@ export const createDatabaseTurnHooks = async (
options?: { reservedTurns?: InMemoryReservedTurnStore }
): Promise<DatabaseTurnHooks> => {
// 턴 처리 결과를 DB에 반영하는 훅을 만든다.
const connector = createPostgresConnector({ url: databaseUrl });
const connector = createGamePostgresConnector({ url: databaseUrl });
await connector.connect();
const prisma = connector.prisma as TurnEngineDatabaseClient;
const prisma = connector.prisma as unknown as TurnEngineDatabaseClient;
const hooks: TurnDaemonHooks = {
flushChanges: async () => {
@@ -1,7 +1,8 @@
import { createPostgresConnector } from '@sammo-ts/infra';
import { createGatewayPostgresConnector } from '@sammo-ts/infra';
export interface GatewayProfileGateOptions {
databaseUrl: string;
gatewayDatabaseUrl?: string;
profileName: string;
cacheMs?: number;
}
@@ -29,7 +30,9 @@ type GatewayProfileClient = {
export const createGatewayProfileGate = async (
options: GatewayProfileGateOptions
): Promise<GatewayProfileGate> => {
const connector = createPostgresConnector({ url: options.databaseUrl });
const connector = createGatewayPostgresConnector({
url: options.gatewayDatabaseUrl ?? options.databaseUrl,
});
await connector.connect();
const prisma = connector.prisma as unknown as {
gatewayProfile: GatewayProfileClient;
@@ -1,5 +1,5 @@
import {
createPostgresConnector,
createGamePostgresConnector,
type InputJsonValue,
type TurnEngineDatabaseClient,
} from '@sammo-ts/infra';
@@ -269,10 +269,10 @@ export class InMemoryReservedTurnStore {
export const createReservedTurnStore = async (
options: ReservedTurnStoreOptions
): Promise<ReservedTurnStoreHandle> => {
const connector = createPostgresConnector({ url: options.databaseUrl });
const connector = createGamePostgresConnector({ url: options.databaseUrl });
await connector.connect();
const store = new InMemoryReservedTurnStore(
connector.prisma as ReservedTurnDatabaseClient,
connector.prisma as unknown as ReservedTurnDatabaseClient,
{
maxGeneralTurns: options.maxGeneralTurns ?? DEFAULT_GENERAL_TURNS,
maxNationTurns: options.maxNationTurns ?? DEFAULT_NATION_TURNS,
+2
View File
@@ -30,6 +30,7 @@ export interface TurnDaemonRuntimeOptions {
profile: string;
profileName?: string;
databaseUrl: string;
gatewayDatabaseUrl?: string;
defaultBudget?: TurnRunBudget;
clock?: Clock;
controlQueue?: TurnDaemonControlQueue;
@@ -137,6 +138,7 @@ export const createTurnDaemonRuntime = async (
options.profileName
? await createGatewayProfileGate({
databaseUrl: options.databaseUrl,
gatewayDatabaseUrl: options.gatewayDatabaseUrl,
profileName: options.profileName,
cacheMs: options.pauseGateIntervalMs,
})
+3 -3
View File
@@ -1,5 +1,5 @@
import {
createPostgresConnector,
createGamePostgresConnector,
type JsonValue,
type TurnEngineCityRow,
type TurnEngineDatabaseClient,
@@ -252,10 +252,10 @@ const mapTroopRow = (row: TurnEngineTroopRow): Troop => ({
export const loadTurnWorldFromDatabase = async (
options: TurnWorldLoaderOptions
): Promise<TurnWorldLoadResult> => {
const connector = createPostgresConnector({ url: options.databaseUrl });
const connector = createGamePostgresConnector({ url: options.databaseUrl });
await connector.connect();
try {
const prisma = connector.prisma as TurnEngineDatabaseClient;
const prisma = connector.prisma as unknown as TurnEngineDatabaseClient;
const worldState = await prisma.worldState.findFirst();
if (!worldState) {
throw new Error('world_state row is required to start turn daemon.');