Introducing FireSageJS, Ultimate Type Safety for Firebase Realtime Database

tylim88

Acid Coder

Posted on November 15, 2022

Introducing FireSageJS, Ultimate Type Safety for Firebase Realtime Database

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 errors:

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> 
}>
Enter fullscreen mode Exit fullscreen mode

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() // or getDatabase(app)

export const exampleRef = createRef<Example>(db) // this is your firesage ref
Enter fullscreen mode Exit fullscreen mode

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 } })
}
Enter fullscreen mode Exit fullscreen mode

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)
        )
    )
}
Enter fullscreen mode Exit fullscreen mode

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 } 
)
Enter fullscreen mode Exit fullscreen mode

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() 
}
Enter fullscreen mode Exit fullscreen mode

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()
}
Enter fullscreen mode Exit fullscreen mode

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

💖 💪 🙅 🚩
tylim88
Acid Coder

Posted on November 15, 2022

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related