UNPKG

hypertune

Version:

[Hypertune](https://www.hypertune.com/) is the most flexible platform for feature flags, A/B testing, analytics and app configuration. Built with full end-to-end type-safety, Git-style version control and local, synchronous, in-memory flag evaluation. Opt

111 lines (95 loc) 3.35 kB
import { breakingSchemaChangesError, graphqlTypeNameKey } from "../constants"; import { Expression, Logs, Value } from "../types"; import nullThrows from "./nullThrows"; import { getExpressionEvaluationCountLogs, mergeLogs } from "./reductionLogs"; export const complexFormExpressionEvaluationError = `After evaluating your expression, the result was still in a complex form. ${breakingSchemaChangesError}`; /** * Expression evaluation takes a fully reduced expression in "normal form" and * converts it to a JSON value. It returns this value with "logs" that track * all of the expressions we've needed to evaluate to compute this final value. */ // TODO: Pass in Expr<Expr> instead of Expr<Expr | null> to avoid null checks? export default function evaluate( expression: Expression // Must be a fully reduced expression ): { value: Value; logs: Logs; shouldLogEvaluation: boolean; } { const thisLogs = mergeLogs( expression.logs, getExpressionEvaluationCountLogs(expression) ); switch (expression.type) { case "NoOpExpression": return { value: true, logs: thisLogs, shouldLogEvaluation: false }; case "BooleanExpression": case "IntExpression": case "FloatExpression": case "StringExpression": case "RegexExpression": case "EnumExpression": return { value: expression.value, logs: thisLogs, shouldLogEvaluation: true, }; case "ObjectExpression": { const fieldEvaluations = Object.fromEntries( Object.keys(expression.fields).map((fieldName) => [ fieldName, evaluate( nullThrows(expression.fields[fieldName], "null object field") ), ]) ); const value: Value = Object.fromEntries([ [graphqlTypeNameKey, expression.objectTypeName], ...Object.entries(fieldEvaluations).map(([fieldName, evaluation]) => [ fieldName, evaluation.value, ]), ]); const logs = mergeLogs( thisLogs, ...Object.values(fieldEvaluations).map((evaluation) => evaluation.logs) ); return { value, logs, shouldLogEvaluation: true }; } case "ListExpression": { const itemEvaluations = expression.items.map((item) => evaluate(nullThrows(item, "null list item")) ); const value: Value = itemEvaluations.map( (evaluation) => evaluation.value ); const logs = mergeLogs( thisLogs, ...itemEvaluations.map((evaluation) => evaluation.logs) ); return { value, logs, shouldLogEvaluation: true }; } case "GetFieldExpression": case "UpdateObjectExpression": case "SwitchExpression": case "EnumSwitchExpression": case "ComparisonExpression": case "ArithmeticExpression": case "RoundNumberExpression": case "StringifyNumberExpression": case "StringConcatExpression": case "GetUrlQueryParameterExpression": case "SplitExpression": case "LogEventExpression": case "FunctionExpression": case "VariableExpression": case "ApplicationExpression": throw new Error(complexFormExpressionEvaluationError); default: { const neverExpression: never = expression; throw new Error( `unexpected expression: ${JSON.stringify(neverExpression)}` ); } } }