very-express
Version:
based on json schema and openapi, generate Express RestApi server with mongoDb
84 lines (71 loc) • 3.78 kB
JavaScript
// Fix openapi-generated filter types to allow primitives, based on swagger schema info.
import { readdirSync, readFileSync, writeFileSync } from 'fs';
import { join, dirname } from 'path';
import { fileURLToPath } from 'url';
const __dir = dirname(fileURLToPath(import.meta.url));
const modelsDir = join(__dir, '../src/api-client/models');
const swaggerPath = join(__dir, '../../api/src/openapi/swagger.json');
const swagger = JSON.parse(readFileSync(swaggerPath, 'utf8'));
const schemas = swagger?.components?.schemas ?? {};
function primitiveTypeFromSwaggerProp(prop) {
if (!prop?.anyOf) return null;
const primitive = prop.anyOf.find(s => s.type && !s['$ref']);
if (!primitive) return null;
if (primitive.type === 'boolean') return 'boolean';
if (primitive.type === 'number' || primitive.type === 'integer') return 'number';
return 'string';
}
function primitiveTypeFromTsTypeName(tsType, swaggerEntityName, fieldKey) {
if (tsType === 'FieldFilterBoolean') return 'boolean';
if (tsType === 'FieldFilterNumber') return 'number';
if (tsType === 'FieldFilterString') return 'string';
if (/^FieldFilter[A-Z]/.test(tsType)) return 'string';
if (swaggerEntityName && fieldKey && schemas[swaggerEntityName]?.properties) {
const swaggerKey = fieldKey === 'id' ? '_id' : fieldKey;
const prop = schemas[swaggerEntityName].properties[swaggerKey];
if (prop) return primitiveTypeFromSwaggerProp(prop);
}
return null;
}
// Field-level: FieldFilterBoolean, FilterAreaActive, FilterAreaCountryCode, etc.
const FIELD_FILTER_RE = /^(FieldFilter[A-Z]|Filter[A-Z][a-z]+[A-Z])[a-zA-Z]*\.ts$/;
// Top-level aggregates: FilterArea, FilterClient, FilterJob, etc.
const TOP_FILTER_RE = /^Filter([A-Z][a-zA-Z]+)\.ts$/;
const NULL_CHECK = ' if (value == null) {\n return value;\n }';
const PRIMITIVE_GUARD = '\n // patch: pass primitives unchanged\n if (typeof value !== \'object\') return value;';
let stats = { fieldPatched: 0, topPatched: 0, skipped: 0 };
// Pass 1: patch field-level ToJSONTyped / FromJSONTyped
for (const file of readdirSync(modelsDir)) {
if (!FIELD_FILTER_RE.test(file)) continue;
const filePath = join(modelsDir, file);
let src = readFileSync(filePath, 'utf8');
if (src.includes('// patch:') || !src.includes(NULL_CHECK)) { stats.skipped++; continue; }
src = src.replaceAll(NULL_CHECK, NULL_CHECK + PRIMITIVE_GUARD);
writeFileSync(filePath, src, 'utf8');
console.log('[patch] field v ' + file);
stats.fieldPatched++;
}
// Pass 2: patch top-level filter interface property types
for (const file of readdirSync(modelsDir)) {
const match = TOP_FILTER_RE.exec(file);
if (!match) continue;
const filePath = join(modelsDir, file);
let src = readFileSync(filePath, 'utf8');
if (src.includes('// patch:')) { stats.skipped++; continue; }
const swaggerEntityName = file.replace('.ts', '');
// Match: ` fieldName?: SomeType;` — skip $or/$and lines
const PROP_RE = /^( {4}(?!\$or|\$and)(\w+)\?:\s*)([A-Za-z]+)(;)$/gm;
let modified = false;
const patched = src.replace(PROP_RE, (line, prefix, fieldKey, tsType, semi) => {
const primitive = primitiveTypeFromTsTypeName(tsType, swaggerEntityName, fieldKey);
if (!primitive || line.includes(' | ' + primitive)) return line;
modified = true;
return prefix + tsType + ' | ' + primitive + semi;
});
if (!modified) { stats.skipped++; continue; }
writeFileSync(filePath, patched, 'utf8');
console.log('[patch] top v ' + file);
stats.topPatched++;
}
console.log('\n[patch-filter-types] Done. Field: ' + stats.fieldPatched + ', Top: ' + stats.topPatched + ', Skipped: ' + stats.skipped);