UNPKG

@stratusjs/angularjs

Version:

This is the AngularJS package for StratusJS.

1,027 lines 52 kB
System.register(["lodash", "@stratusjs/runtime/stratus", "@stratusjs/core/errors/errorBase", "@stratusjs/core/misc", "@stratusjs/core/datastore/modelBase", "@stratusjs/core/environment", "@stratusjs/core/datastore/xhr", "../injector", "./collection", "toastify-js"], function (exports_1, context_1) {
    "use strict";
    var lodash_1, stratus_1, errorBase_1, misc_1, modelBase_1, environment_1, xhr_1, injector_1, collection_1, toastify_js_1, injector, $rootScope, genericValidationMessages, transientWriteFailureCode, retryableWriteMethods, transientWriteRetryBackoff, transientWriteRetryMaxAttempts, serviceVerify, ModelOptionKeys, Model;
    var __moduleName = context_1 && context_1.id;
    function normalizeStatusList(source) {
        const status = lodash_1.get(source, 'meta.status') ||
            lodash_1.get(source, 'payload.meta.status') ||
            lodash_1.get(source, 'payload.status') ||
            lodash_1.get(source, 'status') ||
            source;
        if (lodash_1.isArray(status)) {
            return status.filter((entry) => lodash_1.isObject(entry));
        }
        if (lodash_1.isObject(status) &&
            (lodash_1.isString(lodash_1.get(status, 'code')) ||
                lodash_1.isString(lodash_1.get(status, 'message')) ||
                lodash_1.isString(lodash_1.get(status, 'type')))) {
            return [status];
        }
        return [];
    }
    function parseXHRPayload(error) {
        if (error instanceof errorBase_1.ErrorBase) {
            return lodash_1.get(error, 'payload') || error;
        }
        if (lodash_1.isObject(error) && !lodash_1.isUndefined(lodash_1.get(error, 'response'))) {
            return lodash_1.get(error, 'response');
        }
        const responseText = lodash_1.get(error, 'responseText');
        return lodash_1.isString(responseText) && misc_1.isJSON(responseText) ? JSON.parse(responseText) : null;
    }
    function isGenericValidationMessage(message) {
        return genericValidationMessages.indexOf(message.trim()) !== -1;
    }
    function hasStatusCode(source, code) {
        return normalizeStatusList(source).some((status) => status.code === code);
    }
    function isRetryableTransientWritePayload(action, payload) {
        const method = (action || 'GET').toUpperCase();
        if (retryableWriteMethods.indexOf(method) === -1) {
            return false;
        }
        if (!payload) {
            return false;
        }
        return !!(lodash_1.get(payload, 'code') === transientWriteFailureCode ||
            hasStatusCode(payload, transientWriteFailureCode));
    }
    function isRetryableTransientWrite(action, error) {
        const payload = parseXHRPayload(error);
        if (!payload) {
            return false;
        }
        return isRetryableTransientWritePayload(action, payload);
    }
    function delay(ms) {
        return new Promise((resolve) => setTimeout(resolve, ms));
    }
    function getPreferredStatusMessage(source) {
        const statuses = normalizeStatusList(source);
        if (!statuses.length) {
            return null;
        }
        const messages = statuses
            .map((status) => lodash_1.isString(status.message) ? status.message.trim() : '')
            .filter((message) => !!message);
        if (!messages.length) {
            return null;
        }
        const specificMessage = messages.find((message) => !isGenericValidationMessage(message));
        return specificMessage || messages[0];
    }
    function hasNotableStatus(source) {
        return normalizeStatusList(source).some((status) => {
            const type = lodash_1.isString(status.type) ? status.type.toLowerCase() : '';
            return type === 'warning' || type === 'error' || status.code === 'VALIDATION';
        });
    }
    function hasUsablePayload(payload) {
        return (lodash_1.isArray(payload) && !!payload.length) || (lodash_1.isObject(payload) && !lodash_1.isArray(payload));
    }
    function isSoftStatusFailure(meta, payload) {
        return !!(lodash_1.get(meta, 'success') === false &&
            hasUsablePayload(payload) &&
            getPreferredStatusMessage(meta) &&
            hasNotableStatus(meta));
    }
    return {
        setters: [
            function (lodash_1_1) {
                lodash_1 = lodash_1_1;
            },
            function (stratus_1_1) {
                stratus_1 = stratus_1_1;
            },
            function (errorBase_1_1) {
                errorBase_1 = errorBase_1_1;
            },
            function (misc_1_1) {
                misc_1 = misc_1_1;
            },
            function (modelBase_1_1) {
                modelBase_1 = modelBase_1_1;
            },
            function (environment_1_1) {
                environment_1 = environment_1_1;
            },
            function (xhr_1_1) {
                xhr_1 = xhr_1_1;
            },
            function (injector_1_1) {
                injector_1 = injector_1_1;
            },
            function (collection_1_1) {
                collection_1 = collection_1_1;
            },
            function (toastify_js_1_1) {
                toastify_js_1 = toastify_js_1_1;
            }
        ],
        execute: function () {
            injector = injector_1.getInjector();
            genericValidationMessages = [
                'There were validation errors.'
            ];
            transientWriteFailureCode = 'API_TRANSIENT_WRITE_FAILURE';
            retryableWriteMethods = ['POST', 'PUT', 'PATCH', 'DELETE'];
            transientWriteRetryBackoff = [250, 750, 1500];
            transientWriteRetryMaxAttempts = 3;
            serviceVerify = async () => {
                return new Promise(async (resolve, _reject) => {
                    if ($rootScope) {
                        resolve(true);
                        return;
                    }
                    if (!injector) {
                        injector = injector_1.getInjector();
                    }
                    if (injector) {
                        $rootScope = injector.get('$rootScope');
                    }
                    if ($rootScope) {
                        resolve(true);
                        return;
                    }
                    setTimeout(() => {
                        if (environment_1.cookie('env')) {
                            console.log('wait for $rootScope service:', {
                                $rootScope
                            });
                        }
                        serviceVerify().then(resolve);
                    }, 250);
                });
            };
            exports_1("ModelOptionKeys", ModelOptionKeys = ["autoSave", "autoSaveInterval", "autoSaveHalt", "collection", "completed", "manifest", "serviceId", "stagger", "target", "targetSuffix", "toast", "type", "urlRoot", "urlSync", "watch", "withCredentials", "payload", "convoy", "headers", "ignoreKeys", "received"]);
            Model = class Model extends modelBase_1.ModelBase {
                name = 'Model';
                target = null;
                type = null;
                manifest = false;
                stagger = false;
                toast = true;
                identifier = null;
                urlRoot = '/Api';
                targetSuffix = null;
                serviceId = null;
                header = new modelBase_1.ModelBase();
                meta = new modelBase_1.ModelBase();
                route = new modelBase_1.ModelBase();
                collection = null;
                xhr;
                withCredentials = false;
                headers = {};
                pending = false;
                error = false;
                completed = false;
                saving = false;
                changedExternal = false;
                watch = true;
                status = null;
                autoSave = false;
                autoSaveInterval = 4000;
                autoSaveHalt = true;
                autoSaveTimeout = null;
                urlSync = false;
                bracket = {
                    match: /\[[\d+]]/,
                    search: /\[([\d+])]/g,
                    attr: /(^[^[]+)/
                };
                throttle = lodash_1.throttle(this.fetch, 1000);
                initialize = null;
                constructor(options = {}, attributes) {
                    super(attributes);
                    options = typeof options !== 'object' ? {} : options;
                    options.received = options.received || false;
                    lodash_1.extend(this, this.sanitizeOptions(options));
                    if (options.convoy) {
                        const convoy = misc_1.isJSON(options.convoy) ? JSON.parse(options.convoy) : options.convoy;
                        if (lodash_1.isObject(convoy)) {
                            this.meta.set(convoy.meta || {});
                            const payload = convoy.payload;
                            if (lodash_1.isObject(payload)) {
                                lodash_1.extend(this.data, payload);
                                this.completed = true;
                                options.received = true;
                            }
                            else {
                                console.error('malformed payload:', payload);
                            }
                        }
                        else {
                            console.error('malformed convoy:', convoy);
                        }
                    }
                    if (options.payload) {
                        const payload = misc_1.isJSON(options.payload) ? JSON.parse(options.payload) : options.payload;
                        if (lodash_1.isObject(payload)) {
                            lodash_1.extend(this.data, payload);
                            this.completed = true;
                            options.received = true;
                        }
                        else {
                            console.error('malformed payload:', payload);
                        }
                    }
                    this.header = new modelBase_1.ModelBase();
                    this.meta = new modelBase_1.ModelBase();
                    this.route = new modelBase_1.ModelBase();
                    if (!lodash_1.isEmpty(this.collection)) {
                        if (this.collection.target) {
                            this.target = this.collection.target;
                        }
                        if (this.collection.meta.has('api')) {
                            this.meta.set('api', this.collection.meta.get('api'));
                        }
                    }
                    this.recv = options.received ? lodash_1.cloneDeep(this.data) : {};
                    this.sent = {};
                    this.ignoreKeys = options.ignoreKeys || ['$$hashKey'];
                    if (this.target) {
                        this.urlRoot += '/' + misc_1.ucfirst(this.target);
                    }
                    const that = this;
                    this.initialize = lodash_1.once(this.initialize || function defaultInitializer() {
                        if (that.completed && (that.watch || that.autoSave)) {
                            that.watcher().then();
                        }
                        if (that.manifest && !that.getIdentifier()) {
                            that.sync('POST', that.meta.has('api') ? {
                                meta: that.meta.get('api'),
                                payload: {}
                            } : {}).catch(async (error) => {
                                console.error('MANIFEST:', error);
                                if (!that.toast) {
                                    return;
                                }
                                const errorMessage = that.errorMessage(error);
                                const formatMessage = errorMessage ? `: ${errorMessage}` : '.';
                                toastify_js_1.default({
                                    text: `Unable to Manifest ${that.target}${formatMessage}`,
                                    duration: 12000,
                                    close: true,
                                    stopOnFocus: true,
                                    style: {
                                        background: '#E14D45',
                                    }
                                }).showToast();
                                that.errorMessage(error);
                            });
                        }
                    });
                    if (!this.stagger) {
                        this.initialize();
                    }
                }
                resetXHRFlags() {
                    this.pending = false;
                    this.saving = false;
                }
                sanitizeOptions(options) {
                    const sanitizedOptions = {};
                    lodash_1.forEach(ModelOptionKeys, (key) => {
                        const data = lodash_1.get(options, key);
                        if (lodash_1.isUndefined(data)) {
                            return;
                        }
                        lodash_1.set(sanitizedOptions, key, data);
                    });
                    return sanitizedOptions;
                }
                getReadOnlyFields() {
                    const fields = this.meta.get('readOnlyFields');
                    return lodash_1.isArray(fields) ? fields.filter((field) => lodash_1.isString(field) && !!field) : [];
                }
                deletePath(obj, path) {
                    if (!obj || !lodash_1.isString(path)) {
                        return;
                    }
                    lodash_1.unset(obj, path);
                }
                pruneEmptyBranches(obj) {
                    if (!lodash_1.isObject(obj) || lodash_1.isArray(obj)) {
                        return lodash_1.isEmpty(obj);
                    }
                    const target = obj;
                    for (const key of Object.keys(target)) {
                        const value = target[key];
                        if (lodash_1.isObject(value) && !lodash_1.isArray(value)) {
                            const emptyChild = this.pruneEmptyBranches(value);
                            if (emptyChild) {
                                delete target[key];
                            }
                            continue;
                        }
                        if (lodash_1.isUndefined(value)) {
                            delete target[key];
                        }
                    }
                    return lodash_1.isEmpty(target);
                }
                sanitizeReadOnlyPatchPayload(payload) {
                    if (!lodash_1.isObject(payload)) {
                        return payload;
                    }
                    const sanitized = lodash_1.cloneDeep(payload);
                    const readOnlyFields = this.getReadOnlyFields();
                    if (!readOnlyFields.length) {
                        return sanitized;
                    }
                    lodash_1.forEach(readOnlyFields, (path) => {
                        this.deletePath(sanitized, path);
                    });
                    this.pruneEmptyBranches(sanitized);
                    return sanitized;
                }
                getSavablePatch() {
                    return this.sanitizeReadOnlyPatchPayload(lodash_1.cloneDeep(this.toPatch()));
                }
                async watcher() {
                    if (this.watching) {
                        return true;
                    }
                    this.watching = true;
                    if (!$rootScope) {
                        await serviceVerify();
                    }
                    $rootScope.$watch(() => this.data, (_newData, _priorData) => this.handleChanges(), true);
                }
                handleChanges(changeSet) {
                    const isUserChangeSet = lodash_1.isUndefined(changeSet);
                    if (isUserChangeSet) {
                        changeSet = super.handleChanges();
                        changeSet = this.sanitizeReadOnlyPatchPayload(changeSet);
                        if (!lodash_1.isEmpty(this.patch)) {
                            this.patch = this.sanitizeReadOnlyPatchPayload(this.patch);
                        }
                        this.changed = !lodash_1.isEmpty(this.patch);
                    }
                    if (!changeSet || lodash_1.isEmpty(changeSet)) {
                        return changeSet;
                    }
                    if (this.error && !this.completed && this.getIdentifier()) {
                        const action = isUserChangeSet ? 'save' : 'sync url for';
                        console.warn(`Blocked attempt to ${action} a persisted model that has not been fetched successfully.`);
                        return;
                    }
                    if (!isUserChangeSet) {
                        if (environment_1.cookie('env')) {
                            console.info('Attempting URL Sync for non-User ChangeSet:', changeSet);
                        }
                    }
                    if (this.urlSync) {
                        if (lodash_1.get(changeSet, 'id')) {
                            const newUrl = misc_1.setUrlParams({
                                id: lodash_1.get(changeSet, 'id') || this.getIdentifier()
                            });
                            if (newUrl !== document.location.href) {
                                window.location.replace(newUrl);
                            }
                        }
                        const version = misc_1.getAnchorParams('version');
                        const versionId = !lodash_1.isEmpty(version) ? parseInt(version, 10) : 0;
                        if (versionId && versionId !== lodash_1.get(changeSet, 'version.id')) {
                            if (environment_1.cookie('env')) {
                                console.warn('replacing version:', versionId);
                            }
                        }
                    }
                    if (!isUserChangeSet) {
                        return;
                    }
                    this.saveIdle();
                    this.throttleTrigger('change', this);
                    if (this.collection) {
                        this.collection.throttleTrigger('change', this);
                    }
                    return changeSet;
                }
                getIdentifier() {
                    return (this.identifier = this.get('id') || this.route.get('identifier') || this.identifier);
                }
                getType() {
                    return (this.type = this.type || this.target || 'orphan');
                }
                getHash() {
                    return this.getType() + (lodash_1.isNumber(this.getIdentifier()) ? this.getIdentifier().toString() : this.getIdentifier());
                }
                isNew() {
                    return !this.getIdentifier();
                }
                url() {
                    let url = this.getIdentifier() ? `${this.urlRoot}/${this.getIdentifier()}` : `${this.urlRoot}${this.targetSuffix || ''}`;
                    if (misc_1.getUrlParams('version')) {
                        url += url.includes('?') ? '&' : '?';
                        url += 'options[version]=' + misc_1.getUrlParams('version');
                    }
                    return url;
                }
                serialize(obj, chain) { return misc_1.serializeUrlParams(obj, chain); }
                sync(action, data, options) {
                    this.pending = true;
                    this.trigger('change', this);
                    if (this.collection) {
                        this.collection.pending = true;
                        this.collection.throttleTrigger('change');
                    }
                    this.sent = lodash_1.cloneDeep(this.data);
                    return new Promise(async (resolve, reject) => {
                        let successfulResponseMeta = null;
                        action = action || 'GET';
                        options = options || {};
                        const request = {
                            method: action,
                            url: this.url(),
                            headers: lodash_1.clone(this.headers),
                            withCredentials: this.withCredentials,
                        };
                        if (!lodash_1.isUndefined(data)) {
                            if (['GET', 'DELETE'].includes(action)) {
                                if (lodash_1.isObject(data) && Object.keys(data).length) {
                                    request.url += request.url.includes('?') ? '&' : '?';
                                    request.url += this.serialize(data);
                                }
                            }
                            else {
                                request.headers['Content-Type'] = 'application/json';
                                request.data = data;
                            }
                        }
                        if (environment_1.cookie('env')) {
                            console.log('Prototype:', request);
                        }
                        if (Object.prototype.hasOwnProperty.call(options, 'headers') && typeof options.headers === 'object') {
                            Object.keys(options.headers).forEach((headerKey) => {
                                request.headers[headerKey] = options.headers[headerKey];
                            });
                        }
                        const sendRequest = async (attempt = 1) => {
                            this.xhr = new xhr_1.XHR(request);
                            try {
                                const response = await this.xhr.send();
                                if (attempt < transientWriteRetryMaxAttempts &&
                                    isRetryableTransientWritePayload(request.method, response)) {
                                    const nextAttempt = attempt + 1;
                                    console.warn(`XHR: ${request.method} ${request.url} returned ${transientWriteFailureCode}; retrying ${nextAttempt} of ${transientWriteRetryMaxAttempts}.`, response);
                                    if (this.toast) {
                                        toastify_js_1.default({
                                            text: `The server was busy saving. Retrying ${nextAttempt} of ${transientWriteRetryMaxAttempts}...`,
                                            duration: 4000,
                                            close: true,
                                            stopOnFocus: true,
                                            style: {
                                                background: '#D9902F',
                                            }
                                        }).showToast();
                                    }
                                    await delay(transientWriteRetryBackoff[attempt - 1] || transientWriteRetryBackoff[transientWriteRetryBackoff.length - 1]);
                                    return sendRequest(nextAttempt);
                                }
                                return response;
                            }
                            catch (error) {
                                if (attempt < transientWriteRetryMaxAttempts &&
                                    isRetryableTransientWrite(request.method, error)) {
                                    const nextAttempt = attempt + 1;
                                    console.warn(`XHR: ${request.method} ${request.url} returned ${transientWriteFailureCode}; retrying ${nextAttempt} of ${transientWriteRetryMaxAttempts}.`, error);
                                    if (this.toast) {
                                        toastify_js_1.default({
                                            text: `The server was busy saving. Retrying ${nextAttempt} of ${transientWriteRetryMaxAttempts}...`,
                                            duration: 4000,
                                            close: true,
                                            stopOnFocus: true,
                                            style: {
                                                background: '#D9902F',
                                            }
                                        }).showToast();
                                    }
                                    await delay(transientWriteRetryBackoff[attempt - 1] || transientWriteRetryBackoff[transientWriteRetryBackoff.length - 1]);
                                    return sendRequest(nextAttempt);
                                }
                                throw error;
                            }
                        };
                        sendRequest().then((response) => {
                            this.status = this.xhr.status;
                            if (this.watch || this.autoSave) {
                                this.watcher();
                            }
                            const propagateError = () => {
                                this.error = true;
                                this.resetXHRFlags();
                                if (this.collection) {
                                    this.collection.pending = false;
                                }
                                this.trigger('error', this);
                                this.trigger('complete', this);
                                if (this.collection instanceof collection_1.Collection) {
                                    this.collection.throttleTrigger('change');
                                }
                            };
                            if (!lodash_1.isObject(response) && !lodash_1.isArray(response)) {
                                const error = new errorBase_1.ErrorBase({
                                    payload: response,
                                    message: `Invalid Payload: ${request.method} ${request.url}`
                                }, {});
                                propagateError();
                                reject(error);
                                return;
                            }
                            this.header.set(this.xhr.getAllResponseHeaders() || {});
                            successfulResponseMeta = response.meta || null;
                            const responseMeta = successfulResponseMeta || {};
                            this.meta.set(responseMeta);
                            this.route.set(response.route || {});
                            const payload = response.payload || response;
                            this.error = false;
                            if ((this.meta.has('success') && !this.meta.get('success') && !isSoftStatusFailure(responseMeta, payload))) {
                                this.error = true;
                            }
                            else if (lodash_1.isArray(payload) && payload.length) {
                                this.recv = lodash_1.head(payload);
                            }
                            else if (lodash_1.isObject(payload) && !lodash_1.isArray(payload)) {
                                this.recv = payload;
                            }
                            else {
                                if (!this.meta.has('status') && !this.meta.has('success')) {
                                    this.error = true;
                                }
                                console.warn(`Invalid Payload: ${request.method} ${request.url}`);
                            }
                            if (this.error) {
                                const error = new errorBase_1.ErrorBase({
                                    payload: response,
                                    message: `Invalid Payload: ${request.method} ${request.url}`
                                }, {});
                                propagateError();
                                reject(error);
                                return;
                            }
                            const incomingChangeSet = this.completed ? lodash_1.cloneDeep(misc_1.patch(this.recv, this.sent)) : {};
                            if (!lodash_1.isEmpty(incomingChangeSet)) {
                                if (environment_1.cookie('env')) {
                                    console.log('Incoming ChangeSet detected:', environment_1.cookie('debug_change_set')
                                        ? JSON.stringify(incomingChangeSet)
                                        : incomingChangeSet);
                                }
                                this.handleChanges(incomingChangeSet);
                            }
                            const intermediateData = lodash_1.cloneDeep(this.recv);
                            const intermediateChangeSet = lodash_1.cloneDeep(misc_1.patch(this.data, this.sent));
                            if (!lodash_1.isEmpty(intermediateChangeSet)) {
                                if (environment_1.cookie('env')) {
                                    console.log('Intermediate ChangeSet detected:', environment_1.cookie('debug_change_set')
                                        ? JSON.stringify(intermediateChangeSet)
                                        : intermediateChangeSet);
                                }
                                lodash_1.forEach(intermediateChangeSet, (element, key) => {
                                    lodash_1.set(intermediateData, key, element);
                                });
                            }
                            this.data = lodash_1.cloneDeep(intermediateData);
                            this.changed = false;
                            this.changedExternal = false;
                            this.saving = false;
                            this.handleChanges();
                            this.patch = {};
                            this.resetXHRFlags();
                            this.completed = true;
                            if (this.collection) {
                                this.collection.pending = false;
                            }
                            this.meta.clearTemp();
                            const statusMessage = getPreferredStatusMessage(response.meta || {});
                            if (this.toast && statusMessage && hasNotableStatus(response.meta || {})) {
                                toastify_js_1.default({
                                    text: statusMessage,
                                    duration: 12000,
                                    close: true,
                                    stopOnFocus: true,
                                    style: {
                                        background: '#D9902F',
                                    }
                                }).showToast();
                            }
                            this.trigger('success', this);
                            this.trigger('change', this);
                            this.trigger('complete', this);
                            if (this.collection instanceof collection_1.Collection) {
                                this.collection.throttleTrigger('change');
                            }
                            resolve(this.data);
                            return;
                        })
                            .catch((error) => {
                            if (successfulResponseMeta && lodash_1.get(successfulResponseMeta, 'success') === true) {
                                console.warn(`XHR: ${request.method} ${request.url} completed with API success but post-save handling failed.`, error);
                                this.error = false;
                                this.resetXHRFlags();
                                this.completed = true;
                                if (this.collection) {
                                    this.collection.pending = false;
                                }
                                const statusMessage = getPreferredStatusMessage(successfulResponseMeta);
                                if (this.toast && statusMessage && hasNotableStatus(successfulResponseMeta)) {
                                    toastify_js_1.default({
                                        text: statusMessage,
                                        duration: 12000,
                                        close: true,
                                        stopOnFocus: true,
                                        style: {
                                            background: '#D9902F',
                                        }
                                    }).showToast();
                                }
                                this.trigger('success', this);
                                this.trigger('change', this);
                                this.trigger('complete', this);
                                if (this.collection instanceof collection_1.Collection) {
                                    this.collection.throttleTrigger('change');
                                }
                                resolve(this.data);
                                return;
                            }
                            this.status = 500;
                            this.error = true;
                            this.resetXHRFlags();
                            console.error(`XHR: ${request.method} ${request.url}`, error);
                            reject(error);
                            return;
                        });
                    });
                }
                fetch(action, data, options) {
                    return new Promise(async (resolve, reject) => {
                        this.sync(action, data || this.meta.get('api'), options)
                            .then(resolve)
                            .catch(async (error) => {
                            this.status = 500;
                            this.error = true;
                            this.resetXHRFlags();
                            console.error('FETCH:', error);
                            if (!this.toast) {
                                reject(error);
                                return;
                            }
                            const errorMessage = this.errorMessage(error);
                            const formatMessage = errorMessage ? `: ${errorMessage}` : '.';
                            toastify_js_1.default({
                                text: `Unable to Fetch ${this.target}${formatMessage}`,
                                duration: 12000,
                                close: true,
                                stopOnFocus: true,
                                style: {
                                    background: '#E14D45',
                                }
                            }).showToast();
                            reject(error);
                            return;
                        });
                    });
                }
                save(options) {
                    this.saving = true;
                    options = options || {};
                    if (!lodash_1.isObject(options)) {
                        console.warn('invalid options supplied:', options);
                        options = {};
                    }
                    if (lodash_1.has(options, 'force') && options.force) {
                        options.patch = lodash_1.has(options, 'patch') ? options.patch : false;
                        return this.doSave(options);
                    }
                    if (!this.isNew() && (this.pending || !this.completed || lodash_1.isEmpty(this.getSavablePatch()))) {
                        console.warn(`Blocked attempt to save ${lodash_1.isEmpty(this.toPatch()) ? 'an empty payload' : 'a duplicate XHR'} to a persisted model.`);
                        return new Promise((resolve, _reject) => {
                            this.saving = false;
                            resolve(this.data);
                        });
                    }
                    return this.doSave(options);
                }
                doSave(options) {
                    options = options || {};
                    if (!lodash_1.isObject(options)) {
                        console.warn('invalid options supplied:', options);
                        options = {};
                    }
                    options.patch = lodash_1.has(options, 'patch') ? options.patch : true;
                    return new Promise(async (resolve, reject) => {
                        this.sync(this.getIdentifier() ? 'PUT' : 'POST', this.toJSON({
                            patch: options.patch
                        }))
                            .then(resolve)
                            .catch(async (error) => {
                            this.error = true;
                            this.resetXHRFlags();
                            console.error('SAVE:', error);
                            if (!this.toast) {
                                reject(error);
                                return;
                            }
                            const errorMessage = this.errorMessage(error) || getPreferredStatusMessage(this.meta.data);
                            toastify_js_1.default({
                                text: errorMessage || `Unable to Save ${this.target}.`,
                                duration: 12000,
                                close: true,
                                stopOnFocus: true,
                                style: {
                                    background: '#E14D45',
                                }
                            }).showToast();
                            reject(error);
                            return;
                        });
                    });
                }
                saveIdle() {
                    if (this.autoSaveTimeout) {
                        clearTimeout(this.autoSaveTimeout);
                    }
                    if (this.pending || !this.completed || this.isNew() || lodash_1.isEmpty(this.getSavablePatch())) {
                        return;
                    }
                    if (this.autoSaveHalt && !this.autoSave) {
                        return;
                    }
                    this.autoSaveTimeout = setTimeout(() => {
                        if (!this.autoSaveHalt && !this.autoSave) {
                            this.saveIdle();
                            return;
                        }
                        this.save().then();
                    }, this.autoSaveInterval);
                }
                throttleSave() {
                    return new Promise((resolve, reject) => {
                        const request = this.throttle();
                        console.log('throttle request:', request);
                        request.then((data) => {
                            console.log('throttle received:', data);
                            resolve(data);
                        }).catch(reject);
                    });
                }
                toJSON(options) {
                    options = options || {};
                    if (!lodash_1.isObject(options)) {
                        options = {};
                    }
                    options.patch = (options.patch && !this.isNew());
                    let data = super.toJSON(options);
                    if (options.patch) {
                        data = this.sanitizeReadOnlyPatchPayload(data);
                    }
                    const metaData = this.meta.get('api');
                    if (metaData) {
                        data = {
                            meta: metaData,
                            payload: data
                        };
                    }
                    return data;
                }
                buildPath(path) {
                    const acc = [];
                    if (!lodash_1.isString(path)) {
                        return acc;
                    }
                    let cur;
                    let search;
                    lodash_1.forEach(path.split('.'), (link) => {
                        if (link.match(this.bracket.match)) {
                            cur = this.bracket.attr.exec(link);
                            if (cur !== null) {
                                acc.push(cur[1]);
                                cur = null;
                            }
                            else {
                                cur = false;
                            }
                            search = this.bracket.search.exec(link);
                            while (search !== null) {
                                if (cur !== false) {
                                    cur = parseInt(search[1], 10);
                                    if (!isNaN(cur)) {
                                        acc.push(cur);
                                    }
                                    else {
                                        cur = false;
                                    }
                                }
                                search = this.bracket.search.exec(link);
                            }
                        }
                        else {
                            acc.push(link);
                        }
                    });
                    return acc;
                }
                get(attr) {
                    if (typeof attr !== 'string' || !this.data || typeof this.data !== 'object') {
                        return undefined;
                    }
                    return lodash_1.get(this.data, attr);
                }
                find(attr, key, value) {
                    if (typeof attr === 'string') {
                        attr = this.get(attr);
                    }
                    return !lodash_1.isArray(attr) ? attr : attr.find((obj) => obj[key] === value);
                }
                set(attr, value) {
                    if (!attr) {
                        console.warn('No attr for model.set()!');
                        return this;
                    }
                    if (typeof attr === 'object') {
                        lodash_1.forEach(attr, (v, k) => this.setAttribute(k, v));
                        return this;
                    }
                    this.setAttribute(attr, value);
                    return this;
                }
                setAttribute(attr, value) {
                    if (typeof attr !== 'string') {
                        console.warn('Malformed attr for model.setAttribute()!');
                        return false;
                    }
                    lodash_1.set(this.data, attr, value);
                    this.throttleTrigger('change', this);
                    this.throttleTrigger(`change:${attr}`, value);
                }
                toggle(attribute, item, options) {
                    if (typeof options === 'object' &&
                        !lodash_1.isUndefined(options.multiple) &&
                        lodash_1.isUndefined(options.strict)) {
                        options.strict = true;
                    }
                    options = lodash_1.extend({
                        multiple: true
                    }, lodash_1.isObject(options) ? options : {});
                    const request = attribute.split('[].');
                    let target = this.get(request.length > 1 ? request[0] : attribute);
                    if (lodash_1.isUndefined(target) ||
                        (options.strict && lodash_1.isArray(target) !==
                            options.multiple)) {
                        target = options.multiple ? [] : null;
                        this.set(request.length > 1 ? request[0] : attribute, target);
                    }
                    if (lodash_1.isArray(target)) {
                        if (lodash_1.isUndefined(item)) {
                            this.set(attribute, null);
                        }
                        else if (!this.exists(attribute, item)) {
                            target.push(item);
                        }
                        else {
                            lodash_1.forEach(target, (element, key) => {
                                const child = (request.length > 1 &&
                                    typeof element === 'object' && request[1] in element)
                                    ? element[request[1]]
                                    : element;
                                const childId = (typeof child === 'object' && child.id)
                                    ? child.id
                                    : child;
                                const itemId = (typeof item === 'object' && item.id)
                                    ? item.id
                                    : item;
                                if (childId === itemId || (lodash_1.isString(childId) && lodash_1.isString(itemId) && misc_1.strcmp(childId, itemId) === 0)) {
                                    target.splice(key, 1);
                                }
                            });
                        }
                    }
                    else if (typeof target === 'object' || typeof target === 'number') {
                        this.set(attribute, !this.exists(attribute, item) ? item : null);
                    }
                    else if (lodash_1.isUndefined(item)) {
                        this.set(attribute, !target);
                    }
                    return this.get(attribute);
                }
                pluck(attr) {
                    if (typeof attr !== 'string' || attr.indexOf('[].') === -1) {
                        return this.get(attr);
                    }
                    const request = attr.split('[].');
                    if (request.length <= 1) {
                        return undefined;
                    }
                    attr = this.get(request[0]);
                    if (!attr || !lodash_1.isArray(attr)) {
                        return undefined;
                    }
                    const list = lodash_1.filter(lodash_1.map(attr, (element) => lodash_1.get(element, request[1])));
                    return list.length ? list : undefined;
                }
                exists(attribute, item) {
                    if (!item) {
                        attribute = this.get(attribute);
                        return typeof attribute !== 'undefined' && attribute;
                    }
                    if (typeof attribute === 'string' && item) {
                        attribute = this.pluck(attribute);
                        if (lodash_1.isArray(attribute)) {
                            return typeof attribute.find((element) => element === item || ((typeof element === 'object' && element.id && element.id === item) || lodash_1.isEqual(element, item))) !== 'undefined';
                        }
                        return attribute === item || (typeof attribute === 'object' && attribute.id && (lodash_1.isEqual(attribute, item) || attribute.id === item));
                    }
                    return false;
                }
                destroy() {
                    if (this.isNew()) {
                        return new Promise((resolve, _reject) => {
                            this.throttleTrigger('change');
                            if (this.collection) {
                                this.collection.remove(this);
                            }
                            resolve(this.data);
                        });
                    }
                    return new Promise(async (_resolve, reject) => {
                        let deleteData = {};
                        if (!lodash_1.isEmpty(this.meta.get('api'))) {
                            deleteData = this.meta.get('api');
                        }
                        this.sync('DELETE', deleteData)
                            .then((_data) => {
                            if (this.error) {
                                reject(this.error);
                                return;
                            }
                            this.throttleTrigger('change');
                            if (this.collection) {
                                this.collection.remove(this);
                            }
                            _resolve(this.data);
                        })
                            .catch(async (error) => {
                            this.error = true;
                            this.resetXHRFlags();
                            console.error('DESTROY:', error);
                            if (!this.toast) {
                                reject(error);
                                return;
                            }
                            const errorMessage = this.errorMessage(error);
                            const formatMessage = errorMessage ? `: ${errorMessage}` : '.';
                            toastify_js_1.default({
                                text: `Unable to Delete ${this.target}${formatMessage}`,
                                duration: 12000,
                                close: true,
                                stopOnFocus: true,
                                style: {
                                    background: '#E14D45',
                                }
                            }).showToast();
                            reject(error);
                            return;
                        });
                    });
                }
                errorMessage(error) {
                    if (error instanceof errorBase_1.ErrorBase) {
                        console.error(`[${error.code}] ${error.message}`, error);
                        const message = getPreferredStatusMessage(error);
                        if (message) {
                            return message;
                        }
                        return error.code !== 'Internal' ? error.message : null;
                    }
                    const digest = (error.responseText && misc_1.isJSON(error.responseText)) ? JSON.parse(error.responseText) : null;
                    if (!digest) {
                        return null;
                    }
                    const message = getPreferredStatusMessage(digest) || lodash_1.get(digest, 'error.exception[0].message') || null;
                    if (!message) {
                        return null;
                    }
                    if (!environment_1.cookie('env') && lodash_1.has(digest, 'error.exception[0].message')) {
                        console.error('[xhr] server:', message);
                        return null;
                    }
                    return message;
                }
            };
            exports_1("Model", Model);
            stratus_1.Stratus.Services.Model = [
                '$provide', ($provide) => {
                    $provide.factory('Model', [
                        () => {
                            return Model;
                        }
                    ]);
                }
            ];
            stratus_1.Stratus.Data.Model = Model;
        }
    };
});

//# sourceMappingURL=model.js.map