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

@emelymusic/identifiers

v0.1.0

Published

Validation, normalization and check-digit arithmetic for music industry identifiers: ISWC, ISNI, IPI name/base, ISRC, UPC/EAN, GRid, DPID, LC, IPN. Structured failure reasons, zero dependencies, runs in Node and the browser.

Readme

@emelymusic/identifiers

Validation, normalization and check-digit arithmetic for the identifiers the music industry actually files: ISWC, ISNI, IPI name and base numbers, ISRC, UPC/EAN, GRid, DPID, label code (LC) and IPN. emely. uses this one implementation on both sides of the wire - in the browser forms and in the backend write handlers that refuse a bad value - because the same rule written twice is the same rule drifting twice. It is pure TypeScript ESM with zero runtime dependencies, so the identical check runs in Node and in a browser bundle with no loading state.

A passing check means an identifier is well-formed. It says nothing about whether the number belongs to anybody; it is not a lookup and never claims to be one.

Install

npm install @emelymusic/identifiers
# or
pnpm add @emelymusic/identifiers

Quick start

Works identically in Node (>= 22) and in a bundler for the browser:

import {
    inspectIdentifier,
    inspectIsni,
    validateIpi,
    normalizeIpiBase,
} from '@emelymusic/identifiers';

// Kind-dispatched: one entry point for every identifier.
inspectIdentifier('isni', '0000 0001 2150 062X');
// {
//   valid: false,
//   normalized: '000000012150062X',
//   failure: {reason: 'checkDigit', expected: '7', actual: 'X'},
// }

// Per-identifier inspectors, validators and normalizers.
validateIpi('28476368');
// {valid: true, normalized: '00028476368'}
// (a short all-digit IPI name number is left-padded: leading zeros are
// routinely lost in spreadsheets, and the check digits still have to pass)

normalizeIpiBase('i-001.068.130-6'); // 'I-001068130-6' (Picasso)

The identifiers

| Kind | Format | Check rule | Standard | normalized returns | |---|---|---|---|---| | isni | 15 digits + check char (digit or X), often grouped in fours | ISO 7064 MOD 11-2 | ISO 27729 | 16 chars, separators stripped, x upper-cased | | ipiName | 9-digit body + 2 check digits, 11 digits total | weighted sum (weights 10..2) mod 101 | CISAC IPI | 11 digits; a short all-digit input is left-padded | | ipiBase | I-NNNNNNNNN-C (letter I, 9 digits, check digit) | weighted mod-10, initial weight 2 | CISAC IPI (per CWR spec) | printed form I-NNNNNNNNN-C | | iswc | T-DDD.DDD.DDD-C | weighted mod-10 over the 9 digits, +1 constant | ISO 15707 | T + 10 digits (dots/hyphens stripped) | | isrc | CC-XXX-YY-NNNNN | none; shape only, no check digit exists | ISO 3901 | 12 chars, upper-cased | | upc | 12 (UPC-A) or 13 (EAN-13) digits | GS1 mod-10 | GS1 | the digits as entered | | grid | A1 + 5-char issuer + 10-char release + check char, 18 total | ISO 7064 MOD 37,36 | IFPI GRid standard | the 18-char string; separators are refused, not repaired | | dpid | PADPIDA + 10 digits + check char | ISO 7064 MOD 37,36 (inferred, see below) | DDEX | 18 chars, upper-cased | | lc | LC + 4-6 digits | none; shape only | GVL label code | LC + digits | | ipn | 4-15 digits | none published; digit shape only | SCAPR IPD | the digit run |

API layers

  • inspect*(raw) -> IdentifierInspection is the primary entry point: {valid, normalized, failure?}. normalized is the canonical form and is returned whenever the structure parses, even when the check digit fails, so a caller can echo back what was actually checked.
  • validate*(raw) -> {valid, normalized} is the same verdict without the failure detail.
  • isValid*(value) are strict boolean gates for re-checking an already stored value (e.g. on export). Heads-up: isValidUpc, isValidGrid and isValidIsrc do not strip whitespace while their inspect* counterparts do; that asymmetry is deliberate and documented in the module headers.
  • normalize* / format* are canonical-form helpers (normalizeIpiBase to the printed I-NNNNNNNNN-C form, formatIsni to four-digit groups, formatIswcForCwr to T-DDD.DDD.DDD-C), exported separately from the validators: the caller opts in to transforming.
  • *CheckDigit / *CheckChar expose the check arithmetic for tests and for the nextGtin13 / nextGrid constructors.

Structured failures

An inspect* call never just says "invalid". The failure field carries a machine-readable reason so the caller can render its own wording (no UI strings or i18n keys ship in this package):

type IdentifierFailure = {
    reason: 'length' | 'character' | 'checkDigit' | 'prefix' | 'shape';
    expected?: string; // expected check char(s) or length
    actual?: string;   // entered check char(s) or actual length
    char?: string;     // the offending character (reason 'character')
    position?: number; // its 1-based position in the cleaned input
    suggestion?: string; // a plausible fix, e.g. '0' for the letter O
};
inspectIdentifier('isni', '0000-0001-2150-062X');
// failure: {reason: 'checkDigit', expected: '7', actual: 'X'}
// -> "the check character should be 7, you entered X"

inspectIdentifier('isni', '000O000121500627');
// failure: {reason: 'character', char: 'O', position: 4, suggestion: '0'}
// -> "the character O at position 4 is not allowed there; did you mean 0?"

How the ISWC check digit works

An ISWC is T + 9 digits + 1 check digit (ISO 15707). Take the standard's own printed example, T-034.524.680-1. Body digits 0 3 4 5 2 4 6 8 0, weights 1..9, plus a constant of 1:

sum = 1 + (1*0 + 2*3 + 3*4 + 4*5 + 5*2 + 6*4 + 7*6 + 8*8 + 9*0)
    = 1 + 178 = 179
check = (10 - (179 mod 10)) mod 10 = (10 - 9) mod 10 = 1

So the check digit is 1, and T-034.524.680-1 is well-formed while T-034.524.680-2 reports {reason: 'checkDigit', expected: '1', actual: '2'}.

The ISNI check is ISO 7064 MOD 11-2 over the first 15 digits: start with total = 0, then for each digit total = (total + digit) * 2 mod 11. The check is (12 - total) mod 11, and a result of 10 is written X (0000 0001 2281 955X is valid; the same body ending 3 is not).

Sources

  • ISWC: ISO 15707 - iswc.org/iswc, Wikipedia. Anchored on the standard's printed example T-034.524.680-1.
  • ISNI: ISO 27729 with ISO 7064 MOD 11-2 - Wikipedia.
  • IPI name and base: CISAC's Interested Parties Information system; the checks are per the CWR specification and django-music-publisher's implementation, anchored on Picasso's I-001068130-6 - Wikipedia.
  • ISRC: ISO 3901 - Wikipedia.
  • UPC/EAN: GS1 GTIN mod-10 - Wikipedia.
  • GRid: IFPI GRid standard, ISO 7064 MOD 37,36, anchored on the standard's worked example A12425GABC1234002M - Wikipedia.
  • DPID: DDEX. DDEX does not publish the check rule normatively; our MOD 37,36 implementation is inferred from python-stdnum and confirmed against real allocated DPIDs. If DDEX ever publishes a spec, reconcile against it.
  • LC: the GVL label code, LC + 4-6 digits - Wikipedia.
  • IPN: society-issued performer number (SCAPR IPD). No published check rule exists; the shape is all we check.

The contract vectors

identifier-vectors.json at the package root is the test contract: real published identifiers as anchors (the ISWC example above, Picasso's IPI base, the IFPI GRid worked example, Shakespeare's ISNI and IPI) plus the refusal cases for every kind. It is importable as @emelymusic/identifiers/identifier-vectors.json and drives most of the test suite. To add a case, append an entry:

{
    "identifier": "isni",
    "input": "0000 0001 2103 2683",
    "valid": true,
    "normalized": "0000000121032683",
    "clientAccepts": true,
    "note": "William Shakespeare's ISNI"
}

valid/normalized are the canonical contract. clientAccepts records a deliberately stricter client-side contract used by emely-web's own suite (refine, never transform: a short IPI the canonical rule left-pads is still rejected there). New cases go into this file, never inline in a test.

Development

Requires Node >= 22 and pnpm.

pnpm install
pnpm test        # vitest run
pnpm typecheck   # tsc --noEmit over src + test
pnpm build       # tsc -> dist/ with .d.ts

License

MIT, see LICENSE.

Maintained by Emely GmbH. Contact: [email protected].