@xuda.io/runtime-bundle
Version:
The Xuda Runtime Bundle refers to a collection of scripts and libraries packaged together to provide the necessary runtime environment for executing plugins or components in the Xuda platform.
1,562 lines (1,369 loc) • 148 kB
JavaScript
// todos
//=====================
// check unique index id
// check unique field id
var app_obj, progs_obj, _, progs_str, hide_not_in_use_check, is_server, deployments, UglifyJS, JSON5, _conf, warn_plugin_check, z;
const PROTECTED_VARS = ['_NULL', '_THIS', '_FOR_KEY', '_FOR_VAL', '_ROWNO', '_ROWID', '_ROWDOC', '_KEY', '_VAL'];
const ALL_MENU_TYPE = ['globals', 'component', 'batch', 'get_data', 'set_data', 'alert', 'javascript', 'api', 'table', 'folder'];
const program_ref_triggers_arr = ['call_page', 'call_modal', 'call_popover', 'get_data', 'set_data', 'batch', 'call_native_javascript', 'call_evaluate_javascript', 'call_alert', 'alter_ui_element', 'create_ui_element', 'update', 'raise_event'];
export const init = function (app_obj_in, progs_obj_in = {}, _in, _hide_not_in_use_check, is_server_in, deployments_in = [], UglifyJS_in, JSON5_in, _conf_in, _warn_plugin_check, _z) {
app_obj = app_obj_in;
progs_obj = progs_obj_in;
progs_str = JSON.stringify(progs_obj);
hide_not_in_use_check = _hide_not_in_use_check;
_ = _in;
is_server = is_server_in;
deployments = deployments_in;
UglifyJS = UglifyJS_in;
JSON5 = JSON5_in;
_conf = _conf_in;
warn_plugin_check = _warn_plugin_check;
z = _z;
// if (progs_str.includes("reduce_counter")) {
// debugger;
// }
};
export const check = function (doc) {
var ret = [];
var dependency_progs = [];
// Helper function to add validation errors and continue
const addValidationError = (code, data, type = 'E', category = 'document', ref = null, id = null, not_in_use = false) => {
const error = {
code,
data,
type,
category,
};
if (ref) error.ref = ref;
if (id) error.id = id;
if (not_in_use) error.not_in_use = not_in_use;
ret.push(error);
};
// Early validation checks
if (_.isEmpty(doc)) {
addValidationError('CHK_MSG_OBJ_GEN_110', 'doc in empty');
return {
check_errors: ret,
check_warnings: [],
doc,
dependency_progs: [],
};
}
let critical_error = false;
// Run structure validation first
ret = check_structure(doc);
// Only continue if structure validation passed
// if (!ret?.length) {
if (!critical_error) {
doc.studio_meta.not_in_use = false;
// Process non-folder items
if (doc.properties.menuType !== 'folder') {
if (doc.properties.menuType !== 'table') {
// Handle deployment dependencies for server environment
if (is_server) {
for (let val of deployments) {
if (val.deploy_data?.prog_id === doc._id) {
if (!doc.studio_meta.used_by_deployment_ids) {
doc.studio_meta.used_by_deployment_ids = [];
}
doc.studio_meta.used_by_deployment_ids.push(val._id);
}
}
}
// Check if program is in use
if (!doc.studio_meta.used_by_deployment_id) {
const check_programs_are_active = function (arr) {
if (!arr.length) return false;
var count = 0;
for (let val of arr) {
if (progs_obj?.[val.prog_id]?.studio_meta?.not_in_use) {
count++;
break;
}
}
return count !== arr.length;
};
if (!hide_not_in_use_check) {
const ret_prog_in_use = find_trigger_property_value_in_progs(doc, program_ref_triggers_arr, 'prog', doc._id);
if (!ret_prog_in_use || !check_programs_are_active(ret_prog_in_use)) {
const ret_panel_prog_in_use = find_panel_prog_in_progs(doc, doc._id);
if (!ret_panel_prog_in_use || !check_programs_are_active(ret_panel_prog_in_use)) {
const ret_fieldValue_in_use = find_prog_in_fieldset_fieldValue_progs(doc, doc._id);
if (!ret_fieldValue_in_use || !check_programs_are_active(ret_fieldValue_in_use)) {
doc.studio_meta.not_in_use = true;
}
}
}
}
}
// Add program event validation results
ret = [...ret, ...check_prog_events(doc)];
// Find program dependencies
dependency_progs = find_trigger_property_value_in_progs(doc, program_ref_triggers_arr, 'prog', null, true) || [];
dependency_progs = [...dependency_progs, ...(find_panel_prog_in_progs(doc, null, true) || [])];
} else {
// Handle table usage checking
if (!hide_not_in_use_check) {
if (!find_table_in_progs(doc, doc._id)) {
doc.studio_meta.not_in_use = true;
}
}
}
}
// Run menu type specific validation
switch (doc.properties.menuType) {
case 'globals':
ret = [...ret, ...check_globals(doc)];
break;
case 'table':
ret = [...ret, ...check_table(doc)];
break;
case 'component':
ret = [...ret, ...check_component(doc)];
break;
case 'javascript':
ret = [...ret, ...check_javascript(doc)];
break;
case 'get_data':
ret = [...ret, ...check_get_data(doc)];
break;
case 'set_data':
ret = [...ret, ...check_set_data(doc)];
break;
case 'batch':
ret = [...ret, ...check_batch(doc)];
break;
case 'api':
ret = [...ret, ...check_api(doc)];
break;
case 'alert':
ret = [...ret, ...check_alert(doc)];
break;
case 'route':
ret = [...ret, ...check_route(doc)];
break;
case 'folder':
// No additional validation for folders
break;
default:
addValidationError('CHK_MSG_GEN_050', `invalid ${doc.properties.menuType} properties.menuType`, 'E', 'prog', doc._id, doc._id);
break;
}
}
// Separate errors and warnings
var final_ret = {
check_errors: [],
check_warnings: [],
doc,
dependency_progs,
};
for (let [key, val] of Object.entries(ret)) {
if (val.type === 'E') {
final_ret.check_errors.push(val);
}
if (val.type === 'W') {
final_ret.check_warnings.push(val);
}
}
return final_ret;
};
export const check_structure = function (doc) {
var ret = [];
// Helper function to add validation errors and continue
const addValidationError = (code, data) => {
ret.push({
code,
data,
type: 'E',
category: 'document',
structure_error: true,
});
};
// Early exit conditions that prevent further validation
if (!app_obj || !_.isObject(app_obj) || _.isEmpty(app_obj)) {
addValidationError('CHK_MSG_OBJ_GEN_002', 'invalid app_obj');
critical_error = true;
return ret;
}
if (!doc || !_.isObject(doc) || _.isEmpty(doc)) {
addValidationError('CHK_MSG_OBJ_GEN_004', 'doc is not a object');
critical_error = true;
return ret;
}
try {
JSON5.parse(JSON.stringify(doc));
} catch (err) {
addValidationError('CHK_MSG_OBJ_GEN_005', 'error parsing JSON doc');
critical_error = true;
return ret;
}
if (z) {
const schema = get_zod_schema(doc.properties.menuType);
const result = schema.safeParse(doc);
if (!result.success) {
result.error.errors.forEach((error) => {
const msg = `${error.message}: ${error.path}(${error.expected})`;
addValidationError('CHK_MSG_OBJ_GEN_000', msg);
});
critical_error = true;
return ret;
}
}
// Continue validation even with errors
if (!doc._id) {
addValidationError('CHK_MSG_OBJ_GEN_050', 'missing _id value');
}
if (doc?.studio_meta?.source === 'studio ai' && doc?.properties?.menuType && doc._id && doc._id.substr(0, 7) !== new_node_id(doc.properties.menuType, doc.app_id).substr(0, 7)) {
addValidationError('CHK_MSG_OBJ_GEN_051', 'invalid _id value (e.g: 5b1_tbl_a953848a71c5)');
}
if (!doc._rev) {
if (progs_obj?.[doc._id]) {
addValidationError('CHK_MSG_OBJ_GEN_052', 'duplicate doc _id');
}
}
////////// properties ////////////////
if (!doc.properties) {
addValidationError('CHK_MSG_OBJ_GEN_100', 'missing properties object');
} else {
if (!_.isObject(doc.properties)) {
addValidationError('CHK_MSG_OBJ_GEN_102', 'invalid properties object');
} else {
if (!doc.properties.menuType) {
addValidationError('CHK_MSG_OBJ_GEN_104', 'missing properties.menuType');
} else if (!ALL_MENU_TYPE.includes(doc.properties.menuType)) {
addValidationError('CHK_MSG_OBJ_GEN_105', `invalid properties.menuType (valid values: ${ALL_MENU_TYPE.join(',')})`);
}
if (!doc.properties.menuName) {
addValidationError('CHK_MSG_OBJ_GEN_106', 'missing properties.menuName');
}
}
}
////////// studio_meta ////////////////
if (!doc.studio_meta) {
addValidationError('CHK_MSG_OBJ_GEN_112', 'missing studio_meta property');
} else {
if (!_.isObject(doc.studio_meta)) {
addValidationError('CHK_MSG_OBJ_GEN_112', 'invalid studio_meta object');
} else {
if (!doc.studio_meta.parentId) {
addValidationError('CHK_MSG_OBJ_GEN_114', 'missing studio_meta.parentId value');
} else {
let menuType = doc?.properties?.menuType === 'globals' ? 'globals' : doc?.properties?.menuType === 'table' ? 'database' : doc?.properties?.menuType === 'route' ? 'routes' : 'programs';
if (doc.studio_meta.parentId !== menuType && !progs_obj[doc.studio_meta.parentId]) {
addValidationError('CHK_MSG_OBJ_GEN_116', 'invalid studio_meta.parentId value');
}
}
}
}
const check_progFields = function () {
if (!doc.progFields) return;
var ids = [];
if (!_.isArray(doc.progFields)) {
addValidationError('CHK_MSG_OBJ_GEN_402', 'invalid progFields Array property');
return;
}
const validTypes = ['virtual', 'table', 'expression', 'datasource'];
const validFieldTypes = ['string', 'number', 'boolean', 'array', 'object'];
for (let val of doc.progFields) {
if (!_.isObject(val)) {
addValidationError('CHK_MSG_OBJ_GEN_404', `Invalid progFields object `);
continue;
}
if (!val.id) {
addValidationError('CHK_MSG_OBJ_GEN_406', 'Missing progFields ID property');
}
const fieldId = val.id ? `(${val.id})` : '';
if (val.id) {
if (ids.includes(val.id)) {
addValidationError('CHK_MSG_OBJ_GEN_408', `Duplicate progFields ID (${val.id})`);
continue;
}
ids.push(val.id);
}
if (!_.isObject(val.data)) {
addValidationError('CHK_MSG_OBJ_GEN_410', `Invalid progFields data object ${fieldId}`);
} else {
if (!val.data.field_id) {
addValidationError('CHK_MSG_OBJ_GEN_412', `Missing progFields field_id property ${fieldId}`);
}
if (!val.data.type) {
addValidationError('CHK_MSG_OBJ_GEN_414', `Missing progFields type property ${fieldId}`);
} else if (!validTypes.includes(val.data.type)) {
addValidationError('CHK_MSG_OBJ_GEN_415', `Invalid progFields type "${val.data.type}" ${fieldId}. Must be one of: ${validTypes.join(', ')}`);
}
}
if (!_.isObject(val.props)) {
addValidationError('CHK_MSG_OBJ_GEN_416', `Invalid progFields props object ${fieldId}`);
} else {
if (!val.props.fieldType) {
addValidationError('CHK_MSG_OBJ_GEN_418', `Missing progFields fieldType property ${fieldId}`);
} else if (!validFieldTypes.includes(val.props.fieldType)) {
addValidationError('CHK_MSG_OBJ_GEN_419', `Invalid progFields fieldType "${val.props.fieldType}" ${fieldId}. Must be one of: ${validFieldTypes.join(', ')}`);
}
if (!_.isObject(val.props.propExpressions)) {
addValidationError('CHK_MSG_OBJ_GEN_4120', `Invalid progFields propExpressions object ${fieldId}`);
} else {
// Validate propExpressions only contains allowed properties
const allowedProps = ['fieldValue', 'fieldType'];
const actualProps = Object.keys(val.props.propExpressions);
const invalidProps = actualProps.filter((prop) => !allowedProps.includes(prop));
if (invalidProps.length > 0) {
addValidationError('CHK_MSG_OBJ_GEN_4121', `Invalid propExpressions properties: ${invalidProps.join(', ')} ${fieldId}. Only allowed: ${allowedProps.join(', ')}`);
}
}
}
check_workflow(val.workflow);
}
};
const check_progEvents = function () {
if (!doc.progEvents) return;
var ids = [];
if (!_.isArray(doc.progEvents)) {
addValidationError('CHK_MSG_OBJ_GEN_502', 'invalid progEvents Array property');
return;
}
for (let val of doc.progEvents) {
if (!_.isObject(val)) {
addValidationError('CHK_MSG_OBJ_GEN_504', 'invalid progEvents object property');
continue;
}
if (!val.id) {
addValidationError('CHK_MSG_OBJ_GEN_506', 'missing progEvents.id property');
continue;
}
if (ids.includes(val.id)) {
addValidationError('CHK_MSG_OBJ_GEN_508', `${val.id} id must be unique in progEvents property`);
continue;
}
ids.push(val.id);
if (!_.isObject(val.data)) {
addValidationError('CHK_MSG_OBJ_GEN_510', 'invalid progEvents.data object property');
continue;
}
if (typeof val.data.condition === 'undefined') {
addValidationError('CHK_MSG_OBJ_GEN_512', 'undefined progEvents.data.condition string property');
}
if (typeof val.data.event_name === 'undefined') {
addValidationError('CHK_MSG_OBJ_GEN_514', 'undefined progEvents.data.event_name string property');
}
if (typeof val.data.properties === 'undefined') {
addValidationError('CHK_MSG_OBJ_GEN_516', 'undefined progEvents.data.properties string property');
}
if (typeof val.data.type === 'undefined') {
addValidationError('CHK_MSG_OBJ_GEN_518', 'undefined progEvents.data.type string property');
}
if (!_.isArray(val.data.parameters)) {
addValidationError('CHK_MSG_OBJ_GEN_522', 'invalid progEvents.data.parameters Array property');
}
if (!_.isObject(val.props)) {
addValidationError('CHK_MSG_OBJ_GEN_522', 'invalid progEvents.props Object property');
}
check_workflow(val.workflow);
}
};
const check_workflow = function (workflow) {
if (!workflow) return;
var ids = [];
if (!_.isArray(workflow)) {
addValidationError('CHK_MSG_OBJ_GEN_850', 'invalid workflow Array property');
return;
}
for (let val of workflow) {
if (!_.isObject(val)) {
addValidationError('CHK_MSG_OBJ_GEN_852', 'invalid workflow item object property');
continue;
}
if (!val.id) {
addValidationError('CHK_MSG_OBJ_GEN_854', 'missing workflow.id property');
continue;
}
if (ids.includes(val.id)) {
addValidationError('CHK_MSG_OBJ_GEN_856', `${val.id} id must be unique in workflow property`);
continue;
}
ids.push(val.id);
if (!_.isObject(val.data)) {
addValidationError('CHK_MSG_OBJ_GEN_858', 'invalid workflow.data Object property');
}
if (!_.isObject(val.props)) {
addValidationError('CHK_MSG_OBJ_GEN_860', 'invalid workflow.props item object property');
}
}
};
const check_progDataSource = function () {
if (!doc.progDataSource) return;
if (!_.isObject(doc.progDataSource)) {
addValidationError('CHK_MSG_OBJ_GEN_602', 'invalid progDataSource Object property');
return;
}
if (typeof doc.progDataSource.dataSourceType === 'undefined') {
addValidationError('CHK_MSG_OBJ_GEN_604', 'undefined progDataSource.dataSourceType string property');
}
};
const check_progUi = function () {
if (!doc.progUi) return;
if (!_.isArray(doc.progUi)) {
addValidationError('CHK_MSG_OBJ_GEN_702', 'invalid progUi Array property');
return;
}
var ids = [];
const run_tree = function (node) {
for (let item of node) {
if (!_.isObject(item)) {
addValidationError('CHK_MSG_OBJ_GEN_704', 'invalid progUi item Object property');
continue;
}
if (!item.id) {
addValidationError('CHK_MSG_OBJ_GEN_712', 'missing progUi id item property');
}
if (item?.id) {
if (item?.type === 'comment') {
addValidationError('CHK_MSG_OBJ_GEN_705', `Comment type is not supported in progUi (${item.id})`);
continue;
}
if (item?.type === 'text') {
addValidationError('CHK_MSG_OBJ_GEN_706', `Text type is not supported in progUi (${item.id}). Convert to "element" type and add a "text" property instead`);
continue;
}
if (item?.attributes && !_.isObject(item.attributes)) {
addValidationError('CHK_MSG_OBJ_GEN_707', `Invalid progUi attributes object property (${item.id})`);
}
if (typeof item.type === 'undefined') {
addValidationError('CHK_MSG_OBJ_GEN_708', `Missing progUi type property (${item.id})`);
}
if (typeof item.tagName === 'undefined') {
addValidationError('CHK_MSG_OBJ_GEN_710', `Missing progUi tagName property (${item.id})`);
}
if (item.id !== 'root' && doc?.studio_meta?.source === 'studio ai' && !detectCryptoString(item.id.replace('node-', '').replace('ui-', '')).uuid) {
addValidationError('CHK_MSG_OBJ_GEN_713', `ProgUi item ID must be in UUID format (${item.id})`);
}
if (ids.includes(item.id)) {
addValidationError('CHK_MSG_OBJ_GEN_714', `Duplicate progUi item ID found (${item.id})`);
continue;
}
ids.push(item.id);
}
check_workflow(item.workflow);
if (item.children) {
run_tree(item.children);
}
}
};
run_tree(doc.progUi);
};
// Only proceed with menu type validation if we have the required properties
if (doc.properties && doc.properties.menuType) {
switch (doc.properties.menuType) {
case 'table':
if (!doc.tableFields) {
addValidationError('CHK_MSG_OBJ_GEN_200', 'missing tableFields property');
} else {
if (!_.isArray(doc.tableFields)) {
addValidationError('CHK_MSG_OBJ_GEN_202', 'invalid tableFields Array property');
} else {
var ids = [];
for (let val of doc.tableFields) {
if (!_.isObject(val)) {
addValidationError('CHK_MSG_OBJ_GEN_204', 'invalid tableFields object property');
continue;
}
if (!val.id) {
addValidationError('CHK_MSG_OBJ_GEN_206', 'missing tableFields.id property');
continue;
}
if (ids.includes(val.id)) {
addValidationError('CHK_MSG_OBJ_GEN_208', `${val.id} id must be unique in tableFields property`);
continue;
}
ids.push(val.id);
if (!_.isObject(val.data)) {
addValidationError('CHK_MSG_OBJ_GEN_210', 'invalid tableFields.data object property');
continue;
}
if (!val.data.field_id) {
addValidationError('CHK_MSG_OBJ_GEN_212', 'invalid tableFields.data.field_id property');
}
if (!_.isObject(val.props)) {
addValidationError('CHK_MSG_OBJ_GEN_214', 'invalid tableFields.props object property');
continue;
}
if (!val.props.fieldType) {
addValidationError('CHK_MSG_OBJ_GEN_216', 'invalid tableFields.props.fieldType property');
}
}
}
}
// tableIndexes validation
if (!doc.tableIndexes) {
addValidationError('CHK_MSG_OBJ_GEN_302', 'missing tableIndexes property');
} else if (!_.isArray(doc.tableIndexes)) {
addValidationError('CHK_MSG_OBJ_GEN_302', 'invalid tableIndexes Array property');
} else {
var index_ids = [];
for (let val of doc.tableIndexes) {
if (!_.isObject(val)) {
addValidationError('CHK_MSG_OBJ_GEN_304', 'invalid tableIndexes object property');
continue;
}
if (!val.id) {
addValidationError('CHK_MSG_OBJ_GEN_306', 'missing tableIndexes.id property');
continue;
}
if (index_ids.includes(val.id)) {
addValidationError('CHK_MSG_OBJ_GEN_308', `${val.id} id must be unique in tableIndexes property`);
continue;
}
index_ids.push(val.id);
if (!_.isObject(val.data)) {
addValidationError('CHK_MSG_OBJ_GEN_310', 'invalid tableIndexes.data object property');
continue;
}
if (!val.data.name) {
addValidationError('CHK_MSG_OBJ_GEN_312', 'invalid tableIndexes.data.name property');
}
if (!_.isArray(val.data.keys)) {
addValidationError('CHK_MSG_OBJ_GEN_312', 'invalid tableIndexes.data.keys Array property');
}
}
}
break;
case 'globals':
check_progFields();
check_progEvents();
break;
case 'get_data':
case 'set_data':
case 'batch':
case 'api':
check_progFields();
check_progEvents();
check_progDataSource();
break;
case 'component':
check_progFields();
check_progEvents();
check_progDataSource();
check_progUi();
if (doc.properties && typeof doc.properties.uiFramework === 'undefined') {
addValidationError('CHK_MSG_OBJ_GEN_120', 'undefined properties.uiFramework string property');
}
if (doc.properties && typeof doc.properties.renderType === 'undefined') {
addValidationError('CHK_MSG_OBJ_GEN_122', 'undefined properties.renderType string property');
}
break;
case 'alert':
break;
case 'javascript':
break;
case 'folder':
break;
default:
break;
}
}
return ret;
};
const check_table = function (doc) {
var ret = [];
///////////// db socket ////////////////////
if (!doc.properties.databaseSocket) {
ret.push({
code: 'CHK_MSG_TBL_GEN_010',
data: 'database socket not defined',
type: doc.studio_meta.not_in_use ? 'W' : 'E',
category: `table`,
ref: doc._id,
id: doc._id,
});
} else {
if (!app_obj?.app_plugins_purchased?.[doc.properties.databaseSocket]) {
ret.push({
code: 'CHK_MSG_TBL_GEN_0020',
data: `invalid ${doc.properties.databaseSocket} database socket`,
type: doc.studio_meta.not_in_use || warn_plugin_check ? 'W' : 'E',
category: `table`,
ref: doc._id,
id: doc._id,
});
} else {
if (!app_obj.app_plugins_purchased[doc.properties.databaseSocket].installed) {
ret.push({
code: 'CHK_MSG_TBL_GEN_030',
data: `${doc.properties.databaseSocket} database socket not installed`,
type: doc.studio_meta.not_in_use ? 'W' : 'E',
category: `table`,
ref: doc._id,
id: doc._id,
});
}
}
}
///////////// fields ////////////////////
if (doc.tableIndexes?.length && !doc?.tableFields?.length) {
ret.push({
code: 'CHK_MSG_TBL_FLD_090',
data: `table has no fields`,
type: doc.studio_meta.not_in_use ? 'W' : 'E',
category: `table`,
ref: doc._id,
id: doc._id,
});
}
var name_counts_obj = {};
for (let field of doc.tableFields) {
if (!field?.data?.field_id) {
ret.push({
code: 'CHK_MSG_TBL_FLD_100',
data: `missing field_id`,
type: doc.studio_meta.not_in_use ? 'W' : 'E',
category: `table`,
ref: field.id,
id: `${doc._id}-${field.id}`,
});
} else {
if (!/^[A-Za-z]+[\w\-\:\.]*$/.test(field.data.field_id)) {
ret.push({
code: 'CHK_MSG_TBL_FLD_020',
data: `invalid field_id`,
type: doc.studio_meta.not_in_use ? 'W' : 'E',
category: `table`,
ref: field.id,
id: `${doc._id}-${field.id}`,
});
}
if (!name_counts_obj[field.data.field_id]) {
name_counts_obj[field.data.field_id] = 1;
} else {
name_counts_obj[field.data.field_id]++;
}
if (!hide_not_in_use_check) {
if (!find_field_in_progs(doc, field.data.field_id)) {
ret.push({
code: 'CHK_MSG_TBL_FLD_022',
data: `table field "${field.data.field_id}" not in use`,
type: 'W',
category: `table`,
ref: doc._id,
not_in_use: true,
id: `${doc._id}-${field.id}`,
});
}
}
}
}
for (let [key, val] of Object.entries(name_counts_obj)) {
if (val > 1) {
ret.push({
code: 'CHK_MSG_TBL_FLD_200',
data: `duplicate field id for ${key}`,
type: doc.studio_meta.not_in_use ? 'W' : 'E',
id: `${doc._id}-${key}`,
});
}
}
///////////// indexes ////////////////////
if (!doc.tableIndexes?.length) {
if (doc.properties.databaseSocket !== '@xuda.io/xuda-dbs-plugin-xuda') {
ret.push({
code: 'CHK_MSG_TBL_IDX_040',
data: `at least one index has to be defined`,
type: doc.studio_meta.not_in_use ? 'W' : 'E',
id: `${doc._id}`,
});
}
} else {
let found_unique;
for (let index of doc.tableIndexes) {
if (index?.data?.unique) {
found_unique = true;
}
if (!index?.data?.name) {
ret.push({
code: 'CHK_MSG_TBL_IDX_060',
data: `tableIndexes.data.name cannot be empty in "${index.id}" index`,
type: doc.studio_meta.not_in_use ? 'W' : 'E',
id: `${doc._id}`,
});
}
if (!index?.data?.keys?.length) {
ret.push({
code: 'CHK_MSG_TBL_IDX_070',
data: `tableIndexes.data.keys cannot be empty in "${index.id}" index`,
type: doc.studio_meta.not_in_use ? 'W' : 'E',
id: `${doc._id}-${index.id}`,
});
} else {
for (let key of index.data.keys) {
if (!doc.tableFields || !_.isArray(doc.tableFields) || !find_item_by_key(doc.tableFields, 'field_id', key)) {
ret.push({
code: 'CHK_MSG_TBL_IDX_080',
data: `invalid key ${key} in tableIndexes.data.keys for "${index.id}" index`,
type: doc.studio_meta.not_in_use ? 'W' : 'E',
id: `${doc._id}-${index.id}-${key}`,
});
}
}
}
}
// if (!found_unique) {
// ret.push({
// code: "CHK_MSG_TBL_IDX_050",
// data: `at least one index has to be defined`,
// type: doc.studio_meta.not_in_use ? "W" : "E",
// id: `${doc._id}`,
// });
// }
}
// WARNINGS //
///////////// fields ////////////////////
if (!doc?.tableFields?.length) {
ret.push({
code: 'CHK_MSG_TBL_FLD_012',
data: `table has no fields`,
type: 'W',
id: `${doc._id}`,
});
}
for (let field of doc.tableFields) {
if (!['string', 'number', 'boolean', 'object', 'array', 'date'].includes(field?.props?.fieldType)) {
ret.push({
code: 'CHK_MSG_TBL_FLD_022',
data: `invalid fieldType for field ${field?.data?.field_id}`,
type: 'E',
id: `${doc._id}-${field.id}`,
});
}
}
return ret;
};
const check_globals = function (doc) {
var ret = [];
ret = [...ret, ...check_prog_fieldset(doc)];
return ret;
};
const check_component = function (doc) {
var ret = [];
///////////// uiFramework ////////////////////
if (!doc.properties.uiFramework) {
ret.push({
code: 'CHK_MSG_PRG_COM_010',
data: 'component Ui Framework not defined',
type: doc.studio_meta.not_in_use ? 'W' : 'E',
category: `component`,
ref: doc._id,
id: `${doc._id}`,
});
} else {
if (!app_obj?.app_plugins_purchased?.[doc.properties.uiFramework]) {
ret.push({
code: 'CHK_MSG_PRG_COM_020',
data: `invalid ${doc.properties.uiFramework} Ui Framework`,
type: doc.studio_meta.not_in_use ? 'W' : 'E',
category: `component`,
ref: doc._id,
id: `${doc._id}`,
});
} else {
if (!app_obj.app_plugins_purchased[doc.properties.uiFramework].installed) {
ret.push({
code: 'CHK_MSG_PRG_COM_030',
data: `${doc.properties.uiFramework} Ui Framework not installed`,
type: doc.studio_meta.not_in_use ? 'W' : 'E',
category: `component`,
ref: doc._id,
id: `${doc._id}`,
});
}
}
}
ret = [...ret, ...check_prog_fieldset(doc)];
ret = [...ret, ...check_prog_parameters(doc)];
ret = [...ret, ...check_prog_datasource(doc)];
///////////// ui ////////////////////
if (doc.progUi) {
var xu_tree_id_counts_obj = {};
const run_tree = function (node) {
for (let item of node) {
if (item.xu_tree_id) {
if (!xu_tree_id_counts_obj[item.xu_tree_id]) {
xu_tree_id_counts_obj[item.xu_tree_id] = 1;
} else {
xu_tree_id_counts_obj[item.xu_tree_id]++;
}
}
if (item.attributes) {
var name_counts_obj = {};
for (let [key, val] of Object.entries(item.attributes)) {
if (!key) {
ret.push({
code: 'CHK_MSG_PRG_GUI_010',
data: `missing attribute in tree_id ${item.id}`,
type: doc.studio_meta.not_in_use ? 'W' : 'E',
category: `component_ui`,
ref: item.id,
id: `${doc._id}-${item.id}-${key}`,
});
} else {
// console.log(key);
if (key.substr(0, 6) === 'xu-exp') {
// if (item.id === "node-c1adfbb7-17b4-4631-b17d-000db66cc88d")
ret = [...ret, ...check_prog_expression(doc, val, item.id, 'ui_attribute')];
} else {
if (!/^[A-Za-z]+[\w\-\:\.]*$/.test(key)) {
ret.push({
code: 'CHK_MSG_PRG_GUI_020',
data: `invalid attribute "${key}" in tag "${item.tagName}" "${item.id}"`,
type: doc.studio_meta.not_in_use ? 'W' : 'E',
category: `component_ui`,
ref: item.id,
id: `${doc._id}-${item.id}-${key}`,
});
}
}
if (key.substr(0, 6) === 'xu-on:') {
if (!_.isEmpty(val)) {
for (let trigger_item of val) {
// try {
ret = [...ret, ...check_prog_triggers(doc, trigger_item.workflow, 'ui_trigger', item.id, item.attributes.tree_id)];
// } catch (err) {
// console.error("***********", err);
// debugger;
// }
}
}
}
if (!name_counts_obj[key]) {
name_counts_obj[key] = 1;
} else {
name_counts_obj[key]++;
}
}
}
for (let [key, val] of Object.entries(name_counts_obj)) {
if (val > 1) {
ret.push({
code: 'CHK_MSG_PRG_GUI_200',
data: `duplicate attribute id for ${key} in tree_id ${item.attribute.tree_id}`,
type: doc.studio_meta.not_in_use ? 'W' : 'E',
category: `component_ui`,
ref: doc._id,
id: `${doc._id}-${item.id}-${key}`,
});
}
}
}
// if (!_.isEmpty(item.workflow)) {
// debugger;
// ret = [
// ...ret,
// ...check_prog_triggers(doc, item.workflow, "ui", item.data.tree_id),
// ];
// }
if (item.children) {
run_tree(item.children);
}
}
};
run_tree(doc.progUi);
for (let [key, val] of Object.entries(xu_tree_id_counts_obj)) {
if (val > 1) {
ret.push({
code: 'CHK_MSG_PRG_GUI_250',
data: `duplicate tree_id "${key}"`,
type: 'W',
category: `component_ui`,
ref: doc._id,
id: `${doc._id}-${key}`,
});
}
}
}
return ret;
};
const check_get_data = function (doc) {
var ret = [];
ret = [...ret, ...check_prog_fieldset(doc)];
ret = [...ret, ...check_prog_parameters(doc)];
ret = [...ret, ...check_prog_datasource(doc)];
return ret;
};
const check_batch = function (doc) {
var ret = [];
ret = [...ret, ...check_prog_fieldset(doc)];
ret = [...ret, ...check_prog_parameters(doc)];
ret = [...ret, ...check_prog_datasource(doc)];
return ret;
};
const check_api = function (doc) {
var ret = [];
ret = [...ret, ...check_prog_fieldset(doc)];
ret = [...ret, ...check_prog_parameters(doc)];
ret = [...ret, ...check_prog_datasource(doc)];
return ret;
};
const check_set_data = function (doc) {
var ret = [];
ret = [...ret, ...check_prog_fieldset(doc)];
ret = [...ret, ...check_prog_parameters(doc)];
ret = [...ret, ...check_prog_datasource(doc)];
return ret;
};
const check_alert = function (doc) {
var ret = [];
if (_.isEmpty(doc.alertData)) {
ret.push({
code: 'CHK_MSG_PRG_ART_010',
data: `no alert properties defined`,
type: doc.studio_meta.not_in_use ? 'W' : 'E',
category: `alert`,
ref: doc._id,
id: `${doc._id}`,
});
} else {
if (!doc.alertData.alertType) {
ret.push({
code: 'CHK_MSG_PRG_ART_020',
data: `missing alert type property`,
type: doc.studio_meta.not_in_use ? 'W' : 'E',
category: `alert`,
ref: doc._id,
id: `${doc._id}`,
});
}
if (!doc.alertData.alertDisplay) {
ret.push({
code: 'CHK_MSG_PRG_ART_030',
data: `missing alert display property`,
type: doc.studio_meta.not_in_use ? 'W' : 'E',
category: `alert`,
ref: doc._id,
id: `${doc._id}`,
});
}
if (!doc.alertData.alertTitle && !doc.alertData.alertTitleFx && !doc.alertData.alertBody && !doc.alertData.alertBodyFx) {
ret.push({
code: 'CHK_MSG_PRG_ART_032',
data: `missing alert title and body value`,
type: 'W',
category: `alert`,
ref: doc._id,
id: `${doc._id}`,
});
}
// if (!doc.alertData.alertBody && !doc.alertData.alertBodyFx) {
// ret.push({
// code: "CHK_MSG_PRG_ART_042",
// data: `missing alert body property`,
// type: "W",
// category: `alert`,
// ref: doc._id,
// });
// }
}
return ret;
};
const check_route = function (doc) {
var ret = [];
if (_.isEmpty(doc?.routeMenu?.menu)) {
ret.push({
code: 'CHK_MSG_RTE_MNU_010',
data: `no route properties defined`,
type: doc.studio_meta.not_in_use ? 'W' : 'E',
category: `alert`,
ref: doc._id,
id: `${doc._id}`,
});
} else {
function checkMenuItems(menu) {
let flatMenu = {};
function recurse(items) {
for (let item of items) {
if (!item.prog_id) {
ret.push({
code: 'CHK_MSG_RTE_MNU_020',
data: `no program to call has been defined`,
type: doc.studio_meta.not_in_use ? 'W' : 'E',
category: `alert`,
ref: item.id,
id: `${doc._id}`,
});
continue;
}
if (!progs_obj?.[item.prog_id]) {
ret.push({
code: 'CHK_MSG_RTE_MNU_030',
data: `program ${item.prog_id} not exist`,
type: doc.studio_meta.not_in_use ? 'W' : 'E',
category: `alert`,
ref: item.id,
id: `${doc._id}`,
});
continue;
}
if (item.children && item.children.length > 0) {
recurse(item.children);
}
}
}
recurse(menu);
return flatMenu;
}
checkMenuItems(doc.routeMenu.menu);
}
return ret;
};
// const call_project_api = function (doc) {
// var ret = [];
// if (_.isEmpty(doc.alertData)) {
// ret.push({
// code: "CHK_MSG_PRG_ART_010",
// data: `no alert properties defined`,
// type: doc.studio_meta.not_in_use ? "W" : "E",
// category: `alert`,
// ref: doc._id,
// id: `${doc._id}`,
// });
// }
// return ret;
// };
const check_prog_fieldset = function (doc) {
var ret = [];
if (!doc.progFields || _.isEmpty(doc.progFields)) {
if (doc.progDataSource.dataSourceTableId) {
ret.push({
code: 'CHK_MSG_PRG_FLD_012',
data: `datasource table defined but fieldset is empty`,
type: 'W',
category: 'fieldset',
id: `${doc._id}`,
});
}
} else {
var name_counts_obj = {};
for (let field of doc.progFields) {
if (!field?.data?.field_id) {
ret.push({
code: 'CHK_MSG_PRG_FLD_010',
data: `missing field_id`,
type: doc.studio_meta.not_in_use ? 'W' : 'E',
category: 'fieldset',
ref: field.id,
id: `${doc._id}-${field.id}`,
});
} else {
if (!/^[A-Za-z]+[\w\-\:\.]*$/.test(field.data.field_id) && !PROTECTED_VARS.includes(field.data.field_id)) {
ret.push({
code: 'CHK_MSG_PRG_FLD_020',
data: `invalid ${field.data.field_id} field_id`,
type: doc.studio_meta.not_in_use ? 'W' : 'E',
category: 'fieldset',
ref: field.id,
id: `${doc._id}-${field.id}`,
});
}
if (!name_counts_obj[field.data.field_id]) {
name_counts_obj[field.data.field_id] = 1;
} else {
name_counts_obj[field.data.field_id]++;
}
if (!hide_not_in_use_check) {
if (!find_field_in_progs(doc, field.data.field_id)) {
// find in Out parameters
var found_parameter_out_match;
if (!_.isEmpty(doc.properties.progParams)) {
for (let parameter_item of doc.properties.progParams) {
if (parameter_item.data.dir === 'out' && parameter_item.data.parameter === field.data.field_id) {
found_parameter_out_match = true;
break;
}
}
}
if (!found_parameter_out_match) {
ret.push({
code: 'CHK_MSG_PRG_FLD_022',
data: `field "${field.data.field_id}" not in use`,
type: 'W',
category: 'fieldset',
ref: field.id,
not_in_use: true,
id: `${doc._id}-${field.id}`,
});
}
}
}
}
if (!field?.data?.type) {
ret.push({
code: 'CHK_MSG_PRG_FLD_030',
data: `missing field type`,
type: doc.studio_meta.not_in_use ? 'W' : 'E',
category: 'fieldset',
ref: field.id,
id: `${doc._id}-${field.id}`,
});
return ret;
} else {
switch (field.data.type) {
case 'virtual':
break;
case 'exp':
if (!field?.props?.fieldValue || !field?.props?.propExpressions?.fieldValue) {
ret.push({
code: 'CHK_MSG_PRG_FLD_070',
data: `invalid value or value expression for ${field.data.field_id} field type Exp`,
type: doc.studio_meta.not_in_use ? 'W' : 'E',
category: 'fieldset',
ref: field.id,
id: `${doc._id}-${field.id}`,
});
}
break;
case 'table':
if (doc.properties.menuType === 'globals') {
ret.push({
code: 'CHK_MSG_PRG_FLD_040',
data: `invalid field type for ${field.data.field_id} in globals`,
type: doc.studio_meta.not_in_use ? 'W' : 'E',
category: 'fieldset',
ref: field.id,
id: `${doc._id}-${field.id}`,
});
} else {
// check against table
if (!doc.progDataSource || _.isEmpty(doc.progDataSource) || !doc.progDataSource.dataSourceType || doc.progDataSource.dataSourceType === 'none') {
ret.push({
code: 'CHK_MSG_PRG_FLD_300',
data: `undefined datasource for ${field.data.field_id} field`,
type: doc.studio_meta.not_in_use ? 'W' : 'E',
category: 'fieldset',
ref: field.id,
id: `${doc._id}-${field.id}`,
});
return ret;
} else {
var ret_dsc = check_prog_datasource(doc);
if (ret_dsc.length) return ret;
var _datasource_table_arr = progs_obj?.[doc.progDataSource.dataSourceTableId]?.tableFields;
if (!_datasource_table_arr) {
ret.push({
code: 'CHK_MSG_PRG_FLD_310',
data: `no fields found in datasource table ${doc.progDataSource.dataSourceTableId} `,
type: doc.studio_meta.not_in_use ? 'W' : 'E',
category: 'fieldset',
ref: field.id,
id: `${doc._id}-${field.id}`,
});
return ret;
}
if (!find_item_by_key(_datasource_table_arr, 'field_id', field.data.field_id)) {
if (!doc.progDataSource.dataSourceReduce || field.data.field_id !== 'REDUCE_VALUE') {
ret.push({
code: 'CHK_MSG_PRG_FLD_320',
data: `invalid field ${field.data.field_id} in datasource table`,
type: doc.studio_meta.not_in_use ? 'W' : 'E',
category: 'fieldset',
ref: field.id,
id: `${doc._id}-${field.id}`,
});
return ret;
}
}
}
}
break;
default:
ret.push({
code: 'CHK_MSG_PRG_FLD_060',
data: `invalid field type for ${field.data.field_id}`,
type: doc.studio_meta.not_in_use ? 'W' : 'E',
category: 'fieldset',
ref: field.id,
id: `${doc._id}-${field.id}`,
});
return ret;
break;
}
}
if (!field?.props?.fieldType && !field?.props?.propExpressions?.fieldType) {
ret.push({
code: 'CHK_MSG_PRG_FLD_130',
data: `missing field fieldType`,
type: doc.studio_meta.not_in_use ? 'W' : 'E',
category: 'fieldset',
ref: field.id,
id: `${doc._id}-${field.id}`,
});
return ret;
} else if (field.props.fieldType) {
switch (field.props.fieldType) {
case 'string':
if (field?.props?.fieldValue && typeof field.props.fieldValue !== 'string') {
ret.push({
code: 'CHK_MSG_PRG_FLD_200',
data: `invalid value type ${field.props.fieldType} for ${field.data.field_id} field`,
type: doc.studio_meta.not_in_use ? 'W' : 'E',
category: 'fieldset',
ref: field.id,
id: `${doc._id}-${field.id}`,
});
}
break;
case 'number':
case 'boolean':
if (field?.props?.fieldValue) {
if (_.isNaN(_.toNumber(field?.props?.fieldValue))) {
ret.push({
code: 'CHK_MSG_PRG_FLD_210',
data: `invalid value type ${field.props.fieldType} for ${field.data.field_id} field`,
type: 'E',
category: 'fieldset',
ref: field.id,
id: `${doc._id}-${field.id}`,
});
}
}
break;
case 'object':
case 'array':
if (field?.props?.fieldValue) {
try {
JSON5.parse(field.props.fieldValue);
} catch (err) {
ret.push({
code: 'CHK_MSG_PRG_FLD_220',
data: `invalid value type "${field.props.fieldType}" for "${field.data.field_id}" field`,
type: doc.studio_meta.not_in_use ? 'W' : 'E',
category: 'fieldset',
ref: field.id,
id: `${doc._id}-${field.id}`,
});
}
}
break;
default:
ret.push({
code: 'CHK_MSG_PRG_FLD_160',
data: `invalid field fieldType for ${field.data.field_id}`,
type: doc.studio_meta.not_in_use ? 'W' : 'E',
category: 'fieldset',
ref: field.id,
id: `${doc._id}-${field.id}`,
});
return ret;
break;
}
}
// if (!_.isEmpty(field.workflow)) {}
// propExpressions
if (!_.isEmpty(field.workflow)) {
ret = [...ret, ...check_prog_triggers(doc, field.workflow, 'field', field.id, field.data.field_id)];
}
}
for (let [key, val] of Object.entries(name_counts_obj)) {
if (val > 1) {
ret.push({
code: 'CHK_MSG_PRG_FLD_200',
data: `duplicate field id for ${key}`,
type: doc.studio_meta.not_in_use ? 'W' : 'E',
category: 'fieldset',
id: `${doc._id}-${key}`, // was `${doc._id}-${field.id}-${key}`,
});
}
}
}
return ret;
};
const check_prog_events = function (doc) {
var ret = [];
if (!doc.progEvents || _.isEmpty(doc.progEvents)) return ret;
var name_counts_obj = {};
for (let event_item of doc.progEvents) {
switch (event_item?.data?.type) {
case 'user_defined':
if (!event_item.data.event_name) {
ret.push({
code: 'CHK_MSG_PRG_EVT_010',
data: `missing event name for ${event_item.id}`,
type: 'W',
category: 'event',
ref: event_item.id,
id: `${doc._id}-${event_item.id}`,
});
continue;
} else {
if (!/^[A-Za-z]+[\w\-\:\.]*$/.test(event_item.data.event_name)) {
ret.push({
code: 'CHK_MSG_PRG_EVT_020',
data: `invalid event name for event "${event_item.data.event_name || event_item.data.type}"`,
type: doc.studio_meta.not_in_use ? 'W' : 'E',
category: 'event',
ref: event_item.id,
id: `${doc._id}-${event_item.id}`,
});
}
// search if in use
if (!hide_not_in_use_check) {
const ret_event_id_use = find_trigger_property_value_in_progs(doc, ['raise_event'], 'event', event_item.data.event_name);
if (!ret_event_id_use?.length) {
ret.push({
code: 'CHK_MSG_PRG_EVT_112',
data: `event "${event_item.data.event_name}" not in use`,
type: 'W',
category: 'event',
ref: event_item.id,
not_in_use: true,
id: `${doc._id}-${event_item.id}`,
});
}
}
}
if (event_item.data.condition) {
ret = [...ret, ...check_prog_expression(doc, event_item.data.condition, event_item.id, 'condition')];
} else {
if (!name_counts_obj[event_item.id]) {
name_counts_obj[event_item.id] = 1;
} else {
name_counts_obj[event_item.id]++;
}
}
// validate parameters TBD
break;
case 'screen_ready':
case 'client_interval':
if (doc.properties.menuType !== 'component') {
ret.push({
code: 'CHK_MSG_PRG_EVT_030',
data: `event type "${event_item.data.type}" only allowed for "${doc.properties.menuType}" in event "${event_item.data.event_name || event_item.data.type}"`,
type: doc.studio_meta.not_in_use ? 'W' : 'E',
category: 'event',
ref: event_item.id,
id: `${doc._id}-${event_item.id}`,
});
}
if (event_item.data.type === 'client_interval' && !event_item.data.properties) {
ret.push({
code: 'CHK_MSG_PRG_EVT_035',
data: `empty interval property in event "${event_item.data.event_name || event_item.data.type}"`,
type: doc.studio_meta.not_in_use ? 'W' : 'E',
category: 'event',
ref: event_item.id,
id: `${doc._id}-${event_item.id}`,
});
}
break;
// case "after_init":
// case "before_close":
case 'on_load':
case 'on_exit':
case 'before_record':
case 'after_record':
case 'record_not_found':
case 'locate_not_found':
break;
default:
ret.push({
code: 'CHK_MSG_PRG_EVT_018',
data: `invalid event type for ${event_item.id}`,
type: doc.studio_meta.not_in_use ? 'W' : 'E',
category: 'event',
ref: e