@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.
13,431 lines • 466 kB
JavaScript
'use strict';
if (typeof IS_DOCKER === 'undefined' || typeof IS_PROCESS_SERVER === 'undefined') {
var SESSION_OBJ = {};
var DOCS_OBJ = {};
}
var glb = {};
var func = {};
func.UI = {};
func.GLB = {};
func.mobile = {};
func.runtime = {};
func.runtime.bind = {};
func.runtime.program = {};
func.runtime.resources = {};
func.runtime.render = {};
func.runtime.session = {};
func.runtime.workers = {};
func.runtime.ui = {};
func.runtime.widgets = {};
glb.IS_STUDIO = null;
// Lodash replacement utilities
var xu_isEmpty = function (val) {
if (val == null) return true;
if (typeof val === 'boolean' || typeof val === 'number') return !val;
if (typeof val === 'string' || Array.isArray(val)) return val.length === 0;
if (val instanceof Map || val instanceof Set) return val.size === 0;
return Object.keys(val).length === 0;
};
var xu_isEqual = function (a, b) {
if (a === b) return true;
if (a == null || b == null) return a === b;
if (typeof a !== typeof b) return false;
if (a instanceof Date && b instanceof Date) return a.getTime() === b.getTime();
if (typeof a !== 'object') return false;
var keysA = Object.keys(a);
var keysB = Object.keys(b);
if (keysA.length !== keysB.length) return false;
for (var i = 0; i < keysA.length; i++) {
if (!Object.prototype.hasOwnProperty.call(b, keysA[i]) || !xu_isEqual(a[keysA[i]], b[keysA[i]])) return false;
}
return true;
};
var xu_get = function (obj, path, defaultVal) {
var keys = typeof path === 'string' ? path.split('.') : path;
var result = obj;
for (var i = 0; i < keys.length; i++) {
if (result == null) return defaultVal;
result = result[keys[i]];
}
return result === undefined ? defaultVal : result;
};
var xu_set = function (obj, path, value) {
var keys = typeof path === 'string' ? path.split('.') : path;
var current = obj;
for (var i = 0; i < keys.length - 1; i++) {
if (current[keys[i]] == null) current[keys[i]] = {};
current = current[keys[i]];
}
current[keys[keys.length - 1]] = value;
return obj;
};
var xu_clone = function (value) {
if (Array.isArray(value)) return value.slice();
if (value && typeof value === 'object') return { ...value };
return value;
};
var xu_cloneDeep = function (value) {
if (typeof structuredClone === 'function') {
try {
return structuredClone(value);
} catch (error) {}
}
if (Array.isArray(value)) {
return value.map(function (item) {
return xu_cloneDeep(item);
});
}
if (value && typeof value === 'object') {
var ret = {};
Object.keys(value).forEach(function (key) {
ret[key] = xu_cloneDeep(value[key]);
});
return ret;
}
return value;
};
var xu_map = function (collection, iteratee) {
if (!collection) return [];
if (Array.isArray(collection)) {
return collection.map(function (value, index) {
return iteratee ? iteratee(value, index) : value;
});
}
return Object.keys(collection).map(function (key) {
return iteratee ? iteratee(collection[key], key) : collection[key];
});
};
var xu_forEach = function (collection, iteratee) {
if (!collection || typeof iteratee !== 'function') return collection;
if (Array.isArray(collection)) {
collection.forEach(function (value, index) {
iteratee(value, index);
});
return collection;
}
Object.keys(collection).forEach(function (key) {
iteratee(collection[key], key);
});
return collection;
};
var xu_find = function (collection, predicate) {
if (!collection || typeof predicate !== 'function') return undefined;
var values = Array.isArray(collection)
? collection
: Object.keys(collection).map(function (key) {
return collection[key];
});
for (var i = 0; i < values.length; i++) {
if (predicate(values[i], i)) return values[i];
}
};
var xu_findIndex = function (collection, predicate) {
if (!Array.isArray(collection) || typeof predicate !== 'function') return -1;
for (var i = 0; i < collection.length; i++) {
if (predicate(collection[i], i)) return i;
}
return -1;
};
var xu_reduce = function (collection, iteratee, accumulator) {
if (!collection || typeof iteratee !== 'function') return accumulator;
var keys = Array.isArray(collection) ? collection.map(function (_value, index) { return index; }) : Object.keys(collection);
var has_accumulator = arguments.length > 2;
var result = accumulator;
for (var i = 0; i < keys.length; i++) {
var key = keys[i];
var value = Array.isArray(collection) ? collection[key] : collection[key];
if (!has_accumulator) {
result = value;
has_accumulator = true;
continue;
}
result = iteratee(result, value, key);
}
return result;
};
var xu_debounce = function (callback, wait) {
var timeout_id = null;
return function () {
var args = arguments;
var context = this;
clearTimeout(timeout_id);
timeout_id = setTimeout(function () {
callback.apply(context, args);
}, wait || 0);
};
};
var xu_toStringSafe = function (value) {
if (typeof value === 'string') return value;
if (value == null) return '';
return String(value);
};
var xu_some = function (collection, predicate) {
if (!collection || typeof predicate !== 'function') return false;
var values = Array.isArray(collection)
? collection
: Object.keys(collection).map(function (key) {
return collection[key];
});
for (var i = 0; i < values.length; i++) {
if (predicate(values[i], i)) return true;
}
return false;
};
var xu_has = function (obj, key) {
return !!obj && Object.prototype.hasOwnProperty.call(obj, key);
};
var xu_runtime_global = typeof globalThis !== 'undefined' ? globalThis : {};
if (typeof xu_runtime_global._ === 'undefined') {
xu_runtime_global._ = {
clone: xu_clone,
cloneDeep: xu_cloneDeep,
debounce: xu_debounce,
each: xu_forEach,
find: xu_find,
findIndex: xu_findIndex,
forEach: xu_forEach,
get: xu_get,
has: xu_has,
isArray: Array.isArray,
isEmpty: xu_isEmpty,
map: xu_map,
reduce: xu_reduce,
some: xu_some,
toString: xu_toStringSafe,
// lodash type-checks + common helpers (lodash was removed from the runtime;
// programs still call these on the global `_`).
isBoolean: function (v) { return typeof v === 'boolean'; },
isString: function (v) { return typeof v === 'string'; },
isNumber: function (v) { return typeof v === 'number'; },
isFunction: function (v) { return typeof v === 'function'; },
isObject: function (v) { return v !== null && (typeof v === 'object' || typeof v === 'function'); },
isPlainObject: function (v) { return v !== null && typeof v === 'object' && (Object.getPrototypeOf(v) === Object.prototype || Object.getPrototypeOf(v) === null); },
isNil: function (v) { return v == null; },
isUndefined: function (v) { return v === undefined; },
isNull: function (v) { return v === null; },
isInteger: Number.isInteger,
isNaN: function (v) { return typeof v === 'number' && v !== v; },
keys: function (o) { return o ? Object.keys(o) : []; },
values: function (o) { return o ? Object.values(o) : []; },
size: function (o) { return o == null ? 0 : (typeof o.length === 'number' ? o.length : Object.keys(o).length); },
includes: function (c, v) { return c == null ? false : (typeof c.includes === 'function' ? c.includes(v) : Object.values(c).indexOf(v) > -1); },
filter: function (c, fn) { return (c ? (Array.isArray(c) ? c : Object.values(c)) : []).filter(function (x, i) { return fn(x, i); }); },
last: function (a) { return a && a.length ? a[a.length - 1] : undefined; },
first: function (a) { return a && a.length ? a[0] : undefined; },
head: function (a) { return a && a.length ? a[0] : undefined; },
uniq: function (a) { return a ? Array.from(new Set(a)) : []; },
compact: function (a) { return a ? a.filter(Boolean) : []; },
assign: Object.assign,
merge: function (t) { for (var i = 1; i < arguments.length; i++) Object.assign(t || {}, arguments[i]); return t; },
capitalize: function (s) { s = String(s == null ? '' : s); return s.charAt(0).toUpperCase() + s.slice(1).toLowerCase(); },
noop: function () {},
};
}
if (typeof _ === 'undefined') {
var _ = xu_runtime_global._;
}
var PROJECT_OBJ = {};
var APP_OBJ = {};
var SESSION_ID = null;
var EXP_BUSY = false;
glb.PROTECTED_VARS = ['_NULL', '_THIS', '_FOR_KEY', '_FOR_VAL', '_ROWNO', '_ROWID', '_ROWDOC', '_KEY', '_VAL'];
// glb.newRecord = 999999;
func.common = {};
func.runtime.platform = {
get_global: function (name) {
try {
if (typeof globalThis === 'undefined') {
return null;
}
return globalThis?.[name] || null;
} catch (error) {
return null;
}
},
has_window: function () {
return !!func.runtime.platform.get_window();
},
has_document: function () {
return !!func.runtime.platform.get_document();
},
get_window: function () {
return func.runtime.platform.get_global('window');
},
get_document: function () {
return func.runtime.platform.get_global('document');
},
get_location: function () {
const win = func.runtime.platform.get_window();
return win?.location || null;
},
get_navigator: function () {
const win = func.runtime.platform.get_window();
if (win?.navigator) {
return win.navigator;
}
return func.runtime.platform.get_global('navi' + 'gator');
},
is_html_element: function (value) {
const html_element = func.runtime.platform.get_global('HTML' + 'Element');
if (typeof html_element !== 'function') {
return false;
}
return value instanceof html_element;
},
get_storage: function (type) {
const win = func.runtime.platform.get_window();
const storage_key = type === 'session' ? 'session' + 'Storage' : 'local' + 'Storage';
try {
if (!win) {
return null;
}
return win?.[storage_key] || null;
} catch (error) {
return null;
}
},
get_storage_item: function (key, type) {
const storage = func.runtime.platform.get_storage(type);
if (!storage) {
return null;
}
try {
return storage.getItem(key);
} catch (error) {
return null;
}
},
set_storage_item: function (key, value, type) {
const storage = func.runtime.platform.get_storage(type);
if (!storage) {
return false;
}
try {
storage.setItem(key, value);
return true;
} catch (error) {
return false;
}
},
get_cookie_item: function (key) {
if (!key) {
return null;
}
const doc = func.runtime.platform.get_document();
const cookie_string = doc?.cookie;
if (!cookie_string) {
return null;
}
const cookie_entry = cookie_string.split('; ').find(function (cookie) {
return cookie.startsWith(key + '=');
});
if (!cookie_entry) {
return null;
}
return cookie_entry.split('=').slice(1).join('=') || null;
},
get_url_href: function () {
return func.runtime.platform.get_location()?.href || '';
},
get_url_search: function () {
return func.runtime.platform.get_location()?.search || '';
},
get_url_hash: function () {
return func.runtime.platform.get_location()?.hash || '';
},
get_host: function () {
return func.runtime.platform.get_location()?.host || '';
},
get_hostname: function () {
return func.runtime.platform.get_location()?.hostname || '';
},
get_device_uuid: function () {
const win = func.runtime.platform.get_window();
return win?.device?.uuid || null;
},
get_device_name: function () {
const win = func.runtime.platform.get_window();
return win?.device?.name || null;
},
get_inner_size: function () {
const win = func.runtime.platform.get_window();
return {
width: win?.innerWidth || 0,
height: win?.innerHeight || 0,
};
},
add_window_listener: function (name, handler) {
const win = func.runtime.platform.get_window();
if (!win?.addEventListener) {
return false;
}
win.addEventListener(name, handler);
return true;
},
dispatch_body_event: function (event) {
const doc = func.runtime.platform.get_document();
if (!doc?.body?.dispatchEvent) {
return false;
}
doc.body.dispatchEvent(event);
return true;
},
reload_top_window: function () {
const win = func.runtime.platform.get_window();
if (!win?.top?.location?.reload) {
return false;
}
win.top.location.reload();
return true;
},
get_service_worker: function () {
const nav = func.runtime.platform.get_navigator();
return nav?.serviceWorker || null;
},
has_service_worker: function () {
return !!func.runtime.platform.get_service_worker();
},
register_service_worker: function (script_url) {
const service_worker = func.runtime.platform.get_service_worker();
if (!service_worker?.register) {
return Promise.reject(new Error('serviceWorker is not available'));
}
return service_worker.register(script_url);
},
add_service_worker_listener: function (name, handler) {
const service_worker = func.runtime.platform.get_service_worker();
if (!service_worker?.addEventListener) {
return false;
}
service_worker.addEventListener(name, handler);
return true;
},
};
// ── Platform-agnostic event bus ──
// Works in browser, worker, and Node environments.
// In browser, bridge DOM events into this bus so core code never touches $(document) directly.
func.runtime.platform._event_bus = {};
func.runtime.platform.on = function (name, handler) {
if (!func.runtime.platform._event_bus[name]) {
func.runtime.platform._event_bus[name] = [];
}
func.runtime.platform._event_bus[name].push(handler);
};
func.runtime.platform.off = function (name, handler) {
const handlers = func.runtime.platform._event_bus[name];
if (!handlers) return;
if (!handler) {
delete func.runtime.platform._event_bus[name];
return;
}
const index = handlers.indexOf(handler);
if (index !== -1) {
handlers.splice(index, 1);
}
};
func.runtime.platform._emitting = {};
func.runtime.platform.emit = function (name, data) {
// re-entrancy guard: prevent infinite loops when DOM bridge triggers the same event
if (func.runtime.platform._emitting[name]) return;
func.runtime.platform._emitting[name] = true;
try {
const handlers = func.runtime.platform._event_bus[name];
if (handlers) {
for (let i = 0; i < handlers.length; i++) {
handlers[i](data);
}
}
if (typeof func.runtime.platform.dispatch_document_event === 'function') {
func.runtime.platform.dispatch_document_event(name, data);
}
} finally {
func.runtime.platform._emitting[name] = false;
}
};
// ── Platform helpers for DOM-independent resource loading ──
func.runtime.platform.apply_element_attributes = function (node, attributes, excluded_keys = []) {
if (!node?.setAttribute || !attributes) {
return node;
}
const excluded = new Set(excluded_keys || []);
const attr_keys = Object.keys(attributes);
for (let index = 0; index < attr_keys.length; index++) {
const key = attr_keys[index];
if (!key || excluded.has(key)) {
continue;
}
const value = attributes[key];
if (value === false || typeof value === 'undefined') {
continue;
}
node.setAttribute(key, value === null ? '' : `${value}`);
}
return node;
};
func.runtime.platform.load_script = function (url, type, callback, attributes) {
const normalized_url = typeof url === 'string' ? url.trim() : '';
if (!normalized_url || normalized_url === 'undefined' || normalized_url === 'null') {
if (callback) {
callback();
}
return null;
}
const doc = func.runtime.platform.get_document();
if (!doc?.createElement || !doc?.head?.appendChild) {
if (callback) {
callback();
}
return;
}
const find_existing_script = function () {
const asset_key = attributes?.['data-xuda-asset-key'];
const scripts = doc.querySelectorAll ? Array.from(doc.querySelectorAll('script')) : [];
return scripts.find(function (script) {
if (asset_key && script.getAttribute('data-xuda-asset-key') === asset_key) {
return true;
}
return script.getAttribute('src') === normalized_url;
}) || null;
};
const existing_script = find_existing_script();
if (existing_script) {
if (callback) {
if (existing_script.getAttribute('data-xuda-loaded') === 'true' || !url) {
callback();
} else {
existing_script.addEventListener('load', callback, { once: true });
existing_script.addEventListener('error', callback, { once: true });
}
}
return existing_script;
}
const script = doc.createElement('script');
script.src = normalized_url;
if (type) script.type = type;
func.runtime.platform.apply_element_attributes(script, attributes, ['src', 'type']);
script.onload = function () {
script.setAttribute('data-xuda-loaded', 'true');
if (callback) {
callback();
}
};
script.onerror = function () {
if (callback) {
callback();
}
};
doc.head.appendChild(script);
return script;
};
func.runtime.platform.load_css = function (href, attributes) {
const normalized_href = typeof href === 'string' ? href.trim() : '';
if (!normalized_href || normalized_href === 'undefined' || normalized_href === 'null') {
return null;
}
const doc = func.runtime.platform.get_document();
if (!doc?.createElement || !doc?.head) {
return;
}
try {
const asset_key = attributes?.['data-xuda-asset-key'];
const existing_links = doc.querySelectorAll ? Array.from(doc.querySelectorAll('link')) : [];
const existing = existing_links.find(function (link) {
if (asset_key && link.getAttribute('data-xuda-asset-key') === asset_key) {
return true;
}
return link.getAttribute('href') === normalized_href;
});
if (existing) return existing;
} catch (err) {
return;
}
const link = doc.createElement('link');
link.rel = 'stylesheet';
link.type = 'text/css';
link.href = normalized_href;
func.runtime.platform.apply_element_attributes(link, attributes, ['href']);
doc.head.insertBefore(link, doc.head.firstChild);
return link;
};
func.runtime.platform.remove_js_css = function (filename, filetype) {
const doc = func.runtime.platform.get_document();
if (!doc?.getElementsByTagName) return;
const tagName = filetype === 'js' ? 'script' : filetype === 'css' ? 'link' : 'none';
const attr = filetype === 'js' ? 'src' : filetype === 'css' ? 'href' : 'none';
const elements = doc.getElementsByTagName(tagName);
for (let i = elements.length - 1; i >= 0; i--) {
if (elements[i] && elements[i].getAttribute(attr) != null && elements[i].getAttribute(attr).indexOf(filename) !== -1) {
elements[i].parentNode.removeChild(elements[i]);
}
}
};
func.runtime.platform.inject_css = function (cssText) {
const doc = func.runtime.platform.get_document();
if (!doc?.createElement || !doc?.head?.appendChild || !cssText) return;
const style = doc.createElement('style');
style.type = 'text/css';
style.textContent = cssText;
doc.head.appendChild(style);
};
func.runtime.platform.set_title = function (title) {
const doc = func.runtime.platform.get_document();
if (doc) {
doc.title = title;
}
};
func.runtime.platform.set_cursor = function (element, cursor) {
const node = func.runtime.ui?.get_first_node ? func.runtime.ui.get_first_node(element) : element;
if (node?.style) {
node.style.cursor = cursor;
}
};
func.runtime.program.normalize_doc_for_runtime = function (doc) {
if (!doc || doc.__xudaRuntimeNormalized || !Array.isArray(doc.progUi) || !doc.progUi.length) {
return doc;
}
const normalize_tag_name = function (tag_name) {
return `${tag_name || ''}`.trim().toLowerCase();
};
const merge_attributes = function (target, source) {
const merged = { ...(target || {}) };
const source_attributes = source || {};
const keys = Object.keys(source_attributes);
for (let index = 0; index < keys.length; index++) {
const key = keys[index];
const value = source_attributes[key];
if (typeof value === 'undefined') {
continue;
}
if (key === 'class' && merged.class && value) {
const next_value = `${merged.class} ${value}`.trim();
merged.class = Array.from(new Set(next_value.split(/\s+/).filter(Boolean))).join(' ');
continue;
}
if (key === 'style' && merged.style && value) {
merged.style = `${merged.style}; ${value}`.trim();
continue;
}
if (typeof merged[key] === 'undefined') {
merged[key] = value;
}
}
return merged;
};
const get_attribute_source = function (source) {
if (!source) {
return {};
}
if (typeof source === 'string') {
try {
const parsed = JSON.parse(source);
return parsed && typeof parsed === 'object' && !Array.isArray(parsed) ? parsed : {};
} catch (_) {
return {};
}
}
return typeof source === 'object' && !Array.isArray(source) ? source : {};
};
const normalize_node_attributes = function (node) {
let attributes = get_attribute_source(node?.attributes);
attributes = merge_attributes(attributes, get_attribute_source(node?.attributes_raw_obj));
attributes = merge_attributes(attributes, get_attribute_source(node?.attributes_raw));
return attributes;
};
const normalize_nodes = function (nodes, state) {
const normalized_nodes = [];
for (let index = 0; index < (nodes || []).length; index++) {
const node = nodes[index];
if (!node || typeof node !== 'object') {
continue;
}
const tag_name = normalize_tag_name(node.tagName);
if (tag_name === '!doctype') {
state.changed = true;
continue;
}
if (tag_name === 'html') {
state.changed = true;
state.root_attributes = merge_attributes(state.root_attributes, normalize_node_attributes(node));
normalized_nodes.push.apply(normalized_nodes, normalize_nodes(node.children, state));
continue;
}
if (tag_name === 'head') {
state.changed = true;
normalized_nodes.push.apply(normalized_nodes, normalize_nodes(node.children, state));
continue;
}
if (tag_name === 'body') {
state.changed = true;
state.root_attributes = merge_attributes(state.root_attributes, normalize_node_attributes(node));
normalized_nodes.push.apply(normalized_nodes, normalize_nodes(node.children, state));
continue;
}
let next_node = node;
const merged_node_attributes = normalize_node_attributes(node);
if (!xu_isEqual(merged_node_attributes, node.attributes || {})) {
next_node = {
...next_node,
attributes: merged_node_attributes,
};
state.changed = true;
}
if (Array.isArray(node.children) && node.children.length) {
const next_children = normalize_nodes(node.children, state);
if (next_children !== node.children) {
next_node = {
...next_node,
children: next_children,
};
state.changed = true;
}
}
normalized_nodes.push(next_node);
}
return normalized_nodes;
};
const [root_node, ...extra_nodes] = doc.progUi;
if (!root_node || typeof root_node !== 'object') {
return doc;
}
const state = {
changed: false,
root_attributes: {},
};
const root_node_attributes = normalize_node_attributes(root_node);
if (!xu_isEqual(root_node_attributes, root_node.attributes || {})) {
state.changed = true;
}
const normalized_children = normalize_nodes([...(root_node.children || []), ...extra_nodes], state);
const merged_attributes = merge_attributes(root_node_attributes, state.root_attributes);
if (!state.changed && !Object.keys(state.root_attributes).length) {
doc.__xudaRuntimeNormalized = true;
return doc;
}
return {
...doc,
__xudaRuntimeNormalized: true,
progUi: [
{
...root_node,
attributes: merged_attributes,
children: normalized_children,
},
],
};
};
func.runtime.env = {
get_url_params: function () {
const search = func.runtime.platform.get_url_search();
return new URLSearchParams(search);
},
get_url_parameters_object: function () {
const search_params = func.runtime.env.get_url_params();
const parameters = {};
for (const [key, value] of search_params.entries()) {
parameters[key] = value;
}
return parameters;
},
get_default_session_value: function (key) {
switch (key) {
case 'domain':
return func.runtime.platform.get_host();
case 'engine_mode':
return 'miniapp';
case 'app_id':
return 'unknown';
default:
return null;
}
},
};
func.runtime.session.create_tab_id = function () {
const session_storage = func.runtime.platform.get_storage('session');
const local_storage = func.runtime.platform.get_storage('local');
var page_tab_id = session_storage?.getItem('tabID');
if (page_tab_id == null) {
var local_tab_id = local_storage?.getItem('tabID');
page_tab_id = local_tab_id == null ? 1 : Number(local_tab_id) + 1;
func.runtime.platform.set_storage_item('tabID', page_tab_id, 'local');
func.runtime.platform.set_storage_item('tabID', page_tab_id, 'session');
}
return page_tab_id;
};
func.runtime.session.get_fingerprint = function (components, instance_id) {
const device_uuid = func.runtime.platform.get_device_uuid();
if (func.utils.get_device() && device_uuid) {
if (instance_id) {
return instance_id + device_uuid;
}
return device_uuid;
}
const fingerprint_id = Fingerprint2.x64hash128(
components
.map(function (pair) {
return pair.value;
})
.join(),
31,
);
if (instance_id) {
return instance_id + fingerprint_id + func.runtime.session.create_tab_id();
}
return fingerprint_id;
};
func.runtime.session.create_state = function (SESSION_ID, options) {
const runtime_host = func.runtime.platform.get_host();
SESSION_OBJ[SESSION_ID] = {
JOB_NO: 1000,
opt: options.opt,
root_element: options.root_element,
worker_type: options.worker_type,
api_callback: options.api_callback,
CODE_BUNDLE: options.code_bundle,
SLIM_BUNDLE: options.slim_bundle,
WORKER_OBJ: {
jobs: [],
num: 1000,
stat: null,
},
DS_GLB: {},
SYS_GLOBAL_OBJ_FIREBASE_AUTH_INFO: {
token: '',
first_name: '',
last_name: '',
email: '',
user_id: '',
picture: '',
verified_email: '',
locale: '',
error_code: '',
error_msg: '',
},
SYS_GLOBAL_OBJ_CLIENT_INFO: {
fingerprint: '',
device: '',
user_agent: '',
browser_version: '',
browser_name: '',
engine_version: '',
client_ip: '',
engine_name: '',
os_name: '',
os_version: '',
device_model: '',
device_vendor: '',
device_type: '',
screen_current_resolution_x: '',
screen_current_resolution_y: '',
screen_available_resolution_x: '',
screen_available_resolution_y: '',
language: '',
time_zone: '',
cpu_architecture: '',
uuid: '',
},
PUSH_NOTIFICATION_GRANTED: null,
FIREBASE_TOKEN_ID: null,
USR_OBJ: {},
debug_js: null,
DS_UI_EVENTS_GLB: {},
host: runtime_host,
req_id: 0,
build_info: {},
CACHE_REQ: {},
url_params: {
...func.common.getParametersFromUrl(),
...options.url_params,
},
};
func.runtime.workers.ensure_registry(SESSION_ID);
return SESSION_OBJ[SESSION_ID];
};
func.runtime.session.is_slim = function (SESSION_ID) {
const session = typeof SESSION_ID === 'undefined' || SESSION_ID === null ? null : SESSION_OBJ?.[SESSION_ID];
if (session && typeof session.SLIM_BUNDLE !== 'undefined') {
return !!session.SLIM_BUNDLE;
}
return !!glb.SLIM_BUNDLE;
};
func.runtime.session.set_default_value = function (_session, key, value) {
_session[key] = value || func.runtime.env.get_default_session_value(key);
return _session[key];
};
func.runtime.session.populate_client_info = function (_session, components) {
const _client_info = _session.SYS_GLOBAL_OBJ_CLIENT_INFO;
const platform = func.runtime.platform;
const { engine_mode } = _session;
_client_info.fingerprint = func.runtime.session.get_fingerprint(components);
if (engine_mode === 'live_preview') {
const inner_size = platform.get_inner_size();
_client_info.screen_current_resolution_x = inner_size.width;
_client_info.screen_current_resolution_y = inner_size.height;
_client_info.screen_available_resolution_x = inner_size.width;
_client_info.screen_available_resolution_y = inner_size.height;
} else {
_client_info.screen_current_resolution_x = components[6].value[0];
_client_info.screen_current_resolution_y = components[6].value[1];
_client_info.screen_available_resolution_x = components[7].value[0];
_client_info.screen_available_resolution_y = components[7].value[1];
}
const client = new ClientJS();
_client_info.device = func.utils.get_device();
const browser_data = client.getBrowserData();
_client_info.user_agent = browser_data.ua;
_client_info.browser_version = browser_data.browser.name;
_client_info.browser_name = browser_data.browser.version;
_client_info.engine_version = browser_data.engine.name;
_client_info.engine_name = browser_data.engine.version;
_client_info.os_name = browser_data.os.name;
_client_info.os_version = browser_data.os.version;
_client_info.device_model = browser_data.device.name;
_client_info.device_vendor = browser_data.device.name;
_client_info.device_type = browser_data.device.name;
_client_info.language = client.getLanguage();
_client_info.time_zone = client.getTimeZone();
_client_info.cpu_architecture = browser_data.cpu.architecture;
if (['android', 'ios', 'windows', 'macos', 'linux', 'live_preview'].includes(engine_mode) && func.utils.get_device()) {
_client_info.uuid = platform.get_device_uuid();
const device_name = platform.get_device_name();
if (device_name) {
_client_info.device_name = device_name;
}
}
return _client_info;
};
func.runtime.workers.ensure_registry = function (SESSION_ID) {
if (!WEB_WORKER[SESSION_ID]) {
WEB_WORKER[SESSION_ID] = {};
}
return WEB_WORKER[SESSION_ID];
};
func.runtime.workers.get_registry_entry = function (SESSION_ID, worker_id) {
return func.runtime.workers.ensure_registry(SESSION_ID)?.[worker_id] || null;
};
func.runtime.workers.set_registry_entry = function (SESSION_ID, worker_id, entry) {
const worker_registry = func.runtime.workers.ensure_registry(SESSION_ID);
worker_registry[worker_id] = entry;
return worker_registry[worker_id];
};
func.runtime.workers.build_worker_name = function (glb_worker_type, session, prog_obj, worker_id, build_id) {
return (
`${typeof session.SLIM_BUNDLE === 'undefined' || !session.SLIM_BUNDLE ? '' : 'Slim '}${prog_obj.menuName} worker` +
' ' +
glb_worker_type +
': #' +
worker_id.toString() +
' ' +
(build_id || '') +
' ' +
session.domain
);
};
func.runtime.workers.is_server_transport = function (session) {
return !!(RUNTIME_SERVER_WEBSOCKET && RUNTIME_SERVER_WEBSOCKET_CONNECTED && (!session.opt.app_computing_mode || session.opt.app_computing_mode === 'server'));
};
func.runtime.workers.send_message = function (SESSION_ID, worker_id, session, msg, process_pid) {
const registry_entry = func.runtime.workers.get_registry_entry(SESSION_ID, worker_id);
if (!registry_entry?.worker) {
return false;
}
if (func.runtime.workers.is_server_transport(session)) {
if (process_pid) {
msg.process_pid = process_pid;
}
registry_entry.worker.emit('message', msg);
return true;
}
registry_entry.worker.postMessage(msg);
return true;
};
func.runtime.workers.set_promise = function (SESSION_ID, worker_id, promise_queue_id, value) {
const registry_entry = func.runtime.workers.get_registry_entry(SESSION_ID, worker_id);
if (!registry_entry) {
return null;
}
registry_entry.promise_queue[promise_queue_id] = value;
return registry_entry.promise_queue[promise_queue_id];
};
func.runtime.workers.get_promise = function (SESSION_ID, worker_id, promise_queue_id) {
const registry_entry = func.runtime.workers.get_registry_entry(SESSION_ID, worker_id);
if (!registry_entry) {
return null;
}
return registry_entry.promise_queue[promise_queue_id];
};
func.runtime.workers.delete_promise = function (SESSION_ID, worker_id, promise_queue_id) {
const registry_entry = func.runtime.workers.get_registry_entry(SESSION_ID, worker_id);
if (!registry_entry?.promise_queue) {
return false;
}
delete registry_entry.promise_queue[promise_queue_id];
return true;
};
func.runtime.render.clone_runtime_options = function (value) {
if (typeof structuredClone === 'function') {
try {
return structuredClone(value);
} catch (_) {}
}
if (Array.isArray(value)) {
return value.map(function (item) {
return func.runtime.render.clone_runtime_options(item);
});
}
if (value && typeof value === 'object') {
const cloned = {};
const keys = Object.keys(value);
for (let index = 0; index < keys.length; index++) {
const key = keys[index];
cloned[key] = func.runtime.render.clone_runtime_options(value[key]);
}
return cloned;
}
return value;
};
func.runtime.render.normalize_runtime_bootstrap = function (raw_options = {}) {
const options = raw_options || {};
let app_computing_mode = options.app_computing_mode || '';
let app_render_mode = options.app_render_mode || '';
let app_client_activation = options.app_client_activation || '';
let ssr_payload = options.ssr_payload || null;
if (typeof ssr_payload === 'string') {
try {
ssr_payload = JSON.parse(ssr_payload);
} catch (_) {
ssr_payload = null;
}
}
if (ssr_payload && typeof ssr_payload === 'object') {
ssr_payload = func.runtime.render.clone_runtime_options(ssr_payload);
}
if (!app_computing_mode) {
if (app_render_mode === 'ssr_first_page' || app_render_mode === 'ssr_full') {
app_computing_mode = 'server';
} else {
app_computing_mode = 'main';
}
}
switch (app_computing_mode) {
case 'main':
app_render_mode = 'csr';
app_client_activation = 'none';
break;
case 'worker':
app_render_mode = 'csr';
app_client_activation = 'none';
break;
default:
app_computing_mode = 'server';
if (app_render_mode !== 'ssr_full') {
app_render_mode = 'ssr_first_page';
}
app_client_activation = app_render_mode === 'ssr_full' ? 'hydrate' : 'takeover';
break;
}
if (ssr_payload && typeof ssr_payload === 'object') {
if (!ssr_payload.app_render_mode) {
ssr_payload.app_render_mode = app_render_mode;
}
if (!ssr_payload.app_client_activation) {
ssr_payload.app_client_activation = app_client_activation;
}
if (!ssr_payload.app_computing_mode) {
ssr_payload.app_computing_mode = app_computing_mode;
}
}
return {
app_computing_mode,
app_render_mode,
app_client_activation,
ssr_payload,
};
};
func.runtime.render.apply_runtime_bootstrap_defaults = function (target = {}) {
const normalized = func.runtime.render.normalize_runtime_bootstrap(target);
target.app_computing_mode = normalized.app_computing_mode;
target.app_render_mode = normalized.app_render_mode;
target.app_client_activation = normalized.app_client_activation;
target.ssr_payload = normalized.ssr_payload;
return normalized;
};
func.runtime.render.is_server_render_mode = function (target = {}) {
const normalized = func.runtime.render.normalize_runtime_bootstrap(target?.opt || target);
return normalized.app_computing_mode === 'server' && normalized.app_render_mode !== 'csr';
};
func.runtime.render.is_takeover_mode = function (target = {}) {
const normalized = func.runtime.render.normalize_runtime_bootstrap(target?.opt || target);
return normalized.app_client_activation === 'takeover';
};
func.runtime.render.is_hydration_mode = function (target = {}) {
const normalized = func.runtime.render.normalize_runtime_bootstrap(target?.opt || target);
return normalized.app_client_activation === 'hydrate';
};
func.runtime.render.get_ssr_payload = function (target = {}) {
if (target?.opt?.ssr_payload) {
return target.opt.ssr_payload;
}
if (target?.ssr_payload) {
return target.ssr_payload;
}
const win = func.runtime.platform.get_window();
return win?.__XUDA_SSR__ || null;
};
func.runtime.render.should_use_ssr_payload = function (SESSION_ID, paramsP) {
const session = SESSION_OBJ?.[SESSION_ID];
const payload = func.runtime.render.get_ssr_payload(session);
if (!payload || payload._consumed) {
return false;
}
if (paramsP?.prog_id && payload.prog_id && payload.prog_id !== paramsP.prog_id) {
return false;
}
return true;
};
func.runtime.render.mark_ssr_payload_consumed = function (SESSION_ID) {
const session = SESSION_OBJ?.[SESSION_ID];
const payload = func.runtime.render.get_ssr_payload(session);
if (!payload || typeof payload !== 'object') {
return false;
}
payload._consumed = true;
return true;
};
func.runtime.render.get_root_data_system = function (SESSION_ID) {
return SESSION_OBJ[SESSION_ID]?.DS_GLB?.[0]?.data_system || null;
};
func.runtime.render.resolve_xu_for_source = async function (SESSION_ID, dsSessionP, value) {
let arr = value;
let reference_source_obj;
const normalized_reference = typeof value === 'string' && value.startsWith('@') ? value.substring(1) : value;
const _progFields = await func.datasource.get_progFields(SESSION_ID, dsSessionP);
let view_field_obj = func.common.find_item_by_key(_progFields, 'field_id', normalized_reference);
if (view_field_obj || normalized_reference !== value) {
reference_source_obj = await func.datasource.get_value(SESSION_ID, normalized_reference, dsSessionP);
arr = reference_source_obj?.ret?.value;
} else {
if (typeof value === 'string') {
arr = eval(value.replaceAll('\\', ''));
}
if (typeof arr === 'number') {
arr = Array.from(Array(arr).keys());
}
}
return {
arr,
reference_source_obj,
};
};
func.runtime.render.apply_iterate_value_to_ds = function (SESSION_ID, dsSessionP, currentRecordId, progFields, field_id, value, is_dynamic_field) {
if (is_dynamic_field) {
func.datasource.add_dynamic_field_to_ds(SESSION_ID, dsSessionP, field_id, value);
return true;
}
let view_field_obj = func.common.find_item_by_key(progFields || [], 'field_id', field_id);
if (!view_field_obj) {
console.error('field not exist in dataset for xu-for method');
return false;
}
let _ds = SESSION_OBJ[SESSION_ID].DS_GLB[dsSessionP];
try {
const row_idx = func.common.find_ROWID_idx(_ds, currentRecordId);
_ds.data_feed.rows[row_idx][field_id] = value;
return true;
} catch (err) {
console.error(err);
return false;
}
};
func.runtime.render.build_iterate_info = function (options) {
return {
_val: options._val,
_key: options._key,
iterator_key: options.iterator_key,
iterator_val: options.iterator_val,
is_key_dynamic_field: options.is_key_dynamic_field,
is_val_dynamic_field: options.is_val_dynamic_field,
reference_source_obj: options.reference_source_obj,
};
};
func.runtime.render.apply_iterate_info_to_current_record = function (SESSION_ID, dsSessionP, currentRecordId, progFields, iterate_info) {
if (!iterate_info) {
return false;
}
func.runtime.render.apply_iterate_value_to_ds(SESSION_ID, dsSessionP, currentRecordId, progFields, iterate_info.iterator_key, iterate_info._key, iterate_info.is_key_dynamic_field);
func.runtime.render.apply_iterate_value_to_ds(SESSION_ID, dsSessionP, currentRecordId, progFields, iterate_info.iterator_val, iterate_info._val, iterate_info.is_val_dynamic_field);
return true;
};
func.runtime.render.sync_iterate_info_to_dataset = function (_ds, iterate_info) {
if (!iterate_info) {
return false;
}
const sync_field = function (field_id, value, is_dynamic_field) {
if (is_dynamic_field) {
_ds.dynamic_fields[field_id].value = value;
return true;
}
try {
const row_idx = func.common.find_ROWID_idx(_ds, _ds.currentRecordId);
_ds.data_feed.rows[row_idx][field_id] = value;
return true;
} catch (err) {
console.error(err);
return false;
}
};
sync_field(iterate_info.iterator_key, iterate_info._key, iterate_info.is_key_dynamic_field);
sync_field(iterate_info.iterator_val, iterate_info._val, iterate_info.is_val_dynamic_field);
return true;
};
func.runtime.program.get_params_obj = async function (SESSION_ID, prog_id, nodeP, dsSession) {
const _prog = await func.utils.VIEWS_OBJ.get(SESSION_ID, prog_id);
if (!_prog) return;
let params_res = {},
params_raw = {};
if (_prog?.properties?.progParams) {
for await (const [key, val] of Object.entries(_prog.properties.progParams)) {
if (!['in', 'out'].includes(val.data.dir)) continue;
if (nodeP.attributes) {
if (Object.prototype.hasOwnProperty.call(nodeP.attributes, val.data.parameter)) {
params_res[val.data.parameter] = nodeP.attributes[val.data.parameter];
} else if (Object.prototype.hasOwnProperty.call(nodeP.attributes, `xu-exp:${val.data.parameter}`)) {
if (val.data.dir == 'out') {
params_res[val.data.parameter] = nodeP.attributes[`xu-exp:${val.data.parameter}`].replaceAll('@', '');
} else {
let ret = await func.expression.get(SESSION_ID, nodeP.attributes[`xu-exp:${val.data.parameter}`], dsSession, 'parameters');
params_res[val.data.parameter] = ret.result;
params_raw[val.data.parameter] = nodeP.attributes[`xu-exp:${val.data.parameter}`];
}
}
continue;
}
console.warn(`Warning: Program ${_prog.properties.menuName} expected In parameter: ${val.data.parameter} but received null instead`);
}
}
return { params_res, params_raw };
};
func.runtime.bind.build_datasource_changes = function (dsSessionP, currentRecordId, field_id, value) {
return {
[dsSessionP]: {
[currentRecordId]: {
[field_id]: value,
},
},
};
};
func.runtime.bind.get_bind_node = function (elm) {
if (!elm) {
return null;
}
if (elm.nodeType) {
return elm;
}
if (Array.isArray(elm) || typeof elm.length === 'number') {
return elm[0] || null;
}
return null;
};
func.runtime.bind.is_value_node = function (node) {
if (!node?.tagName) {
return false;
}
const tag_name = node.tagName.toLowerCase();
return ['input', 'select', 'textarea'].includes(tag_name) || typeof node.value !== 'undefined';
};
func.runtime.bind.get_bind_value_node = function (elm) {
const node = func.runtime.bind.get_bind_node(elm);
if (!node) {
return null;
}
if (func.runtime.bind.is_value_node(node)) {
return node;
}
return node.querySelector?.('input, select, textarea') || node;
};
func.runtime.bind.should_use_live_text_listener = function (elm) {
const node = func.runtime.bind.get_bind_value_node(elm);
if (!node?.tagName) {
return false;
}
const tag_name = node.tagName.toLowerCase();
const type = (node.type || node.getAttribute?.('type') || '').toLowerCase();
if (tag_name === 'textarea') {
return true;
}
if (tag_name !== 'input') {
return false;
}
return !['button', 'checkbox', 'file', 'hidden', 'image', 'radio', 'reset', 'submit'].includes(type);
};
func.runtime.bind.get_live_text_debounce_ms = function () {
return 200;
};
func.runtime.bind.to_finite_number = function (value) {
if (value === '' || value === null || typeof value === 'undefined') {
return null;
}
const numeric_value = Number(value);
return Number.isFinite(numeric_value) ? numeric_value : null;
};
func.runtime.bind.get_select_option_number = function (option) {
if (!option) {
return null;
}
const candidate_attributes = ['value', 'data-value', 'data-xuda-value', 'data-xu-value', 'xu-value'];
for (let index = 0; index < candidate_attributes.length; index++) {
const attr_value = option.getAttribute?.(candidate_attributes[index]);
const numeric_value = func.runtime.bind.to_finite_number(attr_value);
if (numeric_value !== null) {
return numeric_value;
}
}
return null;
};
func.runtime.bind.remember_select_numeric_context = function (elm, field_type, value) {
if (field_type !== 'number' || elm?.tagName?.toLowerCase?.() !== 'select') {
return false;
}
const numeric_value = func.runtime.bind.to_finite_number(value);
if (numeric_value === null || elm.selectedIndex < 0) {
return false;
}
elm.__xuda_select_numeric_bind_context = {
selectedIndex: elm.selectedIndex,
value: numeric_value,
};
return true;
};
func.runtime.bind.apply_select_value_once = function (elm, value, field_type) {
const options = Array.from(elm.options || []);
if (!options.length) {
return false;
}
const string_value = value === null || typeof value === 'undefined' ? '' : String(value);
const numeric_value = func.runtime.bind.to_finite_number(value);
let matched_index = -1;
if (field_type === 'number' && numeric_value !== null) {
matched_index = options.findIndex(function (option) {
return func.runtime.bind.get_select_option_number(option) === numeric_value;
});
if (matched_index < 0 && Number.isInteger(numeric_value)) {
if (options[numeric_value - 1]) {
matched_index = numeric_value - 1;
} else if (options[numeric_value]) {
matched_index = numeric_value;
}
}
} else {
matched_index = options.findIndex(function (option) {
return String(option.value) === string_value || String(option.getAttribute?.('value')) === string_value;
});
}
if (matched_index >= 0) {
elm.selectedIndex = matched_index;
func.runtime.bind.remember_select_numeric_context(elm, field_type, value);
return true;
}
return false;
};
func.runtime.bind.schedule_select_value_retry = function (elm, value, field_type, scheduled_at) {
if (elm.__xuda_select_set_retry_timer_ids) {
for (let index = 0; index < elm.__xuda_select_set_retry_timer_ids.length; index++) {
clearTimeout(elm.__xuda_select_set_retry_timer_ids[index]);
}
}
const retry_delays = [0, 50, 250, 500];
elm.__xuda_select_set_retry_timer_ids = retry_delays.map(function (delay) {
return setTimeout(function () {
if (!elm.isConnected) {
return;
}
if (elm.__xuda_select_last_user_change_ts && elm.__xuda_select_last_user_change_ts > scheduled_at) {
return;
}
func.runtime.bind.apply_select_value_once(elm, value, field_type);
}, delay);
});
};
func.runtime.bind.set_select_value = function (elm, value, field_type) {
if (elm?.tagName?.toLowerCase?.() !== 'select') {
return false;
}
const options = Array.from(elm.options || []);
const string_value = value === null || typeof value === 'undefined' ? '' : String(value);
if (elm.multiple) {
const selected_values = Array.isArray(value)
? value.map(function (item) {
return String(item);
})
: [string_value];
options.forEach(function (option) {
option.selected = selected_values.includes(String(option.value));
});
return true;
}
const scheduled_at = Date.now();
func.runtime.bind.apply_select_value_once(elm, value, field_type);
func.runtime.bind.schedule_select_value_retry(elm, value, field_type, scheduled_at);
return true;
};
func.runtime.bind.get_select_numeric_value = function (elm, raw_value) {
const raw_numeric_value = func.runtime.bind.to_finite_number(raw_value);
if (raw_numeric_value !== null) {
return raw_numeric_value;
}
if (elm?.tagName?.toLowerCase?.() !== 'select') {
return raw_value;
}
const selected_option = elm.options?.[elm.selectedIndex];
const selected_option_number = func.runtime.bind.get_select_option_number(selected_option);
if (selected_option_number !== null) {
return selected_option_number;
}
const context = elm.__xuda_select_numeric_bind_context;
if (context && Number.isFinite(context.value) && Number.isFinite(context.selectedIndex) && elm.selectedIndex >= 0) {
return context.value + (elm.selectedIndex - context.selectedIndex);
}
if (elm.selectedIndex >= 0) {
return elm.selectedIndex + 1;
}
return raw_value;
};
func.runtime.bind.normalize_raw_value = function (elm, field_prop, raw_value) {
const field_type = func.runtime.bind.get_field_type(field_prop);
if (field_type === 'number') {
return func.runtime.bind.get_select_numeric_value(elm, raw_value);
}
return raw_value;
};
func.runtime.bind.track_pending_update = function (SESSION_ID, promise) {
const session_obj = SESSION_OBJ?.[SESSION_ID];
if (!session_obj || !promise?.finally) {
return promise;
}
if (!session_obj.pending_bind_updates) {
session_obj.pending_bind_updates = new Set();
}
const tracked_promise = promise.finally(function () {
session_obj.pending_bind_updates.delete(tracked_promise);
});
session_obj.pending_bind_updates.add(tracked_promise);
return tracked_promise;
};
func.runtime.bind.wait_for_pending_updates = async function (SESSION_ID) {
const pending_bind_updates = SESSION_OBJ?.[SESSION_ID]?.pending_bind_updates;
if (!pending_bind_updates?.size) {
return;
}
await Promise.allSettled(Array.from(pending_bind_updates));
};
func.runtime.bind.attach_live_text_listener = function (adapter_name, adapter, elm, handler) {
const node = func.runtime.bind.get_bind_value_node(elm);
if (!node?.addEventListener || typeof handler !== 'function') {
return false;
}
const listener_key = `__xuda_${adapter_name}_live_text_bind_listeners`;
const listener_state_key = `${listener_key}_state`;
const previous_listeners = node[listener_key];
if (previous_listeners) {
for (let index = 0; index < previous_listeners.length; index++) {
node.removeEventListener(previous_listeners[index].event_name, previous_listeners[index].listener);
}
}
if (node[listener_state_key]?.debounce_timer) {
clearTimeout(node[listener_state_key].debounce_timer);
}
const debounce_ms = func.runtime.bind.get_live_text_debounce_ms();
const state = {
debounce_timer: null,
last_value: adapter.getter ? adapter.getter.call(adapter, node) : node.value,
pending_event: null,
pending_value: adapter.getter ? adapter.getter.call(adapter, node) : node.value,
};
node[listener_state_key] = state;
const listener = function (event) {
const current_value = adapter.getter ? adapter.getter.call(adapter, node) : node.value;
if (xu_isEqual(current_value, state.pending_value)) {
return;
}
state.pending_event = event;
state.pending_value = current_value;
clearTimeout(state.debounce_timer);
state.debounce_timer = setTimeout(function () {
const next_value = adapter.getter ? adapter.getter.call(adapter, node) : node.value;
state.debounce_timer = null;
state.pending_value = next_value;
if (xu_isEqual(next_value, state.last_value)) {
return;
}
state.last_value = next_value;
return handler(state.pending_event);
}, debounce_ms);
};
const listeners = [];
for (const event_name of ['input', 'keyup']) {
node.addEventListener(event_name, listener);
listeners.push({ event_name, listener });
}
node[listener_key] = listeners;
return true;
};
func.runtime.bind.normalize_adapter = function (adapter, adapter_name = 'adapter') {
if (!func.runtime.bind.is_valid_adapter(adapter)) {
return adapter;
}
return {
getter: function (elm) {
return adapter.getter.call(adapter, func.runtime.bind.get_bind_value_node(elm) || elm);
},
setter: function (elm, value) {
return adapter.setter.call(adapter, func.runtime.bind.get_bind_value_node(elm) || elm, value);
},
listener: function (elm, handler) {
const node = func.runtime.bind.get_bind_value_node(elm) || elm;
if (func.runtime.bind.should_use_live_text_listener(node)) {
return func.runtime.bind.attach_live_text_listener(adapter_name, adapter, node, handler);
}
return adapter.listener.call(adapter, node, handler);
},
};
};
func.runtime.bind.get_native_adapter = function () {
const has_explicit_value = function (elm) {
return !!elm?.hasAttribute?.('value');
};
const get_listener_event = function (elm) {
const tag_name = elm?.tagName?.toLowerCase?.();
const type = (elm?.type || '').toLowerCase();
if (tag_name === 'select' || ['checkbox', 'radio'].includes(type)) {
return 'change';
}
return 'input';
};
return {
getter: function (elm) {
if (!elm) {
return undefined;
}
const tag_name = elm?.tagName?.toLowerCase?.();
const type = (elm?.type || '').toLowerCase();
if (tag_name === 'select' && elm.multiple) {
return Array.from(elm.options || [])
.filter(function (option) {
return option.selected;
})
.map(function (option) {
return option.value;
});
}
if (type === 'checkbox') {
return has_explicit_value(elm) ? elm.value : !!elm.checked;
}
if (type === 'radio') {
return elm.value;
}
return typeof elm.value !== 'undefined' ? elm.value : undefined;
},
setter: function (elm, value) {
if (!elm) {
return false;
}
const tag_name = elm?.tagName?.toLowerCase?.();
const type = (elm?.type || '').toLowerCase();
if (tag_name === 'select' && elm.multiple) {
const selected_values = Array.isArray(value)
? value.map(function (item) {
return String(item);
})
: [String(value)];
Array.from(elm.options || []).forEach(function (option) {
option.selected = selected_values.includes(String(option.value));
});
return true;
}
if (type === 'checkbox' || type === 'radio') {
return true;
}
if (typeof elm.value !== 'undefined') {
elm.value = value === null || typeof value === 'undefined' ? '' : String(value);
}
return true;
},
listener: function (elm, handler) {
if (!elm?.addEventListener || typeof handler !== 'function') {
return false;
}
const event_name = get_listener_event(elm);
const listener_key = '__xuda_native_bind_listener_' + event_name;
if (elm[listener_key]) {
elm.removeEventListener(event_name, elm[listener_key]);
}
elm.addEventListener(event_name, handler);
elm[listener_key] = handler;
return true;
},
};
};
func.runtime.bind.is_valid_adapter = function (adapter) {
return !!(
adapter &&
typeof adapter.getter === 'function' &&
typeof adapter.setter === 'function' &&
typeof adapter.listener === 'function'
);
};
func.runtime.bind.get_adapter = function (SESSION_ID) {
const native_adapter = func.runtime.bind.get_native_adapter();
if (func.runtime.session.is_slim(SESSION_ID)) {
return func.runtime.bind.normalize_adapter(native_adapter, 'native');
}
const plugin_bind = UI_FRAMEWORK_PLUGIN?.bind;
if (!plugin_bind) {
return func.runtime.bind.normalize_adapter(native_adapter, 'native');
}
if (func.runtime.bind.is_valid_adapter(plugin_bind)) {
return func.runtime.bind.normalize_adapter(plugin_bind, 'plugin');
}
if (typeof plugin_bind === 'function') {
try {
const bind_instance = new plugin_bind();
if (func.runtime.bind.is_valid_adapter(bind_instance)) {
return func.runtime.bind.normalize_adapter(bind_instance, 'plugin');
}
} catch (error) {}
try {
const bind_factory = plugin_bind();
if (func.runtime.bind.is_valid_adapter(bind_factory)) {
return func.runtime.bind.normalize_adapter(bind_factory, 'plugin');
}
} catch (error) {}
}
return func.runtime.bind.normalize_adapter(native_adapter, 'native');
};
func.runtime.bind.resolve_field = async function (SESSION_ID, prog_id, dsSessionP, field_id, iterate_info) {
let _prog_id = prog_id;
let _dsP = dsSessionP;
let is_dynamic_field = false;
let field_prop;
const find_in_view = async function (field_id, prog_id) {
const view_ret = await func.utils.VIEWS_OBJ.get(SESSION_ID, prog_id);
if (!view_ret) {
return null;
}
return func.common.find_item_by_key(view_ret.progFields, 'field_id', field_id);
};
if (['_FOR_VAL', '_FOR_KEY'].includes(field_id)) {
is_dynamic_field = true;
if (iterate_info && (iterate_info.iterator_val === field_id || iterate_info.iterator_key === field_id)) {
const iter_value = iterate_info.iterator_val === field_id ? iterate_info._val : iterate_info._key;
const toType = function (obj) {
return {}.toString.call(obj).match(/\s([a-zA-Z]+)/)[1].toLowerCase();
};
field_prop = {
id: field_id,
data: { type: 'virtual', field_id },
props: { fieldType: typeof iter_value !== 'undefined' ? toType(iter_value) : 'string' },
value: iter_value,
};
} else {
field_prop = SESSION_OBJ[SESSION_ID]?.DS_GLB?.[_dsP]?.dynamic_fields?.[field_id];
}
} else {
field_prop = await find_in_view(field_id, _prog_id);
if (!field_prop) {
const ret_get_value = await func.datasource.get_value(SESSION_ID, field_id, _dsP);
if (ret_get_value.found) {
_dsP = ret_get_value.dsSessionP;
let _ds = SESSION_OBJ[SESSION_ID].DS_GLB[_dsP];
_prog_id = _ds?.prog_id;
field_prop = await find_in_view(field_id, _prog_id);
if (!field_prop) {
field_prop = _ds?.dynamic_fields?.[field_id];
if (field_prop) {
is_dynamic_field = true;
}
}
}
}
}
if (!field_prop) {
throw `field ${field_id} not found in the program scope`;
}
if (!is_dynamic_field) {
const _ds = SESSION_OBJ[SESSION_ID]?.DS_GLB?.[_dsP];
const table_id = _ds?._dataSourceTableId;
if (table_id) {
try {
const table_obj = await func.utils.FILES_OBJ.get(SESSION_ID, table_id);
const table_field_prop = func.common.find_item_by_key(table_obj?.tableFields || [], 'field_id', field_id);
const table_field_type = table_field_prop?.props?.fieldType;
if (table_field_type) {
field_prop = {
...field_prop,
props: {
...(field_prop.props || {}),
fieldType: table_field_type,
},
};
}
} catch (error) {}
}
}
return {
bind_field_id: field_id,
field_prop,
is_dynamic_field,
dsSessionP: _dsP,
prog_id: _prog_id,
};
};
func.runtime.bind.get_field_type = function (field_prop) {
return field_prop?.props?.fieldType;
};
func.runtime.bind.toggle_array_value = function (arr_value_before_cast, value_from_getter) {
if (arr_value_before_cast.includes(value_from_getter)) {
return arr_value_before_cast.filter((item) => !xu_isEqual(item, value_from_getter));
}
arr_value_before_cast.push(value_from_getter);
return arr_value_before_cast;
};
func.runtime.bind.get_cast_value = async function (SESSION_ID, field_prop, input_field_type, raw_value) {
const field_type = func.runtime.bind.get_field_type(field_prop);
if (field_type === 'object') {
return await func.common.get_cast_val(SESSION_ID, 'xu-bind', 'value', input_field_type, raw_value);
}
return await func.common.get_cast_val(SESSION_ID, 'xu-bind', 'value', field_type, raw_value);
};
func.runtime.bind.get_source_value = function (_ds, bind_field_id, is_dynamic_field) {
if (is_dynamic_field) {
return _ds.dynamic_fields[bind_field_id].value;
}
const row_idx = func.common.find_ROWID_idx(_ds, _ds.currentRecordId);
return _ds.data_feed.rows?.[row_idx]?.[bind_field_id];
};
func.runtime.bind.format_display_value = function ($elm, field_prop, bind_field_id, expression_value, value, input_field_type) {
const field_type = func.runtime.bind.get_field_type(field_prop);
const elm_value = func.runtime.ui.get_attr($elm, 'value');
if (field_type === 'array' && input_field_type === 'checkbox' && elm_value) {
return value.includes(elm_value);
}
if (field_type === 'array' && input_field_type === 'radio' && elm_value) {
if (value.includes(elm_value)) {
return elm_value;
}
return false;
}
if (field_type === 'object' && expression_value.split('.').length > 1) {
let str = expression_value.replace(bind_field_id, '(' + JSON.stringify(value) + ')');
return eval(str);
}
return value;
};
func.runtime.bind.update_reference_source_array = async function (options) {
const field_type = func.runtime.bind.get_field_type(options.field_prop);
const reference_source_obj = options.iterate_info?.reference_source_obj;
if (!reference_source_obj || reference_source_obj.ret.type !== 'array' || options.iterate_info?.iterator_val !== options.bind_field_id) {
return false;
}
const arr_idx = Number(options.iterate_info._key);
const dataset_arr = await func.datasource.get_value(options.SESSION_ID, reference_source_obj.fieldIdP, options.dsSessionP, reference_source_obj.currentRecordId);
let new_arr = structuredClone(dataset_arr.ret.value);
if (field_type === 'object' && options.val_is_reference_field) {
let obj_item = new_arr[arr_idx];
let e_exp = options.expression_value.replace(options.bind_field_id, 'obj_item');
eval(e_exp + `=${JSON.stringify(options.value)}`);
new_arr[arr_idx] = obj_item;
} else {
new_arr[arr_idx] = options.value;
}
let datasource_changes = func.runtime.bind.build_datasource_changes(options.dsSessionP, options.currentRecordId, reference_source_obj.fieldIdP, new_arr);
await func.datasource.update(options.SESSION_ID, datasource_changes);
return true;
};
func.runtime.resources.load_cdn = async function (SESSION_ID, resource) {
let normalized_resource = resource;
if (!(typeof normalized_resource === 'object' && normalized_resource !== null) && typeof normalized_resource === 'string') {
normalized_resource = { src: normalized_resource, type: 'js' };
}
if (!(typeof normalized_resource === 'object' && normalized_resource !== null)) {
throw new Error('cdn resource in wrong format');
}
return new Promise(async (resolve) => {
try {
switch (normalized_resource.type) {
case 'js':
await func.utils.load_js_on_demand(normalized_resource.src);
break;
case 'css':
func.runtime.platform.load_css(normalized_resource.src);
break;
case 'module':
await func.utils.load_js_on_demand(normalized_resource.src, 'module');
break;
default:
await func.utils.load_js_on_demand(normalized_resource.src);
break;
}
resolve();
} catch (error) {
func.utils.debug_report(SESSION_ID, 'xu-cdn', 'Fail to load: ' + normalized_resource, 'W');
resolve();
}
});
};
func.runtime.resources.get_plugin_manifest_entry = function (_session, plugin_name) {
return APP_OBJ[_session.app_id]?.app_plugins_purchased?.[plugin_name] || null;
};
func.runtime.resources.get_plugin_resource_candidates = function (_session, plugin, resource) {
const manifest_entry = plugin?.manifest?.[resource];
const default_path = `${manifest_entry?.dist ? 'dist/' : ''}${resource}`;
const candidates = [];
if (_session?.worker_type === 'Dev' && manifest_entry?.dist && /\.mjs$/.test(resource)) {
candidates.push(`src/${resource}`);
}
candidates.push(default_path);
return Array.from(new Set(candidates.filter(Boolean)));
};
func.runtime.resources.get_plugin_module_path = function (plugin, resource, _session) {
return func.runtime.resources.get_plugin_resource_candidates(_session, plugin, resource)[0] || resource;
};
func.runtime.resources.get_plugin_module_url = async function (SESSION_ID, plugin_name, plugin, resource) {
const _session = SESSION_OBJ[SESSION_ID];
return await func.utils.get_plugin_npm_cdn(SESSION_ID, plugin_name, func.runtime.resources.get_plugin_module_path(plugin, resource, _session));
};
func.runtime.resources.import_plugin_module = async function (SESSION_ID, plugin_name, plugin, resource) {
const _session = SESSION_OBJ[SESSION_ID];
const candidates = func.runtime.resources.get_plugin_resource_candidates(_session, plugin, resource);
let last_error = null;
for (let index = 0; index < candidates.length; index++) {
const candidate = candidates[index];
try {
return await func.utils.get_plugin_resource(SESSION_ID, plugin_name, candidate);
} catch (error) {
last_error = error;
}
}
throw last_error || new Error(`plugin resource not found: ${plugin_name}/${resource}`);
};
func.runtime.resources.load_plugin_runtime_css = async function (SESSION_ID, plugin_name, plugin) {
if (!plugin?.manifest?.['runtime.mjs']?.dist || !plugin?.manifest?.['runtime.mjs']?.css) {
return false;
}
const plugin_runtime_css_url = await func.utils.get_plugin_npm_cdn(SESSION_ID, plugin_name, 'dist/runtime.css');
func.utils.load_css_on_demand(plugin_runtime_css_url);
return true;
};
func.runtime.resources.resolve_plugin_properties = async function (SESSION_ID, dsSessionP, attributes, properties) {
// Plugin property schemas can carry function-valued defaults (e.g. tippy
// `placement: () => "top"`), which structuredClone cannot clone and throws on,
// breaking every element that uses the plugin. xu_cloneDeep tries
// structuredClone first and falls back to a recursive clone that preserves
// function references, so the schema clones cleanly either way.
let resolved_properties = xu_cloneDeep(properties);
for await (let [prop_name, prop_val] of Object.entries(resolved_properties || {})) {
prop_val.value = attributes?.[prop_name];
if (attributes?.[`xu-exp:${prop_name}`]) {
const res = await func.expression.get(SESSION_ID, attributes[`xu-exp:${prop_name}`], dsSessionP, 'UI Attr EXP');
prop_val.value = res.result;
}
}
return resolved_properties;
};
func.runtime.resources.run_ui_plugin = async function (SESSION_ID, paramsP, $elm, plugin_name, value) {
var _session = SESSION_OBJ[SESSION_ID];
const plugin = func.runtime.resources.get_plugin_manifest_entry(_session, plugin_name);
if (!plugin?.installed || !plugin?.manifest?.['runtime.mjs']?.exist || !plugin?.manifest?.['index.mjs']?.exist || !value?.enabled) {
return false;
}
await func.runtime.resources.load_plugin_runtime_css(SESSION_ID, plugin_name, plugin);
const plugin_index_resources = await func.runtime.resources.import_plugin_module(SESSION_ID, plugin_name, plugin, 'index.mjs');
const properties = await func.runtime.resources.resolve_plugin_properties(SESSION_ID, paramsP.dsSessionP, value?.attributes, plugin_index_resources.properties);
const plugin_runtime_resources = await func.runtime.resources.import_plugin_module(SESSION_ID, plugin_name, plugin, 'runtime.mjs');
if (plugin_runtime_resources.cdn && Array.isArray(plugin_runtime_resources.cdn)) {
for await (const resource of plugin_runtime_resources.cdn) {
await func.runtime.resources.load_cdn(SESSION_ID, resource);
}
}
if (plugin_runtime_resources.fn) {
const plugin_element = func.runtime.ui.get_first_node?.($elm) || $elm?.[0] || $elm;
if (!plugin_element) {
return false;
}
await plugin_runtime_resources.fn(plugin_name, plugin_element, properties);
}
return true;
};
func.runtime.widgets.create_context = function (SESSION_ID, paramsP, prop) {
const _session = SESSION_OBJ[SESSION_ID];
const plugin_name = prop['xu-widget'];
return {
SESSION_ID,
_session,
plugin_name,
method: prop['xu-method'] || '_default',
dsP: paramsP.dsSessionP,
propsP: prop,
sourceP: 'widgets',
plugin: APP_OBJ[_session.app_id]?.app_plugins_purchased?.[plugin_name] || null,
};
};
func.runtime.widgets.report_error = function (context, descP, warn) {
const program = context?._session?.DS_GLB?.[context.dsP];
if (!program) {
return null;
}
func.utils.debug.log(context.SESSION_ID, program.prog_id + '_' + program.callingMenuId, {
module: 'widgets',
action: 'Init',
source: context.sourceP,
prop: descP,
details: descP,
result: null,
error: warn ? false : true,
fields: null,
type: 'widgets',
prog_id: program.prog_id,
});
return null;
};
func.runtime.widgets.get_property_value = async function (context, fieldIdP, val, props) {
if (!val) return;
var value = fieldIdP in props ? props[fieldIdP] : typeof val.defaultValue === 'function' ? val?.defaultValue?.() : val?.defaultValue;
if (val.render === 'eventId') {
value = props?.[fieldIdP]?.event;
}
if (props[`xu-exp:${fieldIdP}`]) {
value = (await func.expression.get(context.SESSION_ID, props[`xu-exp:${fieldIdP}`], context.dsP, 'widget property')).result;
}
return func.common.get_cast_val(
context.SESSION_ID,
'widgets',
fieldIdP,
val.type,
value,
null,
);
};
func.runtime.widgets.get_fields_data = async function (context, fields, props) {
var data_obj = {};
var return_code = 1;
for await (const [key, val] of Object.entries(fields || {})) {
data_obj[key] = await func.runtime.widgets.get_property_value(context, key, val, props);
if (!data_obj[key] && val.mandatory) {
return_code = -1;
func.runtime.widgets.report_error(context, `${key} is a mandatory field.`);
break;
}
}
for await (const key of ['xu-bind']) {
data_obj[key] = await func.runtime.widgets.get_property_value(context, key, props?.[key], props);
}
return { code: return_code, data: data_obj };
};
func.runtime.widgets.get_resource_candidates = function (context, resource) {
return func.runtime.resources.get_plugin_resource_candidates(context._session, context.plugin, resource);
};
func.runtime.widgets.normalize_capabilities = function (definition) {
const capabilities = definition?.capabilities || {};
return {
browser: capabilities.browser !== false,
headless: capabilities.headless === true,
};
};
func.runtime.widgets.supports_current_environment = function (definition) {
const capabilities = func.runtime.widgets.normalize_capabilities(definition);
if (func.runtime.platform.has_document()) {
return capabilities.browser !== false;
}
return !!capabilities.headless;
};
func.runtime.widgets.get_resource_path = function (context, resource) {
const relative_path = func.runtime.widgets.get_resource_candidates(context, resource)[0] || resource;
const server_origin = typeof globalThis !== 'undefined' ? globalThis.__XU_SERVER_ORIGIN__ : '';
if (server_origin) {
return `${server_origin}/plugins/${context.plugin_name}/${relative_path}?gtp_token=${context._session.gtp_token}&app_id=${context._session.app_id}`;
}
if (context._session.worker_type === 'Dev') {
return `../../plugins/${context.plugin_name}/${relative_path}`;
}
return `https://${context._session.domain}/plugins/${context.plugin_name}/${relative_path}?gtp_token=${context._session.gtp_token}&app_id=${context._session.app_id}`;
};
func.runtime.widgets.load_css_style = function (context) {
func.utils.load_css_on_demand(func.runtime.widgets.get_resource_path(context, 'style.css'));
return true;
};
func.runtime.widgets.get_resource = async function (context, resource) {
const candidates = func.runtime.widgets.get_resource_candidates(context, resource);
let last_error = null;
for (let index = 0; index < candidates.length; index++) {
const candidate = candidates[index];
try {
return await func.utils.get_plugin_resource(context.SESSION_ID, context.plugin_name, candidate);
} catch (error) {
last_error = error;
}
}
throw last_error || new Error(`widget resource not found: ${context.plugin_name}/${resource}`);
};
func.runtime.widgets.get_definition = async function (context) {
return await func.runtime.widgets.get_resource(context, 'index.mjs');
};
func.runtime.widgets.get_methods = async function (context) {
const index = await func.runtime.widgets.get_definition(context);
return index?.methods || {};
};
func.runtime.widgets.load_runtime_css = async function (context) {
if (!context.plugin?.manifest?.['runtime.mjs']?.dist || !context.plugin?.manifest?.['runtime.mjs']?.css) {
return false;
}
const plugin_runtime_css_url = await func.utils.get_plugin_npm_cdn(context.SESSION_ID, context.plugin_name, 'dist/runtime.css');
func.utils.load_css_on_demand(plugin_runtime_css_url);
return true;
};
func.runtime.widgets.build_params = function (context, container_node, container_data, plugin_setup, api_utils, extra = {}) {
return {
SESSION_ID: context.SESSION_ID,
method: context.method,
_session: context._session,
dsP: context.dsP,
sourceP: context.sourceP,
propsP: context.propsP,
plugin_name: context.plugin_name,
container_node,
container_data,
plugin_setup,
report_error: function (descP, warn) {
return func.runtime.widgets.report_error(context, descP, warn);
},
log_error: function (descP, warn) {
return func.runtime.widgets.report_error(context, descP, warn);
},
call_plugin_api: async function (plugin_nameP, dataP) {
return await func.utils.call_plugin_api(context.SESSION_ID, plugin_nameP, dataP);
},
set_SYS_GLOBAL_OBJ_WIDGET_INFO: async function (docP) {
return await func.utils.set_SYS_GLOBAL_OBJ_WIDGET_INFO(context.SESSION_ID, docP);
},
run_widgetCallbackEvent: async function () {
const event_id = context.propsP?.widgetCallbackEvent;
if (!event_id || !api_utils?.invoke_event) {
return false;
}
return await api_utils.invoke_event(event_id);
},
api_utils,
...extra,
};
};
func.common.find_item_by_key = function (arr, key, val) {
return arr.find(function (e) {
return e.data[key] === val;
});
};
func.common.find_item_by_key_root = function (arr, key, val) {
return arr.find(function (e) {
return e[key] === val;
});
};
func.common.find_ROWID_idx = function (_ds, rowId) {
if (!_ds?.data_feed?.rows) {
throw new Error('data_feed not found');
}
// Find the index of the object with the given _ROWID
const index = _ds.data_feed.rows.findIndex((item) => item._ROWID === rowId);
// }
// If the index is -1, the ROWID was not found, so throw an error
if (index === -1) {
throw new Error(`ROWID "${rowId}" not found`);
}
// Return the found index
return index;
};
func.common.input_mask = async function (actionP, valP, typeP, maskP, elemP, grid_objP, grid_row_idP, grid_col_idP, dsSessionP) {
const module = await func.common.get_module(SESSION_ID, 'xuda-input-musk-utils-module.mjs');
module.input_mask(actionP, valP, typeP, maskP, elemP, grid_objP, grid_row_idP, grid_col_idP, dsSessionP);
};
glb.FUNCTION_NODES_ARR = ['batch', 'get_data', 'set_data', 'alert', 'javascript', 'api'];
glb.ALL_MENU_TYPE = ['globals', 'ai_agent', 'component', ...glb.FUNCTION_NODES_ARR];
glb.emailRegex = /^[\w\.-]+@[a-zA-Z\d\.-]+\.[a-zA-Z]{2,}$/;
const FIREBASE_AUTH_PROPERTIES_ARR = ['provider', 'token', 'first_name', 'last_name', 'email', 'user_id', 'picture', 'verified_email', 'locale', 'error_code', 'error_msg'];
const CLIENT_INFO_PROPERTIES_ARR = [
'fingerprint',
'device',
'user_agent',
'browser_version',
'browser_name',
'engine_version',
'engine_name',
'client_ip',
'os_name',
'os_version',
'device_model',
'device_vendor',
'device_type',
'screen_current_resolution_x',
'screen_current_resolution_y',
'screen_available_resolution_x',
'screen_available_resolution_y',
'language',
'time_zone',
'cpu_architecture',
'uuid',
'cursor_pos_x',
'cursor_pos_y',
];
const APP_PROPERTIES_ARR = ['build', 'author', 'date', 'name'];
const DATASOURCE_PROPERTIES_ARR = ['rows', 'type', 'first_row_id', 'last_row_id', 'query_from_segments_json', 'query_to_segments_json', 'locate_query_from_segments_json', 'locate_query_to_segments_json', 'first_row_segments_json', 'last_row_segments_json', 'rowid_snapshot', 'rowid'];
glb.MOBILE_ARR = ['component', 'web_app', 'ios_app', 'android_app', 'electron_app', 'osx_app', 'windows_app'];
glb.SYS_DATE_ARR = ['SYS_DATE', 'SYS_DATE_TIME', 'SYS_DATE_VALUE', 'SYS_DATE_WEEK_YEAR', 'SYS_DATE_MONTH_YEAR', 'SYS_TIME_SHORT', 'SYS_TIME'];
glb.API_OUTPUT_ARR = ['json', 'html', 'xml', 'text', 'css', 'javascript'];
const PROTECTED_NAMES_ARR = ['THIS', 'ROWID']; //tbd
func.common.db = async function (SESSION_ID, serviceP, dataP, opt = {}, dsSession) {
return new Promise(async function (resolve, reject) {
var _session = SESSION_OBJ[SESSION_ID];
const app_id = _session.app_id;
if (glb.DEBUG_MODE) {
console.info('request', dataP);
}
var data = {
app_id: app_id,
fingerprint: _session?.SYS_GLOBAL_OBJ_CLIENT_INFO?.fingerprint,
debug: glb.DEBUG_MODE,
session_id: SESSION_ID,
gtp_token: _session.gtp_token,
app_token: _session.app_token,
res_token: _session.res_token,
engine_mode: _session.engine_mode,
req_id: 'rt_req_' + crypto.randomUUID(),
app_replicate: APP_OBJ[app_id].app_replicate,
};
try {
if (typeof firebase !== 'undefined' && firebase?.auth()?.currentUser?.displayName) {
data.device_name = firebase.auth().currentUser.displayName;
}
} catch (error) {}
for (const [key, val] of Object.entries(dataP)) {
data[key] = val;
}
const success_callback = function (ret) {
if (dataP.table_id && DOCS_OBJ[app_id][dataP.table_id]) {
func.utils.debug.watch(SESSION_ID, dataP.table_id, 'table', DOCS_OBJ[app_id][dataP.table_id].properties.menuName, {
req: data,
res: ret,
});
}
if (glb.DEBUG_MODE) {
console.info('response', ret);
}
resolve(ret, true);
};
const error_callback = function (err) {
reject(err);
};
function cleanString(json) {
let str = JSON.stringify(json);
// Replace all non-alphanumeric characters with an empty string
return str.replace(/[^a-zA-Z0-9]/g, '');
}
const get_rep_id = function () {
let _data = {};
const fields_to_skip = ['fields', 'viewSourceDesc', 'skip', 'limit', 'count', 'reduce', 'prog_id', 'sortModel', 'filterModelMongo', 'filterModelSql', 'filterModelUserMongo', 'filterModelUserSql'];
for (let [key, val] of Object.entries(dataP)) {
if (typeof val !== 'undefined' && val !== null && !fields_to_skip.includes(key)) {
_data[key] = val;
}
}
return cleanString(_data);
};
const validate_existence_of_whole_table_request = async function (db) {
let table_req_id;
try {
table_req_id = cleanString({
key: data.table_id,
table_id: data.table_id,
});
const doc = await db.get(table_req_id);
let ret = await db.find({
selector: {
docType: 'rep_request',
table_id: data.table_id,
},
});
if (doc.stat < 3) {
throw 'not ready';
}
/// delete table refunded requests
for (let doc of ret.docs) {
if (doc.entire_table) continue;
func.db.pouch.remove_db_replication_from_server(SESSION_ID, doc._id);
}
return { code: 1, data: table_req_id };
} catch (err) {
return { code: -1, data: table_req_id };
}
};
const upsert_rep_request_from_remote_response = async function (db, rep_id, table_req_id, json) {
if (!json?.data?.opt) return;
try {
let existing_doc;
try {
existing_doc = await db.get(rep_id);
} catch (err) {}
const rep_doc = {
_id: rep_id,
selector: json.data.opt.selector,
stat: 1,
ts: Date.now(),
docType: 'rep_request',
table_id: dataP.table_id,
prog_id: dataP.prog_id,
entire_table: table_req_id === rep_id,
source: 'runtime',
e: data,
};
if (existing_doc?._rev) {
rep_doc._rev = existing_doc._rev;
}
await db.put(rep_doc);
func.db.pouch.set_db_replication_from_server(SESSION_ID);
} catch (err) {}
};
const read_remote_dbs = async function (db, rep_id, table_req_id) {
const json = await func.common.perform_rpi_request(SESSION_ID, serviceP, opt, data);
await upsert_rep_request_from_remote_response(db, rep_id, table_req_id, json);
return json;
};
const should_retry_live_preview_remote_read = function (json) {
return (
_session?.engine_mode === 'live_preview' &&
serviceP === 'dbs_read' &&
dataP.table_id &&
!dataP.count &&
!dataP.reduce &&
Array.isArray(json?.data?.rows) &&
!json.data.rows.length
);
};
const read_local_dbs_with_live_preview_fallback = async function (db, rep_id, table_req_id) {
const json = {
code: 1,
data: await func.db.pouch[serviceP](SESSION_ID, data),
};
if (should_retry_live_preview_remote_read(json)) {
return await read_remote_dbs(db, rep_id, table_req_id);
}
return json;
};
const read_dbs_pouch = async function (db) {
if (_session?.DS_GLB?.[dsSession]?.refreshed && (dataP.filterModelMongo || dataP.filterModelSql)) {
return await read_local_dbs_with_live_preview_fallback(db, get_rep_id(), null);
}
const rep_id = get_rep_id();
const { code: table_req_code, data: table_req_id } = await validate_existence_of_whole_table_request(db);
if (table_req_code > 0) {
return await read_local_dbs_with_live_preview_fallback(db, rep_id, table_req_id);
}
try {
const doc = await db.get(rep_id);
if (doc.stat < 3) throw 'replication not ready';
return await read_local_dbs_with_live_preview_fallback(db, rep_id, table_req_id);
} catch (err) {
return await read_remote_dbs(db, rep_id, table_req_id);
}
};
const update_dbs_pouch = async function (db) {
try {
// if (_session.DS_GLB[0].data_system.SYS_GLOBAL_BOL_ONLINE) {
// throw "online";
// }
const { code: table_req_code, data: table_req_id } = await validate_existence_of_whole_table_request(db);
if (table_req_code > 0) {
data.full_table_downloaded = true;
}
await db.get(dataP.row_id);
return await func.db.pouch[serviceP](SESSION_ID, data);
} catch (err) {
return await func.common.perform_rpi_request(SESSION_ID, serviceP, opt, data);
}
};
const create_dbs_pouch = async function (db) {
try {
const { code: table_req_code, data: table_req_id } = await validate_existence_of_whole_table_request(db);
if (table_req_code > 0) {
data.full_table_downloaded = true;
}
return await func.db.pouch[serviceP](SESSION_ID, data);
} catch (err) {
return await func.common.perform_rpi_request(SESSION_ID, serviceP, opt, data);
}
};
const delete_dbs_pouch = async function (db) {
for await (let row_id of dataP.ids || []) {
try {
const { code: table_req_code, data: table_req_id } = await validate_existence_of_whole_table_request(db);
if (table_req_code > 0) {
data.full_table_downloaded = true;
}
await db.get(row_id);
let _data = structuredClone(dataP);
_data.ids = [row_id];
return await func.db.pouch['dbs_delete'](SESSION_ID, _data);
} catch (err) {
return await func.common.perform_rpi_request(SESSION_ID, serviceP, opt, data);
}
}
};
if (typeof IS_DOCKER === 'undefined' && typeof IS_PROCESS_SERVER === 'undefined') {
try {
if (!SESSION_OBJ?.[SESSION_ID]?.rpi_http_methods?.includes(serviceP)) {
throw '';
}
const is_local_draft_pouch_runtime =
['miniapp', 'live_preview'].includes(_session?.engine_mode) &&
_session?.is_draft_runtime &&
Array.isArray(_session?.rpi_http_methods) &&
_session.rpi_http_methods.includes('dbs_read');
if (is_local_draft_pouch_runtime && ['dbs_read', 'dbs_update', 'dbs_create', 'dbs_delete'].includes(serviceP)) {
return success_callback({
code: 1,
data: await func.db.pouch[serviceP](SESSION_ID, data),
});
}
if (!(await func?.db?.pouch?.get_replication_stat(SESSION_ID))) throw '';
const db = await func.utils.connect_pouchdb(SESSION_ID);
if (_session?.engine_mode === 'live_preview' && !_session?.is_draft_runtime && ['dbs_update', 'dbs_create', 'dbs_delete'].includes(serviceP)) {
const json = await func.common.perform_rpi_request(SESSION_ID, serviceP, opt, data);
return success_callback(json, true);
}
switch (serviceP) {
case 'dbs_read': {
try {
return success_callback(await read_dbs_pouch(db));
} catch (err) {
if (err === 'creating index in progress') {
throw '';
}
return error_callback(err);
}
break;
}
case 'dbs_update': {
try {
const ret = {
code: 1,
data: await update_dbs_pouch(db),
};
return success_callback(ret);
} catch (err) {
return error_callback(err);
}
break;
}
case 'dbs_create': {
try {
const ret = {
code: 1,
data: await create_dbs_pouch(db),
};
return success_callback(ret);
} catch (err) {
return error_callback(err);
}
break;
}
case 'dbs_delete': {
try {
const ret = {
code: 1,
data: await delete_dbs_pouch(db),
};
return success_callback(ret);
} catch (err) {
return error_callback(err);
}
break;
}
default:
throw '';
break;
}
} catch (err) {
try {
const json = await func.common.perform_rpi_request(SESSION_ID, serviceP, opt, data);
return success_callback(json, true);
} catch (err) {
return error_callback(err);
}
}
}
// DOCKER // PROCESS_SERVER
const response = function (res, ret) {
if (ret.code < 0) {
return error_callback(ret);
}
success_callback(ret);
};
const get_white_spaced_data = function (data) {
var e = {};
for (const [key, val] of Object.entries(data)) {
if (!val) {
if (typeof val === 'boolean') {
e[key] = 'false';
} else {
e[key] = '';
}
} else {
if (typeof val === 'boolean') {
e[key] = 'true';
} else {
e[key] = val;
}
}
}
if (data.fields && !data.fields.length) {
e.fields = '';
}
return e;
};
if (dataP.table_id) {
await func.utils.FILES_OBJ.get(SESSION_ID, dataP.table_id);
await func.utils.TREE_OBJ.get(SESSION_ID, dataP.table_id);
}
data.db_driver = 'xuda';
__.rpi.http_calls(serviceP, { body: get_white_spaced_data(data) }, null, response);
});
};
func.common.getJsonFromUrl = function () {
return func.runtime.env.get_url_params();
};
func.common.getParametersFromUrl = function () {
return func.runtime.env.get_url_parameters_object();
};
func.common.getObjectFromUrl = function (url, element_attributes_obj, embed_params_obj) {
var result = {};
if (element_attributes_obj) {
for (let [key, val] of Object.entries(element_attributes_obj)) {
result[key] = val;
}
}
if (embed_params_obj) {
for (let [key, val] of Object.entries(embed_params_obj)) {
result[key] = val;
}
}
if (!url && typeof IS_DOCKER === 'undefined' && typeof IS_PROCESS_SERVER === 'undefined') {
url = location.href;
}
var question = url.indexOf('?');
var hash = url.indexOf('#');
if (hash == -1 && question == -1) return result;
if (hash == -1) hash = url.length;
var query = question == -1 || hash == question + 1 ? url.substring(hash) : url.substring(question + 1, hash);
// var result = {};
query.split('&').forEach(function (part) {
if (!part) return;
part = part.split('+').join(' '); // replace every + with space, regexp-free version
var eq = part.indexOf('=');
var key = eq > -1 ? part.substr(0, eq) : part;
var val = eq > -1 ? decodeURIComponent(part.substr(eq + 1)) : '';
var from = key.indexOf('[');
if (from == -1) {
result[decodeURIComponent(key)] = val;
} else {
var to = key.indexOf(']', from);
var index = decodeURIComponent(key.substring(from + 1, to));
key = decodeURIComponent(key.substring(0, from));
if (!result[key]) result[key] = [];
if (!index) result[key].push(val);
else result[key][index] = val;
}
});
return result;
};
func.common.getContrast_color = function (hexcolor) {
function colourNameToHex(colour) {
var colours = {
aliceblue: '#f0f8ff',
antiquewhite: '#faebd7',
aqua: '#00ffff',
aquamarine: '#7fffd4',
azure: '#f0ffff',
beige: '#f5f5dc',
bisque: '#ffe4c4',
black: '#000000',
blanchedalmond: '#ffebcd',
blue: '#0000ff',
blueviolet: '#8a2be2',
brown: '#a52a2a',
burlywood: '#deb887',
cadetblue: '#5f9ea0',
chartreuse: '#7fff00',
chocolate: '#d2691e',
coral: '#ff7f50',
cornflowerblue: '#6495ed',
cornsilk: '#fff8dc',
crimson: '#dc143c',
cyan: '#00ffff',
darkblue: '#00008b',
darkcyan: '#008b8b',
darkgoldenrod: '#b8860b',
darkgray: '#a9a9a9',
darkgreen: '#006400',
darkkhaki: '#bdb76b',
darkmagenta: '#8b008b',
darkolivegreen: '#556b2f',
darkorange: '#ff8c00',
darkorchid: '#9932cc',
darkred: '#8b0000',
darksalmon: '#e9967a',
darkseagreen: '#8fbc8f',
darkslateblue: '#483d8b',
darkslategray: '#2f4f4f',
darkturquoise: '#00ced1',
darkviolet: '#9400d3',
deeppink: '#ff1493',
deepskyblue: '#00bfff',
dimgray: '#696969',
dodgerblue: '#1e90ff',
firebrick: '#b22222',
floralwhite: '#fffaf0',
forestgreen: '#228b22',
fuchsia: '#ff00ff',
gainsboro: '#dcdcdc',
ghostwhite: '#f8f8ff',
gold: '#ffd700',
goldenrod: '#daa520',
gray: '#808080',
green: '#008000',
greenyellow: '#adff2f',
honeydew: '#f0fff0',
hotpink: '#ff69b4',
'indianred ': '#cd5c5c',
indigo: '#4b0082',
ivory: '#fffff0',
khaki: '#f0e68c',
lavender: '#e6e6fa',
lavenderblush: '#fff0f5',
lawngreen: '#7cfc00',
lemonchiffon: '#fffacd',
lightblue: '#add8e6',
lightcoral: '#f08080',
lightcyan: '#e0ffff',
lightgoldenrodyellow: '#fafad2',
lightgrey: '#d3d3d3',
lightgreen: '#90ee90',
lightpink: '#ffb6c1',
lightsalmon: '#ffa07a',
lightseagreen: '#20b2aa',
lightskyblue: '#87cefa',
lightslategray: '#778899',
lightsteelblue: '#b0c4de',
lightyellow: '#ffffe0',
lime: '#00ff00',
limegreen: '#32cd32',
linen: '#faf0e6',
magenta: '#ff00ff',
maroon: '#800000',
mediumaquamarine: '#66cdaa',
mediumblue: '#0000cd',
mediumorchid: '#ba55d3',
mediumpurple: '#9370d8',
mediumseagreen: '#3cb371',
mediumslateblue: '#7b68ee',
mediumspringgreen: '#00fa9a',
mediumturquoise: '#48d1cc',
mediumvioletred: '#c71585',
midnightblue: '#191970',
mintcream: '#f5fffa',
mistyrose: '#ffe4e1',
moccasin: '#ffe4b5',
navajowhite: '#ffdead',
navy: '#000080',
oldlace: '#fdf5e6',
olive: '#808000',
olivedrab: '#6b8e23',
orange: '#ffa500',
orangered: '#ff4500',
orchid: '#da70d6',
palegoldenrod: '#eee8aa',
palegreen: '#98fb98',
paleturquoise: '#afeeee',
palevioletred: '#d87093',
papayawhip: '#ffefd5',
peachpuff: '#ffdab9',
peru: '#cd853f',
pink: '#ffc0cb',
plum: '#dda0dd',
powderblue: '#b0e0e6',
purple: '#800080',
rebeccapurple: '#663399',
red: '#ff0000',
rosybrown: '#bc8f8f',
royalblue: '#4169e1',
saddlebrown: '#8b4513',
salmon: '#fa8072',
sandybrown: '#f4a460',
seagreen: '#2e8b57',
seashell: '#fff5ee',
sienna: '#a0522d',
silver: '#c0c0c0',
skyblue: '#87ceeb',
slateblue: '#6a5acd',
slategray: '#708090',
snow: '#fffafa',
springgreen: '#00ff7f',
steelblue: '#4682b4',
tan: '#d2b48c',
teal: '#008080',
thistle: '#d8bfd8',
tomato: '#ff6347',
turquoise: '#40e0d0',
violet: '#ee82ee',
wheat: '#f5deb3',
white: '#ffffff',
whitesmoke: '#f5f5f5',
yellow: '#ffff00',
yellowgreen: '#9acd32',
};
if (typeof colours[colour.toLowerCase()] != 'undefined') return colours[colour.toLowerCase()];
return false;
}
if (!hexcolor.includes('#')) {
hexcolor = colourNameToHex(hexcolor);
}
// If a leading # is provided, remove it
if (hexcolor.slice(0, 1) === '#') {
hexcolor = hexcolor.slice(1);
}
// Convert to RGB value
var r = Number(hexcolor.substr(0, 2), 16);
var g = Number(hexcolor.substr(2, 2), 16);
var b = Number(hexcolor.substr(4, 2), 16);
// Get YIQ ratio
var yiq = (r * 299 + g * 587 + b * 114) / 1000;
// Check contrast
return yiq >= 128 ? 'black' : 'white';
};
func.common.get_url = function (SESSION_ID, method, path) {
const _session = SESSION_OBJ[SESSION_ID] || {};
const origin =
((typeof globalThis !== 'undefined' && globalThis.__XU_SERVER_ORIGIN__)) ||
(_session.domain ? `https://${_session.domain}` : '');
if (!origin) {
return `/${method}${path ? '/' + path : '/'}`;
}
return `${origin}/${method}${path ? '/' + path : '/'}`;
};
var UI_FRAMEWORK_INSTALLED = null;
var UI_FRAMEWORK_PLUGIN = {};
func.common.get_cast_val = async function (SESSION_ID, source, attributeP, typeP, valP, errorP) {
const report_conversion_error = function (res) {
if (errorP) {
return func.utils.debug_report(SESSION_ID, source.charAt(0).toUpperCase() + source.slice(1).toLowerCase(), errorP, 'W');
}
var msg = `error converting ${attributeP} from ${valP} to ${typeP}`;
func.utils.debug_report(SESSION_ID, source.charAt(0).toUpperCase() + source.slice(1).toLowerCase(), msg, 'E');
};
const report_conversion_warn = function (msg) {
// number/boolean/bigint -> string is a lossless coercion (String(v) is always exact); don't surface
// it as a runtime warning — it routes through report_issue and shows as an "Unhandled Runtime Error".
if (typeP === 'string' && (typeof valP === 'number' || typeof valP === 'boolean' || typeof valP === 'bigint')) return;
var msg = `type mismatch auto conversion made to ${attributeP} from value ${valP} to ${typeP}`;
func.utils.debug_report(SESSION_ID, source.charAt(0).toUpperCase() + source.slice(1).toLowerCase(), msg, 'W');
};
const module = await func.common.get_module(SESSION_ID, `xuda-get-cast-util-module.mjs`);
return module.cast(typeP, valP, report_conversion_error, report_conversion_warn);
};
var WEB_WORKER = {};
var WEB_WORKER_CALLBACK_QUEUE = {};
glb.DEBUG_MODE = null;
var DS_UI_EVENTS_GLB = {};
var RUNTIME_SERVER_WEBSOCKET = null;
var RUNTIME_SERVER_WEBSOCKET_CONNECTED = null;
var WEBSOCKET_PROCESS_PID = null;
glb.worker_queue_num = 0;
glb.websocket_queue_num = 0;
func.common._import_cache = func.common._import_cache || {};
func.common.get_module = async function (SESSION_ID, module, paramsP = {}) {
let ret;
const get_ret = async function (src) {
// Cache the import() result to avoid repeated dynamic imports
if (!func.common._import_cache[src]) {
func.common._import_cache[src] = await import(src);
}
const module_ret = func.common._import_cache[src];
var params = get_params();
const ret = module_ret.XudaModule ? new module_ret.XudaModule(params) : await invoke_init_module(module_ret, params);
return ret;
};
const get_params = function () {
let params = {
glb,
func,
APP_OBJ,
SESSION_ID,
PROJECT_OBJ,
DOCS_OBJ,
SESSION_OBJ,
...paramsP,
};
if (typeof IS_PROCESS_SERVER !== 'undefined') params.IS_PROCESS_SERVER = IS_PROCESS_SERVER;
if (typeof IS_API_SERVER !== 'undefined') params.IS_API_SERVER = IS_API_SERVER;
if (typeof IS_DOCKER !== 'undefined') params.IS_DOCKER = IS_DOCKER;
return params;
};
const invoke_init_module = async function (module_ret, params) {
if (!module_ret.init_module) return module_ret;
await module_ret.init_module(params);
return module_ret;
};
const _session = SESSION_OBJ[SESSION_ID];
const append_ts = function (resource_path) {
const is_debug_live_runtime = ['Dev', 'Debug'].includes(_session?.worker_type) && ['live_preview', 'miniapp'].includes(_session?.engine_mode);
let local_runtime_cache_tag =
typeof globalThis !== 'undefined'
? globalThis.__XU_RUNTIME_MODULE_CACHE_TAG__ || globalThis.__XU_SERVER_BOOTSTRAP__?.version
: 0;
if (is_debug_live_runtime && typeof globalThis !== 'undefined') {
globalThis.__XU_RUNTIME_MODULE_CACHE_TAG__ = globalThis.__XU_RUNTIME_MODULE_CACHE_TAG__ || Date.now();
local_runtime_cache_tag = globalThis.__XU_RUNTIME_MODULE_CACHE_TAG__;
}
const cache_tag =
local_runtime_cache_tag ||
_session?.build_info?.runtime_ts ||
_session?.build_info?.last_changed_ts ||
_session?.build_info?.server_ts ||
_session?.opt?.app_build_id ||
0;
if (!cache_tag) {
return resource_path;
}
return `${resource_path}${resource_path.includes('?') ? '&' : '?'}ts=${cache_tag}`;
};
if (_session.worker_type === 'Dev') {
ret = await get_ret(append_ts('./modules/' + module));
return ret;
}
if (_session.worker_type === 'Debug') {
if (typeof IS_DOCKER !== 'undefined' || typeof IS_PROCESS_SERVER !== 'undefined') {
ret = await get_ret(func.utils.get_resource_filename(['live_preview', 'miniapp'].includes(_session.engine_mode) ? '' : _session?.opt?.app_build_id, `${_conf.xuda_home}root/dist/runtime/js/modules/` + module));
} else {
ret = await get_ret(
append_ts(
func.common.get_url(
SESSION_ID,
'dist',
func.utils.get_resource_filename(['live_preview', 'miniapp'].includes(_session.engine_mode) ? '' : _session?.opt?.app_build_id, 'runtime/js/modules/' + module),
),
),
);
}
return ret;
}
const rep = function () {
return module.endsWith('.js') ? module.replace('.js', '.min.js') : module.replace('.mjs', '.min.mjs');
};
if (typeof IS_DOCKER !== 'undefined' || typeof IS_PROCESS_SERVER !== 'undefined') {
ret = await get_ret(func.utils.get_resource_filename(['live_preview', 'miniapp'].includes(_session.engine_mode) ? '' : _session?.opt?.app_build_id, `${_conf.xuda_home}root/dist/runtime/js/modules/` + rep()));
} else {
ret = await get_ret(
append_ts(
func.common.get_url(
SESSION_ID,
'dist',
func.utils.get_resource_filename(['live_preview', 'miniapp'].includes(_session.engine_mode) ? '' : _session?.opt?.app_build_id, 'runtime/js/modules/' + rep()),
),
),
);
}
return ret;
};
func.api = {};
func.api.set_field_value = async function (field_id, value, avoid_refresh) {
const SESSION_ID = Object.keys(SESSION_OBJ)[0];
const api_utils = await func.common.get_module(SESSION_ID, 'xuda-api-library.mjs', {
func,
glb,
SESSION_OBJ,
SESSION_ID,
APP_OBJ,
dsSession: func.utils.get_last_datasource_no(SESSION_ID),
});
return await api_utils.set_field_value(field_id, value, avoid_refresh);
};
func.api.get_field_value = async function (field_id) {
const SESSION_ID = Object.keys(SESSION_OBJ)[0];
const api_utils = await func.common.get_module(SESSION_ID, 'xuda-api-library.mjs', {
func,
glb,
SESSION_OBJ,
SESSION_ID,
APP_OBJ,
dsSession: func.utils.get_last_datasource_no(SESSION_ID),
});
return await api_utils.get_field_value(field_id);
};
func.api.invoke_event = async function (event_id, options) {
const SESSION_ID = Object.keys(SESSION_OBJ)[0];
const api_utils = await func.common.get_module(SESSION_ID, 'xuda-api-library.mjs', {
func,
glb,
SESSION_OBJ,
SESSION_ID,
APP_OBJ,
dsSession: func.utils.get_last_datasource_no(SESSION_ID),
});
return await api_utils.invoke_event(event_id, options);
};
func.api.call_project_api = async function (prog_id, params) {
const SESSION_ID = Object.keys(SESSION_OBJ)[0];
const api_utils = await func.common.get_module(SESSION_ID, 'xuda-api-library.mjs', {
func,
glb,
SESSION_OBJ,
SESSION_ID,
APP_OBJ,
dsSession: func.utils.get_last_datasource_no(SESSION_ID),
});
return await api_utils.call_project_api(prog_id, params, null);
};
func.api.call_system_api = async function (api_method, payload) {
const SESSION_ID = Object.keys(SESSION_OBJ)[0];
const api_utils = await func.common.get_module(SESSION_ID, 'xuda-api-library.mjs', {
func,
glb,
SESSION_OBJ,
SESSION_ID,
APP_OBJ,
dsSession: func.utils.get_last_datasource_no(SESSION_ID),
});
return await api_utils.call_system_api(api_method, payload, null);
};
func.api.dbs_create = async function (table_id, data, cb) {
const SESSION_ID = Object.keys(SESSION_OBJ)[0];
const api_utils = await func.common.get_module(SESSION_ID, 'xuda-api-library.mjs', {
func,
glb,
SESSION_OBJ,
SESSION_ID,
APP_OBJ,
dsSession: func.utils.get_last_datasource_no(SESSION_ID),
});
return await api_utils.dbs_create(table_id, row_id, data, cb);
};
func.api.dbs_read = async function (table_id, selector, fields, sort, limit, skip, cb) {
const SESSION_ID = Object.keys(SESSION_OBJ)[0];
const api_utils = await func.common.get_module(SESSION_ID, 'xuda-api-library.mjs', {
func,
glb,
SESSION_OBJ,
SESSION_ID,
APP_OBJ,
dsSession: func.utils.get_last_datasource_no(SESSION_ID),
});
return await api_utils.dbs_read(table_id, selector, fields, sort, limit, skip, cb);
};
func.api.dbs_update = async function (table_id, row_id, data, cb) {
const SESSION_ID = Object.keys(SESSION_OBJ)[0];
const api_utils = await func.common.get_module(SESSION_ID, 'xuda-api-library.mjs', {
func,
glb,
SESSION_OBJ,
SESSION_ID,
APP_OBJ,
dsSession: func.utils.get_last_datasource_no(SESSION_ID),
});
return await api_utils.dbs_update(table_id, row_id, data, cb);
};
func.api.dbs_delete = async function (table_id, row_id, cb) {
const SESSION_ID = Object.keys(SESSION_OBJ)[0];
const api_utils = await func.common.get_module(SESSION_ID, 'xuda-api-library.mjs', {
func,
glb,
SESSION_OBJ,
SESSION_ID,
APP_OBJ,
dsSession: func.utils.get_last_datasource_no(SESSION_ID),
});
return await api_utils.dbs_delete(table_id, row_id, cb);
};
func.api.call_javascript = async function (prog_id, params, evaluate) {
const SESSION_ID = Object.keys(SESSION_OBJ)[0];
const api_utils = await func.common.get_module(SESSION_ID, 'xuda-api-library.mjs', {
func,
glb,
SESSION_OBJ,
SESSION_ID,
APP_OBJ,
dsSession: func.utils.get_last_datasource_no(SESSION_ID),
});
return await api_utils.call_javascript(prog_id, params, evaluate);
};
func.api.watch = function (path, cb, opt = {}) {
if (!path) return 'path is mandatory';
if (!cb) return 'cb (callback function) is mandatory';
const SESSION_ID = Object.keys(SESSION_OBJ)[0];
let _session = SESSION_OBJ[SESSION_ID];
if (!_session.watchers) {
_session.watchers = {};
}
_session.watchers[path] = { ...opt, handler: cb };
if (opt.immediate) {
const value = xu_get(SESSION_OBJ[SESSION_ID].DS_GLB[0], path);
cb({ path, newValue: value, oldValue: value, timestamp: Date.now(), opt });
if (opt.once) {
delete _session.watchers[path];
}
}
return 'ok';
};
// func.api.call_javascript = async function (prog_id, params, evaluate) {
// const SESSION_ID = Object.keys(SESSION_OBJ)[0];
// const api_utils = await func.common.get_module(SESSION_ID, 'xuda-api-library.mjs', {
// func,
// glb,
// SESSION_OBJ,
// SESSION_ID,
// APP_OBJ,
// dsSession: func.utils.get_last_datasource_no(SESSION_ID),
// });
// return await api_utils.call_javascript(prog_id, params, evaluate);
// };
glb.rpi_request_queue_num = 0;
func.common.perform_rpi_request = async function (SESSION_ID, serviceP, opt = {}, data) {
var _session = SESSION_OBJ[SESSION_ID];
var _data_system = _session?.DS_GLB?.[0]?.data_system;
const set_ajax = async function (stat) {
var datasource_changes = {
[0]: {
['data_system']: { SYS_GLOBAL_BOL_AJAX_BUSY: stat },
},
};
await func.datasource.update(SESSION_ID, datasource_changes);
};
if (_data_system) {
// _data_system.SYS_GLOBAL_BOL_AJAX_BUSY = 1;
await set_ajax(1);
if (!_data_system.SYS_GLOBAL_BOL_CONNECTED) {
func.utils.alerts.toast(SESSION_ID, 'Server connection error', 'You are not connected to the server, so your request cannot be processed.', 'error');
return { code: 88, data: {} };
}
}
const http = async function () {
const fetchWithTimeout = (url, options = {}, timeout = 600000) => {
// 100 seconds
const controller = new AbortController();
const { signal } = controller;
const timeoutPromise = new Promise((_, reject) =>
setTimeout(() => {
controller.abort();
reject(new Error('Request timed out'));
}, timeout),
);
const fetchPromise = fetch(url, { ...options, signal });
return Promise.race([fetchPromise, timeoutPromise]);
};
var url = func.common.get_url(SESSION_ID, 'rpi', '');
var _session = SESSION_OBJ[SESSION_ID];
const app_id = _session.app_id;
if (APP_OBJ[app_id].is_deployment && _session.rpi_http_methods?.includes(serviceP)) {
const origin =
((typeof globalThis !== 'undefined' && globalThis.__XU_SERVER_ORIGIN__)) ||
(_session.host ? 'https://' + _session.host : '');
url = origin ? origin + '/rpi/' : url;
}
url += serviceP;
try {
const response = await fetchWithTimeout(url, {
method: opt.type ? opt.type : 'POST',
headers: {
Accept: 'application/json',
'Content-Type': 'application/json',
'xu-gtp-token': _session.gtp_token,
'xu-app-token': _session.app_token,
},
body: JSON.stringify(data),
});
if (!response.ok) {
throw response.status;
}
const json = await response.json();
return json;
} catch (err) {
console.error(err);
if (err === 503) {
_this.func.UI.utils.progressScreen.show(SESSION_ID, `Error code ${err}, reloading in 5 sec`);
setTimeout(async () => {
await func.index.delete_pouch(SESSION_ID);
location.reload();
}, 5000);
}
return {};
}
};
try {
if (_session.engine_mode === 'live_preview') {
throw new Error('live_preview');
}
if (_session.engine_mode === 'miniapp') {
throw new Error('miniapp');
}
if (SESSION_OBJ?.[SESSION_ID]?.rpi_http_methods?.includes(serviceP)) {
const ret = await func.common.get_data_from_websocket(SESSION_ID, serviceP, data);
if (_data_system) {
// _data_system.SYS_GLOBAL_BOL_AJAX_BUSY = 0;
await set_ajax(0);
}
return ret;
} else {
throw new Error('method not found in rpi_http_methods');
}
} catch (err) {
const ret = await http();
if (_data_system) {
// _data_system.SYS_GLOBAL_BOL_AJAX_BUSY = 0;
await set_ajax(0);
}
return ret;
}
};
func.common.get_data_from_websocket = async function (SESSION_ID, serviceP, data) {
var _session = SESSION_OBJ[SESSION_ID];
return new Promise(function (resolve, reject) {
const dbs_calls = function () {
glb.websocket_queue_num++;
const obj = {
service: serviceP,
data,
websocket_queue_num: glb.websocket_queue_num,
};
if (glb.IS_WORKER) {
func.utils.post_back_to_client(SESSION_ID, 'get_dbs_data_from_websocket', _session.worker_id, obj);
self.addEventListener('get_ws_data_worker_' + glb.websocket_queue_num, (event) => {
resolve(event.detail.data);
});
// throw new Error("not ready yet");
} else {
if (RUNTIME_SERVER_WEBSOCKET && RUNTIME_SERVER_WEBSOCKET_CONNECTED) {
RUNTIME_SERVER_WEBSOCKET.emit('message', obj);
const _ws_event = 'get_ws_data_response_' + glb.websocket_queue_num;
const _ws_handler = function (data) {
resolve(data.data);
func.runtime.platform.off('get_ws_data_response_' + data.e.websocket_queue_num, _ws_handler);
};
func.runtime.platform.on(_ws_event, _ws_handler);
} else {
throw new Error('fail to fetch from ws websocket inactive');
}
}
};
const heartbeat = function () {
const obj = {
service: 'heartbeat',
data,
};
if (RUNTIME_SERVER_WEBSOCKET && RUNTIME_SERVER_WEBSOCKET_CONNECTED) {
RUNTIME_SERVER_WEBSOCKET.emit('message', obj);
const _hb_handler = function (data) {
resolve(data.data);
func.runtime.platform.off('heartbeat_response', _hb_handler);
};
func.runtime.platform.on('heartbeat_response', _hb_handler);
} else {
throw new Error('fail to fetch from ws websocket inactive');
}
};
if (serviceP === 'heartbeat') {
return heartbeat();
}
dbs_calls();
});
};
// func.common.sha256 = async function (inputString) {
// // 1. Create a hash buffer from the input string using SHA-256.
// // This part remains the same as it provides a strong, unique cryptographic starting point.
// const buffer = await crypto.subtle.digest('SHA-256', new TextEncoder().encode(inputString));
// // 2. Interpret the first 8 bytes (64 bits) of the hash as one big number.
// const view = new DataView(buffer);
// const bigInt = view.getBigUint64(0, false); // `false` for big-endian
// // 3. Convert the BigInt to a Base36 string.
// // The .toString(36) method handles the conversion to an alphanumeric representation (0-9, a-z).
// const base36Hash = bigInt.toString(36);
// // 4. Take the first 10 characters. If it's shorter, it will just return the whole string.
// // For a 64-bit integer, the Base36 representation will be about 13 characters long,
// // so slicing is a reliable way to get a fixed length.
// const shortHash = base36Hash.slice(0, 10);
// // 5. Pad the start in the unlikely case the hash is shorter than 10 characters.
// // This ensures the output is always exactly 10 characters long.
// return shortHash.padStart(10, '0');
// };
func.common.fastHash = function (inputString) {
let hash = 0x811c9dc5; // FNV offset basis
for (let i = 0; i < inputString.length; i++) {
hash ^= inputString.charCodeAt(i);
// FNV prime multiplication with 32-bit overflow
hash += (hash << 1) + (hash << 4) + (hash << 7) + (hash << 8) + (hash << 24);
}
// Convert to base36 and pad to 10 characters
return ((hash >>> 0).toString(36) + '0000000000').slice(0, 10);
};
glb.new_xu_render = false;
// XU_PERF: opt-in fast paths (shallow render-context copies, drive-ref
// pre-scan, keyed xu-for reuse). Default off = legacy behavior byte-for-byte.
glb.XU_PERF = glb.XU_PERF || false;
func.runtime = func.runtime || {};
func.runtime.ui = func.runtime.ui || {};
func.runtime.render = func.runtime.render || {};
func.runtime.widgets = func.runtime.widgets || {};
// Shared render-tree contract helpers live here so browser and headless runtimes can resolve the same UI structure.
func.runtime.render.TREE_CONTRACT_VERSION = func.runtime.render.TREE_CONTRACT_VERSION || 'xuda.render_tree.v1';
func.runtime.render._tree_widget_capability_cache = func.runtime.render._tree_widget_capability_cache || {};
func.runtime.render.safe_clone_tree_value = function (value) {
if (typeof structuredClone === 'function') {
try {
return structuredClone(value);
} catch (_) {
// Fall through to the recursive clone below.
}
}
if (Array.isArray(value)) {
return value.map(function (item) {
return func.runtime.render.safe_clone_tree_value(item);
});
}
if (value && typeof value === 'object') {
const cloned = {};
const keys = Object.keys(value);
for (let index = 0; index < keys.length; index++) {
const key = keys[index];
cloned[key] = func.runtime.render.safe_clone_tree_value(value[key]);
}
return cloned;
}
return value;
};
func.runtime.render.sort_tree_debug_value = function (value) {
if (Array.isArray(value)) {
return value.map(function (item) {
return func.runtime.render.sort_tree_debug_value(item);
});
}
if (value && typeof value === 'object') {
const sorted = {};
const keys = Object.keys(value).sort();
for (let index = 0; index < keys.length; index++) {
const key = keys[index];
sorted[key] = func.runtime.render.sort_tree_debug_value(value[key]);
}
return sorted;
}
return value;
};
func.runtime.render.is_tree_node = function (nodeP) {
return !!nodeP?.contract && nodeP.contract === func.runtime.render.TREE_CONTRACT_VERSION;
};
func.runtime.render.get_tree_source_node = function (nodeP) {
if (!func.runtime.render.is_tree_node(nodeP)) {
return nodeP || null;
}
return nodeP?.meta?.source_node || null;
};
func.runtime.render.get_tree_source_snapshot = function (nodeP) {
if (!func.runtime.render.is_tree_node(nodeP)) {
return func.runtime.render.safe_clone_tree_value(nodeP);
}
return nodeP?.meta?.source_snapshot || null;
};
func.runtime.render.get_tree_node_kind = function (nodeP) {
const tag_name = typeof nodeP?.tagName === 'string' ? nodeP.tagName.toLowerCase() : '';
const node_type = typeof nodeP?.type === 'string' ? nodeP.type.toLowerCase() : '';
if (tag_name === 'xu-widget') return 'widget';
if (tag_name === 'xu-single-view') return 'single_view';
if (tag_name === 'xu-multi-view') return 'multi_view';
if (tag_name === 'xu-panel') return 'panel';
if (tag_name === 'xu-teleport') return 'teleport';
if (tag_name === 'xurender') return 'placeholder';
if (tag_name === '#text' || node_type === 'text') return 'text';
if (!tag_name && typeof nodeP?.content === 'string' && !Array.isArray(nodeP?.children)) return 'text';
return 'element';
};
func.runtime.render.get_tree_node_id = function (nodeP, pathP) {
if (nodeP?.id) {
return nodeP.id;
}
if (nodeP?.id_org) {
return nodeP.id_org;
}
const normalized_path = Array.isArray(pathP) && pathP.length ? pathP.join('.') : 'root';
return `tree-node-${normalized_path}`;
};
func.runtime.render.get_tree_controls = function (attributes) {
const attrs = attributes || {};
const get_first_defined = function (keys) {
for (let index = 0; index < keys.length; index++) {
const key = keys[index];
if (Object.prototype.hasOwnProperty.call(attrs, key)) {
return attrs[key];
}
}
return null;
};
return {
xu_for: get_first_defined(['xu-for', 'xu-exp:xu-for']),
xu_if: get_first_defined(['xu-if', 'xu-exp:xu-if']),
xu_render: get_first_defined(['xu-render', 'xu-exp:xu-render']),
};
};
func.runtime.render.get_tree_node_capabilities = async function (options) {
const attributes = options?.attributes || {};
const plugin_name = attributes['xu-widget'];
if (!plugin_name) {
return null;
}
const cache = func.runtime.render._tree_widget_capability_cache;
if (cache[plugin_name]) {
return func.runtime.render.safe_clone_tree_value(cache[plugin_name]);
}
let capabilities = {
browser: true,
headless: false,
};
try {
if (options.SESSION_ID && options.paramsP && func.runtime.widgets?.create_context && func.runtime.widgets?.get_definition) {
const widget_context = func.runtime.widgets.create_context(options.SESSION_ID, options.paramsP, attributes);
const definition = await func.runtime.widgets.get_definition(widget_context);
capabilities = func.runtime.widgets.normalize_capabilities(definition);
}
} catch (_) {
// Keep the safe browser-only default when the widget definition is unavailable.
}
cache[plugin_name] = capabilities;
return func.runtime.render.safe_clone_tree_value(capabilities);
};
func.runtime.render.ensure_tree_node = async function (options) {
if (!options?.nodeP) {
return null;
}
if (func.runtime.render.is_tree_node(options.nodeP)) {
return options.nodeP;
}
return await func.runtime.render.build_tree(options);
};
func.runtime.render.build_tree = async function (options) {
if (Array.isArray(options?.nodeP)) {
return await func.runtime.render.build_tree_list({
...options,
nodesP: options.nodeP,
});
}
const nodeP = options?.nodeP;
if (!nodeP) {
return null;
}
if (func.runtime.render.is_tree_node(nodeP)) {
return nodeP;
}
const pathP = Array.isArray(options?.pathP) ? options.pathP.slice() : [];
const tree_path = pathP.length ? pathP.slice() : [0];
const attributes = func.runtime.render.safe_clone_tree_value(nodeP.attributes || {});
const has_child_nodes = Array.isArray(nodeP.children) && nodeP.children.length > 0;
if (typeof nodeP.content !== 'undefined' && typeof attributes['xu-content'] === 'undefined' && !has_child_nodes && nodeP.content !== '') {
attributes['xu-content'] = func.runtime.render.safe_clone_tree_value(nodeP.content);
}
const widget_capabilities = await func.runtime.render.get_tree_node_capabilities({
SESSION_ID: options?.SESSION_ID,
paramsP: options?.paramsP,
attributes,
});
const children = [];
const child_nodes = Array.isArray(nodeP.children) ? nodeP.children : [];
const parent_tree_id = tree_path.join('.');
for (let index = 0; index < child_nodes.length; index++) {
const child_tree = await func.runtime.render.build_tree({
...options,
nodeP: child_nodes[index],
pathP: tree_path.concat(index),
parent_tree_id: parent_tree_id,
keyP: index,
parent_nodeP: nodeP,
});
if (child_tree) {
children.push(child_tree);
}
}
const tree = {
contract: func.runtime.render.TREE_CONTRACT_VERSION,
id: func.runtime.render.get_tree_node_id(nodeP, tree_path),
xu_tree_id: `tree.${tree_path.join('.')}`,
kind: func.runtime.render.get_tree_node_kind(nodeP),
tagName: nodeP.tagName || null,
attributes,
text: typeof nodeP.text !== 'undefined' ? func.runtime.render.safe_clone_tree_value(nodeP.text) : null,
content: typeof nodeP.content !== 'undefined' ? func.runtime.render.safe_clone_tree_value(nodeP.content) : null,
children,
meta: {
tree_id: tree_path.join('.'),
path: tree_path,
parent_tree_id: options?.parent_tree_id || null,
key: typeof options?.keyP === 'undefined' ? null : options.keyP,
recordid: nodeP?.recordid || null,
dependency_fields: func.runtime.render.safe_clone_tree_value(nodeP?.dependency_fields || null),
iterate_info: func.runtime.render.safe_clone_tree_value(options?.parent_infoP?.iterate_info || nodeP?.iterate_info || null),
controls: func.runtime.render.get_tree_controls(attributes),
capabilities: widget_capabilities,
widget: attributes['xu-widget']
? {
plugin_name: attributes['xu-widget'],
method: attributes['xu-method'] || '_default',
capabilities: widget_capabilities,
}
: null,
source_node_id: nodeP?.id || nodeP?.id_org || null,
source_node: nodeP,
source_snapshot: func.runtime.ui?.get_node_snapshot
? func.runtime.ui.get_node_snapshot(nodeP)
: func.runtime.render.safe_clone_tree_value(nodeP),
},
};
return tree;
};
func.runtime.render.build_tree_list = async function (options) {
const nodes = Array.isArray(options?.nodesP) ? options.nodesP : [];
const trees = [];
for (let index = 0; index < nodes.length; index++) {
const tree = await func.runtime.render.build_tree({
...options,
nodeP: nodes[index],
pathP: Array.isArray(options?.pathP) && options.pathP.length ? options.pathP.concat(index) : [index],
keyP: index,
});
if (tree) {
trees.push(tree);
}
}
return trees;
};
func.runtime.render.sanitize_tree_for_debug = function (treeP) {
if (Array.isArray(treeP)) {
return treeP.map(function (child) {
return func.runtime.render.sanitize_tree_for_debug(child);
});
}
if (!func.runtime.render.is_tree_node(treeP)) {
return func.runtime.render.sort_tree_debug_value(func.runtime.render.safe_clone_tree_value(treeP));
}
return {
contract: treeP.contract,
id: treeP.id,
xu_tree_id: treeP.xu_tree_id || null,
kind: treeP.kind,
tagName: treeP.tagName,
attributes: func.runtime.render.sort_tree_debug_value(treeP.attributes || {}),
text: treeP.text,
content: treeP.content,
children: treeP.children.map(function (child) {
return func.runtime.render.sanitize_tree_for_debug(child);
}),
meta: {
tree_id: treeP.meta?.tree_id || null,
path: func.runtime.render.safe_clone_tree_value(treeP.meta?.path || []),
parent_tree_id: treeP.meta?.parent_tree_id || null,
key: typeof treeP.meta?.key === 'undefined' ? null : treeP.meta.key,
recordid: treeP.meta?.recordid || null,
dependency_fields: func.runtime.render.sort_tree_debug_value(treeP.meta?.dependency_fields || null),
iterate_info: func.runtime.render.sort_tree_debug_value(treeP.meta?.iterate_info || null),
controls: func.runtime.render.sort_tree_debug_value(treeP.meta?.controls || null),
capabilities: func.runtime.render.sort_tree_debug_value(treeP.meta?.capabilities || null),
widget: treeP.meta?.widget
? {
plugin_name: treeP.meta.widget.plugin_name,
method: treeP.meta.widget.method,
capabilities: func.runtime.render.sort_tree_debug_value(treeP.meta.widget.capabilities || null),
}
: null,
source_node_id: treeP.meta?.source_node_id || null,
},
};
};
func.runtime.render.serialize_tree = function (treeP, spacing = 2) {
return JSON.stringify(func.runtime.render.sanitize_tree_for_debug(treeP), null, spacing);
};
func.runtime = func.runtime || {};
func.runtime.ui = func.runtime.ui || {};
func.runtime.render = func.runtime.render || {};
func.runtime.widgets = func.runtime.widgets || {};
// Shared string-renderer helpers live here so headless/server runtimes can materialize the render tree without a DOM.
func.runtime.render.HTML_VOID_TAGS = func.runtime.render.HTML_VOID_TAGS || {
area: true,
base: true,
br: true,
col: true,
embed: true,
hr: true,
img: true,
input: true,
link: true,
meta: true,
param: true,
source: true,
track: true,
wbr: true,
};
func.runtime.render.escape_html = function (value) {
return `${value ?? ''}`
.replaceAll('&', '&')
.replaceAll('<', '<')
.replaceAll('>', '>')
.replaceAll('"', '"')
.replaceAll("'", ''');
};
func.runtime.render.escape_html_attribute = function (value) {
return func.runtime.render.escape_html(value);
};
func.runtime.render.is_html_void_tag = function (tag_name) {
return !!func.runtime.render.HTML_VOID_TAGS[(tag_name || '').toLowerCase()];
};
func.runtime.render.is_falsey_render_value = function (value) {
if (value === false || value === null || typeof value === 'undefined') {
return true;
}
if (typeof value === 'number') {
return value === 0;
}
if (typeof value === 'string') {
const normalized = value.trim().toLowerCase();
return normalized === '' || normalized === 'false' || normalized === '0' || normalized === 'null' || normalized === 'undefined' || normalized === 'off' || normalized === 'no';
}
return false;
};
func.runtime.render.should_render_tree_node = function (treeP) {
const controls = treeP?.meta?.controls || {};
if (controls.xu_if !== null && controls.xu_if !== undefined && func.runtime.render.is_falsey_render_value(controls.xu_if)) {
return false;
}
if (controls.xu_render !== null && controls.xu_render !== undefined && func.runtime.render.is_falsey_render_value(controls.xu_render)) {
return false;
}
return true;
};
func.runtime.render.is_tree_control_attribute = function (key) {
if (!key) {
return false;
}
return (
key.startsWith('xu-exp:') ||
key === 'xu-widget' ||
key === 'xu-method' ||
key === 'xu-for' ||
key === 'xu-for-key' ||
key === 'xu-for-val' ||
key === 'xu-if' ||
key === 'xu-render' ||
key === 'xu-bind' ||
key === 'xu-content' ||
key === 'xu-text' ||
key === 'xu-html' ||
key === 'xu-show' ||
key === 'xu-panel-program' ||
key === 'xu-teleport'
);
};
func.runtime.render.get_string_renderer_tag_name = function (treeP) {
switch (treeP?.kind) {
case 'widget':
case 'single_view':
case 'multi_view':
case 'panel':
case 'teleport':
return 'div';
case 'placeholder':
return null;
case 'text':
return null;
default:
return treeP?.tagName || 'div';
}
};
func.runtime.render.get_tree_terminal_content = function (treeP) {
const attributes = treeP?.attributes || {};
if (typeof attributes['xu-html'] !== 'undefined' && attributes['xu-html'] !== null) {
return {
value: `${attributes['xu-html']}`,
mode: 'html',
};
}
if (typeof attributes['xu-content'] !== 'undefined' && attributes['xu-content'] !== null) {
return {
value: `${attributes['xu-content']}`,
mode: 'html',
};
}
if (typeof attributes['xu-text'] !== 'undefined' && attributes['xu-text'] !== null) {
return {
value: `${attributes['xu-text']}`,
mode: 'text',
};
}
if (treeP?.kind === 'text') {
return {
value: typeof treeP?.text !== 'undefined' && treeP?.text !== null ? `${treeP.text}` : `${treeP?.content || ''}`,
mode: 'text',
};
}
return null;
};
func.runtime.render.render_tree_terminal_content = function (treeP) {
const terminal = func.runtime.render.get_tree_terminal_content(treeP);
if (!terminal) {
return null;
}
if (terminal.mode === 'html') {
return terminal.value;
}
return func.runtime.render.escape_html(terminal.value);
};
func.runtime.render.get_widget_fallback_markup = function (treeP) {
const widget_meta = treeP?.meta?.widget || {};
const capability_state = widget_meta?.capabilities?.headless ? 'headless-capable' : 'browser-only';
return `<!--xuda-widget:${func.runtime.render.escape_html(widget_meta.plugin_name || 'unknown')}:${capability_state}-->`;
};
func.runtime.render.get_tree_string_attributes = function (treeP, renderer_context) {
const attributes = func.runtime.render.safe_clone_tree_value(treeP?.attributes || {});
const attr_pairs = [];
const keys = Object.keys(attributes);
for (let index = 0; index < keys.length; index++) {
const key = keys[index];
if (func.runtime.render.is_tree_control_attribute(key)) {
continue;
}
const value = attributes[key];
if (value === false || value === null || typeof value === 'undefined') {
continue;
}
if (value === true) {
attr_pairs.push(key);
continue;
}
const normalized_value = typeof value === 'object' ? JSON.stringify(value) : `${value}`;
attr_pairs.push(`${key}="${func.runtime.render.escape_html_attribute(normalized_value)}"`);
}
attr_pairs.push(`data-xuda-kind="${func.runtime.render.escape_html_attribute(treeP?.kind || 'element')}"`);
attr_pairs.push(`data-xuda-node-id="${func.runtime.render.escape_html_attribute(treeP?.id || treeP?.meta?.source_node_id || '')}"`);
attr_pairs.push(`data-xuda-tree-id="${func.runtime.render.escape_html_attribute(treeP?.meta?.tree_id || '')}"`);
if (treeP?.kind === 'widget' && treeP?.meta?.widget) {
attr_pairs.push(`data-xuda-widget="${func.runtime.render.escape_html_attribute(treeP.meta.widget.plugin_name || '')}"`);
attr_pairs.push(`data-xuda-widget-method="${func.runtime.render.escape_html_attribute(treeP.meta.widget.method || '_default')}"`);
attr_pairs.push(`data-xuda-widget-capability="${func.runtime.render.escape_html_attribute(treeP.meta.widget.capabilities?.headless ? 'headless' : 'browser')}"`);
}
if (treeP?.kind === 'teleport' && treeP?.attributes?.['xu-teleport']) {
attr_pairs.push(`data-xuda-teleport-target="${func.runtime.render.escape_html_attribute(treeP.attributes['xu-teleport'])}"`);
}
if ((treeP?.meta?.controls?.xu_for !== null && treeP?.meta?.controls?.xu_for !== undefined) && !renderer_context?.strip_iteration_markers) {
attr_pairs.push('data-xuda-xu-for="pending"');
}
return attr_pairs.length ? ' ' + attr_pairs.join(' ') : '';
};
func.runtime.render.render_tree_children_to_string = async function (treeP, renderer_context) {
if (!Array.isArray(treeP?.children) || !treeP.children.length) {
return '';
}
let html = '';
for (let index = 0; index < treeP.children.length; index++) {
html += await func.runtime.render.render_tree_to_string(treeP.children[index], {
...renderer_context,
parent_tree: treeP,
});
}
return html;
};
func.runtime.render.render_tree_to_string = async function (treeP, renderer_context = {}) {
if (!treeP) {
return '';
}
if (Array.isArray(treeP)) {
let html = '';
for (let index = 0; index < treeP.length; index++) {
html += await func.runtime.render.render_tree_to_string(treeP[index], renderer_context);
}
return html;
}
const ensured_tree = await func.runtime.render.ensure_tree_node({
SESSION_ID: renderer_context?.SESSION_ID,
nodeP: treeP,
paramsP: renderer_context?.paramsP,
parent_infoP: renderer_context?.parent_infoP,
keyP: renderer_context?.keyP,
parent_nodeP: renderer_context?.parent_nodeP,
});
if (!ensured_tree || !func.runtime.render.should_render_tree_node(ensured_tree)) {
return '';
}
if (ensured_tree.kind === 'placeholder') {
if (renderer_context?.include_placeholders) {
return `<!--xuda-placeholder:${func.runtime.render.escape_html(ensured_tree.id || '')}-->`;
}
return '';
}
if (ensured_tree.kind === 'text') {
return func.runtime.render.render_tree_terminal_content(ensured_tree) || '';
}
const tag_name = func.runtime.render.get_string_renderer_tag_name(ensured_tree);
if (!tag_name || tag_name.toLowerCase() === 'script') {
return '';
}
const attributes = func.runtime.render.get_tree_string_attributes(ensured_tree, renderer_context);
const terminal_content = func.runtime.render.render_tree_terminal_content(ensured_tree);
let children_html = terminal_content !== null ? terminal_content : await func.runtime.render.render_tree_children_to_string(ensured_tree, renderer_context);
if (ensured_tree.kind === 'widget' && !children_html) {
children_html = func.runtime.render.get_widget_fallback_markup(ensured_tree);
}
if (func.runtime.render.is_html_void_tag(tag_name)) {
return `<${tag_name}${attributes}>`;
}
return `<${tag_name}${attributes}>${children_html}</${tag_name}>`;
};
func.runtime.render.render_to_string = async function (options = {}) {
const treeP = await func.runtime.render.ensure_tree_node({
SESSION_ID: options.SESSION_ID,
nodeP: options.treeP || options.nodeP,
paramsP: options.paramsP,
parent_infoP: options.parent_infoP,
keyP: options.keyP,
parent_nodeP: options.parent_nodeP,
});
return await func.runtime.render.render_tree_to_string(treeP, options);
};
func.runtime.render.get_server_render_mode = function (options = {}) {
const normalized = func.runtime.render.normalize_runtime_bootstrap({
app_computing_mode: options.app_computing_mode,
app_render_mode: options.app_render_mode,
app_client_activation: options.app_client_activation,
});
return normalized;
};
func.runtime.render.build_server_render_params = async function (options = {}) {
const SESSION_ID = options.SESSION_ID;
const prog_id = options.prog_id;
const dsSessionP = options.dsSessionP;
const _session = SESSION_OBJ?.[SESSION_ID] || {};
const _ds = _session?.DS_GLB?.[dsSessionP] || {};
const viewDoc = options.viewDoc || (await func.utils?.VIEWS_OBJ?.get?.(SESSION_ID, prog_id));
if (!viewDoc?.properties) {
throw new Error(`view document not found for ${prog_id}`);
}
const base_params = _ds?.screen_params ? func.runtime.render.safe_clone_tree_value(_ds.screen_params) : {};
const screenId = options.screenId || base_params.screenId || `ssr_${prog_id}_${dsSessionP || '0'}`;
const paramsP = {
...base_params,
prog_id,
sourceScreenP: null,
$callingContainerP: null,
triggerIdP: null,
callingDataSource_objP: _ds,
rowIdP: typeof options.rowIdP !== 'undefined' ? options.rowIdP : (_ds?.currentRecordId || null),
renderType: viewDoc.properties?.renderType,
parameters_obj_inP: options.parameters_obj_inP || base_params.parameters_obj_inP || options.parameters_raw_obj || {},
source_functionP: options.source_functionP || base_params.source_functionP || 'render_string',
is_panelP: false,
screen_type: options.screen_type || base_params.screen_type || 'render_string',
screenInfo: viewDoc,
call_screen_propertiesP: base_params.call_screen_propertiesP,
parentDataSourceNoP: typeof _ds?.parentDataSourceNo === 'undefined' || _ds?.parentDataSourceNo === null ? 0 : _ds.parentDataSourceNo,
parameters_raw_obj: options.parameters_raw_obj || base_params.parameters_raw_obj || {},
dsSessionP,
screenId,
containerIdP: base_params.containerIdP || `ssr_container_${screenId}`,
};
if (_ds) {
_ds.screen_params = paramsP;
}
return paramsP;
};
func.runtime.render.build_prog_tree = async function (options = {}) {
const SESSION_ID = options.SESSION_ID;
const prog_id = options.prog_id;
const viewDoc = options.viewDoc || (await func.utils?.VIEWS_OBJ?.get?.(SESSION_ID, prog_id));
if (!viewDoc?.progUi?.length) {
throw new Error(`progUi not found for ${prog_id}`);
}
const paramsP = options.paramsP || (await func.runtime.render.build_server_render_params({
...options,
SESSION_ID,
prog_id,
viewDoc,
}));
const root_index = typeof options.root_index === 'number' ? options.root_index : 0;
const root_node = func.runtime.render.safe_clone_tree_value(viewDoc.progUi[root_index]);
const tree = await func.runtime.render.build_tree({
SESSION_ID,
nodeP: root_node,
paramsP,
});
return {
tree,
paramsP,
viewDoc,
};
};
func.runtime.render.build_ssr_payload = function (render_program, options = {}) {
const runtime_profile = func.runtime.render.get_server_render_mode(options);
return {
contract: 'xuda.ssr.v1',
prog_id: options.prog_id,
screenId: render_program.paramsP.screenId,
containerId: render_program.paramsP.containerIdP,
app_computing_mode: runtime_profile.app_computing_mode,
app_render_mode: runtime_profile.app_render_mode,
app_client_activation: runtime_profile.app_client_activation,
tree_contract: func.runtime.render.TREE_CONTRACT_VERSION,
};
};
func.runtime.render.build_ssr_screen_html = function (html, render_program, options = {}) {
const payload = func.runtime.render.build_ssr_payload(render_program, options);
const screenId = func.runtime.render.escape_html_attribute(payload.screenId || '');
const containerId = func.runtime.render.escape_html_attribute(payload.containerId || '');
const activation = func.runtime.render.escape_html_attribute(payload.app_client_activation || 'takeover');
return `<div data-xuda-ssr-embed="true" class="xu_embed_div"><div id="${screenId}" class="xu_embed_container" data-xuda-ssr-screen="true" data-xuda-ssr-screen-id="${screenId}" data-xuda-activation="${activation}" style="display: contents;"><div id="${containerId}" data-xuda-ssr-root-frame="true" data-xuda-ssr-screen-id="${screenId}" data-xuda-activation="${activation}" style="display: contents;">${html}</div></div></div>`;
};
func.runtime.render.render_prog_to_string = async function (options = {}) {
const render_program = await func.runtime.render.build_prog_tree(options);
const html = await func.runtime.render.render_to_string({
...options,
SESSION_ID: options.SESSION_ID,
treeP: render_program.tree,
paramsP: render_program.paramsP,
});
const ssr_payload = func.runtime.render.build_ssr_payload(render_program, options);
const screen_html = func.runtime.render.build_ssr_screen_html(html, render_program, options);
return {
prog_id: options.prog_id,
dsSessionP: render_program.paramsP.dsSessionP,
screenId: render_program.paramsP.screenId,
html,
screen_html,
tree_json: func.runtime.render.serialize_tree(render_program.tree),
paramsP: render_program.paramsP,
ssr_payload,
};
};
glb.DEBUG_INFO_OBJ = {};
// var CONNECTION_ATTEMPTS = 0;
glb.APP_INFO = {};
// var vars = {};
// var USERS_CACHE = {};
var SYSTEM_READY = null;
// var CPI_WEBSOCKET;
var GLB_JS_SCRIPTS_LOADED = [];
var STUDIO_WEBSOCKET = null;
var STUDIO_WEBSOCKET_CONNECTION_ID = null;
var STUDIO_PEER = null;
var STUDIO_PEER_CONN_SEND_METHOD = null;
var STUDIO_PEER_CONN_ID = null;
var SUPPORT_PEER = null;
var SUPPORT_PEER_CONN = null;
var STUDIO_PEER_CONN_MSG_QUEUE = [];
var CLIENT_ACTIVITY_TS;
var IS_ONLINE;
glb.REFERENCE_LESS_FUNCTIONS = ['update', 'raise_event', 'call_library', 'invoke_action', 'loader_on', 'loader_off', 'emit_event', 'delay', 'execute_evaluate_javascript', 'execute_native_javascript'];
var CACHE_PROG_UI = {};
var ALERT_IS_ACTIVE = false;
glb.WORKER_ATTEMPTS_NOT_RESPONDING = 200000;
glb.WORKER_TIMEOUT = 600000;
glb.WORKER_PAUSE = false;
// glb.EMAIL_SEND_RECEIVE_STATUS = {};
var DATASOURCE_INTERVALS = {};
var APP_MODAL_OBJ = {};
var CURRENT_APP_POPOVER = null;
var ELEMENT_CLICK_EVENT = null;
var posX = 0; //cursor x
var posY = 0; //cursor x
var LOADER_ACTIVE = false;
var LOADER_TEXT = '';
var REFRESHER_IN_PROGRESS = false;
glb.screen_num = 0;
var RESPONSE_FROM_STUDIO_QUEUE = {};
var SCREEN_BLOCKER_OBJ = {};
var IS_PROGRESS_SCREEN_OPEN = null;
// var UI_ENGINE_OBJ = null;
var UI_WORKER_OBJ = {
jobs: [],
num: 9000,
cache: {},
viewport_height_set_ids: [],
xu_render_cache: {},
// pending_for_viewport_render: {}
};
glb.html5_events_handler = [
'onabort',
'onafterprint',
'onautocomplete',
'onautocompleteerror',
'onbeforeprint',
'onbeforeunload',
'onblur',
'oncancel',
'oncanplay',
'oncanplaythrough',
'onchange',
'onclick',
'onclose',
'oncontextmenu',
'oncopy',
'oncuechange',
'oncut',
'ondblclick',
'ondrag',
'ondragend',
'ondragenter',
'ondragexit',
'ondragleave',
'ondragover',
'ondragstart',
'ondrop',
'ondurationchange',
'onemptied',
'onended',
'onerror',
'onfocus',
'onhashchange',
'oninput',
'oninvalid',
'onkeydown',
'onkeypress',
'onkeyup',
'onload',
'onloadeddata',
'onloadedmetadata',
'onloadstart',
'onmessage',
'onmousedown',
'onmouseenter',
'onmouseleave',
'onmousemove',
'onmouseout',
'onmouseover',
'onmouseup',
'onmousewheel',
'onoffline',
'ononline',
'onpagehide',
'onpageshow',
'onpaste',
'onpause',
'onplay',
'onplaying',
'onpopstate',
'onprogress',
'onratechange',
'onreset',
'onresize',
'onscroll',
'onsearch',
'onseeked',
'onseeking',
'onselect',
'onshow',
'onsort',
'onstalled',
'onstorage',
'onsubmit',
'onsuspend',
'ontimeupdate',
'ontoggle',
'onunload',
'onvolumechange',
'onwaiting',
];
glb.lifecycle = {
plugins: {},
// queue: [],
fn_arr: ['beforeInit', 'initialized', 'systemReady', 'beforeMounted', 'mounted'],
// add(type, fn, params) {
// this.queue.push({ type, fn, params });
// },
execute: async function (SESSION_ID, event) {
const _session = SESSION_OBJ[SESSION_ID];
const xu_api = await func.common.get_module(SESSION_ID, 'xuda-api-library.mjs', {
func,
glb,
SESSION_OBJ,
SESSION_ID,
APP_OBJ,
dsSession: func.utils.get_last_datasource_no(SESSION_ID),
});
var params = {
SESSION_ID,
session_data: _session,
app_obj: APP_OBJ[_session.app_id],
xu_api,
};
for await (const [plugin_name, val] of Object.entries(glb.lifecycle.plugins)) {
if (val?.plugin_script?.[event]) {
params.setup_data = val.setup_data;
await val.plugin_script[event](params);
}
}
},
};
glb.run_xu_before = [
'xu-cdn',
'xu-style',
'xu-render',
'xu-for-key',
'xu-for-val',
// "xu-ui-plugin",
// "programParameters",
];
glb.run_xu_after = ['xu-bind', 'xu-class', 'xu-script', 'xu-ui-plugin', 'xu-ref'];
glb.attr_abbreviations_arr = ['xu-click', 'xu-dblclick', 'xu-contextmenu', 'xu-focus', 'xu-keyup', 'xu-change', 'xu-blur', 'xu-init'];
glb.solid_attributes = ['disabled'];
// ── Headless/SSR DOM adapter ──
// Provides a no-op / in-memory implementation of func.runtime.ui
// for environments without a DOM (Node.js, workers, SSR).
// Elements are plain objects; metadata lives in _meta_store.
func.runtime = func.runtime || {};
func.runtime.ui = func.runtime.ui || {};
func.runtime.render = func.runtime.render || {};
func.runtime.widgets = func.runtime.widgets || {};
func.runtime.ui.ui_id_hash_cache = func.runtime.ui.ui_id_hash_cache || new Map();
func.runtime.ui.node_snapshot_cache = func.runtime.ui.node_snapshot_cache || new WeakMap();
func.runtime.ui.node_child_items_cache = func.runtime.ui.node_child_items_cache || new WeakMap();
func.runtime.ui.node_children_by_id_cache = func.runtime.ui.node_children_by_id_cache || new WeakMap();
// ── Metadata store (shared with browser adapter) ──
func.runtime.ui._meta_store = func.runtime.ui._meta_store || {};
func.runtime.ui._element_id_to_xu_ui_id = func.runtime.ui._element_id_to_xu_ui_id || {};
func.runtime.ui.set_meta = function (xu_ui_id, key, value) {
if (!xu_ui_id) return;
if (!func.runtime.ui._meta_store[xu_ui_id]) {
func.runtime.ui._meta_store[xu_ui_id] = {};
}
func.runtime.ui._meta_store[xu_ui_id][key] = value;
};
func.runtime.ui.get_meta = function (xu_ui_id, key) {
const entry = func.runtime.ui._meta_store[xu_ui_id];
if (!entry) return undefined;
return key ? entry[key] : entry;
};
func.runtime.ui.delete_meta = function (xu_ui_id) {
delete func.runtime.ui._meta_store[xu_ui_id];
for (const id in func.runtime.ui._element_id_to_xu_ui_id) {
if (func.runtime.ui._element_id_to_xu_ui_id[id] === xu_ui_id) {
delete func.runtime.ui._element_id_to_xu_ui_id[id];
}
}
};
func.runtime.ui.register_element_id = function (element_id, xu_ui_id) {
if (element_id && xu_ui_id) {
func.runtime.ui._element_id_to_xu_ui_id[element_id] = xu_ui_id;
}
};
func.runtime.ui.get_meta_by_element_id = function (element_id) {
if (!element_id) return undefined;
const clean_id = element_id.startsWith('#') ? element_id.substring(1) : element_id;
const xu_ui_id = func.runtime.ui._element_id_to_xu_ui_id[clean_id];
if (xu_ui_id) {
return func.runtime.ui._meta_store[xu_ui_id];
}
return undefined;
};
func.runtime.ui.find_element_by_id = function () {
return null;
};
func.runtime.ui.get_parent_element_id = function () {
return null;
};
func.runtime.ui.get_session_root = function () {
return null;
};
func.runtime.ui.clear_screen_blockers = function () {
// no-op in headless
};
// ── Virtual element representation ──
var _next_id = 1;
function create_virtual_element(tag, attrs) {
return {
_v_id: _next_id++,
tag: tag || 'div',
attrs: attrs || {},
children: [],
parent: null,
style: {},
classList: [],
textContent: '',
innerHTML: '',
hidden: false,
_data: {},
};
}
// ── Core adapter methods ──
func.runtime.ui.as_jquery = function (target) {
if (!target) return { length: 0, data: function () { return {}; }, toArray: function () { return []; }, attr: function () { return undefined; } };
if (target._v_id) return target;
return target;
};
func.runtime.ui.get_first_node = function (target) {
if (!target) return null;
if (target._v_id) return target;
if (Array.isArray(target)) return target[0] || null;
return null;
};
func.runtime.ui.get_data = function (target) {
if (!target) return {};
if (target._v_id) {
const xu_ui_id = target.attrs?.['xu-ui-id'];
if (xu_ui_id) {
const meta = func.runtime.ui._meta_store[xu_ui_id];
if (meta) return meta;
}
return target._data;
}
return {};
};
func.runtime.ui.get_parent = function (target) {
if (target?._v_id) return target.parent;
return null;
};
func.runtime.ui.get_children = function (target) {
if (target?._v_id) return target.children.slice();
return [];
};
func.runtime.ui._wrap_matches = function (matches) {
if (!matches) matches = [];
const result = {
length: matches.length,
toArray: function () { return matches.slice(); },
};
for (let i = 0; i < matches.length; i++) {
result[i] = matches[i];
}
result[Symbol.iterator] = function () {
let idx = 0;
return {
next: function () {
if (idx < matches.length) {
return { value: matches[idx++], done: false };
}
return { done: true };
},
};
};
return result;
};
func.runtime.ui.find_by_selector = function () {
return func.runtime.ui._wrap_matches([]);
};
func.runtime.ui.insert_before = function ($element) { return $element; };
func.runtime.ui.insert_after = function ($element) { return $element; };
func.runtime.ui.has_selector = function () { return false; };
func.runtime.ui.append_html = function (target) { return target; };
func.runtime.ui.set_style = function (target, prop, value) {
if (target?._v_id) target.style[prop] = value;
return target;
};
func.runtime.ui.get_attr = function (target, key) {
if (target?._v_id) return target.attrs[key];
return undefined;
};
func.runtime.ui.set_attr = function (target, key, value) {
if (target?._v_id) target.attrs[key] = value;
return target;
};
func.runtime.ui.set_data = function (target, key, value) {
if (target?._v_id) {
target._data[key] = value;
const xu_ui_id = target.attrs?.['xu-ui-id'];
if (xu_ui_id) {
func.runtime.ui.set_meta(xu_ui_id, key, value);
}
}
return target;
};
func.runtime.ui.clear_data = function (target) {
if (target?._v_id) {
target._data = {};
const xu_ui_id = target.attrs?.['xu-ui-id'];
if (xu_ui_id) func.runtime.ui.delete_meta(xu_ui_id);
}
return target;
};
func.runtime.ui.add_class = function (target, cls) {
if (target?._v_id && cls && !target.classList.includes(cls)) target.classList.push(cls);
return target;
};
func.runtime.ui.remove_class = function (target, cls) {
if (target?._v_id) {
const idx = target.classList.indexOf(cls);
if (idx !== -1) target.classList.splice(idx, 1);
}
return target;
};
func.runtime.ui.set_html = function (target, value) {
if (target?._v_id) target.innerHTML = value;
return target;
};
func.runtime.ui.set_text = function (target, value) {
if (target?._v_id) target.textContent = value;
return target;
};
func.runtime.ui.show = function (target) {
if (target?._v_id) target.hidden = false;
return target;
};
func.runtime.ui.hide = function (target) {
if (target?._v_id) target.hidden = true;
return target;
};
func.runtime.ui.append = function ($target, $element) {
if ($target?._v_id && $element?._v_id) {
$target.children.push($element);
$element.parent = $target;
}
return $element;
};
func.runtime.ui.append_to = function ($element, $target) {
return func.runtime.ui.append($target, $element);
};
func.runtime.ui.empty = function (target) {
if (target?._v_id) target.children = [];
return target;
};
func.runtime.ui.remove = function (target) {
if (target?._v_id) {
const xu_ui_id = target.attrs?.['xu-ui-id'];
if (xu_ui_id) func.runtime.ui.delete_meta(xu_ui_id);
if (target.parent?._v_id) {
const idx = target.parent.children.indexOf(target);
if (idx !== -1) target.parent.children.splice(idx, 1);
}
}
return true;
};
func.runtime.ui.set_display_contents = function ($element) {
return func.runtime.ui.set_style($element, 'display', 'contents');
};
func.runtime.ui.create_xurender = function (xu_ui_id, $target, hidden) {
const el = create_virtual_element('xurender', { 'xu-ui-id': xu_ui_id });
if (hidden) el.hidden = true;
return func.runtime.ui.append_to(el, $target);
};
func.runtime.ui.replace_with = function ($source, $target) {
if ($source?._v_id && $source.parent?._v_id) {
const idx = $source.parent.children.indexOf($source);
if (idx !== -1 && $target?._v_id) {
$source.parent.children[idx] = $target;
$target.parent = $source.parent;
}
}
return $target;
};
func.runtime.ui.remove_xu_ui = function (xu_ui_id) {
func.runtime.ui.delete_meta(xu_ui_id);
return true;
};
func.runtime.ui.find_xu_ui_in_root = function () {
return func.runtime.ui._wrap_matches([]);
};
func.runtime.ui.build_debug_info = function (nodeP, $container, items) {
const container_data = func.runtime.ui.get_data($container);
return {
id: nodeP.id,
parent_id: container_data?.xuData?.ui_id,
items: items,
};
};
func.runtime.ui.get_node_snapshot = function (nodeP) {
if (!nodeP) return nodeP;
if (func.runtime.ui.node_snapshot_cache.has(nodeP)) {
return func.runtime.ui.node_snapshot_cache.get(nodeP);
}
const snapshot = structuredClone(nodeP);
func.runtime.ui.node_snapshot_cache.set(nodeP, snapshot);
return snapshot;
};
func.runtime.ui.get_node_child_items = function (nodeP) {
if (!nodeP?.children?.length) return [];
if (func.runtime.ui.node_child_items_cache.has(nodeP)) {
return func.runtime.ui.node_child_items_cache.get(nodeP);
}
const items = nodeP.children.map(function (val) { return val.xu_tree_id || val.id; });
func.runtime.ui.node_child_items_cache.set(nodeP, items);
return items;
};
func.runtime.ui.get_node_children_by_id = function (nodeP) {
if (!nodeP?.children?.length) return {};
if (func.runtime.ui.node_children_by_id_cache.has(nodeP)) {
return func.runtime.ui.node_children_by_id_cache.get(nodeP);
}
const children_by_id = {};
for (let i = 0; i < nodeP.children.length; i++) {
const child = nodeP.children[i];
if (child?.id) children_by_id[child.id] = child;
}
func.runtime.ui.node_children_by_id_cache.set(nodeP, children_by_id);
return children_by_id;
};
func.runtime.ui.build_container_xu_data = function (options) {
const container_data = func.runtime.ui.get_data(options.$container);
const containerXuData = container_data?.xuData;
return {
SESSION_ID: options.SESSION_ID,
prog_id: options.paramsP.prog_id,
nodeid: options.nodeP.id,
ui_type: options.nodeP.tagName,
recordid: options.currentRecordId,
paramsP: options.paramsP,
key: options.keyP,
key_path: options.key_path,
screenId: options.paramsP.screenId,
parent_container: containerXuData?.ui_id,
elem_key: options.elem_key,
properties: options.prop,
node: options.nodeP,
node_org: func.runtime.ui.get_node_snapshot(options.nodeP),
is_panelP: options.paramsP.is_panelP,
ui_id: options.ui_id,
elem_prop: options.elem_propP,
debug_info: func.runtime.ui.build_debug_info(options.nodeP, options.$container, options.items),
parent_node: options.parent_nodeP,
currentRecordId: options.currentRecordId,
$root_container: options.$root_container,
parent_element_ui_id: containerXuData?.ui_id,
is_placeholder: !!options.is_placeholder,
};
};
func.runtime.ui.apply_container_meta = function ($div, options) {
if ($div?._v_id) $div.attrs['xu-ui-id'] = options.ui_id;
const xuData = func.runtime.ui.build_container_xu_data(options);
if (options.parent_infoP?.iterate_info) {
xuData.iterate_info = options.parent_infoP.iterate_info;
}
if ($div?._v_id) {
$div._data.xuData = xuData;
$div._data.xuAttributes = {};
}
func.runtime.ui.set_meta(options.ui_id, 'xuData', xuData);
func.runtime.ui.set_meta(options.ui_id, 'xuAttributes', {});
if (options.is_placeholder && $div?._v_id) $div.classList.push('display_none');
if (options.classP && $div?._v_id) $div.classList.push(options.classP);
return $div;
};
func.runtime.ui.get_append_target = function ($container, $appendToP) {
return $appendToP || $container || null;
};
func.runtime.ui.create_element = function (tag_name) {
return create_virtual_element(tag_name);
};
func.runtime.ui.create_svg_element = function () {
return create_virtual_element('svg');
};
func.runtime.ui.create_container_element = function (div_typeP) {
return create_virtual_element(div_typeP || 'div');
};
func.runtime.ui.build_xu_ui_id_seed = function (nodeP, dsSessionP, key_path, currentRecordId) {
const nodeId = nodeP.xu_tree_id || nodeP.id;
const elem_key = `${nodeId}-${key_path}-${currentRecordId}`;
return `${nodeP.id}-${elem_key}-${dsSessionP?.toString() || ''}`;
};
func.runtime.ui.build_container_key_path = function (container_xu_data, keyP, parent_infoP, nodeP, parent_nodeP) {
const key_segment = typeof keyP === 'undefined' || keyP === null ? '0' : `${keyP}`;
let key_path = `${container_xu_data?.key_path || '0'}-${key_segment}`;
const parent_identity = parent_nodeP?.xu_tree_id || parent_nodeP?.id;
const node_identity = nodeP?.xu_tree_id || nodeP?.id;
const is_iterated_clone = !!(parent_infoP?.iterate_info && parent_identity && node_identity && parent_identity === node_identity);
if (is_iterated_clone) {
key_path += '-iter';
}
return key_path;
};
func.runtime.ui.generate_xu_ui_id = async function (SESSION_ID, nodeP, $container, paramsP, keyP, precomputed) {
precomputed = precomputed || {};
const dsSessionP = paramsP.dsSessionP;
const _ds = SESSION_OBJ[SESSION_ID].DS_GLB[dsSessionP];
const containerXuData = precomputed.container_xu_data || func.runtime.ui.get_data($container)?.xuData;
const currentRecordId = typeof precomputed.currentRecordId !== 'undefined' ? precomputed.currentRecordId : containerXuData?.recordid || _ds?.currentRecordId || '';
const key_path = precomputed.key_path || func.runtime.ui.build_container_key_path(containerXuData, keyP, precomputed.parent_infoP, nodeP, precomputed.parent_nodeP);
const ui_id = func.runtime.ui.build_xu_ui_id_seed(nodeP, dsSessionP, key_path, currentRecordId);
if (func.runtime.ui.ui_id_hash_cache.has(ui_id)) {
return func.runtime.ui.ui_id_hash_cache.get(ui_id);
}
const hashed_ui_id = await func.common.fastHash(ui_id);
func.runtime.ui.ui_id_hash_cache.set(ui_id, hashed_ui_id);
return hashed_ui_id;
};
func.runtime.ui.create_container = async function (options) {
const _paramsP = structuredClone(options.paramsP);
const _ds = SESSION_OBJ[options.SESSION_ID].DS_GLB[_paramsP.dsSessionP];
const $appendTo = func.runtime.ui.get_append_target(options.$container, options.$appendToP);
if (!$appendTo) return null;
const container_data = func.runtime.ui.get_data(options.$container);
const container_xu_data = container_data?.xuData;
const items = func.runtime.ui.get_node_child_items(options.nodeP);
let currentRecordId = container_xu_data?.recordid || (_ds ? _ds.currentRecordId : '');
// Same sentinel rule as the browser create_container: an inherited 'newRecord' must not
// shadow the datasource's real positioned row (dead record bindings on multi-view children).
if (currentRecordId === 'newRecord' && _ds?.currentRecordId && _ds.currentRecordId !== 'newRecord') {
const _live_rows = _ds.data_feed?.rows;
if (Array.isArray(_live_rows) && _live_rows.some((row) => row._ROWID === _ds.currentRecordId)) {
currentRecordId = _ds.currentRecordId;
}
}
try {
const key_path = func.runtime.ui.build_container_key_path(container_xu_data, options.keyP, options.parent_infoP, options.nodeP, options.parent_nodeP);
const elem_key = `${options.nodeP.xu_tree_id || options.nodeP.id}-${key_path}-${currentRecordId}`;
const $div = func.runtime.ui.create_container_element(options.div_typeP);
const new_ui_id = await func.runtime.ui.generate_xu_ui_id(options.SESSION_ID, options.nodeP, options.$container, options.paramsP, options.keyP, {
container_xu_data,
currentRecordId,
key_path,
parent_infoP: options.parent_infoP,
parent_nodeP: options.parent_nodeP,
});
func.runtime.ui.apply_container_meta($div, {
ui_id: new_ui_id,
paramsP: _paramsP,
nodeP: options.nodeP,
currentRecordId,
keyP: options.keyP,
key_path,
$container: options.$container,
prop: options.prop,
elem_key,
elem_propP: options.elem_propP,
items,
parent_nodeP: options.parent_nodeP,
$root_container: options.$root_container,
parent_infoP: options.parent_infoP,
is_placeholder: options.is_placeholder,
classP: options.classP,
SESSION_ID: options.SESSION_ID,
});
func.runtime.ui.append_to($div, $appendTo);
return $div;
} catch (e) {
console.error(e);
}
return null;
};
func.datasource = {};
// Preserve imperatively-set virtual fields without declarative expressions across the datasource refresh
// row-rebuild, so a refresh does not reset them to static defaults. Kept in a WeakMap off _ds so it never
// enters the update_xu_ref snapshot.
func.datasource.__vf_preserve = new WeakMap();
func.datasource._debug_summarize_set_data_feed = function (data_feed) {
const rows = data_feed?.rows;
const first_row = Array.isArray(rows) ? rows[0] : null;
const summarized_row = {};
if (first_row && typeof first_row === 'object') {
for (const key of Object.keys(first_row).slice(0, 12)) {
const value = first_row[key];
if (typeof value === 'string') {
summarized_row[key] = {
type: 'string',
length: value.length,
empty: value.length === 0,
};
} else if (Array.isArray(value)) {
summarized_row[key] = {
type: 'array',
length: value.length,
};
} else {
summarized_row[key] = {
type: typeof value,
value: value && typeof value === 'object' ? '[object]' : value,
};
}
}
}
return {
rows_length: Array.isArray(rows) ? rows.length : null,
rows_changed_length: Array.isArray(data_feed?.rows_changed) ? data_feed.rows_changed.length : null,
rows_added_length: Array.isArray(data_feed?.rows_added) ? data_feed.rows_added.length : null,
rows_deleted_length: Array.isArray(data_feed?.rows_deleted) ? data_feed.rows_deleted.length : null,
first_row: summarized_row,
};
};
func.datasource.create = async function (
SESSION_ID,
prog_id,
dataSourceNoP = null, // if exist then refresh
parentDataSourceNoP,
containerIdP,
rowIdP,
jobNoP,
calling_trigger_prop,
parameters_raw_obj,
NA_isInitP,
NA_callingSourceP,
calling_jobP,
NA_screen_dsP,
is_panelP,
parameters_obj_inP,
static_refreshP,
worker_id,
NA_eventChangesResults,
) {
// Coalesce concurrent refreshes of the same existing datasource onto the first in-flight create
// promise. Without this, a refresh-triggered render can synchronously re-enter create() for the
// same datasource while the previous pass is still rebuilding rows. Brand-new datasource creation
// is not coalesced, and the flag clears on settle so the next real refresh runs normally.
let _guard_ds = null;
if (dataSourceNoP != null) {
const _guard_session = SESSION_OBJ[SESSION_ID];
_guard_ds = _guard_session?.DS_GLB?.[dataSourceNoP];
if (_guard_ds && _guard_ds._create_in_flight) {
return _guard_ds._create_in_flight;
}
}
const _create_promise = new Promise(async function (resolve, reject) {
if (!prog_id) return reject('Program is empty');
var _session = SESSION_OBJ[SESSION_ID];
if (!_session.DS_GLB) return reject('DS_GLB not exist');
var _prog_obj = await func.utils.VIEWS_OBJ.get(SESSION_ID, prog_id);
if (!_prog_obj) return reject('Program not found');
var args = {
SESSION_ID,
prog_id,
dataSourceNoP,
parentDataSourceNoP,
containerIdP,
rowIdP,
jobNoP,
calling_trigger_prop,
calling_jobP,
is_panelP,
parameters_obj_inP,
static_refreshP,
worker_id,
parameters_raw_obj,
};
var IS_DATASOURCE_REFRESH = null;
var _ds = _session.DS_GLB[dataSourceNoP];
var old_dataSource_vars = {};
if (_ds) IS_DATASOURCE_REFRESH = true;
if (IS_DATASOURCE_REFRESH) {
old_dataSource_vars.sortOrder = _ds.sortOrder;
old_dataSource_vars.sortOrderTypeExp = _ds.sortOrderTypeExp;
if (_ds.data_system) {
old_dataSource_vars.SYS_OBJ_WIN_MODE = _ds.data_system.SYS_OBJ_WIN_MODE;
// old_dataSource_vars.SYS_OBJ_WIN_UI = _ds.data_system.SYS_OBJ_WIN_UI;
old_dataSource_vars.SYS_STR_WIN_ID = _ds.data_system.SYS_STR_WIN_ID;
old_dataSource_vars.SYS_STR_WIN_NAME = _ds.data_system.SYS_STR_WIN_NAME;
}
if (static_refreshP) old_dataSource_vars.in_parameters = _ds.in_parameters;
await func.datasource.update(SESSION_ID, {
[dataSourceNoP]: {
['datasource_main']: {
stat: 'busy',
stat_ts: Date.now(),
is_worker: glb.IS_WORKER,
},
},
});
}
const restore_old_dataSource_vars = function (dsSessionP) {
var _ds = SESSION_OBJ[SESSION_ID].DS_GLB[dsSessionP];
if (_ds.data_system) {
_ds.data_system.SYS_OBJ_WIN_MODE = old_dataSource_vars.SYS_OBJ_WIN_MODE;
// _ds.data_system.SYS_OBJ_WIN_UI = old_dataSource_vars.SYS_OBJ_WIN_UI;
_ds.data_system.SYS_STR_WIN_ID = old_dataSource_vars.SYS_STR_WIN_ID;
_ds.data_system.SYS_STR_WIN_NAME = old_dataSource_vars.SYS_STR_WIN_NAME;
}
if (static_refreshP) _ds.in_parameters = old_dataSource_vars.in_parameters;
};
var run_at = _prog_obj?.properties?.runAt;
if (_session.opt.app_computing_mode === 'main') {
run_at = 'client';
}
if (_prog_obj?.properties.menuType === 'globals') {
run_at = 'client';
}
if (!run_at && parentDataSourceNoP && _session.DS_GLB[parentDataSourceNoP]) {
if (_session.DS_GLB[parentDataSourceNoP]._run_at) run_at = _session.DS_GLB[parentDataSourceNoP].v.run_at;
}
const done = function (SESSION_ID, dsSessionP, response_returned_from_worker) {
var _ds = SESSION_OBJ[SESSION_ID].DS_GLB[dsSessionP];
if (IS_DATASOURCE_REFRESH) {
restore_old_dataSource_vars(dsSessionP);
}
if (!IS_DATASOURCE_REFRESH) {
if (!glb.IS_WORKER) {
DATASOURCE_INTERVALS[SESSION_ID][dsSessionP] = new func.datasource.interval(SESSION_ID, dsSessionP, 'client_interval');
DATASOURCE_INTERVALS[SESSION_ID][dsSessionP].init();
}
}
const set_stat_idle = async function () {
let ds_connected = [];
for (const [dsP, _ds] of Object.entries(_session.DS_GLB)) {
if (_ds.parentDataSourceNo == dsSessionP) {
ds_connected.push(dsP);
}
}
const datasource_changes = {
[dsSessionP]: {
['datasource_main']: {
stat: 'idle',
stat_ts: Date.now(),
is_worker: glb.IS_WORKER,
},
},
};
if (!ds_connected.length) {
return await func.datasource.update(SESSION_ID, datasource_changes);
}
let interval = setInterval(() => {
let idle_count = 0;
for (const dsSession of ds_connected) {
const _ds = _session.DS_GLB[dsSession];
// A connected child datasource that is missing/removed from DS_GLB can never become
// "idle"; reading _ds.stat would throw every tick, so the interval would never clear
// and this datasource would stay non-idle. Treat a missing child as idle so the wait can
// complete.
if (!_ds || _ds.stat == 'idle') {
idle_count++;
}
}
if (ds_connected.length === idle_count) {
clearInterval(interval);
func.datasource.update(SESSION_ID, datasource_changes);
}
}, 1000);
};
set_stat_idle();
resolve({
SESSION_ID,
dsSessionP,
rowIdP: _ds.args.rowIdP,
jobNoP: _ds.args.jobNoP,
callingLogId: _ds.callingLogId,
calling_jobP: _ds.calling_jobP,
});
};
var db_driver;
var is_system_client_vars = false;
if (jobNoP) {
}
if (glb.IS_WORKER || run_at === 'client' || is_system_client_vars || db_driver === 'pouchdb') {
const ret = await func.datasource.prepare(
args.SESSION_ID,
args.prog_id,
args.dataSourceNoP,
args.parentDataSourceNoP,
args.containerIdP,
args.rowIdP,
args.jobNoP,
args.calling_trigger_prop,
args.parameters_raw_obj,
null,
null,
args.calling_jobP,
null,
args.is_panelP,
args.parameters_obj_inP,
args.static_refreshP,
run_at,
worker_id,
);
return done(SESSION_ID, ret.dsSessionP);
}
// vvvv run at worker vvvv
if (_ds) IS_DATASOURCE_REFRESH = true;
var data = Object.assign(
{
session_id: SESSION_ID,
dataSourceSessionGlobal: SESSION_OBJ[SESSION_ID].dataSourceSessionGlobal,
parentDataSourceNo: IS_DATASOURCE_REFRESH ? _ds.parentDataSourceNo : null,
IS_DATASOURCE_REFRESH,
},
args,
);
delete data.SESSION_ID;
const jsonP = await func.index.call_worker(SESSION_ID, {
service: 'datasource_create',
data,
id: SESSION_OBJ[SESSION_ID].worker_id,
});
_session.DS_GLB[jsonP.dsSession] = jsonP;
if (jsonP.dataSourceSessionGlobal > _session.dataSourceSessionGlobal) {
_session.dataSourceSessionGlobal = jsonP.dataSourceSessionGlobal;
}
return done(SESSION_ID, jsonP.dsSession, true);
});
// Publish the in-flight promise on the existing _ds SYNCHRONOUSLY (no await between the
// top guard-check and here) so a synchronous burst of concurrent create(ds5) calls all
// coalesce onto this one promise. Then clear it once the create settles (resolve OR
// reject) so the next genuine refresh runs normally — only removing the flag if it still
// points at THIS promise, never clobbering a newer in-flight create.
if (_guard_ds) {
_guard_ds._create_in_flight = _create_promise;
_create_promise.finally(() => {
const _s = SESSION_OBJ[SESSION_ID];
const _d = _s?.DS_GLB?.[dataSourceNoP];
if (_d && _d._create_in_flight === _create_promise) {
delete _d._create_in_flight;
}
});
}
return _create_promise;
};
func.datasource.prepare = async function (SESSION_ID, prog_id, dataSourceNoP, parentDataSourceNoP, containerIdP, rowIdP, jobNoP, calling_trigger_prop, parameters_raw_obj, NA_isInitP, callingSourceP, calling_jobP, NA_screen_dsP, is_panelP, parameters_obj_inP, static_refreshP, run_atP, worker_id) {
const set_parameters = async function () {
var _session = SESSION_OBJ[SESSION_ID];
const get_Out_parameters = async function (fieldIdP, located_field_param_idxP, param_row_idP) {
var ret = parameters_obj_inP?.[fieldIdP] || fieldIdP;
PARAM_OUT_INFO[prog_id + '_' + param_row_idP] = {
module: _ds.viewModule,
action: 'parameters',
prop: 'out',
details: ret,
result: ret,
source: _ds.viewSourceDesc,
type: 'parameters',
prog_id: prog_id,
dsSession: dataSourceSession,
fieldId: fieldIdP,
parentDataSourceNo: parentDataSourceNoP,
};
return ret;
};
// set parameters list
const screenInfo = await func.utils.get_screen_obj(SESSION_ID, prog_id); // locate root receiving parameters screen id - get root screen info
if (
screenInfo?.properties?.progParams
// &&
// glb.PARAMETER_NODES_ARR.includes(screenInfo.properties.menuType)
) {
if (!xu_isEmpty(screenInfo.properties.progParams)) {
_ds.in_parameters = {};
_ds.out_parameters = {};
for await (let [key, val] of Object.entries(screenInfo.properties?.progParams)) {
// run on parameters arr
if (val.data.dir === 'in') {
_ds.in_parameters[val.data.parameter] = {
// value: parameters_obj_inP?.[val.data.parameter],
type: val.data.type,
};
if (typeof parameters_obj_inP?.[val.data.parameter] !== 'undefined') {
_ds.in_parameters[val.data.parameter].value = parameters_obj_inP[val.data.parameter];
} else if (['live_preview', 'miniapp'].includes(_session.engine_mode)) {
_ds.in_parameters[val.data.parameter].value = _session?.url_params?.[val.data.parameter];
}
continue;
}
if (val.data.dir === 'out' && val.data.parameter) {
_ds.out_parameters[val.data.parameter] = await get_Out_parameters(val.data.parameter, key, val.id);
}
}
_ds.PARAM_OUT_INFO = PARAM_OUT_INFO;
}
}
};
const build_GLOBAL_SYS_fields = function () {
if (!_ds.data_system) _ds.data_system = {};
_ds.data_system['SYS_GLOBAL_UTC'] = -new Date().getTimezoneOffset() / 60;
_ds.data_system['SYS_GLOBAL_STR_APP_ID'] = APP_OBJ[_session.app_id]._id;
_ds.data_system['SYS_GLOBAL_STR_SESSION_ID'] = SESSION_ID;
_ds.data_system['SYS_GLOBAL_STR_LOGIN_USER_ID'] = _session.USR_OBJ._id;
if (!['live_preview', 'miniapp'].includes(_session.engine_mode) && PROJECT_OBJ[_session.app_id].info) {
_ds.data_system['SYS_GLOBAL_OBJ_APP_INFO'] = {
build: PROJECT_OBJ[_session.app_id].info.build_id,
author: PROJECT_OBJ[_session.app_id].info.author,
date: PROJECT_OBJ[_session.app_id].info.build_date,
name: APP_OBJ[_session.app_id].app_name,
};
}
_ds.data_system['SYS_GLOBAL_OBJ_LOGIN_USER_INFO'] = {
id: _session.USR_OBJ._id,
user_name: _session.USR_OBJ.usr_name,
first_name: _session.USR_OBJ.usr_first_name,
last_name: _session.USR_OBJ.usr_last_name,
email: _session.USR_OBJ.usr_email,
profile_picture: _session.USR_OBJ.usr_profile_picture,
};
_ds.data_system['SYS_GLOBAL_STR_BROWSER_HASH_ID'] = _session.SYS_GLOBAL_STR_BROWSER_HASH_ID;
_ds.data_system['SYS_GLOBAL_STR_BROWSER_TITLE'] = _session.SYS_GLOBAL_STR_BROWSER_TITLE;
// }
_ds.data_system['SYS_GLOBAL_STR_SITE_CSS'] = {};
_ds.data_system['SYS_GLOBAL_BOL_SHIFT_KEY_STATE'] = 0;
_ds.data_system['SYS_GLOBAL_BOL_COMMAND_KEY_STATE'] = 0;
_ds.data_system['SYS_GLOBAL_BOL_CONTROL_KEY_STATE'] = 0;
_ds.data_system['SYS_GLOBAL_BOL_ALT_KEY_STATE'] = 0;
_ds.data_system['SYS_GLOBAL_BOL_ONLINE'] = 0;
_ds.data_system['SYS_GLOBAL_BOL_REPLICATION_STAT'] = 0;
_ds.data_system['SYS_GLOBAL_BOL_AJAX_BUSY'] = 0;
_ds.data_system['SYS_GLOBAL_BOL_CONNECTED'] = 1;
_ds.data_system['SYS_GLOBAL_BOL_IDLE'] = 0;
_ds.data_system['SYS_GLOBAL_STR_FIREBASE_TOKEN_ID'] = 0;
_ds.data_system['SYS_GLOBAL_BOL_PUSH_NOTIFICATION_GRANTED'] = _session.PUSH_NOTIFICATION_GRANTED;
_ds.data_system['SYS_GLOBAL_OBJ_FIREBASE_AUTH_INFO'] = _session.SYS_GLOBAL_OBJ_FIREBASE_AUTH_INFO;
_ds.data_system['SYS_GLOBAL_OBJ_CLIENT_INFO'] = _session.SYS_GLOBAL_OBJ_CLIENT_INFO;
_ds.data_system['SYS_GLOBAL_OBJ_REFS'] = {};
};
if (!SESSION_OBJ[SESSION_ID].DS_GLB) return;
if (dataSourceNoP && !SESSION_OBJ[SESSION_ID].DS_GLB[dataSourceNoP]) {
return func.utils.debug_report(SESSION_ID, 'Datasource', 'Datasource not exist: ' + dataSourceNoP, 'E');
}
if (!prog_id) {
return func.utils.debug_report(SESSION_ID, 'Datasource', 'Program is null', 'E');
}
const args = {
SESSION_ID,
prog_id,
dataSourceNoP,
parentDataSourceNoP,
containerIdP,
rowIdP,
jobNoP,
calling_trigger_prop,
calling_jobP,
is_panelP,
parameters_obj_inP,
static_refreshP,
run_atP,
worker_id,
parameters_raw_obj,
};
var dataSourceSession = null;
var IS_DATASOURCE_REFRESH = null;
var PARAM_OUT_INFO = {};
const init_dataSource = async function () {
const init_new_dataSource = async function () {
if (!['main'].includes(SESSION_OBJ[SESSION_ID].opt.app_computing_mode) && run_atP === 'client' && prog_id !== 'system') {
const ret = await func.index.call_worker(SESSION_ID, {
service: 'get_dataSourceSessionGlobal',
data: { session_id: SESSION_ID },
id: SESSION_OBJ[SESSION_ID].worker_id,
});
SESSION_OBJ[SESSION_ID].dataSourceSessionGlobal = ret?.new_dataSourceSessionGlobal || 1;
} else {
SESSION_OBJ[SESSION_ID].dataSourceSessionGlobal++;
}
dataSourceSession = SESSION_OBJ[SESSION_ID].dataSourceSessionGlobal;
SESSION_OBJ[SESSION_ID].DS_GLB[dataSourceSession] = {
data_feed: { rows: [] },
};
};
const init_existing_dataSource = function () {
let _ds = SESSION_OBJ[SESSION_ID].DS_GLB[dataSourceNoP];
console.log('DATASOURCE_REFRESH', dataSourceNoP);
if (!_ds) {
return;
}
IS_DATASOURCE_REFRESH = true;
dataSourceSession = dataSourceNoP;
_ds.refreshed = true;
if (_ds.watcher) {
xu_set(_ds, _ds.watcher.path, _ds.watcher.newValue);
}
try {
if (!_ds.v) _ds.v = {};
delete _ds.v.old_dataSource; // to eliminate parse error
delete _ds.rows_found;
try {
if (_ds.data_feed && _ds.data_feed.rows && _ds.data_feed.rows[0]) {
func.datasource.__vf_preserve.set(_ds, Object.assign({}, _ds.data_feed.rows[0]));
}
} catch (e) {}
// B1: keep the full pre-refresh row set so the render layer can detect a no-op
// refresh (identical rows) and skip rebuilding the list (see render_multi_view_node).
_ds.__refresh_prev_rows = _ds.data_feed && Array.isArray(_ds.data_feed.rows) ? _ds.data_feed.rows : null;
_ds.data_feed = {};
_ds.v.old_dataSource = {
currentRecordId: _ds.currentRecordId,
firstRecordId: _ds.firstRecordId,
finalRecordId: _ds.finalRecordId,
locatedRecordId: _ds.locatedRecordId,
sortOrder: _ds.sortOrder,
sortOrderTypeExp: _ds.sortOrderTypeExp,
};
} catch (err) {
console.error('function: init_existing_dataSource - error', err);
}
};
if (typeof dataSourceNoP === 'undefined' || dataSourceNoP === null) {
await init_new_dataSource();
} else {
init_existing_dataSource();
}
return SESSION_OBJ[SESSION_ID].DS_GLB[dataSourceSession];
};
var _ds = await init_dataSource();
if (!_ds) {
return func.utils.debug_report(SESSION_ID, 'Datasource', 'Datasource refresh failed: ' + dataSourceNoP, 'E');
}
_ds.stat = 'busy';
_ds._run_at = run_atP;
if (_ds.refreshed) {
await func.datasource.update(SESSION_ID, {
[_ds.dsSession]: {
['datasource_main']: {
stat: 'busy',
stat_ts: Date.now(),
is_worker: glb.IS_WORKER,
},
},
});
}
// init_v();
if (IS_DATASOURCE_REFRESH) {
if (!static_refreshP) await set_parameters(); // do only on new datasource or refresh
return func.datasource.execute(SESSION_ID, dataSourceSession, true);
}
_ds.tree_obj = await func.utils.TREE_OBJ.get(SESSION_ID, prog_id);
if (!_ds.tree_obj) {
return func.utils.debug_report(SESSION_ID, 'Datasource', 'Program not exist: ' + prog_id, 'E');
}
await func.datasource.set_VIEW_data(SESSION_ID, args, _ds);
if (
!_ds.v.viewSourceDesc // in case of event
) {
_ds.v.viewSourceDesc = callingSourceP;
}
if (dataSourceSession === 0) _ds.v.viewSourceDesc = 'system startup';
var _session = SESSION_OBJ[SESSION_ID];
const set_DS_GLB = async function () {
_ds.dataSource_init_arr = {};
_ds.containerId = containerIdP;
_ds.jobNoP = jobNoP;
_ds.viewSourceDesc = _ds.v.viewSourceDesc;
_ds.callingSource = callingSourceP;
_ds.calling_jobP = calling_jobP;
_ds.viewModule = _ds.v.viewModule;
_ds.viewSourceProp = _ds.v.viewSourceProp;
_ds.dsSession = dataSourceSession;
// _ds.v = v;
_ds.args = args;
_ds.worker_id = worker_id;
_ds.prog_id = prog_id;
// if (!IS_DATASOURCE_REFRESH) {
_ds.parentDataSourceNo = parentDataSourceNoP;
// }
};
await set_DS_GLB();
if (
prog_id === 'system' &&
!parentDataSourceNoP // do only on first time datasource 0 call
) {
build_GLOBAL_SYS_fields();
}
// ======================================
// PARAMETERS
//======================================
await set_parameters();
// ======================================
// INTERVALS
//======================================
_ds.client_interval = func.datasource.get_event_interval_arr(SESSION_ID, dataSourceSession, 'client_interval');
if (prog_id === 'system') {
_ds.server_interval = func.datasource.get_event_interval_arr(SESSION_ID, dataSourceSession, 'server_interval');
}
// if (static_refreshP) {
// delete _ds.raw_data;
// }
// if (!static_refreshP) await set_parameters(); // do only on new datasource or refresh
// try {
let ret_execute = await func.datasource.execute(SESSION_ID, dataSourceSession);
return ret_execute;
// } catch (e) {
// console.error(e);
// }
};
func.datasource.execute = async function (SESSION_ID, dataSourceSession, IS_DATASOURCE_REFRESH) {
var _session = SESSION_OBJ[SESSION_ID];
var _ds = _session.DS_GLB[dataSourceSession];
var args = _ds.args;
// var v = _ds.v;
let tree_obj = await func.utils.TREE_OBJ.get(SESSION_ID, _ds.prog_id);
let prog_obj = await func.utils.VIEWS_OBJ.get(SESSION_ID, _ds.prog_id);
const normalize_filter_model = function (value) {
if (value === null || typeof value === 'undefined') {
return undefined;
}
if (typeof value === 'string') {
const trimmed = value.trim();
if (!trimmed) {
return undefined;
}
if (trimmed.startsWith('{') || trimmed.startsWith('[')) {
try {
return JSON.parse(trimmed);
} catch (err) {
return value;
}
}
}
return value;
};
const callback_datasource = async function () {
const run_on_load_events = async function () {
if (!(await func.datasource.get_view_events_count(SESSION_ID, dataSourceSession, 'on_load'))) {
return false;
}
await func.datasource.execute_view_events(SESSION_ID, dataSourceSession, 'on_load');
return true;
};
const schedule_panel_on_load_events = function () {
setTimeout(async function () {
try {
await run_on_load_events();
} catch (error) {
console.error(error);
}
}, 0);
};
if (typeof IS_WORKER === 'undefined' && typeof IS_DOCKER === 'undefined' && typeof IS_PROCESS_SERVER === 'undefined' && _ds.viewSourceProp === 'globals') {
if (!['main'].includes(_session.opt.app_computing_mode)) {
await func.index.call_worker(SESSION_ID, {
service: 'create_webworker_globals',
data: { ds_data: _ds, session_id: SESSION_ID },
});
}
}
// on_load view events fire on INITIAL load only, never on a datasource REFRESH —
// the same once-per-screen rule as screen_ready. Re-firing on every refresh lets an
// on_load event that raises a render/fetch event (e.g. RENDER_PAGE_EVENT -> get_data +
// field update) re-render the screen, which refreshes the datasource, which re-fires
// on_load -> ... an infinite render loop (the panel "flicker"). A refresh reuses the
// same datasource session (IS_DATASOURCE_REFRESH); navigation creates a new one and
// fires on_load there as expected.
if (args.is_panelP) {
const callback_ret = await func.datasource.callback(SESSION_ID, dataSourceSession, args.rowIdP, args.jobNoP, _ds.prog_id);
if (!IS_DATASOURCE_REFRESH) schedule_panel_on_load_events();
return callback_ret;
}
if (!IS_DATASOURCE_REFRESH) await run_on_load_events();
return await func.datasource.callback(SESSION_ID, dataSourceSession, args.rowIdP, args.jobNoP, _ds.prog_id);
};
const get_limit = async function () {
var ret = 0;
let tree_ret = await func.utils.TREE_OBJ.get(SESSION_ID, _ds.prog_id);
if (tree_ret.menuType === 'get_data') {
return 1;
}
ret = _ds.progDataSource?.dataSourceLimit;
if (prog_obj.progDataSource?.dataSourceLoopExp) {
ret = (await func.expression.get(SESSION_ID, prog_obj.progDataSource.dataSourceLoopExp, dataSourceSession, 'view_loop', args.rowIdP)).result;
}
return ret;
};
const get_skip = async function () {
var ret = 0;
ret = _ds.progDataSource?.dataSourceSkip;
if (prog_obj.progDataSource?.dataSourceSkipExp) {
ret = (await func.expression.get(SESSION_ID, prog_obj.progDataSource.dataSourceSkipExp, dataSourceSession, 'view_loop', args.rowIdP)).result;
}
return ret;
};
const calc_batch_loops = async () => {
if (!prog_obj.progDataSource?.dataSourceType || _ds.progDataSource.dataSourceType === 'none') {
_ds.v.batch_loops = await get_limit();
return false;
}
_ds.v.batch_loops = (await get_limit()) <= _ds.v.raw_data?.rows?.length ? await get_limit() : _ds.v.raw_data?.rows?.length;
return true;
};
const render_api_output = async function () {
if (prog_obj?.scriptData?.value) {
var exp = await func.expression.get(SESSION_ID, prog_obj.scriptData.value, dataSourceSession, 'api_rendered_output', null, null, null, null, null, null, null, null, null, tree_obj.apiOutput);
let output_result = exp.result;
if (tree_obj.apiOutput === 'json') {
// iterate object to fix expressions
try {
// let output_result_obj = JSON5.parse(output_result)
let output_result_obj = await func.expression.secure_eval(SESSION_ID, 'api_rendered_output', '(' + output_result + ')', null, dataSourceSession);
// for await (let [key, val] of Object.entries(output_result_obj)) {
// output_result_obj[key] = (await func.expression.get(
// SESSION_ID,
// val,
// dataSourceSession, "object_property")).result
// }
output_result = JSON.stringify(output_result_obj);
} catch (err) {
console.error(err);
}
}
_ds.api_rendered_output += output_result + (tree_obj.apiOutput === 'json' ? ',' : '');
} else {
_ds.api_rendered_output = ''; //empty
}
};
// ======================================
// COMPUTE GLOBALS
// ======================================
if (_ds.prog_id === 'system') {
//TBD tree_obj.menuType === "globals"
_ds.currentRecordId = 'dataset';
await func.datasource.render_fields_dataset(SESSION_ID, dataSourceSession, {
id: 'dataset',
value: _session.url_params,
});
return await callback_datasource();
}
// ======================================
// BUILD DATA SOURCE
//======================================
let db_adapter_module;
if (prog_obj.progDataSource?.dataSourceType) {
db_adapter_module = await func.common.get_module(SESSION_ID, 'xuda-datasource-db-adapter-module.mjs');
}
const get_data_from_source = async function () {
switch (prog_obj.progDataSource.dataSourceSrcType) {
case 'input': {
const { result, error } = await func.expression.get(SESSION_ID, prog_obj.progDataSource.progDataSourceInput, dataSourceSession, 'datasource select');
if (error) {
func.utils.debug_report(SESSION_ID, 'Data source', `Datasource parse error using ${prog_obj.progDataSource?.dataSourceType} input`, 'E');
return null;
}
return result;
break;
}
case 'url': {
let opt = {
method: prog_obj.progDataSource.dataSourceMethod || 'POST',
headers: {
Accept: 'application/json',
'Content-Type': 'application/json',
},
};
let data = {};
if (prog_obj.progDataSource.dataSourceMethod == 'POST' && prog_obj.progDataSource.dataSourceParameters) {
for (let val of prog_obj.progDataSource.dataSourceParameters) {
data[val.key] = val.val;
}
opt.body = JSON.stringify(data);
}
try {
const response = await fetch('https://' + prog_obj.progDataSource.dataSourceDataUrl, opt);
const json = await response.json();
return json.data;
} catch (err) {
func.utils.debug_report(SESSION_ID, 'Data source', err.message + ' https://' + prog_obj.progDataSource.dataSourceDataUrl, 'E');
return null;
}
break;
}
default:
return null;
break;
}
};
if (!_ds.v.raw_data) {
_ds.v.raw_data = { rows: [] };
}
_ds.data_feed.rows = [];
switch (prog_obj.progDataSource?.dataSourceType) {
case 'table': {
_ds._dataSourceTableId = prog_obj.progDataSource?.dataSourceTableId; // get file id
if (prog_obj.progDataSource?.dataSourceTableIdExp) {
_ds.v.dataSourceTableIdExp = await func.expression.get(SESSION_ID, prog_obj.progDataSource?.dataSourceTableIdExp, dataSourceSession, 'dataSourceTableIdExp', args.rowIdP);
if (_ds.v.dataSourceTableIdExp.result) {
_ds._dataSourceTableId = _ds.v.dataSourceTableIdExp.result;
} else {
func.utils.debug_report(SESSION_ID, 'get_VIEW_data', 'Table Expression returned empty result', 'W');
}
}
if (!_ds._dataSourceTableId) {
return func.utils.debug_report(SESSION_ID, 'Data source', 'Table cannot be empty when Db Table selected', 'E');
}
let table_ret = await func.utils.TREE_OBJ.get(SESSION_ID, _ds._dataSourceTableId);
if (!table_ret) {
return func.utils.debug_report(SESSION_ID, 'Data source', 'Table not found: ' + _ds._dataSourceTableId, 'E');
}
await db_adapter_module.build_filter(SESSION_ID, dataSourceSession, _ds.v, _ds);
let filterModelMongo = _ds.progDataSource.filterModelMongo;
if (_ds.progDataSource.filterModelMongoFx) {
let ret = await func.expression.get(SESSION_ID, _ds.progDataSource.filterModelMongoFx, dataSourceSession, 'query', _ds.args.rowIdP);
filterModelMongo = ret.result;
}
filterModelMongo = normalize_filter_model(filterModelMongo);
let filterModelSql = _ds.progDataSource.filterModelSql;
if (_ds.progDataSource.filterModelSqlFx) {
let ret = await func.expression.get(SESSION_ID, _ds.progDataSource.filterModelSqlFx, dataSourceSession, 'query', _ds.args.rowIdP);
filterModelSql = ret.result;
}
filterModelSql = normalize_filter_model(filterModelSql);
const filterModel = {
filterModelNative: normalize_filter_model(_ds.progDataSource.filterModelNative),
filterModelMongo,
filterModelSql,
filterModelUserMongo: normalize_filter_model(_ds.progDataSource.filterModelUserMongo),
filterModelUserSql: normalize_filter_model(_ds.progDataSource.filterModelUserSql),
};
let _dataSourceFilterModelType = _ds?.progDataSource?.dataSourceFilterModelType;
if (_ds?.progDataSource?.dataSourceFilterModelTypeFx) {
const fx_ret = await func.expression.get(SESSION_ID, _ds.progDataSource.dataSourceFilterModelTypeFx, dataSourceSession, 'query', _ds.args.rowIdP);
_dataSourceFilterModelType = fx_ret.result;
}
// Infer 'index' from a configured index when no explicit type is set, so
// index datasources without an explicit dataSourceFilterModelType still
// apply their index filter. Prefer the value build_filter already resolved
// on _ds.v (it sees the full progDataSource incl. the index id), then fall
// back to inferring from the index id directly.
_dataSourceFilterModelType =
_dataSourceFilterModelType ||
_ds?.v?.dataSourceFilterModelType ||
(_ds?.progDataSource?.dataSourceIndexId || _ds?.progDataSource?.dataSourceIndexIdExp ? 'index' : 'query');
if (_dataSourceFilterModelType && !['query', 'index'].includes(_dataSourceFilterModelType)) {
return func.utils.debug_report(SESSION_ID, 'Data source', `Valid values for dataSourceFilterModelType are: "query" or "index" (${_dataSourceFilterModelType})`, 'E');
}
const sortModel = Array.isArray(_ds?.progDataSource?.sortModel) ? _ds.progDataSource.sortModel : [];
// An EMPTY sortModel must be sent as null, not []. get_query does
// `if (sortModel) data.sortModel = JSON.stringify(sortModel)` and [] is
// truthy, so it forwards sortModel:"[]" to the backend, which overrides the
// configured index's natural key order with an unsorted (_id) order. Index
// datasources that rely on their index ordering (no explicit sort model)
// were rendering unsorted. Only send a real, non-empty sort model.
const sortModelForDb = _dataSourceFilterModelType === 'query' || !sortModel.length ? null : sortModel;
_ds.v.raw_data = await func.db.get_query(
SESSION_ID,
_ds._dataSourceTableId,
_ds.v.couchView,
dataSourceSession,
_ds.viewSourceDesc,
'datasource table',
prog_obj.progDataSource.dataSourceReduce,
await get_skip(),
(await get_limit()) || 99999999,
null,
null,
sortModelForDb,
null,
filterModel,
_dataSourceFilterModelType,
);
if (sortModel.length && _ds?.v?.raw_data?.rows?.length) {
function sortByKeys(array, sortConfig) {
return array.sort((a, b) => {
for (let config of sortConfig) {
const key = config.field_id || config.colId;
const direction = (config.sort_dir || config.sort) === 'desc' ? -1 : 1;
const valA = a.value[key];
const valB = b.value[key];
if (typeof valA === 'undefined' && typeof valB === 'undefined') {
continue;
}
if (typeof valA === 'undefined') {
return 1;
}
if (typeof valB === 'undefined') {
return -1;
}
// Handle numeric comparison
if (typeof valA === 'number' && typeof valB === 'number') {
if (valA !== valB) {
return (valA - valB) * direction;
}
}
// Handle string comparison
else if (typeof valA === 'string' && typeof valB === 'string') {
if (valA !== valB) {
return valA.localeCompare(valB) * direction;
}
} else if (valA !== valB) {
return String(valA).localeCompare(String(valB)) * direction;
}
}
return 0;
});
}
const sorted = sortByKeys(_ds.v.raw_data.rows, _ds.progDataSource.sortModel);
_ds.v.raw_data.rows = sorted;
}
if (_ds?.progDataSource?.dataSourceLimit) {
const ret_rows_found = await func.db.get_query(SESSION_ID, _ds._dataSourceTableId, _ds.v.couchView, dataSourceSession, _ds.viewSourceDesc, 'datasource table', prog_obj.progDataSource.dataSourceReduce, null, null, true, null, null, null, filterModel, _dataSourceFilterModelType);
_ds.rows_found = ret_rows_found?.rows?.[0]?.value || 0;
_ds.rows_found_opt = ret_rows_found?.opt;
} else {
_ds.rows_found = _ds?.v?.raw_data?.rows?.length || 0;
_ds.rows_found_opt = _ds?.v?.raw_data?.opt;
}
break;
}
case 'array': {
let data = await get_data_from_source();
if (data === null) {
data = [];
}
_ds.rows_found = data?.length || 0;
let _KEY = 0;
for (const _VAL of data) {
_ds.v.raw_data.rows.push({ id: _KEY, value: { _KEY, _VAL } });
_KEY++;
}
break;
}
case 'json': {
let data = await get_data_from_source();
if (data === null) {
data = {};
}
_ds.rows_found = Object.keys(data)?.length || 0;
for (let [_KEY, _VAL] of Object.keys(data)) {
_ds.v.raw_data.rows.push({ id: _KEY, value: { _KEY, _VAL } });
}
break;
}
case 'csv': {
let data = await get_data_from_source();
if (data === null) {
data = '';
}
let _KEY = 0;
let arr = data.split(',');
for (const _VAL of arr) {
_ds.v.raw_data.rows.push({ id: _KEY, value: { _KEY, _VAL } });
_KEY++;
}
_ds.rows_found = arr?.length || 0;
break;
}
default:
break;
}
// ======================================
// EXECUTE
// ======================================
let ret;
const get_before_record_count = async () => {
return await func.datasource.get_view_events_count(SESSION_ID, dataSourceSession, 'before_record');
};
const get_after_record_count = async () => {
return await func.datasource.get_view_events_count(SESSION_ID, dataSourceSession, 'after_record');
};
let _raw_data_rows = [];
switch (tree_obj.menuType) {
case 'api': {
_ds.api_rendered_output = '';
let has_datasource = await calc_batch_loops();
_raw_data_rows = _ds.v.raw_data.rows || [];
if (!has_datasource) {
for (n = 0; n < _ds.v.batch_loops; n++) {
_raw_data_rows.push({ id: n, value: {} });
}
}
_ds.currentRecordId = 'dataset';
for await (let [key, raw_data_row] of Object.entries(_raw_data_rows)) {
if (has_datasource && Number(key) >= _ds.v.batch_loops) break;
if (!has_datasource) {
raw_data_row = _ds?.v.raw_data?.rows?.[key] || { id: key, value: {} };
}
if (await get_before_record_count()) {
await func.datasource.execute_view_events(SESSION_ID, dataSourceSession, 'before_record');
}
await func.datasource.render_fields_dataset(SESSION_ID, dataSourceSession, raw_data_row);
if (await get_after_record_count()) {
await func.datasource.execute_view_events(SESSION_ID, dataSourceSession, 'after_record');
}
await render_api_output();
}
if (tree_obj.apiOutput === 'json') {
var str = _ds.api_rendered_output.substring(0, _ds.api_rendered_output.length - 1);
if (Number(_ds.progDataSource?.dataSourceLimit) === 1) {
_ds.api_rendered_output = str;
} else {
_ds.api_rendered_output = '[' + str + ']';
}
}
break;
}
case 'batch': {
let has_datasource = await calc_batch_loops();
_raw_data_rows = _ds?.v.raw_data?.rows || [];
if (!has_datasource) {
for (n = 0; n < _ds.v.batch_loops; n++) {
_raw_data_rows.push({ id: n, value: {} });
}
}
_ds.currentRecordId = 'dataset';
for await (let [key, raw_data_row] of Object.entries(_raw_data_rows)) {
if (has_datasource && Number(key) >= _ds.v.batch_loops) break;
if (!has_datasource) {
raw_data_row = _ds?.v.raw_data?.rows?.[key] || { id: key, value: {} };
}
if (await get_before_record_count()) {
await func.datasource.execute_view_events(SESSION_ID, dataSourceSession, 'before_record');
}
await func.datasource.render_fields_dataset(SESSION_ID, dataSourceSession, raw_data_row);
if (await get_after_record_count()) {
await func.datasource.execute_view_events(SESSION_ID, dataSourceSession, 'after_record');
}
}
await func.datasource.set_outputField(SESSION_ID, dataSourceSession, _ds?.v?.raw_data?.rows, _ds.args);
break;
}
case 'get_data': {
if (await get_before_record_count()) {
await func.datasource.execute_view_events(SESSION_ID, dataSourceSession, 'before_record');
}
ret = await db_adapter_module.process_view_dataset(SESSION_ID, dataSourceSession, _ds);
if (await get_after_record_count()) {
await func.datasource.execute_view_events(SESSION_ID, dataSourceSession, 'after_record');
}
await func.datasource.set_outputField(SESSION_ID, dataSourceSession, _ds?.v?.raw_data?.rows, _ds.args);
break;
}
case 'set_data': {
if (!prog_obj.progDataSource?.dataSourceType || _ds.progDataSource.dataSourceType !== 'table' || !_ds._dataSourceTableId) {
return func.utils.debug_report(SESSION_ID, 'Data source', 'Datasource DB Table must be defined for Set Data operation', 'E');
}
const find_ROWID_idx_from_raw_data_arr = function (rowId) {
if (!_raw_data_rows) {
throw new Error('_raw_data_rows not found');
}
const index = _raw_data_rows.findIndex((item) => item.id === rowId);
if (index === -1) {
throw new Error(`ROWID "${rowId}" not found`);
}
return index;
};
if (tree_obj.crudMode === 'U') {
_ds.set_mode = 'U';
_raw_data_rows = _ds?.v.raw_data?.rows || [];
}
if (tree_obj.crudMode === 'D') {
_ds.set_mode = 'D';
_raw_data_rows = _ds?.v.raw_data?.rows || [];
}
// initiated with Update but no rows found
if (tree_obj.crudMode === 'U' && tree_obj.allowCreate && !_raw_data_rows?.length) {
_ds.set_mode = 'C';
try {
const row_idx = find_ROWID_idx_from_raw_data_arr('newRecord');
_raw_data_rows[row_idx] = { id: 'newRecord', value: {} };
} catch (error) {
_raw_data_rows.push({ id: 'newRecord', value: {} });
}
}
if (tree_obj.crudMode === 'C') {
_ds.set_mode = 'C';
try {
const row_idx = find_ROWID_idx_from_raw_data_arr('newRecord');
_raw_data_rows[row_idx] = [{ id: 'newRecord', value: {} }];
} catch (error) {
_raw_data_rows.push({ id: 'newRecord', value: {} });
}
}
const trace_set_data = function (label, payload) {
if (String(_ds?.prog_id || '') !== '1630849293262' && String(_ds?._dataSourceTableId || '') !== '1630542401398') return;
try {
globalThis.__XUDA_RT_TRACE && console.log('[xuda-runtime] set_data_trace ' +
JSON.stringify({
version: 'runtime-refresh-20260629-set-data-remote-save',
label,
dataSourceSession,
prog_id: _ds?.prog_id,
table_id: _ds?._dataSourceTableId,
set_mode: _ds?.set_mode,
currentRecordId: _ds?.currentRecordId,
...payload,
}),
);
} catch (err) {
console.warn('[xuda-runtime] set_data_trace_failed', err);
}
};
trace_set_data('start', {
crudMode: tree_obj.crudMode,
allowCreate: tree_obj.allowCreate,
raw_rows_length: _raw_data_rows?.length || 0,
data_feed: func.datasource._debug_summarize_set_data_feed(_ds.data_feed),
});
for await (let raw_data_row of _raw_data_rows) {
_ds.currentRecordId = raw_data_row.id;
let data_feed_str = JSON.stringify(_ds.data_feed);
trace_set_data('before_record', {
raw_row_id: raw_data_row.id,
data_feed: func.datasource._debug_summarize_set_data_feed(_ds.data_feed),
});
if (await get_before_record_count()) {
await func.datasource.execute_view_events(SESSION_ID, dataSourceSession, 'before_record');
}
await func.datasource.render_fields_dataset(SESSION_ID, dataSourceSession, raw_data_row);
if (await get_after_record_count()) {
await func.datasource.execute_view_events(SESSION_ID, dataSourceSession, 'after_record');
}
const data_feed_after_str = JSON.stringify(_ds.data_feed);
const should_save = _ds.set_mode === 'C' || data_feed_after_str !== data_feed_str;
trace_set_data('save_decision', {
should_save,
changed: data_feed_after_str !== data_feed_str,
data_feed: func.datasource._debug_summarize_set_data_feed(_ds.data_feed),
});
if (should_save) {
const dbMsgP = await func.db.save_data(SESSION_ID, dataSourceSession);
trace_set_data('save_result', {
result: dbMsgP
? {
code: dbMsgP.code,
id: dbMsgP.id || dbMsgP.data?.id,
data_id: dbMsgP.data?.id,
message: dbMsgP.message,
}
: null,
});
if (dbMsgP) _ds.currentRecordId = dbMsgP.id;
_ds.set_mode = 'U';
}
if (_ds.set_mode === 'D') {
const dbMsgP = await func.db.save_data(SESSION_ID, dataSourceSession);
if (dbMsgP) _ds.currentRecordId = dbMsgP.id;
}
}
await func.datasource.set_outputField(SESSION_ID, dataSourceSession, _ds?.v?.raw_data?.rows, _ds.args);
break;
}
case 'component': {
_raw_data_rows = _ds?.v.raw_data?.rows || [];
_ds.rows_processed = 0;
_ds.viewRangeExp_rows_deleted = 0;
let rows = _ds?.v.raw_data?.rows?.length;
_ds.data_feed.rows_changed = [];
_ds.data_feed.rows_deleted = [];
_ds.data_feed.rows_added = [];
if (tree_obj.rwMode === 'U') {
_ds.set_mode = 'U';
} else {
_ds.set_mode = 'R';
}
const row_not_found = async function () {
if (!prog_obj.progDataSource?.dataSourceType || prog_obj.properties.renderType === 'form' || (tree_obj.rwMode === 'U' && tree_obj.allowCreate)) {
_ds.currentRecordId = 'newRecord';
if (tree_obj.rwMode === 'U' && tree_obj.allowCreate) {
_ds.set_mode = 'C';
}
if (prog_obj.progDataSource?.dataSourceType) {
_ds.record_not_found = true;
}
try {
const row_idx = func.common.find_ROWID_idx(_ds, _ds.currentRecordId);
} catch (error) {
await func.datasource.render_fields_form(SESSION_ID, dataSourceSession, { id: 'newRecord', value: {} });
}
var count = await func.datasource.get_field_init_count(SESSION_ID, dataSourceSession, 'newRecord', false);
if (
count > 0 // was: && !args.dataSourceNoP
) {
await func.datasource.execute_field_init_events(SESSION_ID, dataSourceSession, 'form', 'newRecord');
}
} else {
_ds.record_not_found = true;
delete _ds.currentRecordId;
delete _ds.firstRecordId;
delete _ds.finalRecordId;
delete _ds.locatedRecordId;
}
await func.datasource.callback(SESSION_ID, dataSourceSession, args.rowIdP, args.jobNoP, _ds.prog_id);
};
if (!rows) {
await row_not_found();
break;
}
_ds.firstRecordId = _raw_data_rows[0].id;
const finish_form = async function () {
// check if locatedRecordId exist in SESSION_OBJ[SESSION_ID].DS_GLB
if (_ds.locatedRecordId) {
try {
const row_idx = func.common.find_ROWID_idx(_ds, _ds.locatedRecordId);
} catch (error) {
delete _ds.locatedRecordId;
}
}
_ds.finalRecordId = func.datasource.get_currentRecordId(SESSION_ID, dataSourceSession, true);
_ds.currentRecordId = _ds.finalRecordId;
};
for await (const [key, raw_data_row] of Object.entries(_raw_data_rows)) {
const idx = Number(key);
if (await get_before_record_count()) {
await func.datasource.execute_view_events(SESSION_ID, dataSourceSession, 'before_record');
}
_ds.currentRecordId = raw_data_row.id; // set temporary to allow expression decoder work
await func.datasource.render_fields_form(SESSION_ID, dataSourceSession, raw_data_row);
try {
const init_count = await func.datasource.get_field_init_count(SESSION_ID, dataSourceSession, raw_data_row.id, false, _ds.oninit_triggers_to_run);
if (init_count > 0) {
await func.datasource.execute_field_init_events(SESSION_ID, dataSourceSession, 'form', raw_data_row.id);
}
} catch (err) {
console.error(err);
}
if (await get_after_record_count()) {
await func.datasource.execute_view_events(SESSION_ID, dataSourceSession, 'after_record');
}
}
await finish_form();
break;
}
default:
return func.utils.debug_report(SESSION_ID, 'Data source', 'Program type not defined', 'E');
}
ret = await callback_datasource();
return ret;
};
func.datasource.render_fields_dataset = async function (SESSION_ID, dataSourceSession, raw_data_row) {
var _ds = SESSION_OBJ[SESSION_ID].DS_GLB[dataSourceSession];
var args = _ds.args;
const _progFields = await func.datasource.get_progFields(SESSION_ID, dataSourceSession);
if (!_progFields) {
return;
}
const get_value = async (field_id, value) => {
let view_field_obj = func.common.find_item_by_key(_progFields, 'field_id', field_id);
var fieldType = view_field_obj?.props?.fieldType;
let table_field_obj;
if (view_field_obj.data.type === 'table' && field_id !== 'REDUCE_VALUE') {
if (!_ds.progDataSource?.dataSourceTableId) {
return func.utils.debug_report(SESSION_ID, 'Datasource', `Table type defined without dataSourceTableId deceleration`, 'E');
}
let table_obj = await func.utils.FILES_OBJ.get(SESSION_ID, _ds._dataSourceTableId);
if (!table_obj) {
return func.utils.debug_report(SESSION_ID, 'Datasource', `dataSourceTableId reference error: ` + _ds._dataSourceTableId, 'E');
}
table_field_obj = func.common.find_item_by_key(table_obj.tableFields, 'field_id', field_id);
fieldType = table_field_obj.props?.fieldType;
}
return await func.common.get_cast_val(SESSION_ID, `render fields dataset ${_ds.viewSourceDesc}`, field_id, fieldType, value, null);
};
if (!_ds.data_feed) {
_ds.data_feed = { rows: [{ _ROWID: _ds.currentRecordId }] };
}
let row_idx;
try {
row_idx = func.common.find_ROWID_idx(_ds, _ds.currentRecordId);
} catch (err) {
_ds.data_feed.rows.push({ _ROWID: _ds.currentRecordId });
row_idx = func.common.find_ROWID_idx(_ds, _ds.currentRecordId);
// console.error(err);
}
_ds.dataset_alias = {};
for await (const val of _progFields) {
try {
var fieldId = val.data.field_id;
if (val.data.type === 'virtual' || _ds.set_mode === 'C') {
if (typeof raw_data_row?.value?.[fieldId] !== 'undefined') {
// supports x=x+1
_ds.data_feed.rows[row_idx][fieldId] = await get_value(fieldId, raw_data_row.value[fieldId]);
continue;
}
if (val.props?.propExpressions?.fieldValue) {
let ret = await func.expression.get(SESSION_ID, val.props?.propExpressions?.fieldValue, dataSourceSession, 'update', args.rowIdP);
_ds.data_feed.rows[row_idx][fieldId] = await get_value(fieldId, ret.result);
continue;
}
_ds.data_feed.rows[row_idx][fieldId] = await get_value(fieldId, val.props?.fieldValue);
continue;
}
if (val.data.type === 'table' || val.data.type === 'datasource') {
if (typeof raw_data_row.value[fieldId] === 'undefined') {
throw 'field do not exist in data: ' + fieldId;
}
_ds.data_feed.rows[row_idx][fieldId] = await get_value(fieldId, raw_data_row.value[fieldId]);
}
} catch (err) {
func.utils.debug_report(SESSION_ID, 'Datasource', err, 'E', null, _ds);
}
}
};
func.datasource.run_events_functions = async function (SESSION_ID, dataSourceSession, event_id, calling_job, async_event, event_parameters, event_optionsP) {
if (typeof dataSourceSession === 'undefined' || dataSourceSession === null) {
console.warn(`Event ${event_id} not exist or not found`);
return;
}
// return new Promise(async (resolve, reject) => {
var _ds = SESSION_OBJ[SESSION_ID].DS_GLB[dataSourceSession];
var args = _ds.args;
// var v = _ds.v;
const _view_obj = await func.utils.VIEWS_OBJ.get(SESSION_ID, _ds.prog_id);
let job_promises = [];
if (_view_obj.progEvents) {
for await (const [key, val] of Object.entries(_view_obj.progEvents)) {
if (val.data.type === 'user_defined' && val.data.event_name && val.data.event_name === event_id) {
const jobs = await func.events.validate(
SESSION_ID,
'user_defined',
dataSourceSession,
val.data.event_name,
args.callingSourceP,
event_parameters, // val.data.parameters
null,
event_optionsP,
);
if (calling_job || async_event) continue;
for (let job_num of jobs) {
job_promises.push(
new Promise((resolve, reject) => {
let i = 0;
const interval = setInterval(() => {
i++;
var job_index = func.events.find_job_index(SESSION_ID, job_num);
if (job_index == null) {
clearInterval(interval);
resolve(job_num);
}
if (i > 200) {
func.utils.report_issue(SESSION_ID, {
code: 'RUN_MSG_DSC_050',
source: 'func.datasource.run_events_functions',
message: 'deadlock detected',
type: 'E',
details: {
job_num,
event_id,
dsSessionP,
},
});
clearInterval(interval);
resolve(job_num);
}
}, 100);
}),
);
}
}
}
}
if (job_promises.length) {
await Promise.all(job_promises);
}
};
func.datasource.render_fields_form = async function (SESSION_ID, dataSourceSession, raw_data_row) {
var _ds = SESSION_OBJ[SESSION_ID].DS_GLB[dataSourceSession];
const _progFields = await func.datasource.get_progFields(SESSION_ID, dataSourceSession);
if (!_progFields) {
return;
}
const get_value = async (field_id, value) => {
let view_field_obj = func.common.find_item_by_key(_progFields, 'field_id', field_id);
var fieldType = view_field_obj.props?.fieldType;
let table_field_obj;
if (view_field_obj.data.type === 'table' && field_id !== 'REDUCE_VALUE') {
if (!_ds._dataSourceTableId) {
return func.utils.debug_report(SESSION_ID, 'Datasource', `Table type defined without dataSourceTableId deceleration`, 'E');
}
let table_obj = await func.utils.FILES_OBJ.get(SESSION_ID, _ds._dataSourceTableId);
if (!table_obj) {
return func.utils.debug_report(SESSION_ID, 'Datasource', `dataSourceTableId reference error: ` + _ds._dataSourceTableId, 'E');
}
table_field_obj = func.common.find_item_by_key(table_obj.tableFields, 'field_id', field_id);
if (!table_field_obj) {
return func.utils.debug_report(SESSION_ID, 'Datasource', `Field Id: ${field_id} not exist in table ${table_obj.properties.menuName}`, 'E');
}
fieldType = table_field_obj.props?.fieldType;
}
return await func.common.get_cast_val(SESSION_ID, `render fields datasource ${_ds.viewSourceDesc}`, field_id, fieldType, value, null);
};
let row_idx;
try {
row_idx = func.common.find_ROWID_idx(_ds, raw_data_row.id);
} catch (error) {
_ds.data_feed.rows.push({ _ROWID: raw_data_row.id });
row_idx = func.common.find_ROWID_idx(_ds, raw_data_row.id);
}
_ds.dataset_alias = {};
for await (const val of _progFields) {
try {
var fieldId = val.data.field_id;
if (val.data.type === 'virtual' || raw_data_row.id === 'newRecord') {
if (glb.PROTECTED_VARS.includes(fieldId)) {
switch (fieldId) {
case '_ROWNO': {
_ds.data_feed.rows[row_idx][fieldId] = await get_value(fieldId, row_idx);
continue;
}
case '_ROWID': {
_ds.data_feed.rows[row_idx][fieldId] = await get_value(fieldId, raw_data_row.id);
continue;
}
case '_ROWDOC': {
_ds.data_feed.rows[row_idx][fieldId] = await get_value(fieldId, raw_data_row.value);
continue;
}
}
}
if (val.props?.propExpressions?.fieldValue) {
// check init exp existence
let ret = await func.expression.get(SESSION_ID, val.props?.propExpressions?.fieldValue, dataSourceSession, 'update', raw_data_row.id);
_ds.data_feed.rows[row_idx][fieldId] = await get_value(fieldId, ret.result);
continue;
}
const _vfp = func.datasource.__vf_preserve.get(_ds);
const _vfPrev = _vfp ? _vfp[fieldId] : undefined;
_ds.data_feed.rows[row_idx][fieldId] =
_vfPrev !== undefined && _vfPrev !== null && _vfPrev !== ''
? _vfPrev
: await get_value(fieldId, val.props?.fieldValue);
continue;
}
if (val.data.type === 'table' || val.data.type === 'datasource') {
if (typeof raw_data_row.value[fieldId] === 'undefined') {
throw 'field do not exist in data: ' + fieldId;
}
_ds.data_feed.rows[row_idx][fieldId] = await get_value(fieldId, raw_data_row.value[fieldId]);
}
} catch (err) {
func.utils.debug_report(SESSION_ID, 'Datasource', err, 'E', null, _ds);
}
}
};
func.datasource.execute_field_init_events = async function (SESSION_ID, dataSourceSession, sourceP, rowIdP) {
var _ds = SESSION_OBJ[SESSION_ID].DS_GLB[dataSourceSession];
var args = _ds.args;
var arr = _ds.dataSource_init_arr[rowIdP];
// for await (const [key, val] of Object.entries(arr)) {
for await (const val of arr) {
if (!func.utils.is_onscreen_event(val.eventInfo.data.action)) {
var cond = val?.eventInfo?.data?.enabled;
var expression = undefined;
if (val.eventInfo.props.condition) expression = val.eventInfo.props.condition;
var expCond = {};
if (expression && !xu_isEmpty(expression)) {
// check if expression exist
expCond = await func.expression.get(SESSION_ID, expression, dataSourceSession, 'condition', rowIdP, null, null, val.fieldId); // execute expression
cond = expCond.result;
expCond.conditional = true;
val.DEBUG_INFO_OBJ.result = expCond.result;
val.DEBUG_INFO_OBJ.error = expCond.error;
val.DEBUG_INFO_OBJ.fields = expCond.fields;
val.DEBUG_INFO_OBJ.conditional = expCond.conditional;
val.DEBUG_INFO_OBJ.details = expression;
}
func.utils.debug.log(SESSION_ID, val.node_id, val.DEBUG_INFO_OBJ);
if (cond) {
// check condition again
if (!_ds) continue;
var ds = _ds.prog_id;
await func.events.execute(
SESSION_ID,
null,
val.triggerId,
val.eventInfo.data.name, //val.eventInfo.data.trigger,
val.eventInfo.data.action,
val.eventInfo.data.name, //eventInfo[5],
null,
val.fieldId,
val.rowId,
val.colId,
null,
null,
dataSourceSession,
val.eventInfo.id, //eventInfo[2],
sourceP,
true,
null,
null,
args.jobNoP,
null,
null,
val.eventInfo,
null,
null,
null,
null,
ds.parentDataSourceNo,
null,
);
}
}
}
};
func.datasource.get_field_init_count = async function (SESSION_ID, dataSourceSession, rowIdP, pre_initP, oninit_triggers_to_runP) {
var _ds = SESSION_OBJ[SESSION_ID].DS_GLB[dataSourceSession];
var args = _ds.args;
const _view_obj = await func.utils.VIEWS_OBJ.get(SESSION_ID, _ds.prog_id);
// _ds.dataSource_init_arr[rowIdP] = {};
var ret = 0;
// if (_ds.v.ViewFieldsObjs) {
for await (const field_obj of _view_obj.progFields) {
var fieldId = field_obj.data.field_id;
if (!field_obj?.workflow?.length) {
continue;
}
for await (const trigger_obj of field_obj.workflow) {
if (oninit_triggers_to_runP && !oninit_triggers_to_runP?.includes(trigger_obj.id)) {
continue;
}
if (
// trigger_obj.data.trigger === "oninit" &&
['get_data', 'set_data', 'batch', 'update', 'raise_event'].includes(trigger_obj.data.action)
) {
if (!trigger_obj.data.action) {
func.utils.debug_report(SESSION_ID, '_ds.get_field_init_count', `Error initiating event for field: ${fieldId} prog: ${_ds.v.viewSourceDesc} row: ${rowIdP} reason: missing action`, 'E');
break;
}
if (trigger_obj.data.enabled) {
if (!_ds.dataSource_init_arr[rowIdP]) {
_ds.dataSource_init_arr[rowIdP] = [];
}
// _ds.dataSource_init_arr[rowIdP][ret] = {
// eventInfo: trigger_obj,
// triggerId: trigger_obj.id,
// fieldId: fieldId,
// rowId: rowIdP,
// colId: field_obj.id,
// node_id: args.prog_id + "_" + trigger_obj.id + "_" + field_obj.id,
// fieldProp: field_obj,
// DEBUG_INFO_OBJ: {
// module: _ds.viewModule,
// action: "init field event",
// prop: fieldId,
// source: _ds.viewSourceDesc,
// type: "event",
// prog_id: args.prog_id,
// dsSession: dataSourceSession,
// },
// };
_ds.dataSource_init_arr[rowIdP].push({
eventInfo: trigger_obj,
triggerId: trigger_obj.id,
fieldId: fieldId,
rowId: rowIdP,
colId: field_obj.id,
node_id: args.prog_id + '_' + trigger_obj.id + '_' + field_obj.id,
fieldProp: field_obj,
DEBUG_INFO_OBJ: {
module: _ds.viewModule,
action: 'init field event',
prop: fieldId,
source: _ds.viewSourceDesc,
type: 'event',
prog_id: args.prog_id,
dsSession: dataSourceSession,
},
});
ret++;
}
}
}
}
// }
return ret;
};
func.datasource.get_view_events_count = async function (SESSION_ID, dataSourceSession, typeP, eventIdP) {
var _ds = SESSION_OBJ[SESSION_ID].DS_GLB[dataSourceSession];
const _prog = await func.utils.VIEWS_OBJ.get(SESSION_ID, _ds.prog_id);
if (!_ds) return 0;
var args = _ds.args;
var index = typeP;
if (eventIdP) index = typeP + '_' + eventIdP;
// client interval
if (!_ds.viewEventExec_arr) _ds.viewEventExec_arr = {};
_ds.viewEventExec_arr[index] = [];
if (!_prog.progEvents || xu_isEmpty(_prog.progEvents)) return 0;
for (const event_obj of _prog.progEvents) {
if (event_obj.data.type !== typeP) continue; // was false?? changed to true 020317
if (eventIdP && event_obj.id !== eventIdP) continue; // match w ID added 03312017
if (event_obj.data.condition) {
let res = await func.expression.get(SESSION_ID, event_obj.data.condition, dataSourceSession, 'condition', args.rowIdP, null, null, null, null, event_obj);
if (!res.result) {
continue;
}
}
if (xu_isEmpty(event_obj.workflow)) continue;
for (const trigger_obj of event_obj.workflow) {
if (trigger_obj.data.enabled) {
var expression;
if (trigger_obj.props.condition) expression = trigger_obj.props.condition;
var expCond = {};
if (expression) {
expCond.conditional = true;
}
func.utils.debug.log(SESSION_ID, args.prog_id + '_' + trigger_obj.id, {
module: _ds.viewModule,
action: trigger_obj.data.action,
prop: event_obj.data.type,
details: expression,
result: expCond.result,
error: expCond.error,
source: _ds.viewSourceDesc,
fields: expCond.fields,
type: 'event',
prog_id: args.prog_id,
dsSession: dataSourceSession,
conditional: expCond.conditional,
});
}
if (!trigger_obj.data.action) {
func.utils.debug_report(SESSION_ID, 'get_view_events_count', `Error initiating ${typeP} prog:${_ds.v.viewSourceDesc} reason: missing action`, 'E');
break;
}
if (!glb.REFERENCE_LESS_FUNCTIONS.includes(trigger_obj.data.action) && !trigger_obj.data.action) {
func.utils.debug_report(SESSION_ID, 'get_view_events_count', `Error initiating ${typeP} prog: ${_ds.v.viewSourceDesc} reason: missing reference`, 'E');
break;
}
if (trigger_obj.data.enabled) {
_ds.viewEventExec_arr[index].push({
eventInfo: trigger_obj,
eventId: event_obj.id,
triggerId: trigger_obj.id,
expression: expression,
});
// ret++;
}
}
}
return _ds.viewEventExec_arr[index].length;
};
func.datasource.execute_view_events = async function (SESSION_ID, dataSourceSession, typeP, eventIdP) {
var _ds = SESSION_OBJ[SESSION_ID].DS_GLB[dataSourceSession];
var args = _ds.args;
var i = -1;
var index = typeP;
if (eventIdP) index = typeP + '_' + eventIdP;
var arr = _ds.viewEventExec_arr[index];
if (xu_isEmpty(arr)) return;
for await (const val of arr) {
if (!glb.IS_WORKER || !func.utils.is_onscreen_event(val.eventInfo.data.action) || (glb.IS_WORKER && _ds.v.run_at === 'server' && !func.utils.is_onscreen_event(val.eventInfo.data.action))) {
// val.done = true;
var cond = true;
if (val.expression) {
var expCond = await func.expression.get(SESSION_ID, val.expression, dataSourceSession, 'condition', args.rowIdP, null, null, null, null, val.eventInfo); // execute expression
cond = expCond.result;
}
if (cond) {
var elem_params = undefined;
if (!glb.IS_WORKER) {
const container_meta = func.runtime.ui.get_meta_by_element_id(_ds.containerId);
elem_params = container_meta?.params;
}
const ret = await func.events.execute(
SESSION_ID,
null,
val.triggerId,
val.eventInfo.data.trigger,
val.eventInfo.data.action,
val.eventInfo.data.name,
null,
null,
null,
null,
val.eventInfo.data.action,
null,
dataSourceSession,
val.eventId,
_ds.tree_obj.menuType + ' event',
true,
null,
null,
args.jobNoP,
elem_params,
null,
val.eventInfo,
);
}
continue;
}
// on screen event
if (typeP == 'before_record' || typeP == 'after_record' || typeP == 'on_load' || typeP == 'on_exit') {
_ds.v.onscreen_events_active = {
i: i,
type: typeP,
};
var parent_ds_chain = func.datasource.get_parent_ds_chain(SESSION_ID, dataSourceSession);
var obj = {
ds_obj: func.utils.clean_returned_datasource(SESSION_ID, dataSourceSession),
dsSessionP: dataSourceSession,
};
obj.ds_obj.parent_ds_chain = parent_ds_chain;
}
}
};
func.datasource.get_parent_ds_chain = function (SESSION_ID, dataSourceSession) {
var arr = [];
var drill = function (ds) {
if (SESSION_OBJ[SESSION_ID].DS_GLB[ds]) {
if (typeof SESSION_OBJ[SESSION_ID].DS_GLB[ds].parentDataSourceNo !== 'undefined') {
arr.push(SESSION_OBJ[SESSION_ID].DS_GLB[ds].parentDataSourceNo);
drill(SESSION_OBJ[SESSION_ID].DS_GLB[ds].parentDataSourceNo);
}
}
};
drill(dataSourceSession);
return arr;
};
func.datasource.execute_onscreen_view_events = async function (SESSION_ID, dataSourceSession, sourceP) {
var _ds = SESSION_OBJ[SESSION_ID].DS_GLB[dataSourceSession];
var args = _ds.args;
var i = _ds.v.onscreen_events_active.i;
var type = _ds.v.onscreen_events_active.type;
var evnt;
if (_ds.viewEventExec_arr?.[type]?.[i]) {
evnt = _ds.viewEventExec_arr[type][i];
evnt.done = true;
var cond = true;
if (evnt.expression) {
var expCond = await func.expression.get(SESSION_ID, evnt.expression, dataSourceSession, 'condition', args.rowIdP); // execute expression
cond = expCond.result;
}
if (cond) {
let ret = await func.events.execute(
SESSION_ID,
null,
evnt.triggerId,
evnt.eventInfo.data.trigger,
evnt.eventInfo.data.action,
evnt.eventInfo.data.name, //eventInfo[5],
null,
null,
null,
null,
evnt.eventInfo.data.name, //eventInfo[4],
null,
dataSourceSession,
null,
sourceP + ' event',
true,
null,
null,
args.jobNoP,
);
}
} else console.error('*execute_onscreen_view_events error');
};
func.datasource.get_event_interval_arr = function (SESSION_ID, dataSourceSession, typeP) {
var _ds = SESSION_OBJ[SESSION_ID].DS_GLB[dataSourceSession];
// var v = _ds.v;
var arr = [];
var ret = 0;
if (!_ds.v.progEvents) return 0;
for (let val of _ds.v.progEvents) {
if (val.data.type !== typeP) continue;
arr.push([val.id, val.data.properties, val.data.condition]);
}
return arr;
};
func.datasource.clean_all = function (SESSION_ID, dsP) {
var arr = [dsP];
var get_child_ds = function (ds) {
var arr = [];
for (const [key, val] of Object.entries(SESSION_OBJ[SESSION_ID].DS_GLB)) {
if (val.parentDataSourceNo == ds) {
arr.push(key);
arr = arr.concat(get_child_ds(key));
}
}
return arr;
};
arr = arr.concat(get_child_ds(dsP));
for (let val of arr) {
func.datasource.del(SESSION_ID, val);
}
};
func.datasource.clean = function (SESSION_ID, screenIdP) {
var arr = [];
for (const [key, val] of Object.entries(SESSION_OBJ[SESSION_ID].DS_GLB)) {
try {
const screen_parent_id = val.screenId && func.runtime?.ui?.get_parent_element_id
? func.runtime.ui.get_parent_element_id(val.screenId)
: null;
if (
Number(key) > 0 &&
(val.screenId === screenIdP ||
val.rootScreenId === screenIdP ||
screen_parent_id === screenIdP ||
(val && val.parentDataSourceNo && arr.includes(val.parentDataSourceNo.toString())))
) {
arr.push(key);
if (val.screenId && func.UI?.utils?.screen_blocker) func.UI.utils.screen_blocker(false, val.screenId);
}
} catch (err) {
console.warn('func.datasource.clean failed');
func.datasource.reset_jobs(SESSION_ID, key, 'datasource.clean', err);
}
}
for (let val of arr) {
func.datasource.del(SESSION_ID, val);
}
};
func.datasource.del = function (SESSION_ID, dsP) {
if ((SESSION_OBJ[SESSION_ID].DS_GLB[dsP] && SESSION_OBJ[SESSION_ID].DS_GLB[dsP].keep_alive) || dsP == 0) return;
if (DATASOURCE_INTERVALS[SESSION_ID] && DATASOURCE_INTERVALS[SESSION_ID][dsP]) {
DATASOURCE_INTERVALS[SESSION_ID][dsP].clear();
}
const perform_delete = async function () {
var response = {
success: function (jsonP, ajaxP) {},
error: function (status) {
console.error('error datasource:' + status);
},
fail: function (status) {
console.error('error datasource:' + status);
},
};
var data = {
session_id: SESSION_ID,
dssession: dsP,
};
if (!SESSION_OBJ[SESSION_ID].DS_GLB[dsP]) return;
if (!glb.IS_WORKER) {
let _ds = SESSION_OBJ[SESSION_ID].DS_GLB[dsP];
if (_ds.worker_id) {
// if (_ds.data_xuda) console.log(dsP);
if (RUNTIME_SERVER_WEBSOCKET && RUNTIME_SERVER_WEBSOCKET_CONNECTED && (!_session.opt.app_computing_mode || _session.opt.app_computing_mode === 'server')) {
WEB_WORKER[SESSION_ID][_ds.worker_id].emit('message', {
service: 'close_websocket',
});
} else {
WEB_WORKER[SESSION_ID][_ds.worker_id].worker.terminate();
}
delete WEB_WORKER[SESSION_ID][_ds.worker_id];
} else {
const json = await func.index.call_worker(SESSION_ID, {
service: 'datasource_delete',
data: data,
id: _ds.worker_id,
});
response.success(json, true);
}
if (DS_UI_EVENTS_GLB) delete DS_UI_EVENTS_GLB[dsP];
}
delete SESSION_OBJ[SESSION_ID].DS_GLB[dsP];
};
var delete_pending_jobs = function () {
var arr = [];
for (const [key, val] of Object.entries(SESSION_OBJ[SESSION_ID].WORKER_OBJ.jobs)) {
if (val && val.dsSessionP == dsP) {
arr.push(key);
func.runtime.ui.clear_screen_blockers();
}
}
for (let val of arr.reverse()) {
SESSION_OBJ[SESSION_ID].WORKER_OBJ.jobs.splice(val, 1);
}
if (!SESSION_OBJ[SESSION_ID].WORKER_OBJ.jobs.length) SESSION_OBJ[SESSION_ID].WORKER_OBJ.stat = null;
};
if (!glb.IS_WORKER) {
if (SESSION_OBJ[SESSION_ID].DS_GLB[dsP]) {
delete SCREEN_BLOCKER_OBJ[SESSION_OBJ[SESSION_ID].DS_GLB[dsP].screenId + '_' + SESSION_OBJ[SESSION_ID].DS_GLB[dsP].callingScreenId];
// Refresh settled for this screen: once fully unblocked, reconcile
// teleports (drop orphans / mirror host visibility) on the next microtask
// (after this datasource completion unwinds) so there is no 1s lag on
// panel open/close. Gated so it is a no-op when there are no teleports.
if (func.UI?.reconcile_teleports) {
Promise.resolve().then(function () {
if (xu_isEmpty(SCREEN_BLOCKER_OBJ)) {
try {
func.UI.reconcile_teleports();
} catch (e) {}
}
});
}
delete_pending_jobs();
if (glb.new_xu_render) {
for (const [ui_cache_key, ui_cache_val] of Object.entries(UI_WORKER_OBJ.xu_render_cache)) {
if (ui_cache_val.paramsP.dsSessionP === dsP) {
delete UI_WORKER_OBJ.xu_render_cache[ui_cache_key];
}
}
}
}
const _nav_node = func.runtime.ui.get_first_node(SESSION_OBJ[SESSION_ID].root_element)?.querySelector?.('xu-nav');
if (_nav_node) {
var ds_obj = func.runtime.ui.get_data(_nav_node)?.xuData?.nav_params;
if (ds_obj) {
delete ds_obj[dsP];
}
}
}
perform_delete();
};
func.datasource.update = async function (SESSION_ID, datasource_changes, update_local_scope_only, avoid_xu_for_refresh, trigger) {
return new Promise(async (resolve, reject) => {
var _session = SESSION_OBJ[SESSION_ID];
if (glb.XU_PERF) {
// any state mutation invalidates memoized drive-ref clean verdicts and
// the per-epoch expression slot clones
func.utils.drive_ref_clean_cache = new WeakSet();
func.expression._slot_clone_cache = new WeakMap();
}
const refresh_control = avoid_xu_for_refresh && typeof avoid_xu_for_refresh === 'object' ? avoid_xu_for_refresh : {};
const avoid_refresh = avoid_xu_for_refresh === true || refresh_control.avoid_refresh === true;
const refresh_attributes_when_avoiding = refresh_control.refresh_attributes === true;
const skip_attribute_refresh = avoid_refresh && !refresh_attributes_when_avoiding;
const skip_screen_refresh = avoid_refresh || refresh_control.avoid_screen_refresh === true;
const defer_screen_refresh = !skip_screen_refresh && refresh_control.defer_refresh === true;
if (_session.IS_API || typeof IS_MASTER_WEBSOCKET !== 'undefined' || typeof IS_PROCESS_SERVER !== 'undefined') {
update_local_scope_only = true;
}
if (typeof glb.GLOBAL_VARS === 'undefined') {
glb.GLOBAL_VARS = (await func.common.get_module(SESSION_ID, 'xuda-system-globals-module.mjs')).system_globals;
}
const set_fieldComputed_dependencies = async function (dsNo, field_id, parent_ds) {
// iterate child ds
for (const [dsSession, _ds] of Object.entries(_session.DS_GLB)) {
if (parent_ds !== null) {
if (_ds.parentDataSourceNo != parent_ds) continue;
} else {
if (dsSession != dsNo) continue;
}
let tree_ret = await func.utils.TREE_OBJ.get(SESSION_ID, _ds.prog_id);
if (tree_ret.menuType === 'component' || tree_ret.menuType === 'globals') {
// check if field has fieldComputed property
const _progFields = await func.datasource.get_progFields(SESSION_ID, dsSession);
// find if field is computed
let fieldComputed_propExpressions, fieldComputed_id;
for await (const val of _progFields) {
const fieldId = val.data.field_id;
// if (fieldId !== field_id) continue
if (val.data.type !== 'virtual' || !val.props.fieldComputed) continue;
const _propExpressions = val.props?.propExpressions?.fieldValue;
if (_propExpressions && JSON.stringify(_propExpressions).includes(field_id)) {
fieldComputed_propExpressions = _propExpressions;
fieldComputed_id = fieldId;
}
}
if (!fieldComputed_id) return;
// iterate ds rows
for (const row of _ds.data_feed?.rows || []) {
// iterate row fields
for (const [key, val] of Object.entries(row)) {
if (key !== fieldComputed_id) continue;
try {
let ret = await func.expression.get(SESSION_ID, fieldComputed_propExpressions, dsNo, 'update', row._ROWID);
const row_idx = func.common.find_ROWID_idx(_ds, row._ROWID);
if (_ds.data_feed.rows[row_idx][fieldComputed_id] !== ret.result) {
_ds.data_feed.rows[row_idx][fieldComputed_id] = ret.result;
if (!fields_changed.includes(fieldComputed_id)) {
fields_changed.push(fieldComputed_id);
}
if (!datasource_changed.includes(dsSession)) {
datasource_changed.push(dsSession);
}
}
} catch (err) {
console.error(err);
}
}
}
}
await set_fieldComputed_dependencies(dsNo, field_id, dsSession);
}
};
var fields_changed = [];
var datasource_changed = [];
let client_datasource_changes = {};
let server_datasource_changes = {};
const mark_field_changed = async function (dataSource, field_id) {
if (!fields_changed.includes(field_id)) {
fields_changed.push(field_id);
// Refresh dependent in-parameters that reference this field.
for (const [_dsSession, _ds] of Object.entries(_session.DS_GLB)) {
if (_ds.args.parameters_raw_obj) {
for (const [key, exp] of Object.entries(_ds.args.parameters_raw_obj)) {
if (exp.includes(field_id)) {
let ret = await func.expression.get(SESSION_ID, exp, _dsSession, 'parameters');
_ds.in_parameters[key].value = ret.result;
}
}
}
}
}
if (!datasource_changed.includes(dataSource)) {
datasource_changed.push(dataSource);
}
};
const queue_remote_change = async function (dataSource, record_id, field_id, value, _ds) {
if (update_local_scope_only) {
return;
}
let tree_ret = await func.utils.TREE_OBJ.get(SESSION_ID, _ds.prog_id);
if (glb.IS_WORKER) {
if (tree_ret.menuType === 'globals' || tree_ret.menuType === 'component') {
const _progFields = await func.datasource.get_progFields(SESSION_ID, dataSource);
let view_field_obj = func.common.find_item_by_key(_progFields, 'field_id', field_id);
if (!view_field_obj?.data?.serverField && record_id !== 'data_system') {
if (!client_datasource_changes[dataSource]) {
client_datasource_changes[dataSource] = {};
}
if (!client_datasource_changes[dataSource][record_id]) {
client_datasource_changes[dataSource][record_id] = {};
}
client_datasource_changes[dataSource][record_id][field_id] = value;
}
}
} else {
if ((tree_ret.menuType === 'component' && _ds._run_at !== 'client') || tree_ret.menuType === 'globals') {
if (!server_datasource_changes[dataSource]) {
server_datasource_changes[dataSource] = {};
}
if (!server_datasource_changes[dataSource][record_id]) {
server_datasource_changes[dataSource][record_id] = {};
}
server_datasource_changes[dataSource][record_id][field_id] = value;
}
}
};
const update_xu_ref = function (dataSource) {
let ret;
let _ds_0 = _session.DS_GLB[0];
for ([ref_name, val] of Object.entries(_ds_0.data_system['SYS_GLOBAL_OBJ_REFS'])) {
if (val?.ds?.dsSession == dataSource) {
ret = func.UI.update_xu_ref(SESSION_ID, dataSource, ref_name);
}
}
return ret;
};
const mark_xu_refs_changed = function (dataSource) {
// Pushing the synthetic 'SYS_GLOBAL_OBJ_REFS' pseudo-field into fields_changed makes
// refresh_screen re-evaluate panel/modal visibility conditions that depend on @refs. This
// should only happen when update_xu_ref reports a real ref/data change.
if (!fields_changed.includes('SYS_GLOBAL_OBJ_REFS')) fields_changed.push('SYS_GLOBAL_OBJ_REFS');
};
// --- Watch fields (snapshot) -------------------------------------------------
// Before applying any change, snapshot the current value of every datasource's
// declared progDataSource.dataSourceWatchFields (Studio "Watch" popover). A watch
// field can be an in-parameter (e.g. pt_in) whose value is derived from another
// field, so we cannot detect it by matching fields_changed names — we compare its
// value before/after (see the fire-lifecycle block after the loop below).
const get_watch_field_value = function (watch_ds, watch_field_id) {
if (watch_ds?.in_parameters?.[watch_field_id] && typeof watch_ds.in_parameters[watch_field_id].value !== 'undefined') {
return watch_ds.in_parameters[watch_field_id].value;
}
const watch_rows = watch_ds?.data_feed?.rows || [];
const watch_row = watch_rows.find(function (row) { return row && row._ROWID === watch_ds.currentRecordId; }) || watch_rows[0] || {};
return watch_row[watch_field_id];
};
const watch_field_snapshot = {};
if (!glb.IS_WORKER) {
for (const [watch_dsSession, watch_ds] of Object.entries(_session.DS_GLB)) {
const watch_fields = watch_ds?.progDataSource?.dataSourceWatchFields;
if (!Array.isArray(watch_fields) || !watch_fields.length) {
continue;
}
watch_field_snapshot[watch_dsSession] = {};
for (let watch_index = 0; watch_index < watch_fields.length; watch_index++) {
const watch_field = watch_fields[watch_index];
const watch_field_id = typeof watch_field === 'string' ? watch_field.replace(/^@/, '') : (watch_field?.field_id ?? watch_field?.value ?? watch_field?.id);
if (watch_field_id) {
watch_field_snapshot[watch_dsSession][watch_field_id] = get_watch_field_value(watch_ds, watch_field_id);
}
}
}
}
// iterate changes datasource
for await (const [dataSource, row_data] of Object.entries(datasource_changes)) {
var _ds = _session.DS_GLB[dataSource];
if (!_ds) {
continue;
}
// iterate changes records
for (const [record_id, fields_data] of Object.entries(row_data)) {
// iterate changes fields
for (const [field_id, value] of Object.entries(fields_data)) {
// mechanism to make update directly on the datasource object
if (record_id === 'datasource_main') {
xu_set(_ds, field_id, value);
// stat/stat_ts/is_worker are datasource bookkeeping written on refresh lifecycle
// transitions. The value is still applied to _ds above; skip ref-change detection for
// these fields so lifecycle ticks do not schedule another screen refresh by themselves.
const _is_status_tick = field_id === 'stat' || field_id === 'stat_ts' || field_id === 'is_worker';
const ret = _is_status_tick ? false : update_xu_ref(dataSource);
if (ret) {
fields_changed.push(field_id);
datasource_changed.push(dataSource);
mark_xu_refs_changed(dataSource);
}
if (!glb.IS_WORKER && field_id === 'watcher') {
if (!server_datasource_changes[dataSource]) {
server_datasource_changes[dataSource] = {};
}
if (!server_datasource_changes[dataSource][record_id]) {
server_datasource_changes[dataSource][record_id] = {};
}
server_datasource_changes[dataSource][record_id][field_id] = value;
if (!update_local_scope_only) {
const ret = await func.index.call_worker(SESSION_ID, {
service: 'update_datasource_changes_from_client',
data: {
session_id: SESSION_ID,
datasource_changes: server_datasource_changes,
},
id: _ds.worker_id,
});
}
if (skip_screen_refresh) {
globalThis.__XUDA_RT_TRACE && console.log('[xuda-runtime] datasource_update_refresh_skipped ' +
JSON.stringify({
reason: 'avoid_refresh',
phase: 'watcher',
dataSource,
field_id,
fields_changed: structuredClone(fields_changed),
datasource_changed: structuredClone(datasource_changed),
}),
);
} else if (defer_screen_refresh) {
globalThis.__XUDA_RT_TRACE && console.log('[xuda-runtime] datasource_update_refresh_queued ' +
JSON.stringify({
phase: 'watcher',
dataSource,
field_id,
fields_changed: structuredClone(fields_changed),
datasource_changed: structuredClone(datasource_changed),
}),
);
func.runtime.ui.refresh_screen({
SESSION_ID,
fields_changed_arr: structuredClone(fields_changed),
datasource_changed: datasource_changed[0],
fields_changed_datasource: datasource_changed[0],
watcher: value,
})?.catch?.(function (error) {
console.error(error);
});
} else {
globalThis.__XUDA_RT_TRACE && console.log('[xuda-runtime] datasource_update_refresh ' +
JSON.stringify({
phase: 'watcher',
dataSource,
field_id,
fields_changed: structuredClone(fields_changed),
datasource_changed: structuredClone(datasource_changed),
}),
);
await func.runtime.ui.refresh_screen({
SESSION_ID,
fields_changed_arr: structuredClone(fields_changed),
datasource_changed: datasource_changed[0],
fields_changed_datasource: datasource_changed[0],
watcher: value,
});
}
}
continue;
}
if (typeof fields_data === 'object') {
if (glb.GLOBAL_VARS[field_id]) {
if (!_ds.data_system) {
_ds.data_system = {};
}
_ds.data_system[field_id] = value;
if (dataSource != 0 && _session.DS_GLB[0]) {
if (!_session.DS_GLB[0].data_system) {
_session.DS_GLB[0].data_system = {};
}
_session.DS_GLB[0].data_system[field_id] = value;
}
continue;
}
const dynamic_field = _ds?.dynamic_fields?.[field_id];
if (dynamic_field) {
if (!xu_isEqual(dynamic_field.value, value)) {
dynamic_field.value = value;
await set_fieldComputed_dependencies(dataSource, field_id, null);
if (update_xu_ref(dataSource)) {
mark_xu_refs_changed(dataSource);
}
await queue_remote_change(dataSource, record_id, field_id, value, _ds);
await mark_field_changed(dataSource, field_id);
}
continue;
}
try {
const row_idx = func.common.find_ROWID_idx(_ds, record_id);
// if (_ds.data_feed.rows[row_idx][field_id] !== value) {
if (!xu_isEqual(_ds.data_feed.rows[row_idx][field_id], value)) {
_ds.data_feed.rows[row_idx][field_id] = value;
await set_fieldComputed_dependencies(dataSource, field_id, null);
// search the field in refs
if (update_xu_ref(dataSource)) {
mark_xu_refs_changed(dataSource);
}
await queue_remote_change(dataSource, record_id, field_id, value, _ds);
await mark_field_changed(dataSource, field_id);
if (!_ds.data_feed.rows_changed) {
_ds.data_feed.rows_changed = [];
}
if (!_ds.data_feed.rows_changed.includes(record_id)) _ds.data_feed.rows_changed.push(record_id);
}
} catch (error) {
// normal
}
} else if (fields_data === 'set') {
_ds.currentRecordId = record_id;
}
}
}
}
// --- Watch fields (fire lifecycle) -------------------------------------------
// Re-fire the on_load/screen_ready lifecycle for any datasource whose watched VALUE
// changed vs the pre-change snapshot (captured above, before the change loop). We
// compare VALUES, not fields_changed names, because a watch field is typically an
// in-parameter (e.g. pt_in) whose value is DERIVED from another field (active_pt_id_v
// via xu-exp:pt_in) — so the changed field name never equals the watch name. on_load
// and screen_ready are gated to INITIAL load only (run_on_load_events /
// schedule_panel_on_load_events) to avoid render loops, so data a screen_ready derives
// (e.g. a virtual field) stays stale on refresh. A watch field is an explicit trigger,
// so re-running the lifecycle is intended. Coalesced (latest state wins) + throttled
// (<=15 fires/sec per datasource) so a mis-configured watch on a field the lifecycle
// itself writes is broken with a warning instead of looping forever.
if (!glb.IS_WORKER && !xu_isEmpty(watch_field_snapshot)) {
const watch_field_state = (_session.__watch_field_state = _session.__watch_field_state || { active: new Set(), pending: new Set(), fire_times: {} });
const fire_watch_field_lifecycle = function (watch_dsSession) {
if (watch_field_state.active.has(watch_dsSession)) {
watch_field_state.pending.add(watch_dsSession);
return;
}
const now = Date.now();
watch_field_state.fire_times[watch_dsSession] = (watch_field_state.fire_times[watch_dsSession] || []).filter(function (fired_at) {
return now - fired_at < 1000;
});
if (watch_field_state.fire_times[watch_dsSession].length >= 15) {
console.warn('[xuda-runtime] watch-field lifecycle throttled for datasource ' + watch_dsSession + ' — a watch field may be written by its own on_load/screen_ready (loop).');
return;
}
watch_field_state.fire_times[watch_dsSession].push(now);
watch_field_state.active.add(watch_dsSession);
setTimeout(async function () {
try {
for (const lifecycle_event of ['on_load', 'screen_ready']) {
if (await func.datasource.get_view_events_count(SESSION_ID, watch_dsSession, lifecycle_event)) {
await func.datasource.execute_view_events(SESSION_ID, watch_dsSession, lifecycle_event);
}
}
} catch (watch_error) {
console.error(watch_error);
} finally {
watch_field_state.active.delete(watch_dsSession);
if (watch_field_state.pending.has(watch_dsSession)) {
watch_field_state.pending.delete(watch_dsSession);
fire_watch_field_lifecycle(watch_dsSession);
}
}
}, 0);
};
for (const watch_dsSession of Object.keys(watch_field_snapshot)) {
const watch_ds = _session.DS_GLB[watch_dsSession];
if (!watch_ds) {
continue;
}
let watch_value_changed = false;
const watch_field_ids = Object.keys(watch_field_snapshot[watch_dsSession]);
for (let watch_index = 0; watch_index < watch_field_ids.length; watch_index++) {
const watch_field_id = watch_field_ids[watch_index];
if (!xu_isEqual(watch_field_snapshot[watch_dsSession][watch_field_id], get_watch_field_value(watch_ds, watch_field_id))) {
watch_value_changed = true;
break;
}
}
if (watch_value_changed) {
fire_watch_field_lifecycle(watch_dsSession);
}
}
}
if (glb.IS_WORKER) {
if (!update_local_scope_only && !xu_isEmpty(client_datasource_changes)) {
func.utils.post_back_to_client(SESSION_ID, 'update_client_eventChangesResults_from_worker', _session.worker_id, client_datasource_changes);
}
} else {
if (!update_local_scope_only && !xu_isEmpty(server_datasource_changes)) {
const ret = await func.index.call_worker(SESSION_ID, {
service: 'update_datasource_changes_from_client',
data: {
session_id: SESSION_ID,
datasource_changes: server_datasource_changes,
},
id: _ds.worker_id,
});
}
///// REFRESH SCREEN
if (fields_changed.length) {
function findMin(arr) {
return Math.min(...arr.map(Number));
}
if (!skip_attribute_refresh) {
if (skip_screen_refresh) {
globalThis.__XUDA_RT_TRACE && console.log('[xuda-runtime] datasource_update_attributes_only ' +
JSON.stringify({
reason: 'avoid_screen_refresh',
phase: 'fields',
fields_changed: structuredClone(fields_changed),
datasource_changed: structuredClone(datasource_changed),
trigger: trigger || null,
}),
);
} else if (defer_screen_refresh) {
globalThis.__XUDA_RT_TRACE && console.log('[xuda-runtime] datasource_update_refresh_queued ' +
JSON.stringify({
phase: 'fields',
fields_changed: structuredClone(fields_changed),
datasource_changed: structuredClone(datasource_changed),
trigger: trigger || null,
}),
);
} else {
globalThis.__XUDA_RT_TRACE && console.log('[xuda-runtime] datasource_update_refresh ' +
JSON.stringify({
phase: 'fields',
fields_changed: structuredClone(fields_changed),
datasource_changed: structuredClone(datasource_changed),
trigger: trigger || null,
}),
);
}
// await func.UI.screen.refresh_xu_attributes(SESSION_ID, _.cloneDeep(fields_changed), null, null, findMin(datasource_changed), avoid_xu_for_refresh, trigger);
await func.runtime.ui.refresh_xu_attributes({
SESSION_ID,
fields_arr: structuredClone(fields_changed),
jobNoP: null,
$elm_to_search: null,
dsSession_changed: findMin(datasource_changed),
avoid_xu_for_refresh: skip_screen_refresh,
trigger,
ignore_screen_blocker: true,
});
}
if (skip_screen_refresh) {
globalThis.__XUDA_RT_TRACE && console.log('[xuda-runtime] datasource_update_refresh_skipped ' +
JSON.stringify({
reason: refresh_attributes_when_avoiding ? 'avoid_screen_refresh' : 'avoid_refresh',
phase: 'fields',
attributes_refreshed: !skip_attribute_refresh,
fields_changed: structuredClone(fields_changed),
datasource_changed: structuredClone(datasource_changed),
}),
);
} else if (defer_screen_refresh) {
func.runtime.ui.refresh_screen({
SESSION_ID,
fields_changed_arr: structuredClone(fields_changed),
datasource_changed: null,
fields_changed_datasource: datasource_changed[0],
})?.catch?.(function (error) {
console.error(error);
});
} else {
// await removed from the below function cause to dead lock Mar 3 25
await func.runtime.ui.refresh_screen({
SESSION_ID,
fields_changed_arr: structuredClone(fields_changed),
datasource_changed: null,
fields_changed_datasource: datasource_changed[0],
});
}
}
// ///// REFRESH PARAMETERS IN
// if (fields_changed.length) {
// for (const [_dsSession, _ds] of Object.entries(_session.DS_GLB)) {
// if (_ds.args.parameters_raw_obj) {
// for (const [key, val] of Object.entries(_ds.args.parameters_raw_obj)) {
// if (fields_changed.includes(val)) {
// let ret = await func.expression.get(SESSION_ID, '@' + val, _dsSession, 'parameters');
// _ds.in_parameters[val].value = ret.result;
// }
// }
// }
// }
// }
}
resolve();
});
};
func.datasource.callback = async function (SESSION_ID, dsSessionP, rowIdP, jobNoP, nodeIdP) {
var _session = SESSION_OBJ[SESSION_ID];
var _ds = _session.DS_GLB[dsSessionP];
try {
const row_idx = func.common.find_ROWID_idx(_ds, 'dataset');
if (_ds.PARAM_OUT_INFO) {
// write log for out params
for (const [key, val] of Object.entries(_ds.PARAM_OUT_INFO)) {
if (typeof _ds?.data_feed?.rows?.[row_idx]?.[val.fieldId] === 'undefined') {
// func.utils.debug.log(SESSION_ID, key, val);
func.utils.alerts.invoke(SESSION_ID, 'system_msg', 'SYS_MSG_0310', val.fieldId, dsSessionP);
break;
}
val.result = _ds.data_feed.rows[row_idx][val.fieldId];
}
}
} catch (err) {
// console.error(err);
}
const datasetOutputField = _ds?.progDataSource?.datasetOutputField;
if (datasetOutputField) {
let ret_get_value = await func.datasource.get_value(SESSION_ID, datasetOutputField, _ds.dsSession);
if (ret_get_value.found) {
let datasource_changes = {};
if (!datasource_changes[ret_get_value.dsSessionP]) {
datasource_changes[ret_get_value.dsSessionP] = {};
}
if (!datasource_changes[ret_get_value.dsSessionP][ret_get_value.currentRecordId]) {
datasource_changes[ret_get_value.dsSessionP][ret_get_value.currentRecordId] = {};
datasource_changes[ret_get_value.dsSessionP][ret_get_value.currentRecordId][_ds?.progDataSource?.datasetOutputField] = _ds?.data_feed?.rows || [];
await func.datasource.update(SESSION_ID, datasource_changes);
}
}
}
func.utils.debug.log(SESSION_ID, nodeIdP, {
module: _ds.viewModule,
action: 'close',
source: _ds.viewSourceDesc,
type: 'adapter',
prog_id: _ds.prog_id,
dsSession: dsSessionP,
prop: _ds.log_prop + ' ' + 'adapter',
});
if (!glb.IS_WORKER) func.runtime.platform.set_cursor(_session.root_element, 'default');
if (_ds.prog_id) {
let _ds = _session.DS_GLB[dsSessionP];
func.utils.debug.watch(
SESSION_ID,
_ds.prog_id,
'program',
{
in_parameters: _ds.in_parameters,
out_parameters: _ds.out_parameters,
data_feed: _ds.data_feed,
},
_ds.tree_obj.menuType,
);
}
delete _ds.old_dataSource;
return {
SESSION_ID,
dsSessionP,
rowIdP,
jobNoP,
callingLogId: _ds.callingLogId,
calling_jobP: _ds.calling_jobP,
};
};
func.datasource.validate_viewRange = async function (SESSION_ID, viewRangeExpP, dsSessionP, rowIdP, sourceP) {
var _ds = SESSION_OBJ[SESSION_ID].DS_GLB[dsSessionP];
if (viewRangeExpP && _ds) {
var ret = func.expression.remove_quotes(await func.expression.get(SESSION_ID, viewRangeExpP, dsSessionP, 'range', rowIdP));
ret.result = func.expression.remove_quotes(ret.result);
func.utils.debug.log(SESSION_ID, _ds.prog_id, {
module: _ds.viewModule,
action: 'range Exp',
prop: sourceP,
details: viewRangeExpP,
result: ret.result,
error: ret.error,
source: _ds.viewSourceDesc,
json: ret.explain,
fields: ret.fields,
dsSession: dsSessionP,
});
_ds.viewRangeExpResults = ret.fields;
if (glb.DEBUG_MODE) {
if (!_ds.debug) {
_ds.debug = {};
}
if (!_ds.debug.viewRangeExp) {
_ds.debug.viewRangeExp = [];
}
_ds.debug.viewRangeExp.push(ret);
}
return ret.result;
} else return false;
};
func.datasource.validate_viewLocate = async function (SESSION_ID, viewLocateExpP, dsSessionP, rowIdP, sourceP) {
if (viewLocateExpP && _ds) {
var ret = func.expression.remove_quotes(await func.expression.get(SESSION_ID, viewLocateExpP, dsSessionP, 'locate', rowIdP));
ret.result = func.expression.remove_quotes(ret.result);
func.utils.debug.log(SESSION_ID, _ds.prog_id, {
module: _ds.viewModule,
action: 'locate Exp',
prop: sourceP,
details: viewLocateExpP,
result: ret.result,
error: ret.error,
source: _ds.viewSourceDesc,
json: ret.explain,
fields: ret.fields,
dsSession: dsSessionP,
});
_ds.viewLocateExpResults = ret.fields;
return ret.result;
} else return false;
};
func.datasource.get_viewFields_for_update_function = function (SESSION_ID, calling_trigger_prop, na, dsSessionP) {
var viewFields = [];
var exp = calling_trigger_prop?.data?.name?.value;
if (!exp) {
return viewFields;
}
const trim_wrapping_braces = function (value) {
const trimmed = value.trim();
if (trimmed.startsWith('{') && trimmed.endsWith('}')) {
return trimmed.substring(1, trimmed.length - 1);
}
return trimmed;
};
const strip_wrapping_quotes = function (value) {
const trimmed = value.trim();
const first = trimmed.substring(0, 1);
const last = trimmed.substring(trimmed.length - 1);
if ((first === "'" || first === '"' || first === '`') && last === first) {
return trimmed.substring(1, trimmed.length - 1);
}
return trimmed;
};
const split_top_level = function (value) {
const parts = [];
let current = '';
let quote = null;
let escape = false;
let paren_depth = 0;
let bracket_depth = 0;
let brace_depth = 0;
for (let index = 0; index < value.length; index++) {
const char = value[index];
if (escape) {
current += char;
escape = false;
continue;
}
if (quote) {
current += char;
if (char === '\\') {
escape = true;
} else if (char === quote) {
quote = null;
}
continue;
}
if (char === "'" || char === '"' || char === '`') {
quote = char;
current += char;
continue;
}
if (char === '(') paren_depth++;
if (char === ')') paren_depth = Math.max(0, paren_depth - 1);
if (char === '[') bracket_depth++;
if (char === ']') bracket_depth = Math.max(0, bracket_depth - 1);
if (char === '{') brace_depth++;
if (char === '}') brace_depth = Math.max(0, brace_depth - 1);
if ((char === ',' || char === ';') && !paren_depth && !bracket_depth && !brace_depth) {
if (current.trim()) {
parts.push(current.trim());
}
current = '';
continue;
}
current += char;
}
if (current.trim()) {
parts.push(current.trim());
}
return parts;
};
const find_top_level_colon = function (value) {
let quote = null;
let escape = false;
let paren_depth = 0;
let bracket_depth = 0;
let brace_depth = 0;
for (let index = 0; index < value.length; index++) {
const char = value[index];
if (escape) {
escape = false;
continue;
}
if (quote) {
if (char === '\\') {
escape = true;
} else if (char === quote) {
quote = null;
}
continue;
}
if (char === "'" || char === '"' || char === '`') {
quote = char;
continue;
}
if (char === '(') paren_depth++;
if (char === ')') paren_depth = Math.max(0, paren_depth - 1);
if (char === '[') bracket_depth++;
if (char === ']') bracket_depth = Math.max(0, bracket_depth - 1);
if (char === '{') brace_depth++;
if (char === '}') brace_depth = Math.max(0, brace_depth - 1);
if (char === ':' && !paren_depth && !bracket_depth && !brace_depth) {
return index;
}
}
return -1;
};
exp = trim_wrapping_braces(exp.replace(/\n/gi, ''));
const exp_arr = split_top_level(exp);
for (let index = 0; index < exp_arr.length; index++) {
const segment = exp_arr[index];
const pos = find_top_level_colon(segment);
if (pos === -1) {
continue;
}
let id = strip_wrapping_quotes(segment.substring(0, pos));
const val = segment.substring(pos + 1).trim();
if (id.substring(0, 1) === '@') {
id = id.substring(1);
}
if (!id || !val) {
continue;
}
viewFields.push({
id,
val,
});
}
return viewFields;
};
func.datasource.get_value = async function (SESSION_ID, fieldIdP, dsSessionP, rowIdP, org_dsSessionP) {
const normalize_field_id = function (field_id) {
if (typeof field_id === 'string') {
return field_id;
}
if (typeof field_id?.field_id === 'string') {
return field_id.field_id;
}
if (typeof field_id?.id === 'string') {
return field_id.id;
}
if (typeof field_id === 'number' || typeof field_id === 'boolean' || typeof field_id === 'bigint') {
return field_id.toString();
}
const coerced = field_id?.toString?.();
if (typeof coerced === 'string' && coerced && coerced !== '[object Object]') {
return coerced;
}
return null;
};
const return_missing_value = function (field_id, currentRecordId = null) {
return {
ret: {
value: undefined,
type: 'string',
prop: null,
},
dsSessionP,
fieldIdP: field_id,
currentRecordId,
found: false,
};
};
let row_lookup_issue = null;
const remember_row_lookup_issue = function (err, field_id, record_id) {
if (row_lookup_issue) return;
row_lookup_issue = {
err,
details: {
dsSessionP,
field_id,
record_id,
},
};
};
const report_row_lookup_issue = async function (lookup_ret) {
if (row_lookup_issue && !lookup_ret?.found) {
await func.utils.report_issue(SESSION_ID, {
code: 'RUN_MSG_DSC_060',
source: 'Datasource get value',
message: 'Datasource row lookup failed',
type: 'W',
err: row_lookup_issue.err,
details: row_lookup_issue.details,
skip_log: false,
});
}
return lookup_ret;
};
const return_value = async (field_id, value) => {
const _progFields = await func.datasource.get_progFields(SESSION_ID, dsSessionP);
let view_field_obj = func.common.find_item_by_key(_progFields, 'field_id', field_id);
var fieldType = view_field_obj?.props?.fieldType || 'string';
var fieldProp = view_field_obj?.props;
let table_field_obj;
if (view_field_obj?.data?.type === 'table') {
if (!_ds._dataSourceTableId) {
return func.utils.debug_report(SESSION_ID, 'Datasource', `Table type defined without dataSourceTableId deceleration`, 'E');
}
let table_obj = await func.utils.FILES_OBJ.get(SESSION_ID, _ds._dataSourceTableId);
if (!table_obj) {
return func.utils.debug_report(SESSION_ID, 'Datasource', `dataSourceTableId reference error: ` + _ds._dataSourceTableId, 'E');
}
table_field_obj = func.common.find_item_by_key(table_obj.tableFields, 'field_id', field_id);
fieldType = table_field_obj.props?.fieldType;
fieldProp = table_field_obj.props;
}
let ret = {
value: await func.common.get_cast_val(SESSION_ID, `datasource get value ${_ds.tree_obj.menuName}`, fieldIdP, fieldType, value, null),
type: fieldType,
prop: fieldProp,
};
if (ret.value && typeof ret.value === 'string' && ret.type !== 'exp') {
if (/"/.test(ret.value) && ret.value.indexOf('\\') === -1) ret.value = ret.value.replace(/"/g, '"');
}
return {
ret,
dsSessionP,
fieldIdP: field_id,
currentRecordId: _ds.currentRecordId,
found: typeof value !== 'undefined',
};
};
const return_dynamic_value = async (field_id, value) => {
let view_field_obj = _ds.dynamic_fields[field_id];
var fieldType = view_field_obj?.props?.fieldType || 'string';
var fieldProp = view_field_obj?.props;
let ret = {
value: view_field_obj.value,
type: fieldType,
prop: fieldProp,
};
if (ret.value && typeof ret.value === 'string' && ret.type !== 'exp') {
if (/"/.test(ret.value) && ret.value.indexOf('\\') === -1) ret.value = ret.value.replace(/"/g, '"');
}
return {
ret,
dsSessionP,
fieldIdP: field_id,
currentRecordId: _ds.currentRecordId,
found: typeof value !== 'undefined',
};
};
const return_value_parameters = async (field_id, value) => {
let ret = {
value: await func.common.get_cast_val(SESSION_ID, 'datasource get value', fieldIdP, value.type, value.value, null),
type: value.type,
prop: null,
};
if (ret.value && typeof ret.value === 'string' && ret.type !== 'exp') {
if (!ret.value.includes('<svg xmlns=') && /"/.test(ret.value) && ret.value.indexOf('\\') === -1) ret.value = ret.value.replace(/"/g, '"');
// if (/"/.test(ret.value) && ret.value.indexOf("\\") === -1)
// ret.value = ret.value.replace(/"/g, '\\"');
}
return {
ret,
dsSessionP,
fieldIdP: field_id,
currentRecordId: _ds.currentRecordId,
found: typeof value !== 'undefined',
};
};
const return_value_system = async (field_id, value) => {
let fieldType = glb.GLOBAL_VARS[field_id].type;
let fieldProp = null;
let ret = {
value: await func.common.get_cast_val(SESSION_ID, `datasource get value ${_ds.tree_obj.menuName}`, field_id, fieldType, value, null),
type: fieldType,
prop: fieldProp,
};
return {
ret,
dsSessionP,
fieldIdP: field_id,
currentRecordId: _ds.currentRecordId,
found: typeof value !== 'undefined',
};
};
const search_in_parameters = async (field_id) => {
if (typeof _ds?.in_parameters?.[field_id]?.value !== 'undefined') {
let ret = await return_value_parameters(field_id, _ds.in_parameters[field_id]);
return ret;
}
if (typeof _ds.parentDataSourceNo !== 'undefined') {
var org_dsSession = org_dsSessionP;
if (!org_dsSessionP) org_dsSession = dsSessionP;
let parent_record_id = recordId;
if (parent_record_id && parent_record_id !== 'newRecord') {
const parent_ds = SESSION_OBJ[SESSION_ID].DS_GLB[_ds.parentDataSourceNo];
const parent_has_record = !!parent_ds?.data_feed?.rows?.some((row) => row?._ROWID === parent_record_id);
if (parent_has_record) {
org_dsSession = null;
} else {
parent_record_id = null;
}
}
return await func.datasource.get_value(SESSION_ID, fieldIdP, _ds.parentDataSourceNo, parent_record_id, org_dsSession);
}
return await return_value(field_id);
};
if (typeof glb.GLOBAL_VARS === 'undefined') {
glb.GLOBAL_VARS = (await func.common.get_module(SESSION_ID, 'xuda-system-globals-module.mjs')).system_globals;
}
var _ds = SESSION_OBJ[SESSION_ID].DS_GLB[dsSessionP];
if (!_ds) {
if (dsSessionP > 0) {
// let prev_ds = dsSessionP - 1;
return await func.datasource.get_value(SESSION_ID, fieldIdP, dsSessionP - 1, rowIdP, org_dsSessionP);
}
const normalized_missing_field = normalize_field_id(fieldIdP);
if (normalized_missing_field === null) {
return return_missing_value(fieldIdP);
}
fieldIdP = normalized_missing_field;
return await return_value(fieldIdP);
} //console.error("error: datasource not exist: " + dsSessionP);
const normalized_field_id = normalize_field_id(fieldIdP);
if (normalized_field_id === null) {
func.utils.debug_report(SESSION_ID, 'Datasource get value', `Invalid field id type: ${typeof fieldIdP}`, 'W');
return return_missing_value(fieldIdP, _ds.currentRecordId);
}
fieldIdP = normalized_field_id;
let recordId = rowIdP;
if (!recordId) {
recordId = _ds.currentRecordId;
}
if (glb.GLOBAL_VARS[fieldIdP]) {
if (!_ds.data_system) {
_ds.data_system = {};
}
if (dsSessionP > 0) {
_ds.data_system['SYS_STR_ACTIVE_ROW_ID'] = _ds.currentRecordId;
_ds.data_system['SYS_STR_PROG_DS_SESSION'] = dsSessionP;
}
// set system time
if (glb.SYS_DATE_ARR.includes(fieldIdP)) {
var _ds_0 = SESSION_OBJ[SESSION_ID].DS_GLB[0];
if (_ds_0) {
if (!_ds_0.data_system) {
_ds_0.data_system = {};
}
const ts = await func.utils.get_dateTime(SESSION_ID, 'SYS_DATE_VALUE');
for (const val of glb.SYS_DATE_ARR) {
_ds_0.data_system[val] = await func.utils.get_dateTime(SESSION_ID, val, ts);
}
}
}
// await func.datasource.set_system_vars(SESSION_ID, dsSessionP);
if (typeof _ds?.data_system?.[fieldIdP] !== 'undefined') {
return await return_value_system(fieldIdP, _ds?.data_system?.[fieldIdP]);
}
return await search_in_parameters(fieldIdP);
}
// if (glb.PROTECTED_VARS[fieldIdP]) {
// switch (fieldIdP) {
// case "_ROW_NO": {
// let row_no = Object.keys(_ds?.data_feed?.rows).indexOf(recordId) || 0;
// return await return_value(fieldIdP, row_no);
// }
// default:
// break;
// }
// }
if (!_ds.data_feed) {
return await search_in_parameters(fieldIdP);
}
var _field_id = fieldIdP;
if (fieldIdP.substr(0, 1) === '_') {
if (_ds.alias) _field_id = _ds.alias[fieldIdP];
}
if (typeof _ds?.dynamic_fields?.[_field_id] !== 'undefined') {
return await return_dynamic_value(_field_id, _ds.dynamic_fields[_field_id]);
}
if (!org_dsSessionP && recordId) {
try {
const row_idx = func.common.find_ROWID_idx(_ds, recordId);
if (typeof _ds.data_feed?.rows?.[row_idx]?.[_field_id] !== 'undefined') {
if (Object.keys(_ds.data_feed?.rows?.[row_idx] || {})?.includes(_field_id)) {
return await return_value(_field_id, _ds.data_feed.rows[row_idx][_field_id]);
}
if (Object.keys(_ds?.dynamic_fields || {})?.includes(_field_id)) {
return await return_dynamic_value(_field_id, _ds.dynamic_fields[_field_id]);
}
}
} catch (err) {
remember_row_lookup_issue(err, _field_id, recordId);
}
}
if (_ds.currentRecordId) {
try {
const row_idx = func.common.find_ROWID_idx(_ds, _ds.currentRecordId);
if (typeof _ds.data_feed?.rows?.[row_idx]?.[_field_id] !== 'undefined') {
return await return_value(_field_id, _ds.data_feed.rows[row_idx][_field_id]);
}
} catch (error) {
remember_row_lookup_issue(error, _field_id, _ds.currentRecordId);
}
}
return await report_row_lookup_issue(await search_in_parameters(fieldIdP));
};
func.datasource.find_event_dataSource = async function (SESSION_ID, eventIdP, dsSessionP) {
const _ds = SESSION_OBJ[SESSION_ID].DS_GLB[dsSessionP];
var ret;
//============================
// Search in given datasource
//============================
if (_ds?.prog_id) {
let view_ret = await func.utils.VIEWS_OBJ.get(SESSION_ID, _ds.prog_id);
if (view_ret?.progEvents && func.common.find_item_by_key(view_ret.progEvents, 'event_name', eventIdP)) {
ret = dsSessionP;
if (_ds.callingSource === 'system') ret = 0;
if (_ds.callingSource === 'program' || !_ds.callingSource) ret = dsSessionP;
// >>> found value
return ret;
}
}
if (!ret && dsSessionP !== 0) {
//=======================================
// continue Search in parent datasource
//=======================================
if (_ds && _ds.parentDataSourceNo && Number(_ds.parentDataSourceNo) > 0 && Number(_ds.parentDataSourceNo) < dsSessionP) {
return await func.datasource.find_event_dataSource(SESSION_ID, eventIdP, _ds.parentDataSourceNo);
} else {
//========================================
// continue Search in global system
//=========================================
if (!ret) return await func.datasource.find_event_dataSource(SESSION_ID, eventIdP, 0);
}
}
};
func.datasource.reset_jobs = function (SESSION_ID, dsSessionP, sourceP, errP) {
for (const [key, val] of Object.entries(SESSION_OBJ[SESSION_ID].WORKER_OBJ.jobs)) {
if (val.dsSessionP === dsSessionP) {
func.events.delete_job(SESSION_ID, val.job_num);
break;
}
}
func.utils.debug_report(SESSION_ID, sourceP + 'Missing datasource: ' + dsSessionP, errP, 'W', null);
};
// func.datasource.set_system_vars = async function (SESSION_ID, dsSessionP) {
// var _ds = SESSION_OBJ[SESSION_ID].DS_GLB[dsSessionP];
// if (!_ds) return;
// if (!_ds.data_system) {
// _ds.data_system = {};
// }
// if (dsSessionP > 0) {
// _ds.data_system["SYS_STR_ACTIVE_ROW_ID"] = _ds.currentRecordId;
// _ds.data_system["SYS_GLOBAL_STR_PROG_DS_SESSION"] = dsSessionP;
// }
// // _ds.data_system["SYS_OBJ_DS_INFO"] = {
// // rows: _ds.rows_found,
// // first_row_id: _ds.firstRecordId,
// // last_row_id: _ds.lastRecordId,
// // query_from_segments_json: _ds.filter_from,
// // query_to_segments_json: _ds.filter_from,
// // locate_query_from_segments_json: _ds.locate_from,
// // locate_query_to_segments_json: _ds.locate_to,
// // first_row_segments_json: _ds.firstRecordKey_object,
// // last_row_segments_json: _ds.lastRecordKey_object,
// // rowid: _ds.currentRecordId,
// // };
// // if (!_ds.data_system["SYS_OBJ_DS_DATA"] && dsSessionP == 0) {
// // _ds.data_system["SYS_OBJ_DS_DATA"] = {
// // value: _.cloneDeep(_ds.data_system),
// // };
// // }
// var _ds_0 = SESSION_OBJ[SESSION_ID].DS_GLB[0];
// if (!_ds_0) return;
// if (!_ds_0.data_system) {
// _ds_0.data_system = {};
// }
// const ts = await func.utils.get_dateTime(SESSION_ID, "SYS_DATE_VALUE");
// for (const val of glb.SYS_DATE_ARR) {
// _ds_0.data_system[val] = await func.utils.get_dateTime(SESSION_ID, val, ts);
// }
// };
func.datasource.get_currentRecordId = function (SESSION_ID, dsSessionP, from_datasourceP) {
var _ds = SESSION_OBJ[SESSION_ID].DS_GLB[dsSessionP];
if (_ds._dataSourceTableId !== '') {
var firstRecordId = _ds.firstRecordId; // returns the value of the fist row
var currentRecordId = _ds.currentRecordId; // record id that exist on ds after entering the form
var locatedRecordId = _ds.locatedRecordId; // record id located by ds
if (!currentRecordId || from_datasourceP) {
// first time without locate
if (!locatedRecordId) currentRecordId = firstRecordId;
// set first row when no rec located
else currentRecordId = locatedRecordId; // set with located row
}
} else currentRecordId = 'newRecord'; // create mode
return currentRecordId;
};
func.datasource.interval = function (session_id, dsSessionP, typeP) {
var SESSION_ID = session_id;
var interval = [];
var _ds = SESSION_OBJ[SESSION_ID].DS_GLB[dsSessionP];
// var ds = _ds;
// var v = ds.v;
// var arr = func.datasource.get_event_interval_arr(
// SESSION_ID,
// dsSessionP,
// typeP
// );
var arr = _ds[typeP];
var fx = {
init: async function () {
if (arr?.length) {
for (let val of arr) {
var event_id = val[0];
var interval_rate = val[1];
var condition = val[2];
interval.push(
setInterval(async function () {
if (!SESSION_OBJ[SESSION_ID]) return;
var event_count = await func.datasource.get_view_events_count(SESSION_ID, dsSessionP, typeP, event_id);
if (!event_count) {
fx.clear();
return;
}
var event_condition = await func.expression.get(SESSION_ID, condition, dsSessionP, 'condition'); // execute condition;
if (condition && !event_condition.result) return;
const e = await func.datasource.execute_view_events(SESSION_ID, dsSessionP, typeP, event_id);
// for (const [key, val] of Object.entries(e)) {
// val.done = false;
// }
}, Number(interval_rate) * 1000),
);
}
} else {
await fx.clear();
return;
}
},
clear: function () {
if (DATASOURCE_INTERVALS[session_id]) delete DATASOURCE_INTERVALS[session_id][dsSessionP];
for (const [key, val] of Object.entries(interval)) {
clearInterval(val);
}
},
};
return fx;
};
// func.datasource.server_cron = function (session_id) {
// var SESSION_ID = session_id;
// var jobs = [];
// var arr = func.datasource.get_event_interval_arr(SESSION_ID, 0, 'server_cron');
// const schedule = require('node-schedule');
// if (!arr.length) {
// return jobs;
// }
// for (let val of arr) {
// var event_id = val[0];
// var cron_prop = val[1];
// var condition = val[2];
// jobs.push(
// schedule.scheduleJob(cron_prop, async function () {
// if (!SESSION_OBJ[SESSION_ID]) return;
// var event_count = await func.datasource.get_view_events_count(SESSION_ID, 0, 'server_cron', event_id);
// if (!event_count) {
// fx.clear();
// return;
// }
// var event_condition = await func.expression.get(SESSION_ID, condition, 0, 'condition'); // execute condition;
// if (condition && !event_condition.result) return;
// if (event_count) {
// const e = await func.datasource.execute_view_events(SESSION_ID, 0, 'server_cron', event_id);
// // for (const [key, val] of Object.entries(e)) {
// // val.done = false;
// // }
// }
// }),
// );
// }
// return jobs;
// };
func.datasource.get_viewLoops = async function (SESSION_ID, dataSourceSession, data, batch_source, default_limit) {
var _ds = SESSION_OBJ[SESSION_ID].DS_GLB[dataSourceSession];
var args = _ds.args;
// var v = _ds.v;
var ret = default_limit;
if (batch_source === 'db_data') ret = data.rows.length;
if (batch_source === 'array' || batch_source === 'csv') ret = data.length;
if (batch_source === 'json') ret = Object.keys(data).length;
if (_ds.progDataSource?.dataSourceLimit) {
if (batch_source !== 'no_data' && Number(_ds.progDataSource?.dataSourceLimit) < ret) {
ret = Number(_ds.progDataSource.dataSourceLimit);
}
if (!batch_source) ret = Number(_ds.progDataSource.dataSourceLimit);
}
if (prog_obj.progDataSource?.dataSourceLoopExp) {
var n = (await func.expression.get(SESSION_ID, _ds.v.viewLoopsExp, dataSourceSession, 'view_loop', args.rowIdP)).result;
if (batch_source !== 'no_data' && n < ret) ret = n;
if (!batch_source) ret = n;
}
return ret;
};
func.datasource.set_VIEW_data = async function (SESSION_ID, args, _ds) {
_ds.v = {
viewFieldsProp: {},
segFrom: [],
segTo: [],
segLocateFrom: [],
segLocateTo: [],
viewModule: 'adapter',
};
_ds.viewEventExec_arr = {};
var view = structuredClone(await func.utils.VIEWS_OBJ.get(SESSION_ID, args.prog_id));
// var view = klona.klona(await func.utils.VIEWS_OBJ.get(SESSION_ID, args.prog_id));
_ds.v.dataSourceSrcType = view.dataSourceSrcType;
if (view.progDataSource) _ds.progDataSource = view.progDataSource;
_ds.v.viewIndex = view?.progDataSource?.dataSourceIndexesObj;
let tree_ret = await func.utils.TREE_OBJ.get(SESSION_ID, args.prog_id);
_ds.v.viewSourceDesc = tree_ret.menuName;
if (!_ds.v.viewSourceDesc && tree_ret) {
_ds.v.viewSourceDesc = tree_ret.menuName;
}
if (glb.FUNCTION_NODES_ARR.includes(tree_ret.menuType)) {
_ds.v.viewModule = 'function';
}
_ds.v.viewSourceProp = tree_ret.menuType;
if (view.progEvents) _ds.v.progEvents = view.progEvents;
_ds._progDataSource_fields = [];
if (_ds.progDataSource) {
let ret = func.expression.parse(JSON.stringify(_ds.progDataSource));
_ds._progDataSource_fields = ret.map((e) => {
if (e.fieldId) return e.fieldId;
});
}
};
func.datasource.get_cast_val = async function (SESSION_ID, source, dsSession, valP, typeP, req, error) {
var prog_id, prog_name;
var _session = SESSION_OBJ[SESSION_ID];
var _ds = _session.DS_GLB[dsSession];
prog_id = _ds.prog_id;
prog_name = await func.utils.TREE_OBJ.get(SESSION_ID, _ds.prog_id).menuName;
const prog_info = prog_id ? ` (prog: ${prog_id} ${prog_name})` : '';
const report_conversion_error = function (res) {
var msg = `error converting from ${valP} to ${typeP}`;
if (error) {
return func.utils.debug_report(SESSION_ID, msg, '', 'W');
}
func.utils.debug_report(SESSION_ID, msg + ' ' + (source.charAt(0).toUpperCase() + source.slice(1).toLowerCase()) + prog_info, '', 'E');
};
const report_conversion_warn = function (res) {
// number/boolean/bigint -> string is lossless; skip the noise (it routes as an "Unhandled Runtime Error")
if (typeP === 'string' && (typeof valP === 'number' || typeof valP === 'boolean' || typeof valP === 'bigint')) return;
var msg = `type mismatch auto conversion from value ${valP} to ${typeP}`;
func.utils.debug_report(SESSION_ID, msg + ' ' + (source.charAt(0).toUpperCase() + source.slice(1).toLowerCase()) + prog_info, '', 'W');
};
// var ret = valP;
if (error) {
return report_conversion_error();
}
const module = await func.common.get_module(SESSION_ID, 'xuda-get-cast-util-module.mjs');
return module.cast(typeP, valP, report_conversion_error, report_conversion_warn);
};
func.datasource.get_field_init_triggers_to_run = function (SESSION_ID, dataSourceSession, pre_init_fieldsP) {
var _ds = SESSION_OBJ[SESSION_ID].DS_GLB[dataSourceSession];
if (!_ds) return;
return []; // inactive 021617
}; // inactive temporary, design to execute init workflow when querying large datasets
func.datasource.get_pre_init_fields = function (SESSION_ID, dsSessionP, viewRangeExpP, viewSortExpP, viewGroupByExpP, viewLocateExpP) {
var ret = [];
return; // inactive 021617
}; // temporary inactive, init preformed on the first round
func.datasource.add_dynamic_field_to_ds = function (SESSION_ID, dsSessionP, key, val) {
var _ds = SESSION_OBJ[SESSION_ID].DS_GLB[dsSessionP];
if (!_ds.dynamic_fields) {
_ds.dynamic_fields = {};
}
const toType = function (obj) {
return {}.toString
.call(obj)
.match(/\s([a-zA-Z]+)/)[1]
.toLowerCase();
};
_ds.dynamic_fields[key] = {
id: crypto.randomUUID(),
data: {
type: 'virtual',
field_id: key,
},
props: {
fieldType: typeof val !== 'undefined' ? toType(val) : 'string',
},
value: val,
};
};
func.datasource.get_progFields = async function (SESSION_ID, dsSessionP) {
var _ds = SESSION_OBJ[SESSION_ID].DS_GLB[dsSessionP];
const _view_obj = await func.utils.VIEWS_OBJ.get(SESSION_ID, _ds.prog_id);
return _view_obj.progFields;
};
func.datasource.update_changes_for_out_parameter = async function (SESSION_ID, dsSessionP, calling_dsP, avoid_refreshP) {
let _session = SESSION_OBJ[SESSION_ID];
let _ds = _session.DS_GLB[dsSessionP];
const _calling_ds = _session.DS_GLB[calling_dsP];
const avoid_refresh = avoid_refreshP === true || avoid_refreshP?.avoid_refresh === true;
const refresh_options = avoid_refresh ? { avoid_refresh: true, refresh_attributes: true } : false;
const get_row_idx_safe = function (target_ds, row_id) {
if (!target_ds || !row_id) {
return null;
}
try {
return func.common.find_ROWID_idx(target_ds, row_id);
} catch (_error) {
return null;
}
};
if (!_ds?.PARAM_OUT_INFO || !calling_dsP || !_calling_ds) {
return;
}
let data = {};
for await (const [key, val] of Object.entries(_ds.PARAM_OUT_INFO)) {
if (val.prop === 'out') {
try {
const current_row_idx = get_row_idx_safe(_ds, _ds.currentRecordId);
const dataset_row_idx = get_row_idx_safe(_ds, 'dataset');
let result;
if (
current_row_idx !== null &&
typeof _ds?.data_feed?.rows?.[current_row_idx]?.[val.fieldId] !== 'undefined'
) {
result = _ds.data_feed.rows[current_row_idx][val.fieldId];
} else if (
dataset_row_idx !== null &&
typeof _ds?.data_feed?.rows?.[dataset_row_idx]?.[val.fieldId] !== 'undefined'
) {
result = _ds.data_feed.rows[dataset_row_idx][val.fieldId];
}
if (typeof result !== 'undefined') {
data[val.details] = result;
}
} catch (err) {
console.error(err);
}
}
}
if (!xu_isEmpty(data)) {
let datasource_changes = {
[calling_dsP]: { [_calling_ds.currentRecordId]: data },
};
try {
if (avoid_refresh) {
globalThis.__XUDA_RT_TRACE && console.log('[xuda-runtime] out_parameter_update ' +
JSON.stringify({
avoidRefresh: true,
source_ds: dsSessionP,
target_ds: calling_dsP,
fields: Object.keys(data),
}),
);
}
} catch (e) {}
await func.datasource.update(SESSION_ID, datasource_changes, null, refresh_options);
}
};
func.datasource.set_outputField = async function (SESSION_ID, dsSessionP, result, args, avoid_refreshP) {
var _session = SESSION_OBJ[SESSION_ID];
// let _ds = _session.DS_GLB[dsSessionP];
const avoid_refresh = avoid_refreshP === true || avoid_refreshP?.avoid_refresh === true;
const refresh_options = avoid_refresh ? { avoid_refresh: true, refresh_attributes: true } : false;
const output_field = await func.datasource.get_args_property_value(SESSION_ID, dsSessionP, args, 'outputField');
if (output_field) {
let datasource_changes = {};
let ret_get_value = await func.datasource.get_value(SESSION_ID, output_field, dsSessionP);
if (ret_get_value.found) {
let _ds = _session.DS_GLB[ret_get_value.dsSessionP];
if (!datasource_changes[_ds.dsSession]) {
datasource_changes[_ds.dsSession] = {};
}
if (!datasource_changes[_ds.dsSession][ret_get_value.currentRecordId]) {
datasource_changes[_ds.dsSession][ret_get_value.currentRecordId] = {};
}
datasource_changes[_ds.dsSession][ret_get_value.currentRecordId][output_field] = result;
try {
if (avoid_refresh) {
globalThis.__XUDA_RT_TRACE && console.log('[xuda-runtime] output_field_update ' +
JSON.stringify({
avoidRefresh: true,
target_ds: _ds.dsSession,
field: output_field,
}),
);
}
} catch (e) {}
await func.datasource.update(SESSION_ID, datasource_changes, null, refresh_options);
}
}
};
func.datasource.get_args_property_value = async function (SESSION_ID, dsSession, args, prop_name) {
// Headless executions (cron/emitter workers) have no calling trigger — _prop is
// undefined there and the unguarded read crashed every server-side get_data run.
let _prop = args?.calling_trigger_prop?.data?.name;
let _value = _prop?.[prop_name];
if (_prop?.[`xu-exp:${prop_name}`]) {
_value = (await func.expression.get(SESSION_ID, _prop[`xu-exp:${prop_name}`], dsSession, `${prop_name} expression`)).result;
}
return _value;
};
func.utils = {};
func.utils.debug = {};
func.utils.debug.watch = async function (SESSION_ID, key, type, info, result, condition, not_executed) {
if (!glb.DEBUG_MODE) return;
const debug_utils = await func.common.get_module(SESSION_ID, 'xuda-debug-utils-module.mjs');
debug_utils.watch(SESSION_ID, key, type, info, result, condition, not_executed);
};
func.utils.debug.log = async function (SESSION_ID, node_idP, jsonP) {
if (typeof IS_PROCESS_SERVER !== 'undefined') return;
if (!glb.DEBUG_MODE && !glb.TRACE_ON) return;
const debug_utils = await func.common.get_module(SESSION_ID, 'xuda-debug-utils-module.mjs');
debug_utils.log(SESSION_ID, node_idP, jsonP);
// console.error(jsonP);
};
func.utils.debug.write = async function (SESSION_ID, logP, callbackP) {
if (!glb.DEBUG_MODE) return;
const debug_utils = await func.common.get_module(SESSION_ID, 'xuda-debug-utils-module.mjs');
debug_utils.write(SESSION_ID, logP, callbackP);
};
func.utils.debug.read_command = async function (data) {
if (!glb.DEBUG_MODE) return;
const debug_utils = await func.common.get_module(SESSION_ID, 'xuda-debug-utils-module.mjs');
debug_utils.read_command(data);
};
func.utils.DOCS_OBJ = {};
func.utils.DOCS_OBJ.get = async function (SESSION_ID, idP) {
if (!idP || idP === '0') return;
const normalize_runtime_doc = function (doc) {
if (!doc || !func.runtime.program?.normalize_doc_for_runtime) {
return doc;
}
return func.runtime.program.normalize_doc_for_runtime(doc);
};
var _session = SESSION_OBJ[SESSION_ID];
const _app_id = _session.app_id;
if (!DOCS_OBJ[_app_id]) {
DOCS_OBJ[_app_id] = {};
}
if (DOCS_OBJ[_app_id][idP]) {
return DOCS_OBJ[_app_id][idP];
}
if (_session.project_data) {
if (idP === 'system') {
if (_session.project_data.globals) {
DOCS_OBJ[_app_id][idP] = _session.project_data.globals;
} else {
DOCS_OBJ[_app_id][idP] = {};
}
return DOCS_OBJ[_app_id][idP];
}
let val = _session.project_data?.programs?.[idP];
if (val) {
DOCS_OBJ[_app_id][idP] = normalize_runtime_doc(val);
return DOCS_OBJ[_app_id][idP];
}
}
if (typeof _session.SLIM_BUNDLE === 'undefined' || !_session.SLIM_BUNDLE) {
const module = await func.common.get_module(SESSION_ID, `xuda-progs-loader-module.mjs`);
if (idP !== 'system') {
DOCS_OBJ[_app_id][idP] = normalize_runtime_doc(await module.DOCS_OBJ_get(SESSION_ID, idP));
if (DOCS_OBJ[_app_id][idP] && xu_isEmpty(DOCS_OBJ[_app_id][idP])) {
await func.utils.remove_cached_objects(SESSION_ID);
delete DOCS_OBJ[_app_id][idP];
}
return DOCS_OBJ[_app_id][idP];
}
DOCS_OBJ[_app_id][idP] = await module.DOCS_OBJ_get(SESSION_ID, 'global_' + (APP_OBJ[_app_id].app_replicate || _app_id));
if (APP_OBJ[_app_id].app_imported_projects) {
for await (const imported_app_id of APP_OBJ[_app_id].app_imported_projects) {
var view_ret = await module.DOCS_OBJ_get(SESSION_ID, 'global_' + imported_app_id);
DOCS_OBJ[_app_id][idP] = Object.assign(DOCS_OBJ[_app_id][idP], view_ret);
}
}
return DOCS_OBJ[_app_id][idP];
}
console.error(`${idP} not found`);
};
func.utils.FILES_OBJ = {};
func.utils.FILES_OBJ.get = async function (SESSION_ID, idP) {
if (!idP) return;
return await func.utils.DOCS_OBJ.get(SESSION_ID, idP);
};
func.utils.VIEWS_OBJ = {};
func.utils.VIEWS_OBJ.get = async function (SESSION_ID, idP) {
if (!idP) return;
return await func.utils.DOCS_OBJ.get(SESSION_ID, idP);
};
func.utils.TREE_OBJ = {};
func.utils.TREE_OBJ.get = async function (SESSION_ID, idP) {
if (!idP) return;
var ret = await func.utils.DOCS_OBJ.get(SESSION_ID, idP);
if (ret?.properties) {
ret.properties.id = idP;
}
return ret.properties;
};
func.utils.get_dateTime = async function (SESSION_ID, typeP, dateP) {
// const getUTC = function (ts) {
// var date = new Date(ts);
// var utc_date = new Date(
// date.getUTCFullYear(),
// date.getUTCMonth(),
// date.getUTCDate(),
// date.getUTCHours(),
// date.getUTCMinutes(),
// date.getUTCSeconds(),
// date.getUTCMilliseconds()
// );
// return utc_date;
// };
const get_server_ts = async function () {
var _session = SESSION_OBJ[SESSION_ID];
const response = await fetch(`https://${_session.domain}/cpi/get_utc_ts`, {
method: 'POST',
headers: {
Accept: 'application/json',
'Content-Type': 'application/json',
},
body: JSON.stringify({}),
});
const json = await response.json();
return json.data;
// }
};
function getWeekNumber(d) {
// Copy date so don't modify original
d = new Date(+d);
d.setHours(0, 0, 0);
// Set to nearest Thursday: current date + 4 - current day number
// Make Sunday's day number 7
d.setDate(d.getDate() + 4 - (d.getDay() || 7));
// Get first day of year
var yearStart = new Date(d.getFullYear(), 0, 1);
// Calculate full weeks to nearest Thursday
var weekNo = Math.ceil(((d - yearStart) / 86400000 + 1) / 7);
// Return array of year and week number
return weekNo;
}
var sysDate = new Date(dateP); // getUTC(dateP); //new Date(dateP);
if (!dateP) {
let ts = await get_server_ts();
sysDate = new Date(ts);
}
var day = String(sysDate.getDate()).padStart(2, '0');
var month = String(sysDate.getMonth() + 1).padStart(2, '0');
var year = sysDate.getFullYear();
var week = String(getWeekNumber(sysDate)).padStart(2, '0');
var hour = String(sysDate.getHours()).padStart(2, '0');
var minute = String(sysDate.getMinutes()).padStart(2, '0');
var second = String(sysDate.getSeconds()).padStart(2, '0');
if (typeP === 'SYS_DATE') return year + '-' + month + '-' + day;
if (typeP === 'SYS_DATE_TIME') return year + '-' + month + '-' + day + 'T' + hour + ':' + minute;
if (typeP === 'SYS_DATE_VALUE') return sysDate.valueOf();
if (typeP === 'SYS_DATE_WEEK_YEAR') return year + 'W' + week;
if (typeP === 'SYS_DATE_MONTH_YEAR') return year + '-' + month;
if (typeP === 'SYS_TIME') return hour + ':' + minute + ':' + second;
if (typeP === 'SYS_TIME_SHORT') return hour + ':' + minute;
};
func.utils.is_onscreen_event = function (functionP) {
const arr = ['invoke_action', 'cache_refresh', 'call_popover', 'call_modal', 'call_page', 'loader_on', 'loader_off', 'emit_event'];
return arr.includes(functionP);
};
func.utils.get_screen_obj = async function (SESSION_ID, id) {
const prog_obj = await func.utils.VIEWS_OBJ.get(SESSION_ID, id);
if (!prog_obj) return console.error('prog not found: ' + id);
// if (prog_obj.properties.menuType === 'screen' || prog_obj.properties.menuType === 'api' || glb.FUNCTION_NODES_ARR.includes(prog_obj.properties.menuType) || glb.MOBILE_ARR.includes(prog_obj.properties.menuType)) {
if (['component', ...glb.FUNCTION_NODES_ARR].includes(prog_obj.properties.menuType)) {
return prog_obj;
}
return;
};
func.utils.clean_returned_datasource = function (SESSION_ID, DS) {
const clean_object_functions = function (obj) {
for (const [key, val] of Object.entries(obj)) {
if (typeof val === 'function') {
delete obj[key];
}
}
};
var _session = SESSION_OBJ[SESSION_ID];
if (!_session.DS_GLB[DS]) return;
var obj = { ..._session.DS_GLB[DS] };
delete obj.screen_params;
// try {
// clean_object_functions(obj);
// obj = JSON.parse(JSON.stringify(obj, func.utils.clean_stringify_null, '\t'));
// } catch (e) {
// console.error(e);
// }
delete obj.pre_init_fields;
delete obj.oninit_triggers_to_run;
// delete obj.raw_data;
delete obj.debug;
const clean_empty_objects = function () {
for (const [key, val] of Object.entries(obj)) {
if (typeof val === 'object' && !Array.isArray(val) && xu_isEmpty(val)) {
delete obj[key];
}
}
for (const [key, val] of Object.entries(obj)) {
if (typeof val === 'object' && Array.isArray(val) && !val.length) {
delete obj[key];
}
}
};
delete obj.screenInfo;
delete obj.viewEventsProp;
delete obj.viewSourceDesc;
delete obj.viewSourceProp;
delete obj.v;
clean_empty_objects();
try {
clean_object_functions(obj);
obj = JSON.parse(JSON.stringify(obj, func.utils.clean_stringify_null, '\t'));
} catch (e) {
console.error(e);
}
// clean_dataset();
return obj;
};
func.utils.post_back_to_client = function (SESSION_ID, service, id, data) {
if (typeof IS_PROCESS_SERVER !== 'undefined') return;
worker_post_message({
fx_to_execute: service,
params: data,
session_id: SESSION_ID,
worker_id: id,
});
};
func.utils.job_worker = {};
func.utils.job_worker = function (session_id) {
var SESSION_ID = session_id;
var _session = SESSION_OBJ[SESSION_ID];
var is_progressScreen_on;
var is_not_responding;
var attempt = 0;
const lock = function (dsP) {
if (!_session.WORKER_OBJ.jobs[_session.WORKER_OBJ.stat] || _session.WORKER_OBJ.jobs[_session.WORKER_OBJ.stat].typeP === 'system_interval' || _session.WORKER_OBJ.jobs[_session.WORKER_OBJ.stat].typeP === 'system event') {
return;
}
if (glb.IS_WORKER) {
func.utils.post_back_to_client(SESSION_ID, 'screen_blocker_on', _session.worker_id, null);
} else {
func.UI.utils.screen_blocker(true, 'Worker', dsP);
}
};
const unlock = function () {
if (glb.IS_WORKER) {
} else {
func.UI.utils.screen_blocker(false, 'Worker');
}
};
const not_responding = function () {
is_not_responding = true;
func.UI.utils.progressScreen.hide('Working, Please wait..');
setTimeout(function () {
if (!is_not_responding) return;
reset();
}, 500);
};
const idle = function () {
if (is_progressScreen_on) {
setTimeout(function () {
if (!attempt && is_progressScreen_on) {
is_progressScreen_on = false;
is_not_responding = false;
func.UI.utils.progressScreen.hide('Working, Please wait..');
} else if (attempt > 300 && is_not_responding) {
is_not_responding = false;
busy();
}
}, 310);
} else {
if (!glb.IS_WORKER) {
// func.UI.screen.garbage_collector(SESSION_ID);
}
}
};
const busy = function () {
if (glb.IS_WORKER) return;
func.utils.debug_report(SESSION_ID, 'utils.worker.busy', 'worker processing more then 10 second', 'W', '', _session.WORKER_OBJ.jobs);
is_progressScreen_on = true;
};
const reset = function () {
func.utils.debug_report(SESSION_ID, 'utils.worker.reset', 'worker not responding', 'E', '', _session.WORKER_OBJ.jobs);
_session.WORKER_OBJ.jobs = [];
_session.WORKER_OBJ.stat = null;
func.runtime.ui.clear_screen_blockers();
};
return {
_interval: null,
_was_busy: null,
init: async function () {
var _this = this;
this._interval = setInterval(async function () {
var _session = SESSION_OBJ[SESSION_ID];
if (!_session?.WORKER_OBJ) return;
if (typeof _session.WORKER_OBJ.stat === 'undefined' || _session.WORKER_OBJ.stat === 'undefined' || _session.WORKER_OBJ.stat === null) {
// idle
// if (_this._was_busy) {
// if (glb.IS_WORKER) {
// func.utils.post_back_to_client(
// SESSION_ID,
// "worker_busy_off",
// _session.worker_id,
// null
// );
// } else {
// func.UI.utils.indicator.worker.normal();
// }
// _this._was_busy = false;
// }
unlock();
if (_session.WORKER_OBJ.jobs.length) {
for await (const [key, val] of Object.entries(_session.WORKER_OBJ.jobs)) {
if (val.stat) {
break;
}
// if (!_session.WORKER_OBJ.stat) {
if (!_session.WORKER_OBJ.jobs[Number(key)] || val.job_num === 9999999) {
continue;
}
if (val.dsSessionP && !_session.DS_GLB[val.dsSessionP]) {
func.events.delete_job(SESSION_ID, val.job_num);
break;
}
await func.events.execute(
SESSION_ID,
val.job_num,
val.eventIdP,
val.triggerP,
val.functionP,
val.refIdP,
val.containerP,
val.elementP,
val.rowP,
val.evt,
val.descP,
val.rootScreenIdP,
val.dsSessionP,
null,
val.typeP,
null,
val.event_propertiesP,
val.calling_triggerP,
null,
val.paramsP,
val.target_frame_idP,
val.calling_trigger_prop,
val.calling_program,
val.argumentsP,
val.prog_id,
val.nodeId,
val.parentDataSourceNo,
val.$container,
val.event_optionsP,
);
}
_this._was_busy = true;
} else {
// idle
if (_this._was_busy) {
if (glb.IS_WORKER) {
func.utils.post_back_to_client(SESSION_ID, 'worker_busy_off', _session.worker_id, null);
} else {
func.UI.utils.indicator.worker.normal();
}
}
_this._was_busy = false;
}
attempt = 0;
is_not_responding = false;
idle();
} else {
//busy
_this._was_busy = true;
if (glb.IS_WORKER) {
func.utils.post_back_to_client(SESSION_ID, 'worker_busy_on', _session.worker_id, null);
} else {
func.UI.utils.indicator.worker.busy();
}
if (glb.WORKER_PAUSE) return;
attempt++;
if (!is_progressScreen_on && attempt > glb.WORKER_TIMEOUT) busy();
if (!is_not_responding && attempt >= glb.WORKER_ATTEMPTS_NOT_RESPONDING) {
not_responding();
}
var ds = null;
if (_session.WORKER_OBJ.jobs[0]) ds = _session.WORKER_OBJ.jobs[0].dsSessionP;
lock(ds);
}
}, 1);
},
stop: function () {
clearInterval(this._interval);
},
};
};
func.utils.base64MimeType = function (encoded) {
var result = null;
if (typeof encoded !== 'string') {
return result;
}
var mime = encoded.match(/data:([a-zA-Z0-9]+\/[a-zA-Z0-9-.+]+).*,.*/);
if (mime && mime.length) {
result = mime[1];
}
return result;
};
func.utils.makeid = function (length) {
var result = '';
var characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
var charactersLength = characters.length;
for (var i = 0; i < length; i++) {
result += characters.charAt(Math.floor(Math.random() * charactersLength));
}
return result;
};
func.utils.get_device = function () {
var device;
try {
const win = func.runtime.platform.get_window();
if (win?.cordova) {
device = win.cordova.platformId;
}
} catch (e) {
console.error('error using ui element in server side request');
}
return device;
};
func.utils.ws_worker = {};
func.utils.ws_worker.functions = {
init: async function (data) {
var SESSION_ID = data.SESSION_ID;
APP_OBJ[data.app_id] = data.APP_OBJ;
PROJECT_OBJ[data.app_id] = data.PROJECT_OBJ;
if (['live_preview', 'miniapp'].includes(data.SESSION_INFO.engine_mode)) {
DOCS_OBJ[data.app_id] = data.DOCS_OBJ;
} else if (typeof IS_DOCKER === 'undefined' && typeof IS_PROCESS_SERVER === 'undefined') {
if (!DOCS_OBJ[data.app_id]) {
DOCS_OBJ[data.app_id] = {};
}
}
glb.APP_INFO[data.app_id] = data.APP_INFO;
glb.DEBUG_MODE = data.DEBUG_MODE;
glb.DEBUG_INFO_OBJ = data.DEBUG_INFO_OBJ;
glb.WINDOW_LOCATION_SEARCH = data.WINDOW_LOCATION_SEARCH;
glb.ROOT_ELEMENT_ATTRIBUTES = data.ROOT_ELEMENT_ATTRIBUTES;
DATASOURCE_INTERVALS[SESSION_ID] = {};
SESSION_OBJ[SESSION_ID] = data.SESSION_INFO;
var _session = SESSION_OBJ[SESSION_ID];
glb.SESSION_INFO = data.SESSION_INFO;
_session.engine_mode = data.engine_mode;
STUDIO_WEBSOCKET_CONNECTION_ID = data.STUDIO_WEBSOCKET_CONNECTION_ID;
for (let [key, val] of Object.entries(_session.DS_GLB)) {
if (Number(key) > _session.dataSourceSessionGlobal) {
_session.dataSourceSessionGlobal = Number(key);
}
}
if (typeof _session.SLIM_BUNDLE === 'undefined' || !_session.SLIM_BUNDLE) {
const db_adapter = await func.common.get_module(SESSION_ID, 'xuda-db-adapter-module.mjs');
func.db = db_adapter._db;
}
_session.WORKER_OBJ.fx = new func.utils.job_worker(SESSION_ID);
_session.WORKER_OBJ.fx.init();
if (_session.app_id === 'unknown') {
worker_post_message({
fx_to_execute: 'init_done',
worker_id: ws_worker_id,
session_id: SESSION_ID,
});
} else {
const module = await func.common.get_module(SESSION_ID, `xuda-progs-loader-module.mjs`);
await module.load_objects_cache(SESSION_ID);
worker_post_message({
fx_to_execute: 'init_done',
worker_id: ws_worker_id,
session_id: SESSION_ID,
});
}
WEB_WORKER_CALLBACK_QUEUE[SESSION_ID] = {};
},
datasource_create: async function (params, promise_queue_id) {
var SESSION_ID = params.session_id;
var _session = SESSION_OBJ[SESSION_ID];
_session.ts = new Date().getTime();
var args = params;
args.SESSION_ID = SESSION_ID;
if (show_log) {
console.log('DATASOURCE EXECUTING SESSION_ID: ' + SESSION_ID, APP_OBJ[_session.app_id].app_name);
}
if (Number(params.dataSourceSessionGlobal) > _session.dataSourceSessionGlobal) {
_session.dataSourceSessionGlobal = Number(params.dataSourceSessionGlobal);
}
const ret = await func.datasource.prepare(
args.SESSION_ID,
args.prog_id,
args.dataSourceNoP,
args.parentDataSourceNoP,
args.containerIdP,
args.rowIdP,
args.jobNoP,
args.calling_trigger_prop,
args.parameters_raw_obj,
null,
args.callingSourceP,
args.calling_jobP,
args.screen_dsP,
args.is_panelP,
args.parameters_obj_inP,
args.static_refreshP,
args.run_atP,
args.worker_id,
);
try {
let _ds = _session.DS_GLB[ret.dsSessionP];
if (show_log) console.log('DATASOURCE EXECUTION DONE ' + ret.dsSessionP + ' ' + _ds?.tree_obj?.menuName || '' + ' SESSION_ID: ' + SESSION_ID, APP_OBJ[_session.app_id].app_name);
var obj = func.utils.clean_returned_datasource(SESSION_ID, ret?.dsSessionP);
obj.dataSourceSessionGlobal = _session.dataSourceSessionGlobal;
worker_post_message({
promise_queue_id,
params: obj,
worker_id: ws_worker_id,
session_id: SESSION_ID,
process_pid: params.process_pid,
service: params.service,
});
_ds.stat = 'idle';
} catch (error) {
console.error('[xuda-runtime] caught xuda_utils.js:606:', error);
}
},
datasource_delete: function (params, promise_queue_id) {
var SESSION_ID = params.session_id;
var _session = SESSION_OBJ[SESSION_ID];
if (DATASOURCE_INTERVALS[SESSION_ID] && DATASOURCE_INTERVALS[SESSION_ID][params.dssession]) {
DATASOURCE_INTERVALS[SESSION_ID][params.dssession].clear();
}
delete _session.DS_GLB[params.dssession];
if (show_log) console.log('DATASOURCE DELETE SESSION_ID: ' + SESSION_ID, APP_OBJ[_session.app_id].app_name, params.dssession);
console.error('[xuda-runtime] caught xuda_utils.js:617:', error);
worker_post_message({
promise_queue_id,
worker_id: ws_worker_id,
session_id: SESSION_ID,
process_pid: params.process_pid,
service: params.service,
});
},
update_datasource_changes_from_client: async function (params, promise_queue_id) {
if (xu_isEmpty(SESSION_OBJ)) return;
var SESSION_ID = params.session_id;
var _session = SESSION_OBJ[SESSION_ID];
if (!_session) {
_session = {};
_session.app_id = params.app_id;
_session.dataSourceSessionGlobal = -1;
_session.DS_GLB = {};
}
if (show_log) console.log('DATASOURCE UPDATE SESSION_ID: ' + SESSION_ID, APP_OBJ[_session.app_id].app_name, params.dssession);
await func.datasource.update(SESSION_ID, params.datasource_changes, true);
worker_post_message({
promise_queue_id,
params: params.dssession,
worker_id: ws_worker_id,
session_id: SESSION_ID,
process_pid: params.process_pid,
service: params.service,
});
},
return_to_data_source: function (params, promise_queue_id) {
var SESSION_ID = params.session_id;
var _session = SESSION_OBJ[SESSION_ID];
var ds = _session.DS_GLB[params.dssession];
var type = params.return_to_data_source_type;
var args = ds.args;
if (show_log) console.log('DATASOURCE RETURN TO DATASOURCE ' + params.dssession + ' SESSION_ID: ' + SESSION_ID, APP_OBJ[_session.app_id].app_name);
_session.DS_GLB[params.dssession].v.onscreen_events_active = params.onscreen_events_active;
if (params.viewEventExec_arr) _session.DS_GLB[params.dssession].viewEventExec_arr = JSON.parse(params.viewEventExec_arr);
var done = function (SESSION_ID, DS) {
if (show_log) console.log('DATASOURCE RETURN TO DATASOURCE DONE ' + DS + ' SESSION_ID: ' + SESSION_ID, APP_OBJ[_session.app_id].app_name);
var obj = func.utils.clean_returned_datasource(SESSION_ID, DS);
obj.dataSourceSessionGlobal = _session.dataSourceSessionGlobal;
worker_post_message({
fx_to_execute: 'post_datasource',
params: {
ds_obj: obj,
dsSessionP: params.dssession,
},
worker_id: ws_worker_id,
session_id: SESSION_ID,
process_pid: params.process_pid,
service: params.service,
});
};
done(SESSION_ID, params.dssession);
},
acknowledged_worker_with_eventChangesResults_done: function (params) {
var SESSION_ID = params.session_id;
var _session = SESSION_OBJ[SESSION_ID];
var ds = _session.DS_GLB[params.dssession];
if (show_log) console.log('UPDATE CHANGE EVENT DONE TO DATASOURCE ' + params.dssession + ' SESSION_ID: ' + SESSION_ID, APP_OBJ[_session.app_id].app_name);
ds.eventChangesResults_done = true;
},
return_from_db_query: function (params) {
var SESSION_ID = params.session_id;
var _session = SESSION_OBJ[SESSION_ID];
var id = params.id;
if (show_log) console.log('RETURN FROM DB_QUERY ' + params.dssession + ' SESSION_ID: ' + SESSION_ID, APP_OBJ[_session.app_id].app_name);
var callback = func.utils.get_callback_queue(SESSION_ID, params.callback_id);
if (callback) callback(params.data);
},
return_from_sava_data: function (params) {
var SESSION_ID = params.session_id;
var _session = SESSION_OBJ[SESSION_ID];
var id = params.id;
if (show_log) console.log('RETURN FROM SAVE_DATA ' + params.dssession + ' SESSION_ID: ' + SESSION_ID, APP_OBJ[_session.app_id].app_name);
func.utils.get_callback_queue(SESSION_ID, params.callback_id)();
},
update_debug_info: function (params) {
glb.DEBUG_INFO_OBJ = params;
},
// update_VIEWS_OBJ: function (params) {
// delete VIEWS_OBJ[APP_ID][params.id];
// },
// update_TREE_OBJ: function (params) {
// delete TREE_OBJ[APP_ID][params.id];
// },
// send_object_to_worker: function (params) {
// if (RESPONSE_FROM_STUDIO_QUEUE[params.req_id]) {
// RESPONSE_FROM_STUDIO_QUEUE[params.req_id].data = params;
// }
// },
get_dataSourceSessionGlobal: function (params) {
var SESSION_ID = params.session_id;
SESSION_OBJ[SESSION_ID].dataSourceSessionGlobal++;
let new_dataSourceSessionGlobal = SESSION_OBJ[SESSION_ID].dataSourceSessionGlobal;
return { new_dataSourceSessionGlobal };
},
create_webworker_globals: function (params) {
var SESSION_ID = params.session_id;
SESSION_OBJ[SESSION_ID].DS_GLB[0] = params.ds_data;
},
return_doc_from_studio: function (params) {
var SESSION_ID = params.session_id;
function emitCustomEvent(eventName, detail) {
const event = new CustomEvent(eventName, { detail });
self.dispatchEvent(event);
}
emitCustomEvent('live_preview_get_obj_response_worker_' + params._id, {
data: params,
});
},
return_doc_from_websocket: function (params) {
var SESSION_ID = params.session_id;
function emitCustomEvent(eventName, detail) {
const event = new CustomEvent(eventName, { detail });
self.dispatchEvent(event);
}
emitCustomEvent('get_doc_obj_from_build_worker_' + params._id, {
data: params,
});
},
return_dbs_data_from_websocket: function (params) {
var SESSION_ID = params.session_id;
function emitCustomEvent(eventName, detail) {
const event = new CustomEvent(eventName, { detail });
self.dispatchEvent(event);
}
emitCustomEvent('get_ws_data_worker_' + params.websocket_queue_num, {
data: params.data,
});
},
heartbeat: async function (params) {
var SESSION_ID = params.session_id;
try {
const do_heartbeat = async function (app_replicate, app_id, token_id, fingerprint, device_name, stat) {
try {
module.exports.close_expired_device_log_sessions(app_id);
return await update_device(app_replicate, app_id, token_id, fingerprint, device_name, stat);
} catch (err) {
return { code: -400, data: err.message };
}
};
let ret = await do_heartbeat(params.app_replicate, params.app_id, params.gtp_token || req.body.app_token, params.fingerprint, params.device_name, params.stat);
// session_status: ret_session.data.stat
if (params.token) {
try {
const couch = await __.rpi.get_app_couch(req.body.app_id);
const session_doc = await couch.get(req.body.app_token);
ret.session_stat = session_doc.stat;
} catch (error) {}
}
} catch (error) {
console.error('[xuda-runtime] caught xuda_utils.js:780:', error);
}
},
return_rpi_request_from_studio: function (params) {
var SESSION_ID = params.session_id;
function emitCustomEvent(eventName, detail) {
const event = new CustomEvent(eventName, { detail });
self.dispatchEvent(event);
}
emitCustomEvent('rpi_request_response_worker_' + params.table_id, {
data: params.data,
});
},
};
func.utils.set_callback_queue = function (SESSION_ID, func) {
var t = new Date().valueOf().toString() + Math.random().toString();
try {
WEB_WORKER_CALLBACK_QUEUE[SESSION_ID][t] = func;
} catch (e) {
console.error(id);
func.utils.remove_cached_objects(SESSION_ID);
}
return t;
};
func.utils.get_callback_queue = function (SESSION_ID, t) {
var func = WEB_WORKER_CALLBACK_QUEUE[SESSION_ID][t];
setTimeout(function () {
if (WEB_WORKER_CALLBACK_QUEUE[SESSION_ID][t]) delete WEB_WORKER_CALLBACK_QUEUE[SESSION_ID][t];
}, 1000);
return func;
};
func.utils.clean_stringify_null = function (key, value) {
// Filtering out properties
if (value === null) {
return undefined;
}
return value;
};
func.utils.load_js_on_demand = async function (js_src, type) {
const normalized_src = typeof js_src === 'string' ? js_src.trim() : '';
if (!normalized_src || normalized_src === 'undefined' || normalized_src === 'null') {
return false;
}
const get_script = function (callback) {
if (glb.IS_WORKER) {
callback();
return;
}
function isScriptLoaded(src) {
return GLB_JS_SCRIPTS_LOADED.includes(src);
}
if (isScriptLoaded(normalized_src)) {
callback(false);
} else {
func.runtime.platform.load_script(normalized_src, type, function () {
callback(true);
GLB_JS_SCRIPTS_LOADED.push(normalized_src);
});
}
};
return new Promise((resolve) => {
get_script(resolve);
});
};
func.utils.load_css_on_demand = function (css_href) {
const normalized_href = typeof css_href === 'string' ? css_href.trim() : '';
if (!normalized_href || normalized_href === 'undefined' || normalized_href === 'null') {
return null;
}
return func.runtime.platform.load_css(normalized_href);
};
func.utils.remove_js_css_file = function (filename, filetype) {
func.runtime.platform.remove_js_css(filename, filetype);
};
func.utils.replace_studio_drive_url = function (SESSION_ID, val) {
var _session = SESSION_OBJ[SESSION_ID];
if (!_session.is_deployment) return val;
try {
const rep = APP_OBJ[_session.app_id].app_replicate;
const dest = `https://${_session.domain}/studio-drive/${rep}`;
const rep_esc = rep.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
// Rewrite every legacy studio-drive host to the live domain: stale xuda.io hardcodings,
// and any xuda.ai host incl. subdomains (xuda.ai, master.xuda.ai, eu.xuda.ai, ...).
return val
.replaceAll(`https://xuda.io/studio-drive/${rep}`, dest)
.replace(new RegExp(`https://(?:[a-z0-9-]+\\.)*xuda\\.ai/studio-drive/${rep_esc}`, 'g'), dest);
} catch (err) {
return val;
}
};
func.utils.get_drive_url = function (SESSION_ID, val, wrap) {
// wrap = false; // tbd bypass
var _session = SESSION_OBJ[SESSION_ID];
function replaceFiletoURL(fileString) {
const _app = APP_OBJ[_session.app_id];
let url = `https://${_session.domain}/workspace-drive/${_app.is_deployment ? _app.app_datacenter_id : _app.app_id_reference}/`;
let FILE_REPLACE_URL = `${url}${val}`;
if (!_app.is_deployment) {
// the deployment server will handle the app_token
FILE_REPLACE_URL += `?app_token=${_session.app_token}&ts=${Date.now()}`;
} else {
FILE_REPLACE_URL += `?ts=${_session?.opt?.app_build_id || 0}`;
}
let match = `drv_${_app.app_replicate || _session.app_id}_[0-9a-f\\-]+\\.[a-zA-Z0-9]+`;
let pat = new RegExp(match, 'g');
let URLString = fileString.replace(pat, function (match, idx) {
const hasURLbefore = fileString.substring(idx - url.length, idx) === url;
if (hasURLbefore) {
return match;
}
return FILE_REPLACE_URL.replace('{val}', match);
});
return URLString;
}
if (typeof val === 'string' || typeof val === 'object') {
if (typeof val === 'string') {
if (val.includes('.') && val.includes('drv_') && val.length > 30) {
var ret = replaceFiletoURL(val);
if (wrap) {
return { value: '"' + ret + '"', changed: true };
} else {
return { value: ret, changed: true };
}
} else {
return { value: val, changed: false };
}
}
if (typeof val === 'object') {
// XU_PERF fast path: stringifying every bound object per evaluation just
// to look for 'drv_' is a hot-loop tax. Clean verdicts are memoized per
// object reference; datasource.update clears the cache, so a mutation can
// never outlive the refresh that would re-evaluate it. Only on a
// (possible) hit fall through to the exact legacy stringify path.
if (glb.XU_PERF) {
if (func.utils.drive_ref_clean_cache.has(val)) {
return { value: val, changed: false };
}
if (!func.utils.has_drive_ref(val, 0)) {
func.utils.drive_ref_clean_cache.add(val);
return { value: val, changed: false };
}
}
try {
let str = JSON.stringify(val);
if (str.includes('.') && str.includes('drv_') && str.length > 30) {
let new_val = replaceFiletoURL(str);
return { value: new_val, changed: true };
} else {
return { value: val, changed: false };
}
} catch (err) {
return { value: val, changed: false };
}
}
} else {
return { value: val, changed: false };
}
};
func.utils.drive_ref_clean_cache = new WeakSet();
// Early-exit scan for 'drv_' drive-file references in keys or string values.
// Returns true on any hit OR when traversal gives up (depth cap), so callers
// fall back to the exact legacy stringify detection in those cases.
func.utils.has_drive_ref = function (v, depth) {
if (typeof v === 'string') return v.includes('drv_');
if (v === null || typeof v !== 'object') return false;
if (depth > 8) return true; // too deep to be sure: let legacy path decide
if (Array.isArray(v)) {
for (let i = 0; i < v.length; i++) {
if (func.utils.has_drive_ref(v[i], depth + 1)) return true;
}
return false;
}
for (const k in v) {
if (k.includes('drv_')) return true;
if (func.utils.has_drive_ref(v[k], depth + 1)) return true;
}
return false;
};
func.utils._error_registry_module = func.utils._error_registry_module || null;
func.utils._error_registry_pending = func.utils._error_registry_pending || null;
func.utils.get_error_registry = async function (SESSION_ID) {
if (func.utils._error_registry_module) {
return func.utils._error_registry_module;
}
if (func.utils._error_registry_pending) {
return await func.utils._error_registry_pending;
}
if (!SESSION_ID || !SESSION_OBJ?.[SESSION_ID]) {
return null;
}
func.utils._error_registry_pending = func.common
.get_module(SESSION_ID, 'xuda-error-registry-module.mjs')
.then((module) => {
func.utils._error_registry_module = module;
return module;
})
.catch((err) => {
console.warn('XUDA WARNING RUN_MSG_NET_010', 'Failed to load error registry module', err);
return null;
})
.finally(() => {
func.utils._error_registry_pending = null;
});
return await func.utils._error_registry_pending;
};
func.utils._normalize_issue_severity = function (type, fallback = 'error') {
if (!type) return fallback;
const normalized = type.toString().toLowerCase();
if (['w', 'warn', 'warning'].includes(normalized)) return 'warning';
if (['i', 'info', 'log'].includes(normalized)) return 'info';
return 'error';
};
func.utils._serialize_issue_value = function (value, seen = new WeakSet()) {
if (value instanceof Error) {
return {
name: value.name,
message: value.message,
stack: value.stack,
cause: value.cause,
};
}
if (typeof value === 'undefined' || value === null) {
return value;
}
if (typeof value === 'function') {
return `[Function ${value.name || 'anonymous'}]`;
}
if (typeof value !== 'object') {
return value;
}
if (seen.has(value)) {
return '[Circular]';
}
seen.add(value);
if (Array.isArray(value)) {
return value.map((item) => func.utils._serialize_issue_value(item, seen));
}
const ret = {};
for (const [key, val] of Object.entries(value)) {
ret[key] = func.utils._serialize_issue_value(val, seen);
}
return ret;
};
func.utils._stringify_issue_message = function (value) {
if (typeof value === 'undefined' || value === null) {
return '';
}
if (typeof value === 'string') {
return value;
}
if (value instanceof Error) {
return value.message || value.toString();
}
try {
return JSON.stringify(func.utils._serialize_issue_value(value));
} catch (error) {
return value?.toString?.() || '';
}
};
func.utils._build_fallback_issue_definition = function (payload = {}) {
const code = payload.code || 'RUN_MSG_GEN_000';
const severity = func.utils._normalize_issue_severity(payload.type || payload.severity, code.startsWith('CHK_MSG_') ? 'warning' : 'error');
return {
code,
title: payload.title || (code.startsWith('CHK_MSG_') ? 'Studio Checker Validation Issue' : 'Runtime Error'),
severity,
domain: code.startsWith('CHK_MSG_') ? 'checker' : 'runtime',
category: payload.category || 'general',
summary: payload.message || 'The runtime reported an issue.',
help_slug: payload.help_slug || code.toLowerCase().replace(/_/g, '-'),
};
};
func.utils.report_issue = async function (SESSION_ID, payload = {}) {
const err = payload.err instanceof Error ? payload.err : payload.error instanceof Error ? payload.error : null;
const source = payload.source || payload.method || 'runtime';
const message = func.utils._stringify_issue_message(
typeof payload.message !== 'undefined' ? payload.message : typeof payload.msg !== 'undefined' ? payload.msg : err || payload.details || 'Unknown runtime issue',
);
try {
const registry = await func.utils.get_error_registry(SESSION_ID);
const registry_payload = {
code: payload.code,
source,
message,
type: payload.type || payload.severity,
err,
details: payload.details,
category: payload.category,
};
let definition = null;
if (registry?.get_error_definition && payload.code) {
definition = registry.get_error_definition(payload.code, registry_payload);
}
if (!definition && registry?.get_runtime_report_definition) {
definition = registry.get_runtime_report_definition(registry_payload);
}
if (!definition) {
definition = func.utils._build_fallback_issue_definition({
...payload,
source,
message,
});
}
const severity = func.utils._normalize_issue_severity(payload.type || payload.severity, definition.severity || 'error');
const error_code = definition.code || payload.code || 'RUN_MSG_GEN_000';
const error_title = definition.title || payload.title || 'Runtime Error';
const help_slug = definition.help_slug || error_code.toLowerCase().replace(/_/g, '-');
const console_method = severity === 'warning' ? 'warn' : severity === 'info' ? 'log' : 'error';
const _session = SESSION_OBJ?.[SESSION_ID];
const report = {
error_code,
error_title,
help_slug,
severity,
error_domain: definition.domain || 'runtime',
error_category: definition.category || payload.category || 'general',
source,
message,
stack: err?.stack || null,
app_id: _session?.app_id || payload.app_id || null,
session_id: SESSION_ID || null,
worker: !!glb?.IS_WORKER,
summary: definition.summary || message,
};
const details = {
report,
error: func.utils._serialize_issue_value(err),
context: func.utils._serialize_issue_value(payload.details),
extra: func.utils._serialize_issue_value(payload.extra),
};
const console_prefix = `XUDA ${console_method.toUpperCase()} ${error_code}`;
const console_summary = `${error_title}: ${source}${message ? ' | ' + message : ''}`;
if (glb?.debug_js && console.groupCollapsed) {
console.groupCollapsed(console_prefix, console_summary);
console[console_method](report);
if (details.context) console.log('context', details.context);
if (err) console.error(err);
console.groupEnd();
} else {
console[console_method](console_prefix, console_summary, {
help_slug,
});
}
if (!payload.skip_log && SESSION_ID && _session) {
await func.utils.write_log(SESSION_ID, source, message || error_title, console_method, payload.log_source || 'runtime', details, report);
}
return report;
} catch (report_err) {
console.error('XUDA ERROR RUN_MSG_GEN_000', 'Failed to report runtime issue', report_err, {
source,
message,
payload,
});
return null;
}
};
func.utils.debug_report = async function (SESSION_ID, sourceP, msgP, typeP, errP, objP) {
if (!typeP || typeP === 'E') {
setTimeout(() => {
// if (
// typeof IS_DOCKER === "undefined" &&
// typeof IS_API_SERVER === "undefined" &&
// typeof IS_PROCESS_SERVER === "undefined" &&
// !glb?.IS_WORKER
// ) {
// func.index.delete_pouch(SESSION_ID);
// }
}, 1000);
}
await func.utils.report_issue(SESSION_ID, {
source: sourceP,
message: msgP,
type: typeP,
err: errP,
details: objP,
});
};
func.utils.request_error = function (SESSION_ID, type, e) {
var _session = SESSION_OBJ[SESSION_ID];
console.error(type, e);
if (typeof IS_PROCESS_SERVER !== 'undefined') return;
if (!glb.IS_WORKER) {
func.utils.debug_report(SESSION_ID, type, e, 'E');
setTimeout(function () {
if (!glb.debug_js) {
// location.reload();
console.warn('** reload request');
}
}, 2000);
} else {
func.utils.post_back_to_client(SESSION_ID, 'ajax_error', _session.worker_id, null);
}
};
func.utils.alerts = {};
func.utils.alerts.invoke = async function (SESSION_ID, typeP, paramsP, sourceP, dsSessionP, msgP) {
try {
var _session = SESSION_OBJ[SESSION_ID];
if (ALERT_IS_ACTIVE) return;
ALERT_IS_ACTIVE = true;
var title;
var message = '';
var alert_type = 'console';
var alertDisplay;
var expRet = {};
var _ds = _session.DS_GLB[dsSessionP];
var type = '';
var createLog;
const get_alert_properties = async function (value, fx) {
var ret = value || '';
if (fx) {
const exp_ret = await func.expression.get(SESSION_ID, fx, dsSessionP, 'alert');
ret = exp_ret.result;
}
return ret;
};
switch (typeP) {
case 'alert':
type = 'User defined alert';
title = await get_alert_properties(paramsP.alertTitle, paramsP.alertTitleFx);
alert_type = await get_alert_properties(paramsP.alertType, paramsP.alertTypeFx);
message = await get_alert_properties(paramsP.alertBody, paramsP.alertBodyFx);
alertDisplay = await get_alert_properties(paramsP.alertDisplay, paramsP.alertDisplayFx);
createLog = paramsP.createLog;
break;
case 'call_alert':
type = 'User defined call alert';
let prop = await func.utils.TREE_OBJ.get(SESSION_ID, paramsP.prog);
if (!prop) {
console.log('events.execute', 'Missing details for alert message object: ' + paramsP.prog, 'W');
}
// user defined
let ret = await func.utils.VIEWS_OBJ.get(SESSION_ID, paramsP.prog);
if (ret?.alertData) {
title = await get_alert_properties(ret.alertData.alertTitle, ret.alertData.alertTitleFx);
alert_type = await get_alert_properties(ret.alertData.alertType, ret.alertData.alertTypeFx);
message = await get_alert_properties(ret.alertData.alertBody, ret.alertData.alertBodyFx);
alertDisplay = await get_alert_properties(ret.alertData.alertDisplay, ret.alertData.alertDisplayFx);
createLog = ret.alertData.createLog;
}
if (!title) {
title = prop.menuTitle;
}
if (!alert_type) {
alert_type = 'console';
}
if (!alertDisplay) {
alertDisplay = 'modal';
} //window alert by definition
break;
case 'system_msg': {
type = 'System alert';
const sys_alerts_obj = func.utils.get_system_error_msg();
if (sys_alerts_obj[paramsP]) {
title = sys_alerts_obj[paramsP].subject;
alert_type = sys_alerts_obj[paramsP].alert_type;
alertDisplay = sys_alerts_obj[paramsP].alertDisplay;
expRet = await func.expression.get(SESSION_ID, sys_alerts_obj[paramsP].msg, dsSessionP, 'alert');
message = func.expression.remove_quotes(expRet.result);
if (msgP) message = msgP;
if (alert_type === 'error') {
if (_ds) _ds.error = title + ' ' + sourceP;
func.utils.debug_report(SESSION_ID, sourceP, title + ' ' + sourceP, 'E', '', _ds);
}
}
break;
}
default:
message = msgP;
break;
}
} catch (err) {
console.error(err);
ALERT_IS_ACTIVE = false;
return;
}
if (glb.IS_WORKER) {
if (_session.IS_API) {
if (_ds) {
_ds.api_rendered_output = message;
} else {
console.error(message);
}
return;
}
// if (typeof IS_PROCESS_SERVER !== "undefined") return;
ALERT_IS_ACTIVE = false;
return func.utils.post_back_to_client(SESSION_ID, 'alert', _session.worker_id, [SESSION_ID, alert_type, alertDisplay, message, title]);
}
// const sys_alerts_obj = func.utils.get_system_error_msg();
// if (sys_alerts_obj[paramsP]) {
// var str = paramsP + " " + title + " " + message;
// if (sys_alerts_obj[paramsP].alert_type === "error") {
// console.error(str);
// } else if (sys_alerts_obj[paramsP].alert_type === "warning") {
// console.warn(str);
// } else {
// console.log(str);
// }
// } else {
// console.log(str);
// }
ALERT_IS_ACTIVE = false;
func.utils.alerts.execute(SESSION_ID, alert_type, alertDisplay, message, title, type);
if (createLog) {
func.utils.write_log(SESSION_ID, title, message, alert_type);
}
};
func.utils.alerts.execute = function (SESSION_ID, alert_type, alertDisplay, message, title, type) {
if (!UI_FRAMEWORK_INSTALLED) {
ALERT_IS_ACTIVE = false;
if (alertDisplay !== 'console') {
return alert(title + '\n \n' + message);
}
return console[alert_type === 'error' ? 'error' : 'log'](alert_type, title, message);
}
switch (alertDisplay) {
case 'console':
console[alert_type === 'success' ? 'log' : alert_type === 'warning' ? 'warn' : alert_type](alert_type, title, message);
ALERT_IS_ACTIVE = false;
break;
case 'modal':
func.utils.alerts.popup(title, message, alert_type);
break;
case 'toast':
func.utils.alerts.toast(SESSION_ID, title, message, alert_type);
ALERT_IS_ACTIVE = false;
break;
case 'browser':
alert(title + '\n \n' + message);
ALERT_IS_ACTIVE = false;
default:
console.log(alert_type, title, message);
ALERT_IS_ACTIVE = false;
}
};
func.utils.alerts.toast = function (SESSION_ID, title, message, alert_type) {
if (!UI_FRAMEWORK_PLUGIN.toast) return;
const toast = new UI_FRAMEWORK_PLUGIN.toast();
toast.create(alert_type, message, title, func.common.get_url(SESSION_ID, 'dist', `runtime/images/${alert_type}_alert_ico.svg`));
ALERT_IS_ACTIVE = false;
};
func.utils.alerts.popup = function (title, message, alert_type) {
const popup = new UI_FRAMEWORK_PLUGIN.popup();
var buttons = [
{
text: 'Ok',
role: 'cancel',
// cssClass: "primary",
handler: () => {
ALERT_IS_ACTIVE = false;
},
},
];
popup.create(alert_type.charAt(0).toUpperCase() + alert_type.slice(1), title, message, buttons);
};
func.utils.get_system_error_msg = function () {
var m = {};
m['SYS_MSG_0101'] = {
alert_type: 'success',
alertDisplay: 'toast',
subject: 'Save Success',
msg: 'Settings successfully saved',
};
m['SYS_MSG_0102'] = {
alert_type: 'error',
alertDisplay: 'toast',
subject: 'Save Failed CouchDB',
msg: 'Data fail save to database',
};
m['SYS_MSG_0103'] = {
alert_type: 'error',
alertDisplay: 'toast',
subject: 'Save Failed Table Empty',
msg: 'Table empty, no fields declared',
};
m['SYS_MSG_0104'] = {
alert_type: 'error',
alertDisplay: 'toast',
subject: 'Save Failed Missing Primary Index',
msg: 'Update failed, table missing Primary index',
};
m['SYS_MSG_0105'] = {
alert_type: 'error',
alertDisplay: 'toast',
subject: 'Save Failed Table Missing',
msg: 'Table repository missing',
};
m['SYS_MSG_0106'] = {
alert_type: 'error',
alertDisplay: 'toast',
subject: 'Save Failed Record Not Exist',
msg: 'Save update failed record not exist',
};
m['SYS_MSG_0107'] = {
alert_type: 'error',
alertDisplay: 'modal',
subject: 'Save Failed Unique Key',
msg: 'Save Failed, record already exist',
};
m['SYS_MSG_0108'] = {
alert_type: 'error',
alertDisplay: 'modal',
subject: 'Error reading document',
msg: 'Save Failed, record not found',
};
m['SYS_MSG_0110'] = {
alert_type: 'warning',
alertDisplay: 'toast',
subject: 'Record Changed',
msg: 'Record changed by other user, reload to get the latest changes',
};
m['SYS_MSG_0120'] = {
alert_type: 'error',
alertDisplay: 'modal',
subject: 'Create Mode Denied',
msg: 'Create mode not allowed for this program',
};
m['SYS_MSG_0122'] = {
alert_type: 'error',
alertDisplay: 'modal',
subject: 'Modify Mode Denied',
msg: 'Modify mode not allowed for this program',
};
m['SYS_MSG_0124'] = {
alert_type: 'error',
alertDisplay: 'modal',
subject: 'Delete Mode Denied',
msg: 'Delete mode not allowed for this program',
};
m['SYS_MSG_0126'] = {
alert_type: 'error',
alertDisplay: 'modal',
subject: 'Program Read Only',
msg: 'Program set to Read Only',
};
m['SYS_MSG_0130'] = {
alert_type: 'error',
alertDisplay: 'modal',
subject: 'Error Reduce',
msg: 'Select Index to Reduce',
};
m['SYS_MSG_0201'] = {
alert_type: 'error',
alertDisplay: 'toast',
subject: 'Failed to change GUI Property',
msg: 'Failed to change GUI element property, GUI element missing',
};
m['SYS_MSG_0310'] = {
alert_type: 'error',
alertDisplay: 'toast',
subject: 'Missing Reference Parameters Out',
msg: 'Parameter out not exist in dataset',
};
m['SYS_MSG_0400'] = {
alert_type: 'error',
alertDisplay: 'modal',
subject: 'Delete Widget Folder Denied',
msg: 'The selected folder contains data, Please clean or move content to another folder',
};
m['SYS_MSG_0410'] = {
alert_type: 'error',
alertDisplay: 'toast',
subject: 'Recipient Error',
msg: 'Check recipient data',
};
m['SYS_MSG_0412'] = {
alert_type: 'error',
alertDisplay: 'modal',
subject: 'Recipient Empty',
msg: 'No recipients entered or selected',
};
m['SYS_MSG_0414'] = {
alert_type: 'error',
alertDisplay: 'modal',
subject: 'Data Save Error',
msg: 'Widget has no content',
};
m['SYS_MSG_0416'] = {
alert_type: 'error',
alertDisplay: 'modal',
subject: 'Required Field',
msg: 'Edit url field is empty',
};
m['SYS_MSG_0418'] = {
alert_type: 'error',
alertDisplay: 'modal',
subject: 'Required Field',
msg: 'Publish url field is empty',
};
m['SYS_MSG_0420'] = {
alert_type: 'error',
alertDisplay: 'modal',
subject: 'Connection Error',
msg: 'Cannot connect to mailbox',
};
m['SYS_MSG_0422'] = {
alert_type: 'success',
alertDisplay: 'modal',
subject: 'Connection Ok',
msg: 'Connection Ok :)',
};
m['SYS_MSG_0424'] = {
alert_type: 'error',
alertDisplay: 'modal',
subject: 'Connection Failed',
msg: 'Connection to POP3 failed',
};
m['SYS_MSG_0426'] = {
alert_type: 'error',
alertDisplay: 'modal',
subject: 'Connection Failed',
msg: 'SMTP Connection error, Test Email was not sent',
};
m['SYS_MSG_0430'] = {
alert_type: 'error',
alertDisplay: 'toast',
subject: 'Email Account Error',
msg: 'No email account found, Right Click Tree -> Settings->Manage Accounts -> Right click for menu options',
};
m['SYS_MSG_0440'] = {
alert_type: 'error',
alertDisplay: 'modal',
subject: 'Widget Initiation Error',
msg: 'Missing information for Link Type or Link Name',
};
m['SYS_MSG_0442'] = {
alert_type: 'error',
alertDisplay: 'modal',
subject: 'Error Init Widget',
msg: 'Missing record Id on Create Mode',
};
m['SYS_MSG_0450'] = {
alert_type: 'error',
alertDisplay: 'modal',
subject: 'Validation Failed',
msg: 'Fix fields highlight in Red',
};
m['SYS_MSG_0501'] = {
alert_type: 'error',
alertDisplay: 'modal',
subject: 'Mandatory Alert Save',
msg: 'Save action failed, Mandatory fields missing',
};
m['SYS_MSG_0550'] = {
alert_type: 'error',
alertDisplay: 'toast',
subject: 'Illegal input number',
msg: "@SYS_GLOBAL_OBJ_ACTIVE_FIELD_INFO.nameform +' only allow numbers!'",
};
m['SYS_MSG_0610'] = {
alert_type: 'error',
alertDisplay: 'toast',
subject: 'Form Field Conflict',
msg: 'Field declared more than once for the form',
};
m['SYS_MSG_0612'] = {
alert_type: 'error',
alertDisplay: 'toast',
subject: 'Mask Error - Missing Definition',
msg: 'Missing mask definition',
};
m['SYS_MSG_0614'] = {
alert_type: 'error',
alertDisplay: 'toast',
subject: 'Mask Error - Wrong Definition',
msg: 'Wrong mask definition',
};
m['SYS_MSG_0616'] = {
alert_type: 'error',
alertDisplay: 'toast',
subject: 'Mask Error - Size Parser',
msg: 'Size parser error',
};
m['SYS_MSG_0618'] = {
alert_type: 'error',
alertDisplay: 'toast',
subject: 'Mask Error - Illegal Z switch',
msg: "Illegal 'Z' in string mask",
};
m['SYS_MSG_0620'] = {
alert_type: 'error',
alertDisplay: 'toast',
subject: 'Mask Error - Illegal N switch',
msg: "Illegal 'N' in string mask",
};
m['SYS_MSG_0622'] = {
alert_type: 'error',
alertDisplay: 'toast',
subject: 'Mask Error - Illegal + switch',
msg: "Illegal '+' in string mask",
};
m['SYS_MSG_0624'] = {
alert_type: 'error',
alertDisplay: 'toast',
subject: 'Mask Error - Illegal - switch',
msg: "Illegal '-' in string mask",
};
m['SYS_MSG_0626'] = {
alert_type: 'error',
alertDisplay: 'toast',
subject: 'Mask Error - Illegal C switch',
msg: "Illegal 'C' in string mask",
};
m['SYS_MSG_0628'] = {
alert_type: 'error',
alertDisplay: 'toast',
subject: 'Mask Error - Invalid switch',
msg: 'Invalid switch in string mask',
};
m['SYS_MSG_0630'] = {
alert_type: 'error',
alertDisplay: 'toast',
subject: 'Mask Error - Missing DOM Element',
msg: 'Missing DOM element',
};
m['SYS_MSG_0632'] = {
alert_type: 'error',
alertDisplay: 'toast',
subject: 'Mask Error - Too Big',
msg: 'Size to big, max: 15.5',
};
m['SYS_MSG_0700'] = {
alert_type: 'warning',
alertDisplay: 'console',
subject: 'Table Warning - Empty',
msg: 'Table has no content',
};
m['SYS_MSG_0702'] = {
alert_type: 'error',
alertDisplay: 'console',
subject: 'Table Error - No Fields',
msg: 'Table missing fields content',
};
m['SYS_MSG_0704'] = {
alert_type: 'warning',
alertDisplay: 'console',
subject: 'Table Warning - Not In Use',
msg: 'Table not in use by any object',
};
m['SYS_MSG_0706'] = {
alert_type: 'error',
alertDisplay: 'console',
subject: 'Table Error - No Primary Index',
msg: 'Table must have at least one index',
};
m['SYS_MSG_0708'] = {
alert_type: 'error',
alertDisplay: 'console',
subject: 'Table Error - Bad Index Name',
msg: 'Index name is invalid or cannot contain any of non word characters',
};
m['SYS_MSG_0710'] = {
alert_type: 'error',
alertDisplay: 'console',
subject: 'Table Error - Empty Index',
msg: 'Index has no keys',
};
m['SYS_MSG_0712'] = {
alert_type: 'error',
alertDisplay: 'console',
subject: 'Table Error - Key Not Exist',
msg: 'Key not exist in the table fields repository',
};
m['SYS_MSG_0714'] = {
alert_type: 'error',
alertDisplay: 'console',
subject: 'Table Error - Duplicate Fields',
msg: 'Duplicate fields in the table fields repository',
};
m['SYS_MSG_0716'] = {
alert_type: 'error',
alertDisplay: 'console',
subject: 'Table Error - Bad Field Name',
msg: 'Field name is invalid or cannot contain any of non word characters',
};
m['SYS_MSG_0718'] = {
alert_type: 'error',
alertDisplay: 'console',
subject: 'Table Error - Integrity Broken',
msg: 'Field broken from its properties, edit the field and save',
};
m['SYS_MSG_0720'] = {
alert_type: 'error',
alertDisplay: 'console',
subject: 'Table Error - Model Not Exist',
msg: 'Model assigned to the field not exist',
};
m['SYS_MSG_0722'] = {
alert_type: 'warning',
alertDisplay: 'console',
subject: 'Object Warning - Not In Use',
msg: 'Object not in use or not call by any object',
};
m['SYS_MSG_0724'] = {
alert_type: 'error',
alertDisplay: 'console',
subject: 'Object Error - Table Not Exist',
msg: 'Table assigned in object datasource not exist',
};
m['SYS_MSG_0726'] = {
alert_type: 'error',
alertDisplay: 'console',
subject: 'Object Error - Index Keys Mismatch',
msg: 'Table index has different structure',
};
m['SYS_MSG_0728'] = {
alert_type: 'error',
alertDisplay: 'console',
subject: 'Object Error - Index Key From Empty',
msg: 'Index key From must have a value',
};
m['SYS_MSG_0730'] = {
alert_type: 'error',
alertDisplay: 'console',
subject: 'Object Error - Index Key From Reference',
msg: 'Field reference not exist in any dataset or parameters',
};
m['SYS_MSG_0732'] = {
alert_type: 'error',
alertDisplay: 'console',
subject: 'Object Error - Index Key To Empty',
msg: 'Index key To must have a value',
};
m['SYS_MSG_0734'] = {
alert_type: 'error',
alertDisplay: 'console',
subject: 'Object Error - Index Key To Reference',
msg: 'Field reference not exist in any dataset or parameters',
};
m['SYS_MSG_0736'] = {
alert_type: 'error',
alertDisplay: 'console',
subject: 'Object Error - Index Locate From Reference',
msg: 'Field reference not exist in any dataset or parameters',
};
m['SYS_MSG_0738'] = {
alert_type: 'error',
alertDisplay: 'console',
subject: 'Object Error - Index Locate To Reference',
msg: 'Field reference not exist in any dataset or parameters',
};
m['SYS_MSG_0740'] = {
alert_type: 'error',
alertDisplay: 'console',
subject: 'Object Error - Index Empty',
msg: 'Index empty - no keys defined',
};
m['SYS_MSG_0742'] = {
alert_type: 'error',
alertDisplay: 'console',
subject: 'Object Error - Index Reference',
msg: 'Table index reference error',
};
m['SYS_MSG_0744'] = {
alert_type: 'error',
alertDisplay: 'console',
subject: 'Object Error - Duplicate Fields',
msg: 'Duplicate fields in the dataset fields repository',
};
m['SYS_MSG_0746'] = {
alert_type: 'error',
alertDisplay: 'console',
subject: 'Object Error - Field reference error',
msg: 'Field not exist in datasource table fields repository',
};
m['SYS_MSG_0748'] = {
alert_type: 'error',
alertDisplay: 'console',
subject: 'Object Error - Mismatch Reference Type',
msg: 'Mismatch in calling reference type',
};
m['SYS_MSG_0750'] = {
alert_type: 'error',
alertDisplay: 'console',
subject: 'Object Error - Reference Broken',
msg: 'Reference broken calling object not exist',
};
m['SYS_MSG_0752'] = {
alert_type: 'error',
alertDisplay: 'console',
subject: 'Object Error - Empty Reference',
msg: 'Reference empty',
};
m['SYS_MSG_0754'] = {
alert_type: 'error',
alertDisplay: 'console',
subject: 'Object Error - Action Not Exist',
msg: 'Action not exist',
};
m['SYS_MSG_0756'] = {
alert_type: 'error',
alertDisplay: 'console',
subject: 'Object Error - Empty Event Reference',
msg: 'Empty event reference',
};
m['SYS_MSG_0758'] = {
alert_type: 'error',
alertDisplay: 'console',
subject: 'Object Error - Bad Field Name',
msg: 'Field name is invalid or cannot contain any of non word characters',
};
m['SYS_MSG_0760'] = {
alert_type: 'error',
alertDisplay: 'console',
subject: 'Object Error - Integrity Broken',
msg: 'Field broken from its properties, edit the object and save',
};
m['SYS_MSG_0762'] = {
alert_type: 'error',
alertDisplay: 'console',
subject: 'Object Error - Model Not Exist',
msg: 'Model assigned to the field not exist',
};
m['SYS_MSG_0764'] = {
alert_type: 'error',
alertDisplay: 'console',
subject: 'Object Error - Empty Dataset',
msg: 'Dataset empty from fields',
};
m['SYS_MSG_0766'] = {
alert_type: 'error',
alertDisplay: 'console',
subject: 'Object Error - Field Type Mismatch',
msg: 'Field type not match to the underlined table field definition',
};
m['SYS_MSG_0768'] = {
alert_type: 'error',
alertDisplay: 'console',
subject: 'Object Error - Field Mask Mismatch',
msg: 'Field masks not match to the underlined table field definition',
};
m['SYS_MSG_0770'] = {
alert_type: 'error',
alertDisplay: 'console',
subject: 'Object Error - Bad Event Name',
msg: 'Event name is invalid or cannot contain any of non word characters',
};
m['SYS_MSG_0772'] = {
alert_type: 'error',
alertDisplay: 'console',
subject: 'Object Error - UI Field Not Exist',
msg: 'UI Field not exist in the dataset repository',
};
m['SYS_MSG_0774'] = {
alert_type: 'error',
alertDisplay: 'console',
subject: 'Object Error - UI Field Reference Broken',
msg: 'UI Field reference broken',
};
m['SYS_MSG_0780'] = {
alert_type: 'error',
alertDisplay: 'toast',
subject: 'UI element error',
msg: 'UI element not exist',
};
m['SYS_MSG_1210'] = {
alert_type: 'error',
alertDisplay: 'modal',
subject: 'Program error',
msg: 'Program not exist',
};
m['SYS_MSG_1220'] = {
alert_type: 'error',
alertDisplay: 'modal',
subject: 'Program error',
msg: 'Non grid output defined',
};
m['SYS_MSG_1240'] = {
alert_type: 'error',
alertDisplay: 'toast',
subject: 'Debug error',
msg: '',
};
m['SYS_MSG_1250'] = {
alert_type: 'error',
alertDisplay: 'toast',
subject: 'Debug log error',
msg: '',
};
m['SYS_MSG_1260'] = {
alert_type: 'error',
alertDisplay: 'toast',
subject: 'Session Expired',
msg: 'Renew token session in Studio',
};
return m;
};
func.utils.find_key_in_ViewUITreeObj = function (arr, key, val) {
return arr.reduce((a, item) => {
if (a) return a;
if (item[key] === val) return item;
if (item.children) return findId(val, item.children);
}, null);
};
func.utils.get_plugin_setup = function (SESSION_ID, plugin_name) {
const _session = SESSION_OBJ[SESSION_ID];
const normalize_setup_response = function (value) {
if (value && typeof value === 'object' && Object.prototype.hasOwnProperty.call(value, 'code')) {
return value;
}
return {
code: 1,
data: value && typeof value === 'object' ? value : {},
};
};
const report_error = function (descP, warn) {
func.utils.debug.log(SESSION_ID, plugin_name, {
module: 'plugin',
action: 'Init',
source: 'get_plugin_setup',
prop: descP,
details: descP,
result: null,
error: warn ? false : true,
fields: null,
type: 'plugin',
});
};
return new Promise(async (resolve, reject) => {
try {
const db = await func.utils.connect_pouchdb(SESSION_ID);
const should_bypass_cache =
_session?.worker_type === 'Dev' ||
(_session?.engine_mode === 'miniapp' && !_session?.app_token);
if (!should_bypass_cache) {
try {
let ret = await db.get(`cache_plugin_setup_${plugin_name}`);
return resolve(normalize_setup_response(ret.data));
} catch (err) {
// cache miss
}
}
const json = normalize_setup_response(await func.common.db(SESSION_ID, 'get_plugin_setup', {
plugin_name,
}));
if (json.code < 0) {
report_error('Error: ' + json.data, json.error_type === 'W' ? true : false);
}
resolve(json);
if (!should_bypass_cache) {
var doc = {
_id: `cache_plugin_setup_${plugin_name}`,
data: json,
docType: 'cache_plugin',
};
db.put(doc);
}
} catch (e) {
console.error(e);
const error_message = e?.message || e?.msg || String(e || 'Unknown plugin setup error');
report_error('Error: ' + error_message, e?.error_type === 'W' ? true : false);
resolve({
code: -1,
data: error_message,
error_type: e?.error_type,
});
}
});
};
func.utils.connect_studio_pouchdb = function (app_id, rt, custom) {
if (custom) {
return new PouchDB(custom, { auto_compaction: true });
}
var db_name = 'xuda_studio_db';
if (app_id) {
db_name += '_' + app_id;
}
if (rt) {
db_name = `xuda_rt_${app_id}`;
}
return new PouchDB(db_name, { auto_compaction: true });
};
func.utils.connect_pouchdb = async function (SESSION_ID) {
const app_id = SESSION_OBJ[SESSION_ID].app_id;
return func.utils.connect_studio_pouchdb(app_id, true);
};
func.utils.base64_encode_utf8 = function (value = '') {
if (typeof btoa === 'function') {
return btoa(unescape(encodeURIComponent(value)));
}
if (typeof Buffer !== 'undefined') {
return Buffer.from(value, 'utf8').toString('base64');
}
throw new Error('base64 encoder unavailable');
};
func.utils.should_use_local_studio_plugin_resources = function (SESSION_ID) {
const _session = SESSION_OBJ[SESSION_ID];
return typeof IS_PROCESS_SERVER === 'undefined' && (['live_preview'].includes(_session?.engine_mode) || _session?.is_draft_runtime);
};
func.utils.get_local_studio_plugin_doc = async function (SESSION_ID, plugin_name) {
if (!func.utils.should_use_local_studio_plugin_resources(SESSION_ID)) {
return null;
}
try {
const db = func.utils.connect_studio_pouchdb(null, false, 'xuda_studio_resources');
return await db.get(plugin_name);
} catch (error) {
return null;
}
};
func.utils.get_local_studio_plugin_file = async function (SESSION_ID, plugin_name, resource) {
const plugin_doc = await func.utils.get_local_studio_plugin_doc(SESSION_ID, plugin_name);
return plugin_doc?.files?.[`${plugin_name}/${resource}`] || null;
};
func.utils.get_local_studio_plugin_resource_url = async function (SESSION_ID, plugin_name, resource) {
const file_contents = await func.utils.get_local_studio_plugin_file(SESSION_ID, plugin_name, resource);
if (!file_contents) {
return null;
}
const content_type = resource.endsWith('.css') ? 'text/css' : 'text/javascript';
return `data:${content_type};base64,${func.utils.base64_encode_utf8(file_contents)}`;
};
// func.utils.validate_pouchdb = async function (SESSION_ID) {
// const app_id = SESSION_OBJ[SESSION_ID].app_id;
// const db = new PouchDB("xuda_rt_" + app_id);
// // db.info()
// // .then((info) => {
// // console.log("Database exists", info);
// // })
// // .catch((error) => {
// // if (error.status === 404) {
// // throw new Error("Database does not exist");
// // } else {
// // throw new Error("Error accessing database", error);
// // }
// // });
// try {
// let ret = await db.get(`cache_rt_info`);
// } catch (error) {
// throw new Error("new pouch detect");
// }
// };
func.utils.call_plugin_api = function (SESSION_ID, plugin_nameP, dataP) {
var _session = SESSION_OBJ[SESSION_ID];
const report_error = function (descP, warn) {
func.utils.debug.log(SESSION_ID, plugin_nameP, {
module: 'plugin',
action: 'Init',
source: 'call_plugin_api',
prop: descP,
details: descP,
result: null,
error: warn ? false : true,
fields: null,
type: 'plugin',
});
};
return new Promise(async (resolve) => {
var data = {
app_id: APP_OBJ[_session.app_id]._id,
debug: glb.DEBUG_MODE,
uid: _session.USR_OBJ._id,
gtp_token: _session.gtp_token,
app_token: _session.app_token,
};
data = Object.assign(data, dataP);
fetch(`https://xuda.ai/ppi/${plugin_nameP}`, {
method: 'POST',
headers: {
Accept: 'application/json',
'Content-Type': 'application/json',
},
body: JSON.stringify(data),
})
.then((response) => {
if (!response.ok) {
return response.text().then((text) => {
throw new Error(text);
});
}
return response.json();
})
.then((json) => {
if (json.code < 0) {
report_error('Error: ' + json.data, json.error_type === 'W' ? true : false);
}
resolve(json.data);
// var doc = {
// _id: `cache_plugin_${plugin_nameP}`,
// data: json,
// docType: "cache_plugin",
// };
// db.put(doc);
})
.catch((err) => {
report_error('Error: ' + err.message);
resolve(err.message);
});
// }
});
};
func.utils.get_plugin_resource = function (SESSION_ID, plugin_name, plugin_resource) {
var _session = SESSION_OBJ[SESSION_ID];
const resource_ts =
(typeof globalThis !== 'undefined' ? globalThis.__XU_RUNTIME_MODULE_CACHE_TAG__ : '') ||
_session?.build_info?.runtime_ts ||
_session?.build_info?.last_changed_ts ||
_session?.build_info?.server_ts ||
_session?.opt?.app_build_id ||
(typeof globalThis !== 'undefined' ? globalThis.__XU_SERVER_BOOTSTRAP__?.version : 0) ||
0;
const plugin_resource_ts = `${resource_ts}-plugin-20260505-1`;
const get_path = function (resource) {
const server_origin = typeof globalThis !== 'undefined' ? globalThis.__XU_SERVER_ORIGIN__ : '';
if (server_origin) {
return `${server_origin}/plugins/${plugin_name}/${resource}?app_id=${_session.app_id}&ts=${plugin_resource_ts}`;
}
if (_session.worker_type === 'Dev') {
return `../../plugins/${plugin_name}/${resource}?ts=${plugin_resource_ts}`;
}
if (typeof IS_PROCESS_SERVER !== 'undefined') {
return `${_conf.plugins_drive_path}/${_session.app_id}/node_modules/${plugin_name}/${resource}`;
} else {
// return `./node_modules/${plugin_name}/${resource}?app_id=${_session.app_id}`;
return `https://${_session.domain}/plugins/${plugin_name}/${resource}?app_id=${_session.app_id}&ts=${plugin_resource_ts}`;
}
};
return new Promise(async (resolve, reject) => {
try {
const local_plugin_resource_url = await func.utils.get_local_studio_plugin_resource_url(SESSION_ID, plugin_name, plugin_resource);
if (local_plugin_resource_url) {
const plugin_resource_res = await import(/* @vite-ignore */ local_plugin_resource_url);
return resolve(plugin_resource_res);
}
} catch (err) {}
// const db = await func.utils.connect_pouchdb(SESSION_ID);
// try {
// if (_session.worker_type === "Dev") throw "bypass cache";
// let ret = await db.get(`cache_plugin_${plugin_name}_${plugin_resource}`);
// return resolve(ret.data);
// } catch (err) {
// try {
// const plugin_resource_res = await import(
// `${get_path(plugin_resource)}`
// );
// resolve(plugin_resource_res);
// // var doc = {
// // _id: `cache_plugin_${plugin_name}_${plugin_resource}`,
// // data: JSON.stringify(plugin_resource_res),
// // docType: "cache_plugin",
// // };
// // db.put(doc);
// } catch (err) {
// console.error(err);
// }
// }
try {
const plugin_resource_res = await import(`${get_path(plugin_resource)}`);
resolve(plugin_resource_res);
} catch (err) {
await func.utils.report_issue(SESSION_ID, {
code: 'RUN_MSG_GUI_020',
source: 'func.utils.get_plugin_resource',
message: 'plugin setup import failed',
err,
details: {
plugin_name,
plugin_resource,
plugin_path: get_path(plugin_resource),
},
});
reject(err);
}
});
};
func.utils.remove_cached_objects = async function (SESSION_ID) {
if (typeof IS_DOCKER !== 'undefined' || typeof IS_PROCESS_SERVER !== 'undefined') return;
try {
const db = await func.utils.connect_pouchdb(SESSION_ID);
let opt = {
$or: [
{
docType: 'cache_objects',
},
{
docType: 'cache_plugin',
},
{
docType: 'cache_app',
},
{
docType: 'cache_build_info',
},
],
};
const res = await db.find({ selector: opt });
for await (let val of res.docs) {
await db.remove(val);
}
} catch (err) {
return;
}
};
func.utils.get_plugin_npm_cdn = async function (SESSION_ID, plugin_name, resource) {
const _session = SESSION_OBJ[SESSION_ID];
const resource_ts =
(typeof globalThis !== 'undefined' ? globalThis.__XU_RUNTIME_MODULE_CACHE_TAG__ : '') ||
_session?.build_info?.runtime_ts ||
_session?.build_info?.last_changed_ts ||
_session?.build_info?.server_ts ||
_session?.opt?.app_build_id ||
(typeof globalThis !== 'undefined' ? globalThis.__XU_SERVER_BOOTSTRAP__?.version : 0) ||
0;
const plugin_resource_ts = `${resource_ts}-plugin-20260505-1`;
const local_plugin_resource_url = await func.utils.get_local_studio_plugin_resource_url(SESSION_ID, plugin_name, resource);
if (local_plugin_resource_url) {
return local_plugin_resource_url;
}
const get_path = function (resource) {
const server_origin = typeof globalThis !== 'undefined' ? globalThis.__XU_SERVER_ORIGIN__ : '';
if (server_origin) {
return `${server_origin}/plugins/${plugin_name}/${resource}?app_id=${_session.app_id}&ts=${plugin_resource_ts}`;
}
if (_session.worker_type === 'Dev') {
return `../../plugins/${plugin_name}/${resource}?ts=${plugin_resource_ts}`;
}
return `https://${_session.domain}/plugins/${plugin_name}/${resource}?app_id=${_session.app_id}&ts=${plugin_resource_ts}`;
};
return get_path(resource);
};
func.utils.write_log = async function (SESSION_ID, method = '', msg = '', log_type = 'error', source = 'runtime', details, meta = {}) {
const _session = SESSION_OBJ[SESSION_ID];
const body = {
msg,
log_type,
source,
details,
method,
...meta,
};
const is_offline =
(typeof IS_ONLINE !== 'undefined' && !IS_ONLINE) ||
(typeof navigator !== 'undefined' && navigator.onLine === false);
if (_session?.is_draft_runtime && is_offline) {
return null;
}
if (typeof IS_API_SERVER !== 'undefined' || typeof IS_DOCKER !== 'undefined' || typeof IS_PROCESS_SERVER !== 'undefined') {
return __.rpi.write_log(
_session?.app_id,
log_type,
source,
msg,
details,
null,
body,
method,
_session?.SYS_GLOBAL_OBJ_CLIENT_INFO?.fingerprint,
);
}
if (glb.IS_WORKER) {
let obj = {
service: 'write_log',
data: body,
log_type,
id: STUDIO_WEBSOCKET_CONNECTION_ID,
uid: _session?.USR_OBJ?._id,
source,
app_id: _session?.app_id,
gtp_token: _session?.gtp_token,
app_token: _session?.app_token,
};
return func.utils.post_back_to_client(SESSION_ID, 'write_log', _session.worker_id, obj);
}
await func.common.db(SESSION_ID, 'write_log', body);
};
func.utils.get_error_catalog_manifest = async function (SESSION_ID) {
const registry = await func.utils.get_error_registry(SESSION_ID);
return registry?.get_error_catalog_manifest?.() || null;
};
func.utils.get_resource_filename = function (build, filename) {
if (build) {
return filename.replace(/(\.\w+)$/, `.${build}$1`);
}
return filename;
};
func.utils.set_SYS_GLOBAL_OBJ_WIDGET_INFO = async function (SESSION_ID, docP) {
var obj = { ...docP };
obj.date = await func.utils.get_dateTime(SESSION_ID, 'SYS_DATE', docP.date);
obj.time = await func.utils.get_dateTime(SESSION_ID, 'SYS_TIME', docP.date);
var datasource_changes = {
[0]: {
['data_system']: {
['SYS_GLOBAL_OBJ_WIDGET_INFO']: obj,
},
},
};
await func.datasource.update(SESSION_ID, datasource_changes);
};
func.utils.get_last_datasource_no = function (SESSION_ID) {
if (typeof IS_PROCESS_SERVER !== 'undefined') {
return Object.keys(SESSION_OBJ[SESSION_ID].DS_GLB).at?.(-1);
} else {
const filtered = Object.values(SESSION_OBJ[SESSION_ID].DS_GLB).filter((e) => e.tree_obj.menuType !== 'api');
return filtered?.at?.(-1)?.dsSession;
}
};
func.events = {};
func.events._debug_summarize_value = function (value) {
if (Array.isArray(value)) {
return {
type: 'array',
length: value.length,
first_keys: value[0] && typeof value[0] === 'object' ? Object.keys(value[0]).slice(0, 8) : [],
};
}
if (value && typeof value === 'object') {
return {
type: 'object',
keys: Object.keys(value).slice(0, 12),
};
}
if (typeof value === 'string') {
return {
type: 'string',
length: value.length,
empty: value.length === 0,
};
}
return {
type: typeof value,
value,
};
};
func.events._debug_summarize_object = function (obj) {
const ret = {};
if (!obj || typeof obj !== 'object') return ret;
for (const [key, value] of Object.entries(obj)) {
ret[key] = func.events._debug_summarize_value(value);
}
return ret;
};
func.events._debug_trace_save_asset = async function (SESSION_ID, label, payload, dsSessionP) {
try {
const fields = {};
for (const field_id of ['files_v', 'view_v', 'wysiwyg_v', 'open_modal_v']) {
try {
const field_ret = await func.datasource.get_value(SESSION_ID, field_id, dsSessionP);
fields[field_id] = {
found: !!field_ret?.found,
dsSessionP: field_ret?.dsSessionP,
currentRecordId: field_ret?.currentRecordId,
value: func.events._debug_summarize_value(field_ret?.ret?.value),
};
} catch (err) {
fields[field_id] = {
error: err?.message || String(err),
};
}
}
globalThis.__XUDA_RT_TRACE && console.log('[xuda-runtime] save_asset_trace ' +
JSON.stringify({
label,
dsSessionP,
...payload,
fields,
}),
);
} catch (err) {
console.warn('[xuda-runtime] save_asset_trace_failed', err);
}
};
func.events.validate = async function (SESSION_ID, triggerP, dsSessionP, eventIdP, sourceP, argumentsP, return_validation_onlyP, event_optionsP) {
var _session = SESSION_OBJ[SESSION_ID];
var _ds = _session.DS_GLB[dsSessionP];
const event_options = event_optionsP && typeof event_optionsP === 'object' ? event_optionsP : {};
var args = {
triggerP,
dsSessionP,
eventIdP,
sourceP,
argumentsP,
return_validation_onlyP,
event_options,
};
const search_event_in_parent_ds = async function () {
if (_ds && typeof _ds.parentDataSourceNo !== 'undefined') {
await func.events.validate(SESSION_ID, triggerP, _ds.parentDataSourceNo, eventIdP, sourceP, argumentsP, return_validation_onlyP, event_options);
}
};
var ret = false;
var jobs = [];
// var success;
if (_ds?.prog_id) {
const _view_obj = await func.utils.VIEWS_OBJ.get(SESSION_ID, _ds.prog_id);
// check view exist
if (!glb.IS_WORKER) _ds.event_stat_obj = {};
if (_view_obj.progEvents) {
if (_session.api_callback && eventIdP) {
_session.api_callback(eventIdP, SESSION_ID, SESSION_OBJ);
}
// check events has rows
for await (let val of _view_obj.progEvents) {
//run events rows
var eventProp = undefined;
if (val.data.type === triggerP) {
// compare event trigger
if ((triggerP !== 'user_defined') | (triggerP === 'user_defined' && eventIdP === val.data.event_name)) {
// compare user defined name
var expCond;
if (val.data.condition) expCond = await func.expression.get(SESSION_ID, val.data.condition, dsSessionP, 'condition');
if (!val.data.condition || expCond.result) {
func.utils.debug.watch(SESSION_ID, _ds.prog_id + '%' + val.id, 'view_event', val, triggerP + ' ' + eventIdP, expCond);
ret = true;
if (return_validation_onlyP) break;
const set_arguments = async function () {
var args = argumentsP || {};
// for await (let [key, fieldId] of Object.entries(
// val.data.parameters
// )) {
// const field_info = func.common.find_item_by_key(
// _view_obj.progFields,
// "field_id",
// fieldId
// );
// var value = await func.common.get_cast_val(
// SESSION_ID,
// "events",
// fieldId,
// field_info.props.fieldType,
// args[fieldId]
// );
// const ret = await func.datasource.get_value(
// SESSION_ID,
// fieldId,
// dsSessionP,
// _ds.currentRecordId
// );
// const datasource_changes = {
// [ret.dsSessionP]: {
// [ret.currentRecordId]: { [fieldId]: value },
// },
// };
// await func.datasource.update(SESSION_ID, datasource_changes);
// }
for await (let [key, fieldId] of Object.entries(val.data.parameters)) {
const field_info = func.common.find_item_by_key(_view_obj.progFields, 'field_id', fieldId);
if (field_info?.data?.type !== 'virtual') {
console.warn('parameter field must be virtual, update ignored');
continue;
}
if (!args[fieldId]) continue;
let value = await func.common.get_cast_val(SESSION_ID, 'events', fieldId, field_info.props.fieldType, args[fieldId].value);
if (!xu_isEmpty(args[fieldId].fx)) {
const fx_ret = await func.expression.get(SESSION_ID, args[fieldId].fx, dsSessionP, 'update');
value = fx_ret.result;
}
// find the target field in the program dataset
const ret = await func.datasource.get_value(SESSION_ID, fieldId, dsSessionP, _ds.currentRecordId);
const datasource_changes = {
[ret.dsSessionP]: {
[ret.currentRecordId]: { [fieldId]: value },
},
};
await func.datasource.update(SESSION_ID, datasource_changes, null, event_options.avoid_refresh === true);
}
await add_event();
};
const add_event = async function () {
const _event = func.common.find_item_by_key_root(_view_obj.progEvents, 'id', val.id);
if (_event.workflow) {
// check if event property exist
if (!_event.workflow || xu_isEmpty(_event.workflow)) return;
// check events has rows
for (const trigger_obj of _event.workflow) {
//run events rows
if (!trigger_obj.data.action) continue;
if (!trigger_obj.data.enabled) continue;
// condition ok
var callingEventId = val.data.event_name;
if (!callingEventId) callingEventId = val.id; // default event id when event name is missing
// eventProp = trigger_obj;
// success = true;
const ref_id = trigger_obj.data.name;
var container = undefined;
var screen_prop = undefined;
if (!glb.IS_WORKER) {
if (_ds.panel_div_id) {
try {
container = '#' + _ds.panel_div_id;
const panel_meta = func.runtime.ui.get_meta_by_element_id(_ds.panel_div_id);
if (panel_meta?.xuData?.panel_info) {
screen_prop = panel_meta.xuData.panel_info.paramsP;
} else {
///////////////
container = '#' + _session.DS_GLB[dsSessionP].screenId;
const screen_meta = func.runtime.ui.get_meta_by_element_id(_session.DS_GLB[dsSessionP].screenId);
if (screen_meta?.xuData) {
screen_prop = screen_meta.xuData.paramsP;
}
if (!screen_meta) {
container = '#' + _session.DS_GLB[dsSessionP].containerId;
}
//////////////
}
} catch (e) {
console.error(e);
}
} else {
container = '#' + _ds.screenId;
const screen_meta = func.runtime.ui.get_meta_by_element_id(_ds.screenId);
if (screen_meta?.xuData) {
screen_prop = screen_meta.xuData.paramsP;
}
if (!screen_meta) {
container = '#' + _ds.containerId;
}
}
} else {
screen_prop = {
callingContainerP: _ds.containerId,
};
}
jobs.push(
await func.events.add_to_queue(
SESSION_ID,
sourceP + ' event',
trigger_obj.id,
null, // was click
trigger_obj.data.action,
ref_id,
container,
null,
_ds.currentRecordId,
null,
trigger_obj.data.name,
null,
null,
dsSessionP,
null,
null,
trigger_obj,
triggerP,
screen_prop,
null, // was target frame id
null,
trigger_obj,
trigger_obj.data.parameter_source_data,
val.id,
null,
args,
null,
null,
event_options,
),
);
}
}
};
if (val.data.parameters) {
await set_arguments();
} else {
await add_event();
}
} else {
if (val.data.condition && !expCond.result) {
func.utils.debug.watch(SESSION_ID, _ds.prog_id + '%' + val.id, 'view_event', val, triggerP + ' ' + eventIdP, expCond, true);
}
}
}
}
}
}
}
if (return_validation_onlyP) return ret;
if (!ret) await search_event_in_parent_ds();
return jobs;
};
func.events.add_to_queue = async function (
SESSION_ID,
typeP,
eventIdP,
triggerP,
functionP,
refIdP,
containerP,
elementP,
rowP,
evt,
descP,
NA_rootScreenIdP,
NA_callingEventIdP,
dsSessionP,
NA_isInitP,
NA_calling_program,
event_propertiesP,
calling_triggerP,
paramsP,
NA_target_frame_idP,
_NA2,
calling_trigger_prop,
argumentsP,
source_event_idP,
calling_job,
args,
$div,
$container,
event_optionsP,
) {
var _session = SESSION_OBJ[SESSION_ID];
var obj = {
SESSION_ID,
typeP,
eventIdP,
triggerP,
functionP,
refIdP,
containerP,
elementP,
rowP,
descP,
dsSessionP,
event_propertiesP,
calling_triggerP,
paramsP,
calling_trigger_prop,
argumentsP,
source_event_idP,
calling_job,
args,
$div,
$container,
event_optionsP,
evt,
job_num: _session.WORKER_OBJ.num,
};
var _ds = _session.DS_GLB[dsSessionP];
if (!_ds) return;
if (typeof dsSessionP !== 'undefined' && dsSessionP !== null) {
obj.prog_id = _ds.prog_id;
obj.parentDataSourceNo = _ds.parentDataSourceNo;
obj.nodeId = _ds.nodeId;
}
///////
if (glb.IS_WORKER && func.utils.is_onscreen_event(functionP)) {
obj.client = true;
if (functionP === 'call_library') {
obj.client = false;
}
if (typeof dsSessionP !== 'undefined' && dsSessionP !== null) {
obj.ds_obj = func.utils.clean_returned_datasource(SESSION_ID, dsSessionP);
}
if (obj.client) {
_session.WORKER_OBJ.num++;
// if (!_session.IS_API)
func.utils.post_back_to_client(SESSION_ID, 'job', _session.worker_id, obj);
return;
}
}
////////
if (calling_job) {
var job_index = func.events.find_job_index(SESSION_ID, calling_job);
try {
if (!_session.WORKER_OBJ.jobs[job_index].splice_count) {
_session.WORKER_OBJ.jobs[job_index].splice_count = 0;
}
_session.WORKER_OBJ.jobs[job_index].splice_count++;
_session.WORKER_OBJ.jobs.splice(job_index + _session.WORKER_OBJ.jobs[job_index].splice_count, 0, obj);
// }
} catch (e) {
console.error('bug');
// _session.WORKER_OBJ.jobs.splice(0, 0, obj);
}
} else {
_session.WORKER_OBJ.jobs.push(obj);
}
_session.WORKER_OBJ.num++;
return _session.WORKER_OBJ.num - 1;
};
func.events.find_job_index = function (SESSION_ID, jobNoP) {
var _session = SESSION_OBJ[SESSION_ID];
var ret = null;
if (!_session.WORKER_OBJ) return ret;
for (const [key, val] of Object.entries(_session.WORKER_OBJ.jobs)) {
if (val && val.job_num == jobNoP) {
ret = key;
break;
}
}
return ret;
};
func.events.execute = async function (
SESSION_ID,
jobNoP,
eventIdP,
triggerP,
functionP,
refIdP,
containerP,
elementP,
rowP,
evt,
descP,
rootScreenIdP,
dsSessionP,
NA_callingEventIdP,
callingSourceP,
NA_isInitP,
event_propertiesP,
calling_triggerP,
calling_jobP,
paramsP,
NA_target_frame_idP,
calling_trigger_prop,
NA_calling_program,
argumentsP,
NA_viewIdP,
NA_nodeIdP,
NA_parentDataSourceNoP,
$div,
event_optionsP,
) {
var _session = SESSION_OBJ[SESSION_ID];
var _ds = _session.DS_GLB[dsSessionP];
const event_options = event_optionsP && typeof event_optionsP === 'object' ? event_optionsP : {};
const avoid_event_refresh = event_options.avoid_refresh === true;
if (functionP === 'update') refIdP = null; // in case of left over when program changing from call function to update
var job_index = func.events.find_job_index(SESSION_ID, jobNoP);
if (_session.WORKER_OBJ.jobs?.[job_index]?.stat === 'busy') {
if (jobNoP) _session.WORKER_OBJ.stat = job_index;
return;
}
if (jobNoP && !_session.WORKER_OBJ.jobs[job_index]) {
_session.WORKER_OBJ.stat = null;
return;
}
if (jobNoP) _session.WORKER_OBJ.stat = job_index;
if (jobNoP && calling_trigger_prop?.props?.async) {
func.events.delete_job(SESSION_ID, jobNoP);
_session.WORKER_OBJ.stat = null;
}
if (_session.WORKER_OBJ.jobs[job_index]) {
_session.WORKER_OBJ.jobs[job_index].stat = 'busy';
}
var dsSession = dsSessionP; // new 2020420
var field_elm = elementP;
var calling_field_id = field_elm;
if (field_elm && typeof field_elm === 'object') calling_field_id = func.runtime.ui.get_attr(field_elm, 'xu-ui-id');
var log_nodeId;
var log_prog_id;
var log_source;
if (_session.DS_GLB[dsSession]?.prog_id) log_prog_id = _session.DS_GLB[dsSession].prog_id;
log_nodeId = log_prog_id + '_' + eventIdP;
var log_prop = callingSourceP;
if (callingSourceP === 'system event') {
log_prop = 'global event';
}
if (calling_field_id) {
const _view_obj = await func.utils.VIEWS_OBJ.get(SESSION_ID, _ds.prog_id);
let _field_obj = func.common.find_item_by_key(_view_obj.progFields, 'field_id', calling_field_id);
log_nodeId = log_prog_id + '_' + eventIdP + '_' + _field_obj?.id;
log_source = calling_field_id;
}
if (elementP) {
log_prop = triggerP;
log_nodeId = log_nodeId + '_ui_prop'; //+"_"+eventIdP;
}
//check condition
var expCond;
if (event_propertiesP) {
// conditional event
if (event_propertiesP?.props?.condition) {
expCond = await func.expression.get(SESSION_ID, event_propertiesP.props.condition, dsSession, 'condition', null, null, null, calling_field_id ? calling_field_id : calling_triggerP, null, descP); // execute expression
if (
/files_v|wysiwyg_v|view_v|open_modal_v/.test(event_propertiesP.props.condition) ||
functionP === 'set_data' ||
String(refIdP?.prog || '') === '1630849293262'
) {
await func.events._debug_trace_save_asset(
SESSION_ID,
'condition_eval',
{
functionP,
ref_prog: refIdP?.prog,
eventIdP,
source_event_id: event_propertiesP?.id || calling_trigger_prop?.id,
condition: event_propertiesP.props.condition,
result: expCond?.result,
error: expCond?.error,
fields: expCond?.fields,
},
dsSession,
);
}
func.utils.debug.log(SESSION_ID, log_nodeId, {
module: 'event',
action: log_prop,
source: log_source,
prop: descP,
details: event_propertiesP.props.condition,
result: expCond.result,
error: expCond.error,
fields: expCond.fields,
type: 'event',
prog_id: log_prog_id,
conditional: true,
});
var cond = expCond.result;
if (!cond || expCond.error) {
// condition failed // === true 03/04/16
func.events.delete_job(SESSION_ID, jobNoP);
await func.events._debug_trace_save_asset(
SESSION_ID,
'condition_skip',
{
functionP,
ref_prog: refIdP?.prog,
eventIdP,
source_event_id: event_propertiesP?.id || calling_trigger_prop?.id,
condition: event_propertiesP.props.condition,
result: expCond?.result,
error: expCond?.error,
fields: expCond?.fields,
},
dsSession,
);
func.utils.debug.watch(SESSION_ID, calling_trigger_prop?.id, functionP, '', '', expCond, true);
return;
}
} // Non conditional event
else {
func.utils.debug.log(SESSION_ID, log_nodeId, {
module: 'event',
action: log_prop,
source: log_source,
prop: descP,
details: null,
result: null,
error: null,
fields: null,
type: 'event',
prog_id: log_prog_id,
});
}
}
// var log_error = function (descP, detailsP, warning) {
// func.utils.debug.log(SESSION_ID, log_nodeId, {
// module: "event",
// action: functionP,
// source: log_source,
// prop: descP,
// details: detailsP,
// result: null,
// error: !warning,
// warning: warning,
// fields: null,
// type: "event",
// prog_id: log_prog_id,
// });
// };
const get_params_obj = async function () {
const _prog_id = await get_prog_id();
const _prog = await func.utils.VIEWS_OBJ.get(SESSION_ID, _prog_id);
if (!_prog) {
func.events.delete_job(SESSION_ID, jobNoP);
return func.utils.debug_report(SESSION_ID, 'func.events.execute', 'Program not found: ' + refIdP.prog, 'E');
}
// get in parameters
var params_obj = {};
if (_prog?.properties?.progParams) {
for await (const [key, val] of Object.entries(_prog.properties.progParams)) {
// if (val.data.dir === 'in') continue;
if (typeof args.parameters_obj_inP?.[val.data.parameter] !== 'undefined') {
if (args.parameters_obj_inP?.[val.data.parameter].fx) {
let ret = await func.expression.get(SESSION_ID, args.parameters_obj_inP?.[val.data.parameter].fx, dsSession, 'parameters');
params_obj[val.data.parameter] = ret.result;
} else {
params_obj[val.data.parameter] = args.parameters_obj_inP?.[val.data.parameter].value;
}
continue;
}
if (
val.data.parameter === 'REDUCE_VALUE' &&
typeof args.parameters_obj_inP?.REDUCE_COUNTER !== 'undefined'
) {
const legacy_reduce_counter = args.parameters_obj_inP.REDUCE_COUNTER;
if (legacy_reduce_counter.fx) {
let ret = await func.expression.get(SESSION_ID, legacy_reduce_counter.fx, dsSession, 'parameters');
params_obj[val.data.parameter] = ret.result;
} else {
params_obj[val.data.parameter] = legacy_reduce_counter.value;
}
try {
globalThis.__XUDA_RT_TRACE && console.log('[xuda-runtime] legacy_reduce_counter_alias ' +
JSON.stringify({
program: _prog.properties.menuName,
parameter: val.data.parameter,
target: params_obj[val.data.parameter],
}),
);
} catch (e) {}
continue;
}
console.warn(`Warning: Program ${_prog.properties.menuName} expected In parameter: ${val.data.parameter} but received null instead`);
}
}
if (functionP === 'set_data' || String(_prog_id || '') === '1630849293262') {
await func.events._debug_trace_save_asset(
SESSION_ID,
'set_data_params',
{
functionP,
target_prog: _prog_id,
menuName: _prog?.properties?.menuName,
params: func.events._debug_summarize_object(params_obj),
},
dsSession,
);
}
return params_obj;
};
const get_prog_id = async function () {
let _prop = args?.calling_trigger_prop?.data?.name?.properties;
let _prog_id = args.prog_id;
if (_prop?.['xu-exp:prog']) {
_prog_id = (await func.expression.get(SESSION_ID, _prop['xu-exp:prog'], dsSession, 'prog_id expression')).result;
}
return _prog_id;
};
// const get_args_property_value = async function (prop_name) {
// let _prop = args?.calling_trigger_prop?.data?.name;
// let _value = _prop[prop_name];
// if (_prop?.[`xu-exp:${prop_name}`]) {
// _value = (
// await func.expression.get(
// SESSION_ID,
// _prop[`xu-exp:${prop_name}`],
// dsSession,
// `${prop_name} expression`
// )
// ).result;
// }
// return _value;
// };
var args = {
prog_id: refIdP?.prog,
screenIdP: refIdP?.prog,
callingFieldIdP: field_elm,
dataSourceNoP: null,
parentDataSourceNoP: dsSession,
triggerIdP: eventIdP,
containerIdP: null,
rowIdP: rowP, //sub_row_idP ? sub_row_idP : rowP,
jobNoP: jobNoP,
callingSourceP: callingSourceP,
calling_jobP: calling_jobP,
screen_dsP: null,
is_panelP: null,
argument_listP: null,
calling_trigger_prop: calling_trigger_prop,
parameters_obj_inP: refIdP?.parameters,
call_screen_propertiesP: refIdP?.properties,
};
const get_runtime_module_with_method = async function (module_name, method_name) {
const load_module = async function () {
const module_ret = await func.common.get_module(SESSION_ID, module_name);
if (typeof module_ret?.[method_name] === 'function') {
return module_ret;
}
if (typeof module_ret?.default?.[method_name] === 'function') {
return module_ret.default;
}
return module_ret;
};
let module_ret = await load_module();
if (typeof module_ret?.[method_name] === 'function') {
return module_ret;
}
for (const key of Object.keys(func.common._import_cache || {})) {
if (key.includes(module_name)) {
delete func.common._import_cache[key];
}
}
if (typeof globalThis !== 'undefined') {
globalThis.__XU_RUNTIME_MODULE_CACHE_TAG__ = Date.now();
}
module_ret = await load_module();
if (typeof module_ret?.[method_name] === 'function') {
return module_ret;
}
throw new TypeError(`${module_name}.${method_name} is not available`);
};
const fx = {
Call_window: async function () {
var is_panel;
var $calling_container;
if (_session.WORKER_OBJ.jobs[job_index]) {
if (_session.WORKER_OBJ.jobs[job_index].paramsP) {
$calling_container = func.runtime.ui.find_element_by_id(_session.WORKER_OBJ.jobs[job_index].paramsP.callingContainerP);
} else {
$calling_container = ''; // calling from datasource 0
_session.WORKER_OBJ.jobs[job_index].paramsP = {};
}
}
if (!refIdP.prog) {
func.events.delete_job(SESSION_ID, jobNoP);
return func.utils.debug_report(SESSION_ID, 'func.events.execute', 'Program is empty', 'E');
}
const params_obj = await get_params_obj();
return await func.runtime.ui.init_screen({
SESSION_ID,
prog_id: await get_prog_id(),
sourceScreenP: func.runtime.ui.get_data(containerP)?.xuData?.screenId,
callingDataSource_objP: _session.DS_GLB[dsSession],
$callingContainerP: $calling_container,
triggerIdP: eventIdP,
rowIdP: rowP,
jobNoP,
is_panelP: is_panel,
parameters_obj_inP: params_obj,
source_functionP: functionP,
call_screen_propertiesP: args.call_screen_propertiesP,
});
},
call_modal: async function () {
return await fx.Call_window();
},
call_popover: async function () {
return await fx.Call_window();
},
call_page: async function () {
return await fx.Call_window();
},
call_library: async function () {
let plugin_name = refIdP.plugin_name,
method = refIdP.library_method,
$containerP = $div,
dsP = dsSession,
propsP = refIdP.library_props,
sourceP = descP;
var _session = SESSION_OBJ[SESSION_ID];
const set_SYS_GLOBAL_OBJ_WIDGET_INFO = async function (docP) {
var obj = { ...docP };
obj.date = await func.utils.get_dateTime(SESSION_ID, 'SYS_DATE', docP.date);
obj.time = await func.utils.get_dateTime(SESSION_ID, 'SYS_TIME', docP.date);
var datasource_changes = {
[0]: {
['data_system']: {
['SYS_GLOBAL_OBJ_WIDGET_INFO']: obj,
},
},
};
await func.datasource.update(SESSION_ID, datasource_changes, null, avoid_event_refresh);
};
const call_plugin_api = async function (plugin_nameP, dataP) {
return await func.utils.call_plugin_api(SESSION_ID, plugin_nameP, dataP);
};
const report_error = function (descP, warn) {
func.utils.debug.log(SESSION_ID, _session.DS_GLB[dsP].prog_id + '_' + _session.DS_GLB[dsP].callingMenuId, {
module: 'widgets',
action: 'Init',
source: sourceP,
prop: descP,
details: descP,
result: null,
error: warn ? false : true,
fields: null,
type: 'widgets',
prog_id: _session.DS_GLB[dsP].prog_id,
});
};
const get_fields_data = async function (fields, props) {
const report_error = function (descP, warn) {
func.utils.debug.log(SESSION_ID, _session.DS_GLB[dsP].prog_id + '_' + _session.DS_GLB[dsP].callingMenuId, {
module: 'widgets',
action: 'Init',
source: sourceP,
prop: descP,
details: descP,
result: null,
error: warn ? false : true,
fields: null,
type: 'widgets',
prog_id: _session.DS_GLB[dsP].prog_id,
});
};
const get_property_value = async function (fieldIdP, val) {
// var value = props[fieldIdP];
var value = props[fieldIdP] || (typeof val.defaultValue === 'function' ? val?.defaultValue?.() : val?.defaultValue);
if (props[`xu-exp:${fieldIdP}`]) {
value = (await func.expression.get(SESSION_ID, props[`xu-exp:${fieldIdP}`], dsP, 'widget property')).result;
}
return func.common.get_cast_val(
SESSION_ID,
'widgets',
fieldIdP,
val.type, //val.type !== "string" || val.type !== "number" ? "string" : val.type,
value,
null,
);
};
var data_obj = {};
var return_code = 1;
// $.each(fields, function (key, val) {
for await (const [key, val] of Object.entries(fields)) {
try {
data_obj[key] = await get_property_value(key, val);
if (!data_obj[key] && val.mandatory) {
return_code = -1;
report_error(`${key} is a mandatory field.`);
break;
}
// console.log(val);
} catch (error) {
console.error('[xuda-runtime] caught xuda_events.js:723:', error);
}
}
return { code: return_code, data: data_obj };
};
try {
const _plugin = APP_OBJ[_session.app_id]?.app_plugins_purchased?.[plugin_name];
const index = await func.utils.get_plugin_resource(SESSION_ID, plugin_name, `${_plugin.manifest['index.mjs'].dist ? 'dist/' : ''}index.mjs`);
const methods = index.methods;
if (methods && !methods[method]) {
return report_error('method not found');
}
const fields_ret = await get_fields_data(methods[method].fields, propsP);
if (fields_ret.code < 0) {
return report_error(fields_ret.data);
}
const fields = fields_ret.data;
const plugin_setup_ret = await func.utils.get_plugin_setup(SESSION_ID, plugin_name);
if (plugin_setup_ret.code < 0) {
return report_error(plugin_setup_ret);
}
const api_utils = await func.common.get_module(SESSION_ID, 'xuda-api-library.mjs', {
func,
glb,
SESSION_OBJ,
SESSION_ID,
APP_OBJ,
dsSession: dsP,
job_id: jobNoP,
});
const params = {
SESSION_ID,
method,
_session,
dsP,
sourceP,
propsP,
plugin_name,
$containerP,
plugin_setup: plugin_setup_ret.data,
report_error,
call_plugin_api,
set_SYS_GLOBAL_OBJ_WIDGET_INFO,
api_utils,
};
const fx = await func.utils.get_plugin_resource(SESSION_ID, plugin_name, `${_plugin.manifest['runtime.mjs'].dist ? 'dist/' : ''}runtime.mjs`);
if (!fx[method]) {
throw `Method: ${method} does not exist`;
}
await fx[method](fields, params);
} catch (err) {
report_error(err);
}
func.events.delete_job(SESSION_ID, jobNoP);
},
call_native_javascript: async function () {
const module = await get_runtime_module_with_method('xuda-event-javascript-module.mjs', 'call_javascript');
const result = await module.call_javascript(SESSION_ID, jobNoP, refIdP, dsSession, false, $div);
await func.datasource.set_outputField(SESSION_ID, dsSessionP, result, args, avoid_event_refresh);
return result;
},
call_evaluate_javascript: async function () {
const module = await get_runtime_module_with_method('xuda-event-javascript-module.mjs', 'call_javascript');
const result = await module.call_javascript(SESSION_ID, jobNoP, refIdP, dsSession, true, $div);
await func.datasource.set_outputField(SESSION_ID, dsSessionP, result, args, avoid_event_refresh);
return result;
},
execute_native_javascript: async function () {
const module = await get_runtime_module_with_method('xuda-event-javascript-module.mjs', 'run_javascript');
const resolved_element_expr = `(func.runtime.ui && func.runtime.ui.find_xu_ui_in_root && func.runtime.ui.get_first_node ? func.runtime.ui.get_first_node(func.runtime.ui.find_xu_ui_in_root(SESSION_ID, ${JSON.stringify(elementP)})) : null)`;
const result = await module.run_javascript(
SESSION_ID,
jobNoP,
dsSession,
`(async function(el,evt) {
${refIdP.value}
})(${resolved_element_expr},evt)`,
null,
null,
null,
evt,
$div,
);
await func.datasource.set_outputField(SESSION_ID, dsSessionP, result, args, avoid_event_refresh);
return result;
},
execute_evaluate_javascript: async function () {
const module = await get_runtime_module_with_method('xuda-event-javascript-module.mjs', 'run_javascript');
const resolved_element_expr = `(func.runtime.ui && func.runtime.ui.find_xu_ui_in_root && func.runtime.ui.get_first_node ? func.runtime.ui.get_first_node(func.runtime.ui.find_xu_ui_in_root(SESSION_ID, ${JSON.stringify(elementP)})) : null)`;
const result = await module.run_javascript(
SESSION_ID,
jobNoP,
dsSession,
`(async function(el,evt) {
${refIdP.value}
})(${resolved_element_expr},evt)`,
true,
null,
null,
evt,
$div,
);
await func.datasource.set_outputField(SESSION_ID, dsSessionP, result, args, avoid_event_refresh);
return result;
},
loader_on: async function () {
glb.CURRENT_APP_LOADING = null;
LOADER_ACTIVE = true;
LOADER_TEXT = descP;
// }
func.events.delete_job(SESSION_ID, jobNoP);
// if (callbackP) callbackP();
},
loader_off: async function () {
LOADER_ACTIVE = false;
func.events.delete_job(SESSION_ID, jobNoP);
// if (callbackP) callbackP();
},
emit_event: async function () {
if (refIdP.value) {
// if (descP.value) {
func.runtime.platform.emit(refIdP.value, [_session.DS_GLB[dsSession]]);
} else {
func.utils.debug_report(SESSION_ID, 'func.events.execute', 'Event name missing', 'E');
}
func.events.delete_job(SESSION_ID, jobNoP);
// if (callbackP) callbackP();
},
invoke_action: async function () {
func.utils.debug.watch(SESSION_ID, calling_trigger_prop?.id, functionP, null, null, expCond);
await func.action.execute(SESSION_ID, refIdP.value, _ds, null, null, jobNoP, containerP);
},
raise_event: async function () {
var _ds = _session.DS_GLB[dsSession];
const _view_obj = await func.utils.VIEWS_OBJ.get(SESSION_ID, _ds.prog_id);
if (callingSourceP === 'grid' || callingSourceP === 'form') {
let _field_obj = func.common.find_item_by_key(_view_obj.progFields, 'field_id', field_elm);
var event_name = _field_obj?.workflow?.[eventIdP].name.event;
if (_field_obj?.workflow?.[eventIdP].name?.properties['xu-exp:event']) {
event_name = (await func.expression.get(SESSION_ID, props[`xu-exp:event`], dsSession, 'event_name expression')).result;
}
if (field_elm && event_name) {
// get container for fields events
const dsP = await func.datasource.find_event_dataSource(SESSION_ID, event_name, dsSession);
if (event_name === 'SAVE_ASSET_EVENT') {
await func.events._debug_trace_save_asset(
SESSION_ID,
'raise_event',
{
event_name,
callingSourceP,
dsP,
functionP,
source_prog: _ds?.prog_id,
},
dsSession,
);
}
return await func.datasource.run_events_functions(SESSION_ID, dsP, event_name, jobNoP, null, calling_trigger_prop?.data?.name?.parameters || {}, event_options);
}
// done_execution(event_name);
}
if (callingSourceP.includes('event')) {
let event_name = refIdP.event;
if (refIdP?.properties?.['xu-exp:event']) {
event_name = (await func.expression.get(SESSION_ID, refIdP.properties['xu-exp:event'], dsSession, 'event_name expression')).result;
}
const dsP = await func.datasource.find_event_dataSource(SESSION_ID, event_name, dsSession);
if (event_name === 'SAVE_ASSET_EVENT') {
await func.events._debug_trace_save_asset(
SESSION_ID,
'raise_event',
{
event_name,
callingSourceP,
dsP,
functionP,
source_prog: _ds?.prog_id,
},
dsSession,
);
}
await func.datasource.run_events_functions(SESSION_ID, dsP, event_name, jobNoP, calling_trigger_prop?.props?.async, calling_trigger_prop?.data?.name?.parameters || {}, event_options);
// done_execution(event_name);
}
func.events.delete_job(SESSION_ID, jobNoP);
// if (callbackP) callbackP();
func.utils.debug.watch(SESSION_ID, calling_trigger_prop?.id, functionP, '', '', expCond);
},
get_data: async function () {
const params_obj = await get_params_obj();
if (!(await get_prog_id())) {
func.utils.debug_report(SESSION_ID, 'func.events.execute', `${elementP} > ${triggerP} > ${functionP} > program ${prog} is missing`, 'E');
func.events.delete_job(SESSION_ID, jobNoP);
return;
}
var _ds = _session.DS_GLB[dsSession];
if (!_ds) {
func.events.delete_job(SESSION_ID, jobNoP);
return;
}
if (_ds) {
func.utils.debug.watch(SESSION_ID, calling_trigger_prop?.id, functionP, null, calling_trigger_prop, expCond);
const ret = await func.datasource.create(SESSION_ID, await get_prog_id(), args.dataSourceNoP, args.parentDataSourceNoP, args.containerIdP, args.rowIdP, args.jobNoP, args.calling_trigger_prop, null, null, args.callingSourceP, args.calling_jobP, args.screen_dsP, args.is_panelP, params_obj);
let _ds_new = _session.DS_GLB[ret.dsSessionP];
let parameters = args?.calling_trigger_prop?.data?.name?.parameters;
if (parameters && !xu_isEmpty(parameters)) {
await func.datasource.update_changes_for_out_parameter(SESSION_ID, _ds_new.dsSession, _ds.dsSession, avoid_event_refresh);
}
func.events.delete_job(SESSION_ID, jobNoP);
return _ds_new;
}
},
set_data: async function () {
return this.get_data();
},
batch: async function () {
const result = await this.get_data();
// await set_outputField(SESSION_ID, elementP, triggerP, functionP, dsSessionP, result);
return result;
},
update: async function () {
const resolve_update_field_id = async function (field_expr, iterate_info) {
let trimmed = field_expr?.trim?.() || '';
if (!trimmed) {
return trimmed;
}
const first = trimmed.substring(0, 1);
const last = trimmed.substring(trimmed.length - 1);
if ((first === "'" || first === '"' || first === '`') && last === first) {
trimmed = trimmed.substring(1, trimmed.length - 1).trim();
}
if (/^@?[A-Za-z_][\w\-\:\.]*$/.test(trimmed)) {
return trimmed.substring(0, 1) === '@' ? trimmed.substring(1) : trimmed;
}
let ret_field_id = await func.expression.get(SESSION_ID, trimmed, dsSessionP, 'update', null, null, null, null, null, null, iterate_info);
if (typeof ret_field_id?.result === 'string' && ret_field_id.result.substring(0, 1) === '@') {
return ret_field_id.result.substring(1);
}
return ret_field_id?.result;
};
const obj_values_to_update = func.datasource.get_viewFields_for_update_function(SESSION_ID, calling_trigger_prop, null, dsSessionP);
if (!obj_values_to_update || xu_isEmpty(obj_values_to_update)) {
func.utils.debug_report(SESSION_ID, 'Update values object is empty', '', 'W');
if (jobNoP) func.events.delete_job(SESSION_ID, jobNoP);
return;
}
var updates = [];
for await (const [key, val] of Object.entries(obj_values_to_update)) {
var $element;
var iterate_info = null;
if (elementP) {
const element_meta = func.runtime.ui.get_meta(elementP, 'xuData');
iterate_info = element_meta?.iterate_info || null;
}
let ret_value = await func.expression.get(SESSION_ID, val.val.trim(), dsSessionP, 'update', null, null, null, null, null, null, iterate_info);
let _field_id = await resolve_update_field_id(val.id, iterate_info);
let _value = ret_value.result;
updates.push({ _field_id, _value });
}
let datasource_changes = {};
for await (const change of updates) {
let ret_get_value = await func.datasource.get_value(SESSION_ID, change._field_id, dsSessionP);
if (ret_get_value.found) {
let _ds = _session.DS_GLB[ret_get_value.dsSessionP];
if (!datasource_changes[_ds.dsSession]) {
datasource_changes[_ds.dsSession] = {};
}
if (!datasource_changes[_ds.dsSession][ret_get_value.currentRecordId]) {
datasource_changes[_ds.dsSession][ret_get_value.currentRecordId] = {};
}
datasource_changes[_ds.dsSession][ret_get_value.currentRecordId][change._field_id] = change._value;
}
}
await func.datasource.update(SESSION_ID, datasource_changes, null, avoid_event_refresh, triggerP);
if (_ds.PARAM_OUT_INFO) {
for await (const [key, val] of Object.entries(_ds.PARAM_OUT_INFO)) {
await func.datasource.update_changes_for_out_parameter(SESSION_ID, _ds.dsSession, val.parentDataSourceNo, avoid_event_refresh);
}
}
if (jobNoP) func.events.delete_job(SESSION_ID, jobNoP);
// return changes;
},
call_alert: async function () {
await func.utils.alerts.invoke(SESSION_ID, 'call_alert', refIdP, log_source, dsSession);
func.events.delete_job(SESSION_ID, jobNoP);
},
alert: async function () {
await func.utils.alerts.invoke(SESSION_ID, 'alert', refIdP, log_source, dsSession);
func.events.delete_job(SESSION_ID, jobNoP);
},
delay: async function () {
return new Promise((resolve) => {
setTimeout(function () {
if (jobNoP) func.events.delete_job(SESSION_ID, jobNoP);
resolve();
}, refIdP.value);
});
},
comment: async function () {
if (jobNoP) func.events.delete_job(SESSION_ID, jobNoP);
},
call_project_api: async function () {
const params_obj = await get_params_obj();
const _prog_id = await get_prog_id();
if (!_prog_id) {
func.utils.debug_report(SESSION_ID, 'func.events.execute', `${elementP} > ${triggerP} > ${functionP} > program not defined`, 'E');
func.events.delete_job(SESSION_ID, jobNoP);
return;
}
// if (!output_field) {
// func.utils.debug_report(
// SESSION_ID,
// "func.events.execute",
// `${elementP} >${triggerP} >${functionP} > Output field not defined`,
// "W"
// );
// // func.events.delete_job(SESSION_ID, jobNoP);
// // return;
// }
const api_ret = await func.api.call_project_api(_prog_id, params_obj);
await func.datasource.set_outputField(SESSION_ID, dsSessionP, api_ret, args, avoid_event_refresh);
func.events.delete_job(SESSION_ID, jobNoP);
},
call_system_api: async function () {
const api_method = await func.datasource.get_args_property_value(SESSION_ID, dsSession, args, 'api_method');
if (!api_method) {
func.utils.debug_report(SESSION_ID, 'func.events.execute', `${elementP} >${triggerP} >${functionP} > api_method not defined`, 'E');
func.events.delete_job(SESSION_ID, jobNoP);
return;
}
let payload = {};
const _payload = await func.datasource.get_args_property_value(SESSION_ID, dsSession, args, 'payload');
if (_payload) {
const get_payload_property_value = async function (prop_name) {
let _prop = _payload;
let _value = _prop[prop_name];
if (_prop?.[`xu-exp:${prop_name}`]) {
_value = (await func.expression.get(SESSION_ID, _prop[`xu-exp:${prop_name}`], dsSession, `${prop_name} expression`)).result;
}
return _value;
};
for await (let [key, val] of Object.entries(_payload)) {
// if (key.substring(0, 7) !== "xu-exp:") {
const new_key = key.replaceAll('xu-exp:', ''); //key.substring(0, 7) === "xu-exp:" ? key.substring(7): key
payload[new_key] = await get_payload_property_value(new_key);
// }
}
}
// if (!payload) {
// func.utils.debug_report(
// SESSION_ID,
// "func.events.execute",
// `${elementP} >${triggerP} >${functionP} > payload not defined`,
// "E"
// );
// func.events.delete_job(SESSION_ID, jobNoP);
// return;
// }
const output_field = await func.datasource.get_args_property_value(SESSION_ID, dsSession, args, 'outputField');
// if (!output_field) {
// func.utils.debug_report(
// SESSION_ID,
// "func.events.execute",
// `${elementP} >${triggerP} >${functionP} > Output field not defined`,
// "E"
// );
// func.events.delete_job(SESSION_ID, jobNoP);
// return;
// }
const api_ret = await func.api.call_system_api(api_method, payload);
if (output_field) {
let datasource_changes = {};
let ret_get_value = await func.datasource.get_value(SESSION_ID, output_field, dsSessionP);
if (ret_get_value.found) {
let _ds = _session.DS_GLB[ret_get_value.dsSessionP];
if (!datasource_changes[_ds.dsSession]) {
datasource_changes[_ds.dsSession] = {};
}
if (!datasource_changes[_ds.dsSession][ret_get_value.currentRecordId]) {
datasource_changes[_ds.dsSession][ret_get_value.currentRecordId] = {};
}
datasource_changes[_ds.dsSession][ret_get_value.currentRecordId][output_field] = api_ret;
await func.datasource.update(SESSION_ID, datasource_changes, null, avoid_event_refresh);
}
}
func.events.delete_job(SESSION_ID, jobNoP);
},
call_external_api: async function () {
const method = await func.datasource.get_args_property_value(SESSION_ID, dsSession, args, 'method');
if (!method) {
func.utils.debug_report(SESSION_ID, 'func.events.execute', `${elementP} >${triggerP} >${functionP} > method not defined`, 'E');
func.events.delete_job(SESSION_ID, jobNoP);
return;
}
const url = await func.datasource.get_args_property_value(SESSION_ID, dsSession, args, 'url');
if (!url) {
func.utils.debug_report(SESSION_ID, 'func.events.execute', `${elementP} >${triggerP} >${functionP} > url not defined`, 'E');
func.events.delete_job(SESSION_ID, jobNoP);
return;
}
const payload_arr = await func.datasource.get_args_property_value(SESSION_ID, dsSession, args, 'payload');
const report_conversion_error = function (res, typeP, valP) {
var msg = `${elementP} >${triggerP} >${functionP} > error converting from ${valP} to ${typeP}`;
if (error) {
return func.utils.debug_report(SESSION_ID, msg, '', 'W');
}
func.utils.debug_report(SESSION_ID, msg + ' ' + (source.charAt(0).toUpperCase() + source.slice(1).toLowerCase()) + prog_info, '', 'E');
};
const report_conversion_warn = function (res) {
// number/boolean/bigint -> string is lossless; skip the noise (it routes as an "Unhandled Runtime Error")
if (typeP === 'string' && (typeof valP === 'number' || typeof valP === 'boolean' || typeof valP === 'bigint')) return;
var msg = `${elementP} >${triggerP} >${functionP} > type mismatch auto conversion from value ${valP} to ${typeP}`;
func.utils.debug_report(SESSION_ID, msg + ' ' + (source.charAt(0).toUpperCase() + source.slice(1).toLowerCase()) + prog_info, '', 'W');
};
// var ret = valP;
if (error) {
return report_conversion_error();
}
const module = await func.common.get_module(SESSION_ID, 'xuda-get-cast-util-module.mjs');
// return module.cast(
// typeP,
// valP,
// report_conversion_error,
// report_conversion_warn
// );
var payload = payload_arr.reduce(
(ret, val, key) => {
ret[val.key] = module.cast(val.type, val.val, report_conversion_error, report_conversion_warn);
return ret;
},
{},
);
// if (!payload) {
// func.utils.debug_report(
// SESSION_ID,
// "func.events.execute",
// `${elementP} >${triggerP} >${functionP} > payload not defined`,
// "E"
// );
// func.events.delete_job(SESSION_ID, jobNoP);
// return;
// }
const output_field = await func.datasource.get_args_property_value(SESSION_ID, dsSession, args, 'outputField');
// if (!output_field) {
// func.utils.debug_report(
// SESSION_ID,
// "func.events.execute",
// `${elementP} >${triggerP} >${functionP} > Output field not defined`,
// "E"
// );
// func.events.delete_job(SESSION_ID, jobNoP);
// return;
// }
const api_ret = await func.api.call_external_api(method, url, payload);
if (output_field) {
let datasource_changes = {};
let ret_get_value = await func.datasource.get_value(SESSION_ID, output_field, dsSessionP);
if (ret_get_value.found) {
let _ds = _session.DS_GLB[ret_get_value.dsSessionP];
if (!datasource_changes[_ds.dsSession]) {
datasource_changes[_ds.dsSession] = {};
}
if (!datasource_changes[_ds.dsSession][ret_get_value.currentRecordId]) {
datasource_changes[_ds.dsSession][ret_get_value.currentRecordId] = {};
}
datasource_changes[_ds.dsSession][ret_get_value.currentRecordId][output_field] = api_ret;
await func.datasource.update(SESSION_ID, datasource_changes, null, avoid_event_refresh);
}
}
func.events.delete_job(SESSION_ID, jobNoP);
},
};
// if (functionP.includes("alert")) {
// return await fx.alert();
// } else {
// try {
// console.log("functionP", functionP);
return await fx[functionP]();
// } catch (err) {
// console.error(err);
console.error('[xuda-runtime] caught xuda_events.js:1248:', err);
// }
// }
};
func.events.delete_job = function (SESSION_ID, jobNoP) {
var _session = SESSION_OBJ[SESSION_ID];
var job_index = func.events.find_job_index(SESSION_ID, jobNoP);
// console.log(jobNoP, job_index);
if (!_session.WORKER_OBJ.jobs[job_index]) {
_session.WORKER_OBJ.stat = null;
return;
}
var dsSession = _session.WORKER_OBJ.jobs[job_index].dsSessionP;
let ds_obj = _session?.DS_GLB[dsSession];
if (ds_obj) {
delete SCREEN_BLOCKER_OBJ[ds_obj.screenId + (ds_obj.callingScreenId ? '_' + ds_obj.callingScreenId : '')];
}
if (dsSession && ds_obj?.loops_limit && ds_obj?.loops_count < ds_obj?.loops_limit - 1) {
return;
}
_session.WORKER_OBJ.stat = null;
_session.WORKER_OBJ.jobs.splice(job_index, 1);
};
func.events.delete_job_0 = function (SESSION_ID) {
var job_index = 0;
var _session = SESSION_OBJ[SESSION_ID];
if (!_session.WORKER_OBJ.jobs[job_index]) {
_session.WORKER_OBJ.stat = null;
return;
}
var dsSession = _session.WORKER_OBJ.jobs[job_index].dsSession;
let ds_obj = _session?.DS_GLB[dsSession];
if (ds_obj) {
delete SCREEN_BLOCKER_OBJ[ds_obj.screenId + (ds_obj.callingScreenId ? '_' + ds_obj.callingScreenId : '')];
}
if (dsSession && ds_obj && ds_obj.loops_limit && ds_obj.loops_count < ds_obj.loops_limit - 1) {
return;
}
_session.WORKER_OBJ.stat = null;
_session.WORKER_OBJ.jobs.splice(job_index, 1);
};
func.events.check_jobs_idle = async function (SESSION_ID, jobsP) {
return new Promise((resolve, reject) => {
var _session = SESSION_OBJ[SESSION_ID];
if (!jobsP || (jobsP && jobsP.length === 0)) {
resolve();
return;
}
var listener = setInterval(function () {
var found;
for (const [key, val] of Object.entries(jobsP)) {
for (const [key2, val2] of Object.entries(_session.WORKER_OBJ.jobs)) {
if (key2 === val) {
found = true;
break;
}
}
}
if (!found) {
do_callback();
return;
}
}, 100);
var do_callback = function () {
clearInterval(listener);
resolve();
};
});
};
var loop_detected_obj = {};
setInterval(function () {
loop_detected_obj = {};
}, 1000);
func.events.set_browser_changes = function (dsP, fieldsChangedP) {
if (fieldsChangedP.includes('SYS_GLOBAL_STR_BROWSER_TITLE')) func.runtime.platform.set_title(dsP.dataset_new['SYS_GLOBAL_STR_BROWSER_TITLE']);
};
func.events.execute_PENDING_OPEN_URL_EVENTS = async function () {
for (let [key, url] of Object.entries(PENDING_OPEN_URL_EVENTS)) {
if (url) {
glb.WINDOW_LOCATION_SEARCH = url;
glb.ROOT_ELEMENT_ATTRIBUTES = func.UI.utils.get_root_element_attributes();
const params_obj = func.common.getObjectFromUrl(url, glb.ROOT_ELEMENT_ATTRIBUTES);
if (!params_obj.prog) {
await func.utils.report_issue(SESSION_ID, {
code: 'RUN_MSG_EVT_030',
source: 'func.events.execute_PENDING_OPEN_URL_EVENTS',
message: 'prog empty',
type: 'W',
details: {
url,
},
});
return;
}
await func.utils.TREE_OBJ.get(SESSION_ID, params_obj.prog);
let screen_ret = await func.utils.get_screen_obj(SESSION_ID, params_obj.prog);
if (screen_ret) {
await func.runtime.ui.init_screen({
SESSION_ID,
prog_id: params_obj.prog,
sourceScreenP: null,
callingDataSource_objP: null,
$callingContainerP: func.runtime.ui.get_session_root(SESSION_ID),
triggerIdP: null,
rowIdP: null,
jobNoP: null,
is_panelP: null,
parameters_obj_inP: null,
source_functionP: 'pendingUrlEvent_embed',
});
} else {
await func.utils.report_issue(SESSION_ID, {
code: 'RUN_MSG_EVT_010',
source: 'func.events.execute_PENDING_OPEN_URL_EVENTS',
message: 'Program not exist',
type: 'E',
details: {
prog_id: params_obj.prog_id,
prog: params_obj.prog,
},
});
func.UI.utils.progressScreen.show(SESSION_ID, 'Program not exist', null, true);
}
} else {
await func.utils.report_issue(SESSION_ID, {
code: 'RUN_MSG_EVT_030',
source: 'func.events.execute_PENDING_OPEN_URL_EVENTS',
message: 'url empty',
type: 'W',
});
}
}
};
func.events.invoke = async function (event_id, options) {
var _session = SESSION_OBJ[SESSION_ID];
const event_options = options && typeof options === 'object' ? options : { avoid_refresh: options === true };
if (!event_id) {
await func.utils.report_issue(SESSION_ID, {
code: 'RUN_MSG_EVT_060',
source: 'func.events.invoke',
message: 'event_id Cannot be empty',
type: 'W',
});
return false;
}
var ds;
for await (const [ds_key, val] of Object.entries(_session.DS_GLB)) {
const _view_obj = await func.utils.VIEWS_OBJ.get(SESSION_ID, val.prog_id);
if (xu_isEmpty(_view_obj.progEvents)) continue;
if (ds) break;
for await (const [key, val] of Object.entries(_view_obj.progEvents)) {
if (val?.data?.type === 'user_defined' && val.data.event_name === event_id) {
ds = ds_key;
break;
}
}
}
if (!ds) {
await func.utils.report_issue(SESSION_ID, {
code: 'RUN_MSG_EVT_060',
source: 'func.events.invoke',
message: 'event_id not found',
type: 'W',
details: {
event_id,
},
});
return false;
}
func.events.validate(SESSION_ID, 'user_defined', ds, event_id, null, null, null, event_options);
};
func.expression = {};
func.expression.get = async function (SESSION_ID, valP, dsSessionP, sourceP, rowIdP, sourceActionP, secondPassP, calling_fieldIdP, fieldsP, debug_infoP, iterate_info, js_script_callback, jobNo, api_output_type) {
class xu_class {
async get() {
if (typeof EXP_BUSY !== 'undefined') {
EXP_BUSY = true;
}
var ret;
var fields = {};
var error;
var warning;
// XU_PERF: object token values are passed to the evaluator through this
// slot array instead of being JSON-embedded into the source text, so the
// source stays identical across rows and compiles once (see secure_eval)
var xu_slot_values = null;
var xu_slot_positions = null;
function evalJson(text) {
return eval('(' + text + ')');
}
if (valP === null) {
ret = '';
} else {
switch (typeof valP) {
case 'string':
ret = valP;
break;
case 'undefined':
ret = '';
break;
case 'boolean':
ret = valP ? 'Y' : 'N';
break;
default:
ret = valP.toString();
break;
}
}
if (ret.includes('&')) ret = ret.replace(/\&/g, '&');
ret = func.utils.replace_studio_drive_url(SESSION_ID, ret);
const end_results = function () {
const replace_quotes = function (ret) {
for (const [key, val] of Object.entries(fields)) {
if (typeof val === 'string') ret = ret.replace('"' + val + '"', val.replace(/"/gi, ''));
}
return ret;
};
if (['update', 'javascript'].includes(sourceP)) {
if (typeof ret === 'string') ret = replace_quotes(ret);
}
const log_error = function () {
if (SESSION_OBJ[SESSION_ID].DS_GLB[dsSessionP]) {
func.utils.debug.log(SESSION_ID, SESSION_OBJ[SESSION_ID].DS_GLB[dsSessionP].nodeId, {
module: 'expression',
action: sourceP,
source: calling_fieldIdP,
prop: ret,
details: ret,
result: ret,
error: error,
warning: warning,
fields: null,
type: 'exp',
prog_id: SESSION_OBJ[SESSION_ID].DS_GLB[dsSessionP].prog_id,
debug_info: debug_infoP,
});
}
};
if (error) log_error();
// if (error || warning) log_error();
if (typeof EXP_BUSY !== 'undefined') {
EXP_BUSY = false;
}
const results = {
result: ret,
fields,
res,
explain: result,
error,
warning,
req: valP,
var_error_found,
};
// console.log('EXP>>>', results);
return results;
// return {
// result: ret,
// fields: fields,
// res: res,
// explain: result,
// error: error,
// warning: warning,
// req: valP,
// var_error_found: var_error_found,
// };
};
const variable_not_exist = async function () {
try {
if (sourceP !== 'arguments') {
if (ret && ret.startsWith('_DATE_')) {
ret = ret.slice(6);
} else if (
ret === 'self' || // bypass eval for 'self'
(ret && ret.length === 10 && ret[4] === '-' && ret[7] === '-') // bypass eval for date 2017-03-22
) {
// date or 'self' — skip eval, return as-is
} else {
ret = await func.expression.secure_eval(SESSION_ID, sourceP, ret, jobNo, dsSessionP, js_script_callback, null, undefined, xu_slot_values);
}
// console.log("AFTER OK", ret)
return end_results();
} else {
// do eval for arithmetic vals
ret = ret.replace(/_NULL/gi, '');
return end_results();
}
} catch (err) {
// console.error(err);
return end_results();
}
};
if (!func.expression.validate_variables(valP)) {
return await variable_not_exist();
}
const validate_email = async function () {
const ret = await func.expression.secure_eval(SESSION_ID, sourceP, valP, jobNo, dsSessionP, js_script_callback, null, true);
return glb.emailRegex.test(ret);
};
if (await validate_email()) {
return await variable_not_exist();
}
// var split = [];
var var_Arr = [];
const get_iterate_value_ret = function (fieldIdP) {
if (!iterate_info || (iterate_info.iterator_key !== fieldIdP && iterate_info.iterator_val !== fieldIdP)) {
return null;
}
const iter_value = iterate_info.iterator_key === fieldIdP ? iterate_info._key : iterate_info._val;
const iter_type = typeof iter_value !== 'undefined' ? {}.toString.call(iter_value).match(/\s([a-zA-Z]+)/)[1].toLowerCase() : 'string';
return {
ret: {
value: iter_value,
type: iter_type,
prop: ['array', 'object'].includes(iter_type) ? iter_value : null,
},
fieldIdP,
currentRecordId: rowIdP,
found: typeof iter_value !== 'undefined',
};
};
const split = func.expression.parse(ret) || [];
// console.log(valP, split);
const split_entries = Object.entries(split);
for (let entry_i = 0; entry_i < split_entries.length; entry_i++) {
// run each field
const [arr_key, val] = split_entries[entry_i];
const key = Number(arr_key);
var_Arr[key] = {};
var_Arr[key].value = val.value;
//--------------
const replace_value_in_string = async function (retP, fieldIdP) {
if (iterate_info?.iterator_key === fieldIdP || iterate_info?.iterator_val === fieldIdP) {
if (iterate_info.iterator_key === fieldIdP) {
retP.value = iterate_info._key;
}
if (iterate_info.iterator_val === fieldIdP) {
retP.value = iterate_info._val;
}
}
const set_value = function (valP) {
if (typeof valP !== 'undefined') {
var_Arr[key].value = valP;
if (typeof valP === 'string') var_Arr[key].type = 'string';
} else {
if (retP.type === 'object') {
var_Arr[key].value = '';
var_Arr[key].type = 'string';
}
}
};
if (sourceP === 'exp' && retP.type !== 'exp') {
var_Arr[key].type = retP.type;
return;
}
if (typeof retP.value !== 'undefined') {
var_Arr[key].type = retP.type;
var_Arr[key].value = typeof retP.value === 'string' && !retP.value.includes('<svg xmlns=') && retP.value.indexOf('\\') === -1 && !['UI Attr EXP', 'update'].includes(sourceP) ? retP.value.replaceAll('"', '\\"') : retP.value; // new Apr 6 2025 fixing "\"how much?\"" // new Jul 29 25 to fix quil extra "\" " source!=="UI Attr EXP"
if ((val.value.indexOf('[') > -1) | (val.value.indexOf('.') > -1)) {
//get values from array '@var==="sss" && @var_B==="sss" && @obj.property===5 && @objA["value"]===123 | @objB["value"].property===1234'
var data = retP.prop;
if (retP.type === 'object') data = retP.value;
var property1, property2;
//check for split situation: @objB[@var].property 1: @objB[ 2:@var].property
if (val.value.indexOf('[') === -1 && val.value.indexOf(']') > -1 && val.value.substr(0, 1) === '@') {
//check situation 2
var prevData = var_Arr[key - 1].value;
var_Arr[key].value = prevData[data]; // @objB[@var]
if (val.value.indexOf('.') > -1) {
const props_split = await func.expression.get_property(val.value);
property2 = props_split.property2;
if (prevData[data]) set_value(prevData[data][property2]);
// var_Arr[key].value = prevData[data][property2]; //@objB[@var].property
}
delete var_Arr[key - 1];
} else {
const props = await func.expression.get_property(val.value);
property1 = props.property1;
property2 = props.property2;
if (property1) {
var_Arr[key].value = data[property1]; // @var["value"] or @var.property
if (property2) {
if (data[property1]) set_value(data[property1][property2]);
}
}
if (property2 && !property1) {
if (
data //data[property2]
) {
set_value(data[property2]);
}
}
}
fields[fieldIdP] = var_Arr[key].value; // added 051017 to allow @SYS_GLOBAL_OBJ_WIDGET_INFO.doc_id
var_Arr[key].fieldId = fieldIdP;
} else {
fields[fieldIdP] = var_Arr[key].value; // update flat no warpers
var_Arr[key].fieldId = fieldIdP;
}
}
};
//>>>>>>>>>>>>>>>>>>
//>>>> start here >>
//>>>>>>>>>>>>>>>>>>
if (val.fieldId) {
// @_THIS
if (val.fieldId && val.fieldId.substr(0, 5) === '_THIS' && calling_fieldIdP && (val.fieldId.length === 5 || (val.fieldId.length > 5 && val.fieldId.substr(5, 1) === '.'))) {
if (val.fieldId.length === 5) val.fieldId = calling_fieldIdP;
else val.fieldId = calling_fieldIdP + val.fieldId(5, val.fieldId.length - 1);
}
if (!sourceP === 'exp') {
var_Arr[key].value = '""';
} // put default
fields[val.fieldId] = var_Arr[key].value;
const ret = get_iterate_value_ret(val.fieldId) || (await func.datasource.get_value(SESSION_ID, val.fieldId, dsSessionP, rowIdP)); // find field in dataSources
await replace_value_in_string(ret.ret, ret.fieldIdP);
}
}
try {
// eval the expression
var res = [];
var exp_exist;
var var_error_found;
// merge arr values
var_Arr.forEach(function (val, key) {
if (sourceP === 'UI Property EXP') {
let ret = func.utils.get_drive_url(SESSION_ID, val.value, true);
if (ret.changed) {
res[key] = ret.value;
return true;
}
}
if (sourceP === 'UI Attr EXP') {
let ret = func.utils.get_drive_url(SESSION_ID, val.value, var_Arr.length == 1 ? false : true);
if (ret.changed) {
res[key] = ret.value;
return true;
}
}
if (val.type === 'exp') {
exp_exist = true;
}
res[key] = val.value;
if (var_Arr.length > 1) {
// complex input
if (!['DbQuery', 'alert', 'exp', 'api_rendered_output'].includes(sourceP) && ['string', 'date'].includes(val.type)) {
res[key] = '`' + val.value + '`';
}
// new Dec 18 2024 for Ishai // json,html,xml,text,css
if (['api_rendered_output'].includes(sourceP) && ['json'].includes(api_output_type) && ['string', 'date'].includes(val.type)) {
res[key] = `"` + val.value + `"`;
}
}
if (val.fieldId && val.value && typeof val.value === 'string') {
if (['query', 'condition', 'range', 'sort', 'locate'].includes(sourceP)) {
if (val.value.indexOf('↵') > -1) {
res[key] = val.value.split('↵').join('');
}
res[key] = res[key].replace(/(\r\n|\n|\r)/gm, ''); //.replace(/"/g,'\"');//.replace(/'/g,"");
}
if (['init', 'update', 'virtual'].includes(sourceP)) {
if (val.value.indexOf('↵') > -1) res[key] = val.value.split('↵').join('\n');
res[key] = res[key].replace(/(\r\n|\n|\r)/gm, '\\n');
}
if (typeof IS_PROCESS_SERVER !== 'undefined') {
res[key] = res[key].replace(/(\r\n|\n|\r)/gm, '<br>');
}
fields[val.fieldId] = res[key];
}
// extract object
if (typeof val.value === 'object' && var_Arr.length > 1) {
const _paren = !Array.isArray(val.value) && !var_Arr[key + 1].value?.includes('.');
let _slotted = false;
if (glb.XU_PERF && sourceP === 'UI Attr EXP') {
// slot-ify: clone preserves the legacy parse-of-stringify isolation
try {
const _snap = val.value === null ? null : func.expression.get_slot_clone(val.value);
xu_slot_values = xu_slot_values || [];
xu_slot_positions = xu_slot_positions || [];
const _slot = xu_slot_values.length;
xu_slot_values.push(_snap);
xu_slot_positions.push({ key: key, slot: _slot, paren: _paren });
res[key] = _paren ? '(__xu_v[' + _slot + '])' : '__xu_v[' + _slot + ']';
_slotted = true;
} catch (clone_err) {
_slotted = false;
}
}
if (!_slotted) {
if (_paren) {
// prevent cast on single value expression
res[key] = '(' + JSON.stringify(val.value) + ')';
} else {
res[key] = JSON.stringify(val.value);
}
}
}
if (!exp_exist && sourceP !== 'exp' && val.value && typeof val.value === 'string' && val.value.substr(0, 1) === '@') {
warning = 'Error encoding ' + val.value;
var_error_found = true;
res[key] = 0;
}
});
const join = function (arrP) {
return arrP.join('');
};
var exp = undefined;
if (exp_exist && sourceP !== 'exp') {
// the recursive pass has no access to this call's slot array, so
// materialize slots back into legacy JSON text before recursing
if (xu_slot_values && xu_slot_positions) {
for (let sp = 0; sp < xu_slot_positions.length; sp++) {
const p = xu_slot_positions[sp];
const txt = JSON.stringify(xu_slot_values[p.slot]);
res[p.key] = p.paren ? '(' + txt + ')' : txt;
}
xu_slot_values = null;
xu_slot_positions = null;
}
exp = await func.expression.get(SESSION_ID, join(res), dsSessionP, sourceP, rowIdP, sourceActionP, true, calling_fieldIdP, fields, debug_infoP);
if (exp.res) res = exp.res;
// do second pass when exp exist
else res = [exp.result];
fields = Object.assign(exp.fields, fieldsP);
}
var result = join(res);
if (res.length === 1) {
// bypass join for single value expression
result = res[0];
}
if (secondPassP) {
ret = result;
} else if (sourceP !== 'exp') {
// no eval for second pass
// return single value
if (res.length === 1 && typeof res[0] === 'string' && typeof res[0] !== 'object') {
// avoid eval when query leading zeros problem
ret = join(res);
if (ret && ret.substr(0, 1) === '@') {
error = 'Error encoding @ var';
var_error_found = true;
}
} else {
if (
![
'arguments',
// "alert",
'api_rendered_output',
'DbQuery',
].includes(sourceP)
) {
ret = await func.expression.secure_eval(SESSION_ID, sourceP, result, jobNo, dsSessionP, js_script_callback, null, undefined, xu_slot_values);
} else {
if (sourceP === 'DbQuery') {
ret = JSON.stringify(evalJson(result));
} else {
// try eval
ret = result;
}
}
}
}
// console.log("AAAA")
return end_results();
} catch (err) {
ret = result; // skip eval on error
error = err.message;
return end_results();
}
}
}
const new_class = new xu_class();
return new_class.get();
};
// func.expression.get_bad1 = async function (SESSION_ID, valP, dsSessionP, sourceP, rowIdP, sourceActionP, secondPassP, calling_fieldIdP, fieldsP = {}, debug_infoP, iterate_info, js_script_callback, jobNo, api_output_type) {
// const evalJson = (text) => eval(`(${text})`);
// const replaceQuotes = (str) => {
// for (const [key, val] of Object.entries(fields)) {
// if (typeof val === 'string') str = str.replace(`"${val}"`, val.replace(/"/g, ''));
// }
// return str;
// };
// let ret, error, warning, var_error_found;
// const fields = { ...fieldsP };
// // Initial value processing
// if (valP === null || typeof valP === 'undefined') ret = '';
// else if (typeof valP === 'boolean') ret = valP ? 'Y' : 'N';
// else ret = valP.toString();
// ret = ret.replace(/\&/g, '&');
// ret = func.utils.replace_studio_drive_url(SESSION_ID, ret);
// // End results helper
// const endResults = () => {
// if (['update', 'javascript'].includes(sourceP) && typeof ret === 'string') {
// ret = replaceQuotes(ret);
// }
// if ((error || warning) && SESSION_OBJ[SESSION_ID]?.DS_GLB[dsSessionP]) {
// func.utils.debug.log(SESSION_ID, SESSION_OBJ[SESSION_ID].DS_GLB[dsSessionP].nodeId, {
// module: 'expression',
// action: sourceP,
// source: calling_fieldIdP,
// prop: ret,
// details: ret,
// result: ret,
// error,
// warning,
// fields: null,
// type: 'exp',
// prog_id: SESSION_OBJ[SESSION_ID].DS_GLB[dsSessionP].prog_id,
// debug_info: debug_infoP,
// });
// }
// return { result: ret, fields, error, warning, req: valP, var_error_found };
// };
// // Handle non-variable cases
// const handleNonVariable = async () => {
// try {
// if (sourceP !== 'arguments') {
// if (ret.startsWith('_DATE_')) ret = ret.slice(6);
// else if (/^\d{4}-\d{2}-\d{2}$/.test(ret) || ret === 'self') return endResults();
// else ret = await func.expression.secure_eval(SESSION_ID, sourceP, ret, jobNo, dsSessionP, js_script_callback);
// } else {
// ret = ret.replace(/_NULL/gi, '');
// }
// return endResults();
// } catch (err) {
// error = err.message;
// return endResults();
// }
// };
// // Early return for simple cases
// if (!func.expression.validate_variables(valP)) return await handleNonVariable();
// if (glb.emailRegex.test(await func.expression.secure_eval(SESSION_ID, sourceP, valP, jobNo, dsSessionP, js_script_callback))) {
// return await handleNonVariable();
// }
// // Parse and process variables
// const split = func.expression.parse(ret) || [];
// const var_Arr = await Promise.all(
// split.map(async (val, key) => {
// const result = { value: val.value, fieldId: val.fieldId };
// if (!val.fieldId) return result;
// // Handle _THIS substitution
// if (val.fieldId.startsWith('_THIS') && calling_fieldIdP) {
// result.fieldId = val.fieldId.length === 5 ? calling_fieldIdP : calling_fieldIdP + val.fieldId.slice(5);
// }
// // Fetch value from datasource
// const { ret: fetchedValue, fieldIdP } = await func.datasource.get_value(SESSION_ID, result.fieldId, dsSessionP, rowIdP);
// result.value = fetchedValue?.value ?? (sourceP === 'exp' ? fetchedValue?.value : '""');
// result.type = fetchedValue?.type;
// // Handle iteration
// if (iterate_info) {
// if (iterate_info.iterator_key === fieldIdP) result.value = iterate_info._key;
// if (iterate_info.iterator_val === fieldIdP) result.value = iterate_info._val;
// }
// // Process nested properties
// if (val.value.includes('[') || val.value.includes('.')) {
// const { property1, property2 } = await func.expression.get_property(val.value);
// const data = fetchedValue?.type === 'object' ? fetchedValue.value : fetchedValue?.prop;
// if (key > 0 && val.value.includes(']') && !val.value.includes('[') && split[key - 1].value) {
// const prevData = split[key - 1].value;
// result.value = prevData[fieldIdP];
// if (val.value.includes('.') && prevData[fieldIdP]) {
// result.value = prevData[fieldIdP][property2] ?? '';
// }
// } else if (data) {
// if (property1) result.value = data[property1] ?? '';
// if (property2) result.value = (property1 ? data[property1]?.[property2] : data[property2]) ?? '';
// }
// }
// fields[fieldIdP] = result.value;
// return result;
// }),
// );
// // Final evaluation
// try {
// const res = var_Arr.map((val, key) => {
// if (sourceP === 'UI Property EXP' || sourceP === 'UI Attr EXP') {
// const { changed, value } = func.utils.get_drive_url(SESSION_ID, val.value, sourceP === 'UI Attr EXP' && var_Arr.length > 1);
// if (changed) return value;
// }
// let value = val.value;
// if (var_Arr.length > 1) {
// if (!['DbQuery', 'alert', 'exp', 'api_rendered_output'].includes(sourceP) && ['string', 'date'].includes(val.type)) {
// value = `\`${value}\``;
// } else if (sourceP === 'api_rendered_output' && api_output_type === 'json' && ['string', 'date'].includes(val.type)) {
// value = `"${value}"`;
// }
// }
// if (val.fieldId && typeof value === 'string') {
// if (['query', 'condition', 'range', 'sort', 'locate'].includes(sourceP)) value = value.replace(/↵|\r\n|\n|\r/g, '');
// if (['init', 'update', 'virtual'].includes(sourceP)) value = value.replace(/↵|\r\n|\n|\r/g, '\\n');
// if (typeof IS_PROCESS_SERVER !== 'undefined') value = value.replace(/↵|\r\n|\n|\r/g, '<br>');
// fields[val.fieldId] = value;
// }
// if (typeof value === 'object' && var_Arr.length > 1) {
// value = Array.isArray(value) || var_Arr[key + 1]?.value?.includes('.') ? JSON.stringify(value) : `(${JSON.stringify(value)})`;
// }
// if (!val.type === 'exp' && sourceP !== 'exp' && typeof value === 'string' && value.startsWith('@')) {
// warning = `Error encoding ${value}`;
// var_error_found = true;
// return '0';
// }
// return value;
// });
// ret = res.length === 1 ? res[0] : res.join('');
// if (var_Arr.some((v) => v.type === 'exp') && sourceP !== 'exp' && !secondPassP) {
// const exp = await func.expression.get(SESSION_ID, ret, dsSessionP, sourceP, rowIdP, sourceActionP, true, calling_fieldIdP, fields, debug_infoP);
// ret = exp.res?.[0] ?? exp.result;
// Object.assign(fields, exp.fields);
// } else if (!secondPassP && !['arguments', 'api_rendered_output', 'DbQuery'].includes(sourceP)) {
// ret = await func.expression.secure_eval(SESSION_ID, sourceP, ret, jobNo, dsSessionP, js_script_callback);
// } else if (sourceP === 'DbQuery') {
// ret = JSON.stringify(evalJson(ret));
// }
// if (typeof ret === 'string' && ret.startsWith('@')) {
// error = 'Error encoding @ var';
// var_error_found = true;
// }
// } catch (err) {
// error = err.message;
// }
// return endResults();
// };
// func.expression.parse_org = function (strP) {
// var extract_str = function (strP, posP) {
// if (!posP) posP = 0;
// var clean_split_str = function (arrP) {
// var arr = [];
// if (arrP && arrP.length > 1 && arrP[0] === '' && arrP[1].indexOf('@') > -1) {
// for (var i = 1; i <= arrP.length; i++) {
// arr.push(arrP[i]);
// }
// return arr;
// } else return arrP;
// };
// var nonLettersPatt = /\W/; // non letters
// var validSymbolsNoArray = /[^.@\[]/; //valid symbols no array /[^.@\[\]\]]/
// var validSymbolsWithArray = /[^.@"'\[\]]/; //valid symbols with array
// var validSymbols = validSymbolsNoArray;
// var splitTmp = strP.replace(/@/g, '^^@').split('^^');
// var split = clean_split_str(splitTmp);
// var obj = [];
// if (split) {
// for (let val of split) {
// // run on @ segments
// if (val) {
// var pos = strP.indexOf(val);
// if (val && val.substr(0, 1) === '@') {
// var tmpStr = '';
// var word_start_pos = undefined;
// var word_end_pos = undefined;
// // run on @ segment string
// for (var i = 0; i <= val.length; i++) {
// var key1 = i;
// var val1 = val.substr(i, 1);
// if (
// val1 === '.' &&
// !word_start_pos // find first dot
// )
// word_start_pos = key1;
// if (
// word_start_pos &&
// key1 > word_start_pos &&
// nonLettersPatt.test(val1) // find any sign character to mark the end of word
// )
// word_end_pos = key1;
// if (word_start_pos && word_start_pos >= 0 && word_end_pos && word_end_pos >= 0) {
// // find the word
// var word = val.substr(word_start_pos + 1, word_end_pos - word_start_pos - 1); // get the word
// // if (glb.ALL_PROPERTIES_ARR.indexOf(word) === -1) {
// // compare with internal properties
// tmpStr = tmpStr.substr(0, word_start_pos) + '^^' + tmpStr.substr(word_start_pos, word_end_pos);
// // }
// if (val.substr(word_end_pos, 1) === '.') word_start_pos = word_end_pos;
// else word_start_pos = null;
// word_end_pos = null;
// }
// if (val1 === '[') validSymbols = validSymbolsWithArray;
// if (nonLettersPatt.test(val1) && validSymbols.test(val1) && tmpStr.indexOf('^^') === -1) {
// tmpStr += '^^' + val1;
// } else tmpStr += val1;
// }
// // });
// if (tmpStr.indexOf('^^') > -1) {
// var obj1 = extract_str(tmpStr, pos);
// obj = obj.concat(obj1);
// } else {
// // push clean @var
// var fieldId = undefined;
// if (val) {
// fieldId = val.substr(1, val.length);
// if (val.indexOf('.') > -1) fieldId = val.substr(1, val.indexOf('.') - 1);
// if (val.indexOf('[') > -1) fieldId = val.substr(1, val.indexOf('[') - 1);
// //
// // if (val.indexOf("]") > -1)
// // fieldId = val.substr(1, val.indexOf("]") - 1);
// }
// obj.push({
// value: val,
// fieldId: fieldId,
// pos: pos + posP,
// });
// }
// } else {
// obj.push({
// value: val,
// pos: pos + posP,
// });
// }
// }
// }
// return obj;
// }
// };
// var res = extract_str(strP);
// return res;
// };
func.expression._parse_cache = new Map();
func.expression.parse = function (input) {
if (typeof input !== 'string') return [];
if (func.expression._parse_cache.has(input)) {
return func.expression._parse_cache.get(input).map(function (s) { return Object.assign({}, s); });
}
const segments = [];
let pos = 0;
const parts = input.split(/(@\w+)/).filter(Boolean);
for (const part of parts) {
if (part.startsWith('@')) {
const fieldId = part.slice(1);
segments.push({
value: part,
fieldId,
pos,
});
} else {
segments.push({
value: part,
pos,
});
}
pos += part.length;
}
// Evict oldest entry if cache exceeds limit
if (func.expression._parse_cache.size >= 500) {
const firstKey = func.expression._parse_cache.keys().next().value;
func.expression._parse_cache.delete(firstKey);
}
func.expression._parse_cache.set(input, segments);
return segments.map(function (s) { return Object.assign({}, s); });
};
func.expression.get_property = async function (valP) {
async function secure_eval(val) {
if (typeof IS_PROCESS_SERVER === 'undefined') {
try {
return eval(val);
} catch (err) {
console.error(err);
return;
}
}
try {
let vm = new VM({
sandbox: {
func: func,
SESSION_ID: SESSION_ID,
SESSION_OBJ: { [`${SESSION_ID}`]: SESSION_OBJ[SESSION_ID] },
},
timeout: 1000,
allowAsync: false,
});
return await vm.run(val);
} catch (err) {
throw err;
}
}
var property1, property2;
if (valP.indexOf('[') > -1 && valP.indexOf(']') > -1) {
property1 = valP.substr(valP.indexOf('[') + 1, valP.indexOf(']') - valP.indexOf('[') - 1); // get []
property1 = await secure_eval(property1);
}
if (valP.indexOf('.') > -1) property2 = valP.substr(valP.indexOf('.') + 1, valP.length); // get .
return {
property1: property1,
property2: property2,
};
};
// func.expression.get_property_bad = async function (valP) {
// if (typeof valP !== 'string') return { property1: undefined, property2: undefined };
// const secureEval = async (expr) => {
// if (typeof IS_PROCESS_SERVER === 'undefined') {
// try {
// return eval(expr);
// } catch (err) {
// console.error(err);
// return undefined;
// }
// }
// try {
// const vm = new VM.VM({
// sandbox: {
// func,
// SESSION_ID,
// SESSION_OBJ: { [SESSION_ID]: SESSION_OBJ[SESSION_ID] },
// },
// timeout: 1000,
// allowAsync: false,
// });
// return await vm.run(expr);
// } catch {
// return undefined; // Simplified error handling
// }
// };
// let property1, property2;
// const bracketStart = valP.indexOf('[');
// const bracketEnd = valP.indexOf(']');
// if (bracketStart > -1 && bracketEnd > bracketStart) {
// const expr = valP.slice(bracketStart + 1, bracketEnd);
// property1 = await secureEval(expr);
// }
// const dotIndex = valP.indexOf('.');
// if (dotIndex > -1) {
// property2 = valP.slice(dotIndex + 1);
// }
// return { property1, property2 };
// };
func.expression.validate_constant = function (valP) {
var patt = /["']/;
if (typeof valP === 'string' && patt.test(valP.substr(0, 1)) && patt.test(valP.substr(0, valP.length - 1))) return true;
else return false;
};
func.expression.validate_variables = function (valP) {
if (typeof valP === 'string' && valP.indexOf('@') > -1) return true;
else return false;
};
func.expression.remove_quotes = function (valP) {
if (func.expression.validate_constant(valP)) return valP.substr(1, valP.length - 2);
else return valP;
};
// func.expression.validate_constant = function (valP) {
// typeof valP === 'string' && /^["'].*["']$/.test(valP);
// };
// func.expression.validate_variables = function (valP) {
// typeof valP === 'string' && valP.includes('@');
// };
// func.expression.remove_quotes = function (valP) {
// func.expression.validate_constant(valP) && typeof valP === 'string' ? valP.slice(1, -1) : valP;
// };
// func.expression.secure_eval_org = async function (SESSION_ID, sourceP, val, job_id, dsSessionP, js_script_callback, evt) {
// const api_utils = await func.common.get_module(SESSION_ID, 'xuda-api-library.mjs', {
// func,
// glb,
// SESSION_OBJ,
// SESSION_ID,
// APP_OBJ,
// dsSession: dsSessionP,
// job_id,
// });
// const xu = api_utils;
// if (typeof IS_PROCESS_SERVER === 'undefined' && typeof IS_DOCKER === 'undefined') {
// try {
// return eval(val);
// } catch (err) {
// try {
// return JSON5.parse(val);
// } catch (err) {
// // console.error(err);
// return val;
// }
// }
// }
// // server side execution
// if (sourceP === 'javascript') {
// process.on('uncaughtException', (err) => {
// console.error('Asynchronous error caught.', err);
// func.events.delete_job(SESSION_ID, job_id);
// if (typeof IS_PROCESS_SERVER !== 'undefined' || typeof IS_DOCKER !== 'undefined') {
// if (SESSION_OBJ[SESSION_ID].crawler) return;
// return __.rpi.write_log(SESSION_OBJ[SESSION_ID].app_id, 'error', 'worker', 'vm error', err, null, val, 'func.expression.get.secure_eval');
// }
// });
// try {
// const dir = path.join(_conf.studio_drive_path, SESSION_OBJ[SESSION_ID].app_id, 'node_modules', '/');
// const script = new VM.VMScript(`try{${val}}catch(e){func.api.error(SESSION_ID, "nodejs error", e); console.error(e); func.events.delete_job(SESSION_ID, job_id);}`, { filename: dir, dirname: dir });
// let vm = new VM.NodeVM({
// require: {
// external: true,
// },
// sandbox: {
// func,
// xu,
// SESSION_ID,
// SESSION_OBJ: { [`${SESSION_ID}`]: SESSION_OBJ[SESSION_ID] },
// callback: js_script_callback,
// job_id,
// axios,
// got,
// FormData,
// },
// timeout: 60000,
// });
// return await vm.run(script, {
// filename: dir,
// dirname: dir,
// });
// } catch (err) {
// console.error('Failed to execute script.', err);
// if (typeof IS_PROCESS_SERVER !== 'undefined') {
// func.events.delete_job(SESSION_ID, jobNo);
// return __.db.add_error_log(SESSION_OBJ[SESSION_ID].app_id, 'api', err);
// }
// }
// } else {
// try {
// try {
// let vm = new VM.VM({
// sandbox: {
// xu,
// func,
// SESSION_ID,
// SESSION_OBJ: { [`${SESSION_ID}`]: SESSION_OBJ[SESSION_ID] },
// callback: js_script_callback,
// job_id,
// },
// timeout: 1000,
// allowAsync: false,
// });
// let ret = val;
// if (typeof val === 'string') {
// ret = await vm.run(val);
// }
// return ret;
// } catch (err) {
// throw '';
// }
// } catch (err) {
// try {
// return JSON5.parse(val);
// } catch (err) {
// // console.error(err);
// return val;
// }
// }
// }
// };
// One clone per object per update epoch: many attribute expressions binding
// the same large object (e.g. a whole tree field) would otherwise re-clone it
// per evaluation. datasource.update clears the cache, so expressions never see
// values from before the mutation that triggered their refresh.
func.expression.get_slot_clone = function (v) {
const cache = (func.expression._slot_clone_cache = func.expression._slot_clone_cache || new WeakMap());
let c = cache.get(v);
if (typeof c === 'undefined') {
c = structuredClone(v);
cache.set(v, c);
}
return c;
};
func.expression.secure_eval = async function (SESSION_ID, sourceP, val, job_id, dsSessionP, js_script_callback, evt, ignore_errors, xu_values) {
if (typeof val !== 'string') return val;
const xu = await func.common.get_module(SESSION_ID, 'xuda-api-library.mjs', {
func,
glb,
SESSION_OBJ,
SESSION_ID,
APP_OBJ,
dsSession: dsSessionP,
job_id,
});
const isServer = typeof IS_PROCESS_SERVER !== 'undefined' || typeof IS_DOCKER !== 'undefined';
// visible to both the compiled path and the direct eval below
const __xu_v = xu_values;
// Client-side execution
if (!isServer) {
// XU_PERF: compile once per distinct source instead of parsing on every
// eval. Non-expression sources (multi-statement javascript) and sources
// that fail to compile stay on the legacy eval; any runtime error falls
// through to the legacy path so error semantics are unchanged.
if (glb.XU_PERF && sourceP !== 'javascript') {
// compile on the SECOND sighting: repeated sources (row templates) get a
// compiled function, one-off sources (unique per-node paths) stay on
// plain eval and never pay Function-compile cost
const cache = (func.expression._compiled_exp_cache = func.expression._compiled_exp_cache || new Map());
const entry = cache.get(val);
if (typeof entry === 'function') {
try {
return entry(xu, func, glb, SESSION_ID, SESSION_OBJ, dsSessionP, job_id, js_script_callback, evt, __xu_v);
} catch (compiled_err) {
// fall through to the legacy eval below
}
} else if (entry === 1) {
let fn = null;
try {
fn = new Function('xu', 'func', 'glb', 'SESSION_ID', 'SESSION_OBJ', 'dsSessionP', 'job_id', 'js_script_callback', 'evt', '__xu_v', 'return (' + val + '\n);');
} catch (compile_err) {
fn = null; // uncompilable: stays null, legacy eval from here on
}
cache.set(val, fn);
if (fn) {
try {
return fn(xu, func, glb, SESSION_ID, SESSION_OBJ, dsSessionP, job_id, js_script_callback, evt, __xu_v);
} catch (compiled_err) {
// fall through to the legacy eval below
}
}
} else if (typeof entry === 'undefined') {
if (cache.size > 4000) cache.clear();
cache.set(val, 1);
}
}
try {
return eval(val);
} catch (err) {
if (sourceP === 'javascript' && !ignore_errors) {
await func.utils.report_issue(SESSION_ID, {
code: 'RUN_MSG_EXP_010',
source: 'func.expression.secure_eval',
message: 'Execution error',
type: 'E',
err,
details: {
sourceP,
dsSessionP,
job_id,
},
});
}
try {
return JSON5.parse(val);
} catch (json_error) {
return val;
}
}
}
// Server-side execution
const sandbox = {
func,
xu,
SESSION_ID,
SESSION_OBJ: { [SESSION_ID]: SESSION_OBJ[SESSION_ID] },
callback: js_script_callback,
job_id,
...(sourceP === 'javascript' ? { axios, got, FormData } : {}),
};
const handleError = async (err) => {
await func.utils.report_issue(SESSION_ID, {
code: 'RUN_MSG_EXP_010',
source: 'func.expression.secure_eval',
message: 'Execution error',
type: 'E',
err,
details: {
sourceP,
dsSessionP,
job_id,
},
});
func.events.delete_job(SESSION_ID, job_id);
if (isServer && !SESSION_OBJ[SESSION_ID].crawler) {
if (sourceP !== 'javascript') {
__.db.add_error_log(SESSION_OBJ[SESSION_ID].app_id, 'api', err);
}
}
return val; // Fallback to original value
};
if (sourceP === 'javascript') {
process.on('uncaughtException', function (uncaught_error) {
handleError(uncaught_error);
});
try {
const dir = path.join(_conf.studio_drive_path, SESSION_OBJ[SESSION_ID].app_id, 'node_modules');
const script = new VMScript(`try { ${val} } catch (e) { func.api.error(SESSION_ID, "nodejs error", e); throw e; }`, { filename: dir, dirname: dir });
const vm = new NodeVM({
require: { external: true },
sandbox,
timeout: 60000,
});
return await vm.run(script, { filename: dir, dirname: dir });
} catch (err) {
return await handleError(err);
}
}
try {
const vm = new VM({
sandbox,
timeout: 1000,
allowAsync: false,
});
return await vm.run(val);
} catch {
try {
return JSON5.parse(val);
} catch {
return val;
}
}
};
export default {
glb,
func,
APP_OBJ,
PROJECT_OBJ,
DOCS_OBJ,
SESSION_OBJ,
};