Created
September 19, 2019 00:16
-
-
Save alizbazar/0a8712ecf978ed1ee488e839f0d82402 to your computer and use it in GitHub Desktop.
Firebase real-time database mock: great to use with offline tests etc.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import _, { Dictionary, isNil, pickBy, mapValues } from 'lodash' | |
const { database: firebase } = require('firebase-admin') | |
export const FIREBASE_TIMESTAMP = firebase.ServerValue.TIMESTAMP | |
let cache = {} | |
const convertEmptyToNull = (obj: any): any => { | |
if (isNil(obj)) { | |
return null | |
} | |
if (typeof obj !== 'object') { | |
return obj | |
} | |
if (obj instanceof Array) { | |
let newObj = obj.map(convertEmptyToNull) | |
newObj = obj.filter(val => val !== null) | |
return newObj.length ? newObj : null | |
} | |
let newObj = mapValues(obj, convertEmptyToNull) | |
newObj = pickBy(newObj, val => val !== null) | |
return Object.keys(newObj).length !== 0 ? newObj : null | |
} | |
const convertTimestamps = (val: any): any => { | |
if (val instanceof Array) { | |
return val.map(innerVal => convertTimestamps(innerVal)) | |
} | |
if (typeof val === 'object' && val !== null) { | |
if (_.isEqual(val, FIREBASE_TIMESTAMP)) { | |
return Date.now() | |
} | |
return _.mapValues(val, innerVal => convertTimestamps(innerVal)) | |
} | |
return val | |
} | |
class Snapshot { | |
key: string | |
value: any | |
constructor(path: string, value: any) { | |
this.key = _.last(path.split('/'))! | |
this.value = convertEmptyToNull(value) | |
} | |
val() { | |
return this.value | |
} | |
exists() { | |
return this.value !== null | |
} | |
} | |
class Query { | |
path: string | |
constructor(path = '') { | |
const pathTrimmed = _.trim(path, '/') | |
this.path = pathTrimmed | |
} | |
private getVal() { | |
const val = _.get(cache, this.path.split('/')) | |
return val === undefined ? null : val | |
} | |
private setVal(val: any, subpath?: string) { | |
const path = this.path.split('/') | |
if (subpath) { | |
const subpathTrimmed = _.trim(subpath, '/') | |
path.push(...subpathTrimmed.split('/')) | |
} | |
let cleanedVal = convertEmptyToNull(val) | |
cleanedVal = convertTimestamps(cleanedVal) | |
_.setWith(cache, path, cleanedVal) | |
} | |
child(subpath: string) { | |
const pathTrimmed = _.trim(subpath, '/') | |
return new Query(`${this.path}/${pathTrimmed}`) | |
} | |
once() { | |
return Promise.resolve(new Snapshot(this.path, this.getVal())) | |
} | |
remove() { | |
cache = {} | |
return Promise.resolve() | |
} | |
orderByChild() { | |
return this | |
} | |
orderByKey() { | |
return this | |
} | |
limitToLast() { | |
return this | |
} | |
limitToFirst() { | |
return this | |
} | |
transaction(func: Function) { | |
const previousValue = this.getVal() | |
const result = func(previousValue) | |
const committed = result !== undefined | |
if (committed) { | |
this.setVal(result) | |
} | |
return Promise.resolve({ | |
committed, | |
snapshot: new Snapshot(this.path, committed ? result : previousValue), | |
}) | |
} | |
set(val: any) { | |
this.setVal(val) | |
return Promise.resolve() | |
} | |
push(val: any) { | |
this.setVal(val, Math.random().toString()) | |
return Promise.resolve() | |
} | |
update(writes: Dictionary<any>) { | |
for (const subpath in writes) { | |
this.setVal(writes[subpath], subpath) | |
} | |
return Promise.resolve() | |
} | |
} | |
class Database { | |
ref(path = '') { | |
return new Query(path) | |
} | |
} | |
export const database = () => new Database() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment