apps/api gains Prisma (instance_settings as the first model) with the initial migration applied automatically at startup via prisma migrate deploy, a lazy-connecting PrismaService, and GET /api/v1/readyz reporting named checks (database reachable, migrations applied) with 200/503. DATABASE_URL joins the validated environment schema; MIGRATE_ON_START=false skips deploys for tests and tooling. An idempotent seed script and a Compose dev overlay with PostgreSQL (host port 5434 — 5433 is taken locally) complete the loop. Closes #3 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
44 lines
1.6 KiB
TypeScript
44 lines
1.6 KiB
TypeScript
import { z } from 'zod';
|
|
|
|
/**
|
|
* Environment schemas live here so api, collab, and tooling validate their
|
|
* configuration the same way. Every service calls `parseEnv` once at startup
|
|
* and crashes with a readable list of problems instead of failing later at
|
|
* first use.
|
|
*/
|
|
export const apiEnvSchema = z.object({
|
|
NODE_ENV: z.enum(['development', 'test', 'production']).default('development'),
|
|
PORT: z.coerce.number().int().min(1).max(65535).default(3000),
|
|
LOG_LEVEL: z.enum(['fatal', 'error', 'warn', 'info', 'debug', 'trace']).default('info'),
|
|
/** Version shown in health responses; injected at image build time. */
|
|
APP_VERSION: z.string().default('0.0.0-dev'),
|
|
/** PostgreSQL connection string — required, there is no sensible default. */
|
|
DATABASE_URL: z
|
|
.string()
|
|
.min(1)
|
|
.refine((url) => url.startsWith('postgresql://') || url.startsWith('postgres://'), {
|
|
message: 'must be a postgresql:// connection string',
|
|
}),
|
|
/** Set to "false" to skip `prisma migrate deploy` at startup (tests, tooling). */
|
|
MIGRATE_ON_START: z
|
|
.enum(['true', 'false'])
|
|
.default('true')
|
|
.transform((value) => value === 'true'),
|
|
});
|
|
|
|
export type ApiEnv = z.infer<typeof apiEnvSchema>;
|
|
|
|
export function parseEnv<Schema extends z.ZodTypeAny>(
|
|
schema: Schema,
|
|
env: Record<string, string | undefined>,
|
|
): z.infer<Schema> {
|
|
const result = schema.safeParse(env);
|
|
if (!result.success) {
|
|
const problems = result.error.issues
|
|
.map((issue) => ` - ${issue.path.join('.') || '(root)'}: ${issue.message}`)
|
|
.join('\n');
|
|
throw new Error(`Invalid environment configuration:\n${problems}`);
|
|
}
|
|
return result.data;
|
|
}
|