├── .gitignore
├── LICENSE
├── README.md
├── mongodb
├── mongodb.html
└── mongodb.js
├── package-lock.json
└── package.json
/.gitignore:
--------------------------------------------------------------------------------
1 | /node_modules
2 | *.log
3 |
4 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Apache License
2 | Version 2.0, January 2004
3 | http://www.apache.org/licenses/
4 |
5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
6 |
7 | 1. Definitions.
8 |
9 | "License" shall mean the terms and conditions for use, reproduction,
10 | and distribution as defined by Sections 1 through 9 of this document.
11 |
12 | "Licensor" shall mean the copyright owner or entity authorized by
13 | the copyright owner that is granting the License.
14 |
15 | "Legal Entity" shall mean the union of the acting entity and all
16 | other entities that control, are controlled by, or are under common
17 | control with that entity. For the purposes of this definition,
18 | "control" means (i) the power, direct or indirect, to cause the
19 | direction or management of such entity, whether by contract or
20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the
21 | outstanding shares, or (iii) beneficial ownership of such entity.
22 |
23 | "You" (or "Your") shall mean an individual or Legal Entity
24 | exercising permissions granted by this License.
25 |
26 | "Source" form shall mean the preferred form for making modifications,
27 | including but not limited to software source code, documentation
28 | source, and configuration files.
29 |
30 | "Object" form shall mean any form resulting from mechanical
31 | transformation or translation of a Source form, including but
32 | not limited to compiled object code, generated documentation,
33 | and conversions to other media types.
34 |
35 | "Work" shall mean the work of authorship, whether in Source or
36 | Object form, made available under the License, as indicated by a
37 | copyright notice that is included in or attached to the work
38 | (an example is provided in the Appendix below).
39 |
40 | "Derivative Works" shall mean any work, whether in Source or Object
41 | form, that is based on (or derived from) the Work and for which the
42 | editorial revisions, annotations, elaborations, or other modifications
43 | represent, as a whole, an original work of authorship. For the purposes
44 | of this License, Derivative Works shall not include works that remain
45 | separable from, or merely link (or bind by name) to the interfaces of,
46 | the Work and Derivative Works thereof.
47 |
48 | "Contribution" shall mean any work of authorship, including
49 | the original version of the Work and any modifications or additions
50 | to that Work or Derivative Works thereof, that is intentionally
51 | submitted to Licensor for inclusion in the Work by the copyright owner
52 | or by an individual or Legal Entity authorized to submit on behalf of
53 | the copyright owner. For the purposes of this definition, "submitted"
54 | means any form of electronic, verbal, or written communication sent
55 | to the Licensor or its representatives, including but not limited to
56 | communication on electronic mailing lists, source code control systems,
57 | and issue tracking systems that are managed by, or on behalf of, the
58 | Licensor for the purpose of discussing and improving the Work, but
59 | excluding communication that is conspicuously marked or otherwise
60 | designated in writing by the copyright owner as "Not a Contribution."
61 |
62 | "Contributor" shall mean Licensor and any individual or Legal Entity
63 | on behalf of whom a Contribution has been received by Licensor and
64 | subsequently incorporated within the Work.
65 |
66 | 2. Grant of Copyright License. Subject to the terms and conditions of
67 | this License, each Contributor hereby grants to You a perpetual,
68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
69 | copyright license to reproduce, prepare Derivative Works of,
70 | publicly display, publicly perform, sublicense, and distribute the
71 | Work and such Derivative Works in Source or Object form.
72 |
73 | 3. Grant of Patent License. Subject to the terms and conditions of
74 | this License, each Contributor hereby grants to You a perpetual,
75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
76 | (except as stated in this section) patent license to make, have made,
77 | use, offer to sell, sell, import, and otherwise transfer the Work,
78 | where such license applies only to those patent claims licensable
79 | by such Contributor that are necessarily infringed by their
80 | Contribution(s) alone or by combination of their Contribution(s)
81 | with the Work to which such Contribution(s) was submitted. If You
82 | institute patent litigation against any entity (including a
83 | cross-claim or counterclaim in a lawsuit) alleging that the Work
84 | or a Contribution incorporated within the Work constitutes direct
85 | or contributory patent infringement, then any patent licenses
86 | granted to You under this License for that Work shall terminate
87 | as of the date such litigation is filed.
88 |
89 | 4. Redistribution. You may reproduce and distribute copies of the
90 | Work or Derivative Works thereof in any medium, with or without
91 | modifications, and in Source or Object form, provided that You
92 | meet the following conditions:
93 |
94 | (a) You must give any other recipients of the Work or
95 | Derivative Works a copy of this License; and
96 |
97 | (b) You must cause any modified files to carry prominent notices
98 | stating that You changed the files; and
99 |
100 | (c) You must retain, in the Source form of any Derivative Works
101 | that You distribute, all copyright, patent, trademark, and
102 | attribution notices from the Source form of the Work,
103 | excluding those notices that do not pertain to any part of
104 | the Derivative Works; and
105 |
106 | (d) If the Work includes a "NOTICE" text file as part of its
107 | distribution, then any Derivative Works that You distribute must
108 | include a readable copy of the attribution notices contained
109 | within such NOTICE file, excluding those notices that do not
110 | pertain to any part of the Derivative Works, in at least one
111 | of the following places: within a NOTICE text file distributed
112 | as part of the Derivative Works; within the Source form or
113 | documentation, if provided along with the Derivative Works; or,
114 | within a display generated by the Derivative Works, if and
115 | wherever such third-party notices normally appear. The contents
116 | of the NOTICE file are for informational purposes only and
117 | do not modify the License. You may add Your own attribution
118 | notices within Derivative Works that You distribute, alongside
119 | or as an addendum to the NOTICE text from the Work, provided
120 | that such additional attribution notices cannot be construed
121 | as modifying the License.
122 |
123 | You may add Your own copyright statement to Your modifications and
124 | may provide additional or different license terms and conditions
125 | for use, reproduction, or distribution of Your modifications, or
126 | for any such Derivative Works as a whole, provided Your use,
127 | reproduction, and distribution of the Work otherwise complies with
128 | the conditions stated in this License.
129 |
130 | 5. Submission of Contributions. Unless You explicitly state otherwise,
131 | any Contribution intentionally submitted for inclusion in the Work
132 | by You to the Licensor shall be under the terms and conditions of
133 | this License, without any additional terms or conditions.
134 | Notwithstanding the above, nothing herein shall supersede or modify
135 | the terms of any separate license agreement you may have executed
136 | with Licensor regarding such Contributions.
137 |
138 | 6. Trademarks. This License does not grant permission to use the trade
139 | names, trademarks, service marks, or product names of the Licensor,
140 | except as required for reasonable and customary use in describing the
141 | origin of the Work and reproducing the content of the NOTICE file.
142 |
143 | 7. Disclaimer of Warranty. Unless required by applicable law or
144 | agreed to in writing, Licensor provides the Work (and each
145 | Contributor provides its Contributions) on an "AS IS" BASIS,
146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
147 | implied, including, without limitation, any warranties or conditions
148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
149 | PARTICULAR PURPOSE. You are solely responsible for determining the
150 | appropriateness of using or redistributing the Work and assume any
151 | risks associated with Your exercise of permissions under this License.
152 |
153 | 8. Limitation of Liability. In no event and under no legal theory,
154 | whether in tort (including negligence), contract, or otherwise,
155 | unless required by applicable law (such as deliberate and grossly
156 | negligent acts) or agreed to in writing, shall any Contributor be
157 | liable to You for damages, including any direct, indirect, special,
158 | incidental, or consequential damages of any character arising as a
159 | result of this License or out of the use or inability to use the
160 | Work (including but not limited to damages for loss of goodwill,
161 | work stoppage, computer failure or malfunction, or any and all
162 | other commercial damages or losses), even if such Contributor
163 | has been advised of the possibility of such damages.
164 |
165 | 9. Accepting Warranty or Additional Liability. While redistributing
166 | the Work or Derivative Works thereof, You may choose to offer,
167 | and charge a fee for, acceptance of support, warranty, indemnity,
168 | or other liability obligations and/or rights consistent with this
169 | License. However, in accepting such obligations, You may act only
170 | on Your own behalf and on Your sole responsibility, not on behalf
171 | of any other Contributor, and only if You agree to indemnify,
172 | defend, and hold each Contributor harmless for any liability
173 | incurred by, or claims asserted against, such Contributor by reason
174 | of your accepting any such warranty or additional liability.
175 |
176 | END OF TERMS AND CONDITIONS
177 |
178 | APPENDIX: How to apply the Apache License to your work.
179 |
180 | To apply the Apache License to your work, attach the following
181 | boilerplate notice, with the fields enclosed by brackets "{}"
182 | replaced with your own identifying information. (Don't include
183 | the brackets!) The text should be enclosed in the appropriate
184 | comment syntax for the file format. We also recommend that a
185 | file or class name and description of purpose be included on the
186 | same "printed page" as the copyright notice for easier
187 | identification within third-party archives.
188 |
189 | Copyright 2016 Awear Solutions Ltd
190 |
191 | Licensed under the Apache License, Version 2.0 (the "License");
192 | you may not use this file except in compliance with the License.
193 | You may obtain a copy of the License at
194 |
195 | http://www.apache.org/licenses/LICENSE-2.0
196 |
197 | Unless required by applicable law or agreed to in writing, software
198 | distributed under the License is distributed on an "AS IS" BASIS,
199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
200 | See the License for the specific language governing permissions and
201 | limitations under the License.
202 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # node-red-contrib-mongodb3
2 | MongoDB node driver 3.0 interface for Node-RED
3 |
4 | based on [node-red-bluemix-nodes](https://github.com/node-red/node-red-bluemix-nodes/tree/master/mongo) and [MongoDB 3 Driver](http://mongodb.github.io/node-mongodb-native/3.0)
5 |
6 | Please refer to the [mongoDB node driver 'Collection' documentation](http://mongodb.github.io/node-mongodb-native/3.0/api/Collection.html) to read about each operation.
7 |
8 | # Usage
9 | **msg.payload** -
10 | * To pass a single parameter to an operation use `msg.payload` as your parameter (eg `{_id: 1243}`)
11 | * To pass multiple parameters to an operation fill `msg.payload` with an array
12 | * If you want to pass a single parameter WHICH IS AN ARRAY (eg as with `InserMany`), wrap your array in an outer array: `msg.payload` = `[[{_id: 1243}, {_id: 2345}]]`
13 |
14 | Passing the last operation parameter (callbak) is not supported, and will be stripped if provided.
15 |
16 | **URI** -
17 | Using a single URI field allows you to specify Host, Port and Database configuration as well as all other features that are supported by the [MongoClient.connect](http://mongodb.github.io/node-mongodb-native/2.1/api/MongoClient.html#.connect), such as Mongos Proxy Connection and Replicaset Server Connection - more information can be found [here](http://mongodb.github.io/node-mongodb-native/2.0/tutorials/connecting).
18 | Notice that the Username & Password fields did remain. They will still be saved as Node-RED credentials (i.e. kept private). If the Username is not empty or the Password is not empty, they will be escaped and added the the URI after the `mongodb://` prefix (separated by ':' and with a '@' after them). You can also leave these fields empty and enter the credentials directly in the URI, following the standard syntax: `mongodb://youruser:yourpassword@host1.yourmongoprovider.com:27017,host2.yourmongoprovider.com:27017/yourdb?replicaSet=foo`. **Do not enter your credentials both in the URI and the Username & Password fields** - this will create an invalid URI such as: `mongodb://youruserfromfield:yourpasswordformfield@youruserfromuri:yourpasswordfromuri@host1.yourmongoprovider.com:27017,host2.yourmongoprovider.com:27017/yourdb?replicaSet=foo`.
19 |
20 | **specifying authentication database** -
21 | most recent deployments of mongoDB store user credentials in a separate databse (usually `admin`) rather than allongside the data in each Db. Therefore you will likley need to provide a `authSource` parameter in your URI
22 | eg: `mongodb://host1.yourmongoprovider.com:27017/yourdb?ssl=true&authSource=admin&replicaSet=foo`
23 |
24 | **Parallelism Limit** - Sending a lot of commands in a short time to the underlying mongodb-native driver, without waiting for their response, may cause serious problems and slow down the whole app.
25 | This has probably something to do with the connection sockets being clogged and their cache getting filled.
26 | This option allows to limit the number of operations that are sent before getting a response.
27 | For example, if the parallelism-limit is set to 5 and we are making 7 operations in a short period of time, the first 5 operations will start immediately, but the 6th and 7th operations will wait in a queue.
28 | The 6th operation will start only when one of the first 5 operations has finished.
29 | Similarly, the 7th operation will start only when another operation has finished.
30 |
31 | **db & collection operations** - These operations will simply pass the db/collection instance, so they can be used directly (for example, in function nodes).
32 | The db instance is the same one that node-red-contrib-mongodb3 caches and shares between all relevant nodes - if you disconnect it, all the other mongodb3 nodes will fail.
33 | Furthermore, the parallelism-limit does not consider the future operations that you will do with the db/collection instances.
34 | However, if there are many parallel operations, requesting the db/collection will block until some of these operations finish.
35 |
36 | # Change Log
37 | ## 2.0
38 | BREAKING CHANGES : driver response props are now (correctly) added to message.payload, thus chaning the response shape
39 | see https://github.com/ozomer/node-red-contrib-mongodb2/issues/34
40 |
41 | `1.0` message shape:
42 | ```
43 | msg
44 | |_ payload
45 | |_ ok
46 | |_ n
47 | |_ opTime
48 | |_ electionId
49 | |_ operationTime
50 | |_ "$clusterTime
51 | ```
52 | `2.0` message shape (example for `find()`):
53 | ```
54 | msg
55 | |_ payload
56 | |_ insertedCount
57 | |_ ops
58 | |_ insertedIds
59 | |_ result
60 | |_ ok
61 | |_ n
62 | |_ opTime
63 | |_ electionId
64 | |_ operationTime
65 | |_ "$clusterTime
66 |
67 | ```
68 |
69 | ## Original creation 1.0.0
70 | MongoDB 3 driver is originally based on [MongoDB 2 driver node for Node-RED](https://www.npmjs.com/package/node-red-contrib-mongodb2), and therefore is placed in the same github repository: (https://github.com/ozomer/node-red-contrib-mongodb2)
71 | The very-similar MongoDB 2 driver is in the same git repository, under the node-red-contrib-mongodb2 branch (not the master branch).
72 |
73 |
--------------------------------------------------------------------------------
/mongodb/mongodb.html:
--------------------------------------------------------------------------------
1 |
16 |
17 |
43 |
44 |
75 |
76 |
106 |
107 |
113 |
114 |
209 |
--------------------------------------------------------------------------------
/mongodb/mongodb.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright 2018 Awear Solutions Ltd.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | **/
16 |
17 | module.exports = function(RED) {
18 | "use strict";
19 | const EventEmitter = require('events').EventEmitter;
20 | const appEnv = require('cfenv').getAppEnv();
21 | const mongodb = require('mongodb');
22 | const forEachIteration = new Error("node-red-contrib-mongodb3 forEach iteration");
23 | const forEachEnd = new Error("node-red-contrib-mongodb3 forEach end");
24 |
25 | let services = [];
26 | Object.keys(appEnv.services).forEach(function(label) {
27 | if ((/^mongo/i).test(label)) {
28 | services = services.concat(appEnv.services[label].map(function(service) {
29 | return {
30 | "name": service.name,
31 | "label": service.label
32 | };
33 | }));
34 | }
35 | });
36 |
37 | const operations = {};
38 | Object.keys(mongodb.Collection.prototype).forEach(function(operationName) {
39 | if ('function' == typeof Object.getOwnPropertyDescriptor(mongodb.Collection.prototype, operationName).value) {
40 | operations[operationName] = mongodb.Collection.prototype[operationName];
41 | }
42 | });
43 | // We don't want to pass the find-operation's cursor directly.
44 | delete operations.find;
45 |
46 | operations['find.toArray'] = function() {
47 | const args = Array.prototype.slice.call(arguments, 0);
48 | const callback = args.pop();
49 | mongodb.Collection.prototype.find.apply(this, args).toArray(callback);
50 | };
51 | operations['find.forEach'] = function() {
52 | const args = Array.prototype.slice.call(arguments, 0);
53 | const callback = args.pop();
54 | mongodb.Collection.prototype.find.apply(this, args).forEach(function(doc) {
55 | return callback(forEachIteration, doc);
56 | }, function(err) {
57 | return callback(err || forEachEnd);
58 | });
59 | };
60 |
61 | // We don't want to pass the aggregate's cursor directly.
62 | delete operations.aggregate;
63 | operations['aggregate.toArray'] = function() {
64 | const args = Array.prototype.slice.call(arguments, 0);
65 | const callback = args.pop();
66 | mongodb.Collection.prototype.aggregate.apply(this, args).toArray(callback);
67 | };
68 | operations['aggregate.forEach'] = function() {
69 | const args = Array.prototype.slice.call(arguments, 0);
70 | const callback = args.pop();
71 | mongodb.Collection.prototype.aggregate.apply(this, args).forEach(function(doc) {
72 | return callback(forEachIteration, doc);
73 | }, function(err) {
74 | return callback(err || forEachEnd);
75 | });
76 | };
77 |
78 | // We don't want to pass the listIndexes's cursor directly.
79 | delete operations.listIndexes;
80 | operations['listIndexes.toArray'] = function() {
81 | const args = Array.prototype.slice.call(arguments, 0);
82 | const callback = args.pop();
83 | mongodb.Collection.prototype.listIndexes.apply(this, args).toArray(callback);
84 | };
85 | operations['listIndexes.forEach'] = function() {
86 | const args = Array.prototype.slice.call(arguments, 0);
87 | const callback = args.pop();
88 | mongodb.Collection.prototype.listIndexes.apply(this, args).forEach(function(doc) {
89 | return callback(forEachIteration, doc);
90 | }, function(err) {
91 | return callback(err || forEachEnd);
92 | });
93 | };
94 |
95 | // We don't want to pass the listCollections's cursor directly.
96 | delete operations.listCollections;
97 | operations['db.listCollections.toArray'] = function() {
98 | const args = Array.prototype.slice.call(arguments, 0);
99 | const callback = args.pop();
100 | mongodb.Db.prototype.listCollections.apply(this, args).toArray(callback);
101 | };
102 | operations['db.listCollections.forEach'] = function() {
103 | const args = Array.prototype.slice.call(arguments, 0);
104 | const callback = args.pop();
105 | mongodb.Db.prototype.listCollections.apply(this, args).forEach(
106 | function(doc) {
107 | return callback(forEachIteration, doc);
108 | },
109 | function(err) {
110 | return callback(err || forEachEnd);
111 | }
112 | );
113 | };
114 |
115 | operations.db = function(callback) {
116 | return callback(null, this);
117 | };
118 |
119 | operations.collection = function(callback) {
120 | return callback(null, this);
121 | };
122 |
123 | RED.nodes.registerType("mongodb3", function MongoConfigNode(n) {
124 | RED.nodes.createNode(this, n);
125 | this.uri = '' + n.uri;
126 | if (this.credentials.user || this.credentials.password) {
127 | this.uri = this.uri.replace(/^mongodb:\/\//, 'mongodb://' + encodeURIComponent(this.credentials.user) + ':' + encodeURIComponent(this.credentials.password) + '@');
128 | }
129 | this.name = n.name;
130 | this.parallelism = n.parallelism * 1;
131 | if (!!n.options) {
132 | try {
133 | this.options = JSON.parse(n.options);
134 | } catch (err) {
135 | this.error("Failed to parse options: " + err);
136 | }
137 | }
138 | this.deploymentId = (1 + Math.random() * 0xffffffff).toString(16).replace('.', '');
139 | }, {
140 | "credentials": {
141 | "user": {
142 | "type": "text"
143 | },
144 | "password": {
145 | "type": "password"
146 | }
147 | }
148 | });
149 |
150 | RED.httpAdmin.get('/mongodb3/vcap', function(req, res) {
151 | res.json(services);
152 | });
153 |
154 | RED.httpAdmin.get('/mongodb3/operations', function(req, res) {
155 | res.json(Object.keys(operations).sort());
156 | });
157 |
158 | const mongoPool = {};
159 |
160 | function getClient(config) {
161 | let poolCell = mongoPool['#' + config.deploymentId];
162 | if (!poolCell) {
163 | mongoPool['#' + config.deploymentId] = poolCell = {
164 | "instances": 0,
165 | // es6-promise. A client will be called only once.
166 | "promise": mongodb.MongoClient.connect(config.uri, config.options || {}).then(function(client) {
167 | const dbName = decodeURIComponent((config.uri.match(/^.*\/([^?]*)\??.*$/) || [])[1] || '');
168 | const db = client.db(dbName);
169 | return {
170 | "client": client,
171 | "db": db,
172 | "queue": [],
173 | "parallelOps": 0 // current number of operations
174 | };
175 | })
176 | };
177 | }
178 | poolCell.instances++;
179 | return poolCell.promise;
180 | }
181 |
182 | function closeClient(config) {
183 | const poolCell = mongoPool['#' + config.deploymentId];
184 | if (!poolCell) {
185 | return;
186 | }
187 | poolCell.instances--;
188 | if (poolCell.instances === 0) {
189 | delete mongoPool['#' + config.deploymentId];
190 | poolCell.promise.then(function(client) {
191 | client.client.close().catch(function(err) {
192 | node.error("Error while closing client: " + err);
193 | });
194 | }, function() { // ignore error
195 | // db-client was not created in the first place.
196 | });
197 | }
198 | }
199 |
200 | RED.nodes.registerType("mongodb3 in", function MongoInputNode(n) {
201 | RED.nodes.createNode(this, n);
202 | this.configNode = n.configNode;
203 | this.collection = n.collection;
204 | this.operation = n.operation;
205 | if (n.service == "_ext_") {
206 | // Refer to the config node's id, uri, options, parallelism and warn function.
207 | this.config = RED.nodes.getNode(this.configNode);
208 | } else if (n.service) {
209 | const configService = appEnv.getService(n.service);
210 | if (configService) {
211 | // Only a uri is defined.
212 | this.config = {
213 | "deploymentId": 'service:' + n.service, // different from node-red deployment ids.
214 | "uri": configService.credentials.uri || configService.credentials.url
215 | };
216 | }
217 | }
218 | if (!this.config || !this.config.uri) {
219 | this.error("missing mongodb3 configuration");
220 | return;
221 | }
222 | const node = this;
223 | getClient(node.config).then(function(client) {
224 | let nodeCollection;
225 | if (node.collection) {
226 | nodeCollection = client.db.collection(node.collection);
227 | }
228 | let nodeOperation;
229 | if (node.operation) {
230 | nodeOperation = operations[node.operation];
231 | }
232 | node.on('input', function(msg) {
233 | if (node.config.parallelism && (node.config.parallelism > 0) && (client.parallelOps >= node.config.parallelism)) {
234 | // msg cannot be handled right now - push to queue.
235 | client.queue.push({
236 | "node_id": node.id,
237 | "msg": msg
238 | });
239 | return;
240 | }
241 | client.parallelOps += 1;
242 | setImmediate(function() {
243 | handleMessage(msg);
244 | });
245 | });
246 |
247 | node.on('node-red-contrib-mongodb3 handleMessage', function(msg) {
248 | // see: messageHandlingCompleted
249 | setImmediate(function(){
250 | handleMessage(msg);
251 | });
252 | });
253 |
254 | function handleMessage(msg) {
255 | let operation = nodeOperation;
256 | if (!operation && msg.operation) {
257 | operation = operations[msg.operation];
258 | }
259 | if (!operation) {
260 | node.error("No operation defined", msg);
261 | return messageHandlingCompleted();
262 | }
263 | let collection; // stays undefined in the case of "db" operation.
264 | if (
265 | operation != operations.db &&
266 | operation != operations['db.listCollections.toArray'] &&
267 | operation != operations['db.listCollections.forEach']
268 | ) {
269 | collection = nodeCollection;
270 | if (!collection && msg.collection) {
271 | collection = client.db.collection(msg.collection);
272 | }
273 | if (!collection) {
274 | node.error("No collection defined", msg);
275 | return messageHandlingCompleted();
276 | }
277 | }
278 |
279 | delete msg.collection;
280 | delete msg.operation;
281 | let args = msg.payload;
282 | if (!Array.isArray(args)) {
283 | args = [args];
284 | }
285 | if (args.length === 0) {
286 | // All operations can accept one argument (some can accept more).
287 | // Some operations don't expect a single callback argument.
288 | args.push(undefined);
289 | }
290 | if ((operation.length > 0) && (args.length > operation.length - 1)) {
291 | // The operation was defined with arguments, thus it may not
292 | // assume that the last argument is the callback.
293 | // We must not pass too many arguments to the operation.
294 | args = args.slice(0, operation.length - 1);
295 | }
296 | profiling.requests += 1;
297 | debounceProfilingStatus();
298 | try {
299 | operation.apply(collection || client.db, args.concat(function(err, response) {
300 | if (err && (forEachIteration != err) && (forEachEnd != err)) {
301 | profiling.error += 1;
302 | debounceProfilingStatus();
303 | node.error(err, msg);
304 | return messageHandlingCompleted();
305 | }
306 | if (forEachEnd != err) {
307 | if (!!response) {
308 | // Some operations return a Connection object with the result.
309 | // Passing this large connection object might be heavy - it will
310 | // be cloned over and over by Node-RED, and there is no reason
311 | // the typical user will need it.
312 | // The mongodb package does not export the Connection prototype-function.
313 | // Instead of loading the Connection prototype-function from the
314 | // internal libs (which might change their path), I use the fact
315 | // that it inherits EventEmitter.
316 | if (response.connection instanceof EventEmitter) {
317 | delete response.connection;
318 | }
319 | if (response.result && response.result.connection instanceof EventEmitter) {
320 | delete response.result.connection;
321 | }
322 | }
323 | // `response` is an instance of CommandResult, and does not seem to have the standard Object methods,
324 | // which means that some props are not correctly being forwarded to msg.payload (eg "ops" ouputted from `insertOne`)
325 | // cloning the object fixes that.
326 | response = Object.assign({}, response);
327 | // response.message includes info about the DB op, but is large and never used (like the connection)
328 | delete response.message;
329 |
330 | // send msg (when err == forEachEnd, this is just a forEach completion).
331 | if (forEachIteration == err) {
332 | // Clone, so we can send the same message again with a different payload
333 | // in each iteration.
334 | const messageToSend = RED.util.cloneMessage(msg);
335 | messageToSend.payload = response;
336 | node.send(messageToSend);
337 | } else {
338 | // No need to clone - the same message will not be sent again.
339 | msg.payload = response;
340 | node.send(msg);
341 | }
342 | }
343 | if (forEachIteration != err) {
344 | // clear status
345 | profiling.success += 1;
346 | debounceProfilingStatus();
347 | messageHandlingCompleted();
348 | }
349 | }));
350 | } catch(err) {
351 | profiling.error += 1;
352 | debounceProfilingStatus();
353 | node.error(err, msg);
354 | return messageHandlingCompleted();
355 | }
356 | }
357 | function messageHandlingCompleted() {
358 | setImmediate(handlePendingMessageOnDemand);
359 | }
360 | function handlePendingMessageOnDemand() {
361 | while (client.queue.length > 0) {
362 | const pendingMessage = client.queue.shift();
363 | const targetNode = RED.nodes.getNode(pendingMessage.node_id);
364 | if (!targetNode) {
365 | // The node was removed before handling the pending message.
366 | // This is just a warning because a similar scenario can happen if
367 | // a node was removed just before handling a message that was sent
368 | // to it.
369 | const warningMessage = "Node " + pendingMessage.node_id + " was removed while having a pending message";
370 | if (node.config.warn) {
371 | // The warning will appear from the config node, because the target
372 | // node cannot be found.
373 | node.config.warn(warningMessage, pendingMessage.msg);
374 | } else {
375 | // If the node was configured with a service instead of a config node,
376 | // the warning will appear from the current node.
377 | // This shouldn't happen in real life because in such scenario
378 | // the parallelism limit is not configured.
379 | node.warn(warningMessage, pendingMessage.msg);
380 | }
381 | continue;
382 | }
383 | // Handle the pending message.
384 | if (!targetNode.emit('node-red-contrib-mongodb3 handleMessage', pendingMessage.msg)) {
385 | // Safety check - if emit() returned false it means there are no listeners to the event.
386 | // Was the target node closed?
387 | // This shouldn't happen normally, but if it does, we must try to handle the next message in the queue.
388 | const errorMessage = "Node " + pendingMessage.node_id + " could not handle the pending message";
389 | if (node.config.error) {
390 | node.config.error(errorMessage, pendingMessage.msg);
391 | } else {
392 | node.error(errorMessage, pendingMessage.msg);
393 | }
394 | continue;
395 | }
396 | // Another message is being handled. The number of parallel ops does not change.
397 | return;
398 | }
399 | // The queue is empty. The number of parallel ops has reduced.
400 | if (client.parallelOps <= 0) {
401 | return node.error("Something went wrong with node-red-contrib-mongodb3 parallel-ops count");
402 | }
403 | client.parallelOps -= 1;
404 | }
405 | }, function(err) {
406 | // Failed to create db client
407 | node.error(err);
408 | });
409 | const profiling = {
410 | "requests": 0,
411 | "success": 0,
412 | "error": 0
413 | };
414 | function profilingStatus() {
415 | node.status({
416 | "fill": "yellow",
417 | "shape": "dot",
418 | "text": "" + profiling.requests + ", success: " + profiling.success + ", error: " + profiling.error
419 | });
420 | }
421 |
422 | let debouncer = null;
423 | function debounceProfilingStatus() {
424 | if (debouncer) {
425 | return;
426 | }
427 | // show curent status, create debouncer.
428 | profilingStatus();
429 | debouncer = setTimeout(function() {
430 | profilingStatus(); // should we call only if there was a change?
431 | debouncer = null;
432 | }, 1000);
433 | }
434 | node.on('close', function() {
435 | if (node.config) {
436 | closeClient(node.config);
437 | }
438 | node.removeAllListeners('node-red-contrib-mongodb3 handleMessage');
439 | if (debouncer) {
440 | clearTimeout(debouncer);
441 | }
442 | });
443 | });
444 | };
445 |
--------------------------------------------------------------------------------
/package-lock.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "node-red-contrib-mongodb3",
3 | "version": "2.0.1",
4 | "lockfileVersion": 1,
5 | "requires": true,
6 | "dependencies": {
7 | "argparse": {
8 | "version": "1.0.10",
9 | "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz",
10 | "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==",
11 | "requires": {
12 | "sprintf-js": "~1.0.2"
13 | }
14 | },
15 | "bson": {
16 | "version": "1.1.0",
17 | "resolved": "https://registry.npmjs.org/bson/-/bson-1.1.0.tgz",
18 | "integrity": "sha512-9Aeai9TacfNtWXOYarkFJRW2CWo+dRon+fuLZYJmvLV3+MiUp0bEI6IAZfXEIg7/Pl/7IWlLaDnhzTsD81etQA=="
19 | },
20 | "cfenv": {
21 | "version": "1.0.4",
22 | "resolved": "https://registry.npmjs.org/cfenv/-/cfenv-1.0.4.tgz",
23 | "integrity": "sha1-uXoe694lXs7YNnoPSvvC+FQ44LQ=",
24 | "requires": {
25 | "js-yaml": "3.7.x",
26 | "ports": "1.1.x",
27 | "underscore": "1.8.x"
28 | }
29 | },
30 | "esprima": {
31 | "version": "2.7.3",
32 | "resolved": "https://registry.npmjs.org/esprima/-/esprima-2.7.3.tgz",
33 | "integrity": "sha1-luO3DVd59q1JzQMmc9HDEnZ7pYE="
34 | },
35 | "js-yaml": {
36 | "version": "3.7.0",
37 | "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.7.0.tgz",
38 | "integrity": "sha1-XJZ93YN6m/3KXy3oQlOr6KHAO4A=",
39 | "requires": {
40 | "argparse": "^1.0.7",
41 | "esprima": "^2.6.0"
42 | }
43 | },
44 | "memory-pager": {
45 | "version": "1.4.0",
46 | "resolved": "https://registry.npmjs.org/memory-pager/-/memory-pager-1.4.0.tgz",
47 | "integrity": "sha512-ycuyV5gKpZln7HB/A11wCpAxEY9VQ2EhYU1F56pUAxvmj6OyOHtB9tkLLjAyFsPdghSP2S3Ujk3aYJCusgiMZg==",
48 | "optional": true
49 | },
50 | "mongodb": {
51 | "version": "3.1.10",
52 | "resolved": "https://registry.npmjs.org/mongodb/-/mongodb-3.1.10.tgz",
53 | "integrity": "sha512-Uml42GeFxhTGQVml1XQ4cD0o/rp7J2ROy0fdYUcVitoE7vFqEhKH4TYVqRDpQr/bXtCJVxJdNQC1ntRxNREkPQ==",
54 | "requires": {
55 | "mongodb-core": "3.1.9",
56 | "safe-buffer": "^5.1.2"
57 | }
58 | },
59 | "mongodb-core": {
60 | "version": "3.1.9",
61 | "resolved": "https://registry.npmjs.org/mongodb-core/-/mongodb-core-3.1.9.tgz",
62 | "integrity": "sha512-MJpciDABXMchrZphh3vMcqu8hkNf/Mi+Gk6btOimVg1XMxLXh87j6FAvRm+KmwD1A9fpu3qRQYcbQe4egj23og==",
63 | "requires": {
64 | "bson": "^1.1.0",
65 | "require_optional": "^1.0.1",
66 | "safe-buffer": "^5.1.2",
67 | "saslprep": "^1.0.0"
68 | }
69 | },
70 | "ports": {
71 | "version": "1.1.0",
72 | "resolved": "https://registry.npmjs.org/ports/-/ports-1.1.0.tgz",
73 | "integrity": "sha1-twGqKF6V2ujJbNonUhdySh9/bGA="
74 | },
75 | "require_optional": {
76 | "version": "1.0.1",
77 | "resolved": "https://registry.npmjs.org/require_optional/-/require_optional-1.0.1.tgz",
78 | "integrity": "sha512-qhM/y57enGWHAe3v/NcwML6a3/vfESLe/sGM2dII+gEO0BpKRUkWZow/tyloNqJyN6kXSl3RyyM8Ll5D/sJP8g==",
79 | "requires": {
80 | "resolve-from": "^2.0.0",
81 | "semver": "^5.1.0"
82 | }
83 | },
84 | "resolve-from": {
85 | "version": "2.0.0",
86 | "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-2.0.0.tgz",
87 | "integrity": "sha1-lICrIOlP+h2egKgEx+oUdhGWa1c="
88 | },
89 | "safe-buffer": {
90 | "version": "5.1.2",
91 | "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
92 | "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g=="
93 | },
94 | "saslprep": {
95 | "version": "1.0.2",
96 | "resolved": "https://registry.npmjs.org/saslprep/-/saslprep-1.0.2.tgz",
97 | "integrity": "sha512-4cDsYuAjXssUSjxHKRe4DTZC0agDwsCqcMqtJAQPzC74nJ7LfAJflAtC1Zed5hMzEQKj82d3tuzqdGNRsLJ4Gw==",
98 | "optional": true,
99 | "requires": {
100 | "sparse-bitfield": "^3.0.3"
101 | }
102 | },
103 | "semver": {
104 | "version": "5.6.0",
105 | "resolved": "https://registry.npmjs.org/semver/-/semver-5.6.0.tgz",
106 | "integrity": "sha512-RS9R6R35NYgQn++fkDWaOmqGoj4Ek9gGs+DPxNUZKuwE183xjJroKvyo1IzVFeXvUrvmALy6FWD5xrdJT25gMg=="
107 | },
108 | "sparse-bitfield": {
109 | "version": "3.0.3",
110 | "resolved": "https://registry.npmjs.org/sparse-bitfield/-/sparse-bitfield-3.0.3.tgz",
111 | "integrity": "sha1-/0rm5oZWBWuks+eSqzM004JzyhE=",
112 | "optional": true,
113 | "requires": {
114 | "memory-pager": "^1.0.2"
115 | }
116 | },
117 | "sprintf-js": {
118 | "version": "1.0.3",
119 | "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz",
120 | "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw="
121 | },
122 | "underscore": {
123 | "version": "1.8.3",
124 | "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.8.3.tgz",
125 | "integrity": "sha1-Tz+1OxBuYJf8+ctBCfKl6b36UCI="
126 | }
127 | }
128 | }
129 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "node-red-contrib-mongodb3",
3 | "version": "2.0.1",
4 | "description": "MongoDB 3 driver node for Node-RED",
5 | "dependencies": {
6 | "cfenv": "^1.0.4",
7 | "mongodb": "^3.0.11"
8 | },
9 | "repository": {
10 | "type": "git",
11 | "url": "https://github.com/ozomer/node-red-contrib-mongodb2"
12 | },
13 | "author": {
14 | "name": "Oren Zomer",
15 | "email": "oren@awearsolutions.com"
16 | },
17 | "license": "Apache-2.0",
18 | "keywords": [
19 | "node-red",
20 | "bluemix",
21 | "mongodb"
22 | ],
23 | "node-red": {
24 | "nodes": {
25 | "mongodb": "mongodb/mongodb.js"
26 | }
27 | },
28 | "jshintConfig": {
29 | "undef": true,
30 | "unused": true,
31 | "node": true
32 | }
33 | }
34 |
--------------------------------------------------------------------------------