1(function (exports) { 2 'use strict'; 3 4 /* 5 * Dexie.js - a minimalistic wrapper for IndexedDB 6 * =============================================== 7 * 8 * By David Fahlander, [email protected] 9 * 10 * Version 2.0.4, Fri May 25 2018 11 * 12 * http://dexie.org 13 * 14 * Apache License Version 2.0, January 2004, http://www.apache.org/licenses/ 15 */ 16 17 /** 18 * Modify list for integration with Bitrix Framework: 19 * - removed integration with third-party package builders; 20 * - add check variables before using them, see tags: 28122018; 21 * - add alternative Promise check for Bitrix Cli, see tags: 02032020; 22 * - add export for work in Bitrix CoreJS extensions; 23 */ 24 var keys = Object.keys; 25 var isArray = Array.isArray; 26 27 var _global = typeof self !== 'undefined' ? self : typeof window !== 'undefined' ? window : global; 28 29 function extend(obj, extension) { 30 if (babelHelpers.typeof(extension) !== 'object') return obj; 31 keys(extension).forEach(function (key) { 32 obj[key] = extension[key]; 33 }); 34 return obj; 35 } 36 37 var getProto = Object.getPrototypeOf; 38 var _hasOwn = {}.hasOwnProperty; 39 40 function hasOwn(obj, prop) { 41 return _hasOwn.call(obj, prop); 42 } 43 44 function props(proto, extension) { 45 if (typeof extension === 'function') extension = extension(getProto(proto)); // tag start 28122018 46 47 if (babelHelpers.typeof(extension) !== 'object') return; // tag end 28122018 48 49 keys(extension).forEach(function (key) { 50 setProp(proto, key, extension[key]); 51 }); 52 } 53 54 var defineProperty = Object.defineProperty; 55 56 function setProp(obj, prop, functionOrGetSet, options) { 57 defineProperty(obj, prop, extend(functionOrGetSet && hasOwn(functionOrGetSet, "get") && typeof functionOrGetSet.get === 'function' ? { 58 get: functionOrGetSet.get, 59 set: functionOrGetSet.set, 60 configurable: true 61 } : { 62 value: functionOrGetSet, 63 configurable: true, 64 writable: true 65 }, options)); 66 } 67 68 function derive(Child) { 69 return { 70 from: function from(Parent) { 71 Child.prototype = Object.create(Parent.prototype); 72 setProp(Child.prototype, "constructor", Child); 73 return { 74 extend: props.bind(null, Child.prototype) 75 }; 76 } 77 }; 78 } 79 80 var getOwnPropertyDescriptor = Object.getOwnPropertyDescriptor; 81 82 function getPropertyDescriptor(obj, prop) { 83 var pd = getOwnPropertyDescriptor(obj, prop), 84 proto; 85 return pd || (proto = getProto(obj)) && getPropertyDescriptor(proto, prop); 86 } 87 88 var _slice = [].slice; 89 90 function slice(args, start, end) { 91 return _slice.call(args, start, end); 92 } 93 94 function override(origFunc, overridedFactory) { 95 return overridedFactory(origFunc); 96 } 97 98 function assert(b) { 99 if (!b) throw new Error("Assertion Failed"); 100 } 101 102 function asap(fn) { 103 if (_global.setImmediate) setImmediate(fn);else setTimeout(fn, 0); 104 } 105 /** Generate an object (hash map) based on given array. 106 * @param extractor Function taking an array item and its index and returning an array of 2 items ([key, value]) to 107 * instert on the resulting object for each item in the array. If this function returns a falsy value, the 108 * current item wont affect the resulting object. 109 */ 110 111 112 function arrayToObject(array, extractor) { 113 return array.reduce(function (result, item, i) { 114 var nameAndValue = extractor(item, i); 115 if (nameAndValue) result[nameAndValue[0]] = nameAndValue[1]; 116 return result; 117 }, {}); 118 } 119 120 function trycatcher(fn, reject) { 121 return function () { 122 try { 123 fn.apply(this, arguments); 124 } catch (e) { 125 reject(e); 126 } 127 }; 128 } 129 130 function tryCatch(fn, onerror, args) { 131 try { 132 fn.apply(null, args); 133 } catch (ex) { 134 onerror && onerror(ex); 135 } 136 } 137 138 function getByKeyPath(obj, keyPath) { 139 // http://www.w3.org/TR/IndexedDB/#steps-for-extracting-a-key-from-a-value-using-a-key-path 140 if (hasOwn(obj, keyPath)) return obj[keyPath]; // This line is moved from last to first for optimization purpose. 141 142 if (!keyPath) return obj; 143 144 if (typeof keyPath !== 'string') { 145 var rv = []; 146
147 for (var i = 0, l = keyPath.length; i < l; ++i) { 148 var val = getByKeyPath(obj, keyPath[i]); 149 rv.push(val); 150 } 151 152 return rv; 153 } 154 155 var period = keyPath.indexOf('.'); 156 157 if (period !== -1) { 158 var innerObj = obj[keyPath.substr(0, period)]; 159 return innerObj === undefined ? undefined : getByKeyPath(innerObj, keyPath.substr(period + 1)); 160 } 161 162 return undefined; 163 } 164 165 function setByKeyPath(obj, keyPath, value) { 166 if (!obj || keyPath === undefined) return; 167 if ('isFrozen' in Object && Object.isFrozen(obj)) return; 168 169 if (typeof keyPath !== 'string' && 'length' in keyPath) { 170 assert(typeof value !== 'string' && 'length' in value); 171 172 for (var i = 0, l = keyPath.length; i < l; ++i) { 173 setByKeyPath(obj, keyPath[i], value[i]); 174 } 175 } else { 176 var period = keyPath.indexOf('.'); 177 178 if (period !== -1) { 179 var currentKeyPath = keyPath.substr(0, period); 180 var remainingKeyPath = keyPath.substr(period + 1); 181 if (remainingKeyPath === "") { 182 if (value === undefined) delete obj[currentKeyPath];else obj[currentKeyPath] = value; 183 } else { 184 var innerObj = obj[currentKeyPath]; 185 if (!innerObj) innerObj = obj[currentKeyPath] = {}; 186 setByKeyPath(innerObj, remainingKeyPath, value); 187 } 188 } else { 189 if (value === undefined) delete obj[keyPath];else obj[keyPath] = value; 190 } 191 } 192 } 193 194 function delByKeyPath(obj, keyPath) { 195 if (typeof keyPath === 'string') setByKeyPath(obj, keyPath, undefined);else if ('length' in keyPath) [].map.call(keyPath, function (kp) { 196 setByKeyPath(obj, kp, undefined); 197 }); 198 } 199 200 function shallowClone(obj) { 201 var rv = {}; 202 203 for (var m in obj) { 204 if (hasOwn(obj, m)) rv[m] = obj[m]; 205 } 206 207 return rv; 208 } 209 210 var concat = [].concat; 211 212 function flatten(a) { 213 return concat.apply([], a); 214 } //https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API/Structured_clone_algorithm 215 216 217 var intrinsicTypes = "Boolean,String,Date,RegExp,Blob,File,FileList,ArrayBuffer,DataView,Uint8ClampedArray,ImageData,Map,Set".split(',').concat(flatten([8, 16, 32, 64].map(function (num) { 218 return ["Int", "Uint", "Float"].map(function (t) { 219 return t + num + "Array"; 220 }); 221 }))).filter(function (t) { 222 return _global[t]; 223 }).map(function (t) { 224 return _global[t]; 225 }); 226 227 function deepClone(any) { 228 if (!any || babelHelpers.typeof(any) !== 'object') return any; 229 var rv; 230 231 if (isArray(any)) { 232 rv = []; 233 234 for (var i = 0, l = any.length; i < l; ++i) { 235 rv.push(deepClone(any[i])); 236 } 237 } else if (intrinsicTypes.indexOf(any.constructor) >= 0) { 238 rv = any; 239 } else { 240 rv = any.constructor ? Object.create(any.constructor.prototype) : {}; 241 242 for (var prop in any) { 243 if (hasOwn(any, prop)) { 244 rv[prop] = deepClone(any[prop]); 245 } 246 } 247 } 248 249 return rv; 250 } 251 252 function getObjectDiff(a, b, rv, prfx) { 253 // Compares objects a and b and produces a diff object. 254 rv = rv || {}; 255 prfx = prfx || ''; 256 keys(a).forEach(function (prop) { 257 if (!hasOwn(b, prop)) rv[prfx + prop] = undefined; // Property removed 258 else { 259 var ap = a[prop], 260 bp = b[prop]; 261 if (babelHelpers.typeof(ap) === 'object' && babelHelpers.typeof(bp) === 'object' && ap && bp && // Now compare constructors are same (not equal because wont work in Safari) 262 '' + ap.constructor === '' + bp.constructor) // Same type of object but its properties may have changed 263 getObjectDiff(ap, bp, rv, prfx + prop + ".");else if (ap !== bp) rv[prfx + prop] = b[prop]; // Primitive value changed 264 } 265 }); 266 keys(b).forEach(function (prop) { 267 if (!hasOwn(a, prop)) { 268 rv[prfx + prop] = b[prop]; // Property added 269 } 270 }); 271 return rv; 272 } // If first argument is iterable or array-like, return it as an array 273 274 275 var iteratorSymbol = typeof Symbol !== 'undefined' && Symbol.iterator; 276 var getIteratorOf = iteratorSymbol ? function (x) { 277 var i; 278 return x != null && (i = x[iteratorSymbol]) && i.apply(x); 279 } : function () { 280 return null; 281 }; 282 var NO_CHAR_ARRAY = {}; // Takes one or several arguments and returns an array based on the following criteras: 283 // * If several arguments provided, return arguments converted to an array in a way that 284 // still allows javascript engine to optimize the code. 285 // * If single argument is an array, return a clone of it. 286 // * If this-pointer equals NO_CHAR_ARRAY, don't accept strings as valid iterables as a special 287 // case to the two bullets below. 288 // * If single argument is an iterable, convert it to an array and return the resulting array. 289 // * If single argument is array-like (has length of type number), convert it to an array.
290 291 function getArrayOf(arrayLike) { 292 var i, a, x, it; 293 294 if (arguments.length === 1) { 295 if (isArray(arrayLike)) return arrayLike.slice(); 296 if (this === NO_CHAR_ARRAY && typeof arrayLike === 'string') return [arrayLike]; 297 298 if (it = getIteratorOf(arrayLike)) { 299 a = []; 300 301 while (x = it.next(), !x.done) { 302 a.push(x.value); 303 } 304 305 return a; 306 } 307 308 if (arrayLike == null) return [arrayLike]; 309 i = arrayLike.length; 310 311 if (typeof i === 'number') { 312 a = new Array(i); 313 314 while (i--) { 315 a[i] = arrayLike[i]; 316 } 317 318 return a; 319 } 320 321 return [arrayLike]; 322 } 323 324 i = arguments.length; 325 a = new Array(i); 326 327 while (i--) { 328 a[i] = arguments[i]; 329 } 330 331 return a; 332 } // By default, debug will be true only if platform is a web platform and its page is served from localhost. 333 // When debug = true, error's stacks will contain asyncronic long stacks. 334 335 336 var debug = typeof location !== 'undefined' && // By default, use debug mode if served from localhost. 337 /^(http|https):\/\/(localhost|127\.0\.0\.1)/.test(location.href); 338 339 function setDebug(value, filter) { 340 debug = value; 341 libraryFilter = filter; 342 } 343 344 var libraryFilter = function libraryFilter() { 345 return true; 346 }; 347 348 var NEEDS_THROW_FOR_STACK = !new Error("").stack; 349 350 function getErrorWithStack() { 351 352 if (NEEDS_THROW_FOR_STACK) try { 353 throw new Error(); // Fallback if above line don't throw. 354 } catch (e) { 355 return e; 356 } 357 return new Error(); 358 } 359 360 function prettyStack(exception, numIgnoredFrames) { 361 var stack = exception.stack; 362 if (!stack) return ""; 363 numIgnoredFrames = numIgnoredFrames || 0; 364 if (stack.indexOf(exception.name) === 0) numIgnoredFrames += (exception.name + exception.message).split('\n').length; 365 return stack.split('\n').slice(numIgnoredFrames).filter(libraryFilter).map(function (frame) { 366 return "\n" + frame; 367 }).join(''); 368 } 369 370 function deprecated(what, fn) { 371 return function () { 372 console.warn(what + " is deprecated. See https://github.com/dfahlander/Dexie.js/wiki/Deprecations. " + prettyStack(getErrorWithStack(), 1)); 373 return fn.apply(this, arguments); 374 }; 375 } 376 377 var dexieErrorNames = ['Modify', 'Bulk', 'OpenFailed', 'VersionChange', 'Schema', 'Upgrade', 'InvalidTable', 'MissingAPI', 'NoSuchDatabase', 'InvalidArgument', 'SubTransaction', 'Unsupported', 'Internal', 'DatabaseClosed', 'PrematureCommit', 'ForeignAwait']; 378 var idbDomErrorNames = ['Unknown', 'Constraint', 'Data', 'TransactionInactive', 'ReadOnly', 'Version', 'NotFound', 'InvalidState', 'InvalidAccess', 'Abort', 'Timeout', 'QuotaExceeded', 'Syntax', 'DataClone']; 379 var errorList = dexieErrorNames.concat(idbDomErrorNames); 380 var defaultTexts = { 381 VersionChanged: "Database version changed by other database connection", 382 DatabaseClosed: "Database has been closed", 383 Abort: "Transaction aborted", 384 TransactionInactive: "Transaction has already completed or failed" 385 }; // 386 // DexieError - base class of all out exceptions. 387 // 388 389 function DexieError(name, msg) { 390 // Reason we don't use ES6 classes is because: 391 // 1. It bloats transpiled code and increases size of minified code. 392 // 2. It doesn't give us much in this case. 393 // 3. It would require sub classes to call super(), which 394 // is not needed when deriving from Error. 395 this._e = getErrorWithStack(); 396 this.name = name; 397 this.message = msg; 398 } 399 400 derive(DexieError).from(Error).extend({ 401 stack: { 402 get: function get() { 403 return this._stack || (this._stack = this.name + ": " + this.message + prettyStack(this._e, 2)); 404 } 405 }, 406 toString: function toString() { 407 return this.name + ": " + this.message; 408 } 409 }); 410 411 function getMultiErrorMessage(msg, failures) { 412 return msg + ". Errors: " + failures.map(function (f) { 413 return f.toString(); 414 }).filter(function (v, i, s) { 415 return s.indexOf(v) === i; 416 }) // Only unique error strings 417 .join('\n'); 418 } // 419 // ModifyError - thrown in Collection.modify()
420 // Specific constructor because it contains members failures and failedKeys. 421 // 422 423 424 function ModifyError(msg, failures, successCount, failedKeys) { 425 this._e = getErrorWithStack(); 426 this.failures = failures; 427 this.failedKeys = failedKeys; 428 this.successCount = successCount; 429 } 430 431 derive(ModifyError).from(DexieError); 432 433 function BulkError(msg, failures) { 434 this._e = getErrorWithStack(); 435 this.name = "BulkError"; 436 this.failures = failures; 437 this.message = getMultiErrorMessage(msg, failures); 438 } 439 440 derive(BulkError).from(DexieError); // 441 // 442 // Dynamically generate error names and exception classes based 443 // on the names in errorList. 444 // 445 // 446 // Map of {ErrorName -> ErrorName + "Error"} 447 448 var errnames = errorList.reduce(function (obj, name) { 449 return obj[name] = name + "Error", obj; 450 }, {}); // Need an alias for DexieError because we're gonna create subclasses with the same name. 451 452 var BaseException = DexieError; // Map of {ErrorName -> exception constructor} 453 454 var exceptions = errorList.reduce(function (obj, name) { 455 // Let the name be "DexieError" because this name may 456 // be shown in call stack and when debugging. DexieError is 457 // the most true name because it derives from DexieError, 458 // and we cannot change Function.name programatically without 459 // dynamically create a Function object, which would be considered 460 // 'eval-evil'. 461 var fullName = name + "Error"; 462 463 function DexieError(msgOrInner, inner) { 464 this._e = getErrorWithStack(); 465 this.name = fullName; 466 467 if (!msgOrInner) { 468 this.message = defaultTexts[name] || fullName; 469 this.inner = null; 470 } else if (typeof msgOrInner === 'string') { 471 this.message = msgOrInner; 472 this.inner = inner || null; 473 } else if (babelHelpers.typeof(msgOrInner) === 'object') { 474 this.message = msgOrInner.name + " " + msgOrInner.message; 475 this.inner = msgOrInner; 476 } 477 } 478 479 derive(DexieError).from(BaseException); 480 obj[name] = DexieError; 481 return obj; 482 }, {}); // Use ECMASCRIPT standard exceptions where applicable: 483 484 exceptions.Syntax = SyntaxError; 485 exceptions.Type = TypeError; 486 exceptions.Range = RangeError; 487 var exceptionMap = idbDomErrorNames.reduce(function (obj, name) { 488 obj[name + "Error"] = exceptions[name]; 489 return obj; 490 }, {}); 491 492 function mapError(domError, message) { 493 if (!domError || domError instanceof DexieError || domError instanceof TypeError || domError instanceof SyntaxError || !domError.name || !exceptionMap[domError.name]) return domError; 494 var rv = new exceptionMap[domError.name](message || domError.message, domError); 495 496 if ("stack" in domError) { 497 // Derive stack from inner exception if it has a stack 498 setProp(rv, "stack", { 499 get: function get() { 500 return this.inner.stack; 501 } 502 }); 503 } 504 505 return rv; 506 } 507 508 var fullNameExceptions = errorList.reduce(function (obj, name) { 509 if (["Syntax", "Type", "Range"].indexOf(name) === -1) obj[name + "Error"] = exceptions[name]; 510 return obj; 511 }, {}); 512 fullNameExceptions.ModifyError = ModifyError; 513 fullNameExceptions.DexieError = DexieError; 514 fullNameExceptions.BulkError = BulkError; 515 516 function nop() {} 517 518 function mirror(val) { 519 return val; 520 } 521 522 function pureFunctionChain(f1, f2) { 523 // Enables chained events that takes ONE argument and returns it to the next function in chain. 524 // This pattern is used in the hook("reading") event. 525 if (f1 == null || f1 === mirror) return f2; 526 return function (val) { 527 return f2(f1(val)); 528 }; 529 } 530 531 function callBoth(on1, on2) { 532 return function () { 533 on1.apply(this, arguments); 534 on2.apply(this, arguments); 535 }; 536 } 537 538 function hookCreatingChain(f1, f2) { 539 // Enables chained events that takes several arguments and may modify first argument by making a modification and then returning the same instance. 540 // This pattern is used in the hook("creating") event. 541 if (f1 === nop) return f2; 542 return function () { 543 var res = f1.apply(this, arguments); 544 if (res !== undefined) arguments[0] = res; 545 var onsuccess = this.onsuccess, 546 // In case event listener has set this.onsuccess 547 onerror = this.onerror; // In case event listener has set this.onerror 548 549 this.onsuccess = null; 550 this.onerror = null; 551 var res2 = f2.apply(this, arguments); 552 if (onsuccess) this.onsuccess = this.onsuccess ? callBoth(onsuccess, this.onsuccess) : onsuccess;
553 if (onerror) this.onerror = this.onerror ? callBoth(onerror, this.onerror) : onerror; 554 return res2 !== undefined ? res2 : res; 555 }; 556 } 557 558 function hookDeletingChain(f1, f2) { 559 if (f1 === nop) return f2; 560 return function () { 561 f1.apply(this, arguments); 562 var onsuccess = this.onsuccess, 563 // In case event listener has set this.onsuccess 564 onerror = this.onerror; // In case event listener has set this.onerror 565 566 this.onsuccess = this.onerror = null; 567 f2.apply(this, arguments); 568 if (onsuccess) this.onsuccess = this.onsuccess ? callBoth(onsuccess, this.onsuccess) : onsuccess; 569 if (onerror) this.onerror = this.onerror ? callBoth(onerror, this.onerror) : onerror; 570 }; 571 } 572 573 function hookUpdatingChain(f1, f2) { 574 if (f1 === nop) return f2; 575 return function (modifications) { 576 var res = f1.apply(this, arguments); 577 extend(modifications, res); // If f1 returns new modifications, extend caller's modifications with the result before calling next in chain. 578 579 var onsuccess = this.onsuccess, 580 // In case event listener has set this.onsuccess 581 onerror = this.onerror; // In case event listener has set this.onerror 582 583 this.onsuccess = null; 584 this.onerror = null; 585 var res2 = f2.apply(this, arguments); 586 if (onsuccess) this.onsuccess = this.onsuccess ? callBoth(onsuccess, this.onsuccess) : onsuccess; 587 if (onerror) this.onerror = this.onerror ? callBoth(onerror, this.onerror) : onerror; 588 return res === undefined ? res2 === undefined ? undefined : res2 : extend(res, res2); 589 }; 590 } 591 592 function reverseStoppableEventChain(f1, f2) { 593 if (f1 === nop) return f2; 594 return function () { 595 if (f2.apply(this, arguments) === false) return false; 596 return f1.apply(this, arguments); 597 }; 598 } 599 600 function promisableChain(f1, f2) { 601 if (f1 === nop) return f2; 602 return function () { 603 var res = f1.apply(this, arguments); 604 605 if (res && typeof res.then === 'function') { 606 var thiz = this, 607 i = arguments.length, 608 args = new Array(i); 609 610 while (i--) { 611 args[i] = arguments[i]; 612 } 613 614 return res.then(function () { 615 return f2.apply(thiz, args); 616 }); 617 } 618 619 return f2.apply(this, arguments); 620 }; 621 } 622 /* 623 * Copyright (c) 2014-2017 David Fahlander 624 * Apache License Version 2.0, January 2004, http://www.apache.org/licenses/LICENSE-2.0 625 */ 626 // 627 // Promise and Zone (PSD) for Dexie library 628 // 629 // I started out writing this Promise class by copying promise-light (https://github.com/taylorhakes/promise-light) by 630 // https://github.com/taylorhakes - an A+ and ECMASCRIPT 6 compliant Promise implementation. 631 // 632 // In previous versions this was fixed by not calling setTimeout when knowing that the resolve() or reject() came from another 633 // tick. In Dexie v1.4.0, I've rewritten the Promise class entirely. Just some fragments of promise-light is left. I use 634 // another strategy now that simplifies everything a lot: to always execute callbacks in a new micro-task, but have an own micro-task 635 // engine that is indexedDB compliant across all browsers. 636 // Promise class has also been optimized a lot with inspiration from bluebird - to avoid closures as much as possible. 637 // Also with inspiration from bluebird, asyncronic stacks in debug mode. 638 // 639 // Specific non-standard features of this Promise class: 640 // * Custom zone support (a.k.a. PSD) with ability to keep zones also when using native promises as well as 641 // native async / await. 642 // * Promise.follow() method built upon the custom zone engine, that allows user to track all promises created from current stack frame 643 // and below + all promises that those promises creates or awaits. 644 // * Detect any unhandled promise in a PSD-scope (PSD.onunhandled). 645 // 646 // David Fahlander, https://github.com/dfahlander 647 // 648 // Just a pointer that only this module knows about. 649 // Used in Promise constructor to emulate a private constructor. 650 651 652 var INTERNAL = {}; // Async stacks (long stacks) must not grow infinitely. 653 654 var LONG_STACKS_CLIP_LIMIT = 100; 655 var MAX_LONG_STACKS = 20; 656 var ZONE_ECHO_LIMIT = 7; 657 658 var nativePromiseInstanceAndProto = function () { 659 try { 660 // Be able to patch native async functions 661 return new Function("let F=async ()=>{},p=F();return [p,Object.getPrototypeOf(p),Promise.resolve(),F.constructor];")(); 662 } catch (e) { 663 var P = _global.Promise; 664 return P ? [P.resolve(), P.prototype, P.resolve()] : []; 665 } 666 }(); 667 668 var resolvedNativePromise = nativePromiseInstanceAndProto[0]; 669 var nativePromiseProto = nativePromiseInstanceAndProto[1]; 670 var resolvedGlobalPromise = nativePromiseInstanceAndProto[2]; 671 var nativePromiseThen = nativePromiseProto && nativePromiseProto.then; 672 var NativePromise = resolvedNativePromise && resolvedNativePromise.constructor; 673 var AsyncFunction = nativePromiseInstanceAndProto[3]; 674 var patchGlobalPromise = !!resolvedGlobalPromise; 675 var stack_being_generated = false;
676 /* The default function used only for the very first promise in a promise chain. 677 As soon as then promise is resolved or rejected, all next tasks will be executed in micro ticks 678 emulated in this module. For indexedDB compatibility, this means that every method needs to 679 execute at least one promise before doing an indexedDB operation. Dexie will always call 680 db.ready().then() for every operation to make sure the indexedDB event is started in an 681 indexedDB-compatible emulated micro task loop. 682 */ 683 684 var schedulePhysicalTick = resolvedGlobalPromise ? function () { 685 resolvedGlobalPromise.then(physicalTick); 686 } : _global.setImmediate ? // setImmediate supported. Those modern platforms also supports Function.bind(). 687 setImmediate.bind(null, physicalTick) : _global.MutationObserver ? // MutationObserver supported 688 function () { 689 var hiddenDiv = document.createElement("div"); 690 new MutationObserver(function () { 691 physicalTick(); 692 hiddenDiv = null; 693 }).observe(hiddenDiv, { 694 attributes: true 695 }); 696 hiddenDiv.setAttribute('i', '1'); 697 } : // No support for setImmediate or MutationObserver. No worry, setTimeout is only called 698 // once time. Every tick that follows will be our emulated micro tick. 699 // Could have uses setTimeout.bind(null, 0, physicalTick) if it wasnt for that FF13 and below has a bug 700 function () { 701 setTimeout(physicalTick, 0); 702 }; // Configurable through Promise.scheduler. 703 // Don't export because it would be unsafe to let unknown 704 // code call it unless they do try..catch within their callback. 705 // This function can be retrieved through getter of Promise.scheduler though, 706 // but users must not do Promise.scheduler = myFuncThatThrowsException 707 708 var asap$1 = function asap$1(callback, args) { 709 microtickQueue.push([callback, args]); 710 711 if (needsNewPhysicalTick) { 712 schedulePhysicalTick(); 713 needsNewPhysicalTick = false; 714 } 715 }; 716 717 var isOutsideMicroTick = true; 718 var needsNewPhysicalTick = true; 719 var unhandledErrors = []; 720 var rejectingErrors = []; 721 var currentFulfiller = null; 722 var rejectionMapper = mirror; // Remove in next major when removing error mapping of DOMErrors and DOMExceptions 723 724 var globalPSD = { 725 id: 'global', 726 global: true, 727 ref: 0, 728 unhandleds: [], 729 onunhandled: globalError, 730 pgp: false, 731 env: {}, 732 finalize: function finalize() { 733 this.unhandleds.forEach(function (uh) { 734 try { 735 globalError(uh[0], uh[1]); 736 } catch (e) {} 737 }); 738 } 739 }; 740 var PSD = globalPSD; 741 var microtickQueue = []; // Callbacks to call in this or next physical tick. 742 743 var numScheduledCalls = 0; // Number of listener-calls left to do in this physical tick. 744 745 var tickFinalizers = []; // Finalizers to call when there are no more async calls scheduled within current physical tick. 746 747 function Promise(fn) { 748 if (babelHelpers.typeof(this) !== 'object') throw new TypeError('Promises must be constructed via new'); 749 this._listeners = []; 750 this.onuncatched = nop; // Deprecate in next major. Not needed. Better to use global error handler. 751 // A library may set `promise._lib = true;` after promise is created to make resolve() or reject() 752 // execute the microtask engine implicitely within the call to resolve() or reject(). 753 // To remain A+ compliant, a library must only set `_lib=true` if it can guarantee that the stack 754 // only contains library code when calling resolve() or reject(). 755 // RULE OF THUMB: ONLY set _lib = true for promises explicitely resolving/rejecting directly from 756 // global scope (event handler, timer etc)! 757 758 this._lib = false; // Current async scope 759 760 var psd = this._PSD = PSD; 761 762 if (debug) { 763 this._stackHolder = getErrorWithStack(); 764 this._prev = null; 765 this._numPrev = 0; // Number of previous promises (for long stacks) 766 } 767 768 if (typeof fn !== 'function') { 769 if (fn !== INTERNAL) throw new TypeError('Not a function'); // Private constructor (INTERNAL, state, value). 770 // Used internally by Promise.resolve() and Promise.reject(). 771 772 this._state = arguments[1]; 773 this._value = arguments[2]; 774 if (this._state === false) handleRejection(this, this._value); // Map error, set stack and addPossiblyUnhandledError(). 775 776 return; 777 } 778 779 this._state = null; // null (=pending), false (=rejected) or true (=resolved) 780 781 this._value = null; // error or result 782 783 ++psd.ref; // Refcounting current scope 784 785 executePromiseTask(this, fn); 786 } // Prepare a property descriptor to put onto Promise.prototype.then 787 788 789 var thenProp = { 790 get: function get() { 791 var psd = PSD, 792 microTaskId = totalEchoes; 793 794 function then(onFulfilled, onRejected) { 795 var _this = this; 796 797 var possibleAwait = !psd.global && (psd !== PSD || microTaskId !== totalEchoes); 798 if (possibleAwait) decrementExpectedAwaits(); 799 var rv = new Promise(function (resolve, reject) { 800 propagateToListener(_this, new Listener(nativeAwaitCompatibleWrap(onFulfilled, psd, possibleAwait), nativeAwaitCompatibleWrap(onRejected, psd, possibleAwait), resolve, reject, psd)); 801 }); 802 debug && linkToPreviousPromise(rv, this); 803 return rv; 804 } 805 806 then.prototype = INTERNAL; // For idempotense, see setter below. 807 808 return then; 809 }, 810 // Be idempotent and allow another framework (such as zone.js or another instance of a Dexie.Promise module) to replace Promise.prototype.then 811 // and when that framework wants to restore the original property, we must identify that and restore the original property descriptor. 812 set: function set(value) { 813 setProp(this, 'then', value && value.prototype === INTERNAL ? thenProp : // Restore to original property descriptor. 814 { 815 get: function get() { 816 return value; // Getter returning provided value (behaves like value is just changed) 817 }, 818 set: thenProp.set // Keep a setter that is prepared to restore original. 819 820 }); 821 } 822 }; 823 props(Promise.prototype, { 824 then: thenProp, 825 _then: function _then(onFulfilled, onRejected) { 826 // A little tinier version of then() that don't have to create a resulting promise. 827 propagateToListener(this, new Listener(null, null, onFulfilled, onRejected, PSD)); 828 },
829 catch: function _catch(onRejected) { 830 if (arguments.length === 1) return this.then(null, onRejected); // First argument is the Error type to catch 831 832 var type = arguments[0], 833 handler = arguments[1]; 834 return typeof type === 'function' ? this.then(null, function (err) { 835 // Catching errors by its constructor type (similar to java / c++ / c#) 836 // Sample: promise.catch(TypeError, function (e) { ... }); 837 return err instanceof type ? handler(err) : PromiseReject(err); 838 }) : this.then(null, function (err) { 839 // Catching errors by the error.name property. Makes sense for indexedDB where error type 840 // is always DOMError but where e.name tells the actual error type. 841 // Sample: promise.catch('ConstraintError', function (e) { ... }); 842 return err && err.name === type ? handler(err) : PromiseReject(err); 843 }); 844 }, 845 finally: function _finally(onFinally) { 846 return this.then(function (value) { 847 onFinally(); 848 return value; 849 }, function (err) { 850 onFinally(); 851 return PromiseReject(err); 852 }); 853 }, 854 stack: { 855 get: function get() { 856 if (this._stack) return this._stack; 857 858 try { 859 stack_being_generated = true; 860 var stacks = getStack(this, [], MAX_LONG_STACKS); 861 var stack = stacks.join("\nFrom previous: "); 862 if (this._state !== null) this._stack = stack; // Stack may be updated on reject. 863 864 return stack; 865 } finally { 866 stack_being_generated = false; 867 } 868 } 869 }, 870 timeout: function timeout(ms, msg) { 871 var _this = this; 872 873 return ms < Infinity ? new Promise(function (resolve, reject) { 874 var handle = setTimeout(function () { 875 return reject(new exceptions.Timeout(msg)); 876 }, ms); 877 878 _this.then(resolve, reject).finally(clearTimeout.bind(null, handle)); 879 }) : this; 880 } 881 }); 882 if (typeof Symbol !== 'undefined' && Symbol.toStringTag) setProp(Promise.prototype, Symbol.toStringTag, 'Promise'); // Now that Promise.prototype is defined, we have all it takes to set globalPSD.env. 883 // Environment globals snapshotted on leaving global zone 884 885 globalPSD.env = snapShot(); 886 887 function Listener(onFulfilled, onRejected, resolve, reject, zone) { 888 this.onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : null; 889 this.onRejected = typeof onRejected === 'function' ? onRejected : null; 890 this.resolve = resolve; 891 this.reject = reject; 892 this.psd = zone; 893 } // Promise Static Properties 894 895 896 props(Promise, { 897 all: function all() { 898 var values = getArrayOf.apply(null, arguments) // Supports iterables, implicit arguments and array-like. 899 .map(onPossibleParallellAsync); // Handle parallell async/awaits 900 901 return new Promise(function (resolve, reject) { 902 if (values.length === 0) resolve([]); 903 var remaining = values.length; 904 values.forEach(function (a, i) { 905 return Promise.resolve(a).then(function (x) { 906 values[i] = x; 907 if (! --remaining) resolve(values); 908 }, reject); 909 }); 910 }); 911 }, 912 resolve: function resolve(value) { 913 if (value instanceof Promise) return value; 914 if (value && typeof value.then === 'function') return new Promise(function (resolve, reject) { 915 value.then(resolve, reject); 916 }); 917 var rv = new Promise(INTERNAL, true, value); 918 linkToPreviousPromise(rv, currentFulfiller); 919 return rv; 920 }, 921 reject: PromiseReject, 922 race: function race() { 923 var values = getArrayOf.apply(null, arguments).map(onPossibleParallellAsync); 924 return new Promise(function (resolve, reject) { 925 values.map(function (value) { 926 return Promise.resolve(value).then(resolve, reject); 927 }); 928 }); 929 }, 930 PSD: { 931 get: function get() { 932 return PSD; 933 }, 934 set: function set(value) { 935 return PSD = value; 936 } 937 }, 938 //totalEchoes: {get: ()=>totalEchoes}, 939 //task: {get: ()=>task}, 940 newPSD: newScope, 941 usePSD: usePSD, 942 scheduler: { 943 get: function get() { 944 return asap$1; 945 }, 946 set: function set(value) { 947 asap$1 = value; 948 } 949 }, 950 rejectionMapper: { 951 get: function get() { 952 return rejectionMapper; 953 }, 954 set: function set(value) { 955 rejectionMapper = value; 956 } // Map reject failures 957 958 }, 959 follow: function follow(fn, zoneProps) { 960 return new Promise(function (resolve, reject) { 961 return newScope(function (resolve, reject) { 962 var psd = PSD; 963 psd.unhandleds = []; // For unhandled standard- or 3rd party Promises. Checked at psd.finalize() 964 965 psd.onunhandled = reject; // Triggered directly on unhandled promises of this library. 966 967 psd.finalize = callBoth(function () { 968 var _this = this; // Unhandled standard or 3rd part promises are put in PSD.unhandleds and 969 // examined upon scope completion while unhandled rejections in this Promise 970 // will trigger directly through psd.onunhandled 971 972 973 run_at_end_of_this_or_next_physical_tick(function () { 974 _this.unhandleds.length === 0 ? resolve() : reject(_this.unhandleds[0]); 975 }); 976 }, psd.finalize); 977 fn(); 978 }, zoneProps, resolve, reject); 979 }); 980 } 981 }); 982 /** 983 * Take a potentially misbehaving resolver function and make sure 984 * onFulfilled and onRejected are only called once. 985 * 986 * Makes no guarantees about asynchrony. 987 */ 988 989 function executePromiseTask(promise, fn) { 990 // Promise Resolution Procedure: 991 // https://github.com/promises-aplus/promises-spec#the-promise-resolution-procedure 992 try { 993 fn(function (value) { 994 if (promise._state !== null) return; // Already settled 995 996 if (value === promise) throw new TypeError('A promise cannot be resolved with itself.'); 997 var shouldExecuteTick = promise._lib && beginMicroTickScope(); 998 999 if (value && typeof value.then === 'function') { 1000 executePromiseTask(promise, function (resolve, reject) { 1001 value instanceof Promise ? value._then(resolve, reject) : value.then(resolve, reject); 1002 }); 1003 } else { 1004 promise._state = true; 1005 promise._value = value; 1006 propagateAllListeners(promise); 1007 } 1008 1009 if (shouldExecuteTick) endMicroTickScope(); 1010 }, handleRejection.bind(null, promise)); // If Function.bind is not supported. Exception is handled in catch below 1011 } catch (ex) { 1012 handleRejection(promise, ex); 1013 } 1014 } 1015 1016 function handleRejection(promise, reason) { 1017 rejectingErrors.push(reason); 1018 if (!promise || promise._state !== null) // tag 28122018 1019 return; 1020 var shouldExecuteTick = promise._lib && beginMicroTickScope(); 1021 reason = rejectionMapper(reason); 1022 promise._state = false;
1023 promise._value = reason; 1024 debug && reason !== null && babelHelpers.typeof(reason) === 'object' && !reason._promise && tryCatch(function () { 1025 var origProp = getPropertyDescriptor(reason, "stack"); 1026 reason._promise = promise; 1027 setProp(reason, "stack", { 1028 get: function get() { 1029 return stack_being_generated ? origProp && (origProp.get ? origProp.get.apply(reason) : origProp.value) : promise.stack; 1030 } 1031 }); 1032 }); // Add the failure to a list of possibly uncaught errors 1033 1034 addPossiblyUnhandledError(promise); 1035 propagateAllListeners(promise); 1036 if (shouldExecuteTick) endMicroTickScope(); 1037 } 1038 1039 function propagateAllListeners(promise) { 1040 //debug && linkToPreviousPromise(promise); 1041 var listeners = promise._listeners; 1042 promise._listeners = []; 1043 1044 for (var i = 0, len = listeners.length; i < len; ++i) { 1045 propagateToListener(promise, listeners[i]); 1046 } 1047 1048 var psd = promise._PSD; 1049 --psd.ref || psd.finalize(); // if psd.ref reaches zero, call psd.finalize(); 1050 1051 if (numScheduledCalls === 0) { 1052 // If numScheduledCalls is 0, it means that our stack is not in a callback of a scheduled call, 1053 // and that no deferreds where listening to this rejection or success. 1054 // Since there is a risk that our stack can contain application code that may 1055 // do stuff after this code is finished that may generate new calls, we cannot 1056 // call finalizers here. 1057 ++numScheduledCalls; 1058 asap$1(function () { 1059 if (--numScheduledCalls === 0) finalizePhysicalTick(); // Will detect unhandled errors 1060 }, []); 1061 } 1062 } 1063 1064 function propagateToListener(promise, listener) { 1065 if (promise._state === null) { 1066 promise._listeners.push(listener); 1067 1068 return; 1069 } 1070 1071 var cb = promise._state ? listener.onFulfilled : listener.onRejected; 1072 1073 if (cb === null) { 1074 // This Listener doesnt have a listener for the event being triggered (onFulfilled or onReject) so lets forward the event to any eventual listeners on the Promise instance returned by then() or catch() 1075 return (promise._state ? listener.resolve : listener.reject)(promise._value); 1076 } 1077 1078 ++listener.psd.ref; 1079 ++numScheduledCalls; 1080 asap$1(callListener, [cb, promise, listener]); 1081 } 1082 1083 function callListener(cb, promise, listener) { 1084 try { 1085 // Set static variable currentFulfiller to the promise that is being fullfilled, 1086 // so that we connect the chain of promises (for long stacks support) 1087 currentFulfiller = promise; // Call callback and resolve our listener with it's return value. 1088 1089 var ret, 1090 value = promise._value; 1091 1092 if (promise._state) { 1093 // cb is onResolved 1094 ret = cb(value); 1095 } else { 1096 // cb is onRejected 1097 if (rejectingErrors.length) rejectingErrors = []; 1098 ret = cb(value); 1099 if (rejectingErrors.indexOf(value) === -1) markErrorAsHandled(promise); // Callback didnt do Promise.reject(err) nor reject(err) onto another promise. 1100 } 1101 1102 listener.resolve(ret); 1103 } catch (e) { 1104 // Exception thrown in callback. Reject our listener. 1105 listener.reject(e); 1106 } finally { 1107 // Restore env and currentFulfiller. 1108 currentFulfiller = null; 1109 if (--numScheduledCalls === 0) finalizePhysicalTick(); 1110 --listener.psd.ref || listener.psd.finalize(); 1111 } 1112 } 1113 1114 function getStack(promise, stacks, limit) { 1115 if (stacks.length === limit) return stacks; 1116 var stack = ""; 1117 1118 if (promise._state === false) { 1119 var failure = promise._value, 1120 errorName, 1121 message; 1122 1123 if (failure != null) { 1124 errorName = failure.name || "Error"; 1125 message = failure.message || failure; 1126 stack = prettyStack(failure, 0); 1127 } else { 1128 errorName = failure; // If error is undefined or null, show that. 1129 1130 message = ""; 1131 } 1132 1133 stacks.push(errorName + (message ? ": " + message : "") + stack); 1134 } 1135 1136 if (debug) { 1137 stack = prettyStack(promise._stackHolder, 2); 1138 if (stack && stacks.indexOf(stack) === -1) stacks.push(stack); 1139 if (promise._prev) getStack(promise._prev, stacks, limit); 1140 } 1141 1142 return stacks; 1143 } 1144 1145 function linkToPreviousPromise(promise, prev) {
1146 // Support long stacks by linking to previous completed promise. 1147 var numPrev = prev ? prev._numPrev + 1 : 0; 1148 1149 if (numPrev < LONG_STACKS_CLIP_LIMIT) { 1150 promise._prev = prev; 1151 promise._numPrev = numPrev; 1152 } 1153 } 1154 /* The callback to schedule with setImmediate() or setTimeout(). 1155 It runs a virtual microtick and executes any callback registered in microtickQueue. 1156 */ 1157 1158 1159 function physicalTick() { 1160 beginMicroTickScope() && endMicroTickScope(); 1161 } 1162 1163 function beginMicroTickScope() { 1164 var wasRootExec = isOutsideMicroTick; 1165 isOutsideMicroTick = false; 1166 needsNewPhysicalTick = false; 1167 return wasRootExec; 1168 } 1169 /* Executes micro-ticks without doing try..catch. 1170 This can be possible because we only use this internally and 1171 the registered functions are exception-safe (they do try..catch 1172 internally before calling any external method). If registering 1173 functions in the microtickQueue that are not exception-safe, this 1174 would destroy the framework and make it instable. So we don't export 1175 our asap method. 1176 */ 1177 1178 1179 function endMicroTickScope() { 1180 var callbacks, i, l; 1181 1182 do { 1183 while (microtickQueue.length > 0) { 1184 callbacks = microtickQueue; 1185 microtickQueue = []; 1186 l = callbacks.length; 1187 1188 for (i = 0; i < l; ++i) { 1189 var item = callbacks[i]; 1190 item[0].apply(null, item[1]); 1191 } 1192 } 1193 } while (microtickQueue.length > 0); 1194 1195 isOutsideMicroTick = true; 1196 needsNewPhysicalTick = true; 1197 } 1198 1199 function finalizePhysicalTick() { 1200 var unhandledErrs = unhandledErrors; 1201 unhandledErrors = []; 1202 unhandledErrs.forEach(function (p) { 1203 p._PSD.onunhandled.call(null, p._value, p); 1204 }); 1205 var finalizers = tickFinalizers.slice(0); // Clone first because finalizer may remove itself from list. 1206 1207 var i = finalizers.length; 1208 1209 while (i) { 1210 finalizers[--i](); 1211 } 1212 } 1213 1214 function run_at_end_of_this_or_next_physical_tick(fn) { 1215 function finalizer() { 1216 fn(); 1217 tickFinalizers.splice(tickFinalizers.indexOf(finalizer), 1); 1218 } 1219 1220 tickFinalizers.push(finalizer); 1221 ++numScheduledCalls; 1222 asap$1(function () { 1223 if (--numScheduledCalls === 0) finalizePhysicalTick(); 1224 }, []); 1225 } 1226 1227 function addPossiblyUnhandledError(promise) { 1228 // Only add to unhandledErrors if not already there. The first one to add to this list 1229 // will be upon the first rejection so that the root cause (first promise in the 1230 // rejection chain) is the one listed. 1231 if (!unhandledErrors.some(function (p) { 1232 return p._value === promise._value; 1233 })) unhandledErrors.push(promise); 1234 } 1235 1236 function markErrorAsHandled(promise) { 1237 // Called when a reject handled is actually being called. 1238 // Search in unhandledErrors for any promise whos _value is this promise_value (list 1239 // contains only rejected promises, and only one item per error) 1240 var i = unhandledErrors.length; 1241 1242 while (i) { 1243 if (unhandledErrors[--i]._value === promise._value) { 1244 // Found a promise that failed with this same error object pointer, 1245 // Remove that since there is a listener that actually takes care of it. 1246 unhandledErrors.splice(i, 1); 1247 return; 1248 } 1249 } 1250 } 1251 1252 function PromiseReject(reason) { 1253 return new Promise(INTERNAL, false, reason); 1254 } 1255 1256 function wrap(fn, errorCatcher) { 1257 var psd = PSD; 1258 return function () { 1259 var wasRootExec = beginMicroTickScope(), 1260 outerScope = PSD; 1261 1262 try { 1263 switchToZone(psd, true); 1264 return fn.apply(this, arguments); 1265 } catch (e) { 1266 errorCatcher && errorCatcher(e); 1267 } finally { 1268 switchToZone(outerScope, false); 1269 if (wasRootExec) endMicroTickScope(); 1270 } 1271 }; 1272 } // 1273 // variables used for native await support 1274 // 1275 1276 1277 var task = { 1278 awaits: 0, 1279 echoes: 0, 1280 id: 0 1281 }; // The ongoing macro-task when using zone-echoing. 1282 1283 var taskCounter = 0; // ID counter for macro tasks. 1284 1285 var zoneStack = []; // Stack of left zones to restore asynchronically. 1286 1287 var zoneEchoes = 0; // zoneEchoes is a must in order to persist zones between native await expressions. 1288 1289 var totalEchoes = 0; // ID counter for micro-tasks. Used to detect possible native await in our Promise.prototype.then. 1290 1291 var zone_id_counter = 0; 1292 1293 function newScope(fn, props$$1, a1, a2) { 1294 var parent = PSD, 1295 psd = Object.create(parent); 1296 psd.parent = parent; 1297 psd.ref = 0; 1298 psd.global = false;
vendor: 1,438 bytes, lines 1299-1340
1299 psd.id = ++zone_id_counter; // Prepare for promise patching (done in usePSD): 1300 1301 var globalEnv = globalPSD.env; 1302 psd.env = patchGlobalPromise ? { 1303 Promise: Promise, 1304 PromiseProp: { 1305 value: Promise, 1306 configurable: true, 1307 writable: true 1308 }, 1309 all: Promise.all, 1310 race: Promise.race, 1311 resolve: Promise.resolve, 1312 reject: Promise.reject, 1313 nthen: getPatchedPromiseThen(globalEnv.nthen, psd), 1314 gthen: getPatchedPromiseThen(globalEnv.gthen, psd) // global then 1315 1316 } : {}; 1317 if (props$$1) extend(psd, props$$1); // unhandleds and onunhandled should not be specifically set here. 1318 // Leave them on parent prototype. 1319 // unhandleds.push(err) will push to parent's prototype 1320 // onunhandled() will call parents onunhandled (with this scope's this-pointer though!) 1321 1322 ++parent.ref; 1323 1324 psd.finalize = function () { 1325 --this.parent.ref || this.parent.finalize(); 1326 }; 1327 1328 var rv = usePSD(psd, fn, a1, a2); 1329 if (psd.ref === 0) psd.finalize(); 1330 return rv; 1331 } // Function to call if scopeFunc returns NativePromise 1332 // Also for each NativePromise in the arguments to Promise.all() 1333 1334 1335 function incrementExpectedAwaits() { 1336 if (!task.id) task.id = ++taskCounter; 1337 ++task.awaits; 1338 task.echoes += ZONE_ECHO_LIMIT; 1339 return task.id; 1340 } // Function to call when 'then' calls back on a native promise where onA
1340waitExpected() had been called. 1341 // Also call this when a native await calls then method on a promise. In that case, don't supply 1342 // sourceTaskId because we already know it refers to current task. 1343 1344 1345 function decrementExpectedAwaits(sourceTaskId) { 1346 if (!task.awaits || sourceTaskId && sourceTaskId !== task.id) return; 1347 if (--task.awaits === 0) task.id = 0; 1348 task.echoes = task.awaits * ZONE_ECHO_LIMIT; // Will reset echoes to 0 if awaits is 0. 1349 } // Call from Promise.all() and Promise.race() 1350 1351 1352 function onPossibleParallellAsync(possiblePromise) { 1353 if (task.echoes && possiblePromise && possiblePromise.constructor === NativePromise) { 1354 incrementExpectedAwaits(); 1355 return possiblePromise.then(function (x) { 1356 decrementExpectedAwaits(); 1357 return x; 1358 }, function (e) { 1359 decrementExpectedAwaits(); 1360 return rejection(e); 1361 }); 1362 } 1363 1364 return possiblePromise; 1365 } 1366 1367 function zoneEnterEcho(targetZone) { 1368 ++totalEchoes; 1369 1370 if (!task.echoes || --task.echoes === 0) { 1371 task.echoes = task.id = 0; // Cancel zone echoing. 1372 } 1373 1374 zoneStack.push(PSD); 1375 switchToZone(targetZone, true); 1376 } 1377 1378 function zoneLeaveEcho() { 1379 var zone = zoneStack[zoneStack.length - 1]; 1380 zoneStack.pop(); 1381 switchToZone(zone, false); 1382 } 1383 1384 function switchToZone(targetZone, bEnteringZone) { 1385 var currentZone = PSD; 1386 1387 if (bEnteringZone ? task.echoes && (!zoneEchoes++ || targetZone !== PSD) : zoneEchoes && (! --zoneEchoes || targetZone !== PSD)) { 1388 // Enter or leave zone asynchronically as well, so that tasks initiated during current tick 1389 // will be surrounded by the zone when they are invoked. 1390 enqueueNativeMicroTask(bEnteringZone ? zoneEnterEcho.bind(null, targetZone) : zoneLeaveEcho); 1391 } 1392 1393 if (targetZone === PSD) return; 1394 PSD = targetZone; // The actual zone switch occurs at this line. 1395 // Snapshot on every leave from global zone. 1396 1397 if (currentZone === globalPSD) globalPSD.env = snapShot(); 1398 1399 if (patchGlobalPromise) { 1400 // Let's patch the global and native Promises (may be same or may be different) 1401 var GlobalPromise = globalPSD.env.Promise; // Swich environments (may be PSD-zone or the global zone. Both apply.) 1402 1403 var targetEnv = targetZone.env; // Change Promise.prototype.then for native and global Promise (they MAY differ on polyfilled environments, but both can be accessed) 1404 // Must be done on each zone change because the patched method contains targetZone in its closure. 1405 1406 nativePromiseProto.then = targetEnv.nthen; 1407 GlobalPromise.prototype.then = targetEnv.gthen; 1408 1409 if (currentZone.global || targetZone.global) { 1410 // Leaving or entering global zone. It's time to patch / restore global Promise. 1411 // Set this Promise to window.Promise so that transiled async functions will work on Firefox, Safari and IE, as well as with Zonejs and angular. 1412 Object.defineProperty(_global, 'Promise', targetEnv.PromiseProp); // Support Promise.all() etc to work indexedDB-safe also when people are including es6-promise as a module (they might 1413 // not be accessing global.Promise but a local reference to it) 1414 1415 GlobalPromise.all = targetEnv.all; 1416 GlobalPromise.race = targetEnv.race; 1417 GlobalPromise.resolve = targetEnv.resolve; 1418 GlobalPromise.reject = targetEnv.reject; 1419 } 1420 } 1421 } 1422 1423 function snapShot() { 1424 var GlobalPromise = _global.Promise || Promise; // tag 02032020 1425 1426 return patchGlobalPromise ? { 1427 Promise: GlobalPromise, 1428 PromiseProp: Object.getOwnPropertyDescriptor(_global, "Promise"), 1429 all: GlobalPromise.all, 1430 race: GlobalPromise.race, 1431 resolve: GlobalPromise.resolve, 1432 reject: GlobalPromise.reject, 1433 nthen: nativePromiseProto.then, 1434 gthen: GlobalPromise.prototype.then 1435 } : {}; 1436 } 1437 1438 function usePSD(psd, fn, a1, a2, a3) { 1439 var outerScope = PSD; 1440 1441 try { 1442 switchToZone(psd, true); 1443 return fn(a1, a2, a3); 1444 } finally { 1445 switchToZone(outerScope, false); 1446 } 1447 } 1448 1449 function enqueueNativeMicroTask(job) { 1450 // 1451 // Precondition: nativePromiseThen !== undefined 1452 // 1453 nativePromiseThen.call(resolvedNativePromise, job); 1454 } 1455 1456 function nativeAwaitCompatibleWrap(fn, zone, possibleAwait) { 1457 return typeof fn !== 'function' ? fn : function () { 1458 var outerZone = PSD; 1459 if (possibleAwait) incrementExpectedAwaits(); 1460 switchToZone(zone, true); 1461 1462 try { 1463 return fn.apply(this, arguments); 1464 } finally { 1465 switchToZone(outerZone, false); 1466 } 1467 }; 1468 } 1469 1470 function getPatchedPromiseThen(origThen, zone) { 1471 return function (onResolved, onRejected) { 1472 return origThen.call(this, nativeAwaitCompatibleWrap(onResolved, zone, false), nativeAwaitCompatibleWrap(onRejected, zone, false)); 1473 }; 1474 } 1475 1476 var UNHANDLEDREJECTION = "unhandledrejection"; 1477 1478 function globalError(err, promise) { 1479 var rv; 1480 1481 try { 1482 rv = promise.onuncatched(err); 1483 } catch (e) {} 1484 1485 if (rv !== false) try { 1486 var event, 1487 eventData = { 1488 promise: promise, 1489 reason: err 1490 }; 1491 1492 if (_global.document && document.createEvent) { 1493 event = document.createEvent('Event'); 1494 event.initEvent(UNHANDLEDREJECTION, true, true); 1495 extend(event, eventData); 1496 } else if (_global.CustomEvent) { 1497 event = new CustomEvent(UNHANDLEDREJECTION, { 1498 detail: eventData 1499 }); 1500 extend(event, eventData); 1501 } 1502 1503 if (event && _global.dispatchEvent) { 1504 dispatchEvent(event); 1505 if (!_global.PromiseRejectionEvent && _global.onunhandledrejection) // No native support for PromiseRejectionEvent but user has set window.onunhandledrejection. Manually call it. 1506 try { 1507 _global.onunhandledrejection(event); 1508 } catch (_) {} 1509 } 1510 1511 if (!event.defaultPrevented) { 1512 console.warn("Unhandled rejection: " + (err.stack || err)); 1513 } 1514 } catch (e) {} 1515 } 1516 1517 var rejection = Promise.reject; 1518 1519 function Events(ctx) { 1520 var evs = {}; 1521 1522 var rv = function rv(eventName, subscriber) { 1523 if (subscriber) { 1524 // Subscribe. If additional arguments than just the subscriber was provided, forward them as well. 1525 var i = arguments.length, 1526 args = new Array(i - 1); 1527 1528 while (--i) { 1529 args[i - 1] = arguments[i]; 1530 } 1531
1532 evs[eventName].subscribe.apply(null, args); 1533 return ctx; 1534 } else if (typeof eventName === 'string') { 1535 // Return interface allowing to fire or unsubscribe from event 1536 return evs[eventName]; 1537 } 1538 }; 1539 1540 rv.addEventType = add; 1541 1542 for (var i = 1, l = arguments.length; i < l; ++i) { 1543 add(arguments[i]); 1544 } 1545 1546 return rv; 1547 1548 function add(eventName, chainFunction, defaultFunction) { 1549 if (babelHelpers.typeof(eventName) === 'object') return addConfiguredEvents(eventName); 1550 if (!chainFunction) chainFunction = reverseStoppableEventChain; 1551 if (!defaultFunction) defaultFunction = nop; 1552 var context = { 1553 subscribers: [], 1554 fire: defaultFunction, 1555 subscribe: function subscribe(cb) { 1556 if (context.subscribers.indexOf(cb) === -1) { 1557 context.subscribers.push(cb); 1558 context.fire = chainFunction(context.fire, cb); 1559 } 1560 }, 1561 unsubscribe: function unsubscribe(cb) { 1562 context.subscribers = context.subscribers.filter(function (fn) { 1563 return fn !== cb; 1564 }); 1565 context.fire = context.subscribers.reduce(chainFunction, defaultFunction); 1566 } 1567 }; 1568 evs[eventName] = rv[eventName] = context; 1569 return context; 1570 } 1571 1572 function addConfiguredEvents(cfg) { 1573 // events(this, {reading: [functionChain, nop]}); 1574 keys(cfg).forEach(function (eventName) { 1575 var args = cfg[eventName]; 1576 1577 if (isArray(args)) { 1578 add(eventName, cfg[eventName][0], cfg[eventName][1]); 1579 } else if (args === 'asap') { 1580 // Rather than approaching event subscription using a functional approach, we here do it in a for-loop where subscriber is executed in its own stack 1581 // enabling that any exception that occur wont disturb the initiator and also not nescessary be catched and forgotten. 1582 var context = add(eventName, mirror, function fire() { 1583 // Optimazation-safe cloning of arguments into args. 1584 var i = arguments.length, 1585 args = new Array(i); 1586 1587 while (i--) { 1588 args[i] = arguments[i]; 1589 } // All each subscriber: 1590 1591 1592 context.subscribers.forEach(function (fn) { 1593 asap(function fireEvent() { 1594 fn.apply(null, args); 1595 }); 1596 }); 1597 }); 1598 } else throw new exceptions.InvalidArgument("Invalid event config"); 1599 }); 1600 } 1601 } 1602 /* 1603 * Dexie.js - a minimalistic wrapper for IndexedDB 1604 * =============================================== 1605 * 1606 * Copyright (c) 2014-2017 David Fahlander 1607 * 1608 * Version 2.0.4, Fri May 25 2018 1609 * 1610 * http://dexie.org 1611 * 1612 * Apache License Version 2.0, January 2004, http://www.apache.org/licenses/LICENSE-2.0 1613 * 1614 */ 1615 1616 1617 var DEXIE_VERSION = '2.0.4'; 1618 var maxString = String.fromCharCode(65535); 1619 1620 var maxKey = function () { 1621 try { 1622 IDBKeyRange.only([[]]); 1623 return [[]]; 1624 } catch (e) { 1625 return maxString; 1626 } 1627 }(); 1628 1629 var minKey = -Infinity; 1630 var INVALID_KEY_ARGUMENT = "Invalid key provided. Keys must be of type string, number, Date or Array<string | number | Date>."; 1631 var STRING_EXPECTED = "String expected."; 1632 var connections = []; 1633 var isIEOrEdge = typeof navigator !== 'undefined' && /(MSIE|Trident|Edge)/.test(navigator.userAgent); 1634 var hasIEDeleteObjectStoreBug = isIEOrEdge; 1635 var hangsOnDeleteLargeKeyRange = isIEOrEdge; 1636 1637 var dexieStackFrameFilter = function dexieStackFrameFilter(frame) { 1638 return !/(dexie\.js|dexie\.min\.js)/.test(frame); 1639 }; 1640 1641 var dbNamesDB; // Global database for backing Dexie.getDatabaseNames() on browser without indexedDB.webkitGetDatabaseNames() 1642 // Init debug 1643 1644 setDebug(debug, dexieStackFrameFilter); 1645 1646 function Dexie(dbName, options) { 1647 /// <param name="options" type="Object" optional="true">Specify only if you wich to control which addons that should run on this instance</param> 1648 var deps = Dexie.dependencies; 1649 var opts = extend({ 1650 // Default Options 1651 addons: Dexie.addons, 1652 autoOpen: true, 1653 indexedDB: deps.indexedDB, 1654 IDBKeyRange: deps.IDBKeyRange // Backend IDBKeyRange api. Default to browser env. 1655 1656 }, options); 1657 var addons = opts.addons, 1658 autoOpen = opts.autoOpen, 1659 indexedDB = opts.indexedDB, 1660 IDBKeyRange = opts.IDBKeyRange; 1661 var globalSchema = this._dbSchema = {}; 1662 var versions = []; 1663 var dbStoreNames = []; 1664 var allTables = {}; ///<var type="IDBDatabase" /> 1665 1666 var idbdb = null; // Instance of IDBDatabase 1667 1668 var dbOpenError = null; 1669 var isBeingOpened = false;
1670 var onReadyBeingFired = null; 1671 var openComplete = false; 1672 var READONLY = "readonly", 1673 READWRITE = "readwrite"; 1674 var db = this; 1675 var dbReadyResolve, 1676 dbReadyPromise = new Promise(function (resolve) { 1677 dbReadyResolve = resolve; 1678 }), 1679 cancelOpen, 1680 openCanceller = new Promise(function (_, reject) { 1681 cancelOpen = reject; 1682 }); 1683 var autoSchema = true; 1684 var hasNativeGetDatabaseNames = !!getNativeGetDatabaseNamesFn(indexedDB), 1685 hasGetAll; 1686 1687 function init() { 1688 // Default subscribers to "versionchange" and "blocked". 1689 // Can be overridden by custom handlers. If custom handlers return false, these default 1690 // behaviours will be prevented. 1691 db.on("versionchange", function (ev) { 1692 // Default behavior for versionchange event is to close database connection. 1693 // Caller can override this behavior by doing db.on("versionchange", function(){ return false; }); 1694 // Let's not block the other window from making it's delete() or open() call. 1695 // NOTE! This event is never fired in IE,Edge or Safari. 1696 if (ev.newVersion > 0) console.warn("Another connection wants to upgrade database '" + db.name + "'. Closing db now to resume the upgrade.");else console.warn("Another connection wants to delete database '" + db.name + "'. Closing db now to resume the delete request."); 1697 db.close(); // In many web applications, it would be recommended to force window.reload() 1698 // when this event occurs. To do that, subscribe to the versionchange event 1699 // and call window.location.reload(true) if ev.newVersion > 0 (not a deletion) 1700 // The reason for this is that your current web app obviously has old schema code that needs 1701 // to be updated. Another window got a newer version of the app and needs to upgrade DB but 1702 // your window is blocking it unless we close it here. 1703 }); 1704 db.on("blocked", function (ev) { 1705 if (!ev.newVersion || ev.newVersion < ev.oldVersion) console.warn("Dexie.delete('" + db.name + "') was blocked");else console.warn("Upgrade '" + db.name + "' blocked by other connection holding version " + ev.oldVersion / 10); 1706 }); 1707 } // 1708 // 1709 // 1710 // ------------------------- Versioning Framework--------------------------- 1711 // 1712 // 1713 // 1714 1715 1716 this.version = function (versionNumber) { 1717 /// <param name="versionNumber" type="Number"></param> 1718 /// <returns type="Version"></returns> 1719 if (idbdb || isBeingOpened) throw new exceptions.Schema("Cannot add version when database is open"); 1720 this.verno = Math.max(this.verno, versionNumber); 1721 var versionInstance = versions.filter(function (v) { 1722 return v._cfg.version === versionNumber; 1723 })[0]; 1724 if (versionInstance) return versionInstance; 1725 versionInstance = new Version(versionNumber); 1726 versions.push(versionInstance); 1727 versions.sort(lowerVersionFirst); // Disable autoschema mode, as at least one version is specified. 1728 1729 autoSchema = false; 1730 return versionInstance; 1731 }; 1732 1733 function Version(versionNumber) { 1734 this._cfg = { 1735 version: versionNumber, 1736 storesSource: null, 1737 dbschema: {}, 1738 tables: {}, 1739 contentUpgrade: null 1740 }; 1741 this.stores({}); // Derive earlier schemas by default. 1742 } 1743 1744 extend(Version.prototype, { 1745 stores: function stores(_stores) { 1746 /// <summary> 1747 /// Defines the schema for a particular version 1748 /// </summary> 1749 /// <param name="stores" type="Object"> 1750 /// Example: <br/> 1751 /// {users: "id++,first,last,&username,*email", <br/> 1752 /// passwords: "id++,&username"}<br/> 1753 /// <br/> 1754 /// Syntax: {Table: "[primaryKey][++],[&][*]index1,[&][*]index2,..."}<br/><br/> 1755 /// Special characters:<br/> 1756 /// "&" means unique key, <br/> 1757 /// "*" means value is multiEntry, <br/> 1758 /// "++" means auto-increment and only applicable for primary key <br/> 1759 /// </param> 1760 this._cfg.storesSource = this._cfg.storesSource ? extend(this._cfg.storesSource, _stores) : _stores; // Derive stores from earlier versions if they are not explicitely specified as null or a new syntax. 1761 1762 var storesSpec = {}; 1763 versions.forEach(function (version) { 1764 extend(storesSpec, version._cfg.storesSource); 1765 }); 1766 var dbschema = this._cfg.dbschema = {}; 1767 1768 this._parseStoresSpec(storesSpec, dbschema); // Update the latest schema to this version 1769 // Update API 1770 1771 1772 globalSchema = db._dbSchema = dbschema; 1773 removeTablesApi([allTables, db, Transaction.prototype]); // Keep Transaction.prototype even though it should be depr. 1774 1775 setApiOnPlace([allTables, db, Transaction.prototype, this._cfg.tables], keys(dbschema), dbschema); 1776 dbStoreNames = keys(dbschema); 1777 return this; 1778 }, 1779 upgrade: function upgrade(upgradeFunction) { 1780 this._cfg.contentUpgrade = upgradeFunction; 1781 return this; 1782 }, 1783 _parseStoresSpec: function _parseStoresSpec(stores, outSchema) {
1784 keys(stores).forEach(function (tableName) { 1785 if (stores[tableName] !== null) { 1786 var instanceTemplate = {}; 1787 var indexes = parseIndexSyntax(stores[tableName]); 1788 var primKey = indexes.shift(); 1789 if (primKey.multi) throw new exceptions.Schema("Primary key cannot be multi-valued"); 1790 if (primKey.keyPath) setByKeyPath(instanceTemplate, primKey.keyPath, primKey.auto ? 0 : primKey.keyPath); 1791 indexes.forEach(function (idx) { 1792 if (idx.auto) throw new exceptions.Schema("Only primary key can be marked as autoIncrement (++)"); 1793 if (!idx.keyPath) throw new exceptions.Schema("Index must have a name and cannot be an empty string"); 1794 setByKeyPath(instanceTemplate, idx.keyPath, idx.compound ? idx.keyPath.map(function () { 1795 return ""; 1796 }) : ""); 1797 }); 1798 outSchema[tableName] = new TableSchema(tableName, primKey, indexes, instanceTemplate); 1799 } 1800 }); 1801 } 1802 }); 1803 1804 function runUpgraders(oldVersion, idbtrans, reject) { 1805 var trans = db._createTransaction(READWRITE, dbStoreNames, globalSchema); 1806 1807 trans.create(idbtrans); 1808 1809 trans._completion.catch(reject); 1810 1811 var rejectTransaction = trans._reject.bind(trans); 1812 1813 newScope(function () { 1814 PSD.trans = trans; 1815 1816 if (oldVersion === 0) { 1817 // Create tables: 1818 keys(globalSchema).forEach(function (tableName) { 1819 createTable(idbtrans, tableName, globalSchema[tableName].primKey, globalSchema[tableName].indexes); 1820 }); 1821 Promise.follow(function () { 1822 return db.on.populate.fire(trans); 1823 }).catch(rejectTransaction); 1824 } else updateTablesAndIndexes(oldVersion, trans, idbtrans).catch(rejectTransaction); 1825 }); 1826 } 1827 1828 function updateTablesAndIndexes(oldVersion, trans, idbtrans) { 1829 // Upgrade version to version, step-by-step from oldest to newest version. 1830 // Each transaction object will contain the table set that was current in that version (but also not-yet-deleted tables from its previous version) 1831 var queue = []; 1832 var oldVersionStruct = versions.filter(function (version) { 1833 return version._cfg.version === oldVersion; 1834 })[0]; 1835 if (!oldVersionStruct) throw new exceptions.Upgrade("Dexie specification of currently installed DB version is missing"); 1836 globalSchema = db._dbSchema = oldVersionStruct._cfg.dbschema; 1837 var anyContentUpgraderHasRun = false; 1838 var versToRun = versions.filter(function (v) { 1839 return v._cfg.version > oldVersion; 1840 }); 1841 versToRun.forEach(function (version) { 1842 /// <param name="version" type="Version"></param> 1843 queue.push(function () { 1844 var oldSchema = globalSchema; 1845 var newSchema = version._cfg.dbschema; 1846 adjustToExistingIndexNames(oldSchema, idbtrans); 1847 adjustToExistingIndexNames(newSchema, idbtrans); 1848 globalSchema = db._dbSchema = newSchema; 1849 var diff = getSchemaDiff(oldSchema, newSchema); // Add tables 1850 1851 diff.add.forEach(function (tuple) { 1852 createTable(idbtrans, tuple[0], tuple[1].primKey, tuple[1].indexes); 1853 }); // Change tables 1854 1855 diff.change.forEach(function (change) { 1856 if (change.recreate) { 1857 throw new exceptions.Upgrade("Not yet support for changing primary key"); 1858 } else { 1859 var store = idbtrans.objectStore(change.name); // Add indexes 1860 1861 change.add.forEach(function (idx) { 1862 addIndex(store, idx); 1863 }); // Update indexes 1864 1865 change.change.forEach(function (idx) { 1866 store.deleteIndex(idx.name); 1867 addIndex(store, idx); 1868 }); // Delete indexes 1869 1870 change.del.forEach(function (idxName) { 1871 store.deleteIndex(idxName); 1872 }); 1873 } 1874 }); 1875 1876 if (version._cfg.contentUpgrade) { 1877 anyContentUpgraderHasRun = true; 1878 return Promise.follow(function () { 1879 version._cfg.contentUpgrade(trans); 1880 }); 1881 } 1882 }); 1883 queue.push(function (idbtrans) { 1884 if (!anyContentUpgraderHasRun || !hasIEDeleteObjectStoreBug) { 1885 var newSchema = version._cfg.dbschema; // Delete old tables 1886 1887 deleteRemovedTables(newSchema, idbtrans); 1888 } 1889 }); 1890 }); // Now, create a queue execution engine 1891 1892 function runQueue() { 1893 return queue.length ? Promise.resolve(queue.shift()(trans.idbtrans)).then(runQueue) : Promise.resolve(); 1894 } 1895 1896 return runQueue().then(function () { 1897 createMissingTables(globalSchema, idbtrans); // At last, make sure to create any missing tables. (Needed by addons that add stores to DB without specifying version) 1898 }); 1899 } 1900 1901 function getSchemaDiff(oldSchema, newSchema) { 1902 var diff = { 1903 del: [], 1904 add: [], 1905 change: [] // Array of {name: tableName, recreate: newDefinition, del: delIndexNames, add: newIndexDefs, change: changedIndexDefs} 1906 1907 }; 1908 1909 for (var table in oldSchema) { 1910 if (!newSchema[table]) diff.del.push(table); 1911 } 1912 1913 for (table in newSchema) { 1914 var oldDef = oldSchema[table], 1915 newDef = newSchema[table]; 1916 1917 if (!oldDef) { 1918 diff.add.push([table, newDef]); 1919 } else { 1920 var change = { 1921 name: table, 1922 def: newDef, 1923 recreate: false, 1924 del: [], 1925 add: [], 1926 change: [] 1927 }; 1928 1929 if (oldDef.primKey.src !== newDef.primKey.src) { 1930 // Primary key has changed. Remove and re-add table. 1931 change.recreate = true; 1932 diff.change.push(change); 1933 } else { 1934 // Same primary key. Just find out what differs: 1935 var oldIndexes = oldDef.idxByName; 1936 var newIndexes = newDef.idxByName; 1937 1938 for (var idxName in oldIndexes) { 1939 if (!newIndexes[idxName]) change.del.push(idxName); 1940 } 1941 1942 for (idxName in newIndexes) { 1943 var oldIdx = oldIndexes[idxName], 1944 newIdx = newIndexes[idxName]; 1945 if (!oldIdx) change.add.push(newIdx);else if (oldIdx.src !== newIdx.src) change.change.push(newIdx); 1946 } 1947 1948 if (change.del.length > 0 || change.add.length > 0 || change.change.length > 0) { 1949 diff.change.push(change); 1950 } 1951 } 1952 } 1953 } 1954 1955 return diff; 1956 } 1957 1958 function createTable(idbtrans, tableName, primKey, indexes) { 1959 /// <param name="idbtrans" type="IDBTransaction"></param> 1960 var store = idbtrans.db.createObjectStore(tableName, primKey.keyPath ? { 1961 keyPath: primKey.keyPath, 1962 autoIncrement: primKey.auto 1963 } : { 1964 autoIncrement: primKey.auto 1965 });
1966 indexes.forEach(function (idx) { 1967 addIndex(store, idx); 1968 }); 1969 return store; 1970 } 1971 1972 function createMissingTables(newSchema, idbtrans) { 1973 keys(newSchema).forEach(function (tableName) { 1974 if (!idbtrans.db.objectStoreNames.contains(tableName)) { 1975 createTable(idbtrans, tableName, newSchema[tableName].primKey, newSchema[tableName].indexes); 1976 } 1977 }); 1978 } 1979 1980 function deleteRemovedTables(newSchema, idbtrans) { 1981 for (var i = 0; i < idbtrans.db.objectStoreNames.length; ++i) { 1982 var storeName = idbtrans.db.objectStoreNames[i]; 1983 1984 if (newSchema[storeName] == null) { 1985 idbtrans.db.deleteObjectStore(storeName); 1986 } 1987 } 1988 } 1989 1990 function addIndex(store, idx) { 1991 store.createIndex(idx.name, idx.keyPath, { 1992 unique: idx.unique, 1993 multiEntry: idx.multi 1994 }); 1995 } // 1996 // 1997 // Dexie Protected API 1998 // 1999 // 2000 2001 2002 this._allTables = allTables; 2003 2004 this._createTransaction = function (mode, storeNames, dbschema, parentTransaction) { 2005 return new Transaction(mode, storeNames, dbschema, parentTransaction); 2006 }; 2007 /* Generate a temporary transaction when db operations are done outside a transaction scope. 2008 */ 2009 2010 2011 function tempTransaction(mode, storeNames, fn) { 2012 if (!openComplete && !PSD.letThrough) { 2013 if (!isBeingOpened) { 2014 if (!autoOpen) return rejection(new exceptions.DatabaseClosed()); 2015 db.open().catch(nop); // Open in background. If if fails, it will be catched by the final promise anyway. 2016 } 2017 2018 return dbReadyPromise.then(function () { 2019 return tempTransaction(mode, storeNames, fn); 2020 }); 2021 } else { 2022 var trans = db._createTransaction(mode, storeNames, globalSchema); 2023 2024 try { 2025 trans.create(); 2026 } catch (ex) { 2027 return rejection(ex); 2028 } 2029 2030 return trans._promise(mode, function (resolve, reject) { 2031 return newScope(function () { 2032 PSD.trans = trans; 2033 return fn(resolve, reject, trans); 2034 }); 2035 }).then(function (result) { 2036 // Instead of resolving value directly, wait with resolving it until transaction has completed. 2037 // Otherwise the data would not be in the DB if requesting it in the then() operation. 2038 // Specifically, to ensure that the following expression will work: 2039 // 2040 // db.friends.put({name: "Arne"}).then(function () { 2041 // db.friends.where("name").equals("Arne").count(function(count) { 2042 // assert (count === 1); 2043 // }); 2044 // }); 2045 // 2046 return trans._completion.then(function () { 2047 return result; 2048 }); 2049 }); 2050 /*.catch(err => { // Don't do this as of now. If would affect bulk- and modify methods in a way that could be more intuitive. But wait! Maybe change in next major. 2051 trans._reject(err); 2052 return rejection(err); 2053 });*/ 2054 } 2055 } 2056 2057 this._whenReady = function (fn) { 2058 return openComplete || PSD.letThrough ? fn() : new Promise(function (resolve, reject) { 2059 if (!isBeingOpened) { 2060 if (!autoOpen) { 2061 reject(new exceptions.DatabaseClosed()); 2062 return; 2063 } 2064 2065 db.open().catch(nop); // Open in background. If if fails, it will be catched by the final promise anyway. 2066 } 2067 2068 dbReadyPromise.then(resolve, reject); 2069 }).then(fn); 2070 }; // 2071 // 2072 // 2073 // 2074 // Dexie API 2075 // 2076 // 2077 // 2078 2079 2080 this.verno = 0; 2081 2082 this.open = function () { 2083 if (isBeingOpened || idbdb) return dbReadyPromise.then(function () { 2084 return dbOpenError ? rejection(dbOpenError) : db; 2085 }); 2086 debug && (openCanceller._stackHolder = getErrorWithStack()); // Let stacks point to when open() was called rather than where new Dexie() was called. 2087 2088 isBeingOpened = true; 2089 dbOpenError = null; 2090 openComplete = false; // Function pointers to call when the core opening process completes. 2091 2092 var resolveDbReady = dbReadyResolve, 2093 // upgradeTransaction to abort on failure. 2094 upgradeTransaction = null; 2095 return Promise.race([openCanceller, new Promise(function (resolve, reject) { 2096 // Multiply db.verno with 10 will be needed to workaround upgrading bug in IE: 2097 // IE fails when deleting objectStore after reading from it. 2098 // A future version of Dexie.js will stopover an intermediate version to workaround this. 2099 // At that point, we want to be backward compatible. Could have been multiplied with 2, but by using 10, it is easier to map the number to the real version number. 2100 // If no API, throw! 2101 if (!indexedDB) throw new exceptions.MissingAPI("indexedDB API
2101not found. If using IE10+, make sure to run your code on a server URL " + "(not locally). If using old Safari versions, make sure to include indexedDB polyfill."); 2102 var req = autoSchema ? indexedDB.open(dbName) : indexedDB.open(dbName, Math.round(db.verno * 10)); 2103 if (!req) throw new exceptions.MissingAPI("IndexedDB API not available"); // May happen in Safari private mode, see https://github.com/dfahlander/Dexie.js/issues/134 2104 2105 req.onerror = eventRejectHandler(reject); 2106 req.onblocked = wrap(fireOnBlocked); 2107 req.onupgradeneeded = wrap(function (e) { 2108 upgradeTransaction = req.transaction; 2109 2110 if (autoSchema && !db._allowEmptyDB) { 2111 // Caller did not specify a version or schema. Doing that is only acceptable for opening alread existing databases. 2112 // If onupgradeneeded is called it means database did not exist. Reject the open() promise and make sure that we 2113 // do not create a new database by accident here. 2114 req.onerror = preventDefault; // Prohibit onabort error from firing before we're done! 2115 2116 upgradeTransaction.abort(); // Abort transaction (would hope that this would make DB disappear but it doesnt.) 2117 // Close database and delete it. 2118 2119 req.result.close(); 2120 var delreq = indexedDB.deleteDatabase(dbName); // The upgrade transaction is atomic, and javascript is single threaded - meaning that there is no risk that we delete someone elses database here! 2121 2122 delreq.onsuccess = delreq.onerror = wrap(function () { 2123 reject(new exceptions.NoSuchDatabase("Database " + dbName + " doesnt exist")); 2124 }); 2125 } else { 2126 upgradeTransaction.onerror = eventRejectHandler(reject); 2127 var oldVer = e.oldVersion > Math.pow(2, 62) ? 0 : e.oldVersion; // Safari 8 fix. 2128 2129 runUpgraders(oldVer / 10, upgradeTransaction, reject, req); 2130 } 2131 }, reject); 2132 req.onsuccess = wrap(function () { 2133 // Core opening procedure complete. Now let's just record some stuff. 2134 upgradeTransaction = null; 2135 idbdb = req.result; 2136 connections.push(db); // Used for emulating versionchange event on IE/Edge/Safari. 2137 2138 if (autoSchema) readGlobalSchema();else if (idbdb.objectStoreNames.length > 0) { 2139 try { 2140 adjustToExistingIndexNames(globalSchema, idbdb.transaction(safariMultiStoreFix(idbdb.objectStoreNames), READONLY)); 2141 } catch (e) {// Safari may bail out if > 1 store names. However, this shouldnt be a showstopper. Issue #120. 2142 } 2143 } 2144 idbdb.onversionchange = wrap(function (ev) { 2145 db._vcFired = true; // detect implementations that not support versionchange (IE/Edge/Safari) 2146 2147 db.on("versionchange").fire(ev); 2148 }); 2149 2150 if (!hasNativeGetDatabaseNames && dbName !== '__dbnames') { 2151 dbNamesDB.dbnames.put({ 2152 name: dbName 2153 }).catch(nop); 2154 } 2155 2156 resolve(); 2157 }, reject); 2158 })]).then(function () { 2159 // Before finally resolving the dbReadyPromise and this promise, 2160 // call and await all on('ready') subscribers: 2161 // Dexie.vip() makes subscribers able to use the database while being opened. 2162 // This is a must since these subscribers take part of the opening procedure. 2163 onReadyBeingFired = []; 2164 return Promise.resolve(Dexie.vip(db.on.ready.fire)).then(function fireRemainders() { 2165 if (onReadyBeingFired.length > 0) { 2166 // In case additional subscribers to db.on('ready') were added during the time db.on.ready.fire was executed. 2167 var remainders = onReadyBeingFired.reduce(promisableChain, nop); 2168 onReadyBeingFired = []; 2169 return Promise.resolve(Dexie.vip(remainders)).then(fireRemainders); 2170 } 2171 }); 2172 }).finally(function () { 2173 onReadyBeingFired = null; 2174 }).then(function () { 2175 // Resolve the db.open() with the db instance. 2176 isBeingOpened = false;
2177 return db; 2178 }).catch(function (err) { 2179 try { 2180 // Did we fail within onupgradeneeded? Make sure to abort the upgrade transaction so it doesnt commit. 2181 upgradeTransaction && upgradeTransaction.abort(); 2182 } catch (e) {} 2183 2184 isBeingOpened = false; // Set before calling db.close() so that it doesnt reject openCanceller again (leads to unhandled rejection event). 2185 2186 db.close(); // Closes and resets idbdb, removes connections, resets dbReadyPromise and openCanceller so that a later db.open() is fresh. 2187 // A call to db.close() may have made on-ready subscribers fail. Use dbOpenError if set, since err could be a follow-up error on that. 2188 2189 dbOpenError = err; // Record the error. It will be used to reject further promises of db operations. 2190 2191 return rejection(dbOpenError); 2192 }).finally(function () { 2193 openComplete = true; 2194 resolveDbReady(); // dbReadyPromise is resolved no matter if open() rejects or resolved. It's just to wake up waiters. 2195 }); 2196 }; 2197 2198 this.close = function () { 2199 var idx = connections.indexOf(db); 2200 if (idx >= 0) connections.splice(idx, 1); 2201 2202 if (idbdb) { 2203 try { 2204 idbdb.close(); 2205 } catch (e) {} 2206 2207 idbdb = null; 2208 } 2209 2210 autoOpen = false; 2211 dbOpenError = new exceptions.DatabaseClosed(); 2212 if (isBeingOpened) cancelOpen(dbOpenError); // Reset dbReadyPromise promise: 2213 2214 dbReadyPromise = new Promise(function (resolve) { 2215 dbReadyResolve = resolve; 2216 }); 2217 openCanceller = new Promise(function (_, reject) { 2218 cancelOpen = reject; 2219 }); 2220 }; 2221 2222 this.delete = function () { 2223 var hasArguments = arguments.length > 0; 2224 return new Promise(function (resolve, reject) { 2225 if (hasArguments) throw new exceptions.InvalidArgument("Arguments not allowed in db.delete()"); 2226 2227 if (isBeingOpened) { 2228 dbReadyPromise.then(doDelete); 2229 } else { 2230 doDelete(); 2231 } 2232 2233 function doDelete() { 2234 db.close(); 2235 var req = indexedDB.deleteDatabase(dbName); 2236 req.onsuccess = wrap(function () { 2237 if (!hasNativeGetDatabaseNames) { 2238 dbNamesDB.dbnames.delete(dbName).catch(nop); 2239 } 2240 2241 resolve(); 2242 }); 2243 req.onerror = eventRejectHandler(reject); 2244 req.onblocked = fireOnBlocked; 2245 } 2246 }); 2247 }; 2248 2249 this.backendDB = function () { 2250 return idbdb; 2251 }; 2252 2253 this.isOpen = function () { 2254 return idbdb !== null; 2255 }; 2256 2257 this.hasBeenClosed = function () { 2258 return dbOpenError && dbOpenError instanceof exceptions.DatabaseClosed; 2259 }; 2260 2261 this.hasFailed = function () { 2262 return dbOpenError !== null; 2263 }; 2264 2265 this.dynamicallyOpened = function () { 2266 return autoSchema; 2267 }; // 2268 // Properties 2269 // 2270 2271 2272 this.name = dbName; // db.tables - an array of all Table instances. 2273 2274 props(this, { 2275 tables: { 2276 get: function get() { 2277 /// <returns type="Array" elementType="Table" /> 2278 return keys(allTables).map(function (name) { 2279 return allTables[name]; 2280 }); 2281 } 2282 } 2283 }); // 2284 // Events 2285 // 2286 2287 this.on = Events(this, "populate", "blocked", "versionchange", { 2288 ready: [promisableChain, nop] 2289 }); 2290 this.on.ready.subscribe = override(this.on.ready.subscribe, function (subscribe) { 2291 return function (subscriber, bSticky) { 2292 Dexie.vip(function () { 2293 if (openComplete) { 2294 // Database already open. Call subscriber asap. 2295 if (!dbOpenError) Promise.resolve().then(subscriber); // bSticky: Also subscribe to future open sucesses (after close / reopen) 2296 2297 if (bSticky) subscribe(subscriber); 2298 } else if (onReadyBeingFired) { 2299 // db.on('ready') subscribers are currently being executed and have not yet resolved or rejected 2300 onReadyBeingFired.push(subscriber); 2301 if (bSticky) subscribe(subscriber); 2302 } else { 2303 // Database not yet open. Subscribe to it. 2304 subscribe(subscriber);
2304 // If bSticky is falsy, make sure to unsubscribe subscriber when fired once. 2305 2306 if (!bSticky) subscribe(function unsubscribe() { 2307 db.on.ready.unsubscribe(subscriber); 2308 db.on.ready.unsubscribe(unsubscribe); 2309 }); 2310 } 2311 }); 2312 }; 2313 }); 2314 2315 this.transaction = function () { 2316 /// <summary> 2317 /// 2318 /// </summary> 2319 /// <param name="mode" type="String">"r" for readonly, or "rw" for readwrite</param> 2320 /// <param name="tableInstances">Table instance, Array of Table instances, String or String Array of object stores to include in the transaction</param> 2321 /// <param name="scopeFunc" type="Function">Function to execute with transaction</param> 2322 var args = extractTransactionArgs.apply(this, arguments); 2323 return this._transaction.apply(this, args); 2324 }; 2325 2326 function extractTransactionArgs(mode, _tableArgs_, scopeFunc) { 2327 // Let table arguments be all arguments between mode and last argument. 2328 var i = arguments.length; 2329 if (i < 2) throw new exceptions.InvalidArgument("Too few arguments"); // Prevent optimzation killer (https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#32-leaking-arguments) 2330 // and clone arguments except the first one into local var 'args'. 2331 2332 var args = new Array(i - 1); 2333 2334 while (--i) { 2335 args[i - 1] = arguments[i]; 2336 } // Let scopeFunc be the last argument and pop it so that args now only contain the table arguments. 2337 2338 2339 scopeFunc = args.pop(); 2340 var tables = flatten(args); // Support using array as middle argument, or a mix of arrays and non-arrays. 2341 2342 return [mode, tables, scopeFunc]; 2343 } 2344 2345 this._transaction = function (mode, tables, scopeFunc) { 2346 var parentTransaction = PSD.trans; // Check if parent transactions is bound to this db instance, and if caller wants to reuse it 2347 2348 if (!parentTransaction || parentTransaction.db !== db || mode.indexOf('!') !== -1) parentTransaction = null; 2349 var onlyIfCompatible = mode.indexOf('?') !== -1; 2350 mode = mode.replace('!', '').replace('?', ''); // Ok. Will change arguments[0] as well but we wont touch arguments henceforth. 2351 2352 try { 2353 // 2354 // Get storeNames from arguments. Either through given table instances, or through given table names. 2355 // 2356 var storeNames = tables.map(function (table) { 2357 var storeName = table instanceof Table ? table.name : table; 2358 if (typeof storeName !== 'string') throw new TypeError("Invalid table argument to Dexie.transaction(). Only Table or String are allowed"); 2359 return storeName; 2360 }); // 2361 // Resolve mode. Allow shortcuts "r" and "rw". 2362 // 2363 2364 if (mode == "r" || mode == READONLY) mode = READONLY;else if (mode == "rw" || mode == READWRITE) mode = READWRITE;else throw new exceptions.InvalidArgument("Invalid transaction mode: " + mode); 2365 2366 if (parentTransaction) { 2367 // Basic checks 2368 if (parentTransaction.mode === READONLY && mode === READWRITE) { 2369 if (onlyIfCompatible) { 2370 // Spawn new transaction instead. 2371 parentTransaction = null; 2372 } else throw new exceptions.SubTransaction("Cannot enter a sub-transaction with READWRITE mode when parent transaction is READONLY"); 2373 } 2374 2375 if (parentTransaction) { 2376 storeNames.forEach(function (storeName) { 2377 if (parentTransaction && parentTransaction.storeNames.indexOf(storeName) === -1) { 2378 if (onlyIfCompatible) { 2379 // Spawn new transaction instead. 2380 parentTransaction = null; 2381 } else throw new exceptions.SubTransaction("Table " + storeName + " not included in parent transaction."); 2382 } 2383 }); 2384 } 2385 2386 if (onlyIfCompatible && parentTransaction && !parentTransaction.active) { 2387 // '?' mode should not keep using an inactive transaction. 2388 parentTransaction = null; 2389 } 2390 } 2391 } catch (e) { 2392 return parentTransaction ? parentTransaction._promise(null, function (_, reject) { 2393 reject(e); 2394 }) : rejection(e); 2395 } // If this is a sub-transaction, lock the parent and then launch the sub-transaction. 2396 2397 2398 return parentTransaction ? parentTransaction._promise(mode, enterTransactionScope, "lock") : PSD.trans ? // no parent transaction despite PSD.trans exists. Make sure also 2399 // that the zone we create is not a sub-zone of current, because 2400 // Promise.follow() should not wait for it if so. 2401 usePSD(PSD.transless, function () { 2402 return db._whenReady(enterTransactionScope); 2403 }) : db._whenReady(enterTransactionScope); 2404 2405 function enterTransactionScope() { 2406 return Promise.resolve().then(function () { 2407 // Keep a pointer to last non-transactional PSD to use if someone calls Dexie.ignoreTransaction(). 2408 var transless = PSD.transless || PSD; // Our transaction. 2409 //return new Promise((resolve, reject) => { 2410 2411 var trans = db._createTransaction(mode, storeNames, globalSchema, parentTransaction); // Let the transaction instance be part of a Promise-specific data (PSD) value. 2412 2413 2414 var zoneProps = { 2415 trans: trans, 2416 transless: transless 2417 }; 2418 2419 if (parentTransaction) { 2420 // Emulate transaction commit awareness for inner transaction (must 'commit' when the inner transaction has no more operations ongoing) 2421 trans.idbtrans = parentTransaction.idbtrans; 2422 } else { 2423 trans.create(); // Create the backend transaction so that complete() or error() will trigger even if no operation is made upon it. 2424 } // Support for native async await. 2425 2426 2427 if (scopeFunc.constructor === AsyncFunction) { 2428 incrementExpectedAwaits(); 2429 } 2430 2431 var returnValue; 2432 var promiseFollowed = Promise.follow(function () { 2433 // Finally, call the scope function with our table and transaction arguments. 2434 returnValue = scopeFunc.call(trans, trans); 2435 2436 if (returnValue) { 2437 if (returnValue.constructor === NativePromise) { 2438 var decrementor = decrementExpectedAwaits.bind(null, null); 2439 returnValue.then(decrementor, decrementor); 2440 } else if (typeof returnValue.next === 'function' && typeof returnValue.throw === 'function') { 2441 // scopeFunc returned an iterator with throw-support. Handle yield as await. 2442 returnValue = awaitIterator(returnValue); 2443 } 2444 } 2445 }, zoneProps); 2446 return (returnValue && typeof returnValue.then === 'function' ? // Promise returned. User uses promise-style transactions. 2447 Promise.resolve(returnValue).then(function (x) { 2448 return trans.active ? x // Transaction still active. Continue. 2449 : rejection(new exceptions.PrematureCommit("Transaction committed too early. See http://bit.ly/2kdckMn")); 2450 }) // No promise returned. Wait for all outstanding promises before continuing. 2451 : promiseFollowed.then(function () { 2452 return returnValue; 2453 })).then(function (x) { 2454 // sub transactions don't react to idbtrans.oncomplete. We must trigger a completion: 2455 if (parentTransaction) trans._resolve(); // wait for trans._completion 2456 // (if root transaction, this means 'complete' event. If sub-transaction, we've just fired it ourselves) 2457 2458 return trans._completion.then(function () { 2459 return x; 2460 }); 2461 }).catch(function (e) { 2462 trans._reject(e); // Yes, above then-handler were maybe not called because of an unhandled rejection in scopeFunc! 2463 2464 2465 return rejection(e); 2466 }); 2467 }); 2468 } 2469 }; 2470 2471 this.table = function (tableName) { 2472 /// <returns type="Table"></returns> 2473 if (!hasOwn(allTables, tableName)) { 2474 throw new exceptions.InvalidTable("Table " + tableName + " does not exist"); 2475 } 2476 2477 return allTables[tableName]; 2478 }; // 2479 // 2480 // 2481 // Table Class 2482 // 2483 // 2484 // 2485 2486 2487 function Table(name, tableSchema, optionalTrans) { 2488 /// <param name="name" type="String"></param> 2489 this.name = name; 2490 this.schema = tableSchema; 2491 this._tx = optionalTrans; 2492 this.hook = allTables[name] ? allTables[name].hook : Events(null, { 2493 "creating": [hookCreatingChain, nop], 2494 "reading": [pureFunctionChain, mirror], 2495 "updating": [hookUpdatingChain, nop], 2496 "deleting": [hookDeletingChain, nop] 2497 }); 2498 } 2499 2500 function BulkErrorHandlerCatchAll(errorList, done, supportHooks) { 2501 return (supportHooks ? hookedEventRejectHandler : eventRejectHandler)(function (e) { 2502 errorList.push(e); 2503 done && done(); 2504 }); 2505 } 2506 2507 function _bulkDelete(idbstore, trans, keysOrTuples, hasDeleteHook, deletingHook) { 2508 // If hasDeleteHook, keysOrTuples must be an array of tuples: [[key1, value2],[key2,value2],...], 2509 // else keysOrTuples must be just an array of keys: [key1, key2, ...]. 2510 return new Promise(function (resolve, reject) { 2511 var len = keysOrTuples.length, 2512 lastItem = len - 1; 2513 if (len === 0) return resolve(); 2514 2515 if (!hasDeleteHook) { 2516 for (var i = 0; i < len; ++i) { 2517 var req = idbstore.delete(keysOrTuples[i]); 2518 req.onerror = eventRejectHandler(reject); 2519 if (i === lastItem) req.onsuccess = wrap(function () { 2520 return resolve(); 2521 }); 2522 } 2523 } else { 2524 var hookCtx, 2525 errorHandler = hookedEventRejectHandler(reject), 2526 successHandler = hookedEventSuccessHandler(null); 2527 tryCatch(function () { 2528 for (var i = 0; i < len; ++i) { 2529 hookCtx = { 2530 onsuccess: null, 2531 onerror: null 2532 }; 2533 var tuple = keysOrTuples[i]; 2534 deletingHook.call(hookCtx, tuple[0], tuple[1], trans); 2535 var req = idbstore.delete(tuple[0]); 2536 req._hookCtx = hookCtx; 2537 req.onerror = errorHandler; 2538 if (i === lastItem) req.onsuccess = hookedEventSuccessHandler(resolve);else req.onsuccess = successHandler; 2539 } 2540 }, function (err) {
2541 hookCtx.onerror && hookCtx.onerror(err); 2542 throw err; 2543 }); 2544 } 2545 }); 2546 } 2547 2548 props(Table.prototype, { 2549 // 2550 // Table Protected Methods 2551 // 2552 _trans: function getTransaction(mode, fn, writeLocked) { 2553 var trans = this._tx || PSD.trans; 2554 return trans && trans.db === db ? trans === PSD.trans ? trans._promise(mode, fn, writeLocked) : newScope(function () { 2555 return trans._promise(mode, fn, writeLocked); 2556 }, { 2557 trans: trans, 2558 transless: PSD.transless || PSD 2559 }) : tempTransaction(mode, [this.name], fn); 2560 }, 2561 _idbstore: function getIDBObjectStore(mode, fn, writeLocked) { 2562 var tableName = this.name; 2563 2564 function supplyIdbStore(resolve, reject, trans) { 2565 if (trans.storeNames.indexOf(tableName) === -1) throw new exceptions.NotFound("Table" + tableName + " not part of transaction"); 2566 return fn(resolve, reject, trans.idbtrans.objectStore(tableName), trans); 2567 } 2568 2569 return this._trans(mode, supplyIdbStore, writeLocked); 2570 }, 2571 // 2572 // Table Public Methods 2573 // 2574 get: function get(keyOrCrit, cb) { 2575 if (keyOrCrit && keyOrCrit.constructor === Object) return this.where(keyOrCrit).first(cb); 2576 var self = this; 2577 return this._idbstore(READONLY, function (resolve, reject, idbstore) { 2578 var req = idbstore.get(keyOrCrit); 2579 req.onerror = eventRejectHandler(reject); 2580 req.onsuccess = wrap(function () { 2581 resolve(self.hook.reading.fire(req.result)); 2582 }, reject); 2583 }).then(cb); 2584 }, 2585 where: function where(indexOrCrit) { 2586 if (typeof indexOrCrit === 'string') return new WhereClause(this, indexOrCrit); 2587 if (isArray(indexOrCrit)) return new WhereClause(this, "[" + indexOrCrit.join('+') + "]"); // indexOrCrit is an object map of {[keyPath]:value} 2588 2589 var keyPaths = keys(indexOrCrit); 2590 if (keyPaths.length === 1) // Only one critera. This was the easy case: 2591 return this.where(keyPaths[0]).equals(indexOrCrit[keyPaths[0]]); // Multiple criterias. 2592 // Let's try finding a compound index that matches all keyPaths in 2593 // arbritary order: 2594 2595 var compoundIndex = this.schema.indexes.concat(this.schema.primKey).filter(function (ix) { 2596 return ix.compound && keyPaths.every(function (keyPath) { 2597 return ix.keyPath.indexOf(keyPath) >= 0; 2598 }) && ix.keyPath.every(function (keyPath) { 2599 return keyPaths.indexOf(keyPath) >= 0; 2600 }); 2601 })[0]; 2602 if (compoundIndex && maxKey !== maxString) // Cool! We found such compound index 2603 // and this browser supports compound indexes (maxKey !== maxString)! 2604 return this.where(compoundIndex.name).equals(compoundIndex.keyPath.map(function (kp) { 2605 return indexOrCrit[kp]; 2606 })); 2607 if (!compoundIndex) console.warn("The query " + JSON.stringify(indexOrCrit) + " on " + this.name + " would benefit of a " + ("compound index [" + keyPaths.join('+') + "]")); // Ok, now let's fallback to finding at least one matching index 2608 // and filter the rest. 2609 2610 var idxByName = this.schema.idxByName; 2611 var simpleIndex = keyPaths.reduce(function (r, keyPath) { 2612 return [r[0] || idxByName[keyPath], r[0] || !idxByName[keyPath] ? combine(r[1], function (x) { 2613 return '' + getByKeyPath(x, keyPath) == '' + indexOrCrit[keyPath]; 2614 }) : r[1]]; 2615 }, [null, null]); 2616 var idx = simpleIndex[0]; 2617 return idx ? this.where(idx.name).equals(indexOrCrit[idx.keyPath]).filter(simpleIndex[1]) : compoundIndex ? this.filter(simpleIndex[1]) : // Has compound but browser bad. Allow filter. 2618 this.where(keyPaths).equals(''); // No index at all. Fail lazily. 2619 }, 2620 count: function count(cb) { 2621 return this.toCollection().count(cb); 2622 }, 2623 offset: function offset(_offset) { 2624 return this.toCollection().offset(_offset); 2625 }, 2626 limit: function limit(numRows) { 2627 return this.toCollection().limit(numRows); 2628 }, 2629 reverse: function reverse() { 2630 return this.toCollection().reverse(); 2631 }, 2632 filter: function filter(filterFunction) { 2633 return this.toCollection().and(filterFunction); 2634 }, 2635 each: function each(fn) { 2636 return this.toCollection().each(fn); 2637 }, 2638 toArray: function toArray(cb) { 2639 return this.toCollection().toArray(cb); 2640 }, 2641 orderBy: function orderBy(index) { 2642 return new Collection(new WhereClause(this, isArray(index) ? "[" + index.join('+') + "]" : index)); 2643 }, 2644 toCollection: function toCollection() { 2645 return new Collection(new WhereClause(this)); 2646 }, 2647 mapToClass: function mapToClass(constructor, structure) { 2648 /// <summary> 2649 /// Map table to a javascript constructor function. Objects returned from the database will be instances of this class, making 2650 /// it possible to the instanceOf operator as well as extending the class using constructor.prototype.method = function(){...}. 2651 /// </summary> 2652 /// <param name="constructor">Constructor function representing the class.</param> 2653 /// <param name="structure" optional="true">Helps IDE code completion by knowing the members that objects contain and not just the indexes. Also 2654 /// know what type each member has. Example: {name: String, emailAddresses: [String], password}</param> 2655 this.schema.mappedClass = constructor; 2656 var instanceTemplate = Object.create(constructor.prototype); 2657 2658 if (structure) { 2659 // structure and instanceTemplate is for IDE code competion only while constructor.prototype is for actual inheritance. 2660 applyStructure(instanceTemplate, structure); 2661 } 2662 2663 this.schema.instanceTemplate = instanceTemplate; // Now, subscribe to the when("reading") event to make all objects that come out from this table inherit from given class 2664 // no matter which method to use for reading (Table.get() or Table.where(...)... ) 2665 2666 var readHook = function readHook(obj) { 2667 if (!obj) return obj; // No valid object. (Value is null). Return as is. 2668 // Create a new object that derives from constructor: 2669 2670 var res = Object.create(constructor.prototype); // Clone members: 2671 2672 for (var m in obj) { 2673 if (hasOwn(obj, m)) try { 2674 res[m] = obj[m]; 2675 } catch (_) {} 2676 } 2677 2678 return res; 2679 }; 2680 2681 if (this.schema.readHook) { 2682 this.hook.reading.unsubscribe(this.schema.readHook); 2683 } 2684 2685 this.schema.readHook = readHook; 2686 this.hook("reading", readHook); 2687 return constructor; 2688 }, 2689 defineClass: function defineClass(structure) { 2690 /// <summary> 2691 /// Define all members of the class that represents the table. This will help code completion of when objects are read from the database 2692 /// as well as making it possible to extend the prototype of the returned constructor function. 2693 /// </summary> 2694 /// <param name="structure">Helps IDE code completion by knowing the members that objects contain and not just the indexes. Also 2695 /// know what type each member has. Example: {name: String, emailAddresses: [String], properties: {shoeSize: Number}}</param> 2696 return this.mapToClass(Dexie.defineClass(structure), structure); 2697 }, 2698 bulkDelete: function bulkDelete(keys$$1) { 2699 if (this.hook.deleting.fire === nop) { 2700 return this._idbstore(READWRITE, function (resolve, reject, idbstore, trans) { 2701 resolve(_bulkDelete(idbstore, trans, keys$$1, false, nop)); 2702 }); 2703 } else { 2704 return this.where(':id').anyOf(keys$$1).delete().then(function () {}); // Resolve with undefined. 2705 } 2706 }, 2707 bulkPut: function bulkPut(objects, keys$$1) { 2708 var _this = this; 2709 2710 return this._idbstore(READWRITE, function (resolve, reject, idbstore) { 2711 if (!idbstore.keyPath && !_this.schema.primKey.auto && !keys$$1) throw new exceptions.InvalidArgument("bulkPut() with non-inbound keys requires ke
2711ys array in second argument"); 2712 if (idbstore.keyPath && keys$$1) throw new exceptions.InvalidArgument("bulkPut(): keys argument invalid on tables with inbound keys"); 2713 if (keys$$1 && keys$$1.length !== objects.length) throw new exceptions.InvalidArgument("Arguments objects and keys must have the same length"); 2714 if (objects.length === 0) return resolve(); // Caller provided empty list. 2715 2716 var done = function done(result) { 2717 if (errorList.length === 0) resolve(result);else reject(new BulkError(_this.name + ".bulkPut(): " + errorList.length + " of " + numObjs + " operations failed", errorList)); 2718 }; 2719 2720 var req, 2721 errorList = [], 2722 errorHandler, 2723 numObjs = objects.length, 2724 table = _this; 2725 2726 if (_this.hook.creating.fire === nop && _this.hook.updating.fire === nop) { 2727 // 2728 // Standard Bulk (no 'creating' or 'updating' hooks to care about) 2729 // 2730 errorHandler = BulkErrorHandlerCatchAll(errorList); 2731 2732 for (var i = 0, l = objects.length; i < l; ++i) { 2733 req = keys$$1 ? idbstore.put(objects[i], keys$$1[i]) : idbstore.put(objects[i]); 2734 req.onerror = errorHandler; 2735 } // Only need to catch success or error on the last operation 2736 // according to the IDB spec. 2737 2738 2739 req.onerror = BulkErrorHandlerCatchAll(errorList, done); 2740 req.onsuccess = eventSuccessHandler(done); 2741 } else { 2742 var effectiveKeys = keys$$1 || idbstore.keyPath && objects.map(function (o) { 2743 return getByKeyPath(o, idbstore.keyPath); 2744 }); // Generate map of {[key]: object} 2745 2746 var objectLookup = effectiveKeys && arrayToObject(effectiveKeys, function (key, i) { 2747 return key != null && [key, objects[i]]; 2748 }); 2749 var promise = !effectiveKeys ? // Auto-incremented key-less objects only without any keys argument. 2750 table.bulkAdd(objects) : // Keys provided. Either as inbound in provided objects, or as a keys argument. 2751 // Begin with updating those that exists in DB: 2752 table.where(':id').anyOf(effectiveKeys.filter(function (key) { 2753 return key != null; 2754 })).modify(function () { 2755 this.value = objectLookup[this.primKey]; 2756 objectLookup[this.primKey] = null; // Mark as "don't add this" 2757 }).catch(ModifyError, function (e) { 2758 errorList = e.failures; // No need to concat here. These are the first errors added.
2759 }).then(function () { 2760 // Now, let's examine which items didnt exist so we can add them: 2761 var objsToAdd = [], 2762 keysToAdd = keys$$1 && []; // Iterate backwards. Why? Because if same key was used twice, just add the last one. 2763 2764 for (var i = effectiveKeys.length - 1; i >= 0; --i) { 2765 var key = effectiveKeys[i]; 2766 2767 if (key == null || objectLookup[key]) { 2768 objsToAdd.push(objects[i]); 2769 keys$$1 && keysToAdd.push(key); 2770 if (key != null) objectLookup[key] = null; // Mark as "dont add again" 2771 } 2772 } // The items are in reverse order so reverse them before adding. 2773 // Could be important in order to get auto-incremented keys the way the caller 2774 // would expect. Could have used unshift instead of push()/reverse(), 2775 // but: http://jsperf.com/unshift-vs-reverse 2776 2777 2778 objsToAdd.reverse(); 2779 keys$$1 && keysToAdd.reverse(); 2780 return table.bulkAdd(objsToAdd, keysToAdd); 2781 }).then(function (lastAddedKey) { 2782 // Resolve with key of the last object in given arguments to bulkPut(): 2783 var lastEffectiveKey = effectiveKeys[effectiveKeys.length - 1]; // Key was provided. 2784 2785 return lastEffectiveKey != null ? lastEffectiveKey : lastAddedKey; 2786 }); 2787 promise.then(done).catch(BulkError, function (e) { 2788 // Concat failure from ModifyError and reject using our 'done' method. 2789 errorList = errorList.concat(e.failures); 2790 done(); 2791 }).catch(reject); 2792 } 2793 }, "locked"); // If called from transaction scope, lock transaction til all steps are done. 2794 }, 2795 bulkAdd: function bulkAdd(objects, keys$$1) { 2796 var self = this, 2797 creatingHook = this.hook.creating.fire; 2798 return this._idbstore(READWRITE, function (resolve, reject, idbstore, trans) { 2799 if (!idbstore.keyPath && !self.schema.primKey.auto && !keys$$1) throw new exceptions.InvalidArgument("bulkAdd() with non-inbound keys requires keys array in second argument"); 2800 if (idbstore.keyPath && keys$$1) throw new exceptions.InvalidArgument("bulkAdd(): keys argument invalid on tables with inbound keys"); 2801 if (keys$$1 && keys$$1.length !== objects.length) throw new exceptions.InvalidArgument("Arguments objects and keys must have the same length"); 2802 if (objects.length === 0) return resolve(); // Caller provided empty list. 2803 2804 function done(result) { 2805 if (errorList.length === 0) resolve(result);else reject(new BulkError(self.name + ".bulkAdd(): " + errorList.length + " of " + numObjs + " operations failed", errorList)); 2806 } 2807 2808 var req, 2809 errorList = [], 2810 errorHandler, 2811 successHandler, 2812 numObjs = objects.length; 2813 2814 if (creatingHook !== nop) { 2815 // 2816 // There are subscribers to hook('creating') 2817 // Must behave as documented. 2818 // 2819 var keyPath = idbstore.keyPath, 2820 hookCtx; 2821 errorHandler = BulkErrorHandlerCatchAll(errorList, null, true); 2822 successHandler = hookedEventSuccessHandler(null); 2823 tryCatch(function () { 2824 for (var i = 0, l = objects.length; i < l; ++i) { 2825 hookCtx = { 2826 onerror: null, 2827 onsuccess: null 2828 }; 2829 var key = keys$$1 && keys$$1[i]; 2830 var obj = objects[i], 2831 effectiveKey = keys$$1 ? key : keyPath ? getByKeyPath(obj, keyPath) : undefined, 2832 keyToUse = creatingHook.call(hookCtx, effectiveKey, obj, trans); 2833 2834 if (effectiveKey == null && keyToUse != null) { 2835 if (keyPath) { 2836 obj = deepClone(obj); 2837 setByKeyPath(obj, keyPath, keyToUse); 2838 } else { 2839 key = keyToUse; 2840 } 2841 } 2842 2843 req = key != null ? idbstore.add(obj, key) : idbstore.add(obj); 2844 req._hookCtx = hookCtx; 2845 2846 if (i < l - 1) { 2847 req.onerror = errorHandler; 2848 if (hookCtx.onsuccess) req.onsuccess = successHandler; 2849 } 2850 } 2851 }, function (err) {
2852 hookCtx.onerror && hookCtx.onerror(err); 2853 throw err; 2854 }); 2855 req.onerror = BulkErrorHandlerCatchAll(errorList, done, true); 2856 req.onsuccess = hookedEventSuccessHandler(done); 2857 } else { 2858 // 2859 // Standard Bulk (no 'creating' hook to care about) 2860 // 2861 errorHandler = BulkErrorHandlerCatchAll(errorList); 2862 2863 for (var i = 0, l = objects.length; i < l; ++i) { 2864 req = keys$$1 ? idbstore.add(objects[i], keys$$1[i]) : idbstore.add(objects[i]); 2865 req.onerror = errorHandler; 2866 } // Only need to catch success or error on the last operation 2867 // according to the IDB spec. 2868 2869 2870 req.onerror = BulkErrorHandlerCatchAll(errorList, done); 2871 req.onsuccess = eventSuccessHandler(done); 2872 } 2873 }); 2874 }, 2875 add: function add(obj, key) { 2876 /// <summary> 2877 /// Add an object to the database. In case an object with same primary key already exists, the object will not be added. 2878 /// </summary> 2879 /// <param name="obj" type="Object">A javascript object to insert</param> 2880 /// <param name="key" optional="true">Primary key</param> 2881 var creatingHook = this.hook.creating.fire; 2882 return this._idbstore(READWRITE, function (resolve, reject, idbstore, trans) { 2883 var hookCtx = { 2884 onsuccess: null, 2885 onerror: null 2886 }; 2887 2888 if (creatingHook !== nop) { 2889 var effectiveKey = key != null ? key : idbstore.keyPath ? getByKeyPath(obj, idbstore.keyPath) : undefined; 2890 var keyToUse = creatingHook.call(hookCtx, effectiveKey, obj, trans); // Allow subscribers to when("creating") to generate the key. 2891 2892 if (effectiveKey == null && keyToUse != null) { 2893 if (idbstore.keyPath) setByKeyPath(obj, idbstore.keyPath, keyToUse);else key = keyToUse; 2894 } 2895 } 2896 2897 try { 2898 var req = key != null ? idbstore.add(obj, key) : idbstore.add(obj); 2899 req._hookCtx = hookCtx; 2900 req.onerror = hookedEventRejectHandler(reject); 2901 req.onsuccess = hookedEventSuccessHandler(function (result) { 2902 // TODO: Remove these two lines in next major release (2.0?) 2903 // It's no good practice to have side effects on provided parameters 2904 var keyPath = idbstore.keyPath; 2905 if (keyPath) setByKeyPath(obj, keyPath, result); 2906 resolve(result); 2907 }); 2908 } catch (e) { 2909 if (hookCtx.onerror) hookCtx.onerror(e); 2910 throw e; 2911 } 2912 }); 2913 }, 2914 put: function put(obj, key) { 2915 var _this = this; /// <summary> 2916 /// Add an object to the database but in case an object with same primary key alread exists, the existing one will get updated. 2917 /// </summary> 2918 /// <param name="obj" type="Object">A javascript object to insert or update</param> 2919 /// <param name="key" optional="true">Primary key</param> 2920 2921 2922 var creatingHook = this.hook.creating.fire, 2923 updatingHook = this.hook.updating.fire; 2924 2925 if (creatingHook !== nop || updatingHook !== nop) { 2926 // 2927 // People listens to when("creating") or when("updating") events! 2928 // We must know whether the put operation results in an CREATE or UPDATE. 2929 // 2930 var keyPath = this.schema.primKey.keyPath; 2931 var effectiveKey = key !== undefined ? key : keyPath && getByKeyPath(obj, keyPath); 2932 if (effectiveKey == null) return this.add(obj); // Since key is optional, make sure we get it from obj if not provided 2933 // Primary key exist. Lock transaction and try modifying existing. If nothing modified, call add(). 2934 // clone obj before this async call. If caller modifies obj the line after put(), the IDB spec requires that it should not affect operation. 2935 2936 obj = deepClone(obj); 2937 return this._trans(READWRITE, function () { 2938 return _this.where(":id").equals(effectiveKey).modify(function () { 2939 // Replace extisting value with our object 2940 // CRUD event firing handled in Collection.modify() 2941 this.value = obj; 2942 }).then(function (count) { 2943 return count === 0 ? _this.add(obj, key) : effectiveKey; 2944 }); 2945 }, "locked"); // Lock needed because operation is splitted into modify() and add(). 2946 } else { 2947 // Use the standard IDB put() method. 2948 return this._idbstore(READWRITE, function (resolve, reject, idbstore) { 2949 var req = key !== undefined ? idbstore.put(obj, key) : idbstore.put(obj); 2950 req.onerror = eventRejectHandler(reject); 2951 req.onsuccess = wrap(function (ev) { 2952 var keyPath = idbstore.keyPath; 2953 if (keyPath) setByKeyPath(obj, keyPath, ev.target.result); 2954 resolve(req.result); 2955 }); 2956 }); 2957 } 2958 }, 2959 'delete': function _delete(key) { 2960 /// <param name="key">Primary key of the object to delete</param> 2961 if (this.hook.deleting.subscribers.length) { 2962 // People listens to when("deleting") event. Must implement delete using Collection.delete() that will 2963 // call the CRUD event. Only Collection.delete() will know whether an object was actually deleted. 2964 return this.where(":id").equals(key).delete(); 2965 } else { 2966 // No one listens. Use standard IDB delete() method. 2967 return this._idbstore(READWRITE, function (resolve, reject, idbstore) { 2968 var req = idbstore.delete(key); 2969 req.onerror = eventRejectHandler(reject); 2970 req.onsuccess = wrap(function () { 2971 resolve(req.result); 2972 }); 2973 }); 2974 } 2975 }, 2976 clear: function clear() { 2977 if (this.hook.deleting.subscribers.length) { 2978 // People listens to when("deleting") event. Must implement delete using Collection.delete() that will 2979 // call the CRUD event. Only Collection.delete() will knows which objects that are actually deleted. 2980 return this.toCollection().delete(); 2981 } else { 2982 return this._idbstore(READWRITE, function (resolve, reject, idbstore) { 2983 var req = idbstore.clear(); 2984 req.onerror = eventRejectHandler(reject); 2985 req.onsuccess = wrap(function () { 2986 resolve(req.result); 2987 }); 2988 }); 2989 } 2990 }, 2991 update: function update(keyOrObject, modifications) { 2992 if (babelHelpers.typeof(modifications) !== 'object' || isArray(modifications)) throw new exceptions.InvalidArgument("Modifications must be an object."); 2993 2994 if (babelHelpers.typeof(keyOrObject) === 'object' && !isArray(keyOrObject)) { 2995 // object to modify. Also modify given object with the modifications:
2996 keys(modifications).forEach(function (keyPath) { 2997 setByKeyPath(keyOrObject, keyPath, modifications[keyPath]); 2998 }); 2999 var key = getByKeyPath(keyOrObject, this.schema.primKey.keyPath); 3000 if (key === undefined) return rejection(new exceptions.InvalidArgument("Given object does not contain its primary key")); 3001 return this.where(":id").equals(key).modify(modifications); 3002 } else { 3003 // key to modify 3004 return this.where(":id").equals(keyOrObject).modify(modifications); 3005 } 3006 } 3007 }); // 3008 // 3009 // 3010 // Transaction Class 3011 // 3012 // 3013 // 3014 3015 function Transaction(mode, storeNames, dbschema, parent) { 3016 var _this = this; /// <summary> 3017 /// Transaction class. Represents a database transaction. All operations on db goes through a Transaction. 3018 /// </summary> 3019 /// <param name="mode" type="String">Any of "readwrite" or "readonly"</param> 3020 /// <param name="storeNames" type="Array">Array of table names to operate on</param> 3021 3022 3023 this.db = db; 3024 this.mode = mode; 3025 this.storeNames = storeNames; 3026 this.idbtrans = null; 3027 this.on = Events(this, "complete", "error", "abort"); 3028 this.parent = parent || null; 3029 this.active = true; 3030 this._reculock = 0; 3031 this._blockedFuncs = []; 3032 this._resolve = null; 3033 this._reject = null; 3034 this._waitingFor = null; 3035 this._waitingQueue = null; 3036 this._spinCount = 0; // Just for debugging waitFor() 3037 3038 this._completion = new Promise(function (resolve, reject) { 3039 _this._resolve = resolve; 3040 _this._reject = reject; 3041 }); 3042 3043 this._completion.then(function () { 3044 _this.active = false; 3045 3046 _this.on.complete.fire(); 3047 }, function (e) { 3048 var wasActive = _this.active; 3049 _this.active = false; 3050 3051 _this.on.error.fire(e); 3052 3053 _this.parent ? _this.parent._reject(e) : wasActive && _this.idbtrans && _this.idbtrans.abort(); 3054 return rejection(e); // Indicate we actually DO NOT catch this error. 3055 }); 3056 } 3057 3058 props(Transaction.prototype, { 3059 // 3060 // Transaction Protected Methods (not required by API users, but needed internally and eventually by dexie extensions) 3061 // 3062 _lock: function _lock() { 3063 assert(!PSD.global); // Locking and unlocking reuires to be within a PSD scope. 3064 // Temporary set all requests into a pending queue if they are called before database is ready. 3065 3066 ++this._reculock; // Recursive read/write lock pattern using PSD (Promise Specific Data) instead of TLS (Thread Local Storage) 3067 3068 if (this._reculock === 1 && !PSD.global) PSD.lockOwnerFor = this; 3069 return this; 3070 }, 3071 _unlock: function _unlock() { 3072 assert(!PSD.global); // Locking and unlocking reuires to be within a PSD scope. 3073 3074 if (--this._reculock === 0) { 3075 if (!PSD.global) PSD.lockOwnerFor = null; 3076 3077 while (this._blockedFuncs.length > 0 && !this._locked()) { 3078 var fnAndPSD = this._blockedFuncs.shift(); 3079 3080 try { 3081 usePSD(fnAndPSD[1], fnAndPSD[0]); 3082 } catch (e) {} 3083 } 3084 } 3085 3086 return this; 3087 }, 3088 _locked: function _locked() { 3089 // Checks if any write-lock is applied on this transaction. 3090 // To simplify the Dexie API for extension implementations, we support recursive locks. 3091 // This is accomplished by using "Promise Specific Data" (PSD). 3092 // PSD data is bound to a Promise and any child Promise emitted through then() or resolve( new Promise() ). 3093 // PSD is local to code executing on top of the call stacks of any of any code executed by Promise(): 3094 // * callback given to the Promise() constructor (function (resolve, reject){...}) 3095 // * callbacks given to then()/catch()/finally() methods (function (value){...}) 3096 // If creating a new independant Promise instance from within a Promise call stack, the new Promise will derive the PSD from the call stack of the parent Promise. 3097 // Derivation is done so that the inner PSD __proto__ points to the outer PSD. 3098 // PSD.lockOwnerFor will point to current transaction object if the currently executing PSD scope owns the lock. 3099 return this._reculock && PSD.lockOwnerFor !== this; 3100 }, 3101 create: function create(idbtrans) { 3102 var _this = this; 3103 3104 if (!this.mode) return this; 3105 assert(!this.idbtrans); 3106 3107 if (!idbtrans && !idbdb) { 3108 switch (dbOpenError && dbOpenError.name) { 3109 case "DatabaseClosedError": 3110 // Errors where it is no difference whether it was caused by the user operation or an earlier call to db.open() 3111 throw new exceptions.DatabaseClosed(dbOpenError); 3112 3113 case "MissingAPIError": 3114 // Errors where it is no difference whether it was caused by the user operation or an earlier call to db.open() 3115 throw new exceptions.MissingAPI(dbOpenError.message, dbOpenError); 3116 3117 default: 3118 // Make it clear that the user operation was not what caused the error - the error had occurred earlier on db.open()! 3119 throw new exceptions.OpenFailed(dbOpenError); 3120 } 3121 } 3122 3123 if (!this.active) throw new exceptions.TransactionInactive(); 3124 assert(this._completion._state === null); 3125 idbtrans = this.idbtrans = idbtrans || idbdb.transaction(safariMultiStoreFix(this.storeNames), this.mode); 3126 idbtrans.onerror = wrap(function (ev) { 3127 preventDefault(ev); // Prohibit default bubbling to window.error 3128 3129 _this._reject(idbtrans.error); 3130 }); 3131 idbtrans.onabort = wrap(function (ev) { 3132 preventDefault(ev); 3133 _this.active && _this._reject(new exceptions.Abort(idbtrans.error)); 3134 _this.active = false;
3135 3136 _this.on("abort").fire(ev); 3137 }); 3138 idbtrans.oncomplete = wrap(function () { 3139 _this.active = false; 3140 3141 _this._resolve(); 3142 }); 3143 return this; 3144 }, 3145 _promise: function _promise(mode, fn, bWriteLock) { 3146 var _this = this; 3147 3148 if (mode === READWRITE && this.mode !== READWRITE) return rejection(new exceptions.ReadOnly("Transaction is readonly")); 3149 if (!this.active) return rejection(new exceptions.TransactionInactive()); 3150 3151 if (this._locked()) { 3152 return new Promise(function (resolve, reject) { 3153 _this._blockedFuncs.push([function () { 3154 _this._promise(mode, fn, bWriteLock).then(resolve, reject); 3155 }, PSD]); 3156 }); 3157 } else if (bWriteLock) { 3158 return newScope(function () { 3159 var p = new Promise(function (resolve, reject) { 3160 _this._lock(); 3161 3162 var rv = fn(resolve, reject, _this); 3163 if (rv && rv.then) rv.then(resolve, reject); 3164 }); 3165 p.finally(function () { 3166 return _this._unlock(); 3167 }); 3168 p._lib = true; 3169 return p; 3170 }); 3171 } else { 3172 var p = new Promise(function (resolve, reject) { 3173 var rv = fn(resolve, reject, _this); 3174 if (rv && rv.then) rv.then(resolve, reject); 3175 }); 3176 p._lib = true; 3177 return p; 3178 } 3179 }, 3180 _root: function _root() { 3181 return this.parent ? this.parent._root() : this; 3182 }, 3183 waitFor: function waitFor(promise) { 3184 // Always operate on the root transaction (in case this is a sub stransaction) 3185 var root = this._root(); // For stability reasons, convert parameter to promise no matter what type is passed to waitFor(). 3186 // (We must be able to call .then() on it.) 3187 3188 3189 promise = Promise.resolve(promise); 3190 3191 if (root._waitingFor) { 3192 // Already called waitFor(). Wait for both to complete. 3193 root._waitingFor = root._waitingFor.then(function () { 3194 return promise; 3195 }); 3196 } else {
3197 // We're not in waiting state. Start waiting state. 3198 root._waitingFor = promise; 3199 root._waitingQueue = []; // Start interacting with indexedDB until promise completes: 3200 3201 var store = root.idbtrans.objectStore(root.storeNames[0]); 3202 3203 (function spin() { 3204 ++root._spinCount; // For debugging only 3205 3206 while (root._waitingQueue.length) { 3207 root._waitingQueue.shift()(); 3208 } 3209 3210 if (root._waitingFor) store.get(-Infinity).onsuccess = spin; 3211 })(); 3212 } 3213 3214 var currentWaitPromise = root._waitingFor; 3215 return new Promise(function (resolve, reject) { 3216 promise.then(function (res) { 3217 return root._waitingQueue.push(wrap(resolve.bind(null, res))); 3218 }, function (err) { 3219 return root._waitingQueue.push(wrap(reject.bind(null, err))); 3220 }).finally(function () { 3221 if (root._waitingFor === currentWaitPromise) { 3222 // No one added a wait after us. Safe to stop the spinning. 3223 root._waitingFor = null; 3224 } 3225 }); 3226 }); 3227 }, 3228 // 3229 // Transaction Public Properties and Methods 3230 // 3231 abort: function abort() { 3232 this.active && this._reject(new exceptions.Abort()); 3233 this.active = false; 3234 }, 3235 tables: { 3236 get: deprecated("Transaction.tables", function () { 3237 return allTables; 3238 }) 3239 }, 3240 table: function table(name) { 3241 var table = db.table(name); // Don't check that table is part of transaction. It must fail lazily! 3242 3243 return new Table(name, table.schema, this); 3244 } 3245 }); // 3246 // 3247 // 3248 // WhereClause 3249 // 3250 // 3251 // 3252 3253 function WhereClause(table, index, orCollection) {
3254 /// <param name="table" type="Table"></param> 3255 /// <param name="index" type="String" optional="true"></param> 3256 /// <param name="orCollection" type="Collection" optional="true"></param> 3257 this._ctx = { 3258 table: table, 3259 index: index === ":id" ? null : index, 3260 or: orCollection 3261 }; 3262 } 3263 3264 props(WhereClause.prototype, function () { 3265 // WhereClause private methods 3266 function fail(collectionOrWhereClause, err, T) { 3267 var collection = collectionOrWhereClause instanceof WhereClause ? new Collection(collectionOrWhereClause) : collectionOrWhereClause; 3268 collection._ctx.error = T ? new T(err) : new TypeError(err); 3269 return collection; 3270 } 3271 3272 function emptyCollection(whereClause) { 3273 return new Collection(whereClause, function () { 3274 return IDBKeyRange.only(""); 3275 }).limit(0); 3276 } 3277 3278 function upperFactory(dir) { 3279 return dir === "next" ? function (s) { 3280 return s.toUpperCase(); 3281 } : function (s) { 3282 return s.toLowerCase(); 3283 }; 3284 } 3285 3286 function lowerFactory(dir) { 3287 return dir === "next" ? function (s) { 3288 return s.toLowerCase(); 3289 } : function (s) { 3290 return s.toUpperCase(); 3291 }; 3292 } 3293 3294 function nextCasing(key, lowerKey, upperNeedle, lowerNeedle, cmp, dir) { 3295 var length = Math.min(key.length, lowerNeedle.length); 3296 var llp = -1; 3297 3298 for (var i = 0; i < length; ++i) { 3299 var lwrKeyChar = lowerKey[i]; 3300 3301 if (lwrKeyChar !== lowerNeedle[i]) { 3302 if (cmp(key[i], upperNeedle[i]) < 0) return key.substr(0, i) + upperNeedle[i] + upperNeedle.substr(i + 1); 3303 if (cmp(key[i], lowerNeedle[i]) < 0) return key.substr(0, i) + lowerNeedle[i] + upperNeedle.substr(i + 1); 3304 if (llp >= 0) return key.substr(0, llp) + lowerKey[llp] + upperNeedle.substr(llp + 1); 3305 return null; 3306 } 3307 3308 if (cmp(key[i], lwrKeyChar) < 0) llp = i; 3309 } 3310 3311 if (length < lowerNeedle.length && dir === "next") return key + upperNeedle.substr(key.length); 3312 if (length < key.length && dir === "prev") return key.substr(0, upperNeedle.length); 3313 return llp < 0 ? null : key.substr(0, llp) + lowerNeedle[llp] + upperNeedle.substr(llp + 1); 3314 } 3315 3316 function addIgnoreCaseAlgorithm(whereClause, match, needles, suffix) { 3317 /// <param name="needles" type="Array" elementType="String"></param> 3318 var upper, 3319 lower, 3320 compare, 3321 upperNeedles, 3322 lowerNeedles, 3323 direction, 3324 nextKeySuffix, 3325 needlesLen = needles.length; 3326 3327 if (!needles.every(function (s) { 3328 return typeof s === 'string'; 3329 })) { 3330 return fail(whereClause, STRING_EXPECTED); 3331 } 3332 3333 function initDirection(dir) { 3334 upper = upperFactory(dir); 3335 lower = lowerFactory(dir); 3336 compare = dir === "next" ? simpleCompare : simpleCompareReverse; 3337 var needleBounds = needles.map(function (needle) { 3338 return { 3339 lower: lower(needle), 3340 upper: upper(needle) 3341 }; 3342 }).sort(function (a, b) { 3343 return compare(a.lower, b.lower); 3344 }); 3345 upperNeedles = needleBounds.map(function (nb) { 3346 return nb.upper; 3347 }); 3348 lowerNeedles = needleBounds.map(function (nb) { 3349 return nb.lower; 3350 }); 3351 direction = dir; 3352 nextKeySuffix = dir === "next" ? "" : suffix; 3353 } 3354 3355 initDirection("next"); 3356 var c = new Collection(whereClause, function () { 3357 return IDBKeyRange.bound(upperNeedles[0], lowerNeedles[needlesLen - 1] + suffix); 3358 }); 3359 3360 c._ondirectionchange = function (direction) { 3361 // This event onlys occur before filter is called the first time. 3362 initDirection(direction); 3363 }; 3364 3365 var firstPossibleNeedle = 0; 3366 3367 c._addAlgorithm(function (cursor, advance, resolve) { 3368 /// <param name="cursor" type="IDBCursor"></param> 3369 /// <param name="advance" type="Function"></param> 3370 /// <param name="resolve" type="Function"></param> 3371 var key = cursor.key; 3372 if (typeof key !== 'string') return false;
3373 var lowerKey = lower(key); 3374 3375 if (match(lowerKey, lowerNeedles, firstPossibleNeedle)) { 3376 return true; 3377 } else { 3378 var lowestPossibleCasing = null; 3379 3380 for (var i = firstPossibleNeedle; i < needlesLen; ++i) { 3381 var casing = nextCasing(key, lowerKey, upperNeedles[i], lowerNeedles[i], compare, direction); 3382 if (casing === null && lowestPossibleCasing === null) firstPossibleNeedle = i + 1;else if (lowestPossibleCasing === null || compare(lowestPossibleCasing, casing) > 0) { 3383 lowestPossibleCasing = casing; 3384 } 3385 } 3386 3387 if (lowestPossibleCasing !== null) { 3388 advance(function () { 3389 cursor.continue(lowestPossibleCasing + nextKeySuffix); 3390 }); 3391 } else { 3392 advance(resolve); 3393 } 3394 3395 return false; 3396 } 3397 }); 3398 3399 return c; 3400 } // 3401 // WhereClause public methods 3402 // 3403 3404 3405 return { 3406 between: function between(lower, upper, includeLower, includeUpper) { 3407 /// <summary> 3408 /// Filter out records whose where-field lays between given lower and upper values. Applies to Strings, Numbers and Dates. 3409 /// </summary> 3410 /// <param name="lower"></param> 3411 /// <param name="upper"></param> 3412 /// <param name="includeLower" optional="true">Whether items that equals lower should be included. Default true.</param> 3413 /// <param name="includeUpper" optional="true">Whether items that equals upper should be included. Default false.</param> 3414 /// <returns type="Collection"></returns> 3415 includeLower = includeLower !== false; // Default to true 3416 3417 includeUpper = includeUpper === true; // Default to false 3418 3419 try { 3420 if (cmp(lower, upper) > 0 || cmp(lower, upper) === 0 && (includeLower || includeUpper) && !(includeLower && includeUpper)) return emptyCollection(this); // Workaround for idiotic W3C Specification that DataError must be thrown if lower > upper. The natural result would be to return an empty collection. 3421 3422 return new Collection(this, function () { 3423 return IDBKeyRange.bound(lower, upper, !includeLower, !includeUpper); 3424 }); 3425 } catch (e) { 3426 return fail(this, INVALID_KEY_ARGUMENT); 3427 } 3428 }, 3429 equals: function equals(value) { 3430 return new Collection(this, function () { 3431 return IDBKeyRange.only(value); 3432 }); 3433 }, 3434 above: function above(value) { 3435 return new Collection(this, function () { 3436 return IDBKeyRange.lowerBound(value, true); 3437 }); 3438 }, 3439 aboveOrEqual: function aboveOrEqual(value) { 3440 return new Collection(this, function () { 3441 return IDBKeyRange.lowerBound(value); 3442 }); 3443 }, 3444 below: function below(value) { 3445 return new Collection(this, function () { 3446 return IDBKeyRange.upperBound(value, true); 3447 }); 3448 }, 3449 belowOrEqual: function belowOrEqual(value) { 3450 return new Collection(this, function () { 3451 return IDBKeyRange.upperBound(value); 3452 }); 3453 }, 3454 startsWith: function startsWith(str) { 3455 /// <param name="str" type="String"></param> 3456 if (typeof str !== 'string') return fail(this, STRING_EXPECTED); 3457 return this.between(str, str + maxString, true, true); 3458 }, 3459 startsWithIgnoreCase: function startsWithIgnoreCase(str) { 3460 /// <param name="str" type="String"></param> 3461 if (str === "") return this.startsWith(str); 3462 return addIgnoreCaseAlgorithm(this, function (x, a) { 3463 return x.indexOf(a[0]) === 0; 3464 }, [str], maxString); 3465 }, 3466 equalsIgnoreCase: function equalsIgnoreCase(str) { 3467 /// <param name="str" type="String"></param> 3468 return addIgnoreCaseAlgorithm(this, function (x, a) { 3469 return x === a[0]; 3470 }, [str], ""); 3471 }, 3472 anyOfIgnoreCase: function anyOfIgnoreCase() { 3473 var set = getArrayOf.apply(NO_CHAR_ARRAY, arguments); 3474 if (set.length === 0) return emptyCollection(this); 3475 return addIgnoreCaseAlgorithm(this, function (x, a) { 3476 return a.indexOf(x) !== -1; 3477 }, set, ""); 3478 }, 3479 startsWithAnyOfIgnoreCase: function startsWithAnyOfIgnoreCase() { 3480 var set = getArrayOf.apply(NO_CHAR_ARRAY, arguments); 3481 if (set.length === 0) return emptyCollection(this); 3482 return addIgnoreCaseAlgorithm(this, function (x, a) { 3483 return a.some(function (n) { 3484 return x.indexOf(n) === 0; 3485 }); 3486 }, set, maxString); 3487 }, 3488 anyOf: function anyOf() { 3489 var set = getArrayOf.apply(NO_CHAR_ARRAY, arguments); 3490 var compare = ascending; 3491 3492 try {
3493 set.sort(compare); 3494 } catch (e) { 3495 return fail(this, INVALID_KEY_ARGUMENT); 3496 } 3497 3498 if (set.length === 0) return emptyCollection(this); 3499 var c = new Collection(this, function () { 3500 return IDBKeyRange.bound(set[0], set[set.length - 1]); 3501 }); 3502 3503 c._ondirectionchange = function (direction) { 3504 compare = direction === "next" ? ascending : descending; 3505 set.sort(compare); 3506 }; 3507 3508 var i = 0; 3509 3510 c._addAlgorithm(function (cursor, advance, resolve) { 3511 var key = cursor.key; 3512 3513 while (compare(key, set[i]) > 0) { 3514 // The cursor has passed beyond this key. Check next. 3515 ++i; 3516 3517 if (i === set.length) { 3518 // There is no next. Stop searching. 3519 advance(resolve); 3520 return false; 3521 } 3522 } 3523 3524 if (compare(key, set[i]) === 0) { 3525 // The current cursor value should be included and we should continue a single step in case next item has the same key or possibly our next key in set. 3526 return true; 3527 } else { 3528 // cursor.key not yet at set[i]. Forward cursor to the next key to hunt for. 3529 advance(function () { 3530 cursor.continue(set[i]); 3531 }); 3532 return false; 3533 } 3534 }); 3535 3536 return c; 3537 }, 3538 notEqual: function notEqual(value) { 3539 return this.inAnyRange([[minKey, value], [value, maxKey]], { 3540 includeLowers: false, 3541 includeUppers: false 3542 }); 3543 }, 3544 noneOf: function noneOf() { 3545 var set = getArrayOf.apply(NO_CHAR_ARRAY, arguments); 3546 if (set.length === 0) return new Collection(this); // Return entire collection. 3547 3548 try { 3549 set.sort(ascending); 3550 } catch (e) { 3551 return fail(this, INVALID_KEY_ARGUMENT); 3552 } // Transform ["a","b","c"] to a set of ranges for between/above/below: [[minKey,"a"], ["a","b"], ["b","c"], ["c",maxKey]] 3553 3554 3555 var ranges = set.reduce(function (res, val) { 3556 return res ? res.concat([[res[res.length - 1][1], val]]) : [[minKey, val]]; 3557 }, null); 3558 ranges.push([set[set.length - 1], maxKey]); 3559 return this.inAnyRange(ranges, { 3560 includeLowers: false, 3561 includeUppers: false 3562 }); 3563 }, 3564 3565 /** Filter out values withing given set of ranges. 3566 * Example, give children and elders a rebate of 50%: 3567 * 3568 * db.friends.where('age').inAnyRange([[0,18],[65,Infinity]]).modify({Rebate: 1/2}); 3569 * 3570 * @param {(string|number|Date|Array)[][]} ranges 3571 * @param {{includeLowers: boolean, includeUppers: boolean}} options 3572 */ 3573 inAnyRange: function inAnyRange(ranges, options) { 3574 if (ranges.length === 0) return emptyCollection(this); 3575 3576 if (!ranges.every(function (range) { 3577 return range[0] !== undefined && range[1] !== undefined && ascending(range[0], range[1]) <= 0; 3578 })) { 3579 return fail(this, "First argument to inAnyRange() must be an Array of two-value Arrays [lower,upper] where upper must not be lower than lower", exceptions.InvalidArgument); 3580 } 3581 3582 var includeLowers = !options || options.includeLowers !== false; // Default to true 3583 3584 var includeUppers = options && options.includeUppers === true; // Default to false 3585 3586 function addRange(ranges, newRange) { 3587 for (var i = 0, l = ranges.length; i < l; ++i) { 3588 var range = ranges[i]; 3589 3590 if (cmp(newRange[0], range[1]) < 0 && cmp(newRange[1], range[0]) > 0) { 3591 range[0] = min(range[0], newRange[0]); 3592 range[1] = max(range[1], newRange[1]); 3593 break; 3594 } 3595 } 3596 3597 if (i === l) ranges.push(newRange); 3598 return ranges; 3599 } 3600 3601 var sortDirection = ascending; 3602 3603 function rangeSorter(a, b) { 3604 return sortDirection(a[0], b[0]); 3605 } // Join overlapping ranges 3606 3607 3608 var set; 3609 3610 try { 3611 set = ranges.reduce(addRange, []);
3612 set.sort(rangeSorter); 3613 } catch (ex) { 3614 return fail(this, INVALID_KEY_ARGUMENT); 3615 } 3616 3617 var i = 0; 3618 var keyIsBeyondCurrentEntry = includeUppers ? function (key) { 3619 return ascending(key, set[i][1]) > 0; 3620 } : function (key) { 3621 return ascending(key, set[i][1]) >= 0; 3622 }; 3623 var keyIsBeforeCurrentEntry = includeLowers ? function (key) { 3624 return descending(key, set[i][0]) > 0; 3625 } : function (key) { 3626 return descending(key, set[i][0]) >= 0; 3627 }; 3628 3629 function keyWithinCurrentRange(key) { 3630 return !keyIsBeyondCurrentEntry(key) && !keyIsBeforeCurrentEntry(key); 3631 } 3632 3633 var checkKey = keyIsBeyondCurrentEntry; 3634 var c = new Collection(this, function () { 3635 return IDBKeyRange.bound(set[0][0], set[set.length - 1][1], !includeLowers, !includeUppers); 3636 }); 3637 3638 c._ondirectionchange = function (direction) { 3639 if (direction === "next") { 3640 checkKey = keyIsBeyondCurrentEntry; 3641 sortDirection = ascending; 3642 } else { 3643 checkKey = keyIsBeforeCurrentEntry; 3644 sortDirection = descending; 3645 } 3646 3647 set.sort(rangeSorter); 3648 }; 3649 3650 c._addAlgorithm(function (cursor, advance, resolve) { 3651 var key = cursor.key; 3652 3653 while (checkKey(key)) { 3654 // The cursor has passed beyond this key. Check next. 3655 ++i; 3656 3657 if (i === set.length) { 3658 // There is no next. Stop searching. 3659 advance(resolve); 3660 return false; 3661 } 3662 } 3663 3664 if (keyWithinCurrentRange(key)) { 3665 // The current cursor value should be included and we should continue a single step in case next item has the same key or possibly our next key in set. 3666 return true; 3667 } else if (cmp(key, set[i][1]) === 0 || cmp(key, set[i][0]) === 0) { 3668 // includeUpper or includeLower is false so keyWithinCurrentRange() returns false even though we are at range border. 3669 // Continue to next key but don't include this one. 3670 return false; 3671 } else { 3672 // cursor.key not yet at set[i]. Forward cursor to the next key to hunt for. 3673 advance(function () { 3674 if (sortDirection === ascending) cursor.continue(set[i][0]);else cursor.continue(set[i][1]); 3675 }); 3676 return false; 3677 } 3678 }); 3679 3680 return c; 3681 }, 3682 startsWithAnyOf: function startsWithAnyOf() { 3683 var set = getArrayOf.apply(NO_CHAR_ARRAY, arguments); 3684 3685 if (!set.every(function (s) { 3686 return typeof s === 'string'; 3687 })) { 3688 return fail(this, "startsWithAnyOf() only works with strings"); 3689 } 3690 3691 if (set.length === 0) return emptyCollection(this); 3692 return this.inAnyRange(set.map(function (str) { 3693 return [str, str + maxString]; 3694 })); 3695 } 3696 }; 3697 }); // 3698 // 3699 // 3700 // Collection Class 3701 // 3702 // 3703 // 3704 3705 function Collection(whereClause, keyRangeGenerator) { 3706 /// <summary> 3707 /// 3708 /// </summary> 3709 /// <param name="whereClause" type="WhereClause">Where clause instance</param> 3710 /// <param name="keyRangeGenerator" value="function(){ return IDBKeyRange.bound(0,1);}" optional="true"></param> 3711 var keyRange = null, 3712 error = null; 3713 if (keyRangeGenerator) try { 3714 keyRange = keyRangeGenerator(); 3715 } catch (ex) { 3716 error = ex; 3717 } 3718 var whereCtx = whereClause._ctx, 3719 table = whereCtx.table; 3720 this._ctx = { 3721 table: table, 3722 index: whereCtx.index, 3723 isPrimKey: !whereCtx.index || table.schema.primKey.keyPath && whereCtx.index === table.schema.primKey.name, 3724 range: keyRange, 3725 keysOnly: false, 3726 dir: "next", 3727 unique: "", 3728 algorithm: null, 3729 filter: null, 3730 replayFilter: null, 3731 justLimit: true, 3732 isMatch: null, 3733 offset: 0, 3734 limit: Infinity, 3735 error: error, 3736 or: whereCtx.or, 3737 valueMapper: table.hook.reading.fire 3738 }; 3739 } 3740 3741 function isPlainKeyRange(ctx, ignoreLimitFilter) { 3742 return !(ctx.filter || ctx.algorithm || ctx.or) && (ignoreLimitFilter ? ctx.justLimit : !ctx.replayFilter); 3743 } 3744 3745 props(Collection.prototype, function () { 3746 // 3747 // Collection Private Functions 3748 // 3749 function addFilter(ctx, fn) {
3750 ctx.filter = combine(ctx.filter, fn); 3751 } 3752 3753 function addReplayFilter(ctx, factory, isLimitFilter) { 3754 var curr = ctx.replayFilter; 3755 ctx.replayFilter = curr ? function () { 3756 return combine(curr(), factory()); 3757 } : factory; 3758 ctx.justLimit = isLimitFilter && !curr; 3759 } 3760 3761 function addMatchFilter(ctx, fn) { 3762 ctx.isMatch = combine(ctx.isMatch, fn); 3763 } 3764 /** @param ctx { 3765 * isPrimKey: boolean, 3766 * table: Table, 3767 * index: string 3768 * } 3769 * @param store IDBObjectStore 3770 **/ 3771 3772 3773 function getIndexOrStore(ctx, store) { 3774 if (ctx.isPrimKey) return store; 3775 var indexSpec = ctx.table.schema.idxByName[ctx.index]; 3776 if (!indexSpec) throw new exceptions.Schema("KeyPath " + ctx.index + " on object store " + store.name + " is not indexed"); 3777 return store.index(indexSpec.name); 3778 } 3779 /** @param ctx { 3780 * isPrimKey: boolean, 3781 * table: Table, 3782 * index: string, 3783 * keysOnly: boolean, 3784 * range?: IDBKeyRange, 3785 * dir: "next" | "prev" 3786 * } 3787 */ 3788 3789 3790 function openCursor(ctx, store) { 3791 var idxOrStore = getIndexOrStore(ctx, store); 3792 return ctx.keysOnly && 'openKeyCursor' in idxOrStore ? idxOrStore.openKeyCursor(ctx.range || null, ctx.dir + ctx.unique) : idxOrStore.openCursor(ctx.range || null, ctx.dir + ctx.unique); 3793 } 3794 3795 function iter(ctx, fn, resolve, reject, idbstore) { 3796 var filter = ctx.replayFilter ? combine(ctx.filter, ctx.replayFilter()) : ctx.filter; 3797 3798 if (!ctx.or) { 3799 iterate(openCursor(ctx, idbstore), combine(ctx.algorithm, filter), fn, resolve, reject, !ctx.keysOnly && ctx.valueMapper); 3800 } else (function () { 3801 var set = {}; 3802 var resolved = 0; 3803 3804 function resolveboth() { 3805 if (++resolved === 2) resolve(); // Seems like we just support or btwn max 2 expressions, but there are no limit because we do recursion. 3806 } 3807 3808 function union(item, cursor, advance) { 3809 if (!filter || filter(cursor, advance, resolveboth, reject)) { 3810 var primaryKey = cursor.primaryKey; 3811 var key = '' + primaryKey; 3812 if (key === '[object ArrayBuffer]') key = '' + new Uint8Array(primaryKey); 3813 3814 if (!hasOwn(set, key)) { 3815 set[key] = true; 3816 fn(item, cursor, advance); 3817 } 3818 } 3819 } 3820 3821 ctx.or._iterate(union, resolveboth, reject, idbstore); 3822 3823 iterate(openCursor(ctx, idbstore), ctx.algorithm, union, resolveboth, reject, !ctx.keysOnly && ctx.valueMapper); 3824 })(); 3825 } 3826 3827 return { 3828 // 3829 // Collection Protected Functions 3830 // 3831 _read: function _read(fn, cb) { 3832 var ctx = this._ctx; 3833 return ctx.error ? ctx.table._trans(null, rejection.bind(null, ctx.error)) : ctx.table._idbstore(READONLY, fn).then(cb); 3834 }, 3835 _write: function _write(fn) { 3836 var ctx = this._ctx; 3837 return ctx.error ? ctx.table._trans(null, rejection.bind(null, ctx.error)) : ctx.table._idbstore(READWRITE, fn, "locked"); // When doing write operations on collections, always lock the operation so that upcoming operations gets queued. 3838 }, 3839 _addAlgorithm: function _addAlgorithm(fn) { 3840 var ctx = this._ctx; 3841 ctx.algorithm = combine(ctx.algorithm, fn); 3842 }, 3843 _iterate: function _iterate(fn, resolve, reject, idbstore) { 3844 return iter(this._ctx, fn, resolve, reject, idbstore); 3845 }, 3846 clone: function clone(props$$1) { 3847 var rv = Object.create(this.constructor.prototype), 3848 ctx = Object.create(this._ctx); 3849 if (props$$1) extend(ctx, props$$1); 3850 rv._ctx = ctx; 3851 return rv; 3852 }, 3853 raw: function raw() { 3854 this._ctx.valueMapper = null; 3855 return this; 3856 }, 3857 // 3858 // Collection Public methods 3859 // 3860 each: function each(fn) { 3861 var ctx = this._ctx; 3862 return this._read(function (resolve, reject, idbstore) { 3863 iter(ctx, fn, resolve, reject, idbstore); 3864 }); 3865 }, 3866 count: function count(cb) { 3867 var ctx = this._ctx; 3868 3869 if (isPlainKeyRange(ctx, true)) { 3870 // This is a plain key range. We can use the count() method if the index. 3871 return this._read(function (resolve, reject, idbstore) { 3872 var idx = getIndexOrStore(ctx, idbstore); 3873 var req = ctx.range ? idx.count(ctx.range) : idx.count(); 3874 req.onerror = eventRejectHandler(reject); 3875 3876 req.onsuccess = function (e) { 3877 resolve(Math.min(e.target.result, ctx.limit)); 3878 }; 3879 }, cb); 3880 } else { 3881 // Algorithms, filters or expressions are applied. Need to count manually. 3882 var count = 0; 3883 return this._read(function (resolve, reject, idbstore) { 3884 iter(ctx, function () { 3885 ++count; 3886 return false;
3887 }, function () { 3888 resolve(count); 3889 }, reject, idbstore); 3890 }, cb); 3891 } 3892 }, 3893 sortBy: function sortBy(keyPath, cb) { 3894 /// <param name="keyPath" type="String"></param> 3895 var parts = keyPath.split('.').reverse(), 3896 lastPart = parts[0], 3897 lastIndex = parts.length - 1; 3898 3899 function getval(obj, i) { 3900 if (i) return getval(obj[parts[i]], i - 1); 3901 return obj[lastPart]; 3902 } 3903 3904 var order = this._ctx.dir === "next" ? 1 : -1; 3905 3906 function sorter(a, b) { 3907 var aVal = getval(a, lastIndex), 3908 bVal = getval(b, lastIndex); 3909 return aVal < bVal ? -order : aVal > bVal ? order : 0; 3910 } 3911 3912 return this.toArray(function (a) { 3913 return a.sort(sorter); 3914 }).then(cb); 3915 }, 3916 toArray: function toArray(cb) { 3917 var ctx = this._ctx; 3918 return this._read(function (resolve, reject, idbstore) { 3919 if (hasGetAll && ctx.dir === 'next' && isPlainKeyRange(ctx, true) && ctx.limit > 0) { 3920 // Special optimation if we could use IDBObjectStore.getAll() or 3921 // IDBKeyRange.getAll(): 3922 var readingHook = ctx.table.hook.reading.fire; 3923 var idxOrStore = getIndexOrStore(ctx, idbstore); 3924 var req = ctx.limit < Infinity ? idxOrStore.getAll(ctx.range, ctx.limit) : idxOrStore.getAll(ctx.range); 3925 req.onerror = eventRejectHandler(reject); 3926 req.onsuccess = readingHook === mirror ? eventSuccessHandler(resolve) : eventSuccessHandler(function (res) { 3927 try { 3928 resolve(res.map(readingHook)); 3929 } catch (e) { 3930 reject(e); 3931 } 3932 }); 3933 } else { 3934 // Getting array through a cursor. 3935 var a = []; 3936 iter(ctx, function (item) { 3937 a.push(item); 3938 }, function arrayComplete() { 3939 resolve(a); 3940 }, reject, idbstore); 3941 } 3942 }, cb); 3943 }, 3944 offset: function offset(_offset2) { 3945 var ctx = this._ctx; 3946 if (_offset2 <= 0) return this; 3947 ctx.offset += _offset2; // For count() 3948 3949 if (isPlainKeyRange(ctx)) { 3950 addReplayFilter(ctx, function () { 3951 var offsetLeft = _offset2; 3952 return function (cursor, advance) { 3953 if (offsetLeft === 0) return true; 3954 3955 if (offsetLeft === 1) { 3956 --offsetLeft; 3957 return false; 3958 } 3959 3960 advance(function () { 3961 cursor.advance(offsetLeft); 3962 offsetLeft = 0; 3963 }); 3964 return false; 3965 }; 3966 }); 3967 } else { 3968 addReplayFilter(ctx, function () { 3969 var offsetLeft = _offset2; 3970 return function () { 3971 return --offsetLeft < 0; 3972 }; 3973 }); 3974 } 3975 3976 return this; 3977 }, 3978 limit: function limit(numRows) { 3979 this._ctx.limit = Math.min(this._ctx.limit, numRows); // For count() 3980 3981 addReplayFilter(this._ctx, function () { 3982 var rowsLeft = numRows; 3983 return function (cursor, advance, resolve) { 3984 if (--rowsLeft <= 0) advance(resolve); // Stop after this item has been included 3985 3986 return rowsLeft >= 0; // If numRows is already below 0, return false because then 0 was passed to numRows initially. Otherwise we wouldnt come here. 3987 }; 3988 }, true); 3989 return this; 3990 }, 3991 until: function until(filterFunction, bIncludeStopEntry) { 3992 addFilter(this._ctx, function (cursor, advance, resolve) { 3993 if (filterFunction(cursor.value)) { 3994 advance(resolve); 3995 return bIncludeStopEntry; 3996 } else { 3997 return true; 3998 } 3999 }); 4000 return this; 4001 }, 4002 first: function first(cb) { 4003 return this.limit(1).toArray(function (a) { 4004 return a[0]; 4005 }).then(cb); 4006 }, 4007 last: function last(cb) { 4008 return this.reverse().first(cb); 4009 }, 4010 filter: function filter(filterFunction) { 4011 /// <param name="jsFunctionFilter" type="Function">function(val){return true/false}</param> 4012 addFilter(this._ctx, function (cursor) { 4013 return filterFunction(cursor.value); 4014 }); // match filters not used in Dexie.js but can be used by 3rd part libraries to test a 4015 // collection for a match without querying DB. Used by Dexie.Observable. 4016 4017 addMatchFilter(this._ctx, filterFunction); 4018 return this; 4019 }, 4020 and: function and(filterFunction) { 4021 return this.filter(filterFunction); 4022 }, 4023 or: function or(indexName) { 4024 return new WhereClause(this._ctx.table, indexName, this); 4025 }, 4026 reverse: function reverse() { 4027 this._ctx.dir = this._ctx.dir === "prev" ? "next" : "prev"; 4028 if (this._ondirectionchange) this._ondirectionchange(this._ctx.dir); 4029 return this; 4030 }, 4031 desc: function desc() { 4032 return this.reverse(); 4033 }, 4034 eachKey: function eachKey(cb) { 4035 var ctx = this._ctx; 4036 ctx.keysOnly = !ctx.isMatch; 4037 return this.each(function (val, cursor) { 4038 cb(cursor.key, cursor); 4039 }); 4040 }, 4041 eachUniqueKey: function eachUniqueKey(cb) { 4042 this._ctx.unique = "unique"; 4043 return this.eachKey(cb); 4044 }, 4045 eachPrimaryKey: function eachPrimaryKey(cb) { 4046 var ctx = this._ctx; 4047 ctx.keysOnly = !ctx.isMatch; 4048 return this.each(function (val, cursor) { 4049 cb(cursor.primaryKey, cursor); 4050 }); 4051 }, 4052 keys: function keys(cb) { 4053 var ctx = this._ctx; 4054 ctx.keysOnly = !ctx.isMatch; 4055 var a = []; 4056 return this.each(function (item, cursor) { 4057 a.push(cursor.key); 4058 }).then(function () { 4059 return a; 4060 }).then(cb); 4061 }, 4062 primaryKeys: function primaryKeys(cb) { 4063 var ctx = this._ctx; 4064 4065 if (hasGetAll && ctx.dir === 'next' && isPlainKeyRange(ctx, true) && ctx.limit > 0) { 4066 // Special optimation if we could use IDBObjectStore.getAllKeys() or 4067 // IDBKeyRange.getAllKeys(): 4068 return this._read(function (resolve, reject, idbstore) { 4069 var idxOrStore = getIndexOrStore(ctx, idbstore); 4070 var req = ctx.limit < Infinity ? idxOrStore.getAllKeys(ctx.range, ctx.limit) : idxOrStore.getAllKeys(ctx.range); 4071 req.onerror = eventRejectHandler(reject); 4072 req.onsuccess = eventSuccessHandler(resolve); 4073 }).then(cb); 4074 } 4075 4076 ctx.keysOnly = !ctx.isMatch; 4077 var a = []; 4078 return this.each(function (item, cursor) { 4079 a.push(cursor.primaryKey); 4080 }).then(function () { 4081 return a; 4082 }).then(cb); 4083 }, 4084 uniqueKeys: function uniqueKeys(cb) { 4085 this._ctx.unique = "unique"; 4086 return this.keys(cb); 4087 }, 4088 firstKey: function firstKey(cb) { 4089 return this.limit(1).keys(function (a) { 4090 return a[0]; 4091 }).then(cb); 4092 }, 4093 lastKey: function lastKey(cb) { 4094 return this.reverse().firstKey(cb); 4095 }, 4096 distinct: function distinct() { 4097 var ctx = this._ctx, 4098 idx = ctx.index && ctx.table.schema.idxByName[ctx.index]; 4099 if (!idx || !idx.multi) return this; // distinct() only makes differencies on multiEntry indexes. 4100 4101 var set = {}; 4102 addFilter(this._ctx, function (cursor) { 4103 var strKey = cursor.primaryKey.toString(); // Converts any Date to String, String to String, Number to String and Array to comma-separated string 4104 4105 var found = hasOwn(set, strKey); 4106 set[strKey] = true; 4107 return !found; 4108 }); 4109 return this; 4110 }, 4111 // 4112 // Methods that mutate storage 4113 // 4114 modify: function modify(changes) { 4115 var self = this, 4116 ctx = this._ctx, 4117 hook = ctx.table.hook, 4118 updatingHook = hook.updating.fire, 4119 deletingHook = hook.deleting.fire; 4120 return this._write(function (resolve, reject, idbstore, trans) { 4121 var modifyer; 4122 4123 if (typeof changes === 'function') { 4124 // Changes is a function that may update, add or delete propterties or even require a deletion the object itself (delete this.item) 4125 if (updatingHook === nop && deletingHook === nop) { 4126 // Noone cares about what is being changed. Just let the modifier function be the given argument as is. 4127 modifyer = changes; 4128 } else { 4129 // People want to know exactly what is being modified or deleted. 4130 // Let modifyer be a proxy function that finds out what changes the caller is actually doing 4131 // and call the hooks accordingly! 4132 modifyer = function modifyer(item) { 4133 var origItem = deepClone(item); // Clone the item first so we can compare laters. 4134 4135 if (changes.call(this, item, this) === false) return false; // Call the real modifyer function (If it returns false explicitely, it means it dont want to modify anyting on this object) 4136 4137 if (!hasOwn(this, "value")) { 4138 // The real modifyer function requests a deletion of the object. Inform the deletingHook that a deletion is taking place. 4139 deletingHook.call(this, this.primKey, item, trans); 4140 } else { 4141 // No deletion. Check what was changed 4142 var objectDiff = getObjectDiff(origItem, this.value); 4143 var additionalChanges = updatingHook.call(this, objectDiff, this.primKey, origItem, trans); 4144 4145 if (additionalChanges) { 4146 // Hook want to apply additional modifications. Make sure to fullfill the will of the hook. 4147 item = this.value;
4148 keys(additionalChanges).forEach(function (keyPath) { 4149 setByKeyPath(item, keyPath, additionalChanges[keyPath]); // Adding {keyPath: undefined} means that the keyPath should be deleted. Handled by setByKeyPath 4150 }); 4151 } 4152 } 4153 }; 4154 } 4155 } else if (updatingHook === nop) { 4156 // changes is a set of {keyPath: value} and no one is listening to the updating hook. 4157 var keyPaths = keys(changes); 4158 var numKeys = keyPaths.length; 4159 4160 modifyer = function modifyer(item) { 4161 var anythingModified = false; 4162 4163 for (var i = 0; i < numKeys; ++i) { 4164 var keyPath = keyPaths[i], 4165 val = changes[keyPath]; 4166 4167 if (getByKeyPath(item, keyPath) !== val) { 4168 setByKeyPath(item, keyPath, val); // Adding {keyPath: undefined} means that the keyPath should be deleted. Handled by setByKeyPath 4169 4170 anythingModified = true; 4171 } 4172 } 4173 4174 return anythingModified; 4175 }; 4176 } else { 4177 // changes is a set of {keyPath: value} and people are listening to the updating hook so we need to call it and 4178 // allow it to add additional modifications to make. 4179 var origChanges = changes; 4180 changes = shallowClone(origChanges); // Let's work with a clone of the changes keyPath/value set so that we can restore it in case a hook extends it. 4181 4182 modifyer = function modifyer(item) { 4183 var anythingModified = false; 4184 var additionalChanges = updatingHook.call(this, changes, this.primKey, deepClone(item), trans); 4185 if (additionalChanges) extend(changes, additionalChanges); 4186 keys(changes).forEach(function (keyPath) { 4187 var val = changes[keyPath]; 4188 4189 if (getByKeyPath(item, keyPath) !== val) { 4190 setByKeyPath(item, keyPath, val); 4191 anythingModified = true; 4192 } 4193 }); 4194 if (additionalChanges) changes = shallowClone(origChanges); // Restore original changes for next iteration 4195 4196 return anythingModified; 4197 }; 4198 } 4199 4200 var count = 0; 4201 var successCount = 0; 4202 var iterationComplete = false; 4203 var failures = []; 4204 var failKeys = []; 4205 var currentKey = null; 4206 4207 function modifyItem(item, cursor) { 4208 currentKey = cursor.primaryKey; 4209 var thisContext = { 4210 primKey: cursor.primaryKey, 4211 value: item, 4212 onsuccess: null, 4213 onerror: null 4214 }; 4215 4216 function onerror(e) { 4217 failures.push(e); 4218 failKeys.push(thisContext.primKey); 4219 checkFinished(); 4220 return true; // Catch these errors and let a final rejection decide whether or not to abort entire transaction 4221 } 4222 4223 if (modifyer.call(thisContext, item, thisContext) !== false) { 4224 var bDelete = !hasOwn(thisContext, "value"); 4225 ++count; 4226 tryCatch(function () { 4227 var req = bDelete ? cursor.delete() : cursor.update(thisContext.value); 4228 req._hookCtx = thisContext; 4229 req.onerror = hookedEventRejectHandler(onerror); 4230 req.onsuccess = hookedEventSuccessHandler(function () { 4231 ++successCount; 4232 checkFinished(); 4233 }); 4234 }, onerror); 4235 } else if (thisContext.onsuccess) { 4236 // Hook will expect either onerror or onsuccess to always be called! 4237 thisContext.onsuccess(thisContext.value); 4238 } 4239 } 4240 4241 function doReject(e) { 4242 if (e) { 4243 failures.push(e); 4244 failKeys.push(currentKey); 4245 } 4246 4247 return reject(new ModifyError("Error modifying one or more objects", failures, successCount, failKeys)); 4248 } 4249 4250 function checkFinished() { 4251 if (iterationComplete && successCount + failures.length === count) { 4252 if (failures.length > 0) doReject();else resolve(successCount); 4253 } 4254 } 4255 4256 self.clone().raw()._iterate(modifyItem, function () { 4257 iterationComplete = true; 4258 checkFinished(); 4259 }, doReject, idbstore); 4260 }); 4261 }, 4262 'delete': function _delete() { 4263 var _this = this; 4264 4265 var ctx = this._ctx, 4266 range = ctx.range, 4267 deletingHook = ctx.table.hook.deleting.fire, 4268 hasDeleteHook = deletingHook !== nop; 4269 4270 if (!hasDeleteHook && isPlainKeyRange(ctx) && (ctx.isPrimKey && !hangsOnDeleteLargeKeyRange || !range)) { 4271 // May use IDBObjectStore.delete(IDBKeyRange) in this case (Issue #208) 4272 // For chromium, this is the way most optimized version. 4273 // For IE/Edge, this could hang the indexedDB engine and make operating system instable 4274 // (https://gist.github.com/dfahlander/5a39328f029de18222cf2125d56c38f7) 4275 return this._write(function (resolve, reject, idbstore) { 4276 // Our API contract is to return a count of deleted items, so we have to count() before delete(). 4277 var onerror = eventRejectHandler(reject), 4278 countReq = range ? idbstore.count(range) : idbstore.count(); 4279 countReq.onerror = onerror; 4280 4281 countReq.onsuccess = function () { 4282 var count = countReq.result; 4283 tryCatch(function () { 4284 var delReq = range ? idbstore.delete(range) : idbstore.clear(); 4285 delReq.onerror = onerror; 4286 4287 delReq.onsuccess = function () { 4288 return resolve(count); 4289 }; 4290 }, function (err) { 4291 return reject(err); 4292 }); 4293 }; 4294 }); 4295 } // Default version to use when collection is not a vanilla IDBKeyRange on the primary key. 4296 // Divide into chunks to not starve RAM. 4297 // If has delete hook, we will have to collect not just keys but also objects, so it will use 4298 // more memory and need lower chunk size. 4299 4300 4301 var CHUNKSIZE = hasDeleteHook ? 2000 : 10000; 4302 return this._write(function (resolve, reject, idbstore, trans) { 4303 var totalCount = 0; // Clone collection and change its table and set a limit of CHUNKSIZE on the cloned Collection instance. 4304 4305 var collection = _this.clone({ 4306 keysOnly: !ctx.isMatch && !hasDeleteHook 4307 }) // load just keys (unless filter() or and() or deleteHook has subscribers) 4308 .distinct() // In case multiEntry is used, never delete same key twice because resulting count 4309 .limit(CHUNKSIZE).raw(); // Don't filter through reading-hooks (like mapped classes etc) 4310 4311 4312 var keysOrTuples = []; // We're gonna do things on as many chunks that are needed. 4313 // Use recursion of nextChunk function: 4314 4315 var nextChunk = function nextChunk() { 4316 return collection.each(hasDeleteHook ? function (val, cursor) { 4317 // Somebody subscribes to hook('deleting'). Collect all primary keys and their values, 4318 // so that the hook can be called with its values in bulkDelete(). 4319 keysOrTuples.push([cursor.primaryKey, cursor.value]); 4320 } : function (val, cursor) { 4321 // No one subscribes to hook('deleting'). Collect only primary keys: 4322 keysOrTuples.push(cursor.primaryKey); 4323 }).then(function () { 4324 // Chromium deletes faster when doing it in sort order. 4325 hasDeleteHook ? keysOrTuples.sort(function (a, b) { 4326 return ascending(a[0], b[0]); 4327 }) : keysOrTuples.sort(ascending); 4328 return _bulkDelete(idbstore, trans, keysOrTuples, hasDeleteHook, deletingHook); 4329 }).then(function () { 4330 var count = keysOrTuples.length; 4331 totalCount += count; 4332 keysOrTuples = []; 4333 return count < CHUNKSIZE ? totalCount : nextChunk(); 4334 }); 4335 }; 4336 4337 resolve(nextChunk()); 4338 }); 4339 } 4340 }; 4341 }); // 4342 // 4343 // 4344 // ------------------------- Help functions --------------------------- 4345 // 4346 // 4347 // 4348 4349 function lowerVersionFirst(a, b) { 4350 return a._cfg.version - b._cfg.version; 4351 } 4352 4353 function setApiOnPlace(objs, tableNames, dbschema) {
4354 tableNames.forEach(function (tableName) { 4355 var schema = dbschema[tableName]; 4356 objs.forEach(function (obj) { 4357 if (!(tableName in obj)) { 4358 if (obj === Transaction.prototype || obj instanceof Transaction) { 4359 // obj is a Transaction prototype (or prototype of a subclass to Transaction) 4360 // Make the API a getter that returns this.table(tableName) 4361 setProp(obj, tableName, { 4362 get: function get() { 4363 return this.table(tableName); 4364 } 4365 }); 4366 } else { 4367 // Table will not be bound to a transaction (will use Dexie.currentTransaction) 4368 obj[tableName] = new Table(tableName, schema); 4369 } 4370 } 4371 }); 4372 }); 4373 } 4374 4375 function removeTablesApi(objs) { 4376 objs.forEach(function (obj) { 4377 for (var key in obj) { 4378 if (obj[key] instanceof Table) delete obj[key]; 4379 } 4380 }); 4381 } 4382 4383 function iterate(req, filter, fn, resolve, reject, valueMapper) { 4384 // Apply valueMapper (hook('reading') or mappped class) 4385 var mappedFn = valueMapper ? function (x, c, a) { 4386 return fn(valueMapper(x), c, a); 4387 } : fn; // Wrap fn with PSD and microtick stuff from Promise. 4388 4389 var wrappedFn = wrap(mappedFn, reject); 4390 if (!req.onerror) req.onerror = eventRejectHandler(reject); 4391 4392 if (filter) { 4393 req.onsuccess = trycatcher(function filter_record() { 4394 var cursor = req.result; 4395 4396 if (cursor) { 4397 var c = function c() { 4398 cursor.continue(); 4399 }; 4400 4401 if (filter(cursor, function (advancer) { 4402 c = advancer; 4403 }, resolve, reject)) wrappedFn(cursor.value, cursor, function (advancer) { 4404 c = advancer; 4405 }); 4406 c(); 4407 } else { 4408 resolve(); 4409 } 4410 }, reject); 4411 } else { 4412 req.onsuccess = trycatcher(function filter_record() { 4413 var cursor = req.result; 4414 4415 if (cursor) { 4416 var c = function c() { 4417 cursor.continue(); 4418 }; 4419 4420 wrappedFn(cursor.value, cursor, function (advancer) { 4421 c = advancer; 4422 }); 4423 c(); 4424 } else { 4425 resolve(); 4426 } 4427 }, reject); 4428 } 4429 } 4430 4431 function parseIndexSyntax(indexes) { 4432 /// <param name="indexes" type="String"></param> 4433 /// <returns type="Array" elementType="IndexSpec"></returns> 4434 var rv = []; 4435 indexes.split(',').forEach(function (index) { 4436 index = index.trim(); 4437 var name = index.replace(/([&*]|\+\+)/g, ""); // Remove "&", "++" and "*" 4438 // Let keyPath of "[a+b]" be ["a","b"]: 4439 4440 var keyPath = /^\[/.test(name) ? name.match(/^\[(.*)\]$/)[1].split('+') : name; 4441 rv.push(new IndexSpec(name, keyPath || null, /\&/.test(index), /\*/.test(index), /\+\+/.test(index), isArray(keyPath), /\./.test(index))); 4442 }); 4443 return rv; 4444 } 4445 4446 function cmp(key1, key2) { 4447 return indexedDB.cmp(key1, key2); 4448 } 4449 4450 function min(a, b) { 4451 return cmp(a, b) < 0 ? a : b; 4452 } 4453 4454 function max(a, b) { 4455 return cmp(a, b) > 0 ? a : b; 4456 } 4457 4458 function ascending(a, b) { 4459 return indexedDB.cmp(a, b); 4460 } 4461 4462 function descending(a, b) { 4463 return indexedDB.cmp(b, a); 4464 } 4465 4466 function simpleCompare(a, b) { 4467 return a < b ? -1 : a === b ? 0 : 1; 4468 } 4469 4470 function simpleCompareReverse(a, b) { 4471 return a > b ? -1 : a === b ? 0 : 1; 4472 } 4473 4474 function combine(filter1, filter2) { 4475 return filter1 ? filter2 ? function () { 4476 return filter1.apply(this, arguments) && filter2.apply(this, arguments); 4477 } : filter1 : filter2; 4478 } 4479 4480 function readGlobalSchema() { 4481 db.verno = idbdb.version / 10; 4482 db._dbSchema = globalSchema = {}; 4483 dbStoreNames = slice(idbdb.objectStoreNames, 0); 4484 if (dbStoreNames.length === 0) return;
4484 // Database contains no stores. 4485 4486 var trans = idbdb.transaction(safariMultiStoreFix(dbStoreNames), 'readonly'); 4487 dbStoreNames.forEach(function (storeName) { 4488 var store = trans.objectStore(storeName), 4489 keyPath = store.keyPath, 4490 dotted = keyPath && typeof keyPath === 'string' && keyPath.indexOf('.') !== -1; 4491 var primKey = new IndexSpec(keyPath, keyPath || "", false, false, !!store.autoIncrement, keyPath && typeof keyPath !== 'string', dotted); 4492 var indexes = []; 4493 4494 for (var j = 0; j < store.indexNames.length; ++j) { 4495 var idbindex = store.index(store.indexNames[j]); 4496 keyPath = idbindex.keyPath; 4497 dotted = keyPath && typeof keyPath === 'string' && keyPath.indexOf('.') !== -1; 4498 var index = new IndexSpec(idbindex.name, keyPath, !!idbindex.unique, !!idbindex.multiEntry, false, keyPath && typeof keyPath !== 'string', dotted); 4499 indexes.push(index); 4500 } 4501 4502 globalSchema[storeName] = new TableSchema(storeName, primKey, indexes, {}); 4503 }); 4504 setApiOnPlace([allTables], keys(globalSchema), globalSchema); 4505 } 4506 4507 function adjustToExistingIndexNames(schema, idbtrans) { 4508 /// <summary> 4509 /// Issue #30 Problem with existing db - adjust to existing index names when migrating from non-dexie db 4510 /// </summary> 4511 /// <param name="schema" type="Object">Map between name and TableSchema</param> 4512 /// <param name="idbtrans" type="IDBTransaction"></param> 4513 var storeNames = idbtrans.db.objectStoreNames; 4514 4515 for (var i = 0; i < storeNames.length; ++i) { 4516 var storeName = storeNames[i]; 4517 var store = idbtrans.objectStore(storeName); 4518 hasGetAll = 'getAll' in store; 4519 4520 for (var j = 0; j < store.indexNames.length; ++j) { 4521 var indexName = store.indexNames[j]; 4522 var keyPath = store.index(indexName).keyPath; 4523 var dexieName = typeof keyPath === 'string' ? keyPath : "[" + slice(keyPath).join('+') + "]"; 4524 4525 if (schema[storeName]) { 4526 var indexSpec = schema[storeName].idxByName[dexieName]; 4527 if (indexSpec) indexSpec.name = indexName; 4528 } 4529 } 4530 } // Bug with getAll() on Safari ver<604 on Workers only, see discussion following PR #579 4531 4532 4533 if (/Safari/.test(navigator.userAgent) && !/(Chrome\/|Edge\/)/.test(navigator.userAgent) && _global.WorkerGlobalScope && _global instanceof _global.WorkerGlobalScope && [].concat(navigator.userAgent.match(/Safari\/(\d*)/))[1] < 604) { 4534 hasGetAll = false; 4535 } 4536 } 4537 4538 function fireOnBlocked(ev) { 4539 db.on("blocked").fire(ev); // Workaround (not fully*) for missing "versionchange" event in IE,Edge and Safari: 4540 4541 connections.filter(function (c) { 4542 return c.name === db.name && c !== db && !c._vcFired; 4543 }).map(function (c) { 4544 return c.on("versionchange").fire(ev); 4545 }); 4546 } 4547 4548 extend(this, { 4549 Collection: Collection, 4550 Table: Table, 4551 Transaction: Transaction, 4552 Version: Version, 4553 WhereClause: WhereClause 4554 }); 4555 init(); 4556 addons.forEach(function (fn) { 4557 fn(db); 4558 }); 4559 } 4560 4561 function parseType(type) { 4562 if (typeof type === 'function') { 4563 return new type(); 4564 } else if (isArray(type)) { 4565 return [parseType(type[0])]; 4566 } else if (type && babelHelpers.typeof(type) === 'object') { 4567 var rv = {}; 4568 applyStructure(rv, type); 4569 return rv; 4570 } else { 4571 return type; 4572 } 4573 } 4574 4575 function applyStructure(obj, structure) { 4576 keys(structure).forEach(function (member) { 4577 var value = parseType(structure[member]); 4578 obj[member] = value; 4579 }); 4580 return obj; 4581 } 4582 4583 function hookedEventSuccessHandler(resolve) { 4584 // wrap() is needed when calling hooks because the rare scenario of: 4585 // * hook does a db operation that fails immediately (IDB throws exception) 4586 // For calling db operations on correct transaction, wrap makes sure to set PSD correctly. 4587 // wrap() will also execute in a virtual tick. 4588 // * If not wrapped in a virtual tick, direct exception will launch a new physical tick. 4589 // * If this was the last event in the bulk, the promise will resolve after a physical tick 4590 // and the transaction will have committed already. 4591 // If no hook, the virtual tick will be executed in the reject()/resolve of the final promise, 4592 // because it is always marked with _lib = true when created using Transaction._promise(). 4593 return wrap(function (event) { 4594 var req = event.target, 4595 ctx = req._hookCtx, 4596 // Contains the hook error handler. Put here instead of closure to boost performance. 4597 result = ctx.value || req.result, 4598 // Pass the object value on updates. The result from ID
4598B is the primary key. 4599 hookSuccessHandler = ctx && ctx.onsuccess; 4600 hookSuccessHandler && hookSuccessHandler(result); 4601 resolve && resolve(result); 4602 }, resolve); 4603 } 4604 4605 function eventRejectHandler(reject) { 4606 return wrap(function (event) { 4607 preventDefault(event); 4608 reject(event.target.error); 4609 return false; 4610 }); 4611 } 4612 4613 function eventSuccessHandler(resolve) { 4614 return wrap(function (event) { 4615 resolve(event.target.result); 4616 }); 4617 } 4618 4619 function hookedEventRejectHandler(reject) { 4620 return wrap(function (event) { 4621 // See comment on hookedEventSuccessHandler() why wrap() is needed only when supporting hooks. 4622 var req = event.target, 4623 err = req.error, 4624 ctx = req._hookCtx, 4625 // Contains the hook error handler. Put here instead of closure to boost performance. 4626 hookErrorHandler = ctx && ctx.onerror; 4627 hookErrorHandler && hookErrorHandler(err); 4628 preventDefault(event); 4629 reject(err); 4630 return false; 4631 }); 4632 } 4633 4634 function preventDefault(event) { 4635 if (event.stopPropagation) event.stopPropagation(); 4636 if (event.preventDefault) event.preventDefault(); 4637 } 4638 4639 function awaitIterator(iterator) { 4640 var callNext = function callNext(result) { 4641 return iterator.next(result); 4642 }, 4643 doThrow = function doThrow(error) { 4644 return iterator.throw(error); 4645 }, 4646 onSuccess = step(callNext), 4647 onError = step(doThrow); 4648 4649 function step(getNext) { 4650 return function (val) { 4651 var next = getNext(val), 4652 value = next.value; 4653 return next.done ? value : !value || typeof value.then !== 'function' ? isArray(value) ? Promise.all(value).then(onSuccess, onError) : onSuccess(value) : value.then(onSuccess, onError); 4654 }; 4655 } 4656 4657 return step(callNext)(); 4658 } // 4659 // IndexSpec struct 4660 // 4661 4662 4663 function IndexSpec(name, keyPath, unique, multi, auto, compound, dotted) { 4664 /// <param name="name" type="String"></param> 4665 /// <param name="keyPath" type="String"></param> 4666 /// <param name="unique" type="Boolean"></param> 4667 /// <param name="multi" type="Boolean"></param> 4668 /// <param name="auto" type="Boolean"></param> 4669 /// <param name="compound" type="Boolean"></param> 4670 /// <param name="dotted" type="Boolean"></param> 4671 this.name = name; 4672 this.keyPath = keyPath; 4673 this.unique = unique; 4674 this.multi = multi; 4675 this.auto = auto; 4676 this.compound = compound; 4677 this.dotted = dotted; 4678 var keyPathSrc = typeof keyPath === 'string' ? keyPath : keyPath && '[' + [].join.call(keyPath, '+') + ']'; 4679 this.src = (unique ? '&' : '') + (multi ? '*' : '') + (auto ? "++" : "") + keyPathSrc; 4680 } // 4681 // TableSchema struct 4682 // 4683 4684 4685 function TableSchema(name, primKey, indexes, instanceTemplate) { 4686 /// <param name="name" type="String"></param> 4687 /// <param name="primKey" type="IndexSpec"></param> 4688 /// <param name="indexes" type="Array" elementType="IndexSpec"></param> 4689 /// <param name="instanceTemplate" type="Object"></param> 4690 this.name = name; 4691 this.primKey = primKey || new IndexSpec(); 4692 this.indexes = indexes || [new IndexSpec()]; 4693 this.instanceTemplate = instanceTemplate; 4694 this.mappedClass = null; 4695 this.idxByName = arrayToObject(indexes, function (index) { 4696 return [index.name, index]; 4697 }); 4698 } 4699 4700 function safariMultiStoreFix(storeNames) { 4701 return storeNames.length === 1 ? storeNames[0] : storeNames; 4702 } 4703 4704 function getNativeGetDatabaseNamesFn(indexedDB) { 4705 var fn = indexedDB && (indexedDB.getDatabaseNames || indexedDB.webkitGetDatabaseNames); 4706 return fn && fn.bind(indexedDB); 4707 } // Export Error classes 4708 4709 4710 props(Dexie, fullNameExceptions); // Dexie.XXXError = class XXXError {...}; 4711 // 4712 // Static methods and properties 4713 // 4714 4715 props(Dexie, { 4716 // 4717 // Static delete() method. 4718 // 4719 delete: function _delete(databaseName) { 4720 var db = new Dexie(databaseName), 4721 promise = db.delete(); 4722 4723 promise.onblocked = function (fn) { 4724 db.on("blocked", fn); 4725 return this; 4726 }; 4727 4728 return promise; 4729 }, 4730 // 4731 // Static exists() method. 4732 // 4733 exists: function exists(name) { 4734 return new Dexie(name).open().then(function (db) { 4735 db.close(); 4736 return true; 4737 }).catch(Dexie.NoSuchDatabaseError, function () { 4738 return false;
4739 }); 4740 }, 4741 // 4742 // Static method for retrieving a list of all existing databases at current host. 4743 // 4744 getDatabaseNames: function getDatabaseNames(cb) { 4745 var getDatabaseNames = getNativeGetDatabaseNamesFn(Dexie.dependencies.indexedDB); 4746 return getDatabaseNames ? new Promise(function (resolve, reject) { 4747 var req = getDatabaseNames(); 4748 4749 req.onsuccess = function (event) { 4750 resolve(slice(event.target.result, 0)); // Converst DOMStringList to Array<String> 4751 }; 4752 4753 req.onerror = eventRejectHandler(reject); 4754 }).then(cb) : dbNamesDB.dbnames.toCollection().primaryKeys(cb); 4755 }, 4756 defineClass: function defineClass() { 4757 // Default constructor able to copy given properties into this object. 4758 function Class(properties) { 4759 /// <param name="properties" type="Object" optional="true">Properties to initialize object with. 4760 /// </param> 4761 if (properties) extend(this, properties); 4762 } 4763 4764 return Class; 4765 }, 4766 applyStructure: applyStructure, 4767 ignoreTransaction: function ignoreTransaction(scopeFunc) { 4768 // In case caller is within a transaction but needs to create a separate transaction. 4769 // Example of usage: 4770 // 4771 // Let's say we have a logger function in our app. Other application-logic should be unaware of the 4772 // logger function and not need to include the 'logentries' table in all transaction it performs. 4773 // The logging should always be done in a separate transaction and not be dependant on the current 4774 // running transaction context. Then you could use Dexie.ignoreTransaction() to run code that starts a new transaction. 4775 // 4776 // Dexie.ignoreTransaction(function() { 4777 // db.logentries.add(newLogEntry); 4778 // }); 4779 // 4780 // Unless using Dexie.ignoreTransaction(), the above example would try to reuse the current transaction 4781 // in current Promise-scope. 4782 // 4783 // An alternative to Dexie.ignoreTransaction() would be setImmediate() or setTimeout(). The reason we still provide an 4784 // API for this because 4785 // 1) The intention of writing the statement could be unclear if using setImmediate() or setTimeout(). 4786 // 2) setTimeout() would wait unnescessary until firing. This is however not the case with setImmediate(). 4787 // 3) setImmediate() is not supported in the ES standard. 4788 // 4) You might want to keep other PSD state that was set in a parent PSD, such as PSD.letThrough. 4789 return PSD.trans ? usePSD(PSD.transless, scopeFunc) : // Use the closest parent that was non-transactional. 4790 scopeFunc(); // No need to change scope because there is no ongoing transaction. 4791 }, 4792 vip: function vip(fn) { 4793 // To be used by subscribers to the on('ready') event. 4794 // This will let caller through to access DB even when it is blocked while the db.ready() subscribers are firing. 4795 // This would have worked automatically if we were certain that the Provider was using Dexie.Promise for all asyncronic operations. The promise PSD 4796 // from the provider.connect() call would then be derived all the way to when provider would call localDatabase.applyChanges(). But since 4797 // the provider more likely is using non-promise async APIs or other thenable implementations, we cannot assume that. 4798 // Note that this method is only useful for on('ready') subscribers that is returning a Promise from the event. If not using vip() 4799 // the database could deadlock since it wont open until the returned Promise is resolved, and any non-VIPed operation started by 4800 // the caller will not resolve until database is opened. 4801 return newScope(function () { 4802 PSD.letThrough = true; // Make sure we are let through if still blocking db due to onready is firing. 4803 4804 return fn(); 4805 }); 4806 }, 4807 async: function async(generatorFn) { 4808 return function () { 4809 try { 4810 var rv = awaitIterator(generatorFn.apply(this, arguments)); 4811 if (!rv || typeof rv.then !== 'function') return Promise.resolve(rv); 4812 return rv; 4813 } catch (e) { 4814 return rejection(e); 4815 } 4816 }; 4817 }, 4818 spawn: function spawn(generatorFn, args, thiz) { 4819 try { 4820 var rv = awaitIterator(generatorFn.apply(thiz, args || [])); 4821 if (!rv || typeof rv.then !== 'function') return Promise.resolve(rv); 4822 return rv; 4823 } catch (e) { 4824 return rejection(e); 4825 } 4826 }, 4827 // Dexie.currentTransaction property 4828 currentTransaction: { 4829 get: function get() { 4830 return PSD.trans || null; 4831 } 4832 }, 4833 waitFor: function waitFor(promiseOrFunction, optionalTimeout) { 4834 // If a function is provided, invoke it and pass the returning value to Transaction.waitFor() 4835 var promise = Promise.resolve(typeof promiseOrFunction === 'function' ? Dexie.ignoreTransaction(promiseOrFunction) : promiseOrFunction).timeout(optionalTimeout || 60000); // Default the timeout to one minute. Caller may specify Infinity if required. 4836 // Run given promise on current transaction. If no current transaction, just return a Dexie promise based 4837 // on given value. 4838 4839 return PSD.trans ? PSD.trans.waitFor(promise) : promise; 4840 }, 4841 // Export our Promise implementation since it can be handy as a standalone Promise implementation 4842 Promise: Promise, 4843 // Dexie.debug proptery: 4844 // Dexie.debug = false 4845 // Dexie.debug = true 4846 // Dexie.debug = "dexie" - don't hide dexie's stack frames. 4847 debug: { 4848 get: function get() { 4849 return debug; 4850 }, 4851 set: function set(value) { 4852 setDebug(value, value === 'dexie' ? function () { 4853 return true; 4854 } : dexieStackFrameFilter); 4855 } 4856 }, 4857 // Export our derive/extend/override methodology 4858 derive: derive, 4859 extend: extend, 4860 props: props, 4861 override: override, 4862 // Export our Events() function - can be handy as a toolkit 4863 Events: Events, 4864 // Utilities 4865 getByKeyPath: getByKeyPath, 4866 setByKeyPath: setByKeyPath, 4867 delByKeyPath: delByKeyPath, 4868 shallowClone: shallowClone, 4869 deepClone: deepClone, 4870 getObjectDiff: getObjectDiff, 4871 asap: asap, 4872 maxKey: maxKey, 4873 minKey: minKey, 4874 // Addon registry 4875 addons: [], 4876 // Global DB connection list 4877 connections: connections, 4878 MultiModifyError: exceptions.Modify, 4879 errnames: errnames, 4880 // Export other static classes 4881 IndexSpec: IndexSpec, 4882 TableSchema: TableSchema, 4883 // 4884 // Dependencies 4885 // 4886 // These will automatically work in browsers with indexedDB support, or where an indexedDB polyfill has been included. 4887 // 4888 // In node.js, however, these properties must be set "manually" before instansiating a new Dexie(). 4889 // For node.js, you need to require indexeddb-js or similar and then set these deps. 4890 // 4891 dependencies: function () { 4892 try { 4893 return { 4894 // Required: 4895 indexedDB: _global.indexedDB || _global.mozIndexedDB || _global.webkitIndexedDB || _global.msIndexedDB, 4896 IDBKeyRange: _global.IDBKeyRange || _global.webkitIDBKeyRange 4897 }; 4898 } catch (e) { 4899 return { 4900 indexedDB: null, 4901 IDBKeyRange: null 4902 }; 4903 } 4904 }(), 4905 // API Version Number: Type Number, make sure to always set a version number that can be comparable correctly. Example: 0.9, 0.91, 0.92, 1.0, 1.01, 1.1, 1.2, 1.21, etc. 4906 semVer: DEXIE_VERSION, 4907 version: DEXIE_VERSION.split('.').map(function (n) { 4908 return parseInt(n); 4909 }).reduce(function (p, c, i) { 4910 return p + c / Math.pow(10, i * 2); 4911 }), 4912 // https://github.com/dfahlander/Dexie.js/issues/186 4913 // typescript compiler tsc in mode ts-->es5 & commonJS, will expect require() to return 4914 // x.default. Workaround: Set Dexie.default = Dexie. 4915 default: Dexie, 4916 // Make it possible to import {Dexie} (non-default import) 4917 // Reason 1: May switch to that in future. 4918 // Reason 2: We declare it both default and named exported in d.ts to make it possible 4919 // to let addons extend the Dexie interface with Typescript 2.1 (works only when explicitely 4920 // exporting the symbol, not just default exporting) 4921 Dexie: Dexie 4922 }); // Map DOMErrors and DOMExceptions to corresponding Dexie errors. May change in Dexie v2.0. 4923 4924 Promise.rejectionMapper = mapError; // Initialize dbNamesDB (won't ever be opened on chromium browsers') 4925 4926 dbNamesDB = new Dexie('__dbnames'); 4927 dbNamesDB.version(1).stores({ 4928 dbnames: 'name' 4929 }); 4930 4931 (function () { 4932 // Migrate from Dexie 1.x database names stored in localStorage: 4933 var DBNAMES = 'Dexie.DatabaseNames'; 4934 4935 try { 4936 if ((typeof localStorage === "undefined" ? "undefined" : babelHelpers.typeof(localStorage)) !== undefined && _global.document !== undefined) { 4937 // Have localStorage and is not executing in a worker. Lets migrate from Dexie 1.x. 4938 JSON.parse(localStorage.getItem(DBNAMES) || "[]").forEach(function (name) { 4939 return dbNamesDB.dbnames.put({ 4940 name: name 4941 }).catch(nop); 4942 }); 4943 localStorage.removeItem(DBNAMES); 4944 } 4945 } catch (_e) {} 4946 })(); 4947 4948 /** 4949 * Bitrix UI 4950 * IndexedDB manager (integration with Dexie.js) 4951 * 4952 * @package bitrix 4953 * @subpackage ui 4954 * @copyright 2001-2019 Bitrix 4955 * 4956 * @see https://dexie.org/docs/Tutorial/Getting-started 4957 */ 4958 4959 var Dexie$1 = function Dexie$$1(database) { 4960 babelHelpers.classCallCheck(this, Dexie$$1); 4961 return new Dexie(database); 4962 }; 4963 4964 Dexie$1.delete = Dexie.delete; 4965 Dexie$1.exists = Dexie.exists; 4966 Dexie$1.getDatabaseNames = Dexie.getDatabaseNames; 4967 Dexie$1.defineClass = Dexie.defineClass; 4968 Dexie$1.applyStructure = Dexie.applyStructure; 4969 Dexie$1.ignoreTransaction = Dexie.ignoreTransaction; 4970 Dexie$1.vip = Dexie.vip; 4971 Dexie$1.async = Dexie.async; 4972 Dexie$1.spawn = Dexie.spawn; 4973 Dexie$1.currentTransaction = Dexie.currentTransaction; 4974 Dexie$1.waitFor = Dexie.waitFor; 4975 Dexie$1.Promise = Dexie.Promise; 4976 Dexie$1.debug = Dexie.debug; 4977 Dexie$1.derive = Dexie.derive; 4978 Dexie$1.extend = Dexie.extend; 4979 Dexie$1.props = Dexie.props; 4980 Dexie$1.override = Dexie.override; 4981 Dexie$1.Events = Dexie.Events; 4982 Dexie$1.getByKeyPath = Dexie.getByKeyPath; 4983 Dexie$1.setByKeyPath = Dexie.setByKeyPath; 4984 Dexie$1.delByKeyPath = Dexie.delByKeyPath; 4985 Dexie$1.shallowClone = Dexie.shallowClone; 4986 Dexie$1.deepClone = Dexie.deepClone; 4987 Dexie$1.getObjectDiff = Dexie.getObjectDiff; 4988 Dexie$1.asap = Dexie.asap; 4989 Dexie$1.maxKey = Dexie.maxKey; 4990 Dexie$1.minKey = Dexie.minKey; 4991 Dexie$1.addons = Dexie.addons; 4992 Dexie$1.connections = Dexie.connections; 4993 Dexie$1.MultiModifyError = Dexie.MultiModifyError; 4994 Dexie$1.errnames = Dexie.errnames; 4995 Dexie$1.IndexSpec = Dexie.IndexSpec; 4996 Dexie$1.TableSchema = Dexie.TableSchema; 4997 Dexie$1.dependencies = Dexie.dependencies; 4998 Dexie$1.semVer = Dexie.semVer; 4999 Dexie$1.version = Dexie.version; 5000 Dexie$1.default = Dexie.default; 5001 Dexie$1.Dexie = Dexie.Dexie; 5002 5003 exports.Dexie = Dexie$1; 5004 5005}((this.BX = this.BX || {}))); 5006//# sourceMappingURL=dexie.bitrix.bundle.js.map
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.