UNPKG

node-red-cosmos-r2

Version:

A Node-RED custom node that connects to a Cosmos DB and performs create, update, read, delete, and upsert operations.

235 lines (212 loc) 8.01 kB
const { CosmosClient, BulkOperationType } = require("@azure/cosmos"); module.exports = function (RED) { function CosmosR2Node(config) { RED.nodes.createNode(this, config); const node = this; const uri = this.credentials.uri; const key = this.credentials.key; const databaseId = config.databaseId; const containerId = config.containerId; // Defer client creation until needed. This avoids URI errors from Cosmos when starting/managing flow. let client = null; let container = null; function initializeClient() { if (!uri || typeof uri !== 'string' || !uri.startsWith('https://')) { throw new Error("Invalid or missing Cosmos DB URI. Expected format: https://your-account.documents.azure.com:443/"); } if (!key) { throw new Error("Missing Cosmos DB key"); } if (!databaseId) { throw new Error("Missing database ID"); } if (!containerId) { throw new Error("Missing container ID"); } client = new CosmosClient({ endpoint: uri, key: key }); container = client.database(databaseId).container(containerId); } node.on("input", async function (msg) { try { // Initialize client on first use if (!container) { try { initializeClient(); } catch (initError) { node.error("Cosmos configuration error: " + initError.message); return; } } const operation = msg.operation; const item = msg.item; if (!operation) { node.error("No operation specified"); return; } switch (operation) { case "create": const { resource: createdItem } = await container.items.create(item); msg.payload = createdItem; break; case "read": const { resources: readItems } = await container.items.query(msg.query || "SELECT * from c").fetchAll(); msg.payload = readItems; break; case "update": if (!msg.partitionKey) { node.error("Operation 'update' requires a partitionKey in msg.partitionKey"); return; } const { resource: updatedItem } = await container.item(item.id, msg.partitionKey).replace(item); msg.payload = updatedItem; break; case "delete": if (!msg.partitionKey) { node.error("Operation 'delete' requires a partitionKey in msg.partitionKey"); return; } const { resource: deletedItem } = await container.item(item.id, msg.partitionKey).delete(); msg.payload = deletedItem; break; case "upsert": const { resource: upsertedItem } = await container.items.upsert(item); msg.payload = upsertedItem; break; case "batch-create": if (!Array.isArray(msg.items)) { node.error("batch-create requires an array of items in msg.items"); return; } if (!msg.partitionKey) { node.error("batch-create requires a partitionKey in msg.partitionKey"); return; } try { const operations = msg.items.map(item => ({ operationType: BulkOperationType.Create, resourceBody: item })); const response = await container.items.batch(operations, msg.partitionKey); msg.payload = response; } catch (error) { node.error("Batch create failed: " + error.message); return; } break; case "batch-upsert": if (!Array.isArray(msg.items)) { node.error("batch-upsert requires an array of items in msg.items"); return; } if (!msg.partitionKey) { node.error("batch-upsert requires a partitionKey in msg.partitionKey"); return; } try { const operations = msg.items.map(item => ({ operationType: BulkOperationType.Upsert, resourceBody: item })); const response = await container.items.batch(operations, msg.partitionKey); msg.payload = response; } catch (error) { node.error("Batch upsert failed: " + error.message); return; } break; case "batch-update": if (!Array.isArray(msg.items)) { node.error("batch-update requires an array of items in msg.items"); return; } if (!msg.partitionKey) { node.error("batch-update requires a partitionKey in msg.partitionKey"); return; } try { for (const item of msg.items) { if (!item.id) { throw new Error(`Item missing id field: ${JSON.stringify(item)}`); } } const operations = msg.items.map(item => ({ operationType: BulkOperationType.Replace, id: item.id, resourceBody: item })); const response = await container.items.batch(operations, msg.partitionKey); msg.payload = response; } catch (error) { node.error("Batch update failed: " + error.message); return; } break; case "batch-delete": if (!Array.isArray(msg.items)) { node.error("batch-delete requires an array of items in msg.items"); return; } if (!msg.partitionKey) { node.error("batch-delete requires a partitionKey in msg.partitionKey"); return; } try { for (const item of msg.items) { if (!item.id) { throw new Error(`Item missing id field: ${JSON.stringify(item)}`); } } const operations = msg.items.map(item => ({ operationType: BulkOperationType.Delete, id: item.id })); const response = await container.items.batch(operations, msg.partitionKey); msg.payload = response; } catch (error) { node.error("Batch delete failed: " + error.message); return; } break; case "batch-read": if (!Array.isArray(msg.items)) { node.error("batch-read requires an array of items with id fields in msg.items"); return; } if (!msg.partitionKey) { node.error("batch-read requires a partitionKey in msg.partitionKey"); return; } try { for (const item of msg.items) { if (!item.id) { throw new Error(`Item missing id field: ${JSON.stringify(item)}`); } } const operations = msg.items.map(item => ({ operationType: BulkOperationType.Read, id: item.id })); const response = await container.items.batch(operations, msg.partitionKey); msg.payload = response; } catch (error) { node.error("Batch read failed: " + error.message); return; } break; default: node.error("Invalid operation"); return; } node.send(msg); } catch (error) { node.error("Error processing operation: " + error.message); } }); } RED.nodes.registerType("cosmos-r2", CosmosR2Node,{ credentials: { uri: { type:"text" }, key: { type:"password" } } }); };