Skip to Content
TokenAPI Reference

Ahead of the release

npm install @evanion/token gives you 0.1.0. These pages document main, which has changes that release does not.

API reference

createToken(options?)

function createToken(options?: TokenOptions): Token;

Validates the options and returns a frozen Token.

OptionTypeDefaultMeaning
lengthnumber8total characters, the check character included
chunkSizenumber4characters between separators; must divide length
separatorstring'-'between chunks, and after a prefix
dictionarystringDEFAULT_DICTIONARYthe alphabet

Throws InvalidAlphabetError when the dictionary is confusable, not lowercase, or of a size that biases byte % n; InvalidShapeError when length, chunkSize and separator cannot describe a code; and InvalidDictionaryError from @evanion/luhn when the dictionary cannot carry a check character.

Everything is checked here, once. Nothing is checked at use, so an accepted instance cannot produce a code its own validate rejects.

chunkSize defaults to 4, which does not divide every length — a length that is not a multiple of 4 has to name its own.

The returned object

interface Token { readonly dictionary: string; readonly n: number; readonly length: number; readonly chunkSize: number; readonly separator: string; readonly entropyBits: number; generate(options?: GenerateOptions): GenerateResult; validate(input: string): ValidateResult; }

entropyBits is (length - 1) * log2(n) — see Entropy and collisions.

generate(options?)

interface GenerateOptions { prefix?: string; } interface GenerateResult { value: string; // the code as a person sees it body: string; // the payload the check character was computed over check: string; // the check character prefix: string | undefined; // echoed from the call }

Draws length - 1 characters from crypto.getRandomValues and appends the check character. Never retries, never checks for collisions.

The prefix is not validated against the alphabet and is not part of the checksum: it is written ahead of the code, separated by separator, and compared by literal string match.

validate(input)

type ValidateResult = | { valid: true; body: string } | { valid: false; reason: ValidateFailureReason }; type ValidateFailureReason = 'outside-alphabet' | 'wrong-length' | 'check-failed';

Total and free of side effects. Separators are stripped and input is case-folded first, so a code typed without them, or grouped differently, still validates.

The three checks run in the order listed and the first failure is reported. A prefix is not part of the code — validate reads a leading ORD- as three more characters of it, and its o is outside the alphabet.

body on success is the code without its check character, case-folded: the string to look up.

Constants

ExportValue
DEFAULT_DICTIONARY'0123456789abcdefghjkmnpqrstuvxyz'
CONFUSABLE_CHARACTERS'ilow', excluded from every alphabet
DEFAULT_LENGTH8
DEFAULT_CHUNK_SIZE4
DEFAULT_SEPARATOR'-'

Types

TokenOptions, GenerateOptions, GenerateResult, ValidateResult, ValidToken, InvalidToken, ValidateFailureReason, TokenShape, InvalidDictionaryReason, InvalidShapeReason, and the Token interface above.

Errors

TokenError

Base class for everything this package throws. All of them are raised by createToken: generate and validate are total, so an accepted configuration cannot fail at use.

InvalidAlphabetError extends TokenError

The three dictionary constraints this package owns.

PropertyMeaning
reason'confusable', 'unfolded' or 'non-uniform'
dictionarythe dictionary as supplied
offendingthe code points named; empty for non-uniform, which is about the size

InvalidShapeError extends TokenError

length, chunkSize or separator cannot describe a code.

PropertyMeaning
reasonwhich constraint failed
length, chunkSize, separatorall three, as supplied
type InvalidShapeReason = | 'length' // not an integer, or below 2 | 'chunk-size' // not an integer, or below 1 | 'chunk-size-indivisible' // does not divide length | 'separator-empty' | 'separator-in-dictionary';

It carries all three values rather than only the offending one, because the constraints are relations between them: the value that has to change is not always the value the reason names.

From @evanion/luhn

A dictionary that fails Luhn’s own constraints — even size, no repeats, no case pairs — throws InvalidDictionaryError, which does not extend TokenError. Catch Error to cover both hierarchies, or catch the two by name.

Last updated on