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
+74 -12
View File
@@ -1,4 +1,3 @@
import { PrismaClient } from '@prisma/client';
import { PrismaPg } from '@prisma/adapter-pg';
import { Pool } from 'pg';
@@ -22,21 +21,75 @@ export interface PostgresConnector<TClient = unknown> {
disconnect(): Promise<void>;
}
export interface PrismaClientFactoryOptions {
adapter: PrismaPg;
log?: PostgresLogOption[];
}
export type PrismaClientFactory<TClient> = (
options: PrismaClientFactoryOptions
) => TClient;
const resolveSchemaName = (
value: string | undefined
): string => {
if (!value) {
return 'public';
}
const trimmed = value.trim();
return trimmed ? trimmed : 'public';
};
const applySchemaToDatabaseUrl = (
url: string,
schema: string | undefined
): string => {
if (!schema) {
return url;
}
try {
const parsed = new URL(url);
parsed.searchParams.set('schema', resolveSchemaName(schema));
return parsed.toString();
} catch {
return url;
}
};
const extractSchemaFromDatabaseUrl = (
url: string
): string | undefined => {
try {
const parsed = new URL(url);
const schema = parsed.searchParams.get('schema');
return schema && schema.trim() ? schema.trim() : undefined;
} catch {
return undefined;
}
};
const buildDatabaseUrlFromEnv = (
env: NodeJS.ProcessEnv
env: NodeJS.ProcessEnv,
schemaOverride?: string
): 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`;
const schema = resolveSchemaName(
schemaOverride ?? env.POSTGRES_SCHEMA ?? env.DATABASE_SCHEMA
);
return `postgresql://${user}:${password}@${host}:${port}/${dbName}?schema=${schema}`;
};
export const resolvePostgresConfigFromEnv = (
env: NodeJS.ProcessEnv = process.env
options: { env?: NodeJS.ProcessEnv; schema?: string } = {}
): PostgresConfig => {
const url = env.DATABASE_URL ?? buildDatabaseUrlFromEnv(env);
const env = options.env ?? process.env;
const url = env.DATABASE_URL
? applySchemaToDatabaseUrl(env.DATABASE_URL, options.schema)
: buildDatabaseUrlFromEnv(env, options.schema);
if (!url) {
throw new Error('DATABASE_URL is required to create a Postgres client.');
}
@@ -44,23 +97,32 @@ export const resolvePostgresConfigFromEnv = (
return { url };
};
export const createPostgresConnector = (
config: PostgresConfig
): PostgresConnector => {
export const createPostgresConnector = <TClient>(
config: PostgresConfig,
createClient: PrismaClientFactory<TClient>
): PostgresConnector<TClient> => {
const schema =
extractSchemaFromDatabaseUrl(config.url) ??
process.env.POSTGRES_SCHEMA ??
process.env.DATABASE_SCHEMA;
const pool = new Pool({
connectionString: config.url,
...(schema ? { options: `-c search_path=${schema}` } : {}),
});
const adapter = new PrismaPg(pool);
const prisma = new PrismaClient({
const adapter = new PrismaPg(
pool,
schema ? { schema } : undefined
);
const prisma = createClient({
adapter,
log: config.log,
});
return {
prisma,
connect: () => prisma.$connect(),
connect: () => (prisma as { $connect: () => Promise<void> }).$connect(),
disconnect: async () => {
await prisma.$disconnect();
await (prisma as { $disconnect: () => Promise<void> }).$disconnect();
await pool.end();
},
};