verae-middleware/src/store/db.js

119 lines
2.9 KiB
JavaScript

/**
* @fileoverview JSON file-backed in-memory store (MVP persistence).
* @module store/db
*/
import { readFileSync, writeFileSync, mkdirSync, existsSync } from 'node:fs';
import { dirname } from 'node:path';
import { config } from '../config.js';
import { createDebugger } from '../debug/logger.js';
const log = createDebugger('app');
/**
* @typedef {Object} StoreShape
* @property {Record<string, object>} tenants
* @property {Record<string, string>} apiKeys - apiKey → tenantId
* @property {Record<string, object>} usage - tenantId → counters
* @property {object[]} webhooks
* @property {object[]} jobWatchers
*/
/** @type {StoreShape|null} */
let store = null;
/**
* Create an empty store document.
* @returns {StoreShape}
*/
export function emptyStore() {
return {
tenants: {},
apiKeys: {},
usage: {},
webhooks: [],
jobWatchers: [],
tokenRefs: {},
blobs: {},
shares: {},
trees: {},
};
}
/**
* Load store from disk into memory (or create empty if missing).
* @param {string} [path=config.storePath]
* @returns {StoreShape}
*/
export function loadStore(path = config.storePath) {
if (store) return store;
if (existsSync(path)) {
try {
const raw = readFileSync(path, 'utf8');
const parsed = JSON.parse(raw);
store = {
...emptyStore(),
...parsed,
tenants: parsed.tenants ?? {},
apiKeys: parsed.apiKeys ?? {},
usage: parsed.usage ?? {},
webhooks: Array.isArray(parsed.webhooks) ? parsed.webhooks : [],
jobWatchers: Array.isArray(parsed.jobWatchers) ? parsed.jobWatchers : [],
tokenRefs: parsed.tokenRefs ?? {},
};
log.debug('store loaded', { path, tenants: Object.keys(store.tenants).length });
} catch (err) {
log.error('store load failed, using empty', { path, error: err.message });
store = emptyStore();
}
} else {
store = emptyStore();
log.debug('store initialized empty', { path });
}
return store;
}
/**
* Access the in-memory store (loads if needed).
* @returns {StoreShape}
*/
export function getStore() {
if (!store) return loadStore();
return store;
}
/**
* Replace the in-memory store (tests only).
* @param {StoreShape|null} next
* @returns {void}
*/
export function setStoreForTests(next) {
store = next;
}
/**
* Persist the in-memory store to disk.
* @param {string} [path=config.storePath]
* @returns {void}
*/
export function persist(path = config.storePath) {
const data = getStore();
const dir = dirname(path);
if (!existsSync(dir)) {
mkdirSync(dir, { recursive: true });
}
writeFileSync(path, JSON.stringify(data, null, 2), 'utf8');
log.debug('store persisted', { path });
}
/**
* Force reload from disk (drops memory).
* @param {string} [path=config.storePath]
* @returns {StoreShape}
*/
export function reloadStore(path = config.storePath) {
store = null;
return loadStore(path);
}