import * as types from 'notion-types' ; import { getTextContent , getDateValue } from 'notion-utils' ; import { format , getDate , getDay , getHours , getMinutes , getMonth , getYear , add , sub , intervalToDuration } from 'date-fns' ; export interface EvalFormulaContext { properties : types . PropertyMap ; schema : types . CollectionPropertySchemaMap ; endDate ? : boolean ; } param formula param context export function evalFormula ( formula , context ) { const { endDate , ... ctx } = context ; switch ( formula ?. type ) { case 'symbol' : return formula . name === 'true' ; case 'constant' : { const value = formula . value ; switch ( formula . result_type ) { case 'text' : return value ; case 'number' : return parseFloat ( value ) ; default : return value ; } } case 'property' : { const value = ctx . properties [ formula . id ] ; const text = getTextContent ( value ) ; switch ( formula . result_type ) { case 'text' : return text ; case 'number' : return parseFloat ( text ) ; case 'boolean' : if ( typeof text === 'string' ) { return text === 'true' ; } else { return ! ! text ; } case 'date' : { const v = getDateValue ( value ) ; if ( v ) { if ( endDate && v . end_date ) { return new Date ( v . end_date ) ; } else { return new Date ( v . start_date ) ; } } else { return new Date ( text ) ; } } default : return text ; } } case 'operator' : case 'function' : return evalFunctionFormula ( formula , ctx ) ; default : throw new Error ( `invalid or unsupported formula "${ ( formula as any ) ?. type }` ) ; } } private function evalFunctionFormula ( formula , ctx ) { const args = formula ?. args ; switch ( formula . name ) { case 'and' : return evalFormula ( args [ 0 ] , ctx ) && evalFormula ( args [ 1 ] , ctx ) ; case 'empty' : return ! evalFormula ( args [ 0 ] , ctx ) ; case 'equal' : return evalFormula ( args [ 0 ] , ctx ) == evalFormula ( args [ 1 ] , ctx ) ; case 'if' : return evalFormula ( args [ 0 ] , ctx ) ? evalFormula ( args [ 1 ] , ctx ) : evalFormula ( args [ 2 ] , ctx ) ; case 'larger' : return evalFormula ( args [ 0 ] , ctx ) > evalFormula ( args [ 1 ] , ctx ) ; case 'largerEq' : return evalFormula ( args [ 0 ] , ctx ) >= evalFormula ( args [ 1 ] , ctx ) ; case 'not' : return ! evalFormula ( args [ 0 ] , ctx ) ; case 'or' : return evalFormula ( args [ 0 ] , ctx ) || evalFormula ( args [ 1 ] , ctx ) ; case 'smaller' : return evalFormula ( args [ 0 ] , ctx ) < evalFormula ( args [ 1 ] , ctx ) ; case 'smallerEq' : return evalFormula ( args [ 0 ] , ctx ) <= evalFormula ( args [ 1 ] , ctx ) ; case 'unequal' : return evalFormula ( args [ 0 ] , ctx ) != evalFormula ( args [ 1 ] , ctx ) ; case 'abs' : return Math . abs ( evalFormula ( args [ 0 ] , ctx ) as number ) ; case 'add' : { const v0 = evalFormula ( args [ 0 ] , ctx ) ; const v1 = evalFormula ( args [ 1 ] , ctx ) ; if ( typeof v0 === 'number' ) { return v0 + + v1 ; } else if ( typeof v0 === 'string' ) { return v0 + `${ v1 }` ; } else { return v0 ; } } case 'cbrt' : return Math . cbrt ( evalFormula ( args [ 0 ] , ctx ) as number ) ; case 'ceil' : return Math . ceil ( evalFormula ( args [ 0 ] , ctx ) as number ) ; case 'divide' : return ( ( evalFormula ( args [ 0 ] , ctx ) as number ) / ( evalFormula ( args [ 1 ] , ctx ) as number ) ) ; case 'exp' : return Math . exp ( evalFormula ( args [ 0 ] , ctx ) as number ) ; case 'floor' : return Math . floor ( evalFormula ( args [ 0 ] , ctx ) as number ) ; case 'ln' : return Math . log ( evalFormula ( args [ 0 ] , ctx ) as number ) ; case 'log10' : return Math . log10 ( evalFormula ( args [ 0 ] , ctx ) as number ) ; case 'log2' : return Math . log2 ( evalFormula ( args [ 0 ] , ctx ) as number ) ; case 'max' : { const values = args . map ( ( arg ) => evalFormula ( arg , ctx ) as number ) ; return values . reduce ( ( acc , value ) => Math . max ( acc , value ) , Number . NEGATIVE_INFINITY ) ; } case 'min' : { const values = args . map ( ( arg ) => evalFormula ( arg , ctx ) as number ) ; return values . reduce ( ( acc , value ) => Math . min ( acc , value ) , Number . POSITIVE_INFINITY ) ; } case 'mod' : return ( ( evalFormula ( args [ 0 ] , ctx ) as number ) % ( evalFormula ( args [ 1 ] , ctx ) as number ) ) ; case 'multiply' : return ( ( evalFormula ( args [ 0 ] , ctx ) as number ) * ( evalFormula ( args [ 1 ] , ctx ) as number ) ) ; case 'pow' : return Math . pow ( evalFormula ( args [ 0 ] , ctx ) as number , evalFormula ( args [ 1 ] , ctx ) as number ) ; case 'round' : return Math . round ( evalFormula ( args [ 0 ] , ctx ) as number ) ; case 'sign' : return Math . sign ( evalFormula ( args [ 0 ] , ctx ) as number ) ; case 'sqrt' : return Math . sqrt ( evalFormula ( args [ 0 ] , ctx ) as number ) ; case 'subtract' : return ( ( evalFormula ( args [ 0 ] , ctx ) as number ) - ( evalFormula ( args [ 1 ] , ctx ) as number ) ) ; case 'toNumber' : return parseFloat ( evalFormula ( args [ 0 ] , ctx ) as string ) ; case 'unaryMinus' : return ( evalFormula ( args [ 0 ] , ctx ) as number ) * - 1 ; case 'unaryPlus' : return parseFloat ( evalFormula ( args [ 0 ] , ctx ) as string ) ; case 'concat' : { const values = args . map ( ( arg ) => evalFormula ( arg , ctx ) as number ) ; return values . join ( '' ) ; } case 'contains' : return ( evalFormula ( args [ 0 ] , ctx ) as string ) . includes ( evalFormula ( args [ 1 ] , ctx ) as string ) ; case 'format' : { const value = evalFormula ( args [ 0 ] , ctx ) ; switch ( typeof value ) { case 'string' : return value ; case 'object' : if ( value instanceof Date ) { return format ( value as Date , 'MMM d, YYY' ) ; } else { return `${ value }` ; } case 'number' : default : return `${ value }` ; } } case 'join' : { const [ delimiterArg , ... restArgs ] = args ; const delimiter = evalFormula ( delimiterArg , ctx ) as string ; const values = restArgs . map ( ( arg ) => evalFormula ( arg , ctx ) as number ) ; return values . join ( delimiter ) ; } case 'length' : return ( evalFormula ( args [ 0 ] , ctx ) as string ) . length ; case 'replace' : { const value = evalFormula ( args [ 0 ] , ctx ) as string ; const regex = evalFormula ( args [ 1 ] , ctx ) as string ; const replacement = evalFormula ( args [ 2 ] , ctx ) as string ; return value . replace ( new RegExp ( regex ) , replacement ) ; } case 'replaceAll' : { const value = evalFormula ( args [ 0 ] , ctx ) as string ; const regex = evalFormula ( args [ 1 ] , ctx ) as string ; const replacement = evalFormula ( args [ 2 ] , ctx ) as string ; return value . replace ( new RegExp ( regex , 'g' ) , replacement ) ; } case 'slice' : { const value = evalFormula ( args [ 0 ] , ctx ) as string ; const beginIndex = evalFormula ( args [ 1 ] , ctx ) as number ; const endIndex = args [ 2 ] ? ( evalFormula ( args [ 2 ] , ctx ) as number ) : value . length ; return value . slice ( beginIndex , endIndex ) ; } case 'test' : { const value = evalFormula ( args [ 0 ] , ctx ) as string ; const regex = evalFormula ( args [ 1 ] , ctx ) as string ; return new RegExp ( regex ) . test ( value ) ; } case 'date' : return getDate ( evalFormula ( args [ 0 ] , ctx ) as Date ) ; case 'dateAdd' : { const date = evalFormula ( args [ 0 ] , ctx ) as Date ; const number = evalFormula ( args [ 1 ] , ctx ) as number ; const unit = evalFormula ( args [ 1 ] , ctx ) as string ; return add ( date , { [ unit ] : number } ) ; } case 'dateBetween' : { const date1 = evalFormula ( args [ 0 ] , ctx ) as Date ; const date2 = evalFormula ( args [ 1 ] , ctx ) as Date ; const unit = evalFormula ( args [ 1 ] , ctx ) as string ; return ( intervalToDuration ( { start : date2 , end : date1 } ) as any ) [ unit ] as number ; } case 'dateSubtract' : { const date = evalFormula ( args [ 0 ] , ctx ) as Date ; const number = evalFormula ( args [ 1 ] , ctx ) as number ; const unit = evalFormula ( args [ 1 ] , ctx ) as string ; return sub ( date , { [ unit ] : number } ) ; } case 'day' : return getDay ( evalFormula ( args [ 0 ] , ctx ) as Date ) ; case 'end' : return evalFormula ( args [ 0 ] , { ... ctx , endDate : true } ) as Date ; case 'formatDate' : { const date = evalFormula ( args [ 0 ] , ctx ) as Date ; const formatValue = ( evalFormula ( args [ 1 ] , ctx ) as string ) . replace ( 'dddd' , 'eeee' ) ; return format ( date , formatValue ) ; } case 'fromTimestamp' : return new Date ( evalFormula ( args [ 0 ] , ctx ) as number ) ; case 'hour' : return getHours ( evalFormula ( args [ 0 ] , ctx ) as Date ) ; case 'minute' : return getMinutes ( evalFormula ( args [ 0 ] , ctx ) as Date ) ; case 'month' : return getMonth ( evalFormula ( args [ 0 ] , ctx ) as Date ) ; case 'now' : return new Date ( ) ; case 'start' : return evalFormula ( args [ 0 ] , { ... ctx , endDate : false } ) as Date ; case 'timestamp' : return ( evalFormula ( args [ 0 ] , ctx ) as Date ) . getTime ( ) ; case 'year' : return getYear ( evalFormula ( args [ 0 ] , ctx ) as Date ) ; default : throw new Error ( `invalid or unsupported function formula "${ ( formula as any ) ?. type }` ) ; } }