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