npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@birtalanrobert/vouchers

v1.0.0

Published

Stored value: gift vouchers, prepaid packages and balances, as an append-only ledger

Readme

@birtalanrobert/vouchers

Stored value: gift vouchers, prepaid packages and balances.

Money a customer has already handed over and has not yet used. It is the business's cash and the customer's entitlement at the same time — which is why this is a ledger and not a counter.

Two specifications call for it in the same words: project 02 (gift vouchers and "ten sessions" packages) and project 06 (gift balances and prepaid top-ups). It is built for both.

Why a ledger

A balance stored as a number cannot answer the question that always gets asked: where did the other four go? And it breaks in the two ordinary cases a ledger survives — a redemption written twice, and a refund.

So every change is an append-only entry, and the balance is their sum. The balance column exists only as a cache written in the same transaction, with CHECK (balance >= 0) behind it: the constraint is the guarantee, the ledger is the explanation.

Counting without a database

import { balanceOf, canRedeem, applicable, expiryFrom } from '@birtalanrobert/vouchers';

balanceOf([
  { kind: 'issued', amount: 20_000 },
  { kind: 'redeemed', amount: 5_000 },
]); // 15_000

// A voucher for 200 against a bill of 350 pays 200 and the customer pays the rest.
applicable(voucher, 35_000, Date.now()); // 20_000

// Null means never, and that is the default.
expiryFrom(Date.now(), null); // null

The root entry point is framework-free and browser-safe: a console counts a balance while somebody types, and a booking page shows what a voucher is worth. Everything needing TypeORM or Nest is behind @birtalanrobert/vouchers/nestjs.

Issuing and redeeming

import { VouchersService } from '@birtalanrobert/vouchers/nestjs';

const voucher = await vouchers.issue(tenantId, {
  denomination: 'units',
  amount: 10,
  subject: `service:${massageId}`, // a package is for what it was sold for
  holderId: customer.id,
});

await vouchers.redeem(tenantId, {
  voucherId: voucher.id,
  amount: 1,
  subject: `booking:${booking.id}`,
  serviceSubject: `service:${massageId}`,
});

// An appointment cancelled gives its session back.
await vouchers.release(tenantId, { voucherId: voucher.id, subject: `booking:${booking.id}` });

Two things it deliberately does not do

  • Invent an expiry. Rules for stored value differ by jurisdiction and several treat an unused voucher as the customer's money for years. Null means never; a business that knows its own rule sets one.
  • Take a foreign key to your rows. subject is a string in the consuming product's own words — booking:<id>, order:<id> — because a key to one product's tables is exactly what would stop this one being shared.

Codes

Crockford's base32, twelve characters in four groups of three. Its excluded letters and its substitutions (I and L read as 1, O as 0) are the mistakes people actually make reading a code aloud. normaliseCode applies them, so a customer reading off a photograph is not told their voucher does not exist because they typed a letter O.

A voucher is a bearer instrument: whoever holds the card may spend it. What protects it is the rate limit in front of the lookup, not the entropy — though 32^12 is enough that guessing is not a strategy.