Files
dashback/src/db/store.js
2026-10-20 05:33:14 +02:00

186 lines
4.7 KiB
JavaScript

import LibDashStore from 'libdashstore';
import { MssqlProvider } from 'libdashstore/providers/mssql';
import { BetterSqlite3Provider } from 'libdashstore/providers/better-sqlite3';
import env from '../config/env.js';
import { createLogger, chalk } from '../util/logger.js';
const storeLog = createLogger('libstore');
let provider;
let store;
let connecting;
const STORE_NAMESPACES = [
'companyuser',
'company',
'category',
'article',
'pic',
'unit',
'brand',
'lang',
'tax',
'google_auth',
'posterminal',
'supplier',
'delivery',
'customergroup',
'entity',
'entityaddress',
'entitycontact',
'quote',
'order',
'shipment',
'numbers',
'companysetting',
'articlesupplier',
'manifestattachment',
'ledger',
'warehouse',
'printer',
'labeltemplate',
'stock',
'propertyname',
'articleproperty',
'categoryproperty',
'attributename',
'attributevalue',
'articleattributevalue',
'categoryattributevalue',
'modelconversation',
'modelmessage',
'account',
'payment',
'shipping',
'package',
'articlelist',
'optionslist',
'mcp',
'healthcheck',
];
/**
* Wraps LibDashStore namespace methods so each call logs its duration.
* @param {InstanceType<typeof LibDashStore>} rawStore
* @returns {InstanceType<typeof LibDashStore>}
*/
function wrapStoreWithTiming(rawStore) {
for (const namespace of STORE_NAMESPACES) {
const api = rawStore[namespace];
if (!api || typeof api !== 'object') continue;
for (const [method, fn] of Object.entries(api)) {
if (typeof fn !== 'function') continue;
api[method] = async (...args) => {
const start = Date.now();
try {
const result = await fn(...args);
storeLog.debug(`${namespace}.${method} ${chalk.gray(`(${Date.now() - start}ms)`)}`);
return result;
} catch (err) {
const isNotFound = /not found/i.test(err?.message);
if (isNotFound) {
storeLog.debug(
`${namespace}.${method} ${chalk.gray(`(${Date.now() - start}ms)`)} ${chalk.gray(err.message)}`
);
} else {
storeLog.error(
`${namespace}.${method} ${chalk.red('FAILED')} ` +
`${chalk.gray(`(${Date.now() - start}ms)`)} ${chalk.red(err.message)}`
);
}
throw err;
}
};
}
}
for (const method of ['purge']) {
const fn = rawStore[method];
if (typeof fn !== 'function') continue;
rawStore[method] = async (...args) => {
const start = Date.now();
try {
return await fn(...args);
} finally {
const duration = Date.now() - start;
storeLog.info(`${method} ${chalk.gray(`(${duration}ms)`)}`);
}
};
}
return rawStore;
}
function getMssqlConfig() {
const { server, database, user, password, port, encrypt, trustServerCertificate } = env.mssql;
if (!server || !database || !user || !password) {
throw new Error('MSSQL is not configured (MSSQL_SERVER, MSSQL_DATABASE, MSSQL_USER, MSSQL_PASSWORD required)');
}
return {
server,
database,
user,
password,
port,
pool: {
max: 10,
min: 0,
idleTimeoutMillis: 30_000,
},
options: {
encrypt,
trustServerCertificate,
},
};
}
/**
* Returns the initialized LibDashStore singleton.
* @returns {Promise<InstanceType<typeof LibDashStore>>}
*/
export async function getStore() {
if (store) {
return store;
}
if (!connecting) {
connecting = (async () => {
if (env.dbProvider === 'sqlite' || env.dbProvider === 'better-sqlite3') {
provider = new BetterSqlite3Provider(env.sqlite);
} else {
provider = new MssqlProvider(getMssqlConfig());
}
await provider.initialize();
store = wrapStoreWithTiming(await new LibDashStore(provider));
return store;
})().finally(() => {
connecting = undefined;
});
}
return connecting;
}
export async function getProvider() {
await getStore();
return provider;
}
export async function ensureConnection() {
await getStore();
}
export async function closeStore() {
if (provider) {
await provider.close();
provider = undefined;
store = undefined;
}
}