const { INFO_PATH , DEFAULT_QUERY_OPTIONS } = require('../constants');
const {functionCallIterator} = require('../services/utils');
const {Page, toPage, paginate } = require('./Page');
const SORT_OPTIONS = {ASC: "asc", DSC: 'dsc'}
/**
* Manager Classes in this context should do the bridge between the controllers
* and the services exposing only the necessary api to the controllers while encapsulating <strong>all</strong> business logic.
*
* All Manager Classes should be singletons.
*
* This complete separation of concerns is very beneficial for 2 reasons:
* <ul>
* <li>Allows for testing since there's no browser dependent code (i think) since the DSUStorage can be 'mocked'</li>
* <li>Allows for different controllers access different business logic when necessary (while benefiting from the singleton behaviour)</li>
* </ul>
*
* #### _Manager SPECIFIC DataBase Access API (CRUD)_
*
* Methods:
* - {@link create} - Must be overwritten by child classes
* - {@link getOne}
* - {@link remove}
* - {@link update} - Should be overwritten by child classes
* - {@link getAll} - with querying capabilities via {@link DEFAULT_QUERY_OPTIONS} type configuration
* - {@link getPage} - paging and querying capabilities
*
* <strong>Assumes only reads/writes to {@link INFO_PATH} with JSON parsing to object</strong>
* Otherwise the methods need to be overwritten by child classes.
*
* #### _Manager INDEPENDENT DataBase Access API_
*
* Methods:
* - {@link insertRecord}
* - {@link getRecord}
* - {@link deleteRecord}
* - {@link updateRecord}
* - {@link query} - with querying capabilities via {@link DEFAULT_QUERY_OPTIONS} type configuration
*
* @param {BaseManager} baseManager the base manager to have access to the identity api
* @param {string} tableName the default table name for this manager eg: MessageManager will write to the messages table
* @param {string[]} [indexes] a list of indexes to add to the table in the db upon initialization. requires a callback!
* @param {function(err, Manager)} [callback] optional callback for when the assurance that the table has already been indexed is required.
* Not used in this class. Child classes must implement if this functionality is required like:
* <pre>
* if (this.indexes && callback){
* super._indexTable(...this.indexes, (err) => {
* if (err)
* return self._err(`Could not update Indexes`, err, callback);
* console.log(`Indexes for table ${self.tableName} updated`);
* callback(undefined, self);
* });
* }
* </pre>
* @memberOf Managers
* @class Manager
* @abstract
*/
class Manager{
/**
* @param {BaseManager} baseManager
* @param {string} tableName the name of the table this manager handles
* @param {string[]} indexes the indexes to be added to the table
* @param {function(err, Manager)} [callback] optional callback for better application flow control
* @constructor
*/
constructor(baseManager, tableName, indexes, callback){
let self = this;
this.storage = baseManager.db;
this.dbLock = baseManager.dbLock;
this.getStorage = () => {
if (!self.storage){
self.storage = baseManager.db;
self.dbLock = baseManager.dbLock;
}
if (!self.storage)
throw new Error(`DB is not initialized`);
return self.storage;
}
this.tableName = tableName;
this.indexes = indexes;
this.controllers = undefined;
this.getIdentity = baseManager.getIdentity.bind(baseManager);
this._getResolver = baseManager._getResolver;
this._getKeySSISpace = baseManager._getKeySSISpace;
this._loadDSU = baseManager._loadDSU;
this._err = baseManager._err;
this._sendMessage = function(did, api, message, callback){
if (!callback){
callback = message;
message = api;
api = this.tableName;
}
return baseManager.sendMessage(did, api, message, callback);
}
this._deleteMessage = function(message, callback){
return baseManager.deleteMessage(message, callback);
}
this._getMessages = function(callback){
return baseManager.getMessages(this.tableName, callback);
}
this._registerMessageListener = function(listener){
return baseManager.registerMessageListener(this.tableName, listener);
}
baseManager.cacheManager(this);
this._getManager = baseManager.getManager.bind(baseManager);
if (this.indexes && callback){
this._indexTable(...this.indexes, (err) => {
if (err)
return self._err(`Could not update Indexes`, err, callback);
console.log(`Indexes for table ${self.tableName} updated`);
callback(undefined, self);
});
} else if (callback)
callback(undefined, self);
}
/**
* Util method to give optional access to the controller for event sending purposes
* and UI operations when required eg: refresh the view.
*
* Accepts multiple calls (with keep the reference to all controllers)
* @param {LocalizedController} controller
*/
bindController(controller){
this.controllers = this.controllers || [];
this.controllers.push(controller);
}
/**
* Util method to give optional access to the controller to be able to refresh the view
* (will call the refresh method on all binded controllers via {@link Manager#bindController})
* @param {{}} [props] option props to pass to the controllers refresh method
*/
refreshController(props){
if (this.controllers)
this.controllers.forEach(c => c.refresh(props));
}
beginBatch(){
this.dbLock.beginBatch(this.tableName);
}
commitBatch(force, callback){
this.dbLock.commitBatch(this.tableName, force, callback);
}
cancelBatch(callback){
this.dbLock.cancelBatch(this.tableName, callback);
}
batchAllow(allowedManager){
this.dbLock.allow(this.tableName, allowedManager);
}
batchDisallow(allowedManager){
this.dbLock.disallow(this.tableName, allowedManager);
}
batchSchedule(method){
this.dbLock.schedule(method);
}
/**
* Should be called by child classes if then need to index the table.
* (Can't be called during the constructor of the Manager class due to the need of virtual method
* @param {string|function} props the last argument must be the callback. The properties passed
* must match the ones provided in {@link Manager#_indexItem} for this to work properly.
*
* callback receives the newly created indexes as the second argument
* @private
*/
_indexTable(...props){
if (!Array.isArray(props))
throw new Error(`Invalid properties provided`);
const callback = props.pop();
props.push('__timestamp');
const self = this;
const storage = self.getStorage();
const innerBeginBatch = function(callback){
try {
self.beginBatch();
} catch (e){
return self.batchSchedule(() => self._indexItem.call(self, ...props));
// return callback(e)
}
callback();
}
const errCb = function(message, err, callback){
self.cancelBatch(err2 => {
if (err2)
return self._err(`Could not cancelBatch over error: ${message}`, err2, callback);
self._err(message, err, callback);
});
}
storage.getIndexedFields(self.tableName, (err, indexes) => {
if (err)
return errCb(`Could not retrieve indexes from table ${self.tableName}`, err, callback);
const newIndexes = [];
const indexIterator = function(propsClone, callback){
const index = propsClone.shift();
if (!index)
return callback(undefined, newIndexes);
if (indexes.indexOf(index) !== -1)
return indexIterator(propsClone, callback);
innerBeginBatch((err) => {
if (err)
return errCb('Could not start batch Mode', err, callback);
storage.addIndex(self.tableName, index, (err) => {
if (err)
return errCb(`Could not retrieve indexes from table ${self.tableName}`, err, callback);
newIndexes.push(index);
indexIterator(propsClone, callback);
});
})
}
indexIterator(props.slice(), (err, updatedIndexes) => {
if (err)
return errCb(`Could not update indexes for table ${self.tableName}`, err, callback);
if (!updatedIndexes.length)
return callback(undefined, updatedIndexes);
self.commitBatch(true, (err) => {
if (err)
return errCb(`Indexes committed for table ${self.tableName}`, err, callback);
callback(undefined, updatedIndexes);
});
});
});
}
/**
* Send a message to the specified DID
* @param {string|W3cDID} did
* @param {string} [api] defaults to the tableName
* @param {string} message
* @param {function(err)} callback
*/
sendMessage(did, api, message, callback){
return this._sendMessage(did, api, message, callback);
}
/**
* Because Message sending is implemented as fire and forget (for the user experience)
* we need an async callback that might hold some specific logic
*
* Meant to be overridden by subclasses when needed
* @param err
* @param args
* @protected
*/
_messageCallback(err, ...args){
if (err)
return console.log(err);
console.log(...args);
}
/**
* Send a message to the specified DID
* @param {string|W3cDID} did
* @param {string} [api] defaults to the tableName
* @param {string} message
* @param {function(err)} callback
*/
_sendMessage(did, api, message, callback){}
/**
* @see _registerMessageListener
*/
registerMessageListener(listener){
return this._registerMessageListener(listener);
}
/**
* Proxy call to {@link MessageManager#_registerMessageListener()}.
* @see BaseManager
*/
_registerMessageListener(listener){}
/**
* @see _deleteMessage
*/
deleteMessage(message, callback) {
return this._deleteMessage(message, callback);
}
/**
* Proxy call to {@link MessageManager#deleteMessage()}.
* @see BaseManager
*/
_deleteMessage(message, callback) {}
/**
* @see _getMessages
*/
getMessages(callback){
return this._getMessages(callback);
}
/**
* Proxy call to {@link MessageManager#getMessages()} using tableName as the api value.
*/
_getMessages(callback){}
/**
* Processes the received messages, saves them to the the table and deletes the message
* @param record
* @param {function(err)} callback
*/
processMessageRecord(record, callback) {
let self = this;
// Process one record. If the message is broken, DO NOT DELETE IT, log to console, and skip to the next.
console.log(`Processing record`, record);
if (record.__deleted)
return callback("Skipping deleted record.");
if (!record.api || record.api !== this._getTableName())
return callback(`Message record ${record} does not have api=${this._getTableName()}. Skipping record.`);
self._processMessageRecord(record.message, (err) => {
if (err)
return self._err(`Record processing failed: ${JSON.stringify(record)}`, err, callback);
// and then delete message after processing.
console.log("Going to delete messages's record", record);
self.deleteMessage(record, (err) => {
if (err)
console.log(`Could not delete message. THis usually means there are two instances of this Application running and might cause problems with data integrity`);
callback(undefined);
});
});
};
/**
* Processes the received messages, for the presumed api (tableName)
* Each child class must implement this behaviour if desired
* @param {*} message
* @param {function(err)} callback
* @private
*/
_processMessageRecord(message, callback){
callback(`Message processing is not implemented for ${this.tableName}`);
}
/**
*
* @param records
* @param callback
* @return {*}
* @private
*/
_iterateMessageRecords(records, callback) {
let self = this;
if (!records || !Array.isArray(records))
return callback(`Message records ${records} is not an array!`);
if (records.length <= 0)
return callback(); // done without error
const record0 = records.shift();
self.processMessageRecord(record0, (err) => {
if (err)
console.log(err);
self._iterateMessageRecords(records, callback);
});
};
/**
* Process incoming, looking for receivedOrder messages.
* @param {function(err)} callback
*/
processMessages(callback) {
let self = this;
console.log("Processing messages");
self.getMessages((err, records) => {
console.log("Processing records: ", err, records);
if (err)
return callback(err);
let messageRecords = [...records]; // clone for iteration with shift()
self._iterateMessageRecords(messageRecords, callback);
});
}
/**
* Stops the message service listener when it is running
*/
shutdownMessenger(){
if(!this.messenger)
return console.log(`No message listener active`);
this.messenger.shutdown();
}
/**
* Lazy loads the db
* Is created in the constructor
*/
getStorage(){};
/**
* @param {object} object the business model object
* @param model the Controller's model object
* @returns {{}}
*/
toModel(object, model){
model = model || {};
for (let prop in object) {
prop = typeof prop === 'number' ? '' + prop : prop;
if (object.hasOwnProperty(prop)) {
if (!model[prop])
model[prop] = {};
model[prop].value = object[prop];
}
}
return model;
}
/**
* Should translate the Controller Model into the Business Model
* @param model the Controller's Model
* @returns {object} the Business Model object ready to feed to the constructor
*/
fromModel(model){
let result = {};
Object.keys(model).forEach(key => {
if (model.hasOwnProperty(key) && model[key].value)
result[key] = model[key].value;
});
return result
}
/**
* will be binded as the one from participant manager on initialization
* @param {function(err, identity)} callback
*/
getIdentity(callback){};
/**
* will be binded as the one from participant manager on initialization
*/
_getResolver(){};
/**
* will be binded as the one from participant manager on initialization
*/
_getKeySSISpace(){};
/**
* will be binded as the one from participant manager on initialization
* @param {string|KeySSI} keySSI
*/
_loadDSU(keySSI){};
/**
* Wrapper around OpenDSU's error wrapper
* @param {string} message
* @param {err} err
* @param {function(err, ...args)} callback
* @protected
* @see _err
*/
_err(message, err, callback){};
/**
* @return {string} the tableName passed in the constructor
* @throws {Error} if the manager has no tableName
* @protected
*/
_getTableName(){
if (!this.tableName)
throw new Error('No table name specified');
return this.tableName;
}
/**
* Util function that loads a dsu and reads and JSON parses from the dsu's {@link INFO_PATH}
* @param {string|KeySSI} keySSI
* @param {function(err, any, Archive, KeySSI)} callback. object is the /info parsed as JSON.
* @protected
*/
_getDSUInfo(keySSI, callback){
let self = this;
self._loadDSU(keySSI, (err, dsu) => {
if (err)
return self._err(`Could not load record DSU: ${keySSI}`, err, callback);
dsu.readFile(INFO_PATH, (err, data) => {
if (err)
return self._err(`Could not read file at ${INFO_PATH}`, err, callback);
try{
data = JSON.parse(data);
} catch (e) {
return self._err(`Could not parse dsu data ${data.toString()}`, err, callback);
}
callback(undefined, data, dsu, keySSI);
});
});
}
/**
* Util iterator function
* @param {string[]} records
* @param {function(string, function(err, result))} getter
* @param {result[]} [accumulator] defaults to []
* @param {function(err, result[])} callback
* @protected
*/
_iterator(records, getter, accumulator, callback){
if (!callback) {
callback = accumulator;
accumulator = [];
}
let self = this;
const record = records.shift();
if (!record) {
console.log(`Found ${accumulator.length} items from records ${records}`);
return callback(undefined, accumulator);
}
getter(record, (err, product) => {
if (err)
return self._err(`Could not get product`, err, callback);
accumulator.push(product);
return self._iterator(records, getter, accumulator, callback);
});
}
/**
* Creates a new item
*
* Child classes should override this so they can be called without the key param in Web Components
* (and also to actually create the DSUs)
*
* @param {string} [key] key is optional so child classes can override them
* @param {object} item
* @param {function(err, object, Archive)} callback
*/
create(key, item, callback) {
callback(`The creation method is not implemneted for this Manager ${this.tableName}`);
}
/**
* Creates several new items
*
* @param {string[]} keys key is optional so child classes can override them
* @param {object[]} items
* @param {function(err, object[]?, Archive[]?)} callback
*/
createAll(keys, items, callback){
let self = this;
try {
self.beginBatch();
} catch(e) {
return self.batchSchedule(() => self.createAll.call(self, keys, items, callback));
}
functionCallIterator(this.create.bind(this), keys, items, (err, results) => {
if (err)
return self.cancelBatch((err2) => {
self._err(`Could not update all records`, err, callback);
});
self.commitBatch((err) => {
if (err)
return self.cancelBatch((err2) => {
callback(err)
});
callback(undefined, ...results);
});
});
}
/**
* Must wrap the entry in an object like:
* <pre>
* {
* index1: ...
* index2: ...
* value: item
* }
* </pre>
* so the DB can be queried by each of the indexes and still allow for lazy loading
* @param {string} key
* @param {object|string} [item]
* @param {string|object} record
* @return {any} the indexed object to be stored in the db
* @protected
*/
_indexItem(key, item, record){
if (!record){
record = item;
item = undefined
}
return {
key: key,
value: record
}
};
/**
* reads ssi for that gtin in the db. loads is and reads the info at '/info'
* @param {string} key
* @param {boolean} [readDSU] defaults to true. decides if the manager loads and reads from the dsu or not
* @param {function(err, object
* |KeySSI, Archive)} callback returns the Product if readDSU and the dsu, the keySSI otherwise
*/
getOne(key, readDSU, callback) {
if (!callback){
callback = readDSU;
readDSU = true;
}
let self = this;
self.getRecord(key, (err, record) => {
if (err)
return self._err(`Could not load record with key ${key} on table ${self._getTableName()}`, err, callback);
if (!readDSU)
return callback(undefined, record.value || record);
self._getDSUInfo(record.value || record, callback);
});
}
/**
* reads ssi for that gtin in the db. loads is and reads the info at '/info'
* @param {string} key
* @param {function(err, object
* |KeySSI, Archive?)} callback returns the Product if readDSU and the dsu, the keySSI otherwise
*/
getOneStripped(key, callback) {
let self = this;
self.getRecord(key, (err, record) => {
if (err)
return self._err(`Could not load record with key ${key} on table ${self._getTableName()}`, err, callback);
delete record.pk;
delete record.__timestamp;
delete record.__version ;
callback(undefined, record);
});
}
/**
* Removes a product from the list (does not delete/invalidate DSU, simply 'forgets' the reference)
* @param {string|number} key
* @param {function(err)} callback
*/
remove(key, callback) {
let self = this;
self.deleteRecord(key, callback);
}
/**
* updates an item
*
* @param {string} [key] key is optional so child classes can override them
* @param {object} newItem
* @param {function(err, object, Archive)} callback
*/
update(key, newItem, callback){
if (!callback){
callback = newItem;
newItem = key;
key = undefined;
return callback(`No key Provided...`);
}
callback('Child classes must implement this');
}
/**
* updates a bunch of items
*
* @param {string[]} [keys] key is optional so child classes can override them
* @param {object[]} newItems
* @param {function(err, object[], Archive[])} callback
*/
updateAll(keys, newItems, callback){
if (!callback){
callback = newItems;
newItems = keys;
keys = undefined;
return callback(`No key Provided...`);
}
let self = this;
try {
self.beginBatch();
} catch(e) {
return self.batchSchedule(() => self.updateAll.call(self, keys, newItems, callback));
}
functionCallIterator(this.update.bind(this), keys, newItems, (err, results) => {
if (err)
return self.cancelBatch((err2) => {
self._err(`Could not update all records`, err, callback);
});
self.commitBatch((err) => {
if (err)
return self.cancelBatch((err2) => {
callback(err)
});
callback(undefined, ...results);
});
});
}
/**
* Lists all registered items according to query options provided
* @param {boolean} [readDSU] defaults to true. decides if the manager loads and reads from the dsu's {@link INFO_PATH} or not
* @param {object} [options] query options. defaults to {@link DEFAULT_QUERY_OPTIONS}
* @param {function(err, object[])} callback
*/
getAll(readDSU, options, callback) {
if (!callback){
if (!options){
callback = readDSU;
options = DEFAULT_QUERY_OPTIONS;
readDSU = true;
}
if (typeof readDSU === 'boolean'){
callback = options;
options = DEFAULT_QUERY_OPTIONS;
}
if (typeof readDSU === 'object'){
callback = options;
options = readDSU;
readDSU = true;
}
}
options = options || DEFAULT_QUERY_OPTIONS;
let self = this;
self.query(options.query, options.sort, options.limit, (err, records) => {
if (err)
return self._err(`Could not perform query`, err, callback);
if (!readDSU)
return callback(undefined, records.map(r => r.pk)); // return the primary key if not read DSU
self._iterator(records.map(r => r.value), self._getDSUInfo.bind(self), (err, result) => {
if (err)
return self._err(`Could not parse ${self._getTableName()}s ${JSON.stringify(records)}`, err, callback);
console.log(`Parsed ${result.length} ${self._getTableName()}s`);
callback(undefined, result);
});
});
}
/**
* Converts the text typed in a general text box into the query for the db
* Subclasses should override this
* @param {string} keyword
* @param queryConditions
* @return {string[]} query
* @protected
*/
_keywordToQuery(keyword, queryConditions){
if (!keyword)
return [[...queryConditions, '__timestamp > 0']]
return this.indexes.map((index) => {
return [...queryConditions, `${index} like /${keyword}/g`, '__timestamp > 0']
})
}
/**
* Returns a page object from provided dsuQuery or a keyword
* @param {number} itemsPerPage
* @param {number} page
* @param {string[]} dsuQuery: force a fixed CONDITION in all keyword query or for a simple query paginated.
* @param {string} keyword: keyword to search on all indexes
* @param {string} sort
* @param {boolean} readDSU
* @param {function(err, Page)}callback
*/
getPage(itemsPerPage, page, dsuQuery, keyword, sort, readDSU, callback){
const self = this;
let receivedPage = page || 1;
sort = SORT_OPTIONS[(sort || SORT_OPTIONS.DSC).toUpperCase()] ? SORT_OPTIONS[(sort || SORT_OPTIONS.DSC).toUpperCase()] : SORT_OPTIONS.DSC;
const getPageByDSUQuery = (itemsPerPage, page, dsuQuery, sort, readDSU, callback) => {
const self = this;
let receivedPage = page || 1;
dsuQuery = [...dsuQuery, '__timestamp > 0'];
self.getAll(readDSU, {query: dsuQuery, sort: sort, limit: undefined}, (err, records) => {
if (err)
return self._err(`Could not retrieve records to page`, err, callback);
if (records.length === 0)
return callback(undefined, toPage(0, 0, records, itemsPerPage));
if (records.length <= itemsPerPage)
return callback(undefined, toPage(1, 1, records, itemsPerPage));
const page = paginate(records, itemsPerPage, receivedPage);
callback(undefined, page);
})
}
if(!keyword)
return getPageByDSUQuery(itemsPerPage, page, dsuQuery, sort, readDSU, callback);
const queries = self._keywordToQuery(keyword, dsuQuery);
const iterator = (accum, queriesArray, _callback) => {
const query = queriesArray.shift()
if (!query)
return _callback(undefined, accum)
self.getAll(readDSU, {query, sort: sort, limit: undefined}, (err, records) => {
if (err)
_callback(err)
iterator([...accum, ...records], queriesArray, _callback)
})
}
iterator([], queries.slice(), (err, records) => {
if (err)
return self._err(`Could not retrieve records to page`, err, callback);
if (records.length === 0)
return callback(undefined, toPage(0, 0, records, itemsPerPage));
// remove duplicates
records = Object.values(
records.reduce((accum, record) => {
const key = JSON.stringify(record);
if (!accum.hasOwnProperty(key)) {
accum[key] = record;
}
return accum
}, {})
);
if (records.length <= itemsPerPage)
return callback(undefined, toPage(1, 1, records, itemsPerPage));
const page = paginate(records, itemsPerPage, receivedPage);
callback(undefined, page);
})
}
/**
* Wrapper around the storage's insertRecord where the tableName defaults to the manager's
* @param {string} [tableName] defaults to the manager's table name
* @param {string} key
* @param {object} record
* @param {function(err)} callback
*/
insertRecord(tableName, key, record, callback){
if (!callback){
callback = record;
record = key;
key = tableName;
tableName = this._getTableName();
}
const self = this;
console.log("insertRecord tableName="+tableName, "key", key, "record", record);
try {
self.beginBatch();
} catch (e) {
return self.batchSchedule(() => self.insertRecord.call(self, tableName, key, record, callback));
// return callback(e);
}
this.getStorage().insertRecord(tableName, key, record, (err, ...results) => {
if (err)
return self.cancelBatch((err2) => {
self._err(`Could not insert record with key ${key} in table ${tableName}`, err, callback);
});
self.commitBatch((err) => {
if (err)
return self.cancelBatch((err2) => {
callback(err)
});
callback(undefined, ...results);
});
});
}
/**
* Wrapper around the storage's updateRecord where the tableName defaults to the manager's
* @param {string} [tableName] defaults to the manager's table name
* @param {string} key
* @param {*|string} newRecord
* @param {function(err)} callback
*/
updateRecord(tableName, key, newRecord, callback){
if (!callback){
callback = newRecord;
newRecord = key;
key = tableName;
tableName = this._getTableName();
}
console.log("update Record tableName="+tableName, "key", key, "record", newRecord);
const self = this;
try {
self.beginBatch();
} catch (e) {
return self.batchSchedule(() => self.updateRecord.call(self, tableName, key, newRecord, callback));
// return callback(e);
}
this.getStorage().updateRecord(tableName, key, newRecord, (err, ...results) => {
if (err)
return self.cancelBatch((err2) => {
self._err(`Could not update record with key ${key} in table ${tableName}`, err, callback);
});
self.commitBatch((err) => {
if (err)
return self.cancelBatch((err2) => {
callback(err)
});
callback(undefined, ...results);
});
});
}
/**
* Wrapper around the storage's getRecord where the tableName defaults to the manager's
* @param {string} [tableName] defaults to the manager's table name
* @param {string} key
* @param {function(err)} callback
*/
getRecord(tableName, key, callback){
if (!callback){
callback = key;
key = tableName;
tableName = this._getTableName();
}
this.getStorage().getRecord(tableName, key, callback);
}
/**
* Wrapper around the storage's deleteRecord where the tableName defaults to the manager's
* @param {string} [tableName] defaults to the manager's table name
* @param {string} key
* @param {function(err, record)} callback
*/
deleteRecord(tableName, key, callback) {
if (!callback) {
callback = key;
key = tableName;
tableName = this._getTableName();
}
this.getStorage().deleteRecord(tableName, key, (err, oldRecord) => {
console.log("Deleted key", key, "old record", err, oldRecord);
callback(err, oldRecord);
});
}
/**
* Wrapper around the storage's query where the tableName defaults to the manager's
* @param {string} [tableName] defaults to the manager's table name
* @param {function(record)} query
* @param {string} sort
* @param {number} limit
* @param {function(err, record[])} callback
*/
query(tableName, query, sort, limit, callback) {
if (!callback){
callback = limit;
limit = sort;
sort = query;
query = tableName;
tableName = this._getTableName();
}
console.log("query tableName="+tableName+" query=\""+query+"\" sort="+sort+" limit="+limit);
this.getStorage().query(tableName, query, sort, limit, callback);
}
}
module.exports = Manager;
Source