This typescript definition file can limit the size of array by both minimum and maximum boundaries. Copy the whole thing into a single file and the exported type ArrayOf is used to accomplish the desired behavior.
type MaxArrayLength<Type, Count extends number> = BuildArrayOf<
"exactly",
Count,
Type,
[]
> extends [Type, ...infer Rest]
? [Type, ...Rest] | MaxArrayLength<Type, Rest["length"]>
: Count extends 1
? [] | [Type]
: [];
type BuildArrayOf<
Quantifier extends "exactly" | "min" | "max",
Count extends number,
Type,
Current extends Type[]
> = Quantifier extends "max"
? MaxArrayLength<string, Count>
: Current["length"] extends Count
? Quantifier extends "exactly"
? [...Current]
: [...Current, ...Type[]]
: BuildArrayOf<Quantifier, Count, Type, [...Current, Type]>;
type NonNegativeInteger<T extends number> = number extends T
? never
: `${T}` extends `-${string}` | `${string}.${string}`
? never
: T;
export type ArrayOf<
Type,
MinLength extends number | undefined = undefined,
MaxLength extends number | undefined = undefined
> = MinLength extends number
? NonNegativeInteger<MinLength> extends never
? never
: MaxLength extends number
? NonNegativeInteger<MaxLength> extends never
? never
: MinLength extends MaxLength
? BuildArrayOf<"exactly", MinLength, Type, []>
: BuildArrayOf<"exactly", MinLength, Type, []> extends [
Type,
...infer Rest
]
? Exclude<
BuildArrayOf<"max", MaxLength, Type, []>,
BuildArrayOf<"max", Rest["length"], Type, []>
>
: MinLength extends 1
? Exclude<BuildArrayOf<"max", MaxLength, Type, []>, []>
: BuildArrayOf<"max", MaxLength, Type, []>
: BuildArrayOf<"min", MinLength, Type, []>
: MaxLength extends number
? NonNegativeInteger<MaxLength> extends never
? never
: BuildArrayOf<"max", MaxLength, Type, []>
: Type[];
The usage is simple. Take a look:
const arrayWithNoBoundary: ArrayOf<string> = []; // equals to string[]
const arrayWithMinimumLength: ArrayOf<string, 2> = ["", ""] // equals to [string, string, ...string[]]
const arrayWithMaximumLength: ArrayOf<string, undefined, 2> = []; // equals to ([] | [string] | [string, string])
const arrayWithBothLimits: ArrayOf<string, 3, 5> = ["", "", "", ""] // equals to ([string, string, string] | [string, string, string, string] | [string, string, string, string, string])
const arrayOfFixedSie: ArrayOf<string, 3, 3> = ["", "", ""] // equals to [string, string, string]
Invalid usages will turn the type into never. Here is a list of all invalid usages:
type Invalid = ArrayOf<SomeType, number, number> // Min and max must be of type either undefined or an exact positive integer. The 'number' type is not accepted
type Invalid = ArrayOf<SomeType, -1, 1.2> // Negative numbers or numbers with decimals are invalid for both min and max length.
type Invalid = ArrayOf<SomeType, 2, 1> // Min length can not be grater than max length