Skip to content

Commit 2ee33d5

Browse files
authored
feat: new IDB storing behavior with no wrapping (#82)
1 parent 6b59032 commit 2ee33d5

File tree

7 files changed

+104
-75
lines changed

7 files changed

+104
-75
lines changed

README.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@ import { LocalStorage } from '@ngx-pwa/local-storage';
6060
@Injectable()
6161
export class YourService {
6262

63-
constructor(protected localStorage: LocalStorage) {}
63+
constructor(private localStorage: LocalStorage) {}
6464

6565
}
6666
```

projects/ngx-pwa/local-storage/src/lib/databases/indexeddb-database.ts

+33-23
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,12 @@
1-
import { Injectable, Optional, Inject } from '@angular/core';
1+
import { Injectable, Inject } from '@angular/core';
22
import { Observable, ReplaySubject, fromEvent, of, throwError, race } from 'rxjs';
33
import { map, mergeMap, first, tap, filter } from 'rxjs/operators';
44

55
import { LocalDatabase } from './local-database';
6-
import { PREFIX, IDB_DB_NAME, DEFAULT_IDB_DB_NAME, IDB_STORE_NAME, DEFAULT_IDB_STORE_NAME } from '../tokens';
6+
import {
7+
PREFIX, IDB_DB_NAME, DEFAULT_IDB_DB_NAME, IDB_STORE_NAME, DEFAULT_IDB_STORE_NAME,
8+
COMPATIBILITY_PRIOR_TO_V8, DEFAULT_PREFIX, DEFAULT_COMPATIBILITY_PRIOR_TO_V8
9+
} from '../tokens';
710
import { IDBBrokenError } from '../exceptions';
811

912
@Injectable({
@@ -14,18 +17,23 @@ export class IndexedDBDatabase implements LocalDatabase {
1417
/**
1518
* `indexedDB` database name
1619
*/
17-
protected dbName: string;
20+
private readonly dbName: string;
1821

1922
/**
2023
* `indexedDB` object store name
2124
*/
22-
protected storeName: string;
25+
private readonly storeName: string;
2326

2427
/**
2528
* `indexedDB` data path name for local storage (where items' value will be stored)
2629
*/
2730
private readonly dataPath = 'value';
2831

32+
/**
33+
* Flag to keep storing behavior prior to version 8.
34+
*/
35+
private readonly compatibilityPriorToV8: boolean;
36+
2937
/**
3038
* `indexedDB` database connection, wrapped in a RxJS `ReplaySubject` to be able to access the connection
3139
* even after the connection success event happened
@@ -59,11 +67,13 @@ export class IndexedDBDatabase implements LocalDatabase {
5967
* @param prefix Optional user prefix to avoid collision for multiple apps on the same subdomain
6068
* @param dbName `indexedDB` database name
6169
* @param storeName `indexedDB` store name
70+
* @param compatibilityPriorToV8 Flag to keep storing behavior prior to version 8
6271
*/
6372
constructor(
64-
@Optional() @Inject(PREFIX) prefix: string | null = null,
65-
@Optional() @Inject(IDB_DB_NAME) dbName = DEFAULT_IDB_DB_NAME,
66-
@Optional() @Inject(IDB_STORE_NAME) storeName = DEFAULT_IDB_STORE_NAME,
73+
@Inject(PREFIX) prefix = DEFAULT_PREFIX,
74+
@Inject(IDB_DB_NAME) dbName = DEFAULT_IDB_DB_NAME,
75+
@Inject(IDB_STORE_NAME) storeName = DEFAULT_IDB_STORE_NAME,
76+
@Inject(COMPATIBILITY_PRIOR_TO_V8) compatibilityPriorToV8 = DEFAULT_COMPATIBILITY_PRIOR_TO_V8,
6777
) {
6878

6979
/* Initialize `indexedDB` database name, with prefix if provided by the user */
@@ -72,8 +82,10 @@ export class IndexedDBDatabase implements LocalDatabase {
7282
/* Initialize `indexedDB` store name */
7383
this.storeName = storeName;
7484

85+
this.compatibilityPriorToV8 = compatibilityPriorToV8;
86+
7587
/* Creating the RxJS ReplaySubject */
76-
this.database = new ReplaySubject<IDBDatabase>();
88+
this.database = new ReplaySubject<IDBDatabase>(1);
7789

7890
/* Connect to `indexedDB`, with prefix if provided by the user */
7991
this.connect();
@@ -97,24 +109,20 @@ export class IndexedDBDatabase implements LocalDatabase {
97109
/* Manage success and error events, and get the result */
98110
return this.requestEventsAndMapTo(request, () => {
99111

100-
/* Currently, the lib is wrapping the value in a `{ value: ... }` object, so test this case */
101-
// TODO: add a check to see if the object has only one key
102-
// TODO: stop wrapping
103-
if ((request.result !== undefined)
112+
if (!this.compatibilityPriorToV8 && (request.result !== undefined) && (request.result !== null)) {
113+
114+
/* Cast to the wanted type */
115+
return request.result as T;
116+
117+
} else if (this.compatibilityPriorToV8
118+
&& (request.result !== undefined)
104119
&& (request.result !== null)
105-
&& (typeof request.result === 'object')
106-
&& (this.dataPath in request.result)
107120
&& (request.result[this.dataPath] !== undefined)
108121
&& (request.result[this.dataPath] !== null)) {
109122

110-
/* If so, unwrap the value and cast it to the wanted type */
123+
/* Prior to v8, the value was wrapped in an `{ value: ...}` object */
111124
return (request.result[this.dataPath] as T);
112125

113-
} else if ((request.result !== undefined) && (request.result !== null)) {
114-
115-
/* Otherwise, return the value directly, casted to the wanted type */
116-
return request.result as T;
117-
118126
}
119127

120128
/* Return `null` if the value is `null` or `undefined` */
@@ -165,11 +173,13 @@ export class IndexedDBDatabase implements LocalDatabase {
165173
* otherwise it could lead to concurrency failures
166174
* Avoid https://github.com/cyrilletuzi/angular-async-local-storage/issues/47 */
167175

176+
/* Prior to v8, data was wrapped in a `{ value: ... }` object */
177+
const dataToStore = !this.compatibilityPriorToV8 ? data : { [this.dataPath]: data };
178+
168179
/* Add if the item is not existing yet, or update otherwise */
169-
// TODO: stop wrapping
170180
const request2 = (existingEntry === undefined) ?
171-
store.add({ [this.dataPath]: data }, key) :
172-
store.put({ [this.dataPath]: data }, key);
181+
store.add(dataToStore, key) :
182+
store.put(dataToStore, key);
173183

174184
/* Manage success and error events, and map to `true` */
175185
return this.requestEventsAndMapTo(request2, () => true);

projects/ngx-pwa/local-storage/src/lib/databases/local-database.ts

+3-3
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { Injectable, PLATFORM_ID, Optional } from '@angular/core';
1+
import { Injectable, PLATFORM_ID } from '@angular/core';
22
import { isPlatformBrowser, isPlatformWorkerApp, isPlatformWorkerUi } from '@angular/common';
33
import { Observable } from 'rxjs';
44

@@ -13,7 +13,7 @@ import { PREFIX } from '../tokens';
1313
* @param prefix Optional user prefix to avoid collision for multiple apps on the same subdomain
1414
* @see https://github.com/cyrilletuzi/angular-async-local-storage/blob/master/docs/BROWSERS_SUPPORT.md
1515
*/
16-
export function localDatabaseFactory(platformId: Object, prefix: string | null): LocalDatabase {
16+
export function localDatabaseFactory(platformId: Object, prefix: string): LocalDatabase {
1717

1818
// Do not explicit `window` here, as the global object is not the same in web workers
1919
if ((isPlatformBrowser(platformId) || isPlatformWorkerApp(platformId) || isPlatformWorkerUi(platformId))
@@ -63,7 +63,7 @@ export function localDatabaseFactory(platformId: Object, prefix: string | null):
6363
useFactory: localDatabaseFactory,
6464
deps: [
6565
PLATFORM_ID,
66-
[new Optional(), PREFIX]
66+
PREFIX
6767
]
6868
})
6969
export abstract class LocalDatabase {

projects/ngx-pwa/local-storage/src/lib/databases/localstorage-database.ts

+6-7
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
1-
import { Injectable, Optional, Inject } from '@angular/core';
1+
import { Injectable, Inject } from '@angular/core';
22
import { Observable, of, throwError } from 'rxjs';
33

44
import { LocalDatabase } from './local-database';
5-
import { PREFIX } from '../tokens';
5+
import { PREFIX, DEFAULT_PREFIX } from '../tokens';
66

77
@Injectable({
88
providedIn: 'root'
@@ -12,7 +12,7 @@ export class LocalStorageDatabase implements LocalDatabase {
1212
/**
1313
* Optional user prefix to avoid collision for multiple apps on the same subdomain
1414
*/
15-
protected prefix = '';
15+
private readonly prefix: string;
1616

1717
/**
1818
* Number of items in `localStorage`
@@ -28,11 +28,10 @@ export class LocalStorageDatabase implements LocalDatabase {
2828
* Constructor params are provided by Angular (but can also be passed manually in tests)
2929
* @param prefix Optional user prefix to avoid collision for multiple apps on the same subdomain
3030
*/
31-
constructor(@Optional() @Inject(PREFIX) userPrefix: string | null = null) {
31+
constructor(@Inject(PREFIX) prefix = DEFAULT_PREFIX) {
3232

33-
if (userPrefix) {
34-
this.prefix = `${userPrefix}_`;
35-
}
33+
/* Add `_` after prefix only if not empty */
34+
this.prefix = prefix ? `${prefix}_` : prefix;
3635

3736
}
3837

projects/ngx-pwa/local-storage/src/lib/lib.service.spec.ts

+25-28
Original file line numberDiff line numberDiff line change
@@ -738,7 +738,7 @@ describe('IndexedDB', () => {
738738

739739
request.addEventListener('success', () => {
740740

741-
expect(request.result).toEqual({ value });
741+
expect(request.result).toEqual(value);
742742

743743
done();
744744

@@ -766,21 +766,32 @@ describe('IndexedDB', () => {
766766

767767
});
768768

769+
describe('IndexedDB with compatibilityPriorToV8', () => {
770+
771+
const localStorageService = new LocalStorage(new IndexedDBDatabase(undefined, undefined, undefined, true), new JSONValidator());
772+
773+
beforeEach((done) => {
774+
775+
/* Clear `localStorage` for some browsers private mode which fallbacks to `localStorage` */
776+
localStorage.clear();
777+
778+
clearIndexedDB(done);
779+
780+
});
781+
782+
tests(localStorageService);
783+
784+
});
785+
769786
describe('localStorage and a prefix', () => {
770787

771788
const prefix = 'myapp';
772789

773790
it('check prefix', () => {
774791

775-
class LocalStorageDatabasePrefix extends LocalStorageDatabase {
776-
getPrefix() {
777-
return this.prefix;
778-
}
779-
}
780-
781-
const localStorageServicePrefix = new LocalStorageDatabasePrefix(prefix);
792+
const localStorageServicePrefix = new LocalStorageDatabase(prefix);
782793

783-
expect(localStorageServicePrefix.getPrefix()).toBe(`${prefix}_`);
794+
expect(localStorageServicePrefix['prefix']).toBe(`${prefix}_`);
784795

785796
});
786797

@@ -800,33 +811,19 @@ describe('IndexedDB and a prefix', () => {
800811

801812
it('check prefix', () => {
802813

803-
class IndexedDBDatabasePrefix extends IndexedDBDatabase {
804-
getDbBame() {
805-
return this.dbName;
806-
}
807-
}
814+
const indexedDBService = new IndexedDBDatabase(prefix);
808815

809-
const indexedDBService = new IndexedDBDatabasePrefix(prefix);
810-
811-
expect(indexedDBService.getDbBame()).toBe(`${prefix}_${DEFAULT_IDB_DB_NAME}`);
816+
expect(indexedDBService['dbName']).toBe(`${prefix}_${DEFAULT_IDB_DB_NAME}`);
812817

813818
});
814819

815820
it('check prefix with custom database and store names', () => {
816821

817822
const dbName = 'customDb';
818823

819-
class IndexedDBDatabasePrefix extends IndexedDBDatabase {
820-
821-
getDbBame() {
822-
return this.dbName;
823-
}
824-
825-
}
826-
827-
const indexedDBService = new IndexedDBDatabasePrefix(prefix, dbName);
824+
const indexedDBService = new IndexedDBDatabase(prefix, dbName);
828825

829-
expect(indexedDBService.getDbBame()).toBe(`${prefix}_${dbName}`);
826+
expect(indexedDBService['dbName']).toBe(`${prefix}_${dbName}`);
830827

831828
});
832829

@@ -850,7 +847,7 @@ describe('IndexedDB with custom database and store names', () => {
850847
const dbName = 'dBcustom';
851848
const storeName = 'storeCustom';
852849

853-
const localStorageService = new LocalStorage(new IndexedDBDatabase(null, dbName, storeName), new JSONValidator());
850+
const localStorageService = new LocalStorage(new IndexedDBDatabase(undefined, dbName, storeName), new JSONValidator());
854851

855852
beforeEach((done) => {
856853

projects/ngx-pwa/local-storage/src/lib/lib.service.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { Injectable, Optional, Inject } from '@angular/core';
1+
import { Injectable, Inject } from '@angular/core';
22
import { Observable, throwError, of, OperatorFunction } from 'rxjs';
33
import { mergeMap, catchError } from 'rxjs/operators';
44

@@ -49,7 +49,7 @@ export class LocalStorage {
4949
constructor(
5050
private database: LocalDatabase,
5151
private jsonValidator: JSONValidator,
52-
@Optional() @Inject(PREFIX) protected prefix: string | null = null,
52+
@Inject(PREFIX) private prefix = '',
5353
) {}
5454

5555
/**

projects/ngx-pwa/local-storage/src/lib/tokens.ts

+34-11
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,18 @@
11
import { InjectionToken, Provider } from '@angular/core';
22

33
/**
4-
* Internal. Use the `localStorageProviders()` helper function to provide options.
4+
* Default prefix.
5+
* *Use only to avoid conflict in multiple apps on the same subdomain.*
56
*/
6-
export const PREFIX = new InjectionToken<string>('localStoragePrefix', {
7-
providedIn: 'root',
8-
factory: () => ''
9-
});
7+
export const DEFAULT_PREFIX = '';
108

119
/**
12-
* @deprecated Use the `localStorageProviders()` helper function to provide options. Will be removed in v9.
10+
* Token to provide a prefix to avoid collision when multiple apps on the same subdomain.
1311
*/
14-
export const LOCAL_STORAGE_PREFIX = PREFIX;
12+
export const PREFIX = new InjectionToken<string>('localStoragePrefix', {
13+
providedIn: 'root',
14+
factory: () => DEFAULT_PREFIX
15+
});
1516

1617
/**
1718
* Default name used for `indexedDB` database.
@@ -20,9 +21,9 @@ export const LOCAL_STORAGE_PREFIX = PREFIX;
2021
export const DEFAULT_IDB_DB_NAME = 'ngStorage';
2122

2223
/**
23-
* Internal. Use the `localStorageProviders()` helper function to provide options.
24+
* Token to provide `indexedDB` database name.
2425
*/
25-
export const IDB_DB_NAME = new InjectionToken<string>('localStorageIndexedDbName', {
26+
export const IDB_DB_NAME = new InjectionToken<string>('localStorageIDBDBName', {
2627
providedIn: 'root',
2728
factory: () => DEFAULT_IDB_DB_NAME
2829
});
@@ -34,13 +35,27 @@ export const IDB_DB_NAME = new InjectionToken<string>('localStorageIndexedDbName
3435
export const DEFAULT_IDB_STORE_NAME = 'localStorage';
3536

3637
/**
37-
* Internal. Use the `localStorageProviders()` helper function to provide options.
38+
* Token to provide `indexedDB` store name.
3839
*/
39-
export const IDB_STORE_NAME = new InjectionToken<string>('localStorageIndexedDbStoreName', {
40+
export const IDB_STORE_NAME = new InjectionToken<string>('localStorageIDBStoreName', {
4041
providedIn: 'root',
4142
factory: () => DEFAULT_IDB_STORE_NAME
4243
});
4344

45+
// TODO: revert to true by default if ng update is not possible
46+
/**
47+
* Default compatibility mode.
48+
*/
49+
export const DEFAULT_COMPATIBILITY_PRIOR_TO_V8 = false;
50+
51+
/**
52+
* Token to keep storing behavior prior to version 8.
53+
*/
54+
export const COMPATIBILITY_PRIOR_TO_V8 = new InjectionToken<boolean>('localStorageCompatibilityPriorToV8', {
55+
providedIn: 'root',
56+
factory: () => DEFAULT_COMPATIBILITY_PRIOR_TO_V8
57+
});
58+
4459
export interface LocalStorageProvidersConfig {
4560

4661
/**
@@ -64,6 +79,13 @@ export interface LocalStorageProvidersConfig {
6479
*/
6580
IDBStoreName?: string;
6681

82+
/**
83+
* Flag to keep storing behavior prior to version 8.
84+
* Not needed for new installs,
85+
* **must be `true` for upgrades from versions prior to V8, otherwise previously stored data will be lost.**
86+
*/
87+
compatibilityPriorToV8?: boolean;
88+
6789
}
6890

6991
/**
@@ -77,6 +99,7 @@ export function localStorageProviders(config: LocalStorageProvidersConfig): Prov
7799
config.prefix ? { provide: PREFIX, useValue: config.prefix } : [],
78100
config.IDBDBName ? { provide: IDB_DB_NAME, useValue: config.IDBDBName } : [],
79101
config.IDBStoreName ? { provide: IDB_STORE_NAME, useValue: config.IDBStoreName } : [],
102+
config.compatibilityPriorToV8 ? { provide: COMPATIBILITY_PRIOR_TO_V8, useValue: config.compatibilityPriorToV8 } : [],
80103
];
81104

82105
}

0 commit comments

Comments
 (0)