import { ExpressionAstFunction } from './types' ; import { AnyExpressionFunctionDefinition , ExpressionFunctionDefinition , } from '../expression_functions/types' ; import { buildExpression , ExpressionAstExpressionBuilder , isExpressionAstBuilder , isExpressionAst , } from './build_expression' ; import { format } from './format' ; export type InferFunctionDefinition < FnDef extends AnyExpressionFunctionDefinition > = FnDef extends ExpressionFunctionDefinition < infer Name , infer Input , infer Arguments , infer Output , infer Context > ? { name : Name ; input : Input ; arguments : Arguments ; output : Output ; context : Context ; } : never ; type FunctionArgs < FnDef extends AnyExpressionFunctionDefinition > = InferFunctionDefinition < FnDef > [ 'arguments' ] ; type FunctionArgName < FnDef extends AnyExpressionFunctionDefinition > = { [ A in keyof FunctionArgs < FnDef > ] : A extends string ? A : never ; } [ keyof FunctionArgs < FnDef > ] ; type OptionalKeys < T > = { [ K in keyof T ] - ? : { } extends Pick < T , K > ? ( K extends string ? K : never ) : never ; } [ keyof T ] ; interface FunctionBuilderArguments < FnDef extends AnyExpressionFunctionDefinition > { [ key ] : Array < FunctionArgs < FnDef > [ string ] | ExpressionAstExpressionBuilder > ; } export interface ExpressionAstFunctionBuilder < FnDef extends AnyExpressionFunctionDefinition = AnyExpressionFunctionDefinition > { /**
     * Used to identify expression function builder objects.
     */ type : 'expression_function_builder' ; /**
     * Name of this expression function.
     */ name : InferFunctionDefinition < FnDef > [ 'name' ] ; /**
     * Object of all args currently added to the function. This is
     * structured similarly to `ExpressionAstFunction['arguments']`,
     * however any subexpressions are returned as expression builder
     * instances instead of expression ASTs.
     */ arguments : FunctionBuilderArguments < FnDef > ; param name param value return addArgument : < A extends FunctionArgName < FnDef > > ( name : A , value : FunctionArgs < FnDef > [ A ] | ExpressionAstExpressionBuilder ) => this ; param name return getArgument : < A extends FunctionArgName < FnDef > > ( name : A ) => Array < FunctionArgs < FnDef > [ A ] | ExpressionAstExpressionBuilder > | undefined ; param name param value return replaceArgument : < A extends FunctionArgName < FnDef > > ( name : A , value : Array < FunctionArgs < FnDef > [ A ] | ExpressionAstExpressionBuilder > ) => this ; param name return removeArgument : < A extends OptionalKeys < FunctionArgs < FnDef > > > ( name : A ) => this ; return toAst : ( ) => ExpressionAstFunction ; return toString : ( ) => string ; } param fnName param initialArgs return export function buildExpressionFunction < FnDef extends AnyExpressionFunctionDefinition = AnyExpressionFunctionDefinition > ( fnName , /**
 * To support subexpressions, we override all args to also accept an
 * ExpressionBuilder. This isn't perfectly typesafe since we don't
 * know with certainty that the builder's output matches the required
 * argument input, so we trust that folks using subexpressions in the
 * builder know what they're doing.
 */ initialArgs ) { const args = Object . entries ( initialArgs ) . reduce ( ( acc , [ key , value ] ) => { if ( Array . isArray ( value ) ) { acc [ key ] = value . map ( ( v ) => { return isExpressionAst ( v ) ? buildExpression ( v ) : v ; } ) ; } else if ( value !== undefined ) { acc [ key ] = isExpressionAst ( value ) ? [ buildExpression ( value ) ] : [ value ] ; } else { delete acc [ key ] ; } return acc ; } , initialArgs as FunctionBuilderArguments < FnDef > ) ; return { type : 'expression_function_builder' , name : fnName , arguments : args , addArgument ( key , value ) { if ( value !== undefined ) { if ( ! args . hasOwnProperty ( key ) ) { args [ key ] = [ ] ; } args [ key ] . push ( value ) ; } return this ; } , getArgument ( key ) { if ( ! args . hasOwnProperty ( key ) ) { return ; } return args [ key ] ; } , replaceArgument ( key , values ) { if ( ! args . hasOwnProperty ( key ) ) { throw new Error ( 'Argument to replace does not exist on this function' ) ; } args [ key ] = values ; return this ; } , removeArgument ( key ) { delete args [ key ] ; return this ; } , toAst ( ) { const ast = { } ; return { type : 'function' , function : fnName , arguments : Object . entries ( args ) . reduce ( ( acc , [ key , values ] ) => { acc [ key ] = values . map ( ( val ) => { return isExpressionAstBuilder ( val ) ? val . toAst ( ) : val ; } ) ; return acc ; } , ast ) , } ; } , toString ( ) { return format ( { type : 'expression' , chain : [ this . toAst ( ) ] } , 'expression' ) ; } , } ; }