@angular-libs/signal-storage 0.1.0 → 0.2.0-beta.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.
@@ -1,33 +1,105 @@
1
1
  import * as i0 from '@angular/core';
2
2
  import { InjectionToken, signal, Optional, Inject, Injectable, resource } from '@angular/core';
3
3
 
4
- const SIGNAL_STORAGE_TOKEN = new InjectionToken('SIGNAL_STORAGE_TOKEN', {
4
+ const DEFAULT_CONFIG$1 = {
5
+ syncChannel: 'signal-storage-sync',
6
+ storageFactory: () => (typeof window !== 'undefined' ? window.localStorage : new MemoryStorage()),
7
+ };
8
+ const SIGNAL_STORAGE_CONFIG = new InjectionToken('SIGNAL_STORAGE_CONFIG', {
5
9
  providedIn: 'root',
6
- factory: () => (typeof window !== 'undefined' ? window.localStorage : new MemoryStorage()),
10
+ factory: () => DEFAULT_CONFIG$1,
7
11
  });
12
+ function provideSignalStorage(config) {
13
+ return [
14
+ {
15
+ provide: SIGNAL_STORAGE_CONFIG,
16
+ useValue: { ...DEFAULT_CONFIG$1, ...config },
17
+ },
18
+ SignalStorage,
19
+ ];
20
+ }
21
+ /**
22
+ * A strongly-typed, reactive storage solution powered by Angular Signals.
23
+ * It natively supports `localStorage`, `sessionStorage`, `MemoryStorage`, or any custom storage mechanism.
24
+ * Additionally, it automatically coordinates signal state changes across multiple browser tabs via BroadcastChannel.
25
+ *
26
+ * Define a strict type for the storage keys and values using the generic parameter `T`.
27
+ * You can configure the storage mechanism and sync channel using the `provideSignalStorage` provider function,
28
+ * or by passing a configuration object directly via `useFactory` when extending the class.
29
+ *
30
+ * @typeParam T - An interface defining the expected shape of the storage data.
31
+ *
32
+ * @example
33
+ * ```typescript
34
+ * // 1. Define your storage shape
35
+ * interface AppState {
36
+ * theme: 'light' | 'dark';
37
+ * metrics: { visits: number };
38
+ * }
39
+ *
40
+ * // 2. Create a typed service
41
+ * @Injectable({ providedIn: 'root' })
42
+ * export class AppStateStorage extends SignalStorage<AppState> {}
43
+ *
44
+ * @Component({ ... })
45
+ * export class MyComponent {
46
+ * // 3. Inject the typed service
47
+ * private storage = inject(AppStateStorage);
48
+ *
49
+ * // Reactive: A readonly Signal that auto-updates
50
+ * readonly theme = this.storage.getSignal('theme', 'light');
51
+ *
52
+ * constructor() {
53
+ * // Static: Get the current value statically (no reactivity)
54
+ * const rawTheme = this.storage.get('theme', 'light');
55
+ *
56
+ * // Mutate: Sets value in memory, updates the signal, and persists to storage
57
+ * this.storage.set('theme', 'dark');
58
+ * }
59
+ *
60
+ * toggleTheme() {
61
+ * // Mutate safely via callback
62
+ * this.storage.update('theme', current => current === 'light' ? 'dark' : 'light');
63
+ * }
64
+ * }
65
+ * ```
66
+ */
8
67
  class SignalStorage {
9
68
  storage;
10
69
  signals = new Map();
70
+ defaultValues = new Map();
71
+ channel;
11
72
  /**
12
73
  * Create a new SignalStorage instance
13
- * @param storage The storage to use (localStorage or sessionStorage). Defaults to localStorage.
74
+ * @param config The configuration for the storage and synchronization.
14
75
  */
15
- constructor(storage) {
16
- this.storage =
17
- storage || (typeof window !== 'undefined' ? window.localStorage : new MemoryStorage());
76
+ constructor(config) {
77
+ const mergedConfig = { ...DEFAULT_CONFIG$1, ...config };
78
+ this.storage = mergedConfig.storageFactory();
79
+ const { syncChannel } = mergedConfig;
18
80
  if (typeof window !== 'undefined') {
19
- window.addEventListener('storage', (event) => {
20
- const { key, newValue, storageArea } = event;
21
- if (storageArea === this.storage && key && this.signals.has(key)) {
22
- try {
23
- const value = newValue ? JSON.parse(newValue) : null;
24
- this.signals.get(key).set(value);
25
- }
26
- catch {
27
- // Ignore parse errors from external changes
81
+ // Seamless BroadcastChannel cross-tab sync (for ANY storage type)
82
+ if (typeof BroadcastChannel !== 'undefined') {
83
+ this.channel = new BroadcastChannel(syncChannel);
84
+ this.channel.onmessage = (event) => {
85
+ const data = event.data;
86
+ switch (data.action) {
87
+ case 'set':
88
+ if (data.key) {
89
+ this.set(data.key, data.value, false);
90
+ }
91
+ break;
92
+ case 'remove':
93
+ if (data.key) {
94
+ this.remove(data.key, false);
95
+ }
96
+ break;
97
+ case 'clear':
98
+ this.clear(false);
99
+ break;
28
100
  }
29
- }
30
- });
101
+ };
102
+ }
31
103
  }
32
104
  }
33
105
  get(key, defaultValue) {
@@ -44,6 +116,9 @@ class SignalStorage {
44
116
  }
45
117
  getSignal(key, defaultValue) {
46
118
  if (!this.signals.has(key)) {
119
+ if (defaultValue !== undefined) {
120
+ this.defaultValues.set(key, defaultValue);
121
+ }
47
122
  const initialValue = this.get(key, defaultValue);
48
123
  this.signals.set(key, signal(initialValue));
49
124
  }
@@ -53,8 +128,9 @@ class SignalStorage {
53
128
  * Set typed data in localStorage
54
129
  * @param key The key to set
55
130
  * @param value The value to store
131
+ * @param broadcast Whether to broadcast to other tabs
56
132
  */
57
- set(key, value) {
133
+ set(key, value, broadcast = true) {
58
134
  try {
59
135
  this.storage.setItem(key, JSON.stringify(value));
60
136
  }
@@ -64,6 +140,9 @@ class SignalStorage {
64
140
  if (this.signals.has(key)) {
65
141
  this.signals.get(key).set(value);
66
142
  }
143
+ if (broadcast) {
144
+ this.channel?.postMessage({ action: 'set', key, value });
145
+ }
67
146
  }
68
147
  /**
69
148
  * Update typed data based on current value using a callback
@@ -71,18 +150,24 @@ class SignalStorage {
71
150
  * @param updateFn Callback function that receives the current value and returns the new value
72
151
  */
73
152
  update(key, updateFn) {
74
- const currentValue = this.get(key);
153
+ const fallback = this.defaultValues.get(key);
154
+ const currentValue = fallback !== undefined ? this.get(key, fallback) : this.get(key);
75
155
  const newValue = updateFn(currentValue);
76
156
  this.set(key, newValue);
77
157
  }
78
158
  /**
79
159
  * Remove an item from localStorage
80
160
  * @param key The key to remove
161
+ * @param broadcast Whether to broadcast the removal to other tabs
81
162
  */
82
- remove(key) {
163
+ remove(key, broadcast = true) {
83
164
  this.storage.removeItem(key);
84
165
  if (this.signals.has(key)) {
85
- this.signals.get(key).set(null);
166
+ const def = this.defaultValues.get(key);
167
+ this.signals.get(key).set(def !== undefined ? def : null);
168
+ }
169
+ if (broadcast) {
170
+ this.channel?.postMessage({ action: 'remove', key });
86
171
  }
87
172
  }
88
173
  /**
@@ -93,23 +178,30 @@ class SignalStorage {
93
178
  has(key) {
94
179
  return this.storage.getItem(key) !== null;
95
180
  }
96
- /** Clear all localStorage */
97
- clear() {
181
+ /**
182
+ * Clear all localStorage
183
+ * @param broadcast Whether to broadcast the clear to other tabs
184
+ */
185
+ clear(broadcast = true) {
98
186
  this.storage.clear();
99
- for (const sig of this.signals.values()) {
100
- sig.set(null);
187
+ for (const [key, sig] of this.signals.entries()) {
188
+ const def = this.defaultValues.get(key);
189
+ sig.set(def !== undefined ? def : null);
190
+ }
191
+ if (broadcast) {
192
+ this.channel?.postMessage({ action: 'clear' });
101
193
  }
102
194
  }
103
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.0", ngImport: i0, type: SignalStorage, deps: [{ token: SIGNAL_STORAGE_TOKEN, optional: true }], target: i0.ɵɵFactoryTarget.Injectable });
195
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.0", ngImport: i0, type: SignalStorage, deps: [{ token: SIGNAL_STORAGE_CONFIG, optional: true }], target: i0.ɵɵFactoryTarget.Injectable });
104
196
  static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.1.0", ngImport: i0, type: SignalStorage });
105
197
  }
106
198
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.0", ngImport: i0, type: SignalStorage, decorators: [{
107
199
  type: Injectable
108
- }], ctorParameters: () => [{ type: Storage, decorators: [{
200
+ }], ctorParameters: () => [{ type: undefined, decorators: [{
109
201
  type: Optional
110
202
  }, {
111
203
  type: Inject,
112
- args: [SIGNAL_STORAGE_TOKEN]
204
+ args: [SIGNAL_STORAGE_CONFIG]
113
205
  }] }] });
114
206
  /**
115
207
  * An in-memory implementation of the Storage interface.
@@ -158,11 +250,55 @@ function provideSignalIndexedDb(config) {
158
250
  SignalIndexedDb,
159
251
  ];
160
252
  }
253
+ /**
254
+ * A strongly-typed, reactive, asynchronous storage solution powered by Angular's `resource` API and IndexedDB.
255
+ * It gracefully falls back to an in-memory map if IndexedDB is not supported (e.g. server-side rendering).
256
+ * Additionally, it automatically coordinates resource state changes across multiple browser tabs via BroadcastChannel.
257
+ *
258
+ * Define a strict type for the storage keys and values using the generic parameter `T`.
259
+ * You can configure the DB name, store name, and version using the `provideSignalIndexedDb` provider function.
260
+ *
261
+ * @typeParam T - An interface defining the expected shape of the IndexedDB data.
262
+ *
263
+ * @example
264
+ * ```typescript
265
+ * // 1. Define your storage shape
266
+ * interface AppState {
267
+ * theme: 'light' | 'dark';
268
+ * metrics: { visits: number };
269
+ * }
270
+ *
271
+ * // 2. Create a typed service
272
+ * @Injectable({ providedIn: 'root' })
273
+ * export class AppStateDb extends SignalIndexedDb<AppState> {}
274
+ *
275
+ * @Component({ ... })
276
+ * export class MyComponent {
277
+ * // 3. Inject the typed service
278
+ * private db = inject(AppStateDb);
279
+ *
280
+ * // Reactive: Angular Resource containing the async IndexedDB data
281
+ * readonly themeResource = this.db.getResource('theme', 'light');
282
+ *
283
+ * async toggleTheme() {
284
+ * // Static: Get the current value asynchronously (no reactivity)
285
+ * const rawTheme = await this.db.get('theme');
286
+ *
287
+ * // Mutate: Sets value, updates the resource, and persists to IndexedDB
288
+ * await this.db.set('theme', 'dark');
289
+ *
290
+ * // Mutate safely via callback
291
+ * await this.db.update('theme', current => current === 'light' ? 'dark' : 'light');
292
+ * }
293
+ * }
294
+ * ```
295
+ */
161
296
  class SignalIndexedDb {
162
297
  injector;
163
298
  config;
164
299
  dbPromise;
165
300
  fallbackStorage = new Map();
301
+ defaultValues = new Map();
166
302
  isFallback = false;
167
303
  resources = new Map();
168
304
  broadcastChannel;
@@ -174,13 +310,25 @@ class SignalIndexedDb {
174
310
  this.isFallback = true;
175
311
  return null;
176
312
  });
177
- if (typeof window !== 'undefined' && 'BroadcastChannel' in window) {
313
+ if (typeof window !== 'undefined' && typeof BroadcastChannel !== 'undefined') {
178
314
  try {
179
315
  this.broadcastChannel = new BroadcastChannel(`${this.config.dbName}-sync`);
180
316
  this.broadcastChannel.onmessage = (event) => {
181
- const { key, value } = event.data;
182
- if (this.resources.has(key)) {
183
- this.resources.get(key).value.set(value);
317
+ const data = event.data;
318
+ if (data.action === 'clear') {
319
+ if (this.isFallback)
320
+ this.fallbackStorage.clear();
321
+ this.resources.forEach((res, k) => res.value.set(this.defaultValues.get(k)));
322
+ }
323
+ else if (data.action === 'remove' && data.key) {
324
+ if (this.isFallback)
325
+ this.fallbackStorage.delete(data.key);
326
+ this.resources.get(data.key)?.value.set(this.defaultValues.get(data.key));
327
+ }
328
+ else if (data.action === 'set' && data.key) {
329
+ if (this.isFallback)
330
+ this.fallbackStorage.set(data.key, data.value);
331
+ this.resources.get(data.key)?.value.set(data.value);
184
332
  }
185
333
  };
186
334
  }
@@ -206,10 +354,14 @@ class SignalIndexedDb {
206
354
  });
207
355
  }
208
356
  getResource(key, defaultValue) {
357
+ if (defaultValue !== undefined && !this.defaultValues.has(key)) {
358
+ this.defaultValues.set(key, defaultValue);
359
+ }
209
360
  if (!this.resources.has(key)) {
210
- const dbResource = resource({ ...(ngDevMode ? { debugName: "dbResource" } : {}), loader: async () => {
361
+ const dbResource = resource({ ...(ngDevMode ? { debugName: "dbResource" } : {}), defaultValue,
362
+ loader: async () => {
211
363
  const val = await this.get(key);
212
- return val !== undefined ? val : defaultValue;
364
+ return val !== undefined ? val : this.defaultValues.get(key);
213
365
  },
214
366
  injector: this.injector });
215
367
  this.resources.set(key, dbResource);
@@ -217,7 +369,11 @@ class SignalIndexedDb {
217
369
  return this.resources.get(key);
218
370
  }
219
371
  /**
220
- * Get data asynchronously from IndexedDB
372
+ * Retrieves data asynchronously from IndexedDB without creating a reactive dependency.
373
+ * Useful for one-off reads where a Signal/Resource is not needed.
374
+ *
375
+ * @param key The strongly-typed key to retrieve.
376
+ * @returns A promise resolving to the stored value, or undefined if it does not exist.
221
377
  */
222
378
  async get(key) {
223
379
  if (this.isFallback)
@@ -239,38 +395,45 @@ class SignalIndexedDb {
239
395
  });
240
396
  }
241
397
  /**
242
- * Set data asynchronously in IndexedDB and update the resource
398
+ * Sets data asynchronously in IndexedDB.
399
+ * This is a reactive operation: it will automatically update any Angular `ResourceRef`
400
+ * observing this key and broadcast the change to other open browser tabs.
401
+ *
402
+ * @param key The strongly-typed key to set.
403
+ * @param value The value to store.
243
404
  */
244
405
  async set(key, value) {
245
- if (this.isFallback) {
246
- this.fallbackStorage.set(key, value);
247
- this.updateLocalState(key, value);
248
- return;
406
+ if (value === undefined) {
407
+ return this.remove(key);
249
408
  }
250
409
  const db = await this.dbPromise;
251
- if (!db) {
410
+ if (this.isFallback || !db) {
252
411
  this.fallbackStorage.set(key, value);
253
- this.updateLocalState(key, value);
254
- return;
255
412
  }
256
- return new Promise((resolve, reject) => {
257
- try {
258
- const transaction = db.transaction(this.config.storeName, 'readwrite');
259
- const store = transaction.objectStore(this.config.storeName);
260
- const request = store.put(value, key);
261
- request.onerror = () => reject(request.error);
262
- request.onsuccess = () => {
263
- this.updateLocalState(key, value);
264
- resolve();
265
- };
266
- }
267
- catch (e) {
268
- reject(e);
269
- }
270
- });
413
+ else {
414
+ await new Promise((resolve, reject) => {
415
+ try {
416
+ const request = db
417
+ .transaction(this.config.storeName, 'readwrite')
418
+ .objectStore(this.config.storeName)
419
+ .put(value, key);
420
+ request.onerror = () => reject(request.error);
421
+ request.onsuccess = () => resolve();
422
+ }
423
+ catch (e) {
424
+ reject(e);
425
+ }
426
+ });
427
+ }
428
+ this.resources.get(key)?.value.set(value);
429
+ this.broadcastChannel?.postMessage({ action: 'set', key, value });
271
430
  }
272
431
  /**
273
- * Safely update a value based on its previous state
432
+ * Safely updates a value based on its previous state.
433
+ * Useful when deriving the next value directly from the old value.
434
+ *
435
+ * @param key The strongly-typed key to update.
436
+ * @param updater A callback function that receives the current value and returns the new value.
274
437
  */
275
438
  async update(key, updater) {
276
439
  const current = await this.get(key);
@@ -278,69 +441,65 @@ class SignalIndexedDb {
278
441
  await this.set(key, newValue);
279
442
  }
280
443
  /**
281
- * Remove a key asynchronously from IndexedDB
444
+ * Removes a key asynchronously from IndexedDB.
445
+ * This operation will update the local state to undefined for the given key.
446
+ *
447
+ * @param key The strongly-typed key to remove.
282
448
  */
283
449
  async remove(key) {
284
- if (this.isFallback) {
285
- this.fallbackStorage.delete(key);
286
- this.updateLocalState(key, undefined);
287
- return;
288
- }
289
450
  const db = await this.dbPromise;
290
- if (!db) {
451
+ if (this.isFallback || !db) {
291
452
  this.fallbackStorage.delete(key);
292
- this.updateLocalState(key, undefined);
293
- return;
294
453
  }
295
- return new Promise((resolve, reject) => {
296
- try {
297
- const transaction = db.transaction(this.config.storeName, 'readwrite');
298
- const store = transaction.objectStore(this.config.storeName);
299
- const request = store.delete(key);
300
- request.onerror = () => reject(request.error);
301
- request.onsuccess = () => {
302
- this.updateLocalState(key, undefined);
303
- resolve();
304
- };
305
- }
306
- catch (e) {
307
- reject(e);
308
- }
309
- });
454
+ else {
455
+ await new Promise((resolve, reject) => {
456
+ try {
457
+ const request = db
458
+ .transaction(this.config.storeName, 'readwrite')
459
+ .objectStore(this.config.storeName)
460
+ .delete(key);
461
+ request.onerror = () => reject(request.error);
462
+ request.onsuccess = () => resolve();
463
+ }
464
+ catch (e) {
465
+ reject(e);
466
+ }
467
+ });
468
+ }
469
+ this.resources.get(key)?.value.set(this.defaultValues.get(key));
470
+ this.broadcastChannel?.postMessage({ action: 'remove', key });
310
471
  }
311
472
  /**
312
- * Clear the entire object store
473
+ * Clears the entire object store in IndexedDB.
474
+ * This operation will reset all active resource signals to undefined.
313
475
  */
314
476
  async clear() {
315
- if (this.isFallback) {
316
- this.fallbackStorage.clear();
317
- this.resources.forEach((_, key) => this.updateLocalState(key, undefined));
318
- return;
319
- }
320
477
  const db = await this.dbPromise;
321
- if (!db) {
478
+ if (this.isFallback || !db) {
322
479
  this.fallbackStorage.clear();
323
- this.resources.forEach((_, key) => this.updateLocalState(key, undefined));
324
- return;
325
480
  }
326
- return new Promise((resolve, reject) => {
327
- try {
328
- const transaction = db.transaction(this.config.storeName, 'readwrite');
329
- const store = transaction.objectStore(this.config.storeName);
330
- const request = store.clear();
331
- request.onerror = () => reject(request.error);
332
- request.onsuccess = () => {
333
- this.resources.forEach((_, key) => this.updateLocalState(key, undefined));
334
- resolve();
335
- };
336
- }
337
- catch (e) {
338
- reject(e);
339
- }
340
- });
481
+ else {
482
+ await new Promise((resolve, reject) => {
483
+ try {
484
+ const request = db
485
+ .transaction(this.config.storeName, 'readwrite')
486
+ .objectStore(this.config.storeName)
487
+ .clear();
488
+ request.onerror = () => reject(request.error);
489
+ request.onsuccess = () => resolve();
490
+ }
491
+ catch (e) {
492
+ reject(e);
493
+ }
494
+ });
495
+ }
496
+ this.resources.forEach((res, k) => res.value.set(this.defaultValues.get(k)));
497
+ this.broadcastChannel?.postMessage({ action: 'clear' });
341
498
  }
342
499
  /**
343
- * Get all keys from the object store
500
+ * Gets all keys currently stored in the object store.
501
+ *
502
+ * @returns A promise resolving to an array of stored keys.
344
503
  */
345
504
  async keys() {
346
505
  if (this.isFallback)
@@ -361,11 +520,10 @@ class SignalIndexedDb {
361
520
  }
362
521
  });
363
522
  }
364
- updateLocalState(key, value) {
365
- if (this.resources.has(key)) {
366
- this.resources.get(key).value.set(value);
523
+ ngOnDestroy() {
524
+ if (this.broadcastChannel) {
525
+ this.broadcastChannel.close();
367
526
  }
368
- this.broadcastChannel?.postMessage({ key, value });
369
527
  }
370
528
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.0", ngImport: i0, type: SignalIndexedDb, deps: [{ token: i0.Injector }, { token: SIGNAL_INDEXEDDB_CONFIG, optional: true }], target: i0.ɵɵFactoryTarget.Injectable });
371
529
  static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.1.0", ngImport: i0, type: SignalIndexedDb });
@@ -387,5 +545,5 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.0", ngImpor
387
545
  * Generated bundle index. Do not edit.
388
546
  */
389
547
 
390
- export { MemoryStorage, SIGNAL_INDEXEDDB_CONFIG, SIGNAL_STORAGE_TOKEN, SignalIndexedDb, SignalStorage, provideSignalIndexedDb };
548
+ export { MemoryStorage, SIGNAL_INDEXEDDB_CONFIG, SIGNAL_STORAGE_CONFIG, SignalIndexedDb, SignalStorage, provideSignalIndexedDb, provideSignalStorage };
391
549
  //# sourceMappingURL=angular-libs-signal-storage.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"angular-libs-signal-storage.mjs","sources":["../../../../projects/angular-libs/signal-storage/src/lib/signal-storage.ts","../../../../projects/angular-libs/signal-storage/src/lib/signal-indexeddb.ts","../../../../projects/angular-libs/signal-storage/src/public-api.ts","../../../../projects/angular-libs/signal-storage/src/angular-libs-signal-storage.ts"],"sourcesContent":["import {\n signal,\n WritableSignal,\n Signal,\n Injectable,\n InjectionToken,\n Optional,\n Inject,\n} from '@angular/core';\n\nexport const SIGNAL_STORAGE_TOKEN = new InjectionToken<Storage>('SIGNAL_STORAGE_TOKEN', {\n providedIn: 'root',\n factory: () => (typeof window !== 'undefined' ? window.localStorage : new MemoryStorage()),\n});\n\n@Injectable()\nexport class SignalStorage<T extends Record<string, any> = {}> {\n private storage: Storage;\n private signals = new Map<keyof T, WritableSignal<any>>();\n\n /**\n * Create a new SignalStorage instance\n * @param storage The storage to use (localStorage or sessionStorage). Defaults to localStorage.\n */\n constructor(@Optional() @Inject(SIGNAL_STORAGE_TOKEN) storage?: Storage) {\n this.storage =\n storage || (typeof window !== 'undefined' ? window.localStorage : new MemoryStorage());\n if (typeof window !== 'undefined') {\n window.addEventListener('storage', (event: StorageEvent) => {\n const { key, newValue, storageArea } = event;\n if (storageArea === this.storage && key && this.signals.has(key as keyof T)) {\n try {\n const value = newValue ? JSON.parse(newValue) : null;\n this.signals.get(key as keyof T)!.set(value);\n } catch {\n // Ignore parse errors from external changes\n }\n }\n });\n }\n }\n /**\n * Get typed data from SignalStorage\n * @param key The key to retrieve\n * @returns The typed data or null if not found\n */\n get<K extends keyof T>(key: K): T[K] | null;\n /**\n * Get typed data from SignalStorage with a default value\n * @param key The key to retrieve\n * @param defaultValue The default value to return if key not found\n * @returns The stored data or the default value\n */\n get<K extends keyof T>(key: K, defaultValue: T[K]): T[K];\n get<K extends keyof T>(key: K, defaultValue?: T[K]): T[K] | null {\n const item = this.storage.getItem(key as string);\n if (item === null) {\n return defaultValue ?? null;\n }\n try {\n return JSON.parse(item) as T[K];\n } catch {\n return defaultValue ?? null;\n }\n }\n\n /**\n * Get a reactive Angular signal for a key\n * @param key The key to retrieve\n * @param defaultValue The default value to return if key not found\n * @returns A read-only Signal containing the stored data\n */\n getSignal<K extends keyof T>(key: K): Signal<T[K] | null>;\n getSignal<K extends keyof T>(key: K, defaultValue: T[K]): Signal<T[K]>;\n getSignal<K extends keyof T>(key: K, defaultValue?: T[K]): Signal<any> {\n if (!this.signals.has(key)) {\n const initialValue = this.get(key, defaultValue as T[K]);\n this.signals.set(key, signal(initialValue));\n }\n return this.signals.get(key)!.asReadonly();\n }\n\n /**\n * Set typed data in localStorage\n * @param key The key to set\n * @param value The value to store\n */\n set<K extends keyof T>(key: K, value: T[K]): void {\n try {\n this.storage.setItem(key as string, JSON.stringify(value));\n } catch (e) {\n console.error(\n `Error saving to storage for key \"${String(key)}\". Storage quota may be exceeded.`,\n e,\n );\n }\n if (this.signals.has(key)) {\n this.signals.get(key)!.set(value);\n }\n }\n\n /**\n * Update typed data based on current value using a callback\n * @param key The key to update\n * @param updateFn Callback function that receives the current value and returns the new value\n */\n update<K extends keyof T>(key: K, updateFn: (currentValue: T[K] | null) => T[K]): void {\n const currentValue = this.get(key);\n const newValue = updateFn(currentValue);\n this.set(key, newValue);\n }\n\n /**\n * Remove an item from localStorage\n * @param key The key to remove\n */\n remove<K extends keyof T>(key: K): void {\n this.storage.removeItem(key as string);\n if (this.signals.has(key)) {\n this.signals.get(key)!.set(null);\n }\n }\n\n /**\n * Check if a key exists in localStorage\n * @param key The key to check\n * @returns true if the key exists, false otherwise\n */\n has<K extends keyof T>(key: K): boolean {\n return this.storage.getItem(key as string) !== null;\n }\n\n /** Clear all localStorage */\n clear(): void {\n this.storage.clear();\n for (const sig of this.signals.values()) {\n sig.set(null);\n }\n }\n}\n\n/**\n * An in-memory implementation of the Storage interface.\n * Can be provided to SIGNAL_STORAGE_TOKEN to use SignalStorage for purely in-memory app state.\n */\nexport class MemoryStorage implements Storage {\n private data = new Map<string, string>();\n\n get length(): number {\n return this.data.size;\n }\n\n clear(): void {\n this.data.clear();\n }\n\n getItem(key: string): string | null {\n const value = this.data.get(key);\n return value === undefined ? null : value;\n }\n\n removeItem(key: string): void {\n this.data.delete(key);\n }\n\n setItem(key: string, value: string): void {\n this.data.set(key, String(value));\n }\n\n key(index: number): string | null {\n const keys = Array.from(this.data.keys());\n return keys[index] === undefined ? null : keys[index];\n }\n}\n","import {\n Injectable,\n InjectionToken,\n Injector,\n Optional,\n Inject,\n resource,\n ResourceRef,\n} from '@angular/core';\n\nexport interface SignalIndexedDbConfig {\n /** Default: `SignalStorageDb` */\n dbName: string;\n /** Default: `keyValue` */\n storeName: string;\n /** Default: `1` */\n version: number;\n}\n\nconst DEFAULT_CONFIG: SignalIndexedDbConfig = {\n dbName: 'SignalStorageDb',\n storeName: 'keyValue',\n version: 1,\n};\n\nexport const SIGNAL_INDEXEDDB_CONFIG = new InjectionToken<SignalIndexedDbConfig>(\n 'SIGNAL_INDEXEDDB_CONFIG',\n {\n providedIn: 'root',\n factory: () => DEFAULT_CONFIG,\n },\n);\n\nexport function provideSignalIndexedDb(config?: Partial<SignalIndexedDbConfig>) {\n return [\n {\n provide: SIGNAL_INDEXEDDB_CONFIG,\n useValue: { ...DEFAULT_CONFIG, ...config },\n },\n // Explicitly provide the storage instance here!\n SignalIndexedDb,\n ];\n}\n\n@Injectable()\nexport class SignalIndexedDb<T extends Record<string, any> = {}> {\n private config: SignalIndexedDbConfig;\n private dbPromise: Promise<IDBDatabase | null>;\n private fallbackStorage = new Map<string, any>();\n private isFallback = false;\n\n private resources = new Map<keyof T, ResourceRef<any>>();\n private broadcastChannel?: BroadcastChannel;\n\n constructor(\n private injector: Injector,\n @Optional() @Inject(SIGNAL_INDEXEDDB_CONFIG) config?: SignalIndexedDbConfig,\n ) {\n this.config = config || DEFAULT_CONFIG;\n this.dbPromise = this.initDB().catch((err) => {\n console.warn('IndexedDB initialization failed. Falling back to in-memory storage.', err);\n this.isFallback = true;\n return null;\n });\n\n if (typeof window !== 'undefined' && 'BroadcastChannel' in window) {\n try {\n this.broadcastChannel = new BroadcastChannel(`${this.config.dbName}-sync`);\n this.broadcastChannel.onmessage = (event) => {\n const { key, value } = event.data;\n if (this.resources.has(key as keyof T)) {\n this.resources.get(key as keyof T)!.value.set(value);\n }\n };\n } catch {\n // Ignore broadcast channel errors inside environments that do not fully support it\n }\n }\n }\n\n private initDB(): Promise<IDBDatabase> {\n return new Promise((resolve, reject) => {\n if (typeof window === 'undefined' || !window.indexedDB) {\n return reject('IndexedDB is not supported');\n }\n\n const request = indexedDB.open(this.config.dbName, this.config.version);\n\n request.onerror = () => reject(request.error);\n request.onsuccess = () => resolve(request.result);\n\n request.onupgradeneeded = (event: IDBVersionChangeEvent) => {\n const db = (event.target as IDBOpenDBRequest).result;\n if (!db.objectStoreNames.contains(this.config.storeName)) {\n db.createObjectStore(this.config.storeName);\n }\n };\n });\n }\n\n /**\n * Get an Angular Resource for a key\n */\n getResource<K extends keyof T>(key: K): ResourceRef<T[K] | undefined>;\n getResource<K extends keyof T>(key: K, defaultValue: T[K]): ResourceRef<T[K]>;\n getResource<K extends keyof T>(key: K, defaultValue?: T[K]): ResourceRef<any> {\n if (!this.resources.has(key)) {\n const dbResource = resource({\n loader: async () => {\n const val = await this.get(key);\n return val !== undefined ? val : defaultValue;\n },\n injector: this.injector,\n });\n\n this.resources.set(key, dbResource);\n }\n\n return this.resources.get(key)!;\n }\n\n /**\n * Get data asynchronously from IndexedDB\n */\n async get<K extends keyof T>(key: K): Promise<T[K] | undefined> {\n if (this.isFallback) return this.fallbackStorage.get(key as string);\n\n const db = await this.dbPromise;\n if (!db) return this.fallbackStorage.get(key as string);\n\n return new Promise((resolve, reject) => {\n try {\n const transaction = db.transaction(this.config.storeName, 'readonly');\n const store = transaction.objectStore(this.config.storeName);\n const request = store.get(key as string);\n\n request.onerror = () => reject(request.error);\n request.onsuccess = () => resolve(request.result);\n } catch (e) {\n reject(e);\n }\n });\n }\n\n /**\n * Set data asynchronously in IndexedDB and update the resource\n */\n async set<K extends keyof T>(key: K, value: T[K]): Promise<void> {\n if (this.isFallback) {\n this.fallbackStorage.set(key as string, value);\n this.updateLocalState(key, value);\n return;\n }\n\n const db = await this.dbPromise;\n if (!db) {\n this.fallbackStorage.set(key as string, value);\n this.updateLocalState(key, value);\n return;\n }\n\n return new Promise((resolve, reject) => {\n try {\n const transaction = db.transaction(this.config.storeName, 'readwrite');\n const store = transaction.objectStore(this.config.storeName);\n const request = store.put(value, key as string);\n\n request.onerror = () => reject(request.error);\n request.onsuccess = () => {\n this.updateLocalState(key, value);\n resolve();\n };\n } catch (e) {\n reject(e);\n }\n });\n }\n\n /**\n * Safely update a value based on its previous state\n */\n async update<K extends keyof T>(\n key: K,\n updater: (current: T[K] | undefined) => T[K],\n ): Promise<void> {\n const current = await this.get(key);\n const newValue = updater(current);\n await this.set(key, newValue);\n }\n\n /**\n * Remove a key asynchronously from IndexedDB\n */\n async remove<K extends keyof T>(key: K): Promise<void> {\n if (this.isFallback) {\n this.fallbackStorage.delete(key as string);\n this.updateLocalState(key, undefined);\n return;\n }\n\n const db = await this.dbPromise;\n if (!db) {\n this.fallbackStorage.delete(key as string);\n this.updateLocalState(key, undefined);\n return;\n }\n\n return new Promise((resolve, reject) => {\n try {\n const transaction = db.transaction(this.config.storeName, 'readwrite');\n const store = transaction.objectStore(this.config.storeName);\n const request = store.delete(key as string);\n\n request.onerror = () => reject(request.error);\n request.onsuccess = () => {\n this.updateLocalState(key, undefined);\n resolve();\n };\n } catch (e) {\n reject(e);\n }\n });\n }\n\n /**\n * Clear the entire object store\n */\n async clear(): Promise<void> {\n if (this.isFallback) {\n this.fallbackStorage.clear();\n this.resources.forEach((_, key) => this.updateLocalState(key as keyof T, undefined));\n return;\n }\n\n const db = await this.dbPromise;\n if (!db) {\n this.fallbackStorage.clear();\n this.resources.forEach((_, key) => this.updateLocalState(key as keyof T, undefined));\n return;\n }\n\n return new Promise((resolve, reject) => {\n try {\n const transaction = db.transaction(this.config.storeName, 'readwrite');\n const store = transaction.objectStore(this.config.storeName);\n const request = store.clear();\n\n request.onerror = () => reject(request.error);\n request.onsuccess = () => {\n this.resources.forEach((_, key) => this.updateLocalState(key as keyof T, undefined));\n resolve();\n };\n } catch (e) {\n reject(e);\n }\n });\n }\n\n /**\n * Get all keys from the object store\n */\n async keys(): Promise<string[]> {\n if (this.isFallback) return Array.from(this.fallbackStorage.keys());\n\n const db = await this.dbPromise;\n if (!db) return Array.from(this.fallbackStorage.keys());\n\n return new Promise((resolve, reject) => {\n try {\n const transaction = db.transaction(this.config.storeName, 'readonly');\n const store = transaction.objectStore(this.config.storeName);\n const request = store.getAllKeys();\n\n request.onerror = () => reject(request.error);\n request.onsuccess = () => resolve(request.result as string[]);\n } catch (e) {\n reject(e);\n }\n });\n }\n\n private updateLocalState(key: keyof T, value: any) {\n if (this.resources.has(key)) {\n this.resources.get(key)!.value.set(value);\n }\n this.broadcastChannel?.postMessage({ key, value });\n }\n}\n","/*\n * Public API Surface of signal-storage\n */\n\nexport * from './lib/signal-storage';\nexport * from './lib/signal-indexeddb';\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './public-api';\n"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"angular-libs-signal-storage.mjs","sources":["../../../../projects/angular-libs/signal-storage/src/lib/signal-storage.ts","../../../../projects/angular-libs/signal-storage/src/lib/signal-indexeddb.ts","../../../../projects/angular-libs/signal-storage/src/public-api.ts","../../../../projects/angular-libs/signal-storage/src/angular-libs-signal-storage.ts"],"sourcesContent":["import {\n signal,\n WritableSignal,\n Signal,\n Injectable,\n InjectionToken,\n Optional,\n Inject,\n} from '@angular/core';\n\nexport interface SignalStorageConfig {\n /** The channel name for BroadcastChannel sync. Default: `signal-storage-sync` */\n syncChannel: string;\n /**\n * A factory returning the Storage to use (e.g., `() => sessionStorage`).\n * Defaults to `() => localStorage` (or MemoryStorage in SSR environments).\n */\n storageFactory: () => Storage;\n}\n\nconst DEFAULT_CONFIG: SignalStorageConfig = {\n syncChannel: 'signal-storage-sync',\n storageFactory: () => (typeof window !== 'undefined' ? window.localStorage : new MemoryStorage()),\n};\n\nexport const SIGNAL_STORAGE_CONFIG = new InjectionToken<SignalStorageConfig>(\n 'SIGNAL_STORAGE_CONFIG',\n {\n providedIn: 'root',\n factory: () => DEFAULT_CONFIG,\n },\n);\n\nexport function provideSignalStorage(config?: Partial<SignalStorageConfig>) {\n return [\n {\n provide: SIGNAL_STORAGE_CONFIG,\n useValue: { ...DEFAULT_CONFIG, ...config },\n },\n SignalStorage,\n ];\n}\n\ntype SyncMessage<T> =\n | { action: 'set'; key: keyof T; value: any }\n | { action: 'remove'; key: keyof T }\n | { action: 'clear' };\n\n/**\n * A strongly-typed, reactive storage solution powered by Angular Signals.\n * It natively supports `localStorage`, `sessionStorage`, `MemoryStorage`, or any custom storage mechanism.\n * Additionally, it automatically coordinates signal state changes across multiple browser tabs via BroadcastChannel.\n *\n * Define a strict type for the storage keys and values using the generic parameter `T`.\n * You can configure the storage mechanism and sync channel using the `provideSignalStorage` provider function,\n * or by passing a configuration object directly via `useFactory` when extending the class.\n *\n * @typeParam T - An interface defining the expected shape of the storage data.\n *\n * @example\n * ```typescript\n * // 1. Define your storage shape\n * interface AppState {\n * theme: 'light' | 'dark';\n * metrics: { visits: number };\n * }\n *\n * // 2. Create a typed service\n * @Injectable({ providedIn: 'root' })\n * export class AppStateStorage extends SignalStorage<AppState> {}\n *\n * @Component({ ... })\n * export class MyComponent {\n * // 3. Inject the typed service\n * private storage = inject(AppStateStorage);\n *\n * // Reactive: A readonly Signal that auto-updates\n * readonly theme = this.storage.getSignal('theme', 'light');\n *\n * constructor() {\n * // Static: Get the current value statically (no reactivity)\n * const rawTheme = this.storage.get('theme', 'light');\n *\n * // Mutate: Sets value in memory, updates the signal, and persists to storage\n * this.storage.set('theme', 'dark');\n * }\n *\n * toggleTheme() {\n * // Mutate safely via callback\n * this.storage.update('theme', current => current === 'light' ? 'dark' : 'light');\n * }\n * }\n * ```\n */\n@Injectable()\nexport class SignalStorage<T extends Record<string, any> = {}> {\n private storage: Storage;\n private signals = new Map<keyof T, WritableSignal<any>>();\n private defaultValues = new Map<keyof T, any>();\n private channel?: BroadcastChannel;\n\n /**\n * Create a new SignalStorage instance\n * @param config The configuration for the storage and synchronization.\n */\n constructor(@Optional() @Inject(SIGNAL_STORAGE_CONFIG) config?: Partial<SignalStorageConfig>) {\n const mergedConfig = { ...DEFAULT_CONFIG, ...config };\n this.storage = mergedConfig.storageFactory();\n const { syncChannel } = mergedConfig;\n\n if (typeof window !== 'undefined') {\n // Seamless BroadcastChannel cross-tab sync (for ANY storage type)\n if (typeof BroadcastChannel !== 'undefined') {\n this.channel = new BroadcastChannel(syncChannel);\n this.channel.onmessage = (event: MessageEvent<SyncMessage<T>>) => {\n const data = event.data;\n\n switch (data.action) {\n case 'set':\n if (data.key) {\n this.set(data.key, data.value, false);\n }\n break;\n case 'remove':\n if (data.key) {\n this.remove(data.key, false);\n }\n break;\n case 'clear':\n this.clear(false);\n break;\n }\n };\n }\n }\n }\n /**\n * Get typed data from SignalStorage\n * @param key The key to retrieve\n * @returns The typed data or null if not found\n */\n get<K extends keyof T>(key: K): T[K] | null;\n /**\n * Get typed data from SignalStorage with a default value\n * @param key The key to retrieve\n * @param defaultValue The default value to return if key not found\n * @returns The stored data or the default value\n */\n get<K extends keyof T>(key: K, defaultValue: T[K]): T[K];\n get<K extends keyof T>(key: K, defaultValue?: T[K]): T[K] | null {\n const item = this.storage.getItem(key as string);\n if (item === null) {\n return defaultValue ?? null;\n }\n try {\n return JSON.parse(item) as T[K];\n } catch {\n return defaultValue ?? null;\n }\n }\n\n /**\n * Get a reactive Angular signal for a key\n * @param key The key to retrieve\n * @param defaultValue The default value to return if key not found\n * @returns A read-only Signal containing the stored data\n */\n getSignal<K extends keyof T>(key: K): Signal<T[K] | null>;\n getSignal<K extends keyof T>(key: K, defaultValue: T[K]): Signal<T[K]>;\n getSignal<K extends keyof T>(key: K, defaultValue?: T[K]): Signal<any> {\n if (!this.signals.has(key)) {\n if (defaultValue !== undefined) {\n this.defaultValues.set(key, defaultValue);\n }\n const initialValue = this.get(key, defaultValue as T[K]);\n this.signals.set(key, signal(initialValue));\n }\n return this.signals.get(key)!.asReadonly();\n }\n\n /**\n * Set typed data in localStorage\n * @param key The key to set\n * @param value The value to store\n * @param broadcast Whether to broadcast to other tabs\n */\n set<K extends keyof T>(key: K, value: T[K], broadcast = true): void {\n try {\n this.storage.setItem(key as string, JSON.stringify(value));\n } catch (e) {\n console.error(\n `Error saving to storage for key \"${String(key)}\". Storage quota may be exceeded.`,\n e,\n );\n }\n if (this.signals.has(key)) {\n this.signals.get(key)!.set(value);\n }\n if (broadcast) {\n this.channel?.postMessage({ action: 'set', key, value });\n }\n }\n\n /**\n * Update typed data based on current value using a callback\n * @param key The key to update\n * @param updateFn Callback function that receives the current value and returns the new value\n */\n update<K extends keyof T>(key: K, updateFn: (currentValue: T[K] | null) => T[K]): void {\n const fallback = this.defaultValues.get(key);\n const currentValue = fallback !== undefined ? this.get(key, fallback) : this.get(key);\n const newValue = updateFn(currentValue);\n this.set(key, newValue);\n }\n\n /**\n * Remove an item from localStorage\n * @param key The key to remove\n * @param broadcast Whether to broadcast the removal to other tabs\n */\n remove<K extends keyof T>(key: K, broadcast = true): void {\n this.storage.removeItem(key as string);\n if (this.signals.has(key)) {\n const def = this.defaultValues.get(key);\n this.signals.get(key)!.set(def !== undefined ? def : null);\n }\n if (broadcast) {\n this.channel?.postMessage({ action: 'remove', key });\n }\n }\n\n /**\n * Check if a key exists in localStorage\n * @param key The key to check\n * @returns true if the key exists, false otherwise\n */\n has<K extends keyof T>(key: K): boolean {\n return this.storage.getItem(key as string) !== null;\n }\n\n /**\n * Clear all localStorage\n * @param broadcast Whether to broadcast the clear to other tabs\n */\n clear(broadcast = true): void {\n this.storage.clear();\n for (const [key, sig] of this.signals.entries()) {\n const def = this.defaultValues.get(key);\n sig.set(def !== undefined ? def : null);\n }\n if (broadcast) {\n this.channel?.postMessage({ action: 'clear' });\n }\n }\n}\n\n/**\n * An in-memory implementation of the Storage interface.\n * Can be provided to SIGNAL_STORAGE_TOKEN to use SignalStorage for purely in-memory app state.\n */\nexport class MemoryStorage implements Storage {\n private data = new Map<string, string>();\n\n get length(): number {\n return this.data.size;\n }\n\n clear(): void {\n this.data.clear();\n }\n\n getItem(key: string): string | null {\n const value = this.data.get(key);\n return value === undefined ? null : value;\n }\n\n removeItem(key: string): void {\n this.data.delete(key);\n }\n\n setItem(key: string, value: string): void {\n this.data.set(key, String(value));\n }\n\n key(index: number): string | null {\n const keys = Array.from(this.data.keys());\n return keys[index] === undefined ? null : keys[index];\n }\n}\n","import {\n Injectable,\n InjectionToken,\n Injector,\n Optional,\n Inject,\n resource,\n ResourceRef,\n OnDestroy,\n} from '@angular/core';\n\nexport interface SignalIndexedDbConfig {\n /** Default: `SignalStorageDb` */\n dbName: string;\n /** Default: `keyValue` */\n storeName: string;\n /** Default: `1` */\n version: number;\n}\n\nconst DEFAULT_CONFIG: SignalIndexedDbConfig = {\n dbName: 'SignalStorageDb',\n storeName: 'keyValue',\n version: 1,\n};\n\nexport const SIGNAL_INDEXEDDB_CONFIG = new InjectionToken<SignalIndexedDbConfig>(\n 'SIGNAL_INDEXEDDB_CONFIG',\n {\n providedIn: 'root',\n factory: () => DEFAULT_CONFIG,\n },\n);\n\nexport function provideSignalIndexedDb(config?: Partial<SignalIndexedDbConfig>) {\n return [\n {\n provide: SIGNAL_INDEXEDDB_CONFIG,\n useValue: { ...DEFAULT_CONFIG, ...config },\n },\n // Explicitly provide the storage instance here!\n SignalIndexedDb,\n ];\n}\n\ntype SyncMessage<T> =\n | { action: 'set'; key: keyof T; value: any }\n | { action: 'remove'; key: keyof T }\n | { action: 'clear' };\n\n/**\n * A strongly-typed, reactive, asynchronous storage solution powered by Angular's `resource` API and IndexedDB.\n * It gracefully falls back to an in-memory map if IndexedDB is not supported (e.g. server-side rendering).\n * Additionally, it automatically coordinates resource state changes across multiple browser tabs via BroadcastChannel.\n *\n * Define a strict type for the storage keys and values using the generic parameter `T`.\n * You can configure the DB name, store name, and version using the `provideSignalIndexedDb` provider function.\n *\n * @typeParam T - An interface defining the expected shape of the IndexedDB data.\n *\n * @example\n * ```typescript\n * // 1. Define your storage shape\n * interface AppState {\n * theme: 'light' | 'dark';\n * metrics: { visits: number };\n * }\n *\n * // 2. Create a typed service\n * @Injectable({ providedIn: 'root' })\n * export class AppStateDb extends SignalIndexedDb<AppState> {}\n *\n * @Component({ ... })\n * export class MyComponent {\n * // 3. Inject the typed service\n * private db = inject(AppStateDb);\n *\n * // Reactive: Angular Resource containing the async IndexedDB data\n * readonly themeResource = this.db.getResource('theme', 'light');\n *\n * async toggleTheme() {\n * // Static: Get the current value asynchronously (no reactivity)\n * const rawTheme = await this.db.get('theme');\n *\n * // Mutate: Sets value, updates the resource, and persists to IndexedDB\n * await this.db.set('theme', 'dark');\n *\n * // Mutate safely via callback\n * await this.db.update('theme', current => current === 'light' ? 'dark' : 'light');\n * }\n * }\n * ```\n */\n@Injectable()\nexport class SignalIndexedDb<T extends Record<string, any> = {}> implements OnDestroy {\n private config: SignalIndexedDbConfig;\n private dbPromise: Promise<IDBDatabase | null>;\n private fallbackStorage = new Map<string, any>();\n private defaultValues = new Map<keyof T, any>();\n private isFallback = false;\n\n private resources = new Map<keyof T, ResourceRef<any>>();\n private broadcastChannel?: BroadcastChannel;\n\n constructor(\n private injector: Injector,\n @Optional() @Inject(SIGNAL_INDEXEDDB_CONFIG) config?: SignalIndexedDbConfig,\n ) {\n this.config = config || DEFAULT_CONFIG;\n this.dbPromise = this.initDB().catch((err) => {\n console.warn('IndexedDB initialization failed. Falling back to in-memory storage.', err);\n this.isFallback = true;\n return null;\n });\n\n if (typeof window !== 'undefined' && typeof BroadcastChannel !== 'undefined') {\n try {\n this.broadcastChannel = new BroadcastChannel(`${this.config.dbName}-sync`);\n this.broadcastChannel.onmessage = (event: MessageEvent<SyncMessage<T>>) => {\n const data = event.data;\n\n if (data.action === 'clear') {\n if (this.isFallback) this.fallbackStorage.clear();\n this.resources.forEach((res, k) => res.value.set(this.defaultValues.get(k)));\n } else if (data.action === 'remove' && data.key) {\n if (this.isFallback) this.fallbackStorage.delete(data.key as string);\n this.resources.get(data.key)?.value.set(this.defaultValues.get(data.key));\n } else if (data.action === 'set' && data.key) {\n if (this.isFallback) this.fallbackStorage.set(data.key as string, data.value);\n this.resources.get(data.key)?.value.set(data.value);\n }\n };\n } catch {\n // Ignore broadcast channel errors inside environments that do not fully support it\n }\n }\n }\n\n private initDB(): Promise<IDBDatabase> {\n return new Promise((resolve, reject) => {\n if (typeof window === 'undefined' || !window.indexedDB) {\n return reject('IndexedDB is not supported');\n }\n\n const request = indexedDB.open(this.config.dbName, this.config.version);\n\n request.onerror = () => reject(request.error);\n request.onsuccess = () => resolve(request.result);\n\n request.onupgradeneeded = (event: IDBVersionChangeEvent) => {\n const db = (event.target as IDBOpenDBRequest).result;\n if (!db.objectStoreNames.contains(this.config.storeName)) {\n db.createObjectStore(this.config.storeName);\n }\n };\n });\n }\n\n /**\n * Retrieves an Angular `ResourceRef` for a specific key.\n * The resource will asynchronously load the initial value from IndexedDB.\n * Calling `set()` or `update()` on this key later will automatically update this resource's signal.\n *\n * @param key The strongly-typed key to retrieve.\n * @returns A `ResourceRef` containing the value (or undefined if not found).\n */\n getResource<K extends keyof T>(key: K): ResourceRef<T[K] | undefined>;\n getResource<K extends keyof T>(key: K, defaultValue: T[K]): ResourceRef<T[K]>;\n getResource<K extends keyof T>(key: K, defaultValue?: T[K]): ResourceRef<any> {\n if (defaultValue !== undefined && !this.defaultValues.has(key)) {\n this.defaultValues.set(key, defaultValue);\n }\n if (!this.resources.has(key)) {\n const dbResource = resource({\n defaultValue,\n loader: async () => {\n const val = await this.get(key);\n return val !== undefined ? val : this.defaultValues.get(key);\n },\n injector: this.injector,\n });\n\n this.resources.set(key, dbResource);\n }\n\n return this.resources.get(key)!;\n }\n\n /**\n * Retrieves data asynchronously from IndexedDB without creating a reactive dependency.\n * Useful for one-off reads where a Signal/Resource is not needed.\n *\n * @param key The strongly-typed key to retrieve.\n * @returns A promise resolving to the stored value, or undefined if it does not exist.\n */\n async get<K extends keyof T>(key: K): Promise<T[K] | undefined> {\n if (this.isFallback) return this.fallbackStorage.get(key as string);\n\n const db = await this.dbPromise;\n if (!db) return this.fallbackStorage.get(key as string);\n\n return new Promise((resolve, reject) => {\n try {\n const transaction = db.transaction(this.config.storeName, 'readonly');\n const store = transaction.objectStore(this.config.storeName);\n const request = store.get(key as string);\n\n request.onerror = () => reject(request.error);\n request.onsuccess = () => resolve(request.result);\n } catch (e) {\n reject(e);\n }\n });\n }\n\n /**\n * Sets data asynchronously in IndexedDB.\n * This is a reactive operation: it will automatically update any Angular `ResourceRef`\n * observing this key and broadcast the change to other open browser tabs.\n *\n * @param key The strongly-typed key to set.\n * @param value The value to store.\n */\n async set<K extends keyof T>(key: K, value: T[K]): Promise<void> {\n if (value === undefined) {\n return this.remove(key);\n }\n\n const db = await this.dbPromise;\n if (this.isFallback || !db) {\n this.fallbackStorage.set(key as string, value);\n } else {\n await new Promise<void>((resolve, reject) => {\n try {\n const request = db\n .transaction(this.config.storeName, 'readwrite')\n .objectStore(this.config.storeName)\n .put(value, key as string);\n request.onerror = () => reject(request.error);\n request.onsuccess = () => resolve();\n } catch (e) {\n reject(e);\n }\n });\n }\n\n this.resources.get(key)?.value.set(value);\n this.broadcastChannel?.postMessage({ action: 'set', key, value });\n }\n\n /**\n * Safely updates a value based on its previous state.\n * Useful when deriving the next value directly from the old value.\n *\n * @param key The strongly-typed key to update.\n * @param updater A callback function that receives the current value and returns the new value.\n */\n async update<K extends keyof T>(\n key: K,\n updater: (current: T[K] | undefined) => T[K],\n ): Promise<void> {\n const current = await this.get(key);\n const newValue = updater(current);\n await this.set(key, newValue);\n }\n\n /**\n * Removes a key asynchronously from IndexedDB.\n * This operation will update the local state to undefined for the given key.\n *\n * @param key The strongly-typed key to remove.\n */\n async remove<K extends keyof T>(key: K): Promise<void> {\n const db = await this.dbPromise;\n if (this.isFallback || !db) {\n this.fallbackStorage.delete(key as string);\n } else {\n await new Promise<void>((resolve, reject) => {\n try {\n const request = db\n .transaction(this.config.storeName, 'readwrite')\n .objectStore(this.config.storeName)\n .delete(key as string);\n request.onerror = () => reject(request.error);\n request.onsuccess = () => resolve();\n } catch (e) {\n reject(e);\n }\n });\n }\n\n this.resources.get(key)?.value.set(this.defaultValues.get(key));\n this.broadcastChannel?.postMessage({ action: 'remove', key });\n }\n\n /**\n * Clears the entire object store in IndexedDB.\n * This operation will reset all active resource signals to undefined.\n */\n async clear(): Promise<void> {\n const db = await this.dbPromise;\n if (this.isFallback || !db) {\n this.fallbackStorage.clear();\n } else {\n await new Promise<void>((resolve, reject) => {\n try {\n const request = db\n .transaction(this.config.storeName, 'readwrite')\n .objectStore(this.config.storeName)\n .clear();\n request.onerror = () => reject(request.error);\n request.onsuccess = () => resolve();\n } catch (e) {\n reject(e);\n }\n });\n }\n\n this.resources.forEach((res, k) => res.value.set(this.defaultValues.get(k)));\n this.broadcastChannel?.postMessage({ action: 'clear' });\n }\n\n /**\n * Gets all keys currently stored in the object store.\n *\n * @returns A promise resolving to an array of stored keys.\n */\n async keys(): Promise<string[]> {\n if (this.isFallback) return Array.from(this.fallbackStorage.keys());\n\n const db = await this.dbPromise;\n if (!db) return Array.from(this.fallbackStorage.keys());\n\n return new Promise((resolve, reject) => {\n try {\n const transaction = db.transaction(this.config.storeName, 'readonly');\n const store = transaction.objectStore(this.config.storeName);\n const request = store.getAllKeys();\n\n request.onerror = () => reject(request.error);\n request.onsuccess = () => resolve(request.result as string[]);\n } catch (e) {\n reject(e);\n }\n });\n }\n\n ngOnDestroy(): void {\n if (this.broadcastChannel) {\n this.broadcastChannel.close();\n }\n }\n}\n","/*\n * Public API Surface of signal-storage\n */\n\nexport * from './lib/signal-storage';\nexport * from './lib/signal-indexeddb';\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './public-api';\n"],"names":["DEFAULT_CONFIG"],"mappings":";;;AAoBA,MAAMA,gBAAc,GAAwB;AAC1C,IAAA,WAAW,EAAE,qBAAqB;IAClC,cAAc,EAAE,OAAO,OAAO,MAAM,KAAK,WAAW,GAAG,MAAM,CAAC,YAAY,GAAG,IAAI,aAAa,EAAE,CAAC;CAClG;MAEY,qBAAqB,GAAG,IAAI,cAAc,CACrD,uBAAuB,EACvB;AACE,IAAA,UAAU,EAAE,MAAM;AAClB,IAAA,OAAO,EAAE,MAAMA,gBAAc;AAC9B,CAAA;AAGG,SAAU,oBAAoB,CAAC,MAAqC,EAAA;IACxE,OAAO;AACL,QAAA;AACE,YAAA,OAAO,EAAE,qBAAqB;AAC9B,YAAA,QAAQ,EAAE,EAAE,GAAGA,gBAAc,EAAE,GAAG,MAAM,EAAE;AAC3C,SAAA;QACD,aAAa;KACd;AACH;AAOA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA6CG;MAEU,aAAa,CAAA;AAChB,IAAA,OAAO;AACP,IAAA,OAAO,GAAG,IAAI,GAAG,EAAgC;AACjD,IAAA,aAAa,GAAG,IAAI,GAAG,EAAgB;AACvC,IAAA,OAAO;AAEf;;;AAGG;AACH,IAAA,WAAA,CAAuD,MAAqC,EAAA;QAC1F,MAAM,YAAY,GAAG,EAAE,GAAGA,gBAAc,EAAE,GAAG,MAAM,EAAE;AACrD,QAAA,IAAI,CAAC,OAAO,GAAG,YAAY,CAAC,cAAc,EAAE;AAC5C,QAAA,MAAM,EAAE,WAAW,EAAE,GAAG,YAAY;AAEpC,QAAA,IAAI,OAAO,MAAM,KAAK,WAAW,EAAE;;AAEjC,YAAA,IAAI,OAAO,gBAAgB,KAAK,WAAW,EAAE;gBAC3C,IAAI,CAAC,OAAO,GAAG,IAAI,gBAAgB,CAAC,WAAW,CAAC;gBAChD,IAAI,CAAC,OAAO,CAAC,SAAS,GAAG,CAAC,KAAmC,KAAI;AAC/D,oBAAA,MAAM,IAAI,GAAG,KAAK,CAAC,IAAI;AAEvB,oBAAA,QAAQ,IAAI,CAAC,MAAM;AACjB,wBAAA,KAAK,KAAK;AACR,4BAAA,IAAI,IAAI,CAAC,GAAG,EAAE;AACZ,gCAAA,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,GAAG,EAAE,IAAI,CAAC,KAAK,EAAE,KAAK,CAAC;4BACvC;4BACA;AACF,wBAAA,KAAK,QAAQ;AACX,4BAAA,IAAI,IAAI,CAAC,GAAG,EAAE;gCACZ,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,EAAE,KAAK,CAAC;4BAC9B;4BACA;AACF,wBAAA,KAAK,OAAO;AACV,4BAAA,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC;4BACjB;;AAEN,gBAAA,CAAC;YACH;QACF;IACF;IAcA,GAAG,CAAoB,GAAM,EAAE,YAAmB,EAAA;QAChD,MAAM,IAAI,GAAG,IAAI,CAAC,OAAO,CAAC,OAAO,CAAC,GAAa,CAAC;AAChD,QAAA,IAAI,IAAI,KAAK,IAAI,EAAE;YACjB,OAAO,YAAY,IAAI,IAAI;QAC7B;AACA,QAAA,IAAI;AACF,YAAA,OAAO,IAAI,CAAC,KAAK,CAAC,IAAI,CAAS;QACjC;AAAE,QAAA,MAAM;YACN,OAAO,YAAY,IAAI,IAAI;QAC7B;IACF;IAUA,SAAS,CAAoB,GAAM,EAAE,YAAmB,EAAA;QACtD,IAAI,CAAC,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,GAAG,CAAC,EAAE;AAC1B,YAAA,IAAI,YAAY,KAAK,SAAS,EAAE;gBAC9B,IAAI,CAAC,aAAa,CAAC,GAAG,CAAC,GAAG,EAAE,YAAY,CAAC;YAC3C;YACA,MAAM,YAAY,GAAG,IAAI,CAAC,GAAG,CAAC,GAAG,EAAE,YAAoB,CAAC;AACxD,YAAA,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,GAAG,EAAE,MAAM,CAAC,YAAY,CAAC,CAAC;QAC7C;QACA,OAAO,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,GAAG,CAAE,CAAC,UAAU,EAAE;IAC5C;AAEA;;;;;AAKG;AACH,IAAA,GAAG,CAAoB,GAAM,EAAE,KAAW,EAAE,SAAS,GAAG,IAAI,EAAA;AAC1D,QAAA,IAAI;AACF,YAAA,IAAI,CAAC,OAAO,CAAC,OAAO,CAAC,GAAa,EAAE,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC;QAC5D;QAAE,OAAO,CAAC,EAAE;AACV,YAAA,OAAO,CAAC,KAAK,CACX,CAAA,iCAAA,EAAoC,MAAM,CAAC,GAAG,CAAC,CAAA,iCAAA,CAAmC,EAClF,CAAC,CACF;QACH;QACA,IAAI,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,GAAG,CAAC,EAAE;AACzB,YAAA,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,GAAG,CAAE,CAAC,GAAG,CAAC,KAAK,CAAC;QACnC;QACA,IAAI,SAAS,EAAE;AACb,YAAA,IAAI,CAAC,OAAO,EAAE,WAAW,CAAC,EAAE,MAAM,EAAE,KAAK,EAAE,GAAG,EAAE,KAAK,EAAE,CAAC;QAC1D;IACF;AAEA;;;;AAIG;IACH,MAAM,CAAoB,GAAM,EAAE,QAA6C,EAAA;QAC7E,MAAM,QAAQ,GAAG,IAAI,CAAC,aAAa,CAAC,GAAG,CAAC,GAAG,CAAC;QAC5C,MAAM,YAAY,GAAG,QAAQ,KAAK,SAAS,GAAG,IAAI,CAAC,GAAG,CAAC,GAAG,EAAE,QAAQ,CAAC,GAAG,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC;AACrF,QAAA,MAAM,QAAQ,GAAG,QAAQ,CAAC,YAAY,CAAC;AACvC,QAAA,IAAI,CAAC,GAAG,CAAC,GAAG,EAAE,QAAQ,CAAC;IACzB;AAEA;;;;AAIG;AACH,IAAA,MAAM,CAAoB,GAAM,EAAE,SAAS,GAAG,IAAI,EAAA;AAChD,QAAA,IAAI,CAAC,OAAO,CAAC,UAAU,CAAC,GAAa,CAAC;QACtC,IAAI,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,GAAG,CAAC,EAAE;YACzB,MAAM,GAAG,GAAG,IAAI,CAAC,aAAa,CAAC,GAAG,CAAC,GAAG,CAAC;YACvC,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,GAAG,CAAE,CAAC,GAAG,CAAC,GAAG,KAAK,SAAS,GAAG,GAAG,GAAG,IAAI,CAAC;QAC5D;QACA,IAAI,SAAS,EAAE;AACb,YAAA,IAAI,CAAC,OAAO,EAAE,WAAW,CAAC,EAAE,MAAM,EAAE,QAAQ,EAAE,GAAG,EAAE,CAAC;QACtD;IACF;AAEA;;;;AAIG;AACH,IAAA,GAAG,CAAoB,GAAM,EAAA;QAC3B,OAAO,IAAI,CAAC,OAAO,CAAC,OAAO,CAAC,GAAa,CAAC,KAAK,IAAI;IACrD;AAEA;;;AAGG;IACH,KAAK,CAAC,SAAS,GAAG,IAAI,EAAA;AACpB,QAAA,IAAI,CAAC,OAAO,CAAC,KAAK,EAAE;AACpB,QAAA,KAAK,MAAM,CAAC,GAAG,EAAE,GAAG,CAAC,IAAI,IAAI,CAAC,OAAO,CAAC,OAAO,EAAE,EAAE;YAC/C,MAAM,GAAG,GAAG,IAAI,CAAC,aAAa,CAAC,GAAG,CAAC,GAAG,CAAC;AACvC,YAAA,GAAG,CAAC,GAAG,CAAC,GAAG,KAAK,SAAS,GAAG,GAAG,GAAG,IAAI,CAAC;QACzC;QACA,IAAI,SAAS,EAAE;YACb,IAAI,CAAC,OAAO,EAAE,WAAW,CAAC,EAAE,MAAM,EAAE,OAAO,EAAE,CAAC;QAChD;IACF;AA9JW,IAAA,OAAA,IAAA,GAAA,EAAA,CAAA,kBAAA,CAAA,EAAA,UAAA,EAAA,QAAA,EAAA,OAAA,EAAA,QAAA,EAAA,QAAA,EAAA,EAAA,EAAA,IAAA,EAAA,aAAa,kBAUQ,qBAAqB,EAAA,QAAA,EAAA,IAAA,EAAA,CAAA,EAAA,MAAA,EAAA,EAAA,CAAA,eAAA,CAAA,UAAA,EAAA,CAAA;2GAV1C,aAAa,EAAA,CAAA;;2FAAb,aAAa,EAAA,UAAA,EAAA,CAAA;kBADzB;;0BAWc;;0BAAY,MAAM;2BAAC,qBAAqB;;AAuJvD;;;AAGG;MACU,aAAa,CAAA;AAChB,IAAA,IAAI,GAAG,IAAI,GAAG,EAAkB;AAExC,IAAA,IAAI,MAAM,GAAA;AACR,QAAA,OAAO,IAAI,CAAC,IAAI,CAAC,IAAI;IACvB;IAEA,KAAK,GAAA;AACH,QAAA,IAAI,CAAC,IAAI,CAAC,KAAK,EAAE;IACnB;AAEA,IAAA,OAAO,CAAC,GAAW,EAAA;QACjB,MAAM,KAAK,GAAG,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC;QAChC,OAAO,KAAK,KAAK,SAAS,GAAG,IAAI,GAAG,KAAK;IAC3C;AAEA,IAAA,UAAU,CAAC,GAAW,EAAA;AACpB,QAAA,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC;IACvB;IAEA,OAAO,CAAC,GAAW,EAAE,KAAa,EAAA;AAChC,QAAA,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,GAAG,EAAE,MAAM,CAAC,KAAK,CAAC,CAAC;IACnC;AAEA,IAAA,GAAG,CAAC,KAAa,EAAA;AACf,QAAA,MAAM,IAAI,GAAG,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC;AACzC,QAAA,OAAO,IAAI,CAAC,KAAK,CAAC,KAAK,SAAS,GAAG,IAAI,GAAG,IAAI,CAAC,KAAK,CAAC;IACvD;AACD;;AC5QD,MAAM,cAAc,GAA0B;AAC5C,IAAA,MAAM,EAAE,iBAAiB;AACzB,IAAA,SAAS,EAAE,UAAU;AACrB,IAAA,OAAO,EAAE,CAAC;CACX;MAEY,uBAAuB,GAAG,IAAI,cAAc,CACvD,yBAAyB,EACzB;AACE,IAAA,UAAU,EAAE,MAAM;AAClB,IAAA,OAAO,EAAE,MAAM,cAAc;AAC9B,CAAA;AAGG,SAAU,sBAAsB,CAAC,MAAuC,EAAA;IAC5E,OAAO;AACL,QAAA;AACE,YAAA,OAAO,EAAE,uBAAuB;AAChC,YAAA,QAAQ,EAAE,EAAE,GAAG,cAAc,EAAE,GAAG,MAAM,EAAE;AAC3C,SAAA;;QAED,eAAe;KAChB;AACH;AAOA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA0CG;MAEU,eAAe,CAAA;AAWhB,IAAA,QAAA;AAVF,IAAA,MAAM;AACN,IAAA,SAAS;AACT,IAAA,eAAe,GAAG,IAAI,GAAG,EAAe;AACxC,IAAA,aAAa,GAAG,IAAI,GAAG,EAAgB;IACvC,UAAU,GAAG,KAAK;AAElB,IAAA,SAAS,GAAG,IAAI,GAAG,EAA6B;AAChD,IAAA,gBAAgB;IAExB,WAAA,CACU,QAAkB,EACmB,MAA8B,EAAA;QADnE,IAAA,CAAA,QAAQ,GAAR,QAAQ;AAGhB,QAAA,IAAI,CAAC,MAAM,GAAG,MAAM,IAAI,cAAc;AACtC,QAAA,IAAI,CAAC,SAAS,GAAG,IAAI,CAAC,MAAM,EAAE,CAAC,KAAK,CAAC,CAAC,GAAG,KAAI;AAC3C,YAAA,OAAO,CAAC,IAAI,CAAC,qEAAqE,EAAE,GAAG,CAAC;AACxF,YAAA,IAAI,CAAC,UAAU,GAAG,IAAI;AACtB,YAAA,OAAO,IAAI;AACb,QAAA,CAAC,CAAC;QAEF,IAAI,OAAO,MAAM,KAAK,WAAW,IAAI,OAAO,gBAAgB,KAAK,WAAW,EAAE;AAC5E,YAAA,IAAI;AACF,gBAAA,IAAI,CAAC,gBAAgB,GAAG,IAAI,gBAAgB,CAAC,CAAA,EAAG,IAAI,CAAC,MAAM,CAAC,MAAM,CAAA,KAAA,CAAO,CAAC;gBAC1E,IAAI,CAAC,gBAAgB,CAAC,SAAS,GAAG,CAAC,KAAmC,KAAI;AACxE,oBAAA,MAAM,IAAI,GAAG,KAAK,CAAC,IAAI;AAEvB,oBAAA,IAAI,IAAI,CAAC,MAAM,KAAK,OAAO,EAAE;wBAC3B,IAAI,IAAI,CAAC,UAAU;AAAE,4BAAA,IAAI,CAAC,eAAe,CAAC,KAAK,EAAE;AACjD,wBAAA,IAAI,CAAC,SAAS,CAAC,OAAO,CAAC,CAAC,GAAG,EAAE,CAAC,KAAK,GAAG,CAAC,KAAK,CAAC,GAAG,CAAC,IAAI,CAAC,aAAa,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC;oBAC9E;yBAAO,IAAI,IAAI,CAAC,MAAM,KAAK,QAAQ,IAAI,IAAI,CAAC,GAAG,EAAE;wBAC/C,IAAI,IAAI,CAAC,UAAU;4BAAE,IAAI,CAAC,eAAe,CAAC,MAAM,CAAC,IAAI,CAAC,GAAa,CAAC;wBACpE,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,KAAK,CAAC,GAAG,CAAC,IAAI,CAAC,aAAa,CAAC,GAAG,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;oBAC3E;yBAAO,IAAI,IAAI,CAAC,MAAM,KAAK,KAAK,IAAI,IAAI,CAAC,GAAG,EAAE;wBAC5C,IAAI,IAAI,CAAC,UAAU;AAAE,4BAAA,IAAI,CAAC,eAAe,CAAC,GAAG,CAAC,IAAI,CAAC,GAAa,EAAE,IAAI,CAAC,KAAK,CAAC;AAC7E,wBAAA,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,KAAK,CAAC,GAAG,CAAC,IAAI,CAAC,KAAK,CAAC;oBACrD;AACF,gBAAA,CAAC;YACH;AAAE,YAAA,MAAM;;YAER;QACF;IACF;IAEQ,MAAM,GAAA;QACZ,OAAO,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,MAAM,KAAI;YACrC,IAAI,OAAO,MAAM,KAAK,WAAW,IAAI,CAAC,MAAM,CAAC,SAAS,EAAE;AACtD,gBAAA,OAAO,MAAM,CAAC,4BAA4B,CAAC;YAC7C;AAEA,YAAA,MAAM,OAAO,GAAG,SAAS,CAAC,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,MAAM,EAAE,IAAI,CAAC,MAAM,CAAC,OAAO,CAAC;AAEvE,YAAA,OAAO,CAAC,OAAO,GAAG,MAAM,MAAM,CAAC,OAAO,CAAC,KAAK,CAAC;AAC7C,YAAA,OAAO,CAAC,SAAS,GAAG,MAAM,OAAO,CAAC,OAAO,CAAC,MAAM,CAAC;AAEjD,YAAA,OAAO,CAAC,eAAe,GAAG,CAAC,KAA4B,KAAI;AACzD,gBAAA,MAAM,EAAE,GAAI,KAAK,CAAC,MAA2B,CAAC,MAAM;AACpD,gBAAA,IAAI,CAAC,EAAE,CAAC,gBAAgB,CAAC,QAAQ,CAAC,IAAI,CAAC,MAAM,CAAC,SAAS,CAAC,EAAE;oBACxD,EAAE,CAAC,iBAAiB,CAAC,IAAI,CAAC,MAAM,CAAC,SAAS,CAAC;gBAC7C;AACF,YAAA,CAAC;AACH,QAAA,CAAC,CAAC;IACJ;IAYA,WAAW,CAAoB,GAAM,EAAE,YAAmB,EAAA;AACxD,QAAA,IAAI,YAAY,KAAK,SAAS,IAAI,CAAC,IAAI,CAAC,aAAa,CAAC,GAAG,CAAC,GAAG,CAAC,EAAE;YAC9D,IAAI,CAAC,aAAa,CAAC,GAAG,CAAC,GAAG,EAAE,YAAY,CAAC;QAC3C;QACA,IAAI,CAAC,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,GAAG,CAAC,EAAE;AAC5B,YAAA,MAAM,UAAU,GAAG,QAAQ,CAAA,EAAA,IAAA,SAAA,GAAA,EAAA,SAAA,EAAA,YAAA,EAAA,GAAA,EAAA,CAAA,EACzB,YAAY;gBACZ,MAAM,EAAE,YAAW;oBACjB,MAAM,GAAG,GAAG,MAAM,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC;AAC/B,oBAAA,OAAO,GAAG,KAAK,SAAS,GAAG,GAAG,GAAG,IAAI,CAAC,aAAa,CAAC,GAAG,CAAC,GAAG,CAAC;gBAC9D,CAAC;AACD,gBAAA,QAAQ,EAAE,IAAI,CAAC,QAAQ,GACvB;YAEF,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,GAAG,EAAE,UAAU,CAAC;QACrC;QAEA,OAAO,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,GAAG,CAAE;IACjC;AAEA;;;;;;AAMG;IACH,MAAM,GAAG,CAAoB,GAAM,EAAA;QACjC,IAAI,IAAI,CAAC,UAAU;YAAE,OAAO,IAAI,CAAC,eAAe,CAAC,GAAG,CAAC,GAAa,CAAC;AAEnE,QAAA,MAAM,EAAE,GAAG,MAAM,IAAI,CAAC,SAAS;AAC/B,QAAA,IAAI,CAAC,EAAE;YAAE,OAAO,IAAI,CAAC,eAAe,CAAC,GAAG,CAAC,GAAa,CAAC;QAEvD,OAAO,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,MAAM,KAAI;AACrC,YAAA,IAAI;AACF,gBAAA,MAAM,WAAW,GAAG,EAAE,CAAC,WAAW,CAAC,IAAI,CAAC,MAAM,CAAC,SAAS,EAAE,UAAU,CAAC;AACrE,gBAAA,MAAM,KAAK,GAAG,WAAW,CAAC,WAAW,CAAC,IAAI,CAAC,MAAM,CAAC,SAAS,CAAC;gBAC5D,MAAM,OAAO,GAAG,KAAK,CAAC,GAAG,CAAC,GAAa,CAAC;AAExC,gBAAA,OAAO,CAAC,OAAO,GAAG,MAAM,MAAM,CAAC,OAAO,CAAC,KAAK,CAAC;AAC7C,gBAAA,OAAO,CAAC,SAAS,GAAG,MAAM,OAAO,CAAC,OAAO,CAAC,MAAM,CAAC;YACnD;YAAE,OAAO,CAAC,EAAE;gBACV,MAAM,CAAC,CAAC,CAAC;YACX;AACF,QAAA,CAAC,CAAC;IACJ;AAEA;;;;;;;AAOG;AACH,IAAA,MAAM,GAAG,CAAoB,GAAM,EAAE,KAAW,EAAA;AAC9C,QAAA,IAAI,KAAK,KAAK,SAAS,EAAE;AACvB,YAAA,OAAO,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC;QACzB;AAEA,QAAA,MAAM,EAAE,GAAG,MAAM,IAAI,CAAC,SAAS;AAC/B,QAAA,IAAI,IAAI,CAAC,UAAU,IAAI,CAAC,EAAE,EAAE;YAC1B,IAAI,CAAC,eAAe,CAAC,GAAG,CAAC,GAAa,EAAE,KAAK,CAAC;QAChD;aAAO;YACL,MAAM,IAAI,OAAO,CAAO,CAAC,OAAO,EAAE,MAAM,KAAI;AAC1C,gBAAA,IAAI;oBACF,MAAM,OAAO,GAAG;yBACb,WAAW,CAAC,IAAI,CAAC,MAAM,CAAC,SAAS,EAAE,WAAW;AAC9C,yBAAA,WAAW,CAAC,IAAI,CAAC,MAAM,CAAC,SAAS;AACjC,yBAAA,GAAG,CAAC,KAAK,EAAE,GAAa,CAAC;AAC5B,oBAAA,OAAO,CAAC,OAAO,GAAG,MAAM,MAAM,CAAC,OAAO,CAAC,KAAK,CAAC;oBAC7C,OAAO,CAAC,SAAS,GAAG,MAAM,OAAO,EAAE;gBACrC;gBAAE,OAAO,CAAC,EAAE;oBACV,MAAM,CAAC,CAAC,CAAC;gBACX;AACF,YAAA,CAAC,CAAC;QACJ;AAEA,QAAA,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,GAAG,CAAC,EAAE,KAAK,CAAC,GAAG,CAAC,KAAK,CAAC;AACzC,QAAA,IAAI,CAAC,gBAAgB,EAAE,WAAW,CAAC,EAAE,MAAM,EAAE,KAAK,EAAE,GAAG,EAAE,KAAK,EAAE,CAAC;IACnE;AAEA;;;;;;AAMG;AACH,IAAA,MAAM,MAAM,CACV,GAAM,EACN,OAA4C,EAAA;QAE5C,MAAM,OAAO,GAAG,MAAM,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC;AACnC,QAAA,MAAM,QAAQ,GAAG,OAAO,CAAC,OAAO,CAAC;QACjC,MAAM,IAAI,CAAC,GAAG,CAAC,GAAG,EAAE,QAAQ,CAAC;IAC/B;AAEA;;;;;AAKG;IACH,MAAM,MAAM,CAAoB,GAAM,EAAA;AACpC,QAAA,MAAM,EAAE,GAAG,MAAM,IAAI,CAAC,SAAS;AAC/B,QAAA,IAAI,IAAI,CAAC,UAAU,IAAI,CAAC,EAAE,EAAE;AAC1B,YAAA,IAAI,CAAC,eAAe,CAAC,MAAM,CAAC,GAAa,CAAC;QAC5C;aAAO;YACL,MAAM,IAAI,OAAO,CAAO,CAAC,OAAO,EAAE,MAAM,KAAI;AAC1C,gBAAA,IAAI;oBACF,MAAM,OAAO,GAAG;yBACb,WAAW,CAAC,IAAI,CAAC,MAAM,CAAC,SAAS,EAAE,WAAW;AAC9C,yBAAA,WAAW,CAAC,IAAI,CAAC,MAAM,CAAC,SAAS;yBACjC,MAAM,CAAC,GAAa,CAAC;AACxB,oBAAA,OAAO,CAAC,OAAO,GAAG,MAAM,MAAM,CAAC,OAAO,CAAC,KAAK,CAAC;oBAC7C,OAAO,CAAC,SAAS,GAAG,MAAM,OAAO,EAAE;gBACrC;gBAAE,OAAO,CAAC,EAAE;oBACV,MAAM,CAAC,CAAC,CAAC;gBACX;AACF,YAAA,CAAC,CAAC;QACJ;QAEA,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,GAAG,CAAC,EAAE,KAAK,CAAC,GAAG,CAAC,IAAI,CAAC,aAAa,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;AAC/D,QAAA,IAAI,CAAC,gBAAgB,EAAE,WAAW,CAAC,EAAE,MAAM,EAAE,QAAQ,EAAE,GAAG,EAAE,CAAC;IAC/D;AAEA;;;AAGG;AACH,IAAA,MAAM,KAAK,GAAA;AACT,QAAA,MAAM,EAAE,GAAG,MAAM,IAAI,CAAC,SAAS;AAC/B,QAAA,IAAI,IAAI,CAAC,UAAU,IAAI,CAAC,EAAE,EAAE;AAC1B,YAAA,IAAI,CAAC,eAAe,CAAC,KAAK,EAAE;QAC9B;aAAO;YACL,MAAM,IAAI,OAAO,CAAO,CAAC,OAAO,EAAE,MAAM,KAAI;AAC1C,gBAAA,IAAI;oBACF,MAAM,OAAO,GAAG;yBACb,WAAW,CAAC,IAAI,CAAC,MAAM,CAAC,SAAS,EAAE,WAAW;AAC9C,yBAAA,WAAW,CAAC,IAAI,CAAC,MAAM,CAAC,SAAS;AACjC,yBAAA,KAAK,EAAE;AACV,oBAAA,OAAO,CAAC,OAAO,GAAG,MAAM,MAAM,CAAC,OAAO,CAAC,KAAK,CAAC;oBAC7C,OAAO,CAAC,SAAS,GAAG,MAAM,OAAO,EAAE;gBACrC;gBAAE,OAAO,CAAC,EAAE;oBACV,MAAM,CAAC,CAAC,CAAC;gBACX;AACF,YAAA,CAAC,CAAC;QACJ;AAEA,QAAA,IAAI,CAAC,SAAS,CAAC,OAAO,CAAC,CAAC,GAAG,EAAE,CAAC,KAAK,GAAG,CAAC,KAAK,CAAC,GAAG,CAAC,IAAI,CAAC,aAAa,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC;QAC5E,IAAI,CAAC,gBAAgB,EAAE,WAAW,CAAC,EAAE,MAAM,EAAE,OAAO,EAAE,CAAC;IACzD;AAEA;;;;AAIG;AACH,IAAA,MAAM,IAAI,GAAA;QACR,IAAI,IAAI,CAAC,UAAU;YAAE,OAAO,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,eAAe,CAAC,IAAI,EAAE,CAAC;AAEnE,QAAA,MAAM,EAAE,GAAG,MAAM,IAAI,CAAC,SAAS;AAC/B,QAAA,IAAI,CAAC,EAAE;YAAE,OAAO,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,eAAe,CAAC,IAAI,EAAE,CAAC;QAEvD,OAAO,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,MAAM,KAAI;AACrC,YAAA,IAAI;AACF,gBAAA,MAAM,WAAW,GAAG,EAAE,CAAC,WAAW,CAAC,IAAI,CAAC,MAAM,CAAC,SAAS,EAAE,UAAU,CAAC;AACrE,gBAAA,MAAM,KAAK,GAAG,WAAW,CAAC,WAAW,CAAC,IAAI,CAAC,MAAM,CAAC,SAAS,CAAC;AAC5D,gBAAA,MAAM,OAAO,GAAG,KAAK,CAAC,UAAU,EAAE;AAElC,gBAAA,OAAO,CAAC,OAAO,GAAG,MAAM,MAAM,CAAC,OAAO,CAAC,KAAK,CAAC;AAC7C,gBAAA,OAAO,CAAC,SAAS,GAAG,MAAM,OAAO,CAAC,OAAO,CAAC,MAAkB,CAAC;YAC/D;YAAE,OAAO,CAAC,EAAE;gBACV,MAAM,CAAC,CAAC,CAAC;YACX;AACF,QAAA,CAAC,CAAC;IACJ;IAEA,WAAW,GAAA;AACT,QAAA,IAAI,IAAI,CAAC,gBAAgB,EAAE;AACzB,YAAA,IAAI,CAAC,gBAAgB,CAAC,KAAK,EAAE;QAC/B;IACF;AAjQW,IAAA,OAAA,IAAA,GAAA,EAAA,CAAA,kBAAA,CAAA,EAAA,UAAA,EAAA,QAAA,EAAA,OAAA,EAAA,QAAA,EAAA,QAAA,EAAA,EAAA,EAAA,IAAA,EAAA,eAAe,0CAYJ,uBAAuB,EAAA,QAAA,EAAA,IAAA,EAAA,CAAA,EAAA,MAAA,EAAA,EAAA,CAAA,eAAA,CAAA,UAAA,EAAA,CAAA;2GAZlC,eAAe,EAAA,CAAA;;2FAAf,eAAe,EAAA,UAAA,EAAA,CAAA;kBAD3B;;0BAaI;;0BAAY,MAAM;2BAAC,uBAAuB;;;AC1G/C;;AAEG;;ACFH;;AAEG;;;;"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@angular-libs/signal-storage",
3
- "version": "0.1.0",
3
+ "version": "0.2.0-beta.0",
4
4
  "peerDependencies": {
5
5
  "@angular/common": ">=19.0.0",
6
6
  "@angular/core": ">=19.0.0"
@@ -1,15 +1,79 @@
1
1
  import * as i0 from '@angular/core';
2
- import { InjectionToken, Signal, Injector, ResourceRef } from '@angular/core';
2
+ import { InjectionToken, Signal, OnDestroy, Injector, ResourceRef } from '@angular/core';
3
3
 
4
- declare const SIGNAL_STORAGE_TOKEN: InjectionToken<Storage>;
4
+ interface SignalStorageConfig {
5
+ /** The channel name for BroadcastChannel sync. Default: `signal-storage-sync` */
6
+ syncChannel: string;
7
+ /**
8
+ * A factory returning the Storage to use (e.g., `() => sessionStorage`).
9
+ * Defaults to `() => localStorage` (or MemoryStorage in SSR environments).
10
+ */
11
+ storageFactory: () => Storage;
12
+ }
13
+ declare const SIGNAL_STORAGE_CONFIG: InjectionToken<SignalStorageConfig>;
14
+ declare function provideSignalStorage(config?: Partial<SignalStorageConfig>): (typeof SignalStorage | {
15
+ provide: InjectionToken<SignalStorageConfig>;
16
+ useValue: {
17
+ syncChannel: string;
18
+ storageFactory: () => Storage;
19
+ };
20
+ })[];
21
+ /**
22
+ * A strongly-typed, reactive storage solution powered by Angular Signals.
23
+ * It natively supports `localStorage`, `sessionStorage`, `MemoryStorage`, or any custom storage mechanism.
24
+ * Additionally, it automatically coordinates signal state changes across multiple browser tabs via BroadcastChannel.
25
+ *
26
+ * Define a strict type for the storage keys and values using the generic parameter `T`.
27
+ * You can configure the storage mechanism and sync channel using the `provideSignalStorage` provider function,
28
+ * or by passing a configuration object directly via `useFactory` when extending the class.
29
+ *
30
+ * @typeParam T - An interface defining the expected shape of the storage data.
31
+ *
32
+ * @example
33
+ * ```typescript
34
+ * // 1. Define your storage shape
35
+ * interface AppState {
36
+ * theme: 'light' | 'dark';
37
+ * metrics: { visits: number };
38
+ * }
39
+ *
40
+ * // 2. Create a typed service
41
+ * @Injectable({ providedIn: 'root' })
42
+ * export class AppStateStorage extends SignalStorage<AppState> {}
43
+ *
44
+ * @Component({ ... })
45
+ * export class MyComponent {
46
+ * // 3. Inject the typed service
47
+ * private storage = inject(AppStateStorage);
48
+ *
49
+ * // Reactive: A readonly Signal that auto-updates
50
+ * readonly theme = this.storage.getSignal('theme', 'light');
51
+ *
52
+ * constructor() {
53
+ * // Static: Get the current value statically (no reactivity)
54
+ * const rawTheme = this.storage.get('theme', 'light');
55
+ *
56
+ * // Mutate: Sets value in memory, updates the signal, and persists to storage
57
+ * this.storage.set('theme', 'dark');
58
+ * }
59
+ *
60
+ * toggleTheme() {
61
+ * // Mutate safely via callback
62
+ * this.storage.update('theme', current => current === 'light' ? 'dark' : 'light');
63
+ * }
64
+ * }
65
+ * ```
66
+ */
5
67
  declare class SignalStorage<T extends Record<string, any> = {}> {
6
68
  private storage;
7
69
  private signals;
70
+ private defaultValues;
71
+ private channel?;
8
72
  /**
9
73
  * Create a new SignalStorage instance
10
- * @param storage The storage to use (localStorage or sessionStorage). Defaults to localStorage.
74
+ * @param config The configuration for the storage and synchronization.
11
75
  */
12
- constructor(storage?: Storage);
76
+ constructor(config?: Partial<SignalStorageConfig>);
13
77
  /**
14
78
  * Get typed data from SignalStorage
15
79
  * @param key The key to retrieve
@@ -35,8 +99,9 @@ declare class SignalStorage<T extends Record<string, any> = {}> {
35
99
  * Set typed data in localStorage
36
100
  * @param key The key to set
37
101
  * @param value The value to store
102
+ * @param broadcast Whether to broadcast to other tabs
38
103
  */
39
- set<K extends keyof T>(key: K, value: T[K]): void;
104
+ set<K extends keyof T>(key: K, value: T[K], broadcast?: boolean): void;
40
105
  /**
41
106
  * Update typed data based on current value using a callback
42
107
  * @param key The key to update
@@ -46,16 +111,20 @@ declare class SignalStorage<T extends Record<string, any> = {}> {
46
111
  /**
47
112
  * Remove an item from localStorage
48
113
  * @param key The key to remove
114
+ * @param broadcast Whether to broadcast the removal to other tabs
49
115
  */
50
- remove<K extends keyof T>(key: K): void;
116
+ remove<K extends keyof T>(key: K, broadcast?: boolean): void;
51
117
  /**
52
118
  * Check if a key exists in localStorage
53
119
  * @param key The key to check
54
120
  * @returns true if the key exists, false otherwise
55
121
  */
56
122
  has<K extends keyof T>(key: K): boolean;
57
- /** Clear all localStorage */
58
- clear(): void;
123
+ /**
124
+ * Clear all localStorage
125
+ * @param broadcast Whether to broadcast the clear to other tabs
126
+ */
127
+ clear(broadcast?: boolean): void;
59
128
  static ɵfac: i0.ɵɵFactoryDeclaration<SignalStorage<any>, [{ optional: true; }]>;
60
129
  static ɵprov: i0.ɵɵInjectableDeclaration<SignalStorage<any>>;
61
130
  }
@@ -90,49 +159,117 @@ declare function provideSignalIndexedDb(config?: Partial<SignalIndexedDbConfig>)
90
159
  version: number;
91
160
  };
92
161
  })[];
93
- declare class SignalIndexedDb<T extends Record<string, any> = {}> {
162
+ /**
163
+ * A strongly-typed, reactive, asynchronous storage solution powered by Angular's `resource` API and IndexedDB.
164
+ * It gracefully falls back to an in-memory map if IndexedDB is not supported (e.g. server-side rendering).
165
+ * Additionally, it automatically coordinates resource state changes across multiple browser tabs via BroadcastChannel.
166
+ *
167
+ * Define a strict type for the storage keys and values using the generic parameter `T`.
168
+ * You can configure the DB name, store name, and version using the `provideSignalIndexedDb` provider function.
169
+ *
170
+ * @typeParam T - An interface defining the expected shape of the IndexedDB data.
171
+ *
172
+ * @example
173
+ * ```typescript
174
+ * // 1. Define your storage shape
175
+ * interface AppState {
176
+ * theme: 'light' | 'dark';
177
+ * metrics: { visits: number };
178
+ * }
179
+ *
180
+ * // 2. Create a typed service
181
+ * @Injectable({ providedIn: 'root' })
182
+ * export class AppStateDb extends SignalIndexedDb<AppState> {}
183
+ *
184
+ * @Component({ ... })
185
+ * export class MyComponent {
186
+ * // 3. Inject the typed service
187
+ * private db = inject(AppStateDb);
188
+ *
189
+ * // Reactive: Angular Resource containing the async IndexedDB data
190
+ * readonly themeResource = this.db.getResource('theme', 'light');
191
+ *
192
+ * async toggleTheme() {
193
+ * // Static: Get the current value asynchronously (no reactivity)
194
+ * const rawTheme = await this.db.get('theme');
195
+ *
196
+ * // Mutate: Sets value, updates the resource, and persists to IndexedDB
197
+ * await this.db.set('theme', 'dark');
198
+ *
199
+ * // Mutate safely via callback
200
+ * await this.db.update('theme', current => current === 'light' ? 'dark' : 'light');
201
+ * }
202
+ * }
203
+ * ```
204
+ */
205
+ declare class SignalIndexedDb<T extends Record<string, any> = {}> implements OnDestroy {
94
206
  private injector;
95
207
  private config;
96
208
  private dbPromise;
97
209
  private fallbackStorage;
210
+ private defaultValues;
98
211
  private isFallback;
99
212
  private resources;
100
213
  private broadcastChannel?;
101
214
  constructor(injector: Injector, config?: SignalIndexedDbConfig);
102
215
  private initDB;
103
216
  /**
104
- * Get an Angular Resource for a key
217
+ * Retrieves an Angular `ResourceRef` for a specific key.
218
+ * The resource will asynchronously load the initial value from IndexedDB.
219
+ * Calling `set()` or `update()` on this key later will automatically update this resource's signal.
220
+ *
221
+ * @param key The strongly-typed key to retrieve.
222
+ * @returns A `ResourceRef` containing the value (or undefined if not found).
105
223
  */
106
224
  getResource<K extends keyof T>(key: K): ResourceRef<T[K] | undefined>;
107
225
  getResource<K extends keyof T>(key: K, defaultValue: T[K]): ResourceRef<T[K]>;
108
226
  /**
109
- * Get data asynchronously from IndexedDB
227
+ * Retrieves data asynchronously from IndexedDB without creating a reactive dependency.
228
+ * Useful for one-off reads where a Signal/Resource is not needed.
229
+ *
230
+ * @param key The strongly-typed key to retrieve.
231
+ * @returns A promise resolving to the stored value, or undefined if it does not exist.
110
232
  */
111
233
  get<K extends keyof T>(key: K): Promise<T[K] | undefined>;
112
234
  /**
113
- * Set data asynchronously in IndexedDB and update the resource
235
+ * Sets data asynchronously in IndexedDB.
236
+ * This is a reactive operation: it will automatically update any Angular `ResourceRef`
237
+ * observing this key and broadcast the change to other open browser tabs.
238
+ *
239
+ * @param key The strongly-typed key to set.
240
+ * @param value The value to store.
114
241
  */
115
242
  set<K extends keyof T>(key: K, value: T[K]): Promise<void>;
116
243
  /**
117
- * Safely update a value based on its previous state
244
+ * Safely updates a value based on its previous state.
245
+ * Useful when deriving the next value directly from the old value.
246
+ *
247
+ * @param key The strongly-typed key to update.
248
+ * @param updater A callback function that receives the current value and returns the new value.
118
249
  */
119
250
  update<K extends keyof T>(key: K, updater: (current: T[K] | undefined) => T[K]): Promise<void>;
120
251
  /**
121
- * Remove a key asynchronously from IndexedDB
252
+ * Removes a key asynchronously from IndexedDB.
253
+ * This operation will update the local state to undefined for the given key.
254
+ *
255
+ * @param key The strongly-typed key to remove.
122
256
  */
123
257
  remove<K extends keyof T>(key: K): Promise<void>;
124
258
  /**
125
- * Clear the entire object store
259
+ * Clears the entire object store in IndexedDB.
260
+ * This operation will reset all active resource signals to undefined.
126
261
  */
127
262
  clear(): Promise<void>;
128
263
  /**
129
- * Get all keys from the object store
264
+ * Gets all keys currently stored in the object store.
265
+ *
266
+ * @returns A promise resolving to an array of stored keys.
130
267
  */
131
268
  keys(): Promise<string[]>;
132
- private updateLocalState;
269
+ ngOnDestroy(): void;
133
270
  static ɵfac: i0.ɵɵFactoryDeclaration<SignalIndexedDb<any>, [null, { optional: true; }]>;
134
271
  static ɵprov: i0.ɵɵInjectableDeclaration<SignalIndexedDb<any>>;
135
272
  }
136
273
 
137
- export { MemoryStorage, SIGNAL_INDEXEDDB_CONFIG, SIGNAL_STORAGE_TOKEN, SignalIndexedDb, SignalStorage, provideSignalIndexedDb };
138
- export type { SignalIndexedDbConfig };
274
+ export { MemoryStorage, SIGNAL_INDEXEDDB_CONFIG, SIGNAL_STORAGE_CONFIG, SignalIndexedDb, SignalStorage, provideSignalIndexedDb, provideSignalStorage };
275
+ export type { SignalIndexedDbConfig, SignalStorageConfig };