Skip to content

Instantly share code, notes, and snippets.

@taxilian
Last active May 15, 2020 22:13
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save taxilian/86c99fb839172089c9223f806fa4c8e1 to your computer and use it in GitHub Desktop.
Save taxilian/86c99fb839172089c9223f806fa4c8e1 to your computer and use it in GitHub Desktop.
Mongodb 4.2 update pipeline creator which updates modified field if anything changed
import mongoose, { LeanDocument } from "mongoose";
import '@/lib/PromiseAll';
// Used because Object.keys doesn't give us types for field names
export function keys<T extends object>(obj: T) : (keyof T)[] {
return Object.keys(obj) as (keyof T)[];
}
// Spelled incorrectly on purpose
const defaultIgnoreType = '__UNMODIFEID__';
export function makeUpdatePipeline<T extends mongoose.Document<any>>(updFields: Partial<LeanDocument<T>>, modFieldName = 'modified', ignoreType = defaultIgnoreType) {
// Maps the input document values; anything that is new remains unchanged
// and anything which is the same as the document we're matching against
// gets changed to the value of _ignoreType_.
// Note that using `k` and `v` for key names on this object is important
// so that $arrayToObject works in stage 2
const updateArray = keys(updFields).map(fieldName => ({
k: fieldName,
v: { $cond: {
if: {$eq: [`\$${fieldName}`, updFields[fieldName]]},
then: ignoreType,
else: updFields[fieldName]
} }
}));
const pipeline = [
// Stage 1: Filter the update fields
// we split the document putting the original document at `_root` and making a new
// _update which is updateArray but we filter out anything with a value
// of `ignoreType` (which we calculated when we built updateArray)
// from what is on the object; the $cond in the `updateArray` does that
{ $project: {
_id: 0,
_update: {$filter: {
input: updateArray,
as: "curItem",
cond: {$ne: <any>["$$curItem.v", ignoreType]}
}},
_root: "$$ROOT",
}},
// Stage 2: Update the document
// We merge three objects to create the new output document:
{ $replaceRoot: {
newRoot: { $mergeObjects: [
"$_root", // Object 1: the original document
{$arrayToObject: "$_update"}, // Object 2: our filtered updateArray converted to an object
{$cond: { // Object 3: the modified field *if* updateArray isn't empty
if: {$gt: [ {$reduce: { input: "$_update", initialValue: 0, in: {$sum: 1}}}, 0]},
then: {[modFieldName]: "$$NOW"},
else: {}
}},
]},
} },
];
return pipeline;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment