@sourceregistry/node-ovsdb
Version:
TypeScript OVSDB client for Node.js
217 lines (216 loc) • 7.34 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.generateTypesFromSchema = generateTypesFromSchema;
exports.generateTypesFile = generateTypesFile;
exports.readSchemaFile = readSchemaFile;
exports.fetchSchemaFromOvsdb = fetchSchemaFromOvsdb;
exports.createGeneratorClientOptions = createGeneratorClientOptions;
const promises_1 = require("node:fs/promises");
const index_1 = require("./index");
const OVSDB_IMPORT_TYPES = ["OvsMap", "OvsSet", "Uuid"];
/**
* Generates TypeScript types from an OVSDB schema.
*
* The output is designed for direct use as the generic database model for
* {@link OVSDBClient}.
*/
function generateTypesFromSchema(options) {
const { schema } = options;
const importFrom = options.importFrom ?? "@sourceregistry/node-ovsdb";
const databaseTypeName = options.databaseTypeName ?? `${toPascalCase(schema.name)}Database`;
const importLine = `import type {${OVSDB_IMPORT_TYPES.join(", ")}} from "${importFrom}";`;
const tableNames = Object.keys(schema.tables).sort((left, right) => left.localeCompare(right));
const rowInterfaces = tableNames.map((tableName) => {
const interfaceName = `${toPascalCase(tableName)}Row`;
const table = schema.tables[tableName];
const propertyLines = Object.keys(table.columns)
.sort((left, right) => left.localeCompare(right))
.map((columnName) => {
const column = table.columns[columnName];
const tsType = renderColumnType(column.type);
const optional = isOptionalColumn(column.type) ? "?" : "";
return ` ${toPropertyKey(columnName)}${optional}: ${tsType};`;
});
return [
`/**`,
` * Row model for the \`${tableName}\` table in the \`${schema.name}\` schema.`,
` */`,
`export interface ${interfaceName} {`,
...propertyLines,
`}`
].join("\n");
});
const databaseLines = [
`/**`,
` * Generated database model for the \`${schema.name}\` schema.`,
` */`,
`export interface ${databaseTypeName} {`,
...tableNames.map((tableName) => ` ${toPropertyKey(tableName)}: ${toPascalCase(tableName)}Row;`),
`}`
];
const tableNamesConst = [
`/**`,
` * Table names available in the \`${schema.name}\` schema.`,
` */`,
`export const ${databaseTypeName}TableNames = ${JSON.stringify(tableNames)} as const;`
];
return [
`/* eslint-disable */`,
`/*`,
` * This file was generated by ovsdb-generate.`,
` * Schema: ${schema.name}@${schema.version}`,
` */`,
"",
importLine,
"",
...rowInterfaces.flatMap((block) => [block, ""]),
...databaseLines,
"",
...tableNamesConst
].join("\n").trimEnd() + "\n";
}
/**
* Loads a schema from a file or a live OVSDB server and optionally writes the
* generated TypeScript output to disk.
*/
async function generateTypesFile(options) {
const schema = await loadSchema(options);
const output = generateTypesFromSchema({
schema,
databaseTypeName: options.databaseTypeName,
importFrom: options.importFrom
});
if (options.outputPath) {
await (0, promises_1.writeFile)(options.outputPath, output, "utf8");
}
return output;
}
/**
* Reads a schema JSON file from disk.
*/
async function readSchemaFile(schemaPath) {
const raw = await (0, promises_1.readFile)(schemaPath, "utf8");
return JSON.parse(raw);
}
/**
* Reads a schema from a live OVSDB server.
*/
async function fetchSchemaFromOvsdb(options) {
const client = new index_1.OVSDBClient(await createGeneratorClientOptions(options));
try {
await client.connect();
return await client.getSchema(options.databaseName ?? "Open_vSwitch");
}
finally {
await client.close();
}
}
async function loadSchema(options) {
if (options.schemaPath) {
return await readSchemaFile(options.schemaPath);
}
return await fetchSchemaFromOvsdb({
socketPath: options.socketPath,
host: options.host,
port: options.port,
tls: options.tls,
tlsInsecure: options.tlsInsecure,
tlsServername: options.tlsServername,
tlsCaFile: options.tlsCaFile,
tlsCertFile: options.tlsCertFile,
tlsKeyFile: options.tlsKeyFile,
databaseName: options.databaseName
});
}
/**
* Builds client transport options for live schema introspection.
*/
async function createGeneratorClientOptions(options) {
if (!options.host) {
return {
socketPath: options.socketPath
};
}
return {
host: options.host,
port: options.port,
tls: options.tls,
tlsOptions: {
servername: options.tlsServername ?? options.host,
rejectUnauthorized: options.tlsInsecure ? false : undefined,
ca: options.tlsCaFile ? await (0, promises_1.readFile)(options.tlsCaFile, "utf8") : undefined,
cert: options.tlsCertFile ? await (0, promises_1.readFile)(options.tlsCertFile, "utf8") : undefined,
key: options.tlsKeyFile ? await (0, promises_1.readFile)(options.tlsKeyFile, "utf8") : undefined
}
};
}
function renderColumnType(type) {
if (typeof type === "string") {
return renderAtomicType(type);
}
if ("value" in type && type.value !== undefined) {
return `OvsMap<${renderBaseType(type.key)}, ${renderBaseType(type.value)}>`;
}
if (isScalarType(type)) {
return renderBaseType(type.key);
}
return `OvsSet<${renderBaseType(type.key)}>`;
}
function isOptionalColumn(type) {
if (typeof type === "string") {
return false;
}
return type.min === 0;
}
function isScalarType(type) {
const min = type.min ?? 1;
const max = type.max ?? 1;
return min === 1 && max === 1;
}
function renderBaseType(baseType) {
if (typeof baseType === "string") {
return renderAtomicType(baseType);
}
if (baseType.enum) {
return renderEnum(baseType.enum);
}
return renderAtomicType(baseType.type);
}
function renderAtomicType(type) {
switch (type) {
case "integer":
case "real":
return "number";
case "boolean":
return "boolean";
case "string":
return "string";
case "uuid":
return "Uuid";
default:
return "unknown";
}
}
function renderEnum(value) {
if (Array.isArray(value) && value[0] === "set" && Array.isArray(value[1])) {
const members = value[1]
.map((member) => typeof member === "string" ? JSON.stringify(member) : renderJsonValue(member))
.join(" | ");
return members || "never";
}
return renderJsonValue(value);
}
function renderJsonValue(value) {
return JSON.stringify(value);
}
function toPascalCase(value) {
return value
.replace(/[^a-zA-Z0-9]+/g, " ")
.split(" ")
.filter(Boolean)
.map((part) => part.charAt(0).toUpperCase() + part.slice(1))
.join("");
}
function toPropertyKey(value) {
return /^[$A-Z_][0-9A-Z_$]*$/i.test(value) ? value : JSON.stringify(value);
}