impl/typescript.ts
1,811 bytes · the TypeScript implementation · view raw
Imports name this capability’s declared dependencies, which fune builds next to it in your project; each one links to its page.
import { roundDiv } from "./math_round_div.ts"; ← from math.round-div ^1.0.0 · built alongside by fune
import { type Money, assertSameCurrency, money } from "./money_amount.ts"; ← from money.amount ^1.0.0 · built alongside by fune
import { type DataAllowance } from "./telecoms_data_allowance_types.ts";
/**
* Usage against a bundle: remaining allowance, overage in whole blocks priced
* and limited to an optional cap, and the share used, rounded down so a
* bundle with anything left never reads 100%.
*/
export function dataAllowance(allowance: number, used: number, overageBlock: number, pricePerBlock: Money, overageCap: Money | null): DataAllowance {
if (!Number.isInteger(allowance) || allowance < 0 || !Number.isInteger(used) || used < 0) {
throw new RangeError(`allowance and usage must be whole numbers of 0 or more, received ${allowance} and ${used}`);
}
if (!Number.isInteger(overageBlock) || overageBlock < 1) {
throw new RangeError(`overage block must be 1 or more, received ${overageBlock}`);
}
if (pricePerBlock.minor < 0) {
throw new RangeError(`price per block must not be negative, received ${pricePerBlock.minor}`);
}
if (overageCap !== null) {
assertSameCurrency(pricePerBlock, overageCap);
if (overageCap.minor < 0) throw new RangeError(`overage cap must not be negative, received ${overageCap.minor}`);
}
const overage = Math.max(used - allowance, 0);
const overageBlocks = roundDiv(overage, overageBlock, "up");
const uncapped = overageBlocks * pricePerBlock.minor;
const capped = overageCap !== null && uncapped > overageCap.minor;
return {
used,
remaining: Math.max(allowance - used, 0),
overage,
overageBlocks,
overageCharge: money(capped ? (overageCap as Money).minor : uncapped, pricePerBlock.currency),
capped,
percentUsedBasisPoints: allowance === 0 ? null : roundDiv(used * 10000, allowance, "down"),
};
}