type UnionToIntersection = (U extends any ? (k: U) => void : never) extends (
k: infer I
) => void
? I
: never;
type LastOf = UnionToIntersection<
T extends any ? () => T : never
> extends () => infer R
? R
: never;
type Push = [...T, V];
type UnionToTuple<
T,
L = LastOf,
N = [T] extends [never] ? true : false
> = true extends N ? [] : Push>, L>;
type Tuple = [TItem, ...TItem[]] & {
length: TLength;
};
/**
* Tuple [string, string, string] Λฦ͢
* ͦΕΛԠ༻ͯ͠ 'a' | 'b' Λ ['a' | 'b', 'a' | 'b'] ʹ͢Δ
*
* 'a' | 'b'
* => ['a' | 'b', 'a' | 'b']
* 'a' | 'b' | 'c'
* => ['a' | 'b' | 'c', 'a' | 'b' | 'c', 'a' | 'b' | 'c']
*/
type UnionToUnionTuple = Tuple["length"]>;
type TypeEq = (() => T extends A ? 1 : 2) extends () => T extends B
? 1
: 2
? true
: false;
type RemoveNeverProperties = Pick<
T,
{
[P in keyof T]: [T[P]] extends [never] ? never : P;
}[keyof T]
>;
type AnyAs0 = {
[P in keyof T]: TypeEq extends true ? 0 : T[P];
};
type UnknownAs0 = {
[P in keyof T]: TypeEq extends true ? 0 : T[P];
};
type OptionalAsUndefined = {
[P in keyof T]: T[P] extends NonNullable ? T[P] : undefined;
};
type UndefinedAsNever = {
[P in keyof T]-?: T[P] extends undefined ? never : T[P];
};
/**
* { a?: string; b: number; c?: any; d: any } Λ
* { b: number; d: any } ͚ͩʹ͢Δɻ
*
* AnyAs0 ͕ͳ͍ͱ ?: any ͷରԠ͕࿙ΕΔͷͰҙɻ
*/
type RemoveOptionalProperties = {
[P in keyof RemoveNeverProperties<
UndefinedAsNever>>>
>]: T[P];
};
function isObject(v: unknown): v is Record {
if (typeof v !== "object") {
return false;
}
return v !== null;
}
function assertObject(
v: unknown,
target = ""
): asserts v is Record {
if (!isObject(v)) {
throw new Error(`${target} should be object`.trim());
}
}
function isMatchedType(
v: unknown,
props: UnionToUnionTuple>,
errorPropsRef: string[],
target = ""
): v is T {
assertObject(v, target);
if (new Set(props).size !== props.length) {
throw new Error("Invalid props");
}
return props
.map((prop) => {
if (typeof prop !== "string") {
throw new Error("Invalid prop");
}
const within = prop in v;
if (!within) {
errorPropsRef.push(prop); // mutate
}
return within;
})
.every((flag) => flag);
}
export function assertMatchedType(
v: unknown,
props: UnionToUnionTuple>,
target = ""
): asserts v is T {
const errorPropsRef: string[] = []; // ࢠʹΛ֨ೲͤ͞ΔͨΊͷۭྻࢀর
if (!isMatchedType(v, props, errorPropsRef, target)) {
// ͜͜ʹ֘͢Δͱ͖ errorPropsRef ྻͷதʹΤϥʔՕॴ͕٧·͍ͬͯΔɻ
throw new Error(
`${target} should be aligned type. ${
0 < errorPropsRef.length ? `[${errorPropsRef.join(", ")}]` : ""
}`.trim()
);
}
}