UNPKG

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
"use strict"; 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