flatten app to repo root and update docker deployment for single-stack runtime
This commit is contained in:
102
lib/server/store.ts
Normal file
102
lib/server/store.ts
Normal file
@@ -0,0 +1,102 @@
|
||||
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 nowIso() {
|
||||
return new Date().toISOString();
|
||||
}
|
||||
|
||||
function createDefaultStore(): DataStore {
|
||||
const now = nowIso();
|
||||
|
||||
return {
|
||||
counters: {
|
||||
watchlist: 0,
|
||||
holdings: 0,
|
||||
filings: 0,
|
||||
insights: 0
|
||||
},
|
||||
watchlist: [],
|
||||
holdings: [],
|
||||
filings: [],
|
||||
tasks: [],
|
||||
insights: [
|
||||
{
|
||||
id: 1,
|
||||
user_id: 1,
|
||||
provider: 'local-bootstrap',
|
||||
model: 'zeroclaw',
|
||||
content: [
|
||||
'System initialized in local-first mode.',
|
||||
'Add holdings and sync filings to produce a live AI brief via OpenClaw.'
|
||||
].join('\n'),
|
||||
created_at: now
|
||||
}
|
||||
]
|
||||
};
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
Reference in New Issue
Block a user