@ngx-pwa/local-storage 21.0.0 → 22.0.1

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,1401 +1,665 @@
1
- import * as i0 from '@angular/core';
2
- import { InjectionToken, makeEnvironmentProviders, NgModule, inject, Injectable, PLATFORM_ID } from '@angular/core';
3
- import { of, throwError, Observable, asyncScheduler, from, ReplaySubject, fromEvent, race } from 'rxjs';
4
- import { observeOn, mergeMap, map, first, takeWhile, tap, catchError } from 'rxjs/operators';
5
- import { isPlatformBrowser } from '@angular/common';
6
-
7
- /**
8
- * Exception message when `indexedDB` is not working
9
- */
1
+ import * as i0 from "@angular/core";
2
+ import { Injectable, InjectionToken, NgModule, PLATFORM_ID, Service, assertInInjectionContext, inject, makeEnvironmentProviders } from "@angular/core";
3
+ import { Observable, ReplaySubject, asyncScheduler, from, fromEvent, of, race, throwError } from "rxjs";
4
+ import { catchError, first, map, mergeMap, observeOn, takeWhile, tap } from "rxjs/operators";
5
+ import { isPlatformBrowser } from "@angular/common";
10
6
  const IDB_BROKEN_ERROR = "indexedDB is not working";
11
- /**
12
- * Exception raised when `indexedDB` is not working
13
- */
14
- class IDBBrokenError extends Error {
15
- message = IDB_BROKEN_ERROR;
16
- }
17
- /**
18
- * Exception message when a value cannot be serialized for `localStorage`
19
- */
7
+ var IDBBrokenError = class extends Error {
8
+ message = IDB_BROKEN_ERROR;
9
+ };
20
10
  const SERIALIZATION_ERROR = `The storage is currently localStorage,
21
11
  where data must be serialized, and the provided data can't be serialized.`;
22
- /**
23
- * Exception throwned when a value cannot be serialized for `localStorage`
24
- */
25
- class SerializationError extends Error {
26
- message = SERIALIZATION_ERROR;
27
- }
28
-
29
- /**
30
- * Token to provide a prefix to `localStorage` keys.
31
- */
12
+ var SerializationError = class extends Error {
13
+ message = SERIALIZATION_ERROR;
14
+ };
32
15
  const LS_PREFIX = new InjectionToken("localStoragePrefix", {
33
- providedIn: "root",
34
- factory: () => ""
16
+ providedIn: "root",
17
+ factory: () => ""
35
18
  });
36
- /**
37
- * Default name used for `indexedDB` database.
38
- */
39
19
  const DEFAULT_IDB_DB_NAME = "ngStorage";
40
- /**
41
- * Token to provide `indexedDB` database name.
42
- */
43
20
  const IDB_DB_NAME = new InjectionToken("localStorageIDBDBName", {
44
- providedIn: "root",
45
- factory: () => DEFAULT_IDB_DB_NAME
21
+ providedIn: "root",
22
+ factory: () => DEFAULT_IDB_DB_NAME
46
23
  });
47
- /**
48
- * Default version used for `indexedDB` database.
49
- */
50
- const DEFAULT_IDB_DB_VERSION = 1;
51
- /**
52
- * Token to provide `indexedDB` database version.
53
- * Must be an unsigned **integer**.
54
- */
55
24
  const IDB_DB_VERSION = new InjectionToken("localStorageIDBDBVersion", {
56
- providedIn: "root",
57
- factory: () => DEFAULT_IDB_DB_VERSION
25
+ providedIn: "root",
26
+ factory: () => 1
58
27
  });
59
- /**
60
- * Default name used for `indexedDB` object store.
61
- */
62
28
  const DEFAULT_IDB_STORE_NAME = "localStorage";
63
- /**
64
- * Token to provide `indexedDB` store name.
65
- * For backward compatibility, the default can't be set now, `IndexedDBDatabase` will do it at runtime.
66
- */
67
29
  const IDB_STORE_NAME = new InjectionToken("localStorageIDBStoreName", {
68
- providedIn: "root",
69
- factory: () => DEFAULT_IDB_STORE_NAME
30
+ providedIn: "root",
31
+ factory: () => DEFAULT_IDB_STORE_NAME
70
32
  });
71
- /**
72
- * Default value for interoperability with native `indexedDB` and other storage libs,
73
- * by changing how values are stored in `indexedDB` database.
74
- */
75
- const DEFAULT_IDB_NO_WRAP = true;
76
- /**
77
- * Token to allow interoperability with native `indexedDB` and other storage libs,
78
- * by changing how values are stored in `indexedDB` database.
79
- * Defaults to `true`. Change to `false` for backward compatiblity in existing applications.
80
- * **DO NOT CHANGE THIS BEHAVIOR ONCE IN PRODUCTION**, as it would break with existing data.
81
- */
82
33
  const IDB_NO_WRAP = new InjectionToken("localStorageIDBWrap", {
83
- providedIn: "root",
84
- factory: () => DEFAULT_IDB_NO_WRAP
34
+ providedIn: "root",
35
+ factory: () => true
85
36
  });
86
-
87
- /**
88
- * Allows to add a prefix before `localStorage` keys.
89
- *
90
- * *Use only* for interoperability with other APIs or to avoid collision for multiple applications on the same subdomain.
91
- *
92
- * **WARNING: do not change this option in an application already deployed in production, as previously stored data would be lost.**
93
- *
94
- * @example
95
- * export const appConfig: ApplicationConfig = {
96
- * providers: [provideLocalStoragePrefix('custom_')]
97
- * };
98
- */
99
37
  function provideLocalStoragePrefix(prefix) {
100
- return makeEnvironmentProviders([{ provide: LS_PREFIX, useValue: prefix }]);
38
+ return makeEnvironmentProviders([{
39
+ provide: LS_PREFIX,
40
+ useValue: prefix
41
+ }]);
101
42
  }
102
- /**
103
- * Allows to change the name used for `indexedDB` database.
104
- *
105
- * *Use only* for interoperability with other APIs or to avoid collision for multiple applications on the same subdomain.
106
- *
107
- * **WARNING: do not change this option in an application already deployed in production, as previously stored data would be lost.**
108
- *
109
- * @example
110
- * export const appConfig: ApplicationConfig = {
111
- * providers: [provideIndexedDBDataBaseName('custom')]
112
- * };
113
- */
114
43
  function provideIndexedDBDataBaseName(name) {
115
- return makeEnvironmentProviders([{ provide: IDB_DB_NAME, useValue: name }]);
44
+ return makeEnvironmentProviders([{
45
+ provide: IDB_DB_NAME,
46
+ useValue: name
47
+ }]);
116
48
  }
117
- /**
118
- * Allows to change the database version used for `indexedDB` database.
119
- * Must be an unsigned **integer**.
120
- *
121
- * **Use with caution as the creation of the store depends on the version.**
122
- *
123
- * *Use only* for interoperability with other APIs or to avoid collision for multiple applications on the same subdomain.
124
- *
125
- * **WARNING: do not change this option in an applicattion already deployed in production, as previously stored data would be lost.**
126
- *
127
- * @example
128
- * export const appConfig: ApplicationConfig = {
129
- * providers: [provideIndexedDBDataBaseVersion(2)]
130
- * };
131
- */
132
49
  function provideIndexedDBDataBaseVersion(version) {
133
- return makeEnvironmentProviders([{ provide: IDB_DB_VERSION, useValue: version }]);
50
+ return makeEnvironmentProviders([{
51
+ provide: IDB_DB_VERSION,
52
+ useValue: version
53
+ }]);
134
54
  }
135
- /**
136
- * Allows to change the name used for `indexedDB` object store.
137
- *
138
- * *Use only* for interoperability with other APIs.
139
- *
140
- * **WARNING: do not change this option in an application already deployed in production, as previously stored data would be lost.**
141
- *
142
- * @example
143
- * export const appConfig: ApplicationConfig = {
144
- * providers: [provideIndexedDBStoreName('custom')]
145
- * };
146
- */
147
55
  function provideIndexedDBStoreName(name) {
148
- return makeEnvironmentProviders([{ provide: IDB_STORE_NAME, useValue: name }]);
149
- }
150
-
151
- /**
152
- * This module is only here for backward compatibility, **do not add it by yourself**
153
- *
154
- * @ignore
155
- */
156
- class StorageModule {
157
- /**
158
- * Only useful to provide options, otherwise it does nothing.
159
- *
160
- * **Must be used at initialization, ie. in `AppModule`, and must not be loaded again in another module.**
161
- */
162
- static forRoot(config) {
163
- return {
164
- ngModule: StorageModule,
165
- providers: [
166
- // eslint-disable-next-line @typescript-eslint/no-deprecated
167
- config.LSPrefix !== undefined ? { provide: LS_PREFIX, useValue: config.LSPrefix } : [],
168
- // eslint-disable-next-line @typescript-eslint/no-deprecated
169
- config.IDBDBName !== undefined ? { provide: IDB_DB_NAME, useValue: config.IDBDBName } : [],
170
- // eslint-disable-next-line @typescript-eslint/no-deprecated
171
- config.IDBStoreName !== undefined ? { provide: IDB_STORE_NAME, useValue: config.IDBStoreName } : [],
172
- // eslint-disable-next-line @typescript-eslint/no-deprecated
173
- config.IDBDBVersion !== undefined ? { provide: IDB_DB_VERSION, useValue: config.IDBDBVersion } : [],
174
- (config.IDBNoWrap === false) ? { provide: IDB_NO_WRAP, useValue: config.IDBNoWrap } : [],
175
- ],
176
- };
177
- }
178
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.0", ngImport: i0, type: StorageModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule });
179
- static ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "21.0.0", ngImport: i0, type: StorageModule });
180
- static ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "21.0.0", ngImport: i0, type: StorageModule });
56
+ return makeEnvironmentProviders([{
57
+ provide: IDB_STORE_NAME,
58
+ useValue: name
59
+ }]);
181
60
  }
182
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.0", ngImport: i0, type: StorageModule, decorators: [{
183
- type: NgModule
184
- }] });
185
-
186
- /**
187
- * Exception message when a value is not valid against the JSON schema
188
- */
61
+ var StorageModule = class StorageModule {
62
+ static forRoot(config) {
63
+ return {
64
+ ngModule: StorageModule,
65
+ providers: [
66
+ config.LSPrefix !== void 0 ? {
67
+ provide: LS_PREFIX,
68
+ useValue: config.LSPrefix
69
+ } : [],
70
+ config.IDBDBName !== void 0 ? {
71
+ provide: IDB_DB_NAME,
72
+ useValue: config.IDBDBName
73
+ } : [],
74
+ config.IDBStoreName !== void 0 ? {
75
+ provide: IDB_STORE_NAME,
76
+ useValue: config.IDBStoreName
77
+ } : [],
78
+ config.IDBDBVersion !== void 0 ? {
79
+ provide: IDB_DB_VERSION,
80
+ useValue: config.IDBDBVersion
81
+ } : [],
82
+ config.IDBNoWrap === false ? {
83
+ provide: IDB_NO_WRAP,
84
+ useValue: config.IDBNoWrap
85
+ } : []
86
+ ]
87
+ };
88
+ }
89
+ static ɵfac = i0.ɵɵngDeclareFactory({
90
+ minVersion: "12.0.0",
91
+ version: "22.2.3",
92
+ ngImport: i0,
93
+ type: StorageModule,
94
+ deps: [],
95
+ target: i0.ɵɵFactoryTarget.NgModule
96
+ });
97
+ static ɵmod = i0.ɵɵngDeclareNgModule({
98
+ minVersion: "14.0.0",
99
+ version: "22.2.3",
100
+ ngImport: i0,
101
+ type: StorageModule
102
+ });
103
+ static ɵinj = i0.ɵɵngDeclareInjector({
104
+ minVersion: "12.0.0",
105
+ version: "22.2.3",
106
+ ngImport: i0,
107
+ type: StorageModule
108
+ });
109
+ };
110
+ i0.ɵɵngDeclareClassMetadata({
111
+ minVersion: "12.0.0",
112
+ version: "22.2.3",
113
+ ngImport: i0,
114
+ type: StorageModule,
115
+ decorators: [{ type: NgModule }]
116
+ });
189
117
  const VALIDATION_ERROR = `Data stored is not valid against the provided JSON schema.
190
118
  Check your JSON schema, otherwise it means data has been corrupted.`;
191
- /**
192
- * Exception throwned when a value is not valid against the JSON schema
193
- */
194
- class ValidationError extends Error {
195
- message = VALIDATION_ERROR;
196
- }
197
-
198
- class LocalStorageDatabase {
199
- /**
200
- * Optional user prefix to avoid collision for multiple apps on the same subdomain
201
- */
202
- prefix;
203
- constructor() {
204
- /* Prefix if asked, or no prefix otherwise */
205
- this.prefix = inject(LS_PREFIX);
206
- }
207
- /**
208
- * Number of items in `localStorage`
209
- */
210
- get size() {
211
- /* Wrap in a RxJS `Observable` to be consistent with other storages */
212
- return of(localStorage.length);
213
- }
214
- /**
215
- * Gets an item value in `localStorage`
216
- * @param key The item's key
217
- * @returns The item's value if the key exists, `undefined` otherwise, wrapped in a RxJS `Observable`
218
- */
219
- get(key) {
220
- /* Get raw data */
221
- const unparsedData = localStorage.getItem(this.prefixKey(key));
222
- /* No need to parse if data is `null` or `undefined` */
223
- if (unparsedData !== null) {
224
- /* Try to parse */
225
- try {
226
- const parsedData = JSON.parse(unparsedData);
227
- /* Wrap in a RxJS `Observable` to be consistent with other storages */
228
- return of(parsedData);
229
- }
230
- catch (error) {
231
- // eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
232
- return throwError(() => error);
233
- }
234
- }
235
- return of(undefined);
236
- }
237
- /**
238
- * Store an item in `localStorage`
239
- * @param key The item's key
240
- * @param data The item's value
241
- * @returns A RxJS `Observable` to wait the end of the operation
242
- */
243
- set(key, data) {
244
- let serializedData = null;
245
- /* Check if data can be serialized */
246
- const dataPrototype = Object.getPrototypeOf(data);
247
- if ((typeof data === "object") && !Array.isArray(data) &&
248
- !((dataPrototype === Object.prototype) || (dataPrototype === null))) {
249
- return throwError(() => new SerializationError());
250
- }
251
- /* Try to stringify (can fail on circular references) */
252
- try {
253
- serializedData = JSON.stringify(data);
254
- if (serializedData === undefined) {
255
- throw new Error();
256
- }
257
- }
258
- catch (error) {
259
- // eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
260
- return throwError(() => error);
261
- }
262
- /* Can fail if storage quota is exceeded */
263
- try {
264
- localStorage.setItem(this.prefixKey(key), serializedData);
265
- }
266
- catch (error) {
267
- // eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
268
- return throwError(() => error);
269
- }
270
- /* Wrap in a RxJS `Observable` to be consistent with other storages */
271
- return of(undefined);
272
- }
273
- /**
274
- * Deletes an item in `localStorage`
275
- * @param key The item's key
276
- * @returns A RxJS `Observable` to wait the end of the operation
277
- */
278
- delete(key) {
279
- localStorage.removeItem(this.prefixKey(key));
280
- /* Wrap in a RxJS `Observable` to be consistent with other storages */
281
- return of(undefined);
282
- }
283
- /**
284
- * Deletes all items in `localStorage`
285
- * @returns A RxJS `Observable` to wait the end of the operation
286
- */
287
- clear() {
288
- localStorage.clear();
289
- /* Wrap in a RxJS `Observable` to be consistent with other storages */
290
- return of(undefined);
291
- }
292
- /**
293
- * Get all keys in `localStorage`
294
- * Note the order of the keys may be inconsistent in Firefox
295
- * @returns A RxJS `Observable` iterating on keys
296
- */
297
- keys() {
298
- /* Create an `Observable` from keys */
299
- return new Observable((subscriber) => {
300
- /* Iteretate over all the indexes */
301
- for (let index = 0; index < localStorage.length; index += 1) {
302
- /* Cast as we are sure in this case the key is not `null` */
303
- // eslint-disable-next-line @typescript-eslint/no-non-null-assertion -- Ensured by the logic
304
- subscriber.next(this.getUnprefixedKey(index));
305
- }
306
- subscriber.complete();
307
- }).pipe(
308
- /* Required to work like other databases which are asynchronous */
309
- observeOn(asyncScheduler));
310
- }
311
- /**
312
- * Check if a key exists in `localStorage`
313
- * @param key The item's key
314
- * @returns A RxJS `Observable` telling if the key exists or not
315
- */
316
- has(key) {
317
- /* Itérate over all indexes in storage */
318
- for (let index = 0; index < localStorage.length; index += 1) {
319
- if (key === this.getUnprefixedKey(index)) {
320
- /* Wrap in a RxJS `Observable` to be consistent with other storages */
321
- return of(true);
322
- }
323
- }
324
- /* Wrap in a RxJS `Observable` to be consistent with other storages */
325
- return of(false);
326
- }
327
- /**
328
- * Get an unprefixed key
329
- * @param index Index of the key
330
- * @returns The unprefixed key name if exists, `null` otherwise
331
- */
332
- getUnprefixedKey(index) {
333
- /* Get the key in storage: may have a prefix */
334
- const prefixedKey = localStorage.key(index);
335
- if (prefixedKey !== null) {
336
- /* If no prefix, the key is already good, otherwrite strip the prefix */
337
- return this.prefix === "" ? prefixedKey : prefixedKey.substring(this.prefix.length);
338
- }
339
- return null;
340
- }
341
- /**
342
- * Add the prefix to a key
343
- * @param key The key name
344
- * @returns The prefixed key name
345
- */
346
- prefixKey(key) {
347
- return `${this.prefix}${key}`;
348
- }
349
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.0", ngImport: i0, type: LocalStorageDatabase, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
350
- static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.0.0", ngImport: i0, type: LocalStorageDatabase, providedIn: "root" });
351
- }
352
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.0", ngImport: i0, type: LocalStorageDatabase, decorators: [{
353
- type: Injectable,
354
- args: [{
355
- providedIn: "root"
356
- }]
357
- }], ctorParameters: () => [] });
358
-
359
- class MemoryDatabase {
360
- /**
361
- * Memory storage
362
- */
363
- memoryStorage = new Map();
364
- /**
365
- * Number of items in memory
366
- */
367
- get size() {
368
- /* Wrap in a RxJS `Observable` to be consistent with other storages */
369
- return of(this.memoryStorage.size);
370
- }
371
- /**
372
- * Gets an item value in memory
373
- * @param key The item's key
374
- * @returns The item's value if the key exists, `undefined` otherwise, wrapped in a RxJS `Observable`
375
- */
376
- get(key) {
377
- /* Wrap in a RxJS `Observable` to be consistent with other storages */
378
- return of(this.memoryStorage.get(key));
379
- }
380
- /**
381
- * Sets an item in memory
382
- * @param key The item's key
383
- * @param data The item's value
384
- * @returns A RxJS `Observable` to wait the end of the operation
385
- */
386
- set(key, data) {
387
- this.memoryStorage.set(key, data);
388
- /* Wrap in a RxJS `Observable` to be consistent with other storages */
389
- return of(undefined);
390
- }
391
- /**
392
- * Deletes an item in memory
393
- * @param key The item's key
394
- * @returns A RxJS `Observable` to wait the end of the operation
395
- */
396
- delete(key) {
397
- this.memoryStorage.delete(key);
398
- /* Wrap in a RxJS `Observable` to be consistent with other storages */
399
- return of(undefined);
400
- }
401
- /**
402
- * Deletes all items in memory
403
- * @returns A RxJS `Observable` to wait the end of the operation
404
- */
405
- clear() {
406
- this.memoryStorage.clear();
407
- /* Wrap in a RxJS `Observable` to be consistent with other storages */
408
- return of(undefined);
409
- }
410
- /**
411
- * Get all keys in memory
412
- * @returns A RxJS `Observable` iterating on keys
413
- */
414
- keys() {
415
- /* Create an `Observable` from keys */
416
- return from(this.memoryStorage.keys());
417
- }
418
- /**
419
- * Check if a key exists in memory
420
- * @param key Key name
421
- * @returns a RxJS `Observable` telling if the key exists or not
422
- */
423
- has(key) {
424
- /* Wrap in a RxJS `Observable` to be consistent with other storages */
425
- return of(this.memoryStorage.has(key));
426
- }
427
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.0", ngImport: i0, type: MemoryDatabase, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
428
- static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.0.0", ngImport: i0, type: MemoryDatabase, providedIn: "root" });
429
- }
430
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.0", ngImport: i0, type: MemoryDatabase, decorators: [{
431
- type: Injectable,
432
- args: [{
433
- providedIn: "root"
434
- }]
435
- }] });
436
-
437
- /**
438
- * Factory to create a storage according to browser support
439
- * @see {@link https://github.com/cyrilletuzi/angular-async-local-storage/blob/main/docs/BROWSERS_SUPPORT.md}
440
- */
119
+ var ValidationError = class extends Error {
120
+ message = VALIDATION_ERROR;
121
+ };
122
+ var LocalStorageDatabase = class LocalStorageDatabase {
123
+ prefix;
124
+ constructor() {
125
+ this.prefix = inject(LS_PREFIX);
126
+ }
127
+ get size() {
128
+ return of(localStorage.length);
129
+ }
130
+ get(key) {
131
+ const unparsedData = localStorage.getItem(this.prefixKey(key));
132
+ if (unparsedData !== null) try {
133
+ const parsedData = JSON.parse(unparsedData);
134
+ return of(parsedData);
135
+ } catch (error) {
136
+ return throwError(() => error);
137
+ }
138
+ return of(void 0);
139
+ }
140
+ set(key, data) {
141
+ let serializedData = null;
142
+ const dataPrototype = Object.getPrototypeOf(data);
143
+ if (typeof data === "object" && !Array.isArray(data) && !(dataPrototype === Object.prototype || dataPrototype === null)) return throwError(() => new SerializationError());
144
+ try {
145
+ serializedData = JSON.stringify(data);
146
+ if (serializedData === void 0) throw new Error();
147
+ } catch (error) {
148
+ return throwError(() => error);
149
+ }
150
+ try {
151
+ localStorage.setItem(this.prefixKey(key), serializedData);
152
+ } catch (error) {
153
+ return throwError(() => error);
154
+ }
155
+ return of(void 0);
156
+ }
157
+ delete(key) {
158
+ localStorage.removeItem(this.prefixKey(key));
159
+ return of(void 0);
160
+ }
161
+ clear() {
162
+ localStorage.clear();
163
+ return of(void 0);
164
+ }
165
+ keys() {
166
+ return new Observable((subscriber) => {
167
+ for (let index = 0; index < localStorage.length; index += 1) subscriber.next(this.getUnprefixedKey(index));
168
+ subscriber.complete();
169
+ }).pipe(observeOn(asyncScheduler));
170
+ }
171
+ has(key) {
172
+ for (let index = 0; index < localStorage.length; index += 1) if (key === this.getUnprefixedKey(index)) return of(true);
173
+ return of(false);
174
+ }
175
+ getUnprefixedKey(index) {
176
+ const prefixedKey = localStorage.key(index);
177
+ if (prefixedKey !== null) return this.prefix === "" ? prefixedKey : prefixedKey.substring(this.prefix.length);
178
+ return null;
179
+ }
180
+ prefixKey(key) {
181
+ return `${this.prefix}${key}`;
182
+ }
183
+ static ɵfac = i0.ɵɵngDeclareFactory({
184
+ minVersion: "12.0.0",
185
+ version: "22.2.3",
186
+ ngImport: i0,
187
+ type: LocalStorageDatabase,
188
+ deps: [],
189
+ target: i0.ɵɵFactoryTarget.Service
190
+ });
191
+ static ɵprov = i0.ɵɵngDeclareService({
192
+ minVersion: "22.0.0",
193
+ version: "22.2.3",
194
+ ngImport: i0,
195
+ type: LocalStorageDatabase
196
+ });
197
+ };
198
+ i0.ɵɵngDeclareClassMetadata({
199
+ minVersion: "12.0.0",
200
+ version: "22.2.3",
201
+ ngImport: i0,
202
+ type: LocalStorageDatabase,
203
+ decorators: [{ type: Service }],
204
+ ctorParameters: () => []
205
+ });
206
+ var MemoryDatabase = class MemoryDatabase {
207
+ memoryStorage = /* @__PURE__ */ new Map();
208
+ get size() {
209
+ return of(this.memoryStorage.size);
210
+ }
211
+ get(key) {
212
+ return of(this.memoryStorage.get(key));
213
+ }
214
+ set(key, data) {
215
+ this.memoryStorage.set(key, data);
216
+ return of(void 0);
217
+ }
218
+ delete(key) {
219
+ this.memoryStorage.delete(key);
220
+ return of(void 0);
221
+ }
222
+ clear() {
223
+ this.memoryStorage.clear();
224
+ return of(void 0);
225
+ }
226
+ keys() {
227
+ return from(this.memoryStorage.keys());
228
+ }
229
+ has(key) {
230
+ return of(this.memoryStorage.has(key));
231
+ }
232
+ static ɵfac = i0.ɵɵngDeclareFactory({
233
+ minVersion: "12.0.0",
234
+ version: "22.2.3",
235
+ ngImport: i0,
236
+ type: MemoryDatabase,
237
+ deps: [],
238
+ target: i0.ɵɵFactoryTarget.Service
239
+ });
240
+ static ɵprov = i0.ɵɵngDeclareService({
241
+ minVersion: "22.0.0",
242
+ version: "22.2.3",
243
+ ngImport: i0,
244
+ type: MemoryDatabase
245
+ });
246
+ };
247
+ i0.ɵɵngDeclareClassMetadata({
248
+ minVersion: "12.0.0",
249
+ version: "22.2.3",
250
+ ngImport: i0,
251
+ type: MemoryDatabase,
252
+ decorators: [{ type: Service }]
253
+ });
441
254
  function localDatabaseFactory() {
442
- const platformId = inject(PLATFORM_ID);
443
- /* When storage is fully disabled in browser (via the "Block all cookies" option),
444
- * just trying to check `indexedDB` or `localStorage` variables causes a security exception.
445
- * Prevents https://github.com/cyrilletuzi/angular-async-local-storage/issues/118
446
- */
447
- try {
448
- // Do not explicit `window` here, as the global object is not the same in web workers
449
- // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition -- They can be undefined in some browsers scenarios
450
- if (isPlatformBrowser(platformId) && (indexedDB !== undefined) && (indexedDB !== null) && ("open" in indexedDB)) {
451
- /* Check:
452
- * - if we are in a browser context (issue: server-side rendering)
453
- * - it could exist but be `undefined` or `null`
454
- * - it could exists but not having a working API
455
- * Will be the case for:
456
- * - All other browsers in normal mode
457
- * - Chromium / Safari / Firefox private mode, but in this case, data will be swiped when the user leaves the app */
458
- return new IndexedDBDatabase();
459
- }
460
- else if (isPlatformBrowser(platformId)
461
- // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition -- They can be undefined in some browsers scenarios
462
- && (localStorage !== undefined) && (localStorage !== null) && ("getItem" in localStorage)) {
463
- /* Check:
464
- * - if we are in a browser context (issue: server-side rendering)
465
- * - if `localStorage` exists (to be sure)
466
- * - it could exists but not having a working API
467
- * Will be the case for:
468
- * - Safari cross-origin iframes, detected later in `IndexedDBDatabase.connect()`
469
- * @see {@link https://github.com/cyrilletuzi/angular-async-local-storage/issues/42}
470
- */
471
- return new LocalStorageDatabase();
472
- }
473
- }
474
- catch {
475
- // Nothing to do
476
- }
477
- /* Will be the case for:
478
- * - In browsers if storage has been fully disabled (via the "Block all cookies" option)
479
- * - Server-side rendering
480
- * - All other non-browser context
481
- */
482
- return new MemoryDatabase();
483
- }
484
- class LocalDatabase {
485
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.0", ngImport: i0, type: LocalDatabase, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
486
- static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.0.0", ngImport: i0, type: LocalDatabase, providedIn: "root", useFactory: localDatabaseFactory });
487
- }
488
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.0", ngImport: i0, type: LocalDatabase, decorators: [{
489
- type: Injectable,
490
- args: [{
491
- providedIn: "root",
492
- useFactory: localDatabaseFactory,
493
- }]
494
- }] });
495
-
496
- class IndexedDBDatabase {
497
- /**
498
- * `indexedDB` database name
499
- */
500
- dbName;
501
- /**
502
- * `indexedDB` object store name
503
- */
504
- storeName;
505
- /**
506
- * `indexedDB` database version. Must be an unsigned **integer**
507
- */
508
- dbVersion;
509
- /**
510
- * `indexedDB` database connection, wrapped in a RxJS `ReplaySubject` to be able to access the connection
511
- * even after the connection success event happened
512
- */
513
- database = new ReplaySubject(1);
514
- /**
515
- * Flag to not wrap `indexedDB` values for interoperability or to wrap for backward compatibility.
516
- */
517
- noWrap;
518
- /**
519
- * Index used when wrapping value. *For backward compatibility only.*
520
- */
521
- wrapIndex = "value";
522
- constructor() {
523
- this.dbName = inject(IDB_DB_NAME);
524
- this.storeName = inject(IDB_STORE_NAME);
525
- this.dbVersion = inject(IDB_DB_VERSION);
526
- this.noWrap = inject(IDB_NO_WRAP);
527
- /* Connect to `indexedDB`, with prefix if provided by the user */
528
- this.connect();
529
- }
530
- /**
531
- * Information about `indexedDB` connection. *Only useful for interoperability.*
532
- * @returns `indexedDB` database name, store name and database version
533
- */
534
- get backingStore() {
535
- return {
536
- database: this.dbName,
537
- store: this.storeName,
538
- version: this.dbVersion,
539
- };
540
- }
541
- /**
542
- * Number of items in our `indexedDB` database and object store
543
- */
544
- get size() {
545
- /* Open a transaction in read-only mode */
546
- return this.transaction("readonly").pipe(mergeMap((transactionData) => {
547
- const { store, events } = transactionData;
548
- /* Request to know the number of items */
549
- const request = store.count();
550
- /* Return the result */
551
- return events.pipe(map(() => request.result));
552
- }),
553
- /* The observable will complete after the first value */
554
- first());
555
- }
556
- /**
557
- * Gets an item value in our `indexedDB` store
558
- * @param key The item's key
559
- * @returns The item's value if the key exists, `undefined` otherwise, wrapped in an RxJS `Observable`
560
- */
561
- get(key) {
562
- /* Open a transaction in read-only mode */
563
- return this.transaction("readonly").pipe(mergeMap((transactionData) => {
564
- const { store, events } = transactionData;
565
- /* Request the value with the key provided by the user */
566
- const request = store.get(key);
567
- /* Listen events and return the result */
568
- return events.pipe(map(() => {
569
- if ((request.result !== undefined) && (request.result !== null)) {
570
- /* Prior to v8, the value was wrapped in an `{ value: ...}` object */
571
- if (!this.noWrap && (typeof request.result === "object") && (this.wrapIndex in request.result) &&
572
- // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access -- Required by indexedDb behavior
573
- (request.result[this.wrapIndex] !== undefined) && (request.result[this.wrapIndex] !== null)) {
574
- // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access -- Required by indexedDb behavior
575
- return request.result[this.wrapIndex];
576
- }
577
- else {
578
- /* Cast to the wanted type */
579
- return request.result;
580
- }
581
- }
582
- /* Return `undefined` if the value is empty */
583
- return undefined;
584
- }));
585
- }),
586
- /* The observable will complete after the first value */
587
- first());
588
- }
589
- /**
590
- * Sets an item in our `indexedDB` store
591
- * @param key The item's key
592
- * @param data The item's value
593
- * @returns An RxJS `Observable` to wait the end of the operation
594
- */
595
- set(key, data) {
596
- /* Storing `undefined` in `indexedDb` can cause issues in some browsers so removing item instead */
597
- if (data === undefined) {
598
- return this.delete(key);
599
- }
600
- /* Open a transaction in write mode */
601
- return this.transaction("readwrite").pipe(mergeMap((transactionData) => {
602
- const { store, events } = transactionData;
603
- /* Prior to v8, data was wrapped in a `{ value: ... }` object */
604
- const dataToStore = this.noWrap ? data : { [this.wrapIndex]: data };
605
- /* Add if the item is not existing yet, or update otherwise */
606
- store.put(dataToStore, key);
607
- /* Listen to events and return `undefined` as no value is expected */
608
- return events.pipe(map(() => undefined));
609
- }),
610
- /* The observable will complete after the first value */
611
- first());
612
- }
613
- /**
614
- * Deletes an item in our `indexedDB` store
615
- * @param key The item's key
616
- * @returns An RxJS `Observable` to wait the end of the operation
617
- */
618
- delete(key) {
619
- /* Open a transaction in write mode */
620
- return this.transaction("readwrite").pipe(mergeMap((transactionData) => {
621
- const { store, events } = transactionData;
622
- /* Delete the item in store */
623
- store.delete(key);
624
- /* Listen to events and return `undefined` as no data is expected here */
625
- return events.pipe(map(() => undefined));
626
- }),
627
- /* The observable will complete after the first value */
628
- first());
629
- }
630
- /**
631
- * Deletes all items from our `indexedDB` objet store
632
- * @returns An RxJS `Observable` to wait the end of the operation
633
- */
634
- clear() {
635
- /* Open a transaction in write mode */
636
- return this.transaction("readwrite").pipe(mergeMap((transactionData) => {
637
- const { store, events } = transactionData;
638
- /* Delete all items in object store */
639
- store.clear();
640
- /* Listen to events and return `undefined` as no data is expected here */
641
- return events.pipe(map(() => undefined));
642
- }),
643
- /* The observable will complete */
644
- first());
645
- }
646
- /**
647
- * Get all the keys in our `indexedDB` store
648
- * @returns An RxJS `Observable` iterating on each key
649
- */
650
- keys() {
651
- /* Open a transaction in read-only mode */
652
- return this.transaction("readonly").pipe(
653
- /* `first()` is used as the final operator in other methods to complete the `Observable`
654
- * (as it all starts from a `ReplaySubject` which never ends),
655
- * but as this method is iterating over multiple values, `first()` **must** be used here */
656
- first(), mergeMap((transactionData) => {
657
- const { store } = transactionData;
658
- /* Open a cursor on the store
659
- * Avoid issues like https://github.com/cyrilletuzi/angular-async-local-storage/issues/69 */
660
- const request = store.openKeyCursor();
661
- /* Listen to success event */
662
- const success$ = fromEvent(request, "success").pipe(
663
- /* Stop the `Observable` when the cursor is `null` */
664
- // // eslint-disable-next-line rxjs/no-ignored-takewhile-value -- Required by indexedDb behavior, getting the result from the event does not always work
665
- takeWhile(() => (request.result !== null)),
666
- /* This lib only allows string keys, but user could have added other types of keys from outside
667
- * It's OK to cast as the cursor as been tested in the previous operator */
668
- // eslint-disable-next-line @typescript-eslint/no-non-null-assertion, @typescript-eslint/no-base-to-string -- Required by indexedDb behavior, and strings are enforced by the lib
669
- map(() => request.result.key.toString()),
670
- /* Iterate on the cursor */
671
- // eslint-disable-next-line @typescript-eslint/no-non-null-assertion -- Required by indexedDb behavior
672
- tap(() => { request.result.continue(); }));
673
- /* Listen to error event and if so, throw an error */
674
- const error$ = this.listenError(request);
675
- /* Choose the first event to occur */
676
- return race([success$, error$]);
677
- }));
678
- }
679
- /**
680
- * Check if a key exists in our `indexedDB` store
681
- * @returns An RxJS `Observable` telling if the key exists or not
682
- */
683
- has(key) {
684
- /* Open a transaction in read-only mode */
685
- return this.transaction("readonly").pipe(mergeMap((transactionData) => {
686
- const { store, events } = transactionData;
687
- /* Check if the key exists in the store
688
- * Fixes https://github.com/cyrilletuzi/angular-async-local-storage/issues/69
689
- */
690
- const request = store.getKey(key);
691
- /* Listen to events and return `true` or `false` */
692
- return events.pipe(map(() => (request.result !== undefined) ? true : false));
693
- }),
694
- /* The observable will complete */
695
- first());
696
- }
697
- /**
698
- * Connects to `indexedDB` and creates the object store on first time
699
- */
700
- connect() {
701
- let request;
702
- /* Connect to `indexedDB`
703
- * Will fail in Safari cross-origin iframes
704
- * @see {@link https://github.com/cyrilletuzi/angular-async-local-storage/issues/42} */
705
- try {
706
- /* Do NOT explicit `window` here, as `indexedDB` could be used from a web worker too */
707
- request = indexedDB.open(this.dbName, this.dbVersion);
708
- }
709
- catch {
710
- this.database.error(new IDBBrokenError());
711
- return;
712
- }
713
- /* Create store on first connection */
714
- this.createStore(request);
715
- /* Listen to success and error events */
716
- const success$ = fromEvent(request, "success");
717
- const error$ = this.listenError(request);
718
- /* Choose the first to occur */
719
- race([success$, error$])
720
- /* The observable will complete */
721
- .pipe(first())
722
- .subscribe({
723
- next: () => {
724
- /* Register the database connection in the `ReplaySubject` for further access */
725
- this.database.next(request.result);
726
- },
727
- error: () => {
728
- /* Keeping this error management for safety, but it should not happen anymore.
729
- * It was for Firefox private mode issue in Firefox versions < 115
730
- * @see {@link https://github.com/cyrilletuzi/angular-async-local-storage/issues/26} */
731
- this.database.error(new IDBBrokenError());
732
- },
733
- });
734
- }
735
- /**
736
- * Create store on first use of `indexedDB`
737
- * @param request `indexedDB` database opening request
738
- */
739
- createStore(request) {
740
- /* Listen to the event fired on first connection */
741
- fromEvent(request, "upgradeneeded")
742
- /* The observable will complete */
743
- .pipe(first())
744
- .subscribe({
745
- next: () => {
746
- /* Check if the store already exists, to avoid error */
747
- if (!request.result.objectStoreNames.contains(this.storeName)) {
748
- /* Create the object store */
749
- request.result.createObjectStore(this.storeName);
750
- }
751
- }
752
- });
753
- }
754
- /**
755
- * Open an `indexedDB` transaction and get our store
756
- * @param mode `readonly` or `readwrite`
757
- * @returns An `indexedDB` transaction store and events, wrapped in an RxJS `Observable`
758
- */
759
- transaction(mode) {
760
- /* From the `indexedDB` connection, open a transaction and get the store */
761
- return this.database
762
- .pipe(mergeMap((database) => {
763
- let transaction;
764
- try {
765
- transaction = database.transaction([this.storeName], mode);
766
- }
767
- catch (error) {
768
- /* The store could have been deleted from outside */
769
- // eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
770
- return throwError(() => error);
771
- }
772
- /* Get the store from the transaction */
773
- const store = transaction.objectStore(this.storeName);
774
- /* Listen transaction `complete` and `error` events */
775
- const events = this.listenTransactionEvents(transaction);
776
- return of({ store, events });
777
- }));
778
- }
779
- /**
780
- * Listen errors on a transaction or request, and throw if trigerred
781
- * @param transactionOrRequest `indexedDb` transaction or request to listen
782
- * @returns An `Observable` listening to errors
783
- */
784
- listenError(transactionOrRequest) {
785
- return fromEvent(transactionOrRequest, "error").pipe(
786
- /* Throw on error to be able to catch errors in RxJS way.
787
- * Here `event.target` must be used, as `transactionOrRequest.error` will be `null`
788
- * if we are on the request and the error is only triggered later by the transaction */
789
- // eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
790
- mergeMap((event) => throwError(() => event.target?.error)));
791
- }
792
- /**
793
- * Listen transaction `complete` and `error` events
794
- * @param transaction Transaction to listen
795
- * @returns An `Observable` listening to transaction `complete` and `error` events
796
- */
797
- listenTransactionEvents(transaction) {
798
- /* Listen to the `complete` event */
799
- const complete$ = fromEvent(transaction, "complete");
800
- /* Listen to the `error` event */
801
- const error$ = this.listenError(transaction);
802
- /* Choose the first event to occur */
803
- return race([complete$, error$]);
804
- }
805
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.0", ngImport: i0, type: IndexedDBDatabase, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
806
- static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.0.0", ngImport: i0, type: IndexedDBDatabase, providedIn: "root" });
807
- }
808
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.0", ngImport: i0, type: IndexedDBDatabase, decorators: [{
809
- type: Injectable,
810
- args: [{
811
- providedIn: "root"
812
- }]
813
- }], ctorParameters: () => [] });
814
-
815
- class JSONValidator {
816
- /**
817
- * Validate a JSON data against a Jsubset of the JSON Schema standard.
818
- * Types are enforced to validate everything: each schema must
819
- * @param data JSON data to validate
820
- * @param schema Subset of JSON Schema. Must have a `type`.
821
- * @returns If data is valid: `true`, if it is invalid: `false`
822
- * @see {@link https://github.com/cyrilletuzi/angular-async-local-storage/blob/main/docs/VALIDATION.md}
823
- */
824
- validate(data, schema) {
825
- switch (schema.type) {
826
- case "string":
827
- return this.validateString(data, schema);
828
- case "number":
829
- case "integer":
830
- return this.validateNumber(data, schema);
831
- case "boolean":
832
- return this.validateBoolean(data, schema);
833
- case "array":
834
- return this.validateArray(data, schema);
835
- case "object":
836
- return this.validateObject(data, schema);
837
- }
838
- }
839
- /**
840
- * Validate a string
841
- * @param data Data to validate
842
- * @param schema Schema describing the string
843
- * @returns If data is valid: `true`, if it is invalid: `false`
844
- */
845
- validateString(data, schema) {
846
- if (typeof data !== "string") {
847
- return false;
848
- }
849
- if (!this.validateConst(data, schema)) {
850
- return false;
851
- }
852
- if (!this.validateEnum(data, schema)) {
853
- return false;
854
- }
855
- if ((schema.maxLength !== undefined) && (data.length > schema.maxLength)) {
856
- return false;
857
- }
858
- if ((schema.minLength !== undefined) && (data.length < schema.minLength)) {
859
- return false;
860
- }
861
- if (schema.pattern !== undefined) {
862
- try {
863
- const regularExpression = new RegExp(schema.pattern);
864
- if (!regularExpression.test(data)) {
865
- return false;
866
- }
867
- }
868
- catch {
869
- // Nothing to do
870
- }
871
- }
872
- return true;
873
- }
874
- /**
875
- * Validate a number or an integer
876
- * @param data Data to validate
877
- * @param schema Schema describing the number or integer
878
- * @returns If data is valid: `true`, if it is invalid: `false`
879
- */
880
- validateNumber(data, schema) {
881
- if (typeof data !== "number") {
882
- return false;
883
- }
884
- if ((schema.type === "integer") && !Number.isInteger(data)) {
885
- return false;
886
- }
887
- if (!this.validateConst(data, schema)) {
888
- return false;
889
- }
890
- if (!this.validateEnum(data, schema)) {
891
- return false;
892
- }
893
- /* Test is done this way to not divide by 0 */
894
- if (schema.multipleOf !== undefined && schema.multipleOf !== 0 && Number.isFinite(schema.multipleOf) && !Number.isInteger(data / schema.multipleOf)) {
895
- return false;
896
- }
897
- if ((schema.maximum !== undefined) && (data > schema.maximum)) {
898
- return false;
899
- }
900
- if ((schema.exclusiveMaximum !== undefined) && (data >= schema.exclusiveMaximum)) {
901
- return false;
902
- }
903
- if ((schema.minimum !== undefined) && (data < schema.minimum)) {
904
- return false;
905
- }
906
- if ((schema.exclusiveMinimum !== undefined) && (data <= schema.exclusiveMinimum)) {
907
- return false;
908
- }
909
- return true;
910
- }
911
- /**
912
- * Validate a boolean
913
- * @param data Data to validate
914
- * @param schema Schema describing the boolean
915
- * @returns If data is valid: `true`, if it is invalid: `false`
916
- */
917
- validateBoolean(data, schema) {
918
- if (typeof data !== "boolean") {
919
- return false;
920
- }
921
- if (!this.validateConst(data, schema)) {
922
- return false;
923
- }
924
- return true;
925
- }
926
- /**
927
- * Validate an array
928
- * @param data Data to validate
929
- * @param schema Schema describing the array
930
- * @returns If data is valid: `true`, if it is invalid: `false`
931
- */
932
- validateArray(data, schema) {
933
- if (!Array.isArray(data)) {
934
- return false;
935
- }
936
- if ((schema.maxItems !== undefined) && (data.length > schema.maxItems)) {
937
- return false;
938
- }
939
- if ((schema.minItems !== undefined) && (data.length < schema.minItems)) {
940
- return false;
941
- }
942
- if (schema.uniqueItems === true) {
943
- /* Create a set to eliminate values with multiple occurences */
944
- const dataSet = new Set(data);
945
- if (data.length !== dataSet.size) {
946
- return false;
947
- }
948
- }
949
- /* Specific test for tuples */
950
- if (Array.isArray(schema.items) || schema.items === undefined) {
951
- // TODO: cast should not be needed here
952
- // eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
953
- return this.validateTuple(data, schema.items);
954
- }
955
- /* Validate all the values in array */
956
- for (const value of data) {
957
- // TODO: remove when TypeScript 4.1 is available
958
- // (currently the narrowed type from `Array.isArray()` is lost on readonly arrays)
959
- // eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
960
- if (!this.validate(value, schema.items)) {
961
- return false;
962
- }
963
- }
964
- return true;
965
- }
966
- /**
967
- * Validate a tuple (array with fixed length and multiple types)
968
- * @param data Data to validate
969
- * @param schemas Schemas describing the tuple
970
- * @returns If data is valid: `true`, if it is invalid: `false`
971
- */
972
- validateTuple(data, schemas) {
973
- const lengthToCheck = schemas ? schemas.length : 0;
974
- /* Tuples have a fixed length */
975
- if (data.length !== lengthToCheck) {
976
- return false;
977
- }
978
- if (schemas) {
979
- for (const [index, schema] of schemas.entries()) {
980
- if (!this.validate(data[index], schema)) {
981
- return false;
982
- }
983
- }
984
- }
985
- return true;
986
- }
987
- /**
988
- * Validate an object
989
- * @param data Data to validate
990
- * @param schema JSON schema describing the object
991
- * @returns If data is valid: `true`, if it is invalid: `false`
992
- */
993
- validateObject(data, schema) {
994
- /* Check the type and if not `null` as `null` also have the type `object` in old browsers */
995
- if ((typeof data !== "object") || (data === null)) {
996
- return false;
997
- }
998
- /* Check if the object doesn't have more properties than expected
999
- * Equivalent of `additionalProperties: false`
1000
- */
1001
- if (Object.keys(schema.properties).length < Object.keys(data).length) {
1002
- return false;
1003
- }
1004
- /* Validate required properties */
1005
- if (schema.required) {
1006
- for (const requiredProp of schema.required) {
1007
- if (!Object.hasOwn(data, requiredProp)) {
1008
- return false;
1009
- }
1010
- }
1011
- }
1012
- /* Recursively validate all properties */
1013
- for (const property in schema.properties) {
1014
- /* Filter to keep only real properties (no internal JS stuff) and check if the data has the property too */
1015
- if (Object.hasOwn(schema.properties, property) && Object.hasOwn(data, property)) {
1016
- // eslint-disable-next-line @typescript-eslint/no-non-null-assertion, @typescript-eslint/no-unsafe-type-assertion -- Ensured by the logic
1017
- if (!this.validate(data[property], schema.properties[property])) {
1018
- return false;
1019
- }
1020
- }
1021
- }
1022
- return true;
1023
- }
1024
- /**
1025
- * Validate a constant
1026
- * @param data Data ta validate
1027
- * @param schema JSON schema describing the constant
1028
- * @returns If data is valid: `true`, if it is invalid: `false`
1029
- */
1030
- validateConst(data, schema) {
1031
- if (schema.const === undefined) {
1032
- return true;
1033
- }
1034
- return (data === schema.const);
1035
- }
1036
- /**
1037
- * Validate an enum
1038
- * @param data Data ta validate
1039
- * @param schema JSON schema describing the enum
1040
- * @returns If data is valid: `true`, if it is invalid: `false`
1041
- */
1042
- validateEnum(data, schema) {
1043
- if (!schema.enum) {
1044
- return true;
1045
- }
1046
- /* Cast as the data can be of multiple types, and so TypeScript is lost */
1047
- // eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
1048
- return (schema.enum.includes(data));
1049
- }
1050
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.0", ngImport: i0, type: JSONValidator, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
1051
- static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.0.0", ngImport: i0, type: JSONValidator, providedIn: "root" });
1052
- }
1053
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.0", ngImport: i0, type: JSONValidator, decorators: [{
1054
- type: Injectable,
1055
- args: [{
1056
- providedIn: "root"
1057
- }]
1058
- }] });
1059
-
1060
- class StorageMap {
1061
- #database;
1062
- #jsonValidator;
1063
- #notifiers = new Map();
1064
- /**
1065
- * Constructor params are provided by Angular (but can also be passed manually in tests)
1066
- * @param database Storage to use
1067
- */
1068
- constructor(
1069
- // eslint-disable-next-line @angular-eslint/prefer-inject -- Used in tests
1070
- database) {
1071
- this.#database = database;
1072
- this.#jsonValidator = new JSONValidator();
1073
- }
1074
- /**
1075
- * **Number of items** in storage, wrapped in an Observable.
1076
- *
1077
- * Note you do *not* need to unsubscribe (it is a self-completing Observable).
1078
- *
1079
- * @example
1080
- * this.storageMap.size.subscribe((size) => {
1081
- * console.log(size);
1082
- * });
1083
- */
1084
- get size() {
1085
- return this.#database.size
1086
- /* Catch if `indexedDb` is broken */
1087
- .pipe(this.#catchIDBBroken(() => this.#database.size));
1088
- }
1089
- /**
1090
- * Tells you which storage engine is used.
1091
- *
1092
- * *Only useful for interoperability.*
1093
- *
1094
- * Note that due to some browsers issues in some special contexts
1095
- * (like Safari cross-origin iframes),
1096
- * **this information may be wrong at initialization,**
1097
- * as the storage could fallback from `indexedDB` to `localStorage`
1098
- * only after a first read or write operation.
1099
- * @returns Storage engine used
1100
- *
1101
- * @see {@link https://github.com/cyrilletuzi/angular-async-local-storage/blob/main/docs/INTEROPERABILITY.md}
1102
- *
1103
- * @example
1104
- * if (this.storageMap.backingEngine === 'indexedDB') {}
1105
- */
1106
- get backingEngine() {
1107
- if (this.#database instanceof IndexedDBDatabase) {
1108
- return "indexedDB";
1109
- }
1110
- else if (this.#database instanceof LocalStorageDatabase) {
1111
- return "localStorage";
1112
- }
1113
- else if (this.#database instanceof MemoryDatabase) {
1114
- return "memory";
1115
- }
1116
- else {
1117
- return "unknown";
1118
- }
1119
- }
1120
- /**
1121
- * Information about `indexedDB` database.
1122
- *
1123
- * *Only useful for interoperability.*
1124
- *
1125
- * @returns `indexedDB` database name, store name and database version.
1126
- * **Values will be empty if the storage is not `indexedDB`, so it should be used after an engine check**.
1127
- *
1128
- * @see {@link https://github.com/cyrilletuzi/angular-async-local-storage/blob/main/docs/INTEROPERABILITY.md}
1129
- *
1130
- * @example
1131
- * if (this.storageMap.backingEngine === 'indexedDB') {
1132
- * const { database, store, version } = this.storageMap.backingStore;
1133
- * }
1134
- */
1135
- get backingStore() {
1136
- return (this.#database instanceof IndexedDBDatabase) ?
1137
- this.#database.backingStore :
1138
- { database: "", store: "", version: 0 };
1139
- }
1140
- /**
1141
- * Information about `localStorage` fallback storage.
1142
- *
1143
- * *Only useful for interoperability.*
1144
- *
1145
- * @returns `localStorage` prefix.
1146
- * **Values will be empty if the storage is not `localStorage`, so it should be used after an engine check**.
1147
- *
1148
- * @see {@link https://github.com/cyrilletuzi/angular-async-local-storage/blob/main/docs/INTEROPERABILITY.md}
1149
- *
1150
- * @example
1151
- * if (this.storageMap.backingEngine === 'localStorage') {
1152
- * const { prefix } = this.storageMap.fallbackBackingStore;
1153
- * }
1154
- */
1155
- get fallbackBackingStore() {
1156
- return (this.#database instanceof LocalStorageDatabase) ?
1157
- { prefix: this.#database.prefix } :
1158
- { prefix: "" };
1159
- }
1160
- get(key, schema) {
1161
- /* Get the data in storage */
1162
- return this.#database.get(key).pipe(
1163
- /* Check if `indexedDb` is broken */
1164
- this.#catchIDBBroken(() => this.#database.get(key)), mergeMap((data) => {
1165
- /* No need to validate if the data is empty */
1166
- if ((data === undefined) || (data === null)) {
1167
- return of(undefined);
1168
- }
1169
- else if (schema) {
1170
- /* Validate data against a JSON schema if provided */
1171
- if (!this.#jsonValidator.validate(data, schema)) {
1172
- return throwError(() => new ValidationError());
1173
- }
1174
- /* Data have been checked, so it's OK to cast */
1175
- // eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
1176
- return of(data);
1177
- }
1178
- /* Cast to unknown as the data wasn't checked */
1179
- return of(data);
1180
- }));
1181
- }
1182
- /**
1183
- * Store an item in storage.
1184
- *
1185
- * Note that:
1186
- * * you *do* need to subscribe, even if you do not have something specific to do after writing in storage, otherwise nothing happens (because it is how RxJS Observables work),
1187
- * * but you do *not* need to unsubscribe (it is a self-completing Observable),
1188
- * * setting `null` or `undefined` will remove the item to avoid some browsers issues,
1189
- * * you should stick to serializable JSON data, meaning primitive types, arrays and literal objects. Date, Map, Set, Blob and other special structures can cause issues in some scenarios.
1190
- * @see {@link https://github.com/cyrilletuzi/angular-async-local-storage/blob/main/docs/SERIALIZATION.md}
1191
- *
1192
- * @param key The item's key
1193
- * @param data The item's value
1194
- * @param schema Optional JSON schema to validate the data
1195
- * @returns A RxJS Observable to wait the end of the operation
1196
- *
1197
- * @example
1198
- * this.storageMap.set('key', 'value').subscribe(() => {});
1199
- */
1200
- set(key, data, schema) {
1201
- /* Storing `undefined` or `null` is useless and can cause issues in `indexedDb` in some browsers,
1202
- * so removing item instead for all storages to have a consistent API */
1203
- if ((data === undefined) || (data === null)) {
1204
- return this.delete(key);
1205
- }
1206
- /* Validate data against a JSON schema if provided */
1207
- if (schema && !this.#jsonValidator.validate(data, schema)) {
1208
- return throwError(() => new ValidationError());
1209
- }
1210
- return this.#database.set(key, data).pipe(
1211
- /* Catch if `indexedDb` is broken */
1212
- this.#catchIDBBroken(() => this.#database.set(key, data)),
1213
- /* Notify watchers (must be last because it should only happen if the operation succeeds) */
1214
- tap(() => { this.#notify(key, data); }));
1215
- }
1216
- /**
1217
- * Delete an item in storage.
1218
- *
1219
- * Note that:
1220
- * * you *do* need to subscribe, even if you do not have something specific to do after deleting, otherwise nothing happens (because it is how RxJS Observables work),
1221
- * * but you do *not* need to unsubscribe (it is a self-completing Observable).
1222
- *
1223
- * @param key The item's key
1224
- * @returns A RxJS Observable to wait the end of the operation
1225
- *
1226
- * @example
1227
- * this.storageMap.delete('key').subscribe(() => {});
1228
- */
1229
- delete(key) {
1230
- return this.#database.delete(key).pipe(
1231
- /* Catch if `indexedDb` is broken */
1232
- this.#catchIDBBroken(() => this.#database.delete(key)),
1233
- /* Notify watchers (must be last because it should only happen if the operation succeeds) */
1234
- tap(() => { this.#notify(key, undefined); }));
1235
- }
1236
- /**
1237
- * Delete all items in storage.
1238
- *
1239
- * Note that:
1240
- * * you *do* need to subscribe, even if you do not have something specific to do after clearing, otherwise nothing happens (because it is how RxJS Observables work),
1241
- * * but you do *not* need to unsubscribe (it is a self-completing Observable).
1242
- *
1243
- * @returns A RxJS Observable to wait the end of the operation
1244
- *
1245
- * @example
1246
- * this.storageMap.clear().subscribe(() => {});
1247
- */
1248
- clear() {
1249
- return this.#database.clear().pipe(
1250
- /* Catch if `indexedDb` is broken */
1251
- this.#catchIDBBroken(() => this.#database.clear()),
1252
- /* Notify watchers (must be last because it should only happen if the operation succeeds) */
1253
- tap(() => {
1254
- for (const key of this.#notifiers.keys()) {
1255
- this.#notify(key, undefined);
1256
- }
1257
- }));
1258
- }
1259
- /**
1260
- * Get all keys stored in storage.
1261
- *
1262
- * Note **this is an *iterating* Observable**:
1263
- * * if there is no key, the `next` callback will not be invoked,
1264
- * * if you need to wait the whole operation to end, be sure to act in the `complete` callback,
1265
- * as this Observable can emit several values and so will invoke the `next` callback several times,
1266
- * * you do *not* need to unsubscribe (it is a self-completing Observable).
1267
- *
1268
- * @returns A list of the keys wrapped in a RxJS Observable
1269
- *
1270
- * @example
1271
- * this.storageMap.keys().subscribe({
1272
- * next: (key) => { console.log(key); },
1273
- * complete: () => { console.log('Done'); },
1274
- * });
1275
- */
1276
- keys() {
1277
- return this.#database.keys()
1278
- /* Catch if `indexedDb` is broken */
1279
- .pipe(this.#catchIDBBroken(() => this.#database.keys()));
1280
- }
1281
- /**
1282
- * Tells if a key exists in storage.
1283
- *
1284
- * Note you do *not* need to unsubscribe (it is a self-completing Observable).
1285
- *
1286
- * @returns A RxJS Observable telling if the key exists
1287
- *
1288
- * @example
1289
- * this.storageMap.has('key').subscribe((hasKey) => {
1290
- * if (hasKey) {}
1291
- * });
1292
- */
1293
- has(key) {
1294
- return this.#database.has(key)
1295
- /* Catch if `indexedDb` is broken */
1296
- .pipe(this.#catchIDBBroken(() => this.#database.has(key)));
1297
- }
1298
- watch(key, schema) {
1299
- /* Check if there is already a notifier */
1300
- if (!this.#notifiers.has(key)) {
1301
- this.#notifiers.set(key, new ReplaySubject(1));
1302
- }
1303
- /* Non-null assertion is required because TypeScript doesn't narrow `.has()` yet */
1304
- // eslint-disable-next-line @typescript-eslint/no-non-null-assertion -- Ensured by the logic
1305
- const notifier = this.#notifiers.get(key);
1306
- /* Get the current item value */
1307
- (schema ? this.get(key, schema) : this.get(key)).subscribe({
1308
- next: (result) => {
1309
- notifier.next(result);
1310
- },
1311
- error: (error) => {
1312
- notifier.error(error);
1313
- },
1314
- });
1315
- /* Only the public API of the Observable should be returned */
1316
- return (schema ?
1317
- // eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
1318
- notifier.asObservable() :
1319
- notifier.asObservable());
1320
- }
1321
- /**
1322
- * Notify when a value changes
1323
- * @param key The item's key
1324
- * @param data The new value
1325
- */
1326
- #notify(key, value) {
1327
- const notifier = this.#notifiers.get(key);
1328
- if (notifier) {
1329
- notifier.next(value);
1330
- }
1331
- }
1332
- /**
1333
- * RxJS operator to catch if `indexedDB` is broken
1334
- * @param operationCallback Callback with the operation to redo
1335
- */
1336
- #catchIDBBroken(operationCallback) {
1337
- return catchError((error) => {
1338
- /* Check if `indexedDB` is broken based on error message (the specific error class seems to be lost in the process) */
1339
- if ((error !== undefined) && (error !== null)
1340
- && (typeof error === "object") && ("message" in error)
1341
- // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access -- Required because TypeScript narrowing is not working here
1342
- && (error.message === IDB_BROKEN_ERROR)) {
1343
- /* When storage is fully disabled in browser (via the "Block all cookies" option),
1344
- * just trying to check `localStorage` variable causes a security exception.
1345
- * Prevents https://github.com/cyrilletuzi/angular-async-local-storage/issues/118
1346
- */
1347
- try {
1348
- if ("getItem" in localStorage) {
1349
- /* Fallback to `localStorage` if available */
1350
- this.#database = new LocalStorageDatabase();
1351
- }
1352
- else {
1353
- /* Fallback to memory storage otherwise */
1354
- this.#database = new MemoryDatabase();
1355
- }
1356
- }
1357
- catch {
1358
- /* Fallback to memory storage otherwise */
1359
- this.#database = new MemoryDatabase();
1360
- }
1361
- /* Redo the operation */
1362
- return operationCallback();
1363
- }
1364
- else {
1365
- /* Otherwise, rethrow the error */
1366
- // eslint-disable-next-line @typescript-eslint/no-unsafe-return
1367
- return throwError(() => error);
1368
- }
1369
- });
1370
- }
1371
- /**
1372
- * THIS METHOD IS FOR INTERNAL PURPOSE ONLY AND MUST NOT BE USED,
1373
- * IT CAN BE REMOVED AT ANY TIME AND MESSING WITH IT CAN CAUSE ISSUES
1374
- * @private
1375
- * @ignore
1376
- */
1377
- // eslint-disable-next-line @typescript-eslint/ban-ts-comment -- Silence the not used error, it is used in tests
1378
- // @ts-ignore
1379
- ɵinternalGetDatabase() {
1380
- return this.#database;
1381
- }
1382
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.0", ngImport: i0, type: StorageMap, deps: [{ token: LocalDatabase }], target: i0.ɵɵFactoryTarget.Injectable });
1383
- static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.0.0", ngImport: i0, type: StorageMap, providedIn: "root" });
255
+ assertInInjectionContext(localDatabaseFactory);
256
+ const platformId = inject(PLATFORM_ID);
257
+ try {
258
+ if (isPlatformBrowser(platformId) && indexedDB !== void 0 && indexedDB !== null && "open" in indexedDB) return new IndexedDBDatabase();
259
+ else if (isPlatformBrowser(platformId) && localStorage !== void 0 && localStorage !== null && "getItem" in localStorage) return new LocalStorageDatabase();
260
+ } catch {}
261
+ return new MemoryDatabase();
1384
262
  }
1385
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.0", ngImport: i0, type: StorageMap, decorators: [{
1386
- type: Injectable,
1387
- args: [{
1388
- providedIn: "root"
1389
- }]
1390
- }], ctorParameters: () => [{ type: LocalDatabase }] });
1391
-
1392
- /*
1393
- * Public API Surface of local-storage
1394
- */
1395
-
1396
- /**
1397
- * Generated bundle index. Do not edit.
1398
- */
1399
-
263
+ var LocalDatabase = class LocalDatabase {
264
+ static ɵfac = i0.ɵɵngDeclareFactory({
265
+ minVersion: "12.0.0",
266
+ version: "22.2.3",
267
+ ngImport: i0,
268
+ type: LocalDatabase,
269
+ deps: [],
270
+ target: i0.ɵɵFactoryTarget.Service
271
+ });
272
+ static ɵprov = i0.ɵɵngDeclareService({
273
+ minVersion: "22.0.0",
274
+ version: "22.2.3",
275
+ ngImport: i0,
276
+ type: LocalDatabase,
277
+ factory: localDatabaseFactory
278
+ });
279
+ };
280
+ i0.ɵɵngDeclareClassMetadata({
281
+ minVersion: "12.0.0",
282
+ version: "22.2.3",
283
+ ngImport: i0,
284
+ type: LocalDatabase,
285
+ decorators: [{
286
+ type: Service,
287
+ args: [{ factory: localDatabaseFactory }]
288
+ }]
289
+ });
290
+ var IndexedDBDatabase = class IndexedDBDatabase {
291
+ dbName;
292
+ storeName;
293
+ dbVersion;
294
+ database = new ReplaySubject(1);
295
+ noWrap;
296
+ wrapIndex = "value";
297
+ constructor() {
298
+ this.dbName = inject(IDB_DB_NAME);
299
+ this.storeName = inject(IDB_STORE_NAME);
300
+ this.dbVersion = inject(IDB_DB_VERSION);
301
+ this.noWrap = inject(IDB_NO_WRAP);
302
+ this.connect();
303
+ }
304
+ get backingStore() {
305
+ return {
306
+ database: this.dbName,
307
+ store: this.storeName,
308
+ version: this.dbVersion
309
+ };
310
+ }
311
+ get size() {
312
+ return this.transaction("readonly").pipe(mergeMap((transactionData) => {
313
+ const { store, events } = transactionData;
314
+ const request = store.count();
315
+ return events.pipe(map(() => request.result));
316
+ }), first());
317
+ }
318
+ get(key) {
319
+ return this.transaction("readonly").pipe(mergeMap((transactionData) => {
320
+ const { store, events } = transactionData;
321
+ const request = store.get(key);
322
+ return events.pipe(map(() => {
323
+ if (request.result !== void 0 && request.result !== null) {
324
+ if (!this.noWrap && typeof request.result === "object" && this.wrapIndex in request.result && request.result[this.wrapIndex] !== void 0 && request.result[this.wrapIndex] !== null) return request.result[this.wrapIndex];
325
+ else return request.result;
326
+ }
327
+ }));
328
+ }), first());
329
+ }
330
+ set(key, data) {
331
+ if (data === void 0) return this.delete(key);
332
+ return this.transaction("readwrite").pipe(mergeMap((transactionData) => {
333
+ const { store, events } = transactionData;
334
+ const dataToStore = this.noWrap ? data : { [this.wrapIndex]: data };
335
+ store.put(dataToStore, key);
336
+ return events.pipe(map(() => void 0));
337
+ }), first());
338
+ }
339
+ delete(key) {
340
+ return this.transaction("readwrite").pipe(mergeMap((transactionData) => {
341
+ const { store, events } = transactionData;
342
+ store.delete(key);
343
+ return events.pipe(map(() => void 0));
344
+ }), first());
345
+ }
346
+ clear() {
347
+ return this.transaction("readwrite").pipe(mergeMap((transactionData) => {
348
+ const { store, events } = transactionData;
349
+ store.clear();
350
+ return events.pipe(map(() => void 0));
351
+ }), first());
352
+ }
353
+ keys() {
354
+ return this.transaction("readonly").pipe(first(), mergeMap((transactionData) => {
355
+ const { store } = transactionData;
356
+ const request = store.openKeyCursor();
357
+ const success$ = fromEvent(request, "success").pipe(takeWhile(() => request.result !== null), map(() => request.result.key.toString()), tap(() => {
358
+ request.result.continue();
359
+ }));
360
+ const error$ = this.listenError(request);
361
+ return race([success$, error$]);
362
+ }));
363
+ }
364
+ has(key) {
365
+ return this.transaction("readonly").pipe(mergeMap((transactionData) => {
366
+ const { store, events } = transactionData;
367
+ const request = store.getKey(key);
368
+ return events.pipe(map(() => request.result !== void 0 ? true : false));
369
+ }), first());
370
+ }
371
+ connect() {
372
+ let request;
373
+ try {
374
+ request = indexedDB.open(this.dbName, this.dbVersion);
375
+ } catch {
376
+ this.database.error(new IDBBrokenError());
377
+ return;
378
+ }
379
+ this.createStore(request);
380
+ const success$ = fromEvent(request, "success");
381
+ const error$ = this.listenError(request);
382
+ race([success$, error$]).pipe(first()).subscribe({
383
+ next: () => {
384
+ this.database.next(request.result);
385
+ },
386
+ error: () => {
387
+ this.database.error(new IDBBrokenError());
388
+ }
389
+ });
390
+ }
391
+ createStore(request) {
392
+ fromEvent(request, "upgradeneeded").pipe(first()).subscribe({ next: () => {
393
+ if (!request.result.objectStoreNames.contains(this.storeName)) request.result.createObjectStore(this.storeName);
394
+ } });
395
+ }
396
+ transaction(mode) {
397
+ return this.database.pipe(mergeMap((database) => {
398
+ let transaction;
399
+ try {
400
+ transaction = database.transaction([this.storeName], mode);
401
+ } catch (error) {
402
+ return throwError(() => error);
403
+ }
404
+ const store = transaction.objectStore(this.storeName);
405
+ const events = this.listenTransactionEvents(transaction);
406
+ return of({
407
+ store,
408
+ events
409
+ });
410
+ }));
411
+ }
412
+ listenError(transactionOrRequest) {
413
+ return fromEvent(transactionOrRequest, "error").pipe(mergeMap((event) => throwError(() => event.target?.error)));
414
+ }
415
+ listenTransactionEvents(transaction) {
416
+ const complete$ = fromEvent(transaction, "complete");
417
+ const error$ = this.listenError(transaction);
418
+ return race([complete$, error$]);
419
+ }
420
+ static ɵfac = i0.ɵɵngDeclareFactory({
421
+ minVersion: "12.0.0",
422
+ version: "22.2.3",
423
+ ngImport: i0,
424
+ type: IndexedDBDatabase,
425
+ deps: [],
426
+ target: i0.ɵɵFactoryTarget.Service
427
+ });
428
+ static ɵprov = i0.ɵɵngDeclareService({
429
+ minVersion: "22.0.0",
430
+ version: "22.2.3",
431
+ ngImport: i0,
432
+ type: IndexedDBDatabase
433
+ });
434
+ };
435
+ i0.ɵɵngDeclareClassMetadata({
436
+ minVersion: "12.0.0",
437
+ version: "22.2.3",
438
+ ngImport: i0,
439
+ type: IndexedDBDatabase,
440
+ decorators: [{ type: Service }],
441
+ ctorParameters: () => []
442
+ });
443
+ var JSONValidator = class JSONValidator {
444
+ validate(data, schema) {
445
+ switch (schema.type) {
446
+ case "string": return this.validateString(data, schema);
447
+ case "number":
448
+ case "integer": return this.validateNumber(data, schema);
449
+ case "boolean": return this.validateBoolean(data, schema);
450
+ case "array": return this.validateArray(data, schema);
451
+ case "object": return this.validateObject(data, schema);
452
+ }
453
+ }
454
+ validateString(data, schema) {
455
+ if (typeof data !== "string") return false;
456
+ if (!this.validateConst(data, schema)) return false;
457
+ if (!this.validateEnum(data, schema)) return false;
458
+ if (schema.maxLength !== void 0 && data.length > schema.maxLength) return false;
459
+ if (schema.minLength !== void 0 && data.length < schema.minLength) return false;
460
+ if (schema.pattern !== void 0) try {
461
+ if (!new RegExp(schema.pattern).test(data)) return false;
462
+ } catch {}
463
+ return true;
464
+ }
465
+ validateNumber(data, schema) {
466
+ if (typeof data !== "number") return false;
467
+ if (schema.type === "integer" && !Number.isInteger(data)) return false;
468
+ if (!this.validateConst(data, schema)) return false;
469
+ if (!this.validateEnum(data, schema)) return false;
470
+ if (schema.multipleOf !== void 0 && schema.multipleOf !== 0 && Number.isFinite(schema.multipleOf) && !Number.isInteger(data / schema.multipleOf)) return false;
471
+ if (schema.maximum !== void 0 && data > schema.maximum) return false;
472
+ if (schema.exclusiveMaximum !== void 0 && data >= schema.exclusiveMaximum) return false;
473
+ if (schema.minimum !== void 0 && data < schema.minimum) return false;
474
+ if (schema.exclusiveMinimum !== void 0 && data <= schema.exclusiveMinimum) return false;
475
+ return true;
476
+ }
477
+ validateBoolean(data, schema) {
478
+ if (typeof data !== "boolean") return false;
479
+ if (!this.validateConst(data, schema)) return false;
480
+ return true;
481
+ }
482
+ validateArray(data, schema) {
483
+ if (!Array.isArray(data)) return false;
484
+ if (schema.maxItems !== void 0 && data.length > schema.maxItems) return false;
485
+ if (schema.minItems !== void 0 && data.length < schema.minItems) return false;
486
+ if (schema.uniqueItems === true) {
487
+ const dataSet = new Set(data);
488
+ if (data.length !== dataSet.size) return false;
489
+ }
490
+ if (Array.isArray(schema.items) || schema.items === void 0) return this.validateTuple(data, schema.items);
491
+ for (const value of data) if (!this.validate(value, schema.items)) return false;
492
+ return true;
493
+ }
494
+ validateTuple(data, schemas) {
495
+ const lengthToCheck = schemas ? schemas.length : 0;
496
+ if (data.length !== lengthToCheck) return false;
497
+ if (schemas) {
498
+ for (const [index, schema] of schemas.entries()) if (!this.validate(data[index], schema)) return false;
499
+ }
500
+ return true;
501
+ }
502
+ validateObject(data, schema) {
503
+ if (typeof data !== "object" || data === null) return false;
504
+ if (Object.keys(schema.properties).length < Object.keys(data).length) return false;
505
+ if (schema.required) {
506
+ for (const requiredProp of schema.required) if (!Object.hasOwn(data, requiredProp)) return false;
507
+ }
508
+ for (const property in schema.properties) if (Object.hasOwn(schema.properties, property) && Object.hasOwn(data, property)) {
509
+ if (!this.validate(data[property], schema.properties[property])) return false;
510
+ }
511
+ return true;
512
+ }
513
+ validateConst(data, schema) {
514
+ if (schema.const === void 0) return true;
515
+ return data === schema.const;
516
+ }
517
+ validateEnum(data, schema) {
518
+ if (!schema.enum) return true;
519
+ return schema.enum.includes(data);
520
+ }
521
+ static ɵfac = i0.ɵɵngDeclareFactory({
522
+ minVersion: "12.0.0",
523
+ version: "22.2.3",
524
+ ngImport: i0,
525
+ type: JSONValidator,
526
+ deps: [],
527
+ target: i0.ɵɵFactoryTarget.Service
528
+ });
529
+ static ɵprov = i0.ɵɵngDeclareService({
530
+ minVersion: "22.0.0",
531
+ version: "22.2.3",
532
+ ngImport: i0,
533
+ type: JSONValidator
534
+ });
535
+ };
536
+ i0.ɵɵngDeclareClassMetadata({
537
+ minVersion: "12.0.0",
538
+ version: "22.2.3",
539
+ ngImport: i0,
540
+ type: JSONValidator,
541
+ decorators: [{ type: Service }]
542
+ });
543
+ var StorageMap = class StorageMap {
544
+ #database;
545
+ #jsonValidator;
546
+ #notifiers = /* @__PURE__ */ new Map();
547
+ constructor(database) {
548
+ this.#database = database;
549
+ this.#jsonValidator = new JSONValidator();
550
+ }
551
+ get size() {
552
+ return this.#database.size.pipe(this.#catchIDBBroken(() => this.#database.size));
553
+ }
554
+ get backingEngine() {
555
+ if (this.#database instanceof IndexedDBDatabase) return "indexedDB";
556
+ else if (this.#database instanceof LocalStorageDatabase) return "localStorage";
557
+ else if (this.#database instanceof MemoryDatabase) return "memory";
558
+ else return "unknown";
559
+ }
560
+ get backingStore() {
561
+ return this.#database instanceof IndexedDBDatabase ? this.#database.backingStore : {
562
+ database: "",
563
+ store: "",
564
+ version: 0
565
+ };
566
+ }
567
+ get fallbackBackingStore() {
568
+ return this.#database instanceof LocalStorageDatabase ? { prefix: this.#database.prefix } : { prefix: "" };
569
+ }
570
+ get(key, schema) {
571
+ return this.#database.get(key).pipe(this.#catchIDBBroken(() => this.#database.get(key)), mergeMap((data) => {
572
+ if (data === void 0 || data === null) return of(void 0);
573
+ else if (schema) {
574
+ if (!this.#jsonValidator.validate(data, schema)) return throwError(() => new ValidationError());
575
+ return of(data);
576
+ }
577
+ return of(data);
578
+ }));
579
+ }
580
+ set(key, data, schema) {
581
+ if (data === void 0 || data === null) return this.delete(key);
582
+ if (schema && !this.#jsonValidator.validate(data, schema)) return throwError(() => new ValidationError());
583
+ return this.#database.set(key, data).pipe(this.#catchIDBBroken(() => this.#database.set(key, data)), tap(() => {
584
+ this.#notify(key, data);
585
+ }));
586
+ }
587
+ delete(key) {
588
+ return this.#database.delete(key).pipe(this.#catchIDBBroken(() => this.#database.delete(key)), tap(() => {
589
+ this.#notify(key, void 0);
590
+ }));
591
+ }
592
+ clear() {
593
+ return this.#database.clear().pipe(this.#catchIDBBroken(() => this.#database.clear()), tap(() => {
594
+ for (const key of this.#notifiers.keys()) this.#notify(key, void 0);
595
+ }));
596
+ }
597
+ keys() {
598
+ return this.#database.keys().pipe(this.#catchIDBBroken(() => this.#database.keys()));
599
+ }
600
+ has(key) {
601
+ return this.#database.has(key).pipe(this.#catchIDBBroken(() => this.#database.has(key)));
602
+ }
603
+ watch(key, schema) {
604
+ if (!this.#notifiers.has(key)) this.#notifiers.set(key, new ReplaySubject(1));
605
+ const notifier = this.#notifiers.get(key);
606
+ (schema ? this.get(key, schema) : this.get(key)).subscribe({
607
+ next: (result) => {
608
+ notifier.next(result);
609
+ },
610
+ error: (error) => {
611
+ notifier.error(error);
612
+ }
613
+ });
614
+ return notifier.asObservable();
615
+ }
616
+ #notify(key, value) {
617
+ const notifier = this.#notifiers.get(key);
618
+ if (notifier) notifier.next(value);
619
+ }
620
+ #catchIDBBroken(operationCallback) {
621
+ return catchError((error) => {
622
+ if (error !== void 0 && error !== null && typeof error === "object" && "message" in error && error.message === "indexedDB is not working") {
623
+ try {
624
+ if ("getItem" in localStorage) this.#database = new LocalStorageDatabase();
625
+ else this.#database = new MemoryDatabase();
626
+ } catch {
627
+ this.#database = new MemoryDatabase();
628
+ }
629
+ return operationCallback();
630
+ } else return throwError(() => error);
631
+ });
632
+ }
633
+ ɵinternalGetDatabase() {
634
+ return this.#database;
635
+ }
636
+ static ɵfac = i0.ɵɵngDeclareFactory({
637
+ minVersion: "12.0.0",
638
+ version: "22.2.3",
639
+ ngImport: i0,
640
+ type: StorageMap,
641
+ deps: [{ token: LocalDatabase }],
642
+ target: i0.ɵɵFactoryTarget.Injectable
643
+ });
644
+ static ɵprov = i0.ɵɵngDeclareInjectable({
645
+ minVersion: "12.0.0",
646
+ version: "22.2.3",
647
+ ngImport: i0,
648
+ type: StorageMap,
649
+ providedIn: "root"
650
+ });
651
+ };
652
+ i0.ɵɵngDeclareClassMetadata({
653
+ minVersion: "12.0.0",
654
+ version: "22.2.3",
655
+ ngImport: i0,
656
+ type: StorageMap,
657
+ decorators: [{
658
+ type: Injectable,
659
+ args: [{ providedIn: "root" }]
660
+ }],
661
+ ctorParameters: () => [{ type: LocalDatabase }]
662
+ });
1400
663
  export { SERIALIZATION_ERROR, SerializationError, StorageMap, StorageModule, VALIDATION_ERROR, ValidationError, provideIndexedDBDataBaseName, provideIndexedDBDataBaseVersion, provideIndexedDBStoreName, provideLocalStoragePrefix };
1401
- //# sourceMappingURL=ngx-pwa-local-storage.mjs.map
664
+
665
+ //# sourceMappingURL=ngx-pwa-local-storage.mjs.map