///
import { Cell, Default, handler, lift, recipe, str } from "commontools";
interface CounterEntry {
id?: string;
label?: string;
value?: number;
}
interface AggregatedCounter {
id: string;
label: string;
value: number;
}
interface CounterAggregatorArgs {
counters: Default;
}
type AdjustEvent = {
id?: string;
index?: number;
delta?: number;
set?: number;
};
type AppendEvent = {
id?: string;
label?: string;
value?: number;
};
const adjustCounter = handler(
(
event: AdjustEvent | undefined,
context: { counters: Cell },
) => {
if (!event) {
return;
}
const list = context.counters.get() ?? [];
let index = -1;
if (event.id) {
index = list.findIndex((entry) => entry.id === event.id);
}
if (index === -1 && typeof event.index === "number") {
index = event.index;
}
if (index === -1) {
return;
}
const entryCell = context.counters.key(index) as Cell;
const valueCell = entryCell.key("value") as Cell;
const current = typeof valueCell.get() === "number" ? valueCell.get() : 0;
const delta = typeof event.delta === "number" ? event.delta : 0;
const nextValue = typeof event.set === "number"
? event.set
: current + delta;
valueCell.set(nextValue);
},
);
const appendCounter = handler(
(
event: AppendEvent | undefined,
context: { counters: Cell },
) => {
const list = context.counters.get() ?? [];
const id = event?.id && event.id.length > 0
? event.id
: `counter-${list.length + 1}`;
if (list.some((entry) => entry.id === id)) {
return;
}
const value = typeof event?.value === "number" ? event.value : 0;
const label = event?.label ?? `Counter ${list.length + 1}`;
context.counters.set([
...list,
{ id, label, value },
]);
},
);
export const counterAggregator = recipe(
"Counter Aggregator",
({ counters }) => {
const sanitizedCounters = lift((entries: CounterEntry[]) =>
entries.map((entry, index) => ({
id: entry.id && entry.id.length > 0 ? entry.id : `counter-${index + 1}`,
label: entry.label ?? `Counter ${index + 1}`,
value: typeof entry.value === "number" ? entry.value : 0,
}))
)(counters);
const values = lift((entries: AggregatedCounter[]) =>
entries.map((entry) => entry.value)
)(sanitizedCounters);
const total = lift((numbers: number[]) =>
numbers.reduce((sum, value) => sum + value, 0)
)(values);
const count = lift((numbers: number[]) => numbers.length)(values);
const largest = lift((numbers: number[]) => {
if (numbers.length === 0) {
return 0;
}
return numbers.reduce(
(max, value) => value > max ? value : max,
numbers[0],
);
})(values);
const summary = str`Aggregate total ${total} across ${count} counters`;
const labels = lift((entries: AggregatedCounter[]) =>
entries.map((entry) => entry.label)
)(sanitizedCounters);
return {
counters: sanitizedCounters,
values,
total,
count,
largest,
labels,
summary,
adjust: adjustCounter({ counters }),
append: appendCounter({ counters }),
};
},
);