datahookinc / trigger

An opinionated React state management system based on data-oriented design principles
0 stars 0 forks source link

Add ability to store nested record types #93

Closed datahookinc closed 3 days ago

datahookinc commented 4 weeks ago

We are currently limited to only creating flat tables with primitives, but allowing nested primitives and arrays would open up more opportunities.

datahookinc commented 3 weeks 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;
datahookinc commented 3 days ago

Implemented first version of this with less strict types; closing.