Skip to content

Instantly share code, notes, and snippets.

@hscheuerle
Last active April 29, 2021 14:41
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
Star You must be signed in to star a gist
Save hscheuerle/1cff55db1a5b8bcf7c749f23677815df to your computer and use it in GitHub Desktop.
Many to many ref handling for firebase functions
import * as functions from "firebase-functions";
// import type so that lazy import isn't made useless
import type * as admin from "firebase-admin";
// global types only
type DocumentReference = admin.firestore.DocumentReference;
// lazy init admin, not required in pubsub but when paired in functions
// file with callables is important.
let initialized = false;
function onWriteHandlingLtR(pair: [string, string]) {
const [lhs, rhs] = pair;
return functions.firestore
// careful here, its really easy to forget brackets around docId when using
// a template string with more interpolating brackets, if pubsub emulator fails
// to startup this is your mistake
.document(`${lhs}/{lhsId}`)
.onWrite(async (change) => {
const admin = await import("firebase-admin");
const { arrayUnion, arrayRemove } = admin.firestore.FieldValue;
if (!initialized) {
admin.initializeApp();
initialized = true;
}
const db = admin.firestore();
const batch = db.batch();
const { before, after } = change;
const isCreated = !before.exists && after.exists;
if (isCreated) {
const rhsRefsInLhs: DocumentReference[] = after.get(rhs) ?? [];
// dangerous territory with infinite loop. don't make changes lightly.
rhsRefsInLhs.forEach((rhsRefInLhs) => {
batch.update(rhsRefInLhs, { [lhs]: arrayUnion(after.ref) });
});
return batch.commit();
}
const isDeleted = before.exists && !after.exists;
if (isDeleted) {
const rhsRefsInLhs: DocumentReference[] = before.get(rhs) ?? [];
rhsRefsInLhs.forEach((rhsRefInLhs) => {
batch.update(rhsRefInLhs, { [lhs]: arrayRemove(before.ref) });
});
return batch.commit();
}
const rhsRefsBefore: DocumentReference[] = change.before.get(rhs) ?? [];
const rhsRefsAfter: DocumentReference[] = change.after.get(rhs) ?? [];
rhsRefsBefore
.filter((before) => !rhsRefsAfter.includes(before))
.forEach((ref) => {
batch.update(ref, { [lhs]: arrayRemove(after.ref) });
});
rhsRefsAfter
.filter((after) => !rhsRefsBefore.includes(after))
.forEach((ref) => {
batch.update(ref, { [lhs]: arrayUnion(after.ref) });
});
return batch.commit();
});
}
// return functions change per key
function onWriteHandling(pair: [string, string]) {
const [lhs, rhs] = pair;
return {
onWriteLhs: onWriteHandlingLtR([lhs, rhs]),
onWriteRhs: onWriteHandlingLtR([rhs, lhs]),
};
}
const manyToManyHandle = onWriteHandling(["students", "teachers"]);
// exports for both functions pubsub change register
export const handleStudents = manyToManyHandle.onWriteLhs;
export const handleTeachers = manyToManyHandle.onWriteRhs;
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment