@storion/storion 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js ADDED
@@ -0,0 +1,26 @@
1
+ /**
2
+ * storion – Framework-agnostic client-side database.
3
+ * Use with React, Vue, Angular, or vanilla JS.
4
+ */
5
+
6
+ import { createDatabase, loadConfigFromUrl, loadConfigFromFile } from './Database.js';
7
+ import { getStorageAdapter } from './storage/adapters.js';
8
+ import { executeQuery, validateQuery, QUERY_OPERATORS } from './queryEngine.js';
9
+ import { parseConfig, normalizeColumn, getColumnNames, getColumnType, coerceValue } from './schema.js';
10
+ import { createChangeListener } from './changeListener.js';
11
+
12
+ export {
13
+ createDatabase,
14
+ loadConfigFromUrl,
15
+ loadConfigFromFile,
16
+ getStorageAdapter,
17
+ executeQuery,
18
+ validateQuery,
19
+ QUERY_OPERATORS,
20
+ parseConfig,
21
+ normalizeColumn,
22
+ getColumnNames,
23
+ getColumnType,
24
+ coerceValue,
25
+ createChangeListener
26
+ };
@@ -0,0 +1,299 @@
1
+ /**
2
+ * Query Engine - JSON query language for filtering and sorting table data.
3
+ * Framework-agnostic; no DOM or storage dependencies.
4
+ */
5
+
6
+ const COLUMN_TYPES = ['int', 'float', 'boolean', 'string', 'json'];
7
+
8
+ export const QUERY_OPERATORS = [
9
+ 'eq', 'ne', 'gt', 'gte', 'lt', 'lte',
10
+ 'contains', 'startsWith', 'endsWith',
11
+ 'in', 'notIn', 'isNull', 'isNotNull'
12
+ ];
13
+
14
+ function getColName(col) {
15
+ if (typeof col === 'string') return col;
16
+ if (col && typeof col === 'object' && col.name) return col.name;
17
+ return null;
18
+ }
19
+
20
+ function getColumnType(columns, colName) {
21
+ if (!Array.isArray(columns)) return 'string';
22
+ const col = columns.find(c => getColName(c) === colName);
23
+ if (!col) return 'string';
24
+ return typeof col === 'string' ? 'string' : (col.type || 'string');
25
+ }
26
+
27
+ function coerceValue(value, type) {
28
+ if (value === null || value === undefined || value === '') {
29
+ return type === 'boolean' ? false : null;
30
+ }
31
+ switch (type) {
32
+ case 'int': {
33
+ const i = parseInt(value, 10);
34
+ return isNaN(i) ? null : i;
35
+ }
36
+ case 'float': {
37
+ const f = parseFloat(value);
38
+ return isNaN(f) ? null : f;
39
+ }
40
+ case 'boolean':
41
+ if (typeof value === 'boolean') return value;
42
+ return String(value).toLowerCase() === 'true' || String(value).toLowerCase() === '1' || String(value).toLowerCase() === 'yes';
43
+ case 'json':
44
+ if (typeof value === 'object') return value;
45
+ if (typeof value === 'string') {
46
+ try {
47
+ return JSON.parse(value);
48
+ } catch {
49
+ return value;
50
+ }
51
+ }
52
+ return value;
53
+ case 'string':
54
+ default:
55
+ return String(value);
56
+ }
57
+ }
58
+
59
+ function getRowValue(row, field) {
60
+ if (!row || typeof row !== 'object') return null;
61
+ if (!Object.prototype.hasOwnProperty.call(row, field)) return null;
62
+ return row[field];
63
+ }
64
+
65
+ export function validateQuery(query, columns) {
66
+ if (query == null || typeof query !== 'object') {
67
+ return { valid: true };
68
+ }
69
+
70
+ const colNames = Array.isArray(columns)
71
+ ? columns.map(c => getColName(c)).filter(Boolean)
72
+ : [];
73
+
74
+ function checkField(field, context) {
75
+ if (typeof field !== 'string' || !field.trim()) {
76
+ return `Invalid or missing field in ${context}`;
77
+ }
78
+ if (!colNames.includes(field)) {
79
+ return `Unknown column: ${field}`;
80
+ }
81
+ return null;
82
+ }
83
+
84
+ function validateWhere(where, depth) {
85
+ if (where == null) return null;
86
+ if (depth > 50) return 'Where clause too deeply nested';
87
+
88
+ if (Array.isArray(where.and)) {
89
+ for (const item of where.and) {
90
+ const err = validateWhere(item, depth + 1);
91
+ if (err) return err;
92
+ }
93
+ return null;
94
+ }
95
+ if (Array.isArray(where.or)) {
96
+ for (const item of where.or) {
97
+ const err = validateWhere(item, depth + 1);
98
+ if (err) return err;
99
+ }
100
+ return null;
101
+ }
102
+
103
+ if (where.field != null) {
104
+ const err = checkField(where.field, 'where');
105
+ if (err) return err;
106
+ const op = where.op;
107
+ if (typeof op !== 'string' || !QUERY_OPERATORS.includes(op)) {
108
+ return `Invalid operator: ${op}. Allowed: ${QUERY_OPERATORS.join(', ')}`;
109
+ }
110
+ if ((op === 'in' || op === 'notIn') && !Array.isArray(where.value)) {
111
+ return `Operator "${op}" requires "value" to be an array`;
112
+ }
113
+ return null;
114
+ }
115
+
116
+ return 'Invalid where clause: expected "field"/"op"/"value" or "and"/"or"';
117
+ }
118
+
119
+ function validateOrderBy(orderBy) {
120
+ if (orderBy == null) return null;
121
+ if (!Array.isArray(orderBy)) return 'orderBy must be an array';
122
+ for (let i = 0; i < orderBy.length; i++) {
123
+ const item = orderBy[i];
124
+ if (!item || typeof item !== 'object') return `orderBy[${i}] must be { field, direction }`;
125
+ const err = checkField(item.field, `orderBy[${i}]`);
126
+ if (err) return err;
127
+ const dir = item.direction;
128
+ if (dir != null && dir !== 'asc' && dir !== 'desc') {
129
+ return `orderBy[${i}].direction must be "asc" or "desc"`;
130
+ }
131
+ }
132
+ return null;
133
+ }
134
+
135
+ const whereErr = validateWhere(query.where, 0);
136
+ if (whereErr) return { valid: false, error: whereErr };
137
+
138
+ const orderErr = validateOrderBy(query.orderBy);
139
+ if (orderErr) return { valid: false, error: orderErr };
140
+
141
+ if (query.limit != null && (typeof query.limit !== 'number' || query.limit < 0 || !Number.isInteger(query.limit))) {
142
+ return { valid: false, error: 'limit must be a non-negative integer' };
143
+ }
144
+ if (query.offset != null && (typeof query.offset !== 'number' || query.offset < 0 || !Number.isInteger(query.offset))) {
145
+ return { valid: false, error: 'offset must be a non-negative integer' };
146
+ }
147
+
148
+ return { valid: true };
149
+ }
150
+
151
+ function toComparableString(value, type) {
152
+ if (value == null) return '';
153
+ if (type === 'json') {
154
+ try {
155
+ return JSON.stringify(value);
156
+ } catch {
157
+ return String(value);
158
+ }
159
+ }
160
+ return String(value);
161
+ }
162
+
163
+ function evaluatePredicate(row, condition, columns) {
164
+ const { field, op, value } = condition;
165
+ const type = getColumnType(columns, field);
166
+ const raw = getRowValue(row, field);
167
+ const cell = raw === undefined || raw === null ? null : coerceValue(raw, type);
168
+
169
+ const str = cell != null ? toComparableString(cell, type) : '';
170
+ const strLower = str.toLowerCase();
171
+ const valueStr = value != null && value !== undefined ? toComparableString(value, type) : '';
172
+ const valueStrLower = valueStr.toLowerCase();
173
+
174
+ switch (op) {
175
+ case 'eq':
176
+ if (cell === null) return value === null || value === undefined;
177
+ if (type === 'string' || type === 'json') return strLower === valueStrLower;
178
+ return cell === coerceValue(value, type);
179
+ case 'ne':
180
+ if (cell === null) return value !== null && value !== undefined;
181
+ if (type === 'string' || type === 'json') return strLower !== valueStrLower;
182
+ return cell !== coerceValue(value, type);
183
+ case 'gt':
184
+ if (cell == null) return false;
185
+ return Number(cell) > Number(coerceValue(value, type));
186
+ case 'gte':
187
+ if (cell == null) return false;
188
+ return Number(cell) >= Number(coerceValue(value, type));
189
+ case 'lt':
190
+ if (cell == null) return false;
191
+ return Number(cell) < Number(coerceValue(value, type));
192
+ case 'lte':
193
+ if (cell == null) return false;
194
+ return Number(cell) <= Number(coerceValue(value, type));
195
+ case 'contains':
196
+ return strLower.includes(valueStrLower);
197
+ case 'startsWith':
198
+ return strLower.startsWith(valueStrLower);
199
+ case 'endsWith':
200
+ return strLower.endsWith(valueStrLower);
201
+ case 'in': {
202
+ const arr = Array.isArray(value) ? value : [];
203
+ return arr.some(v => {
204
+ const coerced = coerceValue(v, type);
205
+ if (type === 'string' || type === 'json') {
206
+ const coercedStr = toComparableString(coerced, type).toLowerCase();
207
+ return strLower === coercedStr;
208
+ }
209
+ return cell === coerced;
210
+ });
211
+ }
212
+ case 'notIn': {
213
+ const arr = Array.isArray(value) ? value : [];
214
+ return !arr.some(v => {
215
+ const coerced = coerceValue(v, type);
216
+ if (type === 'string' || type === 'json') {
217
+ const coercedStr = toComparableString(coerced, type).toLowerCase();
218
+ return strLower === coercedStr;
219
+ }
220
+ return cell === coerced;
221
+ });
222
+ }
223
+ case 'isNull':
224
+ return cell === null || cell === undefined;
225
+ case 'isNotNull':
226
+ return cell !== null && cell !== undefined;
227
+ default:
228
+ return false;
229
+ }
230
+ }
231
+
232
+ function evaluateWhere(row, where, columns) {
233
+ if (where == null) return true;
234
+
235
+ if (Array.isArray(where.and)) {
236
+ return where.and.every(item => evaluateWhere(row, item, columns));
237
+ }
238
+ if (Array.isArray(where.or)) {
239
+ return where.or.some(item => evaluateWhere(row, item, columns));
240
+ }
241
+
242
+ if (where.field != null && where.op != null) {
243
+ return evaluatePredicate(row, where, columns);
244
+ }
245
+
246
+ return true;
247
+ }
248
+
249
+ /**
250
+ * Execute query: filter, sort, and apply limit/offset.
251
+ * @param {Array<Object>} rows - All row objects
252
+ * @param {Array} columns - Table columns (string[] or { name, type }[])
253
+ * @param {Object} query - Query object (where, orderBy, limit, offset)
254
+ * @returns {{ rows: Array<Object>, totalCount: number }}
255
+ */
256
+ export function executeQuery(rows, columns, query) {
257
+ if (!Array.isArray(rows)) return { rows: [], totalCount: 0 };
258
+ if (query == null || typeof query !== 'object') {
259
+ return { rows: [...rows], totalCount: rows.length };
260
+ }
261
+
262
+ let result = rows.filter(row => evaluateWhere(row, query.where, columns));
263
+
264
+ if (Array.isArray(query.orderBy) && query.orderBy.length > 0) {
265
+ const colNames = Array.isArray(columns) ? columns.map(c => getColName(c)).filter(Boolean) : [];
266
+ result = [...result];
267
+ result.sort((a, b) => {
268
+ for (const { field, direction } of query.orderBy) {
269
+ const type = getColumnType(columns, field);
270
+ const aVal = getRowValue(a, field);
271
+ const bVal = getRowValue(b, field);
272
+ const aCoerced = aVal == null ? null : coerceValue(aVal, type);
273
+ const bCoerced = bVal == null ? null : coerceValue(bVal, type);
274
+
275
+ let cmp = 0;
276
+ if (aCoerced == null && bCoerced == null) cmp = 0;
277
+ else if (aCoerced == null) cmp = 1;
278
+ else if (bCoerced == null) cmp = -1;
279
+ else if (type === 'string') {
280
+ cmp = String(aCoerced).toLowerCase().localeCompare(String(bCoerced).toLowerCase());
281
+ } else if (type === 'int' || type === 'float') {
282
+ cmp = Number(aCoerced) - Number(bCoerced);
283
+ } else if (type === 'boolean') {
284
+ cmp = (aCoerced ? 1 : 0) - (bCoerced ? 1 : 0);
285
+ } else {
286
+ cmp = String(aCoerced).localeCompare(String(bCoerced));
287
+ }
288
+ if (cmp !== 0) return direction === 'desc' ? -cmp : cmp;
289
+ }
290
+ return 0;
291
+ });
292
+ }
293
+
294
+ const totalCount = result.length;
295
+ const offset = query.offset != null && Number.isInteger(query.offset) ? query.offset : 0;
296
+ const limit = query.limit != null && Number.isInteger(query.limit) ? query.limit : result.length;
297
+ const pageRows = result.slice(offset, offset + limit);
298
+ return { rows: pageRows, totalCount };
299
+ }
package/dist/schema.js ADDED
@@ -0,0 +1,121 @@
1
+ /**
2
+ * Schema helpers: normalize columns, validate config.
3
+ */
4
+
5
+ const COLUMN_TYPES = ['int', 'float', 'boolean', 'string', 'json'];
6
+
7
+ export function normalizeColumn(col) {
8
+ if (typeof col === 'string') {
9
+ return { name: col, type: 'string' };
10
+ }
11
+ if (col && typeof col === 'object' && col.name) {
12
+ const ref = col.references;
13
+ const hasRef = ref && typeof ref === 'object' &&
14
+ typeof ref.table === 'string' && ref.table.trim() !== '' &&
15
+ typeof ref.column === 'string' && ref.column.trim() !== '';
16
+ const out = { name: col.name, type: COLUMN_TYPES.includes(col.type) ? col.type : 'string' };
17
+ if (hasRef) out.references = { table: ref.table.trim(), column: ref.column.trim() };
18
+ return out;
19
+ }
20
+ return null;
21
+ }
22
+
23
+ export function getColumnNames(columns) {
24
+ if (!Array.isArray(columns)) return [];
25
+ return columns.map(c => typeof c === 'string' ? c : (c && c.name) ? c.name : null).filter(Boolean);
26
+ }
27
+
28
+ export function getColumnType(columns, colName) {
29
+ if (!Array.isArray(columns)) return 'string';
30
+ const col = columns.find(c => (typeof c === 'string' ? c : c && c.name) === colName);
31
+ if (!col) return 'string';
32
+ return typeof col === 'string' ? 'string' : (col.type || 'string');
33
+ }
34
+
35
+ export function coerceValue(value, type) {
36
+ if (value === null || value === undefined || value === '') {
37
+ return type === 'boolean' ? false : null;
38
+ }
39
+ switch (type) {
40
+ case 'int': {
41
+ const i = parseInt(value, 10);
42
+ return isNaN(i) ? null : i;
43
+ }
44
+ case 'float': {
45
+ const f = parseFloat(value);
46
+ return isNaN(f) ? null : f;
47
+ }
48
+ case 'boolean':
49
+ if (typeof value === 'boolean') return value;
50
+ const s = String(value).toLowerCase();
51
+ return s === 'true' || s === '1' || s === 'yes';
52
+ case 'json':
53
+ if (typeof value === 'object') return value;
54
+ if (typeof value === 'string') {
55
+ try {
56
+ return JSON.parse(value);
57
+ } catch {
58
+ return value;
59
+ }
60
+ }
61
+ return value;
62
+ case 'string':
63
+ default:
64
+ return String(value);
65
+ }
66
+ }
67
+
68
+ /**
69
+ * Parse config object into internal db structure.
70
+ * Config format: { databases: { dbName: { tables: { tableName: { columns: [...] } } } } }
71
+ * or { tables: { tableName: { columns: [...] } } } for a single-db config.
72
+ * @param {Object} config
73
+ * @param {string} [dbName] - If config has top-level tables, use this db name
74
+ * @returns {{ databases: Object }}
75
+ */
76
+ export function parseConfig(config, dbName = 'default') {
77
+ if (!config || typeof config !== 'object') {
78
+ return { databases: {} };
79
+ }
80
+ if (config.databases && typeof config.databases === 'object') {
81
+ const databases = {};
82
+ for (const [name, db] of Object.entries(config.databases)) {
83
+ if (!db || typeof db !== 'object') continue;
84
+ databases[name] = { tables: {} };
85
+ const tables = db.tables || db;
86
+ if (typeof tables === 'object' && !Array.isArray(tables)) {
87
+ for (const [tName, tDef] of Object.entries(tables)) {
88
+ if (!tDef || typeof tDef !== 'object') continue;
89
+ const cols = Array.isArray(tDef.columns) ? tDef.columns : (tDef.columns && tDef.columns.split) ? [] : [];
90
+ const normalized = cols.map(c => normalizeColumn(c)).filter(Boolean);
91
+ const names = normalized.map(c => c.name);
92
+ if (normalized.length > 0 && !names.includes('id')) {
93
+ normalized.unshift({ name: 'id', type: 'int' });
94
+ } else if (names.includes('id')) {
95
+ const idCol = normalized.find(c => c.name === 'id');
96
+ if (idCol) idCol.type = 'int';
97
+ }
98
+ databases[name].tables[tName] = { columns: normalized, rows: [] };
99
+ }
100
+ }
101
+ }
102
+ return { databases };
103
+ }
104
+ // Single-db: { tables: { tableName: { columns: [...] } } }
105
+ const tables = config.tables || {};
106
+ const databases = { [dbName]: { tables: {} } };
107
+ for (const [tName, tDef] of Object.entries(tables)) {
108
+ if (!tDef || typeof tDef !== 'object') continue;
109
+ const cols = Array.isArray(tDef.columns) ? tDef.columns : [];
110
+ const normalized = cols.map(c => normalizeColumn(c)).filter(Boolean);
111
+ const names = normalized.map(c => c.name);
112
+ if (normalized.length > 0 && !names.includes('id')) {
113
+ normalized.unshift({ name: 'id', type: 'int' });
114
+ } else if (names.includes('id')) {
115
+ const idCol = normalized.find(c => c.name === 'id');
116
+ if (idCol) idCol.type = 'int';
117
+ }
118
+ databases[dbName].tables[tName] = { columns: normalized, rows: [] };
119
+ }
120
+ return { databases };
121
+ }
@@ -0,0 +1,197 @@
1
+ /**
2
+ * Storage adapters for localStorage, sessionStorage, and IndexedDB.
3
+ * All adapters expose getItem(key), setItem(key, value), removeItem(key), getAllKeys().
4
+ * Async adapters return Promises; sync adapters return values directly.
5
+ */
6
+
7
+ const STORAGE_KEY = '__LS_DB__';
8
+
9
+ function createLocalStorageAdapter(storageKey = STORAGE_KEY) {
10
+ const key = storageKey;
11
+ return {
12
+ getItem() {
13
+ try {
14
+ return typeof localStorage !== 'undefined' ? localStorage.getItem(key) : null;
15
+ } catch (e) {
16
+ console.error('localStorage.getItem error:', e);
17
+ return null;
18
+ }
19
+ },
20
+ setItem(_, value) {
21
+ try {
22
+ if (typeof localStorage !== 'undefined') {
23
+ localStorage.setItem(key, value);
24
+ return true;
25
+ }
26
+ return false;
27
+ } catch (e) {
28
+ if (e.name === 'QuotaExceededError') throw new Error('Storage quota exceeded.');
29
+ throw e;
30
+ }
31
+ },
32
+ removeItem() {
33
+ try {
34
+ if (typeof localStorage !== 'undefined') localStorage.removeItem(key);
35
+ return true;
36
+ } catch (e) {
37
+ return false;
38
+ }
39
+ },
40
+ getAllKeys() {
41
+ return typeof localStorage !== 'undefined' && localStorage.getItem(key) != null ? [key] : [];
42
+ },
43
+ isAsync: false
44
+ };
45
+ }
46
+
47
+ function createSessionStorageAdapter(storageKey = STORAGE_KEY) {
48
+ const key = storageKey;
49
+ return {
50
+ getItem() {
51
+ try {
52
+ return typeof sessionStorage !== 'undefined' ? sessionStorage.getItem(key) : null;
53
+ } catch (e) {
54
+ console.error('sessionStorage.getItem error:', e);
55
+ return null;
56
+ }
57
+ },
58
+ setItem(_, value) {
59
+ try {
60
+ if (typeof sessionStorage !== 'undefined') {
61
+ sessionStorage.setItem(key, value);
62
+ return true;
63
+ }
64
+ return false;
65
+ } catch (e) {
66
+ if (e.name === 'QuotaExceededError') throw new Error('Storage quota exceeded.');
67
+ throw e;
68
+ }
69
+ },
70
+ removeItem() {
71
+ try {
72
+ if (typeof sessionStorage !== 'undefined') sessionStorage.removeItem(key);
73
+ return true;
74
+ } catch (e) {
75
+ return false;
76
+ }
77
+ },
78
+ getAllKeys() {
79
+ return typeof sessionStorage !== 'undefined' && sessionStorage.getItem(key) != null ? [key] : [];
80
+ },
81
+ isAsync: false
82
+ };
83
+ }
84
+
85
+ function createIndexedDBAdapter(storageKey = STORAGE_KEY) {
86
+ const DB_NAME = 'BrowserDB_Meta';
87
+ const STORE_NAME = 'meta';
88
+ const key = storageKey;
89
+
90
+ function openDB() {
91
+ return new Promise((resolve, reject) => {
92
+ if (typeof indexedDB === 'undefined') {
93
+ reject(new Error('IndexedDB not available'));
94
+ return;
95
+ }
96
+ const req = indexedDB.open(DB_NAME, 1);
97
+ req.onerror = () => reject(req.error);
98
+ req.onsuccess = () => resolve(req.result);
99
+ req.onupgradeneeded = (e) => {
100
+ e.target.result.createObjectStore(STORE_NAME);
101
+ };
102
+ });
103
+ }
104
+
105
+ return {
106
+ getItem() {
107
+ return openDB().then((db) => {
108
+ return new Promise((resolve, reject) => {
109
+ const tx = db.transaction(STORE_NAME, 'readonly');
110
+ const store = tx.objectStore(STORE_NAME);
111
+ const req = store.get(key);
112
+ req.onsuccess = () => {
113
+ db.close();
114
+ resolve(req.result ?? null);
115
+ };
116
+ req.onerror = () => {
117
+ db.close();
118
+ reject(req.error);
119
+ };
120
+ });
121
+ });
122
+ },
123
+ setItem(_, value) {
124
+ return openDB().then((db) => {
125
+ return new Promise((resolve, reject) => {
126
+ const tx = db.transaction(STORE_NAME, 'readwrite');
127
+ const store = tx.objectStore(STORE_NAME);
128
+ const req = store.put(value, key);
129
+ req.onsuccess = () => {
130
+ db.close();
131
+ resolve(true);
132
+ };
133
+ req.onerror = () => {
134
+ db.close();
135
+ reject(req.error);
136
+ };
137
+ });
138
+ });
139
+ },
140
+ removeItem() {
141
+ return openDB().then((db) => {
142
+ return new Promise((resolve, reject) => {
143
+ const tx = db.transaction(STORE_NAME, 'readwrite');
144
+ const store = tx.objectStore(STORE_NAME);
145
+ const req = store.delete(key);
146
+ req.onsuccess = () => {
147
+ db.close();
148
+ resolve(true);
149
+ };
150
+ req.onerror = () => {
151
+ db.close();
152
+ reject(req.error);
153
+ };
154
+ });
155
+ });
156
+ },
157
+ getAllKeys() {
158
+ return openDB().then((db) => {
159
+ return new Promise((resolve, reject) => {
160
+ const tx = db.transaction(STORE_NAME, 'readonly');
161
+ const store = tx.objectStore(STORE_NAME);
162
+ const req = store.get(key);
163
+ req.onsuccess = () => {
164
+ db.close();
165
+ resolve(req.result != null ? [key] : []);
166
+ };
167
+ req.onerror = () => {
168
+ db.close();
169
+ reject(req.error);
170
+ };
171
+ });
172
+ }).catch(() => []);
173
+ },
174
+ isAsync: true
175
+ };
176
+ }
177
+
178
+ /**
179
+ * Get storage adapter by type.
180
+ * @param {'localStorage'|'sessionStorage'|'indexedDB'} type
181
+ * @param {string} [storageKey] - Optional key to store data under
182
+ * @returns {Object} Adapter with getItem, setItem, removeItem, getAllKeys, isAsync
183
+ */
184
+ export function getStorageAdapter(type, storageKey = STORAGE_KEY) {
185
+ switch (type) {
186
+ case 'localStorage':
187
+ return createLocalStorageAdapter(storageKey);
188
+ case 'sessionStorage':
189
+ return createSessionStorageAdapter(storageKey);
190
+ case 'indexedDB':
191
+ return createIndexedDBAdapter(storageKey);
192
+ default:
193
+ throw new Error(`Unsupported storage type: ${type}. Use localStorage, sessionStorage, or indexedDB.`);
194
+ }
195
+ }
196
+
197
+ export { STORAGE_KEY };