refactor: workspace restructure — contract package + pure domain ported, tests green

- bun workspaces: contract/ (Schemas = source of truth: config raw↔resolved,
  domain DTOs, HttpApi contract, API errors) + plugin/ + ui/
- pure domain moved src/{engine,art}→plugin/src/{domain,art}, types now
  imported from @rom-manager/contract and @punktfunk/plugin-kit/wire (the
  hand-copied wire.ts dies), loggers injected instead of module-global
- ui.* and devEntry dropped from the config shape (standalone server is
  gone; stale keys tolerated on decode, dropped by the next save)
- all 48 domain tests ported and green BEFORE any Effect wiring

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
2026-07-20 18:54:38 +02:00
parent e78df91925
commit d6cf5f3d36
30 changed files with 1938 additions and 181 deletions
+108
View File
@@ -0,0 +1,108 @@
// Domain DTOs shared by the plugin server and the UI — Schemas are the source of truth,
// the derived types keep the original domain names so the pure core reads unchanged.
import { Schema } from "effect";
export const Os = Schema.Literals(["linux", "windows", "mac"]);
export type Os = typeof Os.Type;
/** Default emulator + core for a platform (operator-overridable per platform / per game). */
export const DefaultLaunch = Schema.Struct({
emulator: Schema.String,
core: Schema.optionalKey(Schema.String),
extraArgs: Schema.optionalKey(Schema.String),
});
export type DefaultLaunch = typeof DefaultLaunch.Type;
/** A console platform: extension set, art key, default launch (design §5). */
export const Platform = Schema.Struct({
id: Schema.String,
name: Schema.String,
extensions: Schema.Array(Schema.String),
libretroSystem: Schema.optionalKey(Schema.String),
defaultLaunch: DefaultLaunch,
disc: Schema.optionalKey(Schema.Boolean),
});
export type Platform = typeof Platform.Type;
/** An emulator definition: per-OS detection, launch template, archive support. */
export const EmulatorDef = Schema.Struct({
id: Schema.String,
name: Schema.String,
detect: Schema.Struct({
linux: Schema.Array(Schema.String),
windows: Schema.Array(Schema.String),
}),
coresDir: Schema.optionalKey(
Schema.Struct({
linux: Schema.Array(Schema.String),
windows: Schema.Array(Schema.String),
}),
),
template: Schema.String,
supportsArchives: Schema.Boolean,
contested: Schema.optionalKey(Schema.Boolean),
});
export type EmulatorDef = typeof EmulatorDef.Type;
/** A detected emulator install (UI's Emulators page + launch resolution). */
export const DetectedEmulator = Schema.Struct({
id: Schema.String,
name: Schema.String,
template: Schema.String,
supportsArchives: Schema.Boolean,
contested: Schema.optionalKey(Schema.Boolean),
exeToken: Schema.String,
exePath: Schema.optionalKey(Schema.String),
appId: Schema.optionalKey(Schema.String),
via: Schema.Literals(["path", "file", "flatpak"]),
coresDir: Schema.optionalKey(Schema.String),
cores: Schema.optionalKey(Schema.Array(Schema.String)),
});
export type DetectedEmulator = typeof DetectedEmulator.Type;
/** A candidate the compute rejected, with a human reason (Library page, CLI). */
export const Skipped = Schema.Struct({
external_id: Schema.String,
title: Schema.String,
reason: Schema.String,
});
export type Skipped = typeof Skipped.Type;
/** A summary of one desired-state compute (Overview + Library pages). */
export const SyncReport = Schema.Struct({
considered: Schema.Number,
included: Schema.Number,
skipped: Schema.Array(Skipped),
excluded: Schema.Array(
Schema.Struct({ external_id: Schema.String, title: Schema.String }),
),
warnings: Schema.Array(Schema.String),
truncated: Schema.Number,
perPlatform: Schema.Record(Schema.String, Schema.Number),
overWarn: Schema.Boolean,
});
export type SyncReport = typeof SyncReport.Type;
export const LastSync = Schema.Struct({
fingerprint: Schema.String,
count: Schema.Number,
at: Schema.Number,
});
export type LastSync = typeof LastSync.Type;
/** The engine status the Overview page renders and the SSE feed streams. */
export const EngineStatus = Schema.Struct({
rootsConfigured: Schema.Number,
os: Os,
artProvider: Schema.NullOr(Schema.String),
syncing: Schema.Boolean,
lastSync: Schema.optionalKey(LastSync),
lastReport: Schema.optionalKey(SyncReport),
detectedAt: Schema.optionalKey(Schema.Number),
paths: Schema.Struct({
dir: Schema.String,
config: Schema.String,
cache: Schema.String,
}),
});
export type EngineStatus = typeof EngineStatus.Type;