VulpesOS/gaia/apps/calendar/js/backend/store/abstract.js
OverSu b38dea0597 Publish Vulpes Preview 15 sources for desktop, Android and Tundra
Share Gaia assets and adapters, keep private profiles and development documents local, and update both public READMEs.
2026-09-30 23:13:03 +02:00

386 lines
8.5 KiB
JavaScript
Executable file

define(function(require, exports, module) {
'use strict';
var Responder = require('common/responder');
var core = require('core');
var denodeifyAll = require('common/promise').denodeifyAll;
var nextTick = require('common/next_tick');
/**
* Creates an abstract store instance.
* Every store must contain a reference
* to the database.
*/
function Abstract() {
this._cached = Object.create(null);
Responder.call(this);
denodeifyAll(this, [
'persist',
'all',
'_allCached',
'removeByIndex',
'get',
'remove',
'count'
]);
}
module.exports = Abstract;
Abstract.prototype = {
__proto__: Responder.prototype,
_store: null,
/**
* Stores that will need to be removed
* when a record is removed from this store.
*
* @type {Array}
*/
_dependentStores: null,
_createModel: function(object, id) {
if (typeof(id) !== 'undefined') {
object._id = id;
}
return object;
},
_addToCache: function(object) {
this._cached[object._id] = object;
},
_clearCache: function() {
this._cached = Object.create(null);
},
_removeFromCache: function(id) {
if (id in this._cached) {
delete this._cached[id];
}
},
_transactionCallback: function(trans, callback) {
if (callback) {
trans.addEventListener('error', function(e) {
callback(e);
});
trans.addEventListener('complete', function() {
callback(null);
});
}
},
/**
* Adds an account to the database.
*
* @param {Object} object reference to account object to store.
* @param {IDBTransaction} trans transaction.
* @param {Function} callback node style callback.
*/
persist: function(object, trans, callback) {
if (typeof(trans) === 'function') {
callback = trans;
trans = undefined;
}
if (!trans) {
trans = core.db.transaction(
this._dependentStores || this._store,
'readwrite'
);
}
var self = this;
var store = trans.objectStore(this._store);
var data = this._objectData(object);
var id;
var model;
var putReq;
var reqType = this._detectPersistType(object);
trans.addEventListener('error', function(event) {
if (callback) {
callback(event);
}
});
// determine type of event
if (reqType === 'update') {
putReq = store.put(data);
} else {
this._assignId(data);
putReq = store.add(data);
this._addDependents(object, trans);
}
// when we have the id we can add the model to the cache.
if (data._id) {
id = data._id;
model = self._createModel(object, id);
self._addToCache(model);
}
trans.addEventListener('complete', function(data) {
if (!model) {
id = putReq.result;
model = self._createModel(object, id);
self._addToCache(model);
}
if (callback) {
callback(null, id, model);
}
self.emit(reqType, id, model);
self.emit('persist', id, model);
});
},
_allCached: function(callback) {
var list = this._cached;
nextTick(function() {
callback(null, list);
});
},
/**
* Loads all records in the database
* for this store.
*
* Using this function will fill
* the cache with all records in the store.
* As such this should only be used once
* during the app life-cycle.
*/
all: function(callback) {
if (this._allCallbacks) {
this._allCallbacks.push(callback);
return;
}
// list of pending callbacks
this._allCallbacks = [callback];
var self = this;
var trans = core.db.transaction(this._store);
var store = trans.objectStore(this._store);
function process(data) {
return self._addToCache(self._createModel(data));
}
function fireQueue(err, value) {
var callback;
while ((callback = self._allCallbacks.shift())) {
callback(err, value);
}
}
store.mozGetAll().onsuccess = function(event) {
event.target.result.forEach(process);
};
trans.onerror = function(event) {
fireQueue(event.target.error.name);
};
trans.oncomplete = function() {
fireQueue(null, self._cached);
self.all = self._allCached;
};
},
_addDependents: function() {},
_removeDependents: function(trans) {},
_detectPersistType: function(object) {
return ('_id' in object) ? 'update' : 'add';
},
_parseId: function(id) {
return id;
},
_assignId: function(obj) {
},
/**
* Removes all records a index value and removes
* them from the cache. 'remove' events are *not* emitted
* when removing in this manner for performance reasons.
*
* TODO: the test for this method still lives in the event store tests
* where this code began should refactor those tests to be general
* and live in the abstract tests.
*
* @param {String} indexName name of store index.
* @param {Numeric} indexValue value in index.
* @param {IDBTransation} [trans] optional transaction to reuse.
* @param {Function} [callback] optional callback to use.
* When called without a transaction chances
* are you should pass a callback.
*/
removeByIndex: function(indexName, indexValue, trans, callback) {
var self = this;
if (typeof(trans) === 'function') {
callback = trans;
trans = undefined;
}
if (!trans) {
trans = core.db.transaction(
this._dependentStores || this._store,
'readwrite'
);
}
if (callback) {
trans.addEventListener('complete', function() {
callback(null);
});
trans.addEventListener('error', function(event) {
callback(event);
});
}
var index = trans.objectStore(this._store).index(indexName);
var req = index.openCursor(
IDBKeyRange.only(indexValue)
);
req.onsuccess = function(event) {
var cursor = event.target.result;
if (cursor) {
// remove deps first intentionally to mimic, removes normal behaviour
self._removeDependents(cursor.primaryKey, trans);
self._removeFromCache(cursor.primaryKey);
cursor.delete();
cursor.continue();
}
};
return req;
},
/**
* Finds a single record.
*
* Does not go through any cache or emit any events.
*
* @param {String} id id of record.
* @param {IDBTransaction} [trans] optional transaction.
* @param {Function} callback node style [err, record].
*/
get: function(id, trans, callback) {
var self = this;
if (typeof(trans) === 'function') {
callback = trans;
trans = null;
}
if (!trans) {
trans = core.db.transaction(this._store);
}
var store = trans.objectStore(this._store);
var req = store.get(this._parseId(id));
req.onsuccess = function() {
var model;
if (req.result) {
model = self._createModel(req.result);
}
callback(null, model);
};
req.onerror = function(event) {
callback(event);
};
},
/**
* Removes a object from the store.
*
* @param {String} id record reference.
* @param {IDBTransaction} trans transaction.
* @param {Function} callback node style callback.
*/
remove: function(id, trans, callback) {
if (typeof(trans) === 'function') {
callback = trans;
trans = undefined;
}
if (!trans) {
trans = core.db.transaction(
this._dependentStores || this._store,
'readwrite'
);
}
var self = this;
var store = trans.objectStore(this._store);
id = this._parseId(id);
store.delete(id);
this._removeDependents(id, trans);
self.emit('preRemove', id);
trans.addEventListener('error', function(event) {
if (callback) {
callback(event);
}
});
trans.addEventListener('complete', function() {
if (callback) {
callback(null, id);
}
self.emit('remove', id);
// intentionally after the callbacks...
self._removeFromCache(id);
});
},
/**
* Find number of records in store.
*
* @param {Function} callback node style err/count.
*/
count: function(callback) {
var trans = core.db.transaction(this._store);
var store = trans.objectStore(this._store);
var req = store.count();
req.onsuccess = function() {
callback(null, req.result);
};
req.onerror = function(e) {
callback(e);
};
},
_objectData: function(data) {
if ('toJSON' in data) {
return data.toJSON();
}
return data;
}
};
});