Candidate Summary
- Recommendation Strength: Strong
- Modules Involved:
src/persist.ts, src/manager.ts, main.ts
- Domain Context:
Queue Manager, Persist Engine, Queue, Payload
- Architecture Vocabulary:
module, interface, depth, seam, adapter, leverage, locality
Problem & Evidence
The persistence seam between Queue Manager, Persist Engine (QueueStore), and main.ts exhibits significant architectural friction across three areas:
-
Filesystem details leaking into the QueueStore interface:
QueueStore declares dir(dir: string): void.
MemoryStore (an in-memory adapter) is forced to implement a dummy method dir(): void {}.
In FileStore, this creates temporal coupling: FileStore is instantiated with an empty directory, and callers must remember to invoke .dir(CONFIG.persistDir) before doing storage operations.
-
Adapter bypass and boolean flag in QueueManager:
QueueManager constructor accepts both store: QueueStore<T> AND persistEnabled?: boolean:
constructor(store: QueueStore<T>, queueDepthLimit?: number, queueCountLimit?: number, persistEnabled?: boolean)
Inside QueueManager.enqueue and dequeue:
if (this.persistEnabled) {
this.store.saveEvent(name, payload, true);
}
When persistence is disabled, main.ts passes new Persistency.MemoryStore, yet QueueManager bypasses the store via if (this.persistEnabled). MemoryStore is rendered redundant because QueueManager ignores it when persistEnabled is false.
- Type-sniffing /
instanceof inspection in main.ts:
In main.ts:
if (PERSIST_ENGINE instanceof Persistency.FileStore) {
writeLog("Loading in data from persist.dat...\n");
MANAGER.load();
}
...
if (PERSIST_ENGINE instanceof Persistency.FileStore) {
writeLog("Flushing data to persist.dat...\n");
MANAGER.save();
}
main.ts violates polymorphism at the QueueStore seam by checking whether PERSIST_ENGINE is an instanceof FileStore before deciding whether to call MANAGER.load() and MANAGER.save().
Test Evidence (/deintrovert-tests)
In tests/persist_test.ts and tests/manager_test.ts, tests must manually orchestrate dir() calls and pass both new MemoryStore() and persistEnabled: false or true. Tests observe that passing a store does not mean persistence is active unless the separate boolean flag is also set.
The Deletion Test
- Delete
dir() from QueueStore: Directory configuration moves to FileStore's constructor (new FileStore(persistDir)). The QueueStore interface shrinks, temporal coupling disappears, and MemoryStore no longer needs a dummy method.
- Delete
persistEnabled boolean from QueueManager: QueueManager always writes to its store. If persistence is off, a MemoryStore or NoopStore is passed, satisfying the seam without conditional checks inside QueueManager.
- Delete
instanceof FileStore checks from main.ts: QueueManager coordinates its persistence lifecycle uniformly across all adapters. For in-memory or empty stores, loadState() simply returns [] without error.
Proposed Change
- Remove
dir() from the QueueStore interface. Pass directory: string directly to the FileStore constructor.
- Remove
persistEnabled from QueueManager constructor. QueueManager always calls this.store.saveEvent(). Provide a MemoryStore (or NoopStore) when persistence is disabled.
- Remove
instanceof FileStore checks in main.ts. QueueManager.load() runs unconditionally on startup; QueueManager.save() (or a unified flush()/close()) runs on graceful shutdown.
Benefits
- Leverage: Callers configure storage once at construction. The
QueueStore interface is strictly focused on event persistence (saveEvent, replace, loadState, close).
- Locality: Persistence lifecycle management concentrates inside
Queue Manager and Persist Engine, removing procedural orchestration from main.ts.
- Adapters: Both
FileStore and MemoryStore become true polymorphic adapters satisfying a clean seam.
Candidate Summary
src/persist.ts,src/manager.ts,main.tsQueue Manager,Persist Engine,Queue,Payloadmodule,interface,depth,seam,adapter,leverage,localityProblem & Evidence
The persistence seam between
Queue Manager,Persist Engine(QueueStore), andmain.tsexhibits significant architectural friction across three areas:Filesystem details leaking into the
QueueStoreinterface:QueueStoredeclaresdir(dir: string): void.MemoryStore(an in-memory adapter) is forced to implement a dummy methoddir(): void {}.In
FileStore, this creates temporal coupling:FileStoreis instantiated with an empty directory, and callers must remember to invoke.dir(CONFIG.persistDir)before doing storage operations.Adapter bypass and boolean flag in
QueueManager:QueueManagerconstructor accepts bothstore: QueueStore<T>ANDpersistEnabled?: boolean:Inside
QueueManager.enqueueanddequeue:When persistence is disabled,
main.tspassesnew Persistency.MemoryStore, yetQueueManagerbypasses the store viaif (this.persistEnabled).MemoryStoreis rendered redundant becauseQueueManagerignores it whenpersistEnabledis false.instanceofinspection inmain.ts:In
main.ts:main.tsviolates polymorphism at theQueueStoreseam by checking whetherPERSIST_ENGINEis aninstanceof FileStorebefore deciding whether to callMANAGER.load()andMANAGER.save().Test Evidence (
/deintrovert-tests)In
tests/persist_test.tsandtests/manager_test.ts, tests must manually orchestratedir()calls and pass bothnew MemoryStore()andpersistEnabled: falseortrue. Tests observe that passing a store does not mean persistence is active unless the separate boolean flag is also set.The Deletion Test
dir()fromQueueStore: Directory configuration moves toFileStore's constructor (new FileStore(persistDir)). TheQueueStoreinterface shrinks, temporal coupling disappears, andMemoryStoreno longer needs a dummy method.persistEnabledboolean fromQueueManager:QueueManageralways writes to itsstore. If persistence is off, aMemoryStoreorNoopStoreis passed, satisfying the seam without conditional checks insideQueueManager.instanceof FileStorechecks frommain.ts:QueueManagercoordinates its persistence lifecycle uniformly across all adapters. For in-memory or empty stores,loadState()simply returns[]without error.Proposed Change
dir()from theQueueStoreinterface. Passdirectory: stringdirectly to theFileStoreconstructor.persistEnabledfromQueueManagerconstructor.QueueManageralways callsthis.store.saveEvent(). Provide aMemoryStore(orNoopStore) when persistence is disabled.instanceof FileStorechecks inmain.ts.QueueManager.load()runs unconditionally on startup;QueueManager.save()(or a unifiedflush()/close()) runs on graceful shutdown.Benefits
QueueStoreinterface is strictly focused on event persistence (saveEvent,replace,loadState,close).Queue ManagerandPersist Engine, removing procedural orchestration frommain.ts.FileStoreandMemoryStorebecome true polymorphic adapters satisfying a clean seam.