From 552d5fcafb35c55a2c5bb2fc476ae034f1651144 Mon Sep 17 00:00:00 2001 From: root Date: Mon, 9 Mar 2026 14:25:16 +0000 Subject: [PATCH] feat(persistence): add persistence hooks to event services Co-Authored-By: Claude Opus 4.6 --- server/src/industry/inventionTimeline.ts | 13 +++++++++++++ server/src/industry/stockpileLog.ts | 13 +++++++++++++ server/src/llm/eventMemoryService.ts | 17 +++++++++++++++++ server/src/llm/narrationService.ts | 16 ++++++++++++++++ 4 files changed, 59 insertions(+) diff --git a/server/src/industry/inventionTimeline.ts b/server/src/industry/inventionTimeline.ts index 20f1692..01b22eb 100644 --- a/server/src/industry/inventionTimeline.ts +++ b/server/src/industry/inventionTimeline.ts @@ -18,14 +18,18 @@ export interface InventionTimeline { getAll(): InventionEntry[]; getSummaries(): InventionSummary[]; countByEntity(entityId: EntityId): number; + setPersistence(saveFn: (entry: InventionEntry) => void): void; + loadFromPersistence(loadedEntries: InventionEntry[]): void; } export function createInventionTimeline(): InventionTimeline { const entries: InventionEntry[] = []; + let persistFn: ((entry: InventionEntry) => void) | null = null; return { record(entry: InventionEntry): void { entries.push(entry); + if (persistFn) persistFn(entry); }, getAll(): InventionEntry[] { @@ -48,5 +52,14 @@ export function createInventionTimeline(): InventionTimeline { countByEntity(entityId: EntityId): number { return entries.filter(e => e.inventorEntityId === entityId).length; }, + + setPersistence(saveFn: (entry: InventionEntry) => void): void { + persistFn = saveFn; + }, + + loadFromPersistence(loadedEntries: InventionEntry[]): void { + entries.length = 0; + entries.push(...loadedEntries); + }, }; } diff --git a/server/src/industry/stockpileLog.ts b/server/src/industry/stockpileLog.ts index 0427731..f2d2fdf 100644 --- a/server/src/industry/stockpileLog.ts +++ b/server/src/industry/stockpileLog.ts @@ -3,12 +3,15 @@ import type { StockpileLogEntry } from '@dflike/shared'; export interface StockpileLog { record(entry: StockpileLogEntry): void; getRecent(): StockpileLogEntry[]; + setPersistence(saveFn: (entry: StockpileLogEntry) => void): void; + loadFromPersistence(entries: StockpileLogEntry[]): void; } const MAX_ENTRIES = 50; export function createStockpileLog(): StockpileLog { const entries: StockpileLogEntry[] = []; + let persistFn: ((entry: StockpileLogEntry) => void) | null = null; return { record(entry: StockpileLogEntry): void { @@ -16,10 +19,20 @@ export function createStockpileLog(): StockpileLog { if (entries.length > MAX_ENTRIES) { entries.shift(); } + if (persistFn) persistFn(entry); }, getRecent(): StockpileLogEntry[] { return [...entries]; }, + + setPersistence(saveFn: (entry: StockpileLogEntry) => void): void { + persistFn = saveFn; + }, + + loadFromPersistence(loadedEntries: StockpileLogEntry[]): void { + entries.length = 0; + entries.push(...loadedEntries); + }, }; } diff --git a/server/src/llm/eventMemoryService.ts b/server/src/llm/eventMemoryService.ts index 076be50..9c69dc0 100644 --- a/server/src/llm/eventMemoryService.ts +++ b/server/src/llm/eventMemoryService.ts @@ -10,11 +10,14 @@ export interface EventMemoryService { preferEntityId?: EntityId; }): MemoryEvent[]; onEventRecorded: ((entityId: EntityId, event: MemoryEvent) => void) | null; + setPersistence(saveFn: (entityId: EntityId, event: MemoryEvent) => void): void; + loadFromPersistence(allEvents: Map): void; } export function createEventMemoryService(maxEvents = 50): EventMemoryService { const buffers = new Map(); let nextId = 1; + let persistFn: ((entityId: EntityId, event: MemoryEvent) => void) | null = null; function getBuffer(entityId: EntityId): MemoryEvent[] { let buf = buffers.get(entityId); @@ -43,6 +46,7 @@ export function createEventMemoryService(maxEvents = 50): EventMemoryService { buf.shift(); } + if (persistFn) persistFn(entityId, event); service.onEventRecorded?.(entityId, event); return event; @@ -102,6 +106,19 @@ export function createEventMemoryService(maxEvents = 50): EventMemoryService { return scored.slice(0, opts.maxCount).map((s) => s.event); }, + + setPersistence(saveFn: (entityId: EntityId, event: MemoryEvent) => void): void { + persistFn = saveFn; + }, + + loadFromPersistence(allEvents: Map): void { + for (const [entityId, events] of allEvents) { + buffers.set(entityId, [...events]); + for (const e of events) { + if (e.id >= nextId) nextId = e.id + 1; + } + } + }, }; return service; diff --git a/server/src/llm/narrationService.ts b/server/src/llm/narrationService.ts index d1ccee4..424a60a 100644 --- a/server/src/llm/narrationService.ts +++ b/server/src/llm/narrationService.ts @@ -22,6 +22,8 @@ export interface NarrationService { getEventsForEntity(entityId: EntityId): NarrationEvent[]; onEventUpdated: ((event: NarrationEvent) => void) | null; onEventCreated: ((event: NarrationEvent) => void) | null; + setPersistence(saveFn: (event: NarrationEvent) => void): void; + loadFromPersistence(events: NarrationEvent[]): void; } const MAX_BUFFER_SIZE = 50; @@ -29,6 +31,7 @@ const MAX_BUFFER_SIZE = 50; export function createNarrationService(llmService: LlmService): NarrationService { const buffer: NarrationEvent[] = []; let nextId = 1; + let persistFn: ((event: NarrationEvent) => void) | null = null; const service: NarrationService = { onEventUpdated: null, @@ -56,6 +59,7 @@ export function createNarrationService(llmService: LlmService): NarrationService buffer.shift(); } + if (persistFn) persistFn(event); service.onEventCreated?.(event); const shouldQueueLlm = @@ -96,6 +100,18 @@ export function createNarrationService(llmService: LlmService): NarrationService (e) => e.entityIds[0] === entityId || e.entityIds[1] === entityId, ); }, + + setPersistence(saveFn: (event: NarrationEvent) => void): void { + persistFn = saveFn; + }, + + loadFromPersistence(events: NarrationEvent[]): void { + buffer.length = 0; + buffer.push(...events); + if (events.length > 0) { + nextId = Math.max(...events.map(e => e.id)) + 1; + } + }, }; return service;