typescript-scaffolder
Version:
 
144 lines (143 loc) • 5.39 kB
JavaScript
;
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.prefixDelimiter = void 0;
exports.deriveObjectName = deriveObjectName;
exports.inferPrimitiveType = inferPrimitiveType;
exports.findGloballyDuplicatedKeys = findGloballyDuplicatedKeys;
exports.prefixDuplicateKeys = prefixDuplicateKeys;
exports.toPascalCase = toPascalCase;
exports.unprefixKeysSafe = unprefixKeysSafe;
const path_1 = __importDefault(require("path"));
const logger_1 = require("./logger");
exports.prefixDelimiter = '__PREFIX__';
/**
* Converts a filename (e.g. user-profile.json or order_log.json)
* into a PascalCase TypeScript interface name (e.g. UserProfile, OrderLog).
* @param filePath
*/
function deriveObjectName(filePath) {
const funcName = 'deriveObjectName';
logger_1.Logger.debug(funcName, 'deriving object name from filename...');
const fileBase = path_1.default.basename(filePath, '.json');
// Preserve underscores, strip dashes
const rawParts = fileBase.split(/(_|-)/g); // include delimiters
const transformed = rawParts.map(part => {
if (part === '-')
return '';
if (part === '_')
return '_';
return part;
});
return transformed.join('');
}
/**
* Takes in a stringified value and infers the primitive type
* @param value
*/
function inferPrimitiveType(value) {
if (value === 'true' || value === 'false')
return 'boolean';
if (!isNaN(Number(value)))
return 'number';
return 'string';
}
/**
* scans a json object and identifies all duplicate keys for preprocessing
* @param input
*/
function findGloballyDuplicatedKeys(input) {
const funcName = 'findGloballyDuplicatedKeys';
logger_1.Logger.debug(funcName, 'finding globally duplciated keys...');
const keyCounts = new Map();
function scan(node) {
if (Array.isArray(node)) {
node.forEach(scan);
}
else if (typeof node === 'object' && node !== null) {
for (const key of Object.keys(node)) {
keyCounts.set(key, (keyCounts.get(key) || 0) + 1);
scan(node[key]);
}
}
}
scan(input);
return new Set([...keyCounts.entries()]
.filter(([_, count]) => count > 1)
.map(([key, _]) => key));
}
/**
* A preprocessor for Quicktype to prefix duplicate keys so that it does not create circular references
* or bad interfaces. Will prefix all duplicate keys with the field name of the parent. The prefix is then
* removed later downstream.
* @param input
* @param duplicateKeys
* @param prefixedKeys Optional set to collect the actual prefixed keys during processing.
*/
function prefixDuplicateKeys(input, duplicateKeys, prefixedKeys) {
const funcName = 'prefixDuplicateKeys';
const clone = JSON.parse(JSON.stringify(input)); // deep clone to avoid mutation
function transform(node, parentKey = null) {
if (Array.isArray(node)) {
const objectElements = node.filter(item => typeof item === 'object' && item !== null);
if (objectElements.length > 0) {
logger_1.Logger.debug(funcName, `Processing array at key '${parentKey}' with ${objectElements.length} object(s). Transforming all object elements.`);
objectElements.forEach(obj => transform(obj, parentKey));
}
// Primitive arrays or empty arrays are ignored
return;
}
if (typeof node === 'object' && node !== null) {
// First: rewrite duplicate keys
if (parentKey) {
for (const key of Object.keys(node)) {
if (duplicateKeys.has(key)) {
node[`${parentKey}${exports.prefixDelimiter}${key}`] = node[key];
delete node[key];
if (prefixedKeys) {
prefixedKeys.add(`${parentKey}${exports.prefixDelimiter}${key}`);
}
}
}
}
// Then recurse into each property
for (const key of Object.keys(node)) {
transform(node[key], key);
}
}
}
transform(clone);
return clone;
}
function toPascalCase(str) {
return str
.replace(/([-_]\w)/g, g => g[1].toUpperCase())
.replace(/^\w/, c => c.toUpperCase());
}
/**
* Safely unprefix keys that were actually prefixed by prefixDuplicateKeys.
* Only keys present in `prefixedKeys` are unprefixed; everything else is left untouched.
*/
function unprefixKeysSafe(obj, prefixedKeys) {
function walk(node) {
if (Array.isArray(node)) {
node.forEach(walk);
}
else if (node && typeof node === 'object') {
for (const key of Object.keys(node)) {
const value = node[key];
if (prefixedKeys.has(key)) {
const parts = key.split(exports.prefixDelimiter);
const unprefixedKey = parts[parts.length - 1];
node[unprefixedKey] = value;
delete node[key];
}
walk(value);
}
}
}
walk(obj);
return obj;
}