import { Arr, expectType } from 'ts-data-forge'; import { type IsNever, type UnknownRecord, type ValueOf } from 'ts-type-forge'; import { union } from '../compose/index.mjs'; import { undefinedType } from '../primitives/index.mjs'; import { flattenShapeStructure, hasRecordInternals, type Type, type TypeOf, } from '../type.mjs'; export type { ValueOf } from 'ts-type-forge'; export const valueof = ( recordType: Type, options?: Partial< Readonly<{ typeName: string; }> >, ): ValueOfType => { if (!hasRecordInternals(recordType)) { throw new Error( `Expected a record type but received: ${recordType.typeName}`, ); } const shape = flattenShapeStructure(recordType.shapeStructure); if (shape === undefined) { throw new Error( `valueof() requires a simple or intersection record type, but received a union type`, ); } const types = Object.values(shape); if (Arr.isMinLengthTuple(types, 2)) { return union(types, { typeName: options?.typeName ?? `ValueOf<${recordType.typeName}>`, }); } if (Arr.isNonEmpty(types)) { return types[0]; } // types is empty // eslint-disable-next-line total-functions/no-unsafe-type-assertion return undefinedType satisfies Type as ValueOfType; }; type ValueOfType = IsNever extends true ? Type : Type>; // --- expectType assertions --- { // valueof extracts union of value types type Base = Readonly<{ a: 0; b: 1; c: 2 }>; expectType>>, 0 | 1 | 2>('='); // valueof of single-key record yields that value type type Single = Readonly<{ x: 'hello' }>; expectType>>, 'hello'>('='); // valueof of empty record yields undefined // eslint-disable-next-line @typescript-eslint/no-empty-object-type type EmptyRecord = Readonly<{}>; expectType, Type>('='); expectType>>, undefined>('='); }