import { Arr, asUint32, memoizeFunction, Result } from 'ts-data-forge'; import { type MinLengthArray, type SupportedLength } from 'ts-type-forge'; import { type Type } from '../type.mjs'; import { createPrimitiveValidationError, type ValidationError, } from '../utils/index.mjs'; import { buildType, validateElements } from './build-type.mjs'; export type { MinLengthArray } from 'ts-type-forge'; /** * Creates a `Type` for a readonly array with at least `minLength` elements, * typed as the branded {@link MinLengthArray} instead of the structural * `readonly [A, ..., A, ...A[]]` type produced by `minLengthTuple`. * * Because the length constraint lives only in the brand, the element type is * never expanded into tuple positions, which keeps type-checking cheap even * when `A` is a large type. */ export function minLengthArray( minLength: N, elementType: Type, options?: Partial< Readonly<{ typeName: string; defaultValue: MinLengthArray; }> >, ): Type>; // For bounds outside `SupportedLength` (`0..2048`) the length cannot be encoded in the brand, // so the result length is left unconstrained (`readonly A[]`). export function minLengthArray( minLength: number, elementType: Type, options?: Partial< Readonly<{ typeName: string; defaultValue: readonly A[]; }> >, ): Type; export function minLengthArray( minLength: number, elementType: Type, options?: Partial< Readonly<{ typeName: string; defaultValue: readonly A[]; }> >, ): Type { type T = readonly A[]; const typeName = options?.typeName ?? `MinLengthArray<${minLength}, ${elementType.typeName}>`; const getDefaultValue = memoizeFunction( (): T => options?.defaultValue ?? // An array of `minLength` elements is the shortest value satisfying the bound. Arr.create(asUint32(minLength), elementType.defaultValue), ); const validate: Type['validate'] = (a) => { if (!Arr.isArray(a)) { return Result.err([ createPrimitiveValidationError({ actualValue: a, expectedType: 'array', typeName, details: undefined, }), ]); } if (a.length < minLength) { return Result.err([ { path: [], actualValue: a, expectedType: typeName, typeName, details: { kind: 'array-min-length', minLength, actualLength: a.length, }, } satisfies ValidationError, ]); } return validateElements(a, elementType); }; const fill: Type['fill'] = (a) => { if (!Arr.isArray(a)) { return getDefaultValue(); } // Keep the input if it is long enough; otherwise pad up to `minLength`. return a.length >= minLength ? Arr.map(a, (el) => elementType.fill(el) satisfies A) : Arr.map( Arr.seq(asUint32(minLength)), (i) => elementType.fill(a[i]) satisfies A, ); }; return buildType({ typeName, getDefaultValue, validate, fill, elementType }); }