Introducing FireSageJS, Ultimate Type Safety for Firebase Realtime Database
Tags: webdev, typescript, firebase, programming
Published: 2022-11-15 16:24:48

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](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/i711fszr8n90h3lk2tbe.png)

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

```ts
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
```ts
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
```

## Operation

```ts
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

```ts
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

```ts
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

```ts
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

```ts
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](https://firelordjs.com/firesage/quick_start) 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](https://github.com/tylim88/FireSageJS)