Skip to content

Instantly share code, notes, and snippets.

@filmaj
Last active November 10, 2022 19:07
Show Gist options
  • Select an option

  • Save filmaj/839e9124e2752b0a636a01a43e58ab35 to your computer and use it in GitHub Desktop.

Select an option

Save filmaj/839e9124e2752b0a636a01a43e58ab35 to your computer and use it in GitHub Desktop.

Revisions

  1. Filip Maj revised this gist Nov 10, 2022. 1 changed file with 94 additions and 0 deletions.
    94 changes: 94 additions & 0 deletions definition.test.ts
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,94 @@
    // Some user-defined parameters based on primitive types
    type BaseParamDefinition<N, T> = { type: N; default: T };
    type BooleanParamDefinition = BaseParamDefinition<"boolean", boolean>;
    type StringParamDefinition = BaseParamDefinition<"string", string>;
    type NumberParamDefinition = BaseParamDefinition<"number",number>;
    // A more complex parameter type: an object, with inputs, and an array that specifies which of the inputs are required.
    type ObjectParamDefinition<
    Inputs extends ParameterSetDefinition<PrimitiveParameterDefinition>,
    RequiredInputs extends RequiredProperties<Inputs>,
    > = {
    type: "object";
    input_parameters: InputDefinition<Inputs, RequiredInputs>;
    };

    type PrimitiveParameterDefinition =
    | BooleanParamDefinition
    | StringParamDefinition
    | NumberParamDefinition;
    type ParameterDefinition = PrimitiveParameterDefinition | ObjectParamDefinition<ParameterSetDefinition<PrimitiveParameterDefinition>, RequiredProperties<ParameterSetDefinition<PrimitiveParameterDefinition>>>;

    // Used to define inputs at both function level and function object parameter sub-level
    type ParameterSetDefinition<
    ParamDefn extends ParameterDefinition,
    > = { [key: string]: ParamDefn };

    // Used in conjunction with ParameterSetDefinition: returns an array of string literals derived from the keys of the parameter set.
    type RequiredProperties<PropSet extends ParameterSetDefinition<ParameterDefinition>> =
    (keyof PropSet)[];

    type InputDefinition<
    Properties extends ParameterSetDefinition<ParameterDefinition>,
    Required extends RequiredProperties<Properties>,
    > = {
    required: Required;
    properties: Properties;
    };

    type FunctionDefinitionArgs<
    Inputs extends ParameterSetDefinition<ParameterDefinition>,
    RequiredInputs extends RequiredProperties<Inputs>,
    > = {
    title: string;
    input_parameters: InputDefinition<Inputs, RequiredInputs>;
    };

    const DefineFunction = <
    Inputs extends ParameterSetDefinition<ParameterDefinition>,
    RequiredInputs extends RequiredProperties<Inputs>,
    >(
    definition: FunctionDefinitionArgs<Inputs, RequiredInputs>,
    ) => {
    return new FunctionDefinition(definition);
    };
    class FunctionDefinition<
    Inputs extends ParameterSetDefinition<ParameterDefinition>,
    RequiredInputs extends RequiredProperties<Inputs>,
    > {
    constructor(
    public definition: FunctionDefinitionArgs<
    Inputs,
    RequiredInputs
    >,
    ) {
    this.definition = definition;
    }
    }

    const myfunc = DefineFunction({
    title: "hihi",
    input_parameters: {
    properties: {
    "a_string": {
    type: "string",
    default: "STRING!",
    },
    "an_optional_string": {
    type: "string",
    default: "...string",
    },
    "an_object": {
    type: "object",
    input_parameters: {
    properties: {
    "object_string": { type: "string", default: "[string]" },
    "optional_object_string": { type: "string", default: "string?" },
    },
    required: ["object_string", "whatever"] // <-- does not work! I expect TS to complain about 'whatever'
    }
    },
    },
    required: ["a_string", "an_object"] // works, both elements reference existing keys of the top-level `properties`
    // required: ["a_string", "an_object", "hi"] // also works! "hi" is not listed under `properties` so TS complains, as expected
    },
    });
  2. Filip Maj revised this gist Nov 10, 2022. 1 changed file with 21 additions and 25 deletions.
    46 changes: 21 additions & 25 deletions test.ts
    Original file line number Diff line number Diff line change
    @@ -21,8 +21,10 @@ type ParameterDefinition = PrimitiveParameterDefinition | ObjectParamDefinition;
    type ParameterSetDefinition<
    ParamDefn extends ParameterDefinition = ParameterDefinition,
    > = { [key: string]: ParamDefn };
    // NOTE: RequiredProperties AKA PossibleParameterKeys
    type RequiredProperties<PropSet extends ParameterSetDefinition> =
    (keyof PropSet)[];
    // NOTE: InputDefinition AKA ParameterPropertiesDefinition
    type InputDefinition<
    Properties extends ParameterSetDefinition,
    Required extends RequiredProperties<Properties>,
    @@ -91,45 +93,39 @@ type FunctionInputRuntimeType<
    Param extends ParameterDefinition,
    CurrentDepth extends RecursionDepthLevel = 0,
    > =
    // Recurse through Custom Types, stop when we hit our max depth
    CurrentDepth extends MaxRecursionDepth ? "maxrecursiondepth"
    //: Param extends CustomTypeParameterDefinition ? FunctionInputRuntimeType<
    CurrentDepth extends MaxRecursionDepth ? "debug:maxrecursiondepth"
    // TODO: Custom Type parameter support
    // : Param extends CustomTypeParameterDefinition ? FunctionInputRuntimeType<
    //Param["type"]["definition"],
    //IncreaseDepth<CurrentDepth>
    //>
    // Not a Custom Type, so assign the runtime value
    : Param["type"] extends "string" ? string
    : Param["type"] extends "number" ? number
    : Param["type"] extends "boolean" ? boolean
    //: Param["type"] extends typeof SchemaTypes.array
    // TODO: Array Type parameter support
    // : Param["type"] extends typeof SchemaTypes.array
    //? Param extends TypedArrayParameterDefinition
    //? TypedArrayFunctionInputRuntimeType<Param>
    //: any[]
    : Param["type"] extends "object"
    ? Param extends ObjectParamDefinition
    ? TypedObjectFunctionInputRuntimeType<Param>
    : "object-type-that-does-not-extend-ObjectParamDefinition"
    //: Param["type"] extends
    //| typeof SlackSchemaTypes.user_id
    //| typeof SlackSchemaTypes.usergroup_id
    //| typeof SlackSchemaTypes.channel_id
    //| typeof SlackSchemaTypes.date
    //| typeof SlackSchemaTypes.message_ts ? string
    //: Param["type"] extends typeof SlackSchemaTypes.timestamp ? number
    //: Param["type"] extends typeof SlackSchemaTypes.rich_text
    //? any
    : "ded";
    : "debug:object-type-that-does-not-extend-ObjectParamDefinition"
    : "debug:ded";

    type TypedObjectFunctionInputRuntimeType<Param extends ObjectParamDefinition> =
    Param["required"] extends RequiredProperties<Param["properties"]>
    ? { rainbows: boolean }
    : { poo: boolean };
    Param["required"] extends string[]// TODO: checking if Param[required] extends from RequiredProperties<Param["properties"]> doesn't seem to work here?
    ? RuntimeParameters<Param["properties"], Param["required"]>
    : { "debug:poo": boolean };

    type RuntimeParameters<
    Props extends ParameterSetDefinition,
    Req extends RequiredProperties<Props>,
    > =
    // TODO (h/t mkantor): the below commented out line doesn't work since we have to 'index' into the specific type for the particular input. It could be a StringParam, BoolParam, or more complex types like ObjectParam, etc.
    //Record<Req[number], FunctionInputRuntimeType<??wat?? Props[what?]>> & Partial<Props>;
    & {
    [k in Req[number]]: FunctionInputRuntimeType<
    [k in Req[number]]: FunctionInputRuntimeType< // TODO: <-- this "k in Req" iterator seems to work fine for a Function's input_parameters.properties - but not for an object input's properties.
    Props[k]
    >;
    }
    @@ -138,8 +134,6 @@ type RuntimeParameters<
    Props[k]
    >;
    };
    // TODO: the below doesn't work since we have to 'index' into the specific type for the particular input. It could be a StringParam, BoolParam, etc.
    //Record<Req[number], FunctionInputRuntimeType<??wat?? Props[what?]>> & Partial<Props>;

    const myfunc = DefineFunction({
    title: "hihi",
    @@ -166,10 +160,12 @@ const myfunc = DefineFunction({
    },
    });

    const handler: FunctionHandler<typeof myfunc.definition> = (context) => {
    const _handler: FunctionHandler<typeof myfunc.definition> = (context) => {
    context.inputs;
    context.inputs.a_string;
    context.inputs.an_optional_string;
    context.inputs.a_string; // this should be string
    context.inputs.an_optional_string; // this should be string | undefined
    context.inputs.an_object;
    context.inputs.an_object?.object_string; // this should be string - but it's not! it is string | undefined
    context.inputs.an_object?.optional_object_string; // this should be string | undefined
    return {};
    };
  3. Filip Maj revised this gist Nov 10, 2022. 1 changed file with 17 additions and 7 deletions.
    24 changes: 17 additions & 7 deletions test.ts
    Original file line number Diff line number Diff line change
    @@ -1,12 +1,12 @@
    // Some user-defined parameters based on primitive types
    type BaseParamDefinition<T> = { type: string; default: T };
    type BooleanParamDefinition = BaseParamDefinition<boolean>;
    type StringParamDefinition = BaseParamDefinition<string>;
    type NumberParamDefinition = BaseParamDefinition<number>;
    type BaseParamDefinition<N, T> = { type: N; default: T };
    type BooleanParamDefinition = BaseParamDefinition<"boolean", boolean>;
    type StringParamDefinition = BaseParamDefinition<"string", string>;
    type NumberParamDefinition = BaseParamDefinition<"number",number>;
    type AllPrimitiveValues = string | number | boolean;
    type ObjectValue = Record<string, AllPrimitiveValues>;
    type ObjectParamDefinition =
    & BaseParamDefinition<ObjectValue>
    & Omit<BaseParamDefinition<"object", ObjectValue>, "default">
    & InputDefinition<
    ParameterSetDefinition<PrimitiveParameterDefinition>,
    RequiredProperties<ParameterSetDefinition<PrimitiveParameterDefinition>>
    @@ -17,7 +17,7 @@ type PrimitiveParameterDefinition =
    | NumberParamDefinition;
    type ParameterDefinition = PrimitiveParameterDefinition | ObjectParamDefinition;

    // maybe useful change: make the following take a generic, to differentiate between function inputs and object type input properties
    // maybe useful change: make ParameterSetDefinition take a generic, so that it can be reused for both function input properties and object type input properties
    type ParameterSetDefinition<
    ParamDefn extends ParameterDefinition = ParameterDefinition,
    > = { [key: string]: ParamDefn };
    @@ -61,6 +61,7 @@ class FunctionDefinition<
    }
    }

    // Is FunctionParameters needed? Most of the time it is constrained further to RuntimeParameters<I, RI>
    type FunctionParameters = Record<string, any> | undefined;
    type FunctionContext<In extends FunctionParameters> = {
    inputs: In;
    @@ -152,14 +153,23 @@ const myfunc = DefineFunction({
    type: "string",
    default: "...string",
    },
    "an_object": {
    type: "object",
    properties: {
    "object_string": { type: "string", default: "[string]" },
    "optional_object_string": { type: "string", default: "string?" },
    },
    required: ["object_string"]
    },
    },
    required: ["a_string"],
    required: ["a_string"]
    },
    });

    const handler: FunctionHandler<typeof myfunc.definition> = (context) => {
    context.inputs;
    context.inputs.a_string;
    context.inputs.an_optional_string;
    context.inputs.an_object;
    return {};
    };
  4. Filip Maj created this gist Nov 9, 2022.
    165 changes: 165 additions & 0 deletions test.ts
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,165 @@
    // Some user-defined parameters based on primitive types
    type BaseParamDefinition<T> = { type: string; default: T };
    type BooleanParamDefinition = BaseParamDefinition<boolean>;
    type StringParamDefinition = BaseParamDefinition<string>;
    type NumberParamDefinition = BaseParamDefinition<number>;
    type AllPrimitiveValues = string | number | boolean;
    type ObjectValue = Record<string, AllPrimitiveValues>;
    type ObjectParamDefinition =
    & BaseParamDefinition<ObjectValue>
    & InputDefinition<
    ParameterSetDefinition<PrimitiveParameterDefinition>,
    RequiredProperties<ParameterSetDefinition<PrimitiveParameterDefinition>>
    >;
    type PrimitiveParameterDefinition =
    | BooleanParamDefinition
    | StringParamDefinition
    | NumberParamDefinition;
    type ParameterDefinition = PrimitiveParameterDefinition | ObjectParamDefinition;

    // maybe useful change: make the following take a generic, to differentiate between function inputs and object type input properties
    type ParameterSetDefinition<
    ParamDefn extends ParameterDefinition = ParameterDefinition,
    > = { [key: string]: ParamDefn };
    type RequiredProperties<PropSet extends ParameterSetDefinition> =
    (keyof PropSet)[];
    type InputDefinition<
    Properties extends ParameterSetDefinition,
    Required extends RequiredProperties<Properties>,
    > = {
    required: Required;
    properties: Properties;
    };

    type FunctionDefinitionArgs<
    Inputs extends ParameterSetDefinition,
    RequiredInputs extends RequiredProperties<Inputs>,
    > = {
    title: string;
    input_parameters: InputDefinition<Inputs, RequiredInputs>;
    };

    const DefineFunction = <
    Inputs extends ParameterSetDefinition,
    RequiredInputs extends RequiredProperties<Inputs>,
    >(
    definition: FunctionDefinitionArgs<Inputs, RequiredInputs>,
    ) => {
    return new FunctionDefinition(definition);
    };
    class FunctionDefinition<
    Inputs extends ParameterSetDefinition,
    RequiredInputs extends RequiredProperties<Inputs>,
    > {
    constructor(
    public definition: FunctionDefinitionArgs<
    Inputs,
    RequiredInputs
    >,
    ) {
    this.definition = definition;
    }
    }

    type FunctionParameters = Record<string, any> | undefined;
    type FunctionContext<In extends FunctionParameters> = {
    inputs: In;
    };
    type BaseHandler<
    In extends FunctionParameters,
    Context extends FunctionContext<In>,
    > = {
    (
    context: Context,
    ): FunctionParameters;
    };
    type FunctionHandler<Definition> = Definition extends
    FunctionDefinitionArgs<infer I, infer RI> ? BaseHandler<
    RuntimeParameters<I, RI>,
    FunctionContext<RuntimeParameters<I, RI>>
    >
    : never;

    /** @description Defines accepted depth values */
    type RecursionDepthLevel = 0 | 1 | 2 | 3 | 4 | 5;

    /** @description Defines the max depth we want to recurse */
    type MaxRecursionDepth = 5;

    type FunctionInputRuntimeType<
    Param extends ParameterDefinition,
    CurrentDepth extends RecursionDepthLevel = 0,
    > =
    // Recurse through Custom Types, stop when we hit our max depth
    CurrentDepth extends MaxRecursionDepth ? "maxrecursiondepth"
    //: Param extends CustomTypeParameterDefinition ? FunctionInputRuntimeType<
    //Param["type"]["definition"],
    //IncreaseDepth<CurrentDepth>
    //>
    // Not a Custom Type, so assign the runtime value
    : Param["type"] extends "string" ? string
    : Param["type"] extends "number" ? number
    : Param["type"] extends "boolean" ? boolean
    //: Param["type"] extends typeof SchemaTypes.array
    //? Param extends TypedArrayParameterDefinition
    //? TypedArrayFunctionInputRuntimeType<Param>
    //: any[]
    : Param["type"] extends "object"
    ? Param extends ObjectParamDefinition
    ? TypedObjectFunctionInputRuntimeType<Param>
    : "object-type-that-does-not-extend-ObjectParamDefinition"
    //: Param["type"] extends
    //| typeof SlackSchemaTypes.user_id
    //| typeof SlackSchemaTypes.usergroup_id
    //| typeof SlackSchemaTypes.channel_id
    //| typeof SlackSchemaTypes.date
    //| typeof SlackSchemaTypes.message_ts ? string
    //: Param["type"] extends typeof SlackSchemaTypes.timestamp ? number
    //: Param["type"] extends typeof SlackSchemaTypes.rich_text
    //? any
    : "ded";

    type TypedObjectFunctionInputRuntimeType<Param extends ObjectParamDefinition> =
    Param["required"] extends RequiredProperties<Param["properties"]>
    ? { rainbows: boolean }
    : { poo: boolean };
    type RuntimeParameters<
    Props extends ParameterSetDefinition,
    Req extends RequiredProperties<Props>,
    > =
    & {
    [k in Req[number]]: FunctionInputRuntimeType<
    Props[k]
    >;
    }
    & {
    [k in keyof Props]?: FunctionInputRuntimeType<
    Props[k]
    >;
    };
    // TODO: the below doesn't work since we have to 'index' into the specific type for the particular input. It could be a StringParam, BoolParam, etc.
    //Record<Req[number], FunctionInputRuntimeType<??wat?? Props[what?]>> & Partial<Props>;

    const myfunc = DefineFunction({
    title: "hihi",
    input_parameters: {
    properties: {
    "a_string": {
    type: "string",
    default: "STRING!",
    },
    "an_optional_string": {
    type: "string",
    default: "...string",
    },
    },
    required: ["a_string"],
    },
    });

    const handler: FunctionHandler<typeof myfunc.definition> = (context) => {
    context.inputs;
    context.inputs.a_string;
    context.inputs.an_optional_string;
    return {};
    };