diff --git a/docs/src/pages/reference/configuration/output.md b/docs/src/pages/reference/configuration/output.md index b0007fdd4..208d7c097 100644 --- a/docs/src/pages/reference/configuration/output.md +++ b/docs/src/pages/reference/configuration/output.md @@ -1279,3 +1279,25 @@ module.exports = { }, }; ``` + +#### useNativeEnums + +Type: `Boolean` + +Valid values: true or false. Defaults to false. + +Use this property to generate native Typescript `enum` instead of `type` and `const` combo. + +Example: + +```js +module.exports = { + petstore: { + output: { + override: { + useNativeEnums: true, + }, + }, + }, +}; +``` diff --git a/packages/core/src/generators/schema-definition.ts b/packages/core/src/generators/schema-definition.ts index 01812f7c4..43930ee44 100644 --- a/packages/core/src/generators/schema-definition.ts +++ b/packages/core/src/generators/schema-definition.ts @@ -59,6 +59,7 @@ export const generateSchemasDefinition = ( resolvedValue.value, schemaName, resolvedValue.originalSchema?.['x-enumNames'], + context.override.useNativeEnums, ); } else if (schemaName === resolvedValue.value && resolvedValue.isRef) { // Don't add type if schema has same name and the referred schema will be an interface diff --git a/packages/core/src/getters/enum.ts b/packages/core/src/getters/enum.ts index e5c6d5633..c56924ce0 100644 --- a/packages/core/src/getters/enum.ts +++ b/packages/core/src/getters/enum.ts @@ -1,7 +1,23 @@ import { keyword } from 'esutils'; import { isNumeric, sanitize } from '../utils'; -export const getEnum = (value: string, enumName: string, names?: string[]) => { +export const getEnum = ( + value: string, + enumName: string, + names?: string[], + useNativeEnums?: boolean, +) => { + const enumValue = useNativeEnums + ? getNativeEnum(value, enumName, names) + : getTypeConstEnum(value, enumName, names); + return enumValue; +}; + +const getTypeConstEnum = ( + value: string, + enumName: string, + names?: string[], +) => { let enumValue = `export type ${enumName} = typeof ${enumName}[keyof typeof ${enumName}]`; if (value.endsWith(' | null')) { @@ -59,6 +75,49 @@ export const getEnumImplementation = (value: string, names?: string[]) => { }, ''); }; +const getNativeEnum = (value: string, enumName: string, names?: string[]) => { + const enumItems = getNativeEnumItems(value, names); + const enumValue = `export enum ${enumName} {\n${enumItems}\n}`; + + return enumValue; +}; + +const getNativeEnumItems = (value: string, names?: string[]) => { + if (value === '') return ''; + + return [...new Set(value.split(' | '))].reduce((acc, val, index) => { + const name = names?.[index]; + if (name) { + return ( + acc + + ` ${keyword.isIdentifierNameES5(name) ? name : `'${name}'`}: ${val},\n` + ); + } + + let key = val.startsWith("'") ? val.slice(1, -1) : val; + + const isNumber = isNumeric(key); + + if (isNumber) { + key = toNumberKey(key); + } + + if (key.length > 1) { + key = sanitize(key, { + whitespace: '_', + underscore: true, + dash: true, + special: true, + }); + } + + return ( + acc + + ` ${keyword.isIdentifierNameES5(key) ? key : `'${key}'`}= ${val},\n` + ); + }, ''); +}; + const toNumberKey = (value: string) => { if (value[0] === '-') { return `NUMBER_MINUS_${value.slice(1)}`; diff --git a/packages/core/src/getters/query-params.ts b/packages/core/src/getters/query-params.ts index 8e551e1a6..497a40057 100644 --- a/packages/core/src/getters/query-params.ts +++ b/packages/core/src/getters/query-params.ts @@ -68,11 +68,11 @@ const getQueryParamsTypes = ( if (resolvedValue.isEnum && !resolvedValue.isRef) { const enumName = queryName; - const enumValue = getEnum( resolvedValue.value, enumName, resolvedValue.originalSchema?.['x-enumNames'], + context.override.useNativeEnums, ); return { diff --git a/packages/core/src/resolvers/object.ts b/packages/core/src/resolvers/object.ts index 5f9a4f7f8..1772d5a39 100644 --- a/packages/core/src/resolvers/object.ts +++ b/packages/core/src/resolvers/object.ts @@ -52,6 +52,7 @@ export const resolveObject = ({ resolvedValue.value, propName, resolvedValue.originalSchema?.['x-enumNames'], + context.override.useNativeEnums, ); return { diff --git a/packages/core/src/types.ts b/packages/core/src/types.ts index 97ac6c594..1b853bc2e 100644 --- a/packages/core/src/types.ts +++ b/packages/core/src/types.ts @@ -111,6 +111,7 @@ export type NormalizedOverrideOutput = { useDeprecatedOperations?: boolean; useBigInt?: boolean; useNamedParameters?: boolean; + useNativeEnums?: boolean; }; export type NormalizedMutator = { @@ -302,6 +303,7 @@ export type OverrideOutput = { useDeprecatedOperations?: boolean; useBigInt?: boolean; useNamedParameters?: boolean; + useNativeEnums?: boolean; }; export type OverrideOutputContentType = { diff --git a/packages/orval/src/utils/options.ts b/packages/orval/src/utils/options.ts index 06ab8867e..bb5ba0e46 100644 --- a/packages/orval/src/utils/options.ts +++ b/packages/orval/src/utils/options.ts @@ -199,6 +199,7 @@ export const normalizeOptions = async ( useDates: outputOptions.override?.useDates || false, useDeprecatedOperations: outputOptions.override?.useDeprecatedOperations ?? true, + useNativeEnums: outputOptions.override?.useNativeEnums ?? false, }, }, hooks: options.hooks ? normalizeHooks(options.hooks) : {},