Skip to content

Instantly share code, notes, and snippets.

@wyattades
Last active August 5, 2019 04:42
Show Gist options
  • Save wyattades/bba9be70dc6ba59402c8a4d2d6f27cb1 to your computer and use it in GitHub Desktop.
Save wyattades/bba9be70dc6ba59402c8a4d2d6f27cb1 to your computer and use it in GitHub Desktop.
Firebase - Firestore Collection Utilities
/*
* Utility functions for mass updates to Firebase's firestore collections
* Methods: iterate, update, delete, copy
*/
/**
* @callback IterateCollectionCb
* @param {import('firebase-admin').firestore.DocumentSnapshot} doc
* @param {import('firebase-admin').firestore.WriteBatch} [batch]
*/
/**
* Iterate an entire collection
* @param {import('firebase-admin').firestore.CollectionReference} collectionRef
* @param {IterateCollectionCb} fn - Callback that takes arguments:
* doc - a DocumentSnapshot instance; batch - a WriteBatch instance (only if
* `writeBatch` parameter is `true`)
*/
exports.iterateCollection = async (
collectionRef,
fn,
{
noCursor = false,
getData = false,
writeBatch = false,
batchSize = 300,
} = {},
) => {
let last;
while (true) {
let query = collectionRef.orderBy('__name__').limit(batchSize);
if (!noCursor && last) query = query.startAfter(last);
const snap = await (getData ? query : query.select()).get();
if (snap.size === 0) break;
last = snap.docs[snap.docs.length - 1];
if (writeBatch) {
// Max docs for batch write is 500
const batch = collectionRef.firestore.batch();
for (const doc of snap.docs) fn(doc, batch);
await batch.commit();
} else {
for (const doc of snap.docs) fn(doc);
}
}
};
/**
* Delete an entire collection
* @param {import('firebase-admin').firestore.CollectionReference} collectionRef
*/
exports.deleteCollection = async (collectionRef) => {
await exports.iterateCollection(
collectionRef,
(doc, batch) => {
batch.delete(doc.ref);
},
{
writeBatch: true,
noCursor: true,
},
);
};
/**
* Update all data in a collection
* @param {import('firebase-admin').firestore.CollectionReference} collectionRef
* @param {function|Object} updateData - Can be a plain data object, or a callback
* function that takes DocumentReference and returns a plain data object
*/
exports.updateCollection = async (collectionRef, updateData) => {
const isFunc = typeof updateData === 'function';
await exports.iterateCollection(
collectionRef,
(doc, batch) => {
const updated = isFunc ? updateData(doc) : updateData;
if (updated) batch.update(doc.ref, updated);
},
{
writeBatch: true,
getData: isFunc,
},
);
};
/**
* Copies an entire collection to a new collection with name <newName>
* @param {import('firebase-admin').firestore.CollectionReference} collectionRef
* @param {String} newName
* @param {Boolean} [deleteSourceCollection=false]
*/
exports.copyCollection = async (
collectionRef,
newName,
deleteSourceCollection = false,
) => {
const newCollec = collectionRef.firestore.collection(newName);
await exports.iterateCollection(
collectionRef,
(doc, batch) => {
batch.set(newCollec.doc(doc.id), doc.data());
},
{
writeBatch: true,
},
);
if (deleteSourceCollection) await exports.deleteCollection(collectionRef);
};
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment