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

@tnid/filter

v0.1.0

Published

Blocklist filtering for TNIDs - generate IDs that avoid specified substrings

Readme

@tnid/filter

Generate TNIDs that don't contain blocklisted substrings (e.g., profanity).

Why Filter TNIDs?

The 17-character data portion of a TNID string uses an alphabet that includes letters capable of forming recognizable words. For some applications, it may be undesirable for IDs to accidentally contain offensive terms.

Installation

# npm
npm install @tnid/filter @tnid/core

# pnpm
pnpm add @tnid/filter @tnid/core

# bun
bun add @tnid/filter @tnid/core

# deno
deno add npm:@tnid/filter npm:@tnid/core

For encryption-aware filtering, also install @tnid/encryption.

Quick Start

import { Tnid, TnidType } from "@tnid/core";
import { Blocklist, newV0Filtered, newV1Filtered } from "@tnid/filter";

const UserId = Tnid("user");
type UserId = TnidType<typeof UserId>;

// Create a blocklist
const blocklist = new Blocklist(["TACO", "FOO", "BAZZ"]);

// Generate filtered IDs
const v0: UserId = newV0Filtered(UserId, blocklist);
const v1: UserId = newV1Filtered(UserId, blocklist);

With Encryption

If you use the encryption extension to convert V0 to V1, you probably want both forms to be clean:

import { Tnid } from "@tnid/core";
import { EncryptionKey } from "@tnid/encryption";
import { Blocklist } from "@tnid/filter";
import { newV0FilteredForEncryption } from "@tnid/filter/encryption";

const UserId = Tnid("user");
const blocklist = new Blocklist(["TACO", "FOO"]);
const key = EncryptionKey.fromHex("0102030405060708090a0b0c0d0e0f10");

// Both the V0 and its encrypted V1 will be clean
const v0 = await newV0FilteredForEncryption(UserId, blocklist, key);

API Reference

Blocklist

A compiled blocklist for case-insensitive substring matching. Patterns must only contain characters from the TNID data alphabet (-0-9A-Z_a-z).

const blocklist = new Blocklist(["TACO", "FOO", "BAZZ"]);

blocklist.containsMatch("xyzTACOxyz"); // true
blocklist.containsMatch("xyztacoxyz"); // true (case-insensitive)
blocklist.containsMatch("xyzHELLOxyz"); // false

newV0Filtered(factory, blocklist)

Generate a time-ordered V0 TNID whose data string contains no blocklisted words.

  • Throws: FilterError if the iteration limit is exceeded

newV1Filtered(factory, blocklist)

Generate a random V1 TNID whose data string contains no blocklisted words.

  • Throws: FilterError if the iteration limit is exceeded

newV0FilteredForEncryption(factory, blocklist, key) (from @tnid/filter/encryption)

Generate a V0 TNID where both the V0 and its encrypted V1 form contain no blocklisted words.

  • Returns: Promise<TnidValue<Name>>
  • Throws: FilterError if the iteration limit is exceeded

FilterError

Thrown when filtered generation exceeds the iteration limit, which typically means the blocklist is too restrictive.

import { FilterError } from "@tnid/filter";

try {
  const id = newV0Filtered(UserId, blocklist);
} catch (e) {
  if (e instanceof FilterError) {
    console.log(`Failed after ${e.iterations} iterations`);
  }
}

How It Works

For V1, all bits are random, so the function simply regenerates until clean.

For V0, the strategy depends on where the match appears in the data string:

  • Random portion (characters 7-16): Regenerate the random bits
  • Timestamp portion (characters 0-6): Advance the timestamp enough to change the matched characters, avoiding a potentially large "bad window"

A global last-known-safe timestamp avoids re-discovering the same bad windows across calls.

License

MIT