Skip to content

Instantly share code, notes, and snippets.

@alizbazar
Created September 19, 2019 00:16
Show Gist options
  • Save alizbazar/0a8712ecf978ed1ee488e839f0d82402 to your computer and use it in GitHub Desktop.
Save alizbazar/0a8712ecf978ed1ee488e839f0d82402 to your computer and use it in GitHub Desktop.
Firebase real-time database mock: great to use with offline tests etc.
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