Files
ttrpg-character-builder/app/main/store.ts
T
Ivan Fontosh 45105cd069 Switch to JSON store so Electron runs without native sqlite rebuild.
Auto-seeds 10 presets on first launch; visual designer with palette, drag, resize, and bricks CRUD.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-09-12 14:52:31 +08:00

107 lines
3.0 KiB
TypeScript

import fs from 'node:fs';
import path from 'node:path';
import type { CharacterSheet, SystemDefinition } from '../shared/types.ts';
import { validateSystemDefinition } from '../shared/systemValidate.ts';
type Meta = {
systems: Record<string, SystemDefinition>;
characters: Record<string, CharacterSheet>;
};
export class AppStore {
private readonly rootDir: string;
private readonly dbPath: string;
private data: Meta = { systems: {}, characters: {} };
constructor(dbPath: string) {
// dbPath historically pointed at *.sqlite — keep parent dir, use store.json
this.rootDir = path.dirname(dbPath);
this.dbPath = path.join(this.rootDir, 'store.json');
fs.mkdirSync(this.rootDir, { recursive: true });
this.load();
}
private load(): void {
try {
const raw = fs.readFileSync(this.dbPath, 'utf8');
const parsed = JSON.parse(raw) as Meta;
this.data = {
systems: parsed.systems ?? {},
characters: parsed.characters ?? {},
};
} catch {
this.data = { systems: {}, characters: {} };
this.persist();
}
}
private persist(): void {
fs.writeFileSync(this.dbPath, JSON.stringify(this.data, null, 2), 'utf8');
}
listSystems(): Array<{ id: string; name: string; revision: number; schemaVersion: number }> {
return Object.values(this.data.systems)
.map((s) => ({
id: s.id,
name: s.name,
revision: s.revision,
schemaVersion: s.schemaVersion,
}))
.sort((a, b) => a.name.localeCompare(b.name));
}
getSystem(id: string): SystemDefinition | null {
return this.data.systems[id] ?? null;
}
upsertSystem(system: SystemDefinition): SystemDefinition {
const check = validateSystemDefinition(system);
if (!check.ok) {
throw new Error(check.errors.map((e) => e.message).join('; '));
}
const existing = this.data.systems[system.id];
const next: SystemDefinition = {
...system,
revision: existing ? existing.revision + 1 : system.revision || 1,
};
this.data.systems[next.id] = next;
this.persist();
return next;
}
deleteSystem(id: string): void {
delete this.data.systems[id];
for (const [cid, ch] of Object.entries(this.data.characters)) {
if (ch.systemId === id) delete this.data.characters[cid];
}
this.persist();
}
listCharacters(systemId?: string): Array<{ id: string; name: string; systemId: string }> {
return Object.values(this.data.characters)
.filter((c) => (systemId ? c.systemId === systemId : true))
.map((c) => ({ id: c.id, name: c.name, systemId: c.systemId }))
.sort((a, b) => a.name.localeCompare(b.name));
}
getCharacter(id: string): CharacterSheet | null {
return this.data.characters[id] ?? null;
}
upsertCharacter(character: CharacterSheet): CharacterSheet {
this.data.characters[character.id] = character;
this.persist();
return character;
}
deleteCharacter(id: string): void {
delete this.data.characters[id];
this.persist();
}
close(): void {
this.persist();
}
}