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