Low-level OVSDB client for Node.js with:
- Unix socket, TCP, and TLS transports
- RFC 7047 core RPC support
- Open vSwitch monitor extensions
- typed transaction and monitor payloads
- event-driven notifications
- schema-to-TypeScript generation CLI
- TSDoc-ready public API for Typedoc
npm install @sourceregistry/node-ovsdbGenerate types from a schema with:
npx ovsdb-generate --helpThe client is intentionally low-level and maps closely to the wire protocol.
list_dbsget_schematransactcancelmonitormonitor_condmonitor_cond_sincemonitor_cancellockstealunlockechoset_db_change_aware- notifications:
update,update2,update3,locked,stolen
Unix socket:
import {OVSDBClient} from "@sourceregistry/node-ovsdb"; const client = new OVSDBClient({ socketPath: "/var/run/openvswitch/db.sock", timeout: 5000 }); try { await client.connect(); const databases = await client.listDbs(); const schema = await client.getSchema("Open_vSwitch"); console.log(databases, schema.version); } finally { await client.close(); }Plain TCP:
import {OVSDBClient} from "@sourceregistry/node-ovsdb"; const client = new OVSDBClient({ host: "127.0.0.1", port: 6640 });TLS:
import {OVSDBClient} from "@sourceregistry/node-ovsdb"; const client = new OVSDBClient({ host: "ovsdb.example.internal", port: 6640, tls: true, tlsOptions: { servername: "ovsdb.example.internal", rejectUnauthorized: true } });The package includes an ovsdb-generate CLI that emits TypeScript row and database model types you can use with OVSDBClient<...>.
Generate from a checked-in schema file:
npx ovsdb-generate --schema ./Open_vSwitch.schema.json --out ./src/generated/ovsdb.tsGenerate directly from a live OVSDB server:
npx ovsdb-generate --socket /var/run/openvswitch/db.sock --db Open_vSwitch --out ./src/generated/ovsdb.tsGenerate from a live TCP endpoint:
npx ovsdb-generate --host 127.0.0.1 --port 6640 --db Open_vSwitch --out ./src/generated/ovsdb.tsGenerate from a live TLS endpoint:
npx ovsdb-generate \ --host ovsdb.example.internal \ --port 6640 \ --tls \ --tls-ca-file ./pki/ca.pem \ --tls-cert-file ./pki/client.pem \ --tls-key-file ./pki/client.key \ --db Open_vSwitch \ --out ./src/generated/ovsdb.tsYou can override the generated top-level type name with --name OpenVSwitchDb.
You can provide your own table model to get typed table names, rows, selected columns, conditions, mutations, and tuple-shaped transaction results.
import {OVSDBClient, type DatabaseOperation, type OvsSet} from "@sourceregistry/node-ovsdb"; type OpenVSwitchDb = { Bridge: { name: string; ports: OvsSet<string>; }; Port: { name: string; interfaces: OvsSet<string>; }; }; const client = new OVSDBClient<OpenVSwitchDb>(); await client.connect(); const operations = [ { op: "select", table: "Bridge", where: [["name", "==", "br-int"]], columns: ["name", "ports"] }, { op: "insert", table: "Port", row: { name: "uplink0", interfaces: ["set", []] } } ] satisfies [DatabaseOperation<OpenVSwitchDb>, DatabaseOperation<OpenVSwitchDb>]; const [bridges, insertedPort] = await client.transact("Open_vSwitch", operations);For a higher-level staged flow, use client.transaction(...). The callback can build operations against a transaction-scoped helper, and the library will send one transact request only if the callback completes successfully. By default it appends a trailing commit operation automatically.
const outcome = await client.transaction("Open_vSwitch", (tx) => { tx.comment("prepare bridge lookup"); tx.select({ op: "select", table: "Bridge", where: [["name", "==", "br-int"]], columns: ["name"] }); return "ok"; });import {OVSDBClient} from "@sourceregistry/node-ovsdb"; const client = new OVSDBClient(); await client.connect(); client.on("update", (notification) => { const [monitorId, updates] = notification.params; console.log("monitor", monitorId, updates); }); await client.monitor("Open_vSwitch", "bridges", { Bridge: { columns: ["name"], select: { initial: true, insert: true, modify: true, delete: true } } });For conditional monitoring, use monitorCond() or monitorCondSince().
OVSDB does not usually emit a single semantic event like "interface attached to bridge". Instead, you observe the row changes that together mean an attachment happened:
- a new
Interfacerow may appear - a new
Portrow may appear - an existing
Bridgerow may be modified so itsportsset now includes that port
In practice, the bridge update is usually the strongest signal that something was attached to the virtual switch.
Why this works:
- the
Bridge.portscolumn is the relationship that tells you which ports are attached to the bridge - when that set grows, something new was connected to the bridge
- you can then inspect
PortandInterfacetables to resolve names or metadata for the newly attached objects
If you want richer correlation, monitor Bridge, Port, and Interface together and keep a small in-memory cache keyed by UUID so you can map a changed bridge port set back to the concrete port and interface names.
Example: examples/detect-interface-added.ts
These examples focus on patterns that show up often in virtualized environments, where OVS is used to connect VM or container networking to a virtual switch.
What this does:
- creates an
Interfacerow of typeinternal - creates a
Portthat owns that interface - creates a
Bridgethat owns that port
Why it is done this way:
- in OVS, a bridge usually owns ports, and ports own interfaces
- creating all three rows in one transaction keeps the change atomic
- named UUIDs let later operations refer to rows inserted earlier in the same transaction
Example: examples/bridge-port-interface.ts
What this does:
- creates a new
Interface - creates a
Portthat references that interface - mutates the existing bridge so the new port is added to its
portsset
Why this is a common pattern:
- hypervisors and container hosts often attach new virtual NICs dynamically
- mutating the bridge
portsset avoids rewriting the whole bridge row - keeping it in one transaction prevents partial attachment state
Example: examples/attach-interface-to-bridge.ts
In practice, type: "internal" is useful when you want OVS itself to create the interface device. Leaving type unset is common when attaching an already existing device such as a tap interface created by a hypervisor.
The client implements AsyncDisposable, so it also works with await using in runtimes that support explicit resource management.
await using client = new OVSDBClient(); await client.connect(); const dbs = await client.listDbs();- Transport/request failures reject with
Error - OVSDB JSON-RPC errors reject with
OvsdbRpcError - malformed inbound frames emit
protocolError - socket-level failures emit
transportError
Generate API docs with Typedoc:
npm run docs:buildThe public API is documented with TSDoc so the generated output is usable as a reference, not just a symbol dump.
Planned work for the next iterations of the library:
- relation-aware schema generation so UUID reference columns can emit stronger types such as
PortReforInterfaceRefinstead of plainUuid - richer codegen metadata for table relationships derived from
refTableandrefType - helper utilities for working with generated reference types in transactions and monitor snapshots
- live TLS integration coverage for the transport and generator CLI
- stricter runtime validation for inbound notifications and response payloads
The intended direction is to make the generator more relation-aware first, before attempting a larger ORM-style layer.
npm test npm run build npm run docs:buildApache-2.0. See LICENSE.