Covers Engine 2.0.0
Node.js Client
Connect Node.js tools, read and write channels, receive telemetry, and export recorded data.
The Node.js client provides promise-based operations for services, dashboard backends, and analysis tools. Use Node.js 20 or newer; browser dashboards use the React client.
Download and install locally
Download Node.js Client from Downloads and extract it. From your application’s directory, pack the extracted release and install the resulting local archive. Replace the example path and version with your download:
npm pack ../packages/dartwic-node-client-1.0.0
npm install ./voit-systems-dartwic.node-client-1.0.0.tgznpm pack creates the .tgz in your current directory; it does not publish anything. The client is not published on npm. After this local installation, import it by its package name, @voit-systems/dartwic.node-client, as shown below.
Keep the .tgz at the installed path for future dependency installs, including on other machines that use this project.
The release includes TEMPEST. npm installs zeromq, json-bigint, and semver; you do not need a second DARTWIC download. ZeroMQ uses native bindings, so platforms without a compatible prebuilt binding need the build tools required by zeromq.
Connect
Save this as analysis.mjs and set DARTWIC_PASSWORD. Put the following examples inside the connected try block:
import {DartwicClient} from '@voit-systems/dartwic.node-client';
const client = new DartwicClient();
try {
await client.connect('127.0.0.1', 7000, process.env.DARTWIC_PASSWORD);
// Query, write, or subscribe here.
} finally {
await client.disconnect();
}Pass the engine’s base port; telemetry uses base port + 1. Connection registers the client and checks compatibility. Helpers reject on transport or operation errors.
Query channels
console.log(await client.queryChannel('tank_pressure'));
console.log(await client.queryChannel('tank_pressure', 'units'));
const records = await client.getChannels(['tank_pressure', 'tank_temperature']);
for (const [name, record] of Object.entries(records)) {
if (record.exists) console.log(name, record.channel_data.value);
}A single read rejects for a missing channel or field; batch reads expose exists. The engine rounds live value fields to three decimals in this endpoint.
Search channels
const matches = await client.searchChannels('tank', 25);
if (matches.length) console.log(await client.getChannels(matches));Upsert channels
await client.upsertChannel('analysis_result', 42);
await client.upsertChannel('analysis_result', 'bar', 'units');A device adapter can submit a set of readings without creating a separate helper for every signal:
const readings = {adapter_pressure: 2.5, adapter_temperature: 23.5};
for (const [channel, value] of Object.entries(readings)) {
await client.upsertChannel(channel, value);
}These are independent acknowledged writes; the loop is not an atomic bulk operation. Earlier writes remain applied if a later write fails. Engine authority applies, with no operator manual override. For owned remote channels, timestamped telemetry batches, and incoming driver commands, use Share.
Subscribe and receive telemetry
const stop = await client.subscribeChannel('tank_pressure', snapshot => {
if (snapshot.exists) console.log(snapshot.channel_data.value);
});
try {
await new Promise(resolve => setTimeout(resolve, 10_000));
} finally {
await stop();
}Setup delivers an initial snapshot; asynchronous updates may overlap an awaited initial callback. Multiple listeners share one server reference. The stop function is idempotent. Keep callbacks short: awaited callbacks delay later delivery. Update callback failures are logged and isolated; an initial callback failure rejects setup.
To inspect raw envelopes, bind a prefix alongside a channel subscription:
const stopRaw = client.subscribeTelemetry('rapid/channels/', (message, suffix) => {
console.log(suffix, message.payload);
});
const stopChannel = await client.subscribeChannel('tank_pressure', () => {});
try {
await new Promise(resolve => setTimeout(resolve, 10_000));
} finally {
await stopChannel();
stopRaw();
}The raw binding only installs a local handler; it does not enable publication on the engine.
Example: find a dataframe and export recorded channels
This example finds tank_run, selects its pressure channels, and writes their raw recorded points to a JSON file for another analysis tool:
const {writeFile} = await import('node:fs/promises');
const frames = await client.searchDataframes('tank_run');
const frame = frames.find(item => item.data_frame === 'tank_run');
if (!frame) throw new Error('No matching dataframe: tank_run');
const selected = frame.recorded_channels.filter(name => name.includes('pressure'));
if (!selected.length) throw new Error('No recorded pressure channels');
const history = await client.queryChannelRange(selected, {dataFrame: frame.data_frame});
await writeFile('pressure-history.json', JSON.stringify(history,
(_, value) => typeof value === 'bigint' ? value.toString() : value, 2));History contains a channels array and full query statistics. Integers beyond JavaScript’s safe range arrive as bigint, preserving nanosecond timestamps; the export converts them to decimal strings. Use bigint for nanosecond bounds, such as from: end - 60_000_000_000n with end = BigInt(Date.now()) * 1_000_000n.
Omitting bounds requests available raw history subject to the engine’s point limit. Narrow the time interval if that limit is exceeded. bucketCount: 500 with bucketMode: 'average' or 'extrema' explicitly requests an aggregated preview. A dataframe groups recordings; its channels need not share sampling times.
Raw operations and connection loss
const response = await client.operation('dartwic/get-runtime-metadata');
if (response.error) throw new Error(response.payload.error);
console.log(response.payload.engineVersion);Unlike helpers, raw operation returns an envelope that must be checked. See Using TEMPEST for available operations.
isConnected() is local state. A registered heartbeat probe detects connection or session loss; it is not instantaneous. This client does not reconnect automatically: await disconnect(), call connect() again, then recreate managed channel subscriptions. Serialize connection lifecycle calls. Timed-out operations are never replayed automatically, and a timeout does not cancel work on the engine. Check resulting state before retrying writes.
See the generated Node.js API reference.