TM dat

Nested, type secure and auto saving data proxy on Tampermonkey.

As of 12.07.2021. See ბოლო ვერსია.

ეს სკრიპტი არ უნდა იყოს პირდაპირ დაინსტალირებული. ეს ბიბლიოთეკაა, სხვა სკრიპტებისთვის უნდა ჩართეთ მეტა-დირექტივაში // @require https://update.greasyfork.org/scripts/429255/949653/TM%20dat.js.

// ==UserScript==
// @name         TM dat
// @namespace    https://icelava.root
// @version      0.1
// @description  Nested, type secure and auto saving data proxy on Tampermonkey.
// @author       ForkKILLET
// @match        localhost:1633
// @noframes
// @icon         data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==
// @grant        unsafeWindow
// @grant        GM_getValue
// @grant        GM_listValues
// @grant        GM_setValue
// ==/UserScript==

"use strict"

const err = (t, m) => { throw window[t](`[TM dat] ${m}`) }

const type_dat = v =>
    v === null          ? "null"   :
    v instanceof Array  ? "array"  :
    v instanceof RegExp ? "regexp" :
    typeof v

type_dat.convert = {
    string_number:  v => + v,
    string_regexp:  v => v,
    number_string:  v => "" + v,
    number_boolean: v => !! v
}

const proxy_dat = (dat, scm, oldRoot, old = oldRoot) => {
    const lvs = {}
    for (let k in scm.lvs) {
        const s = scm.lvs[k]
        s.path = (scm.path ?? "") + (s.root ? "!" : ".") + k
        s.pathRoot = s.root ? "!" + s.path : scm.pathRoot ?? k
        s.raw = (s.root ? null : scm.raw) ?? (() => dat[k])

        switch (s.ty) {
        case "object":
            lvs[k] = proxy_dat(dat[k] = {}, s, oldRoot, old[k])
            break
        default:
            lvs[k] = dat[k] = (s.root ? oldRoot[s.pathRoot] : old[k]) ?? s.dft
            break
        }
    }
    return new Proxy(lvs, {
        get: (_, k) => lvs[k],
        set: (_, k, v) => {
            const s = scm.lvs[k]
            const eF = `Field @ ${s.path}`, eS = "Failed strictly."
            if (s.locked) err("TypeError", eF + ` is locked, but was attempted to write.`)

            const ty = type_dat(v)
            if (s.ty !== "any" && ty !== s.ty) {
                const eM = eF + ` requires type ${s.ty}, but got ${ty}: ${v}. `
                if (s.strict) err("TypeError", eM + eS)
                const f = type_dat.convert[`${ty}_${s.ty}`]
                if (f) v = f(v)
                else err("TypeError", eM + "Failed to convert.")
            }

            if (s.ty === "number") {
                let eR = eF + ` requires to be in the range [ ${s.min ??= -Infinity}, ${s.max ??= +Infinity} ], but got ${v}. `
                if (v < s.min || v > s.max) err("RangeError", eR)

                if (s.int) {
                    if (s.strict) err("RangeError", eF + ` requires to be an integer. ` + eS)
                    v = ~~ v
                }
            }

            lvs[k] = dat[k] = v
            if (s.quick || s.root) GM_setValue(s.pathRoot, JSON.stringify(s.raw()))
        }
    })
}

const load_dat = (lvs, autoSave) => {
    const dat = {}; load_dat.dat = dat
    if (autoSave) window.addEventListener("beforeunload", () => save_dat())
    return proxy_dat(dat, { lvs },
        GM_listValues().reduce((o, k) => (
            o[k] = JSON.parse(GM_getValue(k) ?? "null"), o
        ), {})
    )
}

const save_dat = (dat = load_dat.dat) => {
    Object.keys(dat).forEach(k => GM_setValue(k, JSON.stringify(dat[k])))
}

if (location.host === "localhost:1633") Object.assign(unsafeWindow, {
    TM_dat: { type_dat, proxy_dat, load_dat, save_dat }
})