@megasquid/firestore-fetch
v0.1.5
Published
A powerful, observable-based Firestore data fetcher for web apps using the official Firebase JavaScript SDK.
Readme
@megasquid/firestore-fetch
A powerful, observable-based Firestore data fetcher for web apps using the official Firebase JavaScript SDK.
@megasquid/firestore-fetch provides a reactive and highly flexible way to query and compose data from Firestore. It focuses on reads: streaming documents and collections, composing multi-source fetches (including subcollections and reference resolution), and automatically injecting lightweight _meta information into every document.
Key differentiators:
- Observable-first API built on RxJS (streams for
doc$,col$,colGroup$, and the advancedfetch()builder). - Declarative
FetchConfigto describe complex data shapes (multiple docs, collections, subcollections, and properties) in one call. - Automatic
_metaaugmentation (path, id, segments, timestamps) so downstream code always has identity and path info. - Designed for the standard
firebase/firestoreJavaScript SDK (modular or namespaced use).
Installation
npm install @megasquid/firestore-fetch firebaseBasic usage
The package is read-focused. It’s intended to be extended by your app to add write helpers (so you can adapt write semantics and security rules as needed). Example of creating an Angular service that extends FirestoreFetch:
// app/services/firestore.service.ts (Angular example)
import { Injectable } from '@angular/core';
import { FirestoreFetch, Meta } from '@megasquid/firestore-fetch';
@Injectable({ providedIn: 'root' })
export class FirestoreService extends FirestoreFetch {
constructor() {
// Provide a meta factory so `_meta` is created for new docs
super((path: string) => ({
path,
id: path.split('/').pop() || '',
segments: path.split('/'),
createdAt: new Date().toISOString(),
updatedAt: new Date().toISOString(),
}));
}
// Add write methods (see "Extending with Write Operations")
}Core concepts
- Automatic
_metafield: every document returned fromdoc$,col$,colGroup$orfetch()is enriched with_metacontainingpath,id,segmentsand optional timestamps. This makes it trivial to persist changes or resolve related resources. - Lightweight DocumentReference: use
ref(path)to create a serializable{ id, path }reference (preferred for storing refs in documents that will be passed through JSON or stored in Firestore fields). - Declarative fetch:
fetch()accepts aFetchConfig | FetchConfig[]describing what to fetch. UseasNamedPropertyto shape results andsubCollections/propertiesto attach nested data.
API overview
- doc$(path: string): Observable<(T & Meta) | null> — Stream a single document with
_meta. - col$(path: string, filters?, orderBy?, limit?): Observable<(T & Meta)[] | null> — Stream a collection with optional filters, ordering, and limit.
- colGroup$(segment: string, filters?, orderBy?, limit?): Observable<(T & Meta)[] | null> — Collection group queries across all parents.
- fetch(config|configs, buildConfig?): Observable — Advanced multi-source builder that composes results, resolves references, and attaches subcollections.
- id(): string — Generate a Firestore-compatible random document id.
- ref(path: string) — Create a lightweight
{ id, path }reference object. - clone(obj: any): any — Deep clone preserving Firestore-like values (refs, timestamps where applicable).
Practical recipes
- Listen to a document:
this.ffs.doc$<User>('users/abc').subscribe(user => {
// user._meta.path === 'users/abc'
});- Stream a collection with filters and ordering:
this.ffs.col$<Post>(
'posts',
[ ['published', '==', true] ],
['createdAt', 'desc'],
20
).subscribe(posts => {
// posts: (Post & Meta)[]
});- Fetch a document with subcollections and resolved refs:
this.ffs.fetch<{ hunt: Hunt & Meta }>([
{
path: 'hunts/abc',
asNamedProperty: 'hunt',
subCollections: [ { path: 'loot' }, { path: 'rewards' } ],
properties: { '$.coverImage': {} }
}
], { merge: true, onlyNamedProperties: true })
.subscribe(({ hunt }) => {
console.log(hunt.loot, hunt.coverImage);
});Extending with Write Operations
@megasquid/firestore-fetch purposely focuses on reads so you can implement writes in the way that fits your app and security rules. Example using the official Firebase SDK's write helpers:
import { getFirestore, doc, setDoc, updateDoc, deleteDoc } from 'firebase/firestore';
class FirestoreService extends FirestoreFetch {
private db = getFirestore();
async set(path: string, data: any, options = { merge: true }) {
const meta = data._meta ? { ...data._meta, updatedAt: new Date().toISOString() } : this.createMeta(path);
const payload = { ...data, _meta: meta };
await setDoc(doc(this.db, path), payload, { merge: options.merge } as any);
return payload;
}
async update(path: string, patch: any) {
const patchWithMeta = { ...patch, '_meta.updatedAt': new Date().toISOString() } as any;
await updateDoc(doc(this.db, path), patchWithMeta);
}
async delete(path: string) {
await deleteDoc(doc(this.db, path));
}
}Documentation and examples
For real-world usage examples (how the library is used across an app, pipes, services, and recipes) see the project docs:
- docs/firestore-fetch.md — practical examples and recipes from a real app (LootletApp).
Contributing and notes
- This package is intentionally read-centric and designed to be extended for writes so your app can keep full control of security, validation, and triggers.
- If you use server SDKs or cloud functions, adapt write helpers accordingly (use Admin SDK patterns server-side).
License
MIT
Last updated: 2025-10-26
