node-red-contrib-couchdb-nodes 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +311 -0
- package/package.json +56 -0
- package/src/nodes/create-db/couchdb-create-db.html +87 -0
- package/src/nodes/create-db/couchdb-create-db.js +52 -0
- package/src/nodes/delete-db/couchdb-delete-db.html +108 -0
- package/src/nodes/delete-db/couchdb-delete-db.js +59 -0
- package/src/nodes/get/couchdb-get.html +78 -0
- package/src/nodes/get/couchdb-get.js +63 -0
- package/src/nodes/index.js +33 -0
- package/src/nodes/insert/couchdb-insert.html +83 -0
- package/src/nodes/insert/couchdb-insert.js +64 -0
- package/src/nodes/list-dbs/couchdb-list-dbs.html +77 -0
- package/src/nodes/list-dbs/couchdb-list-dbs.js +48 -0
- package/src/nodes/list-docs/couchdb-list-docs.html +175 -0
- package/src/nodes/list-docs/couchdb-list-docs.js +101 -0
- package/src/nodes/query/couchdb-query.html +107 -0
- package/src/nodes/query/couchdb-query.js +67 -0
- package/src/nodes/server/couchdb-server.html +82 -0
- package/src/nodes/server/couchdb-server.js +17 -0
- package/src/nodes/update/couchdb-update.html +90 -0
- package/src/nodes/update/couchdb-update.js +73 -0
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
module.exports = function(RED) {
|
|
2
|
+
function CouchDBListDocsNode(config) {
|
|
3
|
+
RED.nodes.createNode(this, config);
|
|
4
|
+
const node = this;
|
|
5
|
+
const serverConfig = RED.nodes.getNode(config.server);
|
|
6
|
+
|
|
7
|
+
node.on('input', function(msg) {
|
|
8
|
+
if (!serverConfig) {
|
|
9
|
+
node.error("CouchDB server not configured");
|
|
10
|
+
msg.error = {error: "Server not configured"};
|
|
11
|
+
node.send([null, msg]);
|
|
12
|
+
return;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
const nano = require('nano');
|
|
16
|
+
const hostname = serverConfig.hostname;
|
|
17
|
+
const port = serverConfig.port || 5984;
|
|
18
|
+
const username = serverConfig.username;
|
|
19
|
+
const password = serverConfig.credentials.password;
|
|
20
|
+
|
|
21
|
+
const couchUrl = `http://${username}:${password}@${hostname}:${port}`;
|
|
22
|
+
const connection = nano(couchUrl);
|
|
23
|
+
|
|
24
|
+
// Get database name from config or message
|
|
25
|
+
const database = msg.database || config.database;
|
|
26
|
+
|
|
27
|
+
if (!database) {
|
|
28
|
+
node.error("Database name is required");
|
|
29
|
+
msg.error = {error: "Database name not specified"};
|
|
30
|
+
node.send([null, msg]);
|
|
31
|
+
return;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const db = connection.use(database);
|
|
35
|
+
|
|
36
|
+
// Build query options
|
|
37
|
+
const options = {};
|
|
38
|
+
|
|
39
|
+
// Pagination options
|
|
40
|
+
if (msg.limit !== undefined || config.limit) {
|
|
41
|
+
options.limit = parseInt(msg.limit || config.limit);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
if (msg.skip !== undefined) {
|
|
45
|
+
options.skip = parseInt(msg.skip);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
if (msg.startkey !== undefined) {
|
|
49
|
+
options.startkey = msg.startkey;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
if (msg.endkey !== undefined) {
|
|
53
|
+
options.endkey = msg.endkey;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// Include document content if requested
|
|
57
|
+
if (msg.includeDocs !== undefined) {
|
|
58
|
+
options.include_docs = msg.includeDocs;
|
|
59
|
+
} else if (config.includeDocs) {
|
|
60
|
+
options.include_docs = true;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// Descending order
|
|
64
|
+
if (msg.descending !== undefined) {
|
|
65
|
+
options.descending = msg.descending;
|
|
66
|
+
} else if (config.descending) {
|
|
67
|
+
options.descending = true;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
node.status({fill: "blue", shape: "dot", text: "listing..."});
|
|
71
|
+
|
|
72
|
+
db.list(options)
|
|
73
|
+
.then(result => {
|
|
74
|
+
node.status({fill: "green", shape: "dot", text: "success"});
|
|
75
|
+
msg.payload = result;
|
|
76
|
+
msg.rows = result.rows;
|
|
77
|
+
msg.totalRows = result.total_rows;
|
|
78
|
+
msg.offset = result.offset;
|
|
79
|
+
|
|
80
|
+
// If include_docs was true, extract just the docs
|
|
81
|
+
if (options.include_docs) {
|
|
82
|
+
msg.docs = result.rows.map(row => row.doc);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
node.send([msg, null]);
|
|
86
|
+
})
|
|
87
|
+
.catch(err => {
|
|
88
|
+
node.status({fill: "red", shape: "ring", text: "error"});
|
|
89
|
+
node.error("Failed to list documents: " + err.message);
|
|
90
|
+
msg.error = err;
|
|
91
|
+
node.send([null, msg]);
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
node.on("close", function() {
|
|
95
|
+
node.status({});
|
|
96
|
+
});
|
|
97
|
+
});
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
RED.nodes.registerType("couchdb-list-docs", CouchDBListDocsNode);
|
|
101
|
+
};
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
<script type="text/javascript">
|
|
2
|
+
RED.nodes.registerType('couchdb-query', {
|
|
3
|
+
category: 'couchdb',
|
|
4
|
+
color: '#ff6b00',
|
|
5
|
+
defaults: {
|
|
6
|
+
name: { value: '' },
|
|
7
|
+
server: { type: 'couchdb-server', required: true },
|
|
8
|
+
database: { value: '', required: true },
|
|
9
|
+
selector: { value: '{}' },
|
|
10
|
+
limit: { value: '100', validate: RED.validators.number() }
|
|
11
|
+
},
|
|
12
|
+
inputs: 1,
|
|
13
|
+
outputs: 2,
|
|
14
|
+
icon: 'db.png',
|
|
15
|
+
label: function() {
|
|
16
|
+
return this.name || 'CouchDB Query';
|
|
17
|
+
},
|
|
18
|
+
oneditprepare: function() {
|
|
19
|
+
// Validate JSON
|
|
20
|
+
$('#node-input-selector').on('change', function() {
|
|
21
|
+
try {
|
|
22
|
+
JSON.parse($(this).val());
|
|
23
|
+
$(this).removeClass('input-error');
|
|
24
|
+
} catch(e) {
|
|
25
|
+
$(this).addClass('input-error');
|
|
26
|
+
}
|
|
27
|
+
});
|
|
28
|
+
}
|
|
29
|
+
});
|
|
30
|
+
</script>
|
|
31
|
+
|
|
32
|
+
<script type="text/x-red" data-template-name="couchdb-query">
|
|
33
|
+
<div class="form-row">
|
|
34
|
+
<label for="node-input-name"><i class="fa fa-tag"></i> Name</label>
|
|
35
|
+
<input type="text" id="node-input-name" placeholder="Query Documents">
|
|
36
|
+
</div>
|
|
37
|
+
|
|
38
|
+
<div class="form-row">
|
|
39
|
+
<label for="node-input-server"><i class="fa fa-server"></i> Server</label>
|
|
40
|
+
<input type="text" id="node-input-server">
|
|
41
|
+
</div>
|
|
42
|
+
|
|
43
|
+
<div class="form-row">
|
|
44
|
+
<label for="node-input-database"><i class="fa fa-database"></i> Database</label>
|
|
45
|
+
<input type="text" id="node-input-database" placeholder="e.g. node-red-flows">
|
|
46
|
+
</div>
|
|
47
|
+
|
|
48
|
+
<div class="form-row">
|
|
49
|
+
<label for="node-input-selector"><i class="fa fa-filter"></i> Selector (JSON)</label>
|
|
50
|
+
<textarea id="node-input-selector" style="width: 100%; height: 100px" placeholder='{"type": "flow"}'>{"type": "flow"}</textarea>
|
|
51
|
+
</div>
|
|
52
|
+
|
|
53
|
+
<div class="form-row">
|
|
54
|
+
<label for="node-input-limit"><i class="fa fa-list"></i> Limit</label>
|
|
55
|
+
<input type="number" id="node-input-limit" min="1" max="10000">
|
|
56
|
+
</div>
|
|
57
|
+
|
|
58
|
+
<div class="form-tips">
|
|
59
|
+
<p><strong>Selector:</strong> Mango query selector (JSON format)</p>
|
|
60
|
+
<p><strong>Output 1 (Success):</strong> msg.payload with results array</p>
|
|
61
|
+
<p><strong>Output 2 (Error):</strong> msg with error information</p>
|
|
62
|
+
</div>
|
|
63
|
+
</script>
|
|
64
|
+
|
|
65
|
+
<script type="text/x-red" data-help-name="couchdb-query">
|
|
66
|
+
<p>Queries documents from a CouchDB database using Mango queries.</p>
|
|
67
|
+
|
|
68
|
+
<h3>Inputs</h3>
|
|
69
|
+
<dl class="message-properties">
|
|
70
|
+
<dt class="optional">selector <span class="property-type">object</span></dt>
|
|
71
|
+
<dd>Mango query selector object (overrides node config if provided)</dd>
|
|
72
|
+
<dt class="optional">limit <span class="property-type">number</span></dt>
|
|
73
|
+
<dd>Maximum number of documents to return (overrides node config if provided)</dd>
|
|
74
|
+
<dt class="optional">database <span class="property-type">string</span></dt>
|
|
75
|
+
<dd>Database name (overrides node config if provided)</dd>
|
|
76
|
+
</dl>
|
|
77
|
+
|
|
78
|
+
<h3>Outputs</h3>
|
|
79
|
+
<ol class="node-ports">
|
|
80
|
+
<li>Success Output
|
|
81
|
+
<dl class="message-properties">
|
|
82
|
+
<dt>payload <span class="property-type">array</span></dt>
|
|
83
|
+
<dd>Array of documents matching the query</dd>
|
|
84
|
+
<dt>count <span class="property-type">number</span></dt>
|
|
85
|
+
<dd>Number of documents found</dd>
|
|
86
|
+
<dt>result <span class="property-type">object</span></dt>
|
|
87
|
+
<dd>Full query result with metadata</dd>
|
|
88
|
+
</dl>
|
|
89
|
+
</li>
|
|
90
|
+
<li>Error Output
|
|
91
|
+
<dl class="message-properties">
|
|
92
|
+
<dt>error <span class="property-type">object</span></dt>
|
|
93
|
+
<dd>Error details if query fails</dd>
|
|
94
|
+
</dl>
|
|
95
|
+
</li>
|
|
96
|
+
</ol>
|
|
97
|
+
|
|
98
|
+
<h3>Selector Examples</h3>
|
|
99
|
+
<p><strong>Find by field value:</strong></p>
|
|
100
|
+
<pre>{"type": "sensor"}</pre>
|
|
101
|
+
|
|
102
|
+
<p><strong>Find with conditions:</strong></p>
|
|
103
|
+
<pre>{"type": "sensor", "active": true}</pre>
|
|
104
|
+
|
|
105
|
+
<p><strong>Find with comparison:</strong></p>
|
|
106
|
+
<pre>{"temperature": {"\$gt": 20}}</pre>
|
|
107
|
+
</script>
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
module.exports = function(RED) {
|
|
2
|
+
function CouchDBQueryNode(config) {
|
|
3
|
+
RED.nodes.createNode(this, config);
|
|
4
|
+
const node = this;
|
|
5
|
+
|
|
6
|
+
const serverConfig = RED.nodes.getNode(config.server);
|
|
7
|
+
if (!serverConfig) {
|
|
8
|
+
node.error("CouchDB server not configured");
|
|
9
|
+
return;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
const nano = require('nano');
|
|
13
|
+
const url = `http://${serverConfig.username}:${serverConfig.password}@${serverConfig.hostname}:${serverConfig.port}`;
|
|
14
|
+
const couchdb = nano(url);
|
|
15
|
+
|
|
16
|
+
node.on("input", function(msg) {
|
|
17
|
+
const database = config.database || msg.database;
|
|
18
|
+
let selector = config.selector ? JSON.parse(config.selector) : msg.selector;
|
|
19
|
+
const limit = config.limit || msg.limit || 100;
|
|
20
|
+
|
|
21
|
+
if (!database) {
|
|
22
|
+
node.error("Database not specified", msg);
|
|
23
|
+
return;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
if (!selector || typeof selector !== 'object') {
|
|
27
|
+
node.error("Selector must be a valid object", msg);
|
|
28
|
+
return;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
try {
|
|
32
|
+
const db = couchdb.db.use(database);
|
|
33
|
+
|
|
34
|
+
db.find({
|
|
35
|
+
selector: selector,
|
|
36
|
+
limit: limit
|
|
37
|
+
}, (err, result) => {
|
|
38
|
+
if (err) {
|
|
39
|
+
node.error(`Query failed: ${err.message}`, msg);
|
|
40
|
+
msg.error = {
|
|
41
|
+
code: err.code,
|
|
42
|
+
message: err.message,
|
|
43
|
+
status: err.status
|
|
44
|
+
};
|
|
45
|
+
node.send([null, msg]);
|
|
46
|
+
} else {
|
|
47
|
+
msg.payload = result.docs;
|
|
48
|
+
msg.count = result.docs.length;
|
|
49
|
+
msg.result = result;
|
|
50
|
+
node.send([msg, null]);
|
|
51
|
+
node.status({ fill: "green", shape: "dot", text: `Found: ${result.docs.length} docs` });
|
|
52
|
+
}
|
|
53
|
+
});
|
|
54
|
+
} catch (error) {
|
|
55
|
+
node.error(`Connection error: ${error.message}`, msg);
|
|
56
|
+
msg.error = error;
|
|
57
|
+
node.send([null, msg]);
|
|
58
|
+
}
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
node.on("close", function() {
|
|
62
|
+
node.status({ fill: "grey", shape: "ring", text: "disconnected" });
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
RED.nodes.registerType("couchdb-query", CouchDBQueryNode);
|
|
67
|
+
};
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
<script type="text/javascript">
|
|
2
|
+
RED.nodes.registerType('couchdb-server', {
|
|
3
|
+
category: 'config',
|
|
4
|
+
defaults: {
|
|
5
|
+
hostname: { value: 'localhost', required: true },
|
|
6
|
+
port: { value: 5984, required: true, validate: RED.validators.number() },
|
|
7
|
+
username: { value: '', required: true }
|
|
8
|
+
},
|
|
9
|
+
credentials: {
|
|
10
|
+
password: { type: 'password' }
|
|
11
|
+
},
|
|
12
|
+
label: function() {
|
|
13
|
+
return this.username + '@' + this.hostname + ':' + this.port;
|
|
14
|
+
}
|
|
15
|
+
});
|
|
16
|
+
</script>
|
|
17
|
+
|
|
18
|
+
<script type="text/x-red" data-template-name="couchdb-server">
|
|
19
|
+
<div class="form-row">
|
|
20
|
+
<label for="node-config-input-hostname"><i class="fa fa-address-book"></i> Hostname</label>
|
|
21
|
+
<input type="text" id="node-config-input-hostname" placeholder="localhost" style="width: 70%;">
|
|
22
|
+
</div>
|
|
23
|
+
|
|
24
|
+
<div class="form-row">
|
|
25
|
+
<label for="node-config-input-port"><i class="fa fa-plug"></i> Port</label>
|
|
26
|
+
<input type="number" id="node-config-input-port" placeholder="5984" style="width: 70%;">
|
|
27
|
+
</div>
|
|
28
|
+
|
|
29
|
+
<div class="form-row">
|
|
30
|
+
<label for="node-config-input-username"><i class="fa fa-user"></i> Username</label>
|
|
31
|
+
<input type="text" id="node-config-input-username" placeholder="admin" style="width: 70%;">
|
|
32
|
+
</div>
|
|
33
|
+
|
|
34
|
+
<div class="form-row">
|
|
35
|
+
<label for="node-config-input-password"><i class="fa fa-lock"></i> Password</label>
|
|
36
|
+
<input type="password" id="node-config-input-password" placeholder="password" style="width: 70%;">
|
|
37
|
+
</div>
|
|
38
|
+
|
|
39
|
+
<div class="form-tips">
|
|
40
|
+
<p><strong>Hostname:</strong> CouchDB server address (e.g., localhost, couchdb.example.com)</p>
|
|
41
|
+
<p><strong>Port:</strong> CouchDB port (default 5984)</p>
|
|
42
|
+
<p><strong>Username:</strong> CouchDB admin or valid user account</p>
|
|
43
|
+
<p><strong>Password:</strong> Will be stored securely in credentials</p>
|
|
44
|
+
</div>
|
|
45
|
+
</script>
|
|
46
|
+
|
|
47
|
+
<script type="text/x-red" data-help-name="couchdb-server">
|
|
48
|
+
<p>Configuration node for connecting to a CouchDB server.</p>
|
|
49
|
+
<p>This node stores the CouchDB connection details (hostname, port, username, password)
|
|
50
|
+
and is used by all CouchDB operation nodes (Get, Insert, Update, Query).</p>
|
|
51
|
+
|
|
52
|
+
<h3>Configuration</h3>
|
|
53
|
+
<dl class="message-properties">
|
|
54
|
+
<dt>Hostname</dt>
|
|
55
|
+
<dd>The hostname or IP address of the CouchDB server</dd>
|
|
56
|
+
<dt>Port</dt>
|
|
57
|
+
<dd>The port CouchDB is listening on (typically 5984)</dd>
|
|
58
|
+
<dt>Username</dt>
|
|
59
|
+
<dd>Valid CouchDB user with database access</dd>
|
|
60
|
+
<dt>Password</dt>
|
|
61
|
+
<dd>Password for the user (stored securely in Node-RED)</dd>
|
|
62
|
+
</dl>
|
|
63
|
+
|
|
64
|
+
<h3>Examples</h3>
|
|
65
|
+
<h4>Local Development</h4>
|
|
66
|
+
<ul>
|
|
67
|
+
<li>Hostname: <code>localhost</code></li>
|
|
68
|
+
<li>Port: <code>5984</code></li>
|
|
69
|
+
<li>Username: <code>admin</code></li>
|
|
70
|
+
</ul>
|
|
71
|
+
|
|
72
|
+
<h4>Kubernetes</h4>
|
|
73
|
+
<ul>
|
|
74
|
+
<li>Hostname: <code>node-red-kanr-couchdb.node-red-dev.svc.cluster.local</code></li>
|
|
75
|
+
<li>Port: <code>5984</code></li>
|
|
76
|
+
<li>Username: <code>admin</code></li>
|
|
77
|
+
</ul>
|
|
78
|
+
|
|
79
|
+
<h3>Using with Operation Nodes</h3>
|
|
80
|
+
<p>Once created, select this server configuration in any CouchDB operation node
|
|
81
|
+
(Get, Insert, Update, Query). All nodes will use the same connection.</p>
|
|
82
|
+
</script>
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
module.exports = function(RED) {
|
|
2
|
+
'use strict';
|
|
3
|
+
|
|
4
|
+
function CouchDBServerNode(n) {
|
|
5
|
+
RED.nodes.createNode(this, n);
|
|
6
|
+
this.hostname = n.hostname;
|
|
7
|
+
this.port = n.port || 5984;
|
|
8
|
+
this.username = n.username;
|
|
9
|
+
this.password = this.credentials.password;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
RED.nodes.registerType('couchdb-server', CouchDBServerNode, {
|
|
13
|
+
credentials: {
|
|
14
|
+
password: { type: 'password' }
|
|
15
|
+
}
|
|
16
|
+
});
|
|
17
|
+
};
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
<script type="text/javascript">
|
|
2
|
+
RED.nodes.registerType('couchdb-update', {
|
|
3
|
+
category: 'couchdb',
|
|
4
|
+
color: '#ff6b00',
|
|
5
|
+
defaults: {
|
|
6
|
+
name: { value: '' },
|
|
7
|
+
server: { type: 'couchdb-server', required: true },
|
|
8
|
+
database: { value: '', required: true }
|
|
9
|
+
},
|
|
10
|
+
inputs: 1,
|
|
11
|
+
outputs: 2,
|
|
12
|
+
icon: 'db.png',
|
|
13
|
+
label: function() {
|
|
14
|
+
return this.name || 'CouchDB Update';
|
|
15
|
+
}
|
|
16
|
+
});
|
|
17
|
+
</script>
|
|
18
|
+
|
|
19
|
+
<script type="text/x-red" data-template-name="couchdb-update">
|
|
20
|
+
<div class="form-row">
|
|
21
|
+
<label for="node-input-name"><i class="fa fa-tag"></i> Name</label>
|
|
22
|
+
<input type="text" id="node-input-name" placeholder="Update Document">
|
|
23
|
+
</div>
|
|
24
|
+
|
|
25
|
+
<div class="form-row">
|
|
26
|
+
<label for="node-input-server"><i class="fa fa-server"></i> Server</label>
|
|
27
|
+
<input type="text" id="node-input-server">
|
|
28
|
+
</div>
|
|
29
|
+
|
|
30
|
+
<div class="form-row">
|
|
31
|
+
<label for="node-input-database"><i class="fa fa-database"></i> Database</label>
|
|
32
|
+
<input type="text" id="node-input-database" placeholder="e.g. node-red-flows">
|
|
33
|
+
</div>
|
|
34
|
+
|
|
35
|
+
<div class="form-tips">
|
|
36
|
+
<p><strong>Input:</strong> msg.payload must be a document with _id and _rev fields</p>
|
|
37
|
+
<p><strong>Workflow:</strong> Get document first, modify it, then update</p>
|
|
38
|
+
<p><strong>Output 1 (Success):</strong> msg with result containing new _rev</p>
|
|
39
|
+
<p><strong>Output 2 (Error):</strong> msg with error information</p>
|
|
40
|
+
</div>
|
|
41
|
+
</script>
|
|
42
|
+
|
|
43
|
+
<script type="text/x-red" data-help-name="couchdb-update">
|
|
44
|
+
<p>Updates an existing document in a CouchDB database.</p>
|
|
45
|
+
|
|
46
|
+
<h3>Inputs</h3>
|
|
47
|
+
<dl class="message-properties">
|
|
48
|
+
<dt>payload <span class="property-type">object</span></dt>
|
|
49
|
+
<dd>The document to update. <strong>Must include:</strong>
|
|
50
|
+
<ul>
|
|
51
|
+
<li><code>_id</code> - The document ID</li>
|
|
52
|
+
<li><code>_rev</code> - The current revision (get this from Get node first)</li>
|
|
53
|
+
</ul>
|
|
54
|
+
</dd>
|
|
55
|
+
<dt class="optional">database <span class="property-type">string</span></dt>
|
|
56
|
+
<dd>Database name (overrides node config if provided)</dd>
|
|
57
|
+
</dl>
|
|
58
|
+
|
|
59
|
+
<h3>Outputs</h3>
|
|
60
|
+
<ol class="node-ports">
|
|
61
|
+
<li>Success Output
|
|
62
|
+
<dl class="message-properties">
|
|
63
|
+
<dt>result <span class="property-type">object</span></dt>
|
|
64
|
+
<dd>Result containing <code>_id</code>, <code>_rev</code> (new), and <code>ok: true</code></dd>
|
|
65
|
+
<dt>docId <span class="property-type">string</span></dt>
|
|
66
|
+
<dd>The document ID</dd>
|
|
67
|
+
<dt>docRev <span class="property-type">string</span></dt>
|
|
68
|
+
<dd>The new document revision</dd>
|
|
69
|
+
</dl>
|
|
70
|
+
</li>
|
|
71
|
+
<li>Error Output
|
|
72
|
+
<dl class="message-properties">
|
|
73
|
+
<dt>error <span class="property-type">object</span></dt>
|
|
74
|
+
<dd>Error details if update fails (e.g., conflict, not found)</dd>
|
|
75
|
+
</dl>
|
|
76
|
+
</li>
|
|
77
|
+
</ol>
|
|
78
|
+
|
|
79
|
+
<h3>Typical Workflow</h3>
|
|
80
|
+
<ol>
|
|
81
|
+
<li>Use <strong>CouchDB Get</strong> to retrieve the document</li>
|
|
82
|
+
<li>Modify the document in a function node</li>
|
|
83
|
+
<li>Use <strong>CouchDB Update</strong> to save changes</li>
|
|
84
|
+
</ol>
|
|
85
|
+
|
|
86
|
+
<h3>Example Flow</h3>
|
|
87
|
+
<pre>
|
|
88
|
+
Inject → Get [doc_id] → Function [modify payload] → Update → Debug
|
|
89
|
+
</pre>
|
|
90
|
+
</script>
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
module.exports = function(RED) {
|
|
2
|
+
function CouchDBUpdateNode(config) {
|
|
3
|
+
RED.nodes.createNode(this, config);
|
|
4
|
+
const node = this;
|
|
5
|
+
|
|
6
|
+
const serverConfig = RED.nodes.getNode(config.server);
|
|
7
|
+
if (!serverConfig) {
|
|
8
|
+
node.error("CouchDB server not configured");
|
|
9
|
+
return;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
const nano = require('nano');
|
|
13
|
+
const url = `http://${serverConfig.username}:${serverConfig.password}@${serverConfig.hostname}:${serverConfig.port}`;
|
|
14
|
+
const couchdb = nano(url);
|
|
15
|
+
|
|
16
|
+
node.on("input", function(msg) {
|
|
17
|
+
const database = config.database || msg.database;
|
|
18
|
+
const document = msg.payload;
|
|
19
|
+
|
|
20
|
+
if (!database) {
|
|
21
|
+
node.error("Database not specified", msg);
|
|
22
|
+
return;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
if (!document || typeof document !== 'object') {
|
|
26
|
+
node.error("Payload must be an object", msg);
|
|
27
|
+
return;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
if (!document._id) {
|
|
31
|
+
node.error("Document must have _id field", msg);
|
|
32
|
+
return;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
if (!document._rev) {
|
|
36
|
+
node.error("Document must have _rev field (use Get node first)", msg);
|
|
37
|
+
return;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
try {
|
|
41
|
+
const db = couchdb.db.use(database);
|
|
42
|
+
|
|
43
|
+
db.insert(document, (err, result) => {
|
|
44
|
+
if (err) {
|
|
45
|
+
node.error(`Update failed: ${err.message}`, msg);
|
|
46
|
+
msg.error = {
|
|
47
|
+
code: err.code,
|
|
48
|
+
message: err.message,
|
|
49
|
+
status: err.status
|
|
50
|
+
};
|
|
51
|
+
node.send([null, msg]);
|
|
52
|
+
} else {
|
|
53
|
+
msg.result = result;
|
|
54
|
+
msg.docId = result.id;
|
|
55
|
+
msg.docRev = result.rev;
|
|
56
|
+
node.send([msg, null]);
|
|
57
|
+
node.status({ fill: "green", shape: "dot", text: `Updated: ${result.id}` });
|
|
58
|
+
}
|
|
59
|
+
});
|
|
60
|
+
} catch (error) {
|
|
61
|
+
node.error(`Connection error: ${error.message}`, msg);
|
|
62
|
+
msg.error = error;
|
|
63
|
+
node.send([null, msg]);
|
|
64
|
+
}
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
node.on("close", function() {
|
|
68
|
+
node.status({ fill: "grey", shape: "ring", text: "disconnected" });
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
RED.nodes.registerType("couchdb-update", CouchDBUpdateNode);
|
|
73
|
+
};
|