UNPKG

typed-aws

Version:

Helps you write AWS CloudFormation in TypeScript

345 lines (341 loc) 11.7 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.ResourceModel = exports.ModuleModel = exports.getAllResources = exports.createModels = void 0; const normalize_1 = require("./normalize"); const utils_1 = require("./utils"); function createModels(registry, specs) { const modules = {}; Object.values(registry).forEach((schema) => { const module = (modules[schema.module] = modules[schema.module] || new ModuleModel(schema.module)); module.addSchemaType(schema, specs); }); return modules; } exports.createModels = createModels; function getAllResources(modules) { const resourceModels = []; Object.values(modules).forEach((module) => { resourceModels.push(...Object.values(module.resources)); }); return resourceModels; } exports.getAllResources = getAllResources; class ModuleModel { constructor(name) { this.name = name; this.resources = {}; } addSchemaType(schema, specs) { const module = (this.resources[schema.resource] = this.resources[schema.resource] || new ResourceModel(schema.resource, this)); module.addSchemaType(schema, specs); } } exports.ModuleModel = ModuleModel; class ResourceModel { constructor(name, module) { this.name = name; this.module = module; this.types = []; } addSchemaType(schema, specs) { const model = createModel(schema, [schema.name], specs); this.types.push(model); if (schema.id === schema.namespace) { this.mainModel = model; this.mainSchema = schema; } } toString() { return ` import { CfnResource, Resolvable } from '../../base'; export type ${this.mainModel.getName()}_Type = '${this.mainSchema.namespace}'; export const ${this.mainModel.getName()}_Type = '${this.mainSchema.namespace}'; ${(0, utils_1.formatDoc)(this.mainModel.getTypeDocumentation())}export default function ${this.mainModel.getName()}(props: ${this.mainModel.getName()}_Properties) { return new CfnResource<${this.mainModel.getName()}_Properties>(${this.mainModel.getName()}_Type, props); }; ${this.types .map((type) => { return `${(0, utils_1.formatDoc)(type.getTypeDocumentation())}export type ${type.getName()}${type === this.mainModel ? '_Properties' : ''} = ${type.toString()}`; }) .join('\n\n')} `; } } exports.ResourceModel = ResourceModel; class TypeModel { constructor(schema, names, specs) { this.schema = schema; this.names = names; this.specs = specs; this.name = schema.name; this.type = schema.type; // TODO check ref if (schema.namespace === schema.id) { this.resourceType = schema.namespace; } this.description = schema.description; } getName() { if (this.name) return normalizeName(this.name); return normalizeName(this.names.join('_')); } getPropertyDocs(propertyName) { let seeLink = this.specs.getDocumentation(this.schema.id, propertyName === AnyProperty ? '' : propertyName); if (seeLink) seeLink = `{@link ${seeLink}}`; return seeLink; } getTypeDocumentation() { let seeLink = this.specs.getDocumentation(this.schema.id); if (seeLink) seeLink = `{@link ${seeLink}}`; if (!this.description) return (0, utils_1.concatWith)({ prefix: '\n' }, seeLink); return (0, utils_1.concatWith)({ joiner: '\n' }, this.description, seeLink); } } class StringTypeModel extends TypeModel { constructor(schema, names, specs) { super(schema, names, specs); } toString() { if (this.schema.enum) { const enumTypeLiteral = this.schema.enum .map((item) => `'${item}'`) .join(' | '); return `Resolvable<${enumTypeLiteral}>`; } return `Resolvable<string>`; } } class ArrayTypeModel extends TypeModel { constructor(schema, names, specs) { super(schema, names, specs); this.model = createModel(schema.items, this.names, specs); } toString() { return `(${this.model}\n)[]`; } } class NumberTypeModel extends TypeModel { constructor(schema, names, specs) { super(schema, names, specs); } toString() { return `Resolvable<number>`; } } class BooleanTypeModel extends TypeModel { constructor(schema, names, specs) { super(schema, names, specs); } toString() { return `Resolvable<boolean>`; } } class RefTypeModel extends TypeModel { constructor(schema, names, specs) { super(schema, names, specs); this.refType = this.schema.$ref.split('/').pop() || `unknown // Ref type: ${this.schema.$ref}`; } toString() { return this.refType; } } class IntersectionTypeModel extends TypeModel { constructor(names, specs) { super({ type: 'intersection' }, names, specs); this.models = []; } addTypeModel(model) { this.models.push(model); } toString() { return `(${this.models.map((model) => model.toString()).join(' & ')})`; } } class UnionTypeModel extends TypeModel { constructor(names, specs) { super({ type: 'union' }, names, specs); this.models = []; } addTypeModel(model) { this.models.push(model); } toString() { return `(${this.models.map((model) => model.toString()).join(' | ')})`; } } const AnyProperty = Symbol('*'); class ObjectTypeModel extends TypeModel { constructor(schema, names, specs) { super(schema, names, specs); this.members = {}; const { properties, patternProperties, additionalProperties } = schema; if (properties) { Object.keys(properties).forEach((key) => { const valueSchema = properties[key]; const property = (this.members[key] = this.members[key] || new MemberModel(this, key, [...names, key], specs)); property.addSchema(valueSchema); }); } if (patternProperties) { Object.keys(patternProperties).forEach((pattern) => { const valueSchema = patternProperties[pattern]; const property = (this.members[AnyProperty] = this.members[AnyProperty] || new MemberModel(this, AnyProperty, [...names, 'Any'], specs)); property.addPatternSchema(pattern, valueSchema); }); } if (additionalProperties !== false) { const property = (this.members[AnyProperty] = this.members[AnyProperty] || new MemberModel(this, AnyProperty, [...names, 'Any'], specs)); property.setUnknown(); } } isRequired(key) { return this.schema.required?.includes(key); } toStringMember(member) { return `${(0, utils_1.formatDoc)(member.getTypeDocumentation())}${member.toString(this.isRequired(member.name))};`; } toString() { return `{ ${Reflect.ownKeys(this.members) .map((key) => this.toStringMember(this.members[key])) .join('\n')} }`; } } class MemberModel { constructor(parentModel, name, names, specs) { this.parentModel = parentModel; this.name = name; this.names = names; this.specs = specs; } setUnknown() { this.isUnknown = true; } getTypeDocumentation() { const propertyDocs = this.type?.getTypeDocumentation(); const parentPropertyDocs = this.parentModel?.getPropertyDocs(this.name); if (propertyDocs && parentPropertyDocs) return `${propertyDocs}\n${parentPropertyDocs}`; if (propertyDocs && !parentPropertyDocs) return `${propertyDocs}`; if (parentPropertyDocs) return `${parentPropertyDocs}`; } addTypeModel(model) { if (!this.type) { this.type = model; } else if (this.type instanceof UnionTypeModel) { this.type.addTypeModel(model); } else { const typeModel = (this.type = new UnionTypeModel(this.names, this.specs)); typeModel.addTypeModel(model); return typeModel; } return model; } addSchema(schema) { const newModel = createModel(schema, this.names, this.specs); this.addTypeModel(newModel); } addPatternSchema(pattern, schema) { const newModel = createModel(schema, this.names, this.specs); newModel.pattern = pattern; this.addTypeModel(newModel); } toString(isRequired) { return `${this.name === AnyProperty ? '[k: string]' : this.name}${this.name === AnyProperty || isRequired ? '' : '?'}: ${this.isUnknown ? 'unknown' : normalizeName(this.type?.toString()) || 'unknown // PROPERTY MODEL??'}`; } } function createModel(schema, names, specs) { (0, normalize_1.normalizeSchema)(schema); let typeModel = null; switch (schema.type) { case 'string': typeModel = new StringTypeModel(schema, names, specs); break; case 'object': typeModel = new ObjectTypeModel(schema, names, specs); break; case 'array': typeModel = new ArrayTypeModel(schema, names, specs); break; case 'number': typeModel = new NumberTypeModel(schema, names, specs); break; case 'boolean': typeModel = new BooleanTypeModel(schema, names, specs); break; case 'reference': typeModel = new RefTypeModel(schema, names, specs); break; } const conditionalModel = createConditionalModel(schema, names, specs); if (conditionalModel) { return conditionalModel; } else if (typeModel) { return typeModel; } throw new Error(`Invalid schema without type and conditionals (anyOf, allOf, oneOf): ${JSON.stringify(schema)}`); } function hasAnyOf(schema) { return Array.isArray(schema['anyOf']); } function hasAllOf(schema) { return Array.isArray(schema['allOf']); } function hasOneOf(schema) { return Array.isArray(schema['oneOf']); } function createConditionalModel(schema, names, specs) { // eslint-disable-next-line @typescript-eslint/no-unused-vars, @typescript-eslint/no-explicit-any const { anyOf, allOf, oneOf, not, ...parentSchema } = schema; const addSubSchemas = (subSchemas, typeModel) => { subSchemas.map((subSchema, i) => { const newSchema = { ...parentSchema, ...subSchema }; typeModel.addTypeModel(createModel(newSchema, [...names, `${i}`], specs)); }); }; if (hasAnyOf(schema)) { const unionModel = new UnionTypeModel(names, specs); addSubSchemas(schema.anyOf, unionModel); return unionModel; } else if (hasAllOf(schema)) { const intersectionModel = new IntersectionTypeModel(names, specs); addSubSchemas(schema.allOf, intersectionModel); return intersectionModel; } else if (hasOneOf(schema)) { const unionModel = new UnionTypeModel(names, specs); addSubSchemas(schema.oneOf, unionModel); return unionModel; } return null; } function normalizeName(name) { if (!name) return name; if (name === 'Object') return 'ObjectData'; return name; }