@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.
@@ -0,0 +1,167 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.runCli = runCli;
4
+ const promises_1 = require("node:fs/promises");
5
+ const node_path_1 = require("node:path");
6
+ const generator_1 = require("./generator");
7
+ /**
8
+ * Runs the OVSDB type generation CLI.
9
+ */
10
+ async function runCli(argv) {
11
+ const args = parseArgs(argv);
12
+ if (args.help) {
13
+ process.stdout.write(getHelpText());
14
+ return 0;
15
+ }
16
+ if (!args.schemaPath && !args.socketPath && !args.host) {
17
+ process.stderr.write("Missing input. Provide --schema <file>, --socket <path>, or --host <name>.\n");
18
+ process.stderr.write(getHelpText());
19
+ return 1;
20
+ }
21
+ if (!args.stdout && !args.outputPath) {
22
+ process.stderr.write("Missing destination. Provide --out <file> or use --stdout.\n");
23
+ process.stderr.write(getHelpText());
24
+ return 1;
25
+ }
26
+ try {
27
+ const outputPath = args.outputPath ? (0, node_path_1.resolve)(args.outputPath) : undefined;
28
+ if (outputPath) {
29
+ await (0, promises_1.mkdir)((0, node_path_1.dirname)(outputPath), { recursive: true });
30
+ }
31
+ const generated = await (0, generator_1.generateTypesFile)({
32
+ schemaPath: args.schemaPath ? (0, node_path_1.resolve)(args.schemaPath) : undefined,
33
+ socketPath: args.socketPath,
34
+ host: args.host,
35
+ port: args.port,
36
+ tls: args.tls,
37
+ tlsInsecure: args.tlsInsecure,
38
+ tlsServername: args.tlsServername,
39
+ tlsCaFile: args.tlsCaFile ? (0, node_path_1.resolve)(args.tlsCaFile) : undefined,
40
+ tlsCertFile: args.tlsCertFile ? (0, node_path_1.resolve)(args.tlsCertFile) : undefined,
41
+ tlsKeyFile: args.tlsKeyFile ? (0, node_path_1.resolve)(args.tlsKeyFile) : undefined,
42
+ databaseName: args.databaseName,
43
+ outputPath,
44
+ databaseTypeName: args.databaseTypeName,
45
+ importFrom: args.importFrom
46
+ });
47
+ if (args.stdout) {
48
+ process.stdout.write(generated);
49
+ }
50
+ else if (outputPath) {
51
+ process.stdout.write(`Generated TypeScript types at ${outputPath}\n`);
52
+ }
53
+ return 0;
54
+ }
55
+ catch (error) {
56
+ process.stderr.write(`${formatError(error)}\n`);
57
+ return 1;
58
+ }
59
+ }
60
+ function parseArgs(argv) {
61
+ const parsed = {};
62
+ for (let index = 0; index < argv.length; index += 1) {
63
+ const arg = argv[index];
64
+ switch (arg) {
65
+ case "--schema":
66
+ parsed.schemaPath = requireValue(argv, ++index, arg);
67
+ break;
68
+ case "--socket":
69
+ parsed.socketPath = requireValue(argv, ++index, arg);
70
+ break;
71
+ case "--host":
72
+ parsed.host = requireValue(argv, ++index, arg);
73
+ break;
74
+ case "--port":
75
+ parsed.port = Number.parseInt(requireValue(argv, ++index, arg), 10);
76
+ if (Number.isNaN(parsed.port)) {
77
+ throw new Error("Expected --port to be a number");
78
+ }
79
+ break;
80
+ case "--db":
81
+ case "--database":
82
+ parsed.databaseName = requireValue(argv, ++index, arg);
83
+ break;
84
+ case "--tls":
85
+ parsed.tls = true;
86
+ break;
87
+ case "--tls-insecure":
88
+ parsed.tlsInsecure = true;
89
+ break;
90
+ case "--tls-servername":
91
+ parsed.tlsServername = requireValue(argv, ++index, arg);
92
+ break;
93
+ case "--tls-ca-file":
94
+ parsed.tlsCaFile = requireValue(argv, ++index, arg);
95
+ break;
96
+ case "--tls-cert-file":
97
+ parsed.tlsCertFile = requireValue(argv, ++index, arg);
98
+ break;
99
+ case "--tls-key-file":
100
+ parsed.tlsKeyFile = requireValue(argv, ++index, arg);
101
+ break;
102
+ case "--out":
103
+ case "--output":
104
+ parsed.outputPath = requireValue(argv, ++index, arg);
105
+ break;
106
+ case "--name":
107
+ parsed.databaseTypeName = requireValue(argv, ++index, arg);
108
+ break;
109
+ case "--import-from":
110
+ parsed.importFrom = requireValue(argv, ++index, arg);
111
+ break;
112
+ case "--stdout":
113
+ parsed.stdout = true;
114
+ break;
115
+ case "--help":
116
+ case "-h":
117
+ parsed.help = true;
118
+ break;
119
+ default:
120
+ throw new Error(`Unknown argument: ${arg}`);
121
+ }
122
+ }
123
+ return parsed;
124
+ }
125
+ function requireValue(argv, index, flag) {
126
+ const value = argv[index];
127
+ if (!value || value.startsWith("--")) {
128
+ throw new Error(`Expected a value after ${flag}`);
129
+ }
130
+ return value;
131
+ }
132
+ function getHelpText() {
133
+ return [
134
+ "Usage: ovsdb-generate [options]",
135
+ "",
136
+ "Inputs:",
137
+ " --schema <file> Read an OVSDB schema JSON file",
138
+ " --socket <path> Read schema from a live OVSDB Unix socket",
139
+ " --host <name> Read schema from a live OVSDB TCP/TLS endpoint",
140
+ " --port <number> Port for TCP/TLS introspection (default: 6640)",
141
+ " --db <name> Database name for live introspection",
142
+ " --tls Enable TLS for live TCP connections",
143
+ " --tls-insecure Disable TLS certificate verification",
144
+ " --tls-servername <sni> Override TLS server name",
145
+ " --tls-ca-file <file> PEM CA bundle for TLS verification",
146
+ " --tls-cert-file <file> PEM client certificate for mTLS",
147
+ " --tls-key-file <file> PEM client private key for mTLS",
148
+ "",
149
+ "Output:",
150
+ " --out <file> Write generated TypeScript to a file",
151
+ " --stdout Print generated TypeScript to stdout",
152
+ "",
153
+ "Generation:",
154
+ " --name <typeName> Override generated top-level database type name",
155
+ " --import-from <module> Override imported OVSDB type module",
156
+ "",
157
+ "Other:",
158
+ " -h, --help Show this help text",
159
+ ""
160
+ ].join("\n");
161
+ }
162
+ function formatError(error) {
163
+ if (error instanceof Error) {
164
+ return error.message;
165
+ }
166
+ return String(error);
167
+ }
@@ -0,0 +1,216 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.generateTypesFromSchema = generateTypesFromSchema;
4
+ exports.generateTypesFile = generateTypesFile;
5
+ exports.readSchemaFile = readSchemaFile;
6
+ exports.fetchSchemaFromOvsdb = fetchSchemaFromOvsdb;
7
+ exports.createGeneratorClientOptions = createGeneratorClientOptions;
8
+ const promises_1 = require("node:fs/promises");
9
+ const index_1 = require("./index");
10
+ const OVSDB_IMPORT_TYPES = ["OvsMap", "OvsSet", "Uuid"];
11
+ /**
12
+ * Generates TypeScript types from an OVSDB schema.
13
+ *
14
+ * The output is designed for direct use as the generic database model for
15
+ * {@link OVSDBClient}.
16
+ */
17
+ function generateTypesFromSchema(options) {
18
+ const { schema } = options;
19
+ const importFrom = options.importFrom ?? "@sourceregistry/node-ovsdb";
20
+ const databaseTypeName = options.databaseTypeName ?? `${toPascalCase(schema.name)}Database`;
21
+ const importLine = `import type {${OVSDB_IMPORT_TYPES.join(", ")}} from "${importFrom}";`;
22
+ const tableNames = Object.keys(schema.tables).sort((left, right) => left.localeCompare(right));
23
+ const rowInterfaces = tableNames.map((tableName) => {
24
+ const interfaceName = `${toPascalCase(tableName)}Row`;
25
+ const table = schema.tables[tableName];
26
+ const propertyLines = Object.keys(table.columns)
27
+ .sort((left, right) => left.localeCompare(right))
28
+ .map((columnName) => {
29
+ const column = table.columns[columnName];
30
+ const tsType = renderColumnType(column.type);
31
+ const optional = isOptionalColumn(column.type) ? "?" : "";
32
+ return ` ${toPropertyKey(columnName)}${optional}: ${tsType};`;
33
+ });
34
+ return [
35
+ `/**`,
36
+ ` * Row model for the \`${tableName}\` table in the \`${schema.name}\` schema.`,
37
+ ` */`,
38
+ `export interface ${interfaceName} {`,
39
+ ...propertyLines,
40
+ `}`
41
+ ].join("\n");
42
+ });
43
+ const databaseLines = [
44
+ `/**`,
45
+ ` * Generated database model for the \`${schema.name}\` schema.`,
46
+ ` */`,
47
+ `export interface ${databaseTypeName} {`,
48
+ ...tableNames.map((tableName) => ` ${toPropertyKey(tableName)}: ${toPascalCase(tableName)}Row;`),
49
+ `}`
50
+ ];
51
+ const tableNamesConst = [
52
+ `/**`,
53
+ ` * Table names available in the \`${schema.name}\` schema.`,
54
+ ` */`,
55
+ `export const ${databaseTypeName}TableNames = ${JSON.stringify(tableNames)} as const;`
56
+ ];
57
+ return [
58
+ `/* eslint-disable */`,
59
+ `/*`,
60
+ ` * This file was generated by ovsdb-generate.`,
61
+ ` * Schema: ${schema.name}@${schema.version}`,
62
+ ` */`,
63
+ "",
64
+ importLine,
65
+ "",
66
+ ...rowInterfaces.flatMap((block) => [block, ""]),
67
+ ...databaseLines,
68
+ "",
69
+ ...tableNamesConst
70
+ ].join("\n").trimEnd() + "\n";
71
+ }
72
+ /**
73
+ * Loads a schema from a file or a live OVSDB server and optionally writes the
74
+ * generated TypeScript output to disk.
75
+ */
76
+ async function generateTypesFile(options) {
77
+ const schema = await loadSchema(options);
78
+ const output = generateTypesFromSchema({
79
+ schema,
80
+ databaseTypeName: options.databaseTypeName,
81
+ importFrom: options.importFrom
82
+ });
83
+ if (options.outputPath) {
84
+ await (0, promises_1.writeFile)(options.outputPath, output, "utf8");
85
+ }
86
+ return output;
87
+ }
88
+ /**
89
+ * Reads a schema JSON file from disk.
90
+ */
91
+ async function readSchemaFile(schemaPath) {
92
+ const raw = await (0, promises_1.readFile)(schemaPath, "utf8");
93
+ return JSON.parse(raw);
94
+ }
95
+ /**
96
+ * Reads a schema from a live OVSDB server.
97
+ */
98
+ async function fetchSchemaFromOvsdb(options) {
99
+ const client = new index_1.OVSDBClient(await createGeneratorClientOptions(options));
100
+ try {
101
+ await client.connect();
102
+ return await client.getSchema(options.databaseName ?? "Open_vSwitch");
103
+ }
104
+ finally {
105
+ await client.close();
106
+ }
107
+ }
108
+ async function loadSchema(options) {
109
+ if (options.schemaPath) {
110
+ return await readSchemaFile(options.schemaPath);
111
+ }
112
+ return await fetchSchemaFromOvsdb({
113
+ socketPath: options.socketPath,
114
+ host: options.host,
115
+ port: options.port,
116
+ tls: options.tls,
117
+ tlsInsecure: options.tlsInsecure,
118
+ tlsServername: options.tlsServername,
119
+ tlsCaFile: options.tlsCaFile,
120
+ tlsCertFile: options.tlsCertFile,
121
+ tlsKeyFile: options.tlsKeyFile,
122
+ databaseName: options.databaseName
123
+ });
124
+ }
125
+ /**
126
+ * Builds client transport options for live schema introspection.
127
+ */
128
+ async function createGeneratorClientOptions(options) {
129
+ if (!options.host) {
130
+ return {
131
+ socketPath: options.socketPath
132
+ };
133
+ }
134
+ return {
135
+ host: options.host,
136
+ port: options.port,
137
+ tls: options.tls,
138
+ tlsOptions: {
139
+ servername: options.tlsServername ?? options.host,
140
+ rejectUnauthorized: options.tlsInsecure ? false : undefined,
141
+ ca: options.tlsCaFile ? await (0, promises_1.readFile)(options.tlsCaFile, "utf8") : undefined,
142
+ cert: options.tlsCertFile ? await (0, promises_1.readFile)(options.tlsCertFile, "utf8") : undefined,
143
+ key: options.tlsKeyFile ? await (0, promises_1.readFile)(options.tlsKeyFile, "utf8") : undefined
144
+ }
145
+ };
146
+ }
147
+ function renderColumnType(type) {
148
+ if (typeof type === "string") {
149
+ return renderAtomicType(type);
150
+ }
151
+ if ("value" in type && type.value !== undefined) {
152
+ return `OvsMap<${renderBaseType(type.key)}, ${renderBaseType(type.value)}>`;
153
+ }
154
+ if (isScalarType(type)) {
155
+ return renderBaseType(type.key);
156
+ }
157
+ return `OvsSet<${renderBaseType(type.key)}>`;
158
+ }
159
+ function isOptionalColumn(type) {
160
+ if (typeof type === "string") {
161
+ return false;
162
+ }
163
+ return type.min === 0;
164
+ }
165
+ function isScalarType(type) {
166
+ const min = type.min ?? 1;
167
+ const max = type.max ?? 1;
168
+ return min === 1 && max === 1;
169
+ }
170
+ function renderBaseType(baseType) {
171
+ if (typeof baseType === "string") {
172
+ return renderAtomicType(baseType);
173
+ }
174
+ if (baseType.enum) {
175
+ return renderEnum(baseType.enum);
176
+ }
177
+ return renderAtomicType(baseType.type);
178
+ }
179
+ function renderAtomicType(type) {
180
+ switch (type) {
181
+ case "integer":
182
+ case "real":
183
+ return "number";
184
+ case "boolean":
185
+ return "boolean";
186
+ case "string":
187
+ return "string";
188
+ case "uuid":
189
+ return "Uuid";
190
+ default:
191
+ return "unknown";
192
+ }
193
+ }
194
+ function renderEnum(value) {
195
+ if (Array.isArray(value) && value[0] === "set" && Array.isArray(value[1])) {
196
+ const members = value[1]
197
+ .map((member) => typeof member === "string" ? JSON.stringify(member) : renderJsonValue(member))
198
+ .join(" | ");
199
+ return members || "never";
200
+ }
201
+ return renderJsonValue(value);
202
+ }
203
+ function renderJsonValue(value) {
204
+ return JSON.stringify(value);
205
+ }
206
+ function toPascalCase(value) {
207
+ return value
208
+ .replace(/[^a-zA-Z0-9]+/g, " ")
209
+ .split(" ")
210
+ .filter(Boolean)
211
+ .map((part) => part.charAt(0).toUpperCase() + part.slice(1))
212
+ .join("");
213
+ }
214
+ function toPropertyKey(value) {
215
+ return /^[$A-Z_][0-9A-Z_$]*$/i.test(value) ? value : JSON.stringify(value);
216
+ }