@odata2ts/odata2ts
Version:
Flexible generator to produce various TypeScript artefacts (from simple model interfaces to complete odata clients) from OData metadata files
352 lines (351 loc) • 15.2 kB
TypeScript
import { TypeConverterConfig } from "@odata2ts/converter-runtime";
import { AxiosRequestConfig } from "axios";
import { NameSettings, OverridableNamingOptions } from "./NamingModel.js";
import { TypeModel } from "./TypeModel.js";
/**
* Generation mode, by default "all".
*/
export declare enum Modes {
models = 0,
qobjects = 1,
service = 2,
all = 3
}
/**
* What kind of stuff to emit: Either raw TS or TS that has been compiled to JS / DTS.
*/
export declare enum EmitModes {
ts = "ts",
js = "js",
dts = "dts",
js_dts = "js_dts"
}
/**
* Config options for CLI.
*/
export interface CliOptions {
/**
* The URL to the root of your OData service. The URL might end in a slash or not, it might also end
* in $metadata, but we usually add this for you.
*
* Specifying the URL is a convenience feature to download the metadata file from the given URL.
* You can configure this request via `sourceConfig` option.
*
* The `source` option must still be specified as it is used to store the downloaded file on your disk.
* By default, the file is used once it has been downloaded.
*/
sourceUrl?: string;
/**
* Downloads the metadata file and overwrites the existing one, if any.
*
* Only takes effect, if option `sourceUrl` is specified.
*/
refreshFile?: boolean;
/**
* The source is the file to use (must be an EDMX compliant XML file) or the URL to the
* metadata (ROOT_SERVICE/$metadata).
*
* If not specified, at least one service must be configured in config file.
*/
source?: string;
/**
* Specifies the output directory for the generated stuff.
*
* If not specified, at least one service must be configured in config file.
*/
output?: string;
/**
* Only generates the specified services.
* Relies on an existing config file where these service names are maintained.
*/
services?: Array<string>;
/**
* Specifies what to generate:
* - {@code Modes.models} will only generate TS interfaces
* - {@code Modes.qobjects} will generate functional units used in QueryBuilder and for functions and actions
* - {@code Modes.service} will generate one main OData service client and one per each entity
* - {@code Modes.all} the same as {@code Modes.service}
*
* QObjects will also generate models, and generating the service client will also generate models and QObjects.
* Defaults to {@code Modes.all}
*/
mode?: Modes;
/**
* Specifies the type of the output files: TypeScript, JS, DTS only, JS with DTS.
* Defaults to {@code EmitModes.js_dts}
*/
emitMode?: EmitModes;
/**
* Uses prettier with your local configuration to pretty print TypeScript files.
* Only applies if mode is set to {@code EmitModes.ts}.
*/
prettier?: boolean;
/**
* When compiling TypeScript to JS, "tsconfig.json" is used by default to add compilerOptions.
* This option allows to specify an alternative file.
*
* Only takes effect, when mode is set to anything else than {@code EmitModes.ts}.
*/
tsconfig?: string;
/**
* Verbose debugging information.
*/
debug?: boolean;
/**
* Overrides the service name found in the source file.
*
* The service name is the basis for all file names and the name of the main OData client service
* that serves as entry point for the user.
*/
serviceName?: string;
/**
* odata2ts will automatically decide if a key prop is managed on the server side.
* If managed, the property will not be editable (create, update, patch).
* The following rule applies:
* If a property is the only key prop of an entity, then the prop is deemed to be managed;
* in ony other case the prop is unmanaged.
*/
disableAutoManagedKey?: boolean;
/**
* By default, odata2ts doesn't change param, operation, property or model names.
* The generated models and their properties are named exactly as advertised by the server.
*
* By allowing odata2ts to change these names, certain predefined formatting strategies are used:
* Model / class names are formatted with PascalCase; property, param, and operation names with camelCase.
*
* The naming configuration allows to control this and other naming related settings.
* Note: Even if renaming is disabled, model prefixing / suffixing still applies.
*/
allowRenaming?: boolean;
}
/**
* Configuration options of the request to retrieve the metadata.
* Only takes effect if `source` is a URL.
*/
export interface UrlSourceConfiguration {
/**
* Basic auth credentials: the username.
* Only takes effect if `password` has also been set.
*/
username?: string;
/**
* Basic auth credentials: the password.
* Only takes effect if `username` has also been set.
*/
password?: string;
/**
* Custom request configuration.
* URL and method `GET` are set by default, but can be overwritten.
*/
custom?: AxiosRequestConfig;
}
/**
* Available options for configuration files, i.e. odata2ts.config.ts.
*/
export interface ConfigFileOptions extends Omit<CliOptions, "sourceUrl" | "source" | "output" | "services"> {
/**
* Configuration options of the request to retrieve the metadata.
* Only takes effect if `sourceUrl` is a URL.
*/
sourceUrlConfig?: UrlSourceConfiguration;
/**
* Configuration of each service.
*
* @example { services: { trippin: { source: "...", ... } }}
*/
services?: {
[serviceName: string]: ServiceGenerationOptions;
};
/**
* Specify which converters to use by their package name, e.g. "@odata2ts/converter-v2-to-v4".
* Each converter knows which data type to map.
*
* To only use specific converters, the object syntax must be used, where supported converters
* must be listed by their ids.
*/
converters?: Array<string | TypeConverterConfig>;
/**
* For each model an editable version is generated which represents the model definition for
* create, update and patch actions.
*
* You can skip the generation altogether, not generating editable model variants,
* if the generation mode is {@code Mode.model} or {@code Mode.qobject}.
*/
skipEditableModels?: boolean;
/**
* ID models are generated from entity id parameters.
* The generation for one entity entails one model interface representing the id parameters and
* one QId function which allows to format the parameters for URL usage and to parse parameters
* from a URL string.
*
* You can skip the generation altogether, not generating models and QId objects, if the
* generation mode is {@code Mode.model} or {@code Mode.qobject}.
*/
skipIdModels?: boolean;
/**
* Operations are functions and actions of the OData service.
* The generation for one operation entails one parameter model interface
* and one QFunction / QAction class.
*
* You can skip the generation altogether, neither generating model nor query object,
* if the generation mode is {@code Mode.model} or {@code Mode.qobject}.
*/
skipOperations?: boolean;
/**
* Model properties have explaining comments by default.
* With this option you can turn that off.
*/
skipComments?: boolean;
/**
* With OData you can read, update and delete data on a primitive property (`Edm.*`).
* Usually, you wouldn't do that, but go for a bigger request, fetching more relevant information in one go.
*
* There's one exception: Handling `Edm.Stream´ properties and Media entities. Services for stream / media
* stuff are generated regardless of this setting.
*/
enablePrimitivePropertyServices?: boolean;
/**
* The naming options regarding the generated artefacts.
*/
naming?: OverridableNamingOptions;
/**
* Some OData V2 services generate an extra wrapping for entity collection attributes:
* <code>trips: {results: [...]}</code>. So instead of directly returning an array of entities
* an object with the property "results" is wrapped around the entity collection.
*
* If you're using the odata client then there's a build-in workaround in place which transforms
* the results to remove this extra mapping. However, if you're only interested in the types, then
* the generated models will not match that extra wrapping.
*
* Setting this configuration option to <code>true</code> (default: false) will add this extra
* wrapping to the generated models. But this option is only valid if the generation mode is set
* to <code>models</code>; it is ignored otherwise.
*/
v2ModelsWithExtraResultsWrapping?: boolean;
/**
* Numbers of type `Edm.Int64` and `Edm.Decimal` are represented as `number` in V4.
* However, these numbers might not fit into JS' number type, which might result in precision loss.
*
* OData offers a special IEEE754 format option to get those types as `string` instead to prevent any
* precision loss. So if you're handling very large or very small numbers (JS roughly supports 15 digits),
* then you should use this option and, probably, also an appropriate converter (see available converters).
*
* Activating this option affects the type generation and will use `string` for both mentioned types.
* All requests are executed with the "accept" header set to "application/json;IEEE754Compatible=true".
* Additionally, when sending data the very same value will be set for the "content-type" header.
*/
v4BigNumberAsString?: boolean;
/**
* OData allows for namespaces so any entity is unique by virtue of it's name within a namespace.
* odata2ts works with these fully qualified names internally, but only uses the plain name when generating
* stuff. This might lead to name clashes (same name in different namespaces).
*
* odata2ts employs a simple, automatic resolution strategy: Adding a counter at the end of the name.
* Set this property to true in order to disable this automatism.
*/
disableAutomaticNameClashResolution?: boolean;
/**
* By default, odata2ts generates a folder structure with individual files per entity.
* This allows for handling and scaling the generation process for large data structures.
*
* However, especially UI5 has problems with recursive structures, which are absolutely valid within
* OData. Here the solution is to generate only one file per type to circumvent cyclic imports.
* To enable this behaviour set this option to true.
*/
bundledFileGeneration?: boolean;
/**
* If enabled, odata2ts will generate numeric enums instead of string enums.
* This
*/
numericEnums?: boolean;
}
/**
* Custom generation options which are dependent on a specific odata service.
*/
export interface ServiceGenerationOptions extends Required<Pick<CliOptions, "source" | "output">>, Pick<CliOptions, "sourceUrl" | "refreshFile">, Omit<ConfigFileOptions, "services"> {
/**
* Configure generation process for individual properties based on their name.
*/
propertiesByName?: Array<PropertyGenerationOptions>;
/**
* Rename any EntityType, ComplexType, EnumType, Function or Action.
*
* You must match the simple name (e.g. "Person") or the fully qualified name
* (e.g. "Trippin.Person") exactly. Alternatively, you can rename a bunch of types
* by using regular expressions.
*
* By providing additional type information via the "type" attribute you get even more options which only apply
* to the given type.
*/
byTypeAndName?: Array<ComplexTypeGenerationOptions | EntityTypeGenerationOptions | GenericTypeGenerationOptions>;
}
/**
* Available options for the actual generation run.
* Every property is required, except the overriding service name.
*/
export interface RunOptions extends Required<Omit<ServiceGenerationOptions, "serviceName" | "sourceUrl" | "sourceUrlConfig" | "refreshFile">>, Pick<ServiceGenerationOptions, "serviceName" | "sourceUrl" | "sourceUrlConfig" | "refreshFile"> {
naming: NameSettings;
}
export interface RenameOptions {
/**
* Matcher for the name of any EntityType, ComplexType, EnumType, Function or Action
* as it is stated in the EDMX model, e.g. "Person". As OData supports namespaces
* you can also use the fully qualified name (including the namespace) to address any model,
* e.g. "Trippin.Person". You can also match properties by their name.
*
* If the name is specified as plain string, it must match either the name or the fully qualified name
* exactly (case-sensitive).
*
* Alternatively, a regular expression can be used which is always applied to the fully qualified name
* (e.g. Trippin.Person). The regular expression must match the whole string
* (e.g. `/Person/` won't do, `/.*\.Person/` would work).
*
* To make regular expressions useful, captured groups are also supported in combination with
* the `mappedName` attribute.
*/
name: string | RegExp;
/**
* If specified, this attribute value is used as final name for the matched name as it will
* appear in the generated typescript.
*
* When using a regular expression for matching the name, then captured groups can be referenced
* as usual via $1, $2, etc. For example:
* - name: /Trippin\.(.+)/
* - mappedName: "T_$1"
* The result would be "T_Person".
*/
mappedName?: string;
}
export type TypeBasedGenerationOptions = GenericTypeGenerationOptions | ComplexTypeGenerationOptions | EntityTypeGenerationOptions;
export interface GenericTypeGenerationOptions extends RenameOptions {
type: TypeModel.Any | TypeModel.EnumType | TypeModel.OperationType | TypeModel.OperationImportType | TypeModel.Singleton | TypeModel.EntitySet;
}
export interface ComplexTypeGenerationOptions extends RenameOptions {
type: TypeModel.ComplexType;
/**
* Configuration of individual properties.
*/
properties?: Array<PropertyGenerationOptions>;
}
/**
* Configuration options for EntityTypes and ComplexTypes.
* This config applies if the name matches the name of an EntityType or ComplexType as it is specified
* in the metadata (e.g. in EDMX <EntityType name="Test" ...)
*/
export interface EntityTypeGenerationOptions extends Omit<ComplexTypeGenerationOptions, "type"> {
type: TypeModel.EntityType;
/**
* Overwrite the key specification by naming the props by their EDMX name.
*/
keys?: Array<string>;
}
/**
* All configuration options for properties of models.
*/
export interface PropertyGenerationOptions extends RenameOptions {
/**
* Managed attributes - i.e. managed by the server - cannot be created or updated.
* Hence, they are left out of the editable model versions.
*/
managed?: boolean;
}