diff --git a/extension/service.js b/extension/service.js index 781e1d0..9f0faaf 100644 --- a/extension/service.js +++ b/extension/service.js @@ -373,7 +373,10 @@ export default class Service extends EventTarget { console.log("🆕 创建新的 Service 实例..."); Service._instance = new Service(); } - Service.startup(); + if (!Service._startupRunning) { + Service._startupRunning = true; + Service.startup(); + } await Service._instance.start(); } return Service._instance; diff --git a/extension/vendor/dexie.js b/extension/vendor/dexie.js index 607c779..bb3c64d 100644 --- a/extension/vendor/dexie.js +++ b/extension/vendor/dexie.js @@ -4,18 +4,60 @@ * * By David Fahlander, david.fahlander@gmail.com * - * Version {version}, {date} + * Version 4.3.0, Sat Dec 20 2025 * - * http://dexie.org + * https://dexie.org * * Apache License Version 2.0, January 2004, http://www.apache.org/licenses/ */ +var extendStatics = function(d, b) { + extendStatics = Object.setPrototypeOf || + ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) || + function (d, b) { for (var p in b) if (Object.prototype.hasOwnProperty.call(b, p)) d[p] = b[p]; }; + return extendStatics(d, b); +}; +function __extends(d, b) { + if (typeof b !== "function" && b !== null) + throw new TypeError("Class extends value " + String(b) + " is not a constructor or null"); + extendStatics(d, b); + function __() { this.constructor = d; } + d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __()); +} +var __assign = function() { + __assign = Object.assign || function __assign(t) { + for (var s, i = 1, n = arguments.length; i < n; i++) { + s = arguments[i]; + for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p)) t[p] = s[p]; + } + return t; + }; + return __assign.apply(this, arguments); +}; +function __spreadArray(to, from, pack) { + if (pack || arguments.length === 2) for (var i = 0, l = from.length, ar; i < l; i++) { + if (ar || !(i in from)) { + if (!ar) ar = Array.prototype.slice.call(from, 0, i); + ar[i] = from[i]; + } + } + return to.concat(ar || Array.prototype.slice.call(from)); +} +typeof SuppressedError === "function" ? SuppressedError : function (error, suppressed, message) { + var e = new Error(message); + return e.name = "SuppressedError", e.error = error, e.suppressed = suppressed, e; +}; + +var _global = typeof globalThis !== 'undefined' ? globalThis : + typeof self !== 'undefined' ? self : + typeof window !== 'undefined' ? window : + global; + var keys = Object.keys; var isArray = Array.isArray; -var _global = typeof self !== 'undefined' ? self : - typeof window !== 'undefined' ? window : - global; +if (typeof Promise !== 'undefined' && !_global.Promise) { + _global.Promise = Promise; +} function extend(obj, extension) { if (typeof extension !== 'object') return obj; @@ -32,7 +74,7 @@ function hasOwn(obj, prop) { function props(proto, extension) { if (typeof extension === 'function') extension = extension(getProto(proto)); - keys(extension).forEach(function (key) { + (typeof Reflect === "undefined" ? keys : Reflect.ownKeys)(extension).forEach(function (key) { setProp(proto, key, extension[key]); }); } @@ -55,7 +97,8 @@ function derive(Child) { } var getOwnPropertyDescriptor = Object.getOwnPropertyDescriptor; function getPropertyDescriptor(obj, prop) { - var pd = getOwnPropertyDescriptor(obj, prop), proto; + var pd = getOwnPropertyDescriptor(obj, prop); + var proto; return pd || (proto = getProto(obj)) && getPropertyDescriptor(proto, prop); } var _slice = [].slice; @@ -69,18 +112,12 @@ function assert(b) { if (!b) throw new Error("Assertion Failed"); } -function asap(fn) { +function asap$1(fn) { if (_global.setImmediate) setImmediate(fn); else setTimeout(fn, 0); } - -/** Generate an object (hash map) based on given array. - * @param extractor Function taking an array item and its index and returning an array of 2 items ([key, value]) to - * instert on the resulting object for each item in the array. If this function returns a falsy value, the - * current item wont affect the resulting object. - */ function arrayToObject(array, extractor) { return array.reduce(function (result, item, i) { var nameAndValue = extractor(item, i); @@ -89,28 +126,9 @@ function arrayToObject(array, extractor) { return result; }, {}); } -function trycatcher(fn, reject) { - return function () { - try { - fn.apply(this, arguments); - } - catch (e) { - reject(e); - } - }; -} -function tryCatch(fn, onerror, args) { - try { - fn.apply(null, args); - } - catch (ex) { - onerror && onerror(ex); - } -} function getByKeyPath(obj, keyPath) { - // http://www.w3.org/TR/IndexedDB/#steps-for-extracting-a-key-from-a-value-using-a-key-path - if (hasOwn(obj, keyPath)) - return obj[keyPath]; // This line is moved from last to first for optimization purpose. + if (typeof keyPath === 'string' && hasOwn(obj, keyPath)) + return obj[keyPath]; if (!keyPath) return obj; if (typeof keyPath !== 'string') { @@ -124,7 +142,7 @@ function getByKeyPath(obj, keyPath) { var period = keyPath.indexOf('.'); if (period !== -1) { var innerObj = obj[keyPath.substr(0, period)]; - return innerObj === undefined ? undefined : getByKeyPath(innerObj, keyPath.substr(period + 1)); + return innerObj == null ? undefined : getByKeyPath(innerObj, keyPath.substr(period + 1)); } return undefined; } @@ -145,20 +163,28 @@ function setByKeyPath(obj, keyPath, value) { var currentKeyPath = keyPath.substr(0, period); var remainingKeyPath = keyPath.substr(period + 1); if (remainingKeyPath === "") - if (value === undefined) - delete obj[currentKeyPath]; + if (value === undefined) { + if (isArray(obj) && !isNaN(parseInt(currentKeyPath))) + obj.splice(currentKeyPath, 1); + else + delete obj[currentKeyPath]; + } else obj[currentKeyPath] = value; else { var innerObj = obj[currentKeyPath]; - if (!innerObj) + if (!innerObj || !hasOwn(obj, currentKeyPath)) innerObj = (obj[currentKeyPath] = {}); setByKeyPath(innerObj, remainingKeyPath, value); } } else { - if (value === undefined) - delete obj[keyPath]; + if (value === undefined) { + if (isArray(obj) && !isNaN(parseInt(keyPath))) + obj.splice(keyPath, 1); + else + delete obj[keyPath]; + } else obj[keyPath] = value; } @@ -184,73 +210,77 @@ var concat = [].concat; function flatten(a) { return concat.apply([], a); } -//https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API/Structured_clone_algorithm -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) { return ["Int", "Uint", "Float"].map(function (t) { return t + num + "Array"; }); }))).filter(function (t) { return _global[t]; }).map(function (t) { return _global[t]; }); +var intrinsicTypeNames = "BigUint64Array,BigInt64Array,Array,Boolean,String,Date,RegExp,Blob,File,FileList,FileSystemFileHandle,FileSystemDirectoryHandle,ArrayBuffer,DataView,Uint8ClampedArray,ImageBitmap,ImageData,Map,Set,CryptoKey" + .split(',').concat(flatten([8, 16, 32, 64].map(function (num) { return ["Int", "Uint", "Float"].map(function (t) { return t + num + "Array"; }); }))).filter(function (t) { return _global[t]; }); +var intrinsicTypes = new Set(intrinsicTypeNames.map(function (t) { return _global[t]; })); +function cloneSimpleObjectTree(o) { + var rv = {}; + for (var k in o) + if (hasOwn(o, k)) { + var v = o[k]; + rv[k] = !v || typeof v !== 'object' || intrinsicTypes.has(v.constructor) ? v : cloneSimpleObjectTree(v); + } + return rv; +} +function objectIsEmpty(o) { + for (var k in o) + if (hasOwn(o, k)) + return false; + return true; +} +var circularRefs = null; function deepClone(any) { - if (!any || typeof any !== 'object') - return any; - var rv; - if (isArray(any)) { + circularRefs = new WeakMap(); + var rv = innerDeepClone(any); + circularRefs = null; + return rv; +} +function innerDeepClone(x) { + if (!x || typeof x !== 'object') + return x; + var rv = circularRefs.get(x); + if (rv) + return rv; + if (isArray(x)) { rv = []; - for (var i = 0, l = any.length; i < l; ++i) { - rv.push(deepClone(any[i])); + circularRefs.set(x, rv); + for (var i = 0, l = x.length; i < l; ++i) { + rv.push(innerDeepClone(x[i])); } } - else if (intrinsicTypes.indexOf(any.constructor) >= 0) { - rv = any; + else if (intrinsicTypes.has(x.constructor)) { + rv = x; } else { - rv = any.constructor ? Object.create(any.constructor.prototype) : {}; - for (var prop in any) { - if (hasOwn(any, prop)) { - rv[prop] = deepClone(any[prop]); + var proto = getProto(x); + rv = proto === Object.prototype ? {} : Object.create(proto); + circularRefs.set(x, rv); + for (var prop in x) { + if (hasOwn(x, prop)) { + rv[prop] = innerDeepClone(x[prop]); } } } return rv; } -function getObjectDiff(a, b, rv, prfx) { - // Compares objects a and b and produces a diff object. - rv = rv || {}; - prfx = prfx || ''; - keys(a).forEach(function (prop) { - if (!hasOwn(b, prop)) - rv[prfx + prop] = undefined; // Property removed - else { - var ap = a[prop], bp = b[prop]; - if (typeof ap === 'object' && typeof bp === 'object' && - ap && bp && - // Now compare constructors are same (not equal because wont work in Safari) - ('' + ap.constructor) === ('' + bp.constructor)) - // Same type of object but its properties may have changed - getObjectDiff(ap, bp, rv, prfx + prop + "."); - else if (ap !== bp) - rv[prfx + prop] = b[prop]; // Primitive value changed - } - }); - keys(b).forEach(function (prop) { - if (!hasOwn(a, prop)) { - rv[prfx + prop] = b[prop]; // Property added - } - }); - return rv; +var toString = {}.toString; +function toStringTag(o) { + return toString.call(o).slice(8, -1); } -// If first argument is iterable or array-like, return it as an array -var iteratorSymbol = typeof Symbol !== 'undefined' && Symbol.iterator; -var getIteratorOf = iteratorSymbol ? function (x) { +var iteratorSymbol = typeof Symbol !== 'undefined' ? + Symbol.iterator : + '@@iterator'; +var getIteratorOf = typeof iteratorSymbol === "symbol" ? function (x) { var i; return x != null && (i = x[iteratorSymbol]) && i.apply(x); } : function () { return null; }; +function delArrayItem(a, x) { + var i = a.indexOf(x); + if (i >= 0) + a.splice(i, 1); + return i >= 0; +} var NO_CHAR_ARRAY = {}; -// Takes one or several arguments and returns an array based on the following criteras: -// * If several arguments provided, return arguments converted to an array in a way that -// still allows javascript engine to optimize the code. -// * If single argument is an array, return a clone of it. -// * If this-pointer equals NO_CHAR_ARRAY, don't accept strings as valid iterables as a special -// case to the two bullets below. -// * If single argument is an iterable, convert it to an array and return the resulting array. -// * If single argument is array-like (has length of type number), convert it to an array. function getArrayOf(arrayLike) { var i, a, x, it; if (arguments.length === 1) { @@ -281,54 +311,9 @@ function getArrayOf(arrayLike) { a[i] = arguments[i]; return a; } - -// By default, debug will be true only if platform is a web platform and its page is served from localhost. -// When debug = true, error's stacks will contain asyncronic long stacks. -var debug = typeof location !== 'undefined' && - // By default, use debug mode if served from localhost. - /^(http|https):\/\/(localhost|127\.0\.0\.1)/.test(location.href); -function setDebug(value, filter) { - debug = value; - libraryFilter = filter; -} -var libraryFilter = function () { return true; }; -var NEEDS_THROW_FOR_STACK = !new Error("").stack; -function getErrorWithStack() { - "use strict"; - if (NEEDS_THROW_FOR_STACK) - try { - // Doing something naughty in strict mode here to trigger a specific error - // that can be explicitely ignored in debugger's exception settings. - // If we'd just throw new Error() here, IE's debugger's exception settings - // will just consider it as "exception thrown by javascript code" which is - // something you wouldn't want it to ignore. - getErrorWithStack.arguments; - throw new Error(); // Fallback if above line don't throw. - } - catch (e) { - return e; - } - return new Error(); -} -function prettyStack(exception, numIgnoredFrames) { - var stack = exception.stack; - if (!stack) - return ""; - numIgnoredFrames = (numIgnoredFrames || 0); - if (stack.indexOf(exception.name) === 0) - numIgnoredFrames += (exception.name + exception.message).split('\n').length; - return stack.split('\n') - .slice(numIgnoredFrames) - .filter(libraryFilter) - .map(function (frame) { return "\n" + frame; }) - .join(''); -} -function deprecated(what, fn) { - return function () { - console.warn(what + " is deprecated. See https://github.com/dfahlander/Dexie.js/wiki/Deprecations. " + prettyStack(getErrorWithStack(), 1)); - return fn.apply(this, arguments); - }; -} +var isAsyncFunction = typeof Symbol !== 'undefined' + ? function (fn) { return fn[Symbol.toStringTag] === 'AsyncFunction'; } + : function () { return false; }; var dexieErrorNames = [ 'Modify', @@ -369,86 +354,52 @@ var defaultTexts = { VersionChanged: "Database version changed by other database connection", DatabaseClosed: "Database has been closed", Abort: "Transaction aborted", - TransactionInactive: "Transaction has already completed or failed" + TransactionInactive: "Transaction has already completed or failed", + MissingAPI: "IndexedDB API missing. Please visit https://tinyurl.com/y2uuvskb" }; -// -// DexieError - base class of all out exceptions. -// function DexieError(name, msg) { - // Reason we don't use ES6 classes is because: - // 1. It bloats transpiled code and increases size of minified code. - // 2. It doesn't give us much in this case. - // 3. It would require sub classes to call super(), which - // is not needed when deriving from Error. - this._e = getErrorWithStack(); this.name = name; this.message = msg; } derive(DexieError).from(Error).extend({ - stack: { - get: function () { - return this._stack || - (this._stack = this.name + ": " + this.message + prettyStack(this._e, 2)); - } - }, toString: function () { return this.name + ": " + this.message; } }); function getMultiErrorMessage(msg, failures) { - return msg + ". Errors: " + failures - .map(function (f) { return f.toString(); }) - .filter(function (v, i, s) { return s.indexOf(v) === i; }) // Only unique error strings + return msg + ". Errors: " + Object.keys(failures) + .map(function (key) { return failures[key].toString(); }) + .filter(function (v, i, s) { return s.indexOf(v) === i; }) .join('\n'); } -// -// ModifyError - thrown in Collection.modify() -// Specific constructor because it contains members failures and failedKeys. -// function ModifyError(msg, failures, successCount, failedKeys) { - this._e = getErrorWithStack(); this.failures = failures; this.failedKeys = failedKeys; this.successCount = successCount; + this.message = getMultiErrorMessage(msg, failures); } derive(ModifyError).from(DexieError); function BulkError(msg, failures) { - this._e = getErrorWithStack(); this.name = "BulkError"; - this.failures = failures; - this.message = getMultiErrorMessage(msg, failures); + this.failures = Object.keys(failures).map(function (pos) { return failures[pos]; }); + this.failuresByPos = failures; + this.message = getMultiErrorMessage(msg, this.failures); } derive(BulkError).from(DexieError); -// -// -// Dynamically generate error names and exception classes based -// on the names in errorList. -// -// -// Map of {ErrorName -> ErrorName + "Error"} var errnames = errorList.reduce(function (obj, name) { return (obj[name] = name + "Error", obj); }, {}); -// Need an alias for DexieError because we're gonna create subclasses with the same name. var BaseException = DexieError; -// Map of {ErrorName -> exception constructor} var exceptions = errorList.reduce(function (obj, name) { - // Let the name be "DexieError" because this name may - // be shown in call stack and when debugging. DexieError is - // the most true name because it derives from DexieError, - // and we cannot change Function.name programatically without - // dynamically create a Function object, which would be considered - // 'eval-evil'. var fullName = name + "Error"; function DexieError(msgOrInner, inner) { - this._e = getErrorWithStack(); this.name = fullName; if (!msgOrInner) { this.message = defaultTexts[name] || fullName; this.inner = null; } else if (typeof msgOrInner === 'string') { - this.message = msgOrInner; + this.message = "".concat(msgOrInner).concat(!inner ? '' : '\n ' + inner); this.inner = inner || null; } else if (typeof msgOrInner === 'object') { - this.message = msgOrInner.name + " " + msgOrInner.message; + this.message = "".concat(msgOrInner.name, " ").concat(msgOrInner.message); this.inner = msgOrInner; } } @@ -456,7 +407,6 @@ var exceptions = errorList.reduce(function (obj, name) { obj[name] = DexieError; return obj; }, {}); -// Use ECMASCRIPT standard exceptions where applicable: exceptions.Syntax = SyntaxError; exceptions.Type = TypeError; exceptions.Range = RangeError; @@ -469,7 +419,6 @@ function mapError(domError, message) { return domError; var rv = new exceptionMap[domError.name](message || domError.message, domError); if ("stack" in domError) { - // Derive stack from inner exception if it has a stack setProp(rv, "stack", { get: function () { return this.inner.stack; } }); @@ -488,8 +437,6 @@ fullNameExceptions.BulkError = BulkError; function nop() { } function mirror(val) { return val; } function pureFunctionChain(f1, f2) { - // Enables chained events that takes ONE argument and returns it to the next function in chain. - // This pattern is used in the hook("reading") event. if (f1 == null || f1 === mirror) return f2; return function (val) { @@ -503,16 +450,14 @@ function callBoth(on1, on2) { }; } function hookCreatingChain(f1, f2) { - // Enables chained events that takes several arguments and may modify first argument by making a modification and then returning the same instance. - // This pattern is used in the hook("creating") event. if (f1 === nop) return f2; return function () { var res = f1.apply(this, arguments); if (res !== undefined) arguments[0] = res; - var onsuccess = this.onsuccess, // In case event listener has set this.onsuccess - onerror = this.onerror; // In case event listener has set this.onerror + var onsuccess = this.onsuccess, + onerror = this.onerror; this.onsuccess = null; this.onerror = null; var res2 = f2.apply(this, arguments); @@ -528,8 +473,8 @@ function hookDeletingChain(f1, f2) { return f2; return function () { f1.apply(this, arguments); - var onsuccess = this.onsuccess, // In case event listener has set this.onsuccess - onerror = this.onerror; // In case event listener has set this.onerror + var onsuccess = this.onsuccess, + onerror = this.onerror; this.onsuccess = this.onerror = null; f2.apply(this, arguments); if (onsuccess) @@ -543,9 +488,9 @@ function hookUpdatingChain(f1, f2) { return f2; return function (modifications) { var res = f1.apply(this, arguments); - extend(modifications, res); // If f1 returns new modifications, extend caller's modifications with the result before calling next in chain. - var onsuccess = this.onsuccess, // In case event listener has set this.onsuccess - onerror = this.onerror; // In case event listener has set this.onerror + extend(modifications, res); + var onsuccess = this.onsuccess, + onerror = this.onerror; this.onsuccess = null; this.onerror = null; var res2 = f2.apply(this, arguments); @@ -567,7 +512,6 @@ function reverseStoppableEventChain(f1, f2) { return f1.apply(this, arguments); }; } - function promisableChain(f1, f2) { if (f1 === nop) return f2; @@ -585,253 +529,139 @@ function promisableChain(f1, f2) { }; } -/* - * Copyright (c) 2014-2017 David Fahlander - * Apache License Version 2.0, January 2004, http://www.apache.org/licenses/LICENSE-2.0 - */ -// -// Promise and Zone (PSD) for Dexie library -// -// I started out writing this Promise class by copying promise-light (https://github.com/taylorhakes/promise-light) by -// https://github.com/taylorhakes - an A+ and ECMASCRIPT 6 compliant Promise implementation. -// -// In previous versions this was fixed by not calling setTimeout when knowing that the resolve() or reject() came from another -// tick. In Dexie v1.4.0, I've rewritten the Promise class entirely. Just some fragments of promise-light is left. I use -// another strategy now that simplifies everything a lot: to always execute callbacks in a new micro-task, but have an own micro-task -// engine that is indexedDB compliant across all browsers. -// Promise class has also been optimized a lot with inspiration from bluebird - to avoid closures as much as possible. -// Also with inspiration from bluebird, asyncronic stacks in debug mode. -// -// Specific non-standard features of this Promise class: -// * Custom zone support (a.k.a. PSD) with ability to keep zones also when using native promises as well as -// native async / await. -// * Promise.follow() method built upon the custom zone engine, that allows user to track all promises created from current stack frame -// and below + all promises that those promises creates or awaits. -// * Detect any unhandled promise in a PSD-scope (PSD.onunhandled). -// -// David Fahlander, https://github.com/dfahlander -// -// Just a pointer that only this module knows about. -// Used in Promise constructor to emulate a private constructor. +var debug = typeof location !== 'undefined' && + /^(http|https):\/\/(localhost|127\.0\.0\.1)/.test(location.href); +function setDebug(value, filter) { + debug = value; +} + var INTERNAL = {}; -// Async stacks (long stacks) must not grow infinitely. -var LONG_STACKS_CLIP_LIMIT = 100; -var MAX_LONG_STACKS = 20; -var ZONE_ECHO_LIMIT = 7; -var nativePromiseInstanceAndProto = (function () { - try { - // Be able to patch native async functions - return new Function("let F=async ()=>{},p=F();return [p,Object.getPrototypeOf(p),Promise.resolve(),F.constructor];")(); - } - catch (e) { - var P = _global.Promise; - return P ? - [P.resolve(), P.prototype, P.resolve()] : - []; - } -})(); -var resolvedNativePromise = nativePromiseInstanceAndProto[0]; -var nativePromiseProto = nativePromiseInstanceAndProto[1]; -var resolvedGlobalPromise = nativePromiseInstanceAndProto[2]; -var nativePromiseThen = nativePromiseProto && nativePromiseProto.then; +var ZONE_ECHO_LIMIT = 100, _a$1 = typeof Promise === 'undefined' ? + [] : + (function () { + var globalP = Promise.resolve(); + if (typeof crypto === 'undefined' || !crypto.subtle) + return [globalP, getProto(globalP), globalP]; + var nativeP = crypto.subtle.digest("SHA-512", new Uint8Array([0])); + return [ + nativeP, + getProto(nativeP), + globalP + ]; + })(), resolvedNativePromise = _a$1[0], nativePromiseProto = _a$1[1], resolvedGlobalPromise = _a$1[2], nativePromiseThen = nativePromiseProto && nativePromiseProto.then; var NativePromise = resolvedNativePromise && resolvedNativePromise.constructor; -var AsyncFunction = nativePromiseInstanceAndProto[3]; var patchGlobalPromise = !!resolvedGlobalPromise; -var stack_being_generated = false; -/* The default function used only for the very first promise in a promise chain. - As soon as then promise is resolved or rejected, all next tasks will be executed in micro ticks - emulated in this module. For indexedDB compatibility, this means that every method needs to - execute at least one promise before doing an indexedDB operation. Dexie will always call - db.ready().then() for every operation to make sure the indexedDB event is started in an - indexedDB-compatible emulated micro task loop. -*/ -var schedulePhysicalTick = resolvedGlobalPromise ? - function () { resolvedGlobalPromise.then(physicalTick); } - : - _global.setImmediate ? - // setImmediate supported. Those modern platforms also supports Function.bind(). - setImmediate.bind(null, physicalTick) : - _global.MutationObserver ? - // MutationObserver supported - function () { - var hiddenDiv = document.createElement("div"); - (new MutationObserver(function () { - physicalTick(); - hiddenDiv = null; - })).observe(hiddenDiv, { attributes: true }); - hiddenDiv.setAttribute('i', '1'); - } : - // No support for setImmediate or MutationObserver. No worry, setTimeout is only called - // once time. Every tick that follows will be our emulated micro tick. - // Could have uses setTimeout.bind(null, 0, physicalTick) if it wasnt for that FF13 and below has a bug - function () { setTimeout(physicalTick, 0); }; -// Configurable through Promise.scheduler. -// Don't export because it would be unsafe to let unknown -// code call it unless they do try..catch within their callback. -// This function can be retrieved through getter of Promise.scheduler though, -// but users must not do Promise.scheduler = myFuncThatThrowsException -var asap$1 = function (callback, args) { +function schedulePhysicalTick() { + queueMicrotask(physicalTick); +} +var asap = function (callback, args) { microtickQueue.push([callback, args]); if (needsNewPhysicalTick) { schedulePhysicalTick(); needsNewPhysicalTick = false; } }; -var isOutsideMicroTick = true; -var needsNewPhysicalTick = true; -var unhandledErrors = []; -var rejectingErrors = []; -var currentFulfiller = null; -var rejectionMapper = mirror; // Remove in next major when removing error mapping of DOMErrors and DOMExceptions +var isOutsideMicroTick = true, +needsNewPhysicalTick = true, +unhandledErrors = [], +rejectingErrors = [], +rejectionMapper = mirror; var globalPSD = { id: 'global', global: true, ref: 0, unhandleds: [], - onunhandled: globalError, + onunhandled: nop, pgp: false, env: {}, - finalize: function () { - this.unhandleds.forEach(function (uh) { - try { - globalError(uh[0], uh[1]); - } - catch (e) { } - }); - } + finalize: nop }; var PSD = globalPSD; -var microtickQueue = []; // Callbacks to call in this or next physical tick. -var numScheduledCalls = 0; // Number of listener-calls left to do in this physical tick. -var tickFinalizers = []; // Finalizers to call when there are no more async calls scheduled within current physical tick. -function Promise(fn) { +var microtickQueue = []; +var numScheduledCalls = 0; +var tickFinalizers = []; +function DexiePromise(fn) { if (typeof this !== 'object') throw new TypeError('Promises must be constructed via new'); this._listeners = []; - this.onuncatched = nop; // Deprecate in next major. Not needed. Better to use global error handler. - // A library may set `promise._lib = true;` after promise is created to make resolve() or reject() - // execute the microtask engine implicitely within the call to resolve() or reject(). - // To remain A+ compliant, a library must only set `_lib=true` if it can guarantee that the stack - // only contains library code when calling resolve() or reject(). - // RULE OF THUMB: ONLY set _lib = true for promises explicitely resolving/rejecting directly from - // global scope (event handler, timer etc)! this._lib = false; - // Current async scope var psd = (this._PSD = PSD); - if (debug) { - this._stackHolder = getErrorWithStack(); - this._prev = null; - this._numPrev = 0; // Number of previous promises (for long stacks) - } if (typeof fn !== 'function') { if (fn !== INTERNAL) throw new TypeError('Not a function'); - // Private constructor (INTERNAL, state, value). - // Used internally by Promise.resolve() and Promise.reject(). this._state = arguments[1]; this._value = arguments[2]; if (this._state === false) - handleRejection(this, this._value); // Map error, set stack and addPossiblyUnhandledError(). + handleRejection(this, this._value); return; } - this._state = null; // null (=pending), false (=rejected) or true (=resolved) - this._value = null; // error or result - ++psd.ref; // Refcounting current scope + this._state = null; + this._value = null; + ++psd.ref; executePromiseTask(this, fn); } -// Prepare a property descriptor to put onto Promise.prototype.then var thenProp = { get: function () { var psd = PSD, microTaskId = totalEchoes; function then(onFulfilled, onRejected) { var _this = this; var possibleAwait = !psd.global && (psd !== PSD || microTaskId !== totalEchoes); - if (possibleAwait) - decrementExpectedAwaits(); - var rv = new Promise(function (resolve, reject) { - propagateToListener(_this, new Listener(nativeAwaitCompatibleWrap(onFulfilled, psd, possibleAwait), nativeAwaitCompatibleWrap(onRejected, psd, possibleAwait), resolve, reject, psd)); + var cleanup = possibleAwait && !decrementExpectedAwaits(); + var rv = new DexiePromise(function (resolve, reject) { + propagateToListener(_this, new Listener(nativeAwaitCompatibleWrap(onFulfilled, psd, possibleAwait, cleanup), nativeAwaitCompatibleWrap(onRejected, psd, possibleAwait, cleanup), resolve, reject, psd)); }); - debug && linkToPreviousPromise(rv, this); + if (this._consoleTask) + rv._consoleTask = this._consoleTask; return rv; } - then.prototype = INTERNAL; // For idempotense, see setter below. + then.prototype = INTERNAL; return then; }, - // Be idempotent and allow another framework (such as zone.js or another instance of a Dexie.Promise module) to replace Promise.prototype.then - // and when that framework wants to restore the original property, we must identify that and restore the original property descriptor. set: function (value) { setProp(this, 'then', value && value.prototype === INTERNAL ? - thenProp : // Restore to original property descriptor. + thenProp : { get: function () { - return value; // Getter returning provided value (behaves like value is just changed) + return value; }, - set: thenProp.set // Keep a setter that is prepared to restore original. + set: thenProp.set }); } }; -props(Promise.prototype, { +props(DexiePromise.prototype, { then: thenProp, _then: function (onFulfilled, onRejected) { - // A little tinier version of then() that don't have to create a resulting promise. propagateToListener(this, new Listener(null, null, onFulfilled, onRejected, PSD)); }, catch: function (onRejected) { if (arguments.length === 1) return this.then(null, onRejected); - // First argument is the Error type to catch var type = arguments[0], handler = arguments[1]; return typeof type === 'function' ? this.then(null, function (err) { - // Catching errors by its constructor type (similar to java / c++ / c#) - // Sample: promise.catch(TypeError, function (e) { ... }); return err instanceof type ? handler(err) : PromiseReject(err); }) : this.then(null, function (err) { - // Catching errors by the error.name property. Makes sense for indexedDB where error type - // is always DOMError but where e.name tells the actual error type. - // Sample: promise.catch('ConstraintError', function (e) { ... }); return err && err.name === type ? handler(err) : PromiseReject(err); }); }, finally: function (onFinally) { return this.then(function (value) { - onFinally(); - return value; + return DexiePromise.resolve(onFinally()).then(function () { return value; }); }, function (err) { - onFinally(); - return PromiseReject(err); + return DexiePromise.resolve(onFinally()).then(function () { return PromiseReject(err); }); }); }, - stack: { - get: function () { - if (this._stack) - return this._stack; - try { - stack_being_generated = true; - var stacks = getStack(this, [], MAX_LONG_STACKS); - var stack = stacks.join("\nFrom previous: "); - if (this._state !== null) - this._stack = stack; // Stack may be updated on reject. - return stack; - } - finally { - stack_being_generated = false; - } - } - }, timeout: function (ms, msg) { var _this = this; return ms < Infinity ? - new Promise(function (resolve, reject) { + new DexiePromise(function (resolve, reject) { var handle = setTimeout(function () { return reject(new exceptions.Timeout(msg)); }, ms); _this.then(resolve, reject).finally(clearTimeout.bind(null, handle)); }) : this; } }); 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. -// Environment globals snapshotted on leaving global zone + setProp(DexiePromise.prototype, Symbol.toStringTag, 'Dexie.Promise'); globalPSD.env = snapShot(); function Listener(onFulfilled, onRejected, resolve, reject, zone) { this.onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : null; @@ -840,16 +670,15 @@ function Listener(onFulfilled, onRejected, resolve, reject, zone) { this.reject = reject; this.psd = zone; } -// Promise Static Properties -props(Promise, { +props(DexiePromise, { all: function () { - var values = getArrayOf.apply(null, arguments) // Supports iterables, implicit arguments and array-like. - .map(onPossibleParallellAsync); // Handle parallell async/awaits - return new Promise(function (resolve, reject) { + var values = getArrayOf.apply(null, arguments) + .map(onPossibleParallellAsync); + return new DexiePromise(function (resolve, reject) { if (values.length === 0) resolve([]); var remaining = values.length; - values.forEach(function (a, i) { return Promise.resolve(a).then(function (x) { + values.forEach(function (a, i) { return DexiePromise.resolve(a).then(function (x) { values[i] = x; if (!--remaining) resolve(values); @@ -857,50 +686,45 @@ props(Promise, { }); }, resolve: function (value) { - if (value instanceof Promise) + if (value instanceof DexiePromise) return value; if (value && typeof value.then === 'function') - return new Promise(function (resolve, reject) { + return new DexiePromise(function (resolve, reject) { value.then(resolve, reject); }); - var rv = new Promise(INTERNAL, true, value); - linkToPreviousPromise(rv, currentFulfiller); + var rv = new DexiePromise(INTERNAL, true, value); return rv; }, reject: PromiseReject, race: function () { var values = getArrayOf.apply(null, arguments).map(onPossibleParallellAsync); - return new Promise(function (resolve, reject) { - values.map(function (value) { return Promise.resolve(value).then(resolve, reject); }); + return new DexiePromise(function (resolve, reject) { + values.map(function (value) { return DexiePromise.resolve(value).then(resolve, reject); }); }); }, PSD: { get: function () { return PSD; }, set: function (value) { return PSD = value; } }, - //totalEchoes: {get: ()=>totalEchoes}, - //task: {get: ()=>task}, + totalEchoes: { get: function () { return totalEchoes; } }, newPSD: newScope, usePSD: usePSD, scheduler: { - get: function () { return asap$1; }, - set: function (value) { asap$1 = value; } + get: function () { return asap; }, + set: function (value) { asap = value; } }, rejectionMapper: { get: function () { return rejectionMapper; }, - set: function (value) { rejectionMapper = value; } // Map reject failures + set: function (value) { rejectionMapper = value; } }, follow: function (fn, zoneProps) { - return new Promise(function (resolve, reject) { + return new DexiePromise(function (resolve, reject) { return newScope(function (resolve, reject) { var psd = PSD; - psd.unhandleds = []; // For unhandled standard- or 3rd party Promises. Checked at psd.finalize() - psd.onunhandled = reject; // Triggered directly on unhandled promises of this library. + psd.unhandleds = []; + psd.onunhandled = reject; psd.finalize = callBoth(function () { var _this = this; - // Unhandled standard or 3rd part promises are put in PSD.unhandleds and - // examined upon scope completion while unhandled rejections in this Promise - // will trigger directly through psd.onunhandled run_at_end_of_this_or_next_physical_tick(function () { _this.unhandleds.length === 0 ? resolve() : reject(_this.unhandleds[0]); }); @@ -910,25 +734,48 @@ props(Promise, { }); } }); -/** -* Take a potentially misbehaving resolver function and make sure -* onFulfilled and onRejected are only called once. -* -* Makes no guarantees about asynchrony. -*/ +if (NativePromise) { + if (NativePromise.allSettled) + setProp(DexiePromise, "allSettled", function () { + var possiblePromises = getArrayOf.apply(null, arguments).map(onPossibleParallellAsync); + return new DexiePromise(function (resolve) { + if (possiblePromises.length === 0) + resolve([]); + var remaining = possiblePromises.length; + var results = new Array(remaining); + possiblePromises.forEach(function (p, i) { return DexiePromise.resolve(p).then(function (value) { return results[i] = { status: "fulfilled", value: value }; }, function (reason) { return results[i] = { status: "rejected", reason: reason }; }) + .then(function () { return --remaining || resolve(results); }); }); + }); + }); + if (NativePromise.any && typeof AggregateError !== 'undefined') + setProp(DexiePromise, "any", function () { + var possiblePromises = getArrayOf.apply(null, arguments).map(onPossibleParallellAsync); + return new DexiePromise(function (resolve, reject) { + if (possiblePromises.length === 0) + reject(new AggregateError([])); + var remaining = possiblePromises.length; + var failures = new Array(remaining); + possiblePromises.forEach(function (p, i) { return DexiePromise.resolve(p).then(function (value) { return resolve(value); }, function (failure) { + failures[i] = failure; + if (!--remaining) + reject(new AggregateError(failures)); + }); }); + }); + }); + if (NativePromise.withResolvers) + DexiePromise.withResolvers = NativePromise.withResolvers; +} function executePromiseTask(promise, fn) { - // Promise Resolution Procedure: - // https://github.com/promises-aplus/promises-spec#the-promise-resolution-procedure try { fn(function (value) { if (promise._state !== null) - return; // Already settled + return; if (value === promise) throw new TypeError('A promise cannot be resolved with itself.'); var shouldExecuteTick = promise._lib && beginMicroTickScope(); if (value && typeof value.then === 'function') { executePromiseTask(promise, function (resolve, reject) { - value instanceof Promise ? + value instanceof DexiePromise ? value._then(resolve, reject) : value.then(resolve, reject); }); @@ -940,7 +787,7 @@ function executePromiseTask(promise, fn) { } if (shouldExecuteTick) endMicroTickScope(); - }, handleRejection.bind(null, promise)); // If Function.bind is not supported. Exception is handled in catch below + }, handleRejection.bind(null, promise)); } catch (ex) { handleRejection(promise, ex); @@ -954,44 +801,24 @@ function handleRejection(promise, reason) { reason = rejectionMapper(reason); promise._state = false; promise._value = reason; - debug && reason !== null && typeof reason === 'object' && !reason._promise && tryCatch(function () { - var origProp = getPropertyDescriptor(reason, "stack"); - reason._promise = promise; - setProp(reason, "stack", { - get: function () { - return stack_being_generated ? - origProp && (origProp.get ? - origProp.get.apply(reason) : - origProp.value) : - promise.stack; - } - }); - }); - // Add the failure to a list of possibly uncaught errors addPossiblyUnhandledError(promise); propagateAllListeners(promise); if (shouldExecuteTick) endMicroTickScope(); } function propagateAllListeners(promise) { - //debug && linkToPreviousPromise(promise); var listeners = promise._listeners; promise._listeners = []; for (var i = 0, len = listeners.length; i < len; ++i) { propagateToListener(promise, listeners[i]); } var psd = promise._PSD; - --psd.ref || psd.finalize(); // if psd.ref reaches zero, call psd.finalize(); + --psd.ref || psd.finalize(); if (numScheduledCalls === 0) { - // If numScheduledCalls is 0, it means that our stack is not in a callback of a scheduled call, - // and that no deferreds where listening to this rejection or success. - // Since there is a risk that our stack can contain application code that may - // do stuff after this code is finished that may generate new calls, we cannot - // call finalizers here. ++numScheduledCalls; - asap$1(function () { + asap(function () { if (--numScheduledCalls === 0) - finalizePhysicalTick(); // Will detect unhandled errors + finalizePhysicalTick(); }, []); } } @@ -1002,85 +829,36 @@ function propagateToListener(promise, listener) { } var cb = promise._state ? listener.onFulfilled : listener.onRejected; if (cb === null) { - // 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() return (promise._state ? listener.resolve : listener.reject)(promise._value); } ++listener.psd.ref; ++numScheduledCalls; - asap$1(callListener, [cb, promise, listener]); + asap(callListener, [cb, promise, listener]); } function callListener(cb, promise, listener) { try { - // Set static variable currentFulfiller to the promise that is being fullfilled, - // so that we connect the chain of promises (for long stacks support) - currentFulfiller = promise; - // Call callback and resolve our listener with it's return value. var ret, value = promise._value; - if (promise._state) { - // cb is onResolved - ret = cb(value); - } - else { - // cb is onRejected - if (rejectingErrors.length) - rejectingErrors = []; - ret = cb(value); - if (rejectingErrors.indexOf(value) === -1) - markErrorAsHandled(promise); // Callback didnt do Promise.reject(err) nor reject(err) onto another promise. + if (!promise._state && rejectingErrors.length) + rejectingErrors = []; + ret = debug && promise._consoleTask ? promise._consoleTask.run(function () { return cb(value); }) : cb(value); + if (!promise._state && rejectingErrors.indexOf(value) === -1) { + markErrorAsHandled(promise); } listener.resolve(ret); } catch (e) { - // Exception thrown in callback. Reject our listener. listener.reject(e); } finally { - // Restore env and currentFulfiller. - currentFulfiller = null; if (--numScheduledCalls === 0) finalizePhysicalTick(); --listener.psd.ref || listener.psd.finalize(); } } -function getStack(promise, stacks, limit) { - if (stacks.length === limit) - return stacks; - var stack = ""; - if (promise._state === false) { - var failure = promise._value, errorName, message; - if (failure != null) { - errorName = failure.name || "Error"; - message = failure.message || failure; - stack = prettyStack(failure, 0); - } - else { - errorName = failure; // If error is undefined or null, show that. - message = ""; - } - stacks.push(errorName + (message ? ": " + message : "") + stack); - } - if (debug) { - stack = prettyStack(promise._stackHolder, 2); - if (stack && stacks.indexOf(stack) === -1) - stacks.push(stack); - if (promise._prev) - getStack(promise._prev, stacks, limit); - } - return stacks; -} -function linkToPreviousPromise(promise, prev) { - // Support long stacks by linking to previous completed promise. - var numPrev = prev ? prev._numPrev + 1 : 0; - if (numPrev < LONG_STACKS_CLIP_LIMIT) { - promise._prev = prev; - promise._numPrev = numPrev; - } -} -/* The callback to schedule with setImmediate() or setTimeout(). - It runs a virtual microtick and executes any callback registered in microtickQueue. - */ function physicalTick() { - beginMicroTickScope() && endMicroTickScope(); + usePSD(globalPSD, function () { + beginMicroTickScope() && endMicroTickScope(); + }); } function beginMicroTickScope() { var wasRootExec = isOutsideMicroTick; @@ -1088,14 +866,6 @@ function beginMicroTickScope() { needsNewPhysicalTick = false; return wasRootExec; } -/* Executes micro-ticks without doing try..catch. - This can be possible because we only use this internally and - the registered functions are exception-safe (they do try..catch - internally before calling any external method). If registering - functions in the microtickQueue that are not exception-safe, this - would destroy the framework and make it instable. So we don't export - our asap method. -*/ function endMicroTickScope() { var callbacks, i, l; do { @@ -1118,7 +888,7 @@ function finalizePhysicalTick() { unhandledErrs.forEach(function (p) { p._PSD.onunhandled.call(null, p._value, p); }); - var finalizers = tickFinalizers.slice(0); // Clone first because finalizer may remove itself from list. + var finalizers = tickFinalizers.slice(0); var i = finalizers.length; while (i) finalizers[--i](); @@ -1130,33 +900,25 @@ function run_at_end_of_this_or_next_physical_tick(fn) { } tickFinalizers.push(finalizer); ++numScheduledCalls; - asap$1(function () { + asap(function () { if (--numScheduledCalls === 0) finalizePhysicalTick(); }, []); } function addPossiblyUnhandledError(promise) { - // Only add to unhandledErrors if not already there. The first one to add to this list - // will be upon the first rejection so that the root cause (first promise in the - // rejection chain) is the one listed. if (!unhandledErrors.some(function (p) { return p._value === promise._value; })) unhandledErrors.push(promise); } function markErrorAsHandled(promise) { - // Called when a reject handled is actually being called. - // Search in unhandledErrors for any promise whos _value is this promise_value (list - // contains only rejected promises, and only one item per error) var i = unhandledErrors.length; while (i) if (unhandledErrors[--i]._value === promise._value) { - // Found a promise that failed with this same error object pointer, - // Remove that since there is a listener that actually takes care of it. unhandledErrors.splice(i, 1); return; } } function PromiseReject(reason) { - return new Promise(INTERNAL, false, reason); + return new DexiePromise(INTERNAL, false, reason); } function wrap(fn, errorCatcher) { var psd = PSD; @@ -1176,39 +938,31 @@ function wrap(fn, errorCatcher) { } }; } -// -// variables used for native await support -// -var task = { awaits: 0, echoes: 0, id: 0 }; // The ongoing macro-task when using zone-echoing. -var taskCounter = 0; // ID counter for macro tasks. -var zoneStack = []; // Stack of left zones to restore asynchronically. -var zoneEchoes = 0; // zoneEchoes is a must in order to persist zones between native await expressions. -var totalEchoes = 0; // ID counter for micro-tasks. Used to detect possible native await in our Promise.prototype.then. +var task = { awaits: 0, echoes: 0, id: 0 }; +var taskCounter = 0; +var zoneStack = []; +var zoneEchoes = 0; +var totalEchoes = 0; var zone_id_counter = 0; -function newScope(fn, props$$1, a1, a2) { +function newScope(fn, props, a1, a2) { var parent = PSD, psd = Object.create(parent); psd.parent = parent; psd.ref = 0; psd.global = false; psd.id = ++zone_id_counter; - // Prepare for promise patching (done in usePSD): - var globalEnv = globalPSD.env; + globalPSD.env; psd.env = patchGlobalPromise ? { - Promise: Promise, - PromiseProp: { value: Promise, configurable: true, writable: true }, - all: Promise.all, - race: Promise.race, - resolve: Promise.resolve, - reject: Promise.reject, - nthen: getPatchedPromiseThen(globalEnv.nthen, psd), - gthen: getPatchedPromiseThen(globalEnv.gthen, psd) // global then + Promise: DexiePromise, + PromiseProp: { value: DexiePromise, configurable: true, writable: true }, + all: DexiePromise.all, + race: DexiePromise.race, + allSettled: DexiePromise.allSettled, + any: DexiePromise.any, + resolve: DexiePromise.resolve, + reject: DexiePromise.reject, } : {}; - if (props$$1) - extend(psd, props$$1); - // unhandleds and onunhandled should not be specifically set here. - // Leave them on parent prototype. - // unhandleds.push(err) will push to parent's prototype - // onunhandled() will call parents onunhandled (with this scope's this-pointer though!) + if (props) + extend(psd, props); ++parent.ref; psd.finalize = function () { --this.parent.ref || this.parent.finalize(); @@ -1218,8 +972,6 @@ function newScope(fn, props$$1, a1, a2) { psd.finalize(); return rv; } -// Function to call if scopeFunc returns NativePromise -// Also for each NativePromise in the arguments to Promise.all() function incrementExpectedAwaits() { if (!task.id) task.id = ++taskCounter; @@ -1227,17 +979,17 @@ function incrementExpectedAwaits() { task.echoes += ZONE_ECHO_LIMIT; return task.id; } -// Function to call when 'then' calls back on a native promise where onAwaitExpected() had been called. -// Also call this when a native await calls then method on a promise. In that case, don't supply -// sourceTaskId because we already know it refers to current task. -function decrementExpectedAwaits(sourceTaskId) { - if (!task.awaits || (sourceTaskId && sourceTaskId !== task.id)) - return; +function decrementExpectedAwaits() { + if (!task.awaits) + return false; if (--task.awaits === 0) task.id = 0; - task.echoes = task.awaits * ZONE_ECHO_LIMIT; // Will reset echoes to 0 if awaits is 0. + task.echoes = task.awaits * ZONE_ECHO_LIMIT; + return true; +} +if (('' + nativePromiseThen).indexOf('[native code]') === -1) { + incrementExpectedAwaits = decrementExpectedAwaits = nop; } -// Call from Promise.all() and Promise.race() function onPossibleParallellAsync(possiblePromise) { if (task.echoes && possiblePromise && possiblePromise.constructor === NativePromise) { incrementExpectedAwaits(); @@ -1254,7 +1006,7 @@ function onPossibleParallellAsync(possiblePromise) { function zoneEnterEcho(targetZone) { ++totalEchoes; if (!task.echoes || --task.echoes === 0) { - task.echoes = task.id = 0; // Cancel zone echoing. + task.echoes = task.awaits = task.id = 0; } zoneStack.push(PSD); switchToZone(targetZone, true); @@ -1267,35 +1019,26 @@ function zoneLeaveEcho() { function switchToZone(targetZone, bEnteringZone) { var currentZone = PSD; if (bEnteringZone ? task.echoes && (!zoneEchoes++ || targetZone !== PSD) : zoneEchoes && (!--zoneEchoes || targetZone !== PSD)) { - // Enter or leave zone asynchronically as well, so that tasks initiated during current tick - // will be surrounded by the zone when they are invoked. - enqueueNativeMicroTask(bEnteringZone ? zoneEnterEcho.bind(null, targetZone) : zoneLeaveEcho); + queueMicrotask(bEnteringZone ? zoneEnterEcho.bind(null, targetZone) : zoneLeaveEcho); } if (targetZone === PSD) return; - PSD = targetZone; // The actual zone switch occurs at this line. - // Snapshot on every leave from global zone. + PSD = targetZone; if (currentZone === globalPSD) globalPSD.env = snapShot(); if (patchGlobalPromise) { - // Let's patch the global and native Promises (may be same or may be different) var GlobalPromise = globalPSD.env.Promise; - // Swich environments (may be PSD-zone or the global zone. Both apply.) var targetEnv = targetZone.env; - // Change Promise.prototype.then for native and global Promise (they MAY differ on polyfilled environments, but both can be accessed) - // Must be done on each zone change because the patched method contains targetZone in its closure. - nativePromiseProto.then = targetEnv.nthen; - GlobalPromise.prototype.then = targetEnv.gthen; if (currentZone.global || targetZone.global) { - // Leaving or entering global zone. It's time to patch / restore global Promise. - // 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. 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 - // not be accessing global.Promise but a local reference to it) GlobalPromise.all = targetEnv.all; GlobalPromise.race = targetEnv.race; GlobalPromise.resolve = targetEnv.resolve; GlobalPromise.reject = targetEnv.reject; + if (targetEnv.allSettled) + GlobalPromise.allSettled = targetEnv.allSettled; + if (targetEnv.any) + GlobalPromise.any = targetEnv.any; } } } @@ -1306,10 +1049,10 @@ function snapShot() { PromiseProp: Object.getOwnPropertyDescriptor(_global, "Promise"), all: GlobalPromise.all, race: GlobalPromise.race, + allSettled: GlobalPromise.allSettled, + any: GlobalPromise.any, resolve: GlobalPromise.resolve, reject: GlobalPromise.reject, - nthen: nativePromiseProto.then, - gthen: GlobalPromise.prototype.then } : {}; } function usePSD(psd, fn, a1, a2, a3) { @@ -1322,13 +1065,7 @@ function usePSD(psd, fn, a1, a2, a3) { switchToZone(outerScope, false); } } -function enqueueNativeMicroTask(job) { - // - // Precondition: nativePromiseThen !== undefined - // - nativePromiseThen.call(resolvedNativePromise, job); -} -function nativeAwaitCompatibleWrap(fn, zone, possibleAwait) { +function nativeAwaitCompatibleWrap(fn, zone, possibleAwait, cleanup) { return typeof fn !== 'function' ? fn : function () { var outerZone = PSD; if (possibleAwait) @@ -1339,1453 +1076,1824 @@ function nativeAwaitCompatibleWrap(fn, zone, possibleAwait) { } finally { switchToZone(outerZone, false); + if (cleanup) + queueMicrotask(decrementExpectedAwaits); } }; } -function getPatchedPromiseThen(origThen, zone) { - return function (onResolved, onRejected) { - return origThen.call(this, nativeAwaitCompatibleWrap(onResolved, zone, false), nativeAwaitCompatibleWrap(onRejected, zone, false)); - }; +function execInGlobalContext(cb) { + if (Promise === NativePromise && task.echoes === 0) { + if (zoneEchoes === 0) { + cb(); + } + else { + enqueueNativeMicroTask(cb); + } + } + else { + setTimeout(cb, 0); + } } -var UNHANDLEDREJECTION = "unhandledrejection"; -function globalError(err, promise) { - var rv; - try { - rv = promise.onuncatched(err); +var rejection = DexiePromise.reject; + +function tempTransaction(db, mode, storeNames, fn) { + if (!db.idbdb || (!db._state.openComplete && (!PSD.letThrough && !db._vip))) { + if (db._state.openComplete) { + return rejection(new exceptions.DatabaseClosed(db._state.dbOpenError)); + } + if (!db._state.isBeingOpened) { + if (!db._state.autoOpen) + return rejection(new exceptions.DatabaseClosed()); + db.open().catch(nop); + } + return db._state.dbReadyPromise.then(function () { return tempTransaction(db, mode, storeNames, fn); }); } - catch (e) { } - if (rv !== false) + else { + var trans = db._createTransaction(mode, storeNames, db._dbSchema); try { - var event, eventData = { promise: promise, reason: err }; - if (_global.document && document.createEvent) { - event = document.createEvent('Event'); - event.initEvent(UNHANDLEDREJECTION, true, true); - extend(event, eventData); - } - else if (_global.CustomEvent) { - event = new CustomEvent(UNHANDLEDREJECTION, { detail: eventData }); - extend(event, eventData); - } - if (event && _global.dispatchEvent) { - dispatchEvent(event); - if (!_global.PromiseRejectionEvent && _global.onunhandledrejection) - // No native support for PromiseRejectionEvent but user has set window.onunhandledrejection. Manually call it. - try { - _global.onunhandledrejection(event); - } - catch (_) { } + trans.create(); + db._state.PR1398_maxLoop = 3; + } + catch (ex) { + if (ex.name === errnames.InvalidState && db.isOpen() && --db._state.PR1398_maxLoop > 0) { + console.warn('Dexie: Need to reopen db'); + db.close({ disableAutoOpen: false }); + return db.open().then(function () { return tempTransaction(db, mode, storeNames, fn); }); } - if (!event.defaultPrevented) { - console.warn("Unhandled rejection: " + (err.stack || err)); + return rejection(ex); + } + return trans._promise(mode, function (resolve, reject) { + return newScope(function () { + PSD.trans = trans; + return fn(resolve, reject, trans); + }); + }).then(function (result) { + if (mode === 'readwrite') + try { + trans.idbtrans.commit(); + } + catch (_a) { } + return mode === 'readonly' ? result : trans._completion.then(function () { return result; }); + }); + } +} + +var DEXIE_VERSION = '4.3.0'; +var maxString = String.fromCharCode(65535); +var minKey = -Infinity; +var INVALID_KEY_ARGUMENT = "Invalid key provided. Keys must be of type string, number, Date or Array."; +var STRING_EXPECTED = "String expected."; +var connections = []; +var DBNAMES_DB = '__dbnames'; +var READONLY = 'readonly'; +var READWRITE = 'readwrite'; + +function combine(filter1, filter2) { + return filter1 ? + filter2 ? + function () { return filter1.apply(this, arguments) && filter2.apply(this, arguments); } : + filter1 : + filter2; +} + +var AnyRange = { + type: 3 , + lower: -Infinity, + lowerOpen: false, + upper: [[]], + upperOpen: false +}; + +function workaroundForUndefinedPrimKey(keyPath) { + return typeof keyPath === "string" && !/\./.test(keyPath) + ? function (obj) { + if (obj[keyPath] === undefined && (keyPath in obj)) { + obj = deepClone(obj); + delete obj[keyPath]; } + return obj; } - catch (e) { } + : function (obj) { return obj; }; } -var rejection = Promise.reject; -function Events(ctx) { - var evs = {}; - var rv = function (eventName, subscriber) { - if (subscriber) { - // Subscribe. If additional arguments than just the subscriber was provided, forward them as well. - var i = arguments.length, args = new Array(i - 1); - while (--i) - args[i - 1] = arguments[i]; - evs[eventName].subscribe.apply(null, args); - return ctx; +function Entity() { + throw exceptions.Type("Entity instances must never be new:ed. Instances are generated by the framework bypassing the constructor."); +} + +function cmp(a, b) { + try { + var ta = type(a); + var tb = type(b); + if (ta !== tb) { + if (ta === 'Array') + return 1; + if (tb === 'Array') + return -1; + if (ta === 'binary') + return 1; + if (tb === 'binary') + return -1; + if (ta === 'string') + return 1; + if (tb === 'string') + return -1; + if (ta === 'Date') + return 1; + if (tb !== 'Date') + return NaN; + return -1; } - else if (typeof (eventName) === 'string') { - // Return interface allowing to fire or unsubscribe from event - return evs[eventName]; + switch (ta) { + case 'number': + case 'Date': + case 'string': + return a > b ? 1 : a < b ? -1 : 0; + case 'binary': { + return compareUint8Arrays(getUint8Array(a), getUint8Array(b)); + } + case 'Array': + return compareArrays(a, b); } - }; - rv.addEventType = add; - for (var i = 1, l = arguments.length; i < l; ++i) { - add(arguments[i]); } - return rv; - function add(eventName, chainFunction, defaultFunction) { - if (typeof eventName === 'object') - return addConfiguredEvents(eventName); - if (!chainFunction) - chainFunction = reverseStoppableEventChain; - if (!defaultFunction) - defaultFunction = nop; - var context = { - subscribers: [], - fire: defaultFunction, - subscribe: function (cb) { - if (context.subscribers.indexOf(cb) === -1) { - context.subscribers.push(cb); - context.fire = chainFunction(context.fire, cb); + catch (_a) { } + return NaN; +} +function compareArrays(a, b) { + var al = a.length; + var bl = b.length; + var l = al < bl ? al : bl; + for (var i = 0; i < l; ++i) { + var res = cmp(a[i], b[i]); + if (res !== 0) + return res; + } + return al === bl ? 0 : al < bl ? -1 : 1; +} +function compareUint8Arrays(a, b) { + var al = a.length; + var bl = b.length; + var l = al < bl ? al : bl; + for (var i = 0; i < l; ++i) { + if (a[i] !== b[i]) + return a[i] < b[i] ? -1 : 1; + } + return al === bl ? 0 : al < bl ? -1 : 1; +} +function type(x) { + var t = typeof x; + if (t !== 'object') + return t; + if (ArrayBuffer.isView(x)) + return 'binary'; + var tsTag = toStringTag(x); + return tsTag === 'ArrayBuffer' ? 'binary' : tsTag; +} +function getUint8Array(a) { + if (a instanceof Uint8Array) + return a; + if (ArrayBuffer.isView(a)) + return new Uint8Array(a.buffer, a.byteOffset, a.byteLength); + return new Uint8Array(a); +} + +function builtInDeletionTrigger(table, keys, res) { + var yProps = table.schema.yProps; + if (!yProps) + return res; + if (keys && res.numFailures > 0) + keys = keys.filter(function (_, i) { return !res.failures[i]; }); + return Promise.all(yProps.map(function (_a) { + var updatesTable = _a.updatesTable; + return keys + ? table.db.table(updatesTable).where('k').anyOf(keys).delete() + : table.db.table(updatesTable).clear(); + })).then(function () { return res; }); +} + +var PropModification = (function () { + function PropModification(spec) { + this["@@propmod"] = spec; + } + PropModification.prototype.execute = function (value) { + var _a; + var spec = this["@@propmod"]; + if (spec.add !== undefined) { + var term = spec.add; + if (isArray(term)) { + return __spreadArray(__spreadArray([], (isArray(value) ? value : []), true), term, true).sort(); + } + if (typeof term === 'number') + return (Number(value) || 0) + term; + if (typeof term === 'bigint') { + try { + return BigInt(value) + term; + } + catch (_b) { + return BigInt(0) + term; } - }, - unsubscribe: function (cb) { - context.subscribers = context.subscribers.filter(function (fn) { return fn !== cb; }); - context.fire = context.subscribers.reduce(chainFunction, defaultFunction); } - }; - evs[eventName] = rv[eventName] = context; - return context; - } - function addConfiguredEvents(cfg) { - // events(this, {reading: [functionChain, nop]}); - keys(cfg).forEach(function (eventName) { - var args = cfg[eventName]; - if (isArray(args)) { - add(eventName, cfg[eventName][0], cfg[eventName][1]); + throw new TypeError("Invalid term ".concat(term)); + } + if (spec.remove !== undefined) { + var subtrahend_1 = spec.remove; + if (isArray(subtrahend_1)) { + return isArray(value) ? value.filter(function (item) { return !subtrahend_1.includes(item); }).sort() : []; } - else if (args === 'asap') { - // 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 - // enabling that any exception that occur wont disturb the initiator and also not nescessary be catched and forgotten. - var context = add(eventName, mirror, function fire() { - // Optimazation-safe cloning of arguments into args. - var i = arguments.length, args = new Array(i); - while (i--) - args[i] = arguments[i]; - // All each subscriber: - context.subscribers.forEach(function (fn) { - asap(function fireEvent() { - fn.apply(null, args); - }); - }); - }); + if (typeof subtrahend_1 === 'number') + return Number(value) - subtrahend_1; + if (typeof subtrahend_1 === 'bigint') { + try { + return BigInt(value) - subtrahend_1; + } + catch (_c) { + return BigInt(0) - subtrahend_1; + } } - else - throw new exceptions.InvalidArgument("Invalid event config"); - }); + throw new TypeError("Invalid subtrahend ".concat(subtrahend_1)); + } + var prefixToReplace = (_a = spec.replacePrefix) === null || _a === void 0 ? void 0 : _a[0]; + if (prefixToReplace && typeof value === 'string' && value.startsWith(prefixToReplace)) { + return spec.replacePrefix[1] + value.substring(prefixToReplace.length); + } + return value; + }; + return PropModification; +}()); + +function applyUpdateSpec(obj, changes) { + var keyPaths = keys(changes); + var numKeys = keyPaths.length; + var anythingModified = false; + for (var i = 0; i < numKeys; ++i) { + var keyPath = keyPaths[i]; + var value = changes[keyPath]; + var origValue = getByKeyPath(obj, keyPath); + if (value instanceof PropModification) { + setByKeyPath(obj, keyPath, value.execute(origValue)); + anythingModified = true; + } + else if (origValue !== value) { + setByKeyPath(obj, keyPath, value); + anythingModified = true; + } } + return anythingModified; } -/* - * Dexie.js - a minimalistic wrapper for IndexedDB - * =============================================== - * - * Copyright (c) 2014-2017 David Fahlander - * - * Version {version}, {date} - * - * http://dexie.org - * - * Apache License Version 2.0, January 2004, http://www.apache.org/licenses/LICENSE-2.0 - * - */ -var DEXIE_VERSION = '{version}'; -var maxString = String.fromCharCode(65535); -var maxKey = (function () { try { - IDBKeyRange.only([[]]); - return [[]]; -} -catch (e) { - return maxString; -} })(); -var minKey = -Infinity; -var INVALID_KEY_ARGUMENT = "Invalid key provided. Keys must be of type string, number, Date or Array."; -var STRING_EXPECTED = "String expected."; -var connections = []; -var isIEOrEdge = typeof navigator !== 'undefined' && /(MSIE|Trident|Edge)/.test(navigator.userAgent); -var hasIEDeleteObjectStoreBug = isIEOrEdge; -var hangsOnDeleteLargeKeyRange = isIEOrEdge; -var dexieStackFrameFilter = function (frame) { return !/(dexie\.js|dexie\.min\.js)/.test(frame); }; -var dbNamesDB; // Global database for backing Dexie.getDatabaseNames() on browser without indexedDB.webkitGetDatabaseNames() -// Init debug -setDebug(debug, dexieStackFrameFilter); -function Dexie(dbName, options) { - /// Specify only if you wich to control which addons that should run on this instance - var deps = Dexie.dependencies; - var opts = extend({ - // Default Options - addons: Dexie.addons, - autoOpen: true, - indexedDB: deps.indexedDB, - IDBKeyRange: deps.IDBKeyRange // Backend IDBKeyRange api. Default to browser env. - }, options); - var addons = opts.addons, autoOpen = opts.autoOpen, indexedDB = opts.indexedDB, IDBKeyRange = opts.IDBKeyRange; - var globalSchema = this._dbSchema = {}; - var versions = []; - var dbStoreNames = []; - var allTables = {}; - /// - var idbdb = null; // Instance of IDBDatabase - var dbOpenError = null; - var isBeingOpened = false; - var onReadyBeingFired = null; - var openComplete = false; - var READONLY = "readonly", READWRITE = "readwrite"; - var db = this; - var dbReadyResolve, dbReadyPromise = new Promise(function (resolve) { - dbReadyResolve = resolve; - }), cancelOpen, openCanceller = new Promise(function (_, reject) { - cancelOpen = reject; - }); - var autoSchema = true; - var hasNativeGetDatabaseNames = !!getNativeGetDatabaseNamesFn(indexedDB), hasGetAll; - function init() { - // Default subscribers to "versionchange" and "blocked". - // Can be overridden by custom handlers. If custom handlers return false, these default - // behaviours will be prevented. - db.on("versionchange", function (ev) { - // Default behavior for versionchange event is to close database connection. - // Caller can override this behavior by doing db.on("versionchange", function(){ return false; }); - // Let's not block the other window from making it's delete() or open() call. - // NOTE! This event is never fired in IE,Edge or Safari. - 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."); - db.close(); - // In many web applications, it would be recommended to force window.reload() - // when this event occurs. To do that, subscribe to the versionchange event - // and call window.location.reload(true) if ev.newVersion > 0 (not a deletion) - // The reason for this is that your current web app obviously has old schema code that needs - // to be updated. Another window got a newer version of the app and needs to upgrade DB but - // your window is blocking it unless we close it here. - }); - db.on("blocked", function (ev) { - 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); - }); - } - // - // - // - // ------------------------- Versioning Framework--------------------------- - // - // - // - this.version = function (versionNumber) { - /// - /// - if (idbdb || isBeingOpened) - throw new exceptions.Schema("Cannot add version when database is open"); - this.verno = Math.max(this.verno, versionNumber); - var versionInstance = versions.filter(function (v) { return v._cfg.version === versionNumber; })[0]; - if (versionInstance) - return versionInstance; - versionInstance = new Version(versionNumber); - versions.push(versionInstance); - versions.sort(lowerVersionFirst); - // Disable autoschema mode, as at least one version is specified. - autoSchema = false; - return versionInstance; - }; - function Version(versionNumber) { - this._cfg = { - version: versionNumber, - storesSource: null, - dbschema: {}, - tables: {}, - contentUpgrade: null - }; - this.stores({}); // Derive earlier schemas by default. +var Table = (function () { + function Table() { } - extend(Version.prototype, { - stores: function (stores) { - /// - /// Defines the schema for a particular version - /// - /// - /// Example:
- /// {users: "id++,first,last,&username,*email",
- /// passwords: "id++,&username"}
- ///
- /// Syntax: {Table: "[primaryKey][++],[&][*]index1,[&][*]index2,..."}

- /// Special characters:
- /// "&" means unique key,
- /// "*" means value is multiEntry,
- /// "++" means auto-increment and only applicable for primary key
- /// - 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. - var storesSpec = {}; - versions.forEach(function (version) { - extend(storesSpec, version._cfg.storesSource); - }); - var dbschema = (this._cfg.dbschema = {}); - this._parseStoresSpec(storesSpec, dbschema); - // Update the latest schema to this version - // Update API - globalSchema = db._dbSchema = dbschema; - removeTablesApi([allTables, db, Transaction.prototype]); // Keep Transaction.prototype even though it should be depr. - setApiOnPlace([allTables, db, Transaction.prototype, this._cfg.tables], keys(dbschema), dbschema); - dbStoreNames = keys(dbschema); - return this; - }, - upgrade: function (upgradeFunction) { - this._cfg.contentUpgrade = upgradeFunction; - return this; - }, - _parseStoresSpec: function (stores, outSchema) { - keys(stores).forEach(function (tableName) { - if (stores[tableName] !== null) { - var instanceTemplate = {}; - var indexes = parseIndexSyntax(stores[tableName]); - var primKey = indexes.shift(); - if (primKey.multi) - throw new exceptions.Schema("Primary key cannot be multi-valued"); - if (primKey.keyPath) - setByKeyPath(instanceTemplate, primKey.keyPath, primKey.auto ? 0 : primKey.keyPath); - indexes.forEach(function (idx) { - if (idx.auto) - throw new exceptions.Schema("Only primary key can be marked as autoIncrement (++)"); - if (!idx.keyPath) - throw new exceptions.Schema("Index must have a name and cannot be an empty string"); - setByKeyPath(instanceTemplate, idx.keyPath, idx.compound ? idx.keyPath.map(function () { return ""; }) : ""); - }); - outSchema[tableName] = new TableSchema(tableName, primKey, indexes, instanceTemplate); - } - }); + Table.prototype._trans = function (mode, fn, writeLocked) { + var trans = this._tx || PSD.trans; + var tableName = this.name; + var task = debug && typeof console !== 'undefined' && console.createTask && console.createTask("Dexie: ".concat(mode === 'readonly' ? 'read' : 'write', " ").concat(this.name)); + function checkTableInTransaction(resolve, reject, trans) { + if (!trans.schema[tableName]) + throw new exceptions.NotFound("Table " + tableName + " not part of transaction"); + return fn(trans.idbtrans, trans); } - }); - function runUpgraders(oldVersion, idbtrans, reject) { - var trans = db._createTransaction(READWRITE, dbStoreNames, globalSchema); - trans.create(idbtrans); - trans._completion.catch(reject); - var rejectTransaction = trans._reject.bind(trans); - newScope(function () { - PSD.trans = trans; - if (oldVersion === 0) { - // Create tables: - keys(globalSchema).forEach(function (tableName) { - createTable(idbtrans, tableName, globalSchema[tableName].primKey, globalSchema[tableName].indexes); + var wasRootExec = beginMicroTickScope(); + try { + var p = trans && trans.db._novip === this.db._novip ? + trans === PSD.trans ? + trans._promise(mode, checkTableInTransaction, writeLocked) : + newScope(function () { return trans._promise(mode, checkTableInTransaction, writeLocked); }, { trans: trans, transless: PSD.transless || PSD }) : + tempTransaction(this.db, mode, [this.name], checkTableInTransaction); + if (task) { + p._consoleTask = task; + p = p.catch(function (err) { + console.trace(err); + return rejection(err); }); - Promise.follow(function () { return db.on.populate.fire(trans); }).catch(rejectTransaction); } - else - updateTablesAndIndexes(oldVersion, trans, idbtrans).catch(rejectTransaction); - }); - } - function updateTablesAndIndexes(oldVersion, trans, idbtrans) { - // Upgrade version to version, step-by-step from oldest to newest version. - // Each transaction object will contain the table set that was current in that version (but also not-yet-deleted tables from its previous version) - var queue = []; - var oldVersionStruct = versions.filter(function (version) { return version._cfg.version === oldVersion; })[0]; - if (!oldVersionStruct) - throw new exceptions.Upgrade("Dexie specification of currently installed DB version is missing"); - globalSchema = db._dbSchema = oldVersionStruct._cfg.dbschema; - var anyContentUpgraderHasRun = false; - var versToRun = versions.filter(function (v) { return v._cfg.version > oldVersion; }); - versToRun.forEach(function (version) { - /// - queue.push(function () { - var oldSchema = globalSchema; - var newSchema = version._cfg.dbschema; - adjustToExistingIndexNames(oldSchema, idbtrans); - adjustToExistingIndexNames(newSchema, idbtrans); - globalSchema = db._dbSchema = newSchema; - var diff = getSchemaDiff(oldSchema, newSchema); - // Add tables - diff.add.forEach(function (tuple) { - createTable(idbtrans, tuple[0], tuple[1].primKey, tuple[1].indexes); - }); - // Change tables - diff.change.forEach(function (change) { - if (change.recreate) { - throw new exceptions.Upgrade("Not yet support for changing primary key"); - } - else { - var store = idbtrans.objectStore(change.name); - // Add indexes - change.add.forEach(function (idx) { - addIndex(store, idx); - }); - // Update indexes - change.change.forEach(function (idx) { - store.deleteIndex(idx.name); - addIndex(store, idx); - }); - // Delete indexes - change.del.forEach(function (idxName) { - store.deleteIndex(idxName); - }); - } - }); - if (version._cfg.contentUpgrade) { - anyContentUpgraderHasRun = true; - return Promise.follow(function () { - version._cfg.contentUpgrade(trans); - }); - } - }); - queue.push(function (idbtrans) { - if (!anyContentUpgraderHasRun || !hasIEDeleteObjectStoreBug) { - var newSchema = version._cfg.dbschema; - // Delete old tables - deleteRemovedTables(newSchema, idbtrans); - } - }); - }); - // Now, create a queue execution engine - function runQueue() { - return queue.length ? Promise.resolve(queue.shift()(trans.idbtrans)).then(runQueue) : - Promise.resolve(); + return p; } - return runQueue().then(function () { - createMissingTables(globalSchema, idbtrans); // At last, make sure to create any missing tables. (Needed by addons that add stores to DB without specifying version) - }); - } - function getSchemaDiff(oldSchema, newSchema) { - var diff = { - del: [], - add: [], - change: [] // Array of {name: tableName, recreate: newDefinition, del: delIndexNames, add: newIndexDefs, change: changedIndexDefs} - }; - for (var table in oldSchema) { - if (!newSchema[table]) - diff.del.push(table); - } - for (table in newSchema) { - var oldDef = oldSchema[table], newDef = newSchema[table]; - if (!oldDef) { - diff.add.push([table, newDef]); - } - else { - var change = { - name: table, - def: newDef, - recreate: false, - del: [], - add: [], - change: [] - }; - if (oldDef.primKey.src !== newDef.primKey.src) { - // Primary key has changed. Remove and re-add table. - change.recreate = true; - diff.change.push(change); - } - else { - // Same primary key. Just find out what differs: - var oldIndexes = oldDef.idxByName; - var newIndexes = newDef.idxByName; - for (var idxName in oldIndexes) { - if (!newIndexes[idxName]) - change.del.push(idxName); - } - for (idxName in newIndexes) { - var oldIdx = oldIndexes[idxName], newIdx = newIndexes[idxName]; - if (!oldIdx) - change.add.push(newIdx); - else if (oldIdx.src !== newIdx.src) - change.change.push(newIdx); - } - if (change.del.length > 0 || change.add.length > 0 || change.change.length > 0) { - diff.change.push(change); - } - } - } + finally { + if (wasRootExec) + endMicroTickScope(); } - return diff; - } - function createTable(idbtrans, tableName, primKey, indexes) { - /// - var store = idbtrans.db.createObjectStore(tableName, primKey.keyPath ? { keyPath: primKey.keyPath, autoIncrement: primKey.auto } : { autoIncrement: primKey.auto }); - indexes.forEach(function (idx) { addIndex(store, idx); }); - return store; - } - function createMissingTables(newSchema, idbtrans) { - keys(newSchema).forEach(function (tableName) { - if (!idbtrans.db.objectStoreNames.contains(tableName)) { - createTable(idbtrans, tableName, newSchema[tableName].primKey, newSchema[tableName].indexes); - } - }); - } - function deleteRemovedTables(newSchema, idbtrans) { - for (var i = 0; i < idbtrans.db.objectStoreNames.length; ++i) { - var storeName = idbtrans.db.objectStoreNames[i]; - if (newSchema[storeName] == null) { - idbtrans.db.deleteObjectStore(storeName); + }; + Table.prototype.get = function (keyOrCrit, cb) { + var _this = this; + if (keyOrCrit && keyOrCrit.constructor === Object) + return this.where(keyOrCrit).first(cb); + if (keyOrCrit == null) + return rejection(new exceptions.Type("Invalid argument to Table.get()")); + return this._trans('readonly', function (trans) { + return _this.core.get({ trans: trans, key: keyOrCrit }) + .then(function (res) { return _this.hook.reading.fire(res); }); + }).then(cb); + }; + Table.prototype.where = function (indexOrCrit) { + if (typeof indexOrCrit === 'string') + return new this.db.WhereClause(this, indexOrCrit); + if (isArray(indexOrCrit)) + return new this.db.WhereClause(this, "[".concat(indexOrCrit.join('+'), "]")); + var keyPaths = keys(indexOrCrit); + if (keyPaths.length === 1) + return this + .where(keyPaths[0]) + .equals(indexOrCrit[keyPaths[0]]); + var compoundIndex = this.schema.indexes.concat(this.schema.primKey).filter(function (ix) { + if (ix.compound && + keyPaths.every(function (keyPath) { return ix.keyPath.indexOf(keyPath) >= 0; })) { + for (var i = 0; i < keyPaths.length; ++i) { + if (keyPaths.indexOf(ix.keyPath[i]) === -1) + return false; + } + return true; } + return false; + }).sort(function (a, b) { return a.keyPath.length - b.keyPath.length; })[0]; + if (compoundIndex && this.db._maxKey !== maxString) { + var keyPathsInValidOrder = compoundIndex.keyPath.slice(0, keyPaths.length); + return this + .where(keyPathsInValidOrder) + .equals(keyPathsInValidOrder.map(function (kp) { return indexOrCrit[kp]; })); } - } - function addIndex(store, idx) { - store.createIndex(idx.name, idx.keyPath, { unique: idx.unique, multiEntry: idx.multi }); - } - // - // - // Dexie Protected API - // - // - this._allTables = allTables; - this._createTransaction = function (mode, storeNames, dbschema, parentTransaction) { - return new Transaction(mode, storeNames, dbschema, parentTransaction); - }; - /* Generate a temporary transaction when db operations are done outside a transaction scope. - */ - function tempTransaction(mode, storeNames, fn) { - if (!openComplete && (!PSD.letThrough)) { - if (!isBeingOpened) { - if (!autoOpen) - return rejection(new exceptions.DatabaseClosed()); - db.open().catch(nop); // Open in background. If if fails, it will be catched by the final promise anyway. - } - return dbReadyPromise.then(function () { return tempTransaction(mode, storeNames, fn); }); + if (!compoundIndex && debug) + console.warn("The query ".concat(JSON.stringify(indexOrCrit), " on ").concat(this.name, " would benefit from a ") + + "compound index [".concat(keyPaths.join('+'), "]")); + var idxByName = this.schema.idxByName; + function equals(a, b) { + return cmp(a, b) === 0; } - else { - var trans = db._createTransaction(mode, storeNames, globalSchema); - try { - trans.create(); - } - catch (ex) { - return rejection(ex); - } - return trans._promise(mode, function (resolve, reject) { - return newScope(function () { - PSD.trans = trans; - return fn(resolve, reject, trans); + var _a = keyPaths.reduce(function (_a, keyPath) { + var prevIndex = _a[0], prevFilterFn = _a[1]; + var index = idxByName[keyPath]; + var value = indexOrCrit[keyPath]; + return [ + prevIndex || index, + prevIndex || !index ? + combine(prevFilterFn, index && index.multi ? + function (x) { + var prop = getByKeyPath(x, keyPath); + return isArray(prop) && prop.some(function (item) { return equals(value, item); }); + } : function (x) { return equals(value, getByKeyPath(x, keyPath)); }) + : prevFilterFn + ]; + }, [null, null]), idx = _a[0], filterFunction = _a[1]; + return idx ? + this.where(idx.name).equals(indexOrCrit[idx.keyPath]) + .filter(filterFunction) : + compoundIndex ? + this.filter(filterFunction) : + this.where(keyPaths).equals(''); + }; + Table.prototype.filter = function (filterFunction) { + return this.toCollection().and(filterFunction); + }; + Table.prototype.count = function (thenShortcut) { + return this.toCollection().count(thenShortcut); + }; + Table.prototype.offset = function (offset) { + return this.toCollection().offset(offset); + }; + Table.prototype.limit = function (numRows) { + return this.toCollection().limit(numRows); + }; + Table.prototype.each = function (callback) { + return this.toCollection().each(callback); + }; + Table.prototype.toArray = function (thenShortcut) { + return this.toCollection().toArray(thenShortcut); + }; + Table.prototype.toCollection = function () { + return new this.db.Collection(new this.db.WhereClause(this)); + }; + Table.prototype.orderBy = function (index) { + return new this.db.Collection(new this.db.WhereClause(this, isArray(index) ? + "[".concat(index.join('+'), "]") : + index)); + }; + Table.prototype.reverse = function () { + return this.toCollection().reverse(); + }; + Table.prototype.mapToClass = function (constructor) { + var _a = this, db = _a.db, tableName = _a.name; + this.schema.mappedClass = constructor; + if (constructor.prototype instanceof Entity) { + constructor = (function (_super) { + __extends(class_1, _super); + function class_1() { + return _super !== null && _super.apply(this, arguments) || this; + } + Object.defineProperty(class_1.prototype, "db", { + get: function () { return db; }, + enumerable: false, + configurable: true }); - }).then(function (result) { - // Instead of resolving value directly, wait with resolving it until transaction has completed. - // Otherwise the data would not be in the DB if requesting it in the then() operation. - // Specifically, to ensure that the following expression will work: - // - // db.friends.put({name: "Arne"}).then(function () { - // db.friends.where("name").equals("Arne").count(function(count) { - // assert (count === 1); - // }); - // }); - // - return trans._completion.then(function () { return result; }); - }); /*.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. - trans._reject(err); - return rejection(err); - });*/ + class_1.prototype.table = function () { return tableName; }; + return class_1; + }(constructor)); } - } - this._whenReady = function (fn) { - return openComplete || PSD.letThrough ? fn() : new Promise(function (resolve, reject) { - if (!isBeingOpened) { - if (!autoOpen) { - reject(new exceptions.DatabaseClosed()); - return; - } - db.open().catch(nop); // Open in background. If if fails, it will be catched by the final promise anyway. - } - dbReadyPromise.then(resolve, reject); - }).then(fn); - }; - // - // - // - // - // Dexie API - // - // - // - this.verno = 0; - this.open = function () { - if (isBeingOpened || idbdb) - return dbReadyPromise.then(function () { return dbOpenError ? rejection(dbOpenError) : db; }); - debug && (openCanceller._stackHolder = getErrorWithStack()); // Let stacks point to when open() was called rather than where new Dexie() was called. - isBeingOpened = true; - dbOpenError = null; - openComplete = false; - // Function pointers to call when the core opening process completes. - var resolveDbReady = dbReadyResolve, - // upgradeTransaction to abort on failure. - upgradeTransaction = null; - return Promise.race([openCanceller, new Promise(function (resolve, reject) { - // Multiply db.verno with 10 will be needed to workaround upgrading bug in IE: - // IE fails when deleting objectStore after reading from it. - // A future version of Dexie.js will stopover an intermediate version to workaround this. - // 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. - // If no API, throw! - if (!indexedDB) - throw new exceptions.MissingAPI("indexedDB API not 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."); - var req = autoSchema ? indexedDB.open(dbName) : indexedDB.open(dbName, Math.round(db.verno * 10)); - 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 - req.onerror = eventRejectHandler(reject); - req.onblocked = wrap(fireOnBlocked); - req.onupgradeneeded = wrap(function (e) { - upgradeTransaction = req.transaction; - if (autoSchema && !db._allowEmptyDB) { - // Caller did not specify a version or schema. Doing that is only acceptable for opening alread existing databases. - // If onupgradeneeded is called it means database did not exist. Reject the open() promise and make sure that we - // do not create a new database by accident here. - req.onerror = preventDefault; // Prohibit onabort error from firing before we're done! - upgradeTransaction.abort(); // Abort transaction (would hope that this would make DB disappear but it doesnt.) - // Close database and delete it. - req.result.close(); - 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! - delreq.onsuccess = delreq.onerror = wrap(function () { - reject(new exceptions.NoSuchDatabase("Database " + dbName + " doesnt exist")); - }); - } - else { - upgradeTransaction.onerror = eventRejectHandler(reject); - var oldVer = e.oldVersion > Math.pow(2, 62) ? 0 : e.oldVersion; // Safari 8 fix. - runUpgraders(oldVer / 10, upgradeTransaction, reject, req); - } - }, reject); - req.onsuccess = wrap(function () { - // Core opening procedure complete. Now let's just record some stuff. - upgradeTransaction = null; - idbdb = req.result; - connections.push(db); // Used for emulating versionchange event on IE/Edge/Safari. - if (autoSchema) - readGlobalSchema(); - else if (idbdb.objectStoreNames.length > 0) { - try { - adjustToExistingIndexNames(globalSchema, idbdb.transaction(safariMultiStoreFix(idbdb.objectStoreNames), READONLY)); - } - catch (e) { - // Safari may bail out if > 1 store names. However, this shouldnt be a showstopper. Issue #120. - } - } - idbdb.onversionchange = wrap(function (ev) { - db._vcFired = true; // detect implementations that not support versionchange (IE/Edge/Safari) - db.on("versionchange").fire(ev); - }); - if (!hasNativeGetDatabaseNames && dbName !== '__dbnames') { - dbNamesDB.dbnames.put({ name: dbName }).catch(nop); + var inheritedProps = new Set(); + for (var proto = constructor.prototype; proto; proto = getProto(proto)) { + Object.getOwnPropertyNames(proto).forEach(function (propName) { return inheritedProps.add(propName); }); + } + var readHook = function (obj) { + if (!obj) + return obj; + var res = Object.create(constructor.prototype); + for (var m in obj) + if (!inheritedProps.has(m)) + try { + res[m] = obj[m]; } - resolve(); - }, reject); - })]).then(function () { - // Before finally resolving the dbReadyPromise and this promise, - // call and await all on('ready') subscribers: - // Dexie.vip() makes subscribers able to use the database while being opened. - // This is a must since these subscribers take part of the opening procedure. - onReadyBeingFired = []; - return Promise.resolve(Dexie.vip(db.on.ready.fire)).then(function fireRemainders() { - if (onReadyBeingFired.length > 0) { - // In case additional subscribers to db.on('ready') were added during the time db.on.ready.fire was executed. - var remainders = onReadyBeingFired.reduce(promisableChain, nop); - onReadyBeingFired = []; - return Promise.resolve(Dexie.vip(remainders)).then(fireRemainders); + catch (_) { } + return res; + }; + if (this.schema.readHook) { + this.hook.reading.unsubscribe(this.schema.readHook); + } + this.schema.readHook = readHook; + this.hook("reading", readHook); + return constructor; + }; + Table.prototype.defineClass = function () { + function Class(content) { + extend(this, content); + } + return this.mapToClass(Class); + }; + Table.prototype.add = function (obj, key) { + var _this = this; + var _a = this.schema.primKey, auto = _a.auto, keyPath = _a.keyPath; + var objToAdd = obj; + if (keyPath && auto) { + objToAdd = workaroundForUndefinedPrimKey(keyPath)(obj); + } + return this._trans('readwrite', function (trans) { + return _this.core.mutate({ trans: trans, type: 'add', keys: key != null ? [key] : null, values: [objToAdd] }); + }).then(function (res) { return res.numFailures ? DexiePromise.reject(res.failures[0]) : res.lastResult; }) + .then(function (lastResult) { + if (keyPath) { + try { + setByKeyPath(obj, keyPath, lastResult); } - }); - }).finally(function () { - onReadyBeingFired = null; - }).then(function () { - // Resolve the db.open() with the db instance. - isBeingOpened = false; - return db; - }).catch(function (err) { - try { - // Did we fail within onupgradeneeded? Make sure to abort the upgrade transaction so it doesnt commit. - upgradeTransaction && upgradeTransaction.abort(); + catch (_) { } } - catch (e) { } - isBeingOpened = false; // Set before calling db.close() so that it doesnt reject openCanceller again (leads to unhandled rejection event). - db.close(); // Closes and resets idbdb, removes connections, resets dbReadyPromise and openCanceller so that a later db.open() is fresh. - // 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. - dbOpenError = err; // Record the error. It will be used to reject further promises of db operations. - return rejection(dbOpenError); - }).finally(function () { - openComplete = true; - resolveDbReady(); // dbReadyPromise is resolved no matter if open() rejects or resolved. It's just to wake up waiters. + return lastResult; }); }; - this.close = function () { - var idx = connections.indexOf(db); - if (idx >= 0) - connections.splice(idx, 1); - if (idbdb) { - try { - idbdb.close(); - } - catch (e) { } - idbdb = null; - } - autoOpen = false; - dbOpenError = new exceptions.DatabaseClosed(); - if (isBeingOpened) - cancelOpen(dbOpenError); - // Reset dbReadyPromise promise: - dbReadyPromise = new Promise(function (resolve) { - dbReadyResolve = resolve; - }); - openCanceller = new Promise(function (_, reject) { - cancelOpen = reject; + Table.prototype.upsert = function (key, modifications) { + var _this = this; + var keyPath = this.schema.primKey.keyPath; + return this._trans('readwrite', function (trans) { + return _this.core.get({ trans: trans, key: key }).then(function (existing) { + var obj = existing !== null && existing !== void 0 ? existing : {}; + applyUpdateSpec(obj, modifications); + if (keyPath) + setByKeyPath(obj, keyPath, key); + return _this.core.mutate({ + trans: trans, + type: 'put', + values: [obj], + keys: [key], + upsert: true, + updates: { keys: [key], changeSpecs: [modifications] } + }).then(function (res) { return res.numFailures ? DexiePromise.reject(res.failures[0]) : !!existing; }); + }); }); }; - this.delete = function () { - var hasArguments = arguments.length > 0; - return new Promise(function (resolve, reject) { - if (hasArguments) - throw new exceptions.InvalidArgument("Arguments not allowed in db.delete()"); - if (isBeingOpened) { - dbReadyPromise.then(doDelete); - } - else { - doDelete(); - } - function doDelete() { - db.close(); - var req = indexedDB.deleteDatabase(dbName); - req.onsuccess = wrap(function () { - if (!hasNativeGetDatabaseNames) { - dbNamesDB.dbnames.delete(dbName).catch(nop); - } - resolve(); - }); - req.onerror = eventRejectHandler(reject); - req.onblocked = fireOnBlocked; + Table.prototype.update = function (keyOrObject, modifications) { + if (typeof keyOrObject === 'object' && !isArray(keyOrObject)) { + var key = getByKeyPath(keyOrObject, this.schema.primKey.keyPath); + if (key === undefined) + return rejection(new exceptions.InvalidArgument("Given object does not contain its primary key")); + return this.where(":id").equals(key).modify(modifications); + } + else { + return this.where(":id").equals(keyOrObject).modify(modifications); + } + }; + Table.prototype.put = function (obj, key) { + var _this = this; + var _a = this.schema.primKey, auto = _a.auto, keyPath = _a.keyPath; + var objToAdd = obj; + if (keyPath && auto) { + objToAdd = workaroundForUndefinedPrimKey(keyPath)(obj); + } + return this._trans('readwrite', function (trans) { return _this.core.mutate({ trans: trans, type: 'put', values: [objToAdd], keys: key != null ? [key] : null }); }) + .then(function (res) { return res.numFailures ? DexiePromise.reject(res.failures[0]) : res.lastResult; }) + .then(function (lastResult) { + if (keyPath) { + try { + setByKeyPath(obj, keyPath, lastResult); + } + catch (_) { } } + return lastResult; }); }; - this.backendDB = function () { - return idbdb; - }; - this.isOpen = function () { - return idbdb !== null; - }; - this.hasBeenClosed = function () { - return dbOpenError && (dbOpenError instanceof exceptions.DatabaseClosed); + Table.prototype.delete = function (key) { + var _this = this; + return this._trans('readwrite', function (trans) { return _this.core.mutate({ trans: trans, type: 'delete', keys: [key] }) + .then(function (res) { return builtInDeletionTrigger(_this, [key], res); }) + .then(function (res) { return res.numFailures ? DexiePromise.reject(res.failures[0]) : undefined; }); }); }; - this.hasFailed = function () { - return dbOpenError !== null; + Table.prototype.clear = function () { + var _this = this; + return this._trans('readwrite', function (trans) { return _this.core.mutate({ trans: trans, type: 'deleteRange', range: AnyRange }) + .then(function (res) { return builtInDeletionTrigger(_this, null, res); }); }) + .then(function (res) { return res.numFailures ? DexiePromise.reject(res.failures[0]) : undefined; }); }; - this.dynamicallyOpened = function () { - return autoSchema; + Table.prototype.bulkGet = function (keys) { + var _this = this; + return this._trans('readonly', function (trans) { + return _this.core.getMany({ + keys: keys, + trans: trans + }).then(function (result) { return result.map(function (res) { return _this.hook.reading.fire(res); }); }); + }); }; - // - // Properties - // - this.name = dbName; - // db.tables - an array of all Table instances. - props(this, { - tables: { - get: function () { - /// - return keys(allTables).map(function (name) { return allTables[name]; }); - } - } - }); - // - // Events - // - this.on = Events(this, "populate", "blocked", "versionchange", { ready: [promisableChain, nop] }); - this.on.ready.subscribe = override(this.on.ready.subscribe, function (subscribe) { - return function (subscriber, bSticky) { - Dexie.vip(function () { - if (openComplete) { - // Database already open. Call subscriber asap. - if (!dbOpenError) - Promise.resolve().then(subscriber); - // bSticky: Also subscribe to future open sucesses (after close / reopen) - if (bSticky) - subscribe(subscriber); - } - else if (onReadyBeingFired) { - // db.on('ready') subscribers are currently being executed and have not yet resolved or rejected - onReadyBeingFired.push(subscriber); - if (bSticky) - subscribe(subscriber); - } - else { - // Database not yet open. Subscribe to it. - subscribe(subscriber); - // If bSticky is falsy, make sure to unsubscribe subscriber when fired once. - if (!bSticky) - subscribe(function unsubscribe() { - db.on.ready.unsubscribe(subscriber); - db.on.ready.unsubscribe(unsubscribe); - }); - } + Table.prototype.bulkAdd = function (objects, keysOrOptions, options) { + var _this = this; + var keys = Array.isArray(keysOrOptions) ? keysOrOptions : undefined; + options = options || (keys ? undefined : keysOrOptions); + var wantResults = options ? options.allKeys : undefined; + return this._trans('readwrite', function (trans) { + var _a = _this.schema.primKey, auto = _a.auto, keyPath = _a.keyPath; + if (keyPath && keys) + throw new exceptions.InvalidArgument("bulkAdd(): keys argument invalid on tables with inbound keys"); + if (keys && keys.length !== objects.length) + throw new exceptions.InvalidArgument("Arguments objects and keys must have the same length"); + var numObjects = objects.length; + var objectsToAdd = keyPath && auto ? + objects.map(workaroundForUndefinedPrimKey(keyPath)) : + objects; + return _this.core.mutate({ trans: trans, type: 'add', keys: keys, values: objectsToAdd, wantResults: wantResults }) + .then(function (_a) { + var numFailures = _a.numFailures, results = _a.results, lastResult = _a.lastResult, failures = _a.failures; + var result = wantResults ? results : lastResult; + if (numFailures === 0) + return result; + throw new BulkError("".concat(_this.name, ".bulkAdd(): ").concat(numFailures, " of ").concat(numObjects, " operations failed"), failures); }); - }; - }); - this.transaction = function () { - /// - /// - /// - /// "r" for readonly, or "rw" for readwrite - /// Table instance, Array of Table instances, String or String Array of object stores to include in the transaction - /// Function to execute with transaction - var args = extractTransactionArgs.apply(this, arguments); - return this._transaction.apply(this, args); + }); }; - function extractTransactionArgs(mode, _tableArgs_, scopeFunc) { - // Let table arguments be all arguments between mode and last argument. - var i = arguments.length; - if (i < 2) - throw new exceptions.InvalidArgument("Too few arguments"); - // Prevent optimzation killer (https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#32-leaking-arguments) - // and clone arguments except the first one into local var 'args'. - var args = new Array(i - 1); - while (--i) - args[i - 1] = arguments[i]; - // Let scopeFunc be the last argument and pop it so that args now only contain the table arguments. - scopeFunc = args.pop(); - var tables = flatten(args); // Support using array as middle argument, or a mix of arrays and non-arrays. - return [mode, tables, scopeFunc]; - } - this._transaction = function (mode, tables, scopeFunc) { - var parentTransaction = PSD.trans; - // Check if parent transactions is bound to this db instance, and if caller wants to reuse it - if (!parentTransaction || parentTransaction.db !== db || mode.indexOf('!') !== -1) - parentTransaction = null; - var onlyIfCompatible = mode.indexOf('?') !== -1; - mode = mode.replace('!', '').replace('?', ''); // Ok. Will change arguments[0] as well but we wont touch arguments henceforth. - try { - // - // Get storeNames from arguments. Either through given table instances, or through given table names. - // - var storeNames = tables.map(function (table) { - var storeName = table instanceof Table ? table.name : table; - if (typeof storeName !== 'string') - throw new TypeError("Invalid table argument to Dexie.transaction(). Only Table or String are allowed"); - return storeName; + Table.prototype.bulkPut = function (objects, keysOrOptions, options) { + var _this = this; + var keys = Array.isArray(keysOrOptions) ? keysOrOptions : undefined; + options = options || (keys ? undefined : keysOrOptions); + var wantResults = options ? options.allKeys : undefined; + return this._trans('readwrite', function (trans) { + var _a = _this.schema.primKey, auto = _a.auto, keyPath = _a.keyPath; + if (keyPath && keys) + throw new exceptions.InvalidArgument("bulkPut(): keys argument invalid on tables with inbound keys"); + if (keys && keys.length !== objects.length) + throw new exceptions.InvalidArgument("Arguments objects and keys must have the same length"); + var numObjects = objects.length; + var objectsToPut = keyPath && auto ? + objects.map(workaroundForUndefinedPrimKey(keyPath)) : + objects; + return _this.core.mutate({ trans: trans, type: 'put', keys: keys, values: objectsToPut, wantResults: wantResults }) + .then(function (_a) { + var numFailures = _a.numFailures, results = _a.results, lastResult = _a.lastResult, failures = _a.failures; + var result = wantResults ? results : lastResult; + if (numFailures === 0) + return result; + throw new BulkError("".concat(_this.name, ".bulkPut(): ").concat(numFailures, " of ").concat(numObjects, " operations failed"), failures); }); - // - // Resolve mode. Allow shortcuts "r" and "rw". - // - if (mode == "r" || mode == READONLY) - mode = READONLY; - else if (mode == "rw" || mode == READWRITE) - mode = READWRITE; - else - throw new exceptions.InvalidArgument("Invalid transaction mode: " + mode); - if (parentTransaction) { - // Basic checks - if (parentTransaction.mode === READONLY && mode === READWRITE) { - if (onlyIfCompatible) { - // Spawn new transaction instead. - parentTransaction = null; - } - else - throw new exceptions.SubTransaction("Cannot enter a sub-transaction with READWRITE mode when parent transaction is READONLY"); - } - if (parentTransaction) { - storeNames.forEach(function (storeName) { - if (parentTransaction && parentTransaction.storeNames.indexOf(storeName) === -1) { - if (onlyIfCompatible) { - // Spawn new transaction instead. - parentTransaction = null; + }); + }; + Table.prototype.bulkUpdate = function (keysAndChanges) { + var _this = this; + var coreTable = this.core; + var keys = keysAndChanges.map(function (entry) { return entry.key; }); + var changeSpecs = keysAndChanges.map(function (entry) { return entry.changes; }); + var offsetMap = []; + return this._trans('readwrite', function (trans) { + return coreTable.getMany({ trans: trans, keys: keys, cache: 'clone' }).then(function (objs) { + var resultKeys = []; + var resultObjs = []; + keysAndChanges.forEach(function (_a, idx) { + var key = _a.key, changes = _a.changes; + var obj = objs[idx]; + if (obj) { + for (var _i = 0, _b = Object.keys(changes); _i < _b.length; _i++) { + var keyPath = _b[_i]; + var value = changes[keyPath]; + if (keyPath === _this.schema.primKey.keyPath) { + if (cmp(value, key) !== 0) { + throw new exceptions.Constraint("Cannot update primary key in bulkUpdate()"); + } + } + else { + setByKeyPath(obj, keyPath, value); } - else - throw new exceptions.SubTransaction("Table " + storeName + - " not included in parent transaction."); } - }); - } - if (onlyIfCompatible && parentTransaction && !parentTransaction.active) { - // '?' mode should not keep using an inactive transaction. - parentTransaction = null; - } - } - } - catch (e) { - return parentTransaction ? - parentTransaction._promise(null, function (_, reject) { reject(e); }) : - rejection(e); - } - // If this is a sub-transaction, lock the parent and then launch the sub-transaction. - return (parentTransaction ? - parentTransaction._promise(mode, enterTransactionScope, "lock") : - PSD.trans ? - // no parent transaction despite PSD.trans exists. Make sure also - // that the zone we create is not a sub-zone of current, because - // Promise.follow() should not wait for it if so. - usePSD(PSD.transless, function () { return db._whenReady(enterTransactionScope); }) : - db._whenReady(enterTransactionScope)); - function enterTransactionScope() { - return Promise.resolve().then(function () { - // Keep a pointer to last non-transactional PSD to use if someone calls Dexie.ignoreTransaction(). - var transless = PSD.transless || PSD; - // Our transaction. - //return new Promise((resolve, reject) => { - var trans = db._createTransaction(mode, storeNames, globalSchema, parentTransaction); - // Let the transaction instance be part of a Promise-specific data (PSD) value. - var zoneProps = { + offsetMap.push(idx); + resultKeys.push(key); + resultObjs.push(obj); + } + }); + var numEntries = resultKeys.length; + return coreTable + .mutate({ trans: trans, - transless: transless - }; - if (parentTransaction) { - // Emulate transaction commit awareness for inner transaction (must 'commit' when the inner transaction has no more operations ongoing) - trans.idbtrans = parentTransaction.idbtrans; - } - else { - trans.create(); // Create the backend transaction so that complete() or error() will trigger even if no operation is made upon it. - } - // Support for native async await. - if (scopeFunc.constructor === AsyncFunction) { - incrementExpectedAwaits(); - } - var returnValue; - var promiseFollowed = Promise.follow(function () { - // Finally, call the scope function with our table and transaction arguments. - returnValue = scopeFunc.call(trans, trans); - if (returnValue) { - if (returnValue.constructor === NativePromise) { - var decrementor = decrementExpectedAwaits.bind(null, null); - returnValue.then(decrementor, decrementor); - } - else if (typeof returnValue.next === 'function' && typeof returnValue.throw === 'function') { - // scopeFunc returned an iterator with throw-support. Handle yield as await. - returnValue = awaitIterator(returnValue); + type: 'put', + keys: resultKeys, + values: resultObjs, + updates: { + keys: keys, + changeSpecs: changeSpecs + } + }) + .then(function (_a) { + var numFailures = _a.numFailures, failures = _a.failures; + if (numFailures === 0) + return numEntries; + for (var _i = 0, _b = Object.keys(failures); _i < _b.length; _i++) { + var offset = _b[_i]; + var mappedOffset = offsetMap[Number(offset)]; + if (mappedOffset != null) { + var failure = failures[offset]; + delete failures[offset]; + failures[mappedOffset] = failure; } } - }, zoneProps); - return (returnValue && typeof returnValue.then === 'function' ? - // Promise returned. User uses promise-style transactions. - Promise.resolve(returnValue).then(function (x) { return trans.active ? - x // Transaction still active. Continue. - : rejection(new exceptions.PrematureCommit("Transaction committed too early. See http://bit.ly/2kdckMn")); }) - // No promise returned. Wait for all outstanding promises before continuing. - : promiseFollowed.then(function () { return returnValue; })).then(function (x) { - // sub transactions don't react to idbtrans.oncomplete. We must trigger a completion: - if (parentTransaction) - trans._resolve(); - // wait for trans._completion - // (if root transaction, this means 'complete' event. If sub-transaction, we've just fired it ourselves) - return trans._completion.then(function () { return x; }); - }).catch(function (e) { - trans._reject(e); // Yes, above then-handler were maybe not called because of an unhandled rejection in scopeFunc! - return rejection(e); + throw new BulkError("".concat(_this.name, ".bulkUpdate(): ").concat(numFailures, " of ").concat(numEntries, " operations failed"), failures); }); }); - } + }); }; - this.table = function (tableName) { - /// - if (!hasOwn(allTables, tableName)) { - throw new exceptions.InvalidTable("Table " + tableName + " does not exist"); - } - return allTables[tableName]; + Table.prototype.bulkDelete = function (keys) { + var _this = this; + var numKeys = keys.length; + return this._trans('readwrite', function (trans) { + return _this.core.mutate({ trans: trans, type: 'delete', keys: keys }) + .then(function (res) { return builtInDeletionTrigger(_this, keys, res); }); + }).then(function (_a) { + var numFailures = _a.numFailures, lastResult = _a.lastResult, failures = _a.failures; + if (numFailures === 0) + return lastResult; + throw new BulkError("".concat(_this.name, ".bulkDelete(): ").concat(numFailures, " of ").concat(numKeys, " operations failed"), failures); + }); }; - // - // - // - // Table Class - // - // - // - function Table(name, tableSchema, optionalTrans) { - /// - this.name = name; - this.schema = tableSchema; - this._tx = optionalTrans; - this.hook = allTables[name] ? allTables[name].hook : Events(null, { - "creating": [hookCreatingChain, nop], - "reading": [pureFunctionChain, mirror], - "updating": [hookUpdatingChain, nop], - "deleting": [hookDeletingChain, nop] + return Table; +}()); + +function Events(ctx) { + var evs = {}; + var rv = function (eventName, subscriber) { + if (subscriber) { + var i = arguments.length, args = new Array(i - 1); + while (--i) + args[i - 1] = arguments[i]; + evs[eventName].subscribe.apply(null, args); + return ctx; + } + else if (typeof (eventName) === 'string') { + return evs[eventName]; + } + }; + rv.addEventType = add; + for (var i = 1, l = arguments.length; i < l; ++i) { + add(arguments[i]); + } + return rv; + function add(eventName, chainFunction, defaultFunction) { + if (typeof eventName === 'object') + return addConfiguredEvents(eventName); + if (!chainFunction) + chainFunction = reverseStoppableEventChain; + if (!defaultFunction) + defaultFunction = nop; + var context = { + subscribers: [], + fire: defaultFunction, + subscribe: function (cb) { + if (context.subscribers.indexOf(cb) === -1) { + context.subscribers.push(cb); + context.fire = chainFunction(context.fire, cb); + } + }, + unsubscribe: function (cb) { + context.subscribers = context.subscribers.filter(function (fn) { return fn !== cb; }); + context.fire = context.subscribers.reduce(chainFunction, defaultFunction); + } + }; + evs[eventName] = rv[eventName] = context; + return context; + } + function addConfiguredEvents(cfg) { + keys(cfg).forEach(function (eventName) { + var args = cfg[eventName]; + if (isArray(args)) { + add(eventName, cfg[eventName][0], cfg[eventName][1]); + } + else if (args === 'asap') { + var context = add(eventName, mirror, function fire() { + var i = arguments.length, args = new Array(i); + while (i--) + args[i] = arguments[i]; + context.subscribers.forEach(function (fn) { + asap$1(function fireEvent() { + fn.apply(null, args); + }); + }); + }); + } + else + throw new exceptions.InvalidArgument("Invalid event config"); }); } - function BulkErrorHandlerCatchAll(errorList, done, supportHooks) { - return (supportHooks ? hookedEventRejectHandler : eventRejectHandler)(function (e) { - errorList.push(e); - done && done(); +} + +function makeClassConstructor(prototype, constructor) { + derive(constructor).from({ prototype: prototype }); + return constructor; +} + +function createTableConstructor(db) { + return makeClassConstructor(Table.prototype, function Table(name, tableSchema, trans) { + this.db = db; + this._tx = trans; + this.name = name; + this.schema = tableSchema; + this.hook = db._allTables[name] ? db._allTables[name].hook : Events(null, { + "creating": [hookCreatingChain, nop], + "reading": [pureFunctionChain, mirror], + "updating": [hookUpdatingChain, nop], + "deleting": [hookDeletingChain, nop] }); + }); +} + +function isPlainKeyRange(ctx, ignoreLimitFilter) { + return !(ctx.filter || ctx.algorithm || ctx.or) && + (ignoreLimitFilter ? ctx.justLimit : !ctx.replayFilter); +} +function addFilter(ctx, fn) { + ctx.filter = combine(ctx.filter, fn); +} +function addReplayFilter(ctx, factory, isLimitFilter) { + var curr = ctx.replayFilter; + ctx.replayFilter = curr ? function () { return combine(curr(), factory()); } : factory; + ctx.justLimit = isLimitFilter && !curr; +} +function addMatchFilter(ctx, fn) { + ctx.isMatch = combine(ctx.isMatch, fn); +} +function getIndexOrStore(ctx, coreSchema) { + if (ctx.isPrimKey) + return coreSchema.primaryKey; + var index = coreSchema.getIndexByKeyPath(ctx.index); + if (!index) + throw new exceptions.Schema("KeyPath " + ctx.index + " on object store " + coreSchema.name + " is not indexed"); + return index; +} +function openCursor(ctx, coreTable, trans) { + var index = getIndexOrStore(ctx, coreTable.schema); + return coreTable.openCursor({ + trans: trans, + values: !ctx.keysOnly, + reverse: ctx.dir === 'prev', + unique: !!ctx.unique, + query: { + index: index, + range: ctx.range + } + }); +} +function iter(ctx, fn, coreTrans, coreTable) { + var filter = ctx.replayFilter ? combine(ctx.filter, ctx.replayFilter()) : ctx.filter; + if (!ctx.or) { + return iterate(openCursor(ctx, coreTable, coreTrans), combine(ctx.algorithm, filter), fn, !ctx.keysOnly && ctx.valueMapper); } - function bulkDelete(idbstore, trans, keysOrTuples, hasDeleteHook, deletingHook) { - // If hasDeleteHook, keysOrTuples must be an array of tuples: [[key1, value2],[key2,value2],...], - // else keysOrTuples must be just an array of keys: [key1, key2, ...]. - return new Promise(function (resolve, reject) { - var len = keysOrTuples.length, lastItem = len - 1; - if (len === 0) - return resolve(); - if (!hasDeleteHook) { - for (var i = 0; i < len; ++i) { - var req = idbstore.delete(keysOrTuples[i]); - req.onerror = eventRejectHandler(reject); - if (i === lastItem) - req.onsuccess = wrap(function () { return resolve(); }); + else { + var set_1 = {}; + var union = function (item, cursor, advance) { + if (!filter || filter(cursor, advance, function (result) { return cursor.stop(result); }, function (err) { return cursor.fail(err); })) { + var primaryKey = cursor.primaryKey; + var key = '' + primaryKey; + if (key === '[object ArrayBuffer]') + key = '' + new Uint8Array(primaryKey); + if (!hasOwn(set_1, key)) { + set_1[key] = true; + fn(item, cursor, advance); } } + }; + return Promise.all([ + ctx.or._iterate(union, coreTrans), + iterate(openCursor(ctx, coreTable, coreTrans), ctx.algorithm, union, !ctx.keysOnly && ctx.valueMapper) + ]); + } +} +function iterate(cursorPromise, filter, fn, valueMapper) { + var mappedFn = valueMapper ? function (x, c, a) { return fn(valueMapper(x), c, a); } : fn; + var wrappedFn = wrap(mappedFn); + return cursorPromise.then(function (cursor) { + if (cursor) { + return cursor.start(function () { + var c = function () { return cursor.continue(); }; + if (!filter || filter(cursor, function (advancer) { return c = advancer; }, function (val) { cursor.stop(val); c = nop; }, function (e) { cursor.fail(e); c = nop; })) + wrappedFn(cursor.value, cursor, function (advancer) { return c = advancer; }); + c(); + }); + } + }); +} + +var Collection = (function () { + function Collection() { + } + Collection.prototype._read = function (fn, cb) { + var ctx = this._ctx; + return ctx.error ? + ctx.table._trans(null, rejection.bind(null, ctx.error)) : + ctx.table._trans('readonly', fn).then(cb); + }; + Collection.prototype._write = function (fn) { + var ctx = this._ctx; + return ctx.error ? + ctx.table._trans(null, rejection.bind(null, ctx.error)) : + ctx.table._trans('readwrite', fn, "locked"); + }; + Collection.prototype._addAlgorithm = function (fn) { + var ctx = this._ctx; + ctx.algorithm = combine(ctx.algorithm, fn); + }; + Collection.prototype._iterate = function (fn, coreTrans) { + return iter(this._ctx, fn, coreTrans, this._ctx.table.core); + }; + Collection.prototype.clone = function (props) { + var rv = Object.create(this.constructor.prototype), ctx = Object.create(this._ctx); + if (props) + extend(ctx, props); + rv._ctx = ctx; + return rv; + }; + Collection.prototype.raw = function () { + this._ctx.valueMapper = null; + return this; + }; + Collection.prototype.each = function (fn) { + var ctx = this._ctx; + return this._read(function (trans) { return iter(ctx, fn, trans, ctx.table.core); }); + }; + Collection.prototype.count = function (cb) { + var _this = this; + return this._read(function (trans) { + var ctx = _this._ctx; + var coreTable = ctx.table.core; + if (isPlainKeyRange(ctx, true)) { + return coreTable.count({ + trans: trans, + query: { + index: getIndexOrStore(ctx, coreTable.schema), + range: ctx.range + } + }).then(function (count) { return Math.min(count, ctx.limit); }); + } else { - var hookCtx, errorHandler = hookedEventRejectHandler(reject), successHandler = hookedEventSuccessHandler(null); - tryCatch(function () { - for (var i = 0; i < len; ++i) { - hookCtx = { onsuccess: null, onerror: null }; - var tuple = keysOrTuples[i]; - deletingHook.call(hookCtx, tuple[0], tuple[1], trans); - var req = idbstore.delete(tuple[0]); - req._hookCtx = hookCtx; - req.onerror = errorHandler; - if (i === lastItem) - req.onsuccess = hookedEventSuccessHandler(resolve); - else - req.onsuccess = successHandler; + var count = 0; + return iter(ctx, function () { ++count; return false; }, trans, coreTable) + .then(function () { return count; }); + } + }).then(cb); + }; + Collection.prototype.sortBy = function (keyPath, cb) { + var parts = keyPath.split('.').reverse(), lastPart = parts[0], lastIndex = parts.length - 1; + function getval(obj, i) { + if (i) + return getval(obj[parts[i]], i - 1); + return obj[lastPart]; + } + var order = this._ctx.dir === "next" ? 1 : -1; + function sorter(a, b) { + var aVal = getval(a, lastIndex), bVal = getval(b, lastIndex); + return cmp(aVal, bVal) * order; + } + return this.toArray(function (a) { + return a.sort(sorter); + }).then(cb); + }; + Collection.prototype.toArray = function (cb) { + var _this = this; + return this._read(function (trans) { + var ctx = _this._ctx; + if (ctx.dir === 'next' && isPlainKeyRange(ctx, true) && ctx.limit > 0) { + var valueMapper_1 = ctx.valueMapper; + var index = getIndexOrStore(ctx, ctx.table.core.schema); + return ctx.table.core.query({ + trans: trans, + limit: ctx.limit, + values: true, + query: { + index: index, + range: ctx.range } - }, function (err) { - hookCtx.onerror && hookCtx.onerror(err); - throw err; + }).then(function (_a) { + var result = _a.result; + return valueMapper_1 ? result.map(valueMapper_1) : result; }); } - }); - } - props(Table.prototype, { - // - // Table Protected Methods - // - _trans: function getTransaction(mode, fn, writeLocked) { - var trans = this._tx || PSD.trans; - return trans && trans.db === db ? - trans === PSD.trans ? - trans._promise(mode, fn, writeLocked) : - newScope(function () { return trans._promise(mode, fn, writeLocked); }, { trans: trans, transless: PSD.transless || PSD }) : - tempTransaction(mode, [this.name], fn); - }, - _idbstore: function getIDBObjectStore(mode, fn, writeLocked) { - var tableName = this.name; - function supplyIdbStore(resolve, reject, trans) { - if (trans.storeNames.indexOf(tableName) === -1) - throw new exceptions.NotFound("Table" + tableName + " not part of transaction"); - return fn(resolve, reject, trans.idbtrans.objectStore(tableName), trans); - } - return this._trans(mode, supplyIdbStore, writeLocked); - }, - // - // Table Public Methods - // - get: function (keyOrCrit, cb) { - if (keyOrCrit && keyOrCrit.constructor === Object) - return this.where(keyOrCrit).first(cb); - var self = this; - return this._idbstore(READONLY, function (resolve, reject, idbstore) { - var req = idbstore.get(keyOrCrit); - req.onerror = eventRejectHandler(reject); - req.onsuccess = wrap(function () { - resolve(self.hook.reading.fire(req.result)); - }, reject); - }).then(cb); - }, - where: function (indexOrCrit) { - if (typeof indexOrCrit === 'string') - return new WhereClause(this, indexOrCrit); - if (isArray(indexOrCrit)) - return new WhereClause(this, "[" + indexOrCrit.join('+') + "]"); - // indexOrCrit is an object map of {[keyPath]:value} - var keyPaths = keys(indexOrCrit); - if (keyPaths.length === 1) - // Only one critera. This was the easy case: - return this - .where(keyPaths[0]) - .equals(indexOrCrit[keyPaths[0]]); - // Multiple criterias. - // Let's try finding a compound index that matches all keyPaths in - // arbritary order: - var compoundIndex = this.schema.indexes.concat(this.schema.primKey).filter(function (ix) { - return ix.compound && - keyPaths.every(function (keyPath) { return ix.keyPath.indexOf(keyPath) >= 0; }) && - ix.keyPath.every(function (keyPath) { return keyPaths.indexOf(keyPath) >= 0; }); - })[0]; - if (compoundIndex && maxKey !== maxString) - // Cool! We found such compound index - // and this browser supports compound indexes (maxKey !== maxString)! - return this - .where(compoundIndex.name) - .equals(compoundIndex.keyPath.map(function (kp) { return indexOrCrit[kp]; })); - 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 - // and filter the rest. - var idxByName = this.schema.idxByName; - var simpleIndex = keyPaths.reduce(function (r, keyPath) { return [ - r[0] || idxByName[keyPath], - r[0] || !idxByName[keyPath] ? - combine(r[1], function (x) { return '' + getByKeyPath(x, keyPath) == - '' + indexOrCrit[keyPath]; }) - : r[1] - ]; }, [null, null]); - var idx = simpleIndex[0]; - 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. - this.where(keyPaths).equals(''); // No index at all. Fail lazily. - }, - count: function (cb) { - return this.toCollection().count(cb); - }, - offset: function (offset) { - return this.toCollection().offset(offset); - }, - limit: function (numRows) { - return this.toCollection().limit(numRows); - }, - reverse: function () { - return this.toCollection().reverse(); - }, - filter: function (filterFunction) { - return this.toCollection().and(filterFunction); - }, - each: function (fn) { - return this.toCollection().each(fn); - }, - toArray: function (cb) { - return this.toCollection().toArray(cb); - }, - orderBy: function (index) { - return new Collection(new WhereClause(this, isArray(index) ? - "[" + index.join('+') + "]" : - index)); - }, - toCollection: function () { - return new Collection(new WhereClause(this)); - }, - mapToClass: function (constructor, structure) { - /// - /// Map table to a javascript constructor function. Objects returned from the database will be instances of this class, making - /// it possible to the instanceOf operator as well as extending the class using constructor.prototype.method = function(){...}. - /// - /// Constructor function representing the class. - /// Helps IDE code completion by knowing the members that objects contain and not just the indexes. Also - /// know what type each member has. Example: {name: String, emailAddresses: [String], password} - this.schema.mappedClass = constructor; - var instanceTemplate = Object.create(constructor.prototype); - if (structure) { - // structure and instanceTemplate is for IDE code competion only while constructor.prototype is for actual inheritance. - applyStructure(instanceTemplate, structure); + else { + var a_1 = []; + return iter(ctx, function (item) { return a_1.push(item); }, trans, ctx.table.core).then(function () { return a_1; }); } - 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 - // no matter which method to use for reading (Table.get() or Table.where(...)... ) - var readHook = function (obj) { - if (!obj) - return obj; // No valid object. (Value is null). Return as is. - // Create a new object that derives from constructor: - var res = Object.create(constructor.prototype); - // Clone members: - for (var m in obj) - if (hasOwn(obj, m)) - try { - res[m] = obj[m]; - } - catch (_) { } - return res; + }, cb); + }; + Collection.prototype.offset = function (offset) { + var ctx = this._ctx; + if (offset <= 0) + return this; + ctx.offset += offset; + if (isPlainKeyRange(ctx)) { + addReplayFilter(ctx, function () { + var offsetLeft = offset; + return function (cursor, advance) { + if (offsetLeft === 0) + return true; + if (offsetLeft === 1) { + --offsetLeft; + return false; + } + advance(function () { + cursor.advance(offsetLeft); + offsetLeft = 0; + }); + return false; + }; + }); + } + else { + addReplayFilter(ctx, function () { + var offsetLeft = offset; + return function () { return (--offsetLeft < 0); }; + }); + } + return this; + }; + Collection.prototype.limit = function (numRows) { + this._ctx.limit = Math.min(this._ctx.limit, numRows); + addReplayFilter(this._ctx, function () { + var rowsLeft = numRows; + return function (cursor, advance, resolve) { + if (--rowsLeft <= 0) + advance(resolve); + return rowsLeft >= 0; }; - if (this.schema.readHook) { - this.hook.reading.unsubscribe(this.schema.readHook); + }, true); + return this; + }; + Collection.prototype.until = function (filterFunction, bIncludeStopEntry) { + addFilter(this._ctx, function (cursor, advance, resolve) { + if (filterFunction(cursor.value)) { + advance(resolve); + return bIncludeStopEntry; } - this.schema.readHook = readHook; - this.hook("reading", readHook); - return constructor; - }, - defineClass: function (structure) { - /// - /// Define all members of the class that represents the table. This will help code completion of when objects are read from the database - /// as well as making it possible to extend the prototype of the returned constructor function. - /// - /// Helps IDE code completion by knowing the members that objects contain and not just the indexes. Also - /// know what type each member has. Example: {name: String, emailAddresses: [String], properties: {shoeSize: Number}} - return this.mapToClass(Dexie.defineClass(structure), structure); - }, - bulkDelete: function (keys$$1) { - if (this.hook.deleting.fire === nop) { - return this._idbstore(READWRITE, function (resolve, reject, idbstore, trans) { - resolve(bulkDelete(idbstore, trans, keys$$1, false, nop)); + else { + return true; + } + }); + return this; + }; + Collection.prototype.first = function (cb) { + return this.limit(1).toArray(function (a) { return a[0]; }).then(cb); + }; + Collection.prototype.last = function (cb) { + return this.reverse().first(cb); + }; + Collection.prototype.filter = function (filterFunction) { + addFilter(this._ctx, function (cursor) { + return filterFunction(cursor.value); + }); + addMatchFilter(this._ctx, filterFunction); + return this; + }; + Collection.prototype.and = function (filter) { + return this.filter(filter); + }; + Collection.prototype.or = function (indexName) { + return new this.db.WhereClause(this._ctx.table, indexName, this); + }; + Collection.prototype.reverse = function () { + this._ctx.dir = (this._ctx.dir === "prev" ? "next" : "prev"); + if (this._ondirectionchange) + this._ondirectionchange(this._ctx.dir); + return this; + }; + Collection.prototype.desc = function () { + return this.reverse(); + }; + Collection.prototype.eachKey = function (cb) { + var ctx = this._ctx; + ctx.keysOnly = !ctx.isMatch; + return this.each(function (val, cursor) { cb(cursor.key, cursor); }); + }; + Collection.prototype.eachUniqueKey = function (cb) { + this._ctx.unique = "unique"; + return this.eachKey(cb); + }; + Collection.prototype.eachPrimaryKey = function (cb) { + var ctx = this._ctx; + ctx.keysOnly = !ctx.isMatch; + return this.each(function (val, cursor) { cb(cursor.primaryKey, cursor); }); + }; + Collection.prototype.keys = function (cb) { + var ctx = this._ctx; + ctx.keysOnly = !ctx.isMatch; + var a = []; + return this.each(function (item, cursor) { + a.push(cursor.key); + }).then(function () { + return a; + }).then(cb); + }; + Collection.prototype.primaryKeys = function (cb) { + var ctx = this._ctx; + if (ctx.dir === 'next' && isPlainKeyRange(ctx, true) && ctx.limit > 0) { + return this._read(function (trans) { + var index = getIndexOrStore(ctx, ctx.table.core.schema); + return ctx.table.core.query({ + trans: trans, + values: false, + limit: ctx.limit, + query: { + index: index, + range: ctx.range + } }); + }).then(function (_a) { + var result = _a.result; + return result; + }).then(cb); + } + ctx.keysOnly = !ctx.isMatch; + var a = []; + return this.each(function (item, cursor) { + a.push(cursor.primaryKey); + }).then(function () { + return a; + }).then(cb); + }; + Collection.prototype.uniqueKeys = function (cb) { + this._ctx.unique = "unique"; + return this.keys(cb); + }; + Collection.prototype.firstKey = function (cb) { + return this.limit(1).keys(function (a) { return a[0]; }).then(cb); + }; + Collection.prototype.lastKey = function (cb) { + return this.reverse().firstKey(cb); + }; + Collection.prototype.distinct = function () { + var ctx = this._ctx, idx = ctx.index && ctx.table.schema.idxByName[ctx.index]; + if (!idx || !idx.multi) + return this; + var set = {}; + addFilter(this._ctx, function (cursor) { + var strKey = cursor.primaryKey.toString(); + var found = hasOwn(set, strKey); + set[strKey] = true; + return !found; + }); + return this; + }; + Collection.prototype.modify = function (changes) { + var _this = this; + var ctx = this._ctx; + return this._write(function (trans) { + var modifyer; + if (typeof changes === 'function') { + modifyer = changes; } else { - return this - .where(':id') - .anyOf(keys$$1) - .delete() - .then(function () { }); // Resolve with undefined. + modifyer = function (item) { return applyUpdateSpec(item, changes); }; } - }, - bulkPut: function (objects, keys$$1) { - var _this = this; - return this._idbstore(READWRITE, function (resolve, reject, idbstore) { - if (!idbstore.keyPath && !_this.schema.primKey.auto && !keys$$1) - throw new exceptions.InvalidArgument("bulkPut() with non-inbound keys requires keys array in second argument"); - if (idbstore.keyPath && keys$$1) - throw new exceptions.InvalidArgument("bulkPut(): keys argument invalid on tables with inbound keys"); - if (keys$$1 && keys$$1.length !== objects.length) - throw new exceptions.InvalidArgument("Arguments objects and keys must have the same length"); - if (objects.length === 0) - return resolve(); // Caller provided empty list. - var done = function (result) { - if (errorList.length === 0) - resolve(result); - else - reject(new BulkError(_this.name + ".bulkPut(): " + errorList.length + " of " + numObjs + " operations failed", errorList)); - }; - var req, errorList = [], errorHandler, numObjs = objects.length, table = _this; - if (_this.hook.creating.fire === nop && _this.hook.updating.fire === nop) { - // - // Standard Bulk (no 'creating' or 'updating' hooks to care about) - // - errorHandler = BulkErrorHandlerCatchAll(errorList); - for (var i = 0, l = objects.length; i < l; ++i) { - req = keys$$1 ? idbstore.put(objects[i], keys$$1[i]) : idbstore.put(objects[i]); - req.onerror = errorHandler; - } - // Only need to catch success or error on the last operation - // according to the IDB spec. - req.onerror = BulkErrorHandlerCatchAll(errorList, done); - req.onsuccess = eventSuccessHandler(done); + var coreTable = ctx.table.core; + var _a = coreTable.schema.primaryKey, outbound = _a.outbound, extractKey = _a.extractKey; + var limit = 200; + var modifyChunkSize = _this.db._options.modifyChunkSize; + if (modifyChunkSize) { + if (typeof modifyChunkSize == 'object') { + limit = modifyChunkSize[coreTable.name] || modifyChunkSize['*'] || 200; } else { - var effectiveKeys = keys$$1 || idbstore.keyPath && objects.map(function (o) { return getByKeyPath(o, idbstore.keyPath); }); - // Generate map of {[key]: object} - var objectLookup = effectiveKeys && arrayToObject(effectiveKeys, function (key, i) { return key != null && [key, objects[i]]; }); - var promise = !effectiveKeys ? - // Auto-incremented key-less objects only without any keys argument. - table.bulkAdd(objects) : - // Keys provided. Either as inbound in provided objects, or as a keys argument. - // Begin with updating those that exists in DB: - table.where(':id').anyOf(effectiveKeys.filter(function (key) { return key != null; })).modify(function () { - this.value = objectLookup[this.primKey]; - objectLookup[this.primKey] = null; // Mark as "don't add this" - }).catch(ModifyError, function (e) { - errorList = e.failures; // No need to concat here. These are the first errors added. - }).then(function () { - // Now, let's examine which items didnt exist so we can add them: - var objsToAdd = [], keysToAdd = keys$$1 && []; - // Iterate backwards. Why? Because if same key was used twice, just add the last one. - for (var i = effectiveKeys.length - 1; i >= 0; --i) { - var key = effectiveKeys[i]; - if (key == null || objectLookup[key]) { - objsToAdd.push(objects[i]); - keys$$1 && keysToAdd.push(key); - if (key != null) - objectLookup[key] = null; // Mark as "dont add again" - } - } - // The items are in reverse order so reverse them before adding. - // Could be important in order to get auto-incremented keys the way the caller - // would expect. Could have used unshift instead of push()/reverse(), - // but: http://jsperf.com/unshift-vs-reverse - objsToAdd.reverse(); - keys$$1 && keysToAdd.reverse(); - return table.bulkAdd(objsToAdd, keysToAdd); - }).then(function (lastAddedKey) { - // Resolve with key of the last object in given arguments to bulkPut(): - var lastEffectiveKey = effectiveKeys[effectiveKeys.length - 1]; // Key was provided. - return lastEffectiveKey != null ? lastEffectiveKey : lastAddedKey; - }); - promise.then(done).catch(BulkError, function (e) { - // Concat failure from ModifyError and reject using our 'done' method. - errorList = errorList.concat(e.failures); - done(); - }).catch(reject); + limit = modifyChunkSize; } - }, "locked"); // If called from transaction scope, lock transaction til all steps are done. - }, - bulkAdd: function (objects, keys$$1) { - var self = this, creatingHook = this.hook.creating.fire; - return this._idbstore(READWRITE, function (resolve, reject, idbstore, trans) { - if (!idbstore.keyPath && !self.schema.primKey.auto && !keys$$1) - throw new exceptions.InvalidArgument("bulkAdd() with non-inbound keys requires keys array in second argument"); - if (idbstore.keyPath && keys$$1) - throw new exceptions.InvalidArgument("bulkAdd(): keys argument invalid on tables with inbound keys"); - if (keys$$1 && keys$$1.length !== objects.length) - throw new exceptions.InvalidArgument("Arguments objects and keys must have the same length"); - if (objects.length === 0) - return resolve(); // Caller provided empty list. - function done(result) { - if (errorList.length === 0) - resolve(result); - else - reject(new BulkError(self.name + ".bulkAdd(): " + errorList.length + " of " + numObjs + " operations failed", errorList)); + } + var totalFailures = []; + var successCount = 0; + var failedKeys = []; + var applyMutateResult = function (expectedCount, res) { + var failures = res.failures, numFailures = res.numFailures; + successCount += expectedCount - numFailures; + for (var _i = 0, _a = keys(failures); _i < _a.length; _i++) { + var pos = _a[_i]; + totalFailures.push(failures[pos]); } - var req, errorList = [], errorHandler, successHandler, numObjs = objects.length; - if (creatingHook !== nop) { - // - // There are subscribers to hook('creating') - // Must behave as documented. - // - var keyPath = idbstore.keyPath, hookCtx; - errorHandler = BulkErrorHandlerCatchAll(errorList, null, true); - successHandler = hookedEventSuccessHandler(null); - tryCatch(function () { - for (var i = 0, l = objects.length; i < l; ++i) { - hookCtx = { onerror: null, onsuccess: null }; - var key = keys$$1 && keys$$1[i]; - var obj = objects[i], effectiveKey = keys$$1 ? key : keyPath ? getByKeyPath(obj, keyPath) : undefined, keyToUse = creatingHook.call(hookCtx, effectiveKey, obj, trans); - if (effectiveKey == null && keyToUse != null) { - if (keyPath) { - obj = deepClone(obj); - setByKeyPath(obj, keyPath, keyToUse); - } - else { - key = keyToUse; + }; + var isUnconditionalDelete = changes === deleteCallback; + return _this.clone().primaryKeys().then(function (keys) { + var criteria = isPlainKeyRange(ctx) && + ctx.limit === Infinity && + (typeof changes !== 'function' || isUnconditionalDelete) && { + index: ctx.index, + range: ctx.range + }; + var nextChunk = function (offset) { + var count = Math.min(limit, keys.length - offset); + var keysInChunk = keys.slice(offset, offset + count); + return (isUnconditionalDelete ? Promise.resolve([]) : coreTable.getMany({ + trans: trans, + keys: keysInChunk, + cache: "immutable" + })).then(function (values) { + var addValues = []; + var putValues = []; + var putKeys = outbound ? [] : null; + var deleteKeys = isUnconditionalDelete ? keysInChunk : []; + if (!isUnconditionalDelete) + for (var i = 0; i < count; ++i) { + var origValue = values[i]; + var ctx_1 = { + value: deepClone(origValue), + primKey: keys[offset + i] + }; + if (modifyer.call(ctx_1, ctx_1.value, ctx_1) !== false) { + if (ctx_1.value == null) { + deleteKeys.push(keys[offset + i]); + } + else if (!outbound && cmp(extractKey(origValue), extractKey(ctx_1.value)) !== 0) { + deleteKeys.push(keys[offset + i]); + addValues.push(ctx_1.value); + } + else { + putValues.push(ctx_1.value); + if (outbound) + putKeys.push(keys[offset + i]); + } } } - req = key != null ? idbstore.add(obj, key) : idbstore.add(obj); - req._hookCtx = hookCtx; - if (i < l - 1) { - req.onerror = errorHandler; - if (hookCtx.onsuccess) - req.onsuccess = successHandler; - } - } - }, function (err) { - hookCtx.onerror && hookCtx.onerror(err); - throw err; + return Promise.resolve(addValues.length > 0 && + coreTable.mutate({ trans: trans, type: 'add', values: addValues }) + .then(function (res) { + for (var pos in res.failures) { + deleteKeys.splice(parseInt(pos), 1); + } + applyMutateResult(addValues.length, res); + })).then(function () { return (putValues.length > 0 || (criteria && typeof changes === 'object')) && + coreTable.mutate({ + trans: trans, + type: 'put', + keys: putKeys, + values: putValues, + criteria: criteria, + changeSpec: typeof changes !== 'function' + && changes, + isAdditionalChunk: offset > 0 + }).then(function (res) { return applyMutateResult(putValues.length, res); }); }).then(function () { return (deleteKeys.length > 0 || (criteria && isUnconditionalDelete)) && + coreTable.mutate({ + trans: trans, + type: 'delete', + keys: deleteKeys, + criteria: criteria, + isAdditionalChunk: offset > 0 + }).then(function (res) { return builtInDeletionTrigger(ctx.table, deleteKeys, res); }) + .then(function (res) { return applyMutateResult(deleteKeys.length, res); }); }).then(function () { + return keys.length > offset + count && nextChunk(offset + limit); + }); }); - req.onerror = BulkErrorHandlerCatchAll(errorList, done, true); - req.onsuccess = hookedEventSuccessHandler(done); - } - else { - // - // Standard Bulk (no 'creating' hook to care about) - // - errorHandler = BulkErrorHandlerCatchAll(errorList); - for (var i = 0, l = objects.length; i < l; ++i) { - req = keys$$1 ? idbstore.add(objects[i], keys$$1[i]) : idbstore.add(objects[i]); - req.onerror = errorHandler; - } - // Only need to catch success or error on the last operation - // according to the IDB spec. - req.onerror = BulkErrorHandlerCatchAll(errorList, done); - req.onsuccess = eventSuccessHandler(done); - } + }; + return nextChunk(0).then(function () { + if (totalFailures.length > 0) + throw new ModifyError("Error modifying one or more objects", totalFailures, successCount, failedKeys); + return keys.length; + }); }); - }, - add: function (obj, key) { - /// - /// Add an object to the database. In case an object with same primary key already exists, the object will not be added. - /// - /// A javascript object to insert - /// Primary key - var creatingHook = this.hook.creating.fire; - return this._idbstore(READWRITE, function (resolve, reject, idbstore, trans) { - var hookCtx = { onsuccess: null, onerror: null }; - if (creatingHook !== nop) { - var effectiveKey = (key != null) ? key : (idbstore.keyPath ? getByKeyPath(obj, idbstore.keyPath) : undefined); - var keyToUse = creatingHook.call(hookCtx, effectiveKey, obj, trans); // Allow subscribers to when("creating") to generate the key. - if (effectiveKey == null && keyToUse != null) { - if (idbstore.keyPath) - setByKeyPath(obj, idbstore.keyPath, keyToUse); - else - key = keyToUse; - } - } - try { - var req = key != null ? idbstore.add(obj, key) : idbstore.add(obj); - req._hookCtx = hookCtx; - req.onerror = hookedEventRejectHandler(reject); - req.onsuccess = hookedEventSuccessHandler(function (result) { - // TODO: Remove these two lines in next major release (2.0?) - // It's no good practice to have side effects on provided parameters - var keyPath = idbstore.keyPath; - if (keyPath) - setByKeyPath(obj, keyPath, result); - resolve(result); + }); + }; + Collection.prototype.delete = function () { + var ctx = this._ctx, range = ctx.range; + if (isPlainKeyRange(ctx) && + !ctx.table.schema.yProps && + (ctx.isPrimKey || range.type === 3 )) + { + return this._write(function (trans) { + var primaryKey = ctx.table.core.schema.primaryKey; + var coreRange = range; + return ctx.table.core.count({ trans: trans, query: { index: primaryKey, range: coreRange } }).then(function (count) { + return ctx.table.core.mutate({ trans: trans, type: 'deleteRange', range: coreRange }) + .then(function (_a) { + var failures = _a.failures, numFailures = _a.numFailures; + if (numFailures) + throw new ModifyError("Could not delete some values", Object.keys(failures).map(function (pos) { return failures[pos]; }), count - numFailures); + return count - numFailures; }); - } - catch (e) { - if (hookCtx.onerror) - hookCtx.onerror(e); - throw e; - } + }); }); - }, - put: function (obj, key) { - var _this = this; - /// - /// Add an object to the database but in case an object with same primary key alread exists, the existing one will get updated. - /// - /// A javascript object to insert or update - /// Primary key - var creatingHook = this.hook.creating.fire, updatingHook = this.hook.updating.fire; - if (creatingHook !== nop || updatingHook !== nop) { - // - // People listens to when("creating") or when("updating") events! - // We must know whether the put operation results in an CREATE or UPDATE. - // - var keyPath = this.schema.primKey.keyPath; - var effectiveKey = (key !== undefined) ? key : (keyPath && getByKeyPath(obj, keyPath)); - if (effectiveKey == null) - return this.add(obj); - // Since key is optional, make sure we get it from obj if not provided - // Primary key exist. Lock transaction and try modifying existing. If nothing modified, call add(). - // clone obj before this async call. If caller modifies obj the line after put(), the IDB spec requires that it should not affect operation. - obj = deepClone(obj); - return this._trans(READWRITE, function () { - return _this.where(":id").equals(effectiveKey).modify(function () { - // Replace extisting value with our object - // CRUD event firing handled in Collection.modify() - this.value = obj; - }).then(function (count) { return count === 0 ? _this.add(obj, key) : effectiveKey; }); - }, "locked"); // Lock needed because operation is splitted into modify() and add(). + } + return this.modify(deleteCallback); + }; + return Collection; +}()); +var deleteCallback = function (value, ctx) { return ctx.value = null; }; + +function createCollectionConstructor(db) { + return makeClassConstructor(Collection.prototype, function Collection(whereClause, keyRangeGenerator) { + this.db = db; + var keyRange = AnyRange, error = null; + if (keyRangeGenerator) + try { + keyRange = keyRangeGenerator(); + } + catch (ex) { + error = ex; + } + var whereCtx = whereClause._ctx; + var table = whereCtx.table; + var readingHook = table.hook.reading.fire; + this._ctx = { + table: table, + index: whereCtx.index, + isPrimKey: (!whereCtx.index || (table.schema.primKey.keyPath && whereCtx.index === table.schema.primKey.name)), + range: keyRange, + keysOnly: false, + dir: "next", + unique: "", + algorithm: null, + filter: null, + replayFilter: null, + justLimit: true, + isMatch: null, + offset: 0, + limit: Infinity, + error: error, + or: whereCtx.or, + valueMapper: readingHook !== mirror ? readingHook : null + }; + }); +} + +function simpleCompare(a, b) { + return a < b ? -1 : a === b ? 0 : 1; +} +function simpleCompareReverse(a, b) { + return a > b ? -1 : a === b ? 0 : 1; +} + +function fail(collectionOrWhereClause, err, T) { + var collection = collectionOrWhereClause instanceof WhereClause ? + new collectionOrWhereClause.Collection(collectionOrWhereClause) : + collectionOrWhereClause; + collection._ctx.error = T ? new T(err) : new TypeError(err); + return collection; +} +function emptyCollection(whereClause) { + return new whereClause.Collection(whereClause, function () { return rangeEqual(""); }).limit(0); +} +function upperFactory(dir) { + return dir === "next" ? + function (s) { return s.toUpperCase(); } : + function (s) { return s.toLowerCase(); }; +} +function lowerFactory(dir) { + return dir === "next" ? + function (s) { return s.toLowerCase(); } : + function (s) { return s.toUpperCase(); }; +} +function nextCasing(key, lowerKey, upperNeedle, lowerNeedle, cmp, dir) { + var length = Math.min(key.length, lowerNeedle.length); + var llp = -1; + for (var i = 0; i < length; ++i) { + var lwrKeyChar = lowerKey[i]; + if (lwrKeyChar !== lowerNeedle[i]) { + if (cmp(key[i], upperNeedle[i]) < 0) + return key.substr(0, i) + upperNeedle[i] + upperNeedle.substr(i + 1); + if (cmp(key[i], lowerNeedle[i]) < 0) + return key.substr(0, i) + lowerNeedle[i] + upperNeedle.substr(i + 1); + if (llp >= 0) + return key.substr(0, llp) + lowerKey[llp] + upperNeedle.substr(llp + 1); + return null; + } + if (cmp(key[i], lwrKeyChar) < 0) + llp = i; + } + if (length < lowerNeedle.length && dir === "next") + return key + upperNeedle.substr(key.length); + if (length < key.length && dir === "prev") + return key.substr(0, upperNeedle.length); + return (llp < 0 ? null : key.substr(0, llp) + lowerNeedle[llp] + upperNeedle.substr(llp + 1)); +} +function addIgnoreCaseAlgorithm(whereClause, match, needles, suffix) { + var upper, lower, compare, upperNeedles, lowerNeedles, direction, nextKeySuffix, needlesLen = needles.length; + if (!needles.every(function (s) { return typeof s === 'string'; })) { + return fail(whereClause, STRING_EXPECTED); + } + function initDirection(dir) { + upper = upperFactory(dir); + lower = lowerFactory(dir); + compare = (dir === "next" ? simpleCompare : simpleCompareReverse); + var needleBounds = needles.map(function (needle) { + return { lower: lower(needle), upper: upper(needle) }; + }).sort(function (a, b) { + return compare(a.lower, b.lower); + }); + upperNeedles = needleBounds.map(function (nb) { return nb.upper; }); + lowerNeedles = needleBounds.map(function (nb) { return nb.lower; }); + direction = dir; + nextKeySuffix = (dir === "next" ? "" : suffix); + } + initDirection("next"); + var c = new whereClause.Collection(whereClause, function () { return createRange(upperNeedles[0], lowerNeedles[needlesLen - 1] + suffix); }); + c._ondirectionchange = function (direction) { + initDirection(direction); + }; + var firstPossibleNeedle = 0; + c._addAlgorithm(function (cursor, advance, resolve) { + var key = cursor.key; + if (typeof key !== 'string') + return false; + var lowerKey = lower(key); + if (match(lowerKey, lowerNeedles, firstPossibleNeedle)) { + return true; + } + else { + var lowestPossibleCasing = null; + for (var i = firstPossibleNeedle; i < needlesLen; ++i) { + var casing = nextCasing(key, lowerKey, upperNeedles[i], lowerNeedles[i], compare, direction); + if (casing === null && lowestPossibleCasing === null) + firstPossibleNeedle = i + 1; + else if (lowestPossibleCasing === null || compare(lowestPossibleCasing, casing) > 0) { + lowestPossibleCasing = casing; + } + } + if (lowestPossibleCasing !== null) { + advance(function () { cursor.continue(lowestPossibleCasing + nextKeySuffix); }); } else { - // Use the standard IDB put() method. - return this._idbstore(READWRITE, function (resolve, reject, idbstore) { - var req = key !== undefined ? idbstore.put(obj, key) : idbstore.put(obj); - req.onerror = eventRejectHandler(reject); - req.onsuccess = wrap(function (ev) { - var keyPath = idbstore.keyPath; - if (keyPath) - setByKeyPath(obj, keyPath, ev.target.result); - resolve(req.result); - }); - }); + advance(resolve); } + return false; + } + }); + return c; +} +function createRange(lower, upper, lowerOpen, upperOpen) { + return { + type: 2 , + lower: lower, + upper: upper, + lowerOpen: lowerOpen, + upperOpen: upperOpen + }; +} +function rangeEqual(value) { + return { + type: 1 , + lower: value, + upper: value + }; +} + +var WhereClause = (function () { + function WhereClause() { + } + Object.defineProperty(WhereClause.prototype, "Collection", { + get: function () { + return this._ctx.table.db.Collection; }, - 'delete': function (key) { - /// Primary key of the object to delete - if (this.hook.deleting.subscribers.length) { - // People listens to when("deleting") event. Must implement delete using Collection.delete() that will - // call the CRUD event. Only Collection.delete() will know whether an object was actually deleted. - return this.where(":id").equals(key).delete(); + enumerable: false, + configurable: true + }); + WhereClause.prototype.between = function (lower, upper, includeLower, includeUpper) { + includeLower = includeLower !== false; + includeUpper = includeUpper === true; + try { + if ((this._cmp(lower, upper) > 0) || + (this._cmp(lower, upper) === 0 && (includeLower || includeUpper) && !(includeLower && includeUpper))) + return emptyCollection(this); + return new this.Collection(this, function () { return createRange(lower, upper, !includeLower, !includeUpper); }); + } + catch (e) { + return fail(this, INVALID_KEY_ARGUMENT); + } + }; + WhereClause.prototype.equals = function (value) { + if (value == null) + return fail(this, INVALID_KEY_ARGUMENT); + return new this.Collection(this, function () { return rangeEqual(value); }); + }; + WhereClause.prototype.above = function (value) { + if (value == null) + return fail(this, INVALID_KEY_ARGUMENT); + return new this.Collection(this, function () { return createRange(value, undefined, true); }); + }; + WhereClause.prototype.aboveOrEqual = function (value) { + if (value == null) + return fail(this, INVALID_KEY_ARGUMENT); + return new this.Collection(this, function () { return createRange(value, undefined, false); }); + }; + WhereClause.prototype.below = function (value) { + if (value == null) + return fail(this, INVALID_KEY_ARGUMENT); + return new this.Collection(this, function () { return createRange(undefined, value, false, true); }); + }; + WhereClause.prototype.belowOrEqual = function (value) { + if (value == null) + return fail(this, INVALID_KEY_ARGUMENT); + return new this.Collection(this, function () { return createRange(undefined, value); }); + }; + WhereClause.prototype.startsWith = function (str) { + if (typeof str !== 'string') + return fail(this, STRING_EXPECTED); + return this.between(str, str + maxString, true, true); + }; + WhereClause.prototype.startsWithIgnoreCase = function (str) { + if (str === "") + return this.startsWith(str); + return addIgnoreCaseAlgorithm(this, function (x, a) { return x.indexOf(a[0]) === 0; }, [str], maxString); + }; + WhereClause.prototype.equalsIgnoreCase = function (str) { + return addIgnoreCaseAlgorithm(this, function (x, a) { return x === a[0]; }, [str], ""); + }; + WhereClause.prototype.anyOfIgnoreCase = function () { + var set = getArrayOf.apply(NO_CHAR_ARRAY, arguments); + if (set.length === 0) + return emptyCollection(this); + return addIgnoreCaseAlgorithm(this, function (x, a) { return a.indexOf(x) !== -1; }, set, ""); + }; + WhereClause.prototype.startsWithAnyOfIgnoreCase = function () { + var set = getArrayOf.apply(NO_CHAR_ARRAY, arguments); + if (set.length === 0) + return emptyCollection(this); + return addIgnoreCaseAlgorithm(this, function (x, a) { return a.some(function (n) { return x.indexOf(n) === 0; }); }, set, maxString); + }; + WhereClause.prototype.anyOf = function () { + var _this = this; + var set = getArrayOf.apply(NO_CHAR_ARRAY, arguments); + var compare = this._cmp; + try { + set.sort(compare); + } + catch (e) { + return fail(this, INVALID_KEY_ARGUMENT); + } + if (set.length === 0) + return emptyCollection(this); + var c = new this.Collection(this, function () { return createRange(set[0], set[set.length - 1]); }); + c._ondirectionchange = function (direction) { + compare = (direction === "next" ? + _this._ascending : + _this._descending); + set.sort(compare); + }; + var i = 0; + c._addAlgorithm(function (cursor, advance, resolve) { + var key = cursor.key; + while (compare(key, set[i]) > 0) { + ++i; + if (i === set.length) { + advance(resolve); + return false; + } + } + if (compare(key, set[i]) === 0) { + return true; } else { - // No one listens. Use standard IDB delete() method. - return this._idbstore(READWRITE, function (resolve, reject, idbstore) { - var req = idbstore.delete(key); - req.onerror = eventRejectHandler(reject); - req.onsuccess = wrap(function () { - resolve(req.result); - }); - }); + advance(function () { cursor.continue(set[i]); }); + return false; } - }, - clear: function () { - if (this.hook.deleting.subscribers.length) { - // People listens to when("deleting") event. Must implement delete using Collection.delete() that will - // call the CRUD event. Only Collection.delete() will knows which objects that are actually deleted. - return this.toCollection().delete(); + }); + return c; + }; + WhereClause.prototype.notEqual = function (value) { + return this.inAnyRange([[minKey, value], [value, this.db._maxKey]], { includeLowers: false, includeUppers: false }); + }; + WhereClause.prototype.noneOf = function () { + var set = getArrayOf.apply(NO_CHAR_ARRAY, arguments); + if (set.length === 0) + return new this.Collection(this); + try { + set.sort(this._ascending); + } + catch (e) { + return fail(this, INVALID_KEY_ARGUMENT); + } + var ranges = set.reduce(function (res, val) { return res ? + res.concat([[res[res.length - 1][1], val]]) : + [[minKey, val]]; }, null); + ranges.push([set[set.length - 1], this.db._maxKey]); + return this.inAnyRange(ranges, { includeLowers: false, includeUppers: false }); + }; + WhereClause.prototype.inAnyRange = function (ranges, options) { + var _this = this; + var cmp = this._cmp, ascending = this._ascending, descending = this._descending, min = this._min, max = this._max; + if (ranges.length === 0) + return emptyCollection(this); + if (!ranges.every(function (range) { + return range[0] !== undefined && + range[1] !== undefined && + ascending(range[0], range[1]) <= 0; + })) { + 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); + } + var includeLowers = !options || options.includeLowers !== false; + var includeUppers = options && options.includeUppers === true; + function addRange(ranges, newRange) { + var i = 0, l = ranges.length; + for (; i < l; ++i) { + var range = ranges[i]; + if (cmp(newRange[0], range[1]) < 0 && cmp(newRange[1], range[0]) > 0) { + range[0] = min(range[0], newRange[0]); + range[1] = max(range[1], newRange[1]); + break; + } + } + if (i === l) + ranges.push(newRange); + return ranges; + } + var sortDirection = ascending; + function rangeSorter(a, b) { return sortDirection(a[0], b[0]); } + var set; + try { + set = ranges.reduce(addRange, []); + set.sort(rangeSorter); + } + catch (ex) { + return fail(this, INVALID_KEY_ARGUMENT); + } + var rangePos = 0; + var keyIsBeyondCurrentEntry = includeUppers ? + function (key) { return ascending(key, set[rangePos][1]) > 0; } : + function (key) { return ascending(key, set[rangePos][1]) >= 0; }; + var keyIsBeforeCurrentEntry = includeLowers ? + function (key) { return descending(key, set[rangePos][0]) > 0; } : + function (key) { return descending(key, set[rangePos][0]) >= 0; }; + function keyWithinCurrentRange(key) { + return !keyIsBeyondCurrentEntry(key) && !keyIsBeforeCurrentEntry(key); + } + var checkKey = keyIsBeyondCurrentEntry; + var c = new this.Collection(this, function () { return createRange(set[0][0], set[set.length - 1][1], !includeLowers, !includeUppers); }); + c._ondirectionchange = function (direction) { + if (direction === "next") { + checkKey = keyIsBeyondCurrentEntry; + sortDirection = ascending; } else { - return this._idbstore(READWRITE, function (resolve, reject, idbstore) { - var req = idbstore.clear(); - req.onerror = eventRejectHandler(reject); - req.onsuccess = wrap(function () { - resolve(req.result); - }); - }); + checkKey = keyIsBeforeCurrentEntry; + sortDirection = descending; } - }, - update: function (keyOrObject, modifications) { - if (typeof modifications !== 'object' || isArray(modifications)) - throw new exceptions.InvalidArgument("Modifications must be an object."); - if (typeof keyOrObject === 'object' && !isArray(keyOrObject)) { - // object to modify. Also modify given object with the modifications: - keys(modifications).forEach(function (keyPath) { - setByKeyPath(keyOrObject, keyPath, modifications[keyPath]); - }); - var key = getByKeyPath(keyOrObject, this.schema.primKey.keyPath); - if (key === undefined) - return rejection(new exceptions.InvalidArgument("Given object does not contain its primary key")); - return this.where(":id").equals(key).modify(modifications); + set.sort(rangeSorter); + }; + c._addAlgorithm(function (cursor, advance, resolve) { + var key = cursor.key; + while (checkKey(key)) { + ++rangePos; + if (rangePos === set.length) { + advance(resolve); + return false; + } + } + if (keyWithinCurrentRange(key)) { + return true; + } + else if (_this._cmp(key, set[rangePos][1]) === 0 || _this._cmp(key, set[rangePos][0]) === 0) { + return false; } else { - // key to modify - return this.where(":id").equals(keyOrObject).modify(modifications); + advance(function () { + if (sortDirection === ascending) + cursor.continue(set[rangePos][0]); + else + cursor.continue(set[rangePos][1]); + }); + return false; } + }); + return c; + }; + WhereClause.prototype.startsWithAnyOf = function () { + var set = getArrayOf.apply(NO_CHAR_ARRAY, arguments); + if (!set.every(function (s) { return typeof s === 'string'; })) { + return fail(this, "startsWithAnyOf() only works with strings"); + } + if (set.length === 0) + return emptyCollection(this); + return this.inAnyRange(set.map(function (str) { return [str, str + maxString]; })); + }; + return WhereClause; +}()); + +function createWhereClauseConstructor(db) { + return makeClassConstructor(WhereClause.prototype, function WhereClause(table, index, orCollection) { + this.db = db; + this._ctx = { + table: table, + index: index === ":id" ? null : index, + or: orCollection + }; + this._cmp = this._ascending = cmp; + this._descending = function (a, b) { return cmp(b, a); }; + this._max = function (a, b) { return cmp(a, b) > 0 ? a : b; }; + this._min = function (a, b) { return cmp(a, b) < 0 ? a : b; }; + this._IDBKeyRange = db._deps.IDBKeyRange; + if (!this._IDBKeyRange) + throw new exceptions.MissingAPI(); + }); +} + +function eventRejectHandler(reject) { + return wrap(function (event) { + preventDefault(event); + reject(event.target.error); + return false; + }); +} +function preventDefault(event) { + if (event.stopPropagation) + event.stopPropagation(); + if (event.preventDefault) + event.preventDefault(); +} + +var DEXIE_STORAGE_MUTATED_EVENT_NAME = 'storagemutated'; +var STORAGE_MUTATED_DOM_EVENT_NAME = 'x-storagemutated-1'; +var globalEvents = Events(null, DEXIE_STORAGE_MUTATED_EVENT_NAME); + +var Transaction = (function () { + function Transaction() { + } + Transaction.prototype._lock = function () { + assert(!PSD.global); + ++this._reculock; + if (this._reculock === 1 && !PSD.global) + PSD.lockOwnerFor = this; + return this; + }; + Transaction.prototype._unlock = function () { + assert(!PSD.global); + if (--this._reculock === 0) { + if (!PSD.global) + PSD.lockOwnerFor = null; + while (this._blockedFuncs.length > 0 && !this._locked()) { + var fnAndPSD = this._blockedFuncs.shift(); + try { + usePSD(fnAndPSD[1], fnAndPSD[0]); + } + catch (e) { } + } + } + return this; + }; + Transaction.prototype._locked = function () { + return this._reculock && PSD.lockOwnerFor !== this; + }; + Transaction.prototype.create = function (idbtrans) { + var _this = this; + if (!this.mode) + return this; + var idbdb = this.db.idbdb; + var dbOpenError = this.db._state.dbOpenError; + assert(!this.idbtrans); + if (!idbtrans && !idbdb) { + switch (dbOpenError && dbOpenError.name) { + case "DatabaseClosedError": + throw new exceptions.DatabaseClosed(dbOpenError); + case "MissingAPIError": + throw new exceptions.MissingAPI(dbOpenError.message, dbOpenError); + default: + throw new exceptions.OpenFailed(dbOpenError); + } + } + if (!this.active) + throw new exceptions.TransactionInactive(); + assert(this._completion._state === null); + idbtrans = this.idbtrans = idbtrans || + (this.db.core + ? this.db.core.transaction(this.storeNames, this.mode, { durability: this.chromeTransactionDurability }) + : idbdb.transaction(this.storeNames, this.mode, { durability: this.chromeTransactionDurability })); + idbtrans.onerror = wrap(function (ev) { + preventDefault(ev); + _this._reject(idbtrans.error); + }); + idbtrans.onabort = wrap(function (ev) { + preventDefault(ev); + _this.active && _this._reject(new exceptions.Abort(idbtrans.error)); + _this.active = false; + _this.on("abort").fire(ev); + }); + idbtrans.oncomplete = wrap(function () { + _this.active = false; + _this._resolve(); + if ('mutatedParts' in idbtrans) { + globalEvents.storagemutated.fire(idbtrans["mutatedParts"]); + } + }); + return this; + }; + Transaction.prototype._promise = function (mode, fn, bWriteLock) { + var _this = this; + if (mode === 'readwrite' && this.mode !== 'readwrite') + return rejection(new exceptions.ReadOnly("Transaction is readonly")); + if (!this.active) + return rejection(new exceptions.TransactionInactive()); + if (this._locked()) { + return new DexiePromise(function (resolve, reject) { + _this._blockedFuncs.push([function () { + _this._promise(mode, fn, bWriteLock).then(resolve, reject); + }, PSD]); + }); + } + else if (bWriteLock) { + return newScope(function () { + var p = new DexiePromise(function (resolve, reject) { + _this._lock(); + var rv = fn(resolve, reject, _this); + if (rv && rv.then) + rv.then(resolve, reject); + }); + p.finally(function () { return _this._unlock(); }); + p._lib = true; + return p; + }); + } + else { + var p = new DexiePromise(function (resolve, reject) { + var rv = fn(resolve, reject, _this); + if (rv && rv.then) + rv.then(resolve, reject); + }); + p._lib = true; + return p; + } + }; + Transaction.prototype._root = function () { + return this.parent ? this.parent._root() : this; + }; + Transaction.prototype.waitFor = function (promiseLike) { + var root = this._root(); + var promise = DexiePromise.resolve(promiseLike); + if (root._waitingFor) { + root._waitingFor = root._waitingFor.then(function () { return promise; }); + } + else { + root._waitingFor = promise; + root._waitingQueue = []; + var store = root.idbtrans.objectStore(root.storeNames[0]); + (function spin() { + ++root._spinCount; + while (root._waitingQueue.length) + (root._waitingQueue.shift())(); + if (root._waitingFor) + store.get(-Infinity).onsuccess = spin; + }()); + } + var currentWaitPromise = root._waitingFor; + return new DexiePromise(function (resolve, reject) { + promise.then(function (res) { return root._waitingQueue.push(wrap(resolve.bind(null, res))); }, function (err) { return root._waitingQueue.push(wrap(reject.bind(null, err))); }).finally(function () { + if (root._waitingFor === currentWaitPromise) { + root._waitingFor = null; + } + }); + }); + }; + Transaction.prototype.abort = function () { + if (this.active) { + this.active = false; + if (this.idbtrans) + this.idbtrans.abort(); + this._reject(new exceptions.Abort()); } - }); - // - // - // - // Transaction Class - // - // - // - function Transaction(mode, storeNames, dbschema, parent) { + }; + Transaction.prototype.table = function (tableName) { + var memoizedTables = (this._memoizedTables || (this._memoizedTables = {})); + if (hasOwn(memoizedTables, tableName)) + return memoizedTables[tableName]; + var tableSchema = this.schema[tableName]; + if (!tableSchema) { + throw new exceptions.NotFound("Table " + tableName + " not part of transaction"); + } + var transactionBoundTable = new this.db.Table(tableName, tableSchema, this); + transactionBoundTable.core = this.db.core.table(tableName); + memoizedTables[tableName] = transactionBoundTable; + return transactionBoundTable; + }; + return Transaction; +}()); + +function createTransactionConstructor(db) { + return makeClassConstructor(Transaction.prototype, function Transaction(mode, storeNames, dbschema, chromeTransactionDurability, parent) { var _this = this; - /// - /// Transaction class. Represents a database transaction. All operations on db goes through a Transaction. - /// - /// Any of "readwrite" or "readonly" - /// Array of table names to operate on + if (mode !== 'readonly') + storeNames.forEach(function (storeName) { + var _a; + var yProps = (_a = dbschema[storeName]) === null || _a === void 0 ? void 0 : _a.yProps; + if (yProps) + storeNames = storeNames.concat(yProps.map(function (p) { return p.updatesTable; })); + }); this.db = db; this.mode = mode; this.storeNames = storeNames; + this.schema = dbschema; + this.chromeTransactionDurability = chromeTransactionDurability; this.idbtrans = null; this.on = Events(this, "complete", "error", "abort"); this.parent = parent || null; @@ -2796,8 +2904,8 @@ function Dexie(dbName, options) { this._reject = null; this._waitingFor = null; this._waitingQueue = null; - this._spinCount = 0; // Just for debugging waitFor() - this._completion = new Promise(function (resolve, reject) { + this._spinCount = 0; + this._completion = new DexiePromise(function (resolve, reject) { _this._resolve = resolve; _this._reject = reject; }); @@ -2811,1655 +2919,3064 @@ function Dexie(dbName, options) { _this.parent ? _this.parent._reject(e) : wasActive && _this.idbtrans && _this.idbtrans.abort(); - return rejection(e); // Indicate we actually DO NOT catch this error. + return rejection(e); }); + }); +} + +function createIndexSpec(name, keyPath, unique, multi, auto, compound, isPrimKey, type) { + return { + name: name, + keyPath: keyPath, + unique: unique, + multi: multi, + auto: auto, + compound: compound, + src: (unique && !isPrimKey ? '&' : '') + (multi ? '*' : '') + (auto ? "++" : "") + nameFromKeyPath(keyPath), + type: type + }; +} +function nameFromKeyPath(keyPath) { + return typeof keyPath === 'string' ? + keyPath : + keyPath ? ('[' + [].join.call(keyPath, '+') + ']') : ""; +} + +function createTableSchema(name, primKey, indexes) { + return { + name: name, + primKey: primKey, + indexes: indexes, + mappedClass: null, + idxByName: arrayToObject(indexes, function (index) { return [index.name, index]; }), + }; +} + +function safariMultiStoreFix(storeNames) { + return storeNames.length === 1 ? storeNames[0] : storeNames; +} +var getMaxKey = function (IdbKeyRange) { + try { + IdbKeyRange.only([[]]); + getMaxKey = function () { return [[]]; }; + return [[]]; } - props(Transaction.prototype, { - // - // Transaction Protected Methods (not required by API users, but needed internally and eventually by dexie extensions) - // - _lock: function () { - assert(!PSD.global); // Locking and unlocking reuires to be within a PSD scope. - // Temporary set all requests into a pending queue if they are called before database is ready. - ++this._reculock; // Recursive read/write lock pattern using PSD (Promise Specific Data) instead of TLS (Thread Local Storage) - if (this._reculock === 1 && !PSD.global) - PSD.lockOwnerFor = this; - return this; - }, - _unlock: function () { - assert(!PSD.global); // Locking and unlocking reuires to be within a PSD scope. - if (--this._reculock === 0) { - if (!PSD.global) - PSD.lockOwnerFor = null; - while (this._blockedFuncs.length > 0 && !this._locked()) { - var fnAndPSD = this._blockedFuncs.shift(); - try { - usePSD(fnAndPSD[1], fnAndPSD[0]); + catch (e) { + getMaxKey = function () { return maxString; }; + return maxString; + } +}; + +function getKeyExtractor(keyPath) { + if (keyPath == null) { + return function () { return undefined; }; + } + else if (typeof keyPath === 'string') { + return getSinglePathKeyExtractor(keyPath); + } + else { + return function (obj) { return getByKeyPath(obj, keyPath); }; + } +} +function getSinglePathKeyExtractor(keyPath) { + var split = keyPath.split('.'); + if (split.length === 1) { + return function (obj) { return obj[keyPath]; }; + } + else { + return function (obj) { return getByKeyPath(obj, keyPath); }; + } +} + +function arrayify(arrayLike) { + return [].slice.call(arrayLike); +} +var _id_counter = 0; +function getKeyPathAlias(keyPath) { + return keyPath == null ? + ":id" : + typeof keyPath === 'string' ? + keyPath : + "[".concat(keyPath.join('+'), "]"); +} +function createDBCore(db, IdbKeyRange, tmpTrans) { + function extractSchema(db, trans) { + var tables = arrayify(db.objectStoreNames); + return { + schema: { + name: db.name, + tables: tables.map(function (table) { return trans.objectStore(table); }).map(function (store) { + var keyPath = store.keyPath, autoIncrement = store.autoIncrement; + var compound = isArray(keyPath); + var outbound = keyPath == null; + var indexByKeyPath = {}; + var result = { + name: store.name, + primaryKey: { + name: null, + isPrimaryKey: true, + outbound: outbound, + compound: compound, + keyPath: keyPath, + autoIncrement: autoIncrement, + unique: true, + extractKey: getKeyExtractor(keyPath) + }, + indexes: arrayify(store.indexNames).map(function (indexName) { return store.index(indexName); }) + .map(function (index) { + var name = index.name, unique = index.unique, multiEntry = index.multiEntry, keyPath = index.keyPath; + var compound = isArray(keyPath); + var result = { + name: name, + compound: compound, + keyPath: keyPath, + unique: unique, + multiEntry: multiEntry, + extractKey: getKeyExtractor(keyPath) + }; + indexByKeyPath[getKeyPathAlias(keyPath)] = result; + return result; + }), + getIndexByKeyPath: function (keyPath) { return indexByKeyPath[getKeyPathAlias(keyPath)]; } + }; + indexByKeyPath[":id"] = result.primaryKey; + if (keyPath != null) { + indexByKeyPath[getKeyPathAlias(keyPath)] = result.primaryKey; } - catch (e) { } + return result; + }) + }, + hasGetAll: tables.length > 0 && ('getAll' in trans.objectStore(tables[0])) && + !(typeof navigator !== 'undefined' && /Safari/.test(navigator.userAgent) && + !/(Chrome\/|Edge\/)/.test(navigator.userAgent) && + [].concat(navigator.userAgent.match(/Safari\/(\d*)/))[1] < 604) + }; + } + function makeIDBKeyRange(range) { + if (range.type === 3 ) + return null; + if (range.type === 4 ) + throw new Error("Cannot convert never type to IDBKeyRange"); + var lower = range.lower, upper = range.upper, lowerOpen = range.lowerOpen, upperOpen = range.upperOpen; + var idbRange = lower === undefined ? + upper === undefined ? + null : + IdbKeyRange.upperBound(upper, !!upperOpen) : + upper === undefined ? + IdbKeyRange.lowerBound(lower, !!lowerOpen) : + IdbKeyRange.bound(lower, upper, !!lowerOpen, !!upperOpen); + return idbRange; + } + function createDbCoreTable(tableSchema) { + var tableName = tableSchema.name; + function mutate(_a) { + var trans = _a.trans, type = _a.type, keys = _a.keys, values = _a.values, range = _a.range; + return new Promise(function (resolve, reject) { + resolve = wrap(resolve); + var store = trans.objectStore(tableName); + var outbound = store.keyPath == null; + var isAddOrPut = type === "put" || type === "add"; + if (!isAddOrPut && type !== 'delete' && type !== 'deleteRange') + throw new Error("Invalid operation type: " + type); + var length = (keys || values || { length: 1 }).length; + if (keys && values && keys.length !== values.length) { + throw new Error("Given keys array must have same length as given values array."); + } + if (length === 0) + return resolve({ numFailures: 0, failures: {}, results: [], lastResult: undefined }); + var req; + var reqs = []; + var failures = []; + var numFailures = 0; + var errorHandler = function (event) { + ++numFailures; + preventDefault(event); + }; + if (type === 'deleteRange') { + if (range.type === 4 ) + return resolve({ numFailures: numFailures, failures: failures, results: [], lastResult: undefined }); + if (range.type === 3 ) + reqs.push(req = store.clear()); + else + reqs.push(req = store.delete(makeIDBKeyRange(range))); } + else { + var _a = isAddOrPut ? + outbound ? + [values, keys] : + [values, null] : + [keys, null], args1 = _a[0], args2 = _a[1]; + if (isAddOrPut) { + for (var i = 0; i < length; ++i) { + reqs.push(req = (args2 && args2[i] !== undefined ? + store[type](args1[i], args2[i]) : + store[type](args1[i]))); + req.onerror = errorHandler; + } + } + else { + for (var i = 0; i < length; ++i) { + reqs.push(req = store[type](args1[i])); + req.onerror = errorHandler; + } + } + } + var done = function (event) { + var lastResult = event.target.result; + reqs.forEach(function (req, i) { return req.error != null && (failures[i] = req.error); }); + resolve({ + numFailures: numFailures, + failures: failures, + results: type === "delete" ? keys : reqs.map(function (req) { return req.result; }), + lastResult: lastResult + }); + }; + req.onerror = function (event) { + errorHandler(event); + done(event); + }; + req.onsuccess = done; + }); + } + function openCursor(_a) { + var trans = _a.trans, values = _a.values, query = _a.query, reverse = _a.reverse, unique = _a.unique; + return new Promise(function (resolve, reject) { + resolve = wrap(resolve); + var index = query.index, range = query.range; + var store = trans.objectStore(tableName); + var source = index.isPrimaryKey ? + store : + store.index(index.name); + var direction = reverse ? + unique ? + "prevunique" : + "prev" : + unique ? + "nextunique" : + "next"; + var req = values || !('openKeyCursor' in source) ? + source.openCursor(makeIDBKeyRange(range), direction) : + source.openKeyCursor(makeIDBKeyRange(range), direction); + req.onerror = eventRejectHandler(reject); + req.onsuccess = wrap(function (ev) { + var cursor = req.result; + if (!cursor) { + resolve(null); + return; + } + cursor.___id = ++_id_counter; + cursor.done = false; + var _cursorContinue = cursor.continue.bind(cursor); + var _cursorContinuePrimaryKey = cursor.continuePrimaryKey; + if (_cursorContinuePrimaryKey) + _cursorContinuePrimaryKey = _cursorContinuePrimaryKey.bind(cursor); + var _cursorAdvance = cursor.advance.bind(cursor); + var doThrowCursorIsNotStarted = function () { throw new Error("Cursor not started"); }; + var doThrowCursorIsStopped = function () { throw new Error("Cursor not stopped"); }; + cursor.trans = trans; + cursor.stop = cursor.continue = cursor.continuePrimaryKey = cursor.advance = doThrowCursorIsNotStarted; + cursor.fail = wrap(reject); + cursor.next = function () { + var _this = this; + var gotOne = 1; + return this.start(function () { return gotOne-- ? _this.continue() : _this.stop(); }).then(function () { return _this; }); + }; + cursor.start = function (callback) { + var iterationPromise = new Promise(function (resolveIteration, rejectIteration) { + resolveIteration = wrap(resolveIteration); + req.onerror = eventRejectHandler(rejectIteration); + cursor.fail = rejectIteration; + cursor.stop = function (value) { + cursor.stop = cursor.continue = cursor.continuePrimaryKey = cursor.advance = doThrowCursorIsStopped; + resolveIteration(value); + }; + }); + var guardedCallback = function () { + if (req.result) { + try { + callback(); + } + catch (err) { + cursor.fail(err); + } + } + else { + cursor.done = true; + cursor.start = function () { throw new Error("Cursor behind last entry"); }; + cursor.stop(); + } + }; + req.onsuccess = wrap(function (ev) { + req.onsuccess = guardedCallback; + guardedCallback(); + }); + cursor.continue = _cursorContinue; + cursor.continuePrimaryKey = _cursorContinuePrimaryKey; + cursor.advance = _cursorAdvance; + guardedCallback(); + return iterationPromise; + }; + resolve(cursor); + }, reject); + }); + } + function query(hasGetAll) { + return function (request) { + return new Promise(function (resolve, reject) { + resolve = wrap(resolve); + var trans = request.trans, values = request.values, limit = request.limit, query = request.query; + var nonInfinitLimit = limit === Infinity ? undefined : limit; + var index = query.index, range = query.range; + var store = trans.objectStore(tableName); + var source = index.isPrimaryKey ? store : store.index(index.name); + var idbKeyRange = makeIDBKeyRange(range); + if (limit === 0) + return resolve({ result: [] }); + if (hasGetAll) { + var req = values ? + source.getAll(idbKeyRange, nonInfinitLimit) : + source.getAllKeys(idbKeyRange, nonInfinitLimit); + req.onsuccess = function (event) { return resolve({ result: event.target.result }); }; + req.onerror = eventRejectHandler(reject); + } + else { + var count_1 = 0; + var req_1 = values || !('openKeyCursor' in source) ? + source.openCursor(idbKeyRange) : + source.openKeyCursor(idbKeyRange); + var result_1 = []; + req_1.onsuccess = function (event) { + var cursor = req_1.result; + if (!cursor) + return resolve({ result: result_1 }); + result_1.push(values ? cursor.value : cursor.primaryKey); + if (++count_1 === limit) + return resolve({ result: result_1 }); + cursor.continue(); + }; + req_1.onerror = eventRejectHandler(reject); + } + }); + }; + } + return { + name: tableName, + schema: tableSchema, + mutate: mutate, + getMany: function (_a) { + var trans = _a.trans, keys = _a.keys; + return new Promise(function (resolve, reject) { + resolve = wrap(resolve); + var store = trans.objectStore(tableName); + var length = keys.length; + var result = new Array(length); + var keyCount = 0; + var callbackCount = 0; + var req; + var successHandler = function (event) { + var req = event.target; + if ((result[req._pos] = req.result) != null) + ; + if (++callbackCount === keyCount) + resolve(result); + }; + var errorHandler = eventRejectHandler(reject); + for (var i = 0; i < length; ++i) { + var key = keys[i]; + if (key != null) { + req = store.get(keys[i]); + req._pos = i; + req.onsuccess = successHandler; + req.onerror = errorHandler; + ++keyCount; + } + } + if (keyCount === 0) + resolve(result); + }); + }, + get: function (_a) { + var trans = _a.trans, key = _a.key; + return new Promise(function (resolve, reject) { + resolve = wrap(resolve); + var store = trans.objectStore(tableName); + var req = store.get(key); + req.onsuccess = function (event) { return resolve(event.target.result); }; + req.onerror = eventRejectHandler(reject); + }); + }, + query: query(hasGetAll), + openCursor: openCursor, + count: function (_a) { + var query = _a.query, trans = _a.trans; + var index = query.index, range = query.range; + return new Promise(function (resolve, reject) { + var store = trans.objectStore(tableName); + var source = index.isPrimaryKey ? store : store.index(index.name); + var idbKeyRange = makeIDBKeyRange(range); + var req = idbKeyRange ? source.count(idbKeyRange) : source.count(); + req.onsuccess = wrap(function (ev) { return resolve(ev.target.result); }); + req.onerror = eventRejectHandler(reject); + }); } - return this; - }, - _locked: function () { - // Checks if any write-lock is applied on this transaction. - // To simplify the Dexie API for extension implementations, we support recursive locks. - // This is accomplished by using "Promise Specific Data" (PSD). - // PSD data is bound to a Promise and any child Promise emitted through then() or resolve( new Promise() ). - // PSD is local to code executing on top of the call stacks of any of any code executed by Promise(): - // * callback given to the Promise() constructor (function (resolve, reject){...}) - // * callbacks given to then()/catch()/finally() methods (function (value){...}) - // 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. - // Derivation is done so that the inner PSD __proto__ points to the outer PSD. - // PSD.lockOwnerFor will point to current transaction object if the currently executing PSD scope owns the lock. - return this._reculock && PSD.lockOwnerFor !== this; + }; + } + var _a = extractSchema(db, tmpTrans), schema = _a.schema, hasGetAll = _a.hasGetAll; + var tables = schema.tables.map(function (tableSchema) { return createDbCoreTable(tableSchema); }); + var tableMap = {}; + tables.forEach(function (table) { return tableMap[table.name] = table; }); + return { + stack: "dbcore", + transaction: db.transaction.bind(db), + table: function (name) { + var result = tableMap[name]; + if (!result) + throw new Error("Table '".concat(name, "' not found")); + return tableMap[name]; }, - create: function (idbtrans) { - var _this = this; - if (!this.mode) - return this; - assert(!this.idbtrans); - if (!idbtrans && !idbdb) { - switch (dbOpenError && dbOpenError.name) { - case "DatabaseClosedError": - // Errors where it is no difference whether it was caused by the user operation or an earlier call to db.open() - throw new exceptions.DatabaseClosed(dbOpenError); - case "MissingAPIError": - // Errors where it is no difference whether it was caused by the user operation or an earlier call to db.open() - throw new exceptions.MissingAPI(dbOpenError.message, dbOpenError); - default: - // Make it clear that the user operation was not what caused the error - the error had occurred earlier on db.open()! - throw new exceptions.OpenFailed(dbOpenError); + MIN_KEY: -Infinity, + MAX_KEY: getMaxKey(IdbKeyRange), + schema: schema + }; +} + +function createMiddlewareStack(stackImpl, middlewares) { + return middlewares.reduce(function (down, _a) { + var create = _a.create; + return (__assign(__assign({}, down), create(down))); + }, stackImpl); +} +function createMiddlewareStacks(middlewares, idbdb, _a, tmpTrans) { + var IDBKeyRange = _a.IDBKeyRange; _a.indexedDB; + var dbcore = createMiddlewareStack(createDBCore(idbdb, IDBKeyRange, tmpTrans), middlewares.dbcore); + return { + dbcore: dbcore + }; +} +function generateMiddlewareStacks(db, tmpTrans) { + var idbdb = tmpTrans.db; + var stacks = createMiddlewareStacks(db._middlewares, idbdb, db._deps, tmpTrans); + db.core = stacks.dbcore; + db.tables.forEach(function (table) { + var tableName = table.name; + if (db.core.schema.tables.some(function (tbl) { return tbl.name === tableName; })) { + table.core = db.core.table(tableName); + if (db[tableName] instanceof db.Table) { + db[tableName].core = table.core; + } + } + }); +} + +function setApiOnPlace(db, objs, tableNames, dbschema) { + tableNames.forEach(function (tableName) { + var schema = dbschema[tableName]; + objs.forEach(function (obj) { + var propDesc = getPropertyDescriptor(obj, tableName); + if (!propDesc || ("value" in propDesc && propDesc.value === undefined)) { + if (obj === db.Transaction.prototype || obj instanceof db.Transaction) { + setProp(obj, tableName, { + get: function () { return this.table(tableName); }, + set: function (value) { + defineProperty(this, tableName, { value: value, writable: true, configurable: true, enumerable: true }); + } + }); + } + else { + obj[tableName] = new db.Table(tableName, schema); } } - if (!this.active) - throw new exceptions.TransactionInactive(); - assert(this._completion._state === null); - idbtrans = this.idbtrans = idbtrans || idbdb.transaction(safariMultiStoreFix(this.storeNames), this.mode); - idbtrans.onerror = wrap(function (ev) { - preventDefault(ev); // Prohibit default bubbling to window.error - _this._reject(idbtrans.error); + }); + }); +} +function removeTablesApi(db, objs) { + objs.forEach(function (obj) { + for (var key in obj) { + if (obj[key] instanceof db.Table) + delete obj[key]; + } + }); +} +function lowerVersionFirst(a, b) { + return a._cfg.version - b._cfg.version; +} +function runUpgraders(db, oldVersion, idbUpgradeTrans, reject) { + var globalSchema = db._dbSchema; + if (idbUpgradeTrans.objectStoreNames.contains('$meta') && !globalSchema.$meta) { + globalSchema.$meta = createTableSchema("$meta", parseIndexSyntax("")[0], []); + db._storeNames.push('$meta'); + } + var trans = db._createTransaction('readwrite', db._storeNames, globalSchema); + trans.create(idbUpgradeTrans); + trans._completion.catch(reject); + var rejectTransaction = trans._reject.bind(trans); + var transless = PSD.transless || PSD; + newScope(function () { + PSD.trans = trans; + PSD.transless = transless; + if (oldVersion === 0) { + keys(globalSchema).forEach(function (tableName) { + createTable(idbUpgradeTrans, tableName, globalSchema[tableName].primKey, globalSchema[tableName].indexes); }); - idbtrans.onabort = wrap(function (ev) { - preventDefault(ev); - _this.active && _this._reject(new exceptions.Abort(idbtrans.error)); - _this.active = false; - _this.on("abort").fire(ev); + generateMiddlewareStacks(db, idbUpgradeTrans); + DexiePromise.follow(function () { return db.on.populate.fire(trans); }).catch(rejectTransaction); + } + else { + generateMiddlewareStacks(db, idbUpgradeTrans); + return getExistingVersion(db, trans, oldVersion) + .then(function (oldVersion) { return updateTablesAndIndexes(db, oldVersion, trans, idbUpgradeTrans); }) + .catch(rejectTransaction); + } + }); +} +function patchCurrentVersion(db, idbUpgradeTrans) { + createMissingTables(db._dbSchema, idbUpgradeTrans); + if (idbUpgradeTrans.db.version % 10 === 0 && !idbUpgradeTrans.objectStoreNames.contains('$meta')) { + idbUpgradeTrans.db.createObjectStore('$meta').add(Math.ceil((idbUpgradeTrans.db.version / 10) - 1), 'version'); + } + var globalSchema = buildGlobalSchema(db, db.idbdb, idbUpgradeTrans); + adjustToExistingIndexNames(db, db._dbSchema, idbUpgradeTrans); + var diff = getSchemaDiff(globalSchema, db._dbSchema); + var _loop_1 = function (tableChange) { + if (tableChange.change.length || tableChange.recreate) { + console.warn("Unable to patch indexes of table ".concat(tableChange.name, " because it has changes on the type of index or primary key.")); + return { value: void 0 }; + } + var store = idbUpgradeTrans.objectStore(tableChange.name); + tableChange.add.forEach(function (idx) { + if (debug) + console.debug("Dexie upgrade patch: Creating missing index ".concat(tableChange.name, ".").concat(idx.src)); + addIndex(store, idx); + }); + }; + for (var _i = 0, _a = diff.change; _i < _a.length; _i++) { + var tableChange = _a[_i]; + var state_1 = _loop_1(tableChange); + if (typeof state_1 === "object") + return state_1.value; + } +} +function getExistingVersion(db, trans, oldVersion) { + if (trans.storeNames.includes('$meta')) { + return trans.table('$meta').get('version').then(function (metaVersion) { + return metaVersion != null ? metaVersion : oldVersion; + }); + } + else { + return DexiePromise.resolve(oldVersion); + } +} +function updateTablesAndIndexes(db, oldVersion, trans, idbUpgradeTrans) { + var queue = []; + var versions = db._versions; + var globalSchema = db._dbSchema = buildGlobalSchema(db, db.idbdb, idbUpgradeTrans); + var versToRun = versions.filter(function (v) { return v._cfg.version >= oldVersion; }); + if (versToRun.length === 0) { + return DexiePromise.resolve(); + } + versToRun.forEach(function (version) { + queue.push(function () { + var oldSchema = globalSchema; + var newSchema = version._cfg.dbschema; + adjustToExistingIndexNames(db, oldSchema, idbUpgradeTrans); + adjustToExistingIndexNames(db, newSchema, idbUpgradeTrans); + globalSchema = db._dbSchema = newSchema; + var diff = getSchemaDiff(oldSchema, newSchema); + diff.add.forEach(function (tuple) { + createTable(idbUpgradeTrans, tuple[0], tuple[1].primKey, tuple[1].indexes); }); - idbtrans.oncomplete = wrap(function () { - _this.active = false; - _this._resolve(); + diff.change.forEach(function (change) { + if (change.recreate) { + throw new exceptions.Upgrade("Not yet support for changing primary key"); + } + else { + var store_1 = idbUpgradeTrans.objectStore(change.name); + change.add.forEach(function (idx) { return addIndex(store_1, idx); }); + change.change.forEach(function (idx) { + store_1.deleteIndex(idx.name); + addIndex(store_1, idx); + }); + change.del.forEach(function (idxName) { return store_1.deleteIndex(idxName); }); + } }); - return this; - }, - _promise: function (mode, fn, bWriteLock) { - var _this = this; - if (mode === READWRITE && this.mode !== READWRITE) - return rejection(new exceptions.ReadOnly("Transaction is readonly")); - if (!this.active) - return rejection(new exceptions.TransactionInactive()); - if (this._locked()) { - return new Promise(function (resolve, reject) { - _this._blockedFuncs.push([function () { - _this._promise(mode, fn, bWriteLock).then(resolve, reject); - }, PSD]); + var contentUpgrade = version._cfg.contentUpgrade; + if (contentUpgrade && version._cfg.version > oldVersion) { + generateMiddlewareStacks(db, idbUpgradeTrans); + trans._memoizedTables = {}; + var upgradeSchema_1 = shallowClone(newSchema); + diff.del.forEach(function (table) { + upgradeSchema_1[table] = oldSchema[table]; }); - } - else if (bWriteLock) { - return newScope(function () { - var p = new Promise(function (resolve, reject) { - _this._lock(); - var rv = fn(resolve, reject, _this); - if (rv && rv.then) - rv.then(resolve, reject); - }); - p.finally(function () { return _this._unlock(); }); - p._lib = true; - return p; + removeTablesApi(db, [db.Transaction.prototype]); + setApiOnPlace(db, [db.Transaction.prototype], keys(upgradeSchema_1), upgradeSchema_1); + trans.schema = upgradeSchema_1; + var contentUpgradeIsAsync_1 = isAsyncFunction(contentUpgrade); + if (contentUpgradeIsAsync_1) { + incrementExpectedAwaits(); + } + var returnValue_1; + var promiseFollowed = DexiePromise.follow(function () { + returnValue_1 = contentUpgrade(trans); + if (returnValue_1) { + if (contentUpgradeIsAsync_1) { + var decrementor = decrementExpectedAwaits.bind(null, null); + returnValue_1.then(decrementor, decrementor); + } + } }); + return (returnValue_1 && typeof returnValue_1.then === 'function' ? + DexiePromise.resolve(returnValue_1) : promiseFollowed.then(function () { return returnValue_1; })); } - else { - var p = new Promise(function (resolve, reject) { - var rv = fn(resolve, reject, _this); - if (rv && rv.then) - rv.then(resolve, reject); - }); - p._lib = true; - return p; + }); + queue.push(function (idbtrans) { + var newSchema = version._cfg.dbschema; + deleteRemovedTables(newSchema, idbtrans); + removeTablesApi(db, [db.Transaction.prototype]); + setApiOnPlace(db, [db.Transaction.prototype], db._storeNames, db._dbSchema); + trans.schema = db._dbSchema; + }); + queue.push(function (idbtrans) { + if (db.idbdb.objectStoreNames.contains('$meta')) { + if (Math.ceil(db.idbdb.version / 10) === version._cfg.version) { + db.idbdb.deleteObjectStore('$meta'); + delete db._dbSchema.$meta; + db._storeNames = db._storeNames.filter(function (name) { return name !== '$meta'; }); + } + else { + idbtrans.objectStore('$meta').put(version._cfg.version, 'version'); + } } - }, - _root: function () { - return this.parent ? this.parent._root() : this; - }, - waitFor: function (promise) { - // Always operate on the root transaction (in case this is a sub stransaction) - var root = this._root(); - // For stability reasons, convert parameter to promise no matter what type is passed to waitFor(). - // (We must be able to call .then() on it.) - promise = Promise.resolve(promise); - if (root._waitingFor) { - // Already called waitFor(). Wait for both to complete. - root._waitingFor = root._waitingFor.then(function () { return promise; }); + }); + }); + function runQueue() { + return queue.length ? DexiePromise.resolve(queue.shift()(trans.idbtrans)).then(runQueue) : + DexiePromise.resolve(); + } + return runQueue().then(function () { + createMissingTables(globalSchema, idbUpgradeTrans); + }); +} +function getSchemaDiff(oldSchema, newSchema) { + var diff = { + del: [], + add: [], + change: [] + }; + var table; + for (table in oldSchema) { + if (!newSchema[table]) + diff.del.push(table); + } + for (table in newSchema) { + var oldDef = oldSchema[table], newDef = newSchema[table]; + if (!oldDef) { + diff.add.push([table, newDef]); + } + else { + var change = { + name: table, + def: newDef, + recreate: false, + del: [], + add: [], + change: [] + }; + if (( + '' + (oldDef.primKey.keyPath || '')) !== ('' + (newDef.primKey.keyPath || '')) || + (oldDef.primKey.auto !== newDef.primKey.auto)) { + change.recreate = true; + diff.change.push(change); } else { - // We're not in waiting state. Start waiting state. - root._waitingFor = promise; - root._waitingQueue = []; - // Start interacting with indexedDB until promise completes: - var store = root.idbtrans.objectStore(root.storeNames[0]); - (function spin() { - ++root._spinCount; // For debugging only - while (root._waitingQueue.length) - (root._waitingQueue.shift())(); - if (root._waitingFor) - store.get(-Infinity).onsuccess = spin; - }()); + var oldIndexes = oldDef.idxByName; + var newIndexes = newDef.idxByName; + var idxName = void 0; + for (idxName in oldIndexes) { + if (!newIndexes[idxName]) + change.del.push(idxName); + } + for (idxName in newIndexes) { + var oldIdx = oldIndexes[idxName], newIdx = newIndexes[idxName]; + if (!oldIdx) + change.add.push(newIdx); + else if (oldIdx.src !== newIdx.src) + change.change.push(newIdx); + } + if (change.del.length > 0 || change.add.length > 0 || change.change.length > 0) { + diff.change.push(change); + } } - var currentWaitPromise = root._waitingFor; - return new Promise(function (resolve, reject) { - promise.then(function (res) { return root._waitingQueue.push(wrap(resolve.bind(null, res))); }, function (err) { return root._waitingQueue.push(wrap(reject.bind(null, err))); }).finally(function () { - if (root._waitingFor === currentWaitPromise) { - // No one added a wait after us. Safe to stop the spinning. - root._waitingFor = null; - } + } + } + return diff; +} +function createTable(idbtrans, tableName, primKey, indexes) { + var store = idbtrans.db.createObjectStore(tableName, primKey.keyPath ? + { keyPath: primKey.keyPath, autoIncrement: primKey.auto } : + { autoIncrement: primKey.auto }); + indexes.forEach(function (idx) { return addIndex(store, idx); }); + return store; +} +function createMissingTables(newSchema, idbtrans) { + keys(newSchema).forEach(function (tableName) { + if (!idbtrans.db.objectStoreNames.contains(tableName)) { + if (debug) + console.debug('Dexie: Creating missing table', tableName); + createTable(idbtrans, tableName, newSchema[tableName].primKey, newSchema[tableName].indexes); + } + }); +} +function deleteRemovedTables(newSchema, idbtrans) { + [].slice.call(idbtrans.db.objectStoreNames).forEach(function (storeName) { + return newSchema[storeName] == null && idbtrans.db.deleteObjectStore(storeName); + }); +} +function addIndex(store, idx) { + store.createIndex(idx.name, idx.keyPath, { unique: idx.unique, multiEntry: idx.multi }); +} +function buildGlobalSchema(db, idbdb, tmpTrans) { + var globalSchema = {}; + var dbStoreNames = slice(idbdb.objectStoreNames, 0); + dbStoreNames.forEach(function (storeName) { + var store = tmpTrans.objectStore(storeName); + var keyPath = store.keyPath; + var primKey = createIndexSpec(nameFromKeyPath(keyPath), keyPath || "", true, false, !!store.autoIncrement, keyPath && typeof keyPath !== "string", true); + var indexes = []; + for (var j = 0; j < store.indexNames.length; ++j) { + var idbindex = store.index(store.indexNames[j]); + keyPath = idbindex.keyPath; + var index = createIndexSpec(idbindex.name, keyPath, !!idbindex.unique, !!idbindex.multiEntry, false, keyPath && typeof keyPath !== "string", false); + indexes.push(index); + } + globalSchema[storeName] = createTableSchema(storeName, primKey, indexes); + }); + return globalSchema; +} +function readGlobalSchema(db, idbdb, tmpTrans) { + db.verno = idbdb.version / 10; + var globalSchema = db._dbSchema = buildGlobalSchema(db, idbdb, tmpTrans); + db._storeNames = slice(idbdb.objectStoreNames, 0); + setApiOnPlace(db, [db._allTables], keys(globalSchema), globalSchema); +} +function verifyInstalledSchema(db, tmpTrans) { + var installedSchema = buildGlobalSchema(db, db.idbdb, tmpTrans); + var diff = getSchemaDiff(installedSchema, db._dbSchema); + return !(diff.add.length || diff.change.some(function (ch) { return ch.add.length || ch.change.length; })); +} +function adjustToExistingIndexNames(db, schema, idbtrans) { + var storeNames = idbtrans.db.objectStoreNames; + for (var i = 0; i < storeNames.length; ++i) { + var storeName = storeNames[i]; + var store = idbtrans.objectStore(storeName); + db._hasGetAll = 'getAll' in store; + for (var j = 0; j < store.indexNames.length; ++j) { + var indexName = store.indexNames[j]; + var keyPath = store.index(indexName).keyPath; + var dexieName = typeof keyPath === 'string' ? keyPath : "[" + slice(keyPath).join('+') + "]"; + if (schema[storeName]) { + var indexSpec = schema[storeName].idxByName[dexieName]; + if (indexSpec) { + indexSpec.name = indexName; + delete schema[storeName].idxByName[dexieName]; + schema[storeName].idxByName[indexName] = indexSpec; + } + } + } + } + if (typeof navigator !== 'undefined' && /Safari/.test(navigator.userAgent) && + !/(Chrome\/|Edge\/)/.test(navigator.userAgent) && + _global.WorkerGlobalScope && _global instanceof _global.WorkerGlobalScope && + [].concat(navigator.userAgent.match(/Safari\/(\d*)/))[1] < 604) { + db._hasGetAll = false; + } +} +function parseIndexSyntax(primKeyAndIndexes) { + return primKeyAndIndexes.split(',').map(function (index, indexNum) { + var _a; + var typeSplit = index.split(':'); + var type = (_a = typeSplit[1]) === null || _a === void 0 ? void 0 : _a.trim(); + index = typeSplit[0].trim(); + var name = index.replace(/([&*]|\+\+)/g, ""); + var keyPath = /^\[/.test(name) ? name.match(/^\[(.*)\]$/)[1].split('+') : name; + return createIndexSpec(name, keyPath || null, /\&/.test(index), /\*/.test(index), /\+\+/.test(index), isArray(keyPath), indexNum === 0, type); + }); +} + +var Version = (function () { + function Version() { + } + Version.prototype._createTableSchema = function (name, primKey, indexes) { + return createTableSchema(name, primKey, indexes); + }; + Version.prototype._parseIndexSyntax = function (primKeyAndIndexes) { + return parseIndexSyntax(primKeyAndIndexes); + }; + Version.prototype._parseStoresSpec = function (stores, outSchema) { + var _this = this; + keys(stores).forEach(function (tableName) { + if (stores[tableName] !== null) { + var indexes = _this._parseIndexSyntax(stores[tableName]); + var primKey = indexes.shift(); + if (!primKey) { + throw new exceptions.Schema('Invalid schema for table ' + tableName + ': ' + stores[tableName]); + } + primKey.unique = true; + if (primKey.multi) + throw new exceptions.Schema('Primary key cannot be multiEntry*'); + indexes.forEach(function (idx) { + if (idx.auto) + throw new exceptions.Schema('Only primary key can be marked as autoIncrement (++)'); + if (!idx.keyPath) + throw new exceptions.Schema('Index must have a name and cannot be an empty string'); }); - }); + var tblSchema = _this._createTableSchema(tableName, primKey, indexes); + outSchema[tableName] = tblSchema; + } + }); + }; + Version.prototype.stores = function (stores) { + var db = this.db; + this._cfg.storesSource = this._cfg.storesSource + ? extend(this._cfg.storesSource, stores) + : stores; + var versions = db._versions; + var storesSpec = {}; + var dbschema = {}; + versions.forEach(function (version) { + extend(storesSpec, version._cfg.storesSource); + dbschema = version._cfg.dbschema = {}; + version._parseStoresSpec(storesSpec, dbschema); + }); + db._dbSchema = dbschema; + removeTablesApi(db, [db._allTables, db, db.Transaction.prototype]); + setApiOnPlace(db, [db._allTables, db, db.Transaction.prototype, this._cfg.tables], keys(dbschema), dbschema); + db._storeNames = keys(dbschema); + return this; + }; + Version.prototype.upgrade = function (upgradeFunction) { + this._cfg.contentUpgrade = promisableChain(this._cfg.contentUpgrade || nop, upgradeFunction); + return this; + }; + return Version; +}()); + +function createVersionConstructor(db) { + return makeClassConstructor(Version.prototype, function Version(versionNumber) { + this.db = db; + this._cfg = { + version: versionNumber, + storesSource: null, + dbschema: {}, + tables: {}, + contentUpgrade: null + }; + }); +} + +function getDbNamesTable(indexedDB, IDBKeyRange) { + var dbNamesDB = indexedDB["_dbNamesDB"]; + if (!dbNamesDB) { + dbNamesDB = indexedDB["_dbNamesDB"] = new Dexie$1(DBNAMES_DB, { + addons: [], + indexedDB: indexedDB, + IDBKeyRange: IDBKeyRange, + }); + dbNamesDB.version(1).stores({ dbnames: "name" }); + } + return dbNamesDB.table("dbnames"); +} +function hasDatabasesNative(indexedDB) { + return indexedDB && typeof indexedDB.databases === "function"; +} +function getDatabaseNames(_a) { + var indexedDB = _a.indexedDB, IDBKeyRange = _a.IDBKeyRange; + return hasDatabasesNative(indexedDB) + ? Promise.resolve(indexedDB.databases()).then(function (infos) { + return infos + .map(function (info) { return info.name; }) + .filter(function (name) { return name !== DBNAMES_DB; }); + }) + : getDbNamesTable(indexedDB, IDBKeyRange).toCollection().primaryKeys(); +} +function _onDatabaseCreated(_a, name) { + var indexedDB = _a.indexedDB, IDBKeyRange = _a.IDBKeyRange; + !hasDatabasesNative(indexedDB) && + name !== DBNAMES_DB && + getDbNamesTable(indexedDB, IDBKeyRange).put({ name: name }).catch(nop); +} +function _onDatabaseDeleted(_a, name) { + var indexedDB = _a.indexedDB, IDBKeyRange = _a.IDBKeyRange; + !hasDatabasesNative(indexedDB) && + name !== DBNAMES_DB && + getDbNamesTable(indexedDB, IDBKeyRange).delete(name).catch(nop); +} + +function vip(fn) { + return newScope(function () { + PSD.letThrough = true; + return fn(); + }); +} + +function idbReady() { + var isSafari = !navigator.userAgentData && + /Safari\//.test(navigator.userAgent) && + !/Chrom(e|ium)\//.test(navigator.userAgent); + if (!isSafari || !indexedDB.databases) + return Promise.resolve(); + var intervalId; + return new Promise(function (resolve) { + var tryIdb = function () { return indexedDB.databases().finally(resolve); }; + intervalId = setInterval(tryIdb, 100); + tryIdb(); + }).finally(function () { return clearInterval(intervalId); }); +} + +var _a; +function isEmptyRange(node) { + return !("from" in node); +} +var RangeSet = function (fromOrTree, to) { + if (this) { + extend(this, arguments.length ? { d: 1, from: fromOrTree, to: arguments.length > 1 ? to : fromOrTree } : { d: 0 }); + } + else { + var rv = new RangeSet(); + if (fromOrTree && ("d" in fromOrTree)) { + extend(rv, fromOrTree); + } + return rv; + } +}; +props(RangeSet.prototype, (_a = { + add: function (rangeSet) { + mergeRanges(this, rangeSet); + return this; }, - // - // Transaction Public Properties and Methods - // - abort: function () { - this.active && this._reject(new exceptions.Abort()); - this.active = false; + addKey: function (key) { + addRange(this, key, key); + return this; }, - tables: { - get: deprecated("Transaction.tables", function () { return allTables; }) + addKeys: function (keys) { + var _this = this; + keys.forEach(function (key) { return addRange(_this, key, key); }); + return this; }, - table: function (name) { - var table = db.table(name); // Don't check that table is part of transaction. It must fail lazily! - return new Table(name, table.schema, this); + hasKey: function (key) { + var node = getRangeSetIterator(this).next(key).value; + return node && cmp(node.from, key) <= 0 && cmp(node.to, key) >= 0; } - }); - // - // - // - // WhereClause - // - // - // - function WhereClause(table, index, orCollection) { - /// - /// - /// - this._ctx = { - table: table, - index: index === ":id" ? null : index, - or: orCollection - }; + }, + _a[iteratorSymbol] = function () { + return getRangeSetIterator(this); + }, + _a)); +function addRange(target, from, to) { + var diff = cmp(from, to); + if (isNaN(diff)) + return; + if (diff > 0) + throw RangeError(); + if (isEmptyRange(target)) + return extend(target, { from: from, to: to, d: 1 }); + var left = target.l; + var right = target.r; + if (cmp(to, target.from) < 0) { + left + ? addRange(left, from, to) + : (target.l = { from: from, to: to, d: 1, l: null, r: null }); + return rebalance(target); } - props(WhereClause.prototype, function () { - // WhereClause private methods - function fail(collectionOrWhereClause, err, T) { - var collection = collectionOrWhereClause instanceof WhereClause ? - new Collection(collectionOrWhereClause) : - collectionOrWhereClause; - collection._ctx.error = T ? new T(err) : new TypeError(err); - return collection; - } - function emptyCollection(whereClause) { - return new Collection(whereClause, function () { return IDBKeyRange.only(""); }).limit(0); - } - function upperFactory(dir) { - return dir === "next" ? function (s) { return s.toUpperCase(); } : function (s) { return s.toLowerCase(); }; - } - function lowerFactory(dir) { - return dir === "next" ? function (s) { return s.toLowerCase(); } : function (s) { return s.toUpperCase(); }; - } - function nextCasing(key, lowerKey, upperNeedle, lowerNeedle, cmp, dir) { - var length = Math.min(key.length, lowerNeedle.length); - var llp = -1; - for (var i = 0; i < length; ++i) { - var lwrKeyChar = lowerKey[i]; - if (lwrKeyChar !== lowerNeedle[i]) { - if (cmp(key[i], upperNeedle[i]) < 0) - return key.substr(0, i) + upperNeedle[i] + upperNeedle.substr(i + 1); - if (cmp(key[i], lowerNeedle[i]) < 0) - return key.substr(0, i) + lowerNeedle[i] + upperNeedle.substr(i + 1); - if (llp >= 0) - return key.substr(0, llp) + lowerKey[llp] + upperNeedle.substr(llp + 1); - return null; + if (cmp(from, target.to) > 0) { + right + ? addRange(right, from, to) + : (target.r = { from: from, to: to, d: 1, l: null, r: null }); + return rebalance(target); + } + if (cmp(from, target.from) < 0) { + target.from = from; + target.l = null; + target.d = right ? right.d + 1 : 1; + } + if (cmp(to, target.to) > 0) { + target.to = to; + target.r = null; + target.d = target.l ? target.l.d + 1 : 1; + } + var rightWasCutOff = !target.r; + if (left && !target.l) { + mergeRanges(target, left); + } + if (right && rightWasCutOff) { + mergeRanges(target, right); + } +} +function mergeRanges(target, newSet) { + function _addRangeSet(target, _a) { + var from = _a.from, to = _a.to, l = _a.l, r = _a.r; + addRange(target, from, to); + if (l) + _addRangeSet(target, l); + if (r) + _addRangeSet(target, r); + } + if (!isEmptyRange(newSet)) + _addRangeSet(target, newSet); +} +function rangesOverlap(rangeSet1, rangeSet2) { + var i1 = getRangeSetIterator(rangeSet2); + var nextResult1 = i1.next(); + if (nextResult1.done) + return false; + var a = nextResult1.value; + var i2 = getRangeSetIterator(rangeSet1); + var nextResult2 = i2.next(a.from); + var b = nextResult2.value; + while (!nextResult1.done && !nextResult2.done) { + if (cmp(b.from, a.to) <= 0 && cmp(b.to, a.from) >= 0) + return true; + cmp(a.from, b.from) < 0 + ? (a = (nextResult1 = i1.next(b.from)).value) + : (b = (nextResult2 = i2.next(a.from)).value); + } + return false; +} +function getRangeSetIterator(node) { + var state = isEmptyRange(node) ? null : { s: 0, n: node }; + return { + next: function (key) { + var keyProvided = arguments.length > 0; + while (state) { + switch (state.s) { + case 0: + state.s = 1; + if (keyProvided) { + while (state.n.l && cmp(key, state.n.from) < 0) + state = { up: state, n: state.n.l, s: 1 }; + } + else { + while (state.n.l) + state = { up: state, n: state.n.l, s: 1 }; + } + case 1: + state.s = 2; + if (!keyProvided || cmp(key, state.n.to) <= 0) + return { value: state.n, done: false }; + case 2: + if (state.n.r) { + state.s = 3; + state = { up: state, n: state.n.r, s: 0 }; + continue; + } + case 3: + state = state.up; } - if (cmp(key[i], lwrKeyChar) < 0) - llp = i; } - if (length < lowerNeedle.length && dir === "next") - return key + upperNeedle.substr(key.length); - if (length < key.length && dir === "prev") - return key.substr(0, upperNeedle.length); - return (llp < 0 ? null : key.substr(0, llp) + lowerNeedle[llp] + upperNeedle.substr(llp + 1)); - } - function addIgnoreCaseAlgorithm(whereClause, match, needles, suffix) { - /// - var upper, lower, compare, upperNeedles, lowerNeedles, direction, nextKeySuffix, needlesLen = needles.length; - if (!needles.every(function (s) { return typeof s === 'string'; })) { - return fail(whereClause, STRING_EXPECTED); + return { done: true }; + }, + }; +} +function rebalance(target) { + var _a, _b; + var diff = (((_a = target.r) === null || _a === void 0 ? void 0 : _a.d) || 0) - (((_b = target.l) === null || _b === void 0 ? void 0 : _b.d) || 0); + var r = diff > 1 ? "r" : diff < -1 ? "l" : ""; + if (r) { + var l = r === "r" ? "l" : "r"; + var rootClone = __assign({}, target); + var oldRootRight = target[r]; + target.from = oldRootRight.from; + target.to = oldRootRight.to; + target[r] = oldRootRight[r]; + rootClone[r] = oldRootRight[l]; + target[l] = rootClone; + rootClone.d = computeDepth(rootClone); + } + target.d = computeDepth(target); +} +function computeDepth(_a) { + var r = _a.r, l = _a.l; + return (r ? (l ? Math.max(r.d, l.d) : r.d) : l ? l.d : 0) + 1; +} + +function extendObservabilitySet(target, newSet) { + keys(newSet).forEach(function (part) { + if (target[part]) + mergeRanges(target[part], newSet[part]); + else + target[part] = cloneSimpleObjectTree(newSet[part]); + }); + return target; +} + +function obsSetsOverlap(os1, os2) { + return os1.all || os2.all || Object.keys(os1).some(function (key) { return os2[key] && rangesOverlap(os2[key], os1[key]); }); +} + +var cache = {}; + +var unsignaledParts = {}; +var isTaskEnqueued = false; +function signalSubscribersLazily(part, optimistic) { + extendObservabilitySet(unsignaledParts, part); + if (!isTaskEnqueued) { + isTaskEnqueued = true; + setTimeout(function () { + isTaskEnqueued = false; + var parts = unsignaledParts; + unsignaledParts = {}; + signalSubscribersNow(parts, false); + }, 0); + } +} +function signalSubscribersNow(updatedParts, deleteAffectedCacheEntries) { + if (deleteAffectedCacheEntries === void 0) { deleteAffectedCacheEntries = false; } + var queriesToSignal = new Set(); + if (updatedParts.all) { + for (var _i = 0, _a = Object.values(cache); _i < _a.length; _i++) { + var tblCache = _a[_i]; + collectTableSubscribers(tblCache, updatedParts, queriesToSignal, deleteAffectedCacheEntries); + } + } + else { + for (var key in updatedParts) { + var parts = /^idb\:\/\/(.*)\/(.*)\//.exec(key); + if (parts) { + var dbName = parts[1], tableName = parts[2]; + var tblCache = cache["idb://".concat(dbName, "/").concat(tableName)]; + if (tblCache) + collectTableSubscribers(tblCache, updatedParts, queriesToSignal, deleteAffectedCacheEntries); + } + } + } + queriesToSignal.forEach(function (requery) { return requery(); }); +} +function collectTableSubscribers(tblCache, updatedParts, outQueriesToSignal, deleteAffectedCacheEntries) { + var updatedEntryLists = []; + for (var _i = 0, _a = Object.entries(tblCache.queries.query); _i < _a.length; _i++) { + var _b = _a[_i], indexName = _b[0], entries = _b[1]; + var filteredEntries = []; + for (var _c = 0, entries_1 = entries; _c < entries_1.length; _c++) { + var entry = entries_1[_c]; + if (obsSetsOverlap(updatedParts, entry.obsSet)) { + entry.subscribers.forEach(function (requery) { return outQueriesToSignal.add(requery); }); + } + else if (deleteAffectedCacheEntries) { + filteredEntries.push(entry); } - function initDirection(dir) { - upper = upperFactory(dir); - lower = lowerFactory(dir); - compare = (dir === "next" ? simpleCompare : simpleCompareReverse); - var needleBounds = needles.map(function (needle) { - return { lower: lower(needle), upper: upper(needle) }; - }).sort(function (a, b) { - return compare(a.lower, b.lower); + } + if (deleteAffectedCacheEntries) + updatedEntryLists.push([indexName, filteredEntries]); + } + if (deleteAffectedCacheEntries) { + for (var _d = 0, updatedEntryLists_1 = updatedEntryLists; _d < updatedEntryLists_1.length; _d++) { + var _e = updatedEntryLists_1[_d], indexName = _e[0], filteredEntries = _e[1]; + tblCache.queries.query[indexName] = filteredEntries; + } + } +} + +function dexieOpen(db) { + var state = db._state; + var indexedDB = db._deps.indexedDB; + if (state.isBeingOpened || db.idbdb) + return state.dbReadyPromise.then(function () { return state.dbOpenError ? + rejection(state.dbOpenError) : + db; }); + state.isBeingOpened = true; + state.dbOpenError = null; + state.openComplete = false; + var openCanceller = state.openCanceller; + var nativeVerToOpen = Math.round(db.verno * 10); + var schemaPatchMode = false; + function throwIfCancelled() { + if (state.openCanceller !== openCanceller) + throw new exceptions.DatabaseClosed('db.open() was cancelled'); + } + var resolveDbReady = state.dbReadyResolve, + upgradeTransaction = null, wasCreated = false; + var tryOpenDB = function () { return new DexiePromise(function (resolve, reject) { + throwIfCancelled(); + if (!indexedDB) + throw new exceptions.MissingAPI(); + var dbName = db.name; + var req = state.autoSchema || !nativeVerToOpen ? + indexedDB.open(dbName) : + indexedDB.open(dbName, nativeVerToOpen); + if (!req) + throw new exceptions.MissingAPI(); + req.onerror = eventRejectHandler(reject); + req.onblocked = wrap(db._fireOnBlocked); + req.onupgradeneeded = wrap(function (e) { + upgradeTransaction = req.transaction; + if (state.autoSchema && !db._options.allowEmptyDB) { + req.onerror = preventDefault; + upgradeTransaction.abort(); + req.result.close(); + var delreq = indexedDB.deleteDatabase(dbName); + delreq.onsuccess = delreq.onerror = wrap(function () { + reject(new exceptions.NoSuchDatabase("Database ".concat(dbName, " doesnt exist"))); }); - upperNeedles = needleBounds.map(function (nb) { return nb.upper; }); - lowerNeedles = needleBounds.map(function (nb) { return nb.lower; }); - direction = dir; - nextKeySuffix = (dir === "next" ? "" : suffix); } - initDirection("next"); - var c = new Collection(whereClause, function () { - return IDBKeyRange.bound(upperNeedles[0], lowerNeedles[needlesLen - 1] + suffix); - }); - c._ondirectionchange = function (direction) { - // This event onlys occur before filter is called the first time. - initDirection(direction); - }; - var firstPossibleNeedle = 0; - c._addAlgorithm(function (cursor, advance, resolve) { - /// - /// - /// - var key = cursor.key; - if (typeof key !== 'string') - return false; - var lowerKey = lower(key); - if (match(lowerKey, lowerNeedles, firstPossibleNeedle)) { - return true; + else { + upgradeTransaction.onerror = eventRejectHandler(reject); + var oldVer = e.oldVersion > Math.pow(2, 62) ? 0 : e.oldVersion; + wasCreated = oldVer < 1; + db.idbdb = req.result; + if (schemaPatchMode) { + patchCurrentVersion(db, upgradeTransaction); } - else { - var lowestPossibleCasing = null; - for (var i = firstPossibleNeedle; i < needlesLen; ++i) { - var casing = nextCasing(key, lowerKey, upperNeedles[i], lowerNeedles[i], compare, direction); - if (casing === null && lowestPossibleCasing === null) - firstPossibleNeedle = i + 1; - else if (lowestPossibleCasing === null || compare(lowestPossibleCasing, casing) > 0) { - lowestPossibleCasing = casing; - } - } - if (lowestPossibleCasing !== null) { - advance(function () { cursor.continue(lowestPossibleCasing + nextKeySuffix); }); - } + runUpgraders(db, oldVer / 10, upgradeTransaction, reject); + } + }, reject); + req.onsuccess = wrap(function () { + upgradeTransaction = null; + var idbdb = db.idbdb = req.result; + var objectStoreNames = slice(idbdb.objectStoreNames); + if (objectStoreNames.length > 0) + try { + var tmpTrans = idbdb.transaction(safariMultiStoreFix(objectStoreNames), 'readonly'); + if (state.autoSchema) + readGlobalSchema(db, idbdb, tmpTrans); else { - advance(resolve); + adjustToExistingIndexNames(db, db._dbSchema, tmpTrans); + if (!verifyInstalledSchema(db, tmpTrans) && !schemaPatchMode) { + console.warn("Dexie SchemaDiff: Schema was extended without increasing the number passed to db.version(). Dexie will add missing parts and increment native version number to workaround this."); + idbdb.close(); + nativeVerToOpen = idbdb.version + 1; + schemaPatchMode = true; + return resolve(tryOpenDB()); + } } - return false; - } - }); - return c; - } - // - // WhereClause public methods - // - return { - between: function (lower, upper, includeLower, includeUpper) { - /// - /// Filter out records whose where-field lays between given lower and upper values. Applies to Strings, Numbers and Dates. - /// - /// - /// - /// Whether items that equals lower should be included. Default true. - /// Whether items that equals upper should be included. Default false. - /// - includeLower = includeLower !== false; // Default to true - includeUpper = includeUpper === true; // Default to false - try { - 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. - return new Collection(this, function () { return IDBKeyRange.bound(lower, upper, !includeLower, !includeUpper); }); + generateMiddlewareStacks(db, tmpTrans); } catch (e) { - return fail(this, INVALID_KEY_ARGUMENT); } - }, - equals: function (value) { - return new Collection(this, function () { return IDBKeyRange.only(value); }); - }, - above: function (value) { - return new Collection(this, function () { return IDBKeyRange.lowerBound(value, true); }); - }, - aboveOrEqual: function (value) { - return new Collection(this, function () { return IDBKeyRange.lowerBound(value); }); - }, - below: function (value) { - return new Collection(this, function () { return IDBKeyRange.upperBound(value, true); }); - }, - belowOrEqual: function (value) { - return new Collection(this, function () { return IDBKeyRange.upperBound(value); }); - }, - startsWith: function (str) { - /// - if (typeof str !== 'string') - return fail(this, STRING_EXPECTED); - return this.between(str, str + maxString, true, true); - }, - startsWithIgnoreCase: function (str) { - /// - if (str === "") - return this.startsWith(str); - return addIgnoreCaseAlgorithm(this, function (x, a) { return x.indexOf(a[0]) === 0; }, [str], maxString); - }, - equalsIgnoreCase: function (str) { - /// - return addIgnoreCaseAlgorithm(this, function (x, a) { return x === a[0]; }, [str], ""); - }, - anyOfIgnoreCase: function () { - var set = getArrayOf.apply(NO_CHAR_ARRAY, arguments); - if (set.length === 0) - return emptyCollection(this); - return addIgnoreCaseAlgorithm(this, function (x, a) { return a.indexOf(x) !== -1; }, set, ""); - }, - startsWithAnyOfIgnoreCase: function () { - var set = getArrayOf.apply(NO_CHAR_ARRAY, arguments); - if (set.length === 0) - return emptyCollection(this); - return addIgnoreCaseAlgorithm(this, function (x, a) { - return a.some(function (n) { - return x.indexOf(n) === 0; - }); - }, set, maxString); - }, - anyOf: function () { - var set = getArrayOf.apply(NO_CHAR_ARRAY, arguments); - var compare = ascending; - try { - set.sort(compare); + connections.push(db); + idbdb.onversionchange = wrap(function (ev) { + state.vcFired = true; + db.on("versionchange").fire(ev); + }); + idbdb.onclose = wrap(function () { + db.close({ disableAutoOpen: false }); + }); + if (wasCreated) + _onDatabaseCreated(db._deps, dbName); + resolve(); + }, reject); + }).catch(function (err) { + switch (err === null || err === void 0 ? void 0 : err.name) { + case "UnknownError": + if (state.PR1398_maxLoop > 0) { + state.PR1398_maxLoop--; + console.warn('Dexie: Workaround for Chrome UnknownError on open()'); + return tryOpenDB(); } - catch (e) { - return fail(this, INVALID_KEY_ARGUMENT); + break; + case "VersionError": + if (nativeVerToOpen > 0) { + nativeVerToOpen = 0; + return tryOpenDB(); } - if (set.length === 0) - return emptyCollection(this); - var c = new Collection(this, function () { return IDBKeyRange.bound(set[0], set[set.length - 1]); }); - c._ondirectionchange = function (direction) { - compare = (direction === "next" ? ascending : descending); - set.sort(compare); - }; - var i = 0; - c._addAlgorithm(function (cursor, advance, resolve) { - var key = cursor.key; - while (compare(key, set[i]) > 0) { - // The cursor has passed beyond this key. Check next. - ++i; - if (i === set.length) { - // There is no next. Stop searching. - advance(resolve); - return false; - } - } - if (compare(key, set[i]) === 0) { - // 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. - return true; - } - else { - // cursor.key not yet at set[i]. Forward cursor to the next key to hunt for. - advance(function () { cursor.continue(set[i]); }); - return false; - } + break; + } + return DexiePromise.reject(err); + }); }; + return DexiePromise.race([ + openCanceller, + (typeof navigator === 'undefined' ? DexiePromise.resolve() : idbReady()).then(tryOpenDB) + ]).then(function () { + throwIfCancelled(); + state.onReadyBeingFired = []; + return DexiePromise.resolve(vip(function () { return db.on.ready.fire(db.vip); })).then(function fireRemainders() { + if (state.onReadyBeingFired.length > 0) { + var remainders_1 = state.onReadyBeingFired.reduce(promisableChain, nop); + state.onReadyBeingFired = []; + return DexiePromise.resolve(vip(function () { return remainders_1(db.vip); })).then(fireRemainders); + } + }); + }).finally(function () { + if (state.openCanceller === openCanceller) { + state.onReadyBeingFired = null; + state.isBeingOpened = false; + } + }).catch(function (err) { + state.dbOpenError = err; + try { + upgradeTransaction && upgradeTransaction.abort(); + } + catch (_a) { } + if (openCanceller === state.openCanceller) { + db._close(); + } + return rejection(err); + }).finally(function () { + state.openComplete = true; + resolveDbReady(); + }).then(function () { + if (wasCreated) { + var everything_1 = {}; + db.tables.forEach(function (table) { + table.schema.indexes.forEach(function (idx) { + if (idx.name) + everything_1["idb://".concat(db.name, "/").concat(table.name, "/").concat(idx.name)] = new RangeSet(-Infinity, [[[]]]); }); - return c; - }, - notEqual: function (value) { - return this.inAnyRange([[minKey, value], [value, maxKey]], { includeLowers: false, includeUppers: false }); - }, - noneOf: function () { - var set = getArrayOf.apply(NO_CHAR_ARRAY, arguments); - if (set.length === 0) - return new Collection(this); // Return entire collection. - try { - set.sort(ascending); - } - catch (e) { - return fail(this, INVALID_KEY_ARGUMENT); - } - // Transform ["a","b","c"] to a set of ranges for between/above/below: [[minKey,"a"], ["a","b"], ["b","c"], ["c",maxKey]] - var ranges = set.reduce(function (res, val) { return res ? res.concat([[res[res.length - 1][1], val]]) : [[minKey, val]]; }, null); - ranges.push([set[set.length - 1], maxKey]); - return this.inAnyRange(ranges, { includeLowers: false, includeUppers: false }); - }, - /** Filter out values withing given set of ranges. - * Example, give children and elders a rebate of 50%: - * - * db.friends.where('age').inAnyRange([[0,18],[65,Infinity]]).modify({Rebate: 1/2}); - * - * @param {(string|number|Date|Array)[][]} ranges - * @param {{includeLowers: boolean, includeUppers: boolean}} options - */ - inAnyRange: function (ranges, options) { - if (ranges.length === 0) - return emptyCollection(this); - if (!ranges.every(function (range) { return range[0] !== undefined && range[1] !== undefined && ascending(range[0], range[1]) <= 0; })) { - 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); - } - var includeLowers = !options || options.includeLowers !== false; // Default to true - var includeUppers = options && options.includeUppers === true; // Default to false - function addRange(ranges, newRange) { - for (var i = 0, l = ranges.length; i < l; ++i) { - var range = ranges[i]; - if (cmp(newRange[0], range[1]) < 0 && cmp(newRange[1], range[0]) > 0) { - range[0] = min(range[0], newRange[0]); - range[1] = max(range[1], newRange[1]); - break; - } - } - if (i === l) - ranges.push(newRange); - return ranges; + everything_1["idb://".concat(db.name, "/").concat(table.name, "/")] = everything_1["idb://".concat(db.name, "/").concat(table.name, "/:dels")] = new RangeSet(-Infinity, [[[]]]); + }); + globalEvents(DEXIE_STORAGE_MUTATED_EVENT_NAME).fire(everything_1); + signalSubscribersNow(everything_1, true); + } + return db; + }); +} + +function awaitIterator(iterator) { + var callNext = function (result) { return iterator.next(result); }, doThrow = function (error) { return iterator.throw(error); }, onSuccess = step(callNext), onError = step(doThrow); + function step(getNext) { + return function (val) { + var next = getNext(val), value = next.value; + return next.done ? value : + (!value || typeof value.then !== 'function' ? + isArray(value) ? Promise.all(value).then(onSuccess, onError) : onSuccess(value) : + value.then(onSuccess, onError)); + }; + } + return step(callNext)(); +} + +function extractTransactionArgs(mode, _tableArgs_, scopeFunc) { + var i = arguments.length; + if (i < 2) + throw new exceptions.InvalidArgument("Too few arguments"); + var args = new Array(i - 1); + while (--i) + args[i - 1] = arguments[i]; + scopeFunc = args.pop(); + var tables = flatten(args); + return [mode, tables, scopeFunc]; +} +function enterTransactionScope(db, mode, storeNames, parentTransaction, scopeFunc) { + return DexiePromise.resolve().then(function () { + var transless = PSD.transless || PSD; + var trans = db._createTransaction(mode, storeNames, db._dbSchema, parentTransaction); + trans.explicit = true; + var zoneProps = { + trans: trans, + transless: transless + }; + if (parentTransaction) { + trans.idbtrans = parentTransaction.idbtrans; + } + else { + try { + trans.create(); + trans.idbtrans._explicit = true; + db._state.PR1398_maxLoop = 3; + } + catch (ex) { + if (ex.name === errnames.InvalidState && db.isOpen() && --db._state.PR1398_maxLoop > 0) { + console.warn('Dexie: Need to reopen db'); + db.close({ disableAutoOpen: false }); + return db.open().then(function () { return enterTransactionScope(db, mode, storeNames, null, scopeFunc); }); } - var sortDirection = ascending; - function rangeSorter(a, b) { return sortDirection(a[0], b[0]); } - // Join overlapping ranges - var set; - try { - set = ranges.reduce(addRange, []); - set.sort(rangeSorter); + return rejection(ex); + } + } + var scopeFuncIsAsync = isAsyncFunction(scopeFunc); + if (scopeFuncIsAsync) { + incrementExpectedAwaits(); + } + var returnValue; + var promiseFollowed = DexiePromise.follow(function () { + returnValue = scopeFunc.call(trans, trans); + if (returnValue) { + if (scopeFuncIsAsync) { + var decrementor = decrementExpectedAwaits.bind(null, null); + returnValue.then(decrementor, decrementor); } - catch (ex) { - return fail(this, INVALID_KEY_ARGUMENT); + else if (typeof returnValue.next === 'function' && typeof returnValue.throw === 'function') { + returnValue = awaitIterator(returnValue); } - var i = 0; - var keyIsBeyondCurrentEntry = includeUppers ? - function (key) { return ascending(key, set[i][1]) > 0; } : - function (key) { return ascending(key, set[i][1]) >= 0; }; - var keyIsBeforeCurrentEntry = includeLowers ? - function (key) { return descending(key, set[i][0]) > 0; } : - function (key) { return descending(key, set[i][0]) >= 0; }; - function keyWithinCurrentRange(key) { - return !keyIsBeyondCurrentEntry(key) && !keyIsBeforeCurrentEntry(key); + } + }, zoneProps); + return (returnValue && typeof returnValue.then === 'function' ? + DexiePromise.resolve(returnValue).then(function (x) { return trans.active ? + x + : rejection(new exceptions.PrematureCommit("Transaction committed too early. See http://bit.ly/2kdckMn")); }) + : promiseFollowed.then(function () { return returnValue; })).then(function (x) { + if (parentTransaction) + trans._resolve(); + return trans._completion.then(function () { return x; }); + }).catch(function (e) { + trans._reject(e); + return rejection(e); + }); + }); +} + +function pad(a, value, count) { + var result = isArray(a) ? a.slice() : [a]; + for (var i = 0; i < count; ++i) + result.push(value); + return result; +} +function createVirtualIndexMiddleware(down) { + return __assign(__assign({}, down), { table: function (tableName) { + var table = down.table(tableName); + var schema = table.schema; + var indexLookup = {}; + var allVirtualIndexes = []; + function addVirtualIndexes(keyPath, keyTail, lowLevelIndex) { + var keyPathAlias = getKeyPathAlias(keyPath); + var indexList = (indexLookup[keyPathAlias] = indexLookup[keyPathAlias] || []); + var keyLength = keyPath == null ? 0 : typeof keyPath === 'string' ? 1 : keyPath.length; + var isVirtual = keyTail > 0; + var virtualIndex = __assign(__assign({}, lowLevelIndex), { name: isVirtual + ? "".concat(keyPathAlias, "(virtual-from:").concat(lowLevelIndex.name, ")") + : lowLevelIndex.name, lowLevelIndex: lowLevelIndex, isVirtual: isVirtual, keyTail: keyTail, keyLength: keyLength, extractKey: getKeyExtractor(keyPath), unique: !isVirtual && lowLevelIndex.unique }); + indexList.push(virtualIndex); + if (!virtualIndex.isPrimaryKey) { + allVirtualIndexes.push(virtualIndex); } - var checkKey = keyIsBeyondCurrentEntry; - var c = new Collection(this, function () { - return IDBKeyRange.bound(set[0][0], set[set.length - 1][1], !includeLowers, !includeUppers); - }); - c._ondirectionchange = function (direction) { - if (direction === "next") { - checkKey = keyIsBeyondCurrentEntry; - sortDirection = ascending; - } - else { - checkKey = keyIsBeforeCurrentEntry; - sortDirection = descending; - } - set.sort(rangeSorter); - }; - c._addAlgorithm(function (cursor, advance, resolve) { - var key = cursor.key; - while (checkKey(key)) { - // The cursor has passed beyond this key. Check next. - ++i; - if (i === set.length) { - // There is no next. Stop searching. - advance(resolve); - return false; - } - } - if (keyWithinCurrentRange(key)) { - // 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. - return true; - } - else if (cmp(key, set[i][1]) === 0 || cmp(key, set[i][0]) === 0) { - // includeUpper or includeLower is false so keyWithinCurrentRange() returns false even though we are at range border. - // Continue to next key but don't include this one. - return false; - } - else { - // cursor.key not yet at set[i]. Forward cursor to the next key to hunt for. - advance(function () { - if (sortDirection === ascending) - cursor.continue(set[i][0]); - else - cursor.continue(set[i][1]); - }); - return false; - } - }); - return c; - }, - startsWithAnyOf: function () { - var set = getArrayOf.apply(NO_CHAR_ARRAY, arguments); - if (!set.every(function (s) { return typeof s === 'string'; })) { - return fail(this, "startsWithAnyOf() only works with strings"); + if (keyLength > 1) { + var virtualKeyPath = keyLength === 2 ? + keyPath[0] : + keyPath.slice(0, keyLength - 1); + addVirtualIndexes(virtualKeyPath, keyTail + 1, lowLevelIndex); } - if (set.length === 0) - return emptyCollection(this); - return this.inAnyRange(set.map(function (str) { - return [str, str + maxString]; - })); + indexList.sort(function (a, b) { return a.keyTail - b.keyTail; }); + return virtualIndex; } - }; - }); - // - // - // - // Collection Class - // - // - // - function Collection(whereClause, keyRangeGenerator) { - /// - /// - /// - /// Where clause instance - /// - var keyRange = null, error = null; - if (keyRangeGenerator) - try { - keyRange = keyRangeGenerator(); + var primaryKey = addVirtualIndexes(schema.primaryKey.keyPath, 0, schema.primaryKey); + indexLookup[":id"] = [primaryKey]; + for (var _i = 0, _a = schema.indexes; _i < _a.length; _i++) { + var index = _a[_i]; + addVirtualIndexes(index.keyPath, 0, index); } - catch (ex) { - error = ex; + function findBestIndex(keyPath) { + var result = indexLookup[getKeyPathAlias(keyPath)]; + return result && result[0]; } - var whereCtx = whereClause._ctx, table = whereCtx.table; - this._ctx = { - table: table, - index: whereCtx.index, - isPrimKey: (!whereCtx.index || (table.schema.primKey.keyPath && whereCtx.index === table.schema.primKey.name)), - range: keyRange, - keysOnly: false, - dir: "next", - unique: "", - algorithm: null, - filter: null, - replayFilter: null, - justLimit: true, - isMatch: null, - offset: 0, - limit: Infinity, - error: error, - or: whereCtx.or, - valueMapper: table.hook.reading.fire - }; - } - function isPlainKeyRange(ctx, ignoreLimitFilter) { - return !(ctx.filter || ctx.algorithm || ctx.or) && - (ignoreLimitFilter ? ctx.justLimit : !ctx.replayFilter); - } - props(Collection.prototype, function () { - // - // Collection Private Functions - // - function addFilter(ctx, fn) { - ctx.filter = combine(ctx.filter, fn); - } - function addReplayFilter(ctx, factory, isLimitFilter) { - var curr = ctx.replayFilter; - ctx.replayFilter = curr ? function () { return combine(curr(), factory()); } : factory; - ctx.justLimit = isLimitFilter && !curr; - } - function addMatchFilter(ctx, fn) { - ctx.isMatch = combine(ctx.isMatch, fn); - } - /** @param ctx { - * isPrimKey: boolean, - * table: Table, - * index: string - * } - * @param store IDBObjectStore - **/ - function getIndexOrStore(ctx, store) { - if (ctx.isPrimKey) - return store; - var indexSpec = ctx.table.schema.idxByName[ctx.index]; - if (!indexSpec) - throw new exceptions.Schema("KeyPath " + ctx.index + " on object store " + store.name + " is not indexed"); - return store.index(indexSpec.name); - } - /** @param ctx { - * isPrimKey: boolean, - * table: Table, - * index: string, - * keysOnly: boolean, - * range?: IDBKeyRange, - * dir: "next" | "prev" - * } - */ - function openCursor(ctx, store) { - var idxOrStore = getIndexOrStore(ctx, store); - return ctx.keysOnly && 'openKeyCursor' in idxOrStore ? - idxOrStore.openKeyCursor(ctx.range || null, ctx.dir + ctx.unique) : - idxOrStore.openCursor(ctx.range || null, ctx.dir + ctx.unique); - } - function iter(ctx, fn, resolve, reject, idbstore) { - var filter = ctx.replayFilter ? combine(ctx.filter, ctx.replayFilter()) : ctx.filter; - if (!ctx.or) { - iterate(openCursor(ctx, idbstore), combine(ctx.algorithm, filter), fn, resolve, reject, !ctx.keysOnly && ctx.valueMapper); + function translateRange(range, keyTail) { + return { + type: range.type === 1 ? + 2 : + range.type, + lower: pad(range.lower, range.lowerOpen ? down.MAX_KEY : down.MIN_KEY, keyTail), + lowerOpen: true, + upper: pad(range.upper, range.upperOpen ? down.MIN_KEY : down.MAX_KEY, keyTail), + upperOpen: true + }; } - else - (function () { - var set = {}; - var resolved = 0; - function resolveboth() { - if (++resolved === 2) - resolve(); // Seems like we just support or btwn max 2 expressions, but there are no limit because we do recursion. - } - function union(item, cursor, advance) { - if (!filter || filter(cursor, advance, resolveboth, reject)) { - var primaryKey = cursor.primaryKey; - var key = '' + primaryKey; - if (key === '[object ArrayBuffer]') - key = '' + new Uint8Array(primaryKey); - if (!hasOwn(set, key)) { - set[key] = true; - fn(item, cursor, advance); - } + function translateRequest(req) { + var index = req.query.index; + return index.isVirtual ? __assign(__assign({}, req), { query: { + index: index.lowLevelIndex, + range: translateRange(req.query.range, index.keyTail) + } }) : req; + } + var result = __assign(__assign({}, table), { schema: __assign(__assign({}, schema), { primaryKey: primaryKey, indexes: allVirtualIndexes, getIndexByKeyPath: findBestIndex }), count: function (req) { + return table.count(translateRequest(req)); + }, query: function (req) { + return table.query(translateRequest(req)); + }, openCursor: function (req) { + var _a = req.query.index, keyTail = _a.keyTail, isVirtual = _a.isVirtual, keyLength = _a.keyLength; + if (!isVirtual) + return table.openCursor(req); + function createVirtualCursor(cursor) { + function _continue(key) { + key != null ? + cursor.continue(pad(key, req.reverse ? down.MAX_KEY : down.MIN_KEY, keyTail)) : + req.unique ? + cursor.continue(cursor.key.slice(0, keyLength) + .concat(req.reverse + ? down.MIN_KEY + : down.MAX_KEY, keyTail)) : + cursor.continue(); } + var virtualCursor = Object.create(cursor, { + continue: { value: _continue }, + continuePrimaryKey: { + value: function (key, primaryKey) { + cursor.continuePrimaryKey(pad(key, down.MAX_KEY, keyTail), primaryKey); + } + }, + primaryKey: { + get: function () { + return cursor.primaryKey; + } + }, + key: { + get: function () { + var key = cursor.key; + return keyLength === 1 ? + key[0] : + key.slice(0, keyLength); + } + }, + value: { + get: function () { + return cursor.value; + } + } + }); + return virtualCursor; } - ctx.or._iterate(union, resolveboth, reject, idbstore); - iterate(openCursor(ctx, idbstore), ctx.algorithm, union, resolveboth, reject, !ctx.keysOnly && ctx.valueMapper); - })(); - } - return { - // - // Collection Protected Functions - // - _read: function (fn, cb) { - var ctx = this._ctx; - return ctx.error ? - ctx.table._trans(null, rejection.bind(null, ctx.error)) : - ctx.table._idbstore(READONLY, fn).then(cb); - }, - _write: function (fn) { - var ctx = this._ctx; - 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. - }, - _addAlgorithm: function (fn) { - var ctx = this._ctx; - ctx.algorithm = combine(ctx.algorithm, fn); - }, - _iterate: function (fn, resolve, reject, idbstore) { - return iter(this._ctx, fn, resolve, reject, idbstore); - }, - clone: function (props$$1) { - var rv = Object.create(this.constructor.prototype), ctx = Object.create(this._ctx); - if (props$$1) - extend(ctx, props$$1); - rv._ctx = ctx; - return rv; - }, - raw: function () { - this._ctx.valueMapper = null; - return this; - }, - // - // Collection Public methods - // - each: function (fn) { - var ctx = this._ctx; - return this._read(function (resolve, reject, idbstore) { - iter(ctx, fn, resolve, reject, idbstore); - }); - }, - count: function (cb) { - var ctx = this._ctx; - if (isPlainKeyRange(ctx, true)) { - // This is a plain key range. We can use the count() method if the index. - return this._read(function (resolve, reject, idbstore) { - var idx = getIndexOrStore(ctx, idbstore); - var req = (ctx.range ? idx.count(ctx.range) : idx.count()); - req.onerror = eventRejectHandler(reject); - req.onsuccess = function (e) { - resolve(Math.min(e.target.result, ctx.limit)); - }; - }, cb); - } - else { - // Algorithms, filters or expressions are applied. Need to count manually. - var count = 0; - return this._read(function (resolve, reject, idbstore) { - iter(ctx, function () { ++count; return false; }, function () { resolve(count); }, reject, idbstore); - }, cb); + return table.openCursor(translateRequest(req)) + .then(function (cursor) { return cursor && createVirtualCursor(cursor); }); + } }); + return result; + } }); +} +var virtualIndexMiddleware = { + stack: "dbcore", + name: "VirtualIndexMiddleware", + level: 1, + create: createVirtualIndexMiddleware +}; + +function getObjectDiff(a, b, rv, prfx) { + rv = rv || {}; + prfx = prfx || ''; + keys(a).forEach(function (prop) { + if (!hasOwn(b, prop)) { + rv[prfx + prop] = undefined; + } + else { + var ap = a[prop], bp = b[prop]; + if (typeof ap === 'object' && typeof bp === 'object' && ap && bp) { + var apTypeName = toStringTag(ap); + var bpTypeName = toStringTag(bp); + if (apTypeName !== bpTypeName) { + rv[prfx + prop] = b[prop]; } - }, - sortBy: function (keyPath, cb) { - /// - var parts = keyPath.split('.').reverse(), lastPart = parts[0], lastIndex = parts.length - 1; - function getval(obj, i) { - if (i) - return getval(obj[parts[i]], i - 1); - return obj[lastPart]; + else if (apTypeName === 'Object') { + getObjectDiff(ap, bp, rv, prfx + prop + '.'); } - var order = this._ctx.dir === "next" ? 1 : -1; - function sorter(a, b) { - var aVal = getval(a, lastIndex), bVal = getval(b, lastIndex); - return aVal < bVal ? -order : aVal > bVal ? order : 0; + else if (ap !== bp) { + rv[prfx + prop] = b[prop]; } - return this.toArray(function (a) { - return a.sort(sorter); - }).then(cb); - }, - toArray: function (cb) { - var ctx = this._ctx; - return this._read(function (resolve, reject, idbstore) { - if (hasGetAll && ctx.dir === 'next' && isPlainKeyRange(ctx, true) && ctx.limit > 0) { - // Special optimation if we could use IDBObjectStore.getAll() or - // IDBKeyRange.getAll(): - var readingHook = ctx.table.hook.reading.fire; - var idxOrStore = getIndexOrStore(ctx, idbstore); - var req = ctx.limit < Infinity ? - idxOrStore.getAll(ctx.range, ctx.limit) : - idxOrStore.getAll(ctx.range); - req.onerror = eventRejectHandler(reject); - req.onsuccess = readingHook === mirror ? - eventSuccessHandler(resolve) : - eventSuccessHandler(function (res) { - try { - resolve(res.map(readingHook)); + } + else if (ap !== bp) + rv[prfx + prop] = b[prop]; + } + }); + keys(b).forEach(function (prop) { + if (!hasOwn(a, prop)) { + rv[prfx + prop] = b[prop]; + } + }); + return rv; +} + +function getEffectiveKeys(primaryKey, req) { + if (req.type === 'delete') + return req.keys; + return req.keys || req.values.map(primaryKey.extractKey); +} + +var hooksMiddleware = { + stack: "dbcore", + name: "HooksMiddleware", + level: 2, + create: function (downCore) { return (__assign(__assign({}, downCore), { table: function (tableName) { + var downTable = downCore.table(tableName); + var primaryKey = downTable.schema.primaryKey; + var tableMiddleware = __assign(__assign({}, downTable), { mutate: function (req) { + var dxTrans = PSD.trans; + var _a = dxTrans.table(tableName).hook, deleting = _a.deleting, creating = _a.creating, updating = _a.updating; + switch (req.type) { + case 'add': + if (creating.fire === nop) + break; + return dxTrans._promise('readwrite', function () { return addPutOrDelete(req); }, true); + case 'put': + if (creating.fire === nop && updating.fire === nop) + break; + return dxTrans._promise('readwrite', function () { return addPutOrDelete(req); }, true); + case 'delete': + if (deleting.fire === nop) + break; + return dxTrans._promise('readwrite', function () { return addPutOrDelete(req); }, true); + case 'deleteRange': + if (deleting.fire === nop) + break; + return dxTrans._promise('readwrite', function () { return deleteRange(req); }, true); + } + return downTable.mutate(req); + function addPutOrDelete(req) { + var dxTrans = PSD.trans; + var keys = req.keys || getEffectiveKeys(primaryKey, req); + if (!keys) + throw new Error("Keys missing"); + req = req.type === 'add' || req.type === 'put' ? __assign(__assign({}, req), { keys: keys }) : __assign({}, req); + if (req.type !== 'delete') + req.values = __spreadArray([], req.values, true); + if (req.keys) + req.keys = __spreadArray([], req.keys, true); + return getExistingValues(downTable, req, keys).then(function (existingValues) { + var contexts = keys.map(function (key, i) { + var existingValue = existingValues[i]; + var ctx = { onerror: null, onsuccess: null }; + if (req.type === 'delete') { + deleting.fire.call(ctx, key, existingValue, dxTrans); + } + else if (req.type === 'add' || existingValue === undefined) { + var generatedPrimaryKey = creating.fire.call(ctx, key, req.values[i], dxTrans); + if (key == null && generatedPrimaryKey != null) { + key = generatedPrimaryKey; + req.keys[i] = key; + if (!primaryKey.outbound) { + setByKeyPath(req.values[i], primaryKey.keyPath, key); + } + } + } + else { + var objectDiff = getObjectDiff(existingValue, req.values[i]); + var additionalChanges_1 = updating.fire.call(ctx, objectDiff, key, existingValue, dxTrans); + if (additionalChanges_1) { + var requestedValue_1 = req.values[i]; + Object.keys(additionalChanges_1).forEach(function (keyPath) { + if (hasOwn(requestedValue_1, keyPath)) { + requestedValue_1[keyPath] = additionalChanges_1[keyPath]; + } + else { + setByKeyPath(requestedValue_1, keyPath, additionalChanges_1[keyPath]); + } + }); + } } - catch (e) { - reject(e); + return ctx; + }); + return downTable.mutate(req).then(function (_a) { + var failures = _a.failures, results = _a.results, numFailures = _a.numFailures, lastResult = _a.lastResult; + for (var i = 0; i < keys.length; ++i) { + var primKey = results ? results[i] : keys[i]; + var ctx = contexts[i]; + if (primKey == null) { + ctx.onerror && ctx.onerror(failures[i]); + } + else { + ctx.onsuccess && ctx.onsuccess(req.type === 'put' && existingValues[i] ? + req.values[i] : + primKey + ); + } } + return { failures: failures, results: results, numFailures: numFailures, lastResult: lastResult }; + }).catch(function (error) { + contexts.forEach(function (ctx) { return ctx.onerror && ctx.onerror(error); }); + return Promise.reject(error); }); + }); } - else { - // Getting array through a cursor. - var a = []; - iter(ctx, function (item) { a.push(item); }, function arrayComplete() { - resolve(a); - }, reject, idbstore); + function deleteRange(req) { + return deleteNextChunk(req.trans, req.range, 10000); } - }, cb); - }, - offset: function (offset) { - var ctx = this._ctx; - if (offset <= 0) - return this; - ctx.offset += offset; // For count() - if (isPlainKeyRange(ctx)) { - addReplayFilter(ctx, function () { - var offsetLeft = offset; - return function (cursor, advance) { - if (offsetLeft === 0) - return true; - if (offsetLeft === 1) { - --offsetLeft; - return false; - } - advance(function () { - cursor.advance(offsetLeft); - offsetLeft = 0; + function deleteNextChunk(trans, range, limit) { + return downTable.query({ trans: trans, values: false, query: { index: primaryKey, range: range }, limit: limit }) + .then(function (_a) { + var result = _a.result; + return addPutOrDelete({ type: 'delete', keys: result, trans: trans }).then(function (res) { + if (res.numFailures > 0) + return Promise.reject(res.failures[0]); + if (result.length < limit) { + return { failures: [], numFailures: 0, lastResult: undefined }; + } + else { + return deleteNextChunk(trans, __assign(__assign({}, range), { lower: result[result.length - 1], lowerOpen: true }), limit); + } }); - return false; - }; - }); - } - else { - addReplayFilter(ctx, function () { - var offsetLeft = offset; - return function () { return (--offsetLeft < 0); }; - }); - } - return this; - }, - limit: function (numRows) { - this._ctx.limit = Math.min(this._ctx.limit, numRows); // For count() - addReplayFilter(this._ctx, function () { - var rowsLeft = numRows; - return function (cursor, advance, resolve) { - if (--rowsLeft <= 0) - advance(resolve); // Stop after this item has been included - return rowsLeft >= 0; // If numRows is already below 0, return false because then 0 was passed to numRows initially. Otherwise we wouldnt come here. - }; - }, true); - return this; - }, - until: function (filterFunction, bIncludeStopEntry) { - addFilter(this._ctx, function (cursor, advance, resolve) { - if (filterFunction(cursor.value)) { - advance(resolve); - return bIncludeStopEntry; - } - else { - return true; + }); } - }); - return this; - }, - first: function (cb) { - return this.limit(1).toArray(function (a) { return a[0]; }).then(cb); - }, - last: function (cb) { - return this.reverse().first(cb); - }, - filter: function (filterFunction) { - /// function(val){return true/false} - addFilter(this._ctx, function (cursor) { - return filterFunction(cursor.value); - }); - // match filters not used in Dexie.js but can be used by 3rd part libraries to test a - // collection for a match without querying DB. Used by Dexie.Observable. - addMatchFilter(this._ctx, filterFunction); - return this; - }, - and: function (filterFunction) { - return this.filter(filterFunction); - }, - or: function (indexName) { - return new WhereClause(this._ctx.table, indexName, this); - }, - reverse: function () { - this._ctx.dir = (this._ctx.dir === "prev" ? "next" : "prev"); - if (this._ondirectionchange) - this._ondirectionchange(this._ctx.dir); - return this; - }, - desc: function () { - return this.reverse(); - }, - eachKey: function (cb) { - var ctx = this._ctx; - ctx.keysOnly = !ctx.isMatch; - return this.each(function (val, cursor) { cb(cursor.key, cursor); }); - }, - eachUniqueKey: function (cb) { - this._ctx.unique = "unique"; - return this.eachKey(cb); - }, - eachPrimaryKey: function (cb) { - var ctx = this._ctx; - ctx.keysOnly = !ctx.isMatch; - return this.each(function (val, cursor) { cb(cursor.primaryKey, cursor); }); - }, - keys: function (cb) { - var ctx = this._ctx; - ctx.keysOnly = !ctx.isMatch; - var a = []; - return this.each(function (item, cursor) { - a.push(cursor.key); - }).then(function () { - return a; - }).then(cb); + } }); + return tableMiddleware; + } })); } +}; +function getExistingValues(table, req, effectiveKeys) { + return req.type === "add" + ? Promise.resolve([]) + : table.getMany({ trans: req.trans, keys: effectiveKeys, cache: "immutable" }); +} + +function getFromTransactionCache(keys, cache, clone) { + try { + if (!cache) + return null; + if (cache.keys.length < keys.length) + return null; + var result = []; + for (var i = 0, j = 0; i < cache.keys.length && j < keys.length; ++i) { + if (cmp(cache.keys[i], keys[j]) !== 0) + continue; + result.push(clone ? deepClone(cache.values[i]) : cache.values[i]); + ++j; + } + return result.length === keys.length ? result : null; + } + catch (_a) { + return null; + } +} +var cacheExistingValuesMiddleware = { + stack: "dbcore", + level: -1, + create: function (core) { + return { + table: function (tableName) { + var table = core.table(tableName); + return __assign(__assign({}, table), { getMany: function (req) { + if (!req.cache) { + return table.getMany(req); + } + var cachedResult = getFromTransactionCache(req.keys, req.trans["_cache"], req.cache === "clone"); + if (cachedResult) { + return DexiePromise.resolve(cachedResult); + } + return table.getMany(req).then(function (res) { + req.trans["_cache"] = { + keys: req.keys, + values: req.cache === "clone" ? deepClone(res) : res, + }; + return res; + }); + }, mutate: function (req) { + if (req.type !== "add") + req.trans["_cache"] = null; + return table.mutate(req); + } }); }, - primaryKeys: function (cb) { - var ctx = this._ctx; - if (hasGetAll && ctx.dir === 'next' && isPlainKeyRange(ctx, true) && ctx.limit > 0) { - // Special optimation if we could use IDBObjectStore.getAllKeys() or - // IDBKeyRange.getAllKeys(): - return this._read(function (resolve, reject, idbstore) { - var idxOrStore = getIndexOrStore(ctx, idbstore); - var req = ctx.limit < Infinity ? - idxOrStore.getAllKeys(ctx.range, ctx.limit) : - idxOrStore.getAllKeys(ctx.range); - req.onerror = eventRejectHandler(reject); - req.onsuccess = eventSuccessHandler(resolve); - }).then(cb); + }; + }, +}; + +function isCachableContext(ctx, table) { + return (ctx.trans.mode === 'readonly' && + !!ctx.subscr && + !ctx.trans.explicit && + ctx.trans.db._options.cache !== 'disabled' && + !table.schema.primaryKey.outbound); +} + +function isCachableRequest(type, req) { + switch (type) { + case 'query': + return req.values && !req.unique; + case 'get': + return false; + case 'getMany': + return false; + case 'count': + return false; + case 'openCursor': + return false; + } +} + +var observabilityMiddleware = { + stack: "dbcore", + level: 0, + name: "Observability", + create: function (core) { + var dbName = core.schema.name; + var FULL_RANGE = new RangeSet(core.MIN_KEY, core.MAX_KEY); + return __assign(__assign({}, core), { transaction: function (stores, mode, options) { + if (PSD.subscr && mode !== 'readonly') { + throw new exceptions.ReadOnly("Readwrite transaction in liveQuery context. Querier source: ".concat(PSD.querier)); } - ctx.keysOnly = !ctx.isMatch; - var a = []; - return this.each(function (item, cursor) { - a.push(cursor.primaryKey); - }).then(function () { - return a; - }).then(cb); - }, - uniqueKeys: function (cb) { - this._ctx.unique = "unique"; - return this.keys(cb); - }, - firstKey: function (cb) { - return this.limit(1).keys(function (a) { return a[0]; }).then(cb); - }, - lastKey: function (cb) { - return this.reverse().firstKey(cb); - }, - distinct: function () { - var ctx = this._ctx, idx = ctx.index && ctx.table.schema.idxByName[ctx.index]; - if (!idx || !idx.multi) - return this; // distinct() only makes differencies on multiEntry indexes. - var set = {}; - addFilter(this._ctx, function (cursor) { - var strKey = cursor.primaryKey.toString(); // Converts any Date to String, String to String, Number to String and Array to comma-separated string - var found = hasOwn(set, strKey); - set[strKey] = true; - return !found; - }); - return this; - }, - // - // Methods that mutate storage - // - modify: function (changes) { - var self = this, ctx = this._ctx, hook = ctx.table.hook, updatingHook = hook.updating.fire, deletingHook = hook.deleting.fire; - return this._write(function (resolve, reject, idbstore, trans) { - var modifyer; - if (typeof changes === 'function') { - // Changes is a function that may update, add or delete propterties or even require a deletion the object itself (delete this.item) - if (updatingHook === nop && deletingHook === nop) { - // Noone cares about what is being changed. Just let the modifier function be the given argument as is. - modifyer = changes; + return core.transaction(stores, mode, options); + }, table: function (tableName) { + var table = core.table(tableName); + var schema = table.schema; + var primaryKey = schema.primaryKey, indexes = schema.indexes; + var extractKey = primaryKey.extractKey, outbound = primaryKey.outbound; + var indexesWithAutoIncPK = primaryKey.autoIncrement && indexes.filter(function (index) { return index.compound && index.keyPath.includes(primaryKey.keyPath); }); + var tableClone = __assign(__assign({}, table), { mutate: function (req) { + var _a, _b; + var trans = req.trans; + var mutatedParts = req.mutatedParts || (req.mutatedParts = {}); + var getRangeSet = function (indexName) { + var part = "idb://".concat(dbName, "/").concat(tableName, "/").concat(indexName); + return (mutatedParts[part] || + (mutatedParts[part] = new RangeSet())); + }; + var pkRangeSet = getRangeSet(""); + var delsRangeSet = getRangeSet(":dels"); + var type = req.type; + var _c = req.type === "deleteRange" + ? [req.range] + : req.type === "delete" + ? [req.keys] + : req.values.length < 50 + ? [getEffectiveKeys(primaryKey, req).filter(function (id) { return id; }), req.values] + : [], keys = _c[0], newObjs = _c[1]; + var oldCache = req.trans["_cache"]; + if (isArray(keys)) { + pkRangeSet.addKeys(keys); + var oldObjs = type === 'delete' || keys.length === newObjs.length ? getFromTransactionCache(keys, oldCache) : null; + if (!oldObjs) { + delsRangeSet.addKeys(keys); + } + if (oldObjs || newObjs) { + trackAffectedIndexes(getRangeSet, schema, oldObjs, newObjs); + } + } + else if (keys) { + var range = { + from: (_a = keys.lower) !== null && _a !== void 0 ? _a : core.MIN_KEY, + to: (_b = keys.upper) !== null && _b !== void 0 ? _b : core.MAX_KEY + }; + delsRangeSet.add(range); + pkRangeSet.add(range); } else { - // People want to know exactly what is being modified or deleted. - // Let modifyer be a proxy function that finds out what changes the caller is actually doing - // and call the hooks accordingly! - modifyer = function (item) { - var origItem = deepClone(item); // Clone the item first so we can compare laters. - 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) - if (!hasOwn(this, "value")) { - // The real modifyer function requests a deletion of the object. Inform the deletingHook that a deletion is taking place. - deletingHook.call(this, this.primKey, item, trans); + pkRangeSet.add(FULL_RANGE); + delsRangeSet.add(FULL_RANGE); + schema.indexes.forEach(function (idx) { return getRangeSet(idx.name).add(FULL_RANGE); }); + } + return table.mutate(req).then(function (res) { + if (keys && (req.type === 'add' || req.type === 'put')) { + pkRangeSet.addKeys(res.results); + if (indexesWithAutoIncPK) { + indexesWithAutoIncPK.forEach(function (idx) { + var idxVals = req.values.map(function (v) { return idx.extractKey(v); }); + var pkPos = idx.keyPath.findIndex(function (prop) { return prop === primaryKey.keyPath; }); + for (var i = 0, len = res.results.length; i < len; ++i) { + idxVals[i][pkPos] = res.results[i]; + } + getRangeSet(idx.name).addKeys(idxVals); + }); + } + } + trans.mutatedParts = extendObservabilitySet(trans.mutatedParts || {}, mutatedParts); + return res; + }); + } }); + var getRange = function (_a) { + var _b, _c; + var _d = _a.query, index = _d.index, range = _d.range; + return [ + index, + new RangeSet((_b = range.lower) !== null && _b !== void 0 ? _b : core.MIN_KEY, (_c = range.upper) !== null && _c !== void 0 ? _c : core.MAX_KEY), + ]; + }; + var readSubscribers = { + get: function (req) { return [primaryKey, new RangeSet(req.key)]; }, + getMany: function (req) { return [primaryKey, new RangeSet().addKeys(req.keys)]; }, + count: getRange, + query: getRange, + openCursor: getRange, + }; + keys(readSubscribers).forEach(function (method) { + tableClone[method] = function (req) { + var subscr = PSD.subscr; + var isLiveQuery = !!subscr; + var cachable = isCachableContext(PSD, table) && isCachableRequest(method, req); + var obsSet = cachable + ? req.obsSet = {} + : subscr; + if (isLiveQuery) { + var getRangeSet = function (indexName) { + var part = "idb://".concat(dbName, "/").concat(tableName, "/").concat(indexName); + return (obsSet[part] || + (obsSet[part] = new RangeSet())); + }; + var pkRangeSet_1 = getRangeSet(""); + var delsRangeSet_1 = getRangeSet(":dels"); + var _a = readSubscribers[method](req), queriedIndex = _a[0], queriedRanges = _a[1]; + if (method === 'query' && queriedIndex.isPrimaryKey && !req.values) { + delsRangeSet_1.add(queriedRanges); + } + else { + getRangeSet(queriedIndex.name || "").add(queriedRanges); + } + if (!queriedIndex.isPrimaryKey) { + if (method === "count") { + delsRangeSet_1.add(FULL_RANGE); } else { - // No deletion. Check what was changed - var objectDiff = getObjectDiff(origItem, this.value); - var additionalChanges = updatingHook.call(this, objectDiff, this.primKey, origItem, trans); - if (additionalChanges) { - // Hook want to apply additional modifications. Make sure to fullfill the will of the hook. - item = this.value; - keys(additionalChanges).forEach(function (keyPath) { - setByKeyPath(item, keyPath, additionalChanges[keyPath]); // Adding {keyPath: undefined} means that the keyPath should be deleted. Handled by setByKeyPath - }); - } + var keysPromise_1 = method === "query" && + outbound && + req.values && + table.query(__assign(__assign({}, req), { values: false })); + return table[method].apply(this, arguments).then(function (res) { + if (method === "query") { + if (outbound && req.values) { + return keysPromise_1.then(function (_a) { + var resultingKeys = _a.result; + pkRangeSet_1.addKeys(resultingKeys); + return res; + }); + } + var pKeys = req.values + ? res.result.map(extractKey) + : res.result; + if (req.values) { + pkRangeSet_1.addKeys(pKeys); + } + else { + delsRangeSet_1.addKeys(pKeys); + } + } + else if (method === "openCursor") { + var cursor_1 = res; + var wantValues_1 = req.values; + return (cursor_1 && + Object.create(cursor_1, { + key: { + get: function () { + delsRangeSet_1.addKey(cursor_1.primaryKey); + return cursor_1.key; + }, + }, + primaryKey: { + get: function () { + var pkey = cursor_1.primaryKey; + delsRangeSet_1.addKey(pkey); + return pkey; + }, + }, + value: { + get: function () { + wantValues_1 && pkRangeSet_1.addKey(cursor_1.primaryKey); + return cursor_1.value; + }, + }, + })); + } + return res; + }); } - }; + } } - } - else if (updatingHook === nop) { - // changes is a set of {keyPath: value} and no one is listening to the updating hook. - var keyPaths = keys(changes); - var numKeys = keyPaths.length; - modifyer = function (item) { - var anythingModified = false; - for (var i = 0; i < numKeys; ++i) { - var keyPath = keyPaths[i], val = changes[keyPath]; - if (getByKeyPath(item, keyPath) !== val) { - setByKeyPath(item, keyPath, val); // Adding {keyPath: undefined} means that the keyPath should be deleted. Handled by setByKeyPath - anythingModified = true; + return table[method].apply(this, arguments); + }; + }); + return tableClone; + } }); + }, +}; +function trackAffectedIndexes(getRangeSet, schema, oldObjs, newObjs) { + function addAffectedIndex(ix) { + var rangeSet = getRangeSet(ix.name || ""); + function extractKey(obj) { + return obj != null ? ix.extractKey(obj) : null; + } + var addKeyOrKeys = function (key) { return ix.multiEntry && isArray(key) + ? key.forEach(function (key) { return rangeSet.addKey(key); }) + : rangeSet.addKey(key); }; + (oldObjs || newObjs).forEach(function (_, i) { + var oldKey = oldObjs && extractKey(oldObjs[i]); + var newKey = newObjs && extractKey(newObjs[i]); + if (cmp(oldKey, newKey) !== 0) { + if (oldKey != null) + addKeyOrKeys(oldKey); + if (newKey != null) + addKeyOrKeys(newKey); + } + }); + } + schema.indexes.forEach(addAffectedIndex); +} + +function adjustOptimisticFromFailures(tblCache, req, res) { + if (res.numFailures === 0) + return req; + if (req.type === 'deleteRange') { + return null; + } + var numBulkOps = req.keys + ? req.keys.length + : 'values' in req && req.values + ? req.values.length + : 1; + if (res.numFailures === numBulkOps) { + return null; + } + var clone = __assign({}, req); + if (isArray(clone.keys)) { + clone.keys = clone.keys.filter(function (_, i) { return !(i in res.failures); }); + } + if ('values' in clone && isArray(clone.values)) { + clone.values = clone.values.filter(function (_, i) { return !(i in res.failures); }); + } + return clone; +} + +function isAboveLower(key, range) { + return range.lower === undefined + ? true + : range.lowerOpen + ? cmp(key, range.lower) > 0 + : cmp(key, range.lower) >= 0; +} +function isBelowUpper(key, range) { + return range.upper === undefined + ? true + : range.upperOpen + ? cmp(key, range.upper) < 0 + : cmp(key, range.upper) <= 0; +} +function isWithinRange(key, range) { + return isAboveLower(key, range) && isBelowUpper(key, range); +} + +function applyOptimisticOps(result, req, ops, table, cacheEntry, immutable) { + if (!ops || ops.length === 0) + return result; + var index = req.query.index; + var multiEntry = index.multiEntry; + var queryRange = req.query.range; + var primaryKey = table.schema.primaryKey; + var extractPrimKey = primaryKey.extractKey; + var extractIndex = index.extractKey; + var extractLowLevelIndex = (index.lowLevelIndex || index).extractKey; + var finalResult = ops.reduce(function (result, op) { + var modifedResult = result; + var includedValues = []; + if (op.type === 'add' || op.type === 'put') { + var includedPKs = new RangeSet(); + for (var i = op.values.length - 1; i >= 0; --i) { + var value = op.values[i]; + var pk = extractPrimKey(value); + if (includedPKs.hasKey(pk)) + continue; + var key = extractIndex(value); + if (multiEntry && isArray(key) + ? key.some(function (k) { return isWithinRange(k, queryRange); }) + : isWithinRange(key, queryRange)) { + includedPKs.addKey(pk); + includedValues.push(value); + } + } + } + switch (op.type) { + case 'add': { + var existingKeys_1 = new RangeSet().addKeys(req.values ? result.map(function (v) { return extractPrimKey(v); }) : result); + modifedResult = result.concat(req.values + ? includedValues.filter(function (v) { + var key = extractPrimKey(v); + if (existingKeys_1.hasKey(key)) + return false; + existingKeys_1.addKey(key); + return true; + }) + : includedValues + .map(function (v) { return extractPrimKey(v); }) + .filter(function (k) { + if (existingKeys_1.hasKey(k)) + return false; + existingKeys_1.addKey(k); + return true; + })); + break; + } + case 'put': { + var keySet_1 = new RangeSet().addKeys(op.values.map(function (v) { return extractPrimKey(v); })); + modifedResult = result + .filter( + function (item) { return !keySet_1.hasKey(req.values ? extractPrimKey(item) : item); }) + .concat( + req.values + ? includedValues + : includedValues.map(function (v) { return extractPrimKey(v); })); + break; + } + case 'delete': + var keysToDelete_1 = new RangeSet().addKeys(op.keys); + modifedResult = result.filter(function (item) { + return !keysToDelete_1.hasKey(req.values ? extractPrimKey(item) : item); + }); + break; + case 'deleteRange': + var range_1 = op.range; + modifedResult = result.filter(function (item) { return !isWithinRange(extractPrimKey(item), range_1); }); + break; + } + return modifedResult; + }, result); + if (finalResult === result) + return result; + finalResult.sort(function (a, b) { + return cmp(extractLowLevelIndex(a), extractLowLevelIndex(b)) || + cmp(extractPrimKey(a), extractPrimKey(b)); + }); + if (req.limit && req.limit < Infinity) { + if (finalResult.length > req.limit) { + finalResult.length = req.limit; + } + else if (result.length === req.limit && finalResult.length < req.limit) { + cacheEntry.dirty = true; + } + } + return immutable ? Object.freeze(finalResult) : finalResult; +} + +function areRangesEqual(r1, r2) { + return (cmp(r1.lower, r2.lower) === 0 && + cmp(r1.upper, r2.upper) === 0 && + !!r1.lowerOpen === !!r2.lowerOpen && + !!r1.upperOpen === !!r2.upperOpen); +} + +function compareLowers(lower1, lower2, lowerOpen1, lowerOpen2) { + if (lower1 === undefined) + return lower2 !== undefined ? -1 : 0; + if (lower2 === undefined) + return 1; + var c = cmp(lower1, lower2); + if (c === 0) { + if (lowerOpen1 && lowerOpen2) + return 0; + if (lowerOpen1) + return 1; + if (lowerOpen2) + return -1; + } + return c; +} +function compareUppers(upper1, upper2, upperOpen1, upperOpen2) { + if (upper1 === undefined) + return upper2 !== undefined ? 1 : 0; + if (upper2 === undefined) + return -1; + var c = cmp(upper1, upper2); + if (c === 0) { + if (upperOpen1 && upperOpen2) + return 0; + if (upperOpen1) + return -1; + if (upperOpen2) + return 1; + } + return c; +} +function isSuperRange(r1, r2) { + return (compareLowers(r1.lower, r2.lower, r1.lowerOpen, r2.lowerOpen) <= 0 && + compareUppers(r1.upper, r2.upper, r1.upperOpen, r2.upperOpen) >= 0); +} + +function findCompatibleQuery(dbName, tableName, type, req) { + var tblCache = cache["idb://".concat(dbName, "/").concat(tableName)]; + if (!tblCache) + return []; + var queries = tblCache.queries[type]; + if (!queries) + return [null, false, tblCache, null]; + var indexName = req.query ? req.query.index.name : null; + var entries = queries[indexName || '']; + if (!entries) + return [null, false, tblCache, null]; + switch (type) { + case 'query': + var equalEntry = entries.find(function (entry) { + return entry.req.limit === req.limit && + entry.req.values === req.values && + areRangesEqual(entry.req.query.range, req.query.range); + }); + if (equalEntry) + return [ + equalEntry, + true, + tblCache, + entries, + ]; + var superEntry = entries.find(function (entry) { + var limit = 'limit' in entry.req ? entry.req.limit : Infinity; + return (limit >= req.limit && + (req.values ? entry.req.values : true) && + isSuperRange(entry.req.query.range, req.query.range)); + }); + return [superEntry, false, tblCache, entries]; + case 'count': + var countQuery = entries.find(function (entry) { + return areRangesEqual(entry.req.query.range, req.query.range); + }); + return [countQuery, !!countQuery, tblCache, entries]; + } +} + +function subscribeToCacheEntry(cacheEntry, container, requery, signal) { + cacheEntry.subscribers.add(requery); + signal.addEventListener("abort", function () { + cacheEntry.subscribers.delete(requery); + if (cacheEntry.subscribers.size === 0) { + enqueForDeletion(cacheEntry, container); + } + }); +} +function enqueForDeletion(cacheEntry, container) { + setTimeout(function () { + if (cacheEntry.subscribers.size === 0) { + delArrayItem(container, cacheEntry); + } + }, 3000); +} + +var cacheMiddleware = { + stack: 'dbcore', + level: 0, + name: 'Cache', + create: function (core) { + var dbName = core.schema.name; + var coreMW = __assign(__assign({}, core), { transaction: function (stores, mode, options) { + var idbtrans = core.transaction(stores, mode, options); + if (mode === 'readwrite') { + var ac_1 = new AbortController(); + var signal = ac_1.signal; + var endTransaction = function (wasCommitted) { return function () { + ac_1.abort(); + if (mode === 'readwrite') { + var affectedSubscribers_1 = new Set(); + for (var _i = 0, stores_1 = stores; _i < stores_1.length; _i++) { + var storeName = stores_1[_i]; + var tblCache = cache["idb://".concat(dbName, "/").concat(storeName)]; + if (tblCache) { + var table = core.table(storeName); + var ops = tblCache.optimisticOps.filter(function (op) { return op.trans === idbtrans; }); + if (idbtrans._explicit && wasCommitted && idbtrans.mutatedParts) { + for (var _a = 0, _b = Object.values(tblCache.queries.query); _a < _b.length; _a++) { + var entries = _b[_a]; + for (var _c = 0, _d = entries.slice(); _c < _d.length; _c++) { + var entry = _d[_c]; + if (obsSetsOverlap(entry.obsSet, idbtrans.mutatedParts)) { + delArrayItem(entries, entry); + entry.subscribers.forEach(function (requery) { return affectedSubscribers_1.add(requery); }); + } + } + } + } + else if (ops.length > 0) { + tblCache.optimisticOps = tblCache.optimisticOps.filter(function (op) { return op.trans !== idbtrans; }); + for (var _e = 0, _f = Object.values(tblCache.queries.query); _e < _f.length; _e++) { + var entries = _f[_e]; + for (var _g = 0, _h = entries.slice(); _g < _h.length; _g++) { + var entry = _h[_g]; + if (entry.res != null && + idbtrans.mutatedParts +) { + if (wasCommitted && !entry.dirty) { + var freezeResults = Object.isFrozen(entry.res); + var modRes = applyOptimisticOps(entry.res, entry.req, ops, table, entry, freezeResults); + if (entry.dirty) { + delArrayItem(entries, entry); + entry.subscribers.forEach(function (requery) { return affectedSubscribers_1.add(requery); }); + } + else if (modRes !== entry.res) { + entry.res = modRes; + entry.promise = DexiePromise.resolve({ result: modRes }); + } + } + else { + if (entry.dirty) { + delArrayItem(entries, entry); + } + entry.subscribers.forEach(function (requery) { return affectedSubscribers_1.add(requery); }); + } + } + } + } + } } } - return anythingModified; - }; - } - else { - // changes is a set of {keyPath: value} and people are listening to the updating hook so we need to call it and - // allow it to add additional modifications to make. - var origChanges = changes; - 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. - modifyer = function (item) { - var anythingModified = false; - var additionalChanges = updatingHook.call(this, changes, this.primKey, deepClone(item), trans); - if (additionalChanges) - extend(changes, additionalChanges); - keys(changes).forEach(function (keyPath) { - var val = changes[keyPath]; - if (getByKeyPath(item, keyPath) !== val) { - setByKeyPath(item, keyPath, val); - anythingModified = true; - } - }); - if (additionalChanges) - changes = shallowClone(origChanges); // Restore original changes for next iteration - return anythingModified; - }; - } - var count = 0; - var successCount = 0; - var iterationComplete = false; - var failures = []; - var failKeys = []; - var currentKey = null; - function modifyItem(item, cursor) { - currentKey = cursor.primaryKey; - var thisContext = { - primKey: cursor.primaryKey, - value: item, - onsuccess: null, - onerror: null - }; - function onerror(e) { - failures.push(e); - failKeys.push(thisContext.primKey); - checkFinished(); - return true; // Catch these errors and let a final rejection decide whether or not to abort entire transaction + affectedSubscribers_1.forEach(function (requery) { return requery(); }); + } + }; }; + idbtrans.addEventListener('abort', endTransaction(false), { + signal: signal, + }); + idbtrans.addEventListener('error', endTransaction(false), { + signal: signal, + }); + idbtrans.addEventListener('complete', endTransaction(true), { + signal: signal, + }); + } + return idbtrans; + }, table: function (tableName) { + var downTable = core.table(tableName); + var primKey = downTable.schema.primaryKey; + var tableMW = __assign(__assign({}, downTable), { mutate: function (req) { + var trans = PSD.trans; + if (primKey.outbound || + trans.db._options.cache === 'disabled' || + trans.explicit || + trans.idbtrans.mode !== 'readwrite' + ) { + return downTable.mutate(req); } - if (modifyer.call(thisContext, item, thisContext) !== false) { - var bDelete = !hasOwn(thisContext, "value"); - ++count; - tryCatch(function () { - var req = (bDelete ? cursor.delete() : cursor.update(thisContext.value)); - req._hookCtx = thisContext; - req.onerror = hookedEventRejectHandler(onerror); - req.onsuccess = hookedEventSuccessHandler(function () { - ++successCount; - checkFinished(); - }); - }, onerror); + var tblCache = cache["idb://".concat(dbName, "/").concat(tableName)]; + if (!tblCache) + return downTable.mutate(req); + var promise = downTable.mutate(req); + if ((req.type === 'add' || req.type === 'put') && (req.values.length >= 50 || getEffectiveKeys(primKey, req).some(function (key) { return key == null; }))) { + promise.then(function (res) { + var reqWithResolvedKeys = __assign(__assign({}, req), { values: req.values.map(function (value, i) { + var _a; + if (res.failures[i]) + return value; + var valueWithKey = ((_a = primKey.keyPath) === null || _a === void 0 ? void 0 : _a.includes('.')) + ? deepClone(value) + : __assign({}, value); + setByKeyPath(valueWithKey, primKey.keyPath, res.results[i]); + return valueWithKey; + }) }); + var adjustedReq = adjustOptimisticFromFailures(tblCache, reqWithResolvedKeys, res); + tblCache.optimisticOps.push(adjustedReq); + queueMicrotask(function () { return req.mutatedParts && signalSubscribersLazily(req.mutatedParts); }); + }); } - else if (thisContext.onsuccess) { - // Hook will expect either onerror or onsuccess to always be called! - thisContext.onsuccess(thisContext.value); + else { + tblCache.optimisticOps.push(req); + req.mutatedParts && signalSubscribersLazily(req.mutatedParts); + promise.then(function (res) { + if (res.numFailures > 0) { + delArrayItem(tblCache.optimisticOps, req); + var adjustedReq = adjustOptimisticFromFailures(tblCache, req, res); + if (adjustedReq) { + tblCache.optimisticOps.push(adjustedReq); + } + req.mutatedParts && signalSubscribersLazily(req.mutatedParts); + } + }); + promise.catch(function () { + delArrayItem(tblCache.optimisticOps, req); + req.mutatedParts && signalSubscribersLazily(req.mutatedParts); + }); } - } - function doReject(e) { - if (e) { - failures.push(e); - failKeys.push(currentKey); + return promise; + }, query: function (req) { + var _a; + if (!isCachableContext(PSD, downTable) || !isCachableRequest("query", req)) + return downTable.query(req); + var freezeResults = ((_a = PSD.trans) === null || _a === void 0 ? void 0 : _a.db._options.cache) === 'immutable'; + var _b = PSD, requery = _b.requery, signal = _b.signal; + var _c = findCompatibleQuery(dbName, tableName, 'query', req), cacheEntry = _c[0], exactMatch = _c[1], tblCache = _c[2], container = _c[3]; + if (cacheEntry && exactMatch) { + cacheEntry.obsSet = req.obsSet; } - return reject(new ModifyError("Error modifying one or more objects", failures, successCount, failKeys)); - } - function checkFinished() { - if (iterationComplete && successCount + failures.length === count) { - if (failures.length > 0) - doReject(); - else - resolve(successCount); + else { + var promise = downTable.query(req).then(function (res) { + var result = res.result; + if (cacheEntry) + cacheEntry.res = result; + if (freezeResults) { + for (var i = 0, l = result.length; i < l; ++i) { + Object.freeze(result[i]); + } + Object.freeze(result); + } + else { + res.result = deepClone(result); + } + return res; + }).catch(function (error) { + if (container && cacheEntry) + delArrayItem(container, cacheEntry); + return Promise.reject(error); + }); + cacheEntry = { + obsSet: req.obsSet, + promise: promise, + subscribers: new Set(), + type: 'query', + req: req, + dirty: false, + }; + if (container) { + container.push(cacheEntry); + } + else { + container = [cacheEntry]; + if (!tblCache) { + tblCache = cache["idb://".concat(dbName, "/").concat(tableName)] = { + queries: { + query: {}, + count: {}, + }, + objs: new Map(), + optimisticOps: [], + unsignaledParts: {} + }; + } + tblCache.queries.query[req.query.index.name || ''] = container; + } } - } - self.clone().raw()._iterate(modifyItem, function () { - iterationComplete = true; - checkFinished(); - }, doReject, idbstore); - }); - }, - 'delete': function () { - var _this = this; - var ctx = this._ctx, range = ctx.range, deletingHook = ctx.table.hook.deleting.fire, hasDeleteHook = deletingHook !== nop; - if (!hasDeleteHook && - isPlainKeyRange(ctx) && - ((ctx.isPrimKey && !hangsOnDeleteLargeKeyRange) || !range)) { - // May use IDBObjectStore.delete(IDBKeyRange) in this case (Issue #208) - // For chromium, this is the way most optimized version. - // For IE/Edge, this could hang the indexedDB engine and make operating system instable - // (https://gist.github.com/dfahlander/5a39328f029de18222cf2125d56c38f7) - return this._write(function (resolve, reject, idbstore) { - // Our API contract is to return a count of deleted items, so we have to count() before delete(). - var onerror = eventRejectHandler(reject), countReq = (range ? idbstore.count(range) : idbstore.count()); - countReq.onerror = onerror; - countReq.onsuccess = function () { - var count = countReq.result; - tryCatch(function () { - var delReq = (range ? idbstore.delete(range) : idbstore.clear()); - delReq.onerror = onerror; - delReq.onsuccess = function () { return resolve(count); }; - }, function (err) { return reject(err); }); - }; - }); + subscribeToCacheEntry(cacheEntry, container, requery, signal); + return cacheEntry.promise.then(function (res) { + return { + result: applyOptimisticOps(res.result, req, tblCache === null || tblCache === void 0 ? void 0 : tblCache.optimisticOps, downTable, cacheEntry, freezeResults), + }; + }); + } }); + return tableMW; + } }); + return coreMW; + }, +}; + +function vipify(target, vipDb) { + return new Proxy(target, { + get: function (target, prop, receiver) { + if (prop === 'db') + return vipDb; + return Reflect.get(target, prop, receiver); + } + }); +} + +var Dexie$1 = (function () { + function Dexie(name, options) { + var _this = this; + this._middlewares = {}; + this.verno = 0; + var deps = Dexie.dependencies; + this._options = options = __assign({ + addons: Dexie.addons, autoOpen: true, + indexedDB: deps.indexedDB, IDBKeyRange: deps.IDBKeyRange, cache: 'cloned' }, options); + this._deps = { + indexedDB: options.indexedDB, + IDBKeyRange: options.IDBKeyRange + }; + var addons = options.addons; + this._dbSchema = {}; + this._versions = []; + this._storeNames = []; + this._allTables = {}; + this.idbdb = null; + this._novip = this; + var state = { + dbOpenError: null, + isBeingOpened: false, + onReadyBeingFired: null, + openComplete: false, + dbReadyResolve: nop, + dbReadyPromise: null, + cancelOpen: nop, + openCanceller: null, + autoSchema: true, + PR1398_maxLoop: 3, + autoOpen: options.autoOpen, + }; + state.dbReadyPromise = new DexiePromise(function (resolve) { + state.dbReadyResolve = resolve; + }); + state.openCanceller = new DexiePromise(function (_, reject) { + state.cancelOpen = reject; + }); + this._state = state; + this.name = name; + this.on = Events(this, "populate", "blocked", "versionchange", "close", { ready: [promisableChain, nop] }); + this.once = function (event, callback) { + var fn = function () { + var args = []; + for (var _i = 0; _i < arguments.length; _i++) { + args[_i] = arguments[_i]; } - // Default version to use when collection is not a vanilla IDBKeyRange on the primary key. - // Divide into chunks to not starve RAM. - // If has delete hook, we will have to collect not just keys but also objects, so it will use - // more memory and need lower chunk size. - var CHUNKSIZE = hasDeleteHook ? 2000 : 10000; - return this._write(function (resolve, reject, idbstore, trans) { - var totalCount = 0; - // Clone collection and change its table and set a limit of CHUNKSIZE on the cloned Collection instance. - var collection = _this - .clone({ - keysOnly: !ctx.isMatch && !hasDeleteHook - }) // load just keys (unless filter() or and() or deleteHook has subscribers) - .distinct() // In case multiEntry is used, never delete same key twice because resulting count - .limit(CHUNKSIZE) - .raw(); // Don't filter through reading-hooks (like mapped classes etc) - var keysOrTuples = []; - // We're gonna do things on as many chunks that are needed. - // Use recursion of nextChunk function: - var nextChunk = function () { return collection.each(hasDeleteHook ? function (val, cursor) { - // Somebody subscribes to hook('deleting'). Collect all primary keys and their values, - // so that the hook can be called with its values in bulkDelete(). - keysOrTuples.push([cursor.primaryKey, cursor.value]); - } : function (val, cursor) { - // No one subscribes to hook('deleting'). Collect only primary keys: - keysOrTuples.push(cursor.primaryKey); - }).then(function () { - // Chromium deletes faster when doing it in sort order. - hasDeleteHook ? - keysOrTuples.sort(function (a, b) { return ascending(a[0], b[0]); }) : - keysOrTuples.sort(ascending); - return bulkDelete(idbstore, trans, keysOrTuples, hasDeleteHook, deletingHook); - }).then(function () { - var count = keysOrTuples.length; - totalCount += count; - keysOrTuples = []; - return count < CHUNKSIZE ? totalCount : nextChunk(); - }); }; - resolve(nextChunk()); - }); - } + _this.on(event).unsubscribe(fn); + callback.apply(_this, args); + }; + return _this.on(event, fn); }; - }); - // - // - // - // ------------------------- Help functions --------------------------- - // - // - // - function lowerVersionFirst(a, b) { - return a._cfg.version - b._cfg.version; - } - function setApiOnPlace(objs, tableNames, dbschema) { - tableNames.forEach(function (tableName) { - var schema = dbschema[tableName]; - objs.forEach(function (obj) { - if (!(tableName in obj)) { - if (obj === Transaction.prototype || obj instanceof Transaction) { - // obj is a Transaction prototype (or prototype of a subclass to Transaction) - // Make the API a getter that returns this.table(tableName) - setProp(obj, tableName, { get: function () { return this.table(tableName); } }); + this.on.ready.subscribe = override(this.on.ready.subscribe, function (subscribe) { + return function (subscriber, bSticky) { + Dexie.vip(function () { + var state = _this._state; + if (state.openComplete) { + if (!state.dbOpenError) + DexiePromise.resolve().then(subscriber); + if (bSticky) + subscribe(subscriber); + } + else if (state.onReadyBeingFired) { + state.onReadyBeingFired.push(subscriber); + if (bSticky) + subscribe(subscriber); } else { - // Table will not be bound to a transaction (will use Dexie.currentTransaction) - obj[tableName] = new Table(tableName, schema); + subscribe(subscriber); + var db_1 = _this; + if (!bSticky) + subscribe(function unsubscribe() { + db_1.on.ready.unsubscribe(subscriber); + db_1.on.ready.unsubscribe(unsubscribe); + }); } - } - }); + }); + }; }); - } - function removeTablesApi(objs) { - objs.forEach(function (obj) { - for (var key in obj) { - if (obj[key] instanceof Table) - delete obj[key]; + this.Collection = createCollectionConstructor(this); + this.Table = createTableConstructor(this); + this.Transaction = createTransactionConstructor(this); + this.Version = createVersionConstructor(this); + this.WhereClause = createWhereClauseConstructor(this); + this.on("versionchange", function (ev) { + if (ev.newVersion > 0) + console.warn("Another connection wants to upgrade database '".concat(_this.name, "'. Closing db now to resume the upgrade.")); + else + console.warn("Another connection wants to delete database '".concat(_this.name, "'. Closing db now to resume the delete request.")); + _this.close({ disableAutoOpen: false }); + }); + this.on("blocked", function (ev) { + if (!ev.newVersion || ev.newVersion < ev.oldVersion) + console.warn("Dexie.delete('".concat(_this.name, "') was blocked")); + else + console.warn("Upgrade '".concat(_this.name, "' blocked by other connection holding version ").concat(ev.oldVersion / 10)); + }); + this._maxKey = getMaxKey(options.IDBKeyRange); + this._createTransaction = function (mode, storeNames, dbschema, parentTransaction) { return new _this.Transaction(mode, storeNames, dbschema, _this._options.chromeTransactionDurability, parentTransaction); }; + this._fireOnBlocked = function (ev) { + _this.on("blocked").fire(ev); + connections + .filter(function (c) { return c.name === _this.name && c !== _this && !c._state.vcFired; }) + .map(function (c) { return c.on("versionchange").fire(ev); }); + }; + this.use(cacheExistingValuesMiddleware); + this.use(cacheMiddleware); + this.use(observabilityMiddleware); + this.use(virtualIndexMiddleware); + this.use(hooksMiddleware); + var vipDB = new Proxy(this, { + get: function (_, prop, receiver) { + if (prop === '_vip') + return true; + if (prop === 'table') + return function (tableName) { return vipify(_this.table(tableName), vipDB); }; + var rv = Reflect.get(_, prop, receiver); + if (rv instanceof Table) + return vipify(rv, vipDB); + if (prop === 'tables') + return rv.map(function (t) { return vipify(t, vipDB); }); + if (prop === '_createTransaction') + return function () { + var tx = rv.apply(this, arguments); + return vipify(tx, vipDB); + }; + return rv; } }); + this.vip = vipDB; + addons.forEach(function (addon) { return addon(_this); }); } - function iterate(req, filter, fn, resolve, reject, valueMapper) { - // Apply valueMapper (hook('reading') or mappped class) - var mappedFn = valueMapper ? function (x, c, a) { return fn(valueMapper(x), c, a); } : fn; - // Wrap fn with PSD and microtick stuff from Promise. - var wrappedFn = wrap(mappedFn, reject); - if (!req.onerror) - req.onerror = eventRejectHandler(reject); - if (filter) { - req.onsuccess = trycatcher(function filter_record() { - var cursor = req.result; - if (cursor) { - var c = function () { cursor.continue(); }; - if (filter(cursor, function (advancer) { c = advancer; }, resolve, reject)) - wrappedFn(cursor.value, cursor, function (advancer) { c = advancer; }); - c(); - } - else { - resolve(); + Dexie.prototype.version = function (versionNumber) { + if (isNaN(versionNumber) || versionNumber < 0.1) + throw new exceptions.Type("Given version is not a positive number"); + versionNumber = Math.round(versionNumber * 10) / 10; + if (this.idbdb || this._state.isBeingOpened) + throw new exceptions.Schema("Cannot add version when database is open"); + this.verno = Math.max(this.verno, versionNumber); + var versions = this._versions; + var versionInstance = versions.filter(function (v) { return v._cfg.version === versionNumber; })[0]; + if (versionInstance) + return versionInstance; + versionInstance = new this.Version(versionNumber); + versions.push(versionInstance); + versions.sort(lowerVersionFirst); + versionInstance.stores({}); + this._state.autoSchema = false; + return versionInstance; + }; + Dexie.prototype._whenReady = function (fn) { + var _this = this; + return (this.idbdb && (this._state.openComplete || PSD.letThrough || this._vip)) ? fn() : new DexiePromise(function (resolve, reject) { + if (_this._state.openComplete) { + return reject(new exceptions.DatabaseClosed(_this._state.dbOpenError)); + } + if (!_this._state.isBeingOpened) { + if (!_this._state.autoOpen) { + reject(new exceptions.DatabaseClosed()); + return; } - }, reject); + _this.open().catch(nop); + } + _this._state.dbReadyPromise.then(resolve, reject); + }).then(fn); + }; + Dexie.prototype.use = function (_a) { + var stack = _a.stack, create = _a.create, level = _a.level, name = _a.name; + if (name) + this.unuse({ stack: stack, name: name }); + var middlewares = this._middlewares[stack] || (this._middlewares[stack] = []); + middlewares.push({ stack: stack, create: create, level: level == null ? 10 : level, name: name }); + middlewares.sort(function (a, b) { return a.level - b.level; }); + return this; + }; + Dexie.prototype.unuse = function (_a) { + var stack = _a.stack, name = _a.name, create = _a.create; + if (stack && this._middlewares[stack]) { + this._middlewares[stack] = this._middlewares[stack].filter(function (mw) { + return create ? mw.create !== create : + name ? mw.name !== name : + false; + }); + } + return this; + }; + Dexie.prototype.open = function () { + var _this = this; + return usePSD(globalPSD, + function () { return dexieOpen(_this); }); + }; + Dexie.prototype._close = function () { + this.on.close.fire(new CustomEvent('close')); + var state = this._state; + var idx = connections.indexOf(this); + if (idx >= 0) + connections.splice(idx, 1); + if (this.idbdb) { + try { + this.idbdb.close(); + } + catch (e) { } + this.idbdb = null; + } + if (!state.isBeingOpened) { + state.dbReadyPromise = new DexiePromise(function (resolve) { + state.dbReadyResolve = resolve; + }); + state.openCanceller = new DexiePromise(function (_, reject) { + state.cancelOpen = reject; + }); + } + }; + Dexie.prototype.close = function (_a) { + var _b = _a === void 0 ? { disableAutoOpen: true } : _a, disableAutoOpen = _b.disableAutoOpen; + var state = this._state; + if (disableAutoOpen) { + if (state.isBeingOpened) { + state.cancelOpen(new exceptions.DatabaseClosed()); + } + this._close(); + state.autoOpen = false; + state.dbOpenError = new exceptions.DatabaseClosed(); } else { - req.onsuccess = trycatcher(function filter_record() { - var cursor = req.result; - if (cursor) { - var c = function () { cursor.continue(); }; - wrappedFn(cursor.value, cursor, function (advancer) { c = advancer; }); - c(); - } - else { - resolve(); - } - }, reject); + this._close(); + state.autoOpen = this._options.autoOpen || + state.isBeingOpened; + state.openComplete = false; + state.dbOpenError = null; } - } - function parseIndexSyntax(indexes) { - /// - /// - var rv = []; - indexes.split(',').forEach(function (index) { - index = index.trim(); - var name = index.replace(/([&*]|\+\+)/g, ""); // Remove "&", "++" and "*" - // Let keyPath of "[a+b]" be ["a","b"]: - var keyPath = /^\[/.test(name) ? name.match(/^\[(.*)\]$/)[1].split('+') : name; - rv.push(new IndexSpec(name, keyPath || null, /\&/.test(index), /\*/.test(index), /\+\+/.test(index), isArray(keyPath), /\./.test(index))); - }); - return rv; - } - function cmp(key1, key2) { - return indexedDB.cmp(key1, key2); - } - function min(a, b) { - return cmp(a, b) < 0 ? a : b; - } - function max(a, b) { - return cmp(a, b) > 0 ? a : b; - } - function ascending(a, b) { - return indexedDB.cmp(a, b); - } - function descending(a, b) { - return indexedDB.cmp(b, a); - } - function simpleCompare(a, b) { - return a < b ? -1 : a === b ? 0 : 1; - } - function simpleCompareReverse(a, b) { - return a > b ? -1 : a === b ? 0 : 1; - } - function combine(filter1, filter2) { - return filter1 ? - filter2 ? - function () { return filter1.apply(this, arguments) && filter2.apply(this, arguments); } : - filter1 : - filter2; - } - function readGlobalSchema() { - db.verno = idbdb.version / 10; - db._dbSchema = globalSchema = {}; - dbStoreNames = slice(idbdb.objectStoreNames, 0); - if (dbStoreNames.length === 0) - return; // Database contains no stores. - var trans = idbdb.transaction(safariMultiStoreFix(dbStoreNames), 'readonly'); - dbStoreNames.forEach(function (storeName) { - var store = trans.objectStore(storeName), keyPath = store.keyPath, dotted = keyPath && typeof keyPath === 'string' && keyPath.indexOf('.') !== -1; - var primKey = new IndexSpec(keyPath, keyPath || "", false, false, !!store.autoIncrement, keyPath && typeof keyPath !== 'string', dotted); - var indexes = []; - for (var j = 0; j < store.indexNames.length; ++j) { - var idbindex = store.index(store.indexNames[j]); - keyPath = idbindex.keyPath; - dotted = keyPath && typeof keyPath === 'string' && keyPath.indexOf('.') !== -1; - var index = new IndexSpec(idbindex.name, keyPath, !!idbindex.unique, !!idbindex.multiEntry, false, keyPath && typeof keyPath !== 'string', dotted); - indexes.push(index); + }; + Dexie.prototype.delete = function (closeOptions) { + var _this = this; + if (closeOptions === void 0) { closeOptions = { disableAutoOpen: true }; } + var hasInvalidArguments = arguments.length > 0 && typeof arguments[0] !== 'object'; + var state = this._state; + return new DexiePromise(function (resolve, reject) { + var doDelete = function () { + _this.close(closeOptions); + var req = _this._deps.indexedDB.deleteDatabase(_this.name); + req.onsuccess = wrap(function () { + _onDatabaseDeleted(_this._deps, _this.name); + resolve(); + }); + req.onerror = eventRejectHandler(reject); + req.onblocked = _this._fireOnBlocked; + }; + if (hasInvalidArguments) + throw new exceptions.InvalidArgument("Invalid closeOptions argument to db.delete()"); + if (state.isBeingOpened) { + state.dbReadyPromise.then(doDelete); + } + else { + doDelete(); } - globalSchema[storeName] = new TableSchema(storeName, primKey, indexes, {}); }); - setApiOnPlace([allTables], keys(globalSchema), globalSchema); - } - function adjustToExistingIndexNames(schema, idbtrans) { - /// - /// Issue #30 Problem with existing db - adjust to existing index names when migrating from non-dexie db - /// - /// Map between name and TableSchema - /// - var storeNames = idbtrans.db.objectStoreNames; - for (var i = 0; i < storeNames.length; ++i) { - var storeName = storeNames[i]; - var store = idbtrans.objectStore(storeName); - hasGetAll = 'getAll' in store; - for (var j = 0; j < store.indexNames.length; ++j) { - var indexName = store.indexNames[j]; - var keyPath = store.index(indexName).keyPath; - var dexieName = typeof keyPath === 'string' ? keyPath : "[" + slice(keyPath).join('+') + "]"; - if (schema[storeName]) { - var indexSpec = schema[storeName].idxByName[dexieName]; - if (indexSpec) - indexSpec.name = indexName; + }; + Dexie.prototype.backendDB = function () { + return this.idbdb; + }; + Dexie.prototype.isOpen = function () { + return this.idbdb !== null; + }; + Dexie.prototype.hasBeenClosed = function () { + var dbOpenError = this._state.dbOpenError; + return dbOpenError && (dbOpenError.name === 'DatabaseClosed'); + }; + Dexie.prototype.hasFailed = function () { + return this._state.dbOpenError !== null; + }; + Dexie.prototype.dynamicallyOpened = function () { + return this._state.autoSchema; + }; + Object.defineProperty(Dexie.prototype, "tables", { + get: function () { + var _this = this; + return keys(this._allTables).map(function (name) { return _this._allTables[name]; }); + }, + enumerable: false, + configurable: true + }); + Dexie.prototype.transaction = function () { + var args = extractTransactionArgs.apply(this, arguments); + return this._transaction.apply(this, args); + }; + Dexie.prototype._transaction = function (mode, tables, scopeFunc) { + var _this = this; + var parentTransaction = PSD.trans; + if (!parentTransaction || parentTransaction.db !== this || mode.indexOf('!') !== -1) + parentTransaction = null; + var onlyIfCompatible = mode.indexOf('?') !== -1; + mode = mode.replace('!', '').replace('?', ''); + var idbMode, storeNames; + try { + storeNames = tables.map(function (table) { + var storeName = table instanceof _this.Table ? table.name : table; + if (typeof storeName !== 'string') + throw new TypeError("Invalid table argument to Dexie.transaction(). Only Table or String are allowed"); + return storeName; + }); + if (mode == "r" || mode === READONLY) + idbMode = READONLY; + else if (mode == "rw" || mode == READWRITE) + idbMode = READWRITE; + else + throw new exceptions.InvalidArgument("Invalid transaction mode: " + mode); + if (parentTransaction) { + if (parentTransaction.mode === READONLY && idbMode === READWRITE) { + if (onlyIfCompatible) { + parentTransaction = null; + } + else + throw new exceptions.SubTransaction("Cannot enter a sub-transaction with READWRITE mode when parent transaction is READONLY"); + } + if (parentTransaction) { + storeNames.forEach(function (storeName) { + if (parentTransaction && parentTransaction.storeNames.indexOf(storeName) === -1) { + if (onlyIfCompatible) { + parentTransaction = null; + } + else + throw new exceptions.SubTransaction("Table " + storeName + + " not included in parent transaction."); + } + }); + } + if (onlyIfCompatible && parentTransaction && !parentTransaction.active) { + parentTransaction = null; } } } - // Bug with getAll() on Safari ver<604 on Workers only, see discussion following PR #579 - if (/Safari/.test(navigator.userAgent) && - !/(Chrome\/|Edge\/)/.test(navigator.userAgent) && - _global.WorkerGlobalScope && _global instanceof _global.WorkerGlobalScope && - [].concat(navigator.userAgent.match(/Safari\/(\d*)/))[1] < 604) { - hasGetAll = false; + catch (e) { + return parentTransaction ? + parentTransaction._promise(null, function (_, reject) { reject(e); }) : + rejection(e); } + var enterTransaction = enterTransactionScope.bind(null, this, idbMode, storeNames, parentTransaction, scopeFunc); + return (parentTransaction ? + parentTransaction._promise(idbMode, enterTransaction, "lock") : + PSD.trans ? + usePSD(PSD.transless, function () { return _this._whenReady(enterTransaction); }) : + this._whenReady(enterTransaction)); + }; + Dexie.prototype.table = function (tableName) { + if (!hasOwn(this._allTables, tableName)) { + throw new exceptions.InvalidTable("Table ".concat(tableName, " does not exist")); + } + return this._allTables[tableName]; + }; + return Dexie; +}()); + +var symbolObservable = typeof Symbol !== "undefined" && "observable" in Symbol + ? Symbol.observable + : "@@observable"; +var Observable = (function () { + function Observable(subscribe) { + this._subscribe = subscribe; } - function fireOnBlocked(ev) { - db.on("blocked").fire(ev); - // Workaround (not fully*) for missing "versionchange" event in IE,Edge and Safari: - connections - .filter(function (c) { return c.name === db.name && c !== db && !c._vcFired; }) - .map(function (c) { return c.on("versionchange").fire(ev); }); - } - extend(this, { - Collection: Collection, - Table: Table, - Transaction: Transaction, - Version: Version, - WhereClause: WhereClause - }); - init(); - addons.forEach(function (fn) { - fn(db); - }); -} -function parseType(type) { - if (typeof type === 'function') { - return new type(); - } - else if (isArray(type)) { - return [parseType(type[0])]; - } - else if (type && typeof type === 'object') { - var rv = {}; - applyStructure(rv, type); - return rv; - } - else { - return type; - } -} -function applyStructure(obj, structure) { - keys(structure).forEach(function (member) { - var value = parseType(structure[member]); - obj[member] = value; - }); - return obj; -} -function hookedEventSuccessHandler(resolve) { - // wrap() is needed when calling hooks because the rare scenario of: - // * hook does a db operation that fails immediately (IDB throws exception) - // For calling db operations on correct transaction, wrap makes sure to set PSD correctly. - // wrap() will also execute in a virtual tick. - // * If not wrapped in a virtual tick, direct exception will launch a new physical tick. - // * If this was the last event in the bulk, the promise will resolve after a physical tick - // and the transaction will have committed already. - // If no hook, the virtual tick will be executed in the reject()/resolve of the final promise, - // because it is always marked with _lib = true when created using Transaction._promise(). - return wrap(function (event) { - var req = event.target, ctx = req._hookCtx, // Contains the hook error handler. Put here instead of closure to boost performance. - result = ctx.value || req.result, // Pass the object value on updates. The result from IDB is the primary key. - hookSuccessHandler = ctx && ctx.onsuccess; - hookSuccessHandler && hookSuccessHandler(result); - resolve && resolve(result); - }, resolve); -} -function eventRejectHandler(reject) { - return wrap(function (event) { - preventDefault(event); - reject(event.target.error); - return false; - }); -} -function eventSuccessHandler(resolve) { - return wrap(function (event) { - resolve(event.target.result); - }); -} -function hookedEventRejectHandler(reject) { - return wrap(function (event) { - // See comment on hookedEventSuccessHandler() why wrap() is needed only when supporting hooks. - var req = event.target, err = req.error, ctx = req._hookCtx, // Contains the hook error handler. Put here instead of closure to boost performance. - hookErrorHandler = ctx && ctx.onerror; - hookErrorHandler && hookErrorHandler(err); - preventDefault(event); - reject(err); - return false; - }); + Observable.prototype.subscribe = function (x, error, complete) { + return this._subscribe(!x || typeof x === "function" ? { next: x, error: error, complete: complete } : x); + }; + Observable.prototype[symbolObservable] = function () { + return this; + }; + return Observable; +}()); + +var domDeps; +try { + domDeps = { + indexedDB: _global.indexedDB || _global.mozIndexedDB || _global.webkitIndexedDB || _global.msIndexedDB, + IDBKeyRange: _global.IDBKeyRange || _global.webkitIDBKeyRange + }; } -function preventDefault(event) { - if (event.stopPropagation) - event.stopPropagation(); - if (event.preventDefault) - event.preventDefault(); +catch (e) { + domDeps = { indexedDB: null, IDBKeyRange: null }; } -function awaitIterator(iterator) { - var callNext = function (result) { return iterator.next(result); }, doThrow = function (error) { return iterator.throw(error); }, onSuccess = step(callNext), onError = step(doThrow); - function step(getNext) { - return function (val) { - var next = getNext(val), value = next.value; - return next.done ? value : - (!value || typeof value.then !== 'function' ? - isArray(value) ? Promise.all(value).then(onSuccess, onError) : onSuccess(value) : - value.then(onSuccess, onError)); + +function liveQuery(querier) { + var hasValue = false; + var currentValue; + var observable = new Observable(function (observer) { + var scopeFuncIsAsync = isAsyncFunction(querier); + function execute(ctx) { + var wasRootExec = beginMicroTickScope(); + try { + if (scopeFuncIsAsync) { + incrementExpectedAwaits(); + } + var rv = newScope(querier, ctx); + if (scopeFuncIsAsync) { + rv = rv.finally(decrementExpectedAwaits); + } + return rv; + } + finally { + wasRootExec && endMicroTickScope(); + } + } + var closed = false; + var abortController; + var accumMuts = {}; + var currentObs = {}; + var subscription = { + get closed() { + return closed; + }, + unsubscribe: function () { + if (closed) + return; + closed = true; + if (abortController) + abortController.abort(); + if (startedListening) + globalEvents.storagemutated.unsubscribe(mutationListener); + }, }; - } - return step(callNext)(); -} -// -// IndexSpec struct -// -function IndexSpec(name, keyPath, unique, multi, auto, compound, dotted) { - /// - /// - /// - /// - /// - /// - /// - this.name = name; - this.keyPath = keyPath; - this.unique = unique; - this.multi = multi; - this.auto = auto; - this.compound = compound; - this.dotted = dotted; - var keyPathSrc = typeof keyPath === 'string' ? keyPath : keyPath && ('[' + [].join.call(keyPath, '+') + ']'); - this.src = (unique ? '&' : '') + (multi ? '*' : '') + (auto ? "++" : "") + keyPathSrc; -} -// -// TableSchema struct -// -function TableSchema(name, primKey, indexes, instanceTemplate) { - /// - /// - /// - /// - this.name = name; - this.primKey = primKey || new IndexSpec(); - this.indexes = indexes || [new IndexSpec()]; - this.instanceTemplate = instanceTemplate; - this.mappedClass = null; - this.idxByName = arrayToObject(indexes, function (index) { return [index.name, index]; }); -} -function safariMultiStoreFix(storeNames) { - return storeNames.length === 1 ? storeNames[0] : storeNames; + observer.start && observer.start(subscription); + var startedListening = false; + var doQuery = function () { return execInGlobalContext(_doQuery); }; + function shouldNotify() { + return obsSetsOverlap(currentObs, accumMuts); + } + var mutationListener = function (parts) { + extendObservabilitySet(accumMuts, parts); + if (shouldNotify()) { + doQuery(); + } + }; + var _doQuery = function () { + if (closed || + !domDeps.indexedDB) + { + return; + } + accumMuts = {}; + var subscr = {}; + if (abortController) + abortController.abort(); + abortController = new AbortController(); + var ctx = { + subscr: subscr, + signal: abortController.signal, + requery: doQuery, + querier: querier, + trans: null + }; + var ret = execute(ctx); + Promise.resolve(ret).then(function (result) { + hasValue = true; + currentValue = result; + if (closed || ctx.signal.aborted) { + return; + } + accumMuts = {}; + currentObs = subscr; + if (!objectIsEmpty(currentObs) && !startedListening) { + globalEvents(DEXIE_STORAGE_MUTATED_EVENT_NAME, mutationListener); + startedListening = true; + } + execInGlobalContext(function () { return !closed && observer.next && observer.next(result); }); + }, function (err) { + hasValue = false; + if (!['DatabaseClosedError', 'AbortError'].includes(err === null || err === void 0 ? void 0 : err.name)) { + if (!closed) + execInGlobalContext(function () { + if (closed) + return; + observer.error && observer.error(err); + }); + } + }); + }; + setTimeout(doQuery, 0); + return subscription; + }); + observable.hasValue = function () { return hasValue; }; + observable.getValue = function () { return currentValue; }; + return observable; } -function getNativeGetDatabaseNamesFn(indexedDB) { - var fn = indexedDB && (indexedDB.getDatabaseNames || indexedDB.webkitGetDatabaseNames); - return fn && fn.bind(indexedDB); -} -// Export Error classes -props(Dexie, fullNameExceptions); // Dexie.XXXError = class XXXError {...}; -// -// Static methods and properties -// -props(Dexie, { - // - // Static delete() method. - // + +var Dexie = Dexie$1; +props(Dexie, __assign(__assign({}, fullNameExceptions), { delete: function (databaseName) { - var db = new Dexie(databaseName), promise = db.delete(); - promise.onblocked = function (fn) { - db.on("blocked", fn); - return this; - }; - return promise; + var db = new Dexie(databaseName, { addons: [] }); + return db.delete(); }, - // - // Static exists() method. - // exists: function (name) { - return new Dexie(name).open().then(function (db) { + return new Dexie(name, { addons: [] }).open().then(function (db) { db.close(); return true; - }).catch(Dexie.NoSuchDatabaseError, function () { return false; }); + }).catch('NoSuchDatabaseError', function () { return false; }); }, - // - // Static method for retrieving a list of all existing databases at current host. - // getDatabaseNames: function (cb) { - var getDatabaseNames = getNativeGetDatabaseNamesFn(Dexie.dependencies.indexedDB); - return getDatabaseNames ? new Promise(function (resolve, reject) { - var req = getDatabaseNames(); - req.onsuccess = function (event) { - resolve(slice(event.target.result, 0)); // Converst DOMStringList to Array - }; - req.onerror = eventRejectHandler(reject); - }).then(cb) : dbNamesDB.dbnames.toCollection().primaryKeys(cb); + try { + return getDatabaseNames(Dexie.dependencies).then(cb); + } + catch (_a) { + return rejection(new exceptions.MissingAPI()); + } }, defineClass: function () { - // Default constructor able to copy given properties into this object. - function Class(properties) { - /// Properties to initialize object with. - /// - if (properties) - extend(this, properties); + function Class(content) { + extend(this, content); } return Class; - }, - applyStructure: applyStructure, - ignoreTransaction: function (scopeFunc) { - // In case caller is within a transaction but needs to create a separate transaction. - // Example of usage: - // - // Let's say we have a logger function in our app. Other application-logic should be unaware of the - // logger function and not need to include the 'logentries' table in all transaction it performs. - // The logging should always be done in a separate transaction and not be dependant on the current - // running transaction context. Then you could use Dexie.ignoreTransaction() to run code that starts a new transaction. - // - // Dexie.ignoreTransaction(function() { - // db.logentries.add(newLogEntry); - // }); - // - // Unless using Dexie.ignoreTransaction(), the above example would try to reuse the current transaction - // in current Promise-scope. - // - // An alternative to Dexie.ignoreTransaction() would be setImmediate() or setTimeout(). The reason we still provide an - // API for this because - // 1) The intention of writing the statement could be unclear if using setImmediate() or setTimeout(). - // 2) setTimeout() would wait unnescessary until firing. This is however not the case with setImmediate(). - // 3) setImmediate() is not supported in the ES standard. - // 4) You might want to keep other PSD state that was set in a parent PSD, such as PSD.letThrough. + }, ignoreTransaction: function (scopeFunc) { return PSD.trans ? - usePSD(PSD.transless, scopeFunc) : // Use the closest parent that was non-transactional. - scopeFunc(); // No need to change scope because there is no ongoing transaction. - }, - vip: function (fn) { - // To be used by subscribers to the on('ready') event. - // This will let caller through to access DB even when it is blocked while the db.ready() subscribers are firing. - // This would have worked automatically if we were certain that the Provider was using Dexie.Promise for all asyncronic operations. The promise PSD - // from the provider.connect() call would then be derived all the way to when provider would call localDatabase.applyChanges(). But since - // the provider more likely is using non-promise async APIs or other thenable implementations, we cannot assume that. - // Note that this method is only useful for on('ready') subscribers that is returning a Promise from the event. If not using vip() - // the database could deadlock since it wont open until the returned Promise is resolved, and any non-VIPed operation started by - // the caller will not resolve until database is opened. - return newScope(function () { - PSD.letThrough = true; // Make sure we are let through if still blocking db due to onready is firing. - return fn(); - }); - }, - async: function (generatorFn) { + usePSD(PSD.transless, scopeFunc) : + scopeFunc(); + }, vip: vip, async: function (generatorFn) { return function () { try { var rv = awaitIterator(generatorFn.apply(this, arguments)); if (!rv || typeof rv.then !== 'function') - return Promise.resolve(rv); + return DexiePromise.resolve(rv); return rv; } catch (e) { return rejection(e); } }; - }, - spawn: function (generatorFn, args, thiz) { + }, spawn: function (generatorFn, args, thiz) { try { var rv = awaitIterator(generatorFn.apply(thiz, args || [])); if (!rv || typeof rv.then !== 'function') - return Promise.resolve(rv); + return DexiePromise.resolve(rv); return rv; } catch (e) { return rejection(e); } }, - // Dexie.currentTransaction property currentTransaction: { get: function () { return PSD.trans || null; } + }, waitFor: function (promiseOrFunction, optionalTimeout) { + var promise = DexiePromise.resolve(typeof promiseOrFunction === 'function' ? + Dexie.ignoreTransaction(promiseOrFunction) : + promiseOrFunction) + .timeout(optionalTimeout || 60000); + return PSD.trans ? + PSD.trans.waitFor(promise) : + promise; }, - waitFor: function (promiseOrFunction, optionalTimeout) { - // If a function is provided, invoke it and pass the returning value to Transaction.waitFor() - 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. - // Run given promise on current transaction. If no current transaction, just return a Dexie promise based - // on given value. - return PSD.trans ? PSD.trans.waitFor(promise) : promise; - }, - // Export our Promise implementation since it can be handy as a standalone Promise implementation - Promise: Promise, - // Dexie.debug proptery: - // Dexie.debug = false - // Dexie.debug = true - // Dexie.debug = "dexie" - don't hide dexie's stack frames. + Promise: DexiePromise, debug: { get: function () { return debug; }, set: function (value) { - setDebug(value, value === 'dexie' ? function () { return true; } : dexieStackFrameFilter); + setDebug(value); } }, - // Export our derive/extend/override methodology - derive: derive, - extend: extend, - props: props, - override: override, - // Export our Events() function - can be handy as a toolkit - Events: Events, - // Utilities - getByKeyPath: getByKeyPath, - setByKeyPath: setByKeyPath, - delByKeyPath: delByKeyPath, - shallowClone: shallowClone, - deepClone: deepClone, - getObjectDiff: getObjectDiff, - asap: asap, - maxKey: maxKey, + derive: derive, extend: extend, props: props, override: override, + Events: Events, on: globalEvents, liveQuery: liveQuery, extendObservabilitySet: extendObservabilitySet, + getByKeyPath: getByKeyPath, setByKeyPath: setByKeyPath, delByKeyPath: delByKeyPath, shallowClone: shallowClone, deepClone: deepClone, getObjectDiff: getObjectDiff, cmp: cmp, asap: asap$1, minKey: minKey, - // Addon registry addons: [], - // Global DB connection list connections: connections, - MultiModifyError: exceptions.Modify, errnames: errnames, - // Export other static classes - IndexSpec: IndexSpec, - TableSchema: TableSchema, - // - // Dependencies - // - // These will automatically work in browsers with indexedDB support, or where an indexedDB polyfill has been included. - // - // In node.js, however, these properties must be set "manually" before instansiating a new Dexie(). - // For node.js, you need to require indexeddb-js or similar and then set these deps. - // - dependencies: (function () { - try { - return { - // Required: - indexedDB: _global.indexedDB || _global.mozIndexedDB || _global.webkitIndexedDB || _global.msIndexedDB, - IDBKeyRange: _global.IDBKeyRange || _global.webkitIDBKeyRange - }; + dependencies: domDeps, cache: cache, + semVer: DEXIE_VERSION, version: DEXIE_VERSION.split('.') + .map(function (n) { return parseInt(n); }) + .reduce(function (p, c, i) { return p + (c / Math.pow(10, i * 2)); }) })); +Dexie.maxKey = getMaxKey(Dexie.dependencies.IDBKeyRange); + +if (typeof dispatchEvent !== 'undefined' && typeof addEventListener !== 'undefined') { + globalEvents(DEXIE_STORAGE_MUTATED_EVENT_NAME, function (updatedParts) { + if (!propagatingLocally) { + var event_1; + event_1 = new CustomEvent(STORAGE_MUTATED_DOM_EVENT_NAME, { + detail: updatedParts + }); + propagatingLocally = true; + dispatchEvent(event_1); + propagatingLocally = false; } - catch (e) { - return { - indexedDB: null, - IDBKeyRange: null - }; + }); + addEventListener(STORAGE_MUTATED_DOM_EVENT_NAME, function (_a) { + var detail = _a.detail; + if (!propagatingLocally) { + propagateLocally(detail); } - })(), - // 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. - semVer: DEXIE_VERSION, - version: DEXIE_VERSION.split('.') - .map(function (n) { return parseInt(n); }) - .reduce(function (p, c, i) { return p + (c / Math.pow(10, i * 2)); }), - // https://github.com/dfahlander/Dexie.js/issues/186 - // typescript compiler tsc in mode ts-->es5 & commonJS, will expect require() to return - // x.default. Workaround: Set Dexie.default = Dexie. - default: Dexie, - // Make it possible to import {Dexie} (non-default import) - // Reason 1: May switch to that in future. - // Reason 2: We declare it both default and named exported in d.ts to make it possible - // to let addons extend the Dexie interface with Typescript 2.1 (works only when explicitely - // exporting the symbol, not just default exporting) - Dexie: Dexie -}); -// Map DOMErrors and DOMExceptions to corresponding Dexie errors. May change in Dexie v2.0. -Promise.rejectionMapper = mapError; -// Initialize dbNamesDB (won't ever be opened on chromium browsers') -dbNamesDB = new Dexie('__dbnames'); -dbNamesDB.version(1).stores({ dbnames: 'name' }); -(function () { - // Migrate from Dexie 1.x database names stored in localStorage: - var DBNAMES = 'Dexie.DatabaseNames'; + }); +} +function propagateLocally(updateParts) { + var wasMe = propagatingLocally; try { - if (typeof localStorage !== undefined && _global.document !== undefined) { - // Have localStorage and is not executing in a worker. Lets migrate from Dexie 1.x. - JSON.parse(localStorage.getItem(DBNAMES) || "[]") - .forEach(function (name) { return dbNamesDB.dbnames.put({ name: name }).catch(nop); }); - localStorage.removeItem(DBNAMES); - } + propagatingLocally = true; + globalEvents.storagemutated.fire(updateParts); + signalSubscribersNow(updateParts, true); + } + finally { + propagatingLocally = wasMe; + } +} +var propagatingLocally = false; + +var bc; +var createBC = function () { }; +if (typeof BroadcastChannel !== 'undefined') { + createBC = function () { + bc = new BroadcastChannel(STORAGE_MUTATED_DOM_EVENT_NAME); + bc.onmessage = function (ev) { return ev.data && propagateLocally(ev.data); }; + }; + createBC(); + if (typeof bc.unref === 'function') { + bc.unref(); } - catch (_e) { } -})(); + globalEvents(DEXIE_STORAGE_MUTATED_EVENT_NAME, function (changedParts) { + if (!propagatingLocally) { + bc.postMessage(changedParts); + } + }); +} + +if (typeof addEventListener !== 'undefined') { + addEventListener('pagehide', function (event) { + if (!Dexie$1.disableBfCache && event.persisted) { + if (debug) + console.debug('Dexie: handling persisted pagehide'); + bc === null || bc === void 0 ? void 0 : bc.close(); + for (var _i = 0, connections_1 = connections; _i < connections_1.length; _i++) { + var db = connections_1[_i]; + db.close({ disableAutoOpen: false }); + } + } + }); + addEventListener('pageshow', function (event) { + if (!Dexie$1.disableBfCache && event.persisted) { + if (debug) + console.debug('Dexie: handling persisted pageshow'); + createBC(); + propagateLocally({ all: new RangeSet(-Infinity, [[]]) }); + } + }); +} + +function add(value) { + return new PropModification({ add: value }); +} + +function remove(value) { + return new PropModification({ remove: value }); +} + +function replacePrefix(a, b) { + return new PropModification({ replacePrefix: [a, b] }); +} + +DexiePromise.rejectionMapper = mapError; +setDebug(debug); -export default Dexie; +export { Dexie$1 as Dexie, Entity, PropModification, RangeSet, add, cmp, Dexie$1 as default, liveQuery, mergeRanges, rangesOverlap, remove, replacePrefix }; +//# sourceMappingURL=dexie.mjs.map