{"id":50474104,"url":"https://github.com/le0pard/tre-regex","last_synced_at":"2026-06-01T12:02:20.255Z","repository":{"id":355097845,"uuid":"1226741288","full_name":"le0pard/tre-regex","owner":"le0pard","description":null,"archived":false,"fork":false,"pushed_at":"2026-05-01T21:02:19.000Z","size":1101,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-05-01T22:06:34.390Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"Rust","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/le0pard.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-05-01T19:25:57.000Z","updated_at":"2026-05-01T21:02:23.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/le0pard/tre-regex","commit_stats":null,"previous_names":["le0pard/tre-regex"],"tags_count":null,"template":false,"template_full_name":null,"purl":"pkg:github/le0pard/tre-regex","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/le0pard%2Ftre-regex","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/le0pard%2Ftre-regex/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/le0pard%2Ftre-regex/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/le0pard%2Ftre-regex/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/le0pard","download_url":"https://codeload.github.com/le0pard/tre-regex/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/le0pard%2Ftre-regex/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":33773782,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-06-01T02:00:06.963Z","response_time":115,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2026-06-01T12:02:19.488Z","updated_at":"2026-06-01T12:02:20.249Z","avatar_url":"https://github.com/le0pard.png","language":"Rust","funding_links":[],"categories":[],"sub_categories":[],"readme":"# @tre-regex/regex\n\n`@tre-regex/regex` provides a high-performance Node.js interface to the [TRE](https://github.com/laurikari/tre) C library. It brings robust approximate (fuzzy) regular expression matching to JavaScript and TypeScript, featuring multi-byte Unicode string safety, and granular error limits.\n\n## Why?\n\nStandard JavaScript `RegExp` expressions are strictly exact. If you are searching text containing typos, OCR errors, or variations in spelling, standard regex will fail.\n\nWhile string distance metrics (like Levenshtein distance) exist in the JS ecosystem, they usually require comparing whole strings against other whole strings. `@tre-regex/regex` solves this by allowing you to search for a pattern _within_ a larger body of text while permitting a configurable number of errors (insertions, deletions, and substitutions).\n\n## Features\n\n- **Approximate Matching**: Find matches even if the target string has missing, extra, or substituted characters.\n- **Granular Control**: Set strict limits on `maxErrors`, or fine-tune by specific error types (`maxInsertions`, `maxDeletions`, `maxSubstitutions`).\n- **Multi-byte Unicode Safety**: Transparently maps underlying C byte-offsets back to native JavaScript UTF-16 character indices (e.g., emojis won't break your offsets or `String.prototype.slice`).\n\n## Installation\n\nYou can install the package using your preferred package manager. Pre-built binaries are provided for most major operating systems and architectures.\n\n```bash\nyarn add @tre-regex/regex\n# or\nnpm install @tre-regex/regex\n```\n\n## Usage\n\n### Basic Matching\n\nCreate a new `TreRegex` object and use `exec` or `test` to search text.\n\n```javascript\nimport { TreRegex } from '@tre-regex/regex'\n\n// The second parameter is an optional boolean for ignoreCase\nconst regex = new TreRegex('apple', true)\n\n// Simple boolean check\nregex.test('I ate an APPLE today')\n// =\u003e true\n\n// Get detailed match data\nconst result = regex.exec('I ate an apple today')\n/* =\u003e {\n      matchText: \"apple\",\n      submatches: [],\n      index: 9,\n      endIndex: 14,\n      cost: 0,\n      errors: { insertions: 0, deletions: 0, substitutions: 0 }\n    }\n*/\n```\n\n### Fuzzy Matching\n\nYou can configure fuzziness by passing an options object directly to the `exec` method.\n\n```javascript\nconst regex = new TreRegex('apple')\n\n// Allow up to 1 error of any kind\nregex.exec('I ate an aple', { maxErrors: 1 })\n// =\u003e { matchText: \"aple\", submatches: [], index: 9, endIndex: 13, cost: 1, errors: { insertions: 0, deletions: 1, substitutions: 0 } }\n\n// Allow substitutions, but explicitly forbid deletions\nregex.exec('I ate an aple', { maxSubstitutions: 1, maxDeletions: 0 })\n// =\u003e undefined\n```\n\n### Finding All Matches\n\nUse `matchAll` to find every occurrence of a pattern in a string. It returns an array of match objects.\n\n```javascript\nconst regex = new TreRegex('cat')\n\nregex.matchAll('cat, cot, cut', { maxErrors: 1 })\n/* =\u003e [\n  { matchText: \"cat\", submatches: [], index: 0, endIndex: 3, cost: 0, errors: { insertions: 0, deletions: 0, substitutions: 0 } },\n  { matchText: \"cot\", submatches: [], index: 5, endIndex: 8, cost: 1, errors: { insertions: 0, deletions: 0, substitutions: 1 } },\n  { matchText: \"cut\", submatches: [], index: 10, endIndex: 13, cost: 1, errors: { insertions: 0, deletions: 0, substitutions: 1 } }\n] */\n```\n\n### Capture Groups (Submatches)\n\n`TreRegex` fully supports standard POSIX capture groups using parentheses `()`. Whenever a match is found, any captured data is returned as an array of strings under the `submatches` key in the result object.\n\nIf your pattern does not contain any capture groups, `submatches` will simply return an empty array `[]`.\n\n```javascript\nconst regex = new TreRegex('I love (ruby|python|javascript)')\nconst result = regex.exec('I love javascript a lot')\n\n// The captured group is extracted exactly as it was matched\nresult.submatches // =\u003e [\"javascript\"]\n```\n\n#### Multiple and Optional Groups\n\nYou can define multiple capture groups, and they will be returned in the array in the exact order they appear in the pattern.\n\nIf you use an optional capture group `?` that does not end up matching anything in the target text, `TreRegex` will safely insert `undefined` (or `null`) in its place in the array to maintain the correct index order.\n\n```javascript\n// The first group (cat) is optional. The second group (dog) is required.\nconst regex = new TreRegex('(cat)?(dog)')\n\nconst result = regex.exec('dog')\n// result.submatches =\u003e [undefined, \"dog\"]\n```\n\n#### Fuzzy Capture Groups\n\nOne of the most powerful features of `TreRegex` is that capture groups respect your fuzzy matching rules! If a typo occurs _inside_ a capture group, the `submatches` array will return the actual typed text with the typo included.\n\n```javascript\nconst regex = new TreRegex('I ate an (apple)')\n\n// We allow 1 error. The user typed 'aple' (1 deletion).\nconst result = regex.exec('I ate an aple', { maxErrors: 1 })\n\nresult.submatches // =\u003e [\"aple\"]\n```\n\n#### The 9-Group Limit\n\nFor memory safety and performance during the native C-to-Rust bridge, `TreRegex` allocates a strict maximum of 10 slots per match. Because the first slot is always reserved for the full regex match itself, the engine will only extract a maximum of **9 capture groups** per match.\n\nIf your pattern contains 10 or more capture groups `()`, the regex will still compile and match perfectly, but any captured groups beyond the 9th one will be safely ignored and omitted from the `submatches` array.\n\n## Configuration Options\n\n`TreRegex` provides fine-grained control over how patterns are compiled and how fuzzy matching constraints are applied.\n\n### Initialization Options\n\nWhen creating a new `TreRegex` instance, the constructor takes the pattern as the first argument, and an optional `ignoreCase` boolean as the second:\n\n```javascript\n// Fails because case doesn't match\nconst exactRegex = new TreRegex('javascript')\nexactRegex.test('JAVASCRIPT') // =\u003e false\n\n// Succeeds using the ignoreCase flag\nconst caseRegex = new TreRegex('javascript', true)\ncaseRegex.test('JAVASCRIPT') // =\u003e true\n```\n\n### Fuzzy Matching Options\n\nWhen calling `exec`, `test`, or `matchAll`, you can pass an options object. If no options are provided, `TreRegex` forces an **exact match** (0 errors allowed).\n\n#### Error Limits\n\nThese options strictly limit the number of specific operations required to transform the pattern into the matched string.\n\n- **`maxErrors`** _(number)_: The total maximum number of combined errors (insertions + deletions + substitutions) allowed for a match.\n- **`maxInsertions`** _(number)_: The maximum number of extra characters allowed in the searched text. _(e.g., Pattern `cat` matching `cart` is 1 insertion)_.\n- **`maxDeletions`** _(number)_: The maximum number of missing characters in the searched text. _(e.g., Pattern `cat` matching `ct` is 1 deletion)_.\n- **`maxSubstitutions`** _(number)_: The maximum number of swapped characters. _(e.g., Pattern `cat` matching `cot` is 1 substitution)_.\n\n\u003e **Note:** If you specify granular limits (like `maxDeletions: 1`) but omit `maxErrors`, the engine will automatically calculate the maximum allowed errors so you don't accidentally trigger an unlimited fuzzy search.\n\n```javascript\nconst regex = new TreRegex('banana')\n\n// Allow up to 2 typos of any kind\nregex.exec('bananana', { maxErrors: 2 }) // =\u003e matches \"bananana\" (2 insertions)\nregex.exec('bnnna', { maxErrors: 2 }) // =\u003e matches \"bnnna\" (2 deletions)\nregex.exec('bonono', { maxErrors: 2 }) // =\u003e matches \"bonono\" (2 substitutions)\n\n// Another example\nconst strictRegex = new TreRegex('library')\n\n// Allow 1 deletion, but STRICTLY 0 substitutions and 0 insertions\nstrictRegex.exec('librry', { maxDeletions: 1, maxSubstitutions: 0, maxInsertions: 0 })\n// =\u003e matches \"librry\"\n\n// This fails because 'lubrary' requires a substitution, which we set to 0\nstrictRegex.exec('lubrary', { maxDeletions: 1, maxSubstitutions: 0, maxInsertions: 0 })\n// =\u003e undefined\n```\n\n#### Cost and Weights\n\nInstead of hard limits, you can assign different \"costs\" to different types of errors. This is useful if you want to penalize certain typos more heavily than others.\n\n- **`maxCost`** _(number)_: The maximum total cost allowed for a match to be considered successful.\n- **`weightInsertion`** _(number)_: The cost penalty for each inserted character.\n- **`weightDeletion`** _(number)_: The cost penalty for each deleted character.\n- **`weightSubstitution`** _(number)_: The cost penalty for each substituted character.\n\n```javascript\nconst regex = new TreRegex('algorithm')\n\n// We allow a maximum cost of 2.\n// Missing/extra characters cost 1 point.\n// Wrong characters cost 3 points.\nconst options = {\n  maxCost: 2,\n  weightDeletion: 1,\n  weightInsertion: 1,\n  weightSubstitution: 3,\n}\n\n// 'algoritm' has 1 deletion. Cost = 1. (Passes, 1 \u003c 2)\nregex.test('algoritm', options) // =\u003e true\n\n// 'algorethm' has 1 substitution. Cost = 3. (Fails, 3 \u003e 2)\nregex.test('algorethm', options) // =\u003e false\n```\n\n## Gotchas \u0026 Best Practices\n\n### The \"Empty Match\" Phenomenon\n\nBecause `TreRegex` relies on strict mathematical edit distances, you must be careful when setting `maxErrors` to a value that is **greater than or equal to the length of your pattern**.\n\nIf you allow 3 errors on a 3-letter word, the engine considers _deleting all 3 characters_ to be a valid mathematical match (cost = 3). This will result in an unexpected match against an empty string (`\"\"`).\n\n```javascript\nconst regex = new TreRegex('cat')\n\n// We allow 3 errors on a 3-letter word.\n// The engine matches \"cow\" (2 substitutions)...\n// but it also matches \"\" at the end of the string (3 deletions)!\nregex.matchAll('cot, cow', { maxErrors: 3 })\n/* =\u003e [\n  { matchText: \"cot\", ..., cost: 1, errors: { insertions: 0, deletions: 0, substitutions: 1 } },\n  { matchText: \"cow\", ..., cost: 2, errors: { insertions: 0, deletions: 0, substitutions: 2 } },\n  { matchText: \"\", ..., cost: 3, errors: { insertions: 0, deletions: 3, substitutions: 0 } }\n] */\n```\n\n**Best Practice**: if you need a high `maxErrors` limit but want to prevent the engine from matching empty strings, explicitly cap the `maxDeletions` option so that at least one character of your pattern must survive:\n\n```javascript\n// Allow 3 total errors, but strictly forbid the engine from deleting more than 2 characters\nregex.matchAll('cot, cow', { maxErrors: 3, maxDeletions: 2 })\n/* =\u003e [\n  { matchText: \"cot\", ..., cost: 1, errors: { insertions: 0, deletions: 0, substitutions: 1 } },\n  { matchText: \"cow\", ..., cost: 2, errors: { insertions: 0, deletions: 0, substitutions: 2 } }\n] The empty match is mathematically prevented and omitted */\n```\n\n### POSIX vs. PCRE Syntax\n\nJavaScript’s built-in `RegExp` engine uses a PCRE-like syntax, which supports advanced features like lookaheads `(?=...)` and lookbehinds.\n\nThe underlying TRE C-library uses **POSIX Extended Regular Expressions (ERE)**. While it supports standard regex features (character classes `[a-z]`, quantifiers `*`, `+`, `?`, and grouping), it **does not** support Perl-specific extensions.\n\n```javascript\n// Valid TRE syntax\nnew TreRegex('(cat|dog)s?')\n\n// INVALID: Lookarounds are not supported by POSIX ERE\nnew TreRegex('cat(?=s)') // Throws: Failed to compile regex pattern: Invalid regexp\n```\n\n### The Performance Cost of Extreme Fuzziness\n\nFuzzy matching is inherently more computationally expensive than exact matching. The TRE algorithm scales based on the length of the string and the number of allowed errors.\n\nIf you are searching a massive block of text (like a whole book) and set `maxErrors: 10`, the engine has to calculate an enormous number of branching possibilities.\n\n**Best Practice**: Keep your error limits tight and realistic. An error limit of 1 to 3 is usually perfect for catching typos. If you need to allow a massive number of errors, consider breaking the target text into smaller chunks (like sentences or words) before matching.\n\n### Unicode Character Indices vs. Byte Offsets\n\nIn C, strings are just arrays of bytes. An emoji like 🍎 takes up 4 bytes, which often breaks indexing when C-libraries pass data back to high-level languages.\n\n`TreRegex` handles this natively in Rust. The `index` and `endIndex` returned in the match object are strictly mapped back to **JavaScript UTF-16 code units**, not raw C byte offsets.\n\n**Best Practice**: You can safely use the returned indices directly with `String.prototype.slice()`, even if the text is filled with emojis or multi-byte characters\n\n```javascript\nconst regex = new TreRegex('apple')\nconst target = 'I ate 🍎 and an aple'\n\nconst result = regex.exec(target, { maxErrors: 1 })\n// result.index is 15, result.endIndex is 19\n\n// This is 100% safe and will correctly return \"aple\"\ntarget.slice(result.index, result.endIndex)\n```\n\n### Overlapping Matches in `matchAll`\n\nWhen using `matchAll`, be aware that the engine consumes the string as it matches. By default, standard regex engines (including TRE) do not return overlapping matches.\n\nIf you search for `\"ana\"` in `\"banana\"`, it will only match the first `\"ana\"`. Once it consumes those characters, it moves on to the remaining `\"na\"`.\n\n```javascript\nconst regex = new TreRegex('ana')\n\n// Returns 1 match, not 2!\nregex.matchAll('banana')\n// =\u003e [{ matchText: \"ana\", index: 1, endIndex: 4, ... }]\n```\n\nIf you need to find overlapping fuzzy matches, you will need to manually step through the string by advancing your starting index by 1 character after each search.\n\n## Development\n\nThis package uses [napi-rs](https://napi.rs/) to bridge the TRE C library with Node.js.\n\nTo build the native addon locally, you need the Rust toolchain installed on your machine.\n\n```bash\n# Install dependencies\nyarn install\n\n# Build the native add-on (compiles Rust \u0026 C)\nyarn build\n\n# Run tests\nyarn test\n```\n\n## License\n\nThis package is available as open source under the terms of the MIT License.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fle0pard%2Ftre-regex","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fle0pard%2Ftre-regex","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fle0pard%2Ftre-regex/lists"}