prefer-type-fest-require-exactly-one
Require TypeFest RequireExactlyOne<T, Keys> over imported aliases like OneOf or RequireOnlyOne.
Targeted pattern scopeโ
This rule reports imported OneOf/RequireOnlyOne aliases and prefers RequireExactlyOne<T, Keys> for XOR-style object constraints.
Use this when callers must choose one mode, not multiple modes (for example,
id or slug, apiKey or token).
What this rule reportsโ
- Type references that resolve to imported
OneOfaliases. - Type references that resolve to imported
RequireOnlyOnealiases.
Detection boundariesโ
- โ Reports imported aliases with direct named imports.
- โ Does not report namespace-qualified alias usage.
- โ
Auto-fixes imported alias references to
RequireExactlyOnewhen replacement is syntactically safe. - โ
Alias coverage is configurable with
enforcedAliasNames.
Why this rule existsโ
RequireExactlyOne is the canonical TypeFest utility for enforcing exactly one active key among a set. Using the canonical name reduces semantic drift between utility libraries.
This is one of the most error-prone constraints in hand-written unions. Using a known utility keeps intent obvious and consistent.
โ Incorrectโ
import type { OneOf } from "type-aliases";
type Auth = OneOf<{
token?: string;
apiKey?: string;
}>;
โ Correctโ
import type { RequireExactlyOne } from "type-fest";
type Auth = RequireExactlyOne<{
token?: string;
apiKey?: string;
}>;
Behavior and migration notesโ
RequireExactlyOne<T, Keys>encodes XOR object modes where one and only one key can be active.- This rule targets alias names with matching semantics (
OneOf,RequireOnlyOne). - Keep the participating key set small and explicit to avoid hard-to-read error messages in consuming code.
Optionsโ
This rule accepts a single options object:
type PreferTypeFestRequireExactlyOneOptions = {
/**
* Legacy alias names that this rule will report and replace.
*
* @default ["OneOf", "RequireOnlyOne"]
*/
enforcedAliasNames?: ("OneOf" | "RequireOnlyOne")[];
};
Default configuration:
{
enforcedAliasNames: ["OneOf", "RequireOnlyOne"],
}
Flat config setup (default behavior):
import typefest from "eslint-plugin-typefest";
export default [
{
plugins: { typefest },
rules: {
"typefest/prefer-type-fest-require-exactly-one": [
"error",
{ enforcedAliasNames: ["OneOf", "RequireOnlyOne"] },
],
},
},
];
enforcedAliasNames: ["OneOf", "RequireOnlyOne"] (default)โ
Reports both legacy aliases.
enforcedAliasNames: ["RequireOnlyOne"]โ
Reports only RequireOnlyOne and ignores OneOf:
import typefest from "eslint-plugin-typefest";
export default [
{
plugins: { typefest },
rules: {
"typefest/prefer-type-fest-require-exactly-one": [
"error",
{ enforcedAliasNames: ["RequireOnlyOne"] },
],
},
},
];
import type { OneOf, RequireOnlyOne } from "type-aliases";
type A = OneOf<{ a?: string; b?: number }>; // โ
Not reported
type B = RequireOnlyOne<{ a?: string; b?: number }>; // โ Reported
Additional examplesโ
โ Incorrect โ Additional exampleโ
import type { RequireOnlyOne } from "custom-type-utils";
type LookupInput = RequireOnlyOne<
{
id?: string;
slug?: string;
externalRef?: string;
},
"id" | "slug" | "externalRef"
>;
โ Correct โ Additional exampleโ
import type { RequireExactlyOne } from "type-fest";
type LookupInput = RequireExactlyOne<
{
id?: string;
slug?: string;
externalRef?: string;
},
"id" | "slug" | "externalRef"
>;
โ Correct โ Repository-wide usageโ
type AuthInput = RequireExactlyOne<
{ token?: string; apiKey?: string; oauthCode?: string },
"token" | "apiKey" | "oauthCode"
>;
ESLint flat config exampleโ
import typefest from "eslint-plugin-typefest";
export default [
{
plugins: { typefest },
rules: {
"typefest/prefer-type-fest-require-exactly-one": "error",
},
},
];
When not to use itโ
Disable this rule if compatibility requirements force existing alias names.
Package documentationโ
TypeFest package documentation:
Source file: source/require-exactly-one.d.ts
/**
Create a type that requires exactly one of the given keys and disallows more. The remaining keys are kept as is.
Use-cases:
- Creating interfaces for components that only need one of the keys to display properly.
- Declaring generic keys in a single place for a single use-case that gets narrowed down via `RequireExactlyOne`.
The caveat with `RequireExactlyOne` is that TypeScript doesn't always know at compile time every key that will exist at runtime. Therefore `RequireExactlyOne` can't do anything to prevent extra keys it doesn't know about.
@example
```
import type {RequireExactlyOne} from 'type-fest';
type Responder = {
text: () => string;
json: () => string;
secure: boolean;
};
const responder: RequireExactlyOne<Responder, 'text' | 'json'> = {
// Adding a `text` key here would cause a compile error.
json: () => '{"message": "ok"}',
secure: true,
};
```
@category Object
*/
Rule catalog ID: R058