@xuda.io/xuda-dbs-plugin-xuda
Version:
Xuda Database Socket for Xuda's proprietary structure powered by CouchDB
1,020 lines (888 loc) • 28.7 kB
JavaScript
const _ = require('lodash');
const crypto = require('node:crypto');
const check_unique = async function (e, docP, table_obj, db, app_id_reference) {
var len = docP.udfIndex.length;
if (!len) {
return { code: -11, data: 'table must have primary index' };
}
var found_unique = undefined;
var ret = { code: 1, data: 'ok' };
for await (var val of docP.udfIndex) {
if (!table_obj.tableIndexes) {
ret = {
code: -14,
data: 'table definition missing for: ' + val.fileId,
};
break;
}
const table_index = find_item_by_key_root(table_obj.tableIndexes, 'id', val.indexId);
if (!table_index) {
ret = {
code: -13,
data: 'keys definition missing for Table: ' + val.fileId,
};
break;
}
if (!table_index.data.unique) {
continue;
}
if (!table_index?.data?.keys || _.isEmpty(table_index.data.keys)) {
ret = {
code: -14,
data: 'keys definition missing for index: ' + val.indexId,
};
break;
}
var keysArr = table_index.data.keys;
var keyValue = [];
for await (const [keySegment, valSegment] of Object.entries(keysArr)) {
// run on key segment
let _fieldType = find_item_by_key(table_obj.tableFields, 'field_id', valSegment).props.fieldType;
let _val = await get_cast_val('check_unique', valSegment, _fieldType, val.keyValue[Number(keySegment)]);
keyValue.push(_val);
}
var key = '["' + val.fileId + '","' + val.indexId + '",[' + keyValue.join(',') + ']]';
try {
e.view = 'db_index';
const ret = await query_db(e, db, app_id_reference, table_obj);
const json = ret.data;
// var data;
if (json.code > 0) {
var data = json.data;
if (data?.rows?.length) {
for await (var val of data.rows) {
if (val.id !== docP._id) {
found_unique = true;
}
}
if (found_unique) {
throw 'error checking unique:' + key;
}
}
}
// throw json.data;
return { code: 1, data: 'ok' };
} catch (msg) {
console.error(msg);
ret = { code: -22, data: msg };
break;
}
}
return ret;
};
const get_index_json = async function (docInP, table_obj) {
try {
var keysArr = [];
var index_json = [];
var key_val = [];
if (docInP.udfData && table_obj) {
// check for udf data array
if (table_obj.tableIndexes) {
// console.log(udfDicIndexT.rows);
for await (var valIndex of table_obj.tableIndexes) {
// run on index rows
keysArr = valIndex.data.keys; // create array from keys string segment
key_val = [];
for await (var valSegment of keysArr) {
// run on key segment
// find keys values
if (docInP.udfData.data[valSegment]) {
key_val.push(docInP.udfData.data[valSegment]);
continue;
}
// put the value
// debugger;
const field_obj = find_item_by_key(table_obj.tableFields, 'field_id', valSegment);
if (!field_obj) {
throw 'field not found in key: ' + valSegment;
}
let _val = await get_cast_val('get_index_json', field_obj.id, field_obj.props.fieldType, null);
key_val.push(_val);
}
index_json.push({
fileId: docInP.udfData.udffileid,
indexId: valIndex.id,
keySegments: keysArr,
keyValue: key_val,
});
}
// );
}
}
return { code: 1, data: index_json };
} catch (msg) {
return { code: -1, data: msg };
}
};
const find_item_by_key_root = function (arr, key, val) {
return _.find(arr, function (e) {
return e[key] === val;
});
};
const find_item_by_key = function (arr, key, val) {
return _.find(arr, function (e) {
return e.data[key] === val;
});
};
const get_cast_val = async function (source, attributeP, typeP, valP) {
const module = require('@xuda.io/xu_cast');
return module.cast(
typeP,
valP,
function (res) {
var msg = `error converting ${attributeP} from ${valP} to ${typeP}`;
console.error(source, msg);
},
function (res) {
var msg = `type mismatch auto conversion made to ${attributeP} from value ${valP} to ${typeP}`;
console.warn(source, msg);
},
);
};
const get_opt = function (e, table_obj) {
var limit = 99999;
var skip = 0;
if (e.limit) {
limit = Number(e.limit);
}
if (e.skip) {
skip = JSON.parse(e.skip);
}
var fields = [];
if (e.fields) {
for (const [key, val] of Object.entries(typeof e.fields === 'string' ? e.fields.split(',') : e.fields)) {
fields.push(val);
}
} else {
for (const [key, val] of Object.entries(table_obj.tableFields)) {
fields.push(val.data.field_id);
}
}
var data = {};
var from = e.filter_from ? JSON.parse(e.filter_from) : {};
var to = e.filter_to ? JSON.parse(e.filter_to) : {};
var sort = [];
for (const [key, val] of Object.entries(from)) {
var field_name = key;
sort.push(field_name);
if (val === to[key]) {
data[field_name] = val;
} else {
data[field_name] = { $gte: val };
}
}
for (const [key, val] of Object.entries(to)) {
var field_name = key;
if (val !== from[key]) {
data[field_name]['$lte'] = val;
}
}
var selector = {};
if (from && !_.isEmpty(from)) {
selector = data;
}
var opt = {
selector,
limit,
skip,
fields,
};
if (sort.length) {
opt.sort = sort;
}
let _sortModel = e?.sortModel || [];
if (typeof _sortModel === 'string') {
_sortModel = JSON.parse(e.sortModel);
}
if (_sortModel.length) {
opt.sort = _sortModel;
if (opt.sort) {
for (const [key, val] of Object.entries(opt.sort)) {
var field_name = val.field_id || val.colId;
opt.sort[key] = {
['udfData.data.' + field_name]: val.sort_dir || val.sort,
};
}
}
}
if (e.total_fields_info) {
fields = [];
for (const [key, val] of Object.entries(JSON.parse(e.total_fields_info))) {
var field_name = val.field_id;
fields.push(field_name);
}
opt.fields = fields;
}
// fix names
for (const [key, val] of Object.entries(opt.fields)) {
opt.fields[key] = 'udfData.data.' + val;
}
const _sort_model = typeof e.sortModel === 'string' ? JSON.parse(e.sortModel) : e.sortModel;
// if (!e?.sortModel || !JSON.parse(e.sortModel).length) {
if (!e?.sortModel || !_sort_model.length) {
// added 2021 09 10
if (opt.sort) {
for (const [key, val] of Object.entries(opt.sort)) {
opt.sort[key] = {
['udfData.data.' + val]: e.sortOrder === 'des' ? 'desc' : 'asc',
};
}
}
}
var selector_new = {};
function replaceKeysInQuery(query) {
const keys_to_replace = table_obj.tableFields.map((e) => {
return e?.data?.field_id;
});
// Helper function to recursively process the query object
function recursiveReplace(obj) {
if (typeof obj === 'object' && obj !== null) {
// Create a new object to store the modified query
let newObj = Array.isArray(obj) ? [] : {};
// Traverse through the object
for (let key in obj) {
if (obj.hasOwnProperty(key)) {
// If the key is in the keys_to_replace array, replace it
let newKey = keys_to_replace.includes(key) ? `udfData.data.${key}` : key;
// Recursively process nested objects
newObj[newKey] = recursiveReplace(obj[key]);
}
}
return newObj;
} else {
// If it's not an object or array, return the value as is
return obj;
}
}
// Start the recursive replacement
return recursiveReplace(query);
}
function replaceRegexOptions(query) {
// Helper function to recursively process the query object
function recursiveReplace(obj) {
if (typeof obj === 'object' && obj !== null) {
// Create a new object to store the modified query
let newObj = Array.isArray(obj) ? [] : {};
// Traverse through the object
for (let key in obj) {
if (obj.hasOwnProperty(key)) {
if (key === '$regex') {
newObj[key] = obj.$options ? `(?${obj.$options})${obj[key]}` : obj[key];
return newObj;
}
// Recursively process nested objects
newObj[key] = recursiveReplace(obj[key]);
}
}
return newObj;
} else {
// If it's not an object or array, return the value as is
return obj;
}
}
// Start the recursive replacement
return recursiveReplace(query);
}
if (e.dataSourceFilterModelType === 'query' && e.filterModelMongo) {
// selector_new['$and'] = [replaceRegexOptions(replaceKeysInQuery(JSON.parse(e.filterModelMongo)))];
selector_new['$and'] = [replaceRegexOptions(replaceKeysInQuery(typeof e.filterModelMongo === 'string' ? JSON.parse(e.filterModelMongo) : e.filterModelMongo))];
}
const _filterModelUserMongo = typeof e.filterModelUserMongo === 'string' ? JSON.parse(e.filterModelUserMongo) : e.filterModelUserMongo;
if (!_.isEmpty(_filterModelUserMongo)) {
if (!selector_new['$and']) {
selector_new['$and'] = [];
}
selector_new['$and'].push(replaceRegexOptions(replaceKeysInQuery(_filterModelUserMongo)));
}
if (e.dataSourceFilterModelType === 'index') {
for (const [key, val] of Object.entries(opt.selector)) {
selector_new['udfData.data.' + key] = val;
}
}
opt.selector = selector_new;
opt.selector['udfData.udffileid'] = e.table_id;
opt.selector['docType'] = 'database';
opt.selector.stat = e.archived ? 4 : 3;
opt.fields.push('_id');
opt.fields.push('_rev');
if (e.selector) {
opt.selector = { ...e.selector, ...opt.selector };
}
return opt;
};
const query_db = async function (e, db, app_id_reference, table_obj) {
var key = undefined;
var opt = get_opt(e, table_obj);
const runtime_get_mango_data = async function () {
const done = async function (body) {
const raw_data = async function () {
var rows = [];
for (var val of body.docs) {
var data = {};
if (e.fields) {
for (const [key2, val2] of Object.entries(typeof e.fields === 'string' ? e.fields.split(',') : e.fields)) {
if (val.udfData) {
data[val2] = val['udfData']['data'][val2];
} else {
data[val2] = null;
}
}
} else {
for (const [key2, val2] of Object.entries(table_obj.tableFields)) {
if (val.udfData) {
data[val2.data.field_id] = val['udfData']['data'][val2.data.field_id];
} else {
data[val2.data.field_id] = null;
}
}
}
rows.push({
id: val._id,
value: data,
});
}
return { code: 1, data: { rows: rows, total_rows: rows.length, opt } };
};
const count_data = async function () {
var rows = [];
var keys_obj = {};
const table_index = find_item_by_key_root(table_obj.tableIndexes, 'id', e.indexId);
if (e.indexId && table_index) {
var index_keys = table_index.data.keys;
var index_id = e.indexId;
for (var val of body.docs) {
var data = {};
const _tableFieldsObj = find_item_by_key(table_obj.tableFields, 'field_id', e.field_id);
for (const [key2, val2] of Object.entries(table_obj.tableFields)) {
data[val2.data.field_id] = val['udfData']['data'][val2.data.field_id];
}
var key_arr = [];
for (const [key_idx, key_name] of Object.entries(index_keys)) {
key_arr.push(data[key_name]);
}
keys_obj[key_arr]++;
}
for (const [key_arr, key_count] of Object.entries(keys_obj)) {
rows.push({
key: [e.key, index_id, key_arr],
value: body.docs.length,
});
}
} else {
// no index
rows.push({ key: '', value: body.docs.length });
}
return { code: 1, data: { rows: rows, total_rows: rows.length, opt } };
};
const totals = async function () {
const median = (arr) => {
const mid = Math.floor(arr.length / 2),
nums = [...arr].sort((a, b) => a - b);
return arr.length % 2 !== 0 ? nums[mid] : (nums[mid - 1] + nums[mid]) / 2;
};
var total_fields_info = JSON.parse(e.total_fields_info);
var totals_prop = {};
var totals_obj = {};
var totals_counts = {};
var totals_arr = {};
var totals_sums = {};
var totals_group_obj = {};
for (const [key, val] of Object.entries(total_fields_info)) {
totals_counts[val.field_id] = 0;
totals_prop[val.field_id] = val;
totals_arr[val.field_id] = [];
totals_sums[val.field_id] = 0;
totals_group_obj[val.field_id] = {};
}
for (var row_data of body.docs) {
for (const [key, val] of Object.entries(row_data.udfData.data)) {
var field_id = key;
var value = val;
totals_counts[field_id]++;
switch (totals_prop[field_id].sum_type) {
case 'sum':
if (typeof totals_obj[field_id] === 'undefined') {
totals_obj[field_id] = value;
} else {
totals_obj[field_id] += value;
}
break;
case 'average':
totals_sums[field_id] += value;
totals_obj[field_id] = totals_sums[field_id] / totals_counts[field_id];
break;
case 'median':
if (!totals_arr[field_id].includes(value)) {
totals_arr[field_id].push(value);
}
totals_obj[field_id] = median(totals_arr[field_id]);
break;
case 'min':
if (typeof totals_obj[field_id] === 'undefined' || value < totals_obj[field_id]) {
totals_obj[field_id] = value;
}
break;
case 'max':
if (typeof totals_obj[field_id] === 'undefined' || value > totals_obj[field_id]) {
totals_obj[field_id] = value;
}
break;
case 'count':
if (typeof totals_obj[field_id] === 'undefined') {
totals_obj[field_id] = 1;
} else {
totals_obj[field_id]++;
}
break;
case 'distinct':
if (!totals_arr[field_id].includes(value)) {
totals_arr[field_id].push(value);
}
totals_obj[field_id] = totals_arr[field_id].length;
break;
case 'group':
if (!totals_obj[field_id]) {
totals_obj[field_id] = {};
}
if (typeof totals_obj[field_id][value] === 'undefined') {
totals_obj[field_id][value] = 1; //value;
} else {
totals_obj[field_id][value]++; //+= value;
}
break;
default:
totals_obj[field_id] = null;
}
}
}
return { code: 1, data: totals_obj };
};
if (e.count) {
return count_data();
}
if (e.total_fields_info) {
return totals();
}
return raw_data();
};
// xuda
try {
try {
// console.log("opt", opt);
const doc = await db.find(opt);
var mango_index_obj;
if (doc?.warning?.includes('No matching index found')) {
const index_name = `index_${e.table_id}_${new Date().valueOf().toString()}`;
var index = [];
for (const [key, val] of Object.entries(opt.selector)) {
index.push(key);
}
mango_index_obj = {
index: {
fields: index,
},
name: index_name,
ddoc: `mango_index_table_${e.table_id}`,
};
db.createIndex(mango_index_obj).then((result) => {
console.log(result);
});
}
return await done(doc);
} catch (err) {
if (err.error === 'no_usable_index') {
const index_name = `index_${e.table_id}_${new Date().valueOf().toString()}`;
const _selector = _.cloneDeep(opt.selector);
delete _selector['$and'];
const _sort = _.cloneDeep(opt.sort);
mango_index_obj = {
index: {
fields: Object.keys(
_.reduce(
_sort,
(ret, val) => {
ret[Object.keys(val)[0]] = val;
return ret;
},
_selector,
),
),
},
name: index_name,
ddoc: `mango_index_table_${e.table_id}`,
};
try {
const result = await db.createIndex(mango_index_obj);
const doc = await db.find(opt);
return await done(doc);
} catch (err) {
return { code: -1, data: err.message };
}
} else {
return { code: -1, data: err.message, opt };
}
}
} catch (err) {
return { code: -1, data: err.message };
}
};
const runtime_get_query_data = async function () {
var limit = 99999999;
var skip = 0;
if (e.limit) {
limit = e.limitP;
}
if (e.skipP) {
skip = e.skipP;
}
var view_opt = undefined;
if (key) {
view_opt = {
key,
include_docs: !e.ids && view !== 'db_index_reduce',
limit,
skip,
};
} else {
if (e.reduce) {
view_opt = {
startkey: e.startkey ? JSON.parse(e.startkey) : '',
endkey: e.endkey ? JSON.parse(e.endkey) : '',
include_docs: false,
reduce: true,
group_level: 3,
descending: e.desc,
};
} else {
view_opt = {
startkey: e.startkey ? JSON.parse(e.startkey) : '',
endkey: e.endkey ? JSON.parse(e.endkey) : '',
include_docs: !e.ids,
limit,
skip,
descending: e.desc,
};
}
}
try {
const body = await db.view(`xuda`, view, view_opt);
if (e.reduce) {
let rows = [];
const table_index = find_item_by_key_root(table_obj.tableIndexes, 'id', e.indexId);
for await (var val of body.rows) {
const arr = val.key[2];
var obj = {};
for await (const [arr_key, arr_val] of Object.entries(arr)) {
let field_id = table_index.data.keys[Number(arr_key)];
const _tableFieldsObj = find_item_by_key(table_obj.tableFields, 'field_id', field_id);
let field_value = await get_cast_val('dbs_update', e.field_id, _tableFieldsObj.props.fieldType, arr_val);
obj[field_id] = field_value;
}
obj['REDUCE_VALUE'] = val.value;
rows.push({ id: crypto.randomUUID(), value: obj });
}
return { code: 1, data: { rows, opt } };
}
var rows = [];
for await (var val of body.rows) {
if (e.ids) {
rows.push({ id: val.id, value: val.value._id });
continue;
}
rows.push({
id: val.id,
value: val.doc.udfData,
// value: { udfData: val.doc.udfData, _id: val.id },
});
}
// const reduce_rows = () => {
// const reduce_obj = body.docs.reduce(
// (params, data) => {
// const fx = (val, idx) => {
// const group_by_field = data[params.sort[idx]];
// if (params.sort[idx + 1]) {
// val[group_by_field] = val[group_by_field] ?? {};
// val[group_by_field][data[params.sort[idx + 1]]] = 0;
// idx++;
// fx(val[group_by_field], idx);
// return;
// }
// val[group_by_field] = val[group_by_field] ?? 0;
// val[group_by_field] += 1;
// };
// fx(params.group, 0);
// return params;
// },
// { sort: opt.sort, group: {} }
// );}
return { code: 1, data: { rows: rows, total_rows: rows.length, opt } };
} catch (err) {
return { code: -1, data: err.message };
}
};
const count_tables = async function () {
var opt = {
reduce: true,
group_level: 1,
};
if (e.table_id) {
opt.key = e.table_id;
}
const body = await db.view(`xuda`, 'db_table_counts', opt);
return { code: 1, data: body };
};
var view;
if (e.reduce && JSON.parse(e.reduce)) {
view = 'db_index_reduce';
return await runtime_get_query_data();
}
if (e.view === 'db_index') {
key = e.key;
view = e.view;
return await runtime_get_query_data();
}
if (e.count && !e.filter_from) {
// count tables
return await count_tables();
}
return await runtime_get_mango_data();
};
exports.create = async (params, setup_doc, resolve, reject) => {
const e = params.e;
const db = params.db;
const app_id_reference = params.app_id_reference;
const table_obj = params.table_obj;
const create_data_obj = async function (table_data) {
var doc = {};
for await (const val of table_obj.tableFields) {
doc[val.data.field_id] = await get_cast_val('dbs_create', val.data.field_id, val.props.fieldType, table_data[val.data.field_id]);
}
return doc;
};
const get_uuid = function (table_id) {
return 'dbs-' + table_id + '_' + crypto.randomUUID();
};
const batch = async function () {
var docs = [];
try {
for await (var val of e.table_data) {
var doc = {
_id: get_uuid(e.table_id),
docType: 'database',
user: e.uid,
app_id: e.app_id,
stat: 3,
udfData: {
udffileid: e.table_id,
data: await create_data_obj(val),
error: {},
},
ts: Date.now(),
date: Date.now(),
};
const index_ret = await get_index_json(doc, table_obj);
if (index_ret < 0) throw index_ret.data;
doc.udfIndex = index_ret.data;
const ret = await check_unique(e, doc, table_obj, db, app_id_reference);
if (ret.code < 0) {
throw ret;
}
docs.push(doc);
}
try {
const body = await db.bulk({ docs: docs });
return resolve(body);
} catch (error) {
return reject(error);
}
} catch (msg) {
return reject(msg);
}
};
const single = async function () {
var doc = {
_id: get_uuid(e.table_id),
docType: 'database',
user: e.uid,
app_id: e.app_id,
stat: 3,
udfData: {
udffileid: e.table_id,
data: await create_data_obj(e.table_data),
error: {},
},
ts: Date.now(),
date: Date.now(),
};
const index_ret = await get_index_json(doc, table_obj);
if (index_ret.code < 0) {
return index_ret;
}
doc.udfIndex = index_ret.data;
const ret = await check_unique(e, doc, table_obj, db, app_id_reference);
if (ret.code < 0) {
return reject(ret.data);
}
try {
const body = await db.insert(doc);
return resolve(body);
} catch (error) {
return reject(error);
}
};
if (Array.isArray(e.table_data)) {
return await batch();
}
return await single();
};
exports.read = async (params, setup_doc, resolve, reject) => {
const e = params.e;
const db = params.db;
const app_id_reference = params.app_id_reference;
const table_obj = params.table_obj;
const ret = await query_db(e, db, app_id_reference, table_obj);
if (ret.code < 0) {
return reject(ret.data);
}
return resolve(ret.data);
};
exports.update = async (params, setup_doc, resolve, reject) => {
const e = params.e;
const db = params.db;
const app_id_reference = params.app_id_reference;
const table_obj = params.table_obj;
try {
if (e.row_id === 'newRecord') {
return this.create(params, setup_doc, resolve, reject);
}
var doc = await db.get(e.row_id, {});
// let data = doc.udfData.data;
var error = undefined;
if (!e.field_id && !e.table_data) {
error = 'Invalid field_id or table_data object to save';
return reject(error);
}
// single value save
if (e.field_id) {
let _tableFieldsObj = find_item_by_key(table_obj.tableFields, 'field_id', e.field_id);
if (!_tableFieldsObj || _.isEmpty(_tableFieldsObj)) {
return reject('Error: ' + e.field_id + ' - field not found');
}
doc.udfData.data[e.field_id] = await get_cast_val('dbs_update', e.field_id, _tableFieldsObj.props.fieldType, e.field_value);
}
// object value save
if (e.table_data) {
// data = {};
for await (const [key, val] of Object.entries(e.table_data)) {
let _tableFieldsObj = find_item_by_key(table_obj.tableFields, 'field_id', key);
if (!_tableFieldsObj || _.isEmpty(_tableFieldsObj)) {
reject('Error: ' + key + ' - field not found');
break;
}
doc.udfData.data[key] = await get_cast_val('dbs_update', key, _tableFieldsObj.props.fieldType, val);
}
}
if (error) {
return reject(error);
}
doc.ts = Date.now();
const index_ret = await get_index_json(doc, table_obj);
if (index_ret.code < 0) {
return index_ret;
}
doc.udfIndex = index_ret.data;
const ret = await check_unique(e, doc, table_obj, db, app_id_reference);
if (ret.code < 0) {
return ret;
}
try {
const body = await db.insert(doc);
return resolve(body);
} catch (error) {
return reject(error);
}
} catch (error) {
return reject(error);
}
};
exports.delete = async (params, setup_doc, resolve, reject) => {
const e = params.e;
const db = params.db;
const app_id_reference = params.app_id_reference;
const table_obj = params.table_obj;
const data = await db.fetch({ keys: e.ids });
var docs_to_delete = [];
for await (var val of data.rows) {
var doc = val.doc;
if (doc && doc._id) {
if (e.permanent) {
docs_to_delete.push({
_id: doc._id,
_rev: doc._rev,
_deleted: true,
});
} else {
doc.stat = 4;
doc.ts = Date.now();
docs_to_delete.push(doc);
}
}
}
try {
await db.bulk({ docs: docs_to_delete });
resolve(docs_to_delete);
} catch (error) {
return { code: -110, data: error };
}
};
exports.restore = async (params, setup_doc, resolve, reject) => {
const e = params.e;
const db = params.db;
const app_id_reference = params.app_id_reference;
const table_obj = params.table_obj;
var docs_to_restore = [];
const data = await db.fetch({ keys: e.ids });
for await (var val of data.rows) {
var doc = val.doc;
if (doc && doc._id) {
doc.stat = 3;
doc.ts = Date.now();
docs_to_restore.push(doc);
}
}
try {
await db.bulk({ docs: docs_to_restore });
resolve(docs_to_restore);
} catch (error) {
return reject(error);
}
};
exports.check_unique = async (params, setup_doc, resolve, reject) => {
const e = params.e;
const db = params.db;
const app_id_reference = params.app_id_reference;
const table_obj = params.table_obj;
const doc = params.e.doc;
try {
const ret = await check_unique(e, doc, table_obj, db, app_id_reference);
if (ret.code < 0) {
throw ret;
}
resolve(ret);
} catch (error) {
return reject(error);
}
};
exports.get_connection = async (params, setup_doc, resolve, reject) => {
try {
if (!setup_doc.db_connection_string) throw 'db_connection_string missing';
const nano = require('nano')(setup_doc.db_connection_string);
if (!nano) {
return reject('connection error');
}
await nano.get('_design/xuda');
resolve(nano);
} catch (err) {
return reject(err.message || err);
}
};