JSPM

  • ESM via JSPM
  • ES Module Entrypoint
  • Export Map
  • Keywords
  • License
  • Repository URL
  • TypeScript Types
  • README
  • Created
  • Published
  • Downloads 59
  • Score
    100M100P100Q100388F
  • License MIT

rxjs firebase utilities

Package Exports

  • @valian/rxjs-firebase

Readme

@valian/rxjs-firebase

RxJS operators and utilities for Firebase with real-time updates and TypeScript support

npm version License: MIT

Description

@valian/rxjs-firebase is a lightweight RxJS library that provides seamless integration with Firebase services. It offers real-time data synchronization, TypeScript support, and comprehensive state management for your RxJS-based applications.

Key Features

  • 🔄 Real-time updates - Automatic synchronization with Firebase using onSnapshot
  • 📘 Full TypeScript support - Type-safe operators and utilities with generic type parameters
  • 🎯 Simple API - Easy-to-use operators that handle loading, error, and data states
  • Lightweight - Minimal bundle size with zero dependencies
  • 🛡️ Error handling - Built-in error management with optional custom error handlers
  • 🔧 Flexible - Works with any Firebase query, document reference, or auth state
  • 📊 State management - Comprehensive state objects with loading, error, and data states

Getting Started

Installation

pnpm add @valian/rxjs-firebase

Prerequisites

This library requires the following peer dependencies:

  • rxjs ^7 || ^8
  • firebase ^11 || ^12

documentSnapshotState and querySnapshotState

These are the primary operators that map Firestore snapshots into well-typed state objects.

documentSnapshotState

import { doc } from 'firebase/firestore'
import { fromDocumentRef, documentSnapshotState } from '@valian/rxjs-firebase'

const userRef = doc(db, 'users', userId)
const userState$ = fromDocumentRef(userRef).pipe(
  documentSnapshotState({
    onSnapshot: (state) => console.log('exists', state.exists),
    onError: (error) => console.error(error),
  }),
)

With an immediate loading emission:

import { fromDocumentRef, documentSnapshotState, startWithDocumentSnapshotLoadingState } from '@valian/rxjs-firebase'

const userState$ = fromDocumentRef(userRef).pipe(documentSnapshotState(), startWithDocumentSnapshotLoadingState())

This is useful when you want subscribers to get an initial { isLoading: true } state synchronously before Firestore returns the first snapshot.

querySnapshotState

import { collection, query, where } from 'firebase/firestore'
import { fromQuery, querySnapshotState } from '@valian/rxjs-firebase'

const todosQuery = query(collection(db, 'todos'), where('userId', '==', userId))
const todosState$ = fromQuery(todosQuery).pipe(
  querySnapshotState({
    onSnapshot: (state) => console.log('size', state.size),
  }),
)

With an immediate loading emission:

import { fromQuery, querySnapshotState, startWithQuerySnapshotLoadingState } from '@valian/rxjs-firebase'

const todosState$ = fromQuery(todosQuery).pipe(querySnapshotState(), startWithQuerySnapshotLoadingState())

When queries or refs can be null/undefined at times, prefer the documentSnapshot / querySnapshot operators below.


Source functions

fromDocumentRef

Creates an observable of DocumentSnapshot.

import { doc } from 'firebase/firestore'
import { fromDocumentRef } from '@valian/rxjs-firebase'

const user$ = fromDocumentRef(doc(db, 'users', 'user123'))

fromQuery

Creates an observable of QuerySnapshot.

import { collection, orderBy, query, where } from 'firebase/firestore'
import { fromQuery } from '@valian/rxjs-firebase'

const base = query(collection(db, 'todos'))
const todos$ = fromQuery(base)

const activeTodos$ = fromQuery(
  query(collection(db, 'todos'), where('completed', '==', false), orderBy('createdAt', 'desc')),
)

authState

Creates an observable of Firebase Auth user.

import { authState } from '@valian/rxjs-firebase'

const auth$ = authState()

Subjects

DocumentSnapshotSubject

BehaviorSubject that tracks a document state.

import { doc } from 'firebase/firestore'
import { DocumentSnapshotSubject } from '@valian/rxjs-firebase'

const subject = DocumentSnapshotSubject.fromDocumentRef(doc(db, 'users', 'user123'))

const sub = subject.subscribe((state) => {
  if (state.isLoading) return
  if (!state.exists) return console.log('Not found')
  console.log(state.data)
})

sub.unsubscribe()
subject.complete()

QuerySnapshotSubject

BehaviorSubject that tracks a query state.

import { collection, query } from 'firebase/firestore'
import { QuerySnapshotSubject } from '@valian/rxjs-firebase'

const subject = QuerySnapshotSubject.fromQuery(query(collection(db, 'todos')))

const sub = subject.subscribe((state) => {
  if (state.isLoading) return
  console.log(state.size, state.data)
})

sub.unsubscribe()
subject.complete()

Nullable-aware operators and async helpers

documentSnapshot

Takes a stream of DocumentReference | null | undefined and emits a DocumentSnapshotState, yielding a disabled state when the ref is null/undefined.

import { of } from 'rxjs'
import { documentSnapshot } from '@valian/rxjs-firebase'

of(userRef /* or null */)
  .pipe(documentSnapshot())
  .subscribe((state) => {
    if (state.disabled) return
    if (!state.exists) return
    console.log(state.data)
  })

querySnapshot

Takes a stream of Query | null | undefined and emits a QuerySnapshotState, yielding a disabled state when the query is null/undefined.

import { of } from 'rxjs'
import { querySnapshot } from '@valian/rxjs-firebase'

of(todosQuery /* or null */)
  .pipe(querySnapshot())
  .subscribe((state) => {
    if (state.disabled) return
    console.log(state.size)
  })

documentExists

Wait until a document exists or time out.

import { doc } from 'firebase/firestore'
import { documentExists, documentSnapshotState, fromDocumentRef } from '@valian/rxjs-firebase'

const exists = await documentExists(fromDocumentRef(doc(db, 'users', userId)).pipe(documentSnapshotState()), 5_000)

waitForData

Await the first non-loading, non-disabled state for either document or query streams.

import { waitForData } from '@valian/rxjs-firebase'

const ready = await waitForData(state$)

Advanced Usage

Dynamic Queries

import { combineLatest, switchMap } from 'rxjs'
import { fromQuery, querySnapshotState } from '@valian/rxjs-firebase'

function createFilteredTodos(userId$: Observable<string>, completed$: Observable<boolean>) {
  return combineLatest([userId$, completed$]).pipe(
    switchMap(([userId, completed]) => {
      const q = query(collection(db, 'todos'), where('userId', '==', userId), where('completed', '==', completed))
      return fromQuery(q).pipe(querySnapshotState())
    }),
  )
}

Combining Multiple Observables

import { combineLatest } from 'rxjs'
import { map } from 'rxjs/operators'
import { fromDocumentRef, fromQuery, documentSnapshotState, querySnapshotState } from '@valian/rxjs-firebase'

function getUserWithTodos(userId: string) {
  const user$ = fromDocumentRef(doc(db, 'users', userId)).pipe(documentSnapshotState<User>())
  const todos$ = fromQuery(query(collection(db, 'todos'), where('userId', '==', userId))).pipe(
    querySnapshotState<Todo>(),
  )

  return combineLatest([user$, todos$]).pipe(
    map(([userState, todosState]) => ({
      user: userState.data,
      todos: todosState.data,
      isLoading: userState.isLoading || todosState.isLoading,
      hasError: userState.hasError || todosState.hasError,
    })),
  )
}

License

MIT © Valian

Contributing

Contributions are welcome! Please feel free to submit a Pull Request.

Support

If you encounter any issues or have questions, please open an issue on GitHub.