@sourceregistry/node-ovsdb
Version:
TypeScript OVSDB client for Node.js
1 lines • 40.9 kB
Source Map (JSON)
{"version":3,"file":"index.cjs","names":[],"sources":["../src/index.ts"],"sourcesContent":["import {existsSync} from \"node:fs\";\nimport {createConnection as createNetConnection, Socket} from \"node:net\";\nimport {EventEmitter} from \"node:events\";\nimport {Duplex} from \"node:stream\";\nimport {connect as connectTls, TLSSocket} from \"node:tls\";\nimport type {ConnectionOptions as TlsConnectionOptions} from \"node:tls\";\n\nimport type {\n AbortOperation,\n AssertOperation,\n CommentOperation,\n CommitOperation,\n DatabaseOperation,\n DatabaseSchema,\n DatabaseTableMap,\n DeleteOperation,\n InsertOperation,\n JsonObject,\n JsonValue,\n ListDbsResult,\n LockedNotification,\n MutateOperation,\n MonitorCondRequest,\n MonitorCondSinceResult,\n MonitorRequest,\n OperationResult,\n OperationResults,\n OvsdbError,\n OvsdbNotification,\n OvsdbResponse,\n OvsdbValue,\n SelectOperation,\n StolenNotification,\n TableUpdates,\n TableUpdates2,\n Update2Notification,\n Update3Notification,\n UpdateNotification,\n UpdateOperation,\n WaitOperation\n} from \"./types\";\n\n/**\n * A socket-compatible stream used by the client transport.\n */\nexport type OvsdbStream = Duplex | Socket | TLSSocket;\n\n/**\n * Supported connection modes for the OVSDB transport.\n */\nexport type OvsdbTransportKind = \"unix\" | \"tcp\" | \"tls\";\n\n/**\n * Resolved Unix socket transport settings.\n */\nexport interface OvsdbUnixConnectionOptions {\n transport: \"unix\";\n socketPath: string;\n}\n\n/**\n * Resolved TCP transport settings.\n */\nexport interface OvsdbTcpConnectionOptions {\n transport: \"tcp\";\n host: string;\n port: number;\n}\n\n/**\n * Resolved TLS transport settings.\n */\nexport interface OvsdbTlsTransportOptions {\n transport: \"tls\";\n host: string;\n port: number;\n tlsOptions: TlsConnectionOptions;\n}\n\n/**\n * Fully resolved transport settings used by the client.\n */\nexport type OvsdbResolvedConnectionOptions =\n | OvsdbUnixConnectionOptions\n | OvsdbTcpConnectionOptions\n | OvsdbTlsTransportOptions;\n\n/**\n * Typed events emitted by {@link OVSDBClient}.\n */\nexport interface OvsdbClientEvents<TDatabase extends DatabaseTableMap = DatabaseTableMap> {\n connect: [];\n close: [];\n notification: [OvsdbNotification<TDatabase>];\n update: [UpdateNotification<TDatabase>];\n update2: [Update2Notification<TDatabase>];\n update3: [Update3Notification<TDatabase>];\n locked: [LockedNotification];\n stolen: [StolenNotification];\n protocolError: [Error, unknown];\n transportError: [Error];\n}\n\n/**\n * Options for configuring an {@link OVSDBClient}.\n */\nexport interface OvsdbClientOptions {\n /**\n * Path to the OVSDB Unix domain socket.\n *\n * @defaultValue `\"/var/run/openvswitch/db.sock\"`\n */\n socketPath?: string;\n\n /**\n * Hostname for TCP or TLS connections.\n *\n * When set, the client uses a network socket instead of a Unix socket.\n */\n host?: string;\n\n /**\n * Port for TCP or TLS connections.\n *\n * @defaultValue `6640`\n */\n port?: number;\n\n /**\n * Enables TLS for network connections.\n *\n * @defaultValue `false`\n */\n tls?: boolean;\n\n /**\n * Extra TLS connection options forwarded to `node:tls`.\n *\n * These options are only used when `tls` is enabled.\n */\n tlsOptions?: TlsConnectionOptions;\n\n /**\n * Request and connection timeout in milliseconds.\n *\n * @defaultValue `5000`\n */\n timeout?: number;\n\n /**\n * Optional stream factory used for testing or custom transports.\n * When set, the client skips the socket path existence check.\n */\n connectionFactory?: (options: OvsdbResolvedConnectionOptions) => OvsdbStream;\n}\n\n/**\n * JSON-RPC transport error returned by the OVSDB server.\n */\nexport class OvsdbRpcError extends Error {\n /**\n * The error payload returned by the server.\n */\n public readonly response: OvsdbError;\n\n /**\n * Creates a new RPC error wrapper.\n */\n constructor(response: OvsdbError) {\n super(response.details ? `${response.error}: ${response.details}` : response.error);\n this.name = \"OvsdbRpcError\";\n this.response = response;\n }\n}\n\n/**\n * Raised when a message does not match the expected JSON-RPC envelope.\n */\nexport class OvsdbProtocolError extends Error {\n /**\n * The raw payload that failed validation.\n */\n public readonly payload: unknown;\n\n /**\n * Creates a new protocol error wrapper.\n */\n constructor(message: string, payload: unknown) {\n super(message);\n this.name = \"OvsdbProtocolError\";\n this.payload = payload;\n }\n}\n\n/**\n * Raised when an OVSDB transaction response contains an operation-level error.\n */\nexport class OvsdbTransactionError<\n TDatabase extends DatabaseTableMap = DatabaseTableMap\n> extends Error {\n /**\n * Zero-based index of the failed operation in the submitted transaction.\n */\n public readonly operationIndex: number;\n\n /**\n * Operation that produced the error.\n */\n public readonly operation: DatabaseOperation<TDatabase>;\n\n /**\n * OVSDB error payload returned for the failed operation.\n */\n public readonly result: OvsdbError;\n\n /**\n * Raw transaction results returned by the server.\n */\n public readonly results: Array<OperationResult<TDatabase, DatabaseOperation<TDatabase>> | OvsdbError>;\n\n /**\n * Creates a new transaction error wrapper.\n */\n constructor(options: {\n operationIndex: number;\n operation: DatabaseOperation<TDatabase>;\n result: OvsdbError;\n results: Array<OperationResult<TDatabase, DatabaseOperation<TDatabase>> | OvsdbError>;\n }) {\n super(`Transaction operation ${options.operationIndex} failed: ${options.result.error}`);\n this.name = \"OvsdbTransactionError\";\n this.operationIndex = options.operationIndex;\n this.operation = options.operation;\n this.result = options.result;\n this.results = options.results;\n }\n}\n\ninterface PendingRequest {\n resolve: (value: unknown) => void;\n reject: (reason?: unknown) => void;\n timeoutId: NodeJS.Timeout;\n}\n\n/**\n * Result payload returned by a staged transaction helper.\n */\nexport interface OvsdbTransactionOutcome<\n TDatabase extends DatabaseTableMap = DatabaseTableMap,\n TValue = void\n> {\n /**\n * Value returned by the transaction callback.\n */\n value: TValue;\n\n /**\n * Operations that were submitted to OVSDB.\n */\n operations: readonly DatabaseOperation<TDatabase>[];\n\n /**\n * Per-operation OVSDB results in submission order.\n */\n results: Array<OperationResult<TDatabase, DatabaseOperation<TDatabase>>>;\n}\n\n/**\n * Options for the staged transaction helper.\n */\nexport interface OvsdbTransactionOptions {\n /**\n * Appends a trailing `commit` operation when the callback succeeds and the\n * staged operations do not already include `commit` or `abort`.\n *\n * @defaultValue `true`\n */\n autoCommit?: boolean;\n\n /**\n * Durable flag used by the auto-generated `commit` operation.\n *\n * @defaultValue `false`\n */\n durable?: boolean;\n}\n\n/**\n * Stages OVSDB operations before sending them as a single `transact` request.\n */\nexport class OvsdbTransaction<TDatabase extends DatabaseTableMap = DatabaseTableMap> {\n private readonly stagedOperations: DatabaseOperation<TDatabase>[] = [];\n\n /**\n * Returns the currently staged operations.\n */\n public get operations(): readonly DatabaseOperation<TDatabase>[] {\n return this.stagedOperations;\n }\n\n /**\n * Adds an operation to the transaction.\n */\n public add<TOperation extends DatabaseOperation<TDatabase>>(operation: TOperation): TOperation {\n this.stagedOperations.push(operation);\n return operation;\n }\n\n /**\n * Stages an insert operation.\n */\n public insert<TOperation extends InsertOperation<TDatabase>>(operation: TOperation): TOperation {\n return this.add(operation);\n }\n\n /**\n * Stages a select operation.\n */\n public select<TOperation extends SelectOperation<TDatabase>>(operation: TOperation): TOperation {\n return this.add(operation);\n }\n\n /**\n * Stages an update operation.\n */\n public update<TOperation extends UpdateOperation<TDatabase>>(operation: TOperation): TOperation {\n return this.add(operation);\n }\n\n /**\n * Stages a mutate operation.\n */\n public mutate<TOperation extends MutateOperation<TDatabase>>(operation: TOperation): TOperation {\n return this.add(operation);\n }\n\n /**\n * Stages a delete operation.\n */\n public delete<TOperation extends DeleteOperation<TDatabase>>(operation: TOperation): TOperation {\n return this.add(operation);\n }\n\n /**\n * Stages a wait operation.\n */\n public wait<TOperation extends WaitOperation<TDatabase>>(operation: TOperation): TOperation {\n return this.add(operation);\n }\n\n /**\n * Stages a comment operation.\n */\n public comment(comment: string): CommentOperation {\n return this.add({\n op: \"comment\",\n comment\n });\n }\n\n /**\n * Stages an assert operation.\n */\n public assert(lock: string): AssertOperation {\n return this.add({\n op: \"assert\",\n lock\n });\n }\n\n /**\n * Stages a commit operation.\n */\n public commit(durable = false): CommitOperation {\n return this.add({\n op: \"commit\",\n durable\n });\n }\n\n /**\n * Stages an abort operation.\n */\n public abort(): AbortOperation {\n return this.add({\n op: \"abort\"\n });\n }\n}\n\n/**\n * A low-level, event-driven OVSDB client for Unix sockets, TCP, or TLS.\n *\n * The client exposes the RFC 7047 primitives together with common\n * Open vSwitch protocol extensions while keeping the API small and predictable.\n */\nexport class OVSDBClient<\n TDatabase extends DatabaseTableMap = DatabaseTableMap\n> extends EventEmitter<OvsdbClientEvents<TDatabase>> implements AsyncDisposable {\n private readonly timeout: number;\n private readonly connectionOptions: OvsdbResolvedConnectionOptions;\n private readonly connectionFactory?: (options: OvsdbResolvedConnectionOptions) => OvsdbStream;\n\n private socket: OvsdbStream | null = null;\n private requestId = 1;\n private receiveBuffer = \"\";\n private pendingRequests = new Map<JsonValue, PendingRequest>();\n private connected = false;\n private closeEmitted = false;\n\n /**\n * Creates a new OVSDB client instance.\n */\n constructor(options: OvsdbClientOptions = {}) {\n super();\n this.timeout = options.timeout ?? 5000;\n this.connectionOptions = resolveConnectionOptions(options);\n this.connectionFactory = options.connectionFactory;\n }\n\n /**\n * Returns `true` when the underlying socket is currently connected.\n */\n public get isConnected(): boolean {\n return this.connected;\n }\n\n /**\n * Opens the transport connection.\n *\n * @returns The connected client instance for chaining.\n */\n public async connect(): Promise<this> {\n if (this.connected) {\n return this;\n }\n\n if (\n !this.connectionFactory &&\n this.connectionOptions.transport === \"unix\" &&\n !existsSync(this.connectionOptions.socketPath)\n ) {\n throw new Error(`OVSDB socket not found: ${this.connectionOptions.socketPath}`);\n }\n\n const socket = this.connectionFactory\n ? this.connectionFactory(this.connectionOptions)\n : createTransport(this.connectionOptions);\n this.attachSocket(socket);\n const connectEvent = this.connectionOptions.transport === \"tls\" ? \"secureConnect\" : \"connect\";\n\n await new Promise<void>((resolve, reject) => {\n const timeoutId = setTimeout(() => {\n cleanup();\n this.disposeTransport(new Error(`Connection timeout after ${this.timeout}ms`));\n reject(new Error(`Connection timeout after ${this.timeout}ms`));\n }, this.timeout);\n\n const onConnect = () => {\n cleanup();\n this.connected = true;\n this.closeEmitted = false;\n this.emit(\"connect\");\n resolve();\n };\n\n const onError = (error: Error) => {\n cleanup();\n reject(error);\n };\n\n const cleanup = () => {\n clearTimeout(timeoutId);\n socket.off(connectEvent, onConnect);\n socket.off(\"error\", onError);\n };\n\n socket.once(connectEvent, onConnect);\n socket.once(\"error\", onError);\n });\n\n return this;\n }\n\n /**\n * Sends a raw JSON-RPC request and resolves with its `result` payload.\n *\n * @param method RPC method name.\n * @param params RPC parameters.\n */\n public async request<TResult>(method: string, params: JsonValue[] = []): Promise<TResult> {\n this.assertConnected();\n\n const id = this.requestId++;\n const payload = {\n method,\n params,\n id\n };\n\n return await new Promise<TResult>((resolve, reject) => {\n const timeoutId = setTimeout(() => {\n this.pendingRequests.delete(id);\n reject(new Error(`Request timeout for method: ${method}`));\n }, this.timeout);\n\n this.pendingRequests.set(id, {\n resolve: (value) => resolve(value as TResult),\n reject,\n timeoutId\n });\n\n this.writeMessage(payload).catch((error) => {\n clearTimeout(timeoutId);\n this.pendingRequests.delete(id);\n reject(error);\n });\n });\n }\n\n /**\n * Sends a JSON-RPC notification without waiting for a response.\n *\n * @param method RPC method name.\n * @param params RPC parameters.\n */\n public async notify(method: string, params: JsonValue[] = []): Promise<void> {\n this.assertConnected();\n await this.writeMessage({method, params});\n }\n\n /**\n * Returns the database names exposed by the connected OVSDB server.\n */\n public async listDbs(): Promise<ListDbsResult> {\n return await this.request<ListDbsResult>(\"list_dbs\", []);\n }\n\n /**\n * Returns the schema definition for a database.\n *\n * @param dbName Database name.\n */\n public async getSchema(dbName = \"Open_vSwitch\"): Promise<DatabaseSchema> {\n return await this.request<DatabaseSchema>(\"get_schema\", [dbName]);\n }\n\n /**\n * Executes a single transaction.\n *\n * The response preserves operation ordering, so tuple inputs infer tuple outputs.\n *\n * @param dbName Database name.\n * @param operations Transaction operations.\n */\n public async transact<TOperations extends readonly DatabaseOperation<TDatabase>[]>(\n dbName: string,\n operations: [...TOperations]\n ): Promise<OperationResults<TDatabase, TOperations>> {\n return await this.request<OperationResults<TDatabase, TOperations>>(\n \"transact\",\n [dbName, ...operations] as JsonValue[]\n );\n }\n\n /**\n * Stages a transaction in a callback and submits it only if the callback\n * completes successfully.\n *\n * This helper is convenient when you want a scoped, imperative API while\n * still sending exactly one OVSDB `transact` request.\n *\n * @param dbName Database name.\n * @param callback Callback that stages operations on the transaction object.\n * @param options Auto-commit behavior for the staged transaction.\n */\n public async transaction<TValue>(\n dbName: string,\n callback: (transaction: OvsdbTransaction<TDatabase>) => Promise<TValue> | TValue,\n options: OvsdbTransactionOptions = {}\n ): Promise<OvsdbTransactionOutcome<TDatabase, TValue>> {\n const transaction = new OvsdbTransaction<TDatabase>();\n const value = await callback(transaction);\n\n const operations = [...transaction.operations];\n const autoCommit = options.autoCommit ?? true;\n const hasFinalizer = operations.some((operation) => operation.op === \"commit\" || operation.op === \"abort\");\n if (autoCommit && !hasFinalizer) {\n operations.push({\n op: \"commit\",\n durable: options.durable ?? false\n });\n }\n\n if (operations.length === 0) {\n return {\n value,\n operations,\n results: []\n };\n }\n\n const results = await this.request<Array<OperationResult<TDatabase, DatabaseOperation<TDatabase>> | OvsdbError>>(\n \"transact\",\n [dbName, ...operations] as JsonValue[]\n );\n\n for (const [index, result] of results.entries()) {\n if (isOvsdbError(result)) {\n throw new OvsdbTransactionError<TDatabase>({\n operationIndex: index,\n operation: operations[index] as DatabaseOperation<TDatabase>,\n result,\n results\n });\n }\n }\n\n return {\n value,\n operations,\n results: results as Array<OperationResult<TDatabase, DatabaseOperation<TDatabase>>>\n };\n }\n\n /**\n * Cancels a previously issued request by id.\n *\n * @param requestId JSON-RPC request id to cancel.\n */\n public async cancel(requestId: JsonValue): Promise<null | JsonObject> {\n return await this.request<null | JsonObject>(\"cancel\", [requestId]);\n }\n\n /**\n * Starts a standard RFC 7047 monitor.\n *\n * @param dbName Database name.\n * @param monitorId Application-defined monitor id.\n * @param monitorRequests Per-table monitor definitions.\n */\n public async monitor(\n dbName: string,\n monitorId: JsonValue,\n monitorRequests: Record<string, MonitorRequest<TDatabase>>\n ): Promise<TableUpdates<TDatabase>> {\n return await this.request<TableUpdates<TDatabase>>(\"monitor\", [\n dbName,\n monitorId,\n monitorRequests as JsonValue\n ]);\n }\n\n /**\n * Starts an Open vSwitch conditional monitor.\n *\n * @param dbName Database name.\n * @param monitorId Application-defined monitor id.\n * @param monitorRequests Per-table conditional monitor definitions.\n */\n public async monitorCond(\n dbName: string,\n monitorId: JsonValue,\n monitorRequests: Record<string, MonitorCondRequest<TDatabase>>\n ): Promise<TableUpdates2<TDatabase>> {\n return await this.request<TableUpdates2<TDatabase>>(\"monitor_cond\", [\n dbName,\n monitorId,\n monitorRequests as JsonValue\n ]);\n }\n\n /**\n * Starts an Open vSwitch conditional monitor from a known transaction id.\n *\n * @param dbName Database name.\n * @param monitorId Application-defined monitor id.\n * @param monitorRequests Per-table conditional monitor definitions.\n * @param lastTransactionId Last seen transaction id, or `null` for a fresh snapshot.\n */\n public async monitorCondSince(\n dbName: string,\n monitorId: JsonValue,\n monitorRequests: Record<string, MonitorCondRequest<TDatabase>>,\n lastTransactionId: string | null = null\n ): Promise<MonitorCondSinceResult<TDatabase>> {\n return await this.request<MonitorCondSinceResult<TDatabase>>(\"monitor_cond_since\", [\n dbName,\n monitorId,\n monitorRequests as JsonValue,\n lastTransactionId\n ]);\n }\n\n /**\n * Cancels a monitor by its monitor id.\n *\n * @param monitorId Monitor id used when the monitor was created.\n */\n public async monitorCancel(monitorId: JsonValue): Promise<null | JsonObject> {\n return await this.request<null | JsonObject>(\"monitor_cancel\", [monitorId]);\n }\n\n /**\n * Acquires a named database lock.\n *\n * @param lockId Lock identifier.\n */\n public async lock(lockId: string): Promise<null | JsonObject> {\n return await this.request<null | JsonObject>(\"lock\", [lockId]);\n }\n\n /**\n * Forces ownership of a named database lock.\n *\n * @param lockId Lock identifier.\n */\n public async steal(lockId: string): Promise<null | JsonObject> {\n return await this.request<null | JsonObject>(\"steal\", [lockId]);\n }\n\n /**\n * Releases a previously acquired named database lock.\n *\n * @param lockId Lock identifier.\n */\n public async unlock(lockId: string): Promise<null | JsonObject> {\n return await this.request<null | JsonObject>(\"unlock\", [lockId]);\n }\n\n /**\n * Sends an echo request to validate transport liveness.\n *\n * @param payload Values to be echoed back by the server.\n */\n public async echo<TPayload extends OvsdbValue[]>(...payload: TPayload): Promise<TPayload> {\n return await this.request<TPayload>(\"echo\", payload as JsonValue[]);\n }\n\n /**\n * Enables or disables Open vSwitch database change awareness.\n *\n * @param enabled Whether the server should report change awareness metadata.\n */\n public async setDbChangeAware(enabled = true): Promise<boolean | null> {\n return await this.request<boolean | null>(\"set_db_change_aware\", [enabled]);\n }\n\n /**\n * Closes the connection and rejects all pending requests.\n */\n public async close(): Promise<void> {\n this.disposeTransport();\n }\n\n /**\n * Implements `AsyncDisposable`.\n */\n public async [Symbol.asyncDispose](): Promise<void> {\n await this.close();\n }\n\n private attachSocket(socket: OvsdbStream): void {\n this.socket = socket;\n this.receiveBuffer = \"\";\n\n socket.on(\"data\", this.handleData);\n socket.on(\"error\", this.handleSocketError);\n socket.on(\"close\", this.handleSocketClose);\n }\n\n private readonly handleData = (chunk: Buffer | string): void => {\n this.receiveBuffer += typeof chunk === \"string\" ? chunk : chunk.toString(\"utf8\");\n\n let frame = extractJsonFrame(this.receiveBuffer);\n while (frame) {\n this.receiveBuffer = frame.rest;\n this.parseFrame(frame.frame);\n frame = extractJsonFrame(this.receiveBuffer);\n }\n\n const trimmed = this.receiveBuffer.trimStart();\n if (trimmed && trimmed[0] !== \"{\" && trimmed[0] !== \"[\") {\n this.emitProtocolError(\"Received non-JSON data on the transport\", this.receiveBuffer);\n this.receiveBuffer = \"\";\n }\n };\n\n private readonly handleSocketError = (error: Error): void => {\n this.emit(\"transportError\", error);\n this.disposeTransport(error);\n };\n\n private readonly handleSocketClose = (): void => {\n this.disposeTransport();\n };\n\n private parseFrame(frame: string): void {\n try {\n const payload = JSON.parse(frame) as unknown;\n this.handleMessage(payload);\n } catch (error) {\n const protocolError = new OvsdbProtocolError(\"Failed to parse JSON message\", frame);\n this.emit(\"protocolError\", protocolError, frame);\n if (error instanceof Error) {\n void error;\n }\n }\n }\n\n private handleMessage(payload: unknown): void {\n if (!payload || typeof payload !== \"object\") {\n this.emitProtocolError(\"Expected a JSON object message\", payload);\n return;\n }\n\n if (\"method\" in payload && typeof payload.method === \"string\") {\n const message = payload as {method: string; params?: JsonValue[]; id?: JsonValue | null};\n\n if (message.id !== undefined && message.id !== null) {\n void this.handleIncomingRequest(message.method, message.params ?? [], message.id);\n return;\n }\n\n this.handleNotification(payload as OvsdbNotification<TDatabase>);\n return;\n }\n\n if (\"id\" in payload) {\n this.handleResponse(payload as OvsdbResponse<unknown>);\n return;\n }\n\n this.emitProtocolError(\"Received message without method or id\", payload);\n }\n\n private handleResponse(response: OvsdbResponse<unknown>): void {\n const pendingRequest = this.pendingRequests.get(response.id);\n if (!pendingRequest) {\n this.emitProtocolError(\"Received response for an unknown request id\", response);\n return;\n }\n\n this.pendingRequests.delete(response.id);\n clearTimeout(pendingRequest.timeoutId);\n\n if (response.error) {\n pendingRequest.reject(new OvsdbRpcError(response.error));\n return;\n }\n\n pendingRequest.resolve(response.result);\n }\n\n private async handleIncomingRequest(method: string, params: JsonValue[], id: JsonValue): Promise<void> {\n if (method === \"echo\") {\n await this.writeMessage({\n id,\n result: params,\n error: null\n });\n return;\n }\n\n await this.writeMessage({\n id,\n result: null,\n error: {\n error: \"not supported\",\n details: `Unsupported server request: ${method}`\n }\n });\n }\n\n private handleNotification(notification: OvsdbNotification<TDatabase>): void {\n this.emit(\"notification\", notification);\n\n switch (notification.method) {\n case \"update\":\n this.emit(\"update\", notification);\n break;\n case \"update2\":\n this.emit(\"update2\", notification);\n break;\n case \"update3\":\n this.emit(\"update3\", notification);\n break;\n case \"locked\":\n this.emit(\"locked\", notification);\n break;\n case \"stolen\":\n this.emit(\"stolen\", notification);\n break;\n default:\n this.emitProtocolError(\"Received an unknown notification method\", notification);\n break;\n }\n }\n\n private emitProtocolError(message: string, payload: unknown): void {\n const error = new OvsdbProtocolError(message, payload);\n this.emit(\"protocolError\", error, payload);\n }\n\n private async writeMessage(payload: JsonObject): Promise<void> {\n this.assertConnected();\n\n await new Promise<void>((resolve, reject) => {\n this.socket?.write(`${JSON.stringify(payload)}\\n`, (error) => {\n if (error) {\n reject(error);\n return;\n }\n resolve();\n });\n });\n }\n\n private assertConnected(): void {\n if (!this.connected || !this.socket) {\n throw new Error(\"Not connected to OVSDB\");\n }\n }\n\n private disposeTransport(reason?: Error): void {\n const socket = this.socket;\n this.socket = null;\n this.connected = false;\n this.receiveBuffer = \"\";\n\n if (socket) {\n socket.off(\"data\", this.handleData);\n socket.off(\"error\", this.handleSocketError);\n socket.off(\"close\", this.handleSocketClose);\n if (!socket.destroyed) {\n socket.destroy();\n }\n }\n\n const closeError = reason ?? new Error(\"Connection closed\");\n for (const pendingRequest of this.pendingRequests.values()) {\n clearTimeout(pendingRequest.timeoutId);\n pendingRequest.reject(closeError);\n }\n this.pendingRequests.clear();\n\n if (!this.closeEmitted) {\n this.closeEmitted = true;\n this.emit(\"close\");\n }\n }\n}\n\nexport * from \"./types\";\n\n/**\n * Resolves the user-supplied transport options into an explicit connection mode.\n */\nexport function resolveConnectionOptions(options: OvsdbClientOptions = {}): OvsdbResolvedConnectionOptions {\n if (options.host) {\n const port = options.port ?? 6640;\n if (options.tls) {\n return {\n transport: \"tls\",\n host: options.host,\n port,\n tlsOptions: {\n host: options.host,\n port,\n ...options.tlsOptions\n }\n };\n }\n\n return {\n transport: \"tcp\",\n host: options.host,\n port\n };\n }\n\n return {\n transport: \"unix\",\n socketPath: options.socketPath ?? \"/var/run/openvswitch/db.sock\"\n };\n}\n\nfunction createTransport(options: OvsdbResolvedConnectionOptions): OvsdbStream {\n switch (options.transport) {\n case \"unix\":\n return createNetConnection(options.socketPath);\n case \"tcp\":\n return createNetConnection(options.port, options.host);\n case \"tls\":\n return connectTls(options.tlsOptions);\n }\n}\n\nfunction isOvsdbError(value: unknown): value is OvsdbError {\n return typeof value === \"object\" && value !== null && \"error\" in value && typeof value.error === \"string\";\n}\n\nfunction extractJsonFrame(buffer: string): {frame: string; rest: string} | null {\n let startIndex = 0;\n while (startIndex < buffer.length && /\\s/u.test(buffer[startIndex])) {\n startIndex += 1;\n }\n\n if (startIndex >= buffer.length) {\n return null;\n }\n\n const opening = buffer[startIndex];\n const closing = opening === \"{\" ? \"}\" : opening === \"[\" ? \"]\" : null;\n if (!closing) {\n return null;\n }\n\n let depth = 0;\n let inString = false;\n let escaping = false;\n\n for (let index = startIndex; index < buffer.length; index += 1) {\n const char = buffer[index];\n\n if (inString) {\n if (escaping) {\n escaping = false;\n continue;\n }\n\n if (char === \"\\\\\") {\n escaping = true;\n continue;\n }\n\n if (char === \"\\\"\") {\n inString = false;\n }\n continue;\n }\n\n if (char === \"\\\"\") {\n inString = true;\n continue;\n }\n\n if (char === \"{\" || char === \"[\") {\n depth += 1;\n continue;\n }\n\n if (char === \"}\" || char === \"]\") {\n depth -= 1;\n if (depth === 0) {\n return {\n frame: buffer.slice(startIndex, index + 1),\n rest: buffer.slice(index + 1)\n };\n }\n }\n }\n\n return null;\n}\n"],"mappings":"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"}