Last active
November 10, 2022 19:07
-
-
Save filmaj/839e9124e2752b0a636a01a43e58ab35 to your computer and use it in GitHub Desktop.
TypeScript ftw
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// 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 | |
}, | |
}); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// 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>; | |
type AllPrimitiveValues = string | number | boolean; | |
type ObjectValue = Record<string, AllPrimitiveValues>; | |
type ObjectParamDefinition = | |
& Omit<BaseParamDefinition<"object", ObjectValue>, "default"> | |
& InputDefinition< | |
ParameterSetDefinition<PrimitiveParameterDefinition>, | |
RequiredProperties<ParameterSetDefinition<PrimitiveParameterDefinition>> | |
>; | |
type PrimitiveParameterDefinition = | |
| BooleanParamDefinition | |
| StringParamDefinition | |
| NumberParamDefinition; | |
type ParameterDefinition = PrimitiveParameterDefinition | ObjectParamDefinition; | |
// 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 }; | |
// NOTE: RequiredProperties AKA PossibleParameterKeys | |
type RequiredProperties<PropSet extends ParameterSetDefinition> = | |
(keyof PropSet)[]; | |
// NOTE: InputDefinition AKA ParameterPropertiesDefinition | |
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; | |
} | |
} | |
// 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; | |
}; | |
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, | |
> = | |
CurrentDepth extends MaxRecursionDepth ? "debug:maxrecursiondepth" | |
// TODO: Custom Type parameter support | |
// : Param extends CustomTypeParameterDefinition ? FunctionInputRuntimeType< | |
//Param["type"]["definition"], | |
//IncreaseDepth<CurrentDepth> | |
//> | |
: Param["type"] extends "string" ? string | |
: Param["type"] extends "number" ? number | |
: Param["type"] extends "boolean" ? boolean | |
// 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> | |
: "debug:object-type-that-does-not-extend-ObjectParamDefinition" | |
: "debug:ded"; | |
type TypedObjectFunctionInputRuntimeType<Param extends ObjectParamDefinition> = | |
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< // 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] | |
>; | |
} | |
& { | |
[k in keyof Props]?: FunctionInputRuntimeType< | |
Props[k] | |
>; | |
}; | |
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", | |
properties: { | |
"object_string": { type: "string", default: "[string]" }, | |
"optional_object_string": { type: "string", default: "string?" }, | |
}, | |
required: ["object_string"] | |
}, | |
}, | |
required: ["a_string"] | |
}, | |
}); | |
const _handler: FunctionHandler<typeof myfunc.definition> = (context) => { | |
context.inputs; | |
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 {}; | |
}; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment