godcrm/backend/widgets/presets.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

120 lines
3.6 KiB
JavaScript

/**
* Widget Presets Registry - v0.003.002
* Core presets come from shared widget-presets.json; autonomous modules
* (MODULE-CONTRACT v1, ADR-165-P) contribute their own presets via
* `frontend.preset_fragment` in their manifest, merged in at load time — the
* backend half of seam #3, mirroring the frontend's fragment glob-merge.
*
* To add a core widget:
* 1. Edit /shared/widget-presets.json
* 2. Both frontend and backend will use the new preset automatically
*
* A module widget lives ONLY in its module's presets.fragment.json — remove the
* module dirs and the preset disappears from both trees, no core edit.
*/
import { readFileSync } from 'fs';
import { join, dirname } from 'path';
import { fileURLToPath } from 'url';
import { logger } from '../utils/logger.js';
import { modulePresetFragments } from '../modules/loader.js';
// Load presets from shared JSON file
const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);
const presetsPath = join(__dirname, '../../shared/widget-presets.json');
let WIDGET_PRESETS = {};
try {
const presetsJson = readFileSync(presetsPath, 'utf-8');
// Core presets from shared JSON, then module-owned preset fragments layered on
// top (present modules only). Fragments share the shared-JSON entry shape, so the
// transform loop below handles them identically.
const presetConfigs = { ...JSON.parse(presetsJson), ...modulePresetFragments() };
// Transform to backend format (add default_config, icon → emoji mapping)
for (const [id, config] of Object.entries(presetConfigs)) {
WIDGET_PRESETS[id] = {
name: config.name,
description: config.description,
icon: config.emoji,
category: config.category,
default_config: {
table_id: null,
filters: [],
...(config.requiredColumns?.length > 0 ? { column_mapping: {} } : {})
},
component: config.id.split('_').map(w => w.charAt(0).toUpperCase() + w.slice(1)).join('') + 'Widget'
};
}
} catch (error) {
logger.error({ err: error }, 'Failed to load widget presets from JSON');
// Fallback to minimal presets
WIDGET_PRESETS = {
table_view: {
name: 'Table View',
description: 'Display filtered table data',
icon: '📊',
category: 'data',
default_config: { table_id: null },
component: 'TableViewWidget'
}
};
}
export { WIDGET_PRESETS };
/**
* Get preset by name
* @param {string} presetName - Preset name (e.g., 'table_view')
* @returns {object|null} Preset configuration or null
*/
export function getPreset(presetName) {
return WIDGET_PRESETS[presetName] || null;
}
/**
* Get all presets as array
* @returns {array} Array of presets with id
*/
export function getAllPresets() {
return Object.entries(WIDGET_PRESETS).map(([id, preset]) => ({
id,
...preset
}));
}
/**
* Get presets by category
* @param {string} category - Category name
* @returns {array} Array of presets in category
*/
export function getPresetsByCategory(category) {
return Object.entries(WIDGET_PRESETS)
.filter(([_, preset]) => preset.category === category)
.map(([id, preset]) => ({ id, ...preset }));
}
/**
* Validate preset name
* @param {string} presetName - Preset name
* @returns {boolean} True if preset exists
*/
export function isValidPreset(presetName) {
return presetName in WIDGET_PRESETS;
}
/**
* Get preset categories
* @returns {array} Array of unique categories
*/
export function getCategories() {
const categories = new Set();
Object.values(WIDGET_PRESETS).forEach(preset => {
categories.add(preset.category);
});
return Array.from(categories).sort();
}
export default WIDGET_PRESETS;