ultimate-crud
Version:
Ultimate dynamic CRUD API generator with REST, GraphQL, OpenAPI support and association handling for Node.js/Express/Sequelize
102 lines (90 loc) • 3.53 kB
JavaScript
/**
* OpenAPI Schema utilities for Ultimate CRUD
*
* @license MIT
* @copyright 2025 cnos-dev
* @author Harish Kashyap (CNOS Dev)
*/
// Utility to generate OpenAPI schemas from DB columns
const { Sequelize } = require("sequelize");
async function getSchemaForEntity(tableName, schema, sequelize, entities = []) {
// If sequelize instance is not provided, we can't fetch schema
if (!sequelize) {
return { type: "object", properties: {} };
}
let columns;
const dialect = sequelize.getDialect();
if (dialect === 'postgres' && schema) {
columns = await sequelize.query(
`SELECT column_name AS COLUMN_NAME, data_type AS DATA_TYPE FROM information_schema.columns WHERE table_schema = ? AND table_name = ?`,
{ replacements: [schema, tableName], type: sequelize.QueryTypes.SELECT }
);
} else {
columns = await sequelize.query(
`SELECT COLUMN_NAME, DATA_TYPE FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = ?`,
{ replacements: [tableName], type: sequelize.QueryTypes.SELECT }
);
}
const typeMap = {
varchar: "string",
int: "integer",
float: "number",
double: "number",
datetime: "string",
date: "string",
text: "string",
tinyint: "boolean",
uuid: "string",
char: "string",
// Add more mappings as needed
};
const properties = {};
columns.forEach((col) => {
properties[col.COLUMN_NAME] = { type: typeMap[col.DATA_TYPE] || "string" };
});
// --- Association support ---
// 1. DB-level foreign keys
let foreignKeys = [];
if (dialect === 'postgres' && schema) {
foreignKeys = await sequelize.query(
`SELECT kcu.column_name, ccu.table_name AS referenced_table, ccu.column_name AS referenced_column
FROM information_schema.key_column_usage kcu
JOIN information_schema.constraint_column_usage ccu ON kcu.constraint_name = ccu.constraint_name
WHERE kcu.table_schema = ? AND kcu.table_name = ? AND kcu.position_in_unique_constraint IS NOT NULL`,
{ replacements: [schema, tableName], type: sequelize.QueryTypes.SELECT }
);
} else {
foreignKeys = await sequelize.query(
`SELECT COLUMN_NAME, REFERENCED_TABLE_NAME AS referenced_table, REFERENCED_COLUMN_NAME AS referenced_column
FROM INFORMATION_SCHEMA.KEY_COLUMN_USAGE
WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = ? AND REFERENCED_TABLE_NAME IS NOT NULL`,
{ replacements: [tableName], type: sequelize.QueryTypes.SELECT }
);
}
for (const fk of foreignKeys) {
// Add OpenAPI reference for foreign key
properties[fk.COLUMN_NAME] = {
allOf: [
{ type: properties[fk.COLUMN_NAME]?.type || "string" },
{ $ref: `#/components/schemas/${fk.referenced_table}` }
]
};
}
// 2. Config-level associations
const entityConfig = entities.find(e => e.name === tableName);
if (entityConfig && Array.isArray(entityConfig.associations)) {
for (const assoc of entityConfig.associations) {
// Only add if not already present from DB
if (!properties[assoc.foreignKey]) {
properties[assoc.foreignKey] = {
allOf: [
{ type: properties[assoc.foreignKey]?.type || "string" },
{ $ref: `#/components/schemas/${assoc.target}` }
]
};
}
}
}
return { type: "object", properties };
}
module.exports = { getSchemaForEntity };