UNPKG

mobx-utils

Version:

Utility functions and common patterns for MobX

1,756 lines • 69.7 kB
import { action, extendObservable, observable, _allowStateChanges, createAtom, computed, observe, makeObservable, runInAction, keys, isObservableArray, isObservableMap, isComputed, isObservableObject, $mobx, isComputedProp, _getAdministration, getAtom, isAction, autorun, _isComputingDerivation, onBecomeUnobserved, entries, values, transaction, reaction, ObservableMap, _getGlobalState } from 'mobx';

var NOOP = function () { };
var IDENTITY = function (_) { return _; };
function fail(message) {
    throw new Error("[mobx-utils] " + message);
}
function invariant(cond, message) {
    if (message === void 0) { message = "Illegal state"; }
    if (!cond)
        fail(message);
}
function addHiddenProp(object, propName, value) {
    Object.defineProperty(object, propName, {
        enumerable: false,
        writable: true,
        configurable: true,
        value: value,
    });
}
var deepFields = function (x) {
    return (x &&
        x !== Object.prototype &&
        Object.getOwnPropertyNames(x).concat(deepFields(Object.getPrototypeOf(x)) || []));
};
var distinctDeepFields = function (x) {
    var deepFieldsIndistinct = deepFields(x);
    var deepFieldsDistinct = deepFieldsIndistinct.filter(function (item, index) { return deepFieldsIndistinct.indexOf(item) === index; });
    return deepFieldsDistinct;
};
var getAllMethodsAndProperties = function (x) {
    return distinctDeepFields(x).filter(function (name) { return name !== "constructor" && !~name.indexOf("__"); });
};

var PENDING = "pending";
var FULFILLED = "fulfilled";
var REJECTED = "rejected";
function caseImpl(handlers) {
    switch (this.state) {
        case PENDING:
            return handlers.pending && handlers.pending(this.value);
        case REJECTED:
            return handlers.rejected && handlers.rejected(this.value);
        case FULFILLED:
            return handlers.fulfilled ? handlers.fulfilled(this.value) : this.value;
    }
}
/**
 * `fromPromise` takes a Promise, extends it with 2 observable properties that track
 * the status of the promise and returns it. The returned object has the following observable properties:
 *  - `value`: either the initial value, the value the Promise resolved to, or the value the Promise was rejected with. use `.state` if you need to be able to tell the difference.
 *  - `state`: one of `"pending"`, `"fulfilled"` or `"rejected"`
 *
 * And the following methods:
 * - `case({fulfilled, rejected, pending})`: maps over the result using the provided handlers, or returns `undefined` if a handler isn't available for the current promise state.
 *
 * The returned object implements `PromiseLike<TValue>`, so you can chain additional `Promise` handlers using `then`. You may also use it with `await` in `async` functions.
 *
 * Note that the status strings are available as constants:
 * `mobxUtils.PENDING`, `mobxUtils.REJECTED`, `mobxUtil.FULFILLED`
 *
 * fromPromise takes an optional second argument, a previously created `fromPromise` based observable.
 * This is useful to replace one promise based observable with another, without going back to an intermediate
 * "pending" promise state while fetching data. For example:
 *
 * @example
 * \@observer
 * class SearchResults extends React.Component {
 *   \@observable.ref searchResults
 *
 *   componentDidUpdate(nextProps) {
 *     if (nextProps.query !== this.props.query)
 *       this.searchResults = fromPromise(
 *         window.fetch("/search?q=" + nextProps.query),
 *         // by passing, we won't render a pending state if we had a successful search query before
 *         // rather, we will keep showing the previous search results, until the new promise resolves (or rejects)
 *         this.searchResults
 *       )
 *   }
 *
 *   render() {
 *     return this.searchResults.case({
 *        pending: (staleValue) => {
 *          return staleValue || "searching" // <- value might set to previous results while the promise is still pending
 *        },
 *        fulfilled: (value) => {
 *          return value // the fresh results
 *        },
 *        rejected: (error) => {
 *          return "Oops: " + error
 *        }
 *     })
 *   }
 * }
 *
 * Observable promises can be created immediately in a certain state using
 * `fromPromise.reject(reason)` or `fromPromise.resolve(value?)`.
 * The main advantage of `fromPromise.resolve(value)` over `fromPromise(Promise.resolve(value))` is that the first _synchronously_ starts in the desired state.
 *
 * It is possible to directly create a promise using a resolve, reject function:
 * `fromPromise((resolve, reject) => setTimeout(() => resolve(true), 1000))`
 *
 * @example
 * const fetchResult = fromPromise(fetch("http://someurl"))
 *
 * // combine with when..
 * when(
 *   () => fetchResult.state !== "pending",
 *   () => {
 *     console.log("Got ", fetchResult.value)
 *   }
 * )
 *
 * // or a mobx-react component..
 * const myComponent = observer(({ fetchResult }) => {
 *   switch(fetchResult.state) {
 *      case "pending": return <div>Loading...</div>
 *      case "rejected": return <div>Ooops... {fetchResult.value}</div>
 *      case "fulfilled": return <div>Gotcha: {fetchResult.value}</div>
 *   }
 * })
 *
 * // or using the case method instead of switch:
 *
 * const myComponent = observer(({ fetchResult }) =>
 *   fetchResult.case({
 *     pending:   () => <div>Loading...</div>,
 *     rejected:  error => <div>Ooops.. {error}</div>,
 *     fulfilled: value => <div>Gotcha: {value}</div>,
 *   }))
 *
 * // chain additional handler(s) to the resolve/reject:
 *
 * fetchResult.then(
 *   (result) =>  doSomeTransformation(result),
 *   (rejectReason) => console.error('fetchResult was rejected, reason: ' + rejectReason)
 * ).then(
 *   (transformedResult) => console.log('transformed fetchResult: ' + transformedResult)
 * )
 *
 * @param origPromise The promise which will be observed
 * @param oldPromise The previously observed promise
 * @returns origPromise with added properties and methods described above.
 */
function fromPromise(origPromise, oldPromise) {
    invariant(arguments.length <= 2, "fromPromise expects up to two arguments");
    invariant(typeof origPromise === "function" ||
        (typeof origPromise === "object" &&
            origPromise &&
            typeof origPromise.then === "function"), "Please pass a promise or function to fromPromise");
    if (origPromise.isPromiseBasedObservable === true)
        return origPromise;
    if (typeof origPromise === "function") {
        // If it is a (reject, resolve function, wrap it)
        origPromise = new Promise(origPromise);
    }
    var promise = origPromise;
    origPromise.then(action("observableFromPromise-resolve", function (value) {
        promise.value = value;
        promise.state = FULFILLED;
    }), action("observableFromPromise-reject", function (reason) {
        promise.value = reason;
        promise.state = REJECTED;
    }));
    promise.isPromiseBasedObservable = true;
    promise.case = caseImpl;
    var oldData = oldPromise && (oldPromise.state === FULFILLED || oldPromise.state === PENDING)
        ? oldPromise.value
        : undefined;
    extendObservable(promise, {
        value: oldData,
        state: PENDING,
    }, {}, { deep: false });
    return promise;
}
(function (fromPromise) {
    fromPromise.reject = action("fromPromise.reject", function (reason) {
        var p = fromPromise(Promise.reject(reason));
        p.state = REJECTED;
        p.value = reason;
        return p;
    });
    function resolveBase(value) {
        if (value === void 0) { value = undefined; }
        var p = fromPromise(Promise.resolve(value));
        p.state = FULFILLED;
        p.value = value;
        return p;
    }
    fromPromise.resolve = action("fromPromise.resolve", resolveBase);
})(fromPromise || (fromPromise = {}));
/**
 * Returns true if the provided value is a promise-based observable.
 * @param value any
 * @returns {boolean}
 */
function isPromiseBasedObservable(value) {
    return value && value.isPromiseBasedObservable === true;
}

var __spreadArrays = (undefined && undefined.__spreadArrays) || function () {
    for (var s = 0, i = 0, il = arguments.length; i < il; i++) s += arguments[i].length;
    for (var r = Array(s), k = 0, i = 0; i < il; i++)
        for (var a = arguments[i], j = 0, jl = a.length; j < jl; j++, k++)
            r[k] = a[j];
    return r;
};
/**
 * Moves an item from one position to another, checking that the indexes given are within bounds.
 *
 * @example
 * const source = observable([1, 2, 3])
 * moveItem(source, 0, 1)
 * console.log(source.map(x => x)) // [2, 1, 3]
 *
 * @export
 * @param {ObservableArray<T>} target
 * @param {number} fromIndex
 * @param {number} toIndex
 * @returns {ObservableArray<T>}
 */
function moveItem(target, fromIndex, toIndex) {
    checkIndex(target, fromIndex);
    checkIndex(target, toIndex);
    if (fromIndex === toIndex) {
        return;
    }
    var oldItems = target.slice();
    var newItems;
    if (fromIndex < toIndex) {
        newItems = __spreadArrays(oldItems.slice(0, fromIndex), oldItems.slice(fromIndex + 1, toIndex + 1), [
            oldItems[fromIndex]
        ], oldItems.slice(toIndex + 1));
    }
    else {
        // toIndex < fromIndex
        newItems = __spreadArrays(oldItems.slice(0, toIndex), [
            oldItems[fromIndex]
        ], oldItems.slice(toIndex, fromIndex), oldItems.slice(fromIndex + 1));
    }
    target.replace(newItems);
    return target;
}
/**
 * Checks whether the specified index is within bounds. Throws if not.
 *
 * @private
 * @param {ObservableArray<any>} target
 * @param {number }index
 */
function checkIndex(target, index) {
    if (index < 0) {
        throw new Error("[mobx.array] Index out of bounds: " + index + " is negative");
    }
    var length = target.length;
    if (index >= length) {
        throw new Error("[mobx.array] Index out of bounds: " + index + " is not smaller than " + length);
    }
}

/**
 * `lazyObservable` creates an observable around a `fetch` method that will not be invoked
 * until the observable is needed the first time.
 * The fetch method receives a `sink` callback which can be used to replace the
 * current value of the lazyObservable. It is allowed to call `sink` multiple times
 * to keep the lazyObservable up to date with some external resource.
 *
 * Note that it is the `current()` call itself which is being tracked by MobX,
 * so make sure that you don't dereference to early.
 *
 * @example
 * const userProfile = lazyObservable(
 *   sink => fetch("/myprofile").then(profile => sink(profile))
 * )
 *
 * // use the userProfile in a React component:
 * const Profile = observer(({ userProfile }) =>
 *   userProfile.current() === undefined
 *   ? <div>Loading user profile...</div>
 *   : <div>{userProfile.current().displayName}</div>
 * )
 *
 * // triggers refresh the userProfile
 * userProfile.refresh()
 *
 * @param {(sink: (newValue: T) => void) => void} fetch method that will be called the first time the value of this observable is accessed. The provided sink can be used to produce a new value, synchronously or asynchronously
 * @param {T} [initialValue=undefined] optional initialValue that will be returned from `current` as long as the `sink` has not been called at least once
 * @returns {{
 *     current(): T,
 *     refresh(): T,
 *     reset(): T
 *     pending: boolean
 * }}
 */
function lazyObservable(fetch, initialValue) {
    if (initialValue === void 0) { initialValue = undefined; }
    var started = false;
    var value = observable.box(initialValue, { deep: false });
    var pending = observable.box(false);
    var currentFnc = function () {
        if (!started) {
            started = true;
            _allowStateChanges(true, function () {
                pending.set(true);
            });
            fetch(function (newValue) {
                _allowStateChanges(true, function () {
                    value.set(newValue);
                    pending.set(false);
                });
            });
        }
        return value.get();
    };
    var resetFnc = action("lazyObservable-reset", function () {
        started = false;
        value.set(initialValue);
        return value.get();
    });
    return {
        current: currentFnc,
        refresh: function () {
            if (started) {
                started = false;
                return currentFnc();
            }
            else {
                return value.get();
            }
        },
        reset: function () {
            return resetFnc();
        },
        get pending() {
            return pending.get();
        },
    };
}

/**
 * `fromResource` creates an observable whose current state can be inspected using `.current()`,
 * and which can be kept in sync with some external datasource that can be subscribed to.
 *
 * The created observable will only subscribe to the datasource if it is in use somewhere,
 * (un)subscribing when needed. To enable `fromResource` to do that two callbacks need to be provided,
 * one to subscribe, and one to unsubscribe. The subscribe callback itself will receive a `sink` callback, which can be used
 * to update the current state of the observable, allowing observes to react.
 *
 * Whatever is passed to `sink` will be returned by `current()`. The values passed to the sink will not be converted to
 * observables automatically, but feel free to do so.
 * It is the `current()` call itself which is being tracked,
 * so make sure that you don't dereference to early.
 *
 * For inspiration, an example integration with the apollo-client on [github](https://github.com/apollostack/apollo-client/issues/503#issuecomment-241101379),
 * or the [implementation](https://github.com/mobxjs/mobx-utils/blob/1d17cf7f7f5200937f68cc0b5e7ec7f3f71dccba/src/now.ts#L43-L57) of `mobxUtils.now`
 *
 * The following example code creates an observable that connects to a `dbUserRecord`,
 * which comes from an imaginary database and notifies when it has changed.
 *
 * @example
 * function createObservableUser(dbUserRecord) {
 *   let currentSubscription;
 *   return fromResource(
 *     (sink) => {
 *       // sink the current state
 *       sink(dbUserRecord.fields)
 *       // subscribe to the record, invoke the sink callback whenever new data arrives
 *       currentSubscription = dbUserRecord.onUpdated(() => {
 *         sink(dbUserRecord.fields)
 *       })
 *     },
 *     () => {
 *       // the user observable is not in use at the moment, unsubscribe (for now)
 *       dbUserRecord.unsubscribe(currentSubscription)
 *     }
 *   )
 * }
 *
 * // usage:
 * const myUserObservable = createObservableUser(myDatabaseConnector.query("name = 'Michel'"))
 *
 * // use the observable in autorun
 * autorun(() => {
 *   // printed everytime the database updates its records
 *   console.log(myUserObservable.current().displayName)
 * })
 *
 * // ... or a component
 * const userComponent = observer(({ user }) =>
 *   <div>{user.current().displayName}</div>
 * )
 *
 * @export
 * @template T
 * @param {(sink: (newValue: T) => void) => void} subscriber
 * @param {IDisposer} [unsubscriber=NOOP]
 * @param {T} [initialValue=undefined] the data that will be returned by `get()` until the `sink` has emitted its first data
 * @returns {{
 *     current(): T;
 *     dispose(): void;
 *     isAlive(): boolean;
 * }}
 */
function fromResource(subscriber, unsubscriber, initialValue) {
    if (unsubscriber === void 0) { unsubscriber = NOOP; }
    if (initialValue === void 0) { initialValue = undefined; }
    var isActive = false;
    var isDisposed = false;
    var value = initialValue;
    var suspender = function () {
        if (isActive) {
            isActive = false;
            unsubscriber();
        }
    };
    var atom = createAtom("ResourceBasedObservable", function () {
        invariant(!isActive && !isDisposed);
        isActive = true;
        subscriber(function (newValue) {
            _allowStateChanges(true, function () {
                value = newValue;
                atom.reportChanged();
            });
        });
    }, suspender);
    return {
        current: function () {
            invariant(!isDisposed, "subscribingObservable has already been disposed");
            var isBeingTracked = atom.reportObserved();
            if (!isBeingTracked && !isActive)
                console.warn("Called `get` of a subscribingObservable outside a reaction. Current value will be returned but no new subscription has started");
            return value;
        },
        dispose: function () {
            isDisposed = true;
            suspender();
        },
        isAlive: function () { return isActive; },
    };
}

var __decorate = (undefined && undefined.__decorate) || function (decorators, target, key, desc) {
    var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
    if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
    else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
    return c > 3 && r && Object.defineProperty(target, key, r), r;
};
function observableSymbol() {
    return (typeof Symbol === "function" && Symbol.observable) || "@@observable";
}
/**
 * Converts an expression to an observable stream (a.k.a. TC 39 Observable / RxJS observable).
 * The provided expression is tracked by mobx as long as there are subscribers, automatically
 * emitting when new values become available. The expressions respect (trans)actions.
 *
 * @example
 *
 * const user = observable({
 *   firstName: "C.S",
 *   lastName: "Lewis"
 * })
 *
 * Rx.Observable
 *   .from(mobxUtils.toStream(() => user.firstname + user.lastName))
 *   .scan(nameChanges => nameChanges + 1, 0)
 *   .subscribe(nameChanges => console.log("Changed name ", nameChanges, "times"))
 *
 * @export
 * @template T
 * @param {() => T} expression
 * @param {boolean} fireImmediately (by default false)
 * @returns {IObservableStream<T>}
 */
function toStream(expression, fireImmediately) {
    var _a;
    if (fireImmediately === void 0) { fireImmediately = false; }
    var computedValue = computed(expression);
    return _a = {
            subscribe: function (observer) {
                if ("function" === typeof observer) {
                    return {
                        unsubscribe: observe(computedValue, function (_a) {
                            var newValue = _a.newValue;
                            return observer(newValue);
                        }, fireImmediately),
                    };
                }
                if (observer && "object" === typeof observer && observer.next) {
                    return {
                        unsubscribe: observe(computedValue, function (_a) {
                            var newValue = _a.newValue;
                            return observer.next(newValue);
                        }, fireImmediately),
                    };
                }
                return {
                    unsubscribe: function () { },
                };
            }
        },
        _a[observableSymbol()] = function () {
            return this;
        },
        _a;
}
var StreamListener = /** @class */ (function () {
    function StreamListener(observable, initialValue) {
        var _this = this;
        Object.defineProperty(this, "current", {
            enumerable: true,
            configurable: true,
            writable: true,
            value: void 0
        });
        Object.defineProperty(this, "subscription", {
            enumerable: true,
            configurable: true,
            writable: true,
            value: void 0
        });
        makeObservable(this);
        runInAction(function () {
            _this.current = initialValue;
            _this.subscription = observable.subscribe(_this);
        });
    }
    Object.defineProperty(StreamListener.prototype, "dispose", {
        enumerable: false,
        configurable: true,
        writable: true,
        value: function () {
            if (this.subscription) {
                this.subscription.unsubscribe();
            }
        }
    });
    Object.defineProperty(StreamListener.prototype, "next", {
        enumerable: false,
        configurable: true,
        writable: true,
        value: function (value) {
            this.current = value;
        }
    });
    Object.defineProperty(StreamListener.prototype, "complete", {
        enumerable: false,
        configurable: true,
        writable: true,
        value: function () {
            this.dispose();
        }
    });
    Object.defineProperty(StreamListener.prototype, "error", {
        enumerable: false,
        configurable: true,
        writable: true,
        value: function (value) {
            this.current = value;
            this.dispose();
        }
    });
    __decorate([
        observable.ref
    ], StreamListener.prototype, "current", void 0);
    __decorate([
        action.bound
    ], StreamListener.prototype, "next", null);
    __decorate([
        action.bound
    ], StreamListener.prototype, "complete", null);
    __decorate([
        action.bound
    ], StreamListener.prototype, "error", null);
    return StreamListener;
}());
function fromStream(observable, initialValue) {
    if (initialValue === void 0) { initialValue = undefined; }
    return new StreamListener(observable, initialValue);
}

var __assign = (undefined && undefined.__assign) || function () {
    __assign = Object.assign || function(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);
};
var __decorate$1 = (undefined && undefined.__decorate) || function (decorators, target, key, desc) {
    var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
    if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
    else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
    return c > 3 && r && Object.defineProperty(target, key, r), r;
};
var RESERVED_NAMES = ["model", "reset", "submit", "isDirty", "isPropertyDirty", "resetProperty"];
var ViewModel = /** @class */ (function () {
    function ViewModel(model) {
        var _this = this;
        Object.defineProperty(this, "model", {
            enumerable: true,
            configurable: true,
            writable: true,
            value: model
        });
        Object.defineProperty(this, "localValues", {
            enumerable: true,
            configurable: true,
            writable: true,
            value: observable.map({})
        });
        Object.defineProperty(this, "localComputedValues", {
            enumerable: true,
            configurable: true,
            writable: true,
            value: observable.map({})
        });
        Object.defineProperty(this, "isPropertyDirty", {
            enumerable: true,
            configurable: true,
            writable: true,
            value: function (key) {
                return _this.localValues.has(key);
            }
        });
        makeObservable(this);
        invariant(isObservableObject(model), "createViewModel expects an observable object");
        var ownMethodsAndProperties = getAllMethodsAndProperties(this);
        // use this helper as Object.getOwnPropertyNames doesn't return getters
        getAllMethodsAndProperties(model).forEach(function (key) {
            var _a;
            if (ownMethodsAndProperties.includes(key)) {
                return;
            }
            if (key === $mobx || key === "__mobxDidRunLazyInitializers") {
                return;
            }
            invariant(RESERVED_NAMES.indexOf(key) === -1, "The propertyname " + key + " is reserved and cannot be used with viewModels");
            if (isComputedProp(model, key)) {
                var computedBox = _getAdministration(model, key); // Fixme: there is no clear api to get the derivation
                var get = computedBox.derivation.bind(_this);
                var set = (_a = computedBox.setter_) === null || _a === void 0 ? void 0 : _a.bind(_this);
                _this.localComputedValues.set(key, computed(get, { set: set }));
            }
            var descriptor = Object.getOwnPropertyDescriptor(model, key);
            var additionalDescriptor = descriptor ? { enumerable: descriptor.enumerable } : {};
            Object.defineProperty(_this, key, __assign(__assign({}, additionalDescriptor), { configurable: true, get: function () {
                    if (isComputedProp(model, key))
                        return _this.localComputedValues.get(key).get();
                    if (_this.isPropertyDirty(key))
                        return _this.localValues.get(key);
                    else
                        return _this.model[key];
                }, set: action(function (value) {
                    if (isComputedProp(model, key)) {
                        _this.localComputedValues.get(key).set(value);
                    }
                    else if (value !== _this.model[key]) {
                        _this.localValues.set(key, value);
                    }
                    else {
                        _this.localValues.delete(key);
                    }
                }) }));
        });
    }
    Object.defineProperty(ViewModel.prototype, "isDirty", {
        get: function () {
            return this.localValues.size > 0;
        },
        enumerable: false,
        configurable: true
    });
    Object.defineProperty(ViewModel.prototype, "changedValues", {
        get: function () {
            return new Map(this.localValues);
        },
        enumerable: false,
        configurable: true
    });
    Object.defineProperty(ViewModel.prototype, "submit", {
        enumerable: false,
        configurable: true,
        writable: true,
        value: function () {
            var _this = this;
            keys(this.localValues).forEach(function (key) {
                var source = _this.localValues.get(key);
                var destination = _this.model[key];
                if (isObservableArray(destination)) {
                    destination.replace(source);
                }
                else if (isObservableMap(destination)) {
                    destination.clear();
                    destination.merge(source);
                }
                else if (!isComputed(source)) {
                    _this.model[key] = source;
                }
            });
            this.localValues.clear();
        }
    });
    Object.defineProperty(ViewModel.prototype, "reset", {
        enumerable: false,
        configurable: true,
        writable: true,
        value: function () {
            this.localValues.clear();
        }
    });
    Object.defineProperty(ViewModel.prototype, "resetProperty", {
        enumerable: false,
        configurable: true,
        writable: true,
        value: function (key) {
            this.localValues.delete(key);
        }
    });
    __decorate$1([
        computed
    ], ViewModel.prototype, "isDirty", null);
    __decorate$1([
        computed
    ], ViewModel.prototype, "changedValues", null);
    __decorate$1([
        action.bound
    ], ViewModel.prototype, "submit", null);
    __decorate$1([
        action.bound
    ], ViewModel.prototype, "reset", null);
    __decorate$1([
        action.bound
    ], ViewModel.prototype, "resetProperty", null);
    return ViewModel;
}());
/**
 * `createViewModel` takes an object with observable properties (model)
 * and wraps a viewmodel around it. The viewmodel proxies all enumerable properties of the original model with the following behavior:
 *  - as long as no new value has been assigned to the viewmodel property, the original property will be returned.
 *  - any future change in the model will be visible in the viewmodel as well unless the viewmodel property was dirty at the time of the attempted change.
 *  - once a new value has been assigned to a property of the viewmodel, that value will be returned during a read of that property in the future. However, the original model remain untouched until `submit()` is called.
 *
 * The viewmodel exposes the following additional methods, besides all the enumerable properties of the model:
 * - `submit()`: copies all the values of the viewmodel to the model and resets the state
 * - `reset()`: resets the state of the viewmodel, abandoning all local modifications
 * - `resetProperty(propName)`: resets the specified property of the viewmodel
 * - `isDirty`: observable property indicating if the viewModel contains any modifications
 * - `isPropertyDirty(propName)`: returns true if the specified property is dirty
 * - `changedValues`: returns a key / value map with the properties that have been changed in the model so far
 * - `model`: The original model object for which this viewModel was created
 *
 * You may use observable arrays, maps and objects with `createViewModel` but keep in mind to assign fresh instances of those to the viewmodel's properties, otherwise you would end up modifying the properties of the original model.
 * Note that if you read a non-dirty property, viewmodel only proxies the read to the model. You therefore need to assign a fresh instance not only the first time you make the assignment but also after calling `reset()` or `submit()`.
 *
 * @example
 * class Todo {
 *   \@observable title = "Test"
 * }
 *
 * const model = new Todo()
 * const viewModel = createViewModel(model);
 *
 * autorun(() => console.log(viewModel.model.title, ",", viewModel.title))
 * // prints "Test, Test"
 * model.title = "Get coffee"
 * // prints "Get coffee, Get coffee", viewModel just proxies to model
 * viewModel.title = "Get tea"
 * // prints "Get coffee, Get tea", viewModel's title is now dirty, and the local value will be printed
 * viewModel.submit()
 * // prints "Get tea, Get tea", changes submitted from the viewModel to the model, viewModel is proxying again
 * viewModel.title = "Get cookie"
 * // prints "Get tea, Get cookie" // viewModel has diverged again
 * viewModel.reset()
 * // prints "Get tea, Get tea", changes of the viewModel have been abandoned
 *
 * @param {T} model
 * @returns {(T & IViewModel<T>)}
 * ```
 */
function createViewModel(model) {
    return new ViewModel(model);
}

/**
 * MobX normally suspends any computed value that is not in use by any reaction,
 * and lazily re-evaluates the expression if needed outside a reaction while not in use.
 * `keepAlive` marks a computed value as always in use, meaning that it will always fresh, but never disposed automatically.
 *
 * @example
 * const obj = observable({
 *   number: 3,
 *   doubler: function() { return this.number * 2 }
 * })
 * const stop = keepAlive(obj, "doubler")
 *
 * @param {Object} target an object that has a computed property, created by `@computed` or `extendObservable`
 * @param {string} property the name of the property to keep alive
 * @returns {IDisposer} stops this keep alive so that the computed value goes back to normal behavior
 */
/**
 * @example
 * const number = observable(3)
 * const doubler = computed(() => number.get() * 2)
 * const stop = keepAlive(doubler)
 * // doubler will now stay in sync reactively even when there are no further observers
 * stop()
 * // normal behavior, doubler results will be recomputed if not observed but needed, but lazily
 *
 * @param {IComputedValue<any>} computedValue created using the `computed` function
 * @returns {IDisposer} stops this keep alive so that the computed value goes back to normal behavior
 */
function keepAlive(_1, _2) {
    var computed = getAtom(_1, _2);
    if (!computed)
        throw new Error("No computed provided, please provide an object created with `computed(() => expr)` or an object + property name");
    return observe(computed, function () { });
}

/**
 * `queueProcessor` takes an observable array, observes it and calls `processor`
 * once for each item added to the observable array, optionally debouncing the action
 *
 * @example
 * const pendingNotifications = observable([])
 * const stop = queueProcessor(pendingNotifications, msg => {
 *   // show Desktop notification
 *   new Notification(msg);
 * })
 *
 * // usage:
 * pendingNotifications.push("test!")
 *
 * @param {T[]} observableArray observable array instance to track
 * @param {(item: T) => void} processor action to call per item
 * @param {number} [debounce=0] optional debounce time in ms. With debounce 0 the processor will run synchronously
 * @returns {IDisposer} stops the processor
 */
function queueProcessor(observableArray, processor, debounce) {
    if (debounce === void 0) { debounce = 0; }
    if (!isObservableArray(observableArray))
        throw new Error("Expected observable array as first argument");
    if (!isAction(processor))
        processor = action("queueProcessor", processor);
    var runner = function () {
        // construct a final set
        var items = observableArray.slice(0);
        // clear the queue for next iteration
        runInAction(function () { return observableArray.splice(0); });
        // fire processor
        items.forEach(processor);
    };
    if (debounce > 0)
        return autorun(runner, { delay: debounce });
    else
        return autorun(runner);
}

/**
 * `chunkProcessor` takes an observable array, observes it and calls `processor`
 * once for a chunk of items added to the observable array, optionally deboucing the action.
 * The maximum chunk size can be limited by number.
 * This allows both, splitting larger into smaller chunks or (when debounced) combining smaller
 * chunks and/or single items into reasonable chunks of work.
 *
 * @example
 * const trackedActions = observable([])
 * const stop = chunkProcessor(trackedActions, chunkOfMax10Items => {
 *   sendTrackedActionsToServer(chunkOfMax10Items);
 * }, 100, 10)
 *
 * // usage:
 * trackedActions.push("scrolled")
 * trackedActions.push("hoveredButton")
 * // when both pushes happen within 100ms, there will be only one call to server
 *
 * @param {T[]} observableArray observable array instance to track
 * @param {(item: T[]) => void} processor action to call per item
 * @param {number} [debounce=0] optional debounce time in ms. With debounce 0 the processor will run synchronously
 * @param {number} [maxChunkSize=0] optionally do not call on full array but smaller chunks. With 0 it will process the full array.
 * @returns {IDisposer} stops the processor
 */
function chunkProcessor(observableArray, processor, debounce, maxChunkSize) {
    if (debounce === void 0) { debounce = 0; }
    if (maxChunkSize === void 0) { maxChunkSize = 0; }
    if (!isObservableArray(observableArray))
        throw new Error("Expected observable array as first argument");
    if (!isAction(processor))
        processor = action("chunkProcessor", processor);
    var runner = function () {
        var _loop_1 = function () {
            var chunkSize = maxChunkSize === 0
                ? observableArray.length
                : Math.min(observableArray.length, maxChunkSize);
            // construct a final set
            var items = observableArray.slice(0, chunkSize);
            // clear the slice for next iteration
            runInAction(function () { return observableArray.splice(0, chunkSize); });
            // fire processor
            processor(items);
        };
        while (observableArray.length > 0) {
            _loop_1();
        }
    };
    if (debounce > 0)
        return autorun(runner, { delay: debounce });
    else
        return autorun(runner);
}

var tickers = {};
/**
 * Disposes of all the internal Observables created by invocations of `now()`.
 *
 * The use case for this is to ensure that unit tests can run independent of each other.
 * You should not call this in regular application code.
 *
 * @example
 * afterEach(() => {
 *     utils.resetNowInternalState()
 * })
 */
function resetNowInternalState() {
    for (var _i = 0, _a = Object.getOwnPropertyNames(tickers); _i < _a.length; _i++) {
        var key = _a[_i];
        tickers[key].dispose();
        delete tickers[key];
    }
}
/**
 * Returns the current date time as epoch number.
 * The date time is read from an observable which is updated automatically after the given interval.
 * So basically it treats time as an observable.
 *
 * The function takes an interval as parameter, which indicates how often `now()` will return a new value.
 * If no interval is given, it will update each second. If "frame" is specified, it will update each time a
 * `requestAnimationFrame` is available.
 *
 * Multiple clocks with the same interval will automatically be synchronized.
 *
 * Countdown example: https://jsfiddle.net/mweststrate/na0qdmkw/
 *
 * @example
 *
 * const start = Date.now()
 *
 * autorun(() => {
 *   console.log("Seconds elapsed: ", (mobxUtils.now() - start) / 1000)
 * })
 *
 *
 * @export
 * @param {(number | "frame")} [interval=1000] interval in milliseconds about how often the interval should update
 * @returns
 */
function now(interval) {
    if (interval === void 0) { interval = 1000; }
    if (!_isComputingDerivation()) {
        // See #40
        return Date.now();
    }
    if (!tickers[interval]) {
        if (typeof interval === "number")
            tickers[interval] = createIntervalTicker(interval);
        else
            tickers[interval] = createAnimationFrameTicker();
    }
    return tickers[interval].current();
}
function createIntervalTicker(interval) {
    var subscriptionHandle;
    return fromResource(function (sink) {
        sink(Date.now());
        subscriptionHandle = setInterval(function () { return sink(Date.now()); }, interval);
    }, function () {
        clearInterval(subscriptionHandle);
    }, Date.now());
}
function createAnimationFrameTicker() {
    var frameBasedTicker = fromResource(function (sink) {
        sink(Date.now());
        function scheduleTick() {
            window.requestAnimationFrame(function () {
                sink(Date.now());
                if (frameBasedTicker.isAlive())
                    scheduleTick();
            });
        }
        scheduleTick();
    }, function () { }, Date.now());
    return frameBasedTicker;
}

/**
 *`expr` can be used to create temporary computed values inside computed values.
 * Nesting computed values is useful to create cheap computations in order to prevent expensive computations from needing to run.
 * In the following example the expression prevents that a component is rerender _each time_ the selection changes;
 * instead it will only rerenders when the current todo is (de)selected.
 *
 * `expr(func)` is an alias for `computed(func).get()`.
 * Please note that the function given to `expr` is evaluated _twice_ in the scenario that the overall expression value changes.
 * It is evaluated the first time when any observables it depends on change.
 * It is evaluated a second time when a change in its value triggers the outer computed or reaction to evaluate, which recreates and reevaluates the expression.
 *
 * In the following example, the expression prevents the `TodoView` component from being re-rendered if the selection changes elsewhere.
 * Instead, the component will only re-render when the relevant todo is (de)selected, which happens much less frequently.
 *
 * @example
 * const Todo = observer((props) => {
 *     const todo = props.todo
 *     const isSelected = mobxUtils.expr(() => props.viewState.selection === todo)
 * const TodoView = observer(({ todo, editorState }) => {
 *     const isSelected = mobxUtils.expr(() => editorState.selection === todo)
 *     return <div className={isSelected ? "todo todo-selected" : "todo"}>{todo.title}</div>
 * })
 */
function expr(expr) {
    if (!_isComputingDerivation())
        console.warn("'expr' should only be used inside other reactive functions.");
    // optimization: would be more efficient if the expr itself wouldn't be evaluated first on the next change, but just a 'changed' signal would be fired
    return computed(expr).get();
}

var __assign$1 = (undefined && undefined.__assign) || function () {
    __assign$1 = Object.assign || function(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$1.apply(this, arguments);
};
var memoizationId = 0;
/**
 * Creates a function that maps an object to a view.
 * The mapping is memoized.
 *
 * See the [transformer](#createtransformer-in-detail) section for more details.
 *
 * @param transformer
 * @param onCleanup
 */
function createTransformer(transformer, arg2) {
    invariant(typeof transformer === "function" && transformer.length < 2, "createTransformer expects a function that accepts one argument");
    // Memoizes: object id -> reactive view that applies transformer to the object
    var views = {};
    var onCleanup = undefined;
    var keepAlive = false;
    var debugNameGenerator = undefined;
    if (typeof arg2 === "object") {
        onCleanup = arg2.onCleanup;
        keepAlive = arg2.keepAlive !== undefined ? arg2.keepAlive : false;
        debugNameGenerator = arg2.debugNameGenerator;
    }
    else if (typeof arg2 === "function") {
        onCleanup = arg2;
    }
    function createView(sourceIdentifier, sourceObject) {
        var latestValue;
        var computedValueOptions = {};
        if (typeof arg2 === "object") {
            onCleanup = arg2.onCleanup;
            debugNameGenerator = arg2.debugNameGenerator;
            computedValueOptions = arg2;
        }
        else if (typeof arg2 === "function") {
            onCleanup = arg2;
        }
        else {
            onCleanup = undefined;
            debugNameGenerator = undefined;
        }
        var prettifiedName = debugNameGenerator
            ? debugNameGenerator(sourceObject)
            : "Transformer-" + transformer.name + "-" + sourceIdentifier;
        var expr = computed(function () {
            return (latestValue = transformer(sourceObject));
        }, __assign$1(__assign$1({}, computedValueOptions), { name: prettifiedName }));
        if (!keepAlive) {
            var disposer_1 = onBecomeUnobserved(expr, function () {
                delete views[sourceIdentifier];
                disposer_1();
                if (onCleanup)
                    onCleanup(latestValue, sourceObject);
            });
        }
        return expr;
    }
    var memoWarned = false;
    return function (object) {
        var identifier = getMemoizationId(object);
        var reactiveView = views[identifier];
        if (reactiveView)
            return reactiveView.get();
        if (!keepAlive && !_isComputingDerivation()) {
            if (!memoWarned) {
                console.warn("invoking a transformer from outside a reactive context won't memorized " +
                    "and is cleaned up immediately, unless keepAlive is set");
                memoWarned = true;
            }
            var value = transformer(object);
            if (onCleanup)
                onCleanup(value, object);
            return value;
        }
        // Not in cache; create a reactive view
        reactiveView = views[identifier] = createView(identifier, object);
        return reactiveView.get();
    };
}
function getMemoizationId(object) {
    var objectType = typeof object;
    if (objectType === "string")
        return "string:" + object;
    if (objectType === "number")
        return "number:" + object;
    if (object === null || (objectType !== "object" && objectType !== "function"))
        throw new Error("[mobx-utils] transform expected an object, function, string or number, got: " + String(object));
    var tid = object.$transformId;
    if (tid === undefined) {
        tid = "memoizationId:" + ++memoizationId;
        addHiddenProp(object, "$transformId", tid);
    }
    return tid;
}

function buildPath(entry) {
    if (!entry)
        return "ROOT";
    var res = [];
    while (entry.parent) {
        res.push(entry.path);
        entry = entry.parent;
    }
    return res.reverse().join("/");
}
function isRecursivelyObservable(thing) {
    return isObservableObject(thing) || isObservableArray(thing) || isObservableMap(thing);
}
/**
 * Given an object, deeply observes the given object.
 * It is like `observe` from mobx, but applied recursively, including all future children.
 *
 * Note that the given object cannot ever contain cycles and should be a tree.
 *
 * As benefit: path and root will be provided in the callback, so the signature of the listener is
 * (change, path, root) => void
 *
 * The returned disposer can be invoked to clean up the listener
 *
 * deepObserve cannot be used on computed values.
 *
 * @example
 * const disposer = deepObserve(target, (change, path) => {
 *    console.dir(change)
 * })
 */
function deepObserve(target, listener) {
    var entrySet = new WeakMap();
    function genericListener(change) {
        var entry = entrySet.get(change.object);
        processChange(change, entry);
        listener(change, buildPath(entry), target);
    }
    function processChange(change, parent) {
        switch (change.type) {
            // Object changes
            case "add": // also for map
                observeRecursively(change.newValue, parent, change.name);
                break;
            case "update": // also for array and map
                unobserveRecursively(change.oldValue);
                observeRecursively(change.newValue, parent, change.name || "" + change.index);
                break;
            case "remove": // object
            case "delete": // map
                unobserveRecursively(change.oldValue);
                break;
            // Array changes
            case "splice":
                change.removed.map(unobserveRecursively);
                change.added.forEach(function (value, idx) {
                    return observeRecursively(value, parent, "" + (change.index + idx));
                });
                // update paths
                for (var i = change.index + change.addedCount; i < change.object.length; i++) {
                    if (isRecursivelyObservable(change.object[i])) {
                        var entry = entrySet.get(change.object[i]);
                        if (entry)
                            entry.path = "" + i;
                    }
                }
                break;
        }
    }
    function observeRecursively(thing, parent, path) {
        if (isRecursivelyObservable(thing)) {
            var entry = entrySet.get(thing);
            if (entry) {
                if (entry.parent !== parent || entry.path !== path)
                    // MWE: this constraint is artificial, and this tool could be made to work with cycles,
                    // but it increases administration complexity, has tricky edge cases and the meaning of 'path'
                    // would become less clear. So doesn't seem to be needed for now
                    throw new Error("The same observable object cannot appear twice in the same tree," +
                        (" trying to assign it to '" + buildPath(parent) + "/" + path + "',") +
                        (" but it already exists at '" + buildPath(entry.parent) + "/" + entry.path + "'"));
            }
            else {
                var entry_1 = {
                    parent: parent,
                    path: path,
                    dispose: observe(thing, genericListener),
                };
                entrySet.set(thing, entry_1);
                entries(thing).forEach(function (_a) {
                    var key = _a[0], value = _a[1];
                    return observeRecursively(value, entry_1, key);
                });
            }
        }
    }
    function unobserveRecursively(thing) {
        if (isRecursivelyObservable(thing)) {
            var entry = entrySet.get(thing);
            if (!entry)
                return;
            entrySet.delete(thing);
            entry.dispose();
            values(thing).forEach(unobserveRecursively);
        }
    }
    observeRecursively(target, undefined, "");
    return function () {
        unobserveRecursively(target);
    };
}

var __extends = (undefined && undefined.__extends) || (function () {
    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);
    };
    return function (d, b) {
        extendStatics(d, b);
        function __() { this.constructor = d; }
        d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
    };
})();
/**
 * Reactively sorts a base observable array into multiple observable arrays based on the value of a
 * `groupBy: (item: T) => G` function.
 *
 * This observes the individual computed groupBy values and only updates the source and dest arrays
 * when there is an actual change, so this is far more efficient than, for example
 * `base.filter(i => groupBy(i) === 'we')`. Call #dispose() to stop tracking.
 *
 * No guarantees are made about the order of items in the grouped arrays.
 *
 * The resulting map of arrays is read-only. clear(), set(), delete() are not supported and
 * modifying the group arrays will lead to undefined behavior.
 *
 * NB: ObservableGroupMap relies on `Symbol`s. If you are targeting a platform which doesn't
 * support these natively, you will need to provide a polyfill.
 *
 * @param {array} base The array to sort into groups.
 * @param {function} groupBy The function used for grouping.
 * @param options Object with properties:
 *  `name`: Debug name of this ObservableGroupMap.
 *  `keyToName`: Function to create the debug names of the observable group arrays.
 *
 * @example
 * const slices = observable([
 *     { day: "mo", hours: 12 },
 *     { day: "tu", hours: 2 },
 * ])
 * const slicesByDay = new ObservableGroupMap(slices, (slice) => slice.day)
 * autorun(() => console.log(
 *     slicesByDay.get("mo")?.length ?? 0,
 *     slicesByDay.get("we"))) // outputs 1, undefined
 * slices[0].day = "we" // outputs 0, [{ day: "we", hours: 12 }]
 */
var ObservableGroupMap = /** @class */ (function (_super) {
    __extends(ObservableGroupMap, _super);
    function ObservableGroupMap(base, groupBy, _a) {
        var _b = _a === void 0 ? {} : _a, _c = _b.name, name = _c === void 0 ? "ogm" + ((Math.random() * 1000) | 0) : _c, _d = _b.keyToName, keyToName = _d === void 0 ? function (x) { return "" + x; } : _d;
        var _this = _super.call(this) || this;
        /**
         * Base observable array which is being sorted into groups.
         */
        Object.defineProperty(_this, "_base", {
            enumerable: true,
            configurable: true,
            writable: true,
            value: void 0
        });
        /**
         * The ObservableGroupMap needs to track some state per-item. This is the name/symbol of the
         * property used to attach the state.
         */
        Object.defineProperty(_this, "_ogmInfoKey", {
            enumerable: true,
            configurable: true,
            writable: true,
            value: void 0
        });
        /**
         * The function used to group the items.
         */
        Object.defineProperty(_this, "_groupBy", {
            enumerable: true,
            configurable: true,
            writable: true,
            value: void 0
        });
        /**
         * This function is used to generate the mobx debug names of the observable group arrays.
         */
        Object.defineProperty(_this, "_keyToName", {
            enumerable: true,
            configurable: true,
            writable: true,
            value: void 0
        });
        Object.defineProperty(_this, "_disposeBaseObserver", {
            enumerable: true,
            configurable: true,
            writable: true,
            value: void 0
        });
        _this._keyToName = keyToName;
        _this._groupBy = groupBy;
        _this._ogmInfoKey = Symbol("ogmInfo" + name);
        _this._base = base;
        for (var i = 0; i < base.length; i++) {
            _this._addItem(base[i]);
        }
        _this._disposeBaseObserver = observe(_this._base, function (change) {
            if ("splice" === change.type) {
                transaction(function () {
                    for (var _i = 0, _a = change.removed; _i < _a.length; _i++) {
                        var removed = _a[_i];
                        _this._removeItem(removed);
                    }
                    for (var _b = 0, _c = change.added; _b < _c.length; _b++) {
                        var added = _c[_b];
                        _this._addItem(added);
                    }
                });
            }
            else if ("update" === change.type) {
                transaction(function () {
                    _this._removeItem(change.oldValue);
                    _this._addItem(change.newValue);
                });
            }
            else {
                throw new Error("illegal state");
            }
        });
        return _this;
    }
    Object.defineProperty(ObservableGroupMap.prototype, "clear", {
        enumerable: false,
        configurable: true,
        writable: true,
        value: function () {
            throw new Error("not supported");
        }
    });
    Object.defineProperty(ObservableGroupMap.prototype, "delete", {
        enumerable: false,
        configurable: true,
        writable: true,
        value: function (_key) {
            throw new Error("not supported");
        }
    });
    Object.defineProperty(ObservableGroupMap.prototype, "set", {
        enumerable: false,
        configurable: true,
        writable: true,
        value: function (_key, _value) {
            throw new Error("not supported");
        }
    });
    /**
     * Disposes all observers created during construction and removes state added to base array
     * items.
     */
    Object.defineProperty(ObservableGroupMap.prototype, "dispose", {
        enumerable: false,
        configurable: true,
        writable: true,
        value: function () {
            this._disposeBaseObserver();
            for (var i = 0; i < this._base.length; i++) {
                var item = this._base[i];
                var grouperItemInfo = item[this._ogmInfoKey];
                grouperItemInfo.reaction();
                delete item[this._ogmInfoKey];
            }
        }
    });
    Object.defineProperty(ObservableGroupMap.prototype, "_getGroupArr", {
        enumerable: false,
        configurable: true,
        writable: true,
        value: function (key) {
            var result = _super.prototype.get.call(this, key);
            if (undefined === result) {
                result = observable([], { name: "GroupArray[" + this._keyToName(key) + "]", deep: false });
                _super.prototype.set.call(this, key, result);
            }
            return result;
        }
    });
    Object.defineProperty(ObservableGroupMap.prototype, "_removeFromGroupArr", {
        enumerable: false,
        configurable: true,
        writable: true,
        value: function (key, itemIndex) {
            var arr = _super.prototype.get.call(this, key);
            if (1 === arr.length) {
                _super.prototype.delete.call(this, key);
            }
            else if (itemIndex === arr.length - 1) {
                // last position in array
                arr.length--;
            }
            else {
                arr[itemIndex] = arr[arr.length - 1];
                arr[itemIndex][this._ogmInfoKey].groupArrIndex = itemIndex;
                arr.length--;
            }
        }
    });
    Object.defineProperty(ObservableGroupMap.prototype, "_addItem", {
        enumerable: false,
        configurable: true,
        writable: true,
        value: function (item) {
            var _this = this;
            var groupByValue = this._groupBy(item);
            var groupArr = this._getGroupArr(groupByValue);
            var value = {
                groupByValue: groupByValue,
                groupArrIndex: groupArr.length,
                reaction: reaction(function () { return _this._groupBy(item); }, function (newGroupByValue, _r) {
                    var grouperItemInfo = item[_this._ogmInfoKey];
                    _this._removeFromGroupArr(grouperItemInfo.groupByValue, grouperItemInfo.groupArrIndex);
                    var newGroupArr = _this._getGroupArr(newGroupByValue);
                    var newGroupArrIndex = newGroupArr.length;
                    newGroupArr.push(item);
                    grouperItemInfo.groupByValue = newGroupByValue;
                    grouperItemInfo.groupArrIndex = newGroupArrIndex;
                }),
            };
            Object.defineProperty(item, this._ogmInfoKey, {
                configurable: true,
                enumerable: false,
                value: value,
            });
            groupArr.push(item);
        }
    });
    Object.defineProperty(ObservableGroupMap.prototype, "_removeItem", {
        enumerable: false,
        configurable: true,
        writable: true,
        value: function (item) {
            var grouperItemInfo = item[this._ogmInfoKey];
            this._removeFromGroupArr(grouperItemInfo.groupByValue, grouperItemInfo.groupArrIndex);
            grouperItemInfo.reaction();
            delete item[this._ogmInfoKey];
        }
    });
    return ObservableGroupMap;
}(ObservableMap));

/**
 * @private
 */
var DeepMapEntry = /** @class */ (function () {
    function DeepMapEntry(base, args) {
        Object.defineProperty(this, "base", {
            enumerable: true,
            configurable: true,
            writable: true,
            value: base
        });
        Object.defineProperty(this, "args", {
            enumerable: true,
            configurable: true,
            writable: true,
            value: args
        });
        Object.defineProperty(this, "root", {
            enumerable: true,
            configurable: true,
            writable: true,
            value: void 0
        });
        Object.defineProperty(this, "closest", {
            enumerable: true,
            configurable: true,
            writable: true,
            value: void 0
        });
        Object.defineProperty(this, "closestIdx", {
            enumerable: true,
            configurable: true,
            writable: true,
            value: 0
        });
        Object.defineProperty(this, "isDisposed", {
            enumerable: true,
            configurable: true,
            writable: true,
            value: false
        });
        var current = (this.closest = this.root = base);
        var i = 0;
        for (; i < this.args.length - 1; i++) {
            current = current.get(args[i]);
            if (current)
                this.closest = current;
            else
                break;
        }
        this.closestIdx = i;
    }
    Object.defineProperty(DeepMapEntry.prototype, "exists", {
        enumerable: false,
        configurable: true,
        writable: true,
        value: function () {
            this.assertNotDisposed();
            var l = this.args.length;
            return this.closestIdx >= l - 1 && this.closest.has(this.args[l - 1]);
        }
    });
    Object.defineProperty(DeepMapEntry.prototype, "get", {
        enumerable: false,
        configurable: true,
        writable: true,
        value: function () {
            this.assertNotDisposed();
            if (!this.exists())
                throw new Error("Entry doesn't exist");
            return this.closest.get(this.args[this.args.length - 1]);
        }
    });
    Object.defineProperty(DeepMapEntry.prototype, "set", {
        enumerable: false,
        configurable: true,
        writable: true,
        value: function (value) {
            this.assertNotDisposed();
            var l = this.args.length;
            var current = this.closest;
            // create remaining maps
            for (var i = this.closestIdx; i < l - 1; i++) {
                var m = new Map();
                current.set(this.args[i], m);
                current = m;
            }
            this.closestIdx = l - 1;
            this.closest = current;
            current.set(this.args[l - 1], value);
        }
    });
    Object.defineProperty(DeepMapEntry.prototype, "delete", {
        enumerable: false,
        configurable: true,
        writable: true,
        value: function () {
            this.assertNotDisposed();
            if (!this.exists())
                throw new Error("Entry doesn't exist");
            var l = this.args.length;
            this.closest.delete(this.args[l - 1]);
            // clean up remaining maps if needed (reconstruct stack first)
            var c = this.root;
            var maps = [c];
            for (var i = 0; i < l - 1; i++) {
                c = c.get(this.args[i]);
                maps.push(c);
            }
            for (var i = maps.length - 1; i > 0; i--) {
                if (maps[i].size === 0)
                    maps[i - 1].delete(this.args[i - 1]);
            }
            this.isDisposed = true;
        }
    });
    Object.defineProperty(DeepMapEntry.prototype, "assertNotDisposed", {
        enumerable: false,
        configurable: true,
        writable: true,
        value: function () {
            // TODO: once this becomes annoying, we should introduce a reset method to re-run the constructor logic
            if (this.isDisposed)
                throw new Error("Concurrent modification exception");
        }
    });
    return DeepMapEntry;
}());
/**
 * @private
 */
var DeepMap = /** @class */ (function () {
    function DeepMap() {
        Object.defineProperty(this, "store", {
            enumerable: true,
            configurable: true,
            writable: true,
            value: new Map()
        });
        Object.defineProperty(this, "argsLength", {
            enumerable: true,
            configurable: true,
            writable: true,
            value: -1
        });
        Object.defineProperty(this, "last", {
            enumerable: true,
            configurable: true,
            writable: true,
            value: void 0
        });
    }
    Object.defineProperty(DeepMap.prototype, "entry", {
        enumerable: false,
        configurable: true,
        writable: true,
        value: function (args) {
            if (this.argsLength === -1)
                this.argsLength = args.length;
            else if (this.argsLength !== args.length)
                throw new Error("DeepMap should be used with functions with a consistent length, expected: " + this.argsLength + ", got: " + args.length);
            if (this.last)
                this.last.isDisposed = true;
            return (this.last = new DeepMapEntry(this.store, args));
        }
    });
    return DeepMap;
}());

var __assign$2 = (undefined && undefined.__assign) || function () {
    __assign$2 = Object.assign || function(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$2.apply(this, arguments);
};
var __spreadArrays$1 = (undefined && undefined.__spreadArrays) || function () {
    for (var s = 0, i = 0, il = arguments.length; i < il; i++) s += arguments[i].length;
    for (var r = Array(s), k = 0, i = 0; i < il; i++)
        for (var a = arguments[i], j = 0, jl = a.length; j < jl; j++, k++)
            r[k] = a[j];
    return r;
};
/**
 * computedFn takes a function with an arbitrary amount of arguments,
 * and memoizes the output of the function based on the arguments passed in.
 *
 * computedFn(fn) returns a function with the very same signature. There is no limit on the amount of arguments
 * that is accepted. However, the amount of arguments must be constant and default arguments are not supported.
 *
 * By default the output of a function call will only be memoized as long as the
 * output is being observed.
 *
 * The function passes into `computedFn` should be pure, not be an action and only be relying on
 * observables.
 *
 * Setting `keepAlive` to `true` will cause the output to be forcefully cached forever.
 * Note that this might introduce memory leaks!
 *
 * @example
 * const store = observable({
    a: 1,
    b: 2,
    c: 3,
    m: computedFn(function(x) {
      return this.a * this.b * x
    })
  })

  const d = autorun(() => {
    // store.m(3) will be cached as long as this autorun is running
    console.log(store.m(3) * store.c)
  })
 *
 * @param fn
 * @param keepAliveOrOptions
 */
function computedFn(fn, keepAliveOrOptions) {
    if (keepAliveOrOptions === void 0) { keepAliveOrOptions = false; }
    if (isAction(fn))
        throw new Error("computedFn shouldn't be used on actions");
    var memoWarned = false;
    var i = 0;
    var opts = typeof keepAliveOrOptions === "boolean"
        ? { keepAlive: keepAliveOrOptions }
        : keepAliveOrOptions;
    var d = new DeepMap();
    return function () {
        var _this = this;
        var args = [];
        for (var _i = 0; _i < arguments.length; _i++) {
            args[_i] = arguments[_i];
        }
        var entry = d.entry(args);
        // cache hit, return
        if (entry.exists())
            return entry.get().get();
        // if function is invoked, and its a cache miss without reactive, there is no point in caching...
        if (!opts.keepAlive && !_isComputingDerivation()) {
            if (!memoWarned && _getGlobalState().computedRequiresReaction) {
                console.warn("invoking a computedFn from outside an reactive context won't be memoized, unless keepAlive is set");
                memoWarned = true;
            }
            return fn.apply(this, args);
        }
        // create new entry
        var latestValue;
        var c = computed(function () {
            return (latestValue = fn.apply(_this, args));
        }, __assign$2(__assign$2({}, opts), { name: "computedFn(" + (opts.name || fn.name) + "#" + ++i + ")" }));
        entry.set(c);
        // clean up if no longer observed
        if (!opts.keepAlive)
            onBecomeUnobserved(c, function () {
                d.entry(args).delete();
                if (opts.onCleanup)
                    opts.onCleanup.apply(opts, __spreadArrays$1([latestValue], args));
                latestValue = undefined;
            });
        // return current val
        return c.get();
    };
}

export { FULFILLED, IDENTITY, NOOP, ObservableGroupMap, PENDING, REJECTED, ViewModel, addHiddenProp, chunkProcessor, computedFn, createTransformer, createViewModel, deepObserve, expr, fail, fromPromise, fromResource, fromStream, getAllMethodsAndProperties, invariant, isPromiseBasedObservable, keepAlive, lazyObservable, moveItem, now, queueProcessor, resetNowInternalState, toStream };