Notoriously Punctual Manatee

    @healthtree/firestore-join
    TypeScript icon, indicating that this package has built-in type declarations

    0.5.11 • Public • Published

    Firestore Join

    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

    How to use:

    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 interface
    interface SerializedDocument{
      // 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: DocumentSnapshot
    
      constructor(snapshot: DocumentSnapshot, includeConfig: IncludeConfig = {}) {
      ...
      }
      
      // Create and return a SerializedDocument that doesn't exist on firestore, useful to keep consistency.
      static createLocal = (ref: DocumentReference, data: any = {}, includeConfig: IncludeConfig = {}): SerializedDocument => {
      ...
      }
    
      // Gets the document reference and returns a SerializedDocument with any includeConfig
      static fromDocumentReference = (ref: DocumentReference, includeConfig: IncludeConfig): SerializedDocumentPromise => {
      ...  
      }
    }
    // SerializedDocumentArray is basically an array of SerializedDocuments
    // It implements a ready function to know when all included documents are ready
    
    interface SerializedDocumentArray extends Array<SerializedDocument> {
        constructor(querySnapshot: QuerySnapshot, includesConfig: IncludeConfig) {
            
        }
    
        static fromDocumentReferenceArray = (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 ready
        ready() {
        
        }
    }

    To serialize an array of documents without including any references.

    const posts = await SerializedDocumentArray.fromQuery(firestore.collection('posts'));
    
    // or with any firestore supported filters
    
    const postsFromUser = await SerializedDocumentArray.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.

    const posts = await SerializedDocumentArray.fromQuery(
      firestore.collection('posts'), 
      {user: true}
    ).ready();
    
    // with any firestore supported filters
    const postsFromUser = await SerializedDocumentArray.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.

    const posts = await SerializedDocumentArray.fromQuery(
      firestore.collection('posts'), 
      {user: true}
    ).ready();
    
    // with any firestore supported filters
    const postsFromUser = await SerializedDocumentArray.fromQuery(
      firestore.collection('posts').where('user','==', userReference),
      {user: true}
    ).ready();
    
    // with nested references to include
    const posts = await SerializedDocumentArray.fromQuery(
      firestore.collection('posts'),
      {user: {organization: true}}
    ).ready();
    
    
    // You can access the included documents
    console.log(posts[0].includes.user); // User data
    console.log(posts[0].includes.user.includes.organization.data); // User->Organization data
    
    // if included documents are an array
    const posts = await SerializedDocumentArray.fromQuery(
      firestore.collection('posts'),
      {
        user: true,
        tags: true
      }
    ).ready();
    
    // You can access the included documents
    console.log(posts[0].includes.tags); // Array of SerializedDocuments containing all tags

    Listen to firestore query snapshots and serialize

    // Only use if your use case really justifies real time updates.
    let posts;
     firestore.collection('posts').onSnapshot(async querySnapshot => {
       posts = await new SerializedDocumentArray(querySnapshot, {user: true}).ready()
    })

    When to use createLocal?

    Let's pretend we have a page/component used create or edit a document in firestore.

    // Sample using svelte
    onMount(async () => {
      const postId = $page.query.postId;
      let post;
      if(postId === 'new') {
        // Pass the desired reference and any initial data
        post = SerializedDocument.createLocal(db.collection('posts').doc(), {user: userReference})
      } else {
        post = await SerializedDocument.fromDocumentReference(db.collection('posts').doc(postId))
      }
    })
    
    // UI to modify the message on post, no need for double ui if post is new
    
    onSave = () => {
      post.ref.set(post.data); 
      // No need to have extra logic to see if it was a new doc
    }

    Advanced - Include documents but listen to individual include to be ready.

    const posts = await SerializedDocumentArray.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 posts as post (post.ref.id)}
    <div>
      {#await post.promises.user}
      <p>...waiting</p>
      {:then user}
      <p>From: {post.included.user.data.name}</p>
      {:catch error}
      
      {/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

    Install

    npm i @healthtree/firestore-join

    DownloadsWeekly Downloads

    164

    Version

    0.5.11

    License

    MIT

    Unpacked Size

    359 kB

    Total Files

    12

    Last publish

    Collaborators

    • sahagun-jorge
    • alvarocapde
    • healthtree-admin
    • diegofhe
    • jpcapdevila
    • aureliob