@nu-art/schema-to-types
Version:
schema-to-types - Express & Typescript based backend framework
50 lines (49 loc) • 2.1 kB
JavaScript
// schema-to-types.ts
import * as fs from 'node:fs';
import * as path from 'node:path';
import $RefParser from '@apidevtools/json-schema-ref-parser';
import { FetchingJSONSchemaStore, InputData, JSONSchemaInput, quicktype, TypeScriptTargetLanguage, } from 'quicktype-core';
/**
* Generate TypeScript defs from a JSON Schema file.
* - Bundles all external $refs into a single schema object
* - Emits only type/interface declarations (no runtime helpers)
*/
const generateTypeScriptType = async (inputPath, outputPath) => {
// Inline external refs but keep internal $ref to avoid cycles
const bundledSchema = await $RefParser.bundle(path.resolve(inputPath));
const topLevelName = inferTopLevelName(outputPath, inputPath);
const schemaInput = new JSONSchemaInput(new FetchingJSONSchemaStore());
await schemaInput.addSource({
name: topLevelName,
schema: JSON.stringify(bundledSchema),
});
const inputData = new InputData();
inputData.addInput(schemaInput);
const { lines } = await quicktype({
inputData,
lang: new TypeScriptTargetLanguage(),
rendererOptions: { 'just-types': 'true' }, // <= only types/interfaces
});
const ts = [
`// Generated from ${path.basename(inputPath)} — do not edit by hand.`,
`// ${new Date().toISOString()}`,
'',
...lines,
'',
].join('\n');
const outputDir = path.dirname(outputPath);
if (!fs.existsSync(outputDir))
fs.mkdirSync(outputDir, { recursive: true });
fs.writeFileSync(outputPath, ts, 'utf8');
};
function inferTopLevelName(outputPath, fallbackPath) {
const base = path.basename(outputPath || fallbackPath).replace(/\.(d\.)?tsx?$/i, '').trim();
const name = base.replace(/[^A-Za-z0-9_$]/g, '_'); // best-effort valid TS identifier
return name.length ? name : 'Schema';
}
export const processConfig = async (configPath) => {
const config = JSON.parse(fs.readFileSync(configPath, 'utf8'));
for (const { input, output } of config) {
await generateTypeScriptType(input, output);
}
};