experiments in a post-browser web
1const openStore = (prefix, defaults, clear = false) => {
2
3 //console.log('openStore', prefix, (defaults ? Object.keys(defaults) : ''));
4
5 // multiple contexts
6 const keyify = k => `${prefix}+${k}`;
7
8 // Simple localStorage abstraction/wrapper
9 const store = {
10 set: (k, v) => {
11 const key = keyify(k);
12 const value = JSON.stringify(v);
13 //console.log('store.set', key, value)
14 localStorage.setItem(key, value);
15 },
16 get: (k) => {
17 const key = keyify(k);
18 //console.log('store.get', key)
19 const r = localStorage.getItem(key);
20 return r ? JSON.parse(r) : null;
21 },
22 clear: () => localStorage.clear()
23 };
24
25 if (window.app.debug
26 && window.app.debugLevel == window.app.debugLevels.FIRST_RUN) {
27 console.log('openStore(): clearing storage')
28 store.clear();
29 }
30
31 if (clear) {
32 console.log('openStore(): CLEARING');
33 store.clear();
34 }
35
36 const initStore = (store, data) => {
37 Object.keys(data).forEach(k => {
38 const v = store.get(k);
39 if (!v) {
40 //console.log('openStore(): init is setting', k, data[k]);
41 store.set(k, data[k]);
42 }
43 });
44 };
45
46 if (defaults != null) {
47 //console.log('UTILS/openStore()', 'initing');
48 initStore(store, defaults);
49 }
50
51 return store;
52};
53
54// The flattenObj helper is now private - it's only needed for window.open
55
56/**
57 * Create an async store backed by datastore instead of localStorage.
58 * Uses the extension_settings table with extensionId as namespace.
59 *
60 * @param {string} namespace - The extensionId (e.g., 'core', 'cmd', 'scripts')
61 * @param {object} defaults - Default values for each key
62 * @returns {Promise<object>} Store with async get/set methods
63 */
64const createDatastoreStore = async (namespace, defaults = {}) => {
65 const api = window.app;
66 const table = 'extension_settings';
67
68 // Load all settings for this namespace once
69 let cache = {};
70 try {
71 const result = await api.datastore.getTable(table);
72 if (result.success && result.data) {
73 Object.values(result.data).forEach(row => {
74 if (row.extensionId === namespace && row.value) {
75 try {
76 cache[row.key] = JSON.parse(row.value);
77 } catch (e) {
78 console.warn(`[datastoreStore] Failed to parse ${namespace}:${row.key}`, e);
79 }
80 }
81 });
82 }
83 } catch (e) {
84 console.warn(`[datastoreStore] Failed to load ${namespace}`, e);
85 }
86
87 // Apply defaults for missing keys
88 Object.keys(defaults).forEach(key => {
89 if (cache[key] === undefined) {
90 cache[key] = defaults[key];
91 }
92 });
93
94 return {
95 get(key) {
96 return cache[key] !== undefined ? cache[key] : defaults[key];
97 },
98
99 async set(key, value) {
100 cache[key] = value;
101 const rowId = `${namespace}:${key}`;
102 try {
103 await api.datastore.setRow(table, rowId, {
104 extensionId: namespace,
105 key,
106 value: JSON.stringify(value),
107 updatedAt: Date.now()
108 });
109 } catch (e) {
110 console.error(`[datastoreStore] Failed to save ${namespace}:${key}`, e);
111 }
112 },
113
114 // Get all cached values
115 getAll() {
116 return { ...cache };
117 }
118 };
119};
120
121export {
122 openStore,
123 createDatastoreStore
124};