Easily include references inside your document.
Supports:
- Single references
- Arrays of references
- Maps pointing references
- Infinite nested references
- Cache so you don't include
.get()the same reference more than once.
npm i firebase-join
1 - Install:
npm i firebase-join
2 - Import:
import { SerializedDocumentArray } from '@healthtree/firestore-join';
The building blocks for this library are SerializedDocument and SerializedDocumentArray classes & IncludeConfig.
//Only recommended methods and properties are documented in this interfaceinterfaceSerializedDocument{// Data returned after calling snapshot.data() and transforming the data, by default it converts firestore timestamps into JS dates.
data: any// Firestore document reference
ref: firebase.firestore.DocumentReference// Any included documents, as SerializedDocuments.
included: Object={}// Promises for each included reference, this promises resolves once the document is returned by the server or cache.
promises: Object={}// DocumentSnapshot of the document if document came from server.
snapshot: DocumentSnapshotconstructor(snapshot: DocumentSnapshot,includeConfig: IncludeConfig={}){
...
}// Create and return a SerializedDocument that doesn't exist on firestore, useful to keep consistency.staticcreateLocal=(ref: DocumentReference,data: any={},includeConfig: IncludeConfig={}): SerializedDocument=>{
...
}// Gets the document reference and returns a SerializedDocument with any includeConfigstaticfromDocumentReference=(ref: DocumentReference,includeConfig: IncludeConfig): SerializedDocumentPromise=>{
... }}// SerializedDocumentArray is basically an array of SerializedDocuments// It implements a ready function to know when all included documents are readyinterfaceSerializedDocumentArrayextendsArray<SerializedDocument>{constructor(querySnapshot: QuerySnapshot,includesConfig: IncludeConfig){}staticfromDocumentReferenceArray=(documentReferenceArray: [DocumentReference],includesConfig: IncludeConfig): SerializedDocumentArrayPromise=>{
...
})}// Returns a promise that resolves a SerializedDocumentArray// when the documents (without includes) are ready.
static fromQuery=(query: Query,includesConfig: IncludeConfig): SerializedDocumentArrayPromise=>{
...
}// Returns a promise that resolves when all included documents are readyready(){}}constposts=awaitSerializedDocumentArray.fromQuery(firestore.collection('posts'));// or with any firestore supported filtersconstpostsFromUser=awaitSerializedDocumentArray.fromQuery(firestore.collection('posts').where('user','==',userReference));To serialize an array of documents including a reference and waiting for all the included references to be ready.
Let's pretend each post has a property called user, where user is a documentReference of the user that created the post.
To include all the users, you pass an includeConfig object as the second parameter and call a ready function that returns a promise that resolves once all the references are resolved.
constposts=awaitSerializedDocumentArray.fromQuery(firestore.collection('posts'),{user: true}).ready();// with any firestore supported filtersconstpostsFromUser=awaitSerializedDocumentArray.fromQuery(firestore.collection('posts').where('user','==',userReference),{user: true}).ready();To serialize an array of documents including a reference and waiting for all the included references to be ready.
Let's pretend each post has a property called user, where user is a documentReference of the user that created the post.
To include all the users, you pass an includeConfig object as the second parameter and call a ready function that returns a promise that resolves once all the references are resolved.
constposts=awaitSerializedDocumentArray.fromQuery(firestore.collection('posts'),{user: true}).ready();// with any firestore supported filtersconstpostsFromUser=awaitSerializedDocumentArray.fromQuery(firestore.collection('posts').where('user','==',userReference),{user: true}).ready();// with nested references to includeconstposts=awaitSerializedDocumentArray.fromQuery(firestore.collection('posts'),{user: {organization: true}}).ready();// You can access the included documentsconsole.log(posts[0].includes.user);// User dataconsole.log(posts[0].includes.user.includes.organization.data);// User->Organization data// if included documents are an arrayconstposts=awaitSerializedDocumentArray.fromQuery(firestore.collection('posts'),{user: true,tags: true}).ready();// You can access the included documentsconsole.log(posts[0].includes.tags);// Array of SerializedDocuments containing all tags// Only use if your use case really justifies real time updates.letposts;firestore.collection('posts').onSnapshot(asyncquerySnapshot=>{posts=awaitnewSerializedDocumentArray(querySnapshot,{user: true}).ready()})Let's pretend we have a page/component used create or edit a document in firestore.
// Sample using svelteonMount(async()=>{constpostId=$page.query.postId;letpost;if(postId==='new'){// Pass the desired reference and any initial datapost=SerializedDocument.createLocal(db.collection('posts').doc(),{user: userReference})}else{post=awaitSerializedDocument.fromDocumentReference(db.collection('posts').doc(postId))}})// UI to modify the message on post, no need for double ui if post is newonSave=()=>{post.ref.set(post.data);// No need to have extra logic to see if it was a new doc}constposts=awaitSerializedDocumentArray.fromQuery(firestore.collection('posts'),{user: true});// We are not going to wait for includes to be ready, start rendering and only render user name when ready.// Sample using svelte{#each postsaspost(post.ref.id)}<div>{#await post.promises.user}<p>...waiting</p>{:thenuser}<p>From: {post.included.user.data.name}</p>{:catcherror}{/await}{post.data.message}</div>{/each}//This allows for fast, progressive ui rendering // where you don't have to wait for everything to be ready