Skip to content

Instantly share code, notes, and snippets.

@filmaj
Last active November 10, 2022 19:07
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save filmaj/839e9124e2752b0a636a01a43e58ab35 to your computer and use it in GitHub Desktop.
Save filmaj/839e9124e2752b0a636a01a43e58ab35 to your computer and use it in GitHub Desktop.
TypeScript ftw
// 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
},
});
// 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