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
238 lines • 14.4 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.default = fold;
exports.mapExpressionWithResult = mapExpressionWithResult;
exports.mapExpression = mapExpression;
// Expensive
function fold(f, expression) {
if (!expression) {
return f(expression);
}
switch (expression.type) {
case "NoOpExpression":
case "BooleanExpression":
case "IntExpression":
case "FloatExpression":
case "StringExpression":
case "RegexExpression":
case "EnumExpression":
return f(expression);
case "ObjectExpression":
return f(Object.assign(Object.assign({}, expression), { fields: Object.fromEntries(Object.entries(expression.fields).map(([fieldName, field]) => [
fieldName,
fold(f, field),
])) }));
case "GetFieldExpression":
return f(Object.assign(Object.assign({}, expression), { object: fold(f, expression.object) }));
case "UpdateObjectExpression":
return f(Object.assign(Object.assign({}, expression), { object: fold(f, expression.object), updates: Object.fromEntries(Object.entries(expression.updates).map(([fieldName, field]) => [
fieldName,
fold(f, field),
])) }));
case "ListExpression":
return f(Object.assign(Object.assign({}, expression), { items: expression.items.map((item) => fold(f, item)) }));
case "SwitchExpression":
return f(Object.assign(Object.assign({}, expression), { control: fold(f, expression.control), cases: expression.cases.map((item) => ({
id: item.id,
when: fold(f, item.when),
then: fold(f, item.then),
})), default: fold(f, expression.default) }));
case "EnumSwitchExpression":
return f(Object.assign(Object.assign({}, expression), { control: fold(f, expression.control), cases: Object.fromEntries(Object.entries(expression.cases).map(([enumValue, caseExpression]) => [
enumValue,
fold(f, caseExpression),
])) }));
case "ComparisonExpression":
case "ArithmeticExpression":
return f(Object.assign(Object.assign({}, expression), { a: fold(f, expression.a), b: fold(f, expression.b) }));
case "RoundNumberExpression":
case "StringifyNumberExpression":
return f(Object.assign(Object.assign({}, expression), { number: fold(f, expression.number) }));
case "StringConcatExpression":
return f(Object.assign(Object.assign({}, expression), { strings: fold(f, expression.strings) }));
case "GetUrlQueryParameterExpression":
return f(Object.assign(Object.assign({}, expression), { url: fold(f, expression.url), queryParameterName: fold(f, expression.queryParameterName) }));
case "SplitExpression":
return f(Object.assign(Object.assign({}, expression), { expose: fold(f, expression.expose), unitId: fold(f, expression.unitId), dimensionMapping: expression.dimensionMapping.type === "discrete"
? {
type: "discrete",
cases: Object.fromEntries(Object.entries(expression.dimensionMapping.cases).map(([armId, caseExpression]) => [
armId,
fold(f, caseExpression),
])),
}
: {
type: "continuous",
function: fold(f, expression.dimensionMapping.function),
}, eventObjectTypeName: expression.eventObjectTypeName, eventPayload: fold(f, expression.eventPayload) }));
case "LogEventExpression":
return f(Object.assign(Object.assign({}, expression), { unitId: fold(f, expression.unitId), eventPayload: fold(f, expression.eventPayload) }));
case "FunctionExpression":
return f(Object.assign(Object.assign({}, expression), { body: fold(f, expression.body) }));
case "VariableExpression":
return f(expression);
case "ApplicationExpression":
return f(Object.assign(Object.assign({}, expression), { function: fold(f, expression.function), arguments: expression.arguments.map((argument) => fold(f, argument)) }));
default: {
const neverExpression = expression;
throw new Error(`Unexpected expression: ${JSON.stringify(neverExpression)}`);
}
}
}
// Applies f to children, constructs new expression, applies f to it and returns
// it along with merged map results
// Expensive
function mapExpressionWithResult(fn, combineResults, expression) {
// eslint-disable-next-line func-style
const foldFunction = (partialResult) => {
if (!partialResult) {
return fn(partialResult);
}
switch (partialResult.type) {
case "NoOpExpression":
case "BooleanExpression":
case "IntExpression":
case "FloatExpression":
case "StringExpression":
case "RegexExpression":
case "EnumExpression":
return fn(partialResult);
case "ObjectExpression": {
const thisResult = fn(Object.assign(Object.assign({}, partialResult), { fields: Object.fromEntries(Object.entries(partialResult.fields).map(([fieldName, fieldResult]) => [
fieldName,
fieldResult.newExpression,
])) }));
return {
newExpression: thisResult.newExpression,
mapResult: combineResults(thisResult.mapResult, ...Object.values(partialResult.fields).map((fieldResult) => fieldResult.mapResult)),
};
}
case "GetFieldExpression": {
const thisResult = fn(Object.assign(Object.assign({}, partialResult), { object: partialResult.object.newExpression }));
return {
newExpression: thisResult.newExpression,
mapResult: combineResults(thisResult.mapResult, partialResult.object.mapResult),
};
}
case "UpdateObjectExpression": {
const thisResult = fn(Object.assign(Object.assign({}, partialResult), { object: partialResult.object.newExpression, updates: Object.fromEntries(Object.entries(partialResult.updates).map(([fieldName, updateResult]) => [
fieldName,
updateResult.newExpression,
])) }));
return {
newExpression: thisResult.newExpression,
mapResult: combineResults(thisResult.mapResult, partialResult.object.mapResult, ...Object.values(partialResult.updates).map((updateResult) => updateResult.mapResult)),
};
}
case "ListExpression": {
const thisResult = fn(Object.assign(Object.assign({}, partialResult), { items: partialResult.items.map((itemResult) => itemResult.newExpression) }));
return {
newExpression: thisResult.newExpression,
mapResult: combineResults(thisResult.mapResult, ...partialResult.items.map((itemResult) => itemResult.mapResult)),
};
}
case "SwitchExpression": {
const thisResult = fn(Object.assign(Object.assign({}, partialResult), { control: partialResult.control.newExpression, cases: partialResult.cases.map((caseResult) => ({
id: caseResult.id,
when: caseResult.when.newExpression,
then: caseResult.then.newExpression,
})), default: partialResult.default.newExpression }));
return {
newExpression: thisResult.newExpression,
mapResult: combineResults(thisResult.mapResult, partialResult.control.mapResult, ...partialResult.cases.map((caseResult) => combineResults(caseResult.when.mapResult, caseResult.then.mapResult)), partialResult.default.mapResult),
};
}
case "EnumSwitchExpression": {
const thisResult = fn(Object.assign(Object.assign({}, partialResult), { control: partialResult.control.newExpression, cases: Object.fromEntries(Object.entries(partialResult.cases).map(([enumValue, caseResult]) => [enumValue, caseResult.newExpression])) }));
return {
newExpression: thisResult.newExpression,
mapResult: combineResults(thisResult.mapResult, partialResult.control.mapResult, ...Object.values(partialResult.cases).map((caseResult) => caseResult.mapResult)),
};
}
case "ComparisonExpression":
case "ArithmeticExpression": {
const thisResult = fn(Object.assign(Object.assign({}, partialResult), { a: partialResult.a.newExpression, b: partialResult.b.newExpression }));
return {
newExpression: thisResult.newExpression,
mapResult: combineResults(thisResult.mapResult, partialResult.a.mapResult, partialResult.b.mapResult),
};
}
case "RoundNumberExpression":
case "StringifyNumberExpression": {
const thisResult = fn(Object.assign(Object.assign({}, partialResult), { number: partialResult.number.newExpression }));
return {
newExpression: thisResult.newExpression,
mapResult: combineResults(thisResult.mapResult, partialResult.number.mapResult),
};
}
case "StringConcatExpression": {
const thisResult = fn(Object.assign(Object.assign({}, partialResult), { strings: partialResult.strings.newExpression }));
return {
newExpression: thisResult.newExpression,
mapResult: combineResults(thisResult.mapResult, partialResult.strings.mapResult),
};
}
case "GetUrlQueryParameterExpression": {
const thisResult = fn(Object.assign(Object.assign({}, partialResult), { url: partialResult.url.newExpression, queryParameterName: partialResult.queryParameterName.newExpression }));
return {
newExpression: thisResult.newExpression,
mapResult: combineResults(thisResult.mapResult, partialResult.url.mapResult, partialResult.queryParameterName.mapResult),
};
}
case "SplitExpression": {
const thisResult = fn(Object.assign(Object.assign({}, partialResult), { expose: partialResult.expose.newExpression, unitId: partialResult.unitId.newExpression, dimensionMapping: partialResult.dimensionMapping.type === "discrete"
? {
type: "discrete",
cases: Object.fromEntries(Object.entries(partialResult.dimensionMapping.cases).map(([armId, caseResult]) => [armId, caseResult.newExpression])),
}
: {
type: "continuous",
function: partialResult.dimensionMapping.function.newExpression,
}, eventObjectTypeName: partialResult.eventObjectTypeName, eventPayload: partialResult.eventPayload.newExpression, featuresMapping: {} }));
return {
newExpression: thisResult.newExpression,
mapResult: combineResults(thisResult.mapResult, partialResult.expose.mapResult, partialResult.unitId.mapResult, partialResult.eventPayload.mapResult, ...(partialResult.dimensionMapping.type === "discrete"
? Object.values(partialResult.dimensionMapping.cases).map((caseMapResult) => caseMapResult.mapResult)
: [partialResult.dimensionMapping.function.mapResult])),
};
}
case "LogEventExpression": {
const thisResult = fn(Object.assign(Object.assign({}, partialResult), { eventObjectTypeName: partialResult.eventObjectTypeName, eventPayload: partialResult.eventPayload.newExpression, unitId: partialResult.unitId.newExpression, featuresMapping: {} }));
return {
newExpression: thisResult.newExpression,
mapResult: combineResults(thisResult.mapResult, partialResult.unitId.mapResult, partialResult.eventPayload.mapResult),
};
}
case "FunctionExpression": {
const thisResult = fn(Object.assign(Object.assign({}, partialResult), { body: partialResult.body.newExpression }));
return {
newExpression: thisResult.newExpression,
mapResult: combineResults(thisResult.mapResult, partialResult.body.mapResult),
};
}
case "VariableExpression":
return fn(partialResult);
case "ApplicationExpression": {
const thisResult = fn(Object.assign(Object.assign({}, partialResult), { function: partialResult.function.newExpression, arguments: partialResult.arguments.map((argumentResult) => argumentResult.newExpression) }));
return {
newExpression: thisResult.newExpression,
mapResult: combineResults(thisResult.mapResult, partialResult.function.mapResult, ...partialResult.arguments.map((argumentResult) => argumentResult.mapResult)),
};
}
default: {
const neverPartialResult = partialResult;
throw new Error(`Unexpected partial result: ${JSON.stringify(neverPartialResult)}`);
}
}
};
return fold(foldFunction, expression);
}
// Expensive
function mapExpression(mapper, expression) {
const result = mapExpressionWithResult((expr) => ({
newExpression: mapper(expr),
mapResult: null,
}), () => null, expression);
return result.newExpression;
}
//# sourceMappingURL=fold.js.map