Last active
April 29, 2021 14:41
-
-
Save hscheuerle/1cff55db1a5b8bcf7c749f23677815df to your computer and use it in GitHub Desktop.
Many to many ref handling for firebase functions
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
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