Pocket/PocketExtended.js

// providing jsdocs intellisense
// eslint-disable-next-line no-unused-vars
const ProbeModel = require("../Probe/Probe")

// eslint-disable-next-line no-unused-vars
const ProjectPayloadModel = require("../Models/ProjectPayloadModel")

// @ts-ignore
const { onerror, warn, isPromise, validID, isString } = require("x-utils-es/umd")
// const sq = require('simple-q') // nice and simple promise/defer by `eaglex.net`
// const PocketLibs = require('./Pocket.libs')
const PocketModule = require("./Pocket.module")

/**
 * @class
 */
class PocketModuleExt extends PocketModule {
    constructor(opts, debug) {
        super(opts, debug)

        /**
         * @borrows $payload
         * @function $payload an alias on $payload(...)
         */
        this.$project = this.$payload
    }

    /**
     * - delete completed `pocketSet`
     */
    deletePocketSet(id) {
        if (!id) return
        if (Object.values(this.pocket).length) {
            for (let poc of Object.values(this.pocket)) {
                if (this._$cached_data[poc.id]) delete this._$cached_data[poc.id]
                if (poc.id.includes(id)) delete this.pocket[poc.id]
            }
        }
        if (this.payloadData[id]) delete this.payloadData[id]
        if (this._ready[id]) delete this._ready[id]

        // these  two are together
        if (this._projectSetDispatcher[id] !== undefined) delete this._projectSetDispatcher[id]
        if (this._projectSetAsync[id]) delete this._projectSetAsync[id]
        if (this._lastFilterList[id]) delete this._lastFilterList[id]

        // from PocketArchitect dynamicly assigned
        try {
            // @ts-ignore
            if (this.architectConfig[id]) delete this.architectConfig[id]
        } catch (err) {
            // blah
        }

        // empty self
        this.clearStoreTransfers(id)
    }
    /**
     * removes all Probes and references relating to `projectID`
     * @param {*} projectID
     */
    $removeProject(projectID) {
        projectID = !isString(projectID) ? "" : projectID
        projectID = this.lastProjectID(projectID) // also updates last selector reference
        this.deletePocketSet(projectID)
        return this
    }

    /**
     * - you can also use it on concurrent payloads to existing `projectID`, once initial project is created every other call will update each Probe{}.data/status, based on payloadData
     * @param {ProjectPayloadModel | Promise<ProjectPayloadModel>} data required
     * @param {*} async `override current opts.sync for this payload`
     * @param {"new"| "update"} type optional, new/update, `update`: if we call on an existing project we can update `data/status properties` of all assigned tasks at once
     * 
     * - `initialize new payload, for as many tasks`
     * - `sets a multi task with instructions:`
     * - `data = {
            id: '', // 1 id for all tasks
            tasks: [{ taskName: '', data: '', campaign: '' }]
        }`

    * - `call distributor and setDefer`
    * 
    */
    $payload(data, async, type) {
        const returnAs = (val) => {
            this.d = val
            return this
        }
        const asAsync = async !== undefined ? async : this.async // override if set
        if (asAsync && isPromise(data)) {
            return returnAs(
                // @ts-ignore
                data.then(
                    (z) => this.payload(z, false, type),
                    (err) => err
                )
            )
        }
        // @ts-ignore
        if (!asAsync && !isPromise(data)) return returnAs(this.payload(data, false, type))
        else {
            if (this.debug) onerror("[pocket]", `[payload] with opts.async=true, data must be a promise, or do not set async when not a promise`)
            if (asAsync) return returnAs(Promise.reject())
            else return returnAs(false)
        }
    }

    /**
     * memberof PocketModule
     * - resolves currently active `$payload(...)`
     * - `after completion of Pocket, instance data for all Probes is deleted`
     * - can be called even before project was declared thanks to callback dispatcher `$projectSetAsync()`
     * @param {*} payloadID ,required
     * @param { boolean?}  allowsMultiple optional, when set to true will allow multiple calls to resolved data
     * @param { boolean?} strict if true will check {payloadID} exists
     * @returns {PocketModule | {d:Promise<any>}}
     */
    $ready(payloadID, allowsMultiple = false, strict = false) {
        try {
            /**
             *
             * @param {Promise<ProbeModel[]>} val
             */
            const returnAs = (val) => {
                this.d = val
                if (this.d && !allowsMultiple) {
                    this.d.catch((err) => {
                        if (!this.disableWarnings) warn("[pocket]", err)
                    })
                }
                return this
            }

            // soft validation for non existent `payloadID` if called before declaration of a project
            let _payloadID = this.lastProjectID(payloadID, null, strict === true ? "strict" : null)
            if (!payloadID && _payloadID) payloadID = _payloadID // grab last assigned id incase provided none

            // in case it was called the second time, when already resolved!
            if (this.projectsCache[payloadID] === "complete" && !allowsMultiple) {
                return returnAs(Promise.reject(`[$ready] project: ${payloadID} already complete`))
            }

            if (this._ready_method_set[payloadID] !== undefined && !allowsMultiple) {
                if (this._ready_method_set[payloadID] === true) {
                    return returnAs(Promise.reject(`[$ready] project: ${payloadID} already complete, cannot recall same $ready, ALLOW_MULTIPLE_FALSE`))
                }

                if (this._ready_method_set[payloadID] === false) {
                    return returnAs(Promise.reject(`[$ready] project: ${payloadID} you already declared $ready somewhere else, this call is ignored, ALLOW_MULTIPLE_FALSE`))
                }
            }

            if (!_payloadID) {
                return returnAs(Promise.reject(`payloadID must be set`))
            }
            // we wrap it if on ready project so it allows declaring `${$ready()}` even before $project was created, cool ha!
            const p = this.$projectSetAsync(_payloadID).then(({ projectID }) => {
                return this.ready(projectID).then((z) => {
                    // NOTE to help problems with loops and using chaining with last selector
                    // will gradually delete project with specified timeout
                    if (!this.deleteWithDelay) this.deletePocketSet(projectID)
                    else {
                        setTimeout(() => {
                            this.deletePocketSet(projectID)
                        }, this.deleteWithDelay)
                    }

                    this.projectsCache[projectID] = "complete"
                    this._ready_method_set[_payloadID] = true
                    return z
                }, Promise.reject)
            }, Promise.reject)

            this._ready_method_set[_payloadID] = false
            return returnAs(p)
        } catch (error) {
            if (!this.disableWarnings) onerror("[pocket]", error)
        }
    }
}

module.exports = PocketModuleExt