godcrm/backend/services/space/variables.js
GOD CRM Release f89e074dd1
Some checks failed
CI / Lint / Typecheck / Test / Build (push) Has been cancelled
CI / PostgreSQL Integration Tests (push) Has been cancelled
GOD CRM — public scrubbed snapshot
Governed substrate for autonomous agents: scoped identity (passports),
audited actions, MCP workspace. Infra IPs and secrets redacted for public release.
2026-08-10 04:01:45 +03:00

130 lines
3.4 KiB
JavaScript

/**
* space/variables.js — Space Variables management (ADR-026)
*
* Extracted from SpaceService.js.
* Handles getSpaceVariables, recalculateSpaceVariables.
*/
import { dbRun, dbGet, dbAll, safeJsonParse } from '../../database/connection.js';
/**
* Get Variables table ID for a space (ADR-026)
* @param {number} spaceId - Space ID
* @returns {Promise<number|null>} Variables table ID or null
*/
async function getVariablesTableId(spaceId) {
// Find System Data project
const systemProject = await dbGet(
"SELECT id FROM projects WHERE space_id = ? AND name = 'System Data'",
[spaceId]
);
if (!systemProject) {
return null;
}
// Find Variables table in System Data project
const variablesTable = await dbGet(
"SELECT id FROM universal_tables WHERE project_id = ? AND name = 'Variables'",
[systemProject.id]
);
return variablesTable?.id || null;
}
/**
* Get all variables for a space (ADR-026)
* Shortcut API that returns variables from Universal Table
* @param {number} spaceId - Space ID
* @returns {Promise<{tableId: number|null, variables: Array}>}
*/
export async function getSpaceVariables(spaceId) {
const tableId = await getVariablesTableId(spaceId);
if (!tableId) {
return { tableId: null, variables: [] };
}
// Get all variable rows from the table
const rows = await dbAll(
'SELECT id, data FROM table_rows WHERE table_id = ?',
[tableId]
);
// Transform to API format
const variables = rows.map(row => {
const data = safeJsonParse(row.data) || {};
return {
id: row.id,
name: data.name,
value: data.cached_value,
scope: data.scope_type,
scopeRef: data.scope_ref || null,
formula: data.formula,
description: data.description,
streamId: data.stream_id
};
});
return { tableId, variables };
}
/**
* Recalculate all variables for a space (ADR-026)
* @param {number} spaceId - Space ID
* @returns {Promise<{calculated: number, cached: number, errors: Array}>}
*/
export async function recalculateSpaceVariables(spaceId) {
const tableId = await getVariablesTableId(spaceId);
if (!tableId) {
return { calculated: 0, cached: 0, errors: [] };
}
// Get all variable rows
const rows = await dbAll(
'SELECT id, data FROM table_rows WHERE table_id = ?',
[tableId]
);
let calculated = 0;
let cached = 0;
const errors = [];
const now = new Date().toISOString();
for (const row of rows) {
try {
const data = safeJsonParse(row.data) || {};
// Simple evaluation for constants and basic math
// TODO: Full formula engine in Sprint 1
let newValue = data.formula;
// Try to evaluate simple numeric formulas
if (data.formula && /^[\d.\s+\-*/()]+$/.test(data.formula)) {
try {
// Safe eval for simple math only
newValue = String(Function('"use strict"; return (' + data.formula + ')')());
} catch (evalError) {
// Keep formula as is if eval fails
newValue = data.formula;
}
}
// Update cached_value and cached_at
data.cached_value = newValue;
data.cached_at = now;
await dbRun(
'UPDATE table_rows SET data = ? WHERE id = ?',
[JSON.stringify(data), row.id]
);
calculated++;
} catch (error) {
errors.push({ rowId: row.id, error: error.message });
}
}
return { calculated, cached, errors };
}