import { expectType } from 'ts-data-forge'; import { type ArrayElement, type NonEmptyTuple, type PartiallyPartial, type TypeEq, type UnknownRecord, } from 'ts-type-forge'; import { type ExcessPropertyOption, type Type, type TypeOf, type UnknownShape, flattenShapeStructure, hasRecordInternals, } from '../type.mjs'; import { toUnionKeyString } from '../utils/index.mjs'; import { optional } from './optional.mjs'; import { record } from './record.mjs'; /** * Creates a Partial type. If keysToBeOptional is set, only those keys are * optional, otherwise, all properties are optional. */ export const partial = < const R extends UnknownRecord, const KeysToBeOptional extends NonEmptyTuple, >( recordType: Type, options?: Partial< Readonly<{ keysToBeOptional: KeysToBeOptional; typeName: string; excessProperty: ExcessPropertyOption; }> >, ): PartialType => { 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( `partial() requires a simple or intersection record type, but received a union type`, ); } const typeNameFilled: string = options?.typeName ?? (options?.keysToBeOptional === undefined ? `Partial<${recordType.typeName}>` : `PartiallyPartial<${recordType.typeName}, ${toUnionKeyString(options.keysToBeOptional)}>`); const keysToBeOptional: ReadonlySet = new Set( options?.keysToBeOptional ?? Object.keys(shape), ); const partialShape = Object.fromEntries( Object.entries(shape).map( ([k, v]) => [k, keysToBeOptional.has(k) ? optional(v) : v] as const, ), ) satisfies UnknownShape; // eslint-disable-next-line total-functions/no-unsafe-type-assertion return record(partialShape, { typeName: typeNameFilled, excessProperty: options?.excessProperty ?? recordType.excessProperty, }) as unknown as PartialType; }; type PartialType< R extends UnknownRecord, KeysToBeOptional extends NonEmptyTuple | undefined = undefined, > = Type>; /** Compute the partial value type. */ type PartialValue< R extends UnknownRecord, KeysToBeOptional extends NonEmptyTuple | undefined, > = TypeEq extends true ? Partial : PartiallyPartial>; // --- expectType assertions --- { type Base = ReturnType< typeof record; b: Type<1>; c: Type<2> }>> >; expectType< PartialType, readonly ['a', 'b', 'c']>, Type>> >('='); expectType< PartialType, readonly ['a', 'b']>, Type> >('='); expectType< // @ts-expect-error NonEmptyTuple is required if keysToBeOptional is provided PartialType, readonly []>, Type> >('!='); expectType< PartialType>, Type> >('='); // partial with no keys makes all properties optional expectType< TypeOf< ReturnType, NonEmptyTuple<'a' | 'b' | 'c'>>> >, Partial> >('='); }