diff --git a/src/core/store.js b/src/core/store.js new file mode 100644 index 0000000..b1287c8 --- /dev/null +++ b/src/core/store.js @@ -0,0 +1,28 @@ +const PREFIX = 'gmd'; + +export function createStore(namespace, storage = globalThis.localStorage) { + const full = (key) => `${PREFIX}.${namespace}.${key}`; + const nsPrefix = `${PREFIX}.${namespace}.`; + return { + get(key, fallback = null) { + const raw = storage.getItem(full(key)); + if (raw == null) return fallback; + try { return JSON.parse(raw); } catch { return fallback; } + }, + set(key, value) { storage.setItem(full(key), JSON.stringify(value)); }, + remove(key) { storage.removeItem(full(key)); }, + exportAll() { + const out = {}; + for (let i = 0; i < storage.length; i++) { + const k = storage.key(i); + if (k && k.startsWith(nsPrefix)) out[k] = storage.getItem(k); + } + return out; + }, + importAll(obj) { + for (const [k, v] of Object.entries(obj)) { + if (k.startsWith(nsPrefix)) storage.setItem(k, v); + } + }, + }; +} diff --git a/tests/core/store.test.js b/tests/core/store.test.js new file mode 100644 index 0000000..e577f7e --- /dev/null +++ b/tests/core/store.test.js @@ -0,0 +1,40 @@ +import { describe, it, expect, beforeEach } from 'vitest'; +import { createStore } from '../../src/core/store.js'; + +function fakeStorage() { + const map = new Map(); + return { + getItem: (k) => (map.has(k) ? map.get(k) : null), + setItem: (k, v) => map.set(k, String(v)), + removeItem: (k) => map.delete(k), + key: (i) => [...map.keys()][i] ?? null, + get length() { return map.size; }, + }; +} + +describe('createStore', () => { + let s, storage; + beforeEach(() => { storage = fakeStorage(); s = createStore('afterimage', storage); }); + + it('round-trips JSON values under a namespaced key', () => { + s.set('clock', { startedAt: 5 }); + expect(storage.getItem('gmd.afterimage.clock')).toBe('{"startedAt":5}'); + expect(s.get('clock')).toEqual({ startedAt: 5 }); + }); + + it('returns fallback for missing/corrupt keys', () => { + expect(s.get('missing', 42)).toBe(42); + storage.setItem('gmd.afterimage.bad', '{not json'); + expect(s.get('bad', 'fb')).toBe('fb'); + }); + + it('exports only its namespace and imports back', () => { + s.set('a', 1); + storage.setItem('gmd.other.z', '9'); + const dump = s.exportAll(); + expect(Object.keys(dump)).toEqual(['gmd.afterimage.a']); + const s2 = createStore('afterimage', fakeStorage()); + s2.importAll(dump); + expect(s2.get('a')).toBe(1); + }); +});