feat: namespaced localStorage store with export/import

This commit is contained in:
2026-07-20 13:40:01 +01:00
parent e4aafe5147
commit bf2dacefc1
2 changed files with 68 additions and 0 deletions
+28
View File
@@ -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);
}
},
};
}
+40
View File
@@ -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);
});
});