UNPKG

typescript-scaffolder

Version:

![npm version](https://img.shields.io/npm/v/typescript-scaffolder) ![coverage](https://img.shields.io/badge/coverage-97.38%25-green)

144 lines (143 loc) 5.39 kB
"use strict"; 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; }