import { z } from 'zod'; const PLACEHOLDER_TENANT_ID = '01J00000000000000000000000'; const LOOPBACK_HOSTS = new Set(['localhost', '127.0.0.1', '::1']); const configSchema = z.object({ NODE_ENV: z.enum(['development', 'test', 'production']).default('development'), PORT: z.coerce.number().int().min(1).max(65535).default(8080), HOST: z.string().min(1).default('127.0.0.1'), LOG_LEVEL: z.enum(['fatal', 'error', 'warn', 'info', 'debug', 'trace', 'silent']).default('info'), STAFF_RUNTIME_MODE: z.enum(['standalone', 'platform']).default('standalone'), STANDALONE_ACCESS: z.enum(['employee', 'manager', 'terminal']).default('employee'), STANDALONE_MANAGER_TOKEN: emptyStringAsUndefined(z.string().min(32).optional()), STANDALONE_TERMINAL_TOKEN: emptyStringAsUndefined(z.string().min(32).optional()), STANDALONE_TERMINAL_ACTOR_ID: emptyStringAsUndefined( z .string() .regex(/^[0-9A-HJKMNP-TV-Z]{26}$/) .optional(), ), HTTPS_PFX_PATH: emptyStringAsUndefined(z.string().optional()), HTTPS_PFX_PASSPHRASE: emptyStringAsUndefined(z.string().min(16).optional()), DATABASE_URL: z.string().url(), REDIS_URL: optionalUrl(), PLATFORM_GATEWAY_URL: optionalUrl(), PLATFORM_JWT_ISSUER: optionalUrl(), PLATFORM_JWT_AUDIENCE: emptyStringAsUndefined(z.string().optional()), PLATFORM_JWKS_URL: optionalUrl(), STANDALONE_TENANT_ID: z .string({ error: 'STANDALONE_TENANT_ID is required and must be a ULID' }) .regex(/^[0-9A-HJKMNP-TV-Z]{26}$/), STANDALONE_ACTOR_ID: z.string().regex(/^[0-9A-HJKMNP-TV-Z]{26}$/), VAPID_PUBLIC_KEY: emptyStringAsUndefined(z.string().optional()), VAPID_PRIVATE_KEY: emptyStringAsUndefined(z.string().optional()), VAPID_SUBJECT: emptyStringAsUndefined(z.string().optional()), }); function emptyStringAsUndefined(schema: T) { return z.preprocess((value) => (value === '' ? undefined : value), schema); } function optionalUrl() { return emptyStringAsUndefined(z.string().url().optional()); } export type AppConfig = z.infer; export function loadConfig(env: NodeJS.ProcessEnv = process.env): AppConfig { const config = configSchema.parse(env); if ( config.STANDALONE_TENANT_ID === PLACEHOLDER_TENANT_ID && !( config.NODE_ENV === 'test' || (config.NODE_ENV === 'development' && LOOPBACK_HOSTS.has(config.HOST.toLowerCase())) ) ) { throw new Error( 'STANDALONE_TENANT_ID uses the development placeholder, which is allowed only in development on loopback', ); } if (Boolean(config.HTTPS_PFX_PATH) !== Boolean(config.HTTPS_PFX_PASSPHRASE)) { throw new Error('HTTPS_PFX_PATH and HTTPS_PFX_PASSPHRASE must be configured together'); } if (Boolean(config.STANDALONE_MANAGER_TOKEN) !== Boolean(config.STANDALONE_TERMINAL_TOKEN)) { throw new Error('Both standalone manager and terminal tokens must be configured together'); } if (Boolean(config.VAPID_PUBLIC_KEY) !== Boolean(config.VAPID_PRIVATE_KEY)) { throw new Error('VAPID_PUBLIC_KEY and VAPID_PRIVATE_KEY must be configured together'); } if (config.NODE_ENV === 'production' && config.STAFF_RUNTIME_MODE === 'standalone') { if ( !config.HTTPS_PFX_PATH || !config.STANDALONE_MANAGER_TOKEN || !config.STANDALONE_TERMINAL_TOKEN ) { throw new Error( 'STAFF_RUNTIME_MODE=standalone is not allowed in production without HTTPS and manager/terminal tokens', ); } } if (config.STAFF_RUNTIME_MODE === 'platform') { const required = [ 'PLATFORM_GATEWAY_URL', 'PLATFORM_JWT_ISSUER', 'PLATFORM_JWT_AUDIENCE', 'PLATFORM_JWKS_URL', ] as const; for (const key of required) if (!config[key]) throw new Error(`${key} is required in platform mode`); } if (config.NODE_ENV === 'production' && !config.HTTPS_PFX_PATH) { throw new Error('HTTPS is required in production'); } return config; }