Closed datahookinc closed 3 days ago
// Usage examples:
// type Sample1 = AllSameType<string | string>; // true
// type Sample2 = AllSameType<string | number>; // false
// type Sample3 = AllSameType<number | number | number>; // true
// type Sample4 = AllSameType<string>; // true
type IsHomogenous<T> = [T] extends [boolean] ? true : (T extends any ? (x: T) => void : false) extends ((x: infer U) => void) ? T extends U ? true : false : false;
type AllowedPrimitives = string | number | boolean | Date | null;
type IsAllowedPrimitive<T> = T extends AllowedPrimitives ? true : false;
// Usage examples:
type Sample1 = IsHomogenous<string | string>; // true
type Sample2 = IsHomogenous<string | number>; // false
type Sample3 = IsHomogenous<number | number | number>; // true
type Sample4 = IsHomogenous<true | false>; // true
type Sample5 = IsHomogenous<boolean>; // true
type PrimitiveType<T> = T extends string ? string
: T extends number ? number
: T extends boolean ? boolean
: T extends Date ? Date
: T extends null ? (null extends T ? null : never) : never;
type ArrayType<T> = PrimitiveType<T> extends never ? T extends Array<unknown> ? T : never : never
type ObjecType<T> = ArrayType<T> extends never ? T extends NewableFunction | CallableFunction | Map<any, any> | Set<any> | WeakMap<object, any> | WeakSet<object> ? never : T : never
type BaseType<T> = T extends string ? string
: T extends number ? number
: T extends boolean ? boolean
: T extends Date ? Date
: T extends Array<unknown> ? T
: T extends NewableFunction | CallableFunction | Map<any, any> | Set<any> | WeakMap<object, any> | WeakSet<object> ? never
: T // plain object or null
type IsHomogenous2<T> = (T extends any ? (x: BaseType<T>) => void : false) extends ((x: infer U) => void) ? [T] extends [U] ? true : false : false;
// type IsHomogenous2<T> = (T extends any ? (x: BaseType<T>) => void : false) extends ((x: infer U) => void) ? true : false ;
type IsAllowed<T> = IsAllowedPrimitive<T> extends true
// ? IsHomogenous<Exclude<T, null>> extends true
? IsHomogenous2<Exclude<T, null>> extends true
? T
: 'Union is not allowed'
: 'Not an allowed primitive'
type ex1 = IsAllowed<string>;
type ex2 = IsAllowed<Date>;
type ex3 = IsAllowed<string | number>; // union is not allowe
type ex4 = IsAllowed<string | null | number>;
type ex5 = IsAllowed<{ name: string }>; // not allowed primitive
type ex6 = IsAllowed<() => void>; // not allowed primitive
type ex7 = IsAllowedPrimitive<'a' | 'b'>; // true
type ex8 = IsAllowed<'a' | 'b'>; // false (not allowing homogenous literals)
type ex9 = IsAllowed<100 | 200>;
type ex10 = IsAllowed<100 | 200 | null>;
type ex11 = IsAllowed<Date | null>;
type ex12 = IsAllowed<string | null>;
// type ex4 = IsAllowed<string | null | number>;
// a discriminated union can inherently have many unions
// { name: string, age : number } | 10
// the problem I am struggling with is I don't want to allow mixing of primitives, arrays, and objects
// type IsAllowed2<T> = IsAllowedPrimitive<T> extends true
// // ? IsHomogenous<Exclude<T, null>> extends true
// ? IsHomogenous2<Exclude<T, null>> extends true
// ? T
// : 'Union is not allowed'
// : T extends Array<infer U>
// ? IsHomogenous2<Exclude<T, null>> extends true // check that T is homogenous (this can be lifted)
// ? IsHomogenous2<Exclude<U, null>> extends true // check that the type of T is homogenous
// ? T
// : 'Array type not allowed'
// : 'Not an allowed array type'
// : 'Not an allowed type for Trigger'
// type IsAllowed2<T> = IsAllowedPrimitive<T> extends true
// // ? IsHomogenous<Exclude<T, null>> extends true
// ? IsHomogenous2<Exclude<T, null>> extends true
// ? T
// : 'Union is not allowed'
// : T extends Array<infer U> // infer will look at each element of the union
// ? 'Yes it is an array'
// : 'Nope'
// // ? IsHomogenous2<Exclude<T, null>> extends true // check that T is homogenous (this can be lifted)
// // ? IsHomogenous2<Exclude<U, null>> extends true // check that the type of T is homogenous
// // ? T
// // : 'Array type not allowed'
// // : 'Not an allowed array type'
// // : 'Not an allowed type for Trigger'
// x: string[] | number[] // should be allowed
// x: (string | number)[] // should be allowed
type IsUnion<T, B = T> = T extends B ? ([B] extends [T] ? false : true) : false;
// If I want my primitives to only be homogenous, while allowing arrays and objects to be descriminate unions, then I need to change my utility types above
type IsAllowed2<T> = IsHomogenous2<Exclude<T, null>> extends true
? IsAllowedPrimitive<T> extends true
? T
: Exclude<T, null> extends Array<infer U>
// This is likely a problem for me because discriminate types are technically unions
? IsUnion<U> extends true
? IsAllowed2<U>
: T
: Exclude<T, null> extends Record<string, any>
? UserRow<T>
: 'Either an array that is not allowed, or an object that requires recursion'
// : T extends Array<T>
// ? T
// :'Not allowed array type in union'
: 'Not allowed union type'
// :'Not an allowed type for Trigger'
// LEFT-OFF: I have most of the pieces to make this work, but let's just enforce it ourselves in the code for now
// LEFT-OFF: If I want this to work
// IsAllowedPrimitive extends true (check that they are all homogenous); if not fail
// IsArray ensure Exclude<null> does not result in another union; ensure that the array elements are all IsAllowed2
// IsObject run recursively.
type UserRow<T> = {
[P in keyof T]: IsAllowed<T[P]>;
};
// IsAllowedPrimitive<T> extends true
// // ? IsHomogenous<Exclude<T, null>> extends true
// ? IsHomogenous2<Exclude<T, null>> extends true
// ? T
// : 'Union is not allowed'
// : T extends Array<infer U> // infer will look at each element of the union
// ? 'Yes it is an array'
// : 'Nope'
// ? IsHomogenous2<Exclude<T, null>> extends true // check that T is homogenous (this can be lifted)
// ? IsHomogenous2<Exclude<U, null>> extends true // check that the type of T is homogenous
// ? T
// : 'Array type not allowed'
// : 'Not an allowed array type'
// : 'Not an allowed type for Trigger'
type IsArray<T> = T extends Array<unknown> ? T : never
type ex1a = IsAllowed2<string>;
type ex2a = IsAllowed2<Date>;
type ex3a = IsAllowed2<string | number>; // union is not allowed
type ex4a = IsAllowed2<string | null | number>;
type ex5a = IsAllowed2<{ name: string }>; // not allowed primitive
type ex6a = IsAllowed2<() => void>; // not allowed primitive
type ex7a = IsAllowedPrimitive<'a' | 'b'>; // true
type ex8a = IsAllowed2<'a' | 'b'>; // false (not allowing homogenous literals)
type ex9a = IsAllowed2<100 | 200>;
type ex10a = IsAllowed2<100 | 200 | null>;
type ex11a = IsAllowed2<Date | null>;
type ex12a = IsAllowed2<string | null>;
type ex13a = IsAllowed2<string[] | null>; // starting here I now need an IsUnion piece, to break it apart
type ex14a = IsAllowed2<boolean | null>;
type ex15a = IsAllowed2<boolean | true | false> | null;
type ex16a = IsAllowedPrimitive<string[] | null> // why is this failing? why is this coming out as boolean?
type ex17a = IsAllowed2<string[] | number>; // why is this failing for only one part of the union?
type ex19a = IsAllowed2<string[] | null>; // why is this failing for only one part of the union?
type ex20a = IsAllowed2<string[] | number[]>;
type ex21a = IsAllowed2<string[] | string>;
type ex22a = IsAllowed2<(string | null | number)[]>;
type ex23a = IsAllowed2<Customer>; // this fails....FFS (of course it does!)
type CustomerBase = {
type: 'active' | 'inactive'
}
type ActiveCustomer = {
type: 'active';
name: string;
}
type InactiveCustomer = {
type: 'inactive';
name: string;
inactiveDate: Date;
}
type Customer = ActiveCustomer | InactiveCustomer;
Implemented first version of this with less strict types; closing.
We are currently limited to only creating flat tables with primitives, but allowing nested primitives and arrays would open up more opportunities.