{{breadcrumbs}}
← BACK TO HUB
HUB / lib_bejson_state.js

lib_bejson_state.js

Runtime
JavaScript
Category
JavaScript
Path
/storage/emulated/0/Projects/Management/Libraries/js/lib_bejson_state.js
FILE // lib_bejson_state.js
/*
Library:     lib_bejson_state.js
MFDB Version: 1.3.1
Format_Creator: Elton Boehnen
Status:      OFFICIAL - v1.3.1
Date:        2026-05-06
*/

/**
 * lib_bejson_state.js
 * Status: OFFICIAL — BEJSON-Core/Lib (v1.4)
 * Version: 1.4 OFFICIAL
 * Date: 2026-05-03
 */
window.BEJSON = window.BEJSON || {};

class BEJSONState {
    constructor(initialState = {}, options = {}) {
        this.schema_name = options.name || "BEJSONState";
        this.bejson = BEJSON.BEJSON.create104db(this.schema_name, ["StateNode", "History"], [
            { name: "Record_Type_Parent", type: "string" },
            { name: "key", type: "string", Record_Type_Parent: "StateNode" },
            { name: "value", type: "string", Record_Type_Parent: "StateNode" },
            { name: "timestamp", type: "string", Record_Type_Parent: "History" },
            { name: "snapshot", type: "string", Record_Type_Parent: "History" }
        ], []);

        this._listeners = new Map();
        this._historyIndex = -1;
        this._activeEffect = null;
        this._dependencyGraph = new Map(); // path -> Set of effects

        // Reactive Proxy
        this.state = this._createProxy(initialState, '');
        
        // Initialize BEJSON values from initial state
        this._syncToBEJSON();
        this._saveHistory();
    }

    _createProxy(target, path) {
        const self = this;
        return new Proxy(target, {
            get(obj, prop) {
                const fullPath = path ? `${path}.${prop}` : prop;
                if (self._activeEffect) {
                    if (!self._dependencyGraph.has(fullPath)) self._dependencyGraph.set(fullPath, new Set());
                    self._dependencyGraph.get(fullPath).add(self._activeEffect);
                }
                const value = obj[prop];
                if (value && typeof value === 'object') return self._createProxy(value, fullPath);
                return value;
            },
            set(obj, prop, value) {
                const fullPath = path ? `${path}.${prop}` : prop;
                const oldValue = obj[prop];
                if (oldValue !== value) {
                    obj[prop] = value;
                    self._syncToBEJSON();
                    self._saveHistory();
                    self._notify(fullPath, value, oldValue);
                    self._triggerEffects(fullPath);
                }
                return true;
            }
        });
    }

    _syncToBEJSON() {
        // Clear StateNodes
        this.bejson.Values = this.bejson.Values.filter(r => r[0] !== "StateNode");
        // Flatten and add
        for (const [key, value] of Object.entries(this.state)) {
            this.bejson.Values.push(["StateNode", key, JSON.stringify(value), null, null]);
        }
    }

    _saveHistory() {
        const snapshot = JSON.stringify(this.state);
        this.bejson.Values.push(["History", null, null, new Date().toISOString(), snapshot]);
        const historyRows = this.bejson.Values.filter(r => r[0] === "History");
        this._historyIndex = historyRows.length - 1;
    }

    _notify(path, newValue, oldValue) {
        if (this._listeners.has(path)) this._listeners.get(path).forEach(cb => cb(newValue, oldValue, path));
        if (this._listeners.has('*')) this._listeners.get('*').forEach(cb => cb(newValue, oldValue, path));
    }

    _triggerEffects(path) {
        if (this._dependencyGraph.has(path)) this._dependencyGraph.get(path).forEach(effect => effect());
        // Handle nested paths (e.g., user.name triggers user)
        const parts = path.split('.');
        let currentPath = '';
        for (let i = 0; i < parts.length - 1; i++) {
            currentPath += (currentPath ? '.' : '') + parts[i];
            if (this._dependencyGraph.has(currentPath)) this._dependencyGraph.get(currentPath).forEach(e => e());
        }
    }

    subscribe(path, callback) {
        if (!this._listeners.has(path)) this._listeners.set(path, new Set());
        this._listeners.get(path).add(callback);
        return () => this._listeners.get(path).delete(callback);
    }

    effect(fn) {
        const runner = () => {
            this._activeEffect = runner;
            fn(this.state);
            this._activeEffect = null;
        };
        runner();
    }

    undo() {
        const historyRows = this.bejson.Values.filter(r => r[0] === "History");
        if (this._historyIndex <= 0) return false;
        this._historyIndex--;
        this._restore(JSON.parse(historyRows[this._historyIndex][4]));
        return true;
    }

    _restore(snapshot) {
        Object.keys(this.state).forEach(k => delete this.state[k]);
        Object.assign(this.state, snapshot);
        this._notify('*', this.state, null);
    }
}

BEJSON.State = BEJSONState;