feat: add TypeBox/Ajv config schema package to the workspace (E00-S04-T01)

Adds packages/config (@personal-blog/config) — the EPPP configuration
service foundation. The package defines the configuration schema with
TypeBox (configSchema: host, port, databaseUrl, sessionSecret — the
validated config fields, golden-tuple pins @sinclair/typebox@0.34.52 and
ajv@8.20.0) and compiles it with Ajv (validateConfig). The environment
adapter (T04), field-specific startup errors (T02) and secret redaction
(T03) build on this boundary in later tasks; nothing reads process.env yet.

Wiring for the new workspace package: lockfile importer + resolved
typebox/ajv tree, apps/server/Dockerfile manifest copy (frozen in-image
install must match the lockfile importers), config-schema CI job, package
set fixtures (workspace-layout, workspace-config, strict-tsconfig,
typescript-pin), probe-file gitignore entry.
This commit is contained in:
implementer
2026-08-30 02:29:52 +00:00
parent 6a65d4c789
commit bcea489391
13 changed files with 229 additions and 5 deletions
+49
View File
@@ -0,0 +1,49 @@
/**
* EPPP configuration validation — [E00-S04-T01] TypeBox/Ajv schema.
*
* Compiles the TypeBox `configSchema` with Ajv (the golden-tuple validator,
* Technology-Stack §5.2/§7) and exposes `validateConfig`, the generic
* schema-validation entry point: given an unknown value it reports whether
* the value is a valid configuration and the Ajv error messages otherwise.
*
* This is deliberately NOT the E00-S04-T02 field-specific startup error:
* `validateConfig` returns the raw schema-validation outcome (valid or not,
* with the Ajv messages) and performs no startup wiring — the adapter
* (E00-S04-T04) and the startup error formatting (E00-S04-T02) build on it
* in later tasks.
*/
import { Ajv } from 'ajv';
import { configSchema } from './schema.js';
/** Ajv instance for the config schema — `allErrors` reports every violation. */
const ajv = new Ajv({ allErrors: true });
/** The compiled validator — TypeBox schemas are JSON Schema, so Ajv compiles them directly. */
const validateConfigValue = ajv.compile(configSchema);
/** The outcome of validating a value against the config schema. */
export interface ConfigValidationResult {
/** True when the value is a valid configuration. */
valid: boolean;
/** Ajv error messages, empty when `valid` is true. */
errors: string[];
}
/**
* Validates an unknown value against the config schema.
*
* @param value - the value to validate (typically the parsed config object)
* @returns `{ valid: true, errors: [] }` for a valid configuration, or
* `{ valid: false, errors }` with the Ajv messages naming each violation
* (e.g. `"must have required property 'sessionSecret'"`).
*/
export function validateConfig(value: unknown): ConfigValidationResult {
const valid = validateConfigValue(value);
if (valid) {
return { valid: true, errors: [] };
}
const errors = (validateConfigValue.errors ?? []).map((error) => error.message ?? 'invalid');
return { valid: false, errors };
}