Skip to content

Amounts

Costs and rewards share the same normalized shape: resource id plus amount.

ts
import {
  collectAmounts,
  normalizeAmounts,
  type AmountsInput,
  type Cost,
  type Reward,
} from "@idlekitjs/economy";

Shapes

ts
interface ResourceAmount {
  resourceId: ResourceId;
  amount: number;
}

type Cost = readonly ResourceAmount[];
type Reward = readonly ResourceAmount[];
type AmountsInput = readonly (readonly [ResourceId, number] | ResourceAmount)[];

Tuple syntax:

ts
const cost = [["currency:coins", 100]] as const;

Object syntax:

ts
const reward = [{ resourceId: "currency:gems", amount: 5 }] as const;

They can be mixed in one AmountsInput.

Normalization

Normalization is consistent across costs and rewards:

  • duplicate resource ids are merged by sum;
  • zero totals are filtered out;
  • negative amounts are invalid;
  • NaN, Infinity and -Infinity are invalid.
ts
normalizeAmounts([
  ["currency:coins", 100],
  ["currency:coins", 25],
]);
// [{ resourceId: "currency:coins", amount: 125 }]

normalizeAmounts(input) is strict and throws EconomyError when any line is invalid.

collectAmounts(input) is lenient and returns both valid normalized amounts and invalid lines:

ts
const collected = collectAmounts([
  ["currency:coins", 10],
  ["currency:coins", Number.NaN],
]);

collected.amounts; // [{ resourceId: "currency:coins", amount: 10 }]
collected.invalid; // [{ resourceId: "currency:coins", amount: NaN }]

Transactions use the lenient path so bad content becomes a TransactionFailure instead of a thrown programming error.

Costs vs rewards

Cost and Reward use the same line shape, but they mean different actions. A cost is consumed before apply. A reward is credited only after the transaction has previewed successfully, paid the cost and run apply.

Rewards cannot be negative. Use a cost to debit a resource.