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.

507 lines (444 loc) 18.3 kB
export class XudaModule { constructor(e) { this.func = e.func; this.glb = e.glb; this.SESSION_OBJ = e.SESSION_OBJ; this.SESSION_ID = e.SESSION_ID; this.APP_OBJ = e.APP_OBJ; this.dsSession = e.dsSession; this.job_id = e.job_id; this._session = this.SESSION_OBJ[this.SESSION_ID]; return this; } async get_field_value(field_id) { if (!field_id) { this.log('E', 'xu.getFieldValue', 'field_id cannot be empty', { code: 'RUN_MSG_API_010' }); return false; } let ret_get_value = await this.func.datasource.get_value(this.SESSION_ID, field_id, this.dsSession); if (!ret_get_value.found) { this.log('E', 'xu.getFieldValue', `${field_id} field not found`, { code: 'RUN_MSG_API_020' }); return false; } return ret_get_value.ret.value; } async set_field_value(field_id, value, avoid_refresh) { if (!field_id) { this.log('E', 'xu.setFieldValue', 'field_id cannot be empty', { code: 'RUN_MSG_API_010' }); return false; } if (typeof value === 'undefined') { this.log('E', 'xu.setFieldValue', `${field_id} - value cannot be undefined`, { code: 'RUN_MSG_API_030' }); return false; } let ret_get_value = await this.func.datasource.get_value(this.SESSION_ID, field_id, this.dsSession); if (!ret_get_value.found) { this.log('E', 'xu.setFieldValue', `${field_id} field not found`, { code: 'RUN_MSG_API_020' }); return false; } let _ds = this._session.DS_GLB[ret_get_value.dsSessionP]; const datasource_changes = { [_ds.dsSession]: { [ret_get_value.currentRecordId]: { [field_id]: value }, }, }; const refresh_options = avoid_refresh === true ? { avoid_refresh: true, refresh_attributes: true } : avoid_refresh && typeof avoid_refresh === 'object' ? avoid_refresh : { defer_refresh: true }; if (avoid_refresh === true) { try { globalThis.__XUDA_RT_TRACE && console.log( '[xuda-runtime] api_set_field_value_attributes_only ' + JSON.stringify({ field_id, dsSession: _ds.dsSession, }), ); } catch (e) {} } else if (!avoid_refresh) { try { globalThis.__XUDA_RT_TRACE && console.log( '[xuda-runtime] api_set_field_value_deferred_refresh ' + JSON.stringify({ field_id, dsSession: _ds.dsSession, }), ); } catch (e) {} } return await this.func.datasource.update(this.SESSION_ID, datasource_changes, null, refresh_options); } async invoke_event(event_id, options) { if (!event_id) { this.log('E', 'xu.invokeEvent', 'event_id cannot be empty', { code: 'RUN_MSG_API_040' }); return false; } const event_options = options && typeof options === 'object' ? options : { avoid_refresh: options === true }; let ds; for await (const [key, val] of Object.entries(Object.assign([], this._session.DS_GLB).reverse())) { let ds_val = val; let ds_key = ds_val.dsSession; const _view_obj = await this.func.utils.VIEWS_OBJ.get(this.SESSION_ID, ds_val.prog_id); if (xu_isEmpty(_view_obj.progEvents)) continue; if (ds) break; for await (const [key, val] of Object.entries(_view_obj.progEvents)) { if (val?.data?.type === 'user_defined' && val.data.event_name === event_id) { ds = ds_key; break; } } } if (typeof ds === 'undefined') { this.reject('xu.invokeEvent error', `${event_id} event_id not found`, this.job_id, { code: 'RUN_MSG_API_050', source: 'xu.invokeEvent' }); return false; } return this.func.events.validate(this.SESSION_ID, 'user_defined', ds, event_id, null, null, null, event_options); } async read_drive(filename, cb) { try { const response = await fetch(`https://${this._session.domain}/workspace-drive/` + filename + '&' + this._session.gtp_token); if (cb) { return cb(await response.json()); } return await response.json(); } catch (e) { this.reject('xu.readDrive error', e, this.job_id, { code: 'RUN_MSG_API_090', source: 'xu.readDrive', err: e }); } } async write_drive(stream, filename, make_public, cb) { var data = { app_id: this._session.app_id, gtp_token: this._session.gtp_token, app_token: this._session.app_token, req_from_api: 'true', public: make_public ? 'true' : '', }; const form = new FormData(); for (const [key, val] of Object.entries(data)) { form.append(key, val); } form.append('file', stream, filename); try { const response = await fetch(this.func.common.get_url(this.SESSION_ID, 'rpi', 'runtime_upload_file'), { method: 'POST', headers: { Accept: 'application/json', 'Content-Type': 'application/json', }, body: JSON.stringify(form), }); if (cb) { return cb(await response.json()); } return await response.json(); } catch (e) { this.reject('xu.writeDrive error', e, this.job_id, { code: 'RUN_MSG_API_090', source: 'xu.writeDrive', err: e }); } } resolve(cb, job_id) { if (job_id) { this.func.events.delete_job(this.SESSION_ID, job_id); } if (cb) { cb(); } } reject(msg, details, job_id, meta = {}) { if (job_id) { this.func.events.delete_job(this.SESSION_ID, job_id); } this.log('E', msg, details, meta); } log(type, msg, details, meta = {}) { const err = meta.err || (details instanceof Error ? details : null); const message = typeof details === 'undefined' ? msg : details; if (this.func.utils.report_issue) { return this.func.utils.report_issue(this.SESSION_ID, { code: meta.code, source: meta.source || msg, message, type, err, details: meta.details, }); } this.func.utils.debug_report(this.SESSION_ID, meta.source || msg, message, type, err, meta.details); } alert(type, display, msg, details) { this.func.utils.alerts.execute( this.SESSION_ID, type, // error, warn, info, log display, // console, modal, toast, browser details, msg, ); } async call_project_api(prog_id, params, cb) { const _prog_obj = await this.func.utils.DOCS_OBJ.get(this.SESSION_ID, prog_id); if (_prog_obj?.properties?.menuType !== 'api') { return this.reject('xu.callApiProgram error', `${prog_id} is not an API program`, this.job_id); } // also call internal API if call made from the server itself if (this._session.engine_mode === 'live_preview' || typeof IS_PROCESS_SERVER !== 'undefined') { const ret = await this.func.datasource.prepare(this.SESSION_ID, prog_id, null, 0, null, null, null, null, null, null, 'api', null, null, null, params); try { const _ds = this._session.DS_GLB[ret.dsSessionP]; if (typeof _ds.api_rendered_output === 'undefined' || typeof _ds.tree_obj.apiOutput === 'undefined' || !_ds.tree_obj.apiOutput) { throw new Error('undefined api_rendered_output/apiOutput'); } if (_ds.tree_obj.apiOutput === 'json') { try { return JSON5.parse(_ds.api_rendered_output); } catch (err) { this.log('E', 'xu.call_project_api', err, { code: 'RUN_MSG_API_090', source: 'xu.call_project_api', err }); return {}; } } return _ds.api_rendered_output; } catch (e) { this.reject('xu.call_project_api error', e.message || e, this.job_id, { code: 'RUN_MSG_API_090', source: 'xu.call_project_api', err: e }); } } // miniapp if (['miniapp'].includes(this._session.engine_mode)) { try { const response = await fetch(`https://${this._session.domain}/api`, { method: 'POST', headers: { Accept: 'application/json', 'Content-Type': 'application/json', }, body: JSON.stringify({ ...params, ...{ prog_id } }), }); if (cb) { return cb(await response.json()); } return await response.json(); } catch (e) { this.reject('xu.get_table_data error', e, this.job_id, { code: 'RUN_MSG_API_090', source: 'xu.get_table_data', err: e }); } } // deployments try { const response = await fetch(`https://${this._session.domain}/execute_api_program`, { method: 'POST', headers: { Accept: 'application/json', 'Content-Type': 'application/json', }, body: JSON.stringify({ ...params, ...{ prog_id } }), }); if (cb) { return cb(await response.json()); } return await response.json(); } catch (e) { this.reject('xu.get_table_data error', e, this.job_id, { code: 'RUN_MSG_API_090', source: 'xu.get_table_data', err: e }); } } async call_system_api(api_method, payload = {}, cb) { try { let body = { ...{ app_id: this._session.app_id, app_id_query: this._session.app_id, app_token: this._session.app_token, }, ...payload, }; const response = await fetch(`https://${this._session.domain}/cpi/${api_method}`, { method: 'POST', headers: { Accept: 'application/json', 'Content-Type': 'application/json', }, body: JSON.stringify(body), }); // if (!response.ok) { // return { code: response.status, data: response.statusText }; // } const json = await response.json(); if (cb) { return cb(json); } return json; } catch (e) { this.reject('xu.call_system_api error', e, this.job_id, { code: 'RUN_MSG_API_090', source: 'xu.call_system_api', err: e }); } } async call_external_api(method, url, payload = {}, cb) { try { let json = { ...{ app_id: this._session.app_id, app_id_query: this._session.app_id, app_token: this._session.app_token, }, ...payload, }; const response = await fetch(`https://${url}`, { method, headers: { Accept: 'application/json', 'Content-Type': 'application/json', }, body: JSON.stringify(json), }); if (cb) { return cb(await response.json()); } return await response.json(); } catch (e) { this.reject('xu.call_external_api error', e, this.job_id, { code: 'RUN_MSG_API_090', source: 'xu.call_external_api', err: e }); } } async call_javascript(prog_id, params = {}, evaluate = false) { try { const module = await this.get_runtime_module_with_method('xuda-event-javascript-module.mjs', 'call_javascript'); const result = await module.call_javascript(this.SESSION_ID, this.job_id, { prog: prog_id, parameters: params }, this.dsSession, evaluate); return result; } catch (e) { this.reject('xu.call_javascript error', e, this.job_id, { code: 'RUN_MSG_API_090', source: 'xu.call_javascript', err: e }); } } async get_runtime_module_with_method(module_name, method_name) { const load_module = async () => { const module_ret = await this.func.common.get_module(this.SESSION_ID, module_name); if (typeof module_ret?.[method_name] === 'function') { return module_ret; } if (typeof module_ret?.default?.[method_name] === 'function') { return module_ret.default; } return module_ret; }; let module_ret = await load_module(); if (typeof module_ret?.[method_name] === 'function') { return module_ret; } for (const key of Object.keys(this.func.common._import_cache || {})) { if (key.includes(module_name)) { delete this.func.common._import_cache[key]; } } if (typeof globalThis !== 'undefined') { globalThis.__XU_RUNTIME_MODULE_CACHE_TAG__ = Date.now(); } module_ret = await load_module(); if (typeof module_ret?.[method_name] === 'function') { return module_ret; } throw new TypeError(`${module_name}.${method_name} is not available`); } async dbs_create(table_id, data, cb) { const _table_obj = await this.func.utils.DOCS_OBJ.get(this.SESSION_ID, table_id); if (_table_obj?.properties?.menuType !== 'table') { return this.reject('xu.dbsCreate error', `${table_id} is not a table`, this.job_id); } try { const ret = await this.func.common.db(this.SESSION_ID, 'dbs_create', { table_id, table_data: data }); if (cb) { return cb(ret); } return ret; } catch (e) { this.reject('xu.dbs_create error', e, this.job_id, { code: 'RUN_MSG_API_090', source: 'xu.dbs_create', err: e }); } } async dbs_read(table_id, selector = {}, fields = [], sort, limit = 999, skip, cb) { const _table_obj = await this.func.utils.DOCS_OBJ.get(this.SESSION_ID, table_id); if (_table_obj?.properties?.menuType !== 'table') { return this.reject('xu.dbsRead error', `${table_id} is not a table`, this.job_id); } const normalize_rows_response = function (ret) { const rows = Array.isArray(ret) ? ret : Array.isArray(ret?.rows) ? ret.rows : Array.isArray(ret?.data?.rows) ? ret.data.rows : []; Object.defineProperties(rows, { rows: { value: rows, enumerable: false, configurable: true }, code: { value: ret?.code, enumerable: false, configurable: true }, data: { value: ret?.data, enumerable: false, configurable: true }, total_rows: { value: ret?.data?.total_rows ?? ret?.total_rows ?? rows.length, enumerable: false, configurable: true }, response: { value: ret, enumerable: false, configurable: true }, }); return rows; }; let data = { fields, table_id, dataSourceFilterModelType: 'query', filterModelMongo: typeof selector === 'string' ? selector : JSON.stringify(selector || {}), limit, skip, sort, }; try { const ret = await this.func.common.db(this.SESSION_ID, 'dbs_read', data); if (cb) { return cb(ret); } return normalize_rows_response(ret); } catch (e) { this.reject('xu.dbs_read error', e, this.job_id, { code: 'RUN_MSG_API_090', source: 'xu.dbs_read', err: e }); } } async dbs_update(table_id, row_id, data, cb) { const _table_obj = await this.func.utils.DOCS_OBJ.get(this.SESSION_ID, table_id); if (_table_obj?.properties?.menuType !== 'table') { return this.reject('xu.dbsUpdate error', `${table_id} is not a table`, this.job_id); } if (!row_id) { return this.reject('xu.dbsDelete error', `row_id is a mandatory field`, this.job_id); } try { const ret = await this.func.common.db(this.SESSION_ID, 'dbs_update', { table_id, row_id, table_data: data }); if (cb) { return cb(ret); } return ret; } catch (e) { this.reject('xu.dbs_update error', e, this.job_id, { code: 'RUN_MSG_API_090', source: 'xu.dbs_update', err: e }); } } async dbs_delete(table_id, row_id, cb) { const _table_obj = await this.func.utils.DOCS_OBJ.get(this.SESSION_ID, table_id); if (_table_obj?.properties?.menuType !== 'table') { return this.reject('xu.dbsDelete error', `${table_id} is not a table`, this.job_id); } if (!row_id) { return this.reject('xu.dbsDelete error', `row_id is a mandatory field`, this.job_id); } try { const ret = await this.func.common.db(this.SESSION_ID, 'dbs_delete', { table_id, ids: [row_id] }); if (cb) { return cb(ret); } return ret; } catch (e) { this.reject('xu.dbs_delete error', e, this.job_id, { code: 'RUN_MSG_API_090', source: 'xu.dbs_delete', err: e }); } } } // ============================================================================ // LLM knowledge registry — pure data consumed by the LLM docs layer // (runtime/docs/llm/ placeholder {{XU_SCRIPT_API}} via xuda-studio-llm-guide). // Mirrors the XudaModule methods above — keep in sync when the class changes. // ============================================================================ export const XU_SCRIPT_API = { 'xu.get_field_value(field_id)': 'Read a field of the calling context (component/program dataset). Returns the value, or false + logged error when the field does not exist.', 'xu.set_field_value(field_id, value, avoid_refresh?)': 'Write a field; the datasource updates and bound UI refreshes. This is how a script hands results back to the component.', 'xu.invoke_event(event_name, options?)': 'Trigger a user_defined progEvents entry by its event_name.', 'xu.dbs_create(table_id, row_obj)': 'Insert one row into a xuda table prog (menuType table). row_obj keys are the table field_ids. Returns {code, data}.', 'xu.dbs_read(table_id, selector?, fields?, sort?, limit?, skip?)': 'Query a xuda table with a Mongo-style selector object ({} = all). Returns the rows ARRAY (default limit 999; rows also expose .total_rows).', 'xu.dbs_update(table_id, row_id, changes_obj)': 'Update one row by its id.', 'xu.dbs_delete(table_id, row_id)': 'Delete one row by its id.', 'xu.call_javascript(prog_id, params?, evaluate?)': 'Run another javascript prog with parameters.', 'xu.call_project_api(prog_id, params?)': 'Execute an api prog of this project and get its rendered output (parsed when the api outputs json).', 'xu.call_system_api(method, payload?)': 'POST a platform cpi method (app_id/app_token are added automatically).', 'xu.call_external_api(method, url, payload?)': 'HTTP request to an external host (url WITHOUT the https:// prefix).', 'xu.alert(type, display, message, details?)': 'Show an alert - type: error|warn|info|log; display: console|modal|toast|browser.', 'xu.log(type, message, details?)': 'Report to the runtime issue log (type E|W|I).', 'xu.read_drive(filename)': 'Fetch a file from the app workspace drive (JSON-parsed).', 'xu.write_drive(stream, filename, make_public?)': 'Upload a file to the app workspace drive.', };