n8n-nodes-mongo-change-stream-trigger
Version:
n8n-nodes-mongo-change-stream-trigger is a community package for n8n that brings real-time MongoDB integration to your workflows. It uses MongoDB’s native change streams to monitor a specified collection without relying on polling and offers advanced, cas
315 lines • 14 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.MongoChangeTrigger = void 0;
const n8n_workflow_1 = require("n8n-workflow");
const mongodb_1 = require("mongodb");
class MongoChangeTrigger {
constructor() {
this.description = {
displayName: 'Mongo Change Trigger',
name: 'mongoChangeTrigger',
icon: 'file:mongodb.svg',
group: ['trigger'],
version: 1,
description: 'Triggers a workflow on changes in a MongoDB collection that match specified filters',
defaults: {
name: 'Mongo Change Trigger',
},
inputs: [],
outputs: ['main'],
credentials: [
{
name: 'mongoDbTriggerApi',
required: true,
},
],
properties: [
{
displayName: 'Database Name',
name: 'database',
type: 'string',
default: '',
placeholder: 'myDatabase',
description: 'Name of the database to connect to',
},
{
displayName: 'Collection Name',
name: 'collection',
type: 'string',
default: '',
placeholder: 'myCollection',
description: 'The MongoDB collection to watch for changes',
},
{
displayName: 'Fields to Monitor',
name: 'fields',
type: 'string',
default: '*',
placeholder: '* or a comma-separated list (e.g. status,priority)',
description: 'Enter "*" to listen for any change or a comma-separated list of specific fields to monitor for updates',
},
{
displayName: 'Filters',
name: 'filters',
type: 'fixedCollection',
typeOptions: {
multipleValues: true,
},
default: {},
options: [
{
name: 'filterValues',
displayName: 'Filter',
values: [
{
displayName: 'Field',
name: 'field',
type: 'string',
default: '',
description: 'Field to check in the updateDescription.updatedFields',
},
{
displayName: 'Operator',
name: 'operator',
type: 'options',
options: [
{
name: 'Equal To',
value: 'equal',
},
{
name: 'Not Equal To',
value: 'notEqual',
},
],
default: 'equal',
},
{
displayName: 'Value',
name: 'value',
type: 'string',
default: '',
description: 'Value that the field should match to trigger the workflow',
},
],
},
],
description: 'Define filters to only trigger the workflow when all conditions are met',
},
{
displayName: 'Operation Types',
name: 'operationTypes',
type: 'multiOptions',
options: [
{
name: 'Delete',
value: 'delete',
description: 'Trigger on document deletions',
},
{
name: 'Drop',
value: 'drop',
description: 'Trigger when collection is dropped',
},
{
name: 'Insert',
value: 'insert',
description: 'Trigger on document insertions',
},
{
name: 'Rename',
value: 'rename',
description: 'Trigger when collection is renamed',
},
{
name: 'Replace',
value: 'replace',
description: 'Trigger on document replacements',
},
{
name: 'Update',
value: 'update',
description: 'Trigger on document updates',
},
],
default: ['update'],
description: 'Which operation types should trigger the workflow',
},
],
};
}
async trigger() {
var _a, _b;
const credentials = await this.getCredentials('mongoDbTriggerApi');
if (!credentials) {
throw new n8n_workflow_1.NodeApiError(this.getNode(), { message: 'No credentials were returned!' });
}
const database = this.getNodeParameter('database', '');
const collectionName = this.getNodeParameter('collection', '');
const fields = this.getNodeParameter('fields', '*');
const operationTypes = this.getNodeParameter('operationTypes', ['update']);
const filters = this.getNodeParameter('filters.filterValues', []);
const uri = credentials.connectionString;
let client;
try {
client = new mongodb_1.MongoClient(uri);
await client.connect();
}
catch (error) {
throw new n8n_workflow_1.NodeApiError(this.getNode(), error, {
message: 'Failed to connect to MongoDB. Please check your credentials and connection settings.',
});
}
const db = client.db(database);
try {
try {
await db.command({ ping: 1 });
}
catch (error) {
if (((_a = error.message) === null || _a === void 0 ? void 0 : _a.includes("not authorized")) || error.codeName === "Unauthorized") {
throw new n8n_workflow_1.NodeApiError(this.getNode(), {
message: `Not authorized to access database "${database}". Check if the database exists and your credentials have sufficient permissions.`,
});
}
else {
throw new n8n_workflow_1.NodeApiError(this.getNode(), {
message: `Database "${database}" could not be accessed: ${error.message}`,
});
}
}
try {
const collections = await db.listCollections({ name: collectionName }).toArray();
if (collections.length === 0) {
throw new n8n_workflow_1.NodeApiError(this.getNode(), {
message: `Collection "${collectionName}" does not exist in database "${database}".`,
});
}
}
catch (error) {
if (((_b = error.message) === null || _b === void 0 ? void 0 : _b.includes("not authorized")) || error.codeName === "Unauthorized") {
throw new n8n_workflow_1.NodeApiError(this.getNode(), {
message: `Not authorized to list collections in "${database}". Check if your credentials have sufficient permissions.`,
});
}
else {
throw new n8n_workflow_1.NodeApiError(this.getNode(), error, {
message: `Failed to verify if collection "${collectionName}" exists.`,
});
}
}
}
catch (error) {
if (error.name === 'NodeApiError')
throw error;
throw new n8n_workflow_1.NodeApiError(this.getNode(), error, {
message: 'Failed to verify database or collection existence.',
});
}
const collection = db.collection(collectionName);
let pipeline = [];
if (operationTypes.length > 0 && operationTypes.length < 6) {
pipeline.push({
$match: {
operationType: { $in: operationTypes }
}
});
}
if (fields !== '*' && operationTypes.includes('update')) {
const fieldList = fields.split(',').map(field => field.trim());
pipeline.push({
$match: {
$or: [
{
operationType: { $ne: 'update' }
},
{
$and: [
{ operationType: 'update' },
{ 'updateDescription.updatedFields': { $exists: true } },
{
$or: fieldList.map(field => ({
[`updateDescription.updatedFields.${field}`]: { $exists: true },
})),
}
]
}
]
}
});
}
const changeStream = collection.watch(pipeline);
changeStream.on('change', (change) => {
var _a;
const formattedOutput = formatChangeOutput(change);
if (filters.length > 0 && change.operationType === 'update') {
const updatedFields = ((_a = change.updateDescription) === null || _a === void 0 ? void 0 : _a.updatedFields) || {};
const allConditionsMet = filters.every(filter => {
if (!updatedFields.hasOwnProperty(filter.field))
return false;
const actualValue = updatedFields[filter.field];
const expectedValue = filter.value;
switch (filter.operator) {
case 'equal': return actualValue === expectedValue;
case 'notEqual': return actualValue !== expectedValue;
default: return false;
}
});
if (allConditionsMet) {
this.emit([
[{ json: formattedOutput }]
]);
}
}
else {
this.emit([
[{ json: formattedOutput }]
]);
}
});
function formatChangeOutput(change) {
var _a, _b, _c, _d, _e, _f, _g;
const output = {
operation: change.operationType,
timestamp: new Date(change.wallTime || Date.now()).toISOString(),
database: ((_a = change.ns) === null || _a === void 0 ? void 0 : _a.db) || database,
collection: ((_b = change.ns) === null || _b === void 0 ? void 0 : _b.coll) || collectionName,
documentId: ('documentKey' in change) ? (_d = (_c = change.documentKey) === null || _c === void 0 ? void 0 : _c._id) === null || _d === void 0 ? void 0 : _d.toString() : undefined,
};
switch (change.operationType) {
case 'insert':
output.document = change.fullDocument || {};
break;
case 'update':
output.modifiedFields = ((_e = change.updateDescription) === null || _e === void 0 ? void 0 : _e.updatedFields) || {};
output.removedFields = ((_f = change.updateDescription) === null || _f === void 0 ? void 0 : _f.removedFields) || [];
if (change.fullDocument) {
output.documentAfterUpdate = change.fullDocument;
}
break;
case 'replace':
output.documentAfterReplace = change.fullDocument || {};
break;
case 'delete':
break;
case 'drop':
case 'rename':
if (change.operationType === 'rename') {
output.newCollection = (_g = change.to) === null || _g === void 0 ? void 0 : _g.coll;
}
break;
default:
output.details = change;
}
return output;
}
const closeFunction = async () => {
await changeStream.close();
await client.close();
};
return {
closeFunction,
};
}
}
exports.MongoChangeTrigger = MongoChangeTrigger;
//# sourceMappingURL=MongoChangeTrigger.node.js.map