import type { TSchema } from "@sinclair/typebox"; import { Type } from "@sinclair/typebox"; import { variableValueSchema } from "./fragments"; type SchemaKind = "authoring" | "normalized"; const LOG_LEVELS = ["trace", "debug", "info", "warn", "error", "fatal"] as const; const ROTATION_FREQUENCIES = ["hourly", "daily", "weekly"] as const; const sizeSchema = Type.Union([Type.String(), Type.Integer({ minimum: 0 })]); export function createAuthoringConfigSchema(): TSchema { return createConfigSchemaForKind("authoring"); } export function createExternalConfigSchema(): Record { return { ...cloneSchema(createAuthoringConfigSchema()), $id: "https://app.local/config.schema.json", $schema: "http://json-schema.org/draft-07/schema#", }; } export function createNormalizedConfigSchema(): TSchema { return createConfigSchemaForKind("normalized"); } function cloneSchema(schema: TSchema): Record { return JSON.parse(JSON.stringify(schema)) as Record; } function createAuthoringFieldSchema(schema: TSchema): TSchema { return Type.Unsafe({ anyOf: [schema, { pattern: "^\\$\\{[^}]+\\}$", type: "string" }] }); } function createConfigSchemaForKind(kind: SchemaKind): TSchema { const properties: Record = { server: Type.Optional(createServerSchema(kind)), }; if (kind === "authoring") { properties["variables"] = Type.Optional( Type.Record(Type.String({ pattern: "^[a-zA-Z_][a-zA-Z0-9_]*$" }), variableValueSchema), ); } return Type.Object(properties, { additionalProperties: false }); } function createLoggingSchema(kind: SchemaKind): TSchema { const logLevelSchema = Type.Union(LOG_LEVELS.map((l) => Type.Literal(l)) as unknown as [TSchema, ...TSchema[]]); const logLevel = kind === "authoring" ? createAuthoringFieldSchema(logLevelSchema) : logLevelSchema; const frequency = kind === "authoring" ? createAuthoringFieldSchema( Type.Union(ROTATION_FREQUENCIES.map((f) => Type.Literal(f)) as unknown as [TSchema, ...TSchema[]]), ) : Type.Union(ROTATION_FREQUENCIES.map((f) => Type.Literal(f)) as unknown as [TSchema, ...TSchema[]]); const rotationSize = kind === "authoring" ? createAuthoringFieldSchema(sizeSchema) : sizeSchema; const rotationMaxFiles = kind === "authoring" ? createAuthoringFieldSchema(Type.Integer({ minimum: 1 })) : Type.Integer({ minimum: 1 }); return Type.Object( { console: Type.Optional(Type.Object({ level: Type.Optional(logLevel) }, { additionalProperties: false })), file: Type.Optional( Type.Object( { level: Type.Optional(logLevel), path: Type.Optional(Type.String({ minLength: 1 })), rotation: Type.Optional( Type.Object( { frequency: Type.Optional(frequency), maxFiles: Type.Optional(rotationMaxFiles), size: Type.Optional(rotationSize), }, { additionalProperties: false }, ), ), }, { additionalProperties: false }, ), ), level: Type.Optional(logLevel), }, { additionalProperties: false }, ); } function createServerSchema(kind: SchemaKind): TSchema { return Type.Object( { listen: Type.Optional( Type.Object( { host: Type.Optional(Type.String()), port: Type.Optional(integerForKind(kind, { maximum: 65535, minimum: 0 })), }, { additionalProperties: false }, ), ), logging: Type.Optional(createLoggingSchema(kind)), storage: Type.Optional( Type.Object( { dataDir: Type.Optional(Type.String()), }, { additionalProperties: false }, ), ), }, { additionalProperties: false }, ); } function integerForKind(kind: SchemaKind, options?: Parameters[0]): TSchema { const schema = Type.Integer(options); return kind === "authoring" ? createAuthoringFieldSchema(schema) : schema; }