import { Arr, memoizeFunction, Result } from 'ts-data-forge'; import { type NonEmptyArray, type NonEmptyTuple } from 'ts-type-forge'; import { type Type } from '../type.mjs'; import { createPrimitiveValidationError, prependIndexToValidationErrors, type ValidationError, } from '../utils/index.mjs'; import { buildType } from './build-type.mjs'; export type { NonEmptyArray } from 'ts-type-forge'; export const nonEmptyArray = ( elementType: Type, options?: Partial< Readonly<{ typeName: string; defaultValue: NonEmptyTuple; }> >, ): Type> => { const typeName = options?.typeName ?? `NonEmptyArray<${elementType.typeName}>`; const getDefaultValue = memoizeFunction( (): readonly A[] => options?.defaultValue ?? Arr.newArray(1, elementType.defaultValue), ); const validate = ( a: unknown, ): Result => { if (!Arr.isArray(a)) { return Result.err([ createPrimitiveValidationError({ actualValue: a, expectedType: 'array', typeName, details: undefined, }), ]); } if (Arr.isEmpty(a)) { return Result.err([ { path: [], actualValue: a, expectedType: typeName, typeName, details: { kind: 'non-empty-array', }, } satisfies ValidationError, ]); } const errors: readonly ValidationError[] = Arr.generate(function* () { for (const [index, el] of a.entries()) { const res = elementType.validate(el); if (Result.isErr(res)) { yield* prependIndexToValidationErrors(res.value, index); } } }); if (Arr.isNonEmpty(errors)) { return Result.err(errors); } // eslint-disable-next-line total-functions/no-unsafe-type-assertion return Result.ok(a as readonly A[]); }; const fill = (a: unknown): readonly A[] => Arr.isArray(a) && Arr.isMinLengthTuple(a, 1) ? Arr.map(a, (e) => elementType.fill(e) satisfies A) : getDefaultValue(); return buildType>({ typeName, getDefaultValue, validate, fill, elementType, }); };