Skip to content

Extending CmpStr

Paul Köhler edited this page Jan 23, 2026 · 13 revisions

This page explains how to extend the CmpStr npm package with custom metrics, phonetic algorithms, or phonetic mappings — without modifying the package source code.

CmpStr is explicitly designed to be runtime-extensible. New functionality can be registered dynamically after importing the package and is immediately available throughout the entire API.

If you have ideas for improvements or new features that may benefit others, feel free to open an issue on GitHub. Relevant contributions may be included in future releases.

If you want to contribute code directly, fork the repository and submit a pull request.
See the contribution guidelines here: CONTRIBUTING.md

Accessing Development Entry Point

To implement custom metrics or phonetic algorithms, use the dedicated development entry point. It exposes all required base classes, registries, and internal utilities alongside default exports:

// CommonJS
const { ... } = require( 'cmpstr/root' );

// ES Modules / TypeScript
import { ... } from 'cmpstr/root';

This entry point is intentionally separated from the public API to keep the default bundle lightweight while still enabling advanced extensions.

Available Extension Utilities

Metric development

  • MetricRegistry: Central registry for managing metric implementations.
  • Metric: Abstract base class for string similarity metrics.
  • MetricCls: Type definition for metric constructors.

Phonetic algorithms and mappings

  • PhoneticRegistry: Registry for phonetic algorithm implementations.
  • PhoneticMappingRegistry: Registry for phonetic character mappings.
  • Phonetic: Abstract base class for phonetic algorithms.
  • PhoneticCls: Type definition for phonetic algorithm constructors.

Additional helpers

  • DeepMerge: Deep object merge utilities
  • Filter: Filter management and hooks
  • Hasher: Internally used, modified implementation of the FNV1a hash algorithm
  • HashTable: Optimized hash table implementation
  • Pool: Object pooling for memory optimization
  • Profiler: Lightweight execution time profiler
  • StructuredData: Processing structured data (objects)

Adding Custom Metrics

Custom metrics allow you to integrate entirely new similarity calculations into CmpStr.

1. Subclass Metric base class

Create a class extending the abstract Metric base class.

At minimum, you must implement the compute() method, which receives two strings and their lengths, and returns the MetricCompute<R> object with a normalized similarity score (res between 0 and 1).

import {
  type MetricInput, type MetricOptions, type MetricCompute,
  Metric, MetricRegistry
} from 'cmpstr/root';

class MyMetric extends Metric {

  constructor (
    a: MetricInput, b: MetricInput,
    opt: MetricOptions = {}
  ) {

    super( 'myMetric', a, b, opt, true );

  }

  protected override compute (
    a: string, b: string,
    m: number, n: number,
    maxLen: number
  ) : MetricCompute< R > {

    // Example implementation
    return { res: a === b ? 1 : 0 };

  }

}

You may optionally override helper methods such as preCompute() to short-circuit trivial cases or add internal helper functions.

2. Register the metric

Register your metric using a unique identifier:

MetricRegistry.add( 'myMetric', MyMetric );

The metric can now be used like any built-in metric:

import { CmpStr } from 'cmpstr';

const cmp = CmpStr.create( { metric: 'myMetric' } );
const result = cmp.test( 'foo', 'bar' );

Advanced Notes

  • Set the symmetric flag (fifth argument of super()) to true if metric(a, b) === metric(b, a).
  • Metric options can be exposed via the constructor.
  • Reviewing built-in metrics (e.g. Levenshtein, Dice, Cosine) is recommended for advanced patterns.

Adding Custom Phonetic Algorithms

Phonetic algorithms are implemented by extending the Phonetic base class.

1. Subclass Phonetic base class

Most phonetic logic is handled via mappings (see below).
Only override algorithm methods if you need custom behavior.

At a minimum, set up the constructor to call the parent with your algorithm name and options. Override the default static property to set default options for your algorithm, e.g., mapping, delimiter, length, padding, and deduplication.

import {
  type PhoneticOptions,
  Phonetic, PhoneticRegistry, PhoneticMappingRegistry
} from 'cmpstr/root';

class MyPhonetic extends Phonetic {

  protected static override default: PhoneticOptions = {
    map: 'en', delimiter: ' ', length: 4, pad: '0', dedupe: true
  };

  constructor ( opt: PhoneticOptions = {} ) {

    super( 'myPhonetic', opt );

  }

}

2. Configure phonetic algorithm

Mostly, the work of phonetic algorithms is done via character mappings, which you will need to define and register separately. You can also override methods like encode() or adjustCode() if your algorithm requires special handling before or after applying the mappings.

Key methods you may override:

If your algorithm requires special handling, you can override these methods:

  • encode ( word: string ) : string
    Main method to encode a string into its phonetic representation.
  • adjustCode ( code: string, chars: string[] ) : string
    Optional method to adjust the final phonetic code (e.g., truncation, padding).
  • mapChar ( char: string, i: number, chars: string[], charLen: number, lastCode: string | null, map: Record<string, string> ) : string | undefined
    Method to map individual characters using the provided mapping.

3. Register phonetic algorithm

Register your phonetic algorithm with a unique name:

PhoneticRegistry.add( 'myPhonetic', MyPhonetic );

The phonetic algorithm can now be used like any built-in one:

import { CmpStr } from 'cmpstr';

const cmp = CmpStr.create( {
  metric: 'dice',
  processors: {
    phonetic: { algo: 'myPhonetic' }
  }
} );

const result = cmp.test( 'foo', 'bar' );

Adding Custom Phonetic Mappings

Phonetic mappings are essential for language support and algorithm variants. Mappings define how characters are converted to codes, which regex patterns are applied, and may include context-sensitive rules and mapping-specific options.

1. Import mapping registry

import { PhoneticMappingRegistry } from 'cmpstr/root';

2. Define and register a mapping

A mapping can include:

  • map — Object mapping characters to codes.
  • patterns — Array of { pattern: RegExp, replace: string, all?: boolean } for regex-based replacements.
  • ruleset — Array of context-sensitive rules (see below).
  • ignore — Array of characters to skip.
  • options — Mapping-specific options (e.g., length, pad, dedupe).

Example:

PhoneticMappingRegistry.add( 'myPhonetic', 'en', {
  map: { a: '1', b: '2', c: '3' },
  patterns: [
    { pattern: /ph/g, replace: 'f' },
    { pattern: /gh/g, replace: 'g' }
  ],
  ruleset: [
    // 'c' before 'e', 'i', 'y' → 'S'
    { char: 'c', next: [ 'e', 'i', 'y' ], code: 'S' },
    // 'g' after 'n' → removed
    { char: 'g', prev: [ 'n' ], code: '' }
  ],
  ignore: [ 'h', 'w' ],
  options: { length: 6, pad: '0', dedupe: true }
} );

Pattern and rule system:

  • Patterns are applied before character mapping, allowing for global or context-based replacements.
  • Ruleset enables context-sensitive mapping (e.g., c before e becomes S).
  • Options in the mapping are merged with algorithm and user options (see Phonetic base class for details).

3. Use the mapping

Specify the mapping ID in your phonetic options:

const cmp = CmpStr.create().setProcessors( {
  phonetic: { algo: 'myPhonetic', opt: { map: 'en' } }
} );

4. Extending existing algorithms

Mappings can be added to built-in algorithms as well (e.g., for a new language):

PhoneticMappingRegistry.add( 'soundex', 'fr', {
  map: { a: '0', b: '1', ... }, // ...patterns, ruleset, options
} );

Advanced: Rule Syntax

Rules in the ruleset array can match based on:

  • char — The character to match.
  • prev, next — Required previous/next character(s).
  • prevNot, nextNot — Excluded previous/next character(s).
  • positionstart, middle, end.
  • leading, trailing — Multi-character context at start/end.
  • match — Array of characters for n-gram matching.
  • code — The code to use if the rule matches.

The arguments char and code are mandatory; others are optional. A rule matches if all specified conditions are met. See the Phonetic base class and built-in mappings for detailed examples.

Clone this wiki locally