import { JSONSchema4 } from 'json-schema'; import { Code } from './code'; /** * Available opt-in schema transformations */ export interface SchemaTransformations { /** * When true, unions (anyOf, oneOf, allOf) that contain only a single element are hoisted to the top level of the schema. * * This transformation runs late in the transformation stack, to ensure maximal effect. * * --- * * **Considerations**: A future addition to the union will incur a breaking change in the generated code. * Consider if the schema author might have used a union type to convey their future intentions. * However the same risk applies for any schema going from a single type to a union type. * * ---- * * @example { anyOf: [{ type: 'string' }] } -> { type: 'string' } * * @default false */ readonly hoistSingletonUnions?: boolean; /** * When true, unions (anyOf, oneOf) that contain a null-type are converted into an optional type with the null-type removed from the union. * * Combining with `hoistSingletonUnions` can significantly reduce the amount of union types that aren't really unions. * * --- * * **Considerations**: Optional properties and `null` values aren't strictly the same in JavaScript. * Consider if the difference is functional or if the schema uses this pattern to denoted optional types. * * ---- * * @example { required: true, anyOf: [{ type: 'null' }, { type: 'string' }] } -> { required: false, anyOf: [{ type: 'string' }] } * * @default false */ readonly convertNullUnionsToOptional?: boolean; /** * When true, unions (anyOf, oneOf) that contain an array type and its element type are simplified to just the array type. * * This is useful for jsii compatibility where such unions would be typed as 'any', but using just the array type * provides better type safety while maintaining the same functionality since a single value can be wrapped in an array. * * --- * * **Considerations**: This changes the schema's type structure but maintains semantic equivalence since * single values can be wrapped in arrays. The transformation assumes that treating a single value as a * single-element array is acceptable for the use case. * * ---- * * @example { anyOf: [{ type: 'array', items: { type: 'string' } }, { type: 'string' }] } -> { type: 'array', items: { type: 'string' } } * * @default false */ readonly simplifyElementArrayUnions?: boolean; /** * When true, JSON Schema `const` keywords are converted to equivalent single-value `enum` arrays. * * This normalization enables other enum handling logic to process both `const` and `enum` patterns uniformly, * enabling `oneOf` unions containing `const` values to generate TypeScript enums. * * --- * * **Considerations**: Without this transformation, `const` values are emitted as their underlying type (e.g. `string`), * which allows any value of that type in TypeScript even though the schema only permits the specific const value. * Enabling this option provides stricter type safety by generating enums that restrict values to exactly what the schema allows. * * ---- * * @example { const: 'tab' } -> { enum: ['tab'] } * * @default false */ readonly convertConstToEnum?: boolean; } export interface TypeGeneratorOptions { /** * Patterns of type FQNs to exclude. * @default - include all types */ readonly exclude?: string[]; /** * Schema definitions for resolving $refs * @default - $refs are not supported */ readonly definitions?: { [def: string]: JSONSchema4; }; /** * Generate `toJson_Xyz` functions for all types which convert data objects * back to schema-compatible JSON. * * These functions are required since property names in generated structs are * camel cased in order to be compatible with JSII, and this is a lossy * conversion, so the toJson functions are required to convert the data back * to a schema-compatible data objects. * * @default true */ readonly toJson?: boolean; /** * When true, generated `toJson_Xyz` functions will be marked with as internal. * This is useful when you want to hide these functions from public API documentation. * * @default false */ readonly toJsonInternal?: boolean; /** * When set to true, enums are sanitized from the 'null' literal value, * allowing typing the property as an enum, instead of the underlying type. * * Note that switching this from 'false' to 'true' is a breaking change in * the generated code as it might change a property type. * * @default false */ readonly sanitizeEnums?: boolean; /** * Given a definition name, render the type name to be emitted by that definition. * * When `emitType` is invoked, the type name to be emitted is provided by the caller. * However, for complex types containing references to other types, we infer the type name of the reference * by looking at the definition name of the `$ref` attribute. * * This function provides a way to control how those definition names translate into type name. * * For example, if a complex type references a namespaced definition like `api.group.Foo`, we'd like to control * how to translate `api.group.Foo`, which is an illegal typename, into a legal one. * * @default - Only dot namespacing is handled by default. Elements between dots are pascal cased and concatenated. */ readonly renderTypeName?: (def: string) => string; /** * Schemas often define a type in ways that make sense for a schema, but may produce convoluted typescript/jsii code. * In many cases it is possible to transform these definitions into a semantically identically but simpler version, * which will produce "nicer" code. * * These transformations are often not backwards compatible or at least carry a risk of incurring breaking changes in future. * Therefore they are all opt-in. * * @default - no opt-in transformations are applied */ readonly transformations?: SchemaTransformations; /** * JSON Schema supports regular expressions for input validation in the schema. * We can add that pattern for runtime validation of structs in the emitted code, * as well as codegen-time validation of possible enum values. * * Defaults to false as to not break existing previously working but non-compliant code. * * @default false */ readonly emitRegexValidation?: boolean; } /** * Generates typescript types from JSON schemas. */ export declare class TypeGenerator { /** * Convert all-caps acronyms (e.g. "VPC", "FooBARZooFIGoo") to pascal case * (e.g. "Vpc", "FooBarZooFiGoo"). */ static normalizeTypeName(typeName: string): string; /** * Renders a JSII struct (and accompanying types) from a JSON schema. * * If you wish to render multiple top-level structs or include custom types, * create a new instance of `TypeGenerator` manually. * * @param structName The name of the JSII struct (TypeScript interface). * @param schema The JSON schema (top level schema must include "properties") * @returns Generated TypeScript source code that includes the top-level * struct and all other types. */ static forStruct(structName: string, schema: JSONSchema4, options?: TypeGeneratorOptions): TypeGenerator; private readonly typesToEmit; private readonly emittedTypes; private readonly emittedProperties; private readonly exclude; private readonly definitions; private readonly toJson; private readonly toJsonInternal; private readonly sanitizeEnums; private readonly renderTypeName; private readonly transformations; private readonly emitRegexValidation; /** * * @param schema Schema definitions * @param options */ constructor(options?: TypeGeneratorOptions); /** * Adds a JSON schema definition for a type name. This method does not emit the type * but rather just registers the definition that will get resolved if this type is `$ref`ed. * * @param typeName The name of the type. * @param def The JSON schema definition for this type */ addDefinition(typeName: string, def: JSONSchema4): void; /** * Overrides the definition of `fromTypeName` such that any references to it * will be resolved as `toTypeName`. Bear in mind that the type name specified * in `to` must either be defined as a definition (`addDefinition()`) _or_ * emitted as a custom type (`emitCustomType()`). */ addAlias(from: string, to: string): void; /** * Emit a type based on a JSON schema. If `def` is not specified, the * definition of the type will be looked up in the `definitions` provided * during initialization or via `addDefinition()`. * * @param typeName The name of th type * @param def JSON schema. If not specified, the schema is looked up from * `definitions` based on the type name * @param structFqn FQN for the type (defaults to `typeName`) * @returns The resolved type (not always the same as `typeName`) */ emitType(typeName: string, def?: JSONSchema4, structFqn?: string): string; /** * Many schemas define a type in ways that make sense for a schema, * but cannot easily be represented in jsii. * However often it is possible to transform these into a simplified version * of the same type, that will have the same semantic meaning in jsii. * * To avoid having the type generator be aware of all these cases, * we transform those types into their corresponding simplified definitions. * * @param def - the schema to be transformed */ private transformTypes; /** * Helper to conditionally run transformations */ private maybeWith; /** * Emit a type based on a JSON schema. If `def` is not specified, the * definition of the type will be looked up in the `definitions` provided * during initialization or via `addDefinition()`. * * @param typeName The name of th type * @param def JSON schema. If not specified, the schema is looked up from * `definitions` based on the type name * @param structFqn FQN for the type (defaults to `typeName`) * @returns The resolved type (not always the same as `typeName`) */ private emitTypeInternal; /** * Registers a custom type and emits it. This will override any existing * definitions for this type name. * * @param typeName The name of the type emitted by this handler. * @param emitter A function that will be called to emit the code and returns * information about the emitted type. */ emitCustomType(typeName: string, emitter: TypeEmitter | CodeEmitter): void; /** * @deprecated use `emitCustomType()` */ addCode(typeName: string, emitter: TypeEmitter | CodeEmitter): void; /** * Renders all emitted types to a string. * * Use `renderToCode()` in order to render output to an existing `Code` object. */ render(): string; /** * Writes all types to a `CodeMaker` with an open file. * Use this method in case you need to add those type to an existing file. * @param code The `CodeMaker` instance. */ renderToCode(code: Code): void; /** * @deprecated use `renderToCode()` */ emitCode(code: Code): void; /** * @deprecated use `emitType()` */ addType(typeName: string, def?: JSONSchema4, structFqn?: string): string; /** * Emits an array. */ private emitArray; /** * @returns true if this definition can be represented as a union or false if it cannot */ private tryEmitUnion; private emitStruct; private propertyFqn; private emitProperty; private emitEnum; private emitDescription; private typeForProperty; private typeForRef; private typeForArray; private resolveReference; private isPropertyRequired; private isExcluded; } interface EmittedType { /** * The JavaScript type to emit. */ readonly type: string; /** * Returns the code to convert a statement `s` back to JSON. */ readonly toJson: (code: string) => string; } type TypeEmitter = (code: Code) => EmittedType; type CodeEmitter = (code: Code) => void; export {};