Introducing FireSageJS, Ultimate Type Safety for Firebase Realtime Database

Acid Coder
3 min readNov 15, 2022
FireSageJS

I made a library that solves Firebase Realtime Database typing problems.

It is the sister project of my other project, FirelordJS (for Firestore)

And similar to FirelordJS, FireSageJS comes with an extreme type safety mechanism without straying from the official library API.

You get the best of both worlds: low learning curve and safest types.

One image to convince you, runtime errors becomes compile time error:

FireSageJS Example

If we use the equalTo cursor with other cursors such as endBefore, the Firebase SDK throws an error on runtime. With FireSageJS, we know this will happen the moment we type the code.

Quick start:

Define the Meta Type

import {
MetaTypeCreator,
ServerTimestamp,
PushAbleOnly,
NumericKeyRecord,
Removable,
} from 'firesagejs'

export type Example = MetaTypeCreator<{
a: 1 | 90 | 3700
b:
| {
c: boolean | Removable
d: {
e: ServerTimestamp
}
}
| Removable
f: Record<string, 'a' | 'b' | 'c'>
g: PushAbleOnly<{ h: number; j: { k: boolean } }>
i: NumericKeyRecord<string>
}>

Create Ref

import { Example } from './defineMetaType'
import { initializeApp } from 'firebase/app'
import { getDatabase, createRef } from 'firesagejs'

const app = initializeApp({
projectId: '### PROJECT ID ###',
})

export const db = getDatabase()

export const exampleRef = createRef<Example>(db)

Operation

import { exampleRef } from './createRef'
import {
set,
get,
update,
serverTimestamp,
remove,
push,
increment,
} from 'firesagejs'

;async () => {
await set(exampleRef('a'), 1)
await update(exampleRef(), ['b/c', 'b/d/e'], [true, serverTimestamp()])
const snapshot = await get(exampleRef('f'))
const val = snapshot.val()
const exists = snapshot.exists()
const size = snapshot.size
const hasChild = snapshot.hasChild('k')
const hasChildren = snapshot.hasChildren()
const json = snapshot.toJSON()
snapshot.forEach((child, index) => {
//
})
await remove(exampleRef('b/c'))
await push(exampleRef('g'), { h: increment(1), j: { k: true } })
}

Query

import { exampleRef } from './createRef'
import {
get,
orderByChild,
orderByKey,
orderByValue,
startAt,
startAfter,
endAt,
endBefore,
equalTo,
limitToFirst,
limitToLast,
query,
} from 'firesagejs'

;async () => {
const snapshot = await get(
query(
exampleRef('i'),
orderByValue(),
startAt('abc', '1'),
limitToFirst(4)
)
)
const snapshot2 = await get(
query(
exampleRef('f'),
orderByKey(),
endAt('abc'),
limitToLast(2)
)
)
const snapshot3 = await get(
query(
exampleRef('g'),
orderByChild('j/k'),
equalTo(false, 'a1b2c3'),
limitToLast(2)
)
)
}

Listener

import { exampleRef } from './createRef'
import {
orderByKey,
orderByValue,
startAt,
endAt,
limitToFirst,
limitToLast,
query,
onChildAdded,
onChildChanged,
onChildRemoved,
onChildMoved,
onValue,
} from 'firesagejs'

const unsub = onChildAdded(
query(exampleRef('i'), orderByValue(), startAt('abc', '1'), limitToFirst(4)),
snapshot => {}
)

const unsub2 = onChildChanged(
exampleRef('g'),
snapshot => {},
error => {}
)

const unsub3 = onChildRemoved(
query(exampleRef('f'), orderByKey(), endAt('abc'), limitToLast(2)),
snapshot => {},
{ onlyOnce: false }
)

const unsub4 = onChildMoved(
exampleRef('f'),
snapshot => {},
error => {},
{ onlyOnce: false }
)

const unsub6 = onValue(
exampleRef('b/d/e'),
snapshot => {},
error => {},
{ onlyOnce: false }
)

Transaction

import { exampleRef } from './createRef'
import { runTransaction } from 'firesagejs'

;async () => {
const result = await runTransaction(
exampleRef('g/a1b2c3'),
data => {
return { h: 123, j: { k: false } }
},
{ applyLocally: true }
)
const committed = result.committed
const snapshot = result.snapshot
const json = result.toJSON()
}

OnDisconnect

import { exampleRef } from './createRef'
import { serverTimestamp, onDisconnect } from 'firesagejs'

;async () => {
const onDc = onDisconnect(exampleRef('b'))
await onDc.set({ c: false, d: { e: serverTimestamp() } })
await onDc.update(['c', 'd'], [false, { e: serverTimestamp() }])
await onDc.remove()
await onDc.cancel()
}

There are a lot of things going on in this library, please read the documentation for more details

Long thing short, if you are looking for absolute RTDB type safety, this is it

Nothing else can offer the same level of type safety (because FireSageJS is the only RTDB type safe wrapper in existence)

Github

--

--

Acid Coder

Typescript Zombie. Youtube Pikachu On Acid. (Unrelated to programming but by watching it you become a good developer overnight)