@sourceregistry/node-ovsdb 1.0.1 → 1.0.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,124 +1,330 @@
1
1
  # @sourceregistry/node-ovsdb
2
2
 
3
3
  [![License](https://img.shields.io/badge/License-Apache%202.0-blue.svg)](https://opensource.org/licenses/Apache-2.0)
4
+ [![npm version](https://img.shields.io/npm/v/%40sourceregistry%2Fnode-ovsdb)](https://www.npmjs.com/package/@sourceregistry/node-ovsdb)
5
+ [![codecov](https://codecov.io/gh/SourceRegistry/node-ovsdb/graph/badge.svg)](https://codecov.io/gh/SourceRegistry/node-ovsdb)
4
6
  [![Release to NPM](https://github.com/SourceRegistry/node-ovsdb/actions/workflows/publish-npm.yml/badge.svg)](https://github.com/SourceRegistry/node-ovsdb/actions/workflows/publish-npm.yml)
5
7
 
6
- A lightweight, typesafe TypeScript client for the Open vSwitch Database (OVSDB) Management Protocol, as defined in [RFC 7047](https://datatracker.ietf.org/doc/html/rfc7047).
8
+ Low-level OVSDB client for Node.js with:
7
9
 
8
- This library provides a low-level wrapper to interact with the OVSDB over a Unix socket, enabling programmatic configuration and management of Open vSwitch instances.
10
+ - Unix socket, TCP, and TLS transports
11
+ - RFC 7047 core RPC support
12
+ - Open vSwitch monitor extensions
13
+ - typed transaction and monitor payloads
14
+ - event-driven notifications
15
+ - schema-to-TypeScript generation CLI
16
+ - TSDoc-ready public API for Typedoc
9
17
 
10
18
  ## Installation
11
19
 
12
- Install the package using npm:
13
-
14
20
  ```bash
15
21
  npm install @sourceregistry/node-ovsdb
16
22
  ```
17
23
 
18
- ## Usage
24
+ Generate types from a schema with:
19
25
 
20
- ### Getting Started
26
+ ```bash
27
+ npx ovsdb-generate --help
28
+ ```
21
29
 
22
- The primary class is `OVSDBClient`, which handles the connection to the OVSDB server (typically located at `/var/run/openvswitch/db.sock`).
30
+ ## What It Supports
23
31
 
24
- ```typescript
25
- import { OVSDBClient } from '@sourceregistry/node-ovsdb';
32
+ The client is intentionally low-level and maps closely to the wire protocol.
26
33
 
27
- async function main() {
28
- // Create a new client instance
29
- const client = new OVSDBClient();
34
+ - `list_dbs`
35
+ - `get_schema`
36
+ - `transact`
37
+ - `cancel`
38
+ - `monitor`
39
+ - `monitor_cond`
40
+ - `monitor_cond_since`
41
+ - `monitor_cancel`
42
+ - `lock`
43
+ - `steal`
44
+ - `unlock`
45
+ - `echo`
46
+ - `set_db_change_aware`
47
+ - notifications: `update`, `update2`, `update3`, `locked`, `stolen`
30
48
 
31
- try {
32
- // Connect to the OVSDB Unix socket
33
- await client.connect();
34
- console.log('Connected to OVSDB');
49
+ ## Quick Start
35
50
 
36
- // Example: Get the list of available databases
37
- const databases = await client.listDbs();
38
- console.log('Databases:', databases);
51
+ Unix socket:
39
52
 
40
- // Example: Get the schema of the 'Open_vSwitch' database
41
- const schema = await client.getSchema('Open_vSwitch');
42
- console.log('OVS Version:', schema.version);
53
+ ```ts
54
+ import {OVSDBClient} from "@sourceregistry/node-ovsdb";
43
55
 
44
- // Example: Perform a transaction to select all bridges
45
- const operations = [
46
- {
47
- op: 'select',
48
- table: 'Bridge',
49
- where: [],
50
- columns: ['name']
51
- }
52
- ];
56
+ const client = new OVSDBClient({
57
+ socketPath: "/var/run/openvswitch/db.sock",
58
+ timeout: 5000
59
+ });
53
60
 
54
- const result = await client.transact('Open_vSwitch', operations);
55
- console.log('Bridges:', result);
61
+ try {
62
+ await client.connect();
56
63
 
57
- } catch (error) {
58
- console.error('An error occurred:', error.message);
59
- } finally {
60
- // Always close the connection
61
- client.close();
62
- }
64
+ const databases = await client.listDbs();
65
+ const schema = await client.getSchema("Open_vSwitch");
66
+
67
+ console.log(databases, schema.version);
68
+ } finally {
69
+ await client.close();
63
70
  }
71
+ ```
72
+
73
+ Plain TCP:
64
74
 
65
- main();
75
+ ```ts
76
+ import {OVSDBClient} from "@sourceregistry/node-ovsdb";
77
+
78
+ const client = new OVSDBClient({
79
+ host: "127.0.0.1",
80
+ port: 6640
81
+ });
66
82
  ```
67
83
 
68
- ### Using Async Disposable (Recommended)
84
+ TLS:
69
85
 
70
- For guaranteed resource cleanup, use the client with the `using` statement (requires Node.js 18+ or the `--harmony-explicit-resource-management` flag):
86
+ ```ts
87
+ import {OVSDBClient} from "@sourceregistry/node-ovsdb";
71
88
 
72
- ```typescript
73
- import { OVSDBClient } from '@sourceregistry/node-ovsdb';
89
+ const client = new OVSDBClient({
90
+ host: "ovsdb.example.internal",
91
+ port: 6640,
92
+ tls: true,
93
+ tlsOptions: {
94
+ servername: "ovsdb.example.internal",
95
+ rejectUnauthorized: true
96
+ }
97
+ });
98
+ ```
74
99
 
75
- async function main() {
76
- // The 'using' statement ensures the client is closed even if an error occurs
77
- using client = new OVSDBClient();
100
+ ## Schema Generation
78
101
 
79
- await client.connect();
80
- console.log('Connected to OVSDB');
102
+ The package includes an `ovsdb-generate` CLI that emits TypeScript row and database model types you can use with `OVSDBClient<...>`.
81
103
 
82
- const dbs = await client.listDbs();
83
- console.log('Available databases:', dbs);
84
- }
104
+ Generate from a checked-in schema file:
85
105
 
86
- main();
106
+ ```bash
107
+ npx ovsdb-generate --schema ./Open_vSwitch.schema.json --out ./src/generated/ovsdb.ts
87
108
  ```
88
109
 
89
- ### Core Methods
110
+ Generate directly from a live OVSDB server:
90
111
 
91
- The `OVSDBClient` class provides direct access to the core OVSDB RPC methods:
112
+ ```bash
113
+ npx ovsdb-generate --socket /var/run/openvswitch/db.sock --db Open_vSwitch --out ./src/generated/ovsdb.ts
114
+ ```
92
115
 
93
- - `connect()`: Establishes a connection to the Unix socket.
94
- - `listDbs()`: Retrieves the names of available databases.
95
- - `getSchema(dbName)`: Retrieves the schema for a specific database.
96
- - `transact(dbName, operations)`: Executes a series of database operations atomically.
97
- - `monitor(...)`: Subscribes to database change notifications.
98
- - `echo()`: Tests the connection liveness.
99
- - `close()`: Closes the connection.
116
+ Generate from a live TCP endpoint:
100
117
 
101
- ### Advanced: Entity Wrappers (Coming Soon)
118
+ ```bash
119
+ npx ovsdb-generate --host 127.0.0.1 --port 6640 --db Open_vSwitch --out ./src/generated/ovsdb.ts
120
+ ```
102
121
 
103
- For higher-level operations (e.g., `createBridge`, `addPort`), consider building wrapper classes on top of `OVSDBClient`. A future version of this library may include these.
122
+ Generate from a live TLS endpoint:
104
123
 
105
- ## Development
124
+ ```bash
125
+ npx ovsdb-generate \
126
+ --host ovsdb.example.internal \
127
+ --port 6640 \
128
+ --tls \
129
+ --tls-ca-file ./pki/ca.pem \
130
+ --tls-cert-file ./pki/client.pem \
131
+ --tls-key-file ./pki/client.key \
132
+ --db Open_vSwitch \
133
+ --out ./src/generated/ovsdb.ts
134
+ ```
135
+
136
+ You can override the generated top-level type name with `--name OpenVSwitchDb`.
137
+
138
+ ## Typed Transactions
139
+
140
+ You can provide your own table model to get typed table names, rows, selected columns, conditions, mutations, and tuple-shaped transaction results.
141
+
142
+ ```ts
143
+ import {OVSDBClient, type DatabaseOperation, type OvsSet} from "@sourceregistry/node-ovsdb";
144
+
145
+ type OpenVSwitchDb = {
146
+ Bridge: {
147
+ name: string;
148
+ ports: OvsSet<string>;
149
+ };
150
+ Port: {
151
+ name: string;
152
+ interfaces: OvsSet<string>;
153
+ };
154
+ };
155
+
156
+ const client = new OVSDBClient<OpenVSwitchDb>();
157
+ await client.connect();
158
+
159
+ const operations = [
160
+ {
161
+ op: "select",
162
+ table: "Bridge",
163
+ where: [["name", "==", "br-int"]],
164
+ columns: ["name", "ports"]
165
+ },
166
+ {
167
+ op: "insert",
168
+ table: "Port",
169
+ row: {
170
+ name: "uplink0",
171
+ interfaces: ["set", []]
172
+ }
173
+ }
174
+ ] satisfies [DatabaseOperation<OpenVSwitchDb>, DatabaseOperation<OpenVSwitchDb>];
175
+
176
+ const [bridges, insertedPort] = await client.transact("Open_vSwitch", operations);
177
+ ```
178
+
179
+ 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.
180
+
181
+ ```ts
182
+ const outcome = await client.transaction("Open_vSwitch", (tx) => {
183
+ tx.comment("prepare bridge lookup");
184
+ tx.select({
185
+ op: "select",
186
+ table: "Bridge",
187
+ where: [["name", "==", "br-int"]],
188
+ columns: ["name"]
189
+ });
190
+
191
+ return "ok";
192
+ });
193
+ ```
194
+
195
+ ## Monitoring
196
+
197
+ ```ts
198
+ import {OVSDBClient} from "@sourceregistry/node-ovsdb";
199
+
200
+ const client = new OVSDBClient();
201
+ await client.connect();
202
+
203
+ client.on("update", (notification) => {
204
+ const [monitorId, updates] = notification.params;
205
+ console.log("monitor", monitorId, updates);
206
+ });
207
+
208
+ await client.monitor("Open_vSwitch", "bridges", {
209
+ Bridge: {
210
+ columns: ["name"],
211
+ select: {
212
+ initial: true,
213
+ insert: true,
214
+ modify: true,
215
+ delete: true
216
+ }
217
+ }
218
+ });
219
+ ```
220
+
221
+ For conditional monitoring, use `monitorCond()` or `monitorCondSince()`.
222
+
223
+ ### Detect When an Interface Is Attached to a Bridge
106
224
 
107
- ### Prerequisites
225
+ 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:
108
226
 
109
- - Node.js (v18 or higher recommended)
227
+ - a new `Interface` row may appear
228
+ - a new `Port` row may appear
229
+ - an existing `Bridge` row may be modified so its `ports` set now includes that port
110
230
 
111
- ### Scripts
231
+ In practice, the bridge update is usually the strongest signal that something was attached to the virtual switch.
112
232
 
113
- - `npm run dev`: Start the development server.
114
- - `npm run build`: Compile the TypeScript code and generate the distribution files in the `dist/` directory.
115
- - `npm run preview`: Preview the built application.
116
- - `npm run test`: Run the test suite with Vitest.
233
+ Why this works:
117
234
 
118
- ## Contributing
235
+ - the `Bridge.ports` column is the relationship that tells you which ports are attached to the bridge
236
+ - when that set grows, something new was connected to the bridge
237
+ - you can then inspect `Port` and `Interface` tables to resolve names or metadata for the newly attached objects
119
238
 
120
- Contributions are welcome! Please feel free to submit issues and pull requests.
239
+ 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.
240
+
241
+ Example: [examples/detect-interface-added.ts](/Users/alexanderslaa/WebstormProjects/github.com/SourceRegistry/node-ovsdb/examples/detect-interface-added.ts)
242
+
243
+ ## Common OVS Workflows
244
+
245
+ 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.
246
+
247
+ ### Create a Bridge With an Internal Interface
248
+
249
+ What this does:
250
+
251
+ - creates an `Interface` row of type `internal`
252
+ - creates a `Port` that owns that interface
253
+ - creates a `Bridge` that owns that port
254
+
255
+ Why it is done this way:
256
+
257
+ - in OVS, a bridge usually owns ports, and ports own interfaces
258
+ - creating all three rows in one transaction keeps the change atomic
259
+ - named UUIDs let later operations refer to rows inserted earlier in the same transaction
260
+
261
+ Example: [examples/bridge-port-interface.ts](/Users/alexanderslaa/WebstormProjects/github.com/SourceRegistry/node-ovsdb/examples/bridge-port-interface.ts)
262
+
263
+ ### Attach a New Interface to an Existing Bridge
264
+
265
+ What this does:
266
+
267
+ - creates a new `Interface`
268
+ - creates a `Port` that references that interface
269
+ - mutates the existing bridge so the new port is added to its `ports` set
270
+
271
+ Why this is a common pattern:
272
+
273
+ - hypervisors and container hosts often attach new virtual NICs dynamically
274
+ - mutating the bridge `ports` set avoids rewriting the whole bridge row
275
+ - keeping it in one transaction prevents partial attachment state
276
+
277
+ Example: [examples/attach-interface-to-bridge.ts](/Users/alexanderslaa/WebstormProjects/github.com/SourceRegistry/node-ovsdb/examples/attach-interface-to-bridge.ts)
278
+
279
+ 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.
280
+
281
+ ## Resource Management
282
+
283
+ The client implements `AsyncDisposable`, so it also works with `await using` in runtimes that support explicit resource management.
284
+
285
+ ```ts
286
+ await using client = new OVSDBClient();
287
+ await client.connect();
288
+ const dbs = await client.listDbs();
289
+ ```
290
+
291
+ ## Error Handling
292
+
293
+ - Transport/request failures reject with `Error`
294
+ - OVSDB JSON-RPC errors reject with `OvsdbRpcError`
295
+ - malformed inbound frames emit `protocolError`
296
+ - socket-level failures emit `transportError`
297
+
298
+ ## Documentation
299
+
300
+ Generate API docs with Typedoc:
301
+
302
+ ```bash
303
+ npm run docs:build
304
+ ```
305
+
306
+ The public API is documented with TSDoc so the generated output is usable as a reference, not just a symbol dump.
307
+
308
+ ## Roadmap
309
+
310
+ Planned work for the next iterations of the library:
311
+
312
+ - relation-aware schema generation so UUID reference columns can emit stronger types such as `PortRef` or `InterfaceRef` instead of plain `Uuid`
313
+ - richer codegen metadata for table relationships derived from `refTable` and `refType`
314
+ - helper utilities for working with generated reference types in transactions and monitor snapshots
315
+ - live TLS integration coverage for the transport and generator CLI
316
+ - stricter runtime validation for inbound notifications and response payloads
317
+
318
+ The intended direction is to make the generator more relation-aware first, before attempting a larger ORM-style layer.
319
+
320
+ ## Development
321
+
322
+ ```bash
323
+ npm test
324
+ npm run build
325
+ npm run docs:build
326
+ ```
121
327
 
122
328
  ## License
123
329
 
124
- This project is licensed under the Apache-2.0 License. See the [LICENSE](LICENSE) file for details.
330
+ Apache-2.0. See [LICENSE](./LICENSE).
@@ -0,0 +1,7 @@
1
+ #!/usr/bin/env node
2
+
3
+ const {runCli} = require("../dist-cli/cli.js");
4
+
5
+ void runCli(process.argv.slice(2)).then((code) => {
6
+ process.exitCode = code;
7
+ });
package/dist/cli.d.ts ADDED
@@ -0,0 +1,5 @@
1
+ /**
2
+ * Runs the OVSDB type generation CLI.
3
+ */
4
+ export declare function runCli(argv: string[]): Promise<number>;
5
+ //# sourceMappingURL=cli.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"AAwBA;;GAEG;AACH,wBAAsB,MAAM,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,MAAM,CAAC,CAsD5D"}
@@ -0,0 +1,136 @@
1
+ import { OvsdbClientOptions } from './index';
2
+ import { DatabaseSchema } from './types';
3
+ /**
4
+ * Options for generating TypeScript types from an OVSDB schema.
5
+ */
6
+ export interface GenerateTypesOptions {
7
+ /**
8
+ * The schema object to transform.
9
+ */
10
+ schema: DatabaseSchema;
11
+ /**
12
+ * Name of the generated top-level database type.
13
+ *
14
+ * @defaultValue Derived from `schema.name`
15
+ */
16
+ databaseTypeName?: string;
17
+ /**
18
+ * Module specifier used in generated imports.
19
+ *
20
+ * @defaultValue `"@sourceregistry/node-ovsdb"`
21
+ */
22
+ importFrom?: string;
23
+ }
24
+ /**
25
+ * CLI options for reading a schema and writing generated types.
26
+ */
27
+ export interface GenerateTypesCliOptions {
28
+ /**
29
+ * Path to a schema JSON file. When set, live OVSDB access is skipped.
30
+ */
31
+ schemaPath?: string;
32
+ /**
33
+ * Unix socket path for live schema introspection.
34
+ */
35
+ socketPath?: string;
36
+ /**
37
+ * Hostname for live schema introspection over TCP or TLS.
38
+ */
39
+ host?: string;
40
+ /**
41
+ * Port for live schema introspection over TCP or TLS.
42
+ *
43
+ * @defaultValue `6640`
44
+ */
45
+ port?: number;
46
+ /**
47
+ * Enables TLS for live schema introspection.
48
+ */
49
+ tls?: boolean;
50
+ /**
51
+ * Disables TLS peer verification for environments that use self-signed certificates.
52
+ */
53
+ tlsInsecure?: boolean;
54
+ /**
55
+ * Explicit TLS server name used during handshake verification.
56
+ */
57
+ tlsServername?: string;
58
+ /**
59
+ * PEM CA bundle path used for TLS verification.
60
+ */
61
+ tlsCaFile?: string;
62
+ /**
63
+ * PEM client certificate path used for mutual TLS.
64
+ */
65
+ tlsCertFile?: string;
66
+ /**
67
+ * PEM client private key path used for mutual TLS.
68
+ */
69
+ tlsKeyFile?: string;
70
+ /**
71
+ * Database name used with live schema introspection.
72
+ *
73
+ * @defaultValue `"Open_vSwitch"`
74
+ */
75
+ databaseName?: string;
76
+ /**
77
+ * Destination file. When omitted, generated output is returned only.
78
+ */
79
+ outputPath?: string;
80
+ /**
81
+ * Name of the generated top-level database type.
82
+ */
83
+ databaseTypeName?: string;
84
+ /**
85
+ * Module specifier used in generated imports.
86
+ *
87
+ * @defaultValue `"@sourceregistry/node-ovsdb"`
88
+ */
89
+ importFrom?: string;
90
+ }
91
+ /**
92
+ * Generates TypeScript types from an OVSDB schema.
93
+ *
94
+ * The output is designed for direct use as the generic database model for
95
+ * {@link OVSDBClient}.
96
+ */
97
+ export declare function generateTypesFromSchema(options: GenerateTypesOptions): string;
98
+ /**
99
+ * Loads a schema from a file or a live OVSDB server and optionally writes the
100
+ * generated TypeScript output to disk.
101
+ */
102
+ export declare function generateTypesFile(options: GenerateTypesCliOptions): Promise<string>;
103
+ /**
104
+ * Reads a schema JSON file from disk.
105
+ */
106
+ export declare function readSchemaFile(schemaPath: string): Promise<DatabaseSchema>;
107
+ /**
108
+ * Reads a schema from a live OVSDB server.
109
+ */
110
+ export declare function fetchSchemaFromOvsdb(options: {
111
+ socketPath?: string;
112
+ host?: string;
113
+ port?: number;
114
+ tls?: boolean;
115
+ tlsInsecure?: boolean;
116
+ tlsServername?: string;
117
+ tlsCaFile?: string;
118
+ tlsCertFile?: string;
119
+ tlsKeyFile?: string;
120
+ databaseName?: string;
121
+ }): Promise<DatabaseSchema>;
122
+ /**
123
+ * Builds client transport options for live schema introspection.
124
+ */
125
+ export declare function createGeneratorClientOptions(options: {
126
+ socketPath?: string;
127
+ host?: string;
128
+ port?: number;
129
+ tls?: boolean;
130
+ tlsInsecure?: boolean;
131
+ tlsServername?: string;
132
+ tlsCaFile?: string;
133
+ tlsCertFile?: string;
134
+ tlsKeyFile?: string;
135
+ }): Promise<OvsdbClientOptions>;
136
+ //# sourceMappingURL=generator.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"generator.d.ts","sourceRoot":"","sources":["../src/generator.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAC,kBAAkB,EAAC,MAAM,SAAS,CAAC;AAChD,OAAO,KAAK,EAAuB,cAAc,EAAY,MAAM,SAAS,CAAC;AAE7E;;GAEG;AACH,MAAM,WAAW,oBAAoB;IACjC;;OAEG;IACH,MAAM,EAAE,cAAc,CAAC;IAEvB;;;;OAIG;IACH,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAE1B;;;;OAIG;IACH,UAAU,CAAC,EAAE,MAAM,CAAC;CACvB;AAED;;GAEG;AACH,MAAM,WAAW,uBAAuB;IACpC;;OAEG;IACH,UAAU,CAAC,EAAE,MAAM,CAAC;IAEpB;;OAEG;IACH,UAAU,CAAC,EAAE,MAAM,CAAC;IAEpB;;OAEG;IACH,IAAI,CAAC,EAAE,MAAM,CAAC;IAEd;;;;OAIG;IACH,IAAI,CAAC,EAAE,MAAM,CAAC;IAEd;;OAEG;IACH,GAAG,CAAC,EAAE,OAAO,CAAC;IAEd;;OAEG;IACH,WAAW,CAAC,EAAE,OAAO,CAAC;IAEtB;;OAEG;IACH,aAAa,CAAC,EAAE,MAAM,CAAC;IAEvB;;OAEG;IACH,SAAS,CAAC,EAAE,MAAM,CAAC;IAEnB;;OAEG;IACH,WAAW,CAAC,EAAE,MAAM,CAAC;IAErB;;OAEG;IACH,UAAU,CAAC,EAAE,MAAM,CAAC;IAEpB;;;;OAIG;IACH,YAAY,CAAC,EAAE,MAAM,CAAC;IAEtB;;OAEG;IACH,UAAU,CAAC,EAAE,MAAM,CAAC;IAEpB;;OAEG;IACH,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAE1B;;;;OAIG;IACH,UAAU,CAAC,EAAE,MAAM,CAAC;CACvB;AAID;;;;;GAKG;AACH,wBAAgB,uBAAuB,CAAC,OAAO,EAAE,oBAAoB,GAAG,MAAM,CA4D7E;AAED;;;GAGG;AACH,wBAAsB,iBAAiB,CAAC,OAAO,EAAE,uBAAuB,GAAG,OAAO,CAAC,MAAM,CAAC,CAazF;AAED;;GAEG;AACH,wBAAsB,cAAc,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,cAAc,CAAC,CAGhF;AAED;;GAEG;AACH,wBAAsB,oBAAoB,CAAC,OAAO,EAAE;IAChD,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,GAAG,CAAC,EAAE,OAAO,CAAC;IACd,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,YAAY,CAAC,EAAE,MAAM,CAAC;CACzB,GAAG,OAAO,CAAC,cAAc,CAAC,CAS1B;AAqBD;;GAEG;AACH,wBAAsB,4BAA4B,CAAC,OAAO,EAAE;IACxD,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,GAAG,CAAC,EAAE,OAAO,CAAC;IACd,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,UAAU,CAAC,EAAE,MAAM,CAAC;CACvB,GAAG,OAAO,CAAC,kBAAkB,CAAC,CAmB9B"}
package/dist/index.cjs ADDED
@@ -0,0 +1,3 @@
1
+ "use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const v=require("node:fs"),m=require("node:net"),O=require("node:events"),q=require("node:tls");class f extends Error{response;constructor(t){super(t.details?`${t.error}: ${t.details}`:t.error),this.name="OvsdbRpcError",this.response=t}}class d extends Error{payload;constructor(t,e){super(t),this.name="OvsdbProtocolError",this.payload=e}}class w extends Error{operationIndex;operation;result;results;constructor(t){super(`Transaction operation ${t.operationIndex} failed: ${t.result.error}`),this.name="OvsdbTransactionError",this.operationIndex=t.operationIndex,this.operation=t.operation,this.result=t.result,this.results=t.results}}class g{stagedOperations=[];get operations(){return this.stagedOperations}add(t){return this.stagedOperations.push(t),t}insert(t){return this.add(t)}select(t){return this.add(t)}update(t){return this.add(t)}mutate(t){return this.add(t)}delete(t){return this.add(t)}wait(t){return this.add(t)}comment(t){return this.add({op:"comment",comment:t})}assert(t){return this.add({op:"assert",lock:t})}commit(t=!1){return this.add({op:"commit",durable:t})}abort(){return this.add({op:"abort"})}}class y extends O.EventEmitter{timeout;connectionOptions;connectionFactory;socket=null;requestId=1;receiveBuffer="";pendingRequests=new Map;connected=!1;closeEmitted=!1;constructor(t={}){super(),this.timeout=t.timeout??5e3,this.connectionOptions=E(t),this.connectionFactory=t.connectionFactory}get isConnected(){return this.connected}async connect(){if(this.connected)return this;if(!this.connectionFactory&&this.connectionOptions.transport==="unix"&&!v.existsSync(this.connectionOptions.socketPath))throw new Error(`OVSDB socket not found: ${this.connectionOptions.socketPath}`);const t=this.connectionFactory?this.connectionFactory(this.connectionOptions):k(this.connectionOptions);this.attachSocket(t);const e=this.connectionOptions.transport==="tls"?"secureConnect":"connect";return await new Promise((s,n)=>{const a=setTimeout(()=>{i(),this.disposeTransport(new Error(`Connection timeout after ${this.timeout}ms`)),n(new Error(`Connection timeout after ${this.timeout}ms`))},this.timeout),o=()=>{i(),this.connected=!0,this.closeEmitted=!1,this.emit("connect"),s()},c=h=>{i(),n(h)},i=()=>{clearTimeout(a),t.off(e,o),t.off("error",c)};t.once(e,o),t.once("error",c)}),this}async request(t,e=[]){this.assertConnected();const s=this.requestId++,n={method:t,params:e,id:s};return await new Promise((a,o)=>{const c=setTimeout(()=>{this.pendingRequests.delete(s),o(new Error(`Request timeout for method: ${t}`))},this.timeout);this.pendingRequests.set(s,{resolve:i=>a(i),reject:o,timeoutId:c}),this.writeMessage(n).catch(i=>{clearTimeout(c),this.pendingRequests.delete(s),o(i)})})}async notify(t,e=[]){this.assertConnected(),await this.writeMessage({method:t,params:e})}async listDbs(){return await this.request("list_dbs",[])}async getSchema(t="Open_vSwitch"){return await this.request("get_schema",[t])}async transact(t,e){return await this.request("transact",[t,...e])}async transaction(t,e,s={}){const n=new g,a=await e(n),o=[...n.operations],c=s.autoCommit??!0,i=o.some(u=>u.op==="commit"||u.op==="abort");if(c&&!i&&o.push({op:"commit",durable:s.durable??!1}),o.length===0)return{value:a,operations:o,results:[]};const h=await this.request("transact",[t,...o]);for(const[u,l]of h.entries())if(b(l))throw new w({operationIndex:u,operation:o[u],result:l,results:h});return{value:a,operations:o,results:h}}async cancel(t){return await this.request("cancel",[t])}async monitor(t,e,s){return await this.request("monitor",[t,e,s])}async monitorCond(t,e,s){return await this.request("monitor_cond",[t,e,s])}async monitorCondSince(t,e,s,n=null){return await this.request("monitor_cond_since",[t,e,s,n])}async monitorCancel(t){return await this.request("monitor_cancel",[t])}async lock(t){return await this.request("lock",[t])}async steal(t){return await this.request("steal",[t])}async unlock(t){return await this.request("unlock",[t])}async echo(...t){return await this.request("echo",t)}async setDbChangeAware(t=!0){return await this.request("set_db_change_aware",[t])}async close(){this.disposeTransport()}async[Symbol.asyncDispose](){await this.close()}attachSocket(t){this.socket=t,this.receiveBuffer="",t.on("data",this.handleData),t.on("error",this.handleSocketError),t.on("close",this.handleSocketClose)}handleData=t=>{this.receiveBuffer+=typeof t=="string"?t:t.toString("utf8");let e=p(this.receiveBuffer);for(;e;)this.receiveBuffer=e.rest,this.parseFrame(e.frame),e=p(this.receiveBuffer);const s=this.receiveBuffer.trimStart();s&&s[0]!=="{"&&s[0]!=="["&&(this.emitProtocolError("Received non-JSON data on the transport",this.receiveBuffer),this.receiveBuffer="")};handleSocketError=t=>{this.emit("transportError",t),this.disposeTransport(t)};handleSocketClose=()=>{this.disposeTransport()};parseFrame(t){try{const e=JSON.parse(t);this.handleMessage(e)}catch{const s=new d("Failed to parse JSON message",t);this.emit("protocolError",s,t)}}handleMessage(t){if(!t||typeof t!="object"){this.emitProtocolError("Expected a JSON object message",t);return}if("method"in t&&typeof t.method=="string"){const e=t;if(e.id!==void 0&&e.id!==null){this.handleIncomingRequest(e.method,e.params??[],e.id);return}this.handleNotification(t);return}if("id"in t){this.handleResponse(t);return}this.emitProtocolError("Received message without method or id",t)}handleResponse(t){const e=this.pendingRequests.get(t.id);if(!e){this.emitProtocolError("Received response for an unknown request id",t);return}if(this.pendingRequests.delete(t.id),clearTimeout(e.timeoutId),t.error){e.reject(new f(t.error));return}e.resolve(t.result)}async handleIncomingRequest(t,e,s){if(t==="echo"){await this.writeMessage({id:s,result:e,error:null});return}await this.writeMessage({id:s,result:null,error:{error:"not supported",details:`Unsupported server request: ${t}`}})}handleNotification(t){switch(this.emit("notification",t),t.method){case"update":this.emit("update",t);break;case"update2":this.emit("update2",t);break;case"update3":this.emit("update3",t);break;case"locked":this.emit("locked",t);break;case"stolen":this.emit("stolen",t);break;default:this.emitProtocolError("Received an unknown notification method",t);break}}emitProtocolError(t,e){const s=new d(t,e);this.emit("protocolError",s,e)}async writeMessage(t){this.assertConnected(),await new Promise((e,s)=>{this.socket?.write(`${JSON.stringify(t)}
2
+ `,n=>{if(n){s(n);return}e()})})}assertConnected(){if(!this.connected||!this.socket)throw new Error("Not connected to OVSDB")}disposeTransport(t){const e=this.socket;this.socket=null,this.connected=!1,this.receiveBuffer="",e&&(e.off("data",this.handleData),e.off("error",this.handleSocketError),e.off("close",this.handleSocketClose),e.destroyed||e.destroy());const s=t??new Error("Connection closed");for(const n of this.pendingRequests.values())clearTimeout(n.timeoutId),n.reject(s);this.pendingRequests.clear(),this.closeEmitted||(this.closeEmitted=!0,this.emit("close"))}}function E(r={}){if(r.host){const t=r.port??6640;return r.tls?{transport:"tls",host:r.host,port:t,tlsOptions:{host:r.host,port:t,...r.tlsOptions}}:{transport:"tcp",host:r.host,port:t}}return{transport:"unix",socketPath:r.socketPath??"/var/run/openvswitch/db.sock"}}function k(r){switch(r.transport){case"unix":return m.createConnection(r.socketPath);case"tcp":return m.createConnection(r.port,r.host);case"tls":return q.connect(r.tlsOptions)}}function b(r){return typeof r=="object"&&r!==null&&"error"in r&&typeof r.error=="string"}function p(r){let t=0;for(;t<r.length&&/\s/u.test(r[t]);)t+=1;if(t>=r.length)return null;const e=r[t];if(!(e==="{"?"}":e==="["?"]":null))return null;let n=0,a=!1,o=!1;for(let c=t;c<r.length;c+=1){const i=r[c];if(a){if(o){o=!1;continue}if(i==="\\"){o=!0;continue}i==='"'&&(a=!1);continue}if(i==='"'){a=!0;continue}if(i==="{"||i==="["){n+=1;continue}if((i==="}"||i==="]")&&(n-=1,n===0))return{frame:r.slice(t,c+1),rest:r.slice(c+1)}}return null}exports.OVSDBClient=y;exports.OvsdbProtocolError=d;exports.OvsdbRpcError=f;exports.OvsdbTransaction=g;exports.OvsdbTransactionError=w;exports.resolveConnectionOptions=E;
3
+ //# sourceMappingURL=index.cjs.map