Last active
April 7, 2021 04:22
-
-
Save samthecodingman/aea3bc9481bbab0a7fbc72069940e527 to your computer and use it in GitHub Desktop.
Drop-in TypeScript & JavaScript helper functions to help fetch a list of Cloud Firestore documents by their IDs.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/*! firestore-fetch-documents-by-id.js | Samuel Jones 2021 | MIT License | gist.github.com/samthecodingman */ | |
// on server: | |
import * as firebase from "firebase-admin"; | |
// on client: | |
// import firebase from "firebase/app"; | |
// import "firebase/firestore"; | |
/** splits array `arr` into chunks of max size `n` */ | |
function chunkArr(arr, n) { | |
if (n <= 0) throw new Error("n must be greater than 0"); | |
return Array | |
.from({length: Math.ceil(arr.length/n)}) | |
.map((_, i) => arr.slice(n*i, n*(i+1))) | |
} | |
/** | |
* Fetch documents with the given IDs from the given collection query or | |
* collection group query. | |
* | |
* **Note:** If a particular document ID is not found, it is silently omitted | |
* from results. | |
* | |
* **Note:** When used on a collection group query, you must specify the full document | |
* path as the ID as required by the Firestore API, or an error will be thrown. | |
* As an example, searching for the users who have a particular message ID in their | |
* data is not possible with this function | |
* (like `fetchDocumentsWithId(userMessagesCollectionGroup, ["someMessageId", ...])`) | |
* but searching specific users is, as long as you pass in the expected document path | |
* (like `fetchDocumentsWithId(userMessagesCollectionGroup, ["users/userA/userMessages/someMessageId", ...])`). | |
* For that functionality, you would be better off embedding the document ID in | |
* each document and using `where("_id", "in", ids)`. | |
* | |
* This utility function has two modes: | |
* * If `forEachCallback` is omitted, this function returns an array containing | |
* all found documents. | |
* * If `forEachCallback` is given, this function returns undefined, but found | |
* document is passed to `forEachCallback`. | |
* | |
* @param {firebase.firestore.Query} collectionQueryRef A collection-based query | |
* to search for documents | |
* @param {string[]} arrayOfIds An array of document IDs to retrieve | |
* @param {((firebase.firestore.QueryDocumentSnapshot) => void) | undefined} [forEachCallback=undefined] | |
* A callback to be called with a `QueryDocumentSnapshot` for each document found. | |
* @param {*} [forEachThisArg=null] The `this` binding for the callback. | |
* @return {*} an array of `QueryDocumentSnapshot` objects (in no callback mode) or `undefined` (in callback mode) | |
* | |
* @author Samuel Jones 2021 (samthecodingman) [MIT License] | |
*/ | |
async function fetchDocumentsWithId(collectionQueryRef, arrayOfIds, forEachCallback = undefined, forEachThisArg = null) { | |
// in batches of 10, fetch the documents with the given ID from the collection | |
const fetchDocsPromises = chunkArr(arrayOfIds, 10) | |
.map((idsInChunk) => ( | |
collectionQueryRef | |
.where(firebase.firestore.FieldPath.documentId(), "in", idsInChunk) | |
.get() | |
)) | |
// after all documents have been retrieved: | |
// - if forEachCallback has been given, call it for each document and return nothing | |
// - if forEachCallback hasn't been given, return all of the QueryDocumentSnapshot objects collected into one array | |
return Promise.all(fetchDocsPromises) | |
.then((querySnapshotArray) => { | |
if (forEachCallback !== void 0) { | |
// callback mode: call the callback for each document returned | |
for (let querySnapshot of querySnapshotArray) { | |
querySnapshot.forEach(forEachCallback, forEachThisArg) | |
} | |
return; | |
} | |
// no callback mode: get all documents as an array | |
const allDocumentSnapshotsArray = []; | |
for (let querySnapshot of querySnapshotArray) { | |
querySnapshot.forEach(doc => allDocumentSnapshotsArray.push(doc)) | |
} | |
return allDocumentSnapshotsArray; | |
}); | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/*! firestore-fetch-documents-by-id.ts | Samuel Jones 2021 | MIT License | gist.github.com/samthecodingman */ | |
// on server: | |
// import * as firebase from "firebase-admin"; | |
// on client: | |
// import firebase from "firebase/app"; | |
// import "firebase/firestore"; | |
/** | |
* Splits the given array into chunks with a maximum size of `n`. | |
* | |
* @param {T[]} arr The original array to split into chunks | |
* @param {number} n The maximum length of a chunk | |
* @return {T[][]} The array split into chunks | |
* | |
* @author Samuel Jones 2021 (samthecodingman) [MIT License] | |
*/ | |
function chunkArr<T>(arr: T[], n: number): T[][] { | |
if (n <= 0) throw new Error("n must be greater than 0"); | |
return Array | |
.from({length: Math.ceil(arr.length/n)}) | |
.map((_, i) => arr.slice(n*i, n*(i+1))) | |
} | |
/** | |
* Fetch documents with the given IDs from the given collection query or | |
* collection group query. | |
* | |
* **Note:** If a particular document ID is not found, it is silently omitted | |
* from results. | |
* | |
* **Note:** When used on a collection group query, you must specify the full document | |
* path as the ID as required by the Firestore API, or an error will be thrown. | |
* As an example, searching for the users who have a particular message ID in their | |
* data is not possible with this function | |
* (like `fetchDocumentsWithId(userMessagesCollectionGroup, ["someMessageId", ...])`) | |
* but searching specific users is, as long as you pass in the expected document path | |
* (like `fetchDocumentsWithId(userMessagesCollectionGroup, ["users/userA/userMessages/someMessageId", ...])`). | |
* For that functionality, you would be better off embedding the document ID in | |
* each document and using `where("_id", "in", ids)`. | |
* | |
* This utility function has two modes: | |
* * If `forEachCallback` is omitted, this function returns an array containing | |
* all found documents. | |
* * If `forEachCallback` is given, this function returns undefined, but found | |
* document is passed to `forEachCallback`. | |
* | |
* @param {firebase.firestore.Query} collectionQueryRef A collection-based query | |
* to search for documents | |
* @param {string[]} arrayOfIds An array of document IDs to retrieve | |
* @param {((firebase.firestore.QueryDocumentSnapshot) => void) | undefined} [forEachCallback=undefined] | |
* A callback to be called with a `QueryDocumentSnapshot` for each document found. | |
* @param {*} [forEachThisArg=null] The `this` binding for the callback. | |
* @return {*} an array of `QueryDocumentSnapshot` objects (in no callback mode) or `undefined` (in callback mode) | |
* | |
* @author Samuel Jones 2021 (samthecodingman) [MIT License] | |
*/ | |
async function fetchDocumentsWithId(collectionQueryRef: firebase.firestore.Query, arrayOfIds: string[], forEachCallback?: (() => void), forEachThisArg: any = null) { | |
// in batches of 10, fetch the documents with the given ID from the collection | |
const fetchDocsPromises = chunkArr(arrayOfIds, 10) | |
.map((idsInChunk) => ( | |
collectionQueryRef | |
.where(firebase.firestore.FieldPath.documentId(), "in", idsInChunk) | |
.get() | |
)) | |
// after all documents have been retrieved: | |
// - if forEachCallback has been given, call it for each document and return nothing | |
// - if forEachCallback hasn't been given, return all of the QueryDocumentSnapshot objects collected into one array | |
return Promise.all(fetchDocsPromises) | |
.then((querySnapshotArray) => { | |
if (forEachCallback !== void 0) { | |
// callback mode: call the callback for each document returned | |
for (let querySnapshot of querySnapshotArray) { | |
querySnapshot.forEach(forEachCallback, forEachThisArg) | |
} | |
return; | |
} | |
// no callback mode: get all documents as an array | |
const allDocumentSnapshotsArray: firebase.firestore.QueryDocumentSnapshot[] = []; | |
for (let querySnapshot of querySnapshotArray) { | |
querySnapshot.forEach(doc => allDocumentSnapshotsArray.push(doc)) | |
} | |
return allDocumentSnapshotsArray; | |
}); | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment