Skip to content

Commit

Permalink
fix: type when merging optional properties of a record
Browse files Browse the repository at this point in the history
  • Loading branch information
RebeccaStevens committed May 19, 2024
1 parent 19d4944 commit fa9ace2
Show file tree
Hide file tree
Showing 5 changed files with 320 additions and 156 deletions.
2 changes: 2 additions & 0 deletions project-dictionary.txt
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ kinded
klass
knip
litecoin
metas
monero
noreply
octocat
Expand All @@ -29,5 +30,6 @@ Rebecca
sonarjs
Stevens
thud
tuplify
waldo
xyzzy
230 changes: 183 additions & 47 deletions src/types/defaults.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,14 @@ import {
} from "./merging";
import {
type FilterOutNever,
type FlattenTuples,
type FlatternAlias,
type OptionalKeysOf,
type RequiredKeysOf,
type TransposeTuple,
type TupleToIntersection,
type TuplifyUnion,
type UnionMapKeys,
type UnionMapValues,
type UnionSetValues,
type ValueOfKey,
} from "./utils";

/**
Expand Down Expand Up @@ -46,6 +47,38 @@ export type DeepMergeMergeFunctionsDefaultURIs = Readonly<{
DeepMergeOthersURI: DeepMergeLeafURI;
}>;

type RecordEntries<T extends Record<PropertyKey, unknown>> = TuplifyUnion<
{
[K in keyof T]: [K, T[K]];
}[keyof T]
>;

type RecordMeta = Record<PropertyKey, RecordPropertyMeta>;

type RecordPropertyMeta<
Key extends PropertyKey = PropertyKey,
Value = unknown,
Optional extends boolean = boolean,
> = {
key: Key;
value: Value;
optional: Optional;
};

type RecordsToRecordMeta<
Ts extends ReadonlyArray<Record<PropertyKey, unknown>>,
> = {
[I in keyof Ts]: RecordToRecordMeta<Ts[I]>;
};

type RecordToRecordMeta<T extends Record<PropertyKey, unknown>> = {
[K in keyof T]-?: {
key: K;
value: Required<T>[K];
optional: {} extends Pick<T, K> ? true : false;
};
};

/**
* Deep merge records.
*/
Expand All @@ -54,66 +87,169 @@ export type DeepMergeRecordsDefaultHKT<
MF extends DeepMergeMergeFunctionsURIs,
M,
> =
Ts extends Readonly<readonly [unknown, ...Readonly<ReadonlyArray<unknown>>]>
? FlatternAlias<DeepMergeRecordsDefaultHKTInternalProps<Ts, MF, M>>
: {};
Ts extends ReadonlyArray<Record<PropertyKey, unknown>>
? FlatternAlias<
DeepMergeRecordMetaDefaultHKTProps<RecordsToRecordMeta<Ts>, MF, M>
>
: never;

/**
* Deep merge record props.
*/
type DeepMergeRecordsDefaultHKTInternalProps<
Ts extends readonly [unknown, ...ReadonlyArray<unknown>],
type DeepMergeRecordMetaDefaultHKTProps<
RecordMetas extends ReadonlyArray<RecordMeta>,
MF extends DeepMergeMergeFunctionsURIs,
M,
> = {
[K in OptionalKeysOf<Ts>]?: DeepMergeHKT<
DeepMergeRecordsDefaultHKTInternalPropValue<Ts, K, M>,
MF,
M
>;
} & {
[K in RequiredKeysOf<Ts>]: DeepMergeHKT<
DeepMergeRecordsDefaultHKTInternalPropValue<Ts, K, M>,
MF,
M
> = CreateRecordFromMeta<MergeRecordMeta<RecordMetas>, MF, M>;

type MergeRecordMeta<RecordMetas extends ReadonlyArray<RecordMeta>> =
GroupValuesByKey<
FlattenTuples<
TransposeTuple<{
[I in keyof RecordMetas]: TransposeTuple<RecordEntries<RecordMetas[I]>>;
}>
>
>;
};

/**
* Get the value of the property.
*/
type DeepMergeRecordsDefaultHKTInternalPropValue<
Ts extends readonly [unknown, ...ReadonlyArray<unknown>],
K extends PropertyKey,
type GroupValuesByKey<Ts> = Ts extends readonly [
infer Keys extends ReadonlyArray<PropertyKey>,
infer Values,
]
? {
[I in keyof Keys]: DeepMergeRecordPropertyMetaDefaultHKTGetPossible<
Keys[I],
FilterOutNever<{
[J in keyof Values]: Values[J] extends {
key: Keys[I];
}
? Values[J]
: never;
}>
>;
}
: never;

type CreateRecordFromMeta<Ts, MF extends DeepMergeMergeFunctionsURIs, M> =
Ts extends ReadonlyArray<unknown>
? TupleToIntersection<{
[I in keyof Ts]: Ts[I] extends {
key: infer Key extends PropertyKey;
values: infer Values extends ReadonlyArray<unknown>;
optional: infer O extends boolean;
}
? CreateRecordForKeyFromMeta<Key, Values, O, MF, M>
: never;
}>
: never;

type CreateRecordForKeyFromMeta<
Key extends PropertyKey,
Values extends ReadonlyArray<unknown>,
Optional extends boolean,
MF extends DeepMergeMergeFunctionsURIs,
M,
> = FilterOutNever<
DeepMergeRecordsDefaultHKTInternalPropValueHelper<Ts, K, M, readonly []>
>;
> = Optional extends true
? {
[k in Key]+?: DeepMergeHKT<Values, MF, M>;
}
: {
[k in Key]-?: DeepMergeHKT<Values, MF, M>;
};

/**
* Tail-recursive helper type for DeepMergeRecordsDefaultHKTInternalPropValue.
* Get the possible types of a property.
*/
type DeepMergeRecordsDefaultHKTInternalPropValueHelper<
Ts extends readonly [unknown, ...ReadonlyArray<unknown>],
K extends PropertyKey,
M,
Acc extends ReadonlyArray<unknown>,
type DeepMergeRecordPropertyMetaDefaultHKTGetPossible<
Key extends PropertyKey,
Ts,
> = Ts extends readonly [
infer Head extends Readonly<Record<PropertyKey, unknown>>,
RecordPropertyMeta,
...ReadonlyArray<RecordPropertyMeta>,
]
? DeepMergeRecordPropertyMetaDefaultHKTGetPossibleHelper<
Ts,
{ key: Key; values: []; optional: never }
>
: never;

/**
* Tail-recursive helper type for DeepMergeRecordPropertyMetaDefaultHKTGetPossible.
*/
type DeepMergeRecordPropertyMetaDefaultHKTGetPossibleHelper<
Ts extends readonly [
RecordPropertyMeta,
...ReadonlyArray<RecordPropertyMeta>,
],
Acc extends {
key: PropertyKey;
values: ReadonlyArray<unknown>;
optional: boolean;
},
> = Ts extends [
...infer Rest,
{
key: infer K extends PropertyKey;
value: infer V;
optional: infer O extends boolean;
},
]
? Rest extends readonly [unknown, ...ReadonlyArray<unknown>]
? DeepMergeRecordsDefaultHKTInternalPropValueHelper<
Rest,
K,
M,
[...Acc, ValueOfKey<Head, K>]
>
: [...Acc, ValueOfKey<Head, K>]
? Acc["optional"] extends true
? Acc extends { values: [infer Head, ...infer AccRest] }
? Rest extends readonly [
RecordPropertyMeta,
...ReadonlyArray<RecordPropertyMeta>,
]
? DeepMergeRecordPropertyMetaDefaultHKTGetPossibleHelper<
Rest,
{
key: K;
values: [V | Head, ...AccRest];
optional: O;
}
>
: {
key: K;
values: [V | Head, ...AccRest];
optional: O;
}
: Rest extends readonly [
RecordPropertyMeta,
...ReadonlyArray<RecordPropertyMeta>,
]
? DeepMergeRecordPropertyMetaDefaultHKTGetPossibleHelper<
Rest,
{
key: K;
values: [V, ...Acc["values"]];
optional: O;
}
>
: {
key: K;
values: [V, ...Acc["values"]];
optional: O;
}
: Rest extends readonly [
RecordPropertyMeta,
...ReadonlyArray<RecordPropertyMeta>,
]
? DeepMergeRecordPropertyMetaDefaultHKTGetPossibleHelper<
Rest,
{
key: K;
values: [V, ...Acc["values"]];
optional: O;
}
>
: {
key: K;
values: [V, ...Acc["values"]];
optional: O;
}
: never;

/**
* Deep merge 2 arrays.
* Deep merge arrays.
*/
export type DeepMergeArraysDefaultHKT<
Ts extends ReadonlyArray<unknown>,
Expand Down Expand Up @@ -142,14 +278,14 @@ type DeepMergeArraysDefaultHKTHelper<
: never;

/**
* Deep merge 2 sets.
* Deep merge sets.
*/
export type DeepMergeSetsDefaultHKT<Ts extends ReadonlyArray<unknown>> = Set<
UnionSetValues<Ts>
>;

/**
* Deep merge 2 maps.
* Deep merge maps.
*/
export type DeepMergeMapsDefaultHKT<Ts extends ReadonlyArray<unknown>> = Map<
UnionMapKeys<Ts>,
Expand Down

0 comments on commit fa9ace2

Please sign in to comment.