u
This commit is contained in:
@@ -269,6 +269,7 @@ import mcpGetRefreshToken from './store/mcp/getRefreshToken.js';
|
||||
import mcpRevokeRefreshToken from './store/mcp/revokeRefreshToken.js';
|
||||
import mcpListAuthorizations from './store/mcp/listAuthorizations.js';
|
||||
import mcpRevokeAuthorizations from './store/mcp/revokeAuthorizations.js';
|
||||
import healthcheckCheck from './store/healthcheck/check.js';
|
||||
|
||||
export default class LibDashStore {
|
||||
/**
|
||||
@@ -706,6 +707,10 @@ export default class LibDashStore {
|
||||
revokeAuthorizations: mcpRevokeAuthorizations(provider),
|
||||
};
|
||||
|
||||
this.healthcheck = {
|
||||
check: healthcheckCheck(provider),
|
||||
};
|
||||
|
||||
this.purge = storePurge(provider);
|
||||
this.initialize = storeInitialize(provider);
|
||||
this.transaction = (fn) => provider.transaction(fn);
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/** Drop order = reverse of schema create dependency order. */
|
||||
export const TABLES = [
|
||||
|
||||
'healthcheck',
|
||||
'articlewarehousesummary',
|
||||
'deliverysummary',
|
||||
'shipmentsummary',
|
||||
|
||||
19
src/providers/mssql/queries/healthcheck.js
Normal file
19
src/providers/mssql/queries/healthcheck.js
Normal file
@@ -0,0 +1,19 @@
|
||||
export const ENSURE_ROW = `
|
||||
IF NOT EXISTS (SELECT 1 FROM healthcheck)
|
||||
BEGIN
|
||||
INSERT INTO healthcheck ([sequence]) VALUES (0);
|
||||
END
|
||||
`;
|
||||
|
||||
export const SELECT_FOR_UPDATE = `
|
||||
SELECT TOP 1 id, [sequence]
|
||||
FROM healthcheck WITH (UPDLOCK, ROWLOCK)
|
||||
ORDER BY id ASC;
|
||||
`;
|
||||
|
||||
export const UPDATE_SEQUENCE = `
|
||||
UPDATE healthcheck
|
||||
SET [sequence] = @sequence,
|
||||
updated_at = SYSUTCDATETIME()
|
||||
WHERE id = @id;
|
||||
`;
|
||||
@@ -1437,8 +1437,20 @@ BEGIN
|
||||
END
|
||||
`;
|
||||
|
||||
export const INIT_HEALTHCHECK = `
|
||||
IF NOT EXISTS (SELECT * FROM sys.tables WHERE name = 'healthcheck')
|
||||
BEGIN
|
||||
CREATE TABLE healthcheck (
|
||||
id INT IDENTITY(1,1) PRIMARY KEY,
|
||||
[sequence] BIGINT NOT NULL CONSTRAINT DF_healthcheck_sequence DEFAULT 0,
|
||||
updated_at DATETIME2 NOT NULL CONSTRAINT DF_healthcheck_updated_at DEFAULT SYSUTCDATETIME()
|
||||
);
|
||||
END
|
||||
`;
|
||||
|
||||
/** DDL scripts in dependency order for schema init. */
|
||||
export const SCHEMA_SCRIPTS = [
|
||||
INIT_HEALTHCHECK,
|
||||
INIT_META,
|
||||
INIT_LANGUAGE,
|
||||
INIT_TAX,
|
||||
|
||||
24
src/providers/sqlite3/queries/healthcheck.js
Normal file
24
src/providers/sqlite3/queries/healthcheck.js
Normal file
@@ -0,0 +1,24 @@
|
||||
import * as source from '../../mssql/queries/healthcheck.js';
|
||||
import { translateQueries } from './translate.js';
|
||||
|
||||
const queries = translateQueries(source, 'healthcheck');
|
||||
|
||||
export const ENSURE_ROW = `
|
||||
INSERT INTO healthcheck (id, "sequence")
|
||||
SELECT 1, 0
|
||||
WHERE NOT EXISTS (SELECT 1 FROM healthcheck);
|
||||
`;
|
||||
|
||||
export const SELECT_FOR_UPDATE = `
|
||||
SELECT id, "sequence"
|
||||
FROM healthcheck
|
||||
ORDER BY id ASC
|
||||
LIMIT 1;
|
||||
`;
|
||||
|
||||
export const UPDATE_SEQUENCE = `
|
||||
UPDATE healthcheck
|
||||
SET "sequence" = @sequence,
|
||||
updated_at = strftime('%Y-%m-%dT%H:%M:%fZ', 'now')
|
||||
WHERE id = @id;
|
||||
`;
|
||||
@@ -1058,8 +1058,17 @@ CREATE TABLE IF NOT EXISTS subagentschedule (
|
||||
CREATE INDEX IF NOT EXISTS IX_subagentschedule_company ON subagentschedule (company_id);
|
||||
`;
|
||||
|
||||
export const INIT_HEALTHCHECK = `
|
||||
CREATE TABLE IF NOT EXISTS healthcheck (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
"sequence" INTEGER NOT NULL DEFAULT 0,
|
||||
updated_at TEXT NOT NULL DEFAULT (${UTC_NOW})
|
||||
);
|
||||
`;
|
||||
|
||||
/** DDL scripts in dependency order for SQLite schema initialization. */
|
||||
export const SCHEMA_SCRIPTS = [
|
||||
INIT_HEALTHCHECK,
|
||||
INIT_META,
|
||||
INIT_LANGUAGE,
|
||||
INIT_TAX,
|
||||
|
||||
57
src/store/healthcheck/check.js
Normal file
57
src/store/healthcheck/check.js
Normal file
@@ -0,0 +1,57 @@
|
||||
import * as mssqlQueries from '../../providers/mssql/queries/healthcheck.js';
|
||||
import * as sqliteQueries from '../../providers/sqlite3/queries/healthcheck.js';
|
||||
import makeRunner from '../runner.js';
|
||||
|
||||
export default function check(provider) {
|
||||
const isSqlite = provider?.dialect === 'sqlite3';
|
||||
const queries = isSqlite ? sqliteQueries : mssqlQueries;
|
||||
|
||||
/**
|
||||
* Executes a database health check:
|
||||
* 1. Ensures the healthcheck table has one row (creates with sequence 0 if not).
|
||||
* 2. Selects that row for update with minimal, reasonable locking within a transaction.
|
||||
* 3. Updates the sequence by 1 in a separate statement within the transaction.
|
||||
*
|
||||
* @param {object} [tx] - Optional transaction to run within.
|
||||
* @returns {Promise<{ok: boolean, sequence: number}>} The health check result.
|
||||
*
|
||||
* @example
|
||||
* const result = await store.healthcheck.check();
|
||||
* // => { ok: true, sequence: 1 }
|
||||
*/
|
||||
return async (tx) => {
|
||||
const runTx = async (runner) => {
|
||||
// 1. Ensure table has one row, if not create that row with number 0
|
||||
await runner.query(queries.ENSURE_ROW);
|
||||
|
||||
// 2. Select for update on that row with minimal, reasonable locking
|
||||
const selected = await runner.query(queries.SELECT_FOR_UPDATE);
|
||||
const row = selected.rows[0];
|
||||
if (!row) {
|
||||
throw new Error('Healthcheck row not found');
|
||||
}
|
||||
|
||||
// 3. Updating the number by 1 and do an update (not in one statement, but in a tx)
|
||||
const currentSeq = Number(row.sequence ?? 0);
|
||||
const nextSeq = currentSeq + 1;
|
||||
|
||||
await runner.query(queries.UPDATE_SEQUENCE, {
|
||||
id: row.id,
|
||||
sequence: nextSeq,
|
||||
});
|
||||
|
||||
return {
|
||||
ok: true,
|
||||
sequence: nextSeq,
|
||||
};
|
||||
};
|
||||
|
||||
if (tx) {
|
||||
return runTx(makeRunner(provider, tx));
|
||||
}
|
||||
if (typeof provider?.transaction === 'function') {
|
||||
return provider.transaction((runner) => runTx(makeRunner(provider, runner)));
|
||||
}
|
||||
return runTx(makeRunner(provider));
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user