dorfteich/packages/shared/src/env.ts
Claude Fable 5 ca0f7cf4b1 Add Prisma with PostgreSQL, automatic migrations, and /readyz
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>
2026-07-04 19:16:44 +02:00

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;
}