node-ovsdb
    Preparing search index...

    node-ovsdb

    @sourceregistry/node-ovsdb

    License npm version codecov Release to NPM

    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-ovsdb
    

    Generate types from a schema with:

    npx ovsdb-generate --help
    

    The client is intentionally low-level and maps closely to the wire protocol.

    • list_dbs
    • get_schema
    • transact
    • cancel
    • monitor
    • monitor_cond
    • monitor_cond_since
    • monitor_cancel
    • lock
    • steal
    • unlock
    • echo
    • set_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.ts
    

    Generate directly from a live OVSDB server:

    npx ovsdb-generate --socket /var/run/openvswitch/db.sock --db Open_vSwitch --out ./src/generated/ovsdb.ts
    

    Generate from a live TCP endpoint:

    npx ovsdb-generate --host 127.0.0.1 --port 6640 --db Open_vSwitch --out ./src/generated/ovsdb.ts
    

    Generate 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.ts

    You 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 Interface row may appear
    • a new Port row may appear
    • an existing Bridge row may be modified so its ports set 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.ports column 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 Port and Interface tables 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 Interface row of type internal
    • creates a Port that owns that interface
    • creates a Bridge that 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 Port that references that interface
    • mutates the existing bridge so the new port is added to its ports set

    Why this is a common pattern:

    • hypervisors and container hosts often attach new virtual NICs dynamically
    • mutating the bridge ports set 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:build
    

    The 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 PortRef or InterfaceRef instead of plain Uuid
    • richer codegen metadata for table relationships derived from refTable and refType
    • 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:build

    Apache-2.0. See LICENSE.