ts-schema-factory
Version:
Generate TypeScript interfaces from database query results or JS arrays
120 lines (98 loc) • 3.52 kB
JavaScript
// index.js
const fs = require('fs');
// -------------------- Schema generator --------------------
function detectType(value) {
if (value === null) return 'null';
if (Array.isArray(value)) return 'array';
if (value instanceof Date) return 'string';
return typeof value;
}
function mergeTypes(type1, type2) {
const set = new Set([].concat(type1 || []).concat(type2 || []));
return [...set];
}
function mergeSchemas(schema1, schema2) {
if (!schema1) return schema2;
if (!schema2) return schema1;
const type = mergeTypes(schema1.type, schema2.type);
const properties = {};
const keys = new Set([
...Object.keys(schema1.properties || {}),
...Object.keys(schema2.properties || {})
]);
for (const key of keys) {
properties[key] = mergeSchemas(schema1.properties?.[key], schema2.properties?.[key]);
}
return { type, properties };
}
function generateSchemaFromObject(obj) {
const type = detectType(obj);
if (type === 'object') {
const properties = {};
for (const key in obj) {
properties[key] = generateSchemaFromObject(obj[key]);
}
return { type: 'object', properties };
} else if (type === 'array') {
if (obj.length === 0) return { type: 'array', items: { type: 'any' } };
let itemsSchema = generateSchemaFromObject(obj[0]);
for (let i = 1; i < obj.length; i++) {
itemsSchema = mergeSchemas(itemsSchema, generateSchemaFromObject(obj[i]));
}
return { type: 'array', items: itemsSchema };
} else {
return { type };
}
}
function generateSchemaFromArray(arr) {
if (!Array.isArray(arr) || arr.length === 0) return { type: 'array', items: { type: 'any' } };
let schema = generateSchemaFromObject(arr[0]);
for (let i = 1; i < arr.length; i++) {
schema = mergeSchemas(schema, generateSchemaFromObject(arr[i]));
}
return { type: 'array', items: schema };
}
// -------------------- TS Interface generator --------------------
function convertType(prop) {
let types = prop.type;
if (!Array.isArray(types)) types = [types];
const mapped = types.map(t => {
switch (t) {
case 'string': return 'string';
case 'number': return 'number';
case 'boolean': return 'boolean';
case 'array': return prop.items ? `${convertType(prop.items)}[]` : 'any[]';
case 'object':
return prop.properties ? '{ ' + Object.entries(prop.properties).map(([k,v]) => `${k}: ${convertType(v)}`).join('; ') + ' }' : 'any';
case 'null': return 'null';
default: return 'any';
}
});
return mapped.join(' | ');
}
function generateTSInterface(schema, interfaceName = 'Root') {
if (!schema || !schema.properties) return '';
const lines = [];
lines.push(`interface ${interfaceName} {`);
for (const key in schema.properties) {
const prop = schema.properties[key];
const optional = prop.type.includes('null') ? '?' : '';
const tsType = convertType(prop);
lines.push(` ${key}${optional}: ${tsType};`);
}
lines.push('}');
return lines.join('\n');
}
// -------------------- Helper functions --------------------
function generateInterfaceFromArray(dataArray, interfaceName = 'Root', outputFile) {
const schema = generateSchemaFromArray(dataArray);
const objectSchema = schema.items;
const tsInterface = generateTSInterface(objectSchema, interfaceName);
if (outputFile) fs.writeFileSync(outputFile, tsInterface);
return tsInterface;
}
module.exports = {
generateSchemaFromArray,
generateTSInterface,
generateInterfaceFromArray
};