UNPKG

@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.

8,405 lines 281 kB
 '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 = {};
glb.IS_STUDIO = null;

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.common.find_item_by_key = function (arr, key, val) {
  return _.find(arr, function (e) {
    return e.data[key] === val;
  });
};
func.common.find_item_by_key_root = function (arr, key, val) {
  return _.find(arr, 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.MAIN_WINDOW_TYPES_ARR = ['screen', 'program'];
glb.FUNCTION_NODES_ARR = ['batch', 'get_data', 'set_data', 'Alert_error', 'Alert_warning', 'Alert_info', 'Alert_ok', 'javascript', 'SQL'];
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.log('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.log('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 read_dbs_pouch = async function (db) {
      if (_session?.DS_GLB?.[dsSession]?.refreshed && (dataP.filterModelMongo || dataP.filterModelSql)) {
        return {
          code: 1,
          data: await func.db.pouch[serviceP](SESSION_ID, data),
        };
      }

      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 {
          code: 1,
          data: await func.db.pouch[serviceP](SESSION_ID, data),
        };
      }

      try {
        const doc = await db.get(rep_id);
        if (doc.stat < 3) throw 'replication not ready';
        const json = {
          code: 1,
          data: await func.db.pouch[serviceP](SESSION_ID, data),
        };

        return json;
      } catch (err) {
        const json = await func.common.perform_rpi_request(SESSION_ID, serviceP, opt, data);
        if (json.data.opt) {
          try {
            try {
              await db.get(rep_id);
            } catch (err) {
              await db.put({
                _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,
              });
            }
            func.db.pouch.set_db_replication_from_server(SESSION_ID);
          } catch (err) {}
        }
        return json;
      }
    };
    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 = _.cloneDeep(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 '';
        }
        if (!(await func?.db?.pouch?.get_replication_stat(SESSION_ID))) throw '';
        const db = await func.utils.connect_pouchdb(SESSION_ID);
        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 = {};
      _.forEach(data, function (val, key) {
        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 () {
  const queryString = window.location.search;
  const urlParams = new URLSearchParams(queryString);
  return urlParams;
};
func.common.getParametersFromUrl = function () {
  const searchParams = new URLSearchParams(window.location.search);
  const parameters = {};

  for (const [key, value] of searchParams.entries()) {
    parameters[key] = value;
  }

  return parameters;
};

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) {
  return `https://${SESSION_OBJ[SESSION_ID].domain}/${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, _.capitalize(source), errorP, 'W');
    }
    var msg = `error converting  ${attributeP} from ${valP} to ${typeP}`;
    func.utils.debug_report(SESSION_ID, _.capitalize(source), msg, 'E');
  };
  const report_conversion_warn = function (msg) {
    var msg = `type mismatch auto conversion made to ${attributeP} from value ${valP} to ${typeP}`;
    func.utils.debug_report(SESSION_ID, _.capitalize(source), 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.get_module = async function (SESSION_ID, module, paramsP = {}) {
  let ret;

  const get_ret = async function (src) {
    const module_ret = await import(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];
  if (_session.worker_type === 'Dev') {
    ret = await get_ret('./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(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 _.endsWith(module, '.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(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) {
  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);
};
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.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)) {
      url = 'https://' + _session.host + '/rpi/';
    }

    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_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);
          $('body').on('get_ws_data_response_' + glb.websocket_queue_num, (e, data) => {
            resolve(data.data);
            $('body').off('get_ws_data_response_' + data.e.websocket_queue_num);
          });
        } 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);
        $('body').on('heartbeat_response', (e, data) => {
          resolve(data.data);
          $('body').off('heartbeat_response');
        });
      } else {
        throw new Error('fail to fetch from ws websocket inactive');
      }
    };
    if (serviceP === 'heartbeat') {
      return heartbeat();
    }
    dbs_calls();
  });
};

func.common.sha256 = async function (inputString) {
  // const enc = new TextEncoder();
  // const buf = await crypto.subtle.digest('SHA-256', enc.encode(str));
  // const bytes = new Uint8Array(buf);
  // return [...bytes].map((b) => b.toString(16).padStart(2, '0')).join('');

  // 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');
};

glb.new_xu_render = false;
 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;

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'];
glb.attr_abbreviations_arr = ['xu-click', 'xu-dblclick', 'xu-contextmenu', 'xu-focus', 'xu-keyup', 'xu-change', 'xu-blur', 'xu-init'];
glb.solid_attributes = ['disabled'];
 func.datasource = {};
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,
) {
  return 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 (!['live_preview', 'miniapp'].includes(_session.engine_mode)) {
      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];
            if (_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 = _.assignIn(
      {
        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);
  });
};
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 (!_.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);
      IS_DATASOURCE_REFRESH = true;
      _ds.refreshed = true;
      try {
        if (!_ds.v) _ds.v = {};

        delete _ds.v.old_dataSource; // to eliminate parse error
        delete _ds.rows_found;
        // _ds.raw_data = {};
        _ds.data_feed = {};

        _ds.v.old_dataSource = _.cloneDeep(_ds);
      } catch (err) {
        console.error('function: init_existing_dataSource - error');
        return;
      }
      dataSourceSession = dataSourceNoP;
    };

    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();

  _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 callback_datasource = async function () {
    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 },
        });
      }
    }

    if (await func.datasource.get_view_events_count(SESSION_ID, dataSourceSession, 'on_load')) {
      await func.datasource.execute_view_events(SESSION_ID, dataSourceSession, 'on_load');
    }
    // }
    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');
        filterModelMongo = ret.result;
      }
      let filterModelSql = _ds.progDataSource.filterModelSql;
      if (_ds.progDataSource.filterModelSqlFx) {
        let ret = await func.expression.get(SESSION_ID, _ds.progDataSource.filterModelSqlFx, dataSourceSession, 'query');
        filterModelSql = ret.result;
      }

      const filterModel = {
        filterModelNative: _ds.progDataSource.filterModelNative,
        filterModelMongo,
        filterModelSql,
        filterModelUserMongo: _ds.progDataSource.filterModelUserMongo,
        filterModelUserSql: _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');
        _dataSourceFilterModelType = fx_ret.result;
      }
      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');
      }

      _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,
        _ds?.progDataSource?.sortModel,
        null,
        filterModel,
        _dataSourceFilterModelType,
      );

      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;
      } else {
        _ds.rows_found = _ds?.v?.raw_data?.rows?.length || 0;
      }
      break;
    }
    case 'array': {
      let data = await get_data_from_source();

      if (data === null) {
        data = [];
      }

      // let _KEY = 0;
      // for (const _VAL of data) {
      //   _ds.data_feed.rows[_KEY] = { _KEY, _VAL };
      //   _KEY++;
      // }

      _ds.rows_found = data?.length || 0;

      // if (!_ds.v.raw_data) {
      //   _ds.v.raw_data = { rows: [] };
      // }
      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 = {};
      }

      // for (let [_KEY, _VAL] of Object.keys(data)) {
      //   _ds.data_feed.rows[_KEY] = { _KEY, _VAL };
      // }
      _ds.rows_found = Object.keys(data)?.length || 0;
      // if (!_ds.v.raw_data) {
      //   _ds.v.raw_data = { rows: [] };
      // }

      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 = '';
      }

      // if (!_ds.v.raw_data) {
      //   _ds.v.raw_data = { rows: [] };
      // }

      // let _KEY = 0;
      // let arr = data.split(",");
      // for (const _VAL of arr) {
      //   _ds.data_feed.rows[_KEY] = { _KEY, _VAL };
      //   _KEY++;
      // }
      // _ds.rows_found = arr?.length || 0;
      // if (!_ds.v.raw_data) {
      //   _ds.v.raw_data = { rows: [] };
      // }

      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({ _ROWID: 'newRecord' });
        }
      }

      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: {} });
        }
      }

      for await (let raw_data_row of _raw_data_rows) {
        _ds.currentRecordId = raw_data_row.id;
        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);
        let data_feed_str = JSON.stringify(_ds.data_feed);

        if (await get_after_record_count()) {
          await func.datasource.execute_view_events(SESSION_ID, dataSourceSession, 'after_record');
        }

        if (_ds.set_mode === 'C' || JSON.stringify(_ds.data_feed) !== data_feed_str) {
          const dbMsgP = await func.db.save_data(SESSION_ID, dataSourceSession);
          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');
          }
        }

        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) {
  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
        );

        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) {
                  console.error('deadlock detected');
                  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;
        }

        _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.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 && !_.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 || _.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 (_.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 (_.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) {
          elem_params = $('#' + _ds.containerId).data('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 = [];
    _.forEach(SESSION_OBJ[SESSION_ID].DS_GLB, function (val, key) {
      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 {
      if (
        Number(key) > 0 &&
        (val.screenId === screenIdP ||
          val.rootScreenId === screenIdP ||
          $('#' + val.screenId)
            .parent()
            .attr('id') === screenIdP ||
          (val && val.parentDataSourceNo && arr.includes(val.parentDataSourceNo.toString())))
      ) {
        arr.push(key);
        if (val.screenId) 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 = [];
    _.forEach(SESSION_OBJ[SESSION_ID].WORKER_OBJ.jobs, function (val, key) {
      if (val && val.dsSessionP == dsP) {
        arr.push(key);
        for (const [key, val] of Object.entries($('.screen_blocker'))) {
          $(val).remove();
        }
      }
    });
    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];

      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];
          }
        }
      }
    }
    if ($(SESSION_OBJ[SESSION_ID].root_element).find('xu-nav').length) {
      var ds_obj = $(SESSION_OBJ[SESSION_ID].root_element).find('xu-nav')?.data()?.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 (_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 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;
    };

    // 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') {
            _.set(_ds, field_id, value);
            const ret = update_xu_ref(dataSource);
            if (ret) {
              fields_changed.push(field_id);
              datasource_changed.push(dataSource);
            }
            continue;
          }

          if (typeof fields_data === 'object') {
            if (glb.GLOBAL_VARS[field_id]) {
              _ds.data_system[field_id] = value;
              continue;
            }

            try {
              const row_idx = func.common.find_ROWID_idx(_ds, record_id);
              // if (_ds.data_feed.rows[row_idx][field_id] !== value) {
              if (!_.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
                update_xu_ref(dataSource);

                if (!update_local_scope_only) {
                  let tree_ret = await func.utils.TREE_OBJ.get(SESSION_ID, _ds.prog_id);
                  if (glb.IS_WORKER) {
                    // RUN AT SERVER
                    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 {
                    // RUN AT CLIENT
                    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;
                    }
                  }
                }

                if (!fields_changed.includes(field_id)) {
                  fields_changed.push(field_id);

                  ///// REFRESH PARAMETERS IN
                  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);
                }

                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;
          }
        }
      }
    }

    if (glb.IS_WORKER) {
      if (!update_local_scope_only && !_.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 && !_.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));
        }

        await func.UI.screen.refresh_xu_attributes(SESSION_ID, _.cloneDeep(fields_changed), null, null, findMin(datasource_changed), avoid_xu_for_refresh, trigger);
        // await removed from the below function cause to dead lock Mar 3 25
        await func.UI.screen.refresh_screen(
          SESSION_ID,
          _.cloneDeep(fields_changed),
          null,
          datasource_changed[0], // refresh the current datasource only
        );
      }
      // ///// 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) $(_session.root_element).css('cursor', '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;
  }
  try {
    // parse json
    var json = JSON5.parse(exp.replace(/\n/gi, ''));
    for (const [key, val] of Object.entries(json)) {
      let id = key.substr(1, key.length - 1);
      if (!id) continue;
      viewFields.push({
        val,
        id,
      });
    }
    return viewFields;
  } catch (e) {
    if (!exp) {
      return;
    }
    if (exp.substr(exp.length - 1, 1) === ';') exp = exp.substr(0, exp.length - 1); // remove closing if exist;
    var exp_arr = exp.split(/;\s*(?=(?:[^"]|"[^"]*")*$)/g); // split ; outside quotes
    if (exp_arr?.length) {
      for (let val of exp_arr) {
        // run view fields
        var pos = val.indexOf(':');
        var seg = [val.substring(0, pos), val.substring(pos + 1)];
        if (seg && seg.length === 2) {
          let id = seg[0].substr(1, seg[0].length);
          if (!id) continue;
          viewFields.push({
            id,
            val: seg[1],
            //  , props: {},
          });
        } else {
          func.utils.debug.log(SESSION_ID, _ds.prog_id, {
            module: _ds.viewModule,
            action: 'set',
            prop: 'parse update exp',
            details: exp,
            result: '',
            error: 'Invalid json; too many segments',
            source: _ds.viewSourceDesc,
            json: '',
            fields: '',
            dsSession: dsSessionP,
          });
        }
      }
    }

    return viewFields;
  }
};
func.datasource.get_value = async function (SESSION_ID, fieldIdP, dsSessionP, rowIdP, org_dsSessionP) {
  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;
      if (recordId && recordId !== 'newRecord') org_dsSession = null;

      return await func.datasource.get_value(SESSION_ID, fieldIdP, _ds.parentDataSourceNo, recordId, 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);
    }
    return await return_value(fieldIdP);
  } //console.error("error: datasource not exist: " + dsSessionP);

  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 (!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) {
      // console.error(err);
    }
  }

  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) {}

  if (typeof _ds?.dynamic_fields?.[_field_id] !== 'undefined') {
    return await return_dynamic_value(_field_id, _ds.dynamic_fields[_field_id]);
  }

  return 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 ds = SESSION_OBJ[SESSION_ID].DS_GLB[0];
  // var v = ds.v;
  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 = _.size(data.rows);
  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 = _.cloneDeep(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 + ' ' + _.capitalize(source) + prog_info, '', 'E');
  };
  const report_conversion_warn = function (res) {
    var msg = `type mismatch auto conversion from value ${valP} to ${typeP}`;
    func.utils.debug_report(SESSION_ID, msg + ' ' + _.capitalize(source) + 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) {
  let _session = SESSION_OBJ[SESSION_ID];
  let _ds = _session.DS_GLB[dsSessionP];
  const _calling_ds = _session.DS_GLB[calling_dsP];

  if (_ds.PARAM_OUT_INFO) {
    let data = {};
    for await (const [key, val] of Object.entries(_ds.PARAM_OUT_INFO)) {
      if (val.prop === 'out') {
        try {
          const row_idx = func.common.find_ROWID_idx(_ds, _ds.currentRecordId);
          data[val.details] = _ds.data_feed.rows[row_idx][val.fieldId];
        } catch (err) {
          console.error(err);
        }
      }
    }
    if (!_.isEmpty(data)) {
      let datasource_changes = {
        [calling_dsP]: { [_calling_ds.currentRecordId]: data },
      };
      await func.datasource.update(SESSION_ID, datasource_changes);
    }
  }
};

func.datasource.set_outputField = async function (SESSION_ID, dsSessionP, result, args) {
  var _session = SESSION_OBJ[SESSION_ID];
  // let _ds = _session.DS_GLB[dsSessionP];

  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;
      await func.datasource.update(SESSION_ID, datasource_changes);
    }
  }
};

func.datasource.get_args_property_value = async function (SESSION_ID, dsSession, args, 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;
};
 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);
};
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;

  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) {
      return (DOCS_OBJ[_app_id][idP] = val);
    }
  }

  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] = await module.DOCS_OBJ_get(SESSION_ID, idP);
      if (DOCS_OBJ[_app_id][idP] && _.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] = _.merge(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 = _.padStart(sysDate.getDate(), 2, '0');
  var month = _.padStart(sysDate.getMonth() + 1, 2, '0');
  var year = sysDate.getFullYear();
  var week = _.padStart(getWeekNumber(sysDate), 2, '0');
  var hour = _.padStart(sysDate.getHours(), 2, '0');
  var minute = _.padStart(sysDate.getMinutes(), 2, '0');
  var second = _.padStart(sysDate.getSeconds(), 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)) {
    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 = _.clone(_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) && _.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();
  // 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;
    $('.screen_blocker').remove();
  };
  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,
              );
            }
            _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 {
    if (window.cordova) {
      device = window.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) {
      debugger;
    }
  },
  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);
    // debugger;
    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 (_.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);
    debugger;
    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) {
      debugger;
    }
  },
  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 get_script = function (callback) {
    function getScript(scriptUrl, callback) {
      const script = document.createElement('script');
      script.src = scriptUrl;
      if (type) script.type = type;
      script.onload = callback;

      document.head.appendChild(script);
    }
    if (glb.IS_WORKER) {
      callback(script);
      return;
    }
    function isScriptLoaded(src) {
      return GLB_JS_SCRIPTS_LOADED.includes(src);
    }

    if (isScriptLoaded(js_src)) {
      callback(false);
    } else {
      getScript(js_src, function (response, status) {
        // console.log(response,status)
        callback(true);
        GLB_JS_SCRIPTS_LOADED.push(js_src);
      });
    }
  };

  return new Promise((resolve) => {
    get_script(resolve);
  });
};

func.utils.load_css_on_demand = function (css_href) {
  function isCssLoaded(src) {
    try {
      return document.querySelector('link[href="' + src + '"]') ? true : false;
    } catch (err) {
      console.warn(`load css ${css_href} failed`);
      return true;
    }
  }

  if (isCssLoaded(css_href)) {
    return;
  }
  return $('<link/>', {
    rel: 'stylesheet',
    type: 'text/css',
    href: css_href,
  }).prependTo('head');
};

func.utils.remove_js_css_file = function (filename, filetype) {
  var targetelement = filetype == 'js' ? 'script' : filetype == 'css' ? 'link' : 'none'; //determine element type to create nodelist from
  var targetattr = filetype == 'js' ? 'src' : filetype == 'css' ? 'href' : 'none'; //determine corresponding attribute to test for
  var allsuspects = document.getElementsByTagName(targetelement);
  for (var i = allsuspects.length; i >= 0; i--) {
    //search backwards within node list for matching elements to remove
    if (allsuspects[i] && allsuspects[i].getAttribute(targetattr) != null && allsuspects[i].getAttribute(targetattr).indexOf(filename) != -1) allsuspects[i].parentNode.removeChild(allsuspects[i]); //remove element by calling parentNode.removeChild()
  }
};

func.utils.replace_studio_drive_url = function (SESSION_ID, val) {
  var _session = SESSION_OBJ[SESSION_ID];
  if (!_session.is_deployment) return val;
  try {
    return val.replaceAll(`https://xuda.io/studio-drive/${APP_OBJ[_session.app_id].app_replicate}`, `https://${_session.domain}/studio-drive/${APP_OBJ[_session.app_id].app_replicate}`);
  } 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) {
      // debugger;
      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') {
      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.debug_report = async function (SESSION_ID, sourceP, msgP, typeP, errP, objP) {
  var _session = SESSION_OBJ[SESSION_ID];
  var details = [sourceP];
  if (errP) details.push(errP);
  if (objP) details.push(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);

    // // temp inactive cause to loop

    // func.utils.alerts.invoke(
    //   SESSION_ID,
    //   "system_msg",
    //   "SYS_MSG_1240",
    //   "debug",
    //   null,
    //   msgP,
    //   sourceP //arguments.callee.caller.toString()- deprecated es6
    // );
    console.error('XUDA', 'ERROR', sourceP, msgP, details);

    if (typeof IS_API_SERVER !== 'undefined' || typeof IS_DOCKER !== 'undefined' || typeof IS_PROCESS_SERVER !== 'undefined') {
      return __.rpi.write_log(SESSION_OBJ[SESSION_ID].app_id, 'error', 'worker', 'runtime', msgP, null, objP, sourceP);
    }

    if (glb.IS_WORKER) {
      let obj = {
        service: 'write_log',
        data: msgP,
        log_type: 'error',
        id: STUDIO_WEBSOCKET_CONNECTION_ID,
        uid: _session.USR_OBJ._id,
        source: 'runtime',
        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);
    }

    // // temp inactive cause to loop
    // if (!glb.IS_WORKER) {
    //   await func.common.db(SESSION_ID, "write_log", {
    //     client_id: _session?.SYS_GLOBAL_OBJ_CLIENT_INFO?.fingerprint,
    //     source: sourceP,
    //     log_type: "error",
    //     msg: msgP,
    //   });
    // }
  }
  if (typeP === 'W') console.warn('XUDA', 'WARNING', msgP, details);
  if (typeP === 'I') console.log('XUDA', 'INFO', msgP, details);
};

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(_.upperFirst(alert_type), 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 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);

      try {
        let ret = await db.get(`cache_plugin_setup_${plugin_name}`);
        return resolve(ret.data);
      } catch (err) {
        // cache not found
        const json = 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);

        var doc = {
          _id: `cache_plugin_setup_${plugin_name}`,
          data: json,
          docType: 'cache_plugin',
        };
        db.put(doc);
      }
    } catch (e) {
      console.error(e);

      report_error('Error: ' + e.msg, e.error_type === 'W' ? true : false);
      resolve(e.msg);
    }
  });
};

func.utils.connect_pouchdb = async function (SESSION_ID) {
  const app_id = SESSION_OBJ[SESSION_ID].app_id;
  return new PouchDB('xuda_rt_' + app_id);
};

// 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 = _.assignIn(data, dataP);

    fetch(`https://xuda.io/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 get_path = function (resource) {
    if (_session.worker_type === 'Dev') {
      return `../../plugins/${plugin_name}/${resource}`;
    }
    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=${_session?.opt?.app_build_id || 0}`;
    }
  };

  return new Promise(async (resolve, reject) => {
    // 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) {
      console.error(err);
      reject();
    }
  });
};

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',
        },
      ],
    };

    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 get_path = function (resource) {
    if (_session.worker_type === 'Dev') {
      return `../../plugins/${plugin_name}/${resource}`;
    }

    return `https://${_session.domain}/plugins/${plugin_name}/${resource}?app_id=${_session.app_id}&ts=${_session?.opt?.app_build_id || 0}`;
  };

  return get_path(resource);
};

func.utils.write_log = async function (SESSION_ID, method = '', msg = '', log_type = 'error', source = 'runtime', details) {
  const _session = SESSION_OBJ[SESSION_ID];
  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);
  }

  await func.common.db(SESSION_ID, 'write_log', {
    msg,
    log_type,
    source,
    details,
    method,
  });
};

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 = _.clone(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.validate = async function (SESSION_ID, triggerP, dsSessionP, eventIdP, sourceP, argumentsP, return_validation_onlyP) {
  var _session = SESSION_OBJ[SESSION_ID];
  var _ds = _session.DS_GLB[dsSessionP];
  var args = {
    triggerP,
    dsSessionP,
    eventIdP,

    sourceP,
    argumentsP,
    return_validation_onlyP,
  };
  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);
    }
  };
  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 (!_.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);
                }

                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 || _.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;
                          if ($(container).data().xuData.panel_info) {
                            screen_prop = $(container).data().xuData.panel_info.paramsP;
                          } else {
                            ///////////////
                            container = '#' + _session.DS_GLB[dsSessionP].screenId;

                            if ($(container) && $(container).data()) {
                              screen_prop = $(container).data().xuData.paramsP;
                            }

                            if (!$(container) || !$(container).length) {
                              container = '#' + _session.DS_GLB[dsSessionP].containerId;
                            }
                            //////////////
                          }
                        } catch (e) {
                          console.error(e);
                        }
                      } else {
                        container = '#' + _ds.screenId;

                        if ($(container) && $(container).data()) {
                          screen_prop = $(container).data().xuData.paramsP;
                        }

                        if (!$(container) || !$(container).length) {
                          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,
                      ),
                    );
                  }
                }
              };
              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,
) {
  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,
    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;
  _.forEach(_session.WORKER_OBJ.jobs, function (val, key) {
    if (val && val.job_num == jobNoP) {
      ret = key;
      return false;
    }
  });
  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,
) {
  var _session = SESSION_OBJ[SESSION_ID];
  var _ds = _session.DS_GLB[dsSessionP];
  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 = field_elm.attr('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
      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);

        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 = await func.utils.VIEWS_OBJ.get(SESSION_ID, await get_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;
        }
        console.warn(`Warning: Program ${_prog.properties.menuName} expected In parameter: ${val.data.parameter} but received null instead`);
      }
    }
    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 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 = $('#' + _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.UI.screen.init(SESSION_ID, await get_prog_id(), $(containerP)?.data()?.xuData.screenId, _session.DS_GLB[dsSession], $calling_container, eventIdP, rowP, jobNoP, is_panel, params_obj, functionP, 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 = _.clone(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);
      };
      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) {
            debugger;
          }
        }
        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 func.common.get_module(SESSION_ID, 'xuda-event-javascript-module.mjs');
      const result = await module.call_javascript(SESSION_ID, jobNoP, refIdP, dsSession, false, $div);
      await func.datasource.set_outputField(SESSION_ID, dsSessionP, result, args);

      return result;
    },
    call_evaluate_javascript: async function () {
      const module = await func.common.get_module(SESSION_ID, 'xuda-event-javascript-module.mjs');
      const result = await module.call_javascript(SESSION_ID, jobNoP, refIdP, dsSession, true, $div);
      await func.datasource.set_outputField(SESSION_ID, dsSessionP, result, args);

      return result;
    },
    execute_native_javascript: async function () {
      const module = await func.common.get_module(SESSION_ID, 'xuda-event-javascript-module.mjs');

      const result = await module.run_javascript(
        SESSION_ID,
        jobNoP,
        dsSession,
        `(async function(el,evt) {
          ${refIdP.value}
          })(document.querySelector(\`[xu-ui-id="${elementP}"]\`),evt)`,
        null,
        null,
        null,
        evt,
        $div,
      );
      await func.datasource.set_outputField(SESSION_ID, dsSessionP, result, args);

      return result;
    },
    execute_evaluate_javascript: async function () {
      const module = await func.common.get_module(SESSION_ID, 'xuda-event-javascript-module.mjs');

      const result = await module.run_javascript(
        SESSION_ID,
        jobNoP,
        dsSession,
        `(async function(el,evt) {
          ${refIdP.value}
          })(document.querySelector(\`[xu-ui-id="${elementP}"]\`),evt)`,
        true,
        null,
        null,
        evt,
        $div,
      );
      await func.datasource.set_outputField(SESSION_ID, dsSessionP, result, args);

      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) {
        $(document).trigger(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);

          return await func.datasource.run_events_functions(SESSION_ID, dsP, event_name, jobNoP, null, calling_trigger_prop?.data?.name?.parameters || {});
        }
        // 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);

        await func.datasource.run_events_functions(SESSION_ID, dsP, event_name, jobNoP, calling_trigger_prop?.props?.async, calling_trigger_prop?.data?.name?.parameters || {});
        // 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 && !_.isEmpty(parameters)) {
          await func.datasource.update_changes_for_out_parameter(SESSION_ID, _ds_new.dsSession, _ds.dsSession);
        }

        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 obj_values_to_update = func.datasource.get_viewFields_for_update_function(SESSION_ID, calling_trigger_prop, null, dsSessionP);
      if (!obj_values_to_update || _.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;

        if (elementP) {
          $element = $(`[xu-ui-id="${elementP}"]`).clone(true);
        }

        let ret_field_id = await func.expression.get(SESSION_ID, val.id.trim(), dsSessionP, 'update', null, null, null, null, null, null, $element?.length ? $element?.data()?.xuData?.iterate_info : null);
        let ret_value = await func.expression.get(SESSION_ID, val.val.trim(), dsSessionP, 'update', null, null, null, null, null, null, $element?.length ? $element?.data()?.xuData?.iterate_info : null);
        let _field_id = ret_field_id.result;
        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, null, 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);
        }
      }

      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);

      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);
        }
      }
      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 + ' ' + _.capitalize(source) + prog_info, '', 'E');
      };
      const report_conversion_warn = function (res) {
        var msg = `${elementP} >${triggerP} >${functionP} > type mismatch auto conversion from value ${valP} to ${typeP}`;
        func.utils.debug_report(SESSION_ID, msg + ' ' + _.capitalize(source) + 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 = _.reduce(
        payload_arr,
        (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);
        }
      }
      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);
  //   debugger;
  // }
  // }
};

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)) {
        _.forEach(_session.WORKER_OBJ.jobs, function (val2, key2) {
          if (key2 === val) {
            found = true;
            return false;
          }
        });
      }
      if (!found) {
        do_callback();
        return;
      }
    }, 100);
    var do_callback = function () {
      window.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')) document.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) {
        return console.warn('prog empty');
      }

      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.UI.screen.init(SESSION_ID, params_obj.prog, null, null, $('#embed_' + SESSION_ID), null, null, null, null, null, 'pendingUrlEvent_embed');
      } else {
        console.error('Program not exist', params_obj.prog_id);
        func.UI.utils.progressScreen.show(SESSION_ID, 'Program not exist', null, true);
      }
    } else {
      console.warn('url empty');
    }
  }
};
func.events.invoke = async function (event_id) {
  var _session = SESSION_OBJ[SESSION_ID];
  if (!event_id) {
    console.warn('event_id Cannot be empty');
    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 (_.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) {
    console.warn('event_id not found');
    return false;
  }

  func.events.validate(SESSION_ID, 'user_defined', ds, event_id);
};
 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;

      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;
        }
      }

      ret = ret.replace(/\&amp;/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.substr(0, 6) === '_DATE_') {
              ret = ret.substr(6);
            } else if (
              (ret && ret.length === 10 && ret.substr(4, 1) === '-' && ret.substr(7, 1) === '-') ||
              ret === 'self' // bypass eval for date 2017-03-22
            ) {
              ret = ret;
            } else {
              ret = await func.expression.secure_eval(SESSION_ID, sourceP, ret, jobNo, dsSessionP, js_script_callback);
            }
            // 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 split = func.expression.parse(ret) || [];
      // console.log(valP, split);
      for await (const [arr_key, val] of Object.entries(split)) {
        // run each field
        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) {
                  property2 = await func.expression.get_property(val.value).property2; //val.value.substr(val.value.indexOf(".") + 1, val.value.length); // get .
                  if (prevData[data]) set_value(prevData[data][property2]);
                  //                                    var_Arr[key].value = prevData[data][property2]; //@objB[@var].property
                }
                delete var_Arr[key - 1];
              } else {
                property1 = await func.expression.get_property(val.value).property1;
                property2 = await func.expression.get_property(val.value).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 = 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
        _.forEach(var_Arr, 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) {
            if (!Array.isArray(val.value) && !var_Arr[key + 1].value?.includes('.')) {
              // 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') {
          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 = _.assignIn(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);
            } 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 = function (input) {
  if (typeof input !== 'string') return [];

  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;
  }

  return segments;
};

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.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 '';
    }
  }

  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;
//       }
//     }
//   }
// };

func.expression.secure_eval = async function (SESSION_ID, sourceP, val, job_id, dsSessionP, js_script_callback, evt, ignore_errors) {
  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';

  // Client-side execution
  if (!isServer) {
    try {
      return eval(val);
    } catch (err) {
      try {
        if (sourceP === 'javascript' && !ignore_errors) console.error(err);
        return JSON5.parse(val);
      } catch (err) {
        if (sourceP === 'javascript' && !ignore_errors) console.error(err);
        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 = (err) => {
    console.error('Execution error:', err);
    func.events.delete_job(SESSION_ID, job_id);
    if (isServer && !SESSION_OBJ[SESSION_ID].crawler) {
      if (sourceP === 'javascript') {
        __.rpi.write_log(SESSION_OBJ[SESSION_ID].app_id, 'error', 'worker', 'vm error', err, null, val, 'func.expression.get.secure_eval');
      } else {
        __.db.add_error_log(SESSION_OBJ[SESSION_ID].app_id, 'api', err);
      }
    }
    return val; // Fallback to original value
  };

  if (sourceP === 'javascript') {
    process.on('uncaughtException', handleError);
    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 });
      const vm = new VM.NodeVM({
        require: { external: true },
        sandbox,
        timeout: 60000,
      });
      return await vm.run(script, { filename: dir, dirname: dir });
    } catch (err) {
      return handleError(err);
    }
  }

  try {
    const vm = new VM.VM({
      sandbox,
      timeout: 1000,
      allowAsync: false,
    });
    return await vm.run(val);
  } catch {
    try {
      return JSON5.parse(val);
    } catch {
      return val;
    }
  }
};