import { FormatHelpers, TypeScriptPreset } from '@asyncapi/modelina'; import endent from 'endent'; import { ensureNullable, getArrayItemNonNullTypes, getModelPathPrefix, indexOfNullArrayProperty, indexOfNullObjectProperty, replaceAnyType, } from './utils'; /** * Exports types. */ export const EXPORT_TYPES_PRESET: TypeScriptPreset = { interface: { self({ content }) { return `export ${content}`; }, }, enum: { self({ content }) { return `export ${content}`; }, }, type: { self({ content }) { return `export ${content}`; }, }, }; /** * Preset for TypeScriptGenerator, additionalProperties added to models as "[k: string]: unknown;" */ export const ADDITIONAL_PROPERTIES_PRESET: TypeScriptPreset = { interface: { self({ content }) { return content; }, property({ property, content }) { if (property.unconstrainedPropertyName === 'additionalProperties') { return `\n[k: string]: unknown;`; } return content; }, }, }; /** * Preset for TypeScriptGenerator, adds imports to resulting model. */ export const IMPORTS_PRESET: TypeScriptPreset = { interface: { self({ content, model }) { const interfacePrefix = getModelPathPrefix(model.name); const dependencies = model.getNearestDependencies(); const imports = dependencies.map((dep) => ({ class: FormatHelpers.toPascalCase(dep.name), location: interfacePrefix !== getModelPathPrefix(dep.name) ? `../${getModelPathPrefix(dep.name)}/${FormatHelpers.toParamCase( dep.name, )}` : `./${FormatHelpers.toParamCase(dep.name)}`, })); return endent` ${imports .sort((a, b) => a.location.localeCompare(b.location)) .map((i) => `import { ${i.class} } from '${i.location}';`) .join('\n')} ${content}`; }, property({ content }) { return content; }, }, }; /** * Preset for TypeScriptGenerator, sets nullable properties as unions in resulted models(compatibility with zapatos generated models). */ export const NULLABLE_PROPERTY_TO_UNION_PRESET: TypeScriptPreset = { interface: { self({ content }) { return content; }, property({ renderer, property, content }) { let index = indexOfNullObjectProperty(property); // Regular object property, not array. if (index > -1) { property.property.type = ensureNullable(property.property.type, index); return renderer.renderProperty(property); } // Array property. index = indexOfNullArrayProperty(property); if (index > -1) { // Workaround: modelina drops non-null types from nullable array items // e.g. items: { type: ["string", "null"] } renders as null[] instead of (string | null)[]. // Reconstruct from the original schema instead. const nonNullTypes = getArrayItemNonNullTypes(property); if (nonNullTypes) { property.property.type = `(${[...nonNullTypes, 'null'].join( ' | ', )})[]`; return renderer.renderProperty(property); } // This pattern is not perfect but it's good enough for this use case. const regex = /((?\w+)|\((?[\w\s|]+)\))\[\]/; const match = regex.exec(property.property.type); if (match && (match?.groups?.single || match?.groups?.multiple)) { const newType = ensureNullable( match.groups.single ?? match.groups.multiple, index, ); // At this point we assume that the type will be a union of at least two types - null and something else. property.property.type = `(${newType})[]`; return renderer.renderProperty(property); } } return content; }, }, }; /** * Converts all occurrences of 'any' to 'unknown'. */ export const ANY_TO_UNKNOWN_PRESET: TypeScriptPreset = { interface: { property({ renderer, property }) { property.property.type = replaceAnyType( property.property.type, 'unknown', ); return renderer.renderProperty(property); }, }, type: { self({ content }) { // TODO: This is inconsistent with interface but not sure how to use type renderer. return replaceAnyType(content, 'unknown'); }, }, };