/* * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ /* * This file is generated from kinto.js - do not modify directly. */ this.EXPORTED_SYMBOLS = ["loadKinto"]; /* * Version 5.1.0 - 8beb61d */ (function(f){if(typeof exports==="object"&&typeof module!=="undefined"){module.exports=f()}else if(typeof define==="function"&&define.amd){define([],f)}else{var g;if(typeof window!=="undefined"){g=window}else if(typeof global!=="undefined"){g=global}else if(typeof self!=="undefined"){g=self}else{g=this}g.loadKinto = f()}})(function(){var define,module,exports;return (function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o "?"); const stmt = statements.listRecordsById + "(" + placeholders.join(",") + ");"; const rows = yield conn.execute(stmt, parameters); const preloaded = rows.reduce((acc, row) => { const record = JSON.parse(row.getResultByName("record")); acc[row.getResultByName("record_id")] = record; return acc; }, {}); const proxy = transactionProxy(collection, preloaded); result = callback(proxy); for (let { statement, params } of proxy.operations) { yield conn.executeCached(statement, params); } }, conn.TRANSACTION_EXCLUSIVE).then(_ => result); } get(id) { const params = { collection_name: this.collection, record_id: id }; return this._executeStatement(statements.getRecord, params).then(result => { if (result.length == 0) { return; } return JSON.parse(result[0].getResultByName("record")); }); } list(params = { filters: {}, order: "" }) { const parameters = { collection_name: this.collection }; return this._executeStatement(statements.listRecords, parameters).then(result => { const records = []; for (let k = 0; k < result.length; k++) { const row = result[k]; records.push(JSON.parse(row.getResultByName("record"))); } return records; }).then(results => { // The resulting list of records is filtered and sorted. // XXX: with some efforts, this could be implemented using SQL. return reduceRecords(params.filters, params.order, results); }); } /** * Load a list of records into the local database. * * Note: The adapter is not in charge of filtering the already imported * records. This is done in `Collection#loadDump()`, as a common behaviour * between every adapters. * * @param {Array} records. * @return {Array} imported records. */ loadDump(records) { const connection = this._connection; const collection_name = this.collection; return Task.spawn(function* () { yield connection.executeTransaction(function* doImport() { for (let record of records) { const params = { collection_name: collection_name, record_id: record.id, record: (0, _stringify2.default)(record) }; yield connection.execute(statements.importData, params); } const lastModified = Math.max(...records.map(record => record.last_modified)); const params = { collection_name: collection_name }; const previousLastModified = yield connection.execute(statements.getLastModified, params).then(result => { return result.length > 0 ? result[0].getResultByName("last_modified") : -1; }); if (lastModified > previousLastModified) { const params = { collection_name: collection_name, last_modified: lastModified }; yield connection.execute(statements.saveLastModified, params); } }); return records; }); } saveLastModified(lastModified) { const parsedLastModified = parseInt(lastModified, 10) || null; const params = { collection_name: this.collection, last_modified: parsedLastModified }; return this._executeStatement(statements.saveLastModified, params).then(() => parsedLastModified); } getLastModified() { const params = { collection_name: this.collection }; return this._executeStatement(statements.getLastModified, params).then(result => { if (result.length == 0) { return 0; } return result[0].getResultByName("last_modified"); }); } /** * Reset the sync status of every record and collection we have * access to. */ resetSyncStatus() { // We're going to use execute instead of executeCached, so build // in our own sanity check if (!this._connection) { throw new Error("The storage adapter is not open"); } return this._connection.executeTransaction(function* (conn) { const promises = []; yield conn.execute(statements.scanAllRecords, null, function (row) { const record = JSON.parse(row.getResultByName("record")); const record_id = row.getResultByName("record_id"); const collection_name = row.getResultByName("collection_name"); if (record._status === "deleted") { // Garbage collect deleted records. promises.push(conn.execute(statements.deleteData, { collection_name, record_id })); } else { const newRecord = (0, _extends3.default)({}, record, { _status: "created", last_modified: undefined }); promises.push(conn.execute(statements.updateData, { record: (0, _stringify2.default)(newRecord), record_id, collection_name })); } }); yield _promise2.default.all(promises); yield conn.execute(statements.clearCollectionMetadata); }); } } exports.default = FirefoxAdapter; function transactionProxy(collection, preloaded) { const _operations = []; return { get operations() { return _operations; }, create(record) { _operations.push({ statement: statements.createData, params: { collection_name: collection, record_id: record.id, record: (0, _stringify2.default)(record) } }); }, update(record) { _operations.push({ statement: statements.updateData, params: { collection_name: collection, record_id: record.id, record: (0, _stringify2.default)(record) } }); }, delete(id) { _operations.push({ statement: statements.deleteData, params: { collection_name: collection, record_id: id } }); }, get(id) { // Gecko JS engine outputs undesired warnings if id is not in preloaded. return id in preloaded ? preloaded[id] : undefined; } }; } /** * Filter and sort list against provided filters and order. * * @param {Object} filters The filters to apply. * @param {String} order The order to apply. * @param {Array} list The list to reduce. * @return {Array} */ function reduceRecords(filters, order, list) { const filtered = filters ? (0, _utils.filterObjects)(filters, list) : list; return order ? (0, _utils.sortObjects)(order, filtered) : filtered; } },{"../src/adapters/base":85,"../src/utils":87,"babel-runtime/core-js/json/stringify":3,"babel-runtime/core-js/promise":6,"babel-runtime/helpers/extends":8}],2:[function(require,module,exports){ /* * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); var _extends2 = require("babel-runtime/helpers/extends"); var _extends3 = _interopRequireDefault(_extends2); exports.default = loadKinto; var _base = require("../src/adapters/base"); var _base2 = _interopRequireDefault(_base); var _KintoBase = require("../src/KintoBase"); var _KintoBase2 = _interopRequireDefault(_KintoBase); var _FirefoxStorage = require("./FirefoxStorage"); var _FirefoxStorage2 = _interopRequireDefault(_FirefoxStorage); var _utils = require("../src/utils"); function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } const { classes: Cc, interfaces: Ci, utils: Cu } = Components; function loadKinto() { const { EventEmitter } = Cu.import("resource://devtools/shared/event-emitter.js", {}); const { generateUUID } = Cc["@mozilla.org/uuid-generator;1"].getService(Ci.nsIUUIDGenerator); // Use standalone kinto-http module landed in FFx. const { KintoHttpClient } = Cu.import("resource://services-common/kinto-http-client.js"); Cu.import("resource://gre/modules/Timer.jsm"); Cu.importGlobalProperties(['fetch']); // Leverage Gecko service to generate UUIDs. function makeIDSchema() { return { validate: _utils.RE_UUID.test.bind(_utils.RE_UUID), generate: function () { return generateUUID().toString().replace(/[{}]/g, ""); } }; } class KintoFX extends _KintoBase2.default { static get adapters() { return { BaseAdapter: _base2.default, FirefoxAdapter: _FirefoxStorage2.default }; } constructor(options = {}) { const emitter = {}; EventEmitter.decorate(emitter); const defaults = { events: emitter, ApiClass: KintoHttpClient, adapter: _FirefoxStorage2.default }; const expandedOptions = (0, _extends3.default)({}, defaults, options); super(expandedOptions); } collection(collName, options = {}) { const idSchema = makeIDSchema(); const expandedOptions = (0, _extends3.default)({ idSchema }, options); return super.collection(collName, expandedOptions); } } return KintoFX; } // This fixes compatibility with CommonJS required by browserify. // See http://stackoverflow.com/questions/33505992/babel-6-changes-how-it-exports-default/33683495#33683495 if (typeof module === "object") { module.exports = loadKinto; } },{"../src/KintoBase":83,"../src/adapters/base":85,"../src/utils":87,"./FirefoxStorage":1,"babel-runtime/helpers/extends":8}],3:[function(require,module,exports){ module.exports = { "default": require("core-js/library/fn/json/stringify"), __esModule: true }; },{"core-js/library/fn/json/stringify":10}],4:[function(require,module,exports){ module.exports = { "default": require("core-js/library/fn/object/assign"), __esModule: true }; },{"core-js/library/fn/object/assign":11}],5:[function(require,module,exports){ module.exports = { "default": require("core-js/library/fn/object/keys"), __esModule: true }; },{"core-js/library/fn/object/keys":12}],6:[function(require,module,exports){ module.exports = { "default": require("core-js/library/fn/promise"), __esModule: true }; },{"core-js/library/fn/promise":13}],7:[function(require,module,exports){ "use strict"; exports.__esModule = true; var _promise = require("../core-js/promise"); var _promise2 = _interopRequireDefault(_promise); function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } exports.default = function (fn) { return function () { var gen = fn.apply(this, arguments); return new _promise2.default(function (resolve, reject) { function step(key, arg) { try { var info = gen[key](arg); var value = info.value; } catch (error) { reject(error); return; } if (info.done) { resolve(value); } else { return _promise2.default.resolve(value).then(function (value) { return step("next", value); }, function (err) { return step("throw", err); }); } } return step("next"); }); }; }; },{"../core-js/promise":6}],8:[function(require,module,exports){ "use strict"; exports.__esModule = true; var _assign = require("../core-js/object/assign"); var _assign2 = _interopRequireDefault(_assign); function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } exports.default = _assign2.default || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; },{"../core-js/object/assign":4}],9:[function(require,module,exports){ },{}],10:[function(require,module,exports){ var core = require('../../modules/_core') , $JSON = core.JSON || (core.JSON = {stringify: JSON.stringify}); module.exports = function stringify(it){ // eslint-disable-line no-unused-vars return $JSON.stringify.apply($JSON, arguments); }; },{"../../modules/_core":21}],11:[function(require,module,exports){ require('../../modules/es6.object.assign'); module.exports = require('../../modules/_core').Object.assign; },{"../../modules/_core":21,"../../modules/es6.object.assign":77}],12:[function(require,module,exports){ require('../../modules/es6.object.keys'); module.exports = require('../../modules/_core').Object.keys; },{"../../modules/_core":21,"../../modules/es6.object.keys":78}],13:[function(require,module,exports){ require('../modules/es6.object.to-string'); require('../modules/es6.string.iterator'); require('../modules/web.dom.iterable'); require('../modules/es6.promise'); module.exports = require('../modules/_core').Promise; },{"../modules/_core":21,"../modules/es6.object.to-string":79,"../modules/es6.promise":80,"../modules/es6.string.iterator":81,"../modules/web.dom.iterable":82}],14:[function(require,module,exports){ module.exports = function(it){ if(typeof it != 'function')throw TypeError(it + ' is not a function!'); return it; }; },{}],15:[function(require,module,exports){ module.exports = function(){ /* empty */ }; },{}],16:[function(require,module,exports){ module.exports = function(it, Constructor, name, forbiddenField){ if(!(it instanceof Constructor) || (forbiddenField !== undefined && forbiddenField in it)){ throw TypeError(name + ': incorrect invocation!'); } return it; }; },{}],17:[function(require,module,exports){ var isObject = require('./_is-object'); module.exports = function(it){ if(!isObject(it))throw TypeError(it + ' is not an object!'); return it; }; },{"./_is-object":38}],18:[function(require,module,exports){ // false -> Array#indexOf // true -> Array#includes var toIObject = require('./_to-iobject') , toLength = require('./_to-length') , toIndex = require('./_to-index'); module.exports = function(IS_INCLUDES){ return function($this, el, fromIndex){ var O = toIObject($this) , length = toLength(O.length) , index = toIndex(fromIndex, length) , value; // Array#includes uses SameValueZero equality algorithm if(IS_INCLUDES && el != el)while(length > index){ value = O[index++]; if(value != value)return true; // Array#toIndex ignores holes, Array#includes - not } else for(;length > index; index++)if(IS_INCLUDES || index in O){ if(O[index] === el)return IS_INCLUDES || index || 0; } return !IS_INCLUDES && -1; }; }; },{"./_to-index":67,"./_to-iobject":69,"./_to-length":70}],19:[function(require,module,exports){ // getting tag from 19.1.3.6 Object.prototype.toString() var cof = require('./_cof') , TAG = require('./_wks')('toStringTag') // ES3 wrong here , ARG = cof(function(){ return arguments; }()) == 'Arguments'; // fallback for IE11 Script Access Denied error var tryGet = function(it, key){ try { return it[key]; } catch(e){ /* empty */ } }; module.exports = function(it){ var O, T, B; return it === undefined ? 'Undefined' : it === null ? 'Null' // @@toStringTag case : typeof (T = tryGet(O = Object(it), TAG)) == 'string' ? T // builtinTag case : ARG ? cof(O) // ES3 arguments fallback : (B = cof(O)) == 'Object' && typeof O.callee == 'function' ? 'Arguments' : B; }; },{"./_cof":20,"./_wks":74}],20:[function(require,module,exports){ var toString = {}.toString; module.exports = function(it){ return toString.call(it).slice(8, -1); }; },{}],21:[function(require,module,exports){ var core = module.exports = {version: '2.4.0'}; if(typeof __e == 'number')__e = core; // eslint-disable-line no-undef },{}],22:[function(require,module,exports){ // optional / simple context binding var aFunction = require('./_a-function'); module.exports = function(fn, that, length){ aFunction(fn); if(that === undefined)return fn; switch(length){ case 1: return function(a){ return fn.call(that, a); }; case 2: return function(a, b){ return fn.call(that, a, b); }; case 3: return function(a, b, c){ return fn.call(that, a, b, c); }; } return function(/* ...args */){ return fn.apply(that, arguments); }; }; },{"./_a-function":14}],23:[function(require,module,exports){ // 7.2.1 RequireObjectCoercible(argument) module.exports = function(it){ if(it == undefined)throw TypeError("Can't call method on " + it); return it; }; },{}],24:[function(require,module,exports){ // Thank's IE8 for his funny defineProperty module.exports = !require('./_fails')(function(){ return Object.defineProperty({}, 'a', {get: function(){ return 7; }}).a != 7; }); },{"./_fails":28}],25:[function(require,module,exports){ var isObject = require('./_is-object') , document = require('./_global').document // in old IE typeof document.createElement is 'object' , is = isObject(document) && isObject(document.createElement); module.exports = function(it){ return is ? document.createElement(it) : {}; }; },{"./_global":30,"./_is-object":38}],26:[function(require,module,exports){ // IE 8- don't enum bug keys module.exports = ( 'constructor,hasOwnProperty,isPrototypeOf,propertyIsEnumerable,toLocaleString,toString,valueOf' ).split(','); },{}],27:[function(require,module,exports){ var global = require('./_global') , core = require('./_core') , ctx = require('./_ctx') , hide = require('./_hide') , PROTOTYPE = 'prototype'; var $export = function(type, name, source){ var IS_FORCED = type & $export.F , IS_GLOBAL = type & $export.G , IS_STATIC = type & $export.S , IS_PROTO = type & $export.P , IS_BIND = type & $export.B , IS_WRAP = type & $export.W , exports = IS_GLOBAL ? core : core[name] || (core[name] = {}) , expProto = exports[PROTOTYPE] , target = IS_GLOBAL ? global : IS_STATIC ? global[name] : (global[name] || {})[PROTOTYPE] , key, own, out; if(IS_GLOBAL)source = name; for(key in source){ // contains in native own = !IS_FORCED && target && target[key] !== undefined; if(own && key in exports)continue; // export native or passed out = own ? target[key] : source[key]; // prevent global pollution for namespaces exports[key] = IS_GLOBAL && typeof target[key] != 'function' ? source[key] // bind timers to global for call from export context : IS_BIND && own ? ctx(out, global) // wrap global constructors for prevent change them in library : IS_WRAP && target[key] == out ? (function(C){ var F = function(a, b, c){ if(this instanceof C){ switch(arguments.length){ case 0: return new C; case 1: return new C(a); case 2: return new C(a, b); } return new C(a, b, c); } return C.apply(this, arguments); }; F[PROTOTYPE] = C[PROTOTYPE]; return F; // make static versions for prototype methods })(out) : IS_PROTO && typeof out == 'function' ? ctx(Function.call, out) : out; // export proto methods to core.%CONSTRUCTOR%.methods.%NAME% if(IS_PROTO){ (exports.virtual || (exports.virtual = {}))[key] = out; // export proto methods to core.%CONSTRUCTOR%.prototype.%NAME% if(type & $export.R && expProto && !expProto[key])hide(expProto, key, out); } } }; // type bitmap $export.F = 1; // forced $export.G = 2; // global $export.S = 4; // static $export.P = 8; // proto $export.B = 16; // bind $export.W = 32; // wrap $export.U = 64; // safe $export.R = 128; // real proto method for `library` module.exports = $export; },{"./_core":21,"./_ctx":22,"./_global":30,"./_hide":32}],28:[function(require,module,exports){ module.exports = function(exec){ try { return !!exec(); } catch(e){ return true; } }; },{}],29:[function(require,module,exports){ var ctx = require('./_ctx') , call = require('./_iter-call') , isArrayIter = require('./_is-array-iter') , anObject = require('./_an-object') , toLength = require('./_to-length') , getIterFn = require('./core.get-iterator-method') , BREAK = {} , RETURN = {}; var exports = module.exports = function(iterable, entries, fn, that, ITERATOR){ var iterFn = ITERATOR ? function(){ return iterable; } : getIterFn(iterable) , f = ctx(fn, that, entries ? 2 : 1) , index = 0 , length, step, iterator, result; if(typeof iterFn != 'function')throw TypeError(iterable + ' is not iterable!'); // fast case for arrays with default iterator if(isArrayIter(iterFn))for(length = toLength(iterable.length); length > index; index++){ result = entries ? f(anObject(step = iterable[index])[0], step[1]) : f(iterable[index]); if(result === BREAK || result === RETURN)return result; } else for(iterator = iterFn.call(iterable); !(step = iterator.next()).done; ){ result = call(iterator, f, step.value, entries); if(result === BREAK || result === RETURN)return result; } }; exports.BREAK = BREAK; exports.RETURN = RETURN; },{"./_an-object":17,"./_ctx":22,"./_is-array-iter":37,"./_iter-call":39,"./_to-length":70,"./core.get-iterator-method":75}],30:[function(require,module,exports){ // https://github.com/zloirock/core-js/issues/86#issuecomment-115759028 var global = module.exports = typeof window != 'undefined' && window.Math == Math ? window : typeof self != 'undefined' && self.Math == Math ? self : Function('return this')(); if(typeof __g == 'number')__g = global; // eslint-disable-line no-undef },{}],31:[function(require,module,exports){ var hasOwnProperty = {}.hasOwnProperty; module.exports = function(it, key){ return hasOwnProperty.call(it, key); }; },{}],32:[function(require,module,exports){ var dP = require('./_object-dp') , createDesc = require('./_property-desc'); module.exports = require('./_descriptors') ? function(object, key, value){ return dP.f(object, key, createDesc(1, value)); } : function(object, key, value){ object[key] = value; return object; }; },{"./_descriptors":24,"./_object-dp":49,"./_property-desc":57}],33:[function(require,module,exports){ module.exports = require('./_global').document && document.documentElement; },{"./_global":30}],34:[function(require,module,exports){ module.exports = !require('./_descriptors') && !require('./_fails')(function(){ return Object.defineProperty(require('./_dom-create')('div'), 'a', {get: function(){ return 7; }}).a != 7; }); },{"./_descriptors":24,"./_dom-create":25,"./_fails":28}],35:[function(require,module,exports){ // fast apply, http://jsperf.lnkit.com/fast-apply/5 module.exports = function(fn, args, that){ var un = that === undefined; switch(args.length){ case 0: return un ? fn() : fn.call(that); case 1: return un ? fn(args[0]) : fn.call(that, args[0]); case 2: return un ? fn(args[0], args[1]) : fn.call(that, args[0], args[1]); case 3: return un ? fn(args[0], args[1], args[2]) : fn.call(that, args[0], args[1], args[2]); case 4: return un ? fn(args[0], args[1], args[2], args[3]) : fn.call(that, args[0], args[1], args[2], args[3]); } return fn.apply(that, args); }; },{}],36:[function(require,module,exports){ // fallback for non-array-like ES3 and non-enumerable old V8 strings var cof = require('./_cof'); module.exports = Object('z').propertyIsEnumerable(0) ? Object : function(it){ return cof(it) == 'String' ? it.split('') : Object(it); }; },{"./_cof":20}],37:[function(require,module,exports){ // check on default Array iterator var Iterators = require('./_iterators') , ITERATOR = require('./_wks')('iterator') , ArrayProto = Array.prototype; module.exports = function(it){ return it !== undefined && (Iterators.Array === it || ArrayProto[ITERATOR] === it); }; },{"./_iterators":44,"./_wks":74}],38:[function(require,module,exports){ module.exports = function(it){ return typeof it === 'object' ? it !== null : typeof it === 'function'; }; },{}],39:[function(require,module,exports){ // call something on iterator step with safe closing on error var anObject = require('./_an-object'); module.exports = function(iterator, fn, value, entries){ try { return entries ? fn(anObject(value)[0], value[1]) : fn(value); // 7.4.6 IteratorClose(iterator, completion) } catch(e){ var ret = iterator['return']; if(ret !== undefined)anObject(ret.call(iterator)); throw e; } }; },{"./_an-object":17}],40:[function(require,module,exports){ 'use strict'; var create = require('./_object-create') , descriptor = require('./_property-desc') , setToStringTag = require('./_set-to-string-tag') , IteratorPrototype = {}; // 25.1.2.1.1 %IteratorPrototype%[@@iterator]() require('./_hide')(IteratorPrototype, require('./_wks')('iterator'), function(){ return this; }); module.exports = function(Constructor, NAME, next){ Constructor.prototype = create(IteratorPrototype, {next: descriptor(1, next)}); setToStringTag(Constructor, NAME + ' Iterator'); }; },{"./_hide":32,"./_object-create":48,"./_property-desc":57,"./_set-to-string-tag":61,"./_wks":74}],41:[function(require,module,exports){ 'use strict'; var LIBRARY = require('./_library') , $export = require('./_export') , redefine = require('./_redefine') , hide = require('./_hide') , has = require('./_has') , Iterators = require('./_iterators') , $iterCreate = require('./_iter-create') , setToStringTag = require('./_set-to-string-tag') , getPrototypeOf = require('./_object-gpo') , ITERATOR = require('./_wks')('iterator') , BUGGY = !([].keys && 'next' in [].keys()) // Safari has buggy iterators w/o `next` , FF_ITERATOR = '@@iterator' , KEYS = 'keys' , VALUES = 'values'; var returnThis = function(){ return this; }; module.exports = function(Base, NAME, Constructor, next, DEFAULT, IS_SET, FORCED){ $iterCreate(Constructor, NAME, next); var getMethod = function(kind){ if(!BUGGY && kind in proto)return proto[kind]; switch(kind){ case KEYS: return function keys(){ return new Constructor(this, kind); }; case VALUES: return function values(){ return new Constructor(this, kind); }; } return function entries(){ return new Constructor(this, kind); }; }; var TAG = NAME + ' Iterator' , DEF_VALUES = DEFAULT == VALUES , VALUES_BUG = false , proto = Base.prototype , $native = proto[ITERATOR] || proto[FF_ITERATOR] || DEFAULT && proto[DEFAULT] , $default = $native || getMethod(DEFAULT) , $entries = DEFAULT ? !DEF_VALUES ? $default : getMethod('entries') : undefined , $anyNative = NAME == 'Array' ? proto.entries || $native : $native , methods, key, IteratorPrototype; // Fix native if($anyNative){ IteratorPrototype = getPrototypeOf($anyNative.call(new Base)); if(IteratorPrototype !== Object.prototype){ // Set @@toStringTag to native iterators setToStringTag(IteratorPrototype, TAG, true); // fix for some old engines if(!LIBRARY && !has(IteratorPrototype, ITERATOR))hide(IteratorPrototype, ITERATOR, returnThis); } } // fix Array#{values, @@iterator}.name in V8 / FF if(DEF_VALUES && $native && $native.name !== VALUES){ VALUES_BUG = true; $default = function values(){ return $native.call(this); }; } // Define iterator if((!LIBRARY || FORCED) && (BUGGY || VALUES_BUG || !proto[ITERATOR])){ hide(proto, ITERATOR, $default); } // Plug for library Iterators[NAME] = $default; Iterators[TAG] = returnThis; if(DEFAULT){ methods = { values: DEF_VALUES ? $default : getMethod(VALUES), keys: IS_SET ? $default : getMethod(KEYS), entries: $entries }; if(FORCED)for(key in methods){ if(!(key in proto))redefine(proto, key, methods[key]); } else $export($export.P + $export.F * (BUGGY || VALUES_BUG), NAME, methods); } return methods; }; },{"./_export":27,"./_has":31,"./_hide":32,"./_iter-create":40,"./_iterators":44,"./_library":45,"./_object-gpo":52,"./_redefine":59,"./_set-to-string-tag":61,"./_wks":74}],42:[function(require,module,exports){ var ITERATOR = require('./_wks')('iterator') , SAFE_CLOSING = false; try { var riter = [7][ITERATOR](); riter['return'] = function(){ SAFE_CLOSING = true; }; Array.from(riter, function(){ throw 2; }); } catch(e){ /* empty */ } module.exports = function(exec, skipClosing){ if(!skipClosing && !SAFE_CLOSING)return false; var safe = false; try { var arr = [7] , iter = arr[ITERATOR](); iter.next = function(){ return {done: safe = true}; }; arr[ITERATOR] = function(){ return iter; }; exec(arr); } catch(e){ /* empty */ } return safe; }; },{"./_wks":74}],43:[function(require,module,exports){ module.exports = function(done, value){ return {value: value, done: !!done}; }; },{}],44:[function(require,module,exports){ module.exports = {}; },{}],45:[function(require,module,exports){ module.exports = true; },{}],46:[function(require,module,exports){ var global = require('./_global') , macrotask = require('./_task').set , Observer = global.MutationObserver || global.WebKitMutationObserver , process = global.process , Promise = global.Promise , isNode = require('./_cof')(process) == 'process'; module.exports = function(){ var head, last, notify; var flush = function(){ var parent, fn; if(isNode && (parent = process.domain))parent.exit(); while(head){ fn = head.fn; head = head.next; try { fn(); } catch(e){ if(head)notify(); else last = undefined; throw e; } } last = undefined; if(parent)parent.enter(); }; // Node.js if(isNode){ notify = function(){ process.nextTick(flush); }; // browsers with MutationObserver } else if(Observer){ var toggle = true , node = document.createTextNode(''); new Observer(flush).observe(node, {characterData: true}); // eslint-disable-line no-new notify = function(){ node.data = toggle = !toggle; }; // environments with maybe non-completely correct, but existent Promise } else if(Promise && Promise.resolve){ var promise = Promise.resolve(); notify = function(){ promise.then(flush); }; // for other environments - macrotask based on: // - setImmediate // - MessageChannel // - window.postMessag // - onreadystatechange // - setTimeout } else { notify = function(){ // strange IE + webpack dev server bug - use .call(global) macrotask.call(global, flush); }; } return function(fn){ var task = {fn: fn, next: undefined}; if(last)last.next = task; if(!head){ head = task; notify(); } last = task; }; }; },{"./_cof":20,"./_global":30,"./_task":66}],47:[function(require,module,exports){ 'use strict'; // 19.1.2.1 Object.assign(target, source, ...) var getKeys = require('./_object-keys') , gOPS = require('./_object-gops') , pIE = require('./_object-pie') , toObject = require('./_to-object') , IObject = require('./_iobject') , $assign = Object.assign; // should work with symbols and should have deterministic property order (V8 bug) module.exports = !$assign || require('./_fails')(function(){ var A = {} , B = {} , S = Symbol() , K = 'abcdefghijklmnopqrst'; A[S] = 7; K.split('').forEach(function(k){ B[k] = k; }); return $assign({}, A)[S] != 7 || Object.keys($assign({}, B)).join('') != K; }) ? function assign(target, source){ // eslint-disable-line no-unused-vars var T = toObject(target) , aLen = arguments.length , index = 1 , getSymbols = gOPS.f , isEnum = pIE.f; while(aLen > index){ var S = IObject(arguments[index++]) , keys = getSymbols ? getKeys(S).concat(getSymbols(S)) : getKeys(S) , length = keys.length , j = 0 , key; while(length > j)if(isEnum.call(S, key = keys[j++]))T[key] = S[key]; } return T; } : $assign; },{"./_fails":28,"./_iobject":36,"./_object-gops":51,"./_object-keys":54,"./_object-pie":55,"./_to-object":71}],48:[function(require,module,exports){ // 19.1.2.2 / 15.2.3.5 Object.create(O [, Properties]) var anObject = require('./_an-object') , dPs = require('./_object-dps') , enumBugKeys = require('./_enum-bug-keys') , IE_PROTO = require('./_shared-key')('IE_PROTO') , Empty = function(){ /* empty */ } , PROTOTYPE = 'prototype'; // Create object with fake `null` prototype: use iframe Object with cleared prototype var createDict = function(){ // Thrash, waste and sodomy: IE GC bug var iframe = require('./_dom-create')('iframe') , i = enumBugKeys.length , lt = '<' , gt = '>' , iframeDocument; iframe.style.display = 'none'; require('./_html').appendChild(iframe); iframe.src = 'javascript:'; // eslint-disable-line no-script-url // createDict = iframe.contentWindow.Object; // html.removeChild(iframe); iframeDocument = iframe.contentWindow.document; iframeDocument.open(); iframeDocument.write(lt + 'script' + gt + 'document.F=Object' + lt + '/script' + gt); iframeDocument.close(); createDict = iframeDocument.F; while(i--)delete createDict[PROTOTYPE][enumBugKeys[i]]; return createDict(); }; module.exports = Object.create || function create(O, Properties){ var result; if(O !== null){ Empty[PROTOTYPE] = anObject(O); result = new Empty; Empty[PROTOTYPE] = null; // add "__proto__" for Object.getPrototypeOf polyfill result[IE_PROTO] = O; } else result = createDict(); return Properties === undefined ? result : dPs(result, Properties); }; },{"./_an-object":17,"./_dom-create":25,"./_enum-bug-keys":26,"./_html":33,"./_object-dps":50,"./_shared-key":62}],49:[function(require,module,exports){ var anObject = require('./_an-object') , IE8_DOM_DEFINE = require('./_ie8-dom-define') , toPrimitive = require('./_to-primitive') , dP = Object.defineProperty; exports.f = require('./_descriptors') ? Object.defineProperty : function defineProperty(O, P, Attributes){ anObject(O); P = toPrimitive(P, true); anObject(Attributes); if(IE8_DOM_DEFINE)try { return dP(O, P, Attributes); } catch(e){ /* empty */ } if('get' in Attributes || 'set' in Attributes)throw TypeError('Accessors not supported!'); if('value' in Attributes)O[P] = Attributes.value; return O; }; },{"./_an-object":17,"./_descriptors":24,"./_ie8-dom-define":34,"./_to-primitive":72}],50:[function(require,module,exports){ var dP = require('./_object-dp') , anObject = require('./_an-object') , getKeys = require('./_object-keys'); module.exports = require('./_descriptors') ? Object.defineProperties : function defineProperties(O, Properties){ anObject(O); var keys = getKeys(Properties) , length = keys.length , i = 0 , P; while(length > i)dP.f(O, P = keys[i++], Properties[P]); return O; }; },{"./_an-object":17,"./_descriptors":24,"./_object-dp":49,"./_object-keys":54}],51:[function(require,module,exports){ exports.f = Object.getOwnPropertySymbols; },{}],52:[function(require,module,exports){ // 19.1.2.9 / 15.2.3.2 Object.getPrototypeOf(O) var has = require('./_has') , toObject = require('./_to-object') , IE_PROTO = require('./_shared-key')('IE_PROTO') , ObjectProto = Object.prototype; module.exports = Object.getPrototypeOf || function(O){ O = toObject(O); if(has(O, IE_PROTO))return O[IE_PROTO]; if(typeof O.constructor == 'function' && O instanceof O.constructor){ return O.constructor.prototype; } return O instanceof Object ? ObjectProto : null; }; },{"./_has":31,"./_shared-key":62,"./_to-object":71}],53:[function(require,module,exports){ var has = require('./_has') , toIObject = require('./_to-iobject') , arrayIndexOf = require('./_array-includes')(false) , IE_PROTO = require('./_shared-key')('IE_PROTO'); module.exports = function(object, names){ var O = toIObject(object) , i = 0 , result = [] , key; for(key in O)if(key != IE_PROTO)has(O, key) && result.push(key); // Don't enum bug & hidden keys while(names.length > i)if(has(O, key = names[i++])){ ~arrayIndexOf(result, key) || result.push(key); } return result; }; },{"./_array-includes":18,"./_has":31,"./_shared-key":62,"./_to-iobject":69}],54:[function(require,module,exports){ // 19.1.2.14 / 15.2.3.14 Object.keys(O) var $keys = require('./_object-keys-internal') , enumBugKeys = require('./_enum-bug-keys'); module.exports = Object.keys || function keys(O){ return $keys(O, enumBugKeys); }; },{"./_enum-bug-keys":26,"./_object-keys-internal":53}],55:[function(require,module,exports){ exports.f = {}.propertyIsEnumerable; },{}],56:[function(require,module,exports){ // most Object methods by ES6 should accept primitives var $export = require('./_export') , core = require('./_core') , fails = require('./_fails'); module.exports = function(KEY, exec){ var fn = (core.Object || {})[KEY] || Object[KEY] , exp = {}; exp[KEY] = exec(fn); $export($export.S + $export.F * fails(function(){ fn(1); }), 'Object', exp); }; },{"./_core":21,"./_export":27,"./_fails":28}],57:[function(require,module,exports){ module.exports = function(bitmap, value){ return { enumerable : !(bitmap & 1), configurable: !(bitmap & 2), writable : !(bitmap & 4), value : value }; }; },{}],58:[function(require,module,exports){ var hide = require('./_hide'); module.exports = function(target, src, safe){ for(var key in src){ if(safe && target[key])target[key] = src[key]; else hide(target, key, src[key]); } return target; }; },{"./_hide":32}],59:[function(require,module,exports){ module.exports = require('./_hide'); },{"./_hide":32}],60:[function(require,module,exports){ 'use strict'; var global = require('./_global') , core = require('./_core') , dP = require('./_object-dp') , DESCRIPTORS = require('./_descriptors') , SPECIES = require('./_wks')('species'); module.exports = function(KEY){ var C = typeof core[KEY] == 'function' ? core[KEY] : global[KEY]; if(DESCRIPTORS && C && !C[SPECIES])dP.f(C, SPECIES, { configurable: true, get: function(){ return this; } }); }; },{"./_core":21,"./_descriptors":24,"./_global":30,"./_object-dp":49,"./_wks":74}],61:[function(require,module,exports){ var def = require('./_object-dp').f , has = require('./_has') , TAG = require('./_wks')('toStringTag'); module.exports = function(it, tag, stat){ if(it && !has(it = stat ? it : it.prototype, TAG))def(it, TAG, {configurable: true, value: tag}); }; },{"./_has":31,"./_object-dp":49,"./_wks":74}],62:[function(require,module,exports){ var shared = require('./_shared')('keys') , uid = require('./_uid'); module.exports = function(key){ return shared[key] || (shared[key] = uid(key)); }; },{"./_shared":63,"./_uid":73}],63:[function(require,module,exports){ var global = require('./_global') , SHARED = '__core-js_shared__' , store = global[SHARED] || (global[SHARED] = {}); module.exports = function(key){ return store[key] || (store[key] = {}); }; },{"./_global":30}],64:[function(require,module,exports){ // 7.3.20 SpeciesConstructor(O, defaultConstructor) var anObject = require('./_an-object') , aFunction = require('./_a-function') , SPECIES = require('./_wks')('species'); module.exports = function(O, D){ var C = anObject(O).constructor, S; return C === undefined || (S = anObject(C)[SPECIES]) == undefined ? D : aFunction(S); }; },{"./_a-function":14,"./_an-object":17,"./_wks":74}],65:[function(require,module,exports){ var toInteger = require('./_to-integer') , defined = require('./_defined'); // true -> String#at // false -> String#codePointAt module.exports = function(TO_STRING){ return function(that, pos){ var s = String(defined(that)) , i = toInteger(pos) , l = s.length , a, b; if(i < 0 || i >= l)return TO_STRING ? '' : undefined; a = s.charCodeAt(i); return a < 0xd800 || a > 0xdbff || i + 1 === l || (b = s.charCodeAt(i + 1)) < 0xdc00 || b > 0xdfff ? TO_STRING ? s.charAt(i) : a : TO_STRING ? s.slice(i, i + 2) : (a - 0xd800 << 10) + (b - 0xdc00) + 0x10000; }; }; },{"./_defined":23,"./_to-integer":68}],66:[function(require,module,exports){ var ctx = require('./_ctx') , invoke = require('./_invoke') , html = require('./_html') , cel = require('./_dom-create') , global = require('./_global') , process = global.process , setTask = global.setImmediate , clearTask = global.clearImmediate , MessageChannel = global.MessageChannel , counter = 0 , queue = {} , ONREADYSTATECHANGE = 'onreadystatechange' , defer, channel, port; var run = function(){ var id = +this; if(queue.hasOwnProperty(id)){ var fn = queue[id]; delete queue[id]; fn(); } }; var listener = function(event){ run.call(event.data); }; // Node.js 0.9+ & IE10+ has setImmediate, otherwise: if(!setTask || !clearTask){ setTask = function setImmediate(fn){ var args = [], i = 1; while(arguments.length > i)args.push(arguments[i++]); queue[++counter] = function(){ invoke(typeof fn == 'function' ? fn : Function(fn), args); }; defer(counter); return counter; }; clearTask = function clearImmediate(id){ delete queue[id]; }; // Node.js 0.8- if(require('./_cof')(process) == 'process'){ defer = function(id){ process.nextTick(ctx(run, id, 1)); }; // Browsers with MessageChannel, includes WebWorkers } else if(MessageChannel){ channel = new MessageChannel; port = channel.port2; channel.port1.onmessage = listener; defer = ctx(port.postMessage, port, 1); // Browsers with postMessage, skip WebWorkers // IE8 has postMessage, but it's sync & typeof its postMessage is 'object' } else if(global.addEventListener && typeof postMessage == 'function' && !global.importScripts){ defer = function(id){ global.postMessage(id + '', '*'); }; global.addEventListener('message', listener, false); // IE8- } else if(ONREADYSTATECHANGE in cel('script')){ defer = function(id){ html.appendChild(cel('script'))[ONREADYSTATECHANGE] = function(){ html.removeChild(this); run.call(id); }; }; // Rest old browsers } else { defer = function(id){ setTimeout(ctx(run, id, 1), 0); }; } } module.exports = { set: setTask, clear: clearTask }; },{"./_cof":20,"./_ctx":22,"./_dom-create":25,"./_global":30,"./_html":33,"./_invoke":35}],67:[function(require,module,exports){ var toInteger = require('./_to-integer') , max = Math.max , min = Math.min; module.exports = function(index, length){ index = toInteger(index); return index < 0 ? max(index + length, 0) : min(index, length); }; },{"./_to-integer":68}],68:[function(require,module,exports){ // 7.1.4 ToInteger var ceil = Math.ceil , floor = Math.floor; module.exports = function(it){ return isNaN(it = +it) ? 0 : (it > 0 ? floor : ceil)(it); }; },{}],69:[function(require,module,exports){ // to indexed object, toObject with fallback for non-array-like ES3 strings var IObject = require('./_iobject') , defined = require('./_defined'); module.exports = function(it){ return IObject(defined(it)); }; },{"./_defined":23,"./_iobject":36}],70:[function(require,module,exports){ // 7.1.15 ToLength var toInteger = require('./_to-integer') , min = Math.min; module.exports = function(it){ return it > 0 ? min(toInteger(it), 0x1fffffffffffff) : 0; // pow(2, 53) - 1 == 9007199254740991 }; },{"./_to-integer":68}],71:[function(require,module,exports){ // 7.1.13 ToObject(argument) var defined = require('./_defined'); module.exports = function(it){ return Object(defined(it)); }; },{"./_defined":23}],72:[function(require,module,exports){ // 7.1.1 ToPrimitive(input [, PreferredType]) var isObject = require('./_is-object'); // instead of the ES6 spec version, we didn't implement @@toPrimitive case // and the second argument - flag - preferred type is a string module.exports = function(it, S){ if(!isObject(it))return it; var fn, val; if(S && typeof (fn = it.toString) == 'function' && !isObject(val = fn.call(it)))return val; if(typeof (fn = it.valueOf) == 'function' && !isObject(val = fn.call(it)))return val; if(!S && typeof (fn = it.toString) == 'function' && !isObject(val = fn.call(it)))return val; throw TypeError("Can't convert object to primitive value"); }; },{"./_is-object":38}],73:[function(require,module,exports){ var id = 0 , px = Math.random(); module.exports = function(key){ return 'Symbol('.concat(key === undefined ? '' : key, ')_', (++id + px).toString(36)); }; },{}],74:[function(require,module,exports){ var store = require('./_shared')('wks') , uid = require('./_uid') , Symbol = require('./_global').Symbol , USE_SYMBOL = typeof Symbol == 'function'; var $exports = module.exports = function(name){ return store[name] || (store[name] = USE_SYMBOL && Symbol[name] || (USE_SYMBOL ? Symbol : uid)('Symbol.' + name)); }; $exports.store = store; },{"./_global":30,"./_shared":63,"./_uid":73}],75:[function(require,module,exports){ var classof = require('./_classof') , ITERATOR = require('./_wks')('iterator') , Iterators = require('./_iterators'); module.exports = require('./_core').getIteratorMethod = function(it){ if(it != undefined)return it[ITERATOR] || it['@@iterator'] || Iterators[classof(it)]; }; },{"./_classof":19,"./_core":21,"./_iterators":44,"./_wks":74}],76:[function(require,module,exports){ 'use strict'; var addToUnscopables = require('./_add-to-unscopables') , step = require('./_iter-step') , Iterators = require('./_iterators') , toIObject = require('./_to-iobject'); // 22.1.3.4 Array.prototype.entries() // 22.1.3.13 Array.prototype.keys() // 22.1.3.29 Array.prototype.values() // 22.1.3.30 Array.prototype[@@iterator]() module.exports = require('./_iter-define')(Array, 'Array', function(iterated, kind){ this._t = toIObject(iterated); // target this._i = 0; // next index this._k = kind; // kind // 22.1.5.2.1 %ArrayIteratorPrototype%.next() }, function(){ var O = this._t , kind = this._k , index = this._i++; if(!O || index >= O.length){ this._t = undefined; return step(1); } if(kind == 'keys' )return step(0, index); if(kind == 'values')return step(0, O[index]); return step(0, [index, O[index]]); }, 'values'); // argumentsList[@@iterator] is %ArrayProto_values% (9.4.4.6, 9.4.4.7) Iterators.Arguments = Iterators.Array; addToUnscopables('keys'); addToUnscopables('values'); addToUnscopables('entries'); },{"./_add-to-unscopables":15,"./_iter-define":41,"./_iter-step":43,"./_iterators":44,"./_to-iobject":69}],77:[function(require,module,exports){ // 19.1.3.1 Object.assign(target, source) var $export = require('./_export'); $export($export.S + $export.F, 'Object', {assign: require('./_object-assign')}); },{"./_export":27,"./_object-assign":47}],78:[function(require,module,exports){ // 19.1.2.14 Object.keys(O) var toObject = require('./_to-object') , $keys = require('./_object-keys'); require('./_object-sap')('keys', function(){ return function keys(it){ return $keys(toObject(it)); }; }); },{"./_object-keys":54,"./_object-sap":56,"./_to-object":71}],79:[function(require,module,exports){ arguments[4][9][0].apply(exports,arguments) },{"dup":9}],80:[function(require,module,exports){ 'use strict'; var LIBRARY = require('./_library') , global = require('./_global') , ctx = require('./_ctx') , classof = require('./_classof') , $export = require('./_export') , isObject = require('./_is-object') , aFunction = require('./_a-function') , anInstance = require('./_an-instance') , forOf = require('./_for-of') , speciesConstructor = require('./_species-constructor') , task = require('./_task').set , microtask = require('./_microtask')() , PROMISE = 'Promise' , TypeError = global.TypeError , process = global.process , $Promise = global[PROMISE] , process = global.process , isNode = classof(process) == 'process' , empty = function(){ /* empty */ } , Internal, GenericPromiseCapability, Wrapper; var USE_NATIVE = !!function(){ try { // correct subclassing with @@species support var promise = $Promise.resolve(1) , FakePromise = (promise.constructor = {})[require('./_wks')('species')] = function(exec){ exec(empty, empty); }; // unhandled rejections tracking support, NodeJS Promise without it fails @@species test return (isNode || typeof PromiseRejectionEvent == 'function') && promise.then(empty) instanceof FakePromise; } catch(e){ /* empty */ } }(); // helpers var sameConstructor = function(a, b){ // with library wrapper special case return a === b || a === $Promise && b === Wrapper; }; var isThenable = function(it){ var then; return isObject(it) && typeof (then = it.then) == 'function' ? then : false; }; var newPromiseCapability = function(C){ return sameConstructor($Promise, C) ? new PromiseCapability(C) : new GenericPromiseCapability(C); }; var PromiseCapability = GenericPromiseCapability = function(C){ var resolve, reject; this.promise = new C(function($$resolve, $$reject){ if(resolve !== undefined || reject !== undefined)throw TypeError('Bad Promise constructor'); resolve = $$resolve; reject = $$reject; }); this.resolve = aFunction(resolve); this.reject = aFunction(reject); }; var perform = function(exec){ try { exec(); } catch(e){ return {error: e}; } }; var notify = function(promise, isReject){ if(promise._n)return; promise._n = true; var chain = promise._c; microtask(function(){ var value = promise._v , ok = promise._s == 1 , i = 0; var run = function(reaction){ var handler = ok ? reaction.ok : reaction.fail , resolve = reaction.resolve , reject = reaction.reject , domain = reaction.domain , result, then; try { if(handler){ if(!ok){ if(promise._h == 2)onHandleUnhandled(promise); promise._h = 1; } if(handler === true)result = value; else { if(domain)domain.enter(); result = handler(value); if(domain)domain.exit(); } if(result === reaction.promise){ reject(TypeError('Promise-chain cycle')); } else if(then = isThenable(result)){ then.call(result, resolve, reject); } else resolve(result); } else reject(value); } catch(e){ reject(e); } }; while(chain.length > i)run(chain[i++]); // variable length - can't use forEach promise._c = []; promise._n = false; if(isReject && !promise._h)onUnhandled(promise); }); }; var onUnhandled = function(promise){ task.call(global, function(){ var value = promise._v , abrupt, handler, console; if(isUnhandled(promise)){ abrupt = perform(function(){ if(isNode){ process.emit('unhandledRejection', value, promise); } else if(handler = global.onunhandledrejection){ handler({promise: promise, reason: value}); } else if((console = global.console) && console.error){ console.error('Unhandled promise rejection', value); } }); // Browsers should not trigger `rejectionHandled` event if it was handled here, NodeJS - should promise._h = isNode || isUnhandled(promise) ? 2 : 1; } promise._a = undefined; if(abrupt)throw abrupt.error; }); }; var isUnhandled = function(promise){ if(promise._h == 1)return false; var chain = promise._a || promise._c , i = 0 , reaction; while(chain.length > i){ reaction = chain[i++]; if(reaction.fail || !isUnhandled(reaction.promise))return false; } return true; }; var onHandleUnhandled = function(promise){ task.call(global, function(){ var handler; if(isNode){ process.emit('rejectionHandled', promise); } else if(handler = global.onrejectionhandled){ handler({promise: promise, reason: promise._v}); } }); }; var $reject = function(value){ var promise = this; if(promise._d)return; promise._d = true; promise = promise._w || promise; // unwrap promise._v = value; promise._s = 2; if(!promise._a)promise._a = promise._c.slice(); notify(promise, true); }; var $resolve = function(value){ var promise = this , then; if(promise._d)return; promise._d = true; promise = promise._w || promise; // unwrap try { if(promise === value)throw TypeError("Promise can't be resolved itself"); if(then = isThenable(value)){ microtask(function(){ var wrapper = {_w: promise, _d: false}; // wrap try { then.call(value, ctx($resolve, wrapper, 1), ctx($reject, wrapper, 1)); } catch(e){ $reject.call(wrapper, e); } }); } else { promise._v = value; promise._s = 1; notify(promise, false); } } catch(e){ $reject.call({_w: promise, _d: false}, e); // wrap } }; // constructor polyfill if(!USE_NATIVE){ // 25.4.3.1 Promise(executor) $Promise = function Promise(executor){ anInstance(this, $Promise, PROMISE, '_h'); aFunction(executor); Internal.call(this); try { executor(ctx($resolve, this, 1), ctx($reject, this, 1)); } catch(err){ $reject.call(this, err); } }; Internal = function Promise(executor){ this._c = []; // <- awaiting reactions this._a = undefined; // <- checked in isUnhandled reactions this._s = 0; // <- state this._d = false; // <- done this._v = undefined; // <- value this._h = 0; // <- rejection state, 0 - default, 1 - handled, 2 - unhandled this._n = false; // <- notify }; Internal.prototype = require('./_redefine-all')($Promise.prototype, { // 25.4.5.3 Promise.prototype.then(onFulfilled, onRejected) then: function then(onFulfilled, onRejected){ var reaction = newPromiseCapability(speciesConstructor(this, $Promise)); reaction.ok = typeof onFulfilled == 'function' ? onFulfilled : true; reaction.fail = typeof onRejected == 'function' && onRejected; reaction.domain = isNode ? process.domain : undefined; this._c.push(reaction); if(this._a)this._a.push(reaction); if(this._s)notify(this, false); return reaction.promise; }, // 25.4.5.1 Promise.prototype.catch(onRejected) 'catch': function(onRejected){ return this.then(undefined, onRejected); } }); PromiseCapability = function(){ var promise = new Internal; this.promise = promise; this.resolve = ctx($resolve, promise, 1); this.reject = ctx($reject, promise, 1); }; } $export($export.G + $export.W + $export.F * !USE_NATIVE, {Promise: $Promise}); require('./_set-to-string-tag')($Promise, PROMISE); require('./_set-species')(PROMISE); Wrapper = require('./_core')[PROMISE]; // statics $export($export.S + $export.F * !USE_NATIVE, PROMISE, { // 25.4.4.5 Promise.reject(r) reject: function reject(r){ var capability = newPromiseCapability(this) , $$reject = capability.reject; $$reject(r); return capability.promise; } }); $export($export.S + $export.F * (LIBRARY || !USE_NATIVE), PROMISE, { // 25.4.4.6 Promise.resolve(x) resolve: function resolve(x){ // instanceof instead of internal slot check because we should fix it without replacement native Promise core if(x instanceof $Promise && sameConstructor(x.constructor, this))return x; var capability = newPromiseCapability(this) , $$resolve = capability.resolve; $$resolve(x); return capability.promise; } }); $export($export.S + $export.F * !(USE_NATIVE && require('./_iter-detect')(function(iter){ $Promise.all(iter)['catch'](empty); })), PROMISE, { // 25.4.4.1 Promise.all(iterable) all: function all(iterable){ var C = this , capability = newPromiseCapability(C) , resolve = capability.resolve , reject = capability.reject; var abrupt = perform(function(){ var values = [] , index = 0 , remaining = 1; forOf(iterable, false, function(promise){ var $index = index++ , alreadyCalled = false; values.push(undefined); remaining++; C.resolve(promise).then(function(value){ if(alreadyCalled)return; alreadyCalled = true; values[$index] = value; --remaining || resolve(values); }, reject); }); --remaining || resolve(values); }); if(abrupt)reject(abrupt.error); return capability.promise; }, // 25.4.4.4 Promise.race(iterable) race: function race(iterable){ var C = this , capability = newPromiseCapability(C) , reject = capability.reject; var abrupt = perform(function(){ forOf(iterable, false, function(promise){ C.resolve(promise).then(capability.resolve, reject); }); }); if(abrupt)reject(abrupt.error); return capability.promise; } }); },{"./_a-function":14,"./_an-instance":16,"./_classof":19,"./_core":21,"./_ctx":22,"./_export":27,"./_for-of":29,"./_global":30,"./_is-object":38,"./_iter-detect":42,"./_library":45,"./_microtask":46,"./_redefine-all":58,"./_set-species":60,"./_set-to-string-tag":61,"./_species-constructor":64,"./_task":66,"./_wks":74}],81:[function(require,module,exports){ 'use strict'; var $at = require('./_string-at')(true); // 21.1.3.27 String.prototype[@@iterator]() require('./_iter-define')(String, 'String', function(iterated){ this._t = String(iterated); // target this._i = 0; // next index // 21.1.5.2.1 %StringIteratorPrototype%.next() }, function(){ var O = this._t , index = this._i , point; if(index >= O.length)return {value: undefined, done: true}; point = $at(O, index); this._i += point.length; return {value: point, done: false}; }); },{"./_iter-define":41,"./_string-at":65}],82:[function(require,module,exports){ require('./es6.array.iterator'); var global = require('./_global') , hide = require('./_hide') , Iterators = require('./_iterators') , TO_STRING_TAG = require('./_wks')('toStringTag'); for(var collections = ['NodeList', 'DOMTokenList', 'MediaList', 'StyleSheetList', 'CSSRuleList'], i = 0; i < 5; i++){ var NAME = collections[i] , Collection = global[NAME] , proto = Collection && Collection.prototype; if(proto && !proto[TO_STRING_TAG])hide(proto, TO_STRING_TAG, NAME); Iterators[NAME] = Iterators.Array; } },{"./_global":30,"./_hide":32,"./_iterators":44,"./_wks":74,"./es6.array.iterator":76}],83:[function(require,module,exports){ "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); var _extends2 = require("babel-runtime/helpers/extends"); var _extends3 = _interopRequireDefault(_extends2); var _collection = require("./collection"); var _collection2 = _interopRequireDefault(_collection); var _base = require("./adapters/base"); var _base2 = _interopRequireDefault(_base); function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } const DEFAULT_BUCKET_NAME = "default"; const DEFAULT_REMOTE = "http://localhost:8888/v1"; /** * KintoBase class. */ class KintoBase { /** * Provides a public access to the base adapter class. Users can create a * custom DB adapter by extending {@link BaseAdapter}. * * @type {Object} */ static get adapters() { return { BaseAdapter: _base2.default }; } /** * Synchronization strategies. Available strategies are: * * - `MANUAL`: Conflicts will be reported in a dedicated array. * - `SERVER_WINS`: Conflicts are resolved using remote data. * - `CLIENT_WINS`: Conflicts are resolved using local data. * * @type {Object} */ static get syncStrategy() { return _collection2.default.strategy; } /** * Constructor. * * Options: * - `{String}` `remote` The server URL to use. * - `{String}` `bucket` The collection bucket name. * - `{EventEmitter}` `events` Events handler. * - `{BaseAdapter}` `adapter` The base DB adapter class. * - `{Object}` `adapterOptions` Options given to the adapter. * - `{String}` `dbPrefix` The DB name prefix. * - `{Object}` `headers` The HTTP headers to use. * - `{String}` `requestMode` The HTTP CORS mode to use. * - `{Number}` `timeout` The requests timeout in ms (default: `5000`). * * @param {Object} options The options object. */ constructor(options = {}) { const defaults = { bucket: DEFAULT_BUCKET_NAME, remote: DEFAULT_REMOTE }; this._options = (0, _extends3.default)({}, defaults, options); if (!this._options.adapter) { throw new Error("No adapter provided"); } const { remote, events, headers, requestMode, timeout, ApiClass } = this._options; // public properties /** * The kinto HTTP client instance. * @type {KintoClient} */ this.api = new ApiClass(remote, { events, headers, requestMode, timeout }); /** * The event emitter instance. * @type {EventEmitter} */ this.events = this._options.events; } /** * Creates a {@link Collection} instance. The second (optional) parameter * will set collection-level options like e.g. `remoteTransformers`. * * @param {String} collName The collection name. * @param {Object} options May contain the following fields: * remoteTransformers: Array * @return {Collection} */ collection(collName, options = {}) { if (!collName) { throw new Error("missing collection name"); } const bucket = this._options.bucket; return new _collection2.default(bucket, collName, this.api, { events: this._options.events, adapter: this._options.adapter, adapterOptions: this._options.adapterOptions, dbPrefix: this._options.dbPrefix, idSchema: options.idSchema, remoteTransformers: options.remoteTransformers, hooks: options.hooks }); } } exports.default = KintoBase; },{"./adapters/base":85,"./collection":86,"babel-runtime/helpers/extends":8}],84:[function(require,module,exports){ "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); var _asyncToGenerator2 = require("babel-runtime/helpers/asyncToGenerator"); var _asyncToGenerator3 = _interopRequireDefault(_asyncToGenerator2); var _promise = require("babel-runtime/core-js/promise"); var _promise2 = _interopRequireDefault(_promise); var _keys = require("babel-runtime/core-js/object/keys"); var _keys2 = _interopRequireDefault(_keys); var _base = require("./base.js"); var _base2 = _interopRequireDefault(_base); var _utils = require("../utils"); function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } const INDEXED_FIELDS = ["id", "_status", "last_modified"]; /** * IDB cursor handlers. * @type {Object} */ const cursorHandlers = { all(filters, done) { const results = []; return function (event) { const cursor = event.target.result; if (cursor) { if ((0, _utils.filterObject)(filters, cursor.value)) { results.push(cursor.value); } cursor.continue(); } else { done(results); } }; }, in(values, done) { if (values.length === 0) { return done([]); } const sortedValues = [].slice.call(values).sort(); const results = []; return function (event) { const cursor = event.target.result; if (!cursor) { done(results); return; } const { key, value } = cursor; let i = 0; while (key > sortedValues[i]) { // The cursor has passed beyond this key. Check next. ++i; if (i === sortedValues.length) { done(results); // There is no next. Stop searching. return; } } if (key === sortedValues[i]) { results.push(value); cursor.continue(); } else { cursor.continue(sortedValues[i]); } }; } }; /** * Extract from filters definition the first indexed field. Since indexes were * created on single-columns, extracting a single one makes sense. * * @param {Object} filters The filters object. * @return {String|undefined} */ function findIndexedField(filters) { const filteredFields = (0, _keys2.default)(filters); const indexedFields = filteredFields.filter(field => { return INDEXED_FIELDS.indexOf(field) !== -1; }); return indexedFields[0]; } /** * 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 {IDBStore} store The IDB store. * @param {String|undefined} indexField The indexed field to query, if any. * @param {Any} value The value to filter, if any. * @param {Object} filters More filters. * @param {Function} done The operation completion handler. * @return {IDBRequest} */ function createListRequest(store, indexField, value, filters, done) { if (!indexField) { // Get all records. const request = store.openCursor(); request.onsuccess = cursorHandlers.all(filters, done); return request; } // WHERE IN equivalent clause if (Array.isArray(value)) { const request = store.index(indexField).openCursor(); request.onsuccess = cursorHandlers.in(value, done); return request; } // WHERE field = value clause const request = store.index(indexField).openCursor(IDBKeyRange.only(value)); request.onsuccess = cursorHandlers.all(filters, done); return request; } /** * IndexedDB adapter. * * This adapter doesn't support any options. */ class IDB extends _base2.default { /** * Constructor. * * @param {String} dbname The database nale. */ constructor(dbname) { super(); this._db = null; // public properties /** * The database name. * @type {String} */ this.dbname = dbname; } _handleError(method, err) { const error = new Error(method + "() " + err.message); error.stack = err.stack; throw error; } /** * Ensures a connection to the IndexedDB database has been opened. * * @override * @return {Promise} */ open() { if (this._db) { return _promise2.default.resolve(this); } return new _promise2.default((resolve, reject) => { const request = indexedDB.open(this.dbname, 1); request.onupgradeneeded = event => { // DB object const db = event.target.result; // Main collection store const collStore = db.createObjectStore(this.dbname, { keyPath: "id" }); // Primary key (generated by IdSchema, UUID by default) collStore.createIndex("id", "id", { unique: true }); // Local record status ("synced", "created", "updated", "deleted") collStore.createIndex("_status", "_status"); // Last modified field collStore.createIndex("last_modified", "last_modified"); // Metadata store const metaStore = db.createObjectStore("__meta__", { keyPath: "name" }); metaStore.createIndex("name", "name", { unique: true }); }; request.onerror = event => reject(event.target.error); request.onsuccess = event => { this._db = event.target.result; resolve(this); }; }); } /** * Closes current connection to the database. * * @override * @return {Promise} */ close() { if (this._db) { this._db.close(); // indexedDB.close is synchronous this._db = null; } return super.close(); } /** * Returns a transaction and a store objects for this collection. * * 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} mode Transaction mode ("readwrite" or undefined) * @param {String|null} name Store name (defaults to coll name) * @return {Object} */ prepare(mode = undefined, name = null) { const storeName = name || this.dbname; // On Safari, calling IDBDatabase.transaction with mode == undefined raises // a TypeError. const transaction = mode ? this._db.transaction([storeName], mode) : this._db.transaction([storeName]); const store = transaction.objectStore(storeName); return { transaction, store }; } /** * Deletes every records in the current collection. * * @override * @return {Promise} */ clear() { var _this = this; return (0, _asyncToGenerator3.default)(function* () { try { yield _this.open(); return new _promise2.default(function (resolve, reject) { const { transaction, store } = _this.prepare("readwrite"); store.clear(); transaction.onerror = function (event) { return reject(new Error(event.target.error)); }; transaction.oncomplete = function () { return resolve(); }; }); } catch (e) { _this._handleError("clear", e); } })(); } /** * 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"); * db.execute(transaction => { * transaction.create({id: 1, title: "foo"}); * transaction.update({id: 2, title: "bar"}); * transaction.delete(3); * return "foo"; * }) * .catch(console.error.bind(console)); * .then(console.log.bind(console)); // => "foo" * * @param {Function} callback The operation description callback. * @param {Object} options The options object. * @return {Promise} */ execute(callback, options = { preload: [] }) { var _this2 = this; return (0, _asyncToGenerator3.default)(function* () { // Transactions in IndexedDB are autocommited when a callback does not // perform any additional operation. // The way Promises are implemented in Firefox (see https://bugzilla.mozilla.org/show_bug.cgi?id=1193394) // prevents using within an opened transaction. // To avoid managing asynchronocity in the specified `callback`, we preload // a list of record in order to execute the `callback` synchronously. // See also: // - http://stackoverflow.com/a/28388805/330911 // - http://stackoverflow.com/a/10405196 // - https://jakearchibald.com/2015/tasks-microtasks-queues-and-schedules/ yield _this2.open(); return new _promise2.default(function (resolve, reject) { // Start transaction. const { transaction, store } = _this2.prepare("readwrite"); // Preload specified records using index. const ids = options.preload; store.index("id").openCursor().onsuccess = cursorHandlers.in(ids, function (records) { // Store obtained records by id. const preloaded = records.reduce(function (acc, record) { acc[record.id] = record; return acc; }, {}); // Expose a consistent API for every adapter instead of raw store methods. const proxy = transactionProxy(store, preloaded); // The callback is executed synchronously within the same transaction. let result; try { result = callback(proxy); } catch (e) { transaction.abort(); reject(e); } if (result instanceof _promise2.default) { // XXX: investigate how to provide documentation details in error. reject(new Error("execute() callback should not return a Promise.")); } // XXX unsure if we should manually abort the transaction on error transaction.onerror = function (event) { return reject(new Error(event.target.error)); }; transaction.oncomplete = function (event) { return resolve(result); }; }); }); })(); } /** * Retrieve a record by its primary key from the IndexedDB database. * * @override * @param {String} id The record id. * @return {Promise} */ get(id) { var _this3 = this; return (0, _asyncToGenerator3.default)(function* () { try { yield _this3.open(); return new _promise2.default(function (resolve, reject) { const { transaction, store } = _this3.prepare(); const request = store.get(id); transaction.onerror = function (event) { return reject(new Error(event.target.error)); }; transaction.oncomplete = function () { return resolve(request.result); }; }); } catch (e) { _this3._handleError("get", e); } })(); } /** * Lists all records from the IndexedDB database. * * @override * @return {Promise} */ list(params = { filters: {} }) { var _this4 = this; return (0, _asyncToGenerator3.default)(function* () { const { filters } = params; const indexField = findIndexedField(filters); const value = filters[indexField]; try { yield _this4.open(); const results = yield new _promise2.default(function (resolve, reject) { let results = []; // If `indexField` was used already, don't filter again. const remainingFilters = (0, _utils.omitKeys)(filters, indexField); const { transaction, store } = _this4.prepare(); createListRequest(store, indexField, value, remainingFilters, function (_results) { // we have received all requested records, parking them within // current scope results = _results; }); transaction.onerror = function (event) { return reject(new Error(event.target.error)); }; transaction.oncomplete = function (event) { return resolve(results); }; }); // The resulting list of records is sorted. // XXX: with some efforts, this could be fully implemented using IDB API. return params.order ? (0, _utils.sortObjects)(params.order, results) : results; } catch (e) { _this4._handleError("list", e); } })(); } /** * Store the lastModified value into metadata store. * * @override * @param {Number} lastModified * @return {Promise} */ saveLastModified(lastModified) { var _this5 = this; return (0, _asyncToGenerator3.default)(function* () { const value = parseInt(lastModified, 10) || null; yield _this5.open(); return new _promise2.default(function (resolve, reject) { const { transaction, store } = _this5.prepare("readwrite", "__meta__"); store.put({ name: "lastModified", value: value }); transaction.onerror = function (event) { return reject(event.target.error); }; transaction.oncomplete = function (event) { return resolve(value); }; }); })(); } /** * Retrieve saved lastModified value. * * @override * @return {Promise} */ getLastModified() { var _this6 = this; return (0, _asyncToGenerator3.default)(function* () { yield _this6.open(); return new _promise2.default(function (resolve, reject) { const { transaction, store } = _this6.prepare(undefined, "__meta__"); const request = store.get("lastModified"); transaction.onerror = function (event) { return reject(event.target.error); }; transaction.oncomplete = function (event) { resolve(request.result && request.result.value || null); }; }); })(); } /** * Load a dump of records exported from a server. * * @abstract * @return {Promise} */ loadDump(records) { var _this7 = this; return (0, _asyncToGenerator3.default)(function* () { try { yield _this7.execute(function (transaction) { records.forEach(function (record) { return transaction.update(record); }); }); const previousLastModified = yield _this7.getLastModified(); const lastModified = Math.max(...records.map(function (record) { return record.last_modified; })); if (lastModified > previousLastModified) { yield _this7.saveLastModified(lastModified); } return records; } catch (e) { _this7._handleError("loadDump", e); } })(); } } exports.default = IDB; /** * IDB transaction proxy. * * @param {IDBStore} store The IndexedDB database store. * @param {Array} preloaded The list of records to make available to * get() (default: []). * @return {Object} */ function transactionProxy(store, preloaded = []) { return { create(record) { store.add(record); }, update(record) { store.put(record); }, delete(id) { store.delete(id); }, get(id) { return preloaded[id]; } }; } },{"../utils":87,"./base.js":85,"babel-runtime/core-js/object/keys":5,"babel-runtime/core-js/promise":6,"babel-runtime/helpers/asyncToGenerator":7}],85:[function(require,module,exports){ "use strict"; /** * Base db adapter. * * @abstract */ Object.defineProperty(exports, "__esModule", { value: true }); var _promise = require("babel-runtime/core-js/promise"); var _promise2 = _interopRequireDefault(_promise); function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } class BaseAdapter { /** * Opens a connection to the database. * * @abstract * @return {Promise} */ open() { return _promise2.default.resolve(); } /** * Closes current connection to the database. * * @abstract * @return {Promise} */ close() { return _promise2.default.resolve(); } /** * Deletes every records present in the database. * * @abstract * @return {Promise} */ clear() { throw new Error("Not Implemented."); } /** * Executes a batch of operations within a single transaction. * * @abstract * @param {Function} callback The operation callback. * @param {Object} options The options object. * @return {Promise} */ execute(callback, options = { preload: [] }) { throw new Error("Not Implemented."); } /** * Retrieve a record by its primary key from the database. * * @abstract * @param {String} id The record id. * @return {Promise} */ get(id) { throw new Error("Not Implemented."); } /** * Lists all records from the database. * * @abstract * @param {Object} params The filters and order to apply to the results. * @return {Promise} */ list(params = { filters: {}, order: "" }) { throw new Error("Not Implemented."); } /** * Store the lastModified value. * * @abstract * @param {Number} lastModified * @return {Promise} */ saveLastModified(lastModified) { throw new Error("Not Implemented."); } /** * Retrieve saved lastModified value. * * @abstract * @return {Promise} */ getLastModified() { throw new Error("Not Implemented."); } /** * Load a dump of records exported from a server. * * @abstract * @return {Promise} */ loadDump(records) { throw new Error("Not Implemented."); } } exports.default = BaseAdapter; },{"babel-runtime/core-js/promise":6}],86:[function(require,module,exports){ "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.CollectionTransaction = exports.SyncResultObject = undefined; var _stringify = require("babel-runtime/core-js/json/stringify"); var _stringify2 = _interopRequireDefault(_stringify); var _promise = require("babel-runtime/core-js/promise"); var _promise2 = _interopRequireDefault(_promise); var _asyncToGenerator2 = require("babel-runtime/helpers/asyncToGenerator"); var _asyncToGenerator3 = _interopRequireDefault(_asyncToGenerator2); var _extends2 = require("babel-runtime/helpers/extends"); var _extends3 = _interopRequireDefault(_extends2); var _assign = require("babel-runtime/core-js/object/assign"); var _assign2 = _interopRequireDefault(_assign); exports.recordsEqual = recordsEqual; var _base = require("./adapters/base"); var _base2 = _interopRequireDefault(_base); var _IDB = require("./adapters/IDB"); var _IDB2 = _interopRequireDefault(_IDB); var _utils = require("./utils"); var _uuid = require("uuid"); function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } const RECORD_FIELDS_TO_CLEAN = ["_status"]; const AVAILABLE_HOOKS = ["incoming-changes"]; /** * Compare two records omitting local fields and synchronization * attributes (like _status and last_modified) * @param {Object} a A record to compare. * @param {Object} b A record to compare. * @return {boolean} */ function recordsEqual(a, b, localFields = []) { const fieldsToClean = RECORD_FIELDS_TO_CLEAN.concat(["last_modified"]).concat(localFields); const cleanLocal = r => (0, _utils.omitKeys)(r, fieldsToClean); return (0, _utils.deepEqual)(cleanLocal(a), cleanLocal(b)); } /** * Synchronization result object. */ class SyncResultObject { /** * Object default values. * @type {Object} */ static get defaults() { return { ok: true, lastModified: null, errors: [], created: [], updated: [], deleted: [], published: [], conflicts: [], skipped: [], resolved: [] }; } /** * Public constructor. */ constructor() { /** * Current synchronization result status; becomes `false` when conflicts or * errors are registered. * @type {Boolean} */ this.ok = true; (0, _assign2.default)(this, SyncResultObject.defaults); } /** * Adds entries for a given result type. * * @param {String} type The result type. * @param {Array} entries The result entries. * @return {SyncResultObject} */ add(type, entries) { if (!Array.isArray(this[type])) { return; } // Deduplicate entries by id. If the values don't have `id` attribute, just // keep all. const deduplicated = this[type].concat(entries).reduce((acc, cur) => { const existing = acc.filter(r => cur.id && r.id ? cur.id != r.id : true); return existing.concat(cur); }, []); this[type] = deduplicated; this.ok = this.errors.length + this.conflicts.length === 0; return this; } /** * Reinitializes result entries for a given result type. * * @param {String} type The result type. * @return {SyncResultObject} */ reset(type) { this[type] = SyncResultObject.defaults[type]; this.ok = this.errors.length + this.conflicts.length === 0; return this; } } exports.SyncResultObject = SyncResultObject; function createUUIDSchema() { return { generate() { return (0, _uuid.v4)(); }, validate(id) { return (0, _utils.isUUID)(id); } }; } function markStatus(record, status) { return (0, _extends3.default)({}, record, { _status: status }); } function markDeleted(record) { return markStatus(record, "deleted"); } function markSynced(record) { return markStatus(record, "synced"); } /** * Import a remote change into the local database. * * @param {IDBTransactionProxy} transaction The transaction handler. * @param {Object} remote The remote change object to import. * @param {Array} localFields The list of fields that remain local. * @return {Object} */ function importChange(transaction, remote, localFields) { const local = transaction.get(remote.id); if (!local) { // Not found locally but remote change is marked as deleted; skip to // avoid recreation. if (remote.deleted) { return { type: "skipped", data: remote }; } const synced = markSynced(remote); transaction.create(synced); return { type: "created", data: synced }; } // Compare local and remote, ignoring local fields. const isIdentical = recordsEqual(local, remote, localFields); // Apply remote changes on local record. const synced = (0, _extends3.default)({}, local, markSynced(remote)); // Detect or ignore conflicts if record has also been modified locally. if (local._status !== "synced") { // Locally deleted, unsynced: scheduled for remote deletion. if (local._status === "deleted") { return { type: "skipped", data: local }; } if (isIdentical) { // If records are identical, import anyway, so we bump the // local last_modified value from the server and set record // status to "synced". transaction.update(synced); return { type: "updated", data: { old: local, new: synced } }; } if (local.last_modified !== undefined && local.last_modified === remote.last_modified) { // If our local version has the same last_modified as the remote // one, this represents an object that corresponds to a resolved // conflict. Our local version represents the final output, so // we keep that one. (No transaction operation to do.) // But if our last_modified is undefined, // that means we've created the same object locally as one on // the server, which *must* be a conflict. return { type: "void" }; } return { type: "conflicts", data: { type: "incoming", local: local, remote: remote } }; } // Local record was synced. if (remote.deleted) { transaction.delete(remote.id); return { type: "deleted", data: local }; } // Import locally. transaction.update(synced); // if identical, simply exclude it from all SyncResultObject lists const type = isIdentical ? "void" : "updated"; return { type, data: { old: local, new: synced } }; } /** * Abstracts a collection of records stored in the local database, providing * CRUD operations and synchronization helpers. */ class Collection { /** * Constructor. * * Options: * - `{BaseAdapter} adapter` The DB adapter (default: `IDB`) * - `{String} dbPrefix` The DB name prefix (default: `""`) * * @param {String} bucket The bucket identifier. * @param {String} name The collection name. * @param {Api} api The Api instance. * @param {Object} options The options object. */ constructor(bucket, name, api, options = {}) { this._bucket = bucket; this._name = name; this._lastModified = null; const DBAdapter = options.adapter || _IDB2.default; if (!DBAdapter) { throw new Error("No adapter provided"); } const dbPrefix = options.dbPrefix || ""; const db = new DBAdapter(`${ dbPrefix }${ bucket }/${ name }`, options.adapterOptions); if (!(db instanceof _base2.default)) { throw new Error("Unsupported adapter."); } // public properties /** * The db adapter instance * @type {BaseAdapter} */ this.db = db; /** * The Api instance. * @type {KintoClient} */ this.api = api; /** * The event emitter instance. * @type {EventEmitter} */ this.events = options.events; /** * The IdSchema instance. * @type {Object} */ this.idSchema = this._validateIdSchema(options.idSchema); /** * The list of remote transformers. * @type {Array} */ this.remoteTransformers = this._validateRemoteTransformers(options.remoteTransformers); /** * The list of hooks. * @type {Object} */ this.hooks = this._validateHooks(options.hooks); /** * The list of fields names that will remain local. * @type {Array} */ this.localFields = options.localFields || []; } /** * The collection name. * @type {String} */ get name() { return this._name; } /** * The bucket name. * @type {String} */ get bucket() { return this._bucket; } /** * The last modified timestamp. * @type {Number} */ get lastModified() { return this._lastModified; } /** * Synchronization strategies. Available strategies are: * * - `MANUAL`: Conflicts will be reported in a dedicated array. * - `SERVER_WINS`: Conflicts are resolved using remote data. * - `CLIENT_WINS`: Conflicts are resolved using local data. * * @type {Object} */ static get strategy() { return { CLIENT_WINS: "client_wins", SERVER_WINS: "server_wins", MANUAL: "manual" }; } /** * Validates an idSchema. * * @param {Object|undefined} idSchema * @return {Object} */ _validateIdSchema(idSchema) { if (typeof idSchema === "undefined") { return createUUIDSchema(); } if (typeof idSchema !== "object") { throw new Error("idSchema must be an object."); } else if (typeof idSchema.generate !== "function") { throw new Error("idSchema must provide a generate function."); } else if (typeof idSchema.validate !== "function") { throw new Error("idSchema must provide a validate function."); } return idSchema; } /** * Validates a list of remote transformers. * * @param {Array|undefined} remoteTransformers * @return {Array} */ _validateRemoteTransformers(remoteTransformers) { if (typeof remoteTransformers === "undefined") { return []; } if (!Array.isArray(remoteTransformers)) { throw new Error("remoteTransformers should be an array."); } return remoteTransformers.map(transformer => { if (typeof transformer !== "object") { throw new Error("A transformer must be an object."); } else if (typeof transformer.encode !== "function") { throw new Error("A transformer must provide an encode function."); } else if (typeof transformer.decode !== "function") { throw new Error("A transformer must provide a decode function."); } return transformer; }); } /** * Validate the passed hook is correct. * * @param {Array|undefined} hook. * @return {Array} **/ _validateHook(hook) { if (!Array.isArray(hook)) { throw new Error("A hook definition should be an array of functions."); } return hook.map(fn => { if (typeof fn !== "function") { throw new Error("A hook definition should be an array of functions."); } return fn; }); } /** * Validates a list of hooks. * * @param {Object|undefined} hooks * @return {Object} */ _validateHooks(hooks) { if (typeof hooks === "undefined") { return {}; } if (Array.isArray(hooks)) { throw new Error("hooks should be an object, not an array."); } if (typeof hooks !== "object") { throw new Error("hooks should be an object."); } const validatedHooks = {}; for (let hook in hooks) { if (AVAILABLE_HOOKS.indexOf(hook) === -1) { throw new Error("The hook should be one of " + AVAILABLE_HOOKS.join(", ")); } validatedHooks[hook] = this._validateHook(hooks[hook]); } return validatedHooks; } /** * Deletes every records in the current collection and marks the collection as * never synced. * * @return {Promise} */ clear() { var _this = this; return (0, _asyncToGenerator3.default)(function* () { yield _this.db.clear(); yield _this.db.saveLastModified(null); return { data: [], permissions: {} }; })(); } /** * Encodes a record. * * @param {String} type Either "remote" or "local". * @param {Object} record The record object to encode. * @return {Promise} */ _encodeRecord(type, record) { if (!this[`${ type }Transformers`].length) { return _promise2.default.resolve(record); } return (0, _utils.waterfall)(this[`${ type }Transformers`].map(transformer => { return record => transformer.encode(record); }), record); } /** * Decodes a record. * * @param {String} type Either "remote" or "local". * @param {Object} record The record object to decode. * @return {Promise} */ _decodeRecord(type, record) { if (!this[`${ type }Transformers`].length) { return _promise2.default.resolve(record); } return (0, _utils.waterfall)(this[`${ type }Transformers`].reverse().map(transformer => { return record => transformer.decode(record); }), record); } /** * Adds a record to the local database, asserting that none * already exist with this ID. * * Note: If either the `useRecordId` or `synced` options are true, then the * record object must contain the id field to be validated. If none of these * options are true, an id is generated using the current IdSchema; in this * case, the record passed must not have an id. * * Options: * - {Boolean} synced Sets record status to "synced" (default: `false`). * - {Boolean} useRecordId Forces the `id` field from the record to be used, * instead of one that is generated automatically * (default: `false`). * * @param {Object} record * @param {Object} options * @return {Promise} */ create(record, options = { useRecordId: false, synced: false }) { // Validate the record and its ID (if any), even though this // validation is also done in the CollectionTransaction method, // because we need to pass the ID to preloadIds. const reject = msg => _promise2.default.reject(new Error(msg)); if (typeof record !== "object") { return reject("Record is not an object."); } if ((options.synced || options.useRecordId) && !record.hasOwnProperty("id")) { return reject("Missing required Id; synced and useRecordId options require one"); } if (!options.synced && !options.useRecordId && record.hasOwnProperty("id")) { return reject("Extraneous Id; can't create a record having one set."); } const newRecord = (0, _extends3.default)({}, record, { id: options.synced || options.useRecordId ? record.id : this.idSchema.generate(), _status: options.synced ? "synced" : "created" }); if (!this.idSchema.validate(newRecord.id)) { return reject(`Invalid Id: ${ newRecord.id }`); } return this.execute(txn => txn.create(newRecord), { preloadIds: [newRecord.id] }).catch(err => { if (options.useRecordId) { throw new Error("Couldn't create record. It may have been virtually deleted."); } throw err; }); } /** * Like {@link CollectionTransaction#update}, but wrapped in its own transaction. * * Options: * - {Boolean} synced: Sets record status to "synced" (default: false) * - {Boolean} patch: Extends the existing record instead of overwriting it * (default: false) * * @param {Object} record * @param {Object} options * @return {Promise} */ update(record, options = { synced: false, patch: false }) { // Validate the record and its ID, even though this validation is // also done in the CollectionTransaction method, because we need // to pass the ID to preloadIds. if (typeof record !== "object") { return _promise2.default.reject(new Error("Record is not an object.")); } if (!record.hasOwnProperty("id")) { return _promise2.default.reject(new Error("Cannot update a record missing id.")); } if (!this.idSchema.validate(record.id)) { return _promise2.default.reject(new Error(`Invalid Id: ${ record.id }`)); } return this.execute(txn => txn.update(record, options), { preloadIds: [record.id] }); } /** * Like {@link CollectionTransaction#upsert}, but wrapped in its own transaction. * * @param {Object} record * @return {Promise} */ upsert(record) { // Validate the record and its ID, even though this validation is // also done in the CollectionTransaction method, because we need // to pass the ID to preloadIds. if (typeof record !== "object") { return _promise2.default.reject(new Error("Record is not an object.")); } if (!record.hasOwnProperty("id")) { return _promise2.default.reject(new Error("Cannot update a record missing id.")); } if (!this.idSchema.validate(record.id)) { return _promise2.default.reject(new Error(`Invalid Id: ${ record.id }`)); } return this.execute(txn => txn.upsert(record), { preloadIds: [record.id] }); } /** * Like {@link CollectionTransaction#get}, but wrapped in its own transaction. * * Options: * - {Boolean} includeDeleted: Include virtually deleted records. * * @param {String} id * @param {Object} options * @return {Promise} */ get(id, options = { includeDeleted: false }) { return this.execute(txn => txn.get(id, options), { preloadIds: [id] }); } /** * Like {@link CollectionTransaction#getAny}, but wrapped in its own transaction. * * @param {String} id * @return {Promise} */ getAny(id) { return this.execute(txn => txn.getAny(id), { preloadIds: [id] }); } /** * Same as {@link Collection#delete}, but wrapped in its own transaction. * * Options: * - {Boolean} virtual: When set to `true`, doesn't actually delete the record, * update its `_status` attribute to `deleted` instead (default: true) * * @param {String} id The record's Id. * @param {Object} options The options object. * @return {Promise} */ delete(id, options = { virtual: true }) { return this.execute(transaction => { return transaction.delete(id, options); }, { preloadIds: [id] }); } /** * The same as {@link CollectionTransaction#deleteAny}, but wrapped * in its own transaction. * * @param {String} id The record's Id. * @return {Promise} */ deleteAny(id) { return this.execute(txn => txn.deleteAny(id), { preloadIds: [id] }); } /** * Lists records from the local database. * * Params: * - {Object} filters Filter the results (default: `{}`). * - {String} order The order to apply (default: `-last_modified`). * * Options: * - {Boolean} includeDeleted: Include virtually deleted records. * * @param {Object} params The filters and order to apply to the results. * @param {Object} options The options object. * @return {Promise} */ list(params = {}, options = { includeDeleted: false }) { var _this2 = this; return (0, _asyncToGenerator3.default)(function* () { params = (0, _extends3.default)({ order: "-last_modified", filters: {} }, params); const results = yield _this2.db.list(params); let data = results; if (!options.includeDeleted) { data = results.filter(function (record) { return record._status !== "deleted"; }); } return { data, permissions: {} }; })(); } /** * Imports remote changes into the local database. * This method is in charge of detecting the conflicts, and resolve them * according to the specified strategy. * @param {SyncResultObject} syncResultObject The sync result object. * @param {Array} decodedChanges The list of changes to import in the local database. * @param {String} strategy The {@link Collection.strategy} (default: MANUAL) * @return {Promise} */ importChanges(syncResultObject, decodedChanges, strategy = Collection.strategy.MANUAL) { var _this3 = this; return (0, _asyncToGenerator3.default)(function* () { // Retrieve records matching change ids. try { const { imports, resolved } = yield _this3.db.execute(function (transaction) { const imports = decodedChanges.map(function (remote) { // Store remote change into local database. return importChange(transaction, remote, _this3.localFields); }); const conflicts = imports.filter(function (i) { return i.type === "conflicts"; }).map(function (i) { return i.data; }); const resolved = _this3._handleConflicts(transaction, conflicts, strategy); return { imports, resolved }; }, { preload: decodedChanges.map(function (record) { return record.id; }) }); // Lists of created/updated/deleted records imports.forEach(function ({ type, data }) { return syncResultObject.add(type, data); }); // Automatically resolved conflicts (if not manual) if (resolved.length > 0) { syncResultObject.reset("conflicts").add("resolved", resolved); } } catch (err) { const data = { type: "incoming", message: err.message, stack: err.stack }; // XXX one error of the whole transaction instead of per atomic op syncResultObject.add("errors", data); } return syncResultObject; })(); } /** * Imports the responses of pushed changes into the local database. * Basically it stores the timestamp assigned by the server into the local * database. * @param {SyncResultObject} syncResultObject The sync result object. * @param {Array} toApplyLocally The list of changes to import in the local database. * @param {Array} conflicts The list of conflicts that have to be resolved. * @param {String} strategy The {@link Collection.strategy}. * @return {Promise} */ _applyPushedResults(syncResultObject, toApplyLocally, conflicts, strategy = Collection.strategy.MANUAL) { var _this4 = this; return (0, _asyncToGenerator3.default)(function* () { const toDeleteLocally = toApplyLocally.filter(function (r) { return r.deleted; }); const toUpdateLocally = toApplyLocally.filter(function (r) { return !r.deleted; }); const { published, resolved } = yield _this4.db.execute(function (transaction) { const updated = toUpdateLocally.map(function (record) { const synced = markSynced(record); transaction.update(synced); return synced; }); const deleted = toDeleteLocally.map(function (record) { transaction.delete(record.id); // Amend result data with the deleted attribute set return { id: record.id, deleted: true }; }); const published = updated.concat(deleted); // Handle conflicts, if any const resolved = _this4._handleConflicts(transaction, conflicts, strategy); return { published, resolved }; }); syncResultObject.add("published", published); if (resolved.length > 0) { syncResultObject.reset("conflicts").reset("resolved").add("resolved", resolved); } return syncResultObject; })(); } /** * Handles synchronization conflicts according to specified strategy. * * @param {SyncResultObject} result The sync result object. * @param {String} strategy The {@link Collection.strategy}. * @return {Promise} */ _handleConflicts(transaction, conflicts, strategy) { if (strategy === Collection.strategy.MANUAL) { return []; } return conflicts.map(conflict => { const resolution = strategy === Collection.strategy.CLIENT_WINS ? conflict.local : conflict.remote; const updated = this._resolveRaw(conflict, resolution); transaction.update(updated); return updated; }); } /** * Execute a bunch of operations in a transaction. * * This transaction should be atomic -- either all of its operations * will succeed, or none will. * * The argument to this function is itself a function which will be * called with a {@link CollectionTransaction}. Collection methods * are available on this transaction, but instead of returning * promises, they are synchronous. execute() returns a Promise whose * value will be the return value of the provided function. * * Most operations will require access to the record itself, which * must be preloaded by passing its ID in the preloadIds option. * * Options: * - {Array} preloadIds: list of IDs to fetch at the beginning of * the transaction * * @return {Promise} Resolves with the result of the given function * when the transaction commits. */ execute(doOperations, { preloadIds = [] } = {}) { for (let id of preloadIds) { if (!this.idSchema.validate(id)) { return _promise2.default.reject(Error(`Invalid Id: ${ id }`)); } } return this.db.execute(transaction => { const txn = new CollectionTransaction(this, transaction); const result = doOperations(txn); txn.emitEvents(); return result; }, { preload: preloadIds }); } /** * Resets the local records as if they were never synced; existing records are * marked as newly created, deleted records are dropped. * * A next call to {@link Collection.sync} will thus republish the whole * content of the local collection to the server. * * @return {Promise} Resolves with the number of processed records. */ resetSyncStatus() { var _this5 = this; return (0, _asyncToGenerator3.default)(function* () { const unsynced = yield _this5.list({ filters: { _status: ["deleted", "synced"] }, order: "" }, { includeDeleted: true }); yield _this5.db.execute(function (transaction) { unsynced.data.forEach(function (record) { if (record._status === "deleted") { // Garbage collect deleted records. transaction.delete(record.id); } else { // Records that were synced become «created». transaction.update((0, _extends3.default)({}, record, { last_modified: undefined, _status: "created" })); } }); }); _this5._lastModified = null; yield _this5.db.saveLastModified(null); return unsynced.data.length; })(); } /** * Returns an object containing two lists: * * - `toDelete`: unsynced deleted records we can safely delete; * - `toSync`: local updates to send to the server. * * @return {Promise} */ gatherLocalChanges() { var _this6 = this; return (0, _asyncToGenerator3.default)(function* () { const unsynced = yield _this6.list({ filters: { _status: ["created", "updated"] }, order: "" }); const deleted = yield _this6.list({ filters: { _status: "deleted" }, order: "" }, { includeDeleted: true }); const toSync = yield _promise2.default.all(unsynced.data.map(_this6._encodeRecord.bind(_this6, "remote"))); const toDelete = yield _promise2.default.all(deleted.data.map(_this6._encodeRecord.bind(_this6, "remote"))); return { toSync, toDelete }; })(); } /** * Fetch remote changes, import them to the local database, and handle * conflicts according to `options.strategy`. Then, updates the passed * {@link SyncResultObject} with import results. * * Options: * - {String} strategy: The selected sync strategy. * * @param {KintoClient.Collection} client Kinto client Collection instance. * @param {SyncResultObject} syncResultObject The sync result object. * @param {Object} options * @return {Promise} */ pullChanges(client, syncResultObject, options = {}) { var _this7 = this; return (0, _asyncToGenerator3.default)(function* () { if (!syncResultObject.ok) { return syncResultObject; } const since = _this7.lastModified ? _this7.lastModified : yield _this7.db.getLastModified(); options = (0, _extends3.default)({ strategy: Collection.strategy.MANUAL, lastModified: since, headers: {} }, options); // Optionally ignore some records when pulling for changes. // (avoid redownloading our own changes on last step of #sync()) let filters; if (options.exclude) { // Limit the list of excluded records to the first 50 records in order // to remain under de-facto URL size limit (~2000 chars). // http://stackoverflow.com/questions/417142/what-is-the-maximum-length-of-a-url-in-different-browsers/417184#417184 const exclude_id = options.exclude.slice(0, 50).map(function (r) { return r.id; }).join(","); filters = { exclude_id }; } // First fetch remote changes from the server const { data, last_modified } = yield client.listRecords({ // Since should be ETag (see https://github.com/Kinto/kinto.js/issues/356) since: options.lastModified ? `${ options.lastModified }` : undefined, headers: options.headers, filters }); // last_modified is the ETag header value (string). // For retro-compatibility with first kinto.js versions // parse it to integer. const unquoted = last_modified ? parseInt(last_modified, 10) : undefined; // Check if server was flushed. // This is relevant for the Kinto demo server // (and thus for many new comers). const localSynced = options.lastModified; const serverChanged = unquoted > options.lastModified; const emptyCollection = data.length === 0; if (!options.exclude && localSynced && serverChanged && emptyCollection) { throw Error("Server has been flushed."); } syncResultObject.lastModified = unquoted; // Decode incoming changes. const decodedChanges = yield _promise2.default.all(data.map(function (change) { return _this7._decodeRecord("remote", change); })); // Hook receives decoded records. const payload = { lastModified: unquoted, changes: decodedChanges }; const afterHooks = yield _this7.applyHook("incoming-changes", payload); // No change, nothing to import. if (afterHooks.changes.length > 0) { // Reflect these changes locally yield _this7.importChanges(syncResultObject, afterHooks.changes, options.strategy); } return syncResultObject; })(); } applyHook(hookName, payload) { if (typeof this.hooks[hookName] == "undefined") { return _promise2.default.resolve(payload); } return (0, _utils.waterfall)(this.hooks[hookName].map(hook => { return record => { const result = hook(payload, this); const resultThenable = result && typeof result.then === "function"; const resultChanges = result && result.hasOwnProperty("changes"); if (!(resultThenable || resultChanges)) { throw new Error(`Invalid return value for hook: ${ (0, _stringify2.default)(result) } has no 'then()' or 'changes' properties`); } return result; }; }), payload); } /** * Publish local changes to the remote server and updates the passed * {@link SyncResultObject} with publication results. * * @param {KintoClient.Collection} client Kinto client Collection instance. * @param {SyncResultObject} syncResultObject The sync result object. * @param {Object} changes The change object. * @param {Array} changes.toDelete The list of records to delete. * @param {Array} changes.toSync The list of records to create/update. * @param {Object} options The options object. * @return {Promise} */ pushChanges(client, { toDelete = [], toSync }, syncResultObject, options = {}) { var _this8 = this; return (0, _asyncToGenerator3.default)(function* () { if (!syncResultObject.ok) { return syncResultObject; } const safe = !options.strategy || options.strategy !== Collection.CLIENT_WINS; // Perform a batch request with every changes. const synced = yield client.batch(function (batch) { toDelete.forEach(function (r) { // never published locally deleted records should not be pusblished if (r.last_modified) { batch.deleteRecord(r); } }); toSync.forEach(function (r) { // Clean local fields (like _status) before sending to server. const published = _this8.cleanLocalFields(r); if (r._status === "created") { batch.createRecord(published); } else { batch.updateRecord(published); } }); }, { headers: options.headers, safe, aggregate: true }); // Store outgoing errors into sync result object syncResultObject.add("errors", synced.errors.map(function (e) { return (0, _extends3.default)({}, e, { type: "outgoing" }); })); // Store outgoing conflicts into sync result object const conflicts = []; for (let { type, local, remote } of synced.conflicts) { // Note: we ensure that local data are actually available, as they may // be missing in the case of a published deletion. const safeLocal = local && local.data || { id: remote.id }; const realLocal = yield _this8._decodeRecord("remote", safeLocal); const realRemote = yield _this8._decodeRecord("remote", remote); const conflict = { type, local: realLocal, remote: realRemote }; conflicts.push(conflict); } syncResultObject.add("conflicts", conflicts); // Records that must be deleted are either deletions that were pushed // to server (published) or deleted records that were never pushed (skipped). const missingRemotely = synced.skipped.map(function (r) { return (0, _extends3.default)({}, r, { deleted: true }); }); // For created and updated records, the last_modified coming from server // will be stored locally. // Reflect publication results locally using the response from // the batch request. const published = synced.published.map(function (c) { return c.data; }); const toApplyLocally = published.concat(missingRemotely); // Apply the decode transformers, if any const decoded = yield _promise2.default.all(toApplyLocally.map(function (record) { return _this8._decodeRecord("remote", record); })); // We have to update the local records with the responses of the server // (eg. last_modified values etc.). if (decoded.length > 0 || conflicts.length > 0) { yield _this8._applyPushedResults(syncResultObject, decoded, conflicts, options.strategy); } return syncResultObject; })(); } /** * Return a copy of the specified record without the local fields. * * @param {Object} record A record with potential local fields. * @return {Object} */ cleanLocalFields(record) { const localKeys = RECORD_FIELDS_TO_CLEAN.concat(this.localFields); return (0, _utils.omitKeys)(record, localKeys); } /** * Resolves a conflict, updating local record according to proposed * resolution — keeping remote record `last_modified` value as a reference for * further batch sending. * * @param {Object} conflict The conflict object. * @param {Object} resolution The proposed record. * @return {Promise} */ resolve(conflict, resolution) { return this.db.execute(transaction => { const updated = this._resolveRaw(conflict, resolution); transaction.update(updated); return { data: updated, permissions: {} }; }); } /** * @private */ _resolveRaw(conflict, resolution) { const resolved = (0, _extends3.default)({}, resolution, { // Ensure local record has the latest authoritative timestamp last_modified: conflict.remote.last_modified }); // If the resolution object is strictly equal to the // remote record, then we can mark it as synced locally. // Otherwise, mark it as updated (so that the resolution is pushed). const synced = (0, _utils.deepEqual)(resolved, conflict.remote); return markStatus(resolved, synced ? "synced" : "updated"); } /** * Synchronize remote and local data. The promise will resolve with a * {@link SyncResultObject}, though will reject: * * - if the server is currently backed off; * - if the server has been detected flushed. * * Options: * - {Object} headers: HTTP headers to attach to outgoing requests. * - {Collection.strategy} strategy: See {@link Collection.strategy}. * - {Boolean} ignoreBackoff: Force synchronization even if server is currently * backed off. * - {String} bucket: The remove bucket id to use (default: null) * - {String} collection: The remove collection id to use (default: null) * - {String} remote The remote Kinto server endpoint to use (default: null). * * @param {Object} options Options. * @return {Promise} * @throws {Error} If an invalid remote option is passed. */ sync(options = { strategy: Collection.strategy.MANUAL, headers: {}, ignoreBackoff: false, bucket: null, collection: null, remote: null }) { var _this9 = this; return (0, _asyncToGenerator3.default)(function* () { const previousRemote = _this9.api.remote; if (options.remote) { // Note: setting the remote ensures it's valid, throws when invalid. _this9.api.remote = options.remote; } if (!options.ignoreBackoff && _this9.api.backoff > 0) { const seconds = Math.ceil(_this9.api.backoff / 1000); return _promise2.default.reject(new Error(`Server is asking clients to back off; retry in ${ seconds }s or use the ignoreBackoff option.`)); } const client = _this9.api.bucket(options.bucket || _this9.bucket).collection(options.collection || _this9.name); const result = new SyncResultObject(); try { // Fetch last changes from the server. yield _this9.pullChanges(client, result, options); const { lastModified } = result; // Fetch local changes const { toDelete, toSync } = yield _this9.gatherLocalChanges(); // Publish local changes and pull local resolutions yield _this9.pushChanges(client, { toDelete, toSync }, result, options); // Publish local resolution of push conflicts to server (on CLIENT_WINS) const resolvedUnsynced = result.resolved.filter(function (r) { return r._status !== "synced"; }); if (resolvedUnsynced.length > 0) { const resolvedEncoded = yield _promise2.default.all(resolvedUnsynced.map(_this9._encodeRecord.bind(_this9, "remote"))); yield _this9.pushChanges(client, { toSync: resolvedEncoded }, result, options); } // Perform a last pull to catch changes that occured after the last pull, // while local changes were pushed. Do not do it nothing was pushed. if (result.published.length > 0) { // Avoid redownloading our own changes during the last pull. const pullOpts = (0, _extends3.default)({}, options, { lastModified, exclude: result.published }); yield _this9.pullChanges(client, result, pullOpts); } // Don't persist lastModified value if any conflict or error occured if (result.ok) { // No conflict occured, persist collection's lastModified value _this9._lastModified = yield _this9.db.saveLastModified(result.lastModified); } } finally { // Ensure API default remote is reverted if a custom one's been used _this9.api.remote = previousRemote; } return result; })(); } /** * Load a list of records already synced with the remote server. * * The local records which are unsynced or whose timestamp is either missing * or superior to those being loaded will be ignored. * * @param {Array} records The previously exported list of records to load. * @return {Promise} with the effectively imported records. */ loadDump(records) { var _this10 = this; return (0, _asyncToGenerator3.default)(function* () { if (!Array.isArray(records)) { throw new Error("Records is not an array."); } for (let record of records) { if (!record.hasOwnProperty("id") || !_this10.idSchema.validate(record.id)) { throw new Error("Record has invalid ID: " + (0, _stringify2.default)(record)); } if (!record.last_modified) { throw new Error("Record has no last_modified value: " + (0, _stringify2.default)(record)); } } // Fetch all existing records from local database, // and skip those who are newer or not marked as synced. // XXX filter by status / ids in records const { data } = yield _this10.list({}, { includeDeleted: true }); const existingById = data.reduce(function (acc, record) { acc[record.id] = record; return acc; }, {}); const newRecords = records.filter(function (record) { const localRecord = existingById[record.id]; const shouldKeep = // No local record with this id. localRecord === undefined || // Or local record is synced localRecord._status === "synced" && // And was synced from server localRecord.last_modified !== undefined && // And is older than imported one. record.last_modified > localRecord.last_modified; return shouldKeep; }); return yield _this10.db.loadDump(newRecords.map(markSynced)); })(); } } exports.default = Collection; /** * A Collection-oriented wrapper for an adapter's transaction. * * This defines the high-level functions available on a collection. * The collection itself offers functions of the same name. These will * perform just one operation in its own transaction. */ class CollectionTransaction { constructor(collection, adapterTransaction) { this.collection = collection; this.adapterTransaction = adapterTransaction; this._events = []; } _queueEvent(action, payload) { this._events.push({ action, payload }); } /** * Emit queued events, to be called once every transaction operations have * been executed successfully. */ emitEvents() { for (let { action, payload } of this._events) { this.collection.events.emit(action, payload); } if (this._events.length > 0) { const targets = this._events.map(({ action, payload }) => (0, _extends3.default)({ action }, payload)); this.collection.events.emit("change", { targets }); } this._events = []; } /** * Retrieve a record by its id from the local database, or * undefined if none exists. * * This will also return virtually deleted records. * * @param {String} id * @return {Object} */ getAny(id) { const record = this.adapterTransaction.get(id); return { data: record, permissions: {} }; } /** * Retrieve a record by its id from the local database. * * Options: * - {Boolean} includeDeleted: Include virtually deleted records. * * @param {String} id * @param {Object} options * @return {Object} */ get(id, options = { includeDeleted: false }) { const res = this.getAny(id); if (!res.data || !options.includeDeleted && res.data._status === "deleted") { throw new Error(`Record with id=${ id } not found.`); } return res; } /** * Deletes a record from the local database. * * Options: * - {Boolean} virtual: When set to `true`, doesn't actually delete the record, * update its `_status` attribute to `deleted` instead (default: true) * * @param {String} id The record's Id. * @param {Object} options The options object. * @return {Object} */ delete(id, options = { virtual: true }) { // Ensure the record actually exists. const existing = this.adapterTransaction.get(id); const alreadyDeleted = existing && existing._status == "deleted"; if (!existing || alreadyDeleted && options.virtual) { throw new Error(`Record with id=${ id } not found.`); } // Virtual updates status. if (options.virtual) { this.adapterTransaction.update(markDeleted(existing)); } else { // Delete for real. this.adapterTransaction.delete(id); } this._queueEvent("delete", { data: existing }); return { data: existing, permissions: {} }; } /** * Deletes a record from the local database, if any exists. * Otherwise, do nothing. * * @param {String} id The record's Id. * @return {Object} */ deleteAny(id) { const existing = this.adapterTransaction.get(id); if (existing) { this.adapterTransaction.update(markDeleted(existing)); this._queueEvent("delete", { data: existing }); } return { data: (0, _extends3.default)({ id }, existing), deleted: !!existing, permissions: {} }; } /** * Adds a record to the local database, asserting that none * already exist with this ID. * * @param {Object} record, which must contain an ID * @return {Object} */ create(record) { if (typeof record !== "object") { throw new Error("Record is not an object."); } if (!record.hasOwnProperty("id")) { throw new Error("Cannot create a record missing id"); } if (!this.collection.idSchema.validate(record.id)) { throw new Error(`Invalid Id: ${ record.id }`); } this.adapterTransaction.create(record); this._queueEvent("create", { data: record }); return { data: record, permissions: {} }; } /** * Updates a record from the local database. * * Options: * - {Boolean} synced: Sets record status to "synced" (default: false) * - {Boolean} patch: Extends the existing record instead of overwriting it * (default: false) * * @param {Object} record * @param {Object} options * @return {Object} */ update(record, options = { synced: false, patch: false }) { if (typeof record !== "object") { throw new Error("Record is not an object."); } if (!record.hasOwnProperty("id")) { throw new Error("Cannot update a record missing id."); } if (!this.collection.idSchema.validate(record.id)) { throw new Error(`Invalid Id: ${ record.id }`); } const oldRecord = this.adapterTransaction.get(record.id); if (!oldRecord) { throw new Error(`Record with id=${ record.id } not found.`); } const newRecord = options.patch ? (0, _extends3.default)({}, oldRecord, record) : record; const updated = this._updateRaw(oldRecord, newRecord, options); this.adapterTransaction.update(updated); this._queueEvent("update", { data: updated, oldRecord }); return { data: updated, oldRecord, permissions: {} }; } /** * Lower-level primitive for updating a record while respecting * _status and last_modified. * * @param {Object} oldRecord: the record retrieved from the DB * @param {Object} newRecord: the record to replace it with * @return {Object} */ _updateRaw(oldRecord, newRecord, { synced = false } = {}) { const updated = (0, _extends3.default)({}, newRecord); // Make sure to never loose the existing timestamp. if (oldRecord && oldRecord.last_modified && !updated.last_modified) { updated.last_modified = oldRecord.last_modified; } // If only local fields have changed, then keep record as synced. // If status is created, keep record as created. // If status is deleted, mark as updated. const isIdentical = oldRecord && recordsEqual(oldRecord, updated, this.localFields); const keepSynced = isIdentical && oldRecord._status == "synced"; const neverSynced = !oldRecord || oldRecord && oldRecord._status == "created"; const newStatus = keepSynced || synced ? "synced" : neverSynced ? "created" : "updated"; return markStatus(updated, newStatus); } /** * Upsert a record into the local database. * * This record must have an ID. * * If a record with this ID already exists, it will be replaced. * Otherwise, this record will be inserted. * * @param {Object} record * @return {Object} */ upsert(record) { if (typeof record !== "object") { throw new Error("Record is not an object."); } if (!record.hasOwnProperty("id")) { throw new Error("Cannot update a record missing id."); } if (!this.collection.idSchema.validate(record.id)) { throw new Error(`Invalid Id: ${ record.id }`); } let oldRecord = this.adapterTransaction.get(record.id); const updated = this._updateRaw(oldRecord, record); this.adapterTransaction.update(updated); // Don't return deleted records -- pretend they are gone if (oldRecord && oldRecord._status == "deleted") { oldRecord = undefined; } if (oldRecord) { this._queueEvent("update", { data: updated, oldRecord }); } else { this._queueEvent("create", { data: updated }); } return { data: updated, oldRecord, permissions: {} }; } } exports.CollectionTransaction = CollectionTransaction; },{"./adapters/IDB":84,"./adapters/base":85,"./utils":87,"babel-runtime/core-js/json/stringify":3,"babel-runtime/core-js/object/assign":4,"babel-runtime/core-js/promise":6,"babel-runtime/helpers/asyncToGenerator":7,"babel-runtime/helpers/extends":8,"uuid":9}],87:[function(require,module,exports){ "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.RE_UUID = undefined; var _promise = require("babel-runtime/core-js/promise"); var _promise2 = _interopRequireDefault(_promise); var _keys = require("babel-runtime/core-js/object/keys"); var _keys2 = _interopRequireDefault(_keys); exports.sortObjects = sortObjects; exports.filterObject = filterObject; exports.filterObjects = filterObjects; exports.isUUID = isUUID; exports.waterfall = waterfall; exports.deepEqual = deepEqual; exports.omitKeys = omitKeys; function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } const RE_UUID = exports.RE_UUID = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i; /** * Checks if a value is undefined. * @param {Any} value * @return {Boolean} */ function _isUndefined(value) { return typeof value === "undefined"; } /** * Sorts records in a list according to a given ordering. * * @param {String} order The ordering, eg. `-last_modified`. * @param {Array} list The collection to order. * @return {Array} */ function sortObjects(order, list) { const hasDash = order[0] === "-"; const field = hasDash ? order.slice(1) : order; const direction = hasDash ? -1 : 1; return list.slice().sort((a, b) => { if (a[field] && _isUndefined(b[field])) { return direction; } if (b[field] && _isUndefined(a[field])) { return -direction; } if (_isUndefined(a[field]) && _isUndefined(b[field])) { return 0; } return a[field] > b[field] ? direction : -direction; }); } /** * Test if a single object matches all given filters. * * @param {Object} filters The filters object. * @param {Object} entry The object to filter. * @return {Function} */ function filterObject(filters, entry) { return (0, _keys2.default)(filters).every(filter => { const value = filters[filter]; if (Array.isArray(value)) { return value.some(candidate => candidate === entry[filter]); } return entry[filter] === value; }); } /** * Filters records in a list matching all given filters. * * @param {Object} filters The filters object. * @param {Array} list The collection to filter. * @return {Array} */ function filterObjects(filters, list) { return list.filter(entry => { return filterObject(filters, entry); }); } /** * Checks if a string is an UUID. * * @param {String} uuid The uuid to validate. * @return {Boolean} */ function isUUID(uuid) { return RE_UUID.test(uuid); } /** * Resolves a list of functions sequentially, which can be sync or async; in * case of async, functions must return a promise. * * @param {Array} fns The list of functions. * @param {Any} init The initial value. * @return {Promise} */ function waterfall(fns, init) { if (!fns.length) { return _promise2.default.resolve(init); } return fns.reduce((promise, nextFn) => { return promise.then(nextFn); }, _promise2.default.resolve(init)); } /** * Simple deep object comparison function. This only supports comparison of * serializable JavaScript objects. * * @param {Object} a The source object. * @param {Object} b The compared object. * @return {Boolean} */ function deepEqual(a, b) { if (a === b) { return true; } if (typeof a !== typeof b) { return false; } if (!(a && typeof a == "object") || !(b && typeof b == "object")) { return false; } if ((0, _keys2.default)(a).length !== (0, _keys2.default)(b).length) { return false; } for (let k in a) { if (!deepEqual(a[k], b[k])) { return false; } } return true; } /** * Return an object without the specified keys. * * @param {Object} obj The original object. * @param {Array} keys The list of keys to exclude. * @return {Object} A copy without the specified keys. */ function omitKeys(obj, keys = []) { return (0, _keys2.default)(obj).reduce((acc, key) => { if (keys.indexOf(key) === -1) { acc[key] = obj[key]; } return acc; }, {}); } },{"babel-runtime/core-js/object/keys":5,"babel-runtime/core-js/promise":6}]},{},[2])(2) });