🎩 You're Invited:Meet the Socket team at Black Hat in Las Vegas, August 3-6.RSVP
Sign In

@stll/aho-corasick

Package Overview
Dependencies
Maintainers
1
Versions
18
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@stll/aho-corasick

NAPI-RS bindings to the Rust aho-corasick crate for Node.js/Bun

Source
npmnpm
Version
0.1.5
Version published
Maintainers
1
Created
Source

Stella

@stll/aho-corasick

NAPI-RS bindings to the Rust aho-corasick crate for Node.js and Bun.

Multi-pattern string searching in linear time. Built on BurntSushi's aho-corasick (the same engine that powers ripgrep), exposed to JavaScript via NAPI-RS.

Install

npm install @stll/aho-corasick
# or
bun add @stll/aho-corasick

Prebuilt binaries are available for:

PlatformArchitecture
macOSx64, arm64
Linux (glibc)x64, arm64
Linux (musl)x64
Windowsx64

Usage

import { AhoCorasick } from "@stll/aho-corasick";

const ac = new AhoCorasick(["foo", "bar", "baz"]);

// Check for any match
ac.isMatch("hello foo world"); // true

// Find all non-overlapping matches
ac.findIter("foo bar baz");
// [
//   { pattern: 0, start: 0, end: 3, text: "foo" },
//   { pattern: 1, start: 4, end: 7, text: "bar" },
//   { pattern: 2, start: 8, end: 11, text: "baz" },
// ]

// Find overlapping matches
ac.findOverlappingIter("foobar");

// Replace matches
ac.replaceAll("foo bar", ["FOO", "BAR", "BAZ"]);
// "FOO BAR"

Options

const ac = new AhoCorasick(patterns, {
  // Match semantics (default: "leftmost-first")
  matchKind: "leftmost-longest",
  // ASCII case-insensitive (default: false)
  caseInsensitive: true,
  // Only match whole words (default: false)
  // Unicode-aware; CJK always passes
  wholeWords: true,
});

Streaming

For processing large files or streams chunk by chunk:

import { StreamMatcher } from "@stll/aho-corasick";

const sm = new StreamMatcher(["needle", "haystack"]);

for await (const chunk of readableStream) {
  const matches = sm.write(chunk);
  for (const m of matches) {
    console.log(
      `Pattern ${m.pattern} ` + `at ${m.start}..${m.end}`,
    );
  }
}

sm.flush(); // finalize stream state
sm.reset(); // reuse for another stream

StreamMatcher automatically handles overlap between chunks so that matches spanning chunk boundaries are found.

Groups

To organize patterns into named groups (e.g., for entity recognition), use the pattern index as a lookup key into a parallel array:

const GROUPS = {
  LEGAL_FORM: ["s.r.o.", "GmbH", "LLC"],
  CURRENCY: ["EUR", "USD", "CZK"],
};

const patterns: string[] = [];
const tag: string[] = [];

for (const [group, terms] of Object.entries(GROUPS)) {
  for (const term of terms) {
    patterns.push(term);
    tag.push(group);
  }
}

const ac = new AhoCorasick(patterns, {
  wholeWords: true,
});

for (const m of ac.findIter(text)) {
  console.log(m.text, tag[m.pattern]);
  // "GmbH" "LEGAL_FORM"
  // "EUR"  "CURRENCY"
}

tag[m.pattern] is a single array index lookup (O(1), no hashing).

Benchmarks

Measured on Apple M3, 24 GB RAM, macOS 25.3.0, Bun 1.3.10. Automaton pre-built; times are search-only averaged over multiple runs.

Corpora: Canterbury Large Corpus (ASCII), Leipzig Corpora Collection (Unicode).

Run locally: bun run bench:install && bun run bench:download && bun run bench:all

ASCII (Canterbury Large Corpus)

HaystackPatterns@stllmodern-ahocorasickahocorasick@monyone@tanishiking
bible.txt (4.0 MB)20 legal terms5 ms444 ms130 ms129 ms585 ms
E.coli (4.6 MB)16 DNA codons2 ms288 ms16 ms135 ms637 ms
world192.txt (2.5 MB)20 legal terms1 ms300 ms121 ms71 ms312 ms
bible.txt (4.0 MB)1 pattern1 ms254 ms19 ms53 ms420 ms

Unicode (Leipzig Corpora Collection)

HaystackPatterns@stllmodern-ahocorasickahocorasick@monyone@tanishiking
Czech news 2024 (4.8 MB)10 legal terms23 ms563 ms271 ms94 ms652 ms
Turkish news 2024 (5.4 MB)10 terms28 ms724 ms358 ms158 ms731 ms
Japanese newscrawl 2019 (2.4 MB)10 terms16 ms521 ms411 ms168 ms620 ms
Chinese Wikipedia 2021 (2.0 MB)10 terms15 ms361 ms323 ms94 ms607 ms
German news 2024 (5.5 MB)10 terms13 ms742 ms229 ms107 ms846 ms

WASM (browser target)

The same Rust code compiles to WASM via wasm32-wasip1-threads. Bundlers (Vite, Webpack) auto-select the WASM build for browser targets.

Haystack@stll WASM@stll nativeBest pure JS
bible.txt (4.0 MB)34 ms4 ms186 ms
Czech news (4.8 MB)61 ms17 ms208 ms

WASM is 4-8x slower than native, but 3-6x faster than the best pure-JS alternative; in browsers where native modules are unavailable, it is the fastest option.

All match counts verified equal across libraries. Match offsets are UTF-16 code unit indices, compatible with String.prototype.slice().

Alternatives tested

API

AhoCorasick

MethodReturnsDescription
new AhoCorasick(patterns, options?)instanceBuild automaton
.findIter(haystack)Match[]Non-overlapping matches
.findOverlappingIter(haystack)Match[]All overlapping matches
.isMatch(haystack)booleanAny pattern matches?
.replaceAll(haystack, replacements)stringReplace matched patterns
.findIterBuf(haystack)ByteMatch[]Matches in a Buffer / Uint8Array (byte offsets)
.isMatchBuf(haystack)booleanAny pattern matches in a Buffer / Uint8Array?
.patternCountnumberNumber of patterns

StreamMatcher

MethodReturnsDescription
new StreamMatcher(patterns, options?)instanceBuild streaming matcher
.write(chunk)ByteMatch[]Feed chunk, get global byte-offset matches
.flush()ByteMatch[]Finalize stream
.reset()voidReset for reuse

Types

type MatchKind = "leftmost-first" | "leftmost-longest";

type Options = {
  matchKind?: MatchKind;
  caseInsensitive?: boolean;
  wholeWords?: boolean;
  dfa?: boolean;
};

// Returned by string methods (findIter, etc.)
type Match = {
  pattern: number; // index into patterns array
  start: number; // UTF-16 code unit offset
  end: number; // exclusive
  text: string; // matched substring
};

// Returned by Buffer/streaming methods
type ByteMatch = {
  pattern: number; // index into patterns array
  start: number; // byte offset
  end: number; // exclusive
};

Error handling

  • new AhoCorasick([...]) throws if the automaton cannot be built (e.g. patterns exceed internal size limits).
  • replaceAll(haystack, replacements) throws if replacements.length !== patternCount.
  • Empty patterns arrays are valid; all search methods return no matches.

Limitations

  • Case insensitivity is ASCII-only. The underlying Rust crate folds A-Z to a-z but does not handle Unicode case folding (Turkish İ/ı, German ß/ss, etc.). This is a documented upstream limitation.
  • WASM requires SharedArrayBuffer. Browser builds need Cross-Origin-Opener-Policy: same-origin and Cross-Origin-Embedder-Policy: require-corp headers. Edge runtimes without WASM support (some Cloudflare Workers configurations) are not supported.

Acknowledgements

This package is a thin binding layer. The hard work is done by:

  • aho-corasick by Andrew Gallant (BurntSushi) — the Rust implementation of the Aho-Corasick algorithm. MIT licensed.
  • NAPI-RS — the Rust framework for building Node.js native addons. MIT licensed.

Development

# Install dependencies
bun install

# Build native module (requires Rust toolchain)
bun run build

# Run tests (113 tests, including Unicode edge cases)
bun test

# Download benchmark corpora
bun run bench:download

# Install benchmark dependencies (alternatives)
bun run bench:install

# Run benchmarks
bun run bench:speed       # Canterbury corpus
bun run bench:unicode     # Leipzig corpora
bun run bench:correctness # cross-library comparison
bun run bench:all         # all three

# Lint & format
bun run lint
bun run format

License

MIT

Keywords

aho-corasick

FAQs

Package last updated on 30 Mar 2026

Did you know?

Socket

Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.

Install

Related posts