fix: add missing migrateConfig method, config versioning, and formatters module
ConfigManager.migrateConfig() was called but never defined — would crash at runtime. Added config version checking, migration support, and fixed createEndpoint indentation. New formatters module (csv, influxdb, json) for pluggable output formatting. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
44
src/helper/formatters/csvFormatter.js
Normal file
44
src/helper/formatters/csvFormatter.js
Normal file
@@ -0,0 +1,44 @@
|
||||
/**
|
||||
* CSV formatter
|
||||
* Produces a single CSV line: timestamp,measurement,field1=val1,field2=val2,...
|
||||
*
|
||||
* Values are escaped if they contain commas or quotes.
|
||||
*
|
||||
* @param {string} measurement - The measurement name (e.g. node name)
|
||||
* @param {object} metadata - { fields, tags }
|
||||
* - fields: key/value pairs of changed data points
|
||||
* - tags: flat key/value string pairs (included as columns)
|
||||
* @returns {string} CSV-formatted line
|
||||
*/
|
||||
function format(measurement, metadata) {
|
||||
const { fields, tags } = metadata;
|
||||
const timestamp = new Date().toISOString();
|
||||
const parts = [escapeCSV(timestamp), escapeCSV(measurement)];
|
||||
|
||||
// Append tags first, then fields
|
||||
if (tags) {
|
||||
for (const key of Object.keys(tags).sort()) {
|
||||
parts.push(escapeCSV(`${key}=${tags[key]}`));
|
||||
}
|
||||
}
|
||||
|
||||
for (const key of Object.keys(fields).sort()) {
|
||||
parts.push(escapeCSV(`${key}=${fields[key]}`));
|
||||
}
|
||||
|
||||
return parts.join(',');
|
||||
}
|
||||
|
||||
/**
|
||||
* Escapes a value for safe inclusion in a CSV field.
|
||||
* Wraps in double quotes if the value contains a comma, quote, or newline.
|
||||
*/
|
||||
function escapeCSV(value) {
|
||||
const str = String(value);
|
||||
if (str.includes(',') || str.includes('"') || str.includes('\n')) {
|
||||
return '"' + str.replace(/"/g, '""') + '"';
|
||||
}
|
||||
return str;
|
||||
}
|
||||
|
||||
module.exports = { format };
|
||||
58
src/helper/formatters/index.js
Normal file
58
src/helper/formatters/index.js
Normal file
@@ -0,0 +1,58 @@
|
||||
/**
|
||||
* Formatter Registry
|
||||
* ------------------
|
||||
* Maps format names to formatter modules.
|
||||
* Each formatter exports: format(measurement, metadata) => string|object
|
||||
*
|
||||
* Usage:
|
||||
* const { getFormatter, registerFormatter } = require('./formatters');
|
||||
* const fmt = getFormatter('json');
|
||||
* const output = fmt.format('pump1', { fields: {...}, tags: {...} });
|
||||
*/
|
||||
|
||||
const influxdbFormatter = require('./influxdbFormatter');
|
||||
const jsonFormatter = require('./jsonFormatter');
|
||||
const csvFormatter = require('./csvFormatter');
|
||||
|
||||
// Built-in registry
|
||||
const registry = {
|
||||
influxdb: influxdbFormatter,
|
||||
json: jsonFormatter,
|
||||
csv: csvFormatter,
|
||||
};
|
||||
|
||||
/**
|
||||
* Retrieve a formatter by name.
|
||||
* @param {string} name - Format name (e.g. 'influxdb', 'json', 'csv')
|
||||
* @returns {object} Formatter with a .format() method
|
||||
* @throws {Error} If the format name is not registered
|
||||
*/
|
||||
function getFormatter(name) {
|
||||
const formatter = registry[name];
|
||||
if (!formatter) {
|
||||
throw new Error(`Unknown output format: "${name}". Registered formats: ${Object.keys(registry).join(', ')}`);
|
||||
}
|
||||
return formatter;
|
||||
}
|
||||
|
||||
/**
|
||||
* Register a custom formatter at runtime.
|
||||
* @param {string} name - Format name
|
||||
* @param {object} formatter - Object with a .format(measurement, metadata) method
|
||||
*/
|
||||
function registerFormatter(name, formatter) {
|
||||
if (typeof formatter.format !== 'function') {
|
||||
throw new Error('Formatter must have a .format(measurement, metadata) method');
|
||||
}
|
||||
registry[name] = formatter;
|
||||
}
|
||||
|
||||
/**
|
||||
* List all registered format names.
|
||||
* @returns {string[]}
|
||||
*/
|
||||
function getRegisteredFormats() {
|
||||
return Object.keys(registry);
|
||||
}
|
||||
|
||||
module.exports = { getFormatter, registerFormatter, getRegisteredFormats };
|
||||
22
src/helper/formatters/influxdbFormatter.js
Normal file
22
src/helper/formatters/influxdbFormatter.js
Normal file
@@ -0,0 +1,22 @@
|
||||
/**
|
||||
* InfluxDB formatter
|
||||
* Produces the structured object expected by Node-RED InfluxDB nodes:
|
||||
* { measurement, fields, tags, timestamp }
|
||||
*
|
||||
* @param {string} measurement - The measurement name (e.g. node name)
|
||||
* @param {object} metadata - { fields, tags }
|
||||
* - fields: key/value pairs of changed data points
|
||||
* - tags: flat key/value string pairs (InfluxDB tags)
|
||||
* @returns {string|object} Formatted payload (object for InfluxDB)
|
||||
*/
|
||||
function format(measurement, metadata) {
|
||||
const { fields, tags } = metadata;
|
||||
return {
|
||||
measurement: measurement,
|
||||
fields: fields,
|
||||
tags: tags || {},
|
||||
timestamp: new Date(),
|
||||
};
|
||||
}
|
||||
|
||||
module.exports = { format };
|
||||
22
src/helper/formatters/jsonFormatter.js
Normal file
22
src/helper/formatters/jsonFormatter.js
Normal file
@@ -0,0 +1,22 @@
|
||||
/**
|
||||
* JSON formatter
|
||||
* Produces a JSON string suitable for MQTT, REST APIs, etc.
|
||||
*
|
||||
* @param {string} measurement - The measurement name (e.g. node name)
|
||||
* @param {object} metadata - { fields, tags }
|
||||
* - fields: key/value pairs of changed data points
|
||||
* - tags: flat key/value string pairs
|
||||
* @returns {string} JSON-encoded string
|
||||
*/
|
||||
function format(measurement, metadata) {
|
||||
const { fields, tags } = metadata;
|
||||
const payload = {
|
||||
measurement: measurement,
|
||||
fields: fields,
|
||||
tags: tags || {},
|
||||
timestamp: new Date().toISOString(),
|
||||
};
|
||||
return JSON.stringify(payload);
|
||||
}
|
||||
|
||||
module.exports = { format };
|
||||
Reference in New Issue
Block a user