@weasel-js/storage 0.0.0-stage → 1.9.4

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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 orochi235
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -1,3 +1,30 @@
1
- # Temporary Holding Version
1
+ # @weasel-js/storage
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ Keyed storage behind one async interface, `StorageAdapter`, with adapters for
4
+ IndexedDB, `localStorage`, `sessionStorage`, the URL hash and memory.
5
+ `openRecords` loads every record under a prefix into a `RecordCache`: reads
6
+ are synchronous from memory, writes land in memory at once and reach storage
7
+ on a debounce, and changes other tabs make arrive as `remote` changes.
8
+
9
+ Adapters that can read synchronously (`localStorage`, `sessionStorage`,
10
+ memory) also implement `listSync`, and `openRecordsSync` opens a cache from
11
+ one with no `await`.
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
+
25
+ `defaultStorage()` is IndexedDB under the database `'weasel'`, or
26
+ `localStorage`, with a warning, where IndexedDB will not open; it chooses once
27
+ per page. `fallbackStorage(preferred, fallback, label)` makes the same choice
28
+ between any two adapters, and `createDefaultStorage(preferred, label)` builds a
29
+ `defaultStorage` of your own over a different preferred adapter, such as
30
+ IndexedDB under another database.
@@ -0,0 +1,163 @@
1
+ /** A change someone else made to one record: its new value, or `undefined`
2
+ * when it was deleted. */
3
+ type StorageChange = [key: string, value: unknown];
4
+ /** Asynchronous keyed storage of structured-clone values, so IndexedDB, the
5
+ * URL, memory or a server can all back one. */
6
+ interface StorageAdapter {
7
+ /** `undefined` when the key is absent. */
8
+ get(key: string): Promise<unknown>;
9
+ list(prefix: string): Promise<[string, unknown][]>;
10
+ /** Every record under `prefix`, read without waiting. Only substrates that
11
+ * can answer synchronously implement it. */
12
+ listSync?(prefix: string): [string, unknown][];
13
+ /** Rejects when the value did not land. */
14
+ set(key: string, value: unknown): Promise<void>;
15
+ delete(key: string): Promise<void>;
16
+ /** Reports writes under `prefix` made by anyone but this adapter — another
17
+ * tab, another instance, a server. Omit when the substrate cannot tell. */
18
+ subscribe?(prefix: string, on: (changes: StorageChange[]) => void): () => void;
19
+ }
20
+ /** An adapter `openRecordsSync` can open. */
21
+ type SyncStorageAdapter = StorageAdapter & Required<Pick<StorageAdapter, 'listSync'>>;
22
+
23
+ /** `preferred` if it opens, otherwise `fallback` — with a warning. */
24
+ declare function fallbackStorage(preferred: StorageAdapter, fallback?: StorageAdapter, label?: string): Promise<StorageAdapter>;
25
+ /** A page-wide default: `preferred`, or localStorage where it will not open,
26
+ * chosen on first call and kept until `resetDefaultStorage` (tests only). */
27
+ declare function createDefaultStorage(preferred: StorageAdapter, label?: string): {
28
+ defaultStorage: () => Promise<StorageAdapter>;
29
+ resetDefaultStorage: () => void;
30
+ };
31
+ /** IndexedDB under the default database, 'weasel', or localStorage where it
32
+ * will not open. Resolved once per page. */
33
+ declare const defaultStorage: () => Promise<StorageAdapter>;
34
+
35
+ /** Options for `createIndexedDbAdapter`. */
36
+ interface IndexedDbAdapterOptions {
37
+ /** Default `'weasel'`. */
38
+ database?: string;
39
+ /** Default `'records'`. One object store per database: a store added to a
40
+ * database that already exists is not created. */
41
+ store?: string;
42
+ }
43
+ /** Persist to IndexedDB, which stores structured-clone values natively — typed
44
+ * arrays, `Map`s, `Blob`s — and holds far more than localStorage. Other tabs
45
+ * hear writes through a `BroadcastChannel`. The database opens on first use. */
46
+ declare function createIndexedDbAdapter({ database, store, }?: IndexedDbAdapterOptions): StorageAdapter;
47
+ /** IndexedDB under the default database and store. */
48
+ declare const indexedDbAdapter: StorageAdapter;
49
+
50
+ /** An in-memory store, discarded on reload. Values are copied in and out, as a
51
+ * real substrate would. Adapters built over one shared `backing` hear each
52
+ * other's writes — two tabs, for a test. */
53
+ declare function createMemoryAdapter(backing?: Map<string, unknown>): SyncStorageAdapter;
54
+ /** Persists nothing and reads back nothing. */
55
+ declare const noneAdapter: SyncStorageAdapter;
56
+
57
+ /** Persist into the URL fragment, so the page's link carries its state and can
58
+ * be shared or bookmarked. Changes to the fragment made elsewhere — a pasted
59
+ * link, back and forward — arrive through `hashchange`. */
60
+ declare const urlHashAdapter: StorageAdapter;
61
+
62
+ /** Persist to `localStorage`, one JSON value per record. Survives a reload and
63
+ * reaches other tabs through the `storage` event. */
64
+ declare const localStorageAdapter: SyncStorageAdapter;
65
+ /** Persist to `sessionStorage` — survives a reload but not a new tab. */
66
+ declare const sessionStorageAdapter: SyncStorageAdapter;
67
+
68
+ /** One record changing, as a cache listener hears it. `value` is `undefined`
69
+ * for a delete. `local` changes were made through this cache; `remote` ones
70
+ * came from another writer. */
71
+ interface RecordChange {
72
+ name: string;
73
+ value: unknown;
74
+ origin: 'local' | 'remote';
75
+ }
76
+ /**
77
+ * Every record under one prefix, held in memory: the only thing that talks to
78
+ * a `StorageAdapter`. Names are keys with the prefix removed.
79
+ *
80
+ * Writes land in memory at once and reach storage on a debounce. The newest
81
+ * write to a record wins: another writer's change to a record with a write
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.
84
+ */
85
+ interface RecordCache {
86
+ readonly prefix: string;
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. */
90
+ readonly writable: boolean;
91
+ has(name: string): boolean;
92
+ get(name: string): unknown;
93
+ /** Every record whose name starts with `namePrefix`. */
94
+ entries(namePrefix?: string): [string, unknown][];
95
+ /** `undefined` is no value a record can hold: setting it deletes. */
96
+ set(name: string, value: unknown): void;
97
+ delete(name: string): void;
98
+ subscribe(listener: (changes: RecordChange[]) => void): () => void;
99
+ /** Send queued writes now. Resolves true when every queued write landed or
100
+ * none was queued; false when a write threw (it stays queued) or writing
101
+ * is off and writes were queued. */
102
+ flush(): Promise<boolean>;
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. */
111
+ close(): Promise<void>;
112
+ }
113
+ /** The parts of a cache only its opener uses. */
114
+ interface OwnedRecordCache extends RecordCache {
115
+ /** Hold these records without writing them back. */
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;
121
+ stopWriting(): void;
122
+ }
123
+ /** Options for `createRecordCache` and `openRecords`. */
124
+ interface RecordCacheOptions {
125
+ storage: StorageAdapter;
126
+ prefix: string;
127
+ /** Trailing debounce on writes. Default 300 ms. */
128
+ debounceMs?: number;
129
+ /** The longest a write waits under continuous change. Default 1000 ms. */
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;
137
+ }
138
+ type CreateOptions = RecordCacheOptions & {
139
+ initial?: Iterable<[string, unknown]>;
140
+ writable?: boolean;
141
+ };
142
+ /** A cache holding `initial`, with nothing read from storage. */
143
+ declare function createRecordCache(options: CreateOptions): OwnedRecordCache;
144
+ /** Read every record under `prefix` and hold them. A failed read opens the
145
+ * cache empty with writing off, so it never overwrites what it could not
146
+ * read, and tries again on a backoff. When a read lands, the cache holds it,
147
+ * reports it as remote changes, and starts writing, sending first whatever
148
+ * was written meanwhile. */
149
+ declare function openRecords(options: RecordCacheOptions): Promise<OwnedRecordCache>;
150
+ /** `openRecords` for an adapter that reads synchronously: the cache is ready
151
+ * on return. A failed read opens it empty with writing off, and it recovers
152
+ * the way `openRecords` does. */
153
+ declare function openRecordsSync(options: RecordCacheOptions & {
154
+ storage: SyncStorageAdapter;
155
+ }): OwnedRecordCache;
156
+
157
+ /** Encode a string for the URL fragment. */
158
+ declare function encodeUrlHash(value: string): string;
159
+ /** Decode a URL fragment written by `encodeUrlHash`, or `null` if it is
160
+ * malformed. */
161
+ declare function decodeUrlHash(hash: string): string | null;
162
+
163
+ 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 };
package/dist/index.js ADDED
@@ -0,0 +1,672 @@
1
+ // src/indexedDb.ts
2
+ function createIndexedDbAdapter({
3
+ database = "weasel",
4
+ store = "records"
5
+ } = {}) {
6
+ let opened = null;
7
+ let connection = null;
8
+ const forget = (db) => {
9
+ if (connection !== db) return;
10
+ connection = null;
11
+ opened = null;
12
+ };
13
+ const open = () => {
14
+ if (opened) return opened;
15
+ const mine = new Promise((resolve, reject) => {
16
+ if (typeof indexedDB === "undefined") {
17
+ reject(new Error("[storage] IndexedDB is not available here"));
18
+ return;
19
+ }
20
+ const request = indexedDB.open(database, 1);
21
+ request.onupgradeneeded = () => {
22
+ if (!request.result.objectStoreNames.contains(store)) {
23
+ request.result.createObjectStore(store);
24
+ }
25
+ };
26
+ request.onsuccess = () => {
27
+ const db = request.result;
28
+ if (!db.objectStoreNames.contains(store)) {
29
+ db.close();
30
+ reject(new Error(`[storage] IndexedDB database "${database}" has no store "${store}"`));
31
+ return;
32
+ }
33
+ connection = db;
34
+ db.onclose = () => forget(db);
35
+ db.onversionchange = () => {
36
+ db.close();
37
+ forget(db);
38
+ };
39
+ resolve(db);
40
+ };
41
+ request.onerror = () => reject(request.error);
42
+ });
43
+ opened = mine;
44
+ mine.catch(() => {
45
+ if (opened === mine) opened = null;
46
+ });
47
+ return mine;
48
+ };
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) => {
60
+ const result = body(tx.objectStore(store));
61
+ tx.oncomplete = () => resolve(result());
62
+ tx.onerror = () => reject(tx.error);
63
+ tx.onabort = () => reject(tx.error);
64
+ });
65
+ };
66
+ let channel = null;
67
+ const getChannel = () => {
68
+ if (typeof BroadcastChannel === "undefined") return null;
69
+ if (!channel) {
70
+ channel = new BroadcastChannel(`weasel-storage:${database}:${store}`);
71
+ channel.unref?.();
72
+ }
73
+ return channel;
74
+ };
75
+ const announce = (changes) => {
76
+ getChannel()?.postMessage(changes);
77
+ };
78
+ const adapter = {
79
+ get: (key) => run("readonly", (s) => {
80
+ const request = s.get(key);
81
+ return () => request.result;
82
+ }),
83
+ list: (prefix) => run("readonly", (s) => {
84
+ const range = IDBKeyRange.bound(prefix, `${prefix}\uFFFF`);
85
+ const keys = s.getAllKeys(range);
86
+ const values = s.getAll(range);
87
+ return () => keys.result.map((key, i) => [String(key), values.result[i]]);
88
+ }),
89
+ set: async (key, value) => {
90
+ await run("readwrite", (s) => {
91
+ s.put(value, key);
92
+ return () => void 0;
93
+ });
94
+ announce([[key, value]]);
95
+ },
96
+ delete: async (key) => {
97
+ await run("readwrite", (s) => {
98
+ s.delete(key);
99
+ return () => void 0;
100
+ });
101
+ announce([[key, void 0]]);
102
+ }
103
+ };
104
+ if (typeof BroadcastChannel !== "undefined") {
105
+ adapter.subscribe = (prefix, on) => {
106
+ const ch = getChannel();
107
+ if (!ch) return () => {
108
+ };
109
+ const handler = (e) => {
110
+ const mine = e.data.filter(([key]) => key.startsWith(prefix));
111
+ if (mine.length > 0) on(mine);
112
+ };
113
+ ch.addEventListener("message", handler);
114
+ return () => ch.removeEventListener("message", handler);
115
+ };
116
+ }
117
+ return adapter;
118
+ }
119
+ var indexedDbAdapter = createIndexedDbAdapter();
120
+
121
+ // src/json.ts
122
+ function parse(raw) {
123
+ try {
124
+ return JSON.parse(raw);
125
+ } catch {
126
+ return raw;
127
+ }
128
+ }
129
+ function isJsonSafe(value, seen = /* @__PURE__ */ new Set()) {
130
+ if (value === null) return true;
131
+ switch (typeof value) {
132
+ case "string":
133
+ case "boolean":
134
+ return true;
135
+ case "number":
136
+ return Number.isFinite(value);
137
+ case "object": {
138
+ if (seen.has(value)) return false;
139
+ seen.add(value);
140
+ const proto = Object.getPrototypeOf(value);
141
+ const ok = Array.isArray(value) ? value.every((v) => isJsonSafe(v, seen)) : (proto === Object.prototype || proto === null) && Object.values(value).every((v) => v === void 0 || isJsonSafe(v, seen));
142
+ seen.delete(value);
143
+ return ok;
144
+ }
145
+ default:
146
+ return false;
147
+ }
148
+ }
149
+
150
+ // src/webStorage.ts
151
+ function webStorageAdapter(area, name, crossTab) {
152
+ const warned = /* @__PURE__ */ new Set();
153
+ const keysNow = (prefix) => {
154
+ const store = area();
155
+ const out = [];
156
+ for (let i = 0; i < store.length; i++) {
157
+ const key = store.key(i);
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)) {
166
+ const raw = store.getItem(key);
167
+ if (raw !== null) out.push([key, parse(raw)]);
168
+ }
169
+ return out;
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
+ };
179
+ const adapter = {
180
+ get: async (key) => {
181
+ const raw = area().getItem(key);
182
+ return raw === null ? void 0 : parse(raw);
183
+ },
184
+ list: async (prefix) => listNow(prefix),
185
+ listSync: listNow,
186
+ set: async (key, value) => {
187
+ if (!warned.has(key) && !isJsonSafe(value)) {
188
+ warned.add(key);
189
+ console.warn(
190
+ `[storage] "${key}" holds a value JSON cannot keep exactly; ${name} stores what JSON keeps. Use IndexedDB for binary or non-JSON state.`
191
+ );
192
+ }
193
+ area().setItem(key, JSON.stringify(value));
194
+ note(key, true);
195
+ },
196
+ delete: async (key) => {
197
+ area().removeItem(key);
198
+ note(key, false);
199
+ }
200
+ };
201
+ if (crossTab) {
202
+ adapter.subscribe = (prefix, on) => {
203
+ if (typeof window === "undefined") return () => {
204
+ };
205
+ const sub = { prefix, keys: new Set(keysNow(prefix)) };
206
+ subscribers.add(sub);
207
+ const handler = (e) => {
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);
219
+ on([[e.key, e.newValue === null ? void 0 : parse(e.newValue)]]);
220
+ };
221
+ window.addEventListener("storage", handler);
222
+ return () => {
223
+ subscribers.delete(sub);
224
+ window.removeEventListener("storage", handler);
225
+ };
226
+ };
227
+ }
228
+ return adapter;
229
+ }
230
+ var localStorageAdapter = webStorageAdapter(
231
+ () => localStorage,
232
+ "localStorage",
233
+ true
234
+ );
235
+ var sessionStorageAdapter = webStorageAdapter(
236
+ () => sessionStorage,
237
+ "sessionStorage",
238
+ false
239
+ );
240
+
241
+ // src/defaultStorage.ts
242
+ async function fallbackStorage(preferred, fallback = localStorageAdapter, label = "preferred storage") {
243
+ try {
244
+ await preferred.list(" ");
245
+ return preferred;
246
+ } catch (error) {
247
+ console.warn(`[storage] ${label} would not open; using the fallback instead`, error);
248
+ return fallback;
249
+ }
250
+ }
251
+ function createDefaultStorage(preferred, label) {
252
+ let resolved = null;
253
+ return {
254
+ defaultStorage: () => resolved ??= fallbackStorage(preferred, localStorageAdapter, label),
255
+ resetDefaultStorage: () => {
256
+ resolved = null;
257
+ }
258
+ };
259
+ }
260
+ var { defaultStorage, resetDefaultStorage } = createDefaultStorage(indexedDbAdapter, "IndexedDB");
261
+
262
+ // src/memory.ts
263
+ var memorySubscribers = /* @__PURE__ */ new WeakMap();
264
+ function createMemoryAdapter(backing = /* @__PURE__ */ new Map()) {
265
+ let subscribers = memorySubscribers.get(backing);
266
+ if (!subscribers) {
267
+ subscribers = /* @__PURE__ */ new Set();
268
+ memorySubscribers.set(backing, subscribers);
269
+ }
270
+ const peers = subscribers;
271
+ const notify = (key, value) => {
272
+ for (const sub of peers) {
273
+ if (sub.owner === adapter || !key.startsWith(sub.prefix)) continue;
274
+ const copy = value === void 0 ? void 0 : structuredClone(value);
275
+ queueMicrotask(() => sub.on([[key, copy]]));
276
+ }
277
+ };
278
+ const listNow = (prefix) => [...backing].filter(([key]) => key.startsWith(prefix)).map(([key, value]) => [key, structuredClone(value)]);
279
+ const adapter = {
280
+ get: async (key) => backing.has(key) ? structuredClone(backing.get(key)) : void 0,
281
+ list: async (prefix) => listNow(prefix),
282
+ listSync: listNow,
283
+ set: async (key, value) => {
284
+ const copy = structuredClone(value);
285
+ backing.set(key, copy);
286
+ notify(key, copy);
287
+ },
288
+ delete: async (key) => {
289
+ backing.delete(key);
290
+ notify(key, void 0);
291
+ },
292
+ subscribe: (prefix, on) => {
293
+ const sub = { owner: adapter, prefix, on };
294
+ peers.add(sub);
295
+ return () => {
296
+ peers.delete(sub);
297
+ };
298
+ }
299
+ };
300
+ return adapter;
301
+ }
302
+ var noneAdapter = {
303
+ get: async () => void 0,
304
+ list: async () => [],
305
+ listSync: () => [],
306
+ set: async () => {
307
+ },
308
+ delete: async () => {
309
+ }
310
+ };
311
+
312
+ // src/urlHash.ts
313
+ function encodeUrlHash(value) {
314
+ return btoa(encodeURIComponent(value));
315
+ }
316
+ function decodeUrlHash(hash) {
317
+ if (!hash) return null;
318
+ try {
319
+ return decodeURIComponent(atob(hash));
320
+ } catch {
321
+ return null;
322
+ }
323
+ }
324
+
325
+ // src/urlHashAdapter.ts
326
+ var URL_HASH_GUARD = typeof window !== "undefined";
327
+ function readHashMap() {
328
+ const map = /* @__PURE__ */ Object.create(null);
329
+ if (!URL_HASH_GUARD) return map;
330
+ const raw = decodeUrlHash(window.location.hash.replace(/^#/, ""));
331
+ if (!raw) return map;
332
+ let parsed;
333
+ try {
334
+ parsed = JSON.parse(raw);
335
+ } catch {
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;
341
+ }
342
+ return map;
343
+ }
344
+ var hashSubscribers = /* @__PURE__ */ new Set();
345
+ function writeHashMap(map) {
346
+ if (!URL_HASH_GUARD) return;
347
+ const encoded = encodeUrlHash(JSON.stringify(map));
348
+ window.history.replaceState(window.history.state, "", `#${encoded}`);
349
+ for (const sub of hashSubscribers) sub.last = Object.assign(/* @__PURE__ */ Object.create(null), map);
350
+ }
351
+ var urlHashAdapter = {
352
+ get: async (key) => {
353
+ const raw = readHashMap()[key];
354
+ return raw === void 0 ? void 0 : parse(raw);
355
+ },
356
+ list: async (prefix) => Object.entries(readHashMap()).filter(([key]) => key.startsWith(prefix)).map(([key, raw]) => [key, parse(raw)]),
357
+ set: async (key, value) => {
358
+ const map = readHashMap();
359
+ map[key] = JSON.stringify(value);
360
+ writeHashMap(map);
361
+ },
362
+ delete: async (key) => {
363
+ const map = readHashMap();
364
+ delete map[key];
365
+ writeHashMap(map);
366
+ },
367
+ subscribe: (prefix, on) => {
368
+ if (!URL_HASH_GUARD) return () => {
369
+ };
370
+ const sub = { last: readHashMap() };
371
+ hashSubscribers.add(sub);
372
+ const handler = () => {
373
+ const next = readHashMap();
374
+ const changes = [];
375
+ for (const key of /* @__PURE__ */ new Set([...Object.keys(sub.last), ...Object.keys(next)])) {
376
+ if (!key.startsWith(prefix) || sub.last[key] === next[key]) continue;
377
+ const raw = next[key];
378
+ changes.push([key, raw === void 0 ? void 0 : parse(raw)]);
379
+ }
380
+ sub.last = next;
381
+ if (changes.length > 0) on(changes);
382
+ };
383
+ window.addEventListener("hashchange", handler);
384
+ return () => {
385
+ hashSubscribers.delete(sub);
386
+ window.removeEventListener("hashchange", handler);
387
+ };
388
+ }
389
+ };
390
+
391
+ // src/records.ts
392
+ var DELETED = /* @__PURE__ */ Symbol("deleted");
393
+ function createRecordCache(options) {
394
+ return buildCache(options, false).cache;
395
+ }
396
+ function buildCache(options, startUnread) {
397
+ const { storage, prefix } = options;
398
+ const debounceMs = options.debounceMs ?? 300;
399
+ const maxWaitMs = options.maxWaitMs ?? 1e3;
400
+ const retryMs = options.retryMs ?? 1e3;
401
+ const retryMaxMs = options.retryMaxMs ?? 3e4;
402
+ const values = new Map(options.initial ?? []);
403
+ const queued = /* @__PURE__ */ new Map();
404
+ const inFlight = /* @__PURE__ */ new Map();
405
+ const listeners = /* @__PURE__ */ new Set();
406
+ const overruledListeners = /* @__PURE__ */ new Set();
407
+ let unread = startUnread;
408
+ let stopped = !(options.writable ?? true);
409
+ const writable = () => !unread && !stopped;
410
+ let closed = false;
411
+ let timer = null;
412
+ let firstQueuedAt = null;
413
+ let retryTimer = null;
414
+ const firstRetryWait = retryMs === false ? 0 : retryMs;
415
+ let retryWait = firstRetryWait;
416
+ let reading = null;
417
+ let warned = false;
418
+ let writeRetryTimer = null;
419
+ let writeRetryWait = retryWait;
420
+ let writeWarned = false;
421
+ let sending = null;
422
+ let landed = () => {
423
+ };
424
+ const hasRead = new Promise((resolve) => {
425
+ if (startUnread) landed = resolve;
426
+ else resolve();
427
+ });
428
+ const emit = (changes) => {
429
+ for (const listener of [...listeners]) listener(changes);
430
+ };
431
+ const schedule = () => {
432
+ if (!writable() || closed) return;
433
+ const now = Date.now();
434
+ firstQueuedAt ??= now;
435
+ if (timer) clearTimeout(timer);
436
+ const wait = Math.max(0, Math.min(debounceMs, firstQueuedAt + maxWaitMs - now));
437
+ timer = setTimeout(() => {
438
+ timer = null;
439
+ void flush();
440
+ }, wait);
441
+ };
442
+ function flush() {
443
+ if (timer) clearTimeout(timer);
444
+ timer = null;
445
+ firstQueuedAt = null;
446
+ const mine = sending ? sending.then(send) : send();
447
+ sending = mine;
448
+ void mine.finally(() => {
449
+ if (sending === mine) sending = null;
450
+ });
451
+ return mine;
452
+ }
453
+ async function send() {
454
+ if (queued.size === 0) return true;
455
+ if (!writable()) return false;
456
+ stopRetryingWrites();
457
+ const batch = [...queued];
458
+ queued.clear();
459
+ for (const [name] of batch) inFlight.set(name, (inFlight.get(name) ?? 0) + 1);
460
+ const landed2 = await Promise.all(
461
+ batch.map(async ([name, value]) => {
462
+ const key = prefix + name;
463
+ try {
464
+ if (value === DELETED) await storage.delete(key);
465
+ else await storage.set(key, value);
466
+ return true;
467
+ } catch (error) {
468
+ if (!writeWarned) {
469
+ writeWarned = true;
470
+ console.warn(`[storage] could not write "${key}"; trying again`, error);
471
+ }
472
+ if (!queued.has(name) && !stopped) queued.set(name, value);
473
+ return false;
474
+ } finally {
475
+ const count = inFlight.get(name) ?? 1;
476
+ if (count > 1) inFlight.set(name, count - 1);
477
+ else inFlight.delete(name);
478
+ }
479
+ })
480
+ );
481
+ if (landed2.every(Boolean)) {
482
+ writeWarned = false;
483
+ writeRetryWait = firstRetryWait;
484
+ return true;
485
+ }
486
+ retryWrites();
487
+ return false;
488
+ }
489
+ function stopRetryingWrites() {
490
+ if (writeRetryTimer) clearTimeout(writeRetryTimer);
491
+ writeRetryTimer = null;
492
+ }
493
+ function retryWrites() {
494
+ stopRetryingWrites();
495
+ if (retryMs === false || closed || stopped || queued.size === 0) return;
496
+ writeRetryTimer = setTimeout(() => {
497
+ writeRetryTimer = null;
498
+ void flush();
499
+ }, writeRetryWait);
500
+ writeRetryWait = Math.min(writeRetryWait * 2, retryMaxMs);
501
+ }
502
+ const applyRemote = (changes) => {
503
+ const applied = [];
504
+ const overruled = [];
505
+ for (const [key, value] of changes) {
506
+ if (!key.startsWith(prefix)) continue;
507
+ const name = key.slice(prefix.length);
508
+ if (queued.has(name) || inFlight.has(name)) {
509
+ overruled.push([name, value]);
510
+ continue;
511
+ }
512
+ if (value === void 0) {
513
+ if (!values.has(name)) continue;
514
+ values.delete(name);
515
+ } else {
516
+ values.set(name, value);
517
+ }
518
+ applied.push({ name, value, origin: "remote" });
519
+ }
520
+ if (overruled.length > 0) for (const listener of [...overruledListeners]) listener(overruled);
521
+ if (applied.length > 0) emit(applied);
522
+ };
523
+ let unsubscribe = writable() ? storage.subscribe?.(prefix, applyRemote) : void 0;
524
+ const stopRetrying = () => {
525
+ if (retryTimer) clearTimeout(retryTimer);
526
+ retryTimer = null;
527
+ };
528
+ const failed = (error) => {
529
+ if (!warned) {
530
+ warned = true;
531
+ console.warn(
532
+ `[storage] could not read "${prefix}"; opening empty and holding writes until a read lands`,
533
+ error
534
+ );
535
+ }
536
+ if (retryMs === false || closed || stopped) return;
537
+ retryTimer = setTimeout(() => {
538
+ retryTimer = null;
539
+ void read();
540
+ }, retryWait);
541
+ retryWait = Math.min(retryWait * 2, retryMaxMs);
542
+ };
543
+ async function attempt() {
544
+ stopRetrying();
545
+ const early = [];
546
+ const stopEarly = storage.subscribe?.(prefix, (c) => early.push(...c));
547
+ let listed;
548
+ try {
549
+ listed = await storage.list(prefix);
550
+ } catch (error) {
551
+ stopEarly?.();
552
+ failed(error);
553
+ return false;
554
+ }
555
+ if (closed || stopped) {
556
+ stopEarly?.();
557
+ return false;
558
+ }
559
+ unread = false;
560
+ unsubscribe = storage.subscribe?.(prefix, applyRemote);
561
+ stopEarly?.();
562
+ applyRemote([...listed, ...early]);
563
+ if (queued.size > 0) schedule();
564
+ landed();
565
+ return true;
566
+ }
567
+ function read() {
568
+ if (!unread || stopped || closed) return Promise.resolve(!unread);
569
+ reading ??= attempt().finally(() => {
570
+ reading = null;
571
+ });
572
+ return reading;
573
+ }
574
+ const onPageHide = () => {
575
+ void flush();
576
+ };
577
+ const onVisibility = () => {
578
+ if (document.visibilityState === "hidden") void flush();
579
+ };
580
+ if (typeof window !== "undefined") {
581
+ window.addEventListener("pagehide", onPageHide);
582
+ document.addEventListener("visibilitychange", onVisibility);
583
+ }
584
+ const remove = (name) => {
585
+ if (!unread && !values.has(name) && !queued.has(name)) return;
586
+ values.delete(name);
587
+ queued.set(name, DELETED);
588
+ emit([{ name, value: void 0, origin: "local" }]);
589
+ schedule();
590
+ };
591
+ const cache = {
592
+ prefix,
593
+ get writable() {
594
+ return writable();
595
+ },
596
+ has: (name) => values.has(name),
597
+ get: (name) => values.get(name),
598
+ entries: (namePrefix = "") => [...values].filter(([name]) => name.startsWith(namePrefix)),
599
+ set: (name, value) => {
600
+ if (value === void 0) return remove(name);
601
+ values.set(name, value);
602
+ queued.set(name, value);
603
+ emit([{ name, value, origin: "local" }]);
604
+ schedule();
605
+ },
606
+ delete: remove,
607
+ subscribe: (listener) => {
608
+ listeners.add(listener);
609
+ return () => {
610
+ listeners.delete(listener);
611
+ };
612
+ },
613
+ flush,
614
+ read,
615
+ whenRead: () => hasRead,
616
+ close: async () => {
617
+ if (closed) return;
618
+ stopRetrying();
619
+ await flush();
620
+ closed = true;
621
+ stopRetryingWrites();
622
+ unsubscribe?.();
623
+ if (typeof window !== "undefined") {
624
+ window.removeEventListener("pagehide", onPageHide);
625
+ document.removeEventListener("visibilitychange", onVisibility);
626
+ }
627
+ },
628
+ onOverruled: (listener) => {
629
+ overruledListeners.add(listener);
630
+ return () => {
631
+ overruledListeners.delete(listener);
632
+ };
633
+ },
634
+ seed: (entries) => {
635
+ for (const [name, value] of entries) values.set(name, value);
636
+ },
637
+ stopWriting: () => {
638
+ stopped = true;
639
+ queued.clear();
640
+ if (timer) clearTimeout(timer);
641
+ timer = null;
642
+ stopRetrying();
643
+ stopRetryingWrites();
644
+ unsubscribe?.();
645
+ }
646
+ };
647
+ return { cache, failed };
648
+ }
649
+ async function openRecords(options) {
650
+ const { cache } = buildCache(options, true);
651
+ await cache.read();
652
+ return cache;
653
+ }
654
+ function openRecordsSync(options) {
655
+ let listed;
656
+ try {
657
+ listed = options.storage.listSync(options.prefix);
658
+ } catch (error) {
659
+ const { cache, failed } = buildCache(options, true);
660
+ failed(error);
661
+ return cache;
662
+ }
663
+ const initial = listed.map(([key, value]) => [
664
+ key.slice(options.prefix.length),
665
+ value
666
+ ]);
667
+ return createRecordCache({ ...options, initial });
668
+ }
669
+
670
+ export { createDefaultStorage, createIndexedDbAdapter, createMemoryAdapter, createRecordCache, decodeUrlHash, defaultStorage, encodeUrlHash, fallbackStorage, indexedDbAdapter, localStorageAdapter, noneAdapter, openRecords, openRecordsSync, sessionStorageAdapter, urlHashAdapter };
671
+ //# sourceMappingURL=index.js.map
672
+ //# sourceMappingURL=index.js.map
@@ -0,0 +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":["landed"],"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, 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 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 `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 /** 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\ntype CreateOptions = RecordCacheOptions & {\n initial?: Iterable<[string, unknown]>;\n writable?: boolean;\n};\n\nconst DELETED = Symbol('deleted');\n\n/** A cache holding `initial`, with nothing read from storage. */\nexport function createRecordCache(options: CreateOptions): 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: CreateOptions,\n startUnread: boolean,\n): { cache: OwnedRecordCache; failed: (error: unknown) => void } {\n const { storage, prefix } = options;\n const debounceMs = options.debounceMs ?? 300;\n const maxWaitMs = options.maxWaitMs ?? 1000;\n const retryMs = options.retryMs ?? 1000;\n const retryMaxMs = options.retryMaxMs ?? 30_000;\n const values = new Map<string, unknown>(options.initial ?? []);\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 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 timer: ReturnType<typeof setTimeout> | null = null;\n let firstQueuedAt: number | null = null;\n let retryTimer: ReturnType<typeof setTimeout> | null = null;\n const firstRetryWait = retryMs === false ? 0 : retryMs;\n let retryWait = firstRetryWait;\n let reading: Promise<boolean> | null = null;\n let warned = false;\n let writeRetryTimer: ReturnType<typeof setTimeout> | null = null;\n let writeRetryWait = retryWait;\n let writeWarned = false;\n let sending: Promise<boolean> | null = null;\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 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 send(): Promise<boolean> {\n if (queued.size === 0) return true;\n if (!writable()) return false;\n stopRetryingWrites();\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(\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 if (!writeWarned) {\n writeWarned = 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 if (landed.every(Boolean)) {\n writeWarned = false;\n writeRetryWait = firstRetryWait;\n return true;\n }\n retryWrites();\n return false;\n }\n\n function stopRetryingWrites(): void {\n if (writeRetryTimer) clearTimeout(writeRetryTimer);\n writeRetryTimer = null;\n }\n\n function retryWrites(): void {\n stopRetryingWrites();\n if (retryMs === false || closed || stopped || queued.size === 0) return;\n writeRetryTimer = setTimeout(() => {\n writeRetryTimer = null;\n void flush();\n }, writeRetryWait);\n writeRetryWait = Math.min(writeRetryWait * 2, retryMaxMs);\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 (queued.has(name) || inFlight.has(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 (queued.size > 0) 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 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 remove = (name: string): void => {\n // An unread cache cannot tell what storage holds, so it queues the delete.\n if (!unread && !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\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 queued.set(name, value);\n emit([{ name, value, origin: 'local' }]);\n schedule();\n },\n delete: remove,\n subscribe: (listener) => {\n listeners.add(listener);\n return () => {\n listeners.delete(listener);\n };\n },\n flush,\n read,\n whenRead: () => hasRead,\n close: async () => {\n if (closed) return;\n stopRetrying();\n await flush();\n closed = true;\n stopRetryingWrites();\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 queued.clear();\n if (timer) clearTimeout(timer);\n timer = null;\n stopRetrying();\n stopRetryingWrites();\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,43 @@
1
1
  {
2
2
  "name": "@weasel-js/storage",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
6
- }
3
+ "version": "1.9.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
+ "license": "MIT",
6
+ "type": "module",
7
+ "sideEffects": false,
8
+ "main": "./dist/index.js",
9
+ "module": "./dist/index.js",
10
+ "types": "./dist/index.d.ts",
11
+ "exports": {
12
+ ".": {
13
+ "import": "./dist/index.js",
14
+ "types": "./dist/index.d.ts"
15
+ },
16
+ "./package.json": "./package.json"
17
+ },
18
+ "author": "orochi235",
19
+ "homepage": "https://orochi235.github.io/weasel/",
20
+ "repository": {
21
+ "type": "git",
22
+ "url": "git+https://github.com/orochi235/weasel.git",
23
+ "directory": "packages/storage"
24
+ },
25
+ "bugs": {
26
+ "url": "https://github.com/orochi235/weasel/issues"
27
+ },
28
+ "engines": {
29
+ "node": ">=22"
30
+ },
31
+ "files": [
32
+ "dist",
33
+ "README.md",
34
+ "LICENSE"
35
+ ],
36
+ "scripts": {
37
+ "build": "tsup"
38
+ },
39
+ "publishConfig": {
40
+ "access": "public",
41
+ "provenance": true
42
+ }
43
+ }