Files
CapaKraken/packages/engine/src/blueprint/validator.ts
T
Hartmut cd78f72f33 chore: full technical rename planarchy → capakraken
Complete rename of all technical identifiers across the codebase:

Package names (11 packages):
- @planarchy/* → @capakraken/* in all package.json, tsconfig, imports

Import statements: 277 files, 548 occurrences replaced

Database & Docker:
- PostgreSQL user/db: planarchy → capakraken
- Docker volumes: planarchy_pgdata → capakraken_pgdata
- Connection strings updated in docker-compose, .env, CI

CI/CD:
- GitHub Actions workflow: all filter commands updated
- Test database credentials updated

Infrastructure:
- Redis channel: planarchy:sse → capakraken:sse
- Logger service name: planarchy-api → capakraken-api
- Anonymization seed updated
- Start/stop/restart scripts updated

Test data:
- Seed emails: @planarchy.dev → @capakraken.dev
- E2E test credentials: all 11 spec files updated
- Email defaults: @planarchy.app → @capakraken.app
- localStorage keys: planarchy_* → capakraken_*

Documentation: 30+ .md files updated

Verification:
- pnpm install: workspace resolution works
- TypeScript: only pre-existing TS2589 (no new errors)
- Engine: 310/310 tests pass
- Staffing: 37/37 tests pass

Co-Authored-By: claude-flow <ruv@ruv.net>
2026-03-27 13:18:09 +01:00

93 lines
3.1 KiB
TypeScript

import { FieldType, type BlueprintFieldDefinition } from "@capakraken/shared";
export interface CustomFieldValidationError {
key: string;
message: string;
}
/**
* Validates a `dynamicFields` record against an array of BlueprintFieldDefinitions.
* Returns an array of errors (empty = valid).
*/
export function validateCustomFields(
fieldDefs: BlueprintFieldDefinition[],
dynamicFields: Record<string, unknown>,
): CustomFieldValidationError[] {
const errors: CustomFieldValidationError[] = [];
for (const def of fieldDefs) {
const value = dynamicFields[def.key];
const isEmpty = value === undefined || value === null || value === "";
if (def.required && isEmpty) {
errors.push({ key: def.key, message: `${def.label} is required` });
continue;
}
if (isEmpty) continue;
switch (def.type) {
case FieldType.NUMBER: {
if (typeof value !== "number" && isNaN(Number(value))) {
errors.push({ key: def.key, message: `${def.label} must be a number` });
}
const validation = def.validation;
if (validation) {
const num = Number(value);
if (validation.min !== undefined && num < validation.min) {
errors.push({ key: def.key, message: `${def.label} must be at least ${validation.min}` });
}
if (validation.max !== undefined && num > validation.max) {
errors.push({ key: def.key, message: `${def.label} must be at most ${validation.max}` });
}
}
break;
}
case FieldType.BOOLEAN:
if (value !== true && value !== false && value !== "true" && value !== "false") {
errors.push({ key: def.key, message: `${def.label} must be a boolean` });
}
break;
case FieldType.SELECT:
if (def.options && def.options.length > 0) {
const valid = def.options.some((o) => o.value === value);
if (!valid) {
const allowed = def.options.map((o) => o.label || o.value).join(", ");
errors.push({ key: def.key, message: `${def.label} must be one of: ${allowed}` });
}
}
break;
case FieldType.MULTI_SELECT:
if (Array.isArray(value) && def.options && def.options.length > 0) {
const validSet = new Set(def.options.map((o) => o.value));
const invalid = (value as string[]).filter((v) => !validSet.has(v));
if (invalid.length > 0) {
errors.push({ key: def.key, message: `${def.label} contains invalid values: ${invalid.join(", ")}` });
}
}
break;
case FieldType.URL:
try {
new URL(String(value));
} catch {
errors.push({ key: def.key, message: `${def.label} must be a valid URL` });
}
break;
case FieldType.EMAIL:
if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(String(value))) {
errors.push({ key: def.key, message: `${def.label} must be a valid email address` });
}
break;
// TEXT, TEXTAREA, DATE — no structural validation beyond required
}
}
return errors;
}