Skip to content

Commit a9cc40d

Browse files
fix(firebase): rtdb improvements
1 parent 08e1e99 commit a9cc40d

File tree

8 files changed

+122
-44
lines changed

8 files changed

+122
-44
lines changed

package-lock.json

Lines changed: 10 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,7 @@
7272
"@types/express": "^4.16.0",
7373
"@types/jest": "^23.1.5",
7474
"@types/node": "^10.5.2",
75+
"@types/object-hash": "^1.2.0",
7576
"ajv": "^6.5.2",
7677
"angularfire2": "^5.0.0-rc.11",
7778
"chalk": "^2.4.1",
@@ -92,6 +93,7 @@
9293
"ng2-fused": "^0.5.1",
9394
"node-sass": "^4.9.2",
9495
"npm": "^6.1.0",
96+
"object-hash": "^1.3.0",
9597
"pug": "^2.0.3",
9698
"reload": "^2.3.0",
9799
"rxjs": "^6.2.1",

src/modules/firebase/browser.firebase.rtdb.service.ts

Lines changed: 29 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,18 @@
1-
import { catchError, filter, map, startWith, take } from 'rxjs/operators'
1+
import {
2+
catchError,
3+
filter,
4+
map,
5+
startWith,
6+
take,
7+
distinctUntilChanged
8+
} from 'rxjs/operators'
29
import { ApplicationRef, Injectable, Inject } from '@angular/core'
3-
import { AngularFireDatabase } from 'angularfire2/database'
10+
import { AngularFireDatabase, QueryFn } from 'angularfire2/database'
411
import { makeStateKey, TransferState } from '@angular/platform-browser'
512
import { Observable, of } from 'rxjs'
6-
import { FIREBASE_DATABASE_URL } from './server.firebase.module'
713
import { IUniversalRtdbService } from './rtdb.interface'
8-
// import { sha1 } from 'object-hash'
9-
10-
interface CachedHttp<T> {
11-
readonly body: T
12-
}
14+
import { FIREBASE_DATABASE_URL } from './tokens'
15+
import { sha1 } from 'object-hash'
1316

1417
// tslint:disable:no-this
1518
// tslint:disable-next-line:no-class
@@ -38,10 +41,7 @@ export class UniversalRtDbService implements IUniversalRtdbService {
3841
}
3942

4043
universalObject<T>(path: string): Observable<T | undefined> {
41-
const cached = this.ts.get<CachedHttp<T> | undefined>(
42-
this.cacheKey(path),
43-
undefined
44-
)
44+
const cached = this.ts.get<T | undefined>(this.cacheKey(path), undefined)
4545
const base = this.angularFireDatabase
4646
.object<T>(path)
4747
.valueChanges()
@@ -53,12 +53,26 @@ export class UniversalRtDbService implements IUniversalRtdbService {
5353
return !this.readFromCache
5454
? base
5555
: base.pipe(
56-
startWith(cached && cached.body)
57-
// distinctUntilChanged((x, y) => (x && sha1(x)) === (y && sha1(y)))
56+
startWith(cached),
57+
distinctUntilChanged((x, y) => (x && sha1(x)) === (y && sha1(y)))
58+
)
59+
}
60+
61+
universalList<T>(
62+
path: string,
63+
queryFn?: QueryFn
64+
): Observable<ReadonlyArray<T>> {
65+
const cached = this.ts.get<ReadonlyArray<T>>(this.cacheKey(path), [])
66+
const base = this.angularFireDatabase.list<T>(path, queryFn).valueChanges()
67+
return this.readFromCache
68+
? base.pipe(
69+
startWith(cached),
70+
distinctUntilChanged((x, y) => (x && sha1(x)) === (y && sha1(y)))
5871
)
72+
: base
5973
}
6074

6175
private cacheKey(path: string) {
62-
return makeStateKey<string>(`G.${this.firebaseDatabaseUrl}/${path}.json`)
76+
return makeStateKey<string>(`RTDB.${this.firebaseDatabaseUrl}/${path}.json`)
6377
}
6478
}

src/modules/firebase/common.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { makeStateKey } from '@angular/platform-browser'
22

33
export function makeRtDbStateTransferKey(fullUrl: string) {
4-
return makeStateKey<string>(`G.${fullUrl}`)
4+
return makeStateKey<string>(`RTDB.${fullUrl}`)
55
}
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,10 @@
11
import { Observable } from 'rxjs'
2+
import { QueryFn } from 'angularfire2/database'
23

34
export interface IUniversalRtdbService {
45
readonly universalObject: <T>(path: string) => Observable<T | undefined>
6+
readonly universalList: <T>(
7+
path: string,
8+
queryFn?: QueryFn
9+
) => Observable<ReadonlyArray<T>>
510
}

src/modules/firebase/server.firebase.module.ts

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -13,11 +13,6 @@ export interface LruCache {
1313
readonly set: <T>(key: string, value: T) => T
1414
}
1515

16-
export const FIREBASE_USER_AUTH_TOKEN = new InjectionToken<string>(
17-
'fng.fb.svr.usr.auth'
18-
)
19-
20-
export const FIREBASE_DATABASE_URL = new InjectionToken<string>('fng.fb.db.url')
2116
export const LRU_CACHE = new InjectionToken<LruCache>('fng.lru')
2217

2318
// tslint:disable-next-line:no-class

src/modules/firebase/server.firebase.rtdb.service.ts

Lines changed: 72 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,22 @@
11
import { catchError, take, tap, map } from 'rxjs/operators'
2-
import { AngularFireDatabase } from 'angularfire2/database'
3-
import { Inject, Injectable, Optional } from '@angular/core'
4-
import { HttpClient, HttpParams, HttpResponse } from '@angular/common/http'
52
import {
6-
FIREBASE_USER_AUTH_TOKEN,
7-
LRU_CACHE,
8-
LruCache
9-
} from './server.firebase.module'
3+
AngularFireDatabase,
4+
QueryFn,
5+
PathReference
6+
} from 'angularfire2/database'
7+
import { Inject, Injectable, InjectionToken, Optional } from '@angular/core'
8+
import { HttpClient, HttpParams, HttpResponse } from '@angular/common/http'
9+
import { LRU_CACHE, LruCache } from './server.firebase.module'
1010
import { Observable, of } from 'rxjs'
1111
import { IUniversalRtdbService } from './rtdb.interface'
1212
import { createHash } from 'crypto'
1313
import { TransferState, StateKey } from '@angular/platform-browser'
1414
import { makeRtDbStateTransferKey } from './common'
15+
import { makeStateKey } from '@angular/platform-browser'
16+
17+
export const FIREBASE_USER_AUTH_TOKEN = new InjectionToken<string>(
18+
'fng.fb.svr.usr.auth'
19+
)
1520

1621
function sha256(data: string) {
1722
return createHash('sha256')
@@ -29,39 +34,49 @@ function getFullUrl(base: string, params: HttpParams) {
2934
return stringifiedParams ? `${base}?${params.toString()}` : base
3035
}
3136

32-
function getParams(fromObject = {}) {
37+
function getParams(fromObject = {} as any) {
3338
return new HttpParams({
34-
fromObject
39+
fromObject: Object.keys(fromObject).reduce((acc, curr) => {
40+
return fromObject[curr]
41+
? {
42+
...acc,
43+
[curr]: fromObject[curr]
44+
}
45+
: { ...acc }
46+
}, {})
3547
})
3648
}
3749

3850
function mapUndefined(err: any) {
3951
return of(undefined)
4052
}
4153

54+
function mapEmptyList<T>(err: any) {
55+
return of([] as ReadonlyArray<T>)
56+
}
57+
4258
function attemptToCacheInLru(key: string, lru?: LruCache) {
4359
return function(response?: any) {
4460
lru && response && lru.set(sha256(key), response)
4561
}
4662
}
4763

48-
function httpResponseToValue<T>(value: HttpResponse<T>): T | undefined {
49-
return (value && value.body) || undefined
50-
}
51-
5264
function attemptToGetCachedValue<T>(key: string, lru?: LruCache) {
5365
return lru && lru.get<T>(sha256(key))
5466
}
5567

56-
function writeLruCacheToTransferState<T>(
57-
ts: TransferState,
58-
key: StateKey<string>
59-
) {
68+
function cacheInStateTransfer<T>(ts: TransferState, key: StateKey<string>) {
6069
return function(val: T) {
6170
ts.set(key, val)
6271
}
6372
}
6473

74+
function removeHttpInterceptorCache<T>(ts: TransferState, key: string) {
75+
return function(val: T) {
76+
ts.remove(makeStateKey<string>(`G.${key}`))
77+
}
78+
}
79+
6580
// tslint:disable:no-this
6681
// tslint:disable-next-line:no-class
6782
@Injectable()
@@ -84,18 +99,52 @@ export class ServerUniversalRtDbService implements IUniversalRtdbService {
8499
const cacheKey = getFullUrl(url, params)
85100
const cachedValue = attemptToGetCachedValue<T>(cacheKey, this.lru)
86101
const tsKey = makeRtDbStateTransferKey(url)
87-
88-
const baseObs = this.authToken
89-
? this.http.get<HttpResponse<T>>(url, { params })
90-
: this.http.get<HttpResponse<T>>(url)
102+
const baseObs = this.http.get<HttpResponse<T>>(url, { params })
91103

92104
return cachedValue
93-
? of(cachedValue).pipe(tap(writeLruCacheToTransferState(this.ts, tsKey)))
105+
? of(cachedValue).pipe(tap(cacheInStateTransfer(this.ts, tsKey)))
94106
: baseObs.pipe(
95107
take(1),
96-
map(httpResponseToValue),
108+
tap(removeHttpInterceptorCache(this.ts, cacheKey)),
109+
tap(cacheInStateTransfer(this.ts, tsKey)),
97110
tap(attemptToCacheInLru(cacheKey, this.lru)),
98111
catchError(mapUndefined)
99112
)
100113
}
114+
115+
universalList<T>(
116+
path: PathReference,
117+
queryFn?: QueryFn
118+
): Observable<ReadonlyArray<T>> {
119+
const query =
120+
(queryFn && queryFn(this.afdb.database.ref(path.toString()))) ||
121+
this.afdb.database.ref(path.toString())
122+
const internalQueryParams = (query as any).queryParams_
123+
const paramsFromString = internalQueryParams.toRestQueryStringParameters()
124+
const url = `${query.toString()}.json`
125+
const params = getParams({ ...paramsFromString, auth: this.authToken })
126+
const cacheKey = getFullUrl(url, params)
127+
const tsKey = makeRtDbStateTransferKey(url)
128+
const baseObs = this.http.get<ReadonlyArray<T>>(url, { params })
129+
const cachedValue = attemptToGetCachedValue<ReadonlyArray<T>>(
130+
cacheKey,
131+
this.lru
132+
)
133+
134+
return cachedValue
135+
? of(cachedValue).pipe(tap(cacheInStateTransfer(this.ts, tsKey)))
136+
: baseObs.pipe(
137+
take(1),
138+
tap(removeHttpInterceptorCache(this.ts, url)),
139+
map((val: any) => {
140+
return Array.isArray(val)
141+
? val.filter(Boolean)
142+
: Object.keys(val).map(key => val[key])
143+
}),
144+
tap(cacheInStateTransfer(this.ts, tsKey)),
145+
tap(attemptToCacheInLru(cacheKey, this.lru)),
146+
147+
catchError(mapEmptyList)
148+
)
149+
}
101150
}

src/modules/firebase/tokens.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
import { InjectionToken } from '@angular/core'
2+
3+
export const FIREBASE_DATABASE_URL = new InjectionToken<string>('fng.fb.db.url')

0 commit comments

Comments
 (0)