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