Add src/services/dataEvents.ts (ADR 0022): notifyDataChanged() and subscribeDataChanged(), without React, on the model of logService.subscribe. A throwing subscriber is logged and does not stop the others. Notify after every successful write of the tables the getting-started counters read, plus the import folder: setImportFolder, createSource, deleteSource, createImportedFile, updateFileStatus, both import deletions, insertBatch, updateTransactionCategory, applyKeywordWithReassignment (after COMMIT), the four budget writes and runRestore (after COMMIT, one point for the three restore modes). executeImport notifies once more when it ends, succeeded or not. A failed write notifies nothing; transactional writes notify once withTransaction has returned. applyTemplate notifies once for the whole template through a private writeBudgetEntry helper. Each service suite proves the notification after a success and its absence after a failure; executeImport is driven as a plain function with React's hooks stubbed. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
45 lines
1.5 KiB
TypeScript
45 lines
1.5 KiB
TypeScript
import { logError } from "./logService";
|
|
|
|
/**
|
|
* Data-change channel (#396, ADR 0022).
|
|
*
|
|
* The writes that may move a counter read elsewhere (the getting-started path
|
|
* reads `COUNTERS_SQL` and the import folder) call `notifyDataChanged()` once
|
|
* they have succeeded. The message only says "data that counters read may have
|
|
* changed": it carries no payload, and the data layer does not know who
|
|
* listens. Subscribers re-read on their own terms (the onboarding provider
|
|
* waits 150 ms to group a burst, then ignores any stale answer).
|
|
*
|
|
* Modeled on `logService.subscribe`, without React.
|
|
*/
|
|
|
|
type DataChangedListener = () => void;
|
|
|
|
const listeners = new Set<DataChangedListener>();
|
|
|
|
/**
|
|
* Tells every subscriber that data read by counters may have changed. Never
|
|
* throws: a subscriber that fails is logged and the others still run, so a
|
|
* write that succeeded is never reported as failed because of a listener.
|
|
*/
|
|
export function notifyDataChanged(): void {
|
|
// Iterate over a copy: a listener may unsubscribe while being called.
|
|
for (const listener of [...listeners]) {
|
|
try {
|
|
listener();
|
|
} catch (e) {
|
|
logError(
|
|
"Data events: a subscriber failed",
|
|
e instanceof Error ? e.message : String(e),
|
|
);
|
|
}
|
|
}
|
|
}
|
|
|
|
/** Subscribes to data changes. Returns the function that unsubscribes. */
|
|
export function subscribeDataChanged(listener: DataChangedListener): () => void {
|
|
listeners.add(listener);
|
|
return () => {
|
|
listeners.delete(listener);
|
|
};
|
|
}
|