UNPKG

kinto

Version:

An Offline-First JavaScript client for Kinto.

1,351 lines (1,134 loc) 42.4 kB
"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); }