UNPKG

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
/** * 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 };