@weasel-js/storage 1.9.3 → 1.10.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/README.md +12 -0
- package/dist/index.d.ts +40 -13
- package/dist/index.js +302 -101
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -10,6 +10,18 @@ Adapters that can read synchronously (`localStorage`, `sessionStorage`,
|
|
|
10
10
|
memory) also implement `listSync`, and `openRecordsSync` opens a cache from
|
|
11
11
|
one with no `await`.
|
|
12
12
|
|
|
13
|
+
A cache whose first read fails opens empty with `writable` false, and reads
|
|
14
|
+
again on a backoff (`retryMs`, doubling to `retryMaxMs`; `retryMs: false`
|
|
15
|
+
turns that off). `cache.read()` tries at once, and `cache.whenRead()` resolves
|
|
16
|
+
once a read has landed. When a read lands, the cache
|
|
17
|
+
reports the records as `remote` changes and starts writing, beginning with
|
|
18
|
+
whatever was set while it waited; those writes win over what it read.
|
|
19
|
+
|
|
20
|
+
A write storage rejects stays queued and is sent again on the same backoff,
|
|
21
|
+
and `flush()` resolves false until it lands. While a write is queued or on its
|
|
22
|
+
way, another writer's change to that record is ignored. With `retryMs: false`
|
|
23
|
+
a rejected write waits for the next flush.
|
|
24
|
+
|
|
13
25
|
`defaultStorage()` is IndexedDB under the database `'weasel'`, or
|
|
14
26
|
`localStorage`, with a warning, where IndexedDB will not open; it chooses once
|
|
15
27
|
per page. `fallbackStorage(preferred, fallback, label)` makes the same choice
|
package/dist/index.d.ts
CHANGED
|
@@ -79,34 +79,48 @@ interface RecordChange {
|
|
|
79
79
|
*
|
|
80
80
|
* Writes land in memory at once and reach storage on a debounce. The newest
|
|
81
81
|
* write to a record wins: another writer's change to a record with a write
|
|
82
|
-
* still queued here is ignored, since
|
|
82
|
+
* still queued or on its way here is ignored, since that write lands after
|
|
83
|
+
* it. A write storage rejects stays queued and is sent again on a backoff.
|
|
83
84
|
*/
|
|
84
85
|
interface RecordCache {
|
|
85
86
|
readonly prefix: string;
|
|
86
|
-
/** False
|
|
87
|
-
*
|
|
87
|
+
/** False while writing is off, so nothing here can overwrite the records:
|
|
88
|
+
* until a first read that failed lands on a later try, and for good once
|
|
89
|
+
* the opener marks them read-only. */
|
|
88
90
|
readonly writable: boolean;
|
|
89
91
|
has(name: string): boolean;
|
|
90
92
|
get(name: string): unknown;
|
|
91
93
|
/** Every record whose name starts with `namePrefix`. */
|
|
92
94
|
entries(namePrefix?: string): [string, unknown][];
|
|
95
|
+
/** `undefined` is no value a record can hold: setting it deletes. */
|
|
93
96
|
set(name: string, value: unknown): void;
|
|
94
97
|
delete(name: string): void;
|
|
95
98
|
subscribe(listener: (changes: RecordChange[]) => void): () => void;
|
|
96
99
|
/** Send queued writes now. Resolves true when every queued write landed or
|
|
97
|
-
* none was queued; false when a write threw (
|
|
98
|
-
*
|
|
100
|
+
* none was queued; false when a write threw (it stays queued) or writing
|
|
101
|
+
* is off and writes were queued. */
|
|
99
102
|
flush(): Promise<boolean>;
|
|
100
|
-
/**
|
|
103
|
+
/** Try a failed first read again now, ahead of the backoff. Resolves
|
|
104
|
+
* whether the records have been read. */
|
|
105
|
+
read(): Promise<boolean>;
|
|
106
|
+
/** Resolves once the records have been read: at once for a cache that
|
|
107
|
+
* opened with them, and when a later read lands for one whose first read
|
|
108
|
+
* failed. Never resolves for a cache closed or stopped before that. */
|
|
109
|
+
whenRead(): Promise<void>;
|
|
110
|
+
/** Send queued writes, stop hearing other writers, and stop retrying. */
|
|
101
111
|
close(): Promise<void>;
|
|
102
112
|
}
|
|
103
113
|
/** The parts of a cache only its opener uses. */
|
|
104
114
|
interface OwnedRecordCache extends RecordCache {
|
|
105
115
|
/** Hold these records without writing them back. */
|
|
106
116
|
seed(entries: Iterable<[string, unknown]>): void;
|
|
117
|
+
/** Hear another writer's changes this cache did not take, because its own
|
|
118
|
+
* write to the record was queued or on its way: each a name and the value
|
|
119
|
+
* the other writer set, `undefined` for a delete. */
|
|
120
|
+
onOverruled(listener: (changes: [string, unknown][]) => void): () => void;
|
|
107
121
|
stopWriting(): void;
|
|
108
122
|
}
|
|
109
|
-
/** Options for `
|
|
123
|
+
/** Options for `openRecords`, and the part of `createRecordCache`'s the two share. */
|
|
110
124
|
interface RecordCacheOptions {
|
|
111
125
|
storage: StorageAdapter;
|
|
112
126
|
prefix: string;
|
|
@@ -114,18 +128,31 @@ interface RecordCacheOptions {
|
|
|
114
128
|
debounceMs?: number;
|
|
115
129
|
/** The longest a write waits under continuous change. Default 1000 ms. */
|
|
116
130
|
maxWaitMs?: number;
|
|
131
|
+
/** The wait before a failed first read, or a write storage rejected, is
|
|
132
|
+
* tried again, doubling after each failure. Default 1000 ms; `false` never
|
|
133
|
+
* tries again on its own, leaving a rejected write for the next flush. */
|
|
134
|
+
retryMs?: number | false;
|
|
135
|
+
/** The longest wait between those tries. Default 30000 ms. */
|
|
136
|
+
retryMaxMs?: number;
|
|
117
137
|
}
|
|
118
|
-
/**
|
|
119
|
-
|
|
138
|
+
/** Options for `createRecordCache`. */
|
|
139
|
+
interface CreateRecordCacheOptions extends RecordCacheOptions {
|
|
140
|
+
/** The records the cache opens holding, each a name and its value. Default none. */
|
|
120
141
|
initial?: Iterable<[string, unknown]>;
|
|
142
|
+
/** False opens the cache with writing off for good. Default true. */
|
|
121
143
|
writable?: boolean;
|
|
122
|
-
}
|
|
144
|
+
}
|
|
145
|
+
/** A cache holding `initial`, with nothing read from storage. */
|
|
146
|
+
declare function createRecordCache(options: CreateRecordCacheOptions): OwnedRecordCache;
|
|
123
147
|
/** Read every record under `prefix` and hold them. A failed read opens the
|
|
124
148
|
* cache empty with writing off, so it never overwrites what it could not
|
|
125
|
-
* read.
|
|
149
|
+
* read, and tries again on a backoff. When a read lands, the cache holds it,
|
|
150
|
+
* reports it as remote changes, and starts writing, sending first whatever
|
|
151
|
+
* was written meanwhile. */
|
|
126
152
|
declare function openRecords(options: RecordCacheOptions): Promise<OwnedRecordCache>;
|
|
127
153
|
/** `openRecords` for an adapter that reads synchronously: the cache is ready
|
|
128
|
-
* on return. A failed read opens it empty with writing off
|
|
154
|
+
* on return. A failed read opens it empty with writing off, and it recovers
|
|
155
|
+
* the way `openRecords` does. */
|
|
129
156
|
declare function openRecordsSync(options: RecordCacheOptions & {
|
|
130
157
|
storage: SyncStorageAdapter;
|
|
131
158
|
}): OwnedRecordCache;
|
|
@@ -136,4 +163,4 @@ declare function encodeUrlHash(value: string): string;
|
|
|
136
163
|
* malformed. */
|
|
137
164
|
declare function decodeUrlHash(hash: string): string | null;
|
|
138
165
|
|
|
139
|
-
export { type IndexedDbAdapterOptions, type OwnedRecordCache, type RecordCache, type RecordCacheOptions, type RecordChange, type StorageAdapter, type StorageChange, type SyncStorageAdapter, createDefaultStorage, createIndexedDbAdapter, createMemoryAdapter, createRecordCache, decodeUrlHash, defaultStorage, encodeUrlHash, fallbackStorage, indexedDbAdapter, localStorageAdapter, noneAdapter, openRecords, openRecordsSync, sessionStorageAdapter, urlHashAdapter };
|
|
166
|
+
export { type CreateRecordCacheOptions, type IndexedDbAdapterOptions, type OwnedRecordCache, type RecordCache, type RecordCacheOptions, type RecordChange, type StorageAdapter, type StorageChange, type SyncStorageAdapter, createDefaultStorage, createIndexedDbAdapter, createMemoryAdapter, createRecordCache, decodeUrlHash, defaultStorage, encodeUrlHash, fallbackStorage, indexedDbAdapter, localStorageAdapter, noneAdapter, openRecords, openRecordsSync, sessionStorageAdapter, urlHashAdapter };
|
package/dist/index.js
CHANGED
|
@@ -4,8 +4,15 @@ function createIndexedDbAdapter({
|
|
|
4
4
|
store = "records"
|
|
5
5
|
} = {}) {
|
|
6
6
|
let opened = null;
|
|
7
|
+
let connection = null;
|
|
8
|
+
const forget = (db) => {
|
|
9
|
+
if (connection !== db) return;
|
|
10
|
+
connection = null;
|
|
11
|
+
opened = null;
|
|
12
|
+
};
|
|
7
13
|
const open = () => {
|
|
8
|
-
|
|
14
|
+
if (opened) return opened;
|
|
15
|
+
const mine = new Promise((resolve, reject) => {
|
|
9
16
|
if (typeof indexedDB === "undefined") {
|
|
10
17
|
reject(new Error("[storage] IndexedDB is not available here"));
|
|
11
18
|
return;
|
|
@@ -19,25 +26,43 @@ function createIndexedDbAdapter({
|
|
|
19
26
|
request.onsuccess = () => {
|
|
20
27
|
const db = request.result;
|
|
21
28
|
if (!db.objectStoreNames.contains(store)) {
|
|
29
|
+
db.close();
|
|
22
30
|
reject(new Error(`[storage] IndexedDB database "${database}" has no store "${store}"`));
|
|
23
31
|
return;
|
|
24
32
|
}
|
|
33
|
+
connection = db;
|
|
34
|
+
db.onclose = () => forget(db);
|
|
35
|
+
db.onversionchange = () => {
|
|
36
|
+
db.close();
|
|
37
|
+
forget(db);
|
|
38
|
+
};
|
|
25
39
|
resolve(db);
|
|
26
40
|
};
|
|
27
41
|
request.onerror = () => reject(request.error);
|
|
28
|
-
request.onblocked = () => reject(new Error(`[storage] IndexedDB "${database}" is blocked`));
|
|
29
42
|
});
|
|
30
|
-
|
|
43
|
+
opened = mine;
|
|
44
|
+
mine.catch(() => {
|
|
45
|
+
if (opened === mine) opened = null;
|
|
46
|
+
});
|
|
47
|
+
return mine;
|
|
31
48
|
};
|
|
32
|
-
const run = (mode, body) =>
|
|
33
|
-
|
|
34
|
-
|
|
49
|
+
const run = async (mode, body) => {
|
|
50
|
+
let db = await open();
|
|
51
|
+
let tx;
|
|
52
|
+
try {
|
|
53
|
+
tx = db.transaction(store, mode);
|
|
54
|
+
} catch {
|
|
55
|
+
forget(db);
|
|
56
|
+
db = await open();
|
|
57
|
+
tx = db.transaction(store, mode);
|
|
58
|
+
}
|
|
59
|
+
return new Promise((resolve, reject) => {
|
|
35
60
|
const result = body(tx.objectStore(store));
|
|
36
61
|
tx.oncomplete = () => resolve(result());
|
|
37
62
|
tx.onerror = () => reject(tx.error);
|
|
38
63
|
tx.onabort = () => reject(tx.error);
|
|
39
|
-
})
|
|
40
|
-
|
|
64
|
+
});
|
|
65
|
+
};
|
|
41
66
|
let channel = null;
|
|
42
67
|
const getChannel = () => {
|
|
43
68
|
if (typeof BroadcastChannel === "undefined") return null;
|
|
@@ -125,17 +150,32 @@ function isJsonSafe(value, seen = /* @__PURE__ */ new Set()) {
|
|
|
125
150
|
// src/webStorage.ts
|
|
126
151
|
function webStorageAdapter(area, name, crossTab) {
|
|
127
152
|
const warned = /* @__PURE__ */ new Set();
|
|
128
|
-
const
|
|
153
|
+
const keysNow = (prefix) => {
|
|
129
154
|
const store = area();
|
|
130
155
|
const out = [];
|
|
131
156
|
for (let i = 0; i < store.length; i++) {
|
|
132
157
|
const key = store.key(i);
|
|
133
|
-
if (key
|
|
158
|
+
if (key?.startsWith(prefix)) out.push(key);
|
|
159
|
+
}
|
|
160
|
+
return out;
|
|
161
|
+
};
|
|
162
|
+
const listNow = (prefix) => {
|
|
163
|
+
const store = area();
|
|
164
|
+
const out = [];
|
|
165
|
+
for (const key of keysNow(prefix)) {
|
|
134
166
|
const raw = store.getItem(key);
|
|
135
167
|
if (raw !== null) out.push([key, parse(raw)]);
|
|
136
168
|
}
|
|
137
169
|
return out;
|
|
138
170
|
};
|
|
171
|
+
const subscribers = /* @__PURE__ */ new Set();
|
|
172
|
+
const note = (key, held) => {
|
|
173
|
+
for (const sub of subscribers) {
|
|
174
|
+
if (!key.startsWith(sub.prefix)) continue;
|
|
175
|
+
if (held) sub.keys.add(key);
|
|
176
|
+
else sub.keys.delete(key);
|
|
177
|
+
}
|
|
178
|
+
};
|
|
139
179
|
const adapter = {
|
|
140
180
|
get: async (key) => {
|
|
141
181
|
const raw = area().getItem(key);
|
|
@@ -151,22 +191,38 @@ function webStorageAdapter(area, name, crossTab) {
|
|
|
151
191
|
);
|
|
152
192
|
}
|
|
153
193
|
area().setItem(key, JSON.stringify(value));
|
|
194
|
+
note(key, true);
|
|
154
195
|
},
|
|
155
196
|
delete: async (key) => {
|
|
156
197
|
area().removeItem(key);
|
|
198
|
+
note(key, false);
|
|
157
199
|
}
|
|
158
200
|
};
|
|
159
201
|
if (crossTab) {
|
|
160
202
|
adapter.subscribe = (prefix, on) => {
|
|
161
203
|
if (typeof window === "undefined") return () => {
|
|
162
204
|
};
|
|
205
|
+
const sub = { prefix, keys: new Set(keysNow(prefix)) };
|
|
206
|
+
subscribers.add(sub);
|
|
163
207
|
const handler = (e) => {
|
|
164
|
-
|
|
165
|
-
if (e.storageArea !==
|
|
208
|
+
const store = area();
|
|
209
|
+
if (e.storageArea !== store) return;
|
|
210
|
+
if (e.key === null) {
|
|
211
|
+
const gone = [...sub.keys].filter((key) => store.getItem(key) === null);
|
|
212
|
+
for (const key of gone) sub.keys.delete(key);
|
|
213
|
+
if (gone.length > 0) on(gone.map((key) => [key, void 0]));
|
|
214
|
+
return;
|
|
215
|
+
}
|
|
216
|
+
if (!e.key.startsWith(prefix)) return;
|
|
217
|
+
if (e.newValue === null) sub.keys.delete(e.key);
|
|
218
|
+
else sub.keys.add(e.key);
|
|
166
219
|
on([[e.key, e.newValue === null ? void 0 : parse(e.newValue)]]);
|
|
167
220
|
};
|
|
168
221
|
window.addEventListener("storage", handler);
|
|
169
|
-
return () =>
|
|
222
|
+
return () => {
|
|
223
|
+
subscribers.delete(sub);
|
|
224
|
+
window.removeEventListener("storage", handler);
|
|
225
|
+
};
|
|
170
226
|
};
|
|
171
227
|
}
|
|
172
228
|
return adapter;
|
|
@@ -269,21 +325,28 @@ function decodeUrlHash(hash) {
|
|
|
269
325
|
// src/urlHashAdapter.ts
|
|
270
326
|
var URL_HASH_GUARD = typeof window !== "undefined";
|
|
271
327
|
function readHashMap() {
|
|
272
|
-
|
|
328
|
+
const map = /* @__PURE__ */ Object.create(null);
|
|
329
|
+
if (!URL_HASH_GUARD) return map;
|
|
273
330
|
const raw = decodeUrlHash(window.location.hash.replace(/^#/, ""));
|
|
274
|
-
if (!raw) return
|
|
331
|
+
if (!raw) return map;
|
|
332
|
+
let parsed;
|
|
275
333
|
try {
|
|
276
|
-
|
|
334
|
+
parsed = JSON.parse(raw);
|
|
277
335
|
} catch {
|
|
278
|
-
return
|
|
336
|
+
return map;
|
|
337
|
+
}
|
|
338
|
+
if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) return map;
|
|
339
|
+
for (const [key, value] of Object.entries(parsed)) {
|
|
340
|
+
if (typeof value === "string") map[key] = value;
|
|
279
341
|
}
|
|
342
|
+
return map;
|
|
280
343
|
}
|
|
281
344
|
var hashSubscribers = /* @__PURE__ */ new Set();
|
|
282
345
|
function writeHashMap(map) {
|
|
283
346
|
if (!URL_HASH_GUARD) return;
|
|
284
347
|
const encoded = encodeUrlHash(JSON.stringify(map));
|
|
285
|
-
window.history.replaceState(
|
|
286
|
-
for (const sub of hashSubscribers) sub.last =
|
|
348
|
+
window.history.replaceState(window.history.state, "", `#${encoded}`);
|
|
349
|
+
for (const sub of hashSubscribers) sub.last = Object.assign(/* @__PURE__ */ Object.create(null), map);
|
|
287
350
|
}
|
|
288
351
|
var urlHashAdapter = {
|
|
289
352
|
get: async (key) => {
|
|
@@ -325,24 +388,21 @@ var urlHashAdapter = {
|
|
|
325
388
|
}
|
|
326
389
|
};
|
|
327
390
|
|
|
328
|
-
// src/
|
|
391
|
+
// src/writeQueue.ts
|
|
329
392
|
var DELETED = /* @__PURE__ */ Symbol("deleted");
|
|
330
|
-
function
|
|
331
|
-
const { storage, prefix } = options;
|
|
332
|
-
const debounceMs = options.debounceMs ?? 300;
|
|
333
|
-
const maxWaitMs = options.maxWaitMs ?? 1e3;
|
|
334
|
-
const values = new Map(options.initial ?? []);
|
|
393
|
+
function createWriteQueue(options) {
|
|
394
|
+
const { storage, prefix, debounceMs, maxWaitMs, retryMs, retryMaxMs, writable, closed, stopped } = options;
|
|
335
395
|
const queued = /* @__PURE__ */ new Map();
|
|
336
|
-
const
|
|
337
|
-
let writable = options.writable ?? true;
|
|
338
|
-
let closed = false;
|
|
396
|
+
const inFlight = /* @__PURE__ */ new Map();
|
|
339
397
|
let timer = null;
|
|
340
398
|
let firstQueuedAt = null;
|
|
341
|
-
const
|
|
342
|
-
|
|
343
|
-
|
|
399
|
+
const firstRetryWait = retryMs === false ? 0 : retryMs;
|
|
400
|
+
let retryTimer = null;
|
|
401
|
+
let retryWait = firstRetryWait;
|
|
402
|
+
let warned = false;
|
|
403
|
+
let sending = null;
|
|
344
404
|
const schedule = () => {
|
|
345
|
-
if (!writable || closed) return;
|
|
405
|
+
if (!writable() || closed()) return;
|
|
346
406
|
const now = Date.now();
|
|
347
407
|
firstQueuedAt ??= now;
|
|
348
408
|
if (timer) clearTimeout(timer);
|
|
@@ -352,35 +412,135 @@ function createRecordCache(options) {
|
|
|
352
412
|
void flush();
|
|
353
413
|
}, wait);
|
|
354
414
|
};
|
|
355
|
-
|
|
415
|
+
function flush() {
|
|
356
416
|
if (timer) clearTimeout(timer);
|
|
357
417
|
timer = null;
|
|
358
418
|
firstQueuedAt = null;
|
|
419
|
+
const mine = sending ? sending.then(send) : send();
|
|
420
|
+
sending = mine;
|
|
421
|
+
void mine.finally(() => {
|
|
422
|
+
if (sending === mine) sending = null;
|
|
423
|
+
});
|
|
424
|
+
return mine;
|
|
425
|
+
}
|
|
426
|
+
async function write(name, value) {
|
|
427
|
+
const key = prefix + name;
|
|
428
|
+
try {
|
|
429
|
+
if (value === DELETED) await storage.delete(key);
|
|
430
|
+
else await storage.set(key, value);
|
|
431
|
+
return true;
|
|
432
|
+
} catch (error) {
|
|
433
|
+
if (!warned) {
|
|
434
|
+
warned = true;
|
|
435
|
+
console.warn(`[storage] could not write "${key}"; trying again`, error);
|
|
436
|
+
}
|
|
437
|
+
if (!queued.has(name) && !stopped()) queued.set(name, value);
|
|
438
|
+
return false;
|
|
439
|
+
} finally {
|
|
440
|
+
const count = inFlight.get(name) ?? 1;
|
|
441
|
+
if (count > 1) inFlight.set(name, count - 1);
|
|
442
|
+
else inFlight.delete(name);
|
|
443
|
+
}
|
|
444
|
+
}
|
|
445
|
+
async function send() {
|
|
359
446
|
if (queued.size === 0) return true;
|
|
360
|
-
if (!writable) return false;
|
|
447
|
+
if (!writable()) return false;
|
|
448
|
+
stopRetrying();
|
|
361
449
|
const batch = [...queued];
|
|
362
450
|
queued.clear();
|
|
363
|
-
const
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
return false;
|
|
373
|
-
}
|
|
374
|
-
})
|
|
375
|
-
);
|
|
376
|
-
return landed.every(Boolean);
|
|
451
|
+
for (const [name] of batch) inFlight.set(name, (inFlight.get(name) ?? 0) + 1);
|
|
452
|
+
const landed = await Promise.all(batch.map(([name, value]) => write(name, value)));
|
|
453
|
+
if (landed.every(Boolean)) {
|
|
454
|
+
warned = false;
|
|
455
|
+
retryWait = firstRetryWait;
|
|
456
|
+
return true;
|
|
457
|
+
}
|
|
458
|
+
retry();
|
|
459
|
+
return false;
|
|
377
460
|
}
|
|
461
|
+
function stopRetrying() {
|
|
462
|
+
if (retryTimer) clearTimeout(retryTimer);
|
|
463
|
+
retryTimer = null;
|
|
464
|
+
}
|
|
465
|
+
function retry() {
|
|
466
|
+
stopRetrying();
|
|
467
|
+
if (retryMs === false || closed() || stopped() || queued.size === 0) return;
|
|
468
|
+
retryTimer = setTimeout(() => {
|
|
469
|
+
retryTimer = null;
|
|
470
|
+
void flush();
|
|
471
|
+
}, retryWait);
|
|
472
|
+
retryWait = Math.min(retryWait * 2, retryMaxMs);
|
|
473
|
+
}
|
|
474
|
+
return {
|
|
475
|
+
get size() {
|
|
476
|
+
return queued.size;
|
|
477
|
+
},
|
|
478
|
+
has: (name) => queued.has(name),
|
|
479
|
+
owns: (name) => queued.has(name) || inFlight.has(name),
|
|
480
|
+
add: (name, value) => {
|
|
481
|
+
queued.set(name, value);
|
|
482
|
+
},
|
|
483
|
+
schedule,
|
|
484
|
+
flush,
|
|
485
|
+
stopRetrying,
|
|
486
|
+
discard: () => {
|
|
487
|
+
queued.clear();
|
|
488
|
+
if (timer) clearTimeout(timer);
|
|
489
|
+
timer = null;
|
|
490
|
+
stopRetrying();
|
|
491
|
+
}
|
|
492
|
+
};
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
// src/records.ts
|
|
496
|
+
function createRecordCache(options) {
|
|
497
|
+
return buildCache(options, false).cache;
|
|
498
|
+
}
|
|
499
|
+
function buildCache(options, startUnread) {
|
|
500
|
+
const { storage, prefix } = options;
|
|
501
|
+
const retryMs = options.retryMs ?? 1e3;
|
|
502
|
+
const retryMaxMs = options.retryMaxMs ?? 3e4;
|
|
503
|
+
const values = new Map(options.initial ?? []);
|
|
504
|
+
const listeners = /* @__PURE__ */ new Set();
|
|
505
|
+
const overruledListeners = /* @__PURE__ */ new Set();
|
|
506
|
+
let unread = startUnread;
|
|
507
|
+
let stopped = !(options.writable ?? true);
|
|
508
|
+
const writable = () => !unread && !stopped;
|
|
509
|
+
let closed = false;
|
|
510
|
+
let retryTimer = null;
|
|
511
|
+
let retryWait = retryMs === false ? 0 : retryMs;
|
|
512
|
+
let reading = null;
|
|
513
|
+
let warned = false;
|
|
514
|
+
let landed = () => {
|
|
515
|
+
};
|
|
516
|
+
const hasRead = new Promise((resolve) => {
|
|
517
|
+
if (startUnread) landed = resolve;
|
|
518
|
+
else resolve();
|
|
519
|
+
});
|
|
520
|
+
const emit = (changes) => {
|
|
521
|
+
for (const listener of [...listeners]) listener(changes);
|
|
522
|
+
};
|
|
523
|
+
const queue = createWriteQueue({
|
|
524
|
+
storage,
|
|
525
|
+
prefix,
|
|
526
|
+
debounceMs: options.debounceMs ?? 300,
|
|
527
|
+
maxWaitMs: options.maxWaitMs ?? 1e3,
|
|
528
|
+
retryMs,
|
|
529
|
+
retryMaxMs,
|
|
530
|
+
writable,
|
|
531
|
+
closed: () => closed,
|
|
532
|
+
stopped: () => stopped
|
|
533
|
+
});
|
|
378
534
|
const applyRemote = (changes) => {
|
|
379
535
|
const applied = [];
|
|
536
|
+
const overruled = [];
|
|
380
537
|
for (const [key, value] of changes) {
|
|
381
538
|
if (!key.startsWith(prefix)) continue;
|
|
382
539
|
const name = key.slice(prefix.length);
|
|
383
|
-
if (
|
|
540
|
+
if (queue.owns(name)) {
|
|
541
|
+
overruled.push([name, value]);
|
|
542
|
+
continue;
|
|
543
|
+
}
|
|
384
544
|
if (value === void 0) {
|
|
385
545
|
if (!values.has(name)) continue;
|
|
386
546
|
values.delete(name);
|
|
@@ -389,97 +549,135 @@ function createRecordCache(options) {
|
|
|
389
549
|
}
|
|
390
550
|
applied.push({ name, value, origin: "remote" });
|
|
391
551
|
}
|
|
552
|
+
if (overruled.length > 0) for (const listener of [...overruledListeners]) listener(overruled);
|
|
392
553
|
if (applied.length > 0) emit(applied);
|
|
393
554
|
};
|
|
394
|
-
|
|
555
|
+
let unsubscribe = writable() ? storage.subscribe?.(prefix, applyRemote) : void 0;
|
|
556
|
+
const stopRetrying = () => {
|
|
557
|
+
if (retryTimer) clearTimeout(retryTimer);
|
|
558
|
+
retryTimer = null;
|
|
559
|
+
};
|
|
560
|
+
const failed = (error) => {
|
|
561
|
+
if (!warned) {
|
|
562
|
+
warned = true;
|
|
563
|
+
console.warn(
|
|
564
|
+
`[storage] could not read "${prefix}"; opening empty and holding writes until a read lands`,
|
|
565
|
+
error
|
|
566
|
+
);
|
|
567
|
+
}
|
|
568
|
+
if (retryMs === false || closed || stopped) return;
|
|
569
|
+
retryTimer = setTimeout(() => {
|
|
570
|
+
retryTimer = null;
|
|
571
|
+
void read();
|
|
572
|
+
}, retryWait);
|
|
573
|
+
retryWait = Math.min(retryWait * 2, retryMaxMs);
|
|
574
|
+
};
|
|
575
|
+
async function attempt() {
|
|
576
|
+
stopRetrying();
|
|
577
|
+
const early = [];
|
|
578
|
+
const stopEarly = storage.subscribe?.(prefix, (c) => early.push(...c));
|
|
579
|
+
let listed;
|
|
580
|
+
try {
|
|
581
|
+
listed = await storage.list(prefix);
|
|
582
|
+
} catch (error) {
|
|
583
|
+
stopEarly?.();
|
|
584
|
+
failed(error);
|
|
585
|
+
return false;
|
|
586
|
+
}
|
|
587
|
+
if (closed || stopped) {
|
|
588
|
+
stopEarly?.();
|
|
589
|
+
return false;
|
|
590
|
+
}
|
|
591
|
+
unread = false;
|
|
592
|
+
unsubscribe = storage.subscribe?.(prefix, applyRemote);
|
|
593
|
+
stopEarly?.();
|
|
594
|
+
applyRemote([...listed, ...early]);
|
|
595
|
+
if (queue.size > 0) queue.schedule();
|
|
596
|
+
landed();
|
|
597
|
+
return true;
|
|
598
|
+
}
|
|
599
|
+
function read() {
|
|
600
|
+
if (!unread || stopped || closed) return Promise.resolve(!unread);
|
|
601
|
+
reading ??= attempt().finally(() => {
|
|
602
|
+
reading = null;
|
|
603
|
+
});
|
|
604
|
+
return reading;
|
|
605
|
+
}
|
|
395
606
|
const onPageHide = () => {
|
|
396
|
-
void flush();
|
|
607
|
+
void queue.flush();
|
|
397
608
|
};
|
|
398
609
|
const onVisibility = () => {
|
|
399
|
-
if (document.visibilityState === "hidden") void flush();
|
|
610
|
+
if (document.visibilityState === "hidden") void queue.flush();
|
|
400
611
|
};
|
|
401
612
|
if (typeof window !== "undefined") {
|
|
402
613
|
window.addEventListener("pagehide", onPageHide);
|
|
403
614
|
document.addEventListener("visibilitychange", onVisibility);
|
|
404
615
|
}
|
|
616
|
+
const remove = (name) => {
|
|
617
|
+
if (!unread && !values.has(name) && !queue.has(name)) return;
|
|
618
|
+
values.delete(name);
|
|
619
|
+
queue.add(name, DELETED);
|
|
620
|
+
emit([{ name, value: void 0, origin: "local" }]);
|
|
621
|
+
queue.schedule();
|
|
622
|
+
};
|
|
405
623
|
const cache = {
|
|
406
624
|
prefix,
|
|
407
625
|
get writable() {
|
|
408
|
-
return writable;
|
|
626
|
+
return writable();
|
|
409
627
|
},
|
|
410
628
|
has: (name) => values.has(name),
|
|
411
629
|
get: (name) => values.get(name),
|
|
412
630
|
entries: (namePrefix = "") => [...values].filter(([name]) => name.startsWith(namePrefix)),
|
|
413
631
|
set: (name, value) => {
|
|
632
|
+
if (value === void 0) return remove(name);
|
|
414
633
|
values.set(name, value);
|
|
415
|
-
|
|
634
|
+
queue.add(name, value);
|
|
416
635
|
emit([{ name, value, origin: "local" }]);
|
|
417
|
-
schedule();
|
|
418
|
-
},
|
|
419
|
-
delete: (name) => {
|
|
420
|
-
if (!values.has(name) && !queued.has(name)) return;
|
|
421
|
-
values.delete(name);
|
|
422
|
-
queued.set(name, DELETED);
|
|
423
|
-
emit([{ name, value: void 0, origin: "local" }]);
|
|
424
|
-
schedule();
|
|
636
|
+
queue.schedule();
|
|
425
637
|
},
|
|
638
|
+
delete: remove,
|
|
426
639
|
subscribe: (listener) => {
|
|
427
640
|
listeners.add(listener);
|
|
428
641
|
return () => {
|
|
429
642
|
listeners.delete(listener);
|
|
430
643
|
};
|
|
431
644
|
},
|
|
432
|
-
flush,
|
|
645
|
+
flush: queue.flush,
|
|
646
|
+
read,
|
|
647
|
+
whenRead: () => hasRead,
|
|
433
648
|
close: async () => {
|
|
434
649
|
if (closed) return;
|
|
435
|
-
|
|
650
|
+
stopRetrying();
|
|
651
|
+
await queue.flush();
|
|
436
652
|
closed = true;
|
|
653
|
+
queue.stopRetrying();
|
|
437
654
|
unsubscribe?.();
|
|
438
655
|
if (typeof window !== "undefined") {
|
|
439
656
|
window.removeEventListener("pagehide", onPageHide);
|
|
440
657
|
document.removeEventListener("visibilitychange", onVisibility);
|
|
441
658
|
}
|
|
442
659
|
},
|
|
660
|
+
onOverruled: (listener) => {
|
|
661
|
+
overruledListeners.add(listener);
|
|
662
|
+
return () => {
|
|
663
|
+
overruledListeners.delete(listener);
|
|
664
|
+
};
|
|
665
|
+
},
|
|
443
666
|
seed: (entries) => {
|
|
444
667
|
for (const [name, value] of entries) values.set(name, value);
|
|
445
668
|
},
|
|
446
669
|
stopWriting: () => {
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
timer = null;
|
|
670
|
+
stopped = true;
|
|
671
|
+
queue.discard();
|
|
672
|
+
stopRetrying();
|
|
451
673
|
unsubscribe?.();
|
|
452
|
-
}
|
|
453
|
-
applyRemote
|
|
674
|
+
}
|
|
454
675
|
};
|
|
455
|
-
return cache;
|
|
456
|
-
}
|
|
457
|
-
function stripPrefix(prefix, rows) {
|
|
458
|
-
return rows.map(([key, value]) => [key.slice(prefix.length), value]);
|
|
459
|
-
}
|
|
460
|
-
function unreadable(options, error) {
|
|
461
|
-
console.warn(
|
|
462
|
-
`[storage] could not read "${options.prefix}"; opening empty and not persisting`,
|
|
463
|
-
error
|
|
464
|
-
);
|
|
465
|
-
return createRecordCache({ ...options, writable: false });
|
|
676
|
+
return { cache, failed };
|
|
466
677
|
}
|
|
467
678
|
async function openRecords(options) {
|
|
468
|
-
const
|
|
469
|
-
|
|
470
|
-
let listed;
|
|
471
|
-
try {
|
|
472
|
-
listed = await options.storage.list(options.prefix);
|
|
473
|
-
} catch (error) {
|
|
474
|
-
stopEarly?.();
|
|
475
|
-
return unreadable(options, error);
|
|
476
|
-
}
|
|
477
|
-
const cache = createRecordCache({
|
|
478
|
-
...options,
|
|
479
|
-
initial: stripPrefix(options.prefix, listed)
|
|
480
|
-
});
|
|
481
|
-
stopEarly?.();
|
|
482
|
-
cache.applyRemote(early);
|
|
679
|
+
const { cache } = buildCache(options, true);
|
|
680
|
+
await cache.read();
|
|
483
681
|
return cache;
|
|
484
682
|
}
|
|
485
683
|
function openRecordsSync(options) {
|
|
@@ -487,12 +685,15 @@ function openRecordsSync(options) {
|
|
|
487
685
|
try {
|
|
488
686
|
listed = options.storage.listSync(options.prefix);
|
|
489
687
|
} catch (error) {
|
|
490
|
-
|
|
688
|
+
const { cache, failed } = buildCache(options, true);
|
|
689
|
+
failed(error);
|
|
690
|
+
return cache;
|
|
491
691
|
}
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
692
|
+
const initial = listed.map(([key, value]) => [
|
|
693
|
+
key.slice(options.prefix.length),
|
|
694
|
+
value
|
|
695
|
+
]);
|
|
696
|
+
return createRecordCache({ ...options, initial });
|
|
496
697
|
}
|
|
497
698
|
|
|
498
699
|
export { createDefaultStorage, createIndexedDbAdapter, createMemoryAdapter, createRecordCache, decodeUrlHash, defaultStorage, encodeUrlHash, fallbackStorage, indexedDbAdapter, localStorageAdapter, noneAdapter, openRecords, openRecordsSync, sessionStorageAdapter, urlHashAdapter };
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/indexedDb.ts","../src/json.ts","../src/webStorage.ts","../src/defaultStorage.ts","../src/memory.ts","../src/urlHash.ts","../src/urlHashAdapter.ts","../src/records.ts"],"names":[],"mappings":"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","file":"index.js","sourcesContent":["import type { StorageAdapter, StorageChange } from './types';\n\n/** Options for `createIndexedDbAdapter`. */\nexport interface IndexedDbAdapterOptions {\n /** Default `'weasel'`. */\n database?: string;\n /** Default `'records'`. One object store per database: a store added to a\n * database that already exists is not created. */\n store?: string;\n}\n\ninterface NodeChannel extends BroadcastChannel {\n unref?: () => void;\n}\n\n/** Persist to IndexedDB, which stores structured-clone values natively — typed\n * arrays, `Map`s, `Blob`s — and holds far more than localStorage. Other tabs\n * hear writes through a `BroadcastChannel`. The database opens on first use. */\nexport function createIndexedDbAdapter({\n database = 'weasel',\n store = 'records',\n}: IndexedDbAdapterOptions = {}): StorageAdapter {\n let opened: Promise<IDBDatabase> | null = null;\n const open = (): Promise<IDBDatabase> => {\n opened ??= new Promise((resolve, reject) => {\n if (typeof indexedDB === 'undefined') {\n reject(new Error('[storage] IndexedDB is not available here'));\n return;\n }\n const request = indexedDB.open(database, 1);\n request.onupgradeneeded = () => {\n if (!request.result.objectStoreNames.contains(store)) {\n request.result.createObjectStore(store);\n }\n };\n request.onsuccess = () => {\n const db = request.result;\n if (!db.objectStoreNames.contains(store)) {\n reject(new Error(`[storage] IndexedDB database \"${database}\" has no store \"${store}\"`));\n return;\n }\n resolve(db);\n };\n request.onerror = () => reject(request.error);\n request.onblocked = () => reject(new Error(`[storage] IndexedDB \"${database}\" is blocked`));\n });\n return opened;\n };\n\n const run = <T>(mode: IDBTransactionMode, body: (s: IDBObjectStore) => () => T): Promise<T> =>\n open().then(\n (db) =>\n new Promise<T>((resolve, reject) => {\n const tx = db.transaction(store, mode);\n const result = body(tx.objectStore(store));\n tx.oncomplete = () => resolve(result());\n tx.onerror = () => reject(tx.error);\n tx.onabort = () => reject(tx.error);\n }),\n );\n\n let channel: NodeChannel | null = null;\n const getChannel = (): NodeChannel | null => {\n if (typeof BroadcastChannel === 'undefined') return null;\n if (!channel) {\n channel = new BroadcastChannel(`weasel-storage:${database}:${store}`) as NodeChannel;\n // Node keeps its event loop alive for an open channel; a browser has no unref.\n channel.unref?.();\n }\n return channel;\n };\n const announce = (changes: StorageChange[]): void => {\n getChannel()?.postMessage(changes);\n };\n\n const adapter: StorageAdapter = {\n get: (key) =>\n run('readonly', (s) => {\n const request = s.get(key);\n return () => request.result as unknown;\n }),\n list: (prefix) =>\n run('readonly', (s) => {\n const range = IDBKeyRange.bound(prefix, `${prefix}`);\n const keys = s.getAllKeys(range);\n const values = s.getAll(range);\n return () =>\n keys.result.map((key, i): [string, unknown] => [String(key), values.result[i]]);\n }),\n set: async (key, value) => {\n await run('readwrite', (s) => {\n s.put(value, key);\n return () => undefined;\n });\n announce([[key, value]]);\n },\n delete: async (key) => {\n await run('readwrite', (s) => {\n s.delete(key);\n return () => undefined;\n });\n announce([[key, undefined]]);\n },\n };\n if (typeof BroadcastChannel !== 'undefined') {\n adapter.subscribe = (prefix, on) => {\n const ch = getChannel();\n if (!ch) return () => {};\n const handler = (e: MessageEvent<StorageChange[]>): void => {\n const mine = e.data.filter(([key]) => key.startsWith(prefix));\n if (mine.length > 0) on(mine);\n };\n ch.addEventListener('message', handler);\n return () => ch.removeEventListener('message', handler);\n };\n }\n return adapter;\n}\n\n/** IndexedDB under the default database and store. */\nexport const indexedDbAdapter: StorageAdapter = createIndexedDbAdapter();\n","/** Raw strings written before records existed come back as the string they\n * are. */\nexport function parse(raw: string): unknown {\n try {\n return JSON.parse(raw);\n } catch {\n return raw;\n }\n}\n\n/** Whether `JSON.stringify` would keep `value` exactly. An object key holding\n * `undefined` counts as kept: JSON drops the key, and reading back an absent\n * key yields the same `undefined`. */\nexport function isJsonSafe(value: unknown, seen: Set<object> = new Set()): boolean {\n if (value === null) return true;\n switch (typeof value) {\n case 'string':\n case 'boolean':\n return true;\n case 'number':\n return Number.isFinite(value);\n case 'object': {\n if (seen.has(value)) return false;\n seen.add(value);\n const proto = Object.getPrototypeOf(value);\n const ok = Array.isArray(value)\n ? value.every((v) => isJsonSafe(v, seen))\n : (proto === Object.prototype || proto === null) &&\n Object.values(value).every((v) => v === undefined || isJsonSafe(v, seen));\n seen.delete(value);\n return ok;\n }\n default:\n return false;\n }\n}\n","import { isJsonSafe, parse } from './json';\nimport type { SyncStorageAdapter } from './types';\n\nfunction webStorageAdapter(area: () => Storage, name: string, crossTab: boolean): SyncStorageAdapter {\n const warned = new Set<string>();\n const listNow = (prefix: string): [string, unknown][] => {\n const store = area();\n const out: [string, unknown][] = [];\n for (let i = 0; i < store.length; i++) {\n const key = store.key(i);\n if (key === null || !key.startsWith(prefix)) continue;\n const raw = store.getItem(key);\n if (raw !== null) out.push([key, parse(raw)]);\n }\n return out;\n };\n const adapter: SyncStorageAdapter = {\n get: async (key) => {\n const raw = area().getItem(key);\n return raw === null ? undefined : parse(raw);\n },\n list: async (prefix) => listNow(prefix),\n listSync: listNow,\n set: async (key, value) => {\n if (!warned.has(key) && !isJsonSafe(value)) {\n warned.add(key);\n console.warn(\n `[storage] \"${key}\" holds a value JSON cannot keep exactly; ${name} stores what JSON keeps. Use IndexedDB for binary or non-JSON state.`,\n );\n }\n area().setItem(key, JSON.stringify(value));\n },\n delete: async (key) => {\n area().removeItem(key);\n },\n };\n if (crossTab) {\n adapter.subscribe = (prefix, on) => {\n if (typeof window === 'undefined') return () => {};\n const handler = (e: StorageEvent): void => {\n if (e.key === null || !e.key.startsWith(prefix)) return;\n if (e.storageArea !== area()) return;\n on([[e.key, e.newValue === null ? undefined : parse(e.newValue)]]);\n };\n window.addEventListener('storage', handler);\n return () => window.removeEventListener('storage', handler);\n };\n }\n return adapter;\n}\n\n/** Persist to `localStorage`, one JSON value per record. Survives a reload and\n * reaches other tabs through the `storage` event. */\nexport const localStorageAdapter: SyncStorageAdapter = webStorageAdapter(\n () => localStorage,\n 'localStorage',\n true,\n);\n\n/** Persist to `sessionStorage` — survives a reload but not a new tab. */\nexport const sessionStorageAdapter: SyncStorageAdapter = webStorageAdapter(\n () => sessionStorage,\n 'sessionStorage',\n false,\n);\n","import { indexedDbAdapter } from './indexedDb';\nimport type { StorageAdapter } from './types';\nimport { localStorageAdapter } from './webStorage';\n\n/** `preferred` if it opens, otherwise `fallback` — with a warning. */\nexport async function fallbackStorage(\n preferred: StorageAdapter,\n fallback: StorageAdapter = localStorageAdapter,\n label = 'preferred storage',\n): Promise<StorageAdapter> {\n try {\n await preferred.list(' ');\n return preferred;\n } catch (error) {\n console.warn(`[storage] ${label} would not open; using the fallback instead`, error);\n return fallback;\n }\n}\n\n/** A page-wide default: `preferred`, or localStorage where it will not open,\n * chosen on first call and kept until `resetDefaultStorage` (tests only). */\nexport function createDefaultStorage(\n preferred: StorageAdapter,\n label?: string,\n): { defaultStorage: () => Promise<StorageAdapter>; resetDefaultStorage: () => void } {\n let resolved: Promise<StorageAdapter> | null = null;\n return {\n defaultStorage: () => (resolved ??= fallbackStorage(preferred, localStorageAdapter, label)),\n resetDefaultStorage: () => {\n resolved = null;\n },\n };\n}\n\n/** IndexedDB under the default database, 'weasel', or localStorage where it\n * will not open. Resolved once per page. */\nexport const { defaultStorage, resetDefaultStorage } = createDefaultStorage(indexedDbAdapter, 'IndexedDB');\n","import type { StorageAdapter, StorageChange, SyncStorageAdapter } from './types';\n\ninterface MemorySubscriber {\n owner: StorageAdapter;\n prefix: string;\n on: (changes: StorageChange[]) => void;\n}\n\nconst memorySubscribers = new WeakMap<Map<string, unknown>, Set<MemorySubscriber>>();\n\n/** An in-memory store, discarded on reload. Values are copied in and out, as a\n * real substrate would. Adapters built over one shared `backing` hear each\n * other's writes — two tabs, for a test. */\nexport function createMemoryAdapter(backing: Map<string, unknown> = new Map()): SyncStorageAdapter {\n let subscribers = memorySubscribers.get(backing);\n if (!subscribers) {\n subscribers = new Set();\n memorySubscribers.set(backing, subscribers);\n }\n const peers = subscribers;\n const notify = (key: string, value: unknown): void => {\n for (const sub of peers) {\n if (sub.owner === adapter || !key.startsWith(sub.prefix)) continue;\n const copy = value === undefined ? undefined : structuredClone(value);\n queueMicrotask(() => sub.on([[key, copy]]));\n }\n };\n const listNow = (prefix: string): [string, unknown][] =>\n [...backing]\n .filter(([key]) => key.startsWith(prefix))\n .map(([key, value]): [string, unknown] => [key, structuredClone(value)]);\n const adapter: SyncStorageAdapter = {\n get: async (key) => (backing.has(key) ? structuredClone(backing.get(key)) : undefined),\n list: async (prefix) => listNow(prefix),\n listSync: listNow,\n set: async (key, value) => {\n const copy = structuredClone(value);\n backing.set(key, copy);\n notify(key, copy);\n },\n delete: async (key) => {\n backing.delete(key);\n notify(key, undefined);\n },\n subscribe: (prefix, on) => {\n const sub = { owner: adapter, prefix, on };\n peers.add(sub);\n return () => {\n peers.delete(sub);\n };\n },\n };\n return adapter;\n}\n\n/** Persists nothing and reads back nothing. */\nexport const noneAdapter: SyncStorageAdapter = {\n get: async () => undefined,\n list: async () => [],\n listSync: () => [],\n set: async () => {},\n delete: async () => {},\n};\n","/** Encode a string for the URL fragment. */\nexport function encodeUrlHash(value: string): string {\n return btoa(encodeURIComponent(value));\n}\n\n/** Decode a URL fragment written by `encodeUrlHash`, or `null` if it is\n * malformed. */\nexport function decodeUrlHash(hash: string): string | null {\n if (!hash) return null;\n try {\n return decodeURIComponent(atob(hash));\n } catch {\n return null;\n }\n}\n","import { parse } from './json';\nimport type { StorageAdapter, StorageChange } from './types';\nimport { decodeUrlHash, encodeUrlHash } from './urlHash';\n\nconst URL_HASH_GUARD = typeof window !== 'undefined';\n\nfunction readHashMap(): Record<string, string> {\n if (!URL_HASH_GUARD) return {};\n const raw = decodeUrlHash(window.location.hash.replace(/^#/, ''));\n if (!raw) return {};\n try {\n return JSON.parse(raw) as Record<string, string>;\n } catch {\n return {};\n }\n}\n\n/** What each live subscription last saw, refreshed on this page's own writes\n * so `hashchange` reports only somebody else's. */\nconst hashSubscribers = new Set<{ last: Record<string, string> }>();\n\nfunction writeHashMap(map: Record<string, string>): void {\n if (!URL_HASH_GUARD) return;\n const encoded = encodeUrlHash(JSON.stringify(map));\n window.history.replaceState(null, '', `#${encoded}`);\n for (const sub of hashSubscribers) sub.last = { ...map };\n}\n\n/** Persist into the URL fragment, so the page's link carries its state and can\n * be shared or bookmarked. Changes to the fragment made elsewhere — a pasted\n * link, back and forward — arrive through `hashchange`. */\nexport const urlHashAdapter: StorageAdapter = {\n get: async (key) => {\n const raw = readHashMap()[key];\n return raw === undefined ? undefined : parse(raw);\n },\n list: async (prefix) =>\n Object.entries(readHashMap())\n .filter(([key]) => key.startsWith(prefix))\n .map(([key, raw]): [string, unknown] => [key, parse(raw)]),\n set: async (key, value) => {\n const map = readHashMap();\n map[key] = JSON.stringify(value);\n writeHashMap(map);\n },\n delete: async (key) => {\n const map = readHashMap();\n delete map[key];\n writeHashMap(map);\n },\n subscribe: (prefix, on) => {\n if (!URL_HASH_GUARD) return () => {};\n const sub = { last: readHashMap() };\n hashSubscribers.add(sub);\n const handler = (): void => {\n const next = readHashMap();\n const changes: StorageChange[] = [];\n for (const key of new Set([...Object.keys(sub.last), ...Object.keys(next)])) {\n if (!key.startsWith(prefix) || sub.last[key] === next[key]) continue;\n const raw = next[key];\n changes.push([key, raw === undefined ? undefined : parse(raw)]);\n }\n sub.last = next;\n if (changes.length > 0) on(changes);\n };\n window.addEventListener('hashchange', handler);\n return () => {\n hashSubscribers.delete(sub);\n window.removeEventListener('hashchange', handler);\n };\n },\n};\n","import type { StorageAdapter, StorageChange, SyncStorageAdapter } from './types';\n\n/** One record changing, as a cache listener hears it. `value` is `undefined`\n * for a delete. `local` changes were made through this cache; `remote` ones\n * came from another writer. */\nexport interface RecordChange {\n name: string;\n value: unknown;\n origin: 'local' | 'remote';\n}\n\n/**\n * Every record under one prefix, held in memory: the only thing that talks to\n * a `StorageAdapter`. Names are keys with the prefix removed.\n *\n * Writes land in memory at once and reach storage on a debounce. The newest\n * write to a record wins: another writer's change to a record with a write\n * still queued here is ignored, since the queued write lands after it.\n */\nexport interface RecordCache {\n readonly prefix: string;\n /** False once writing is off — the records could not be read, or the\n * opener marked them read-only — so nothing here can overwrite them. */\n readonly writable: boolean;\n has(name: string): boolean;\n get(name: string): unknown;\n /** Every record whose name starts with `namePrefix`. */\n entries(namePrefix?: string): [string, unknown][];\n set(name: string, value: unknown): void;\n delete(name: string): void;\n subscribe(listener: (changes: RecordChange[]) => void): () => void;\n /** Send queued writes now. Resolves true when every queued write landed or\n * none was queued; false when a write threw (its value stays in memory) or\n * writing is off and writes were queued. */\n flush(): Promise<boolean>;\n /** Send queued writes and stop hearing other writers. */\n close(): Promise<void>;\n}\n\n/** The parts of a cache only its opener uses. */\nexport interface OwnedRecordCache extends RecordCache {\n /** Hold these records without writing them back. */\n seed(entries: Iterable<[string, unknown]>): void;\n stopWriting(): void;\n}\n\n/** Options for `createRecordCache` and `openRecords`. */\nexport interface RecordCacheOptions {\n storage: StorageAdapter;\n prefix: string;\n /** Trailing debounce on writes. Default 300 ms. */\n debounceMs?: number;\n /** The longest a write waits under continuous change. Default 1000 ms. */\n maxWaitMs?: number;\n}\n\nconst DELETED = Symbol('deleted');\n\n/** A cache holding `initial`, with nothing read from storage. */\nexport function createRecordCache(\n options: RecordCacheOptions & { initial?: Iterable<[string, unknown]>; writable?: boolean },\n): OwnedRecordCache {\n const { storage, prefix } = options;\n const debounceMs = options.debounceMs ?? 300;\n const maxWaitMs = options.maxWaitMs ?? 1000;\n const values = new Map<string, unknown>(options.initial ?? []);\n const queued = new Map<string, unknown>();\n const listeners = new Set<(changes: RecordChange[]) => void>();\n let writable = options.writable ?? true;\n let closed = false;\n let timer: ReturnType<typeof setTimeout> | null = null;\n let firstQueuedAt: number | null = null;\n\n const emit = (changes: RecordChange[]): void => {\n for (const listener of [...listeners]) listener(changes);\n };\n\n const schedule = (): void => {\n if (!writable || closed) return;\n const now = Date.now();\n firstQueuedAt ??= now;\n if (timer) clearTimeout(timer);\n const wait = Math.max(0, Math.min(debounceMs, firstQueuedAt + maxWaitMs - now));\n timer = setTimeout(() => {\n timer = null;\n void flush();\n }, wait);\n };\n\n async function flush(): Promise<boolean> {\n if (timer) clearTimeout(timer);\n timer = null;\n firstQueuedAt = null;\n if (queued.size === 0) return true;\n if (!writable) return false;\n const batch = [...queued];\n queued.clear();\n const landed = await Promise.all(\n batch.map(async ([name, value]) => {\n const key = prefix + name;\n try {\n if (value === DELETED) await storage.delete(key);\n else await storage.set(key, value);\n return true;\n } catch (error) {\n console.warn(`[storage] could not write \"${key}\"; keeping it in memory`, error);\n return false;\n }\n }),\n );\n return landed.every(Boolean);\n }\n\n const applyRemote = (changes: StorageChange[]): void => {\n const applied: RecordChange[] = [];\n for (const [key, value] of changes) {\n if (!key.startsWith(prefix)) continue;\n const name = key.slice(prefix.length);\n if (queued.has(name)) continue;\n if (value === undefined) {\n if (!values.has(name)) continue;\n values.delete(name);\n } else {\n values.set(name, value);\n }\n applied.push({ name, value, origin: 'remote' });\n }\n if (applied.length > 0) emit(applied);\n };\n\n const unsubscribe = writable ? storage.subscribe?.(prefix, applyRemote) : undefined;\n\n const onPageHide = (): void => {\n void flush();\n };\n const onVisibility = (): void => {\n if (document.visibilityState === 'hidden') void flush();\n };\n if (typeof window !== 'undefined') {\n window.addEventListener('pagehide', onPageHide);\n document.addEventListener('visibilitychange', onVisibility);\n }\n\n const cache: OwnedRecordCache & { applyRemote: typeof applyRemote } = {\n prefix,\n get writable() {\n return writable;\n },\n has: (name) => values.has(name),\n get: (name) => values.get(name),\n entries: (namePrefix = '') => [...values].filter(([name]) => name.startsWith(namePrefix)),\n set: (name, value) => {\n values.set(name, value);\n queued.set(name, value);\n emit([{ name, value, origin: 'local' }]);\n schedule();\n },\n delete: (name) => {\n if (!values.has(name) && !queued.has(name)) return;\n values.delete(name);\n queued.set(name, DELETED);\n emit([{ name, value: undefined, origin: 'local' }]);\n schedule();\n },\n subscribe: (listener) => {\n listeners.add(listener);\n return () => {\n listeners.delete(listener);\n };\n },\n flush,\n close: async () => {\n if (closed) return;\n await flush();\n closed = true;\n unsubscribe?.();\n if (typeof window !== 'undefined') {\n window.removeEventListener('pagehide', onPageHide);\n document.removeEventListener('visibilitychange', onVisibility);\n }\n },\n seed: (entries) => {\n for (const [name, value] of entries) values.set(name, value);\n },\n stopWriting: () => {\n writable = false;\n queued.clear();\n if (timer) clearTimeout(timer);\n timer = null;\n unsubscribe?.();\n },\n applyRemote,\n };\n return cache;\n}\n\nfunction stripPrefix(prefix: string, rows: [string, unknown][]): [string, unknown][] {\n return rows.map(([key, value]): [string, unknown] => [key.slice(prefix.length), value]);\n}\n\nfunction unreadable(options: RecordCacheOptions, error: unknown): OwnedRecordCache {\n console.warn(\n `[storage] could not read \"${options.prefix}\"; opening empty and not persisting`,\n error,\n );\n return createRecordCache({ ...options, writable: false });\n}\n\n/** Read every record under `prefix` and hold them. A failed read opens the\n * cache empty with writing off, so it never overwrites what it could not\n * read. */\nexport async function openRecords(options: RecordCacheOptions): Promise<OwnedRecordCache> {\n // Listen before listing, so a change landing between the two is not lost.\n const early: StorageChange[] = [];\n const stopEarly = options.storage.subscribe?.(options.prefix, (c) => early.push(...c));\n let listed: [string, unknown][];\n try {\n listed = await options.storage.list(options.prefix);\n } catch (error) {\n stopEarly?.();\n return unreadable(options, error);\n }\n const cache = createRecordCache({\n ...options,\n initial: stripPrefix(options.prefix, listed),\n });\n stopEarly?.();\n (cache as unknown as { applyRemote: (c: StorageChange[]) => void }).applyRemote(early);\n return cache;\n}\n\n/** `openRecords` for an adapter that reads synchronously: the cache is ready\n * on return. A failed read opens it empty with writing off. */\nexport function openRecordsSync(\n options: RecordCacheOptions & { storage: SyncStorageAdapter },\n): OwnedRecordCache {\n let listed: [string, unknown][];\n try {\n listed = options.storage.listSync(options.prefix);\n } catch (error) {\n return unreadable(options, error);\n }\n return createRecordCache({\n ...options,\n initial: stripPrefix(options.prefix, listed),\n });\n}\n"]}
|
|
1
|
+
{"version":3,"sources":["../src/indexedDb.ts","../src/json.ts","../src/webStorage.ts","../src/defaultStorage.ts","../src/memory.ts","../src/urlHash.ts","../src/urlHashAdapter.ts","../src/writeQueue.ts","../src/records.ts"],"names":[],"mappings":"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","file":"index.js","sourcesContent":["import type { StorageAdapter, StorageChange } from './types';\n\n/** Options for `createIndexedDbAdapter`. */\nexport interface IndexedDbAdapterOptions {\n /** Default `'weasel'`. */\n database?: string;\n /** Default `'records'`. One object store per database: a store added to a\n * database that already exists is not created. */\n store?: string;\n}\n\ninterface NodeChannel extends BroadcastChannel {\n unref?: () => void;\n}\n\n/** Persist to IndexedDB, which stores structured-clone values natively — typed\n * arrays, `Map`s, `Blob`s — and holds far more than localStorage. Other tabs\n * hear writes through a `BroadcastChannel`. The database opens on first use. */\nexport function createIndexedDbAdapter({\n database = 'weasel',\n store = 'records',\n}: IndexedDbAdapterOptions = {}): StorageAdapter {\n let opened: Promise<IDBDatabase> | null = null;\n let connection: IDBDatabase | null = null;\n const forget = (db: IDBDatabase): void => {\n if (connection !== db) return;\n connection = null;\n opened = null;\n };\n const open = (): Promise<IDBDatabase> => {\n if (opened) return opened;\n const mine = new Promise<IDBDatabase>((resolve, reject) => {\n if (typeof indexedDB === 'undefined') {\n reject(new Error('[storage] IndexedDB is not available here'));\n return;\n }\n // No `onblocked`: a blocked open is still pending, and lands once the\n // connections in its way close.\n const request = indexedDB.open(database, 1);\n request.onupgradeneeded = () => {\n if (!request.result.objectStoreNames.contains(store)) {\n request.result.createObjectStore(store);\n }\n };\n request.onsuccess = () => {\n const db = request.result;\n if (!db.objectStoreNames.contains(store)) {\n db.close();\n reject(new Error(`[storage] IndexedDB database \"${database}\" has no store \"${store}\"`));\n return;\n }\n connection = db;\n // The browser closing the connection: site data cleared, or evicted.\n db.onclose = () => forget(db);\n // Another connection wants to delete or upgrade the database, and\n // waits for as long as this one stays open.\n db.onversionchange = () => {\n db.close();\n forget(db);\n };\n resolve(db);\n };\n request.onerror = () => reject(request.error);\n });\n opened = mine;\n // A failed open is not kept, so the next call tries again.\n mine.catch(() => {\n if (opened === mine) opened = null;\n });\n return mine;\n };\n\n const run = async <T>(\n mode: IDBTransactionMode,\n body: (s: IDBObjectStore) => () => T,\n ): Promise<T> => {\n let db = await open();\n let tx: IDBTransaction;\n try {\n tx = db.transaction(store, mode);\n } catch {\n // A connection that closed without saying so. One more, on a new one.\n forget(db);\n db = await open();\n tx = db.transaction(store, mode);\n }\n return new Promise<T>((resolve, reject) => {\n const result = body(tx.objectStore(store));\n tx.oncomplete = () => resolve(result());\n tx.onerror = () => reject(tx.error);\n tx.onabort = () => reject(tx.error);\n });\n };\n\n let channel: NodeChannel | null = null;\n const getChannel = (): NodeChannel | null => {\n if (typeof BroadcastChannel === 'undefined') return null;\n if (!channel) {\n channel = new BroadcastChannel(`weasel-storage:${database}:${store}`) as NodeChannel;\n // Node keeps its event loop alive for an open channel; a browser has no unref.\n channel.unref?.();\n }\n return channel;\n };\n const announce = (changes: StorageChange[]): void => {\n getChannel()?.postMessage(changes);\n };\n\n const adapter: StorageAdapter = {\n get: (key) =>\n run('readonly', (s) => {\n const request = s.get(key);\n return () => request.result as unknown;\n }),\n list: (prefix) =>\n run('readonly', (s) => {\n const range = IDBKeyRange.bound(prefix, `${prefix}`);\n const keys = s.getAllKeys(range);\n const values = s.getAll(range);\n return () =>\n keys.result.map((key, i): [string, unknown] => [String(key), values.result[i]]);\n }),\n set: async (key, value) => {\n await run('readwrite', (s) => {\n s.put(value, key);\n return () => undefined;\n });\n announce([[key, value]]);\n },\n delete: async (key) => {\n await run('readwrite', (s) => {\n s.delete(key);\n return () => undefined;\n });\n announce([[key, undefined]]);\n },\n };\n if (typeof BroadcastChannel !== 'undefined') {\n adapter.subscribe = (prefix, on) => {\n const ch = getChannel();\n if (!ch) return () => {};\n const handler = (e: MessageEvent<StorageChange[]>): void => {\n const mine = e.data.filter(([key]) => key.startsWith(prefix));\n if (mine.length > 0) on(mine);\n };\n ch.addEventListener('message', handler);\n return () => ch.removeEventListener('message', handler);\n };\n }\n return adapter;\n}\n\n/** IndexedDB under the default database and store. */\nexport const indexedDbAdapter: StorageAdapter = createIndexedDbAdapter();\n","/** Raw strings written before records existed come back as the string they\n * are. */\nexport function parse(raw: string): unknown {\n try {\n return JSON.parse(raw);\n } catch {\n return raw;\n }\n}\n\n/** Whether `JSON.stringify` would keep `value` exactly. An object key holding\n * `undefined` counts as kept: JSON drops the key, and reading back an absent\n * key yields the same `undefined`. */\nexport function isJsonSafe(value: unknown, seen: Set<object> = new Set()): boolean {\n if (value === null) return true;\n switch (typeof value) {\n case 'string':\n case 'boolean':\n return true;\n case 'number':\n return Number.isFinite(value);\n case 'object': {\n if (seen.has(value)) return false;\n seen.add(value);\n const proto = Object.getPrototypeOf(value);\n const ok = Array.isArray(value)\n ? value.every((v) => isJsonSafe(v, seen))\n : (proto === Object.prototype || proto === null) &&\n Object.values(value).every((v) => v === undefined || isJsonSafe(v, seen));\n seen.delete(value);\n return ok;\n }\n default:\n return false;\n }\n}\n","import { isJsonSafe, parse } from './json';\nimport type { SyncStorageAdapter } from './types';\n\nfunction webStorageAdapter(area: () => Storage, name: string, crossTab: boolean): SyncStorageAdapter {\n const warned = new Set<string>();\n const keysNow = (prefix: string): string[] => {\n const store = area();\n const out: string[] = [];\n for (let i = 0; i < store.length; i++) {\n const key = store.key(i);\n if (key?.startsWith(prefix)) out.push(key);\n }\n return out;\n };\n const listNow = (prefix: string): [string, unknown][] => {\n const store = area();\n const out: [string, unknown][] = [];\n for (const key of keysNow(prefix)) {\n const raw = store.getItem(key);\n if (raw !== null) out.push([key, parse(raw)]);\n }\n return out;\n };\n // The keys each live subscription knows to exist. `clear()` in another tab\n // arrives as one event naming no key, so this is what says what went.\n const subscribers = new Set<{ prefix: string; keys: Set<string> }>();\n const note = (key: string, held: boolean): void => {\n for (const sub of subscribers) {\n if (!key.startsWith(sub.prefix)) continue;\n if (held) sub.keys.add(key);\n else sub.keys.delete(key);\n }\n };\n const adapter: SyncStorageAdapter = {\n get: async (key) => {\n const raw = area().getItem(key);\n return raw === null ? undefined : parse(raw);\n },\n list: async (prefix) => listNow(prefix),\n listSync: listNow,\n set: async (key, value) => {\n if (!warned.has(key) && !isJsonSafe(value)) {\n warned.add(key);\n console.warn(\n `[storage] \"${key}\" holds a value JSON cannot keep exactly; ${name} stores what JSON keeps. Use IndexedDB for binary or non-JSON state.`,\n );\n }\n area().setItem(key, JSON.stringify(value));\n note(key, true);\n },\n delete: async (key) => {\n area().removeItem(key);\n note(key, false);\n },\n };\n if (crossTab) {\n adapter.subscribe = (prefix, on) => {\n if (typeof window === 'undefined') return () => {};\n const sub = { prefix, keys: new Set(keysNow(prefix)) };\n subscribers.add(sub);\n const handler = (e: StorageEvent): void => {\n const store = area();\n if (e.storageArea !== store) return;\n if (e.key === null) {\n const gone = [...sub.keys].filter((key) => store.getItem(key) === null);\n for (const key of gone) sub.keys.delete(key);\n if (gone.length > 0) on(gone.map((key): [string, unknown] => [key, undefined]));\n return;\n }\n if (!e.key.startsWith(prefix)) return;\n if (e.newValue === null) sub.keys.delete(e.key);\n else sub.keys.add(e.key);\n on([[e.key, e.newValue === null ? undefined : parse(e.newValue)]]);\n };\n window.addEventListener('storage', handler);\n return () => {\n subscribers.delete(sub);\n window.removeEventListener('storage', handler);\n };\n };\n }\n return adapter;\n}\n\n/** Persist to `localStorage`, one JSON value per record. Survives a reload and\n * reaches other tabs through the `storage` event. */\nexport const localStorageAdapter: SyncStorageAdapter = webStorageAdapter(\n () => localStorage,\n 'localStorage',\n true,\n);\n\n/** Persist to `sessionStorage` — survives a reload but not a new tab. */\nexport const sessionStorageAdapter: SyncStorageAdapter = webStorageAdapter(\n () => sessionStorage,\n 'sessionStorage',\n false,\n);\n","import { indexedDbAdapter } from './indexedDb';\nimport type { StorageAdapter } from './types';\nimport { localStorageAdapter } from './webStorage';\n\n/** `preferred` if it opens, otherwise `fallback` — with a warning. */\nexport async function fallbackStorage(\n preferred: StorageAdapter,\n fallback: StorageAdapter = localStorageAdapter,\n label = 'preferred storage',\n): Promise<StorageAdapter> {\n try {\n await preferred.list(' ');\n return preferred;\n } catch (error) {\n console.warn(`[storage] ${label} would not open; using the fallback instead`, error);\n return fallback;\n }\n}\n\n/** A page-wide default: `preferred`, or localStorage where it will not open,\n * chosen on first call and kept until `resetDefaultStorage` (tests only). */\nexport function createDefaultStorage(\n preferred: StorageAdapter,\n label?: string,\n): { defaultStorage: () => Promise<StorageAdapter>; resetDefaultStorage: () => void } {\n let resolved: Promise<StorageAdapter> | null = null;\n return {\n defaultStorage: () => (resolved ??= fallbackStorage(preferred, localStorageAdapter, label)),\n resetDefaultStorage: () => {\n resolved = null;\n },\n };\n}\n\n/** IndexedDB under the default database, 'weasel', or localStorage where it\n * will not open. Resolved once per page. */\nexport const { defaultStorage, resetDefaultStorage } = createDefaultStorage(indexedDbAdapter, 'IndexedDB');\n","import type { StorageAdapter, StorageChange, SyncStorageAdapter } from './types';\n\ninterface MemorySubscriber {\n owner: StorageAdapter;\n prefix: string;\n on: (changes: StorageChange[]) => void;\n}\n\nconst memorySubscribers = new WeakMap<Map<string, unknown>, Set<MemorySubscriber>>();\n\n/** An in-memory store, discarded on reload. Values are copied in and out, as a\n * real substrate would. Adapters built over one shared `backing` hear each\n * other's writes — two tabs, for a test. */\nexport function createMemoryAdapter(backing: Map<string, unknown> = new Map()): SyncStorageAdapter {\n let subscribers = memorySubscribers.get(backing);\n if (!subscribers) {\n subscribers = new Set();\n memorySubscribers.set(backing, subscribers);\n }\n const peers = subscribers;\n const notify = (key: string, value: unknown): void => {\n for (const sub of peers) {\n if (sub.owner === adapter || !key.startsWith(sub.prefix)) continue;\n const copy = value === undefined ? undefined : structuredClone(value);\n queueMicrotask(() => sub.on([[key, copy]]));\n }\n };\n const listNow = (prefix: string): [string, unknown][] =>\n [...backing]\n .filter(([key]) => key.startsWith(prefix))\n .map(([key, value]): [string, unknown] => [key, structuredClone(value)]);\n const adapter: SyncStorageAdapter = {\n get: async (key) => (backing.has(key) ? structuredClone(backing.get(key)) : undefined),\n list: async (prefix) => listNow(prefix),\n listSync: listNow,\n set: async (key, value) => {\n const copy = structuredClone(value);\n backing.set(key, copy);\n notify(key, copy);\n },\n delete: async (key) => {\n backing.delete(key);\n notify(key, undefined);\n },\n subscribe: (prefix, on) => {\n const sub = { owner: adapter, prefix, on };\n peers.add(sub);\n return () => {\n peers.delete(sub);\n };\n },\n };\n return adapter;\n}\n\n/** Persists nothing and reads back nothing. */\nexport const noneAdapter: SyncStorageAdapter = {\n get: async () => undefined,\n list: async () => [],\n listSync: () => [],\n set: async () => {},\n delete: async () => {},\n};\n","/** Encode a string for the URL fragment. */\nexport function encodeUrlHash(value: string): string {\n return btoa(encodeURIComponent(value));\n}\n\n/** Decode a URL fragment written by `encodeUrlHash`, or `null` if it is\n * malformed. */\nexport function decodeUrlHash(hash: string): string | null {\n if (!hash) return null;\n try {\n return decodeURIComponent(atob(hash));\n } catch {\n return null;\n }\n}\n","import { parse } from './json';\nimport type { StorageAdapter, StorageChange } from './types';\nimport { decodeUrlHash, encodeUrlHash } from './urlHash';\n\nconst URL_HASH_GUARD = typeof window !== 'undefined';\n\n/** The fragment's records, each still the JSON text it is stored as. Anyone\n * can write a fragment, so whatever is not a map of strings reads as absent.\n * No prototype, so a record may be named for anything an object inherits. */\nfunction readHashMap(): Record<string, string> {\n const map = Object.create(null) as Record<string, string>;\n if (!URL_HASH_GUARD) return map;\n const raw = decodeUrlHash(window.location.hash.replace(/^#/, ''));\n if (!raw) return map;\n let parsed: unknown;\n try {\n parsed = JSON.parse(raw);\n } catch {\n return map;\n }\n if (parsed === null || typeof parsed !== 'object' || Array.isArray(parsed)) return map;\n for (const [key, value] of Object.entries(parsed)) {\n if (typeof value === 'string') map[key] = value;\n }\n return map;\n}\n\n/** What each live subscription last saw, refreshed on this page's own writes\n * so `hashchange` reports only somebody else's. */\nconst hashSubscribers = new Set<{ last: Record<string, string> }>();\n\nfunction writeHashMap(map: Record<string, string>): void {\n if (!URL_HASH_GUARD) return;\n const encoded = encodeUrlHash(JSON.stringify(map));\n window.history.replaceState(window.history.state, '', `#${encoded}`);\n for (const sub of hashSubscribers) sub.last = Object.assign(Object.create(null), map);\n}\n\n/** Persist into the URL fragment, so the page's link carries its state and can\n * be shared or bookmarked. Changes to the fragment made elsewhere — a pasted\n * link, back and forward — arrive through `hashchange`. */\nexport const urlHashAdapter: StorageAdapter = {\n get: async (key) => {\n const raw = readHashMap()[key];\n return raw === undefined ? undefined : parse(raw);\n },\n list: async (prefix) =>\n Object.entries(readHashMap())\n .filter(([key]) => key.startsWith(prefix))\n .map(([key, raw]): [string, unknown] => [key, parse(raw)]),\n set: async (key, value) => {\n const map = readHashMap();\n map[key] = JSON.stringify(value);\n writeHashMap(map);\n },\n delete: async (key) => {\n const map = readHashMap();\n delete map[key];\n writeHashMap(map);\n },\n subscribe: (prefix, on) => {\n if (!URL_HASH_GUARD) return () => {};\n const sub = { last: readHashMap() };\n hashSubscribers.add(sub);\n const handler = (): void => {\n const next = readHashMap();\n const changes: StorageChange[] = [];\n for (const key of new Set([...Object.keys(sub.last), ...Object.keys(next)])) {\n if (!key.startsWith(prefix) || sub.last[key] === next[key]) continue;\n const raw = next[key];\n changes.push([key, raw === undefined ? undefined : parse(raw)]);\n }\n sub.last = next;\n if (changes.length > 0) on(changes);\n };\n window.addEventListener('hashchange', handler);\n return () => {\n hashSubscribers.delete(sub);\n window.removeEventListener('hashchange', handler);\n };\n },\n};\n","import type { StorageAdapter } from './types';\n\n/** What the queue holds for a record to delete. */\nexport const DELETED = Symbol('deleted');\n\n/** What a write queue reads from the cache it writes for. */\nexport interface WriteQueueOptions {\n storage: StorageAdapter;\n prefix: string;\n debounceMs: number;\n maxWaitMs: number;\n retryMs: number | false;\n retryMaxMs: number;\n /** Whether a batch may leave now. */\n writable(): boolean;\n closed(): boolean;\n stopped(): boolean;\n}\n\n/** A cache's writes on their way to storage: the newest per record, sent on a\n * debounce, and sent again on a backoff when storage rejects one. */\nexport interface WriteQueue {\n /** How many records have a write waiting to leave. */\n readonly size: number;\n /** Whether a write to `name` is waiting to leave. */\n has(name: string): boolean;\n /** Whether a write to `name` is waiting or on its way. */\n owns(name: string): boolean;\n /** Queue `value` for `name`, replacing a write still waiting. */\n add(name: string, value: unknown): void;\n /** Send what is queued after the debounce. */\n schedule(): void;\n /** Send what is queued now. Resolves as `RecordCache.flush` does. */\n flush(): Promise<boolean>;\n /** Stop sending rejected writes again on a timer. */\n stopRetrying(): void;\n /** Drop every queued write and every timer. */\n discard(): void;\n}\n\nexport function createWriteQueue(options: WriteQueueOptions): WriteQueue {\n const { storage, prefix, debounceMs, maxWaitMs, retryMs, retryMaxMs, writable, closed, stopped } = options;\n const queued = new Map<string, unknown>();\n // How many writes to each name are on their way to storage.\n const inFlight = new Map<string, number>();\n let timer: ReturnType<typeof setTimeout> | null = null;\n let firstQueuedAt: number | null = null;\n const firstRetryWait = retryMs === false ? 0 : retryMs;\n let retryTimer: ReturnType<typeof setTimeout> | null = null;\n let retryWait = firstRetryWait;\n let warned = false;\n let sending: Promise<boolean> | null = null;\n\n const schedule = (): void => {\n if (!writable() || closed()) return;\n const now = Date.now();\n firstQueuedAt ??= now;\n if (timer) clearTimeout(timer);\n const wait = Math.max(0, Math.min(debounceMs, firstQueuedAt + maxWaitMs - now));\n timer = setTimeout(() => {\n timer = null;\n void flush();\n }, wait);\n };\n\n // One batch is on its way at a time. A rejected write is back in the queue\n // before the next batch is taken, so a newer write to its record stands.\n function flush(): Promise<boolean> {\n if (timer) clearTimeout(timer);\n timer = null;\n firstQueuedAt = null;\n // With nothing on its way the batch leaves in this tick, which a flush\n // on `pagehide` depends on.\n const mine = sending ? sending.then(send) : send();\n sending = mine;\n void mine.finally(() => {\n if (sending === mine) sending = null;\n });\n return mine;\n }\n\n async function write(name: string, value: unknown): Promise<boolean> {\n const key = prefix + name;\n try {\n if (value === DELETED) await storage.delete(key);\n else await storage.set(key, value);\n return true;\n } catch (error) {\n if (!warned) {\n warned = true;\n console.warn(`[storage] could not write \"${key}\"; trying again`, error);\n }\n // A write queued since is newer, and stands.\n if (!queued.has(name) && !stopped()) queued.set(name, value);\n return false;\n } finally {\n const count = inFlight.get(name) ?? 1;\n if (count > 1) inFlight.set(name, count - 1);\n else inFlight.delete(name);\n }\n }\n\n async function send(): Promise<boolean> {\n if (queued.size === 0) return true;\n if (!writable()) return false;\n stopRetrying();\n const batch = [...queued];\n queued.clear();\n for (const [name] of batch) inFlight.set(name, (inFlight.get(name) ?? 0) + 1);\n const landed = await Promise.all(batch.map(([name, value]) => write(name, value)));\n if (landed.every(Boolean)) {\n warned = false;\n retryWait = firstRetryWait;\n return true;\n }\n retry();\n return false;\n }\n\n function stopRetrying(): void {\n if (retryTimer) clearTimeout(retryTimer);\n retryTimer = null;\n }\n\n function retry(): void {\n stopRetrying();\n if (retryMs === false || closed() || stopped() || queued.size === 0) return;\n retryTimer = setTimeout(() => {\n retryTimer = null;\n void flush();\n }, retryWait);\n retryWait = Math.min(retryWait * 2, retryMaxMs);\n }\n\n return {\n get size() {\n return queued.size;\n },\n has: (name) => queued.has(name),\n owns: (name) => queued.has(name) || inFlight.has(name),\n add: (name, value) => {\n queued.set(name, value);\n },\n schedule,\n flush,\n stopRetrying,\n discard: () => {\n queued.clear();\n if (timer) clearTimeout(timer);\n timer = null;\n stopRetrying();\n },\n };\n}\n","import type { StorageAdapter, StorageChange, SyncStorageAdapter } from './types';\nimport { DELETED, createWriteQueue } from './writeQueue';\n\n/** One record changing, as a cache listener hears it. `value` is `undefined`\n * for a delete. `local` changes were made through this cache; `remote` ones\n * came from another writer. */\nexport interface RecordChange {\n name: string;\n value: unknown;\n origin: 'local' | 'remote';\n}\n\n/**\n * Every record under one prefix, held in memory: the only thing that talks to\n * a `StorageAdapter`. Names are keys with the prefix removed.\n *\n * Writes land in memory at once and reach storage on a debounce. The newest\n * write to a record wins: another writer's change to a record with a write\n * still queued or on its way here is ignored, since that write lands after\n * it. A write storage rejects stays queued and is sent again on a backoff.\n */\nexport interface RecordCache {\n readonly prefix: string;\n /** False while writing is off, so nothing here can overwrite the records:\n * until a first read that failed lands on a later try, and for good once\n * the opener marks them read-only. */\n readonly writable: boolean;\n has(name: string): boolean;\n get(name: string): unknown;\n /** Every record whose name starts with `namePrefix`. */\n entries(namePrefix?: string): [string, unknown][];\n /** `undefined` is no value a record can hold: setting it deletes. */\n set(name: string, value: unknown): void;\n delete(name: string): void;\n subscribe(listener: (changes: RecordChange[]) => void): () => void;\n /** Send queued writes now. Resolves true when every queued write landed or\n * none was queued; false when a write threw (it stays queued) or writing\n * is off and writes were queued. */\n flush(): Promise<boolean>;\n /** Try a failed first read again now, ahead of the backoff. Resolves\n * whether the records have been read. */\n read(): Promise<boolean>;\n /** Resolves once the records have been read: at once for a cache that\n * opened with them, and when a later read lands for one whose first read\n * failed. Never resolves for a cache closed or stopped before that. */\n whenRead(): Promise<void>;\n /** Send queued writes, stop hearing other writers, and stop retrying. */\n close(): Promise<void>;\n}\n\n/** The parts of a cache only its opener uses. */\nexport interface OwnedRecordCache extends RecordCache {\n /** Hold these records without writing them back. */\n seed(entries: Iterable<[string, unknown]>): void;\n /** Hear another writer's changes this cache did not take, because its own\n * write to the record was queued or on its way: each a name and the value\n * the other writer set, `undefined` for a delete. */\n onOverruled(listener: (changes: [string, unknown][]) => void): () => void;\n stopWriting(): void;\n}\n\n/** Options for `openRecords`, and the part of `createRecordCache`'s the two share. */\nexport interface RecordCacheOptions {\n storage: StorageAdapter;\n prefix: string;\n /** Trailing debounce on writes. Default 300 ms. */\n debounceMs?: number;\n /** The longest a write waits under continuous change. Default 1000 ms. */\n maxWaitMs?: number;\n /** The wait before a failed first read, or a write storage rejected, is\n * tried again, doubling after each failure. Default 1000 ms; `false` never\n * tries again on its own, leaving a rejected write for the next flush. */\n retryMs?: number | false;\n /** The longest wait between those tries. Default 30000 ms. */\n retryMaxMs?: number;\n}\n\n/** Options for `createRecordCache`. */\nexport interface CreateRecordCacheOptions extends RecordCacheOptions {\n /** The records the cache opens holding, each a name and its value. Default none. */\n initial?: Iterable<[string, unknown]>;\n /** False opens the cache with writing off for good. Default true. */\n writable?: boolean;\n}\n\n/** A cache holding `initial`, with nothing read from storage. */\nexport function createRecordCache(options: CreateRecordCacheOptions): OwnedRecordCache {\n return buildCache(options, false).cache;\n}\n\n/** `startUnread` leaves the cache waiting on a first read: `cache.read()`\n * makes it, and `failed` reports one made elsewhere that threw. */\nfunction buildCache(\n options: CreateRecordCacheOptions,\n startUnread: boolean,\n): { cache: OwnedRecordCache; failed: (error: unknown) => void } {\n const { storage, prefix } = options;\n const retryMs = options.retryMs ?? 1000;\n const retryMaxMs = options.retryMaxMs ?? 30_000;\n const values = new Map<string, unknown>(options.initial ?? []);\n const listeners = new Set<(changes: RecordChange[]) => void>();\n const overruledListeners = new Set<(changes: [string, unknown][]) => void>();\n // Two reasons writing is off. A read landing clears `unread`; nothing\n // clears `stopped`, which is the opener's choice.\n let unread = startUnread;\n let stopped = !(options.writable ?? true);\n const writable = (): boolean => !unread && !stopped;\n let closed = false;\n let retryTimer: ReturnType<typeof setTimeout> | null = null;\n let retryWait = retryMs === false ? 0 : retryMs;\n let reading: Promise<boolean> | null = null;\n let warned = false;\n let landed = (): void => {};\n const hasRead = new Promise<void>((resolve) => {\n if (startUnread) landed = resolve;\n else resolve();\n });\n\n const emit = (changes: RecordChange[]): void => {\n for (const listener of [...listeners]) listener(changes);\n };\n\n const queue = createWriteQueue({\n storage,\n prefix,\n debounceMs: options.debounceMs ?? 300,\n maxWaitMs: options.maxWaitMs ?? 1000,\n retryMs,\n retryMaxMs,\n writable,\n closed: () => closed,\n stopped: () => stopped,\n });\n\n const applyRemote = (changes: StorageChange[]): void => {\n const applied: RecordChange[] = [];\n const overruled: [string, unknown][] = [];\n for (const [key, value] of changes) {\n if (!key.startsWith(prefix)) continue;\n const name = key.slice(prefix.length);\n if (queue.owns(name)) {\n overruled.push([name, value]);\n continue;\n }\n if (value === undefined) {\n if (!values.has(name)) continue;\n values.delete(name);\n } else {\n values.set(name, value);\n }\n applied.push({ name, value, origin: 'remote' });\n }\n if (overruled.length > 0) for (const listener of [...overruledListeners]) listener(overruled);\n if (applied.length > 0) emit(applied);\n };\n\n let unsubscribe = writable() ? storage.subscribe?.(prefix, applyRemote) : undefined;\n\n const stopRetrying = (): void => {\n if (retryTimer) clearTimeout(retryTimer);\n retryTimer = null;\n };\n\n const failed = (error: unknown): void => {\n if (!warned) {\n warned = true;\n console.warn(\n `[storage] could not read \"${prefix}\"; opening empty and holding writes until a read lands`,\n error,\n );\n }\n if (retryMs === false || closed || stopped) return;\n retryTimer = setTimeout(() => {\n retryTimer = null;\n void read();\n }, retryWait);\n retryWait = Math.min(retryWait * 2, retryMaxMs);\n };\n\n async function attempt(): Promise<boolean> {\n stopRetrying();\n // Listen before listing, so a change landing between the two is not lost.\n const early: StorageChange[] = [];\n const stopEarly = storage.subscribe?.(prefix, (c) => early.push(...c));\n let listed: StorageChange[];\n try {\n listed = await storage.list(prefix);\n } catch (error) {\n stopEarly?.();\n failed(error);\n return false;\n }\n if (closed || stopped) {\n stopEarly?.();\n return false;\n }\n unread = false;\n unsubscribe = storage.subscribe?.(prefix, applyRemote);\n stopEarly?.();\n // Skips every name with a write queued while unread, so those win.\n applyRemote([...listed, ...early]);\n if (queue.size > 0) queue.schedule();\n landed();\n return true;\n }\n\n function read(): Promise<boolean> {\n if (!unread || stopped || closed) return Promise.resolve(!unread);\n reading ??= attempt().finally(() => {\n reading = null;\n });\n return reading;\n }\n\n const onPageHide = (): void => {\n void queue.flush();\n };\n const onVisibility = (): void => {\n if (document.visibilityState === 'hidden') void queue.flush();\n };\n if (typeof window !== 'undefined') {\n window.addEventListener('pagehide', onPageHide);\n document.addEventListener('visibilitychange', onVisibility);\n }\n\n const remove = (name: string): void => {\n // An unread cache cannot tell what storage holds, so it queues the delete.\n if (!unread && !values.has(name) && !queue.has(name)) return;\n values.delete(name);\n queue.add(name, DELETED);\n emit([{ name, value: undefined, origin: 'local' }]);\n queue.schedule();\n };\n\n const cache: OwnedRecordCache = {\n prefix,\n get writable() {\n return writable();\n },\n has: (name) => values.has(name),\n get: (name) => values.get(name),\n entries: (namePrefix = '') => [...values].filter(([name]) => name.startsWith(namePrefix)),\n set: (name, value) => {\n if (value === undefined) return remove(name);\n values.set(name, value);\n queue.add(name, value);\n emit([{ name, value, origin: 'local' }]);\n queue.schedule();\n },\n delete: remove,\n subscribe: (listener) => {\n listeners.add(listener);\n return () => {\n listeners.delete(listener);\n };\n },\n flush: queue.flush,\n read,\n whenRead: () => hasRead,\n close: async () => {\n if (closed) return;\n stopRetrying();\n await queue.flush();\n closed = true;\n queue.stopRetrying();\n unsubscribe?.();\n if (typeof window !== 'undefined') {\n window.removeEventListener('pagehide', onPageHide);\n document.removeEventListener('visibilitychange', onVisibility);\n }\n },\n onOverruled: (listener) => {\n overruledListeners.add(listener);\n return () => {\n overruledListeners.delete(listener);\n };\n },\n seed: (entries) => {\n for (const [name, value] of entries) values.set(name, value);\n },\n stopWriting: () => {\n stopped = true;\n queue.discard();\n stopRetrying();\n unsubscribe?.();\n },\n };\n return { cache, failed };\n}\n\n/** Read every record under `prefix` and hold them. A failed read opens the\n * cache empty with writing off, so it never overwrites what it could not\n * read, and tries again on a backoff. When a read lands, the cache holds it,\n * reports it as remote changes, and starts writing, sending first whatever\n * was written meanwhile. */\nexport async function openRecords(options: RecordCacheOptions): Promise<OwnedRecordCache> {\n const { cache } = buildCache(options, true);\n await cache.read();\n return cache;\n}\n\n/** `openRecords` for an adapter that reads synchronously: the cache is ready\n * on return. A failed read opens it empty with writing off, and it recovers\n * the way `openRecords` does. */\nexport function openRecordsSync(\n options: RecordCacheOptions & { storage: SyncStorageAdapter },\n): OwnedRecordCache {\n let listed: [string, unknown][];\n try {\n listed = options.storage.listSync(options.prefix);\n } catch (error) {\n const { cache, failed } = buildCache(options, true);\n failed(error);\n return cache;\n }\n const initial = listed.map(([key, value]): [string, unknown] => [\n key.slice(options.prefix.length),\n value,\n ]);\n return createRecordCache({ ...options, initial });\n}\n"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@weasel-js/storage",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.10.0",
|
|
4
4
|
"description": "Keyed async storage behind one adapter interface — IndexedDB, localStorage, the URL hash, memory — and an in-memory record cache that writes behind and hears other tabs. No React, no weasel deps.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|