@restorecommerce/chassis-srv
Version:
Restore Commerce microservice chassis
410 lines • 13.5 kB
JavaScript
;
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
__setModuleDefault(result, mod);
return result;
};
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.create = void 0;
const nedb_1 = __importDefault(require("nedb"));
const _ = __importStar(require("lodash"));
/**
* Converts unsupported functions to regexp.
* @param {object} filter query filter
* @return {object} the filter querys which are not supported by nedb converted to regexp.
*/
const convertToRegexp = (filter) => {
const f = filter;
_.forEach(f, (value, key) => {
if (value.$startswith) {
f[key] = {
$regex: new RegExp(`^${value.$startswith}.*$`, 'i'),
};
}
else if (value.$endswith) {
f[key] = {
$regex: new RegExp(`^.*${value.$endswith}$`, 'i'),
};
}
else if (_.has(value, '$isEmpty')) {
f[key] = {
$regex: new RegExp(`^$`),
};
}
else if (_.has(value, '$iLike')) {
// neDB does not have ILIKE (LIKE with ignore case sensitive)
// e.g.: convert %sOrT% => to /sort/ and find all fields
// whose name contain the substring 'sort' using the regular expression
const iLikeVal = f[key].$iLike.slice(1, -1).toLowerCase();
// convert sort => to regexp /sort/
f[key] = new RegExp(iLikeVal);
}
else if (_.isObject(value)) {
f[key] = convertToRegexp(value);
}
});
return f;
};
/**
* Construct or operator.
* @param {Object} options the or statement
* example: { $or: [{ planet: 'Earth' }, { planet: 'Mars' }] }
* @param {string} name the field name the comparison is based on.
* @return {Object} NeDB or operator query filter.
*/
const buildOrQuery = (options, name) => {
let opts = options;
if (!_.isArray(options)) {
opts = [options];
}
const obj = { $or: [] };
opts.forEach((item) => {
const toInsert = {};
toInsert[name] = item;
obj.$or.push(toInsert);
});
return obj;
};
/**
* NeDB database provider.
*/
class NedbProvider {
/**
* @param {Object} collections a map, collection name mapped to store
*/
constructor(collections) {
this.collections = collections;
}
/**
* Insert documents into database.
*
* @param {String} collection Collection name
* @param {Object|array.Object} documents A single or multiple documents.
*/
async insert(collection, documents) {
const collections = this.collections;
if (!_.isArray(documents)) {
documents = [documents];
}
const docs = _.cloneDeep(documents);
for (const doc of docs) {
_.set(doc, '_id', doc.id);
}
return new Promise((resolve, reject) => {
collections[collection].insert(docs, (err, newdocs) => {
// docs
if (err) {
resolve([{
error: true,
errorMessage: err.message
}]);
}
else {
for (const newdoc of newdocs) {
_.unset(newdoc, '_id');
}
resolve(newdocs);
}
});
});
}
/**
* Find documents based on filter.
*
* @param {String} collection Collection name
* @param {Object} filter Key, value Object
* @param {Object} options options.limit, options.offset
* @return {array.Object} A list of found documents.
*/
async find(collection, filter = {}, options = {}) {
const fil = convertToRegexp(filter || {});
let q = this.collections[collection].find(fil, options.fields);
if (options.offset) {
q = q.skip(options.offset);
}
if (options.limit) {
q = q.limit(options.limit);
}
if (options.sort) {
q = q.sort(options.sort);
}
const result = new Promise((resolve, reject) => {
q.exec((err, docs) => {
// docs
if (err) {
reject(err);
}
else {
_.map(docs, (doc) => {
if (_.isNil(doc.id)) {
_.set(doc, '_id', doc._id);
}
_.unset(doc, '_id');
});
resolve(docs);
}
});
});
return result;
}
/**
* Find documents by id (_key).
*
* @param {String} collection Collection name
* @param {String|array.String} identifications A single ID or multiple IDs.
* @return {array.Object} A list of found documents.
*/
async findByID(collection, identifications) {
let ids = identifications;
if (!_.isArray(identifications)) {
ids = [identifications];
}
const q = buildOrQuery(ids, 'id');
const collections = this.collections;
const result = new Promise((resolve, reject) => {
collections[collection].find(q).exec((err, docs) => {
if (docs) {
const l = docs.length;
for (let i = 0; i < l; i += 1) {
_.unset(docs[i], '_id');
}
resolve(docs);
}
else if (err) {
reject(err);
}
});
});
return result;
}
/**
* Find documents by filter and updates them with document.
*
* @param {String} collection Collection name
* @param {Object} filter Key, value Object
* @param {Object} document A document patch.
*/
async update(collection, document) {
const collections = this.collections;
if (_.isArray(document)) {
document = document[0];
}
const obj = {
$set: {},
};
Object.keys(document).forEach((key) => {
obj.$set[key] = document[key];
});
const filter = { id: document.id }; // construct filter using document ids
const fil = convertToRegexp(filter || {});
const updatedDocs = new Promise((resolve, reject) => {
collections[collection].update(fil, obj, { multi: true, returnUpdatedDocs: true }, (err, numReplaced, updatedDocs) => {
if (err) {
resolve(err);
}
else {
resolve(updatedDocs);
}
});
});
if (_.isEmpty(updatedDocs)) {
// document not found for update
return [{
error: true,
errorMessage: 'document not found'
}];
}
return _.map(updatedDocs, (doc) => {
_.unset(doc, '_id');
return doc;
});
}
/**
* Find each document based on it's key and update it.
* If the document does not exist it will be created.
*
* @param {String} collection Collection name
* @param {Object|Array.Object} documents
*/
async upsert(collection, documents) {
const collections = this.collections;
let docs = _.cloneDeep(documents);
if (!_.isArray(docs)) {
docs = [docs];
}
const results = [];
for (const doc of docs) {
_.set(doc, '_id', doc.id);
const result = new Promise((resolve, reject) => {
collections[collection].update({ _id: doc._id }, doc, { upsert: true, returnUpdatedDocs: true }, (err, numReplaced, upserted) => {
if (err) {
reject(err);
}
resolve(upserted);
});
});
results.push(await result);
}
return _.map(results, (doc) => {
_.unset(doc, '_id');
return doc;
});
}
/**
* Delete all documents selected by filter.
*
* @param {String} collection Collection name
* @param {Object} filter
*/
async delete(collection, ids) {
const collections = this.collections;
let fil = {};
const deleteResponse = [];
for (const id of ids) {
collections[collection].find({ id }, (err, docs) => {
if (_.isEmpty(docs)) {
deleteResponse.push({
error: true,
errorMessage: 'Document not found'
});
}
else {
deleteResponse.push(docs[0]);
}
});
}
if (_.isEmpty(ids)) {
fil = {}; // if no ids are provided delete all documents and filter for this is {}
}
else {
fil = { id: { $in: ids } };
}
const numRemoved = await new Promise((resolve, reject) => {
collections[collection].remove(fil, { multi: true }, (err, numRemoved) => {
if (err) {
throw new Error(err);
}
else {
resolve(numRemoved);
}
});
});
return deleteResponse;
}
/**
* Count all documents selected by filter.
*
* @param {String} collection Collection name
* @param {Object} filter
*/
async count(collection, filter = {}) {
const collections = this.collections;
const fil = convertToRegexp(filter || {});
const result = new Promise((resolve, reject) => {
collections[collection].count(fil, (err, count) => {
if (err)
reject(err);
resolve(count);
});
});
return result;
}
/**
* When calling without a collection name,
* delete all documents in all collections in the database.
* When providing a collection name,
* delete all documents in specified collection in the database.
* @param [string] collection Collection name.
*/
async truncate(collection) {
if (_.isNil(collection)) {
const collections = _.keys(this.collections);
for (let i = 0; i < collections.length; i += 1) {
await this.delete(collections[i], []);
}
}
else {
await this.delete(collection, []);
}
}
}
/**
* Open all configured NeDB datastores.
* @param {Object} config
* example:
* {
* "provider": "nedb",
* "collections": {
* "notifications": {}
* }
* }
* @param {Logger} logger
* @return {Object} key, value map containing collection names
* as keys and the corresponding NeDB datastores as values.
*/
const loadDatastores = async (config, logger) => {
if (_.isNil(config.collections)) {
throw new Error('missing collection config value');
}
const collections = {};
const colNames = _.keys(config.collections);
for (let i = 0; i < colNames.length; i += 1) {
const name = colNames[i];
const conf = config.collections[name];
if (conf.filename) {
logger.verbose(`collection ${name} has filename ${conf.filename}`);
conf.autoload = true;
const load = () => {
return (cb) => {
conf.onload = cb;
collections[name] = new nedb_1.default(conf);
};
};
await load();
}
else {
collections[name] = new nedb_1.default(conf);
}
}
return collections;
};
/**
* Create a new NeDB provider.
*
* @param {Object} conf NeDB configuration
* @param {Object} [logger] Logger
* @return {NedbProvider} NeDB provider
*/
const create = async (conf, logger) => {
let log = logger;
if (_.isNil(logger)) {
log = {
verbose: () => { },
};
}
const collections = await loadDatastores(conf, log);
return new NedbProvider(collections);
};
exports.create = create;
//# sourceMappingURL=index.js.map