kinto
Version:
An Offline-First JavaScript client for Kinto.
1,351 lines (1,134 loc) • 42.4 kB
JavaScript
"use strict";
var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.open = _open2;
exports.execute = execute;
exports.default = void 0;
var _objectSpread2 = _interopRequireDefault(require("@babel/runtime/helpers/objectSpread"));
var _toConsumableArray2 = _interopRequireDefault(require("@babel/runtime/helpers/toConsumableArray"));
var _regenerator = _interopRequireDefault(require("@babel/runtime/regenerator"));
var _classCallCheck2 = _interopRequireDefault(require("@babel/runtime/helpers/classCallCheck"));
var _createClass2 = _interopRequireDefault(require("@babel/runtime/helpers/createClass"));
var _possibleConstructorReturn2 = _interopRequireDefault(require("@babel/runtime/helpers/possibleConstructorReturn"));
var _getPrototypeOf2 = _interopRequireDefault(require("@babel/runtime/helpers/getPrototypeOf"));
var _inherits2 = _interopRequireDefault(require("@babel/runtime/helpers/inherits"));
var _asyncToGenerator2 = _interopRequireDefault(require("@babel/runtime/helpers/asyncToGenerator"));
var _base = _interopRequireDefault(require("./base.js"));
var _utils = require("../utils");
var INDEXED_FIELDS = ["id", "_status", "last_modified"];
/**
* Small helper that wraps the opening of an IndexedDB into a Promise.
*
* @param dbname {String} The database name.
* @param version {Integer} Schema version
* @param onupgradeneeded {Function} The callback to execute if schema is
* missing or different.
* @return {Promise<IDBDatabase>}
*/
function _open2(_x, _x2) {
return _open.apply(this, arguments);
}
/**
* Helper to run the specified callback in a single transaction on the
* specified store.
* The helper focuses on transaction wrapping into a promise.
*
* @param db {IDBDatabase} The database instance.
* @param name {String} The store name.
* @param callback {Function} The piece of code to execute in the transaction.
* @param options {Object} Options.
* @param options.mode {String} Transaction mode (default: read).
* @return {Promise} any value returned by the callback.
*/
function _open() {
_open = (0, _asyncToGenerator2.default)(
/*#__PURE__*/
_regenerator.default.mark(function _callee13(dbname, _ref) {
var version, onupgradeneeded;
return _regenerator.default.wrap(function _callee13$(_context13) {
while (1) {
switch (_context13.prev = _context13.next) {
case 0:
version = _ref.version, onupgradeneeded = _ref.onupgradeneeded;
return _context13.abrupt("return", new Promise(function (resolve, reject) {
var request = indexedDB.open(dbname, version);
request.onupgradeneeded = function (event) {
var db = event.target.result;
db.onerror = function (event) {
return reject(event.target.error);
};
return onupgradeneeded(event);
};
request.onerror = function (event) {
reject(event.target.error);
};
request.onsuccess = function (event) {
var db = event.target.result;
resolve(db);
};
}));
case 2:
case "end":
return _context13.stop();
}
}
}, _callee13, this);
}));
return _open.apply(this, arguments);
}
function execute(_x3, _x4, _x5) {
return _execute.apply(this, arguments);
}
/**
* Helper to wrap the deletion of an IndexedDB database into a promise.
*
* @param dbName {String} the database to delete
* @return {Promise}
*/
function _execute() {
_execute = (0, _asyncToGenerator2.default)(
/*#__PURE__*/
_regenerator.default.mark(function _callee14(db, name, callback) {
var options,
mode,
_args14 = arguments;
return _regenerator.default.wrap(function _callee14$(_context14) {
while (1) {
switch (_context14.prev = _context14.next) {
case 0:
options = _args14.length > 3 && _args14[3] !== undefined ? _args14[3] : {};
mode = options.mode;
return _context14.abrupt("return", new Promise(function (resolve, reject) {
// On Safari, calling IDBDatabase.transaction with mode == undefined raises
// a TypeError.
var transaction = mode ? db.transaction([name], mode) : db.transaction([name]);
var store = transaction.objectStore(name); // Let the callback abort this transaction.
var abort = function abort(e) {
transaction.abort();
reject(e);
}; // Execute the specified callback **synchronously**.
var result;
try {
result = callback(store, abort);
} catch (e) {
abort(e);
}
transaction.onerror = function (event) {
return reject(event.target.error);
};
transaction.oncomplete = function (event) {
return resolve(result);
};
}));
case 3:
case "end":
return _context14.stop();
}
}
}, _callee14, this);
}));
return _execute.apply(this, arguments);
}
function deleteDatabase(_x6) {
return _deleteDatabase.apply(this, arguments);
}
/**
* IDB cursor handlers.
* @type {Object}
*/
function _deleteDatabase() {
_deleteDatabase = (0, _asyncToGenerator2.default)(
/*#__PURE__*/
_regenerator.default.mark(function _callee15(dbName) {
return _regenerator.default.wrap(function _callee15$(_context15) {
while (1) {
switch (_context15.prev = _context15.next) {
case 0:
return _context15.abrupt("return", new Promise(function (resolve, reject) {
var request = indexedDB.deleteDatabase(dbName);
request.onsuccess = function (event) {
return resolve(event.target);
};
request.onerror = function (event) {
return reject(event.target.error);
};
}));
case 1:
case "end":
return _context15.stop();
}
}
}, _callee15, this);
}));
return _deleteDatabase.apply(this, arguments);
}
var cursorHandlers = {
all: function all(filters, done) {
var results = [];
return function (event) {
var cursor = event.target.result;
if (cursor) {
var value = cursor.value;
if ((0, _utils.filterObject)(filters, value)) {
results.push(value);
}
cursor.continue();
} else {
done(results);
}
};
},
in: function _in(values, filters, done) {
var results = [];
var i = 0;
return function (event) {
var cursor = event.target.result;
if (!cursor) {
done(results);
return;
}
var key = cursor.key,
value = cursor.value; // `key` can be an array of two values (see `keyPath` in indices definitions).
// `values` can be an array of arrays if we filter using an index whose key path
// is an array (eg. `cursorHandlers.in([["bid/cid", 42], ["bid/cid", 43]], ...)`)
while (key > values[i]) {
// The cursor has passed beyond this key. Check next.
++i;
if (i === values.length) {
done(results); // There is no next. Stop searching.
return;
}
}
var isEqual = Array.isArray(key) ? (0, _utils.arrayEqual)(key, values[i]) : key === values[i];
if (isEqual) {
if ((0, _utils.filterObject)(filters, value)) {
results.push(value);
}
cursor.continue();
} else {
cursor.continue(values[i]);
}
};
}
};
/**
* Creates an IDB request and attach it the appropriate cursor event handler to
* perform a list query.
*
* Multiple matching values are handled by passing an array.
*
* @param {String} cid The collection id (ie. `{bid}/{cid}`)
* @param {IDBStore} store The IDB store.
* @param {Object} filters Filter the records by field.
* @param {Function} done The operation completion handler.
* @return {IDBRequest}
*/
function createListRequest(cid, store, filters, done) {
var filterFields = Object.keys(filters); // If no filters, get all results in one bulk.
if (filterFields.length == 0) {
var _request = store.index("cid").getAll(IDBKeyRange.only(cid));
_request.onsuccess = function (event) {
return done(event.target.result);
};
return _request;
} // Introspect filters and check if they leverage an indexed field.
var indexField = filterFields.find(function (field) {
return INDEXED_FIELDS.includes(field);
});
if (!indexField) {
// Iterate on all records for this collection (ie. cid)
var isSubQuery = Object.keys(filters).some(function (key) {
return key.includes(".");
}); // (ie. filters: {"article.title": "hello"})
if (isSubQuery) {
var newFilter = (0, _utils.transformSubObjectFilters)(filters);
var _request3 = store.index("cid").openCursor(IDBKeyRange.only(cid));
_request3.onsuccess = cursorHandlers.all(newFilter, done);
return _request3;
}
var _request2 = store.index("cid").openCursor(IDBKeyRange.only(cid));
_request2.onsuccess = cursorHandlers.all(filters, done);
return _request2;
} // If `indexField` was used already, don't filter again.
var remainingFilters = (0, _utils.omitKeys)(filters, [indexField]); // value specified in the filter (eg. `filters: { _status: ["created", "updated"] }`)
var value = filters[indexField]; // For the "id" field, use the primary key.
var indexStore = indexField == "id" ? store : store.index(indexField); // WHERE IN equivalent clause
if (Array.isArray(value)) {
if (value.length === 0) {
return done([]);
}
var values = value.map(function (i) {
return [cid, i];
}).sort();
var range = IDBKeyRange.bound(values[0], values[values.length - 1]);
var _request4 = indexStore.openCursor(range);
_request4.onsuccess = cursorHandlers.in(values, remainingFilters, done);
return _request4;
} // If no filters on custom attribute, get all results in one bulk.
if (remainingFilters.length == 0) {
var _request5 = indexStore.getAll(IDBKeyRange.only([cid, value]));
_request5.onsuccess = function (event) {
return done(event.target.result);
};
return _request5;
} // WHERE field = value clause
var request = indexStore.openCursor(IDBKeyRange.only([cid, value]));
request.onsuccess = cursorHandlers.all(remainingFilters, done);
return request;
}
/**
* IndexedDB adapter.
*
* This adapter doesn't support any options.
*/
var IDB =
/*#__PURE__*/
function (_BaseAdapter) {
(0, _inherits2.default)(IDB, _BaseAdapter);
/**
* Constructor.
*
* @param {String} cid The key base for this collection (eg. `bid/cid`)
* @param {Object} options
* @param {String} options.dbName The IndexedDB name (default: `"KintoDB"`)
* @param {String} options.migrateOldData Whether old database data should be migrated (default: `false`)
*/
function IDB(cid) {
var _this;
var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
(0, _classCallCheck2.default)(this, IDB);
_this = (0, _possibleConstructorReturn2.default)(this, (0, _getPrototypeOf2.default)(IDB).call(this));
_this.cid = cid;
_this.dbName = options.dbName || "KintoDB";
_this._options = options;
_this._db = null;
return _this;
}
(0, _createClass2.default)(IDB, [{
key: "_handleError",
value: function _handleError(method, err) {
var error = new Error("IndexedDB ".concat(method, "() ").concat(err.message));
error.stack = err.stack;
throw error;
}
/**
* Ensures a connection to the IndexedDB database has been opened.
*
* @override
* @return {Promise}
*/
}, {
key: "open",
value: function () {
var _open3 = (0, _asyncToGenerator2.default)(
/*#__PURE__*/
_regenerator.default.mark(function _callee() {
var dataToMigrate, records, timestamp;
return _regenerator.default.wrap(function _callee$(_context) {
while (1) {
switch (_context.prev = _context.next) {
case 0:
if (!this._db) {
_context.next = 2;
break;
}
return _context.abrupt("return", this);
case 2:
if (!this._options.migrateOldData) {
_context.next = 8;
break;
}
_context.next = 5;
return migrationRequired(this.cid);
case 5:
_context.t0 = _context.sent;
_context.next = 9;
break;
case 8:
_context.t0 = null;
case 9:
dataToMigrate = _context.t0;
_context.next = 12;
return _open2(this.dbName, {
version: 2,
onupgradeneeded: function onupgradeneeded(event) {
var db = event.target.result;
if (event.oldVersion < 1) {
// Records store
var recordsStore = db.createObjectStore("records", {
keyPath: ["_cid", "id"]
}); // An index to obtain all the records in a collection.
recordsStore.createIndex("cid", "_cid"); // Here we create indices for every known field in records by collection.
// Local record status ("synced", "created", "updated", "deleted")
recordsStore.createIndex("_status", ["_cid", "_status"]); // Last modified field
recordsStore.createIndex("last_modified", ["_cid", "last_modified"]); // Timestamps store
db.createObjectStore("timestamps", {
keyPath: "cid"
});
}
if (event.oldVersion < 2) {
// Collections store
db.createObjectStore("collections", {
keyPath: "cid"
});
}
}
});
case 12:
this._db = _context.sent;
if (!dataToMigrate) {
_context.next = 23;
break;
}
records = dataToMigrate.records, timestamp = dataToMigrate.timestamp;
_context.next = 17;
return this.importBulk(records);
case 17:
_context.next = 19;
return this.saveLastModified(timestamp);
case 19:
console.log("".concat(this.cid, ": data was migrated successfully.")); // Delete the old database.
_context.next = 22;
return deleteDatabase(this.cid);
case 22:
console.warn("".concat(this.cid, ": old database was deleted."));
case 23:
return _context.abrupt("return", this);
case 24:
case "end":
return _context.stop();
}
}
}, _callee, this);
}));
return function open() {
return _open3.apply(this, arguments);
};
}()
/**
* Closes current connection to the database.
*
* @override
* @return {Promise}
*/
}, {
key: "close",
value: function close() {
if (this._db) {
this._db.close(); // indexedDB.close is synchronous
this._db = null;
}
return Promise.resolve();
}
/**
* Returns a transaction and an object store for a store name.
*
* To determine if a transaction has completed successfully, we should rather
* listen to the transaction’s complete event rather than the IDBObjectStore
* request’s success event, because the transaction may still fail after the
* success event fires.
*
* @param {String} name Store name
* @param {Function} callback to execute
* @param {Object} options Options
* @param {String} options.mode Transaction mode ("readwrite" or undefined)
* @return {Object}
*/
}, {
key: "prepare",
value: function () {
var _prepare = (0, _asyncToGenerator2.default)(
/*#__PURE__*/
_regenerator.default.mark(function _callee2(name, callback, options) {
return _regenerator.default.wrap(function _callee2$(_context2) {
while (1) {
switch (_context2.prev = _context2.next) {
case 0:
_context2.next = 2;
return this.open();
case 2:
_context2.next = 4;
return execute(this._db, name, callback, options);
case 4:
case "end":
return _context2.stop();
}
}
}, _callee2, this);
}));
return function prepare(_x7, _x8, _x9) {
return _prepare.apply(this, arguments);
};
}()
/**
* Deletes every records in the current collection.
*
* @override
* @return {Promise}
*/
}, {
key: "clear",
value: function () {
var _clear = (0, _asyncToGenerator2.default)(
/*#__PURE__*/
_regenerator.default.mark(function _callee3() {
var _this2 = this;
return _regenerator.default.wrap(function _callee3$(_context3) {
while (1) {
switch (_context3.prev = _context3.next) {
case 0:
_context3.prev = 0;
_context3.next = 3;
return this.prepare("records", function (store) {
var range = IDBKeyRange.only(_this2.cid);
var request = store.index("cid").openKeyCursor(range);
request.onsuccess = function (event) {
var cursor = event.target.result;
if (cursor) {
store.delete(cursor.primaryKey);
cursor.continue();
}
};
return request;
}, {
mode: "readwrite"
});
case 3:
_context3.next = 8;
break;
case 5:
_context3.prev = 5;
_context3.t0 = _context3["catch"](0);
this._handleError("clear", _context3.t0);
case 8:
case "end":
return _context3.stop();
}
}
}, _callee3, this, [[0, 5]]);
}));
return function clear() {
return _clear.apply(this, arguments);
};
}()
/**
* Executes the set of synchronous CRUD operations described in the provided
* callback within an IndexedDB transaction, for current db store.
*
* The callback will be provided an object exposing the following synchronous
* CRUD operation methods: get, create, update, delete.
*
* Important note: because limitations in IndexedDB implementations, no
* asynchronous code should be performed within the provided callback; the
* promise will therefore be rejected if the callback returns a Promise.
*
* Options:
* - {Array} preload: The list of record IDs to fetch and make available to
* the transaction object get() method (default: [])
*
* @example
* const db = new IDB("example");
* const result = await db.execute(transaction => {
* transaction.create({id: 1, title: "foo"});
* transaction.update({id: 2, title: "bar"});
* transaction.delete(3);
* return "foo";
* });
*
* @override
* @param {Function} callback The operation description callback.
* @param {Object} options The options object.
* @return {Promise}
*/
}, {
key: "execute",
value: function () {
var _execute2 = (0, _asyncToGenerator2.default)(
/*#__PURE__*/
_regenerator.default.mark(function _callee4(callback) {
var _this3 = this;
var options,
result,
_args4 = arguments;
return _regenerator.default.wrap(function _callee4$(_context4) {
while (1) {
switch (_context4.prev = _context4.next) {
case 0:
options = _args4.length > 1 && _args4[1] !== undefined ? _args4[1] : {
preload: []
};
_context4.next = 3;
return this.prepare("records", function (store, abort) {
var runCallback = function runCallback() {
var preloaded = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : [];
// Expose a consistent API for every adapter instead of raw store methods.
var proxy = transactionProxy(_this3, store, preloaded); // The callback is executed synchronously within the same transaction.
try {
var returned = callback(proxy);
if (returned instanceof Promise) {
// XXX: investigate how to provide documentation details in error.
throw new Error("execute() callback should not return a Promise.");
} // Bring to scope that will be returned (once promise awaited).
result = returned;
} catch (e) {
// The callback has thrown an error explicitly. Abort transaction cleanly.
abort(e);
}
}; // No option to preload records, go straight to `callback`.
if (!options.preload.length) {
return runCallback();
} // Preload specified records using a list request.
var filters = {
id: options.preload
};
createListRequest(_this3.cid, store, filters, function (records) {
// Store obtained records by id.
var preloaded = {};
var _iteratorNormalCompletion = true;
var _didIteratorError = false;
var _iteratorError = undefined;
try {
for (var _iterator = records[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true) {
var record = _step.value;
delete record["_cid"];
preloaded[record.id] = record;
}
} catch (err) {
_didIteratorError = true;
_iteratorError = err;
} finally {
try {
if (!_iteratorNormalCompletion && _iterator.return != null) {
_iterator.return();
}
} finally {
if (_didIteratorError) {
throw _iteratorError;
}
}
}
runCallback(preloaded);
});
}, {
mode: "readwrite"
});
case 3:
return _context4.abrupt("return", result);
case 4:
case "end":
return _context4.stop();
}
}
}, _callee4, this);
}));
return function execute(_x10) {
return _execute2.apply(this, arguments);
};
}()
/**
* Retrieve a record by its primary key from the IndexedDB database.
*
* @override
* @param {String} id The record id.
* @return {Promise}
*/
}, {
key: "get",
value: function () {
var _get = (0, _asyncToGenerator2.default)(
/*#__PURE__*/
_regenerator.default.mark(function _callee5(id) {
var _this4 = this;
var record;
return _regenerator.default.wrap(function _callee5$(_context5) {
while (1) {
switch (_context5.prev = _context5.next) {
case 0:
_context5.prev = 0;
_context5.next = 3;
return this.prepare("records", function (store) {
store.get([_this4.cid, id]).onsuccess = function (e) {
return record = e.target.result;
};
});
case 3:
return _context5.abrupt("return", record);
case 6:
_context5.prev = 6;
_context5.t0 = _context5["catch"](0);
this._handleError("get", _context5.t0);
case 9:
case "end":
return _context5.stop();
}
}
}, _callee5, this, [[0, 6]]);
}));
return function get(_x11) {
return _get.apply(this, arguments);
};
}()
/**
* Lists all records from the IndexedDB database.
*
* @override
* @param {Object} params The filters and order to apply to the results.
* @return {Promise}
*/
}, {
key: "list",
value: function () {
var _list = (0, _asyncToGenerator2.default)(
/*#__PURE__*/
_regenerator.default.mark(function _callee6() {
var _this5 = this;
var params,
filters,
results,
_args6 = arguments;
return _regenerator.default.wrap(function _callee6$(_context6) {
while (1) {
switch (_context6.prev = _context6.next) {
case 0:
params = _args6.length > 0 && _args6[0] !== undefined ? _args6[0] : {
filters: {}
};
filters = params.filters;
_context6.prev = 2;
results = [];
_context6.next = 6;
return this.prepare("records", function (store) {
createListRequest(_this5.cid, store, filters, function (_results) {
// we have received all requested records that match the filters,
// we now park them within current scope and hide the `_cid` attribute.
var _iteratorNormalCompletion2 = true;
var _didIteratorError2 = false;
var _iteratorError2 = undefined;
try {
for (var _iterator2 = _results[Symbol.iterator](), _step2; !(_iteratorNormalCompletion2 = (_step2 = _iterator2.next()).done); _iteratorNormalCompletion2 = true) {
var result = _step2.value;
delete result["_cid"];
}
} catch (err) {
_didIteratorError2 = true;
_iteratorError2 = err;
} finally {
try {
if (!_iteratorNormalCompletion2 && _iterator2.return != null) {
_iterator2.return();
}
} finally {
if (_didIteratorError2) {
throw _iteratorError2;
}
}
}
results = _results;
});
});
case 6:
return _context6.abrupt("return", params.order ? (0, _utils.sortObjects)(params.order, results) : results);
case 9:
_context6.prev = 9;
_context6.t0 = _context6["catch"](2);
this._handleError("list", _context6.t0);
case 12:
case "end":
return _context6.stop();
}
}
}, _callee6, this, [[2, 9]]);
}));
return function list() {
return _list.apply(this, arguments);
};
}()
/**
* Store the lastModified value into metadata store.
*
* @override
* @param {Number} lastModified
* @return {Promise}
*/
}, {
key: "saveLastModified",
value: function () {
var _saveLastModified = (0, _asyncToGenerator2.default)(
/*#__PURE__*/
_regenerator.default.mark(function _callee7(lastModified) {
var _this6 = this;
var value;
return _regenerator.default.wrap(function _callee7$(_context7) {
while (1) {
switch (_context7.prev = _context7.next) {
case 0:
value = parseInt(lastModified, 10) || null;
_context7.prev = 1;
_context7.next = 4;
return this.prepare("timestamps", function (store) {
return store.put({
cid: _this6.cid,
value: value
});
}, {
mode: "readwrite"
});
case 4:
return _context7.abrupt("return", value);
case 7:
_context7.prev = 7;
_context7.t0 = _context7["catch"](1);
this._handleError("saveLastModified", _context7.t0);
case 10:
case "end":
return _context7.stop();
}
}
}, _callee7, this, [[1, 7]]);
}));
return function saveLastModified(_x12) {
return _saveLastModified.apply(this, arguments);
};
}()
/**
* Retrieve saved lastModified value.
*
* @override
* @return {Promise}
*/
}, {
key: "getLastModified",
value: function () {
var _getLastModified = (0, _asyncToGenerator2.default)(
/*#__PURE__*/
_regenerator.default.mark(function _callee8() {
var _this7 = this;
var entry;
return _regenerator.default.wrap(function _callee8$(_context8) {
while (1) {
switch (_context8.prev = _context8.next) {
case 0:
_context8.prev = 0;
entry = null;
_context8.next = 4;
return this.prepare("timestamps", function (store) {
store.get(_this7.cid).onsuccess = function (e) {
return entry = e.target.result;
};
});
case 4:
return _context8.abrupt("return", entry ? entry.value : null);
case 7:
_context8.prev = 7;
_context8.t0 = _context8["catch"](0);
this._handleError("getLastModified", _context8.t0);
case 10:
case "end":
return _context8.stop();
}
}
}, _callee8, this, [[0, 7]]);
}));
return function getLastModified() {
return _getLastModified.apply(this, arguments);
};
}()
/**
* Load a dump of records exported from a server.
*
* @deprecated Use {@link importBulk} instead.
* @abstract
* @param {Array} records The records to load.
* @return {Promise}
*/
}, {
key: "loadDump",
value: function () {
var _loadDump = (0, _asyncToGenerator2.default)(
/*#__PURE__*/
_regenerator.default.mark(function _callee9(records) {
return _regenerator.default.wrap(function _callee9$(_context9) {
while (1) {
switch (_context9.prev = _context9.next) {
case 0:
return _context9.abrupt("return", this.importBulk(records));
case 1:
case "end":
return _context9.stop();
}
}
}, _callee9, this);
}));
return function loadDump(_x13) {
return _loadDump.apply(this, arguments);
};
}()
/**
* Load records in bulk that were exported from a server.
*
* @abstract
* @param {Array} records The records to load.
* @return {Promise}
*/
}, {
key: "importBulk",
value: function () {
var _importBulk = (0, _asyncToGenerator2.default)(
/*#__PURE__*/
_regenerator.default.mark(function _callee10(records) {
var previousLastModified, lastModified;
return _regenerator.default.wrap(function _callee10$(_context10) {
while (1) {
switch (_context10.prev = _context10.next) {
case 0:
_context10.prev = 0;
_context10.next = 3;
return this.execute(function (transaction) {
// Since the put operations are asynchronous, we chain
// them together. The last one will be waited for the
// `transaction.oncomplete` callback. (see #execute())
var i = 0;
putNext();
function putNext() {
if (i == records.length) {
return;
} // On error, `transaction.onerror` is called.
transaction.update(records[i]).onsuccess = putNext;
++i;
}
});
case 3:
_context10.next = 5;
return this.getLastModified();
case 5:
previousLastModified = _context10.sent;
lastModified = Math.max.apply(Math, (0, _toConsumableArray2.default)(records.map(function (record) {
return record.last_modified;
})));
if (!(lastModified > previousLastModified)) {
_context10.next = 10;
break;
}
_context10.next = 10;
return this.saveLastModified(lastModified);
case 10:
return _context10.abrupt("return", records);
case 13:
_context10.prev = 13;
_context10.t0 = _context10["catch"](0);
this._handleError("importBulk", _context10.t0);
case 16:
case "end":
return _context10.stop();
}
}
}, _callee10, this, [[0, 13]]);
}));
return function importBulk(_x14) {
return _importBulk.apply(this, arguments);
};
}()
}, {
key: "saveMetadata",
value: function () {
var _saveMetadata = (0, _asyncToGenerator2.default)(
/*#__PURE__*/
_regenerator.default.mark(function _callee11(metadata) {
var _this8 = this;
return _regenerator.default.wrap(function _callee11$(_context11) {
while (1) {
switch (_context11.prev = _context11.next) {
case 0:
_context11.prev = 0;
_context11.next = 3;
return this.prepare("collections", function (store) {
return store.put({
cid: _this8.cid,
metadata: metadata
});
}, {
mode: "readwrite"
});
case 3:
return _context11.abrupt("return", metadata);
case 6:
_context11.prev = 6;
_context11.t0 = _context11["catch"](0);
this._handleError("saveMetadata", _context11.t0);
case 9:
case "end":
return _context11.stop();
}
}
}, _callee11, this, [[0, 6]]);
}));
return function saveMetadata(_x15) {
return _saveMetadata.apply(this, arguments);
};
}()
}, {
key: "getMetadata",
value: function () {
var _getMetadata = (0, _asyncToGenerator2.default)(
/*#__PURE__*/
_regenerator.default.mark(function _callee12() {
var _this9 = this;
var entry;
return _regenerator.default.wrap(function _callee12$(_context12) {
while (1) {
switch (_context12.prev = _context12.next) {
case 0:
_context12.prev = 0;
entry = null;
_context12.next = 4;
return this.prepare("collections", function (store) {
store.get(_this9.cid).onsuccess = function (e) {
return entry = e.target.result;
};
});
case 4:
return _context12.abrupt("return", entry ? entry.metadata : null);
case 7:
_context12.prev = 7;
_context12.t0 = _context12["catch"](0);
this._handleError("getMetadata", _context12.t0);
case 10:
case "end":
return _context12.stop();
}
}
}, _callee12, this, [[0, 7]]);
}));
return function getMetadata() {
return _getMetadata.apply(this, arguments);
};
}()
}]);
return IDB;
}(_base.default);
/**
* IDB transaction proxy.
*
* @param {IDB} adapter The call IDB adapter
* @param {IDBStore} store The IndexedDB database store.
* @param {Array} preloaded The list of records to make available to
* get() (default: []).
* @return {Object}
*/
exports.default = IDB;
function transactionProxy(adapter, store) {
var preloaded = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : [];
var _cid = adapter.cid;
return {
create: function create(record) {
store.add((0, _objectSpread2.default)({}, record, {
_cid: _cid
}));
},
update: function update(record) {
return store.put((0, _objectSpread2.default)({}, record, {
_cid: _cid
}));
},
delete: function _delete(id) {
store.delete([_cid, id]);
},
get: function get(id) {
return preloaded[id];
}
};
}
/**
* Up to version 10.X of kinto.js, each collection had its own collection.
* The database name was `${bid}/${cid}` (eg. `"blocklists/certificates"`)
* and contained only one store with the same name.
*/
function migrationRequired(_x16) {
return _migrationRequired.apply(this, arguments);
}
function _migrationRequired() {
_migrationRequired = (0, _asyncToGenerator2.default)(
/*#__PURE__*/
_regenerator.default.mark(function _callee16(dbName) {
var exists, db, records, timestamp;
return _regenerator.default.wrap(function _callee16$(_context16) {
while (1) {
switch (_context16.prev = _context16.next) {
case 0:
exists = true;
_context16.next = 3;
return _open2(dbName, {
version: 1,
onupgradeneeded: function onupgradeneeded(event) {
exists = false;
}
});
case 3:
db = _context16.sent;
// Check that the DB we're looking at is really a legacy one,
// and not some remainder of the open() operation above.
exists &= db.objectStoreNames.contains("__meta__") && db.objectStoreNames.contains(dbName);
if (exists) {
_context16.next = 10;
break;
}
db.close(); // Testing the existence creates it, so delete it :)
_context16.next = 9;
return deleteDatabase(dbName);
case 9:
return _context16.abrupt("return", null);
case 10:
console.warn("".concat(dbName, ": old IndexedDB database found."));
_context16.prev = 11;
_context16.next = 14;
return execute(db, dbName, function (store) {
store.openCursor().onsuccess = cursorHandlers.all({}, function (res) {
return records = res;
});
});
case 14:
console.log("".concat(dbName, ": found ").concat(records.length, " records.")); // Check if there's a entry for this.
timestamp = null;
_context16.next = 18;
return execute(db, "__meta__", function (store) {
store.get("".concat(dbName, "-lastModified")).onsuccess = function (e) {
timestamp = e.target.result ? e.target.result.value : null;
};
});
case 18:
if (timestamp) {
_context16.next = 21;
break;
}
_context16.next = 21;
return execute(db, "__meta__", function (store) {
store.get("lastModified").onsuccess = function (e) {
timestamp = e.target.result ? e.target.result.value : null;
};
});
case 21:
console.log("".concat(dbName, ": ").concat(timestamp ? "found" : "no", " timestamp.")); // Those will be inserted in the new database/schema.
return _context16.abrupt("return", {
records: records,
timestamp: timestamp
});
case 25:
_context16.prev = 25;
_context16.t0 = _context16["catch"](11);
console.error("Error occured during migration", _context16.t0);
return _context16.abrupt("return", null);
case 29:
_context16.prev = 29;
db.close();
return _context16.finish(29);
case 32:
case "end":
return _context16.stop();
}
}
}, _callee16, this, [[11, 25, 29, 32]]);
}));
return _migrationRequired.apply(this, arguments);
}