SkillAgentSearch skills...

Pesa

A JS money lib whose precision goes up to 11 (and beyond).

Install / Use

/learn @frappe/Pesa
About this skill

Quality Score

0/100

Supported Platforms

Universal

README

<div align="center" markdown="1"> <img src="https://user-images.githubusercontent.com/29507195/207266449-e035883d-fab5-49ef-a60e-d2b085f21835.png" alt="pesa logo" width="256"/>

पेसा

</div>

A money handling library for JavaScript that can handle USD to VEF conversions for Jeff without breaking a sweat!


const money = pesa(135, 'USD').add(25).to('INR', 75);

money.round(2);
// '12000.00'

Why should I use this, when I can just do all of this with plain old JavaScript numbers?!

Because JavaScript numbers full of fun foibles such as these:

0.1 + 0.2;
// 0.30000000000000004

9007199254740992 + 1;
// 9007199254740992

Using them for financial transactions will most likely lead to technical bankruptcy [1].

(check this talk by Bartek Szopka to understand why JS numbers are like this)

pesa circumvents this by conducting scaled integer operations using JS BigInt instead of Number. This allows for arithmetic involving arbitrarily large numbers with unnecessarily high precision.

Index

<details> <summary><code>show/hide</code></summary>
  1. Installation
  2. Usage
    1. Initialization
    2. Currency and Conversions
    3. Immutability
    4. Chaining
  3. Documentation
    1. Arithmetic Functions
    2. Comparison Functions
    3. Check Functions
    4. Other Calculation Functions
    5. Display
    6. Chainable Methods
    7. Non Chainable Methods
    8. Internals
  4. Additional Notes
    1. Storing The Value
    2. Non-Money Stuff
    3. Support
    4. Precision vs Display
    5. Why High Precision
    6. Implicit Conversions
    7. Rounding
    8. Use Cases
  5. Alternatives
</details>

Documentation Index

<details> <summary><code>show/hide</code></summary>
  1. Arithmetic Functions
  2. Comparison Functions
  3. Check Functions
  4. Other Calculation Functions
  5. Display
  6. Chainable Methods
  7. Non Chainable Methods
  8. Internals - Numeric Representation - Conversion Rates
</details>

Installation

For npm users:

npm install pesa

For yarn users:

yarn add pesa

Index

Usage

pesa(200, 'USD').add(250, 'INR', 75).percent(50).round(3);
// '9475.000'

This section describes the usage in brief. For more details, check the Documentation section. For even more details, check the source code or raise an issue.

Index

Initialization

To create an initialize a money object you can either use the constructor function pesa:

import { pesa } from 'pesa';

const money = pesa(200, 'USD');
// OR
const money = pesa(200, options);

or the constructor function maker getMoneyMaker, this can be used if you don't want to set the options everytime you call pesa:

import { getMoneyMaker } from 'pesa';

const pesa = getMoneyMaker('USD');
// OR
const pesa = getMoneyMaker(options);

const money = pesa(200);

Options and Value

Options are optional, but currency has to be set before any conversions can take place.

interface Options {
  bankersRounding?: boolean; // Default: true, use bankers rounding instead of traditional rounding.
  currency?: string; // Default: '', Three letter alphabetical code in uppercase ('INR').
  precision?: number; // Default: 6, Integer between 0 and 20.
  display?: number; // Default: 3, Number of digits .round defaults to.
  wrapper?: Wrapper; // Default: (m) => m, Used to augment all returned money objects.
  rate?: RateSetting | RateSetting[]; // Default: [], Conversion rates
}

interface RateSetting {
  from: string;
  to: string;
  rate: string | number;
}

type Wrapper = (m: Money) => Money;

Value can be a string, number or a bigint. If value is not passed the value is set as 0.

type Value = string | number | bigint;

If bigint is passed then it doesn't undergo any conversion or scaling and is used to set the internal bigint.

pesa(235).internal;
// { bigint: 235000000n, precision: 6 }

pesa('235').internal;
// { bigint: 235000000n, precision: 6 }

pesa(235n).internal;
// { bigint: 235n, precision: 6 }

Wrapper is a function that can add additional properties to the returned object. One use case is Vue3 where everything is deeply converted into a Proxy, this is incompatible with pesa because of it's private variables and immutability.

So to remedy this you can pass markRaw as the wrapper function.

This will prevent the proxification of pesa objects. Which in the case of pesa shouldn't be required anyway because the underlying value is never changed.

Currency and Conversions

A numeric value isn't money unless a currency is assigned to it.

Setting Currency

Currency can be assigned any time before a conversion is applied.

// During initialization
const money = pesa(200, 'USD');

// After initialization
const money = pesa(200).currency('USD');

Setting Rates

To allow for conversion between two currencies, a conversion rate has to be set. This can be set before the operation or during the operation.

// Rate set before the operation
pesa(200).currency('USD').rate('INR', 75).add(2000, 'INR');

// Rate set during the operation
pesa(200).currency('USD').add(2000, 'INR', 0.013);

Conversion

The result of an operation will always have the currency on the left (USD in the above example). To convert to a currency:

// Rate set during the operation
money.to('INR', 75);

// Rate set before the operation
money.to('INR');

This returns a new Money object.

Does it provide conversion rates?

pesa doesn't provide or fetch conversion rates. This would cause dependencies on exchange rate APIs and async behaviour. There are a lot of exchange rate apis such as Coinbase, VAT Comply, European Central Bank, and others.

Immutability

The underlying value or currency of a Money object doesn't change after an operation.

const a = pesa(200, 'USD');
const b = pesa(125, 'INR').rate('USD', 0.013);
const c = a.add(b);

// Statements below will evaluate to true
a.float === 200;
b.float === 125;
c.float === 201.625;

a.getCurrency() === 'USD';
b.getCurrency() === 'INR';
c.getCurrency() === 'USD';

Chaining

Due to the following two points:

  1. All arithmetic operation (add, sub, mul and div), create a new Money object having the values that is the result of that operation.

  2. All setter methods (currency, rate), set the value of an internal parameter and return the calling Money object.

Methods can be chained and executed like so:

pesa(200)
  .add(22)
  .currency('USD')
  .sub(33)
  .rate('INR', 75)
  .mul(2, 'INR')
  .to('INR')
  .round(2);
// '377.99'

Index

Documentation

Calling the main function pesa returns an object of the Money class.

const money: Money = pesa(200, 'USD');

The rest of the documentation pertains to the methods and parameters of this class.

Arithmetic Functions

Operations that involve the value of two Money objects and return a new Money object as the result.

Function signature

[operationName](value: Input, currency?: string, rate?: number) : Money;

type Input = Money | number | string;

Example:

money = pesa(200, 'USD');

money.add(150).round();
// '350.000'

money.sub(150, 'INR', 0.013);
// '198.050'

Note: The rate argument here is from the currency given in the function to the calling objects currency. So in the above example rate of 0.013 is for converting 'INR' to 'USD'. The reason for this is to prevent precision loss due to reciprocal.

Arguments (arithmetic)

| name | description | example | | ---------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------- | | value | This is compulsory. If input is a string then '_' or ',' can be used as digit separator

View on GitHub
GitHub Stars48
CategoryDevelopment
Updated4mo ago
Forks15

Languages

TypeScript

Security Score

92/100

Audited on Nov 6, 2025

No findings