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

@smart-science/sid

v0.3.0

Published

SID: generator, parser, formatter, and checksum verifier

Readme

SID (smart-id)

Fast 20-character identifiers with two check characters: generate, parse, format, and verify. For TypeScript and JavaScript, with no dependencies.


Installation

# bun
bun add @smart-science/sid

# npm
npm install @smart-science/sid

Usage

Create an ID: generate() and generateFormatted()

import { generate, generateFormatted } from '@smart-science/sid';

const id = generate(); // '0123456789ABCDEFGHWJ'
const pretty = generateFormatted(); // '0123-4567-89AB-CDEF-GHWJ'
  • generate() returns a new random 20-character ID, typed SID.
  • generateFormatted() returns the same kind of ID grouped for reading, typed FormattedSID.

Both throw a TypeError if the runtime has no Web Crypto (which every supported runtime has).

Derive an ID from bytes: fromBytes(bytes)

Returns the same ID for the same bytes, for example to derive an ID from an existing key. Hash the input yourself and pass the digest:

import { fromBytes } from '@smart-science/sid';

// Node.js / Bun
import { createHash } from 'node:crypto';
const id = fromBytes(createHash('sha256').update('10.1000/xyz123').digest());

// browsers
const data = new TextEncoder().encode('10.1000/xyz123');
const id = fromBytes(new Uint8Array(await crypto.subtle.digest('SHA-256', data)));
  • Only the first 90 bits (12 bytes) are used; a full 32-byte SHA-256 digest can be passed directly.
  • Returns null for fewer than 12 bytes or input that is not a Uint8Array (a Node.js Buffer is accepted).
  • The output is not random: anyone with the same input gets the same ID.

Read an ID: parse(input)

Use parse() whenever an ID comes from outside. It accepts both forms, cleans up the input, and returns the canonical 20-character ID:

import { parse } from '@smart-science/sid';

parse('0123-4567-89AB-CDEF-GHWJ'); // { ok: true, data: '0123456789ABCDEFGHWJ' }
parse(' oi23-4567-89ab-cdef-ghwj '); // { ok: true, data: '0123456789ABCDEFGHWJ' } (see "Self-repairing input")
parse('0123-4567-89AB-CDEF-GHWK'); // { ok: false, code: 'CHECKSUM_MISMATCH', error: '...' }

The result is either { ok: true, data } or { ok: false, code, error }. Check ok first:

const res = parse(input);
if (res.ok) {
    console.log(res.data); // res.data is type SID
} else {
    console.error(res.code); // e.g. 'CHECKSUM_MISMATCH'; see "Error codes"
}

Always store and compare the canonical res.data, never the raw input.

Display an ID: format(input)

Works like parse(), but returns the hyphenated form XXXX-XXXX-XXXX-XXXX-XXXX, typed FormattedSID:

import { format } from '@smart-science/sid';

format('0123456789ABCDEFGHWJ'); // { ok: true, data: '0123-4567-89AB-CDEF-GHWJ' }
format('0123456789abcdefghwj'); // { ok: true, data: '0123-4567-89AB-CDEF-GHWJ' }
format('not an id'); // { ok: false, code: 'INVALID_LENGTH', error: '...' }

Just check: verify(input)

Returns true or false. It accepts the same forgiving input as parse():

import { verify } from '@smart-science/sid';

verify('0123-4567-89ab-cdef-ghwj'); // true
verify('0123-4567-89AB-CDEF-GHWK'); // false (wrong check characters)

Use parse() instead if you want to keep the ID, because verify() doesn't return the cleaned-up form.

Strict checks: isSID(input) and isFormattedSID(input)

Return true only when the input is already exactly in canonical form: uppercase, no extra spaces, no repaired characters. They are useful for checking data you store yourself:

import { isFormattedSID, isSID } from '@smart-science/sid';

isSID('0123456789ABCDEFGHWJ'); // true
isSID('0123456789abcdefghwj'); // false (valid, but not canonical: use parse())
isFormattedSID('0123-4567-89AB-CDEF-GHWJ'); // true
isFormattedSID('0123456789ABCDEFGHWJ'); // false (not hyphenated)

In TypeScript, a true result also narrows the value's type to SID or FormattedSID.

import type { FormattedSID, SID } from '@smart-science/sid';

declare const input: unknown;

if (isSID(input)) {
    const typed: SID = input; // typed SID
} else if (isFormattedSID(input)) {
    const typed: FormattedSID = input; // typed FormattedSID
}

All functions that take input accept any value, including null, numbers, and objects. They never throw; invalid input is simply rejected.


Error codes

parse() and format() report why an input was rejected. Branch on code. error is a human-readable message whose wording may change.

{
    ok: false;
    code: SidErrorCode;
    error: string;
}

| code | Meaning | |---|---| | NOT_A_STRING | The input is not a string | | INVALID_LENGTH | Too short or too long for an ID | | INVALID_FORMAT | The right length for the hyphenated form, but the hyphens are in the wrong places | | INVALID_CHARACTER | Contains a character that can't appear in an ID; error names it | | CHECKSUM_MISMATCH | All characters are allowed, but the check characters don't match: most likely a typo |


TypeScript types

import type { FormattedSID, SID, SidErrorCode, SidResult } from '@smart-science/sid';

SID and FormattedSID are strings at runtime. In TypeScript they are kept apart from plain string; an unchecked string can't be passed where a typed ID is expected:

function load(id: SID) { /* ... */ }

declare const input: string;
load(input); // compile error: plain string is not a SID

const res = parse(input);
if (res.ok) load(res.data); // OK: validated by parse()
if (isSID(input)) load(input); // OK: validated by isSID()
load(input as SID); // OK: a cast, unsafe if unchecked

How IDs work

Characters

An ID has 20 characters drawn from 32 symbols (Crockford's Base32): digits 0–9 and letters A–Z without I, L, O, and U. Left out because easily confused with 1 and 0 (and U to avoid accidental words). The 32 symbols are exported in order as CROCKFORD_ALPHABET, for example to build input masks.

First 18 characters are random, which gives about 1.24 × 10²⁷ possible IDs. Two randomly generated IDs are practically never the same. The last two characters are check characters: each of the first 18 characters is multiplied by its position (1 to 18), the products are summed, and the sum modulo 1024 is written as two characters.

Self-repairing input

  • lowercase letters are accepted: abc → ABC
  • I and L converted to 1, and O to 0
  • surrounding spaces are ignored
  • the hyphenated and plain forms are both accepted

What the check characters catch

  • Any single wrong character is always detected, including in the check characters.
  • Any two characters swapped (…AB… typed as …BA…) is always detected, at any distance and at any of the 20 positions.
  • Random input passes with a probability of 1 in 1024.
  • Not detected: some combinations of several errors (about 0.32% of inputs with two wrong characters). The check guards against typos. It is not a guarantee that random input can never pass.

What an ID does not do

  • It contains no date and has no order.
  • It is not secret and proves nothing about who created it.

Runtime support

| Runtime | Versions | |---|---| | Node.js | 22.12 and later (import and require()) | | Bun | 1.3 and later | | Deno | Current, via npm:@smart-science/sid | | Browsers | Current evergreen browsers |


Changelog

See CHANGELOG.md.

License

Apache-2.0