Storage

Simple, prefixed localStorage with auto-serialisation and model persistence.

Basic Operations

Store and retrieve data with automatic JSON serialisation. All keys are prefixed with domma: to avoid collisions.

get()

set()

remove()

has()

clear()

keys()

getAll()

setAll()

size()

totalSize()

isAvailable()

// Set values (auto-stringify)
S.set('user', { name: 'Alice', role: 'admin' });
S.set('preferences', ['dark', 'compact']);
S.set('count', 42);

// Get values (auto-parse)
S.get('user');           // { name: 'Alice', role: 'admin' }
S.get('preferences');    // ['dark', 'compact']
S.get('missing', []);    // Default value if not found

// Check and manage
S.has('user');           // true
S.keys();                // ['user', 'preferences', 'count']
S.remove('count');
S.clear();               // Clear only Domma keys
Try It
Click a button to see result
<div class="grid grid-cols-2 gap-4">
    <div>
        <div class="form-group">
            <label>Key</label>
            <input type="text" id="storage-key" class="form-input" value="test-key">
        </div>
        <div class="form-group">
            <label>Value (JSON)</label>
            <textarea id="storage-value" class="form-input" rows="3">{"message": "Hello, Domma!"}</textarea>
        </div>
        <div class="flex gap-2">
            <button type="button" class="btn btn-primary" id="btn-set">Set</button>
            <button type="button" class="btn btn-secondary" id="btn-get">Get</button>
            <button type="button" class="btn btn-outline" id="btn-remove">Remove</button>
            <button type="button" class="btn btn-danger" id="btn-clear">Clear All</button>
        </div>
    </div>
    <div>
        <label class="font-semibold mb-2 block">Result</label>
        <div class="storage-demo-result" id="storage-result">Click a button to see result</div>
    </div>
</div>
/**
 * Try It - from public/showcase/storage.
 */
export function mount(root) {
    const pageRoot = $(root).closest('.sx-showcase').get(0) || root;
        // ============================================
        // Basic Storage Demo
        // ============================================
    const $result = $(root).find('#storage-result');
    function showResult(data) {
            $result.text(JSON.stringify(data, null, 2));
        }
    $(root).find('#btn-set').on('click', function () {
            const key = $(root).find('#storage-key').val();
            let value;
            try {
                value = JSON.parse($(root).find('#storage-value').val());
            } catch (e) {
                value = $(root).find('#storage-value').val();
            }

            const success = S.set(key, value);
            showResult({action: 'set', key, success, value});
            refreshInspector();
        });
    $(root).find('#btn-get').on('click', function () {
            const key = $(root).find('#storage-key').val();
            const value = S.get(key, null);
            showResult({action: 'get', key, value, found: value !== null});
        });
    $(root).find('#btn-remove').on('click', function () {
            const key = $(root).find('#storage-key').val();
            const success = S.remove(key);
            showResult({action: 'remove', key, success});
            refreshInspector();
        });
    $(root).find('#btn-clear').on('click', function () {
            const count = S.clear();
            showResult({action: 'clear', keysCleared: count});
            refreshInspector();
            updatePrefsDisplay();
        });
        // ============================================
        // Model Persistence Demo
        // ============================================
    const prefsModel = M.create({
            theme: {type: 'string', default: 'light'},
            fontSize: {type: 'number', default: 14, min: 10, max: 24},
            notifications: {type: 'boolean', default: true}
        }, {}, {persist: 'demo-prefs'});
    function updatePrefsDisplay() {
            const data = prefsModel.toJSON();
            $(pageRoot).find('#prefs-state').text(JSON.stringify(data, null, 2));

            // Update form inputs to match model
            $(pageRoot).find('#pref-theme').val(data.theme);
            $(pageRoot).find('#pref-fontsize').val(data.fontSize);
            $(pageRoot).find('#pref-notifications').val(String(data.notifications));
        }
        // ============================================
        // Storage Inspector
        // ============================================
    function formatBytes(bytes) {
            if (bytes === 0) return '0 B';
            const k = 1024;
            const sizes = ['B', 'KB', 'MB'];
            const i = Math.floor(Math.log(bytes) / Math.log(k));
            return parseFloat((bytes / Math.pow(k, i)).toFixed(1)) + ' ' + sizes[i];
        }
    function refreshInspector() {
            const keys = S.keys();
            const totalSize = S.totalSize();
            const available = S.isAvailable();

            // Update stats
            $(pageRoot).find('#stat-keys').text(keys.length);
            $(pageRoot).find('#stat-size').text(formatBytes(totalSize));
            $(pageRoot).find('#stat-available').text(available ? 'Yes' : 'No');

            // Update inspector
            const $inspector = $(pageRoot).find('#storage-inspector');

            if (keys.length === 0) {
                $inspector.html('<p class="text-muted">No data stored</p>');
                return;
            }

            let html = '';
            keys.forEach(key => {
                const value = S.get(key);
                const displayValue = JSON.stringify(value);
                html += `
                    <div class="storage-item-style">
                        <span class="storage-key-style">${_.escape(key)}</span>
                        <span class="storage-value-style">${_.escape(displayValue)}</span>
                    </div>
                `;
            });

            $inspector.html(html);
        }
}

Model Persistence

Models can automatically persist to localStorage. Data is saved on every change and restored when the model is created.

// Create a persistent model
const settings = M.create({
    theme: { type: 'string', default: 'light' },
    fontSize: { type: 'number', default: 14 },
    notifications: { type: 'boolean', default: true }
}, {}, { persist: 'app-settings' });

// Changes auto-save to localStorage
settings.set('theme', 'dark');
settings.set('fontSize', 16);

// On page reload, data is automatically restored!

// Manual control
settings.save();           // Force save
settings.load();           // Force reload from storage
settings.clearStorage();   // Remove from localStorage
settings.reset(true);      // Reset and clear storage
Persistent Settings

Change these settings and refresh the page - they'll persist!

Loading...
<p class="text-muted mb-4">Change these settings and refresh the page - they'll persist!</p>

<div class="grid grid-cols-3 gap-4 mb-4">
    <div class="form-group">
        <label>Theme</label>
        <select id="pref-theme" class="form-select">
            <option value="light">Light</option>
            <option value="dark">Dark</option>
        </select>
    </div>
    <div class="form-group">
        <label>Font Size</label>
        <input type="number" id="pref-fontsize" class="form-input" min="10" max="24" value="14">
    </div>
    <div class="form-group">
        <label>Notifications</label>
        <select id="pref-notifications" class="form-select">
            <option value="true">Enabled</option>
            <option value="false">Disabled</option>
        </select>
    </div>
</div>

<div class="flex gap-2">
    <button type="button" class="btn btn-outline" id="btn-reset-prefs">Reset to Defaults</button>
    <button type="button" class="btn btn-danger btn-sm" id="btn-clear-prefs">Clear Storage</button>
</div>

<div class="storage-demo-result mt-4" id="prefs-state">Loading...</div>
/**
 * Persistent Settings - from public/showcase/storage.
 */
export function mount(root) {
    const pageRoot = $(root).closest('.sx-showcase').get(0) || root;
        // ============================================
        // Model Persistence Demo
        // ============================================
    const prefsModel = M.create({
            theme: {type: 'string', default: 'light'},
            fontSize: {type: 'number', default: 14, min: 10, max: 24},
            notifications: {type: 'boolean', default: true}
        }, {}, {persist: 'demo-prefs'});
    function updatePrefsDisplay() {
            const data = prefsModel.toJSON();
            $(root).find('#prefs-state').text(JSON.stringify(data, null, 2));

            // Update form inputs to match model
            $(root).find('#pref-theme').val(data.theme);
            $(root).find('#pref-fontsize').val(data.fontSize);
            $(root).find('#pref-notifications').val(String(data.notifications));
        }
        // Bind form inputs to model
    $(root).find('#pref-theme').on('change', function () {
            prefsModel.set('theme', $(this).val());
            updatePrefsDisplay();
            refreshInspector();
        });
    $(root).find('#pref-fontsize').on('input', function () {
            const val = parseInt($(this).val(), 10);
            if (val >= 10 && val <= 24) {
                prefsModel.set('fontSize', val);
                updatePrefsDisplay();
                refreshInspector();
            }
        });
    $(root).find('#pref-notifications').on('change', function () {
            prefsModel.set('notifications', $(this).val() === 'true');
            updatePrefsDisplay();
            refreshInspector();
        });
    $(root).find('#btn-reset-prefs').on('click', function () {
            prefsModel.reset();
            updatePrefsDisplay();
            refreshInspector();
        });
    $(root).find('#btn-clear-prefs').on('click', function () {
            prefsModel.reset(true);
            updatePrefsDisplay();
            refreshInspector();
        });
        // Initial display
    updatePrefsDisplay();
        // ============================================
        // Storage Inspector
        // ============================================
    function formatBytes(bytes) {
            if (bytes === 0) return '0 B';
            const k = 1024;
            const sizes = ['B', 'KB', 'MB'];
            const i = Math.floor(Math.log(bytes) / Math.log(k));
            return parseFloat((bytes / Math.pow(k, i)).toFixed(1)) + ' ' + sizes[i];
        }
    function refreshInspector() {
            const keys = S.keys();
            const totalSize = S.totalSize();
            const available = S.isAvailable();

            // Update stats
            $(pageRoot).find('#stat-keys').text(keys.length);
            $(pageRoot).find('#stat-size').text(formatBytes(totalSize));
            $(pageRoot).find('#stat-available').text(available ? 'Yes' : 'No');

            // Update inspector
            const $inspector = $(pageRoot).find('#storage-inspector');

            if (keys.length === 0) {
                $inspector.html('<p class="text-muted">No data stored</p>');
                return;
            }

            let html = '';
            keys.forEach(key => {
                const value = S.get(key);
                const displayValue = JSON.stringify(value);
                html += `
                    <div class="storage-item-style">
                        <span class="storage-key-style">${_.escape(key)}</span>
                        <span class="storage-value-style">${_.escape(displayValue)}</span>
                    </div>
                `;
            });

            $inspector.html(html);
        }
}

Storage Inspector

View all Domma storage keys in real-time.

Storage Inspector
0
Keys
0 B
Total Size
-
Available
Stored Data

No data stored

<div class="storage-stat-card">
    <div class="storage-stat-value" id="stat-keys">0</div>
    <div class="storage-stat-label">Keys</div>
</div>
<div class="storage-stat-card">
    <div class="storage-stat-value" id="stat-size">0 B</div>
    <div class="storage-stat-label">Total Size</div>
</div>
<div class="storage-stat-card">
    <div class="storage-stat-value" id="stat-available">-</div>
    <div class="storage-stat-label">Available</div>
</div>
<span class="font-semibold">Stored Data</span>
<button type="button" class="btn btn-outline btn-sm" id="btn-refresh-inspector">Refresh</button>
<div class="storage-inspector-style" id="storage-inspector">
    <p class="text-muted">No data stored</p>
</div>
/**
 * Storage Inspector - from public/showcase/storage.
 */
export function mount(root) {
        // ============================================
        // Storage Inspector
        // ============================================
    function formatBytes(bytes) {
            if (bytes === 0) return '0 B';
            const k = 1024;
            const sizes = ['B', 'KB', 'MB'];
            const i = Math.floor(Math.log(bytes) / Math.log(k));
            return parseFloat((bytes / Math.pow(k, i)).toFixed(1)) + ' ' + sizes[i];
        }
    function refreshInspector() {
            const keys = S.keys();
            const totalSize = S.totalSize();
            const available = S.isAvailable();

            // Update stats
            $(root).find('#stat-keys').text(keys.length);
            $(root).find('#stat-size').text(formatBytes(totalSize));
            $(root).find('#stat-available').text(available ? 'Yes' : 'No');

            // Update inspector
            const $inspector = $(root).find('#storage-inspector');

            if (keys.length === 0) {
                $inspector.html('<p class="text-muted">No data stored</p>');
                return;
            }

            let html = '';
            keys.forEach(key => {
                const value = S.get(key);
                const displayValue = JSON.stringify(value);
                html += `
                    <div class="storage-item-style">
                        <span class="storage-key-style">${_.escape(key)}</span>
                        <span class="storage-value-style">${_.escape(displayValue)}</span>
                    </div>
                `;
            });

            $inspector.html(html);
        }
    $(root).find('#btn-refresh-inspector').on('click', refreshInspector);
        // Initial inspector load
    refreshInspector();
}

Method Reference

Core Methods

get(key, default) Retrieve and auto-parse a value. Returns default if not found.
set(key, value) Store a value with auto-stringify. Returns true if successful.
remove(key) Remove a key from storage.
has(key) Check if a key exists.
clear() Clear all Domma-prefixed keys. Returns count of cleared keys.
keys() List all Domma keys (without prefix).

Utility Methods

getAll() Get all stored data as an object.
setAll(data) Set multiple values at once from an object.
size(key) Get size of a stored value in bytes.
totalSize() Get total size of all Domma storage in bytes.
isAvailable() Check if localStorage is available.

Model Persistence Methods

model.save() Manually save model to localStorage.
model.load() Manually load model from localStorage.
model.clearStorage() Remove model data from localStorage.
model.isPersisted() Check if model has persistence enabled.
model.getPersistKey() Get the persistence key.
model.reset(true) Reset model and clear storage.