-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathAppCacheManager.umd.js
More file actions
204 lines (179 loc) · 6.42 KB
/
AppCacheManager.umd.js
File metadata and controls
204 lines (179 loc) · 6.42 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
/* UMD: AppCacheManager */
(function (root, factory) {
if (typeof define === 'function' && define.amd) {
define([], factory);
} else if (typeof module === 'object' && module.exports) {
module.exports = factory();
} else {
root.AppCacheManager = factory();
}
}(typeof self !== 'undefined' ? self : this, function () {
'use strict';
// Small utility
const now = () => Date.now();
const sleep = (ms) => new Promise(res => setTimeout(res, ms));
function createDB({ dbName, version = 1, stores = [], debug = false } = {}) {
if (!dbName) throw new Error('createDB requires a dbName');
const log = (...a) => debug && console.log('[AppCacheManager]', ...a);
const warn = (...a) => debug && console.warn('[AppCacheManager]', ...a);
const error = (...a) => console.error('[AppCacheManager]', ...a);
let db = null;
function open() {
return new Promise((resolve, reject) => {
const req = indexedDB.open(dbName, version);
req.onupgradeneeded = (e) => {
const _db = e.target.result;
stores.forEach(s => {
if (!_db.objectStoreNames.contains(s)) {
_db.createObjectStore(s, { keyPath: 'key' });
}
});
};
req.onsuccess = (e) => { db = e.target.result; resolve(db); };
req.onerror = (e) => reject(e.target.error);
});
}
function withStore(store, mode, fn) {
return new Promise((resolve, reject) => {
if (!db) return reject(new Error('DB not initialized'));
const tx = db.transaction(store, mode);
const os = tx.objectStore(store);
tx.oncomplete = () => resolve(result);
tx.onerror = (e) => reject(e.target.error);
let result;
try {
const r = fn(os);
// If fn returns an IDBRequest, attach onsuccess/onerror
if (r instanceof IDBRequest) {
r.onsuccess = () => { result = r.result; };
r.onerror = (e) => reject(e.target.error);
} else if (r instanceof Promise) {
// If fn returns a promise, await it and assign to result
r.then(res => { result = res; }).catch(reject);
} else {
// synchronous return value
result = r;
}
} catch (e) {
reject(e);
}
});
}
// ---- Corrected get ----
async function get(store, key) {
try {
const row = await withStore(store, 'readonly', os => os.get(key));
const rec = row ?? null;
if (!rec) return null;
const exp = rec.meta?.expiresAt;
if (exp && exp <= now()) {
// Soft-expired: return value but indicate expiration
return Object.assign(
Array.isArray(rec.value) ? [...rec.value] : rec.value,
{ __expired: true }
);
}
return Array.isArray(rec.value) ? [...rec.value] : rec.value;
} catch (e) {
console.warn('[AppCacheManager] get failed', { store, key, e });
return null;
}
}
// ---- Corrected getAll ----
async function getAll(store) {
try {
const rows = await withStore(store, 'readonly', os => os.getAll());
return rows.map(rec => {
const exp = rec.meta?.expiresAt;
if (exp && exp <= now()) {
return Object.assign(
Array.isArray(rec.value) ? [...rec.value] : rec.value,
{ __expired: true }
);
}
return Array.isArray(rec.value) ? [...rec.value] : rec.value;
});
} catch (e) {
console.warn('[AppCacheManager] getAll failed', { store, e });
return [];
}
}
async function init() {
if (!db) await open();
log('DB ready:', dbName);
return api;
}
async function set(store, key, value, { ttlMs } = {}) {
if (!db) throw new Error('DB not initialized');
const meta = {};
if (ttlMs && ttlMs > 0) meta.expiresAt = now() + ttlMs;
return new Promise((resolve, reject) => {
const tx = db.transaction(store, 'readwrite');
const os = tx.objectStore(store);
const req = os.put({ key, value, meta });
req.onsuccess = () => resolve(true);
req.onerror = (e) => reject(e.target.error);
});
}
async function invalidate(store, key) {
if (!db) throw new Error('DB not initialized');
return new Promise((resolve, reject) => {
const tx = db.transaction(store, 'readwrite');
const os = tx.objectStore(store);
const req = os.delete(key);
req.onsuccess = () => resolve(true);
req.onerror = (e) => reject(e.target.error);
});
}
/**
* Preload helper
* defs: [{ store, key, run: () => Promise<any>, ttlMs?: number }]
* options: { concurrency?: number, batchDelayMs?: number, retries?: number }
*/
async function preload(defs, { concurrency = 4, batchDelayMs = 0, retries = 0 } = {}) {
log(`Starting preload of ${defs.length} items (concurrency=${concurrency})...`);
let i = 0;
async function worker() {
while (i < defs.length) {
const idx = i++;
const def = defs[idx];
const { store, key, run, ttlMs } = def;
try {
const cached = await get(store, key);
if (cached && !cached.__expired) {
log(`Cache hit for ${store}:${key}, skipping fetch.`);
continue;
}
const attempt = async (triesLeft) => {
try {
const value = await run();
await set(store, key, value, { ttlMs });
log('Inserted', store, key);
} catch (err) {
if (triesLeft > 0) {
warn(`Retrying ${store}:${key} (${triesLeft} left)`, err);
await sleep(250);
return attempt(triesLeft - 1);
}
error('Preload failed', { store, key, err });
}
};
await attempt(retries);
} catch (e) {
warn('Preload get failed', e);
}
if (batchDelayMs) await sleep(batchDelayMs);
}
}
const workers = Array.from({ length: Math.min(concurrency, defs.length) }, worker);
await Promise.all(workers);
log('Preload complete.');
}
const api = { init, get, set, getAll, invalidate, preload, name: dbName };
return api;
}
// Public UMD API
return {
createDB
};
}));