// The config model. ONE schema: the Encoded side IS the authored file shape (RawConfig), // the Type side is the fully-resolved shape the engine consumes (Config). Defaults live // ONLY here (`withDecodingDefaultKey` + encodingStrategy "omit") — the kit's ConfigService // persists the raw shape verbatim, so a UI save never bakes defaults into the file. // // Dropped vs 0.2.x: `ui.*` (the standalone password server is gone — console surface + // CLI only) and `devEntry` (dev flag out of the prod shape; use `preview` instead). // Stale keys in existing files are tolerated on decode and dropped by the next save. import { Effect, Schema } from "effect"; import { EmulatorDef, Platform } from "./domain.js"; const withDefault = ( schema: S, value: S["Encoded"], ) => schema.pipe( Schema.withDecodingDefaultKey(Effect.succeed(value), { encodingStrategy: "omit", }), ); /** A ROM root: a directory paired with the platform its files belong to (design §5). */ export const RomRoot = Schema.Struct({ dir: Schema.String, platform: Schema.String, excludes: Schema.optionalKey(Schema.Array(Schema.String)), }); export type RomRoot = typeof RomRoot.Type; /** A per-platform launch override — replaces the platform's built-in default. */ export const PlatformLaunch = Schema.Struct({ emulator: Schema.String, core: Schema.optionalKey(Schema.String), extraArgs: Schema.optionalKey(Schema.String), }); export type PlatformLaunch = typeof PlatformLaunch.Type; /** A per-game override, keyed by `external_id` (`snes/Chrono Trigger (USA).sfc`). */ export const GameOverride = Schema.Struct({ exclude: Schema.optionalKey(Schema.Boolean), emulator: Schema.optionalKey(Schema.String), core: Schema.optionalKey(Schema.String), extraArgs: Schema.optionalKey(Schema.String), title: Schema.optionalKey(Schema.String), art: Schema.optionalKey(Schema.String), }); export type GameOverride = typeof GameOverride.Type; export const ArtProviderChoice = Schema.Literals([ "auto", "steamgriddb", "libretro", ]); export type ArtProviderChoice = typeof ArtProviderChoice.Type; export const SyncOptions = Schema.Struct({ /** Interval poll in minutes (watchers are unreliable on SMB/NFS — poll is primary). */ pollMinutes: withDefault(Schema.Number, 15), watch: withDefault(Schema.Boolean, true), debounceMs: withDefault(Schema.Number, 2000), /** Platform ids to region-dedupe ("one entry per title", design §5). */ dedupeRegions: withDefault(Schema.Array(Schema.String), []), regionPriority: withDefault(Schema.Array(Schema.String), [ "USA", "World", "Europe", "Japan", ]), warnEntries: withDefault(Schema.Number, 2000), maxEntries: withDefault(Schema.Number, 5000), closeOnEnd: withDefault(Schema.Boolean, false), }); export type SyncOptions = typeof SyncOptions.Type; export const ArtOptions = Schema.Struct({ enabled: withDefault(Schema.Boolean, true), provider: withDefault(ArtProviderChoice, "auto"), steamGridDbKey: Schema.optionalKey(Schema.String), }); export type ArtOptions = typeof ArtOptions.Type; export const RomConfigSchema = Schema.Struct({ roots: withDefault(Schema.Array(RomRoot), []), platformLaunch: withDefault(Schema.Record(Schema.String, PlatformLaunch), {}), gameOverrides: withDefault(Schema.Record(Schema.String, GameOverride), {}), /** Operator-added / overriding platform defs (merged over built-ins by id). */ platforms: Schema.optionalKey(Schema.Array(Platform)), /** Operator-added / overriding emulator defs (merged over built-ins by id). */ emulators: Schema.optionalKey(Schema.Array(EmulatorDef)), sync: withDefault(SyncOptions, {}), art: withDefault(ArtOptions, {}), }); /** The fully-resolved config the engine consumes. */ export type Config = typeof RomConfigSchema.Type; /** The on-disk config as authored (all optional). */ export type RawConfig = typeof RomConfigSchema.Encoded; /** * Resolve an authored raw config to the total shape (defaults filled). Throws on an * invalid shape — the sync path for tests and the UI; the server side uses the kit's * ConfigService (Effect + typed errors) instead. */ export const resolveConfig = (raw: unknown): Config => Schema.decodeUnknownSync(RomConfigSchema)(raw);