Files
Neon-Desk/lib/server/store.ts

85 lines
2.1 KiB
TypeScript

import { mkdir, readFile, rename, writeFile } from 'node:fs/promises';
import path from 'node:path';
import type { Filing, Holding, PortfolioInsight, Task, WatchlistItem } from '@/lib/types';
export type DataStore = {
counters: {
watchlist: number;
holdings: number;
filings: number;
insights: number;
};
watchlist: WatchlistItem[];
holdings: Holding[];
filings: Filing[];
tasks: Task[];
insights: PortfolioInsight[];
};
const DATA_DIR = path.join(process.cwd(), 'data');
const STORE_PATH = path.join(DATA_DIR, 'store.json');
let writeQueue = Promise.resolve();
function createDefaultStore(): DataStore {
return {
counters: {
watchlist: 0,
holdings: 0,
filings: 0,
insights: 0
},
watchlist: [],
holdings: [],
filings: [],
tasks: [],
insights: []
};
}
async function ensureStoreFile() {
await mkdir(DATA_DIR, { recursive: true });
try {
await readFile(STORE_PATH, 'utf8');
} catch {
const defaultStore = createDefaultStore();
defaultStore.counters.insights = defaultStore.insights.length;
await writeFile(STORE_PATH, JSON.stringify(defaultStore, null, 2), 'utf8');
}
}
async function readStore(): Promise<DataStore> {
await ensureStoreFile();
const raw = await readFile(STORE_PATH, 'utf8');
return JSON.parse(raw) as DataStore;
}
async function writeStore(store: DataStore) {
const tempPath = `${STORE_PATH}.tmp`;
await writeFile(tempPath, JSON.stringify(store, null, 2), 'utf8');
await rename(tempPath, STORE_PATH);
}
function cloneStore(store: DataStore): DataStore {
return JSON.parse(JSON.stringify(store)) as DataStore;
}
export async function getStoreSnapshot() {
const store = await readStore();
return cloneStore(store);
}
export async function withStore<T>(mutator: (store: DataStore) => T | Promise<T>): Promise<T> {
const run = async () => {
const store = await readStore();
const result = await mutator(store);
await writeStore(store);
return result;
};
const nextRun = writeQueue.then(run, run);
writeQueue = nextRun.then(() => undefined, () => undefined);
return await nextRun;
}